diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,2 +1,6 @@
+## 1.1.0 (2024-04-05)
+* add struct serializer
+* add type-level bytestring utilities (generalized from binrep)
+
 ## 1.0.0 (2024-03-17)
 * initial release, finally its own package
diff --git a/bytezap.cabal b/bytezap.cabal
--- a/bytezap.cabal
+++ b/bytezap.cabal
@@ -5,7 +5,7 @@
 -- see: https://github.com/sol/hpack
 
 name:           bytezap
-version:        1.0.0
+version:        1.1.0
 synopsis:       Bytestring builder with zero intermediate allocation
 description:    Please see README.md.
 category:       Data, Serialization
@@ -34,6 +34,9 @@
       Bytezap.Poke.Derived.Endian
       Bytezap.Poke.Json
       Bytezap.Poke.KnownLen
+      Bytezap.Struct
+      Bytezap.Struct.Generic
+      Bytezap.Struct.TypeLits
       Bytezap.Write
       Bytezap.Write.Derived
       Bytezap.Write.Internal
@@ -43,6 +46,7 @@
       Raehik.Compat.Data.Word.ByteSwap
       Raehik.Compat.GHC.Exts.GHC908MemcpyPrimops
       Raehik.Compat.GHC.Exts.GHC910UnalignedAddrPrimops
+      Raehik.TypeLevelBytes
       Util.TypeNats
   other-modules:
       Paths_bytezap
@@ -60,6 +64,8 @@
       DataKinds
       MagicHash
   ghc-options: -Wall
+  c-sources:
+      cbits/aligned-static-hs-data.c
   build-depends:
       base >=4.18.0.0 && <4.20
     , bytestring >=0.11.5.3 && <0.13.0.0
diff --git a/cbits/aligned-static-hs-data.c b/cbits/aligned-static-hs-data.c
new file mode 100644
--- /dev/null
+++ b/cbits/aligned-static-hs-data.c
@@ -0,0 +1,26 @@
+// This file contains various chunks of raw static data that we can't
+// put into GHC-Haskell primitive string literals because we perform
+// /aligned/ reads with them.
+
+#include "MachDeps.h"
+#include <stdint.h>
+
+extern const char hs_bytestring_lower_hex_table[513];
+const char hs_bytestring_lower_hex_table[513]
+  __attribute__(( aligned(ALIGNMENT_WORD16) ))
+  = "000102030405060708090a0b0c0d0e0f"
+    "101112131415161718191a1b1c1d1e1f"
+    "202122232425262728292a2b2c2d2e2f"
+    "303132333435363738393a3b3c3d3e3f"
+    "404142434445464748494a4b4c4d4e4f"
+    "505152535455565758595a5b5c5d5e5f"
+    "606162636465666768696a6b6c6d6e6f"
+    "707172737475767778797a7b7c7d7e7f"
+    "808182838485868788898a8b8c8d8e8f"
+    "909192939495969798999a9b9c9d9e9f"
+    "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf"
+    "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf"
+    "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf"
+    "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf"
+    "e0e1e2e3e4e5e6e7e8e9eaebecedeeef"
+    "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff";
diff --git a/src/Bytezap/Poke.hs b/src/Bytezap/Poke.hs
--- a/src/Bytezap/Poke.hs
+++ b/src/Bytezap/Poke.hs
@@ -7,8 +7,7 @@
 import GHC.Exts
 import Raehik.Compat.GHC.Exts.GHC908MemcpyPrimops
 
-import GHC.IO
-import GHC.Word
+import GHC.Word ( Word8(W8#) )
 
 import Data.ByteString qualified as BS
 import Data.ByteString.Internal qualified as BS
@@ -19,6 +18,10 @@
 
 import GHC.ForeignPtr
 
+import Control.Monad.Primitive
+
+import Bytezap.Struct qualified as Struct
+
 type Poke# s = Addr# -> Int# -> State# s -> (# State# s, Int# #)
 
 -- | Poke newtype wrapper.
@@ -26,52 +29,69 @@
 
 -- | Sequence two 'Poke's left-to-right.
 instance Semigroup (Poke s) where
-    Poke l <> Poke r = Poke $ \addr# os0# s0 ->
-        case l addr# os0# s0 of (# s1, os1# #) -> r addr# os1# s1
+    Poke l <> Poke r = Poke $ \base# os0# s0 ->
+        case l base# os0# s0 of (# s1, os1# #) -> r base# os1# s1
 
 instance Monoid (Poke s) where
-    mempty = Poke $ \_addr# os# s -> (# s, os# #)
+    mempty = Poke $ \_base# os# s -> (# s, os# #)
 
 -- | Execute a 'Poke' at a fresh 'BS.ByteString' of the given length.
 unsafeRunPokeBS :: Int -> Poke RealWorld -> BS.ByteString
-unsafeRunPokeBS len = BS.unsafeCreate len . wrapIO
-
-wrapIO :: Poke RealWorld -> Ptr Word8 -> IO ()
-wrapIO f p = void (wrapIOUptoN f p)
-
-wrapIOUptoN :: Poke RealWorld -> Ptr Word8 -> IO Int
-wrapIOUptoN (Poke p) (Ptr addr#) = IO $ \s0 ->
-    case p addr# 0# s0 of (# s1, len# #) -> (# s1, I# len# #)
+unsafeRunPokeBS len p = BS.unsafeCreate len (void <$> unsafeRunPoke p)
 
 -- | Execute a 'Poke' at a fresh 'BS.ByteString' of the given maximum length.
 --   Does not reallocate if final size is less than estimated.
 unsafeRunPokeBSUptoN :: Int -> Poke RealWorld -> BS.ByteString
-unsafeRunPokeBSUptoN len = BS.unsafeCreateUptoN len . wrapIOUptoN
+unsafeRunPokeBSUptoN len = BS.unsafeCreateUptoN len . unsafeRunPoke
 
+-- | Execute a 'Poke' at a pointer. Returns the number of bytes written.
+--
+-- The pointer must be a mutable buffer with enough space to hold the poke.
+-- Absolutely none of this is checked. Use with caution. Sensible uses:
+--
+-- * implementing pokes to ByteStrings and the like
+-- * executing known-length (!!) pokes to known-length (!!) buffers e.g.
+--   together with allocaBytes
+unsafeRunPoke :: MonadPrim s m => Poke s -> Ptr Word8 -> m Int
+unsafeRunPoke (Poke p) (Ptr base#) = primitive $ \s0 ->
+    case p base# 0# s0 of (# s1, os# #) -> (# s1, I# os# #)
+
 -- | Poke a type via its 'Prim'' instance.
 prim :: forall a s. Prim' a => a -> Poke s
-prim a = Poke $ \addr# os# s0 ->
-    case writeWord8OffAddrAs# addr# os# a s0 of
+prim a = Poke $ \base# os# s0 ->
+    case writeWord8OffAddrAs# base# os# a s0 of
       s1 -> (# s1, os# +# sizeOf# (undefined :: a) #)
 
 -- we reimplement withForeignPtr because it's too high level.
 -- keepAlive# has the wrong type before GHC 9.10, but it doesn't matter here
 -- because copyAddrToAddrNonOverlapping# forces RealWorld.
 byteString :: BS.ByteString -> Poke RealWorld
-byteString (BS.BS (ForeignPtr p# r) (I# len#)) = Poke $ \addr# os# s0 ->
+byteString (BS.BS (ForeignPtr p# r) (I# len#)) = Poke $ \base# os# s0 ->
     keepAlive# r s0 $ \s1 ->
-        case copyAddrToAddrNonOverlapping# p# (addr# `plusAddr#` os#) len# s1 of
+        case copyAddrToAddrNonOverlapping# p# (base# `plusAddr#` os#) len# s1 of
           s2 -> (# s2, os# +# len# #)
 
 byteArray# :: ByteArray# -> Int# -> Int# -> Poke s
-byteArray# ba# baos# balen# = Poke $ \addr# os# s0 ->
-    case copyByteArrayToAddr# ba# baos# (addr# `plusAddr#` os#) balen# s0 of
+byteArray# ba# baos# balen# = Poke $ \base# os# s0 ->
+    case copyByteArrayToAddr# ba# baos# (base# `plusAddr#` os#) balen# s0 of
       s1 -> (# s1, os# +# balen# #)
 
 -- | essentially memset
 replicateByte :: Int -> Word8 -> Poke RealWorld
-replicateByte (I# len#) (W8# byte#) = Poke $ \addr# os# s0 ->
-    case setAddrRange# (addr# `plusAddr#` os#) len# byteAsInt# s0 of
+replicateByte (I# len#) (W8# byte#) = Poke $ \base# os# s0 ->
+    case setAddrRange# (base# `plusAddr#` os#) len# byteAsInt# s0 of
       s1 -> (# s1, os# +# len# #)
   where
     byteAsInt# = word2Int# (word8ToWord# byte#)
+
+-- | Use a struct poke as a regular poke.
+--
+-- To do this, we must associate a constant byte length with an existing poker.
+-- Note that pokers don't expose the type of the data they are serializing,
+-- so this is a very clumsy operation by itself. You should only be using this
+-- when you have such types in scope, and the constant length should be obtained
+-- in a sensible manner (e.g. 'Bytezap.Struct.Generic.KnownSizeOf' for generic
+-- struct pokers, or your own constant size class if you're doing funky stuff).
+fromStructPoke :: Int -> Struct.Poke s -> Poke s
+fromStructPoke (I# len#) (Struct.Poke p) = Poke $ \base# os# s ->
+    (# p base# os# s, os# +# len# #)
diff --git a/src/Bytezap/Struct.hs b/src/Bytezap/Struct.hs
new file mode 100644
--- /dev/null
+++ b/src/Bytezap/Struct.hs
@@ -0,0 +1,83 @@
+{-# LANGUAGE UnboxedTuples #-}
+
+{- | Struct serializer: serialize fields of known length.
+
+In Haskell-ish terminology, one may consider a C struct to be a product type
+where each field is of known length. Thus, fields may be accessed by a fixed
+offset from the struct start. This is convenient for efficient access, since
+those offsets may be turned into immediates on a register in a MOV instruction.
+
+Given a struct-like type, we don't need to track "bytes serialized so far" like
+the general case. We can serialize fields in any order we like, since we know
+where they will sit in the resulting bytestring.
+
+This module provides a serializer specifically for these struct-like types.
+Maybe GHC can write more efficient code for these super-simple types!
+I have no idea. So I'm trying it, and will compare performance.
+
+Notably, this serializer is much less flexible. No monoid! I don't really expect
+anyone to write manual stuff with it-- you should just use the generics.
+That reminds me, TODO could easily provide some TH too, and again compare.
+-}
+
+module Bytezap.Struct where
+
+import GHC.Exts
+import Raehik.Compat.Data.Primitive.Types
+
+import Control.Monad.Primitive ( MonadPrim, primitive )
+import Data.Word ( Word8 )
+import Data.ByteString qualified as BS
+import Data.ByteString.Internal qualified as BS
+
+import GHC.Word ( Word8(W8#) )
+import Raehik.Compat.GHC.Exts.GHC908MemcpyPrimops ( setAddrRange# )
+
+-- | A struct poker: base address (constant), byte offset, state token.
+--
+-- We could combine base address and byte offset, but we're aiming for code that
+-- stores the address in a register and uses immediates to access fields (like
+-- a good C compiler will do for its structs). So by keeping them separate, I'm
+-- hoping that we can nudge GHC towards such behaviour.
+type Poke# s = Addr# -> Int# -> State# s -> State# s
+
+-- | Poke newtype wrapper.
+newtype Poke s = Poke { unPoke :: Poke# s }
+
+-- One may write a valid 'Semigroup' instance, but it's nonsensical, so let's
+-- not.
+
+-- | Execute a 'Poke' at a fresh 'BS.ByteString' of the given length.
+unsafeRunPokeBS :: Int -> Poke RealWorld -> BS.ByteString
+unsafeRunPokeBS len p = BS.unsafeCreate len (unsafeRunPoke p)
+
+-- | Execute a 'Poke' at a pointer. Returns the number of bytes written.
+--
+-- The pointer must be a mutable buffer with enough space to hold the poke.
+-- Absolutely none of this is checked. Use with caution. Sensible uses:
+--
+-- * implementing pokes to ByteStrings and the like
+-- * executing known-length (!!) pokes to known-length (!!) buffers e.g.
+--   together with allocaBytes
+unsafeRunPoke :: MonadPrim s m => Poke s -> Ptr Word8 -> m ()
+unsafeRunPoke (Poke p) (Ptr base#) = primitive $ \s0 -> (# p base# 0# s0, () #)
+
+-- | Poke a type via its 'Prim'' instance.
+prim :: forall a s. Prim' a => a -> Poke s
+prim a = Poke $ \base# os# s0 -> writeWord8OffAddrAs# base# os# a s0
+
+-- | The empty poke. Provided here as we can't provide it via 'Monoid.empty'.
+emptyPoke :: Poke s
+emptyPoke = Poke $ \_base# _os# s0 -> s0
+
+-- | Sequence two 'Poke's. We only require the length of the left poke.
+sequencePokes :: Poke s -> Int -> Poke s -> Poke s
+sequencePokes (Poke pl) (I# ll#) (Poke pr) = Poke $ \base# os# s0 -> do
+    case pl base# os# s0 of s1 -> pr base# (os# +# ll#) s1
+
+-- | essentially memset
+replicateByte :: Int -> Word8 -> Poke RealWorld
+replicateByte (I# len#) (W8# byte#) = Poke $ \base# os# s0 ->
+    setAddrRange# (base# `plusAddr#` os#) len# byteAsInt# s0
+  where
+    byteAsInt# = word2Int# (word8ToWord# byte#)
diff --git a/src/Bytezap/Struct/Generic.hs b/src/Bytezap/Struct/Generic.hs
new file mode 100644
--- /dev/null
+++ b/src/Bytezap/Struct/Generic.hs
@@ -0,0 +1,80 @@
+{-# LANGUAGE AllowAmbiguousTypes #-}
+{-# LANGUAGE UndecidableInstances #-} -- thanks to type manipulation
+
+{- TODO
+* in some low-level Haskell code (probably bytestring or other GHC lib) I've
+  seen a pattern of binding in a certain way in order to hint a value's
+  stickiness (whether it will change) to GHC. Probably whether to put it on LHS
+  or RHS.
+  * yeah, I think see https://wiki.haskell.org/Let_vs._Where
+  * and Data.ByteString.Builder.Internal for a purposeful eta expansion
+-}
+
+{- | Generics for bytezap's struct serializer.
+
+We can't use my generic-data-functions library, because we're doing more than
+just basic monoidal composition. But I still want the same pluggable generics,
+where the user provides the class to use for base cases. So I do that. However,
+unlike g-d-f, the class info can't be provided via the user-selected monoid,
+because you don't select that. Instead, we take a simple "index" type. It's
+pretty much the same idea, surprisingly. This way, we can provide a few sensible
+"versions" like in g-f-d, while primarily designing for DIY.
+-}
+
+module Bytezap.Struct.Generic where
+
+import Bytezap.Struct
+import GHC.Generics
+import GHC.Exts
+
+-- TODO fill in kind of a (some generic thing)
+type family UnwrapGenericS1 a where
+    UnwrapGenericS1 (S1 c (Rec0 a)) = a
+
+-- | Class for holding info on class to use for poking base cases.
+--
+-- The type is just used to map to class info. It is never instantiated.
+-- By packing @KnownSizeOf@ into here, we don't need to enforce a type-level
+-- solution! Now it's up to you how you want to track your constant lengths.
+--
+-- We stay unboxed here because the internals are unboxed, just for convenience.
+-- Maybe this is bad, let me know.
+class GPokeBase idx where
+    -- | The state token of our poker.
+    type GPokeBaseSt idx
+
+    -- | The type class that provides base case poking.
+    --
+    -- The type class should provide a function that looks like 'gPokeBase'.
+    type GPokeBaseC idx a :: Constraint
+
+    gPokeBase :: GPokeBaseC idx a => a -> Poke# (GPokeBaseSt idx)
+
+    -- | The type class that provides poked length (known at compile time).
+    type KnownSizeOf' idx a :: Constraint
+
+    -- | Get the poked length of the given type. Unboxed because I felt like it.
+    --
+    -- I think we have to pass a proxy, because of forall limitations on
+    -- instance signatures. This would be much better with explicit type
+    -- variables (GHC 9.10 or 9.12).
+    sizeOf' :: forall a. KnownSizeOf' idx a => Proxy# a -> Int#
+
+class GPoke idx f where gPoke :: f p -> Poke# (GPokeBaseSt idx)
+
+instance GPoke idx f => GPoke idx (D1 c f) where gPoke = gPoke @idx . unM1
+instance GPoke idx f => GPoke idx (C1 c f) where gPoke = gPoke @idx . unM1
+
+instance (GPoke idx l, GPoke idx r, GPokeBase idx, KnownSizeOf' idx (UnwrapGenericS1 l))
+  => GPoke idx (l :*: r) where
+    -- TODO moved os and s0 to RHS because base is const and those aren't?
+    -- will this change anything?? idk!!!!
+    gPoke (l :*: r) base# = \os# s0 ->
+        case gPoke @idx l base# os# s0 of
+          s1 -> gPoke @idx r base# (os# +# sizeOf' @idx @(UnwrapGenericS1 l) proxy#) s1
+
+instance (GPokeBase idx, GPokeBaseC idx a) => GPoke idx (S1 c (Rec0 a)) where
+    gPoke = gPokeBase @idx . unK1 . unM1
+
+-- | Wow, look! Nothing!
+instance GPoke idx U1 where gPoke U1 _base# = \_os# s0 -> s0
diff --git a/src/Bytezap/Struct/TypeLits.hs b/src/Bytezap/Struct/TypeLits.hs
new file mode 100644
--- /dev/null
+++ b/src/Bytezap/Struct/TypeLits.hs
@@ -0,0 +1,102 @@
+{- | Efficient type-level bytestring serialization.
+
+@['Natural']@s have a convenient syntax, and we can use them as a type-level
+bytestring by asserting that each 'Natural' is <=255 when reifying. This module
+provides type classes which give you a serializer for a given @['Natural']@.
+
+We maximize efficiency by grouping bytes into machine words. We have to be
+pretty verbose to achieve this. Each type class attempts to group bytes into its
+machine word type, and if it can't (i.e. not enough bytes remain), it hands off
+to the next type class which handles the next smaller machine word.
+
+I did a quick Core check and found that GHC seems to successfully generate
+minimal code for this e.g. for an 8-byte magic, GHC will do one
+@writeWord64OffAddr#@ of a constant. Great!
+-}
+
+{-# LANGUAGE AllowAmbiguousTypes, UndecidableInstances #-}
+
+module Bytezap.Struct.TypeLits where
+
+import Raehik.TypeLevelBytes
+import Bytezap.Struct ( Poke, sequencePokes, emptyPoke, prim )
+import Numeric.Natural ( Natural )
+
+-- | Serialize a type-level bytestring, largest grouping 'Word64'.
+class ReifyBytesW64 (ns :: [Natural]) where reifyBytesW64 :: Poke n
+
+-- | Enough bytes to make a 'Word64'.
+instance {-# OVERLAPPING #-}
+  ( ReifyW8 n1
+  , ReifyW8 n2
+  , ReifyW8 n3
+  , ReifyW8 n4
+  , ReifyW8 n5
+  , ReifyW8 n6
+  , ReifyW8 n7
+  , ReifyW8 n8
+  , ReifyBytesW64 ns
+  ) => ReifyBytesW64 (n1 ': n2 ': n3 ': n4 ': n5 ': n6 ': n7 ': n8 ': ns) where
+    {-# INLINE reifyBytesW64 #-}
+    reifyBytesW64 = sequencePokes
+        (prim (reifyW64 @n1 @n2 @n3 @n4 @n5 @n6 @n7 @n8)) 8 (reifyBytesW64 @ns)
+
+-- | Try to group 'Word32's next.
+instance ReifyBytesW32 ns => ReifyBytesW64 ns where
+    {-# INLINE reifyBytesW64 #-}
+    reifyBytesW64 = reifyBytesW32 @ns
+
+-- | Serialize a type-level bytestring, largest grouping 'Word32'.
+class ReifyBytesW32 (ns :: [Natural]) where reifyBytesW32 :: Poke s
+
+-- | Enough bytes to make a 'Word32'.
+instance {-# OVERLAPPING #-}
+  ( ReifyW8 n1
+  , ReifyW8 n2
+  , ReifyW8 n3
+  , ReifyW8 n4
+  , ReifyBytesW32 ns
+  ) => ReifyBytesW32 (n1 ': n2 ': n3 ': n4 ': ns) where
+    {-# INLINE reifyBytesW32 #-}
+    reifyBytesW32 = sequencePokes
+        (prim (reifyW32 @n1 @n2 @n3 @n4)) 4 (reifyBytesW32 @ns)
+
+-- | Try to group 'Word16's next.
+instance ReifyBytesW16 ns => ReifyBytesW32 ns where
+    {-# INLINE reifyBytesW32 #-}
+    reifyBytesW32 = reifyBytesW16 @ns
+
+-- | Serialize a type-level bytestring, largest grouping 'Word32'.
+class ReifyBytesW16 (ns :: [Natural]) where reifyBytesW16 :: Poke s
+
+-- | Enough bytes to make a 'Word16'.
+instance
+  ( ReifyW8 n1
+  , ReifyW8 n2
+  , ReifyBytesW16 ns
+  ) => ReifyBytesW16 (n1 ': n2 ': ns) where
+    {-# INLINE reifyBytesW16 #-}
+    reifyBytesW16 = sequencePokes
+        (prim (reifyW16 @n1 @n2)) 2 (reifyBytesW16 @ns)
+
+-- | Reify byte-by-byte next.
+instance ReifyBytesW8 ns => ReifyBytesW16 ns where
+    {-# INLINE reifyBytesW16 #-}
+    reifyBytesW16 = reifyBytesW8 @ns
+
+-- | Serialize a type-level bytestring, byte-by-byte.
+class ReifyBytesW8 (ns :: [Natural]) where reifyBytesW8 :: Poke s
+
+-- | Reify the next byte.
+instance
+  ( ReifyW8 n1
+  , ReifyBytesW8 ns
+  ) => ReifyBytesW8 (n1 ': ns) where
+    {-# INLINE reifyBytesW8 #-}
+    reifyBytesW8 = sequencePokes
+        (prim (reifyW8 @n1)) 1 (reifyBytesW8 @ns)
+
+-- | End of the line.
+instance ReifyBytesW8 '[] where
+    {-# INLINE reifyBytesW8 #-}
+    reifyBytesW8 = emptyPoke
diff --git a/src/Raehik/TypeLevelBytes.hs b/src/Raehik/TypeLevelBytes.hs
new file mode 100644
--- /dev/null
+++ b/src/Raehik/TypeLevelBytes.hs
@@ -0,0 +1,319 @@
+-- | Utilities for using @Natural@s as type-level bytes.
+
+{-# LANGUAGE AllowAmbiguousTypes #-}
+
+module Raehik.TypeLevelBytes where
+
+import Numeric.Natural ( Natural )
+import Data.Word ( Word8, Word16, Word32, Word64 )
+import Data.Bits ( unsafeShiftL, (.|.) )
+
+{-# INLINE reifyW64 #-}
+-- | Reify 8 type-level bytes to a 'Word64'.
+reifyW64
+    :: forall n1 n2 n3 n4 n5 n6 n7 n8
+    .  ( ReifyW8 n1
+       , ReifyW8 n2
+       , ReifyW8 n3
+       , ReifyW8 n4
+       , ReifyW8 n5
+       , ReifyW8 n6
+       , ReifyW8 n7
+       , ReifyW8 n8
+    ) => Word64
+reifyW64 =            fI (reifyW8 @n1)
+    .|. unsafeShiftL (fI (reifyW8 @n2))  8
+    .|. unsafeShiftL (fI (reifyW8 @n3)) 16
+    .|. unsafeShiftL (fI (reifyW8 @n4)) 24
+    .|. unsafeShiftL (fI (reifyW8 @n5)) 32
+    .|. unsafeShiftL (fI (reifyW8 @n6)) 40
+    .|. unsafeShiftL (fI (reifyW8 @n7)) 48
+    .|. unsafeShiftL (fI (reifyW8 @n8)) 56
+  where fI = fromIntegral
+
+{-# INLINE reifyW32 #-}
+-- | Reify 4 type-level bytes to a 'Word32'.
+reifyW32
+    :: forall n1 n2 n3 n4
+    .  ( ReifyW8 n1
+       , ReifyW8 n2
+       , ReifyW8 n3
+       , ReifyW8 n4
+    ) => Word32
+reifyW32 =            fI (reifyW8 @n1)
+    .|. unsafeShiftL (fI (reifyW8 @n2))  8
+    .|. unsafeShiftL (fI (reifyW8 @n3)) 16
+    .|. unsafeShiftL (fI (reifyW8 @n4)) 24
+  where fI = fromIntegral
+
+{-# INLINE reifyW16 #-}
+-- | Reify 2 type-level bytes to a 'Word16'.
+reifyW16
+    :: forall n1 n2
+    .  ( ReifyW8 n1
+       , ReifyW8 n2
+    ) => Word16
+reifyW16 =            fI (reifyW8 @n1)
+    .|. unsafeShiftL (fI (reifyW8 @n2))  8
+  where fI = fromIntegral
+
+-- | Reify a type-level byte (stored in a type-level 'Natural') to its 'Word8'.
+--
+-- Attempting to reify a 'Natural' larger than 255 results in a type error.
+class ReifyW8 (n :: Natural) where reifyW8 :: Word8
+instance ReifyW8 0x00 where reifyW8 = 0x00
+instance ReifyW8 0x01 where reifyW8 = 0x01
+instance ReifyW8 0x02 where reifyW8 = 0x02
+instance ReifyW8 0x03 where reifyW8 = 0x03
+instance ReifyW8 0x04 where reifyW8 = 0x04
+instance ReifyW8 0x05 where reifyW8 = 0x05
+instance ReifyW8 0x06 where reifyW8 = 0x06
+instance ReifyW8 0x07 where reifyW8 = 0x07
+instance ReifyW8 0x08 where reifyW8 = 0x08
+instance ReifyW8 0x09 where reifyW8 = 0x09
+instance ReifyW8 0x0a where reifyW8 = 0x0a
+instance ReifyW8 0x0b where reifyW8 = 0x0b
+instance ReifyW8 0x0c where reifyW8 = 0x0c
+instance ReifyW8 0x0d where reifyW8 = 0x0d
+instance ReifyW8 0x0e where reifyW8 = 0x0e
+instance ReifyW8 0x0f where reifyW8 = 0x0f
+instance ReifyW8 0x10 where reifyW8 = 0x10
+instance ReifyW8 0x11 where reifyW8 = 0x11
+instance ReifyW8 0x12 where reifyW8 = 0x12
+instance ReifyW8 0x13 where reifyW8 = 0x13
+instance ReifyW8 0x14 where reifyW8 = 0x14
+instance ReifyW8 0x15 where reifyW8 = 0x15
+instance ReifyW8 0x16 where reifyW8 = 0x16
+instance ReifyW8 0x17 where reifyW8 = 0x17
+instance ReifyW8 0x18 where reifyW8 = 0x18
+instance ReifyW8 0x19 where reifyW8 = 0x19
+instance ReifyW8 0x1a where reifyW8 = 0x1a
+instance ReifyW8 0x1b where reifyW8 = 0x1b
+instance ReifyW8 0x1c where reifyW8 = 0x1c
+instance ReifyW8 0x1d where reifyW8 = 0x1d
+instance ReifyW8 0x1e where reifyW8 = 0x1e
+instance ReifyW8 0x1f where reifyW8 = 0x1f
+instance ReifyW8 0x20 where reifyW8 = 0x20
+instance ReifyW8 0x21 where reifyW8 = 0x21
+instance ReifyW8 0x22 where reifyW8 = 0x22
+instance ReifyW8 0x23 where reifyW8 = 0x23
+instance ReifyW8 0x24 where reifyW8 = 0x24
+instance ReifyW8 0x25 where reifyW8 = 0x25
+instance ReifyW8 0x26 where reifyW8 = 0x26
+instance ReifyW8 0x27 where reifyW8 = 0x27
+instance ReifyW8 0x28 where reifyW8 = 0x28
+instance ReifyW8 0x29 where reifyW8 = 0x29
+instance ReifyW8 0x2a where reifyW8 = 0x2a
+instance ReifyW8 0x2b where reifyW8 = 0x2b
+instance ReifyW8 0x2c where reifyW8 = 0x2c
+instance ReifyW8 0x2d where reifyW8 = 0x2d
+instance ReifyW8 0x2e where reifyW8 = 0x2e
+instance ReifyW8 0x2f where reifyW8 = 0x2f
+instance ReifyW8 0x30 where reifyW8 = 0x30
+instance ReifyW8 0x31 where reifyW8 = 0x31
+instance ReifyW8 0x32 where reifyW8 = 0x32
+instance ReifyW8 0x33 where reifyW8 = 0x33
+instance ReifyW8 0x34 where reifyW8 = 0x34
+instance ReifyW8 0x35 where reifyW8 = 0x35
+instance ReifyW8 0x36 where reifyW8 = 0x36
+instance ReifyW8 0x37 where reifyW8 = 0x37
+instance ReifyW8 0x38 where reifyW8 = 0x38
+instance ReifyW8 0x39 where reifyW8 = 0x39
+instance ReifyW8 0x3a where reifyW8 = 0x3a
+instance ReifyW8 0x3b where reifyW8 = 0x3b
+instance ReifyW8 0x3c where reifyW8 = 0x3c
+instance ReifyW8 0x3d where reifyW8 = 0x3d
+instance ReifyW8 0x3e where reifyW8 = 0x3e
+instance ReifyW8 0x3f where reifyW8 = 0x3f
+instance ReifyW8 0x40 where reifyW8 = 0x40
+instance ReifyW8 0x41 where reifyW8 = 0x41
+instance ReifyW8 0x42 where reifyW8 = 0x42
+instance ReifyW8 0x43 where reifyW8 = 0x43
+instance ReifyW8 0x44 where reifyW8 = 0x44
+instance ReifyW8 0x45 where reifyW8 = 0x45
+instance ReifyW8 0x46 where reifyW8 = 0x46
+instance ReifyW8 0x47 where reifyW8 = 0x47
+instance ReifyW8 0x48 where reifyW8 = 0x48
+instance ReifyW8 0x49 where reifyW8 = 0x49
+instance ReifyW8 0x4a where reifyW8 = 0x4a
+instance ReifyW8 0x4b where reifyW8 = 0x4b
+instance ReifyW8 0x4c where reifyW8 = 0x4c
+instance ReifyW8 0x4d where reifyW8 = 0x4d
+instance ReifyW8 0x4e where reifyW8 = 0x4e
+instance ReifyW8 0x4f where reifyW8 = 0x4f
+instance ReifyW8 0x50 where reifyW8 = 0x50
+instance ReifyW8 0x51 where reifyW8 = 0x51
+instance ReifyW8 0x52 where reifyW8 = 0x52
+instance ReifyW8 0x53 where reifyW8 = 0x53
+instance ReifyW8 0x54 where reifyW8 = 0x54
+instance ReifyW8 0x55 where reifyW8 = 0x55
+instance ReifyW8 0x56 where reifyW8 = 0x56
+instance ReifyW8 0x57 where reifyW8 = 0x57
+instance ReifyW8 0x58 where reifyW8 = 0x58
+instance ReifyW8 0x59 where reifyW8 = 0x59
+instance ReifyW8 0x5a where reifyW8 = 0x5a
+instance ReifyW8 0x5b where reifyW8 = 0x5b
+instance ReifyW8 0x5c where reifyW8 = 0x5c
+instance ReifyW8 0x5d where reifyW8 = 0x5d
+instance ReifyW8 0x5e where reifyW8 = 0x5e
+instance ReifyW8 0x5f where reifyW8 = 0x5f
+instance ReifyW8 0x60 where reifyW8 = 0x60
+instance ReifyW8 0x61 where reifyW8 = 0x61
+instance ReifyW8 0x62 where reifyW8 = 0x62
+instance ReifyW8 0x63 where reifyW8 = 0x63
+instance ReifyW8 0x64 where reifyW8 = 0x64
+instance ReifyW8 0x65 where reifyW8 = 0x65
+instance ReifyW8 0x66 where reifyW8 = 0x66
+instance ReifyW8 0x67 where reifyW8 = 0x67
+instance ReifyW8 0x68 where reifyW8 = 0x68
+instance ReifyW8 0x69 where reifyW8 = 0x69
+instance ReifyW8 0x6a where reifyW8 = 0x6a
+instance ReifyW8 0x6b where reifyW8 = 0x6b
+instance ReifyW8 0x6c where reifyW8 = 0x6c
+instance ReifyW8 0x6d where reifyW8 = 0x6d
+instance ReifyW8 0x6e where reifyW8 = 0x6e
+instance ReifyW8 0x6f where reifyW8 = 0x6f
+instance ReifyW8 0x70 where reifyW8 = 0x70
+instance ReifyW8 0x71 where reifyW8 = 0x71
+instance ReifyW8 0x72 where reifyW8 = 0x72
+instance ReifyW8 0x73 where reifyW8 = 0x73
+instance ReifyW8 0x74 where reifyW8 = 0x74
+instance ReifyW8 0x75 where reifyW8 = 0x75
+instance ReifyW8 0x76 where reifyW8 = 0x76
+instance ReifyW8 0x77 where reifyW8 = 0x77
+instance ReifyW8 0x78 where reifyW8 = 0x78
+instance ReifyW8 0x79 where reifyW8 = 0x79
+instance ReifyW8 0x7a where reifyW8 = 0x7a
+instance ReifyW8 0x7b where reifyW8 = 0x7b
+instance ReifyW8 0x7c where reifyW8 = 0x7c
+instance ReifyW8 0x7d where reifyW8 = 0x7d
+instance ReifyW8 0x7e where reifyW8 = 0x7e
+instance ReifyW8 0x7f where reifyW8 = 0x7f
+instance ReifyW8 0x80 where reifyW8 = 0x80
+instance ReifyW8 0x81 where reifyW8 = 0x81
+instance ReifyW8 0x82 where reifyW8 = 0x82
+instance ReifyW8 0x83 where reifyW8 = 0x83
+instance ReifyW8 0x84 where reifyW8 = 0x84
+instance ReifyW8 0x85 where reifyW8 = 0x85
+instance ReifyW8 0x86 where reifyW8 = 0x86
+instance ReifyW8 0x87 where reifyW8 = 0x87
+instance ReifyW8 0x88 where reifyW8 = 0x88
+instance ReifyW8 0x89 where reifyW8 = 0x89
+instance ReifyW8 0x8a where reifyW8 = 0x8a
+instance ReifyW8 0x8b where reifyW8 = 0x8b
+instance ReifyW8 0x8c where reifyW8 = 0x8c
+instance ReifyW8 0x8d where reifyW8 = 0x8d
+instance ReifyW8 0x8e where reifyW8 = 0x8e
+instance ReifyW8 0x8f where reifyW8 = 0x8f
+instance ReifyW8 0x90 where reifyW8 = 0x90
+instance ReifyW8 0x91 where reifyW8 = 0x91
+instance ReifyW8 0x92 where reifyW8 = 0x92
+instance ReifyW8 0x93 where reifyW8 = 0x93
+instance ReifyW8 0x94 where reifyW8 = 0x94
+instance ReifyW8 0x95 where reifyW8 = 0x95
+instance ReifyW8 0x96 where reifyW8 = 0x96
+instance ReifyW8 0x97 where reifyW8 = 0x97
+instance ReifyW8 0x98 where reifyW8 = 0x98
+instance ReifyW8 0x99 where reifyW8 = 0x99
+instance ReifyW8 0x9a where reifyW8 = 0x9a
+instance ReifyW8 0x9b where reifyW8 = 0x9b
+instance ReifyW8 0x9c where reifyW8 = 0x9c
+instance ReifyW8 0x9d where reifyW8 = 0x9d
+instance ReifyW8 0x9e where reifyW8 = 0x9e
+instance ReifyW8 0x9f where reifyW8 = 0x9f
+instance ReifyW8 0xa0 where reifyW8 = 0xa0
+instance ReifyW8 0xa1 where reifyW8 = 0xa1
+instance ReifyW8 0xa2 where reifyW8 = 0xa2
+instance ReifyW8 0xa3 where reifyW8 = 0xa3
+instance ReifyW8 0xa4 where reifyW8 = 0xa4
+instance ReifyW8 0xa5 where reifyW8 = 0xa5
+instance ReifyW8 0xa6 where reifyW8 = 0xa6
+instance ReifyW8 0xa7 where reifyW8 = 0xa7
+instance ReifyW8 0xa8 where reifyW8 = 0xa8
+instance ReifyW8 0xa9 where reifyW8 = 0xa9
+instance ReifyW8 0xaa where reifyW8 = 0xaa
+instance ReifyW8 0xab where reifyW8 = 0xab
+instance ReifyW8 0xac where reifyW8 = 0xac
+instance ReifyW8 0xad where reifyW8 = 0xad
+instance ReifyW8 0xae where reifyW8 = 0xae
+instance ReifyW8 0xaf where reifyW8 = 0xaf
+instance ReifyW8 0xb0 where reifyW8 = 0xb0
+instance ReifyW8 0xb1 where reifyW8 = 0xb1
+instance ReifyW8 0xb2 where reifyW8 = 0xb2
+instance ReifyW8 0xb3 where reifyW8 = 0xb3
+instance ReifyW8 0xb4 where reifyW8 = 0xb4
+instance ReifyW8 0xb5 where reifyW8 = 0xb5
+instance ReifyW8 0xb6 where reifyW8 = 0xb6
+instance ReifyW8 0xb7 where reifyW8 = 0xb7
+instance ReifyW8 0xb8 where reifyW8 = 0xb8
+instance ReifyW8 0xb9 where reifyW8 = 0xb9
+instance ReifyW8 0xba where reifyW8 = 0xba
+instance ReifyW8 0xbb where reifyW8 = 0xbb
+instance ReifyW8 0xbc where reifyW8 = 0xbc
+instance ReifyW8 0xbd where reifyW8 = 0xbd
+instance ReifyW8 0xbe where reifyW8 = 0xbe
+instance ReifyW8 0xbf where reifyW8 = 0xbf
+instance ReifyW8 0xc0 where reifyW8 = 0xc0
+instance ReifyW8 0xc1 where reifyW8 = 0xc1
+instance ReifyW8 0xc2 where reifyW8 = 0xc2
+instance ReifyW8 0xc3 where reifyW8 = 0xc3
+instance ReifyW8 0xc4 where reifyW8 = 0xc4
+instance ReifyW8 0xc5 where reifyW8 = 0xc5
+instance ReifyW8 0xc6 where reifyW8 = 0xc6
+instance ReifyW8 0xc7 where reifyW8 = 0xc7
+instance ReifyW8 0xc8 where reifyW8 = 0xc8
+instance ReifyW8 0xc9 where reifyW8 = 0xc9
+instance ReifyW8 0xca where reifyW8 = 0xca
+instance ReifyW8 0xcb where reifyW8 = 0xcb
+instance ReifyW8 0xcc where reifyW8 = 0xcc
+instance ReifyW8 0xcd where reifyW8 = 0xcd
+instance ReifyW8 0xce where reifyW8 = 0xce
+instance ReifyW8 0xcf where reifyW8 = 0xcf
+instance ReifyW8 0xd0 where reifyW8 = 0xd0
+instance ReifyW8 0xd1 where reifyW8 = 0xd1
+instance ReifyW8 0xd2 where reifyW8 = 0xd2
+instance ReifyW8 0xd3 where reifyW8 = 0xd3
+instance ReifyW8 0xd4 where reifyW8 = 0xd4
+instance ReifyW8 0xd5 where reifyW8 = 0xd5
+instance ReifyW8 0xd6 where reifyW8 = 0xd6
+instance ReifyW8 0xd7 where reifyW8 = 0xd7
+instance ReifyW8 0xd8 where reifyW8 = 0xd8
+instance ReifyW8 0xd9 where reifyW8 = 0xd9
+instance ReifyW8 0xda where reifyW8 = 0xda
+instance ReifyW8 0xdb where reifyW8 = 0xdb
+instance ReifyW8 0xdc where reifyW8 = 0xdc
+instance ReifyW8 0xdd where reifyW8 = 0xdd
+instance ReifyW8 0xde where reifyW8 = 0xde
+instance ReifyW8 0xdf where reifyW8 = 0xdf
+instance ReifyW8 0xe0 where reifyW8 = 0xe0
+instance ReifyW8 0xe1 where reifyW8 = 0xe1
+instance ReifyW8 0xe2 where reifyW8 = 0xe2
+instance ReifyW8 0xe3 where reifyW8 = 0xe3
+instance ReifyW8 0xe4 where reifyW8 = 0xe4
+instance ReifyW8 0xe5 where reifyW8 = 0xe5
+instance ReifyW8 0xe6 where reifyW8 = 0xe6
+instance ReifyW8 0xe7 where reifyW8 = 0xe7
+instance ReifyW8 0xe8 where reifyW8 = 0xe8
+instance ReifyW8 0xe9 where reifyW8 = 0xe9
+instance ReifyW8 0xea where reifyW8 = 0xea
+instance ReifyW8 0xeb where reifyW8 = 0xeb
+instance ReifyW8 0xec where reifyW8 = 0xec
+instance ReifyW8 0xed where reifyW8 = 0xed
+instance ReifyW8 0xee where reifyW8 = 0xee
+instance ReifyW8 0xef where reifyW8 = 0xef
+instance ReifyW8 0xf0 where reifyW8 = 0xf0
+instance ReifyW8 0xf1 where reifyW8 = 0xf1
+instance ReifyW8 0xf2 where reifyW8 = 0xf2
+instance ReifyW8 0xf3 where reifyW8 = 0xf3
+instance ReifyW8 0xf4 where reifyW8 = 0xf4
+instance ReifyW8 0xf5 where reifyW8 = 0xf5
+instance ReifyW8 0xf6 where reifyW8 = 0xf6
+instance ReifyW8 0xf7 where reifyW8 = 0xf7
+instance ReifyW8 0xf8 where reifyW8 = 0xf8
+instance ReifyW8 0xf9 where reifyW8 = 0xf9
+instance ReifyW8 0xfa where reifyW8 = 0xfa
+instance ReifyW8 0xfb where reifyW8 = 0xfb
+instance ReifyW8 0xfc where reifyW8 = 0xfc
+instance ReifyW8 0xfd where reifyW8 = 0xfd
+instance ReifyW8 0xfe where reifyW8 = 0xfe
+instance ReifyW8 0xff where reifyW8 = 0xff
