【发布时间】:2023-09-22 10:51:01
【问题描述】:
我有一个如下形式的数据结构(V是Data.Storable.Vector):
data Elems = I {-# UNPACK #-} !GHC.Int.Int32
| S {-# UNPACK #-} !GHC.Int.Int32 {-# UNPACK #-} !(Ptr CChar)
| T {-# UNPACK #-} !(V.Vector Elems)
deriving (Show)
我首先为非递归形式编写了一个自定义的可存储定义(即,没有T 构造函数)。然后,我尝试使用来自Vector 的ForeignPtr 和length 信息为T 添加自定义peek 和poke 定义(代码如下)。 GHC 编译器抱怨没有为ForeignPtr Elems 类型定义Storable 实例。我的问题是是否可以在 Storable 定义中将 ptr 存储到 Vector,而不必为 ForeignPtr 编写 Storable 实例定义。
从Haddocs 文档来看,ForeignPtr 似乎只是一个分配有终结器的 Ptr:
ForeignPtrs 和 vanilla memory 的本质区别 Ptr a 类型的引用是前者可能与 终结者。
我不想通过使用Ptr 而不是ForeignPtr 来解决此问题,因为存在最终确定的问题。所以,我更喜欢存储 ForeignPtr 的位置(通过Ptr (ForeignPtr a)),以便 GHC 垃圾收集器知道对它的引用。但是,这种方法将迫使我定义一个Storable instance(因为约束(Storable a) => Ptr a 是有道理的)。
有没有办法在 Storable 中将 ptr 存储和检索到 Vector 中,而无需为 ForeignPtr 定义 Storable 实例?如果没有,那么编写 ForeignPtr 的 Storable 定义是必须的。在那种情况下,它会是什么样子?我的猜测是它只会将 Ptr 存储到 ForeignPtr。
完整代码如下:
{-# LANGUAGE MagicHash #-}
import qualified Data.Vector.Storable as V
import Foreign
import Foreign.C.Types (CChar)
import Foreign.Marshal.Array (lengthArray0)
import GHC.Int
data Elems = I {-# UNPACK #-} !GHC.Int.Int32
| S {-# UNPACK #-} !GHC.Int.Int32 {-# UNPACK #-} !(Ptr CChar)
| T {-# UNPACK #-} !(V.Vector Elems)
deriving (Show)
instance Storable Elems where
sizeOf _ = sizeOf (undefined :: Word8) + sizeOf (undefined :: Int32) + sizeOf (undefined :: Ptr CChar)
alignment _ = 4
{-# INLINE peek #-}
peek p = do
let p1 = (castPtr p::Ptr Word8) `plusPtr` 1 -- get pointer to start of the element. First byte is type of element
t <- peek (castPtr p::Ptr Word8)
case t of
1 -> do
x <- peek (castPtr p1 :: Ptr GHC.Int.Int32)
return (I x)
2 -> do
x <- peek (castPtr p1 :: Ptr GHC.Int.Int32)
y <- peek (castPtr (p1 `plusPtr` 4) :: Ptr (Ptr CChar)) -- increment pointer by 4 bytes first
return (S x y)
_ -> do
x <- peek (castPtr p1 :: Ptr Int)
y <- peek (castPtr (p1 `plusPtr` 8) :: Ptr (ForeignPtr Elems))
return (T (V.unsafeFromForeignPtr y 0 x)) -- return vector
{-# INLINE poke #-}
poke p x = case x of
I a -> do
poke (castPtr p :: Ptr Word8) 1
poke (castPtr p1) a
S a b -> do
poke (castPtr p :: Ptr Word8) 2
poke (castPtr p1) a
poke (castPtr (p1 `plusPtr` 4)) b -- increment pointer by 4 bytes first
T x -> do
poke (castPtr p :: Ptr Word8) 3
let (fp,_,n) = V.unsafeToForeignPtr x
poke (castPtr p1) n
poke (castPtr (p1 `plusPtr` 8)) fp
where p1 = (castPtr p :: Ptr Word8) `plusPtr` 1 -- get pointer to start of the element. First byte is type of element
【问题讨论】:
-
您可能需要考虑使用
-funbox-strict-fields,而不是在每个字段上都添加{-# UNPACK #-}pragma。