diff --git a/Control/Monad/Primitive.hs b/Control/Monad/Primitive.hs
--- a/Control/Monad/Primitive.hs
+++ b/Control/Monad/Primitive.hs
@@ -2,6 +2,10 @@
 {-# LANGUAGE FlexibleContexts, FlexibleInstances, UndecidableInstances #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE DataKinds #-}
+#if __GLASGOW_HASKELL__ < 806
+{-# LANGUAGE TypeInType #-}
+#endif
 {-# OPTIONS_GHC -fno-warn-deprecations #-}
 
 -- |
@@ -22,15 +26,25 @@
   liftPrim, primToPrim, primToIO, primToST, ioToPrim, stToPrim,
   unsafePrimToPrim, unsafePrimToIO, unsafePrimToST, unsafeIOToPrim,
   unsafeSTToPrim, unsafeInlinePrim, unsafeInlineIO, unsafeInlineST,
-  touch, evalPrim, unsafeInterleave, unsafeDupableInterleave, noDuplicate
+  touch, touchUnlifted,
+  keepAlive, keepAliveUnlifted,
+  evalPrim, unsafeInterleave, unsafeDupableInterleave, noDuplicate
 ) where
 
+import Data.Kind (Type)
+
 import GHC.Exts   ( State#, RealWorld, noDuplicate#, touch#
                   , unsafeCoerce#, realWorld#, seq# )
+import Data.Primitive.Internal.Operations (UnliftedType)
+#if defined(HAVE_KEEPALIVE)
+import Data.Primitive.Internal.Operations (keepAliveLiftedLifted#,keepAliveUnliftedLifted#)
+#endif
 import GHC.IO     ( IO(..) )
 import GHC.ST     ( ST(..) )
 
+#if __GLASGOW_HASKELL__ >= 802
 import qualified Control.Monad.ST.Lazy as L
+#endif
 
 import Control.Monad.Trans.Class (lift)
 
@@ -225,14 +239,14 @@
   {-# INLINE internal #-}
 #endif
 
--- | 'PrimMonad''s state token type can be annoying to handle
+-- | 'PrimMonad'\'s state token type can be annoying to handle
 --   in constraints. This typeclass lets users (visually) notice
 --   'PrimState' equality constraints less, by witnessing that
 --   @s ~ 'PrimState' m@.
 class (PrimMonad m, s ~ PrimState m) => MonadPrim s m
 instance (PrimMonad m, s ~ PrimState m) => MonadPrim s m
 
--- | 'PrimBase''s state token type can be annoying to handle
+-- | 'PrimBase'\'s state token type can be annoying to handle
 --   in constraints. This typeclass lets users (visually) notice
 --   'PrimState' equality constraints less, by witnessing that
 --   @s ~ 'PrimState' m@.
@@ -330,10 +344,51 @@
 {-# INLINE unsafeInlineST #-}
 unsafeInlineST = unsafeInlinePrim
 
+-- | Ensure that the value is considered alive by the garbage collection.
+-- Warning: GHC has optimization passes that can erase @touch@ if it is
+-- certain that an exception is thrown afterward. Prefer 'keepAlive'.
 touch :: PrimMonad m => a -> m ()
 {-# INLINE touch #-}
 touch x = unsafePrimToPrim
         $ (primitive (\s -> case touch# x s of { s' -> (# s', () #) }) :: IO ())
+
+-- | Variant of 'touch' that keeps a value of an unlifted type
+-- (e.g. @MutableByteArray#@) alive.
+touchUnlifted :: forall (m :: Type -> Type) (a :: UnliftedType). PrimMonad m => a -> m ()
+{-# INLINE touchUnlifted #-}
+touchUnlifted x = unsafePrimToPrim
+        $ (primitive (\s -> case touch# x s of { s' -> (# s', () #) }) :: IO ())
+
+-- | Keep value @x@ alive until computation @k@ completes.
+-- Warning: This primop exists for completeness, but it is difficult to use
+-- correctly. Prefer 'keepAliveUnlifted' if the value to keep alive is simply
+-- a wrapper around an unlifted type (e.g. @ByteArray@).
+keepAlive :: PrimBase m
+  => a -- ^ Value @x@ to keep alive while computation @k@ runs.
+  -> m r -- ^ Computation @k@
+  -> m r
+#if defined(HAVE_KEEPALIVE)
+{-# INLINE keepAlive #-}
+keepAlive x k =
+  primitive $ \s0 -> keepAliveLiftedLifted# x s0 (internal k)
+
+#else
+{-# NOINLINE keepAlive #-}
+keepAlive x k = k <* touch x
+#endif
+
+-- | Variant of 'keepAlive' in which the value kept alive is of an unlifted
+-- boxed type.
+keepAliveUnlifted :: forall (m :: Type -> Type) (a :: UnliftedType) (r :: Type). PrimBase m => a -> m r -> m r
+#if defined(HAVE_KEEPALIVE)
+{-# INLINE keepAliveUnlifted #-}
+keepAliveUnlifted x k =
+  primitive $ \s0 -> keepAliveUnliftedLifted# x s0 (internal k)
+
+#else
+{-# NOINLINE keepAliveUnlifted #-}
+keepAliveUnlifted x k = k <* touchUnlifted x
+#endif
 
 -- | Create an action to force a value; generalizes 'Control.Exception.evaluate'
 --
diff --git a/Data/Primitive.hs b/Data/Primitive.hs
--- a/Data/Primitive.hs
+++ b/Data/Primitive.hs
@@ -1,6 +1,3 @@
-{-# LANGUAGE MagicHash #-}
-{-# OPTIONS_GHC -fno-warn-duplicate-exports #-}
-
 -- |
 -- Module      : Data.Primitive
 -- Copyright   : (c) Roman Leshchinskiy 2009-2012
@@ -68,9 +65,11 @@
 variants produce the same results and differ only in their strictness.
 Monads that are sufficiently affine include:
 
-* 'IO' and 'ST'
-* Any combination of 'MaybeT', 'ExceptT', 'StateT' and 'Writer' on top
-  of another sufficiently affine monad.
+* 'IO' and 'Control.Monad.ST'
+* Any combination of 'Control.Monad.Trans.Maybe.MaybeT',
+  'Control.Monad.Trans.Except.ExceptT', 'Control.Monad.Trans.State.Lazy.StateT'
+  and 'Control.Monad.Trans.Writer.Lazy.WriterT' on top of another sufficiently
+  affine monad.
 * Any Monad which does not include backtracking or other mechanisms where an effect can
   happen more than once is an affine Monad in the sense we care about. @ContT@, @LogicT@, @ListT@ are all
   examples of search/control monads which are NOT affine: they can run a sub computation more than once.
diff --git a/Data/Primitive/Array.hs b/Data/Primitive/Array.hs
--- a/Data/Primitive/Array.hs
+++ b/Data/Primitive/Array.hs
@@ -1,6 +1,7 @@
 {-# LANGUAGE CPP, MagicHash, UnboxedTuples, DeriveDataTypeable, BangPatterns #-}
 {-# LANGUAGE RankNTypes #-}
 {-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE TemplateHaskellQuotes #-}
 
 -- |
 -- Module      : Data.Primitive.Array
@@ -22,7 +23,6 @@
   cloneArray, cloneMutableArray,
   sizeofArray, sizeofMutableArray,
   emptyArray,
-  fromListN, fromList,
   arrayFromListN, arrayFromList,
   mapArray',
   traverseArrayP
@@ -30,6 +30,7 @@
 
 import Control.DeepSeq
 import Control.Monad.Primitive
+import Data.Primitive.Internal.Read (Tag(..),lexTag)
 
 import GHC.Exts hiding (toList)
 import qualified GHC.Exts as Exts
@@ -47,17 +48,10 @@
 import qualified Data.Foldable as Foldable
 import Control.Monad.Zip
 import Data.Foldable (Foldable(..), toList)
-#if MIN_VERSION_base(4,9,0)
 import qualified GHC.ST as GHCST
 import qualified Data.Foldable as F
 import Data.Semigroup
-#endif
 import Data.Functor.Identity
-#if MIN_VERSION_base(4,10,0)
-import GHC.Exts (runRW#)
-#elif MIN_VERSION_base(4,9,0)
-import GHC.Base (runRW#)
-#endif
 
 import Text.Read (Read (..), parens, prec)
 import Text.ParserCombinators.ReadPrec (ReadPrec)
@@ -65,12 +59,43 @@
 import Text.ParserCombinators.ReadP
 
 import Data.Functor.Classes (Eq1(..), Ord1(..), Show1(..), Read1(..))
+import Language.Haskell.TH.Syntax (Lift (..))
 
 -- | Boxed arrays.
 data Array a = Array
   { array# :: Array# a }
   deriving ( Typeable )
 
+instance Lift a => Lift (Array a) where
+#if MIN_VERSION_template_haskell(2,16,0)
+  liftTyped ary = case lst of
+    [] -> [|| Array (emptyArray# (##)) ||]
+    [x] -> [|| pure $! x ||]
+    x : xs -> [|| unsafeArrayFromListN' len x xs ||]
+#else
+  lift ary = case lst of
+    [] -> [| Array (emptyArray# (##)) |]
+    [x] -> [| pure $! x |]
+    x : xs -> [| unsafeArrayFromListN' len x xs |]
+#endif
+    where
+      len = length ary
+      lst = toList ary
+
+-- | Strictly create an array from a nonempty list (represented as
+-- a first element and a list of the rest) of a known length. If the length
+-- of the list does not match the given length, this makes demons fly
+-- out of your nose. We use it in the 'Lift' instance. If you edit the
+-- splice and break it, you get to keep both pieces.
+unsafeArrayFromListN' :: Int -> a -> [a] -> Array a
+unsafeArrayFromListN' n y ys =
+  createArray n y $ \ma ->
+    let go !_ix [] = return ()
+        go !ix (!x : xs) = do
+            writeArray ma ix x
+            go (ix+1) xs
+    in go 1 ys
+
 #if MIN_VERSION_deepseq(1,4,3)
 instance NFData1 Array where
   liftRnf r = Foldable.foldl' (\_ -> r) ()
@@ -136,7 +161,7 @@
 indexArray## arr (I# i) = indexArray# (array# arr) i
 {-# INLINE indexArray## #-}
 
--- | Monadically read a value from the immutable array at the given index.
+-- | Read a value from the immutable array at the given index using an applicative.
 -- This allows us to be strict in the array while remaining lazy in the read
 -- element which is very useful for collective operations. Suppose we want to
 -- copy an array. We could do something like this:
@@ -160,10 +185,10 @@
 -- still not evaluated.
 --
 -- /Note:/ this function does not do bounds checking.
-indexArrayM :: Monad m => Array a -> Int -> m a
+indexArrayM :: Applicative m => Array a -> Int -> m a
 {-# INLINE indexArrayM #-}
 indexArrayM arr (I# i#)
-  = case indexArray# (array# arr) i# of (# x #) -> return x
+  = case indexArray# (array# arr) i# of (# x #) -> pure x
 
 -- | Create an immutable copy of a slice of an array.
 --
@@ -297,9 +322,6 @@
 runArray
   :: (forall s. ST s (MutableArray s a))
   -> Array a
-#if !MIN_VERSION_base(4,9,0)
-runArray m = runST $ m >>= unsafeFreezeArray
-#else /* Below, runRW# is available. */
 runArray m = Array (runArray# m)
 
 runArray#
@@ -315,7 +337,6 @@
 emptyArray# :: (# #) -> Array# a
 emptyArray# _ = case emptyArray of Array ar -> ar
 {-# NOINLINE emptyArray# #-}
-#endif
 
 -- | Create an array of the given size with a default value,
 -- apply the monadic function and freeze the result. If the
@@ -331,9 +352,6 @@
   -> a
   -> (forall s. MutableArray s a -> ST s ())
   -> Array a
-#if !MIN_VERSION_base(4,9,0)
-createArray 0 _ _ = emptyArray
-#else
 -- This low-level business is designed to work with GHC's worker-wrapper
 -- transformation. A lot of the time, we don't actually need an Array
 -- constructor. By putting it on the outside, and being careful about
@@ -342,7 +360,6 @@
 -- their Array constructors, although they'll share their underlying
 -- Array#s.
 createArray 0 _ _ = Array (emptyArray# (# #))
-#endif
 createArray n x f = runArray $ do
   mary <- newArray n x
   f mary
@@ -364,11 +381,7 @@
 
 -- | @since 0.6.4.0
 instance Eq1 Array where
-#if MIN_VERSION_base(4,9,0) || MIN_VERSION_transformers(0,5,0)
   liftEq = arrayLiftEq
-#else
-  eq1 = arrayLiftEq (==)
-#endif
 
 instance Eq (MutableArray s a) where
   ma1 == ma2 = isTrue# (sameMutableArray# (marray# ma1) (marray# ma2))
@@ -390,11 +403,7 @@
 
 -- | @since 0.6.4.0
 instance Ord1 Array where
-#if MIN_VERSION_base(4,9,0) || MIN_VERSION_transformers(0,5,0)
   liftCompare = arrayLiftCompare
-#else
-  compare1 = arrayLiftCompare compare
-#endif
 
 instance Foldable Array where
   -- Note: we perform the array lookups eagerly so we won't
@@ -574,18 +583,28 @@
 
 -- | Create an array from a list of a known length. If the length
 -- of the list does not match the given length, this throws an exception.
+
+-- Note [fromListN]
+-- ~~~~~~~~~~~~~~~~
+-- We want arrayFromListN to be a "good consumer" in list fusion, so we define
+-- the function using foldr and inline it to help fire fusion rules.
+-- If fusion occurs with a "good producer", it may reduce to a fold on some
+-- structure. In certain cases (such as for Data.Set) GHC is not be able to
+-- optimize the index to an unboxed Int# (see GHC #24628), so we explicitly use
+-- an Int# here.
 arrayFromListN :: Int -> [a] -> Array a
+{-# INLINE arrayFromListN #-}
 arrayFromListN n l =
   createArray n (die "fromListN" "uninitialized element") $ \sma ->
-    let go !ix [] = if ix == n
+    let z ix# = if I# ix# == n
           then return ()
           else die "fromListN" "list length less than specified size"
-        go !ix (x : xs) = if ix < n
+        f x k = GHC.Exts.oneShot $ \ix# -> if I# ix# < n
           then do
-            writeArray sma ix x
-            go (ix+1) xs
+            writeArray sma (I# ix#) x
+            k (ix# +# 1#)
           else die "fromListN" "list length greater than specified size"
-    in go 0 l
+    in foldr f z l 0#
 
 -- | Create an array from a list.
 arrayFromList :: [a] -> Array a
@@ -731,17 +750,24 @@
       sz = sizeofArray (f err)
       err = error "mfix for Data.Primitive.Array applied to strict function."
 
-#if MIN_VERSION_base(4,9,0)
 -- | @since 0.6.3.0
 instance Semigroup (Array a) where
   (<>) = (<|>)
   sconcat = mconcat . F.toList
-#endif
+  stimes n arr = case compare n 0 of
+    LT -> die "stimes" "negative multiplier"
+    EQ -> empty
+    GT -> createArray (n' * sizeofArray arr) (die "stimes" "impossible") $ \ma ->
+      let go i = when (i < n') $ do
+            copyArray ma (i * sizeofArray arr) arr 0 (sizeofArray arr)
+            go (i + 1)
+      in go 0
+    where n' = fromIntegral n :: Int
 
 instance Monoid (Array a) where
   mempty = empty
 #if !(MIN_VERSION_base(4,11,0))
-  mappend = (<|>)
+  mappend = (<>)
 #endif
   mconcat l = createArray sz (die "mconcat" "impossible") $ \ma ->
     let go !_  [    ] = return ()
@@ -751,9 +777,8 @@
    where sz = sum . fmap sizeofArray $ l
 
 arrayLiftShowsPrec :: (Int -> a -> ShowS) -> ([a] -> ShowS) -> Int -> Array a -> ShowS
-arrayLiftShowsPrec elemShowsPrec elemListShowsPrec p a = showParen (p > 10) $
-  showString "fromListN " . shows (sizeofArray a) . showString " "
-    . listLiftShowsPrec elemShowsPrec elemListShowsPrec 11 (toList a)
+arrayLiftShowsPrec elemShowsPrec elemListShowsPrec _ a =
+  listLiftShowsPrec elemShowsPrec elemListShowsPrec 11 (toList a)
 
 -- this need to be included for older ghcs
 listLiftShowsPrec :: (Int -> a -> ShowS) -> ([a] -> ShowS) -> Int -> [a] -> ShowS
@@ -764,67 +789,34 @@
 
 -- | @since 0.6.4.0
 instance Show1 Array where
-#if MIN_VERSION_base(4,9,0) || MIN_VERSION_transformers(0,5,0)
   liftShowsPrec = arrayLiftShowsPrec
-#else
-  showsPrec1 = arrayLiftShowsPrec showsPrec showList
-#endif
 
 instance Read a => Read (Array a) where
   readPrec = arrayLiftReadPrec readPrec readListPrec
 
 -- | @since 0.6.4.0
 instance Read1 Array where
-#if MIN_VERSION_base(4,10,0)
   liftReadPrec = arrayLiftReadPrec
-#elif MIN_VERSION_base(4,9,0) || MIN_VERSION_transformers(0,5,0)
-  liftReadsPrec = arrayLiftReadsPrec
-#else
-  readsPrec1 = arrayLiftReadsPrec readsPrec readList
-#endif
 
+-- Note [Forgiving Array Read Instance]
 -- We're really forgiving here. We accept
 -- "[1,2,3]", "fromList [1,2,3]", and "fromListN 3 [1,2,3]".
 -- We consider fromListN with an invalid length to be an
 -- error, rather than a parse failure, because doing otherwise
 -- seems weird and likely to make debugging difficult.
 arrayLiftReadPrec :: ReadPrec a -> ReadPrec [a] -> ReadPrec (Array a)
-arrayLiftReadPrec _ read_list = parens $ prec app_prec $ RdPrc.lift skipSpaces >>
-    ((fromList <$> read_list) RdPrc.+++
-      do
-        tag <- RdPrc.lift lexTag
-        case tag of
-          FromListTag -> fromList <$> read_list
-          FromListNTag -> liftM2 fromListN readPrec read_list)
-   where
-     app_prec = 10
-
-data Tag = FromListTag | FromListNTag
-
--- Why don't we just use lexP? The general problem with lexP is that
--- it doesn't always fail as fast as we might like. It will
--- happily read to the end of an absurdly long lexeme (e.g., a 200MB string
--- literal) before returning, at which point we'll immediately discard
--- the result because it's not an identifier. Doing the job ourselves, we
--- can see very quickly when we've run into a problem. We should also get
--- a slight efficiency boost by going through the string just once.
-lexTag :: ReadP Tag
-lexTag = do
-  _ <- string "fromList"
-  s <- look
-  case s of
-    'N':c:_
-      | '0' <= c && c <= '9'
-      -> fail "" -- We have fromListN3 or similar
-      | otherwise -> FromListNTag <$ get -- Skip the 'N'
-    _ -> return FromListTag
-
-#if !MIN_VERSION_base(4,10,0)
-arrayLiftReadsPrec :: (Int -> ReadS a) -> ReadS [a] -> Int -> ReadS (Array a)
-arrayLiftReadsPrec reads_prec list_reads_prec = RdPrc.readPrec_to_S $
-  arrayLiftReadPrec (RdPrc.readS_to_Prec reads_prec) (RdPrc.readS_to_Prec (const list_reads_prec))
-#endif
-
+arrayLiftReadPrec _ read_list =
+  ( RdPrc.lift skipSpaces >> fmap fromList read_list )
+  RdPrc.+++
+  ( parens $ prec app_prec $ do
+      RdPrc.lift skipSpaces
+      tag <- RdPrc.lift lexTag
+      case tag of
+        FromListTag -> fromList <$> read_list
+        FromListNTag -> liftM2 fromListN readPrec read_list
+  )
+  where
+  app_prec = 10
 
 arrayDataType :: DataType
 arrayDataType = mkDataType "Data.Primitive.Array.Array" [fromListConstr]
diff --git a/Data/Primitive/ByteArray.hs b/Data/Primitive/ByteArray.hs
--- a/Data/Primitive/ByteArray.hs
+++ b/Data/Primitive/ByteArray.hs
@@ -1,6 +1,7 @@
-{-# LANGUAGE BangPatterns, CPP, MagicHash, UnboxedTuples, UnliftedFFITypes, DeriveDataTypeable #-}
+{-# LANGUAGE BangPatterns, CPP, MagicHash, UnboxedTuples, UnliftedFFITypes #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE TypeApplications #-}
 {-# LANGUAGE RankNTypes #-}
 
 -- |
@@ -28,6 +29,9 @@
 
   -- * Element access
   readByteArray, writeByteArray, indexByteArray,
+  -- * Char Element Access
+  -- $charElementAccess
+  readCharArray, writeCharArray, indexCharArray,
 
   -- * Constructing
   emptyByteArray,
@@ -40,13 +44,14 @@
   compareByteArrays,
 
   -- * Freezing and thawing
-  freezeByteArray, thawByteArray, runByteArray,
+  freezeByteArray, thawByteArray, runByteArray, createByteArray,
   unsafeFreezeByteArray, unsafeThawByteArray,
 
   -- * Block operations
   copyByteArray, copyMutableByteArray,
   copyByteArrayToPtr, copyMutableByteArrayToPtr,
   copyByteArrayToAddr, copyMutableByteArrayToAddr,
+  copyPtrToMutableByteArray,
   moveByteArray,
   setByteArray, fillByteArray,
   cloneByteArray, cloneMutableByteArray,
@@ -57,56 +62,40 @@
 #if __GLASGOW_HASKELL__ >= 802
   isByteArrayPinned, isMutableByteArrayPinned,
 #endif
-  byteArrayContents, mutableByteArrayContents
+  byteArrayAsForeignPtr,
+  mutableByteArrayAsForeignPtr,
+  byteArrayContents,
+  withByteArrayContents,
+  mutableByteArrayContents,
+  withMutableByteArrayContents
 
 ) where
 
 import Control.Monad.Primitive
 import Control.Monad.ST
-import Control.DeepSeq
 import Data.Primitive.Types
+import Data.Proxy
 
 import qualified GHC.ST as GHCST
 
-import Foreign.C.Types
 import Data.Word ( Word8 )
-import Data.Bits ( (.&.), unsafeShiftR )
-import GHC.Show ( intToDigit )
-import qualified GHC.Exts as Exts ( IsList(..) )
-import GHC.Exts hiding (setByteArray#)
-
-import Data.Typeable ( Typeable )
-import Data.Data ( Data(..), mkNoRepType )
-
-#if MIN_VERSION_base(4,9,0)
-import qualified Data.Semigroup as SG
-import qualified Data.Foldable as F
-#endif
-
 #if __GLASGOW_HASKELL__ >= 802
-import GHC.Exts as Exts (isByteArrayPinned#,isMutableByteArrayPinned#)
+import qualified GHC.Exts as Exts
 #endif
+import GHC.Exts hiding (setByteArray#)
+import GHC.ForeignPtr (ForeignPtr(..), ForeignPtrContents(..))
 
-#if __GLASGOW_HASKELL__ >= 804
-import GHC.Exts (compareByteArrays#)
-#else
+#if __GLASGOW_HASKELL__ < 804
+import Foreign.C.Types
 import System.IO.Unsafe (unsafeDupablePerformIO)
 #endif
 
--- | Byte arrays.
-data ByteArray = ByteArray ByteArray# deriving ( Typeable )
-
--- | Mutable byte arrays associated with a primitive state token.
-data MutableByteArray s = MutableByteArray (MutableByteArray# s)
-  deriving ( Typeable )
-
-instance NFData ByteArray where
-  rnf (ByteArray _) = ()
+import Data.Array.Byte (ByteArray(..), MutableByteArray(..))
 
-instance NFData (MutableByteArray s) where
-  rnf (MutableByteArray _) = ()
+import Data.Primitive.Internal.Operations (mutableByteArrayContentsShim)
 
 -- | Create a new mutable byte array of the specified size in bytes.
+-- The underlying memory is left uninitialized.
 --
 -- /Note:/ this function does not check if the input is non-negative.
 newByteArray :: PrimMonad m => Int -> m (MutableByteArray (PrimState m))
@@ -116,7 +105,7 @@
                         (# s'#, arr# #) -> (# s'#, MutableByteArray arr# #))
 
 -- | Create a /pinned/ byte array of the specified size in bytes. The garbage
--- collector is guaranteed not to move it.
+-- collector is guaranteed not to move it. The underlying memory is left uninitialized.
 --
 -- /Note:/ this function does not check if the input is non-negative.
 newPinnedByteArray :: PrimMonad m => Int -> m (MutableByteArray (PrimState m))
@@ -127,6 +116,7 @@
 
 -- | Create a /pinned/ byte array of the specified size in bytes and with the
 -- given alignment. The garbage collector is guaranteed not to move it.
+-- The underlying memory is left uninitialized.
 --
 -- /Note:/ this function does not check if the input is non-negative.
 newAlignedPinnedByteArray
@@ -139,21 +129,67 @@
   = primitive (\s# -> case newAlignedPinnedByteArray# n# k# s# of
                         (# s'#, arr# #) -> (# s'#, MutableByteArray arr# #))
 
+-- | Create a foreign pointer that points to the array's data. This operation
+-- is only safe on /pinned/ byte arrays.  The array's data is not garbage
+-- collected while references to the foreign pointer exist. Writing to the
+-- array through the foreign pointer results in undefined behavior.
+byteArrayAsForeignPtr :: ByteArray -> ForeignPtr Word8
+{-# INLINE byteArrayAsForeignPtr #-}
+byteArrayAsForeignPtr (ByteArray arr#) = ForeignPtr (byteArrayContents# arr#) (PlainPtr (unsafeCoerce# arr#))
+
+
+-- | Variant of 'byteArrayAsForeignPtr' for mutable byte arrays. Similarly, this
+-- is only safe on /pinned/ mutable byte arrays. This function differs from the
+-- variant for immutable arrays in that it is safe to write to the array though
+-- the foreign pointer.
+mutableByteArrayAsForeignPtr :: MutableByteArray RealWorld -> ForeignPtr Word8
+{-# INLINE mutableByteArrayAsForeignPtr #-}
+mutableByteArrayAsForeignPtr (MutableByteArray arr#) = ForeignPtr (mutableByteArrayContentsShim arr#) (PlainPtr arr#)
+
 -- | Yield a pointer to the array's data. This operation is only safe on
--- /pinned/ byte arrays allocated by 'newPinnedByteArray' or
--- 'newAlignedPinnedByteArray'.
+-- /pinned/ byte arrays. Byte arrays allocated by 'newPinnedByteArray' and
+-- 'newAlignedPinnedByteArray' are guaranteed to be pinned. Byte arrays
+-- allocated by 'newByteArray' may or may not be pinned (use
+-- 'isByteArrayPinned' to figure out).
+--
+-- Prefer 'withByteArrayContents', which ensures that the array is not
+-- garbage collected while the pointer is being used.
 byteArrayContents :: ByteArray -> Ptr Word8
 {-# INLINE byteArrayContents #-}
 byteArrayContents (ByteArray arr#) = Ptr (byteArrayContents# arr#)
 
+-- | A composition of 'byteArrayContents' and 'keepAliveUnlifted'.
+-- The callback function must not return the pointer. The argument byte
+-- array must be /pinned/. See 'byteArrayContents' for an explanation
+-- of which byte arrays are pinned.
+--
+-- Note: This could be implemented with 'keepAlive' instead of
+-- 'keepAliveUnlifted', but 'keepAlive' here would cause GHC to materialize
+-- the wrapper data constructor on the heap.
+withByteArrayContents :: PrimBase m => ByteArray -> (Ptr Word8 -> m a) -> m a
+{-# INLINE withByteArrayContents #-}
+withByteArrayContents (ByteArray arr#) f =
+  keepAliveUnlifted arr# (f (Ptr (byteArrayContents# arr#)))
+
 -- | Yield a pointer to the array's data. This operation is only safe on
--- /pinned/ byte arrays allocated by 'newPinnedByteArray' or
--- 'newAlignedPinnedByteArray'.
+-- /pinned/ byte arrays. See 'byteArrayContents' for an explanation
+-- of which byte arrays are pinned.
+--
+-- Prefer 'withByteArrayContents', which ensures that the array is not
+-- garbage collected while the pointer is being used.
 mutableByteArrayContents :: MutableByteArray s -> Ptr Word8
 {-# INLINE mutableByteArrayContents #-}
-mutableByteArrayContents (MutableByteArray arr#)
-  = Ptr (byteArrayContents# (unsafeCoerce# arr#))
+mutableByteArrayContents (MutableByteArray arr#) = Ptr (mutableByteArrayContentsShim arr#)
 
+-- | A composition of 'mutableByteArrayContents' and 'keepAliveUnlifted'.
+-- The callback function must not return the pointer. The argument byte
+-- array must be /pinned/. See 'byteArrayContents' for an explanation
+-- of which byte arrays are pinned.
+withMutableByteArrayContents :: PrimBase m => MutableByteArray (PrimState m) -> (Ptr Word8 -> m a) -> m a
+{-# INLINE withMutableByteArrayContents #-}
+withMutableByteArrayContents (MutableByteArray arr#) f =
+  keepAliveUnlifted arr# (f (Ptr (mutableByteArrayContentsShim arr#)))
+
 -- | Check if the two arrays refer to the same memory block.
 sameMutableByteArray :: MutableByteArray s -> MutableByteArray s -> Bool
 {-# INLINE sameMutableByteArray #-}
@@ -258,13 +294,15 @@
 {-# INLINE sizeofByteArray #-}
 sizeofByteArray (ByteArray arr#) = I# (sizeofByteArray# arr#)
 
--- | Size of the mutable byte array in bytes. This function\'s behavior
+-- | Size of the mutable byte array in bytes.
+--
+-- This function is deprecated and will be removed. Its behavior
 -- is undefined if 'resizeMutableByteArray' is ever called on the mutable
--- byte array given as the argument. Consequently, use of this function
--- is discouraged. Prefer 'getSizeofMutableByteArray', which ensures correct
--- sequencing in the presence of resizing.
+-- byte array given as the argument. Prefer 'getSizeofMutableByteArray',
+-- which ensures correct sequencing in the presence of resizing.
 sizeofMutableByteArray :: MutableByteArray s -> Int
 {-# INLINE sizeofMutableByteArray #-}
+{-# DEPRECATED sizeofMutableByteArray "use getSizeofMutableByteArray instead" #-}
 sizeofMutableByteArray (MutableByteArray arr#) = I# (sizeofMutableByteArray# arr#)
 
 -- | Shrink a mutable byte array. The new size is given in bytes.
@@ -281,18 +319,22 @@
 
 #if __GLASGOW_HASKELL__ >= 802
 -- | Check whether or not the byte array is pinned. Pinned byte arrays cannot
--- be moved by the garbage collector. It is safe to use 'byteArrayContents'
--- on such byte arrays. This function is only available when compiling with
--- GHC 8.2 or newer.
+-- be moved by the garbage collector. It is safe to use 'byteArrayContents' on
+-- such byte arrays.
 --
+-- Caution: This function is only available when compiling with GHC 8.2 or
+-- newer.
+--
 -- @since 0.6.4.0
 isByteArrayPinned :: ByteArray -> Bool
 {-# INLINE isByteArrayPinned #-}
 isByteArrayPinned (ByteArray arr#) = isTrue# (Exts.isByteArrayPinned# arr#)
 
--- | Check whether or not the mutable byte array is pinned. This function is
--- only available when compiling with GHC 8.2 or newer.
+-- | Check whether or not the mutable byte array is pinned.
 --
+-- Caution: This function is only available when compiling with GHC 8.2 or
+-- newer.
+--
 -- @since 0.6.4.0
 isMutableByteArrayPinned :: MutableByteArray s -> Bool
 {-# INLINE isMutableByteArrayPinned #-}
@@ -335,7 +377,7 @@
     go i
       | i < maxI  = f (indexByteArray arr i) (go (i + 1))
       | otherwise = z
-    maxI = sizeofByteArray arr `quot` sizeOf (undefined :: a)
+    maxI = sizeofByteArray arr `quot` sizeOfType @a
 
 -- | Create a 'ByteArray' from a list.
 --
@@ -345,20 +387,21 @@
 
 -- | Create a 'ByteArray' from a list of a known length. If the length
 -- of the list does not match the given length, this throws an exception.
-byteArrayFromListN :: Prim a => Int -> [a] -> ByteArray
-byteArrayFromListN n ys = runST $ do
-    marr <- newByteArray (n * sizeOf (head ys))
-    let go !ix [] = if ix == n
-          then return ()
-          else die "byteArrayFromListN" "list length less than specified size"
-        go !ix (x : xs) = if ix < n
-          then do
-            writeByteArray marr ix x
-            go (ix + 1) xs
-          else die "byteArrayFromListN" "list length greater than specified size"
-    go 0 ys
-    unsafeFreezeByteArray marr
 
+-- See Note [fromListN] in Data.Primitive.Array
+byteArrayFromListN :: forall a. Prim a => Int -> [a] -> ByteArray
+{-# INLINE byteArrayFromListN #-}
+byteArrayFromListN n ys = createByteArray (n * sizeOfType @a) $ \marr ->
+  let z ix# = if I# ix# == n
+        then return ()
+        else die "byteArrayFromListN" "list length less than specified size"
+      f x k = GHC.Exts.oneShot $ \ix# -> if I# ix# < n
+        then do
+          writeByteArray marr (I# ix#) x
+          k (ix# +# 1#)
+        else die "byteArrayFromListN" "list length greater than specified size"
+  in foldr f z ys 0#
+
 unI# :: Int -> Int#
 unI# (I# n#) = n#
 
@@ -392,7 +435,13 @@
 {-# INLINE copyMutableByteArray #-}
 copyMutableByteArray (MutableByteArray dst#) doff
                      (MutableByteArray src#) soff sz
-  = primitive_ (copyMutableByteArray# src# (unI# soff) dst# (unI# doff) (unI# sz))
+  = primitive_ (op src# (unI# soff) dst# (unI# doff) (unI# sz))
+  where
+#if MIN_VERSION_base(4,19,0)
+    op = copyMutableByteArrayNonOverlapping#
+#else
+    op = copyMutableByteArray#
+#endif
 
 -- | Copy a slice of a byte array to an unmanaged pointer address. These must not
 -- overlap. The offset and length are given in elements, not in bytes.
@@ -409,10 +458,27 @@
   -> m ()
 {-# INLINE copyByteArrayToPtr #-}
 copyByteArrayToPtr (Ptr dst#) (ByteArray src#) soff sz
-  = primitive_ (copyByteArrayToAddr# src# (unI# soff *# siz# ) dst# (unI# sz))
+  = primitive_ (copyByteArrayToAddr# src# (unI# soff *# siz#) dst# (unI# sz *# siz#))
   where
-  siz# = sizeOf# (undefined :: a)
+  siz# = sizeOfType# (Proxy :: Proxy a)
 
+-- | Copy from an unmanaged pointer address to a byte array. These must not
+-- overlap. The offset and length are given in elements, not in bytes.
+--
+-- /Note:/ this function does not do bounds or overlap checking.
+copyPtrToMutableByteArray :: forall m a. (PrimMonad m, Prim a)
+  => MutableByteArray (PrimState m) -- ^ destination array
+  -> Int   -- ^ destination offset given in elements of type @a@
+  -> Ptr a -- ^ source pointer
+  -> Int   -- ^ number of elements
+  -> m ()
+{-# INLINE copyPtrToMutableByteArray #-}
+copyPtrToMutableByteArray (MutableByteArray ba#) (I# doff#) (Ptr addr#) (I# n#) =
+  primitive_ (copyAddrToByteArray# addr# ba# (doff# *# siz#) (n# *# siz#))
+  where
+  siz# = sizeOfType# (Proxy :: Proxy a)
+
+
 -- | Copy a slice of a mutable byte array to an unmanaged pointer address.
 -- These must not overlap. The offset and length are given in elements, not
 -- in bytes.
@@ -429,9 +495,9 @@
   -> m ()
 {-# INLINE copyMutableByteArrayToPtr #-}
 copyMutableByteArrayToPtr (Ptr dst#) (MutableByteArray src#) soff sz
-  = primitive_ (copyMutableByteArrayToAddr# src# (unI# soff *# siz# ) dst# (unI# sz))
+  = primitive_ (copyMutableByteArrayToAddr# src# (unI# soff *# siz#) dst# (unI# sz *# siz#))
   where
-  siz# = sizeOf# (undefined :: a)
+  siz# = sizeOfType# (Proxy :: Proxy a)
 
 ------
 --- These latter two should be DEPRECATED
@@ -484,9 +550,7 @@
 {-# INLINE moveByteArray #-}
 moveByteArray (MutableByteArray dst#) doff
               (MutableByteArray src#) soff sz
-  = unsafePrimToPrim
-  $ memmove_mba dst# (fromIntegral doff) src# (fromIntegral soff)
-                     (fromIntegral sz)
+  = primitive_ (copyMutableByteArray# src# (unI# soff) dst# (unI# doff) (unI# sz))
 
 -- | Fill a slice of a mutable byte array with a value. The offset and length
 -- are given in elements of type @a@ rather than in bytes.
@@ -516,67 +580,6 @@
 {-# INLINE fillByteArray #-}
 fillByteArray = setByteArray
 
-foreign import ccall unsafe "primitive-memops.h hsprimitive_memmove"
-  memmove_mba :: MutableByteArray# s -> CPtrdiff
-              -> MutableByteArray# s -> CPtrdiff
-              -> CSize -> IO ()
-
-instance Eq (MutableByteArray s) where
-  (==) = sameMutableByteArray
-
-instance Data ByteArray where
-  toConstr _ = error "toConstr"
-  gunfold _ _ = error "gunfold"
-  dataTypeOf _ = mkNoRepType "Data.Primitive.ByteArray.ByteArray"
-
-instance Typeable s => Data (MutableByteArray s) where
-  toConstr _ = error "toConstr"
-  gunfold _ _ = error "gunfold"
-  dataTypeOf _ = mkNoRepType "Data.Primitive.ByteArray.MutableByteArray"
-
--- | @since 0.6.3.0
---
--- Behavior changed in 0.7.2.0. Before 0.7.2.0, this instance rendered
--- 8-bit words less than 16 as a single hexadecimal digit (e.g. 13 was @0xD@).
--- Starting with 0.7.2.0, all 8-bit words are represented as two digits
--- (e.g. 13 is @0x0D@).
-instance Show ByteArray where
-  showsPrec _ ba =
-      showString "[" . go 0
-    where
-      showW8 :: Word8 -> String -> String
-      showW8 !w s =
-          '0'
-        : 'x'
-        : intToDigit (fromIntegral (unsafeShiftR w 4))
-        : intToDigit (fromIntegral (w .&. 0x0F))
-        : s
-      go i
-        | i < sizeofByteArray ba = comma . showW8 (indexByteArray ba i :: Word8) . go (i+1)
-        | otherwise              = showChar ']'
-        where
-          comma | i == 0    = id
-                | otherwise = showString ", "
-
-
--- Only used internally
-compareByteArraysFromBeginning :: ByteArray -> ByteArray -> Int -> Ordering
-{-# INLINE compareByteArraysFromBeginning #-}
-#if __GLASGOW_HASKELL__ >= 804
-compareByteArraysFromBeginning (ByteArray ba1#) (ByteArray ba2#) (I# n#)
-  = compare (I# (compareByteArrays# ba1# 0# ba2# 0# n#)) 0
-#else
--- Emulate GHC 8.4's 'GHC.Prim.compareByteArrays#'
-compareByteArraysFromBeginning (ByteArray ba1#) (ByteArray ba2#) (I# n#)
-  = compare (fromCInt (unsafeDupablePerformIO (memcmp_ba ba1# ba2# n))) 0
-  where
-    n = fromIntegral (I# n#) :: CSize
-    fromCInt = fromIntegral :: CInt -> Int
-
-foreign import ccall unsafe "primitive-memops.h hsprimitive_memcmp"
-  memcmp_ba :: ByteArray# -> ByteArray# -> CSize -> IO CInt
-#endif
-
 -- | Lexicographic comparison of equal-length slices into two byte arrays.
 -- This wraps the @compareByteArrays#@ primop, which wraps @memcmp@.
 compareByteArrays
@@ -602,107 +605,14 @@
   memcmp_ba_offs :: ByteArray# -> Int# -> ByteArray# -> Int# -> CSize -> IO CInt
 #endif
 
-
-sameByteArray :: ByteArray# -> ByteArray# -> Bool
-sameByteArray ba1 ba2 =
-    case reallyUnsafePtrEquality# (unsafeCoerce# ba1 :: ()) (unsafeCoerce# ba2 :: ()) of
-      r -> isTrue# r
-
--- | @since 0.6.3.0
-instance Eq ByteArray where
-  ba1@(ByteArray ba1#) == ba2@(ByteArray ba2#)
-    | sameByteArray ba1# ba2# = True
-    | n1 /= n2 = False
-    | otherwise = compareByteArraysFromBeginning ba1 ba2 n1 == EQ
-    where
-      n1 = sizeofByteArray ba1
-      n2 = sizeofByteArray ba2
-
--- | Non-lexicographic ordering. This compares the lengths of
--- the byte arrays first and uses a lexicographic ordering if
--- the lengths are equal. Subject to change between major versions.
---
--- @since 0.6.3.0
-instance Ord ByteArray where
-  ba1@(ByteArray ba1#) `compare` ba2@(ByteArray ba2#)
-    | sameByteArray ba1# ba2# = EQ
-    | n1 /= n2 = n1 `compare` n2
-    | otherwise = compareByteArraysFromBeginning ba1 ba2 n1
-    where
-      n1 = sizeofByteArray ba1
-      n2 = sizeofByteArray ba2
--- Note: On GHC 8.4, the primop compareByteArrays# performs a check for pointer
--- equality as a shortcut, so the check here is actually redundant. However, it
--- is included here because it is likely better to check for pointer equality
--- before checking for length equality. Getting the length requires deferencing
--- the pointers, which could cause accesses to memory that is not in the cache.
--- By contrast, a pointer equality check is always extremely cheap.
-
-appendByteArray :: ByteArray -> ByteArray -> ByteArray
-appendByteArray a b = runST $ do
-  marr <- newByteArray (sizeofByteArray a + sizeofByteArray b)
-  copyByteArray marr 0 a 0 (sizeofByteArray a)
-  copyByteArray marr (sizeofByteArray a) b 0 (sizeofByteArray b)
-  unsafeFreezeByteArray marr
-
-concatByteArray :: [ByteArray] -> ByteArray
-concatByteArray arrs = runST $ do
-  let len = calcLength arrs 0
-  marr <- newByteArray len
-  pasteByteArrays marr 0 arrs
-  unsafeFreezeByteArray marr
-
-pasteByteArrays :: MutableByteArray s -> Int -> [ByteArray] -> ST s ()
-pasteByteArrays !_ !_ [] = return ()
-pasteByteArrays !marr !ix (x : xs) = do
-  copyByteArray marr ix x 0 (sizeofByteArray x)
-  pasteByteArrays marr (ix + sizeofByteArray x) xs
-
-calcLength :: [ByteArray] -> Int -> Int
-calcLength [] !n = n
-calcLength (x : xs) !n = calcLength xs (sizeofByteArray x + n)
-
 -- | The empty 'ByteArray'.
 emptyByteArray :: ByteArray
 {-# NOINLINE emptyByteArray #-}
 emptyByteArray = runST (newByteArray 0 >>= unsafeFreezeByteArray)
 
-replicateByteArray :: Int -> ByteArray -> ByteArray
-replicateByteArray n arr = runST $ do
-  marr <- newByteArray (n * sizeofByteArray arr)
-  let go i = if i < n
-        then do
-          copyByteArray marr (i * sizeofByteArray arr) arr 0 (sizeofByteArray arr)
-          go (i + 1)
-        else return ()
-  go 0
-  unsafeFreezeByteArray marr
-
-#if MIN_VERSION_base(4,9,0)
-instance SG.Semigroup ByteArray where
-  (<>) = appendByteArray
-  sconcat = mconcat . F.toList
-  stimes i arr
-    | itgr < 1 = emptyByteArray
-    | itgr <= fromIntegral (maxBound :: Int) = replicateByteArray (fromIntegral itgr) arr
-    | otherwise = error "Data.Primitive.ByteArray#stimes: cannot allocate the requested amount of memory"
-    where itgr = toInteger i :: Integer
-#endif
-
-instance Monoid ByteArray where
-  mempty = emptyByteArray
-#if !(MIN_VERSION_base(4,11,0))
-  mappend = appendByteArray
-#endif
-  mconcat = concatByteArray
-
--- | @since 0.6.3.0
-instance Exts.IsList ByteArray where
-  type Item ByteArray = Word8
-
-  toList = foldrByteArray (:) []
-  fromList xs = byteArrayFromListN (length xs) xs
-  fromListN = byteArrayFromListN
+emptyByteArray# :: (# #) -> ByteArray#
+{-# NOINLINE emptyByteArray# #-}
+emptyByteArray# _ = case emptyByteArray of ByteArray arr# -> arr#
 
 die :: String -> String -> a
 die fun problem = error $ "Data.Primitive.ByteArray." ++ fun ++ ": " ++ problem
@@ -716,10 +626,8 @@
   -> Int       -- ^ number of bytes to copy
   -> ByteArray
 {-# INLINE cloneByteArray #-}
-cloneByteArray src off n = runByteArray $ do
-  dst <- newByteArray n
+cloneByteArray src off n = createByteArray n $ \dst ->
   copyByteArray dst 0 src off n
-  return dst
 
 -- | Return a newly allocated mutable array with the specified subrange of
 -- the provided mutable array. The provided mutable array should contain the
@@ -741,7 +649,6 @@
 runByteArray
   :: (forall s. ST s (MutableByteArray s))
   -> ByteArray
-#if MIN_VERSION_base(4,10,0) /* In new GHCs, runRW# is available. */
 runByteArray m = ByteArray (runByteArray# m)
 
 runByteArray#
@@ -753,6 +660,55 @@
 
 unST :: ST s a -> State# s -> (# State# s, a #)
 unST (GHCST.ST f) = f
-#else /* In older GHCs, runRW# is not available. */
-runByteArray m = runST $ m >>= unsafeFreezeByteArray
-#endif
+
+-- Create an uninitialized array of the given size in bytes, apply the function
+-- to it, and freeze the result.
+--
+-- /Note:/ this function does not check if the input is non-negative.
+--
+-- @since FIXME
+createByteArray :: Int -> (forall s. MutableByteArray s -> ST s ()) -> ByteArray
+{-# INLINE createByteArray #-}
+createByteArray 0 _ = ByteArray (emptyByteArray# (# #))
+createByteArray n f = runByteArray $ do
+  marr <- newByteArray n
+  f marr
+  pure marr
+
+{- $charElementAccess
+GHC provides two sets of element accessors for 'Char'. One set faithfully
+represents 'Char' as 32-bit words using UTF-32. The other set represents
+'Char' as 8-bit words using Latin-1 (ISO-8859-1), and the write operation
+has undefined behavior for codepoints outside of the ASCII and Latin-1
+blocks. The 'Prim' instance for 'Char' uses the UTF-32 set of operators.
+-}
+
+-- | Read an 8-bit element from the byte array, interpreting it as a
+-- Latin-1-encoded character. The offset is given in bytes.
+--
+-- /Note:/ this function does not do bounds checking.
+readCharArray :: PrimMonad m => MutableByteArray (PrimState m) -> Int -> m Char
+{-# INLINE readCharArray #-}
+readCharArray (MutableByteArray arr#) (I# i#) = primitive
+  (\s0 -> case readCharArray# arr# i# s0 of
+    (# s1, c #) -> (# s1, C# c #)
+  )
+
+-- | Write a character to the byte array, encoding it with Latin-1 as
+-- a single byte. Behavior is undefined for codepoints outside of the
+-- ASCII and Latin-1 blocks. The offset is given in bytes.
+--
+-- /Note:/ this function does not do bounds checking.
+writeCharArray
+  :: PrimMonad m => MutableByteArray (PrimState m) -> Int -> Char -> m ()
+{-# INLINE writeCharArray #-}
+writeCharArray (MutableByteArray arr#) (I# i#) (C# c)
+  = primitive_ (writeCharArray# arr# i# c)
+
+-- | Read an 8-bit element from the byte array, interpreting it as a
+-- Latin-1-encoded character. The offset is given in bytes.
+--
+-- /Note:/ this function does not do bounds checking.
+indexCharArray :: ByteArray -> Int -> Char
+{-# INLINE indexCharArray #-}
+indexCharArray (ByteArray arr#) (I# i#) = C# (indexCharArray# arr# i#)
diff --git a/Data/Primitive/Internal/Operations.hs b/Data/Primitive/Internal/Operations.hs
--- a/Data/Primitive/Internal/Operations.hs
+++ b/Data/Primitive/Internal/Operations.hs
@@ -1,4 +1,9 @@
-{-# LANGUAGE CPP, MagicHash, UnliftedFFITypes #-}
+{-# LANGUAGE CPP, MagicHash, UnliftedFFITypes, UnboxedTuples #-}
+{-# LANGUAGE RankNTypes, KindSignatures, ScopedTypeVariables #-} 
+{-# LANGUAGE DataKinds #-}
+#if __GLASGOW_HASKELL__ < 806
+{-# LANGUAGE TypeInType #-}
+#endif
 
 -- |
 -- Module      : Data.Primitive.Internal.Operations
@@ -24,13 +29,25 @@
   setInt64OffAddr#, setIntOffAddr#,
   setAddrOffAddr#, setFloatOffAddr#, setDoubleOffAddr#, setWideCharOffAddr#,
   setStablePtrOffAddr#
+
+
+#if defined(HAVE_KEEPALIVE)
+  , keepAliveLiftedLifted#
+  , keepAliveUnliftedLifted#
+#endif
+  , mutableByteArrayContentsShim
+  , UnliftedType
 ) where
 
 import Data.Primitive.MachDeps (Word64_#, Int64_#)
 import Foreign.C.Types
 import GHC.Exts
 
+#if defined(HAVE_KEEPALIVE)
+import Data.Kind (Type)
+#endif
 
+
 #if __GLASGOW_HASKELL__ >= 902
 foreign import ccall unsafe "primitive-memops.h hsprimitive_memset_Word8"
   setWord8Array# :: MutableByteArray# s -> CPtrdiff -> CSize -> Word8# -> IO ()
@@ -136,3 +153,53 @@
   setDoubleOffAddr# :: Addr# -> CPtrdiff -> CSize -> Double# -> IO ()
 foreign import ccall unsafe "primitive-memops.h hsprimitive_memset_Char"
   setWideCharOffAddr# :: Addr# -> CPtrdiff -> CSize -> Char# -> IO ()
+
+#if defined(HAVE_KEEPALIVE)
+keepAliveLiftedLifted# :: forall (s :: Type) (a :: Type) (b :: Type).
+     a
+  -> State# s
+  -> (State# s -> (# State# s, b #))
+  -> (# State# s, b #)
+{-# inline keepAliveLiftedLifted# #-}
+keepAliveLiftedLifted# x s0 f =
+  (unsafeCoerce# :: (# State# RealWorld, b #) -> (# State# s, b #))
+    ( keepAlive# x
+      ((unsafeCoerce# :: State# s -> State# RealWorld) s0)
+      ((unsafeCoerce# ::
+         (State# s -> (# State# s, b #)) ->
+         (State# RealWorld -> (# State# RealWorld, b #))
+       ) f)
+    )
+
+keepAliveUnliftedLifted# :: forall (s :: Type) (a :: UnliftedType) (b :: Type).
+     a
+  -> State# s
+  -> (State# s -> (# State# s, b #))
+  -> (# State# s, b #)
+{-# inline keepAliveUnliftedLifted# #-}
+keepAliveUnliftedLifted# x s0 f =
+  (unsafeCoerce# :: (# State# RealWorld, b #) -> (# State# s, b #))
+    ( keepAlive# x
+      ((unsafeCoerce# :: State# s -> State# RealWorld) s0)
+      ((unsafeCoerce# ::
+         (State# s -> (# State# s, b #)) ->
+         (State# RealWorld -> (# State# RealWorld, b #))
+       ) f)
+    )
+#endif
+
+#if __GLASGOW_HASKELL__ < 802
+type UnliftedType = TYPE 'PtrRepUnlifted
+#elif __GLASGOW_HASKELL__ < 902
+type UnliftedType = TYPE 'UnliftedRep
+#endif
+
+mutableByteArrayContentsShim :: MutableByteArray# s -> Addr#
+{-# INLINE mutableByteArrayContentsShim #-}
+mutableByteArrayContentsShim x =
+#if __GLASGOW_HASKELL__ >= 902
+  mutableByteArrayContents# x
+#else
+  byteArrayContents# (unsafeCoerce# x)
+#endif
+
diff --git a/Data/Primitive/Internal/Read.hs b/Data/Primitive/Internal/Read.hs
new file mode 100644
--- /dev/null
+++ b/Data/Primitive/Internal/Read.hs
@@ -0,0 +1,27 @@
+module Data.Primitive.Internal.Read
+  ( Tag(..)
+  , lexTag
+  ) where
+
+import Data.Char (isDigit)
+import Text.ParserCombinators.ReadP
+
+data Tag = FromListTag | FromListNTag
+
+-- Why don't we just use lexP? The general problem with lexP is that
+-- it doesn't always fail as fast as we might like. It will
+-- happily read to the end of an absurdly long lexeme (e.g., a 200MB string
+-- literal) before returning, at which point we'll immediately discard
+-- the result because it's not an identifier. Doing the job ourselves, we
+-- can see very quickly when we've run into a problem. We should also get
+-- a slight efficiency boost by going through the string just once.
+lexTag :: ReadP Tag
+lexTag = do
+  _ <- string "fromList"
+  s <- look
+  case s of
+    'N':c:_
+      | isDigit c
+      -> fail "" -- We have fromListN3 or similar
+      | otherwise -> FromListNTag <$ get -- Skip the 'N'
+    _ -> return FromListTag
diff --git a/Data/Primitive/MVar.hs b/Data/Primitive/MVar.hs
--- a/Data/Primitive/MVar.hs
+++ b/Data/Primitive/MVar.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE BangPatterns #-}
 {-# LANGUAGE CPP #-}
 {-# LANGUAGE MagicHash #-}
 {-# LANGUAGE UnboxedTuples #-}
diff --git a/Data/Primitive/MachDeps.hs b/Data/Primitive/MachDeps.hs
--- a/Data/Primitive/MachDeps.hs
+++ b/Data/Primitive/MachDeps.hs
@@ -113,7 +113,7 @@
 sIZEOF_WORD64 = SIZEOF_WORD64
 aLIGNMENT_WORD64 = ALIGNMENT_WORD64
 
-#if WORD_SIZE_IN_BITS == 32
+#if WORD_SIZE_IN_BITS == 32 || __GLASGOW_HASKELL__ >= 903
 type Word64_# = Word64#
 type Int64_# = Int64#
 #else
diff --git a/Data/Primitive/MutVar.hs b/Data/Primitive/MutVar.hs
--- a/Data/Primitive/MutVar.hs
+++ b/Data/Primitive/MutVar.hs
@@ -19,16 +19,24 @@
   readMutVar,
   writeMutVar,
 
+  -- * Modify
   atomicModifyMutVar,
   atomicModifyMutVar',
   modifyMutVar,
-  modifyMutVar'
+  modifyMutVar',
+  -- * Interop with STRef and IORef
+  mutVarFromIORef,
+  mutVarToIORef,
+  mutVarFromSTRef,
+  mutVarToSTRef
 ) where
 
 import Control.Monad.Primitive ( PrimMonad(..), primitive_ )
+import GHC.IORef (IORef(IORef))
+import GHC.STRef (STRef(STRef))
 import GHC.Exts ( MutVar#, sameMutVar#, newMutVar#
                 , readMutVar#, writeMutVar#, atomicModifyMutVar#
-                , isTrue# )
+                , isTrue#, RealWorld)
 import Data.Typeable ( Typeable )
 
 -- | A 'MutVar' behaves like a single-element mutable array associated
@@ -103,3 +111,23 @@
 modifyMutVar' (MutVar mv#) g = primitive_ $ \s# ->
   case readMutVar# mv# s# of
     (# s'#, a #) -> let a' = g a in a' `seq` writeMutVar# mv# a' s'#
+
+-- | Convert 'MutVar' to 'IORef'
+mutVarToIORef :: MutVar RealWorld a -> IORef a
+{-# INLINE mutVarToIORef #-}
+mutVarToIORef (MutVar mv#) = IORef (STRef mv#)
+
+-- | Convert 'MutVar' to 'IORef'
+mutVarFromIORef :: IORef a -> MutVar RealWorld a
+{-# INLINE mutVarFromIORef #-}
+mutVarFromIORef (IORef (STRef mv#)) = MutVar mv#
+
+-- | Convert 'MutVar' to 'STRef'
+mutVarToSTRef :: MutVar s a -> STRef s a
+{-# INLINE mutVarToSTRef #-}
+mutVarToSTRef (MutVar mv#) = STRef mv#
+
+-- | Convert 'MutVar' to 'STRef'
+mutVarFromSTRef :: STRef s a -> MutVar s a
+{-# INLINE mutVarFromSTRef #-}
+mutVarFromSTRef (STRef mv#) = MutVar mv#
diff --git a/Data/Primitive/PrimArray.hs b/Data/Primitive/PrimArray.hs
--- a/Data/Primitive/PrimArray.hs
+++ b/Data/Primitive/PrimArray.hs
@@ -4,7 +4,10 @@
 {-# LANGUAGE RankNTypes #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE TypeApplications #-}
 {-# LANGUAGE UnboxedTuples #-}
+{-# LANGUAGE TemplateHaskellQuotes #-}
+{-# LANGUAGE RoleAnnotations #-}
 
 -- |
 -- Module      : Data.Primitive.PrimArray
@@ -20,7 +23,7 @@
 -- However, the type constructors 'PrimArray' and 'MutablePrimArray' take one additional
 -- argument compared to their respective counterparts 'ByteArray' and 'Data.Primitive.ByteArray.MutableByteArray'.
 -- This argument is used to designate the type of element in the array.
--- Consequently, all functions in this module accept length and incides in
+-- Consequently, all functions in this module accept length and indices in
 -- terms of elements, not bytes.
 --
 -- @since 0.6.4.0
@@ -43,6 +46,7 @@
   , freezePrimArray
   , thawPrimArray
   , runPrimArray
+  , createPrimArray
   , unsafeFreezePrimArray
   , unsafeThawPrimArray
     -- * Block Operations
@@ -50,6 +54,7 @@
   , copyMutablePrimArray
   , copyPrimArrayToPtr
   , copyMutablePrimArrayToPtr
+  , copyPtrToMutablePrimArray
   , clonePrimArray
   , cloneMutablePrimArray
   , setPrimArray
@@ -59,7 +64,9 @@
   , sizeofMutablePrimArray
   , sizeofPrimArray
   , primArrayContents
+  , withPrimArrayContents
   , mutablePrimArrayContents
+  , withMutablePrimArrayContents
 #if __GLASGOW_HASKELL__ >= 802
   , isPrimArrayPinned
   , isMutablePrimArrayPinned
@@ -107,32 +114,46 @@
 import GHC.Exts
 import Data.Primitive.Types
 import Data.Primitive.ByteArray (ByteArray(..))
-import Data.Monoid ((<>))
-import Control.Applicative
+import Data.Proxy
+#if !MIN_VERSION_base(4,18,0)
+import Control.Applicative (liftA2)
+#endif
 import Control.DeepSeq
+import Control.Monad (when)
 import Control.Monad.Primitive
 import Control.Monad.ST
 import qualified Data.List as L
 import qualified Data.Primitive.ByteArray as PB
 import qualified Data.Primitive.Types as PT
 import qualified GHC.ST as GHCST
+import Language.Haskell.TH.Syntax (Lift (..))
 
-#if MIN_VERSION_base(4,9,0)
-import Data.Semigroup (Semigroup)
-import qualified Data.Semigroup as SG
-#endif
+import Data.Semigroup
 
 #if __GLASGOW_HASKELL__ >= 802
 import qualified GHC.Exts as Exts
 #endif
 
+import Data.Primitive.Internal.Operations (mutableByteArrayContentsShim)
+
 -- | Arrays of unboxed elements. This accepts types like 'Double', 'Char',
--- 'Int' and 'Word', as well as their fixed-length variants ('Word8',
--- 'Word16', etc.). Since the elements are unboxed, a 'PrimArray' is strict
--- in its elements. This differs from the behavior of 'Data.Primitive.Array.Array',
--- which is lazy in its elements.
+-- 'Int' and 'Word', as well as their fixed-length variants ('Data.Word.Word8',
+-- 'Data.Word.Word16', etc.). Since the elements are unboxed, a 'PrimArray' is
+-- strict in its elements. This differs from the behavior of
+-- 'Data.Primitive.Array.Array', which is lazy in its elements.
 data PrimArray a = PrimArray ByteArray#
 
+type role PrimArray nominal
+
+instance Lift (PrimArray a) where
+#if MIN_VERSION_template_haskell(2,16,0)
+  liftTyped ary = [|| byteArrayToPrimArray ba ||]
+#else
+  lift ary = [| byteArrayToPrimArray ba |]
+#endif
+    where
+      ba = primArrayToByteArray ary
+
 instance NFData (PrimArray a) where
   rnf (PrimArray _) = ()
 
@@ -161,7 +182,7 @@
   a1@(PrimArray ba1#) == a2@(PrimArray ba2#)
     | sameByteArray ba1# ba2# = True
     | sz1 /= sz2 = False
-    | otherwise = loop (quot sz1 (sizeOf (undefined :: a)) - 1)
+    | otherwise = loop (quot sz1 (sizeOfType @a) - 1)
     where
     -- Here, we take the size in bytes, not in elements. We do this
     -- since it allows us to defer performing the division to
@@ -183,7 +204,7 @@
     where
     sz1 = PB.sizeofByteArray (ByteArray ba1#)
     sz2 = PB.sizeofByteArray (ByteArray ba2#)
-    sz = quot (min sz1 sz2) (sizeOf (undefined :: a))
+    sz = quot (min sz1 sz2) (sizeOfType @a)
     loop !i
       | i < sz = compare (indexPrimArray a1 i) (indexPrimArray a2 i) <> loop (i + 1)
       | otherwise = compare sz1 sz2
@@ -198,9 +219,7 @@
 
 -- | @since 0.6.4.0
 instance (Show a, Prim a) => Show (PrimArray a) where
-  showsPrec p a = showParen (p > 10) $
-    showString "fromListN " . shows (sizeofPrimArray a) . showString " "
-      . shows (primArrayToList a)
+  showsPrec _ a = shows (primArrayToList a)
 
 die :: String -> String -> a
 die fun problem = error $ "Data.Primitive.PrimArray." ++ fun ++ ": " ++ problem
@@ -213,22 +232,20 @@
 
 -- | Create a 'PrimArray' from a list of a known length. If the length
 -- of the list does not match the given length, this throws an exception.
+
+-- See Note [fromListN] in Data.Primitive.Array
 primArrayFromListN :: forall a. Prim a => Int -> [a] -> PrimArray a
-primArrayFromListN len vs = runST run where
-  run :: forall s. ST s (PrimArray a)
-  run = do
-    arr <- newPrimArray len
-    let go :: [a] -> Int -> ST s ()
-        go [] !ix = if ix == len
-          then return ()
-          else die "fromListN" "list length less than specified size"
-        go (a : as) !ix = if ix < len
-          then do
-            writePrimArray arr ix a
-            go as (ix + 1)
-          else die "fromListN" "list length greater than specified size"
-    go vs 0
-    unsafeFreezePrimArray arr
+{-# INLINE primArrayFromListN #-}
+primArrayFromListN len vs = createPrimArray len $ \arr ->
+  let z ix# = if I# ix# == len
+        then return ()
+        else die "fromListN" "list length less than specified size"
+      f a k = GHC.Exts.oneShot $ \ix# -> if I# ix# < len
+        then do
+          writePrimArray arr (I# ix#) a
+          k (ix# +# 1#)
+        else die "fromListN" "list length greater than specified size"
+  in foldr f z vs 0#
 
 -- | Convert a 'PrimArray' to a list.
 {-# INLINE primArrayToList #-}
@@ -241,19 +258,17 @@
 byteArrayToPrimArray :: ByteArray -> PrimArray a
 byteArrayToPrimArray (PB.ByteArray x) = PrimArray x
 
-#if MIN_VERSION_base(4,9,0)
 -- | @since 0.6.4.0
 instance Semigroup (PrimArray a) where
-  x <> y = byteArrayToPrimArray (primArrayToByteArray x SG.<> primArrayToByteArray y)
-  sconcat = byteArrayToPrimArray . SG.sconcat . fmap primArrayToByteArray
-  stimes i arr = byteArrayToPrimArray (SG.stimes i (primArrayToByteArray arr))
-#endif
+  x <> y = byteArrayToPrimArray (primArrayToByteArray x <> primArrayToByteArray y)
+  sconcat = byteArrayToPrimArray . sconcat . fmap primArrayToByteArray
+  stimes i arr = byteArrayToPrimArray (stimes i (primArrayToByteArray arr))
 
 -- | @since 0.6.4.0
 instance Monoid (PrimArray a) where
   mempty = emptyPrimArray
 #if !(MIN_VERSION_base(4,11,0))
-  mappend x y = byteArrayToPrimArray (mappend (primArrayToByteArray x) (primArrayToByteArray y))
+  mappend = (<>)
 #endif
   mconcat = byteArrayToPrimArray . mconcat . map primArrayToByteArray
 
@@ -264,6 +279,10 @@
   (# s1#, arr# #) -> case unsafeFreezeByteArray# arr# s1# of
     (# s2#, arr'# #) -> (# s2#, PrimArray arr'# #)
 
+emptyPrimArray# :: (# #) -> ByteArray#
+{-# NOINLINE emptyPrimArray# #-}
+emptyPrimArray# _ = case emptyPrimArray of PrimArray arr# -> arr#
+
 -- | Create a new mutable primitive array of the given length. The
 -- underlying memory is left uninitialized.
 --
@@ -272,7 +291,7 @@
 {-# INLINE newPrimArray #-}
 newPrimArray (I# n#)
   = primitive (\s# ->
-      case newByteArray# (n# *# sizeOf# (undefined :: a)) s# of
+      case newByteArray# (n# *# sizeOfType# (Proxy :: Proxy a)) s# of
         (# s'#, arr# #) -> (# s'#, MutablePrimArray arr# #)
     )
 
@@ -292,7 +311,7 @@
   -> m (MutablePrimArray (PrimState m) a)
 {-# INLINE resizeMutablePrimArray #-}
 resizeMutablePrimArray (MutablePrimArray arr#) (I# n#)
-  = primitive (\s# -> case resizeMutableByteArray# arr# (n# *# sizeOf# (undefined :: a)) s# of
+  = primitive (\s# -> case resizeMutableByteArray# arr# (n# *# sizeOfType# (Proxy :: Proxy a)) s# of
                         (# s'#, arr'# #) -> (# s'#, MutablePrimArray arr'# #))
 
 -- | Shrink a mutable primitive array. The new size is given in elements.
@@ -303,7 +322,7 @@
   -> m ()
 {-# INLINE shrinkMutablePrimArray #-}
 shrinkMutablePrimArray (MutablePrimArray arr#) (I# n#)
-  = primitive_ (shrinkMutableByteArray# arr# (n# *# sizeOf# (undefined :: a)))
+  = primitive_ (shrinkMutableByteArray# arr# (n# *# sizeOfType# (Proxy :: Proxy a)))
 
 -- | Read a value from the array at the given index.
 --
@@ -343,10 +362,10 @@
 copyMutablePrimArray (MutablePrimArray dst#) (I# doff#) (MutablePrimArray src#) (I# soff#) (I# n#)
   = primitive_ (copyMutableByteArray#
       src#
-      (soff# *# sizeOf# (undefined :: a))
+      (soff# *# sizeOfType# (Proxy :: Proxy a))
       dst#
-      (doff# *# sizeOf# (undefined :: a))
-      (n# *# sizeOf# (undefined :: a))
+      (doff# *# sizeOfType# (Proxy :: Proxy a))
+      (n# *# sizeOfType# (Proxy :: Proxy a))
     )
 
 -- | Copy part of an array into another mutable array.
@@ -364,16 +383,16 @@
 copyPrimArray (MutablePrimArray dst#) (I# doff#) (PrimArray src#) (I# soff#) (I# n#)
   = primitive_ (copyByteArray#
       src#
-      (soff# *# sizeOf# (undefined :: a))
+      (soff# *# sizeOfType# (Proxy :: Proxy a))
       dst#
-      (doff# *# sizeOf# (undefined :: a))
-      (n# *# sizeOf# (undefined :: a))
+      (doff# *# sizeOfType# (Proxy :: Proxy a))
+      (n# *# sizeOfType# (Proxy :: Proxy a))
     )
 
 -- | Copy a slice of an immutable primitive array to a pointer.
 -- The offset and length are given in elements of type @a@.
 -- This function assumes that the 'Prim' instance of @a@
--- agrees with the 'Storable' instance.
+-- agrees with the 'Foreign.Storable.Storable' instance.
 --
 -- /Note:/ this function does not do bounds or overlap checking.
 copyPrimArrayToPtr :: forall m a. (PrimMonad m, Prim a)
@@ -387,12 +406,12 @@
     primitive (\ s# ->
         let s'# = copyByteArrayToAddr# ba# (soff# *# siz#) addr# (n# *# siz#) s#
         in (# s'#, () #))
-  where siz# = sizeOf# (undefined :: a)
+  where siz# = sizeOfType# (Proxy :: Proxy a)
 
 -- | Copy a slice of a mutable primitive array to a pointer.
 -- The offset and length are given in elements of type @a@.
 -- This function assumes that the 'Prim' instance of @a@
--- agrees with the 'Storable' instance.
+-- agrees with the 'Foreign.Storable.Storable' instance.
 --
 -- /Note:/ this function does not do bounds or overlap checking.
 copyMutablePrimArrayToPtr :: forall m a. (PrimMonad m, Prim a)
@@ -406,8 +425,26 @@
     primitive (\ s# ->
         let s'# = copyMutableByteArrayToAddr# mba# (soff# *# siz#) addr# (n# *# siz#) s#
         in (# s'#, () #))
-  where siz# = sizeOf# (undefined :: a)
+  where siz# = sizeOfType# (Proxy :: Proxy a)
 
+-- | Copy from a pointer to a mutable primitive array.
+-- The offset and length are given in elements of type @a@.
+-- This function assumes that the 'Prim' instance of @a@
+-- agrees with the 'Foreign.Storable.Storable' instance.
+--
+-- /Note:/ this function does not do bounds or overlap checking.
+copyPtrToMutablePrimArray :: forall m a. (PrimMonad m, Prim a)
+  => MutablePrimArray (PrimState m) a -- ^ destination array
+  -> Int -- ^ destination offset
+  -> Ptr a -- ^ source pointer
+  -> Int -- ^ number of elements
+  -> m ()
+{-# INLINE copyPtrToMutablePrimArray #-}
+copyPtrToMutablePrimArray (MutablePrimArray ba#) (I# doff#) (Ptr addr#) (I# n#) =
+  primitive_ (copyAddrToByteArray# addr# ba# (doff# *# siz#) (n# *# siz#))
+  where
+  siz# = sizeOfType# (Proxy :: Proxy a)
+
 -- | Fill a slice of a mutable primitive array with a value.
 --
 -- /Note:/ this function does not do bounds checking.
@@ -432,7 +469,7 @@
 getSizeofMutablePrimArray (MutablePrimArray arr#)
   = primitive (\s# ->
       case getSizeofMutableByteArray# arr# s# of
-        (# s'#, sz# #) -> (# s'#, I# (quotInt# sz# (sizeOf# (undefined :: a))) #)
+        (# s'#, sz# #) -> (# s'#, I# (quotInt# sz# (sizeOfType# (Proxy :: Proxy a))) #)
     )
 #else
 -- On older GHCs, it is not possible to resize a byte array, so
@@ -445,10 +482,13 @@
 -- | Size of the mutable primitive array in elements. This function shall not
 -- be used on primitive arrays that are an argument to or a result of
 -- 'resizeMutablePrimArray' or 'shrinkMutablePrimArray'.
+--
+-- This function is deprecated and will be removed.
 sizeofMutablePrimArray :: forall s a. Prim a => MutablePrimArray s a -> Int
 {-# INLINE sizeofMutablePrimArray #-}
+{-# DEPRECATED sizeofMutablePrimArray "use getSizeofMutablePrimArray instead" #-}
 sizeofMutablePrimArray (MutablePrimArray arr#) =
-  I# (quotInt# (sizeofMutableByteArray# arr#) (sizeOf# (undefined :: a)))
+  I# (quotInt# (sizeofMutableByteArray# arr#) (sizeOfType# (Proxy :: Proxy a)))
 
 -- | Check if the two arrays refer to the same memory block.
 sameMutablePrimArray :: MutablePrimArray s a -> MutablePrimArray s a -> Bool
@@ -525,7 +565,7 @@
 -- | Get the size, in elements, of the primitive array.
 sizeofPrimArray :: forall a. Prim a => PrimArray a -> Int
 {-# INLINE sizeofPrimArray #-}
-sizeofPrimArray (PrimArray arr#) = I# (quotInt# (sizeofByteArray# arr#) (sizeOf# (undefined :: a)))
+sizeofPrimArray (PrimArray arr#) = I# (quotInt# (sizeofByteArray# arr#) (sizeOfType# (Proxy :: Proxy a)))
 
 #if __GLASGOW_HASKELL__ >= 802
 -- | Check whether or not the primitive array is pinned. Pinned primitive arrays cannot
@@ -632,12 +672,10 @@
 traversePrimArrayP f arr = do
   let !sz = sizeofPrimArray arr
   marr <- newPrimArray sz
-  let go !ix = if ix < sz
-        then do
-          b <- f (indexPrimArray arr ix)
-          writePrimArray marr ix b
-          go (ix + 1)
-        else return ()
+  let go !ix = when (ix < sz) $ do
+        b <- f (indexPrimArray arr ix)
+        writePrimArray marr ix b
+        go (ix + 1)
   go 0
   unsafeFreezePrimArray marr
 
@@ -698,12 +736,10 @@
   -> m (PrimArray a)
 generatePrimArrayP sz f = do
   marr <- newPrimArray sz
-  let go !ix = if ix < sz
-        then do
-          b <- f ix
-          writePrimArray marr ix b
-          go (ix + 1)
-        else return ()
+  let go !ix = when (ix < sz) $ do
+        b <- f ix
+        writePrimArray marr ix b
+        go (ix + 1)
   go 0
   unsafeFreezePrimArray marr
 
@@ -716,12 +752,10 @@
   -> m (PrimArray a)
 replicatePrimArrayP sz f = do
   marr <- newPrimArray sz
-  let go !ix = if ix < sz
-        then do
-          b <- f
-          writePrimArray marr ix b
-          go (ix + 1)
-        else return ()
+  let go !ix = when (ix < sz) $ do
+        b <- f
+        writePrimArray marr ix b
+        go (ix + 1)
   go 0
   unsafeFreezePrimArray marr
 
@@ -731,17 +765,14 @@
   => (a -> b)
   -> PrimArray a
   -> PrimArray b
-mapPrimArray f arr = runST $ do
-  let !sz = sizeofPrimArray arr
-  marr <- newPrimArray sz
-  let go !ix = if ix < sz
-        then do
-          let b = f (indexPrimArray arr ix)
-          writePrimArray marr ix b
-          go (ix + 1)
-        else return ()
-  go 0
-  unsafeFreezePrimArray marr
+mapPrimArray f arr = createPrimArray sz $ \marr ->
+  let go !ix = when (ix < sz) $ do
+        let b = f (indexPrimArray arr ix)
+        writePrimArray marr ix b
+        go (ix + 1)
+  in go 0
+  where
+    !sz = sizeofPrimArray arr
 
 -- | Indexed map over the elements of a primitive array.
 {-# INLINE imapPrimArray #-}
@@ -749,17 +780,14 @@
   => (Int -> a -> b)
   -> PrimArray a
   -> PrimArray b
-imapPrimArray f arr = runST $ do
-  let !sz = sizeofPrimArray arr
-  marr <- newPrimArray sz
-  let go !ix = if ix < sz
-        then do
-          let b = f ix (indexPrimArray arr ix)
-          writePrimArray marr ix b
-          go (ix + 1)
-        else return ()
-  go 0
-  unsafeFreezePrimArray marr
+imapPrimArray f arr = createPrimArray sz $ \marr ->
+  let go !ix = when (ix < sz) $ do
+        let b = f ix (indexPrimArray arr ix)
+        writePrimArray marr ix b
+        go (ix + 1)
+  in go 0
+  where
+    !sz = sizeofPrimArray arr
 
 -- | Filter elements of a primitive array according to a predicate.
 {-# INLINE filterPrimArray #-}
@@ -929,15 +957,11 @@
   => Int -- ^ length
   -> (Int -> a) -- ^ element from index
   -> PrimArray a
-generatePrimArray len f = runST $ do
-  marr <- newPrimArray len
-  let go !ix = if ix < len
-        then do
-          writePrimArray marr ix (f ix)
-          go (ix + 1)
-        else return ()
-  go 0
-  unsafeFreezePrimArray marr
+generatePrimArray len f = createPrimArray len $ \marr ->
+  let go !ix = when (ix < len) $ do
+        writePrimArray marr ix (f ix)
+        go (ix + 1)
+  in go 0
 
 -- | Create a primitive array by copying the element the given
 -- number of times.
@@ -946,10 +970,8 @@
   => Int -- ^ length
   -> a -- ^ element
   -> PrimArray a
-replicatePrimArray len a = runST $ do
-  marr <- newPrimArray len
+replicatePrimArray len a = createPrimArray len $ \marr ->
   setPrimArray marr 0 len a
-  unsafeFreezePrimArray marr
 
 -- | Generate a primitive array by evaluating the applicative generator
 -- function at each index.
@@ -1001,9 +1023,8 @@
   -> f ()
 traversePrimArray_ f a = go 0 where
   !sz = sizeofPrimArray a
-  go !ix = if ix < sz
-    then f (indexPrimArray a ix) *> go (ix + 1)
-    else pure ()
+  go !ix = when (ix < sz) $
+    f (indexPrimArray a ix) *> go (ix + 1)
 
 -- | Traverse the primitive array with the indices, discarding the results.
 -- There is no 'PrimMonad' variant of this function, since it would not
@@ -1015,9 +1036,8 @@
   -> f ()
 itraversePrimArray_ f a = go 0 where
   !sz = sizeofPrimArray a
-  go !ix = if ix < sz
-    then f ix (indexPrimArray a ix) *> go (ix + 1)
-    else pure ()
+  go !ix = when (ix < sz) $
+    f ix (indexPrimArray a ix) *> go (ix + 1)
 
 newtype IxSTA a = IxSTA {_runIxSTA :: forall s. Int -> MutableByteArray# s -> ST s Int}
 
@@ -1047,31 +1067,32 @@
 -}
 
 -- | Create a /pinned/ primitive array of the specified size (in elements). The garbage
--- collector is guaranteed not to move it.
+-- collector is guaranteed not to move it. The underlying memory is left uninitialized.
 --
 -- @since 0.7.1.0
 newPinnedPrimArray :: forall m a. (PrimMonad m, Prim a)
   => Int -> m (MutablePrimArray (PrimState m) a)
 {-# INLINE newPinnedPrimArray #-}
 newPinnedPrimArray (I# n#)
-  = primitive (\s# -> case newPinnedByteArray# (n# *# sizeOf# (undefined :: a)) s# of
+  = primitive (\s# -> case newPinnedByteArray# (n# *# sizeOfType# (Proxy :: Proxy a)) s# of
                         (# s'#, arr# #) -> (# s'#, MutablePrimArray arr# #))
 
 -- | Create a /pinned/ primitive array of the specified size (in elements) and
 -- with the alignment given by its 'Prim' instance. The garbage collector is
--- guaranteed not to move it.
+-- guaranteed not to move it. The underlying memory is left uninitialized.
 --
 -- @since 0.7.0.0
 newAlignedPinnedPrimArray :: forall m a. (PrimMonad m, Prim a)
   => Int -> m (MutablePrimArray (PrimState m) a)
 {-# INLINE newAlignedPinnedPrimArray #-}
 newAlignedPinnedPrimArray (I# n#)
-  = primitive (\s# -> case newAlignedPinnedByteArray# (n# *# sizeOf# (undefined :: a)) (alignment# (undefined :: a)) s# of
+  = primitive (\s# -> case newAlignedPinnedByteArray# (n# *# sizeOfType# (Proxy :: Proxy a)) (alignmentOfType# (Proxy :: Proxy a)) s# of
                         (# s'#, arr# #) -> (# s'#, MutablePrimArray arr# #))
 
 -- | Yield a pointer to the array's data. This operation is only safe on
--- /pinned/ prim arrays allocated by 'newPinnedByteArray' or
--- 'newAlignedPinnedByteArray'.
+-- /pinned/ prim arrays allocated by
+-- 'Data.Primitive.ByteArray.newPinnedByteArray' or
+-- 'Data.Primitive.ByteArray.newAlignedPinnedByteArray'.
 --
 -- @since 0.7.1.0
 primArrayContents :: PrimArray a -> Ptr a
@@ -1079,14 +1100,15 @@
 primArrayContents (PrimArray arr#) = Ptr (byteArrayContents# arr#)
 
 -- | Yield a pointer to the array's data. This operation is only safe on
--- /pinned/ byte arrays allocated by 'newPinnedByteArray' or
--- 'newAlignedPinnedByteArray'.
+-- /pinned/ byte arrays allocated by
+-- 'Data.Primitive.ByteArray.newPinnedByteArray' or
+-- 'Data.Primitive.ByteArray.newAlignedPinnedByteArray'.
 --
 -- @since 0.7.1.0
 mutablePrimArrayContents :: MutablePrimArray s a -> Ptr a
 {-# INLINE mutablePrimArrayContents #-}
-mutablePrimArrayContents (MutablePrimArray arr#)
-  = Ptr (byteArrayContents# (unsafeCoerce# arr#))
+mutablePrimArrayContents (MutablePrimArray arr#) =
+  Ptr (mutableByteArrayContentsShim arr#)
 
 -- | Return a newly allocated array with the specified subrange of the
 -- provided array. The provided array should contain the full subrange
@@ -1097,10 +1119,8 @@
   -> Int     -- ^ number of elements to copy
   -> PrimArray a
 {-# INLINE clonePrimArray #-}
-clonePrimArray src off n = runPrimArray $ do
-  dst <- newPrimArray n
+clonePrimArray src off n = createPrimArray n $ \dst ->
   copyPrimArray dst 0 src off n
-  return dst
 
 -- | Return a newly allocated mutable array with the specified subrange of
 -- the provided mutable array. The provided mutable array should contain the
@@ -1122,7 +1142,6 @@
 runPrimArray
   :: (forall s. ST s (MutablePrimArray s a))
   -> PrimArray a
-#if MIN_VERSION_base(4,10,0) /* In new GHCs, runRW# is available. */
 runPrimArray m = PrimArray (runPrimArray# m)
 
 runPrimArray#
@@ -1134,6 +1153,40 @@
 
 unST :: ST s a -> State# s -> (# State# s, a #)
 unST (GHCST.ST f) = f
-#else /* In older GHCs, runRW# is not available. */
-runPrimArray m = runST $ m >>= unsafeFreezePrimArray
-#endif
+
+-- | Create an uninitialized array of the given length, apply the function to
+-- it, and freeze the result.
+--
+-- /Note:/ this function does not check if the input is non-negative.
+--
+-- @since FIXME
+createPrimArray
+  :: Prim a => Int -> (forall s. MutablePrimArray s a -> ST s ()) -> PrimArray a
+{-# INLINE createPrimArray #-}
+createPrimArray 0 _ = PrimArray (emptyPrimArray# (# #))
+createPrimArray n f = runPrimArray $ do
+  marr <- newPrimArray n
+  f marr
+  pure marr
+
+-- | A composition of 'primArrayContents' and 'keepAliveUnlifted'.
+-- The callback function must not return the pointer. The argument
+-- array must be /pinned/. See 'primArrayContents' for an explanation
+-- of which primitive arrays are pinned.
+--
+-- Note: This could be implemented with 'keepAlive' instead of
+-- 'keepAliveUnlifted', but 'keepAlive' here would cause GHC to materialize
+-- the wrapper data constructor on the heap.
+withPrimArrayContents :: PrimBase m => PrimArray a -> (Ptr a -> m a) -> m a
+{-# INLINE withPrimArrayContents #-}
+withPrimArrayContents (PrimArray arr#) f =
+  keepAliveUnlifted arr# (f (Ptr (byteArrayContents# arr#)))
+
+-- | A composition of 'mutablePrimArrayContents' and 'keepAliveUnlifted'.
+-- The callback function must not return the pointer. The argument
+-- array must be /pinned/. See 'primArrayContents' for an explanation
+-- of which primitive arrays are pinned.
+withMutablePrimArrayContents :: PrimBase m => MutablePrimArray (PrimState m) a -> (Ptr a -> m a) -> m a
+{-# INLINE withMutablePrimArrayContents #-}
+withMutablePrimArrayContents (MutablePrimArray arr#) f =
+  keepAliveUnlifted arr# (f (Ptr (mutableByteArrayContentsShim arr#)))
diff --git a/Data/Primitive/PrimVar.hs b/Data/Primitive/PrimVar.hs
new file mode 100644
--- /dev/null
+++ b/Data/Primitive/PrimVar.hs
@@ -0,0 +1,167 @@
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE MagicHash #-}
+{-# LANGUAGE RoleAnnotations #-}
+{-# LANGUAGE UnboxedTuples #-}
+{-# LANGUAGE Unsafe #-}
+
+-- | Variant of @MutVar@ that has one less indirection for primitive types.
+-- The difference is illustrated by comparing @MutVar Int@ and @PrimVar Int@:
+--
+-- * @MutVar Int@: @MutVar# --> I#@
+-- * @PrimVar Int@: @MutableByteArray#@
+--
+-- This module is adapted from a module in Edward Kmett\'s @prim-ref@ library.
+module Data.Primitive.PrimVar
+  (
+  -- * Primitive References
+    PrimVar(..)
+  , newPrimVar
+  , newPinnedPrimVar
+  , newAlignedPinnedPrimVar
+  , readPrimVar
+  , writePrimVar
+  , modifyPrimVar
+  , primVarContents
+  , primVarToMutablePrimArray
+  -- * Atomic Operations
+  -- $atomic
+  , casInt
+  , fetchAddInt
+  , fetchSubInt
+  , fetchAndInt
+  , fetchNandInt
+  , fetchOrInt
+  , fetchXorInt
+  , atomicReadInt
+  , atomicWriteInt
+  ) where
+
+import Control.Monad.Primitive
+import Data.Primitive
+import GHC.Exts
+import GHC.Ptr (castPtr)
+
+--------------------------------------------------------------------------------
+-- * Primitive References
+--------------------------------------------------------------------------------
+
+-- | A 'PrimVar' behaves like a single-element mutable primitive array.
+newtype PrimVar s a = PrimVar (MutablePrimArray s a)
+
+type role PrimVar nominal nominal
+
+-- | Create a primitive reference.
+newPrimVar :: (PrimMonad m, Prim a) => a -> m (PrimVar (PrimState m) a)
+newPrimVar a = do
+  m <- newPrimArray 1
+  writePrimArray m 0 a
+  return (PrimVar m)
+{-# INLINE newPrimVar #-}
+
+-- | Create a pinned primitive reference.
+newPinnedPrimVar :: (PrimMonad m, Prim a) => a -> m (PrimVar (PrimState m) a)
+newPinnedPrimVar a = do
+  m <- newPinnedPrimArray 1
+  writePrimArray m 0 a
+  return (PrimVar m)
+{-# INLINE newPinnedPrimVar #-}
+
+-- | Create a pinned primitive reference with the appropriate alignment for its contents.
+newAlignedPinnedPrimVar :: (PrimMonad m, Prim a) => a -> m (PrimVar (PrimState m) a)
+newAlignedPinnedPrimVar a = do
+  m <- newAlignedPinnedPrimArray 1
+  writePrimArray m 0 a
+  return (PrimVar m)
+{-# INLINE newAlignedPinnedPrimVar #-}
+
+-- | Read a value from the 'PrimVar'.
+readPrimVar :: (PrimMonad m, Prim a) => PrimVar (PrimState m) a -> m a
+readPrimVar (PrimVar m) = readPrimArray m 0
+{-# INLINE readPrimVar #-}
+
+-- | Write a value to the 'PrimVar'.
+writePrimVar :: (PrimMonad m, Prim a) => PrimVar (PrimState m) a -> a -> m ()
+writePrimVar (PrimVar m) a = writePrimArray m 0 a
+{-# INLINE writePrimVar #-}
+
+-- | Mutate the contents of a 'PrimVar'.
+modifyPrimVar :: (PrimMonad m, Prim a) => PrimVar (PrimState m) a -> (a -> a) -> m ()
+modifyPrimVar pv f = do
+    x <- readPrimVar pv
+    writePrimVar pv (f x)
+{-# INLINE modifyPrimVar #-}
+
+instance Eq (PrimVar s a) where
+  PrimVar m == PrimVar n = sameMutablePrimArray m n
+  {-# INLINE (==) #-}
+
+-- | Yield a pointer to the data of a 'PrimVar'. This operation is only safe on pinned byte arrays allocated by
+-- 'newPinnedPrimVar' or 'newAlignedPinnedPrimVar'.
+primVarContents :: PrimVar s a -> Ptr a
+primVarContents (PrimVar m) = castPtr $ mutablePrimArrayContents m
+{-# INLINE primVarContents #-}
+
+-- | Convert a 'PrimVar' to a one-elment 'MutablePrimArray'.
+primVarToMutablePrimArray :: PrimVar s a -> MutablePrimArray s a
+primVarToMutablePrimArray (PrimVar m) = m
+{-# INLINE primVarToMutablePrimArray #-}
+
+--------------------------------------------------------------------------------
+-- * Atomic Operations
+--------------------------------------------------------------------------------
+
+-- $atomic
+-- Atomic operations on `PrimVar s Int`. All atomic operations imply a full memory barrier.
+
+-- | Given a primitive reference, the expected old value, and the new value, perform an atomic compare and swap i.e. write the new value if the current value matches the provided old value. Returns the value of the element before the operation.
+casInt :: PrimMonad m => PrimVar (PrimState m) Int -> Int -> Int -> m Int
+casInt (PrimVar (MutablePrimArray m)) (I# old) (I# new) = primitive $ \s -> case casIntArray# m 0# old new s of
+  (# s', result #) -> (# s', I# result #)
+{-# INLINE casInt #-}
+
+-- | Given a reference, and a value to add, atomically add the value to the element. Returns the value of the element before the operation.
+fetchAddInt :: PrimMonad m => PrimVar (PrimState m) Int -> Int -> m Int
+fetchAddInt (PrimVar (MutablePrimArray m)) (I# x) = primitive $ \s -> case fetchAddIntArray# m 0# x s of
+  (# s', result #) -> (# s', I# result #)
+{-# INLINE fetchAddInt #-}
+
+-- | Given a reference, and a value to subtract, atomically subtract the value from the element. Returns the value of the element before the operation.
+fetchSubInt :: PrimMonad m => PrimVar (PrimState m) Int -> Int -> m Int
+fetchSubInt (PrimVar (MutablePrimArray m)) (I# x) = primitive $ \s -> case fetchSubIntArray# m 0# x s of
+  (# s', result #) -> (# s', I# result #)
+{-# INLINE fetchSubInt #-}
+
+-- | Given a reference, and a value to bitwise and, atomically and the value with the element. Returns the value of the element before the operation.
+fetchAndInt :: PrimMonad m => PrimVar (PrimState m) Int -> Int -> m Int
+fetchAndInt (PrimVar (MutablePrimArray m)) (I# x) = primitive $ \s -> case fetchAndIntArray# m 0# x s of
+  (# s', result #) -> (# s', I# result #)
+{-# INLINE fetchAndInt #-}
+
+-- | Given a reference, and a value to bitwise nand, atomically nand the value with the element. Returns the value of the element before the operation.
+fetchNandInt :: PrimMonad m => PrimVar (PrimState m) Int -> Int -> m Int
+fetchNandInt (PrimVar (MutablePrimArray m)) (I# x) = primitive $ \s -> case fetchNandIntArray# m 0# x s of
+  (# s', result #) -> (# s', I# result #)
+{-# INLINE fetchNandInt #-}
+
+-- | Given a reference, and a value to bitwise or, atomically or the value with the element. Returns the value of the element before the operation.
+fetchOrInt :: PrimMonad m => PrimVar (PrimState m) Int -> Int -> m Int
+fetchOrInt (PrimVar (MutablePrimArray m)) (I# x) = primitive $ \s -> case fetchOrIntArray# m 0# x s of
+  (# s', result #) -> (# s', I# result #)
+{-# INLINE fetchOrInt #-}
+
+-- | Given a reference, and a value to bitwise xor, atomically xor the value with the element. Returns the value of the element before the operation.
+fetchXorInt :: PrimMonad m => PrimVar (PrimState m) Int -> Int -> m Int
+fetchXorInt (PrimVar (MutablePrimArray m)) (I# x) = primitive $ \s -> case fetchXorIntArray# m 0# x s of
+  (# s', result #) -> (# s', I# result #)
+{-# INLINE fetchXorInt #-}
+
+-- | Given a reference, atomically read an element.
+atomicReadInt :: PrimMonad m => PrimVar (PrimState m) Int -> m Int
+atomicReadInt (PrimVar (MutablePrimArray m)) = primitive $ \s -> case atomicReadIntArray# m 0# s of
+  (# s', result #) -> (# s', I# result #)
+{-# INLINE atomicReadInt #-}
+
+-- | Given a reference, atomically write an element.
+atomicWriteInt :: PrimMonad m => PrimVar (PrimState m) Int -> Int -> m ()
+atomicWriteInt (PrimVar (MutablePrimArray m)) (I# x) = primitive_ $ \s -> atomicWriteIntArray# m 0# x s
+{-# INLINE atomicWriteInt #-}
diff --git a/Data/Primitive/Ptr.hs b/Data/Primitive/Ptr.hs
--- a/Data/Primitive/Ptr.hs
+++ b/Data/Primitive/Ptr.hs
@@ -1,7 +1,7 @@
 {-# LANGUAGE CPP #-}
 {-# LANGUAGE MagicHash #-}
-{-# LANGUAGE UnboxedTuples #-}
 {-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TypeApplications #-}
 
 -- |
 -- Module      : Data.Primitive.Ptr
@@ -28,17 +28,16 @@
   -- * Block operations
   copyPtr, movePtr, setPtr
 
-#if __GLASGOW_HASKELL__ >= 708
   , copyPtrToMutablePrimArray
   , copyPtrToMutableByteArray
-#endif
 ) where
 
 import Control.Monad.Primitive
 import Data.Primitive.Types
-import Data.Primitive.PrimArray (MutablePrimArray(..))
-import Data.Primitive.ByteArray (MutableByteArray(..))
+import Data.Primitive.PrimArray (copyPtrToMutablePrimArray)
+import Data.Primitive.ByteArray (copyPtrToMutableByteArray)
 
+import Data.Proxy
 import GHC.Exts
 import GHC.Ptr
 import Foreign.Marshal.Utils
@@ -47,14 +46,14 @@
 -- | Offset a pointer by the given number of elements.
 advancePtr :: forall a. Prim a => Ptr a -> Int -> Ptr a
 {-# INLINE advancePtr #-}
-advancePtr (Ptr a#) (I# i#) = Ptr (plusAddr# a# (i# *# sizeOf# (undefined :: a)))
+advancePtr (Ptr a#) (I# i#) = Ptr (plusAddr# a# (i# *# sizeOfType# (Proxy :: Proxy a)))
 
 -- | Subtract a pointer from another pointer. The result represents
 -- the number of elements of type @a@ that fit in the contiguous
 -- memory range bounded by these two pointers.
 subtractPtr :: forall a. Prim a => Ptr a -> Ptr a -> Int
 {-# INLINE subtractPtr #-}
-subtractPtr (Ptr a#) (Ptr b#) = I# (quotInt# (minusAddr# a# b#) (sizeOf# (undefined :: a)))
+subtractPtr (Ptr a#) (Ptr b#) = I# (quotInt# (minusAddr# a# b#) (sizeOfType# (Proxy :: Proxy a)))
 
 -- | Read a value from a memory position given by a pointer and an offset.
 -- The memory block the address refers to must be immutable. The offset is in
@@ -84,7 +83,7 @@
   -> m ()
 {-# INLINE copyPtr #-}
 copyPtr (Ptr dst#) (Ptr src#) n
-  = unsafePrimToPrim $ copyBytes (Ptr dst#) (Ptr src#) (n * sizeOf (undefined :: a))
+  = unsafePrimToPrim $ copyBytes (Ptr dst#) (Ptr src#) (n * sizeOfType @a)
 
 -- | Copy the given number of elements from the second 'Ptr' to the first. The
 -- areas may overlap.
@@ -95,39 +94,10 @@
   -> m ()
 {-# INLINE movePtr #-}
 movePtr (Ptr dst#) (Ptr src#) n
-  = unsafePrimToPrim $ moveBytes (Ptr dst#) (Ptr src#) (n * sizeOf (undefined :: a))
+  = unsafePrimToPrim $ moveBytes (Ptr dst#) (Ptr src#) (n * sizeOfType @a)
 
 -- | Fill a memory block with the given value. The length is in
 -- elements of type @a@ rather than in bytes.
 setPtr :: (Prim a, PrimMonad m) => Ptr a -> Int -> a -> m ()
 {-# INLINE setPtr #-}
 setPtr (Ptr addr#) (I# n#) x = primitive_ (setOffAddr# addr# 0# n# x)
-
-
--- | Copy from a pointer to a mutable primitive array.
--- The offset and length are given in elements of type @a@.
-copyPtrToMutablePrimArray :: forall m a. (PrimMonad m, Prim a)
-  => MutablePrimArray (PrimState m) a -- ^ destination array
-  -> Int -- ^ destination offset
-  -> Ptr a -- ^ source pointer
-  -> Int -- ^ number of elements
-  -> m ()
-{-# INLINE copyPtrToMutablePrimArray #-}
-copyPtrToMutablePrimArray (MutablePrimArray ba#) (I# doff#) (Ptr addr#) (I# n#) =
-  primitive_ (copyAddrToByteArray# addr# ba# (doff# *# siz#) (n# *# siz#))
-  where
-  siz# = sizeOf# (undefined :: a)
-
--- | Copy from a pointer to a mutable byte array.
--- The offset and length are given in elements of type @a@.
-copyPtrToMutableByteArray :: forall m a. (PrimMonad m, Prim a)
-  => MutableByteArray (PrimState m) -- ^ destination array
-  -> Int   -- ^ destination offset given in elements of type @a@
-  -> Ptr a -- ^ source pointer
-  -> Int   -- ^ number of elements
-  -> m ()
-{-# INLINE copyPtrToMutableByteArray #-}
-copyPtrToMutableByteArray (MutableByteArray ba#) (I# doff#) (Ptr addr#) (I# n#) =
-  primitive_ (copyAddrToByteArray# addr# ba# (doff# *# siz#) (n# *# siz#))
-  where
-  siz# = sizeOf# (undefined :: a)
diff --git a/Data/Primitive/SmallArray.hs b/Data/Primitive/SmallArray.hs
--- a/Data/Primitive/SmallArray.hs
+++ b/Data/Primitive/SmallArray.hs
@@ -7,6 +7,7 @@
 {-# LANGUAGE DeriveDataTypeable #-}
 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
 {-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE TemplateHaskellQuotes #-}
 
 -- |
 -- Module : Data.Primitive.SmallArray
@@ -53,9 +54,11 @@
   , runSmallArray
   , createSmallArray
   , sizeofSmallArray
+  , getSizeofSmallMutableArray
   , sizeofSmallMutableArray
 #if MIN_VERSION_base(4,14,0)
   , shrinkSmallMutableArray
+  , resizeSmallMutableArray
 #endif
   , emptySmallArray
   , smallArrayFromList
@@ -78,21 +81,16 @@
 import Data.Data
 import Data.Foldable as Foldable
 import Data.Functor.Identity
-#if !(MIN_VERSION_base(4,10,0))
-import Data.Monoid
-#endif
-#if MIN_VERSION_base(4,9,0)
+import Data.Primitive.Internal.Read (Tag(..),lexTag)
+import Text.Read (Read (..), parens, prec)
 import qualified GHC.ST as GHCST
-import qualified Data.Semigroup as Sem
-#endif
+import Data.Semigroup
 import Text.ParserCombinators.ReadP
-#if MIN_VERSION_base(4,10,0)
-import GHC.Exts (runRW#)
-#elif MIN_VERSION_base(4,9,0)
-import GHC.Base (runRW#)
-#endif
+import Text.ParserCombinators.ReadPrec (ReadPrec)
+import qualified Text.ParserCombinators.ReadPrec as RdPrc
 
 import Data.Functor.Classes (Eq1(..), Ord1(..), Show1(..), Read1(..))
+import Language.Haskell.TH.Syntax (Lift(..))
 
 data SmallArray a = SmallArray (SmallArray# a)
   deriving Typeable
@@ -108,6 +106,36 @@
 data SmallMutableArray s a = SmallMutableArray (SmallMutableArray# s a)
   deriving Typeable
 
+instance Lift a => Lift (SmallArray a) where
+#if MIN_VERSION_template_haskell(2,16,0)
+  liftTyped ary = case lst of
+    [] -> [|| SmallArray (emptySmallArray# (##)) ||]
+    [x] -> [|| pure $! x ||]
+    x : xs -> [|| unsafeSmallArrayFromListN' len x xs ||]
+#else
+  lift ary = case lst of
+    [] -> [| SmallArray (emptySmallArray# (##)) |]
+    [x] -> [| pure $! x |]
+    x : xs -> [| unsafeSmallArrayFromListN' len x xs |]
+#endif
+    where
+      len = length ary
+      lst = toList ary
+
+-- | Strictly create an array from a nonempty list (represented as
+-- a first element and a list of the rest) of a known length. If the length
+-- of the list does not match the given length, this makes demons fly
+-- out of your nose. We use it in the 'Lift' instance. If you edit the
+-- splice and break it, you get to keep both pieces.
+unsafeSmallArrayFromListN' :: Int -> a -> [a] -> SmallArray a
+unsafeSmallArrayFromListN' n y ys =
+  createSmallArray n y $ \sma ->
+    let go !_ix [] = return ()
+        go !ix (!x : xs) = do
+            writeSmallArray sma ix x
+            go (ix+1) xs
+    in go 1 ys
+
 -- | Create a new small mutable array.
 --
 -- /Note:/ this function does not check if the input is non-negative.
@@ -148,7 +176,7 @@
 
 -- | Look up an element in an immutable array.
 --
--- The purpose of returning a result using a monad is to allow the caller to
+-- The purpose of returning a result using an applicative is to allow the caller to
 -- avoid retaining references to the array. Evaluating the return value will
 -- cause the array lookup to be performed, even though it may not require the
 -- element of the array to be evaluated (which could throw an exception). For
@@ -160,19 +188,19 @@
 -- > f sa = case indexSmallArrayM sa 0 of
 -- >   Box x -> ...
 --
--- 'x' is not a closure that references 'sa' as it would be if we instead
+-- @x@ is not a closure that references @sa@ as it would be if we instead
 -- wrote:
 --
 -- > let x = indexSmallArray sa 0
 --
--- It also does not prevent 'sa' from being garbage collected.
+-- It also does not prevent @sa@ from being garbage collected.
 --
 -- Note that 'Identity' is not adequate for this use, as it is a newtype, and
 -- cannot be evaluated without evaluating the element.
 --
 -- /Note:/ this function does not do bounds checking.
 indexSmallArrayM
-  :: Monad m
+  :: Applicative m
   => SmallArray a -- ^ array
   -> Int          -- ^ index
   -> m a
@@ -322,10 +350,54 @@
 sizeofSmallArray (SmallArray sa#) = I# (sizeofSmallArray# sa#)
 {-# INLINE sizeofSmallArray #-}
 
--- | The number of elements in a mutable array.
+-- | Get the number of elements in a mutable array. Unlike
+-- 'sizeofSmallMutableArray', this function will be sure to produce the correct
+-- result if 'SmallMutableArray' has been shrunk in place. Consider the following:
+--
+-- @
+-- do
+--   sa <- 'newSmallArray' 10 x
+--   print $ 'sizeofSmallMutableArray' sa
+--   'shrinkSmallMutableArray' sa 5
+--   print $ sizeofSmallMutableArray sa
+-- @
+--
+-- The compiler is well within its rights to eliminate the second size check
+-- and print @10@ twice. However, 'getSizeofSmallMutableArray' will check
+-- the size each time it's /executed/ (not /evaluated/), so it won't have this
+-- problem:
+--
+-- @
+-- do
+--   sa <- 'newSmallArray' 10 x
+--   print =<< getSizeofSmallMutableArray sa
+--   'shrinkSmallMutableArray' sa 5
+--   print =<< getSizeofSmallMutableArray sa
+-- @
+--
+-- will certainly print @10@ and then @5@.
+getSizeofSmallMutableArray
+  :: PrimMonad m
+  => SmallMutableArray (PrimState m) a
+  -> m Int
+#if MIN_VERSION_base(4,14,0)
+getSizeofSmallMutableArray (SmallMutableArray sa#) = primitive $ \s ->
+  case getSizeofSmallMutableArray# sa# s of
+    (# s', sz# #) -> (# s', I# sz# #)
+#else
+getSizeofSmallMutableArray sa = pure $! sizeofSmallMutableArray sa
+#endif
+{-# INLINE getSizeofSmallMutableArray #-}
+
+-- | The number of elements in a mutable array. This should only be used
+-- for arrays that are not shrunk in place.
+--
+-- This is deprecated and will be removed in a future release. Use
+-- 'getSizeofSmallMutableArray' instead.
 sizeofSmallMutableArray :: SmallMutableArray s a -> Int
 sizeofSmallMutableArray (SmallMutableArray sa#) =
   I# (sizeofSmallMutableArray# sa#)
+{-# DEPRECATED sizeofSmallMutableArray "use getSizeofSmallMutableArray instead" #-}
 {-# INLINE sizeofSmallMutableArray #-}
 
 -- | This is the fastest, most straightforward way to traverse
@@ -371,9 +443,6 @@
 runSmallArray
   :: (forall s. ST s (SmallMutableArray s a))
   -> SmallArray a
-#if !MIN_VERSION_base(4,9,0)
-runSmallArray m = runST $ m >>= unsafeFreezeSmallArray
-#else
 -- This low-level business is designed to work with GHC's worker-wrapper
 -- transformation. A lot of the time, we don't actually need an Array
 -- constructor. By putting it on the outside, and being careful about
@@ -392,7 +461,6 @@
 
 unST :: ST s a -> State# s -> (# State# s, a #)
 unST (GHCST.ST f) = f
-#endif
 
 -- | Create an array of the given size with a default value,
 -- apply the monadic function and freeze the result. If the
@@ -450,11 +518,7 @@
 
 -- | @since 0.6.4.0
 instance Eq1 SmallArray where
-#if MIN_VERSION_base(4,9,0) || MIN_VERSION_transformers(0,5,0)
   liftEq = smallArrayLiftEq
-#else
-  eq1 = smallArrayLiftEq (==)
-#endif
 
 instance Eq a => Eq (SmallArray a) where
   sa1 == sa2 = smallArrayLiftEq (==) sa1 sa2
@@ -476,11 +540,7 @@
 
 -- | @since 0.6.4.0
 instance Ord1 SmallArray where
-#if MIN_VERSION_base(4,9,0) || MIN_VERSION_transformers(0,5,0)
   liftCompare = smallArrayLiftCompare
-#else
-  compare1 = smallArrayLiftCompare compare
-#endif
 
 -- | Lexicographic ordering. Subject to change between major versions.
 instance Ord a => Ord (SmallArray a) where
@@ -762,17 +822,24 @@
       sz = sizeofSmallArray (f err)
       err = error "mfix for Data.Primitive.SmallArray applied to strict function."
 
-#if MIN_VERSION_base(4,9,0)
 -- | @since 0.6.3.0
-instance Sem.Semigroup (SmallArray a) where
+instance Semigroup (SmallArray a) where
   (<>) = (<|>)
   sconcat = mconcat . toList
-#endif
+  stimes n arr = case compare n 0 of
+    LT -> die "stimes" "negative multiplier"
+    EQ -> empty
+    GT -> createSmallArray (n' * sizeofSmallArray arr) (die "stimes" "impossible") $ \sma ->
+      let go i = when (i < n') $ do
+            copySmallArray sma (i * sizeofSmallArray arr) arr 0 (sizeofSmallArray arr)
+            go (i + 1)
+      in go 0
+    where n' = fromIntegral n :: Int
 
 instance Monoid (SmallArray a) where
   mempty = empty
 #if !(MIN_VERSION_base(4,11,0))
-  mappend = (<|>)
+  mappend = (<>)
 #endif
   mconcat l = createSmallArray n (die "mconcat" "impossible") $ \ma ->
     let go !_  [    ] = return ()
@@ -788,9 +855,8 @@
   toList = Foldable.toList
 
 smallArrayLiftShowsPrec :: (Int -> a -> ShowS) -> ([a] -> ShowS) -> Int -> SmallArray a -> ShowS
-smallArrayLiftShowsPrec elemShowsPrec elemListShowsPrec p sa = showParen (p > 10) $
-  showString "fromListN " . shows (length sa) . showString " "
-    . listLiftShowsPrec elemShowsPrec elemListShowsPrec 11 (toList sa)
+smallArrayLiftShowsPrec elemShowsPrec elemListShowsPrec _ sa =
+  listLiftShowsPrec elemShowsPrec elemListShowsPrec 11 (toList sa)
 
 -- this need to be included for older ghcs
 listLiftShowsPrec :: (Int -> a -> ShowS) -> ([a] -> ShowS) -> Int -> [a] -> ShowS
@@ -801,33 +867,29 @@
 
 -- | @since 0.6.4.0
 instance Show1 SmallArray where
-#if MIN_VERSION_base(4,9,0) || MIN_VERSION_transformers(0,5,0)
   liftShowsPrec = smallArrayLiftShowsPrec
-#else
-  showsPrec1 = smallArrayLiftShowsPrec showsPrec showList
-#endif
 
-smallArrayLiftReadsPrec :: (Int -> ReadS a) -> ReadS [a] -> Int -> ReadS (SmallArray a)
-smallArrayLiftReadsPrec _ listReadsPrec p = readParen (p > 10) . readP_to_S $ do
-  () <$ string "fromListN"
-  skipSpaces
-  n <- readS_to_P reads
-  skipSpaces
-  l <- readS_to_P listReadsPrec
-  return $ smallArrayFromListN n l
+-- See Note [Forgiving Array Read Instance]
+smallArrayLiftReadPrec :: ReadPrec a -> ReadPrec [a] -> ReadPrec (SmallArray a)
+smallArrayLiftReadPrec _ read_list =
+  ( RdPrc.lift skipSpaces >> fmap fromList read_list )
+  RdPrc.+++
+  ( parens $ prec app_prec $ do
+      RdPrc.lift skipSpaces
+      tag <- RdPrc.lift lexTag
+      case tag of
+        FromListTag -> fromList <$> read_list
+        FromListNTag -> liftM2 fromListN readPrec read_list
+  )
+  where
+  app_prec = 10
 
 instance Read a => Read (SmallArray a) where
-  readsPrec = smallArrayLiftReadsPrec readsPrec readList
+  readPrec = smallArrayLiftReadPrec readPrec readListPrec
 
 -- | @since 0.6.4.0
 instance Read1 SmallArray where
-#if MIN_VERSION_base(4,9,0) || MIN_VERSION_transformers(0,5,0)
-  liftReadsPrec = smallArrayLiftReadsPrec
-#else
-  readsPrec1 = smallArrayLiftReadsPrec readsPrec readList
-#endif
-
-
+  liftReadPrec = smallArrayLiftReadPrec
 
 smallArrayDataType :: DataType
 smallArrayDataType =
@@ -852,18 +914,19 @@
 -- | Create a 'SmallArray' from a list of a known length. If the length
 -- of the list does not match the given length, this throws an exception.
 smallArrayFromListN :: Int -> [a] -> SmallArray a
+{-# INLINE smallArrayFromListN #-}
 smallArrayFromListN n l =
   createSmallArray n
       (die "smallArrayFromListN" "uninitialized element") $ \sma ->
-  let go !ix [] = if ix == n
+  let z ix# = if I# ix# == n
         then return ()
         else die "smallArrayFromListN" "list length less than specified size"
-      go !ix (x : xs) = if ix < n
+      f x k = GHC.Exts.oneShot $ \ix# -> if I# ix# < n
         then do
-          writeSmallArray sma ix x
-          go (ix + 1) xs
+          writeSmallArray sma (I# ix#) x
+          k (ix# +# 1#)
         else die "smallArrayFromListN" "list length greater than specified size"
-  in go 0 l
+  in foldr f z l 0#
 
 -- | Create a 'SmallArray' from a list.
 smallArrayFromList :: [a] -> SmallArray a
@@ -881,4 +944,26 @@
   (\s0 -> case GHC.Exts.shrinkSmallMutableArray# x n s0 of
     s1 -> (# s1, () #)
   )
+
+-- | Resize a mutable array to new specified size. The returned
+-- 'SmallMutableArray' is either the original 'SmallMutableArray'
+-- resized in-place or, if not possible, a newly allocated
+-- 'SmallMutableArray' with the original content copied over.
+--
+-- To avoid undefined behaviour, the original 'SmallMutableArray'
+-- shall not be accessed anymore after a 'resizeSmallMutableArray' has
+-- been performed. Moreover, no reference to the old one should be
+-- kept in order to allow garbage collection of the original
+-- 'SmallMutableArray' in case a new 'SmallMutableArray' had to be
+-- allocated.
+resizeSmallMutableArray :: PrimMonad m
+  => SmallMutableArray (PrimState m) a
+  -> Int -- ^ New size
+  -> a   -- ^ Newly created slots initialized to this element. Only used when array is grown.
+  -> m (SmallMutableArray (PrimState m) a)
+resizeSmallMutableArray (SmallMutableArray arr) (I# n) x = primitive
+  (\s0 -> case GHC.Exts.resizeSmallMutableArray# arr n x s0 of
+    (# s1, arr' #) -> (# s1, SmallMutableArray arr' #)
+  )
+{-# INLINE resizeSmallMutableArray #-}
 #endif
diff --git a/Data/Primitive/Types.hs b/Data/Primitive/Types.hs
--- a/Data/Primitive/Types.hs
+++ b/Data/Primitive/Types.hs
@@ -1,9 +1,14 @@
-{-# LANGUAGE CPP, UnboxedTuples, MagicHash, DeriveDataTypeable #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving, StandaloneDeriving #-}
+{-# LANGUAGE AllowAmbiguousTypes #-}
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE UnboxedTuples #-}
+{-# LANGUAGE MagicHash #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE StandaloneDeriving #-}
 {-# LANGUAGE ScopedTypeVariables #-}
-#if __GLASGOW_HASKELL__ >= 800
+{-# LANGUAGE TypeApplications #-}
+
+#if __GLASGOW_HASKELL__ < 906
 {-# LANGUAGE TypeInType #-}
-{-# LANGUAGE DeriveGeneric #-}
 #endif
 
 #include "HsBaseConfig.h"
@@ -20,7 +25,7 @@
 
 module Data.Primitive.Types
   ( Prim(..)
-  , sizeOf, alignment, defaultSetByteArray#, defaultSetOffAddr#
+  , sizeOf, sizeOfType, alignment, alignmentOfType, defaultSetByteArray#, defaultSetOffAddr#
   , PrimStorable(..)
   , Ptr(..)
   ) where
@@ -31,6 +36,7 @@
 import Foreign.Ptr (IntPtr, intPtrToPtr, ptrToIntPtr, WordPtr, wordPtrToPtr, ptrToWordPtr)
 import Foreign.C.Types
 import System.Posix.Types
+import Data.Complex
 
 import GHC.Word (Word8(..), Word16(..), Word32(..), Word64(..))
 import GHC.Int (Int8(..), Int16(..), Int32(..), Int64(..))
@@ -47,13 +53,14 @@
 import GHC.IO (IO(..))
 import qualified GHC.Exts
 
-
 import Control.Applicative (Const(..))
 import Data.Functor.Identity (Identity(..))
 import qualified Data.Monoid as Monoid
-import Data.Ord (Down(..))
-#if MIN_VERSION_base(4,9,0)
 import qualified Data.Semigroup as Semigroup
+import Data.Proxy
+
+#if !MIN_VERSION_base(4,13,0)
+import Data.Ord (Down(..))
 #endif
 
 -- | Class of types supporting primitive array operations. This includes
@@ -61,11 +68,31 @@
 -- and interfacing with unmanaged memory (functions suffixed with @Addr#@).
 -- Endianness is platform-dependent.
 class Prim a where
-  -- | Size of values of type @a@. The argument is not used.
+  -- We use `Proxy` instead of `Proxy#`, since the latter doesn't work with GND for GHC <= 8.8.
+
+  -- | The size of values of type @a@ in bytes. This has to be used with TypeApplications: @sizeOfType \@a@.
+  --
+  -- @since 0.9.0.0
+  sizeOfType# :: Proxy a -> Int#
+  sizeOfType# _ = sizeOf# (dummy :: a)
+
+  -- | The size of values of type @a@ in bytes. The argument is not used.
+  --
+  -- It is recommended to use 'sizeOfType#' instead.
   sizeOf# :: a -> Int#
+  sizeOf# _ = sizeOfType# (Proxy :: Proxy a)
 
-  -- | Alignment of values of type @a@. The argument is not used.
+  -- | The alignment of values of type @a@ in bytes. This has to be used with TypeApplications: @alignmentOfType \@a@.
+  --
+  -- @since 0.9.0.0
+  alignmentOfType# :: Proxy a -> Int#
+  alignmentOfType# _ = alignment# (dummy :: a)
+
+  -- | The alignment of values of type @a@ in bytes. The argument is not used.
+  --
+  -- It is recommended to use 'alignmentOfType#' instead.
   alignment# :: a -> Int#
+  alignment# _ = alignmentOfType# (Proxy :: Proxy a)
 
   -- | Read a value from the array. The offset is in elements of type
   -- @a@ rather than in bytes.
@@ -88,6 +115,7 @@
     -> a
     -> State# s
     -> State# s
+  setByteArray# = defaultSetByteArray#
 
   -- | Read a value from a memory position given by an address and an offset.
   -- The memory block the address refers to must be immutable. The offset is in
@@ -111,21 +139,88 @@
     -> a
     -> State# s
     -> State# s
+  setOffAddr# = defaultSetOffAddr#
 
--- | Size of values of type @a@. The argument is not used.
+  {-# MINIMAL (sizeOfType# | sizeOf#), (alignmentOfType# | alignment#), indexByteArray#, readByteArray#, writeByteArray#,
+    indexOffAddr#, readOffAddr#, writeOffAddr# #-}
+
+-- | A dummy value of type @a@.
+dummy :: a
+dummy = errorWithoutStackTrace "Data.Primitive.Types: implementation mistake in `Prim` instance"
+{-# NOINLINE dummy #-}
+
+-- | The size of values of type @a@ in bytes. This has to be used with TypeApplications: @sizeOfType \@a@.
 --
+-- >>> :set -XTypeApplications
+-- >>> import Data.Int (Int32)
+-- >>> sizeOfType @Int32
+-- 4
+--
+-- @since 0.9.0.0
+sizeOfType :: forall a. Prim a => Int
+sizeOfType = I# (sizeOfType# (Proxy :: Proxy a))
+
+-- | The size of values of type @a@ in bytes. The argument is not used.
+--
+-- It is recommended to use 'sizeOfType' instead.
+--
 -- This function has existed since 0.1, but was moved from 'Data.Primitive'
 -- to 'Data.Primitive.Types' in version 0.6.3.0.
 sizeOf :: Prim a => a -> Int
 sizeOf x = I# (sizeOf# x)
 
--- | Alignment of values of type @a@. The argument is not used.
+-- | The alignment of values of type @a@ in bytes. This has to be used with TypeApplications: @alignmentOfType \@a@.
 --
+-- @since 0.9.0.0
+alignmentOfType :: forall a. Prim a => Int
+alignmentOfType = I# (alignmentOfType# (Proxy :: Proxy a))
+
+-- | The alignment of values of type @a@ in bytes. The argument is not used.
+--
+-- It is recommended to use 'alignmentOfType' instead.
+--
 -- This function has existed since 0.1, but was moved from 'Data.Primitive'
 -- to 'Data.Primitive.Types' in version 0.6.3.0.
 alignment :: Prim a => a -> Int
 alignment x = I# (alignment# x)
 
+-- | @since 0.9.0.0
+instance Prim a => Prim (Complex a) where
+  sizeOf# _ = 2# *# sizeOf# (undefined :: a)
+  alignment# _ = alignment# (undefined :: a)
+  indexByteArray# arr# i# =
+    let x = indexByteArray# arr# (2# *# i#)
+        y = indexByteArray# arr# (2# *# i# +# 1#)
+    in x :+ y
+  readByteArray# arr# i# =
+    \s0 -> case readByteArray# arr# (2# *# i#) s0 of
+       (# s1#, x #) -> case readByteArray# arr# (2# *# i# +# 1#) s1# of
+          (# s2#, y #) -> (# s2#, x :+ y #)
+  writeByteArray# arr# i# (a :+ b) =
+    \s0 -> case writeByteArray# arr# (2# *# i#) a s0 of
+       s1 -> case writeByteArray# arr# (2# *# i# +# 1#) b s1 of
+         s2 -> s2
+  indexOffAddr# addr# i# =
+    let x = indexOffAddr# addr# (2# *# i#)
+        y = indexOffAddr# addr# (2# *# i# +# 1#)
+    in x :+ y
+  readOffAddr# addr# i# =
+    \s0 -> case readOffAddr# addr# (2# *# i#) s0 of
+       (# s1, x #) -> case readOffAddr# addr# (2# *# i# +# 1#) s1 of
+         (# s2, y #) -> (# s2, x :+ y #)
+  writeOffAddr# addr# i# (a :+ b) =
+    \s0 -> case writeOffAddr# addr# (2# *# i#) a s0 of
+       s1 -> case writeOffAddr# addr# (2# *# i# +# 1#) b s1 of
+         s2 -> s2
+  {-# INLINE sizeOf# #-}
+  {-# INLINE alignment# #-}
+  {-# INLINE indexByteArray# #-}
+  {-# INLINE readByteArray# #-}
+  {-# INLINE writeByteArray# #-}
+  {-# INLINE indexOffAddr# #-}
+  {-# INLINE readOffAddr# #-}
+  {-# INLINE writeOffAddr# #-}
+
 -- | An implementation of 'setByteArray#' that calls 'writeByteArray#'
 -- to set each element. This is helpful when writing a 'Prim' instance
 -- for a multi-word data type for which there is no CPU-accelerated way
@@ -135,8 +230,8 @@
 -- > data Trip = Trip Int Int Int
 -- >
 -- > instance Prim Trip
--- >   sizeOf# _ = 3# *# sizeOf# (undefined :: Int)
--- >   alignment# _ = alignment# (undefined :: Int)
+-- >   sizeOfType# _ = 3# *# sizeOfType# (proxy# :: Proxy# Int)
+-- >   alignmentOfType# _ = alignmentOfType# (proxy# :: Proxy# Int)
 -- >   indexByteArray# arr# i# = ...
 -- >   readByteArray# arr# i# = ...
 -- >   writeByteArray# arr# i# (Trip a b c) =
@@ -186,8 +281,8 @@
 newtype PrimStorable a = PrimStorable { getPrimStorable :: a }
 
 instance Prim a => Storable (PrimStorable a) where
-  sizeOf _ = sizeOf (undefined :: a)
-  alignment _ = alignment (undefined :: a)
+  sizeOf _ = sizeOfType @a
+  alignment _ = alignmentOfType @a
   peekElemOff (Ptr addr#) (I# i#) =
     primitive $ \s0# -> case readOffAddr# addr# i# s0# of
       (# s1, x #) -> (# s1, PrimStorable x #)
@@ -196,8 +291,8 @@
 
 #define derivePrim(ty, ctr, sz, align, idx_arr, rd_arr, wr_arr, set_arr, idx_addr, rd_addr, wr_addr, set_addr) \
 instance Prim (ty) where {                                        \
-  sizeOf# _ = unI# sz                                             \
-; alignment# _ = unI# align                                       \
+  sizeOfType# _ = unI# sz                                         \
+; alignmentOfType# _ = unI# align                                 \
 ; indexByteArray# arr# i# = ctr (idx_arr arr# i#)                 \
 ; readByteArray#  arr# i# s# = case rd_arr arr# i# s# of          \
                         { (# s1#, x# #) -> (# s1#, ctr x# #) }    \
@@ -219,8 +314,8 @@
           } in                                                    \
       case unsafeCoerce# (internal (set_addr addr# i n x#)) s# of \
         { (# s1#, _ #) -> s1# }                                   \
-; {-# INLINE sizeOf# #-}                                          \
-; {-# INLINE alignment# #-}                                       \
+; {-# INLINE sizeOfType# #-}                                      \
+; {-# INLINE alignmentOfType# #-}                                 \
 ; {-# INLINE indexByteArray# #-}                                  \
 ; {-# INLINE readByteArray# #-}                                   \
 ; {-# INLINE writeByteArray# #-}                                  \
@@ -315,9 +410,7 @@
 deriving instance Prim CSigAtomic
 deriving instance Prim CLLong
 deriving instance Prim CULLong
-#if MIN_VERSION_base(4,10,0)
 deriving instance Prim CBool
-#endif
 deriving instance Prim CIntPtr
 deriving instance Prim CUIntPtr
 deriving instance Prim CIntMax
@@ -404,8 +497,8 @@
 
 -- | @since 0.7.1.0
 instance Prim WordPtr where
-  sizeOf# _ = sizeOf# (undefined :: Ptr ())
-  alignment# _ = alignment# (undefined :: Ptr ())
+  sizeOfType# _ = sizeOfType# (Proxy :: Proxy (Ptr ()))
+  alignmentOfType# _ = alignmentOfType# (Proxy :: Proxy (Ptr ()))
   indexByteArray# a i = ptrToWordPtr (indexByteArray# a i)
   readByteArray# a i s0 = case readByteArray# a i s0 of
     (# s1, p #) -> (# s1, ptrToWordPtr p #)
@@ -419,8 +512,8 @@
 
 -- | @since 0.7.1.0
 instance Prim IntPtr where
-  sizeOf# _ = sizeOf# (undefined :: Ptr ())
-  alignment# _ = alignment# (undefined :: Ptr ())
+  sizeOfType# _ = sizeOfType# (Proxy :: Proxy (Ptr ()))
+  alignmentOfType# _ = alignmentOfType# (Proxy :: Proxy (Ptr ()))
   indexByteArray# a i = ptrToIntPtr (indexByteArray# a i)
   readByteArray# a i s0 = case readByteArray# a i s0 of
     (# s1, p #) -> (# s1, ptrToIntPtr p #)
@@ -444,7 +537,6 @@
 deriving instance Prim a => Prim (Monoid.Sum a)
 -- | @since 0.6.5.0
 deriving instance Prim a => Prim (Monoid.Product a)
-#if MIN_VERSION_base(4,9,0)
 -- | @since 0.6.5.0
 deriving instance Prim a => Prim (Semigroup.First a)
 -- | @since 0.6.5.0
@@ -453,4 +545,3 @@
 deriving instance Prim a => Prim (Semigroup.Min a)
 -- | @since 0.6.5.0
 deriving instance Prim a => Prim (Semigroup.Max a)
-#endif
diff --git a/bench/main.hs b/bench/main.hs
--- a/bench/main.hs
+++ b/bench/main.hs
@@ -9,6 +9,7 @@
 import Control.Monad.ST
 import Data.Primitive
 import Control.Monad.Trans.State.Strict
+import Data.Set (Set)
 
 -- These are fixed implementations of certain operations. In the event
 -- that primitive changes its implementation of a function, these
@@ -25,6 +26,8 @@
 import qualified PrimArray.Compare
 import qualified PrimArray.Traverse
 
+import qualified Data.Set as Set
+
 main :: IO ()
 main = defaultMain
   [ bgroup "Array"
@@ -34,6 +37,9 @@
         , bench "unsafe" (nf (\x -> runST (runStateT (Array.Traverse.Unsafe.traversePoly cheap x) 0)) numbers)
         ]
       ]
+    , bgroup "arrayFromListN"
+      [ bench "set-to-list-to-array" (whnf arrayFromSet setOfIntegers1024)
+      ]
     ]
   , bgroup "ByteArray"
     [ bgroup "compare"
@@ -62,8 +68,18 @@
     ]
   ]
 
+setOfIntegers1024 :: Set Integer
+{-# noinline setOfIntegers1024 #-}
+setOfIntegers1024 = Set.fromList [1..1024]
+
+-- The performance of this is used to confirm whether or not arrayFromListN is
+-- actining as a good consumer for list fusion.
+arrayFromSet :: Set Integer -> Array Integer
+{-# noinline arrayFromSet #-}
+arrayFromSet s = arrayFromListN (Set.size s) (Set.toList s)
+
 cheap :: Int -> StateT Int (ST s) Int
 cheap i = modify (\x -> x + i) >> return (i * i)
 
 numbers :: Array Int
-numbers = fromList (enumFromTo 0 10000)
+numbers = arrayFromList (enumFromTo 0 10000)
diff --git a/changelog.md b/changelog.md
--- a/changelog.md
+++ b/changelog.md
@@ -1,3 +1,91 @@
+## Changes in version 0.9.1.0
+
+  * Make fromListN functions good consumers for list fusion.
+
+  * Add functions to improve `MutVar`'s interoperability with `IORef` and `STRef`.
+
+  * Add `createPrimArray` and `createByteArray`.
+
+  * Add `byteArrayAsForeignPtr` and `mutableByteArrayAsForeignPtr`.
+
+  * Use `copyMutableByteArrayNonOverlapping#` in the implementation of `copyMutableByteArray`
+    on sufficiently new GHCs. This does not change the contract for `copyMutableByteArray`.
+    This function has always been documented as having undefined behavior when the slices
+    overlap. However, overlaps previously were handled gracefully (with the semantics
+    of C's `memmove`). Going forward, users who do not uphold `copyMutableByteArray`'s
+    precondition will be met with unpredictable results.
+
+  * Drop support for GHC 8.0.
+
+## Changes in version 0.9.0.0
+
+  * Add `withByteArrayContents`, `withMutableByteArrayContents`,
+    `withPrimArrayContents`, `withMutablePrimArrayContents`.
+
+  * Fix signature of `keepAlive`.
+
+  * Remove re-export of `fromList` and `fromListN` from `Data.Primitive.Array`.
+
+  * Use `mutableByteArrayContents#` in GHC 9.2+
+
+  * Add `Prim` instance for `Complex`.
+
+  * Add `getSizeofSmallMutableArray` that wraps `getSizeofSmallMutableArray#`
+    from `GHC.Exts`.
+
+  * Add default definitions for the `setByteArray#` and `setOffAddr#` methods,
+    so they don't need to be defined explicitly anymore.
+
+  * Add standalone `sizeOfType`/`alignmentOfType` (recommended over `sizeOf`/`alignment`)
+    and `Prim` class methods `sizeOfType#`/`alignmentOfType#` (recommended over `sizeOf#`/`alignment#`)
+
+  * Change `Show` instances of `PrimArray`, `Array`, and `SmallArray`. These
+    previously used the `fromListN n [...]` form, but they now used the more
+    terse `[...]` form.
+
+  * Correct the `Read` instances of `Array` and `SmallArray`. These instances
+    are supposed to be able to handle all three of these forms: `fromList [...]`,
+    `fromListN n [...]`, and `[...]`. They had been rejected the last form, but
+    this mistake was discovered by the test suite when the Show instances were
+    changed.
+
+## Changes in version 0.8.0.0
+
+  * Add `resizeSmallMutableArray` that wraps `resizeSmallMutableArray#` from
+    `GHC.Exts`.
+
+  * New module `Data.Primitive.PrimVar`. This is essentially `PrimArray` with
+    element length 1. For types with `Prim` instances, this is a drop-in
+    replacement for `MutVar` with fewer indirections.
+
+  * `PrimArray`'s type argument has been given a nominal role instead of a phantom role.
+    This is a breaking change.
+
+  * Add `readCharArray`, `writeCharArray`, `indexCharArray` for operating on
+    8-bit characters in a byte array.
+
+  * When building with `base-4.17` and newer, re-export the `ByteArray` and
+    `MutableByteArray` types from `base` instead of defining them in this
+    library. This does not change the user-facing interface of
+    `Data.Primitive.ByteArray`.
+
+  * Add `keepAlive` that wraps `keepAlive#` for GHC 9.2 and newer. It
+    falls back to using `touch` for older GHCs.
+
+## Changes in version 0.7.4.0
+
+  * Add Lift instances (#332)
+
+  * Expose `copyPtrToMutablePrimArray`
+
+  * Improve definitions for stimes (#326)
+
+  * Support GHC 9.4. Note: GHC 9.4 is not released at the time of
+    primitive-0.7.4.0's release, so this support might be reverted by
+    a hackage metadata revision if things change.
+
+  * Drop support for GHC 7.10
+
 ## Changes in version 0.7.3.0
 
   * Correct implementations of `*>` for `Array` and `SmallArray`.
diff --git a/primitive.cabal b/primitive.cabal
--- a/primitive.cabal
+++ b/primitive.cabal
@@ -1,7 +1,7 @@
-Cabal-Version:  2.2
+Cabal-Version:  2.0
 Name:           primitive
-Version:        0.7.3.0
-License:        BSD-3-Clause
+Version:        0.9.1.0
+License:        BSD3
 License-File:   LICENSE
 
 Author:         Roman Leshchinskiy <rl@cse.unsw.edu.au>
@@ -19,16 +19,22 @@
                     test/LICENSE
 
 Tested-With:
-  GHC == 7.10.3,
-  GHC == 8.0.2,
-  GHC == 8.2.2,
-  GHC == 8.4.4,
-  GHC == 8.6.5,
-  GHC == 8.8.4,
+  GHC == 8.2.2
+  GHC == 8.4.4
+  GHC == 8.6.5
+  GHC == 8.8.4
   GHC == 8.10.7
+  GHC == 9.0.2
+  GHC == 9.2.8
+  GHC == 9.4.8
+  GHC == 9.6.6
+  GHC == 9.8.2
+  GHC == 9.10.1
 
 Library
   Default-Language: Haskell2010
+  Default-Extensions:
+        TypeOperators
   Other-Extensions:
         BangPatterns, CPP, DeriveDataTypeable,
         MagicHash, TypeFamilies, UnboxedTuples, UnliftedFFITypes
@@ -45,21 +51,27 @@
         Data.Primitive.Ptr
         Data.Primitive.MutVar
         Data.Primitive.MVar
+        Data.Primitive.PrimVar
 
   Other-Modules:
         Data.Primitive.Internal.Operations
+        Data.Primitive.Internal.Read
 
-  Build-Depends: base >= 4.8 && < 4.17
-               , deepseq >= 1.1 && < 1.5
-               , transformers >= 0.4.2 && < 0.7
-  if !impl(ghc >= 8.0)
-    Build-Depends: fail == 4.9.*
+  Build-Depends: base >= 4.10 && < 4.22
+               , deepseq >= 1.1 && < 1.6
+               , transformers >= 0.5 && < 0.7
+               , template-haskell >= 2.11
 
+  if impl(ghc >= 9.2)
+    cpp-options: -DHAVE_KEEPALIVE
+
+  if impl(ghc < 9.4)
+    build-depends: data-array-byte >= 0.1 && < 0.1.1
+
   Ghc-Options: -O2
 
   Include-Dirs: cbits
   Install-Includes: primitive-memops.h
-  includes: primitive-memops.h
   c-sources: cbits/primitive-memops.c
   if !os(solaris)
       cc-options: -ftree-vectorize
@@ -70,22 +82,19 @@
   Default-Language: Haskell2010
   hs-source-dirs: test
                   test/src
-  main-is: main.hs
+  main-is: Main.hs
   Other-Modules: PrimLaws
   type: exitcode-stdio-1.0
   build-depends: base
                , base-orphans
-               , ghc-prim
                , primitive
                , quickcheck-classes-base >= 0.6 && <0.7
-               , QuickCheck >= 2.13 && < 2.15
-               , tasty ^>= 1.2 || ^>= 1.3 || ^>= 1.4
+               , QuickCheck >= 2.13 && < 2.16
+               , tasty >= 1.2 && < 1.6
                , tasty-quickcheck
                , tagged
-               , transformers >= 0.4
+               , transformers >= 0.5
                , transformers-compat
-  if !impl(ghc >= 8.0)
-    build-depends: semigroups
 
   cpp-options: -DHAVE_UNARY_LAWS
   ghc-options: -O2
@@ -104,10 +113,11 @@
     PrimArray.Traverse
   build-depends:
       base
+    , containers
     , primitive
     , deepseq
     , tasty-bench
-    , transformers >= 0.3
+    , transformers >= 0.5
 
 source-repository head
   type:     git
diff --git a/test/Main.hs b/test/Main.hs
new file mode 100644
--- /dev/null
+++ b/test/Main.hs
@@ -0,0 +1,439 @@
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE KindSignatures #-}
+{-# LANGUAGE MagicHash #-}
+{-# LANGUAGE UnboxedTuples #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE StandaloneDeriving #-}
+{-# LANGUAGE TypeApplications #-}
+
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+
+#if __GLASGOW_HASKELL__ >= 805
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE DerivingVia #-}
+{-# LANGUAGE TypeInType #-}
+#endif
+
+import Control.Monad
+import Control.Monad.ST
+import Data.Complex
+import Data.Primitive
+import Data.Word
+import Data.Proxy (Proxy(..))
+import GHC.Int
+import GHC.IO
+import GHC.Exts
+import Data.Function (on)
+import Control.Applicative (Const(..))
+import PrimLaws (primLaws)
+
+import Data.Functor.Identity (Identity(..))
+import qualified Data.Monoid as Monoid
+import Data.Semigroup (stimes, stimesMonoid)
+import qualified Data.Semigroup as Semigroup
+#if !(MIN_VERSION_base(4,11,0))
+import Data.Monoid ((<>))
+#endif
+#if __GLASGOW_HASKELL__ >= 805
+import Foreign.Storable (Storable)
+#endif
+import Data.Orphans ()
+
+import Test.Tasty (defaultMain,testGroup,TestTree)
+import Test.QuickCheck (Arbitrary,Arbitrary1,Gen,CoArbitrary,Function,(===),(==>))
+import qualified Test.Tasty.QuickCheck as TQC
+import qualified Test.QuickCheck as QC
+import qualified Test.QuickCheck.Classes.Base as QCC
+import qualified Test.QuickCheck.Classes.Base.IsList as QCCL
+import qualified Data.List as L
+
+main :: IO ()
+main = do
+  testArray
+  testByteArray
+  defaultMain $ testGroup "properties"
+    [ testGroup "Array"
+      [ lawsToTest (QCC.eqLaws (Proxy :: Proxy (Array Int)))
+      , lawsToTest (QCC.ordLaws (Proxy :: Proxy (Array Int)))
+      , lawsToTest (QCC.monoidLaws (Proxy :: Proxy (Array Int)))
+      , lawsToTest (QCC.showReadLaws (Proxy :: Proxy (Array Int)))
+      , lawsToTest (QCC.functorLaws (Proxy :: Proxy Array))
+      , lawsToTest (QCC.applicativeLaws (Proxy :: Proxy Array))
+      , lawsToTest (QCC.alternativeLaws (Proxy :: Proxy Array))
+      , lawsToTest (QCC.monadLaws (Proxy :: Proxy Array))
+      , lawsToTest (QCC.monadZipLaws (Proxy :: Proxy Array))
+      , lawsToTest (QCC.monadPlusLaws (Proxy :: Proxy Array))
+      , lawsToTest (QCC.foldableLaws (Proxy :: Proxy Array))
+      , lawsToTest (QCC.traversableLaws (Proxy :: Proxy Array))
+      , lawsToTest (QCC.isListLaws (Proxy :: Proxy (Array Int)))
+      , TQC.testProperty "mapArray'" (QCCL.mapProp int16 int32 mapArray')
+      , TQC.testProperty "*>" $ \(xs :: Array Int) (ys :: Array Int) -> toList (xs *> ys) === (toList xs *> toList ys)
+      , TQC.testProperty "<*" $ \(xs :: Array Int) (ys :: Array Int) -> toList (xs <* ys) === (toList xs <* toList ys)
+      , lawsToTest (QCC.semigroupLaws (Proxy :: Proxy (Array Int)))
+      , TQC.testProperty "stimes" $ \(QC.NonNegative (n :: Int)) (xs :: Array Int) -> stimes n xs == stimesMonoid n xs
+      ]
+    , testGroup "SmallArray"
+      [ lawsToTest (QCC.eqLaws (Proxy :: Proxy (SmallArray Int)))
+      , lawsToTest (QCC.ordLaws (Proxy :: Proxy (SmallArray Int)))
+      , lawsToTest (QCC.monoidLaws (Proxy :: Proxy (SmallArray Int)))
+      , lawsToTest (QCC.showReadLaws (Proxy :: Proxy (Array Int)))
+      , lawsToTest (QCC.functorLaws (Proxy :: Proxy SmallArray))
+      , lawsToTest (QCC.applicativeLaws (Proxy :: Proxy SmallArray))
+      , lawsToTest (QCC.alternativeLaws (Proxy :: Proxy SmallArray))
+      , lawsToTest (QCC.monadLaws (Proxy :: Proxy SmallArray))
+      , lawsToTest (QCC.monadZipLaws (Proxy :: Proxy SmallArray))
+      , lawsToTest (QCC.monadPlusLaws (Proxy :: Proxy SmallArray))
+      , lawsToTest (QCC.foldableLaws (Proxy :: Proxy SmallArray))
+      , lawsToTest (QCC.traversableLaws (Proxy :: Proxy SmallArray))
+      , lawsToTest (QCC.isListLaws (Proxy :: Proxy (SmallArray Int)))
+      , TQC.testProperty "mapSmallArray'" (QCCL.mapProp int16 int32 mapSmallArray')
+      , TQC.testProperty "*>" $ \(xs :: SmallArray Int) (ys :: SmallArray Int) -> toList (xs *> ys) === (toList xs *> toList ys)
+      , TQC.testProperty "<*" $ \(xs :: SmallArray Int) (ys :: SmallArray Int) -> toList (xs <* ys) === (toList xs <* toList ys)
+      , lawsToTest (QCC.semigroupLaws (Proxy :: Proxy (SmallArray Int)))
+      , TQC.testProperty "stimes" $ \(QC.NonNegative (n :: Int)) (xs :: SmallArray Int) -> stimes n xs == stimesMonoid n xs
+      ]
+    , testGroup "ByteArray"
+      [ testGroup "Ordering"
+        [ TQC.testProperty "equality" byteArrayEqProp
+        , TQC.testProperty "compare" byteArrayCompareProp
+      , testGroup "Filling"
+        [ TQC.testProperty "Int8" (setByteArrayProp (Proxy :: Proxy Int8))
+        , TQC.testProperty "Int16" (setByteArrayProp (Proxy :: Proxy Int16))
+        , TQC.testProperty "Int32" (setByteArrayProp (Proxy :: Proxy Int32))
+        , TQC.testProperty "Int64" (setByteArrayProp (Proxy :: Proxy Int64))
+        , TQC.testProperty "Int" (setByteArrayProp (Proxy :: Proxy Int))
+        , TQC.testProperty "Word8" (setByteArrayProp (Proxy :: Proxy Word8))
+        , TQC.testProperty "Word16" (setByteArrayProp (Proxy :: Proxy Word16))
+        , TQC.testProperty "Word32" (setByteArrayProp (Proxy :: Proxy Word32))
+        , TQC.testProperty "Word64" (setByteArrayProp (Proxy :: Proxy Word64))
+        , TQC.testProperty "Word" (setByteArrayProp (Proxy :: Proxy Word))
+        ]
+      ]
+      , testGroup "Resize"
+        [ TQC.testProperty "shrink" byteArrayShrinkProp
+        , TQC.testProperty "grow" byteArrayGrowProp
+        ]
+      , lawsToTest (QCC.eqLaws (Proxy :: Proxy ByteArray))
+      , lawsToTest (QCC.ordLaws (Proxy :: Proxy ByteArray))
+      , lawsToTest (QCC.monoidLaws (Proxy :: Proxy ByteArray))
+      , lawsToTest (QCC.showReadLaws (Proxy :: Proxy (Array Int)))
+      , lawsToTest (QCC.isListLaws (Proxy :: Proxy ByteArray))
+      , TQC.testProperty "foldrByteArray" (QCCL.foldrProp word8 foldrByteArray)
+      , lawsToTest (QCC.semigroupLaws (Proxy :: Proxy ByteArray))
+      , TQC.testProperty "stimes" $ \(QC.NonNegative (n :: Int)) (xs :: ByteArray) -> stimes n xs == stimesMonoid n xs
+      ]
+    , testGroup "PrimArray"
+      [ lawsToTest (QCC.eqLaws (Proxy :: Proxy (PrimArray Word16)))
+      , lawsToTest (QCC.ordLaws (Proxy :: Proxy (PrimArray Word16)))
+      , lawsToTest (QCC.monoidLaws (Proxy :: Proxy (PrimArray Word16)))
+      , lawsToTest (QCC.isListLaws (Proxy :: Proxy (PrimArray Word16)))
+      , TQC.testProperty "foldrPrimArray" (QCCL.foldrProp int16 foldrPrimArray)
+      , TQC.testProperty "foldrPrimArray'" (QCCL.foldrProp int16 foldrPrimArray')
+      , TQC.testProperty "foldlPrimArray" (QCCL.foldlProp int16 foldlPrimArray)
+      , TQC.testProperty "foldlPrimArray'" (QCCL.foldlProp int16 foldlPrimArray')
+      , TQC.testProperty "foldlPrimArrayM'" (QCCL.foldlMProp int16 foldlPrimArrayM')
+      , TQC.testProperty "mapPrimArray" (QCCL.mapProp int16 int32 mapPrimArray)
+      , TQC.testProperty "traversePrimArray" (QCCL.traverseProp int16 int32 traversePrimArray)
+      , TQC.testProperty "traversePrimArrayP" (QCCL.traverseProp int16 int32 traversePrimArrayP)
+      , TQC.testProperty "imapPrimArray" (QCCL.imapProp int16 int32 imapPrimArray)
+      , TQC.testProperty "itraversePrimArray" (QCCL.imapMProp int16 int32 itraversePrimArray)
+      , TQC.testProperty "itraversePrimArrayP" (QCCL.imapMProp int16 int32 itraversePrimArrayP)
+      , TQC.testProperty "generatePrimArray" (QCCL.generateProp int16 generatePrimArray)
+      , TQC.testProperty "generatePrimArrayA" (QCCL.generateMProp int16 generatePrimArrayA)
+      , TQC.testProperty "generatePrimArrayP" (QCCL.generateMProp int16 generatePrimArrayP)
+      , TQC.testProperty "replicatePrimArray" (QCCL.replicateProp int16 replicatePrimArray)
+      , TQC.testProperty "replicatePrimArrayA" (QCCL.replicateMProp int16 replicatePrimArrayA)
+      , TQC.testProperty "replicatePrimArrayP" (QCCL.replicateMProp int16 replicatePrimArrayP)
+      , TQC.testProperty "filterPrimArray" (QCCL.filterProp int16 filterPrimArray)
+      , TQC.testProperty "filterPrimArrayA" (QCCL.filterMProp int16 filterPrimArrayA)
+      , TQC.testProperty "filterPrimArrayP" (QCCL.filterMProp int16 filterPrimArrayP)
+      , TQC.testProperty "mapMaybePrimArray" (QCCL.mapMaybeProp int16 int32 mapMaybePrimArray)
+      , TQC.testProperty "mapMaybePrimArrayA" (QCCL.mapMaybeMProp int16 int32 mapMaybePrimArrayA)
+      , TQC.testProperty "mapMaybePrimArrayP" (QCCL.mapMaybeMProp int16 int32 mapMaybePrimArrayP)
+      , lawsToTest (QCC.semigroupLaws (Proxy :: Proxy (PrimArray Word16)))
+      , TQC.testProperty "stimes" $ \(QC.NonNegative (n :: Int)) (xs :: PrimArray Word16) -> stimes n xs == stimesMonoid n xs
+      ]
+    , testGroup "DefaultSetMethod"
+      [ lawsToTest (primLaws (Proxy :: Proxy DefaultSetMethod))
+      ]
+#if __GLASGOW_HASKELL__ >= 805
+    , testGroup "PrimStorable"
+      [ lawsToTest (QCC.storableLaws (Proxy :: Proxy Derived))
+      ]
+#endif
+    , testGroup "Prim"
+      [ renameLawsToTest "Word" (primLaws (Proxy :: Proxy Word))
+      , renameLawsToTest "Word8" (primLaws (Proxy :: Proxy Word8))
+      , renameLawsToTest "Word16" (primLaws (Proxy :: Proxy Word16))
+      , renameLawsToTest "Word32" (primLaws (Proxy :: Proxy Word32))
+      , renameLawsToTest "Word64" (primLaws (Proxy :: Proxy Word64))
+      , renameLawsToTest "Int" (primLaws (Proxy :: Proxy Int))
+      , renameLawsToTest "Int8" (primLaws (Proxy :: Proxy Int8))
+      , renameLawsToTest "Int16" (primLaws (Proxy :: Proxy Int16))
+      , renameLawsToTest "Int32" (primLaws (Proxy :: Proxy Int32))
+      , renameLawsToTest "Int64" (primLaws (Proxy :: Proxy Int64))
+      , renameLawsToTest "Const" (primLaws (Proxy :: Proxy (Const Int16 Int16)))
+      , renameLawsToTest "Down" (primLaws (Proxy :: Proxy (Down Int16)))
+      , renameLawsToTest "Identity" (primLaws (Proxy :: Proxy (Identity Int16)))
+      , renameLawsToTest "Dual" (primLaws (Proxy :: Proxy (Monoid.Dual Int16)))
+      , renameLawsToTest "Sum" (primLaws (Proxy :: Proxy (Monoid.Sum Int16)))
+      , renameLawsToTest "Product" (primLaws (Proxy :: Proxy (Monoid.Product Int16)))
+      , renameLawsToTest "First" (primLaws (Proxy :: Proxy (Semigroup.First Int16)))
+      , renameLawsToTest "Last" (primLaws (Proxy :: Proxy (Semigroup.Last Int16)))
+      , renameLawsToTest "Min" (primLaws (Proxy :: Proxy (Semigroup.Min Int16)))
+      , renameLawsToTest "Max" (primLaws (Proxy :: Proxy (Semigroup.Max Int16)))
+      , renameLawsToTest "Complex" (primLaws (Proxy :: Proxy (Complex Double)))
+      ]
+    ]
+
+deriving instance Arbitrary a => Arbitrary (Down a)
+-- Const, Dual, Sum, Product: all have Arbitrary instances defined
+-- in QuickCheck itself
+deriving instance Arbitrary a => Arbitrary (Semigroup.First a)
+deriving instance Arbitrary a => Arbitrary (Semigroup.Last a)
+deriving instance Arbitrary a => Arbitrary (Semigroup.Min a)
+deriving instance Arbitrary a => Arbitrary (Semigroup.Max a)
+
+word8 :: Proxy Word8
+word8 = Proxy
+
+int16 :: Proxy Int16
+int16 = Proxy
+
+int32 :: Proxy Int32
+int32 = Proxy
+
+
+setByteArrayProp :: forall a. (Prim a, Eq a, Arbitrary a, Show a) => Proxy a -> QC.Property
+setByteArrayProp _ = QC.property $ \(QC.NonNegative (n :: Int)) (QC.NonNegative (off :: Int)) (QC.NonNegative (len :: Int)) (x :: a) (y :: a) ->
+  (off < n && off + len <= n) ==>
+  -- We use PrimArray in this test because it makes it easier to
+  -- get the element-vs-byte distinction right.
+  let actual = runST $ do
+        m <- newPrimArray n
+        forM_ (enumFromTo 0 (n - 1)) $ \ix -> writePrimArray m ix x
+        setPrimArray m off len y
+        unsafeFreezePrimArray m
+      expected = runST $ do
+        m <- newPrimArray n
+        forM_ (enumFromTo 0 (n - 1)) $ \ix -> writePrimArray m ix x
+        forM_ (enumFromTo off (off + len - 1)) $ \ix -> writePrimArray m ix y
+        unsafeFreezePrimArray m
+   in expected === actual
+
+
+-- Tests that using resizeByteArray to shrink a byte array produces
+-- the same results as calling Data.List.take on the list that the
+-- byte array corresponds to.
+byteArrayShrinkProp :: QC.Property
+byteArrayShrinkProp = QC.property $ \(QC.NonNegative (n :: Int)) (QC.NonNegative (m :: Int)) ->
+  let large = max n m
+      small = min n m
+      xs = intsLessThan large
+      ys = byteArrayFromList xs
+      largeBytes = large * sizeOfType @Int
+      smallBytes = small * sizeOfType @Int
+      expected = byteArrayFromList (L.take small xs)
+      actual = runST $ do
+        mzs0 <- newByteArray largeBytes
+        copyByteArray mzs0 0 ys 0 largeBytes
+        mzs1 <- resizeMutableByteArray mzs0 smallBytes
+        unsafeFreezeByteArray mzs1
+   in expected === actual
+
+-- Tests that using resizeByteArray with copyByteArray (to fill in the
+-- new empty space) to grow a byte array produces the same results as
+-- calling Data.List.++ on the lists corresponding to the original
+-- byte array and the appended byte array.
+byteArrayGrowProp :: QC.Property
+byteArrayGrowProp = QC.property $ \(QC.NonNegative (n :: Int)) (QC.NonNegative (m :: Int)) ->
+  let large = max n m
+      small = min n m
+      xs1 = intsLessThan small
+      xs2 = intsLessThan (large - small)
+      ys1 = byteArrayFromList xs1
+      ys2 = byteArrayFromList xs2
+      largeBytes = large * sizeOfType @Int
+      smallBytes = small * sizeOfType @Int
+      expected = byteArrayFromList (xs1 ++ xs2)
+      actual = runST $ do
+        mzs0 <- newByteArray smallBytes
+        copyByteArray mzs0 0 ys1 0 smallBytes
+        mzs1 <- resizeMutableByteArray mzs0 largeBytes
+        copyByteArray mzs1 smallBytes ys2 0 ((large - small) * sizeOfType @Int)
+        unsafeFreezeByteArray mzs1
+   in expected === actual
+
+-- Tests that writing stable ptrs to a PrimArray, reading them back
+-- out, and then dereferencing them gives correct results.
+--stablePtrPrimProp :: QC.Property
+--stablePtrPrimProp = QC.property $ \(xs :: [Integer]) -> unsafePerformIO $ do
+--  ptrs <- mapM newStablePtr xs
+--  let ptrs' = primArrayToList (primArrayFromList ptrs)
+--  ys <- mapM deRefStablePtr ptrs'
+--  mapM_ freeStablePtr ptrs'
+--  return (xs === ys)
+
+--stablePtrPrimBlockProp :: QC.Property
+--stablePtrPrimBlockProp = QC.property $ \(x :: Word) (QC.NonNegative (len :: Int)) -> unsafePerformIO $ do
+--  ptr <- newStablePtr x
+--  let ptrs' = replicatePrimArray len ptr
+--  let go ix = if ix < len
+--        then do
+--          n <- deRefStablePtr (indexPrimArray ptrs' ix)
+--          ns <- go (ix + 1)
+--          return (n : ns)
+--        else return []
+--  ys <- go 0
+--  freeStablePtr ptr
+--  return (L.replicate len x === ys)
+
+
+
+-- Provide the non-negative integers up to the bound. For example:
+--
+-- >>> intsLessThan 5
+-- [0,1,2,3,4]
+intsLessThan :: Int -> [Int]
+intsLessThan i = if i < 1
+  then []
+  else (i - 1) : intsLessThan (i - 1)
+
+byteArrayCompareProp :: QC.Property
+byteArrayCompareProp = QC.property $ \(xs :: [Word8]) (ys :: [Word8]) ->
+  compareLengthFirst xs ys === compare (byteArrayFromList xs) (byteArrayFromList ys)
+
+byteArrayEqProp :: QC.Property
+byteArrayEqProp = QC.property $ \(xs :: [Word8]) (ys :: [Word8]) ->
+  (compareLengthFirst xs ys == EQ) === (byteArrayFromList xs == byteArrayFromList ys)
+
+compareLengthFirst :: [Word8] -> [Word8] -> Ordering
+compareLengthFirst xs ys = (compare `on` length) xs ys <> compare xs ys
+
+lawsToTest :: QCC.Laws -> TestTree
+lawsToTest (QCC.Laws name pairs) = testGroup name (map (uncurry TQC.testProperty) pairs)
+
+renameLawsToTest :: String -> QCC.Laws -> TestTree
+renameLawsToTest name (QCC.Laws _ pairs) = testGroup name (map (uncurry TQC.testProperty) pairs)
+
+testArray :: IO ()
+testArray = do
+    arr <- newArray 1 'A'
+    let unit =
+            case writeArray arr 0 'B' of
+                IO f ->
+                    case f realWorld# of
+                        (# _, _ #) -> ()
+    c1 <- readArray arr 0
+    return $! unit
+    c2 <- readArray arr 0
+    if c1 == 'A' && c2 == 'B'
+        then return ()
+        else error $ "Expected AB, got: " ++ show (c1, c2)
+
+testByteArray :: IO ()
+testByteArray = do
+    let arr1 = mkByteArray ([0xde, 0xad, 0xbe, 0xef] :: [Word8])
+        arr2 = mkByteArray ([0xde, 0xad, 0xbe, 0xef] :: [Word8])
+        arr3 = mkByteArray ([0xde, 0xad, 0xbe, 0xee] :: [Word8])
+        arr4 = mkByteArray ([0xde, 0xad, 0xbe, 0xdd] :: [Word8])
+        arr5 = mkByteArray ([0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xdd] :: [Word8])
+        arr6 = mkByteArray ([0xde, 0xad, 0x00, 0x01, 0xb0] :: [Word8])
+    when (show arr1 /= "[0xde, 0xad, 0xbe, 0xef]") $
+        fail $ "ByteArray Show incorrect: "++show arr1
+    when (show arr6 /= "[0xde, 0xad, 0x00, 0x01, 0xb0]") $
+        fail $ "ByteArray Show incorrect: "++ show arr6
+    when (compareByteArrays arr3 1 arr4 1 3 /= GT) $
+        fail $ "arr3[1,3] should be greater than arr4[1,3]"
+    when (compareByteArrays arr3 0 arr4 1 3 /= GT) $
+        fail $ "arr3[0,3] should be greater than arr4[1,3]"
+    when (compareByteArrays arr5 1 arr2 1 3 /= EQ) $
+        fail $ "arr3[1,3] should be equal to than arr4[1,3]"
+    unless (arr1 > arr3) $
+        fail $ "ByteArray Ord incorrect"
+    unless (arr1 == arr2) $
+        fail $ "ByteArray Eq incorrect"
+    unless (mappend arr1 arr4 == arr5) $
+        fail $ "ByteArray Monoid mappend incorrect"
+    unless (mappend arr1 (mappend arr3 arr4) == mappend (mappend arr1 arr3) arr4) $
+        fail $ "ByteArray Monoid mappend not associative"
+    unless (mconcat [arr1,arr2,arr3,arr4,arr5] == (arr1 <> arr2 <> arr3 <> arr4 <> arr5)) $
+        fail $ "ByteArray Monoid mconcat incorrect"
+    unless (stimes (3 :: Int) arr4 == (arr4 <> arr4 <> arr4)) $
+        fail $ "ByteArray Semigroup stimes incorrect"
+
+mkByteArray :: forall a. Prim a => [a] -> ByteArray
+mkByteArray xs = runST $ do
+    marr <- newByteArray (length xs * sizeOfType @a)
+    sequence_ $ zipWith (writeByteArray marr) [0..] xs
+    unsafeFreezeByteArray marr
+
+instance Arbitrary1 Array where
+  liftArbitrary elemGen = fmap fromList (QC.liftArbitrary elemGen)
+
+instance Arbitrary a => Arbitrary (Array a) where
+  arbitrary = fmap fromList QC.arbitrary
+
+instance Arbitrary1 SmallArray where
+  liftArbitrary elemGen = fmap smallArrayFromList (QC.liftArbitrary elemGen)
+
+instance Arbitrary a => Arbitrary (SmallArray a) where
+  arbitrary = fmap smallArrayFromList QC.arbitrary
+
+instance Arbitrary ByteArray where
+  arbitrary = do
+    xs <- QC.arbitrary :: Gen [Word8]
+    return $ runST $ do
+      a <- newByteArray (L.length xs)
+      iforM_ xs $ \ix x -> do
+        writeByteArray a ix x
+      unsafeFreezeByteArray a
+
+instance (Arbitrary a, Prim a) => Arbitrary (PrimArray a) where
+  arbitrary = do
+    xs <- QC.arbitrary :: Gen [a]
+    return $ runST $ do
+      a <- newPrimArray (L.length xs)
+      iforM_ xs $ \ix x -> do
+        writePrimArray a ix x
+      unsafeFreezePrimArray a
+
+
+
+instance (Prim a, CoArbitrary a) => CoArbitrary (PrimArray a) where
+  coarbitrary x = QC.coarbitrary (primArrayToList x)
+
+instance (Prim a, Function a) => Function (PrimArray a) where
+  function = QC.functionMap primArrayToList primArrayFromList
+
+iforM_ :: Monad m => [a] -> (Int -> a -> m b) -> m ()
+iforM_ xs0 f = go 0 xs0 where
+  go !_ [] = return ()
+  go !ix (x : xs) = f ix x >> go (ix + 1) xs
+
+newtype DefaultSetMethod = DefaultSetMethod Int16
+  deriving (Eq,Show,Arbitrary)
+
+instance Prim DefaultSetMethod where
+  sizeOfType# _ = sizeOfType# (Proxy :: Proxy Int16)
+  alignmentOfType# _ = alignmentOfType# (Proxy :: Proxy Int16)
+  indexByteArray# arr ix = DefaultSetMethod (indexByteArray# arr ix)
+  readByteArray# arr ix s0 = case readByteArray# arr ix s0 of
+    (# s1, n #) -> (# s1, DefaultSetMethod n #)
+  writeByteArray# arr ix (DefaultSetMethod n) s0 = writeByteArray# arr ix n s0
+  setByteArray# = defaultSetByteArray#
+  indexOffAddr# addr off = DefaultSetMethod (indexOffAddr# addr off)
+  readOffAddr# addr off s0 = case readOffAddr# addr off s0 of
+    (# s1, n #) -> (# s1, DefaultSetMethod n #)
+  writeOffAddr# addr off (DefaultSetMethod n) s0 = writeOffAddr# addr off n s0
+  setOffAddr# = defaultSetOffAddr#
+
+#if __GLASGOW_HASKELL__ >= 805
+newtype Derived = Derived Int16
+  deriving stock (Eq, Show)
+  deriving newtype (Arbitrary, Prim)
+  deriving Storable via (PrimStorable Derived)
+#endif
diff --git a/test/main.hs b/test/main.hs
deleted file mode 100644
--- a/test/main.hs
+++ /dev/null
@@ -1,433 +0,0 @@
-{-# LANGUAGE BangPatterns #-}
-{-# LANGUAGE CPP #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE KindSignatures #-}
-{-# LANGUAGE MagicHash #-}
-{-# LANGUAGE UnboxedTuples #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-{-# LANGUAGE StandaloneDeriving #-}
-
-{-# OPTIONS_GHC -fno-warn-orphans #-}
-
-#if __GLASGOW_HASKELL__ >= 805
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE DerivingStrategies #-}
-{-# LANGUAGE DerivingVia #-}
-{-# LANGUAGE TypeInType #-}
-#endif
-
-import Control.Monad
-import Control.Monad.ST
-import Data.Primitive
-import Data.Word
-import Data.Proxy (Proxy(..))
-import GHC.Int
-import GHC.IO
-import GHC.Exts
-import Data.Function (on)
-import Control.Applicative (Const(..))
-import PrimLaws (primLaws)
-
-import Data.Functor.Identity (Identity(..))
-import qualified Data.Monoid as Monoid
-import Data.Ord (Down(..))
-#if MIN_VERSION_base(4,9,0)
-import Data.Semigroup (stimes)
-import qualified Data.Semigroup as Semigroup
-#endif
-#if !(MIN_VERSION_base(4,11,0))
-import Data.Monoid ((<>))
-#endif
-#if __GLASGOW_HASKELL__ >= 805
-import Foreign.Storable (Storable)
-#endif
-import Data.Orphans ()
-
-import Test.Tasty (defaultMain,testGroup,TestTree)
-import Test.QuickCheck (Arbitrary,Arbitrary1,Gen,CoArbitrary,Function,(===),(==>))
-import qualified Test.Tasty.QuickCheck as TQC
-import qualified Test.QuickCheck as QC
-import qualified Test.QuickCheck.Classes.Base as QCC
-import qualified Test.QuickCheck.Classes.Base.IsList as QCCL
-import qualified Data.List as L
-
-main :: IO ()
-main = do
-  testArray
-  testByteArray
-  defaultMain $ testGroup "properties"
-    [ testGroup "Array"
-      [ lawsToTest (QCC.eqLaws (Proxy :: Proxy (Array Int)))
-      , lawsToTest (QCC.ordLaws (Proxy :: Proxy (Array Int)))
-      , lawsToTest (QCC.monoidLaws (Proxy :: Proxy (Array Int)))
-      , lawsToTest (QCC.showReadLaws (Proxy :: Proxy (Array Int)))
-      , lawsToTest (QCC.functorLaws (Proxy1 :: Proxy1 Array))
-      , lawsToTest (QCC.applicativeLaws (Proxy1 :: Proxy1 Array))
-      , lawsToTest (QCC.monadLaws (Proxy1 :: Proxy1 Array))
-      , lawsToTest (QCC.foldableLaws (Proxy1 :: Proxy1 Array))
-      , lawsToTest (QCC.traversableLaws (Proxy1 :: Proxy1 Array))
-      , lawsToTest (QCC.isListLaws (Proxy :: Proxy (Array Int)))
-      , TQC.testProperty "mapArray'" (QCCL.mapProp int16 int32 mapArray')
-      , TQC.testProperty "*>" $ \(xs :: Array Int) (ys :: Array Int) -> toList (xs *> ys) === (toList xs *> toList ys)
-      , TQC.testProperty "<*" $ \(xs :: Array Int) (ys :: Array Int) -> toList (xs <* ys) === (toList xs <* toList ys)
-      ]
-    , testGroup "SmallArray"
-      [ lawsToTest (QCC.eqLaws (Proxy :: Proxy (SmallArray Int)))
-      , lawsToTest (QCC.ordLaws (Proxy :: Proxy (SmallArray Int)))
-      , lawsToTest (QCC.monoidLaws (Proxy :: Proxy (SmallArray Int)))
-      , lawsToTest (QCC.showReadLaws (Proxy :: Proxy (Array Int)))
-      , lawsToTest (QCC.functorLaws (Proxy1 :: Proxy1 SmallArray))
-      , lawsToTest (QCC.applicativeLaws (Proxy1 :: Proxy1 SmallArray))
-      , lawsToTest (QCC.monadLaws (Proxy1 :: Proxy1 SmallArray))
-      , lawsToTest (QCC.foldableLaws (Proxy1 :: Proxy1 SmallArray))
-      , lawsToTest (QCC.traversableLaws (Proxy1 :: Proxy1 SmallArray))
-      , lawsToTest (QCC.isListLaws (Proxy :: Proxy (SmallArray Int)))
-      , TQC.testProperty "mapSmallArray'" (QCCL.mapProp int16 int32 mapSmallArray')
-      , TQC.testProperty "*>" $ \(xs :: SmallArray Int) (ys :: SmallArray Int) -> toList (xs *> ys) === (toList xs *> toList ys)
-      , TQC.testProperty "<*" $ \(xs :: SmallArray Int) (ys :: SmallArray Int) -> toList (xs <* ys) === (toList xs <* toList ys)
-      ]
-    , testGroup "ByteArray"
-      [ testGroup "Ordering"
-        [ TQC.testProperty "equality" byteArrayEqProp
-        , TQC.testProperty "compare" byteArrayCompareProp
-      , testGroup "Filling"
-        [ TQC.testProperty "Int8" (setByteArrayProp (Proxy :: Proxy Int8))
-        , TQC.testProperty "Int16" (setByteArrayProp (Proxy :: Proxy Int16))
-        , TQC.testProperty "Int32" (setByteArrayProp (Proxy :: Proxy Int32))
-        , TQC.testProperty "Int64" (setByteArrayProp (Proxy :: Proxy Int64))
-        , TQC.testProperty "Int" (setByteArrayProp (Proxy :: Proxy Int))
-        , TQC.testProperty "Word8" (setByteArrayProp (Proxy :: Proxy Word8))
-        , TQC.testProperty "Word16" (setByteArrayProp (Proxy :: Proxy Word16))
-        , TQC.testProperty "Word32" (setByteArrayProp (Proxy :: Proxy Word32))
-        , TQC.testProperty "Word64" (setByteArrayProp (Proxy :: Proxy Word64))
-        , TQC.testProperty "Word" (setByteArrayProp (Proxy :: Proxy Word))
-        ]
-      ]
-      , testGroup "Resize"
-        [ TQC.testProperty "shrink" byteArrayShrinkProp
-        , TQC.testProperty "grow" byteArrayGrowProp
-        ]
-      , lawsToTest (QCC.eqLaws (Proxy :: Proxy ByteArray))
-      , lawsToTest (QCC.ordLaws (Proxy :: Proxy ByteArray))
-      , lawsToTest (QCC.showReadLaws (Proxy :: Proxy (Array Int)))
-      , lawsToTest (QCC.isListLaws (Proxy :: Proxy ByteArray))
-      , TQC.testProperty "foldrByteArray" (QCCL.foldrProp word8 foldrByteArray)
-      ]
-    , testGroup "PrimArray"
-      [ lawsToTest (QCC.eqLaws (Proxy :: Proxy (PrimArray Word16)))
-      , lawsToTest (QCC.ordLaws (Proxy :: Proxy (PrimArray Word16)))
-      , lawsToTest (QCC.monoidLaws (Proxy :: Proxy (PrimArray Word16)))
-      , lawsToTest (QCC.isListLaws (Proxy :: Proxy (PrimArray Word16)))
-      , TQC.testProperty "foldrPrimArray" (QCCL.foldrProp int16 foldrPrimArray)
-      , TQC.testProperty "foldrPrimArray'" (QCCL.foldrProp int16 foldrPrimArray')
-      , TQC.testProperty "foldlPrimArray" (QCCL.foldlProp int16 foldlPrimArray)
-      , TQC.testProperty "foldlPrimArray'" (QCCL.foldlProp int16 foldlPrimArray')
-      , TQC.testProperty "foldlPrimArrayM'" (QCCL.foldlMProp int16 foldlPrimArrayM')
-      , TQC.testProperty "mapPrimArray" (QCCL.mapProp int16 int32 mapPrimArray)
-      , TQC.testProperty "traversePrimArray" (QCCL.traverseProp int16 int32 traversePrimArray)
-      , TQC.testProperty "traversePrimArrayP" (QCCL.traverseProp int16 int32 traversePrimArrayP)
-      , TQC.testProperty "imapPrimArray" (QCCL.imapProp int16 int32 imapPrimArray)
-      , TQC.testProperty "itraversePrimArray" (QCCL.imapMProp int16 int32 itraversePrimArray)
-      , TQC.testProperty "itraversePrimArrayP" (QCCL.imapMProp int16 int32 itraversePrimArrayP)
-      , TQC.testProperty "generatePrimArray" (QCCL.generateProp int16 generatePrimArray)
-      , TQC.testProperty "generatePrimArrayA" (QCCL.generateMProp int16 generatePrimArrayA)
-      , TQC.testProperty "generatePrimArrayP" (QCCL.generateMProp int16 generatePrimArrayP)
-      , TQC.testProperty "replicatePrimArray" (QCCL.replicateProp int16 replicatePrimArray)
-      , TQC.testProperty "replicatePrimArrayA" (QCCL.replicateMProp int16 replicatePrimArrayA)
-      , TQC.testProperty "replicatePrimArrayP" (QCCL.replicateMProp int16 replicatePrimArrayP)
-      , TQC.testProperty "filterPrimArray" (QCCL.filterProp int16 filterPrimArray)
-      , TQC.testProperty "filterPrimArrayA" (QCCL.filterMProp int16 filterPrimArrayA)
-      , TQC.testProperty "filterPrimArrayP" (QCCL.filterMProp int16 filterPrimArrayP)
-      , TQC.testProperty "mapMaybePrimArray" (QCCL.mapMaybeProp int16 int32 mapMaybePrimArray)
-      , TQC.testProperty "mapMaybePrimArrayA" (QCCL.mapMaybeMProp int16 int32 mapMaybePrimArrayA)
-      , TQC.testProperty "mapMaybePrimArrayP" (QCCL.mapMaybeMProp int16 int32 mapMaybePrimArrayP)
-      ]
-    , testGroup "DefaultSetMethod"
-      [ lawsToTest (primLaws (Proxy :: Proxy DefaultSetMethod))
-      ]
-#if __GLASGOW_HASKELL__ >= 805
-    , testGroup "PrimStorable"
-      [ lawsToTest (QCC.storableLaws (Proxy :: Proxy Derived))
-      ]
-#endif
-    , testGroup "Prim"
-      [ renameLawsToTest "Word" (primLaws (Proxy :: Proxy Word))
-      , renameLawsToTest "Word8" (primLaws (Proxy :: Proxy Word8))
-      , renameLawsToTest "Word16" (primLaws (Proxy :: Proxy Word16))
-      , renameLawsToTest "Word32" (primLaws (Proxy :: Proxy Word32))
-      , renameLawsToTest "Word64" (primLaws (Proxy :: Proxy Word64))
-      , renameLawsToTest "Int" (primLaws (Proxy :: Proxy Int))
-      , renameLawsToTest "Int8" (primLaws (Proxy :: Proxy Int8))
-      , renameLawsToTest "Int16" (primLaws (Proxy :: Proxy Int16))
-      , renameLawsToTest "Int32" (primLaws (Proxy :: Proxy Int32))
-      , renameLawsToTest "Int64" (primLaws (Proxy :: Proxy Int64))
-      , renameLawsToTest "Const" (primLaws (Proxy :: Proxy (Const Int16 Int16)))
-      , renameLawsToTest "Down" (primLaws (Proxy :: Proxy (Down Int16)))
-      , renameLawsToTest "Identity" (primLaws (Proxy :: Proxy (Identity Int16)))
-      , renameLawsToTest "Dual" (primLaws (Proxy :: Proxy (Monoid.Dual Int16)))
-      , renameLawsToTest "Sum" (primLaws (Proxy :: Proxy (Monoid.Sum Int16)))
-      , renameLawsToTest "Product" (primLaws (Proxy :: Proxy (Monoid.Product Int16)))
-#if MIN_VERSION_base(4,9,0)
-      , renameLawsToTest "First" (primLaws (Proxy :: Proxy (Semigroup.First Int16)))
-      , renameLawsToTest "Last" (primLaws (Proxy :: Proxy (Semigroup.Last Int16)))
-      , renameLawsToTest "Min" (primLaws (Proxy :: Proxy (Semigroup.Min Int16)))
-      , renameLawsToTest "Max" (primLaws (Proxy :: Proxy (Semigroup.Max Int16)))
-#endif
-      ]
-    ]
-
-deriving instance Arbitrary a => Arbitrary (Down a)
--- Const, Dual, Sum, Product: all have Arbitrary instances defined
--- in QuickCheck itself
-#if MIN_VERSION_base(4,9,0)
-deriving instance Arbitrary a => Arbitrary (Semigroup.First a)
-deriving instance Arbitrary a => Arbitrary (Semigroup.Last a)
-deriving instance Arbitrary a => Arbitrary (Semigroup.Min a)
-deriving instance Arbitrary a => Arbitrary (Semigroup.Max a)
-#endif
-
-word8 :: Proxy Word8
-word8 = Proxy
-
-int16 :: Proxy Int16
-int16 = Proxy
-
-int32 :: Proxy Int32
-int32 = Proxy
-
-
-setByteArrayProp :: forall a. (Prim a, Eq a, Arbitrary a, Show a) => Proxy a -> QC.Property
-setByteArrayProp _ = QC.property $ \(QC.NonNegative (n :: Int)) (QC.NonNegative (off :: Int)) (QC.NonNegative (len :: Int)) (x :: a) (y :: a) ->
-  (off < n && off + len <= n) ==>
-  -- We use PrimArray in this test because it makes it easier to
-  -- get the element-vs-byte distinction right.
-  let actual = runST $ do
-        m <- newPrimArray n
-        forM_ (enumFromTo 0 (n - 1)) $ \ix -> writePrimArray m ix x
-        setPrimArray m off len y
-        unsafeFreezePrimArray m
-      expected = runST $ do
-        m <- newPrimArray n
-        forM_ (enumFromTo 0 (n - 1)) $ \ix -> writePrimArray m ix x
-        forM_ (enumFromTo off (off + len - 1)) $ \ix -> writePrimArray m ix y
-        unsafeFreezePrimArray m
-   in expected === actual
-
-
--- Tests that using resizeByteArray to shrink a byte array produces
--- the same results as calling Data.List.take on the list that the
--- byte array corresponds to.
-byteArrayShrinkProp :: QC.Property
-byteArrayShrinkProp = QC.property $ \(QC.NonNegative (n :: Int)) (QC.NonNegative (m :: Int)) ->
-  let large = max n m
-      small = min n m
-      xs = intsLessThan large
-      ys = byteArrayFromList xs
-      largeBytes = large * sizeOf (undefined :: Int)
-      smallBytes = small * sizeOf (undefined :: Int)
-      expected = byteArrayFromList (L.take small xs)
-      actual = runST $ do
-        mzs0 <- newByteArray largeBytes
-        copyByteArray mzs0 0 ys 0 largeBytes
-        mzs1 <- resizeMutableByteArray mzs0 smallBytes
-        unsafeFreezeByteArray mzs1
-   in expected === actual
-
--- Tests that using resizeByteArray with copyByteArray (to fill in the
--- new empty space) to grow a byte array produces the same results as
--- calling Data.List.++ on the lists corresponding to the original
--- byte array and the appended byte array.
-byteArrayGrowProp :: QC.Property
-byteArrayGrowProp = QC.property $ \(QC.NonNegative (n :: Int)) (QC.NonNegative (m :: Int)) ->
-  let large = max n m
-      small = min n m
-      xs1 = intsLessThan small
-      xs2 = intsLessThan (large - small)
-      ys1 = byteArrayFromList xs1
-      ys2 = byteArrayFromList xs2
-      largeBytes = large * sizeOf (undefined :: Int)
-      smallBytes = small * sizeOf (undefined :: Int)
-      expected = byteArrayFromList (xs1 ++ xs2)
-      actual = runST $ do
-        mzs0 <- newByteArray smallBytes
-        copyByteArray mzs0 0 ys1 0 smallBytes
-        mzs1 <- resizeMutableByteArray mzs0 largeBytes
-        copyByteArray mzs1 smallBytes ys2 0 ((large - small) * sizeOf (undefined :: Int))
-        unsafeFreezeByteArray mzs1
-   in expected === actual
-
--- Tests that writing stable ptrs to a PrimArray, reading them back
--- out, and then dereferencing them gives correct results.
---stablePtrPrimProp :: QC.Property
---stablePtrPrimProp = QC.property $ \(xs :: [Integer]) -> unsafePerformIO $ do
---  ptrs <- mapM newStablePtr xs
---  let ptrs' = primArrayToList (primArrayFromList ptrs)
---  ys <- mapM deRefStablePtr ptrs'
---  mapM_ freeStablePtr ptrs'
---  return (xs === ys)
-
---stablePtrPrimBlockProp :: QC.Property
---stablePtrPrimBlockProp = QC.property $ \(x :: Word) (QC.NonNegative (len :: Int)) -> unsafePerformIO $ do
---  ptr <- newStablePtr x
---  let ptrs' = replicatePrimArray len ptr
---  let go ix = if ix < len
---        then do
---          n <- deRefStablePtr (indexPrimArray ptrs' ix)
---          ns <- go (ix + 1)
---          return (n : ns)
---        else return []
---  ys <- go 0
---  freeStablePtr ptr
---  return (L.replicate len x === ys)
-
-
-
--- Provide the non-negative integers up to the bound. For example:
---
--- >>> intsLessThan 5
--- [0,1,2,3,4]
-intsLessThan :: Int -> [Int]
-intsLessThan i = if i < 1
-  then []
-  else (i - 1) : intsLessThan (i - 1)
-
-byteArrayCompareProp :: QC.Property
-byteArrayCompareProp = QC.property $ \(xs :: [Word8]) (ys :: [Word8]) ->
-  compareLengthFirst xs ys === compare (byteArrayFromList xs) (byteArrayFromList ys)
-
-byteArrayEqProp :: QC.Property
-byteArrayEqProp = QC.property $ \(xs :: [Word8]) (ys :: [Word8]) ->
-  (compareLengthFirst xs ys == EQ) === (byteArrayFromList xs == byteArrayFromList ys)
-
-compareLengthFirst :: [Word8] -> [Word8] -> Ordering
-compareLengthFirst xs ys = (compare `on` length) xs ys <> compare xs ys
-
--- on GHC 7.4, Proxy is not polykinded, so we need this instead.
-data Proxy1 (f :: * -> *) = Proxy1
-
-lawsToTest :: QCC.Laws -> TestTree
-lawsToTest (QCC.Laws name pairs) = testGroup name (map (uncurry TQC.testProperty) pairs)
-
-renameLawsToTest :: String -> QCC.Laws -> TestTree
-renameLawsToTest name (QCC.Laws _ pairs) = testGroup name (map (uncurry TQC.testProperty) pairs)
-
-testArray :: IO ()
-testArray = do
-    arr <- newArray 1 'A'
-    let unit =
-            case writeArray arr 0 'B' of
-                IO f ->
-                    case f realWorld# of
-                        (# _, _ #) -> ()
-    c1 <- readArray arr 0
-    return $! unit
-    c2 <- readArray arr 0
-    if c1 == 'A' && c2 == 'B'
-        then return ()
-        else error $ "Expected AB, got: " ++ show (c1, c2)
-
-testByteArray :: IO ()
-testByteArray = do
-    let arr1 = mkByteArray ([0xde, 0xad, 0xbe, 0xef] :: [Word8])
-        arr2 = mkByteArray ([0xde, 0xad, 0xbe, 0xef] :: [Word8])
-        arr3 = mkByteArray ([0xde, 0xad, 0xbe, 0xee] :: [Word8])
-        arr4 = mkByteArray ([0xde, 0xad, 0xbe, 0xdd] :: [Word8])
-        arr5 = mkByteArray ([0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xdd] :: [Word8])
-        arr6 = mkByteArray ([0xde, 0xad, 0x00, 0x01, 0xb0] :: [Word8])
-    when (show arr1 /= "[0xde, 0xad, 0xbe, 0xef]") $
-        fail $ "ByteArray Show incorrect: "++show arr1
-    when (show arr6 /= "[0xde, 0xad, 0x00, 0x01, 0xb0]") $
-        fail $ "ByteArray Show incorrect: "++ show arr6
-    when (compareByteArrays arr3 1 arr4 1 3 /= GT) $
-        fail $ "arr3[1,3] should be greater than arr4[1,3]"
-    when (compareByteArrays arr3 0 arr4 1 3 /= GT) $
-        fail $ "arr3[0,3] should be greater than arr4[1,3]"
-    when (compareByteArrays arr5 1 arr2 1 3 /= EQ) $
-        fail $ "arr3[1,3] should be equal to than arr4[1,3]"
-    unless (arr1 > arr3) $
-        fail $ "ByteArray Ord incorrect"
-    unless (arr1 == arr2) $
-        fail $ "ByteArray Eq incorrect"
-    unless (mappend arr1 arr4 == arr5) $
-        fail $ "ByteArray Monoid mappend incorrect"
-    unless (mappend arr1 (mappend arr3 arr4) == mappend (mappend arr1 arr3) arr4) $
-        fail $ "ByteArray Monoid mappend not associative"
-    unless (mconcat [arr1,arr2,arr3,arr4,arr5] == (arr1 <> arr2 <> arr3 <> arr4 <> arr5)) $
-        fail $ "ByteArray Monoid mconcat incorrect"
-#if MIN_VERSION_base(4,9,0)
-    unless (stimes (3 :: Int) arr4 == (arr4 <> arr4 <> arr4)) $
-        fail $ "ByteArray Semigroup stimes incorrect"
-#endif
-
-mkByteArray :: Prim a => [a] -> ByteArray
-mkByteArray xs = runST $ do
-    marr <- newByteArray (length xs * sizeOf (head xs))
-    sequence_ $ zipWith (writeByteArray marr) [0..] xs
-    unsafeFreezeByteArray marr
-
-instance Arbitrary1 Array where
-  liftArbitrary elemGen = fmap fromList (QC.liftArbitrary elemGen)
-
-instance Arbitrary a => Arbitrary (Array a) where
-  arbitrary = fmap fromList QC.arbitrary
-
-instance Arbitrary1 SmallArray where
-  liftArbitrary elemGen = fmap smallArrayFromList (QC.liftArbitrary elemGen)
-
-instance Arbitrary a => Arbitrary (SmallArray a) where
-  arbitrary = fmap smallArrayFromList QC.arbitrary
-
-instance Arbitrary ByteArray where
-  arbitrary = do
-    xs <- QC.arbitrary :: Gen [Word8]
-    return $ runST $ do
-      a <- newByteArray (L.length xs)
-      iforM_ xs $ \ix x -> do
-        writeByteArray a ix x
-      unsafeFreezeByteArray a
-
-instance (Arbitrary a, Prim a) => Arbitrary (PrimArray a) where
-  arbitrary = do
-    xs <- QC.arbitrary :: Gen [a]
-    return $ runST $ do
-      a <- newPrimArray (L.length xs)
-      iforM_ xs $ \ix x -> do
-        writePrimArray a ix x
-      unsafeFreezePrimArray a
-
-
-
-instance (Prim a, CoArbitrary a) => CoArbitrary (PrimArray a) where
-  coarbitrary x = QC.coarbitrary (primArrayToList x)
-
-instance (Prim a, Function a) => Function (PrimArray a) where
-  function = QC.functionMap primArrayToList primArrayFromList
-
-iforM_ :: Monad m => [a] -> (Int -> a -> m b) -> m ()
-iforM_ xs0 f = go 0 xs0 where
-  go !_ [] = return ()
-  go !ix (x : xs) = f ix x >> go (ix + 1) xs
-
-newtype DefaultSetMethod = DefaultSetMethod Int16
-  deriving (Eq,Show,Arbitrary)
-
-instance Prim DefaultSetMethod where
-  sizeOf# _ = sizeOf# (undefined :: Int16)
-  alignment# _ = alignment# (undefined :: Int16)
-  indexByteArray# arr ix = DefaultSetMethod (indexByteArray# arr ix)
-  readByteArray# arr ix s0 = case readByteArray# arr ix s0 of
-    (# s1, n #) -> (# s1, DefaultSetMethod n #)
-  writeByteArray# arr ix (DefaultSetMethod n) s0 = writeByteArray# arr ix n s0
-  setByteArray# = defaultSetByteArray#
-  indexOffAddr# addr off = DefaultSetMethod (indexOffAddr# addr off)
-  readOffAddr# addr off s0 = case readOffAddr# addr off s0 of
-    (# s1, n #) -> (# s1, DefaultSetMethod n #)
-  writeOffAddr# addr off (DefaultSetMethod n) s0 = writeOffAddr# addr off n s0
-  setOffAddr# = defaultSetOffAddr#
-
-#if __GLASGOW_HASKELL__ >= 805
-newtype Derived = Derived Int16
-  deriving stock (Eq, Show)
-  deriving newtype (Arbitrary, Prim)
-  deriving Storable via (PrimStorable Derived)
-#endif
diff --git a/test/src/PrimLaws.hs b/test/src/PrimLaws.hs
--- a/test/src/PrimLaws.hs
+++ b/test/src/PrimLaws.hs
@@ -3,7 +3,7 @@
 {-# LANGUAGE MagicHash #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE TypeFamilies #-}
-{-# LANGUAGE UnboxedTuples #-}
+{-# LANGUAGE TypeApplications #-}
 
 {-# OPTIONS_GHC -Wall #-}
 
@@ -17,7 +17,6 @@
   ( primLaws
   ) where
 
-import Control.Applicative
 import Control.Monad.Primitive (primitive_)
 import Control.Monad.ST
 import Data.Proxy (Proxy)
@@ -26,9 +25,7 @@
 import Data.Primitive.Types
 import Data.Primitive.Ptr
 import Foreign.Marshal.Alloc
-import GHC.Exts (State#,Int#,Int(I#),(+#),(<#))
-
-import GHC.Exts (IsList(fromList,toList))
+import GHC.Exts (State#, Int#, Int(I#), (+#), (<#), IsList(fromList,toList))
 
 import System.IO.Unsafe
 import Test.QuickCheck hiding ((.&.))
@@ -54,7 +51,7 @@
 primListAddr :: forall a. (Prim a, Eq a, Arbitrary a, Show a) => Proxy a -> Property
 primListAddr _ = property $ \(as :: [a]) -> unsafePerformIO $ do
   let len = L.length as
-  ptr :: Ptr a <- mallocBytes (len * P.sizeOf (undefined :: a))
+  ptr :: Ptr a <- mallocBytes (len * P.sizeOfType @a)
   let go :: Int -> [a] -> IO ()
       go !ix xs = case xs of
         [] -> return ()
@@ -113,7 +110,7 @@
 primPutGetAddr _ = property $ \(a :: a) len -> (len > 0) ==> do
   ix <- choose (0,len - 1)
   return $ unsafePerformIO $ do
-    ptr :: Ptr a <- mallocBytes (len * P.sizeOf (undefined :: a))
+    ptr :: Ptr a <- mallocBytes (len * P.sizeOfType @a)
     writeOffPtr ptr ix a
     a' <- readOffPtr ptr ix
     free ptr
