内衬为什么会在这种结构上cho住?

时间:2019-05-29 02:42:01

标签: haskell recursion clash

我正在尝试在仿真器和CPU的CLaSH实现之间共享尽可能多的代码。作为此过程的一部分,我正在按照以下方式写指令获取和解码:

fetchInstr :: (Monad m) => m Word8 -> m Instr

使用monad在模拟器中运行很简单,monad的状态为程序计数器并直接访问内存。对于硬件版本,我制作了一个固定大小的缓冲区(因为指令字节的长度是有界的),并且在每个周期中,如果缓冲区中没有足够的数据,则将获取操作短路。

data Failure
    = Underrun
    | Overrun
    deriving Show

data Buffer n dat = Buffer
    { bufferContents :: Vec n dat
    , bufferNext :: Index (1 + n)
    }
    deriving (Show, Generic, Undefined)

instance (KnownNat n, Default dat) => Default (Buffer n dat) where
    def = Buffer (pure def) 0

remember :: (KnownNat n) => Buffer n dat -> dat -> Buffer n dat
remember Buffer{..} x = Buffer
    { bufferContents = replace bufferNext x bufferContents
    , bufferNext = bufferNext + 1
    }

newtype FetchM n dat m a = FetchM{ unFetchM :: ReaderT (Buffer n dat) (StateT (Index (1 + n)) (ExceptT Failure m)) a }
    deriving newtype (Functor, Applicative, Monad)

runFetchM :: (Monad m, KnownNat n) => Buffer n dat -> FetchM n dat m a -> m (Either Failure a)
runFetchM buf act = runExceptT $ evalStateT (runReaderT (unFetchM act) buf) 0

fetch :: (Monad m, KnownNat n) => FetchM n dat m dat
fetch = do
    Buffer{..} <- FetchM ask
    idx <- FetchM get
    when (idx == maxBound) overrun
    when (idx >= bufferNext) underrun
    FetchM $ modify (+ 1)
    return $ bufferContents !! idx
  where
    overrun = FetchM . lift . lift . throwE $ Overrun
    underrun = FetchM . lift . lift . throwE $ Underrun

这个想法是通过在获取指令期间以CPU的状态存储Buffer n dat并在内存不足的情况下remember从内存中输入值来使用此方法:

case cpuState of
 Fetching buf -> do
            buf' <- remember buf <$> do
                modify $ \s -> s{ pc = succ pc }
                return cpuInMem
            instr_ <- runFetchM buf' $ fetchInstr fetch
            instr <- case instr_ of
                Left Underrun -> goto (Fetching buf') >> abort
                Left Overrun -> errorX "Overrun"
                Right instr -> return instr
            goto $ Fetching def
            exec instr

这在CLaSH模拟器中工作正常。

问题是,如果我开始以这种方式使用它,则CLaSH需要更大的内联限制才能合成它。例如,在CHIP-8实现中,this commit开始使用上述FetchM。在进行此更改之前,只有100的内联深度足以通过CLaSH合成器。更改之后,300不够,而1000会使CLaSH搅动直到内存耗尽。

FetchM有什么邪恶之处,以至于内线人士对此感到窒息?

1 个答案:

答案 0 :(得分:2)

事实证明,真正的罪魁祸首不是FetchM,而是我代码的其他部分,它们需要内联很多功能(在我的主要CPU monad中,每个monadic绑定一个!),以及FetchM刚刚增加了绑定数。

真正的问题是我的CPU monad was, among other things, a Writer (Endo CPUOut)和所有这些CPUOut -> CPUOut函数必须完全内联,因为CLaSH不能将函数表示为信号。

所有这些都在the related CLaSH bug ticket中进行了详细说明。