为什么内衬会在此构造上窒息?
Why does the inliner choke on this construct?
我正在尝试在模拟器和 CPUs 的 CLaSH 实现之间共享尽可能多的代码。作为其中的一部分,我正在按照
的方式编写指令获取和解码
fetchInstr :: (Monad m) => m Word8 -> m Instr
这对于模拟器中的 运行 来说是微不足道的,使用在其状态中具有程序计数器并直接访问内存的 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
这个想法是,这将通过在指令获取期间将 Buffer n dat
存储在 CPU 的状态中来使用,并且 remember
ing 从内存中获取值时运行:
下的缓冲区
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
有什么不对,内衬会卡住?
原来真正的罪魁祸首不是 FetchM
,而是我的代码的其他部分需要内联很多函数(我的主 CPU
monad 中每个 monadic 绑定一个!) , FetchM
只是增加了绑定的数量。
真正的问题是我的 CPU
monad was, among other things, a Writer (Endo CPUOut)
和所有那些 CPUOut -> CPUOut
函数都需要完全内联,因为 CLaSH 不能将函数表示为信号。
所有这些都在 the related CLaSH bug ticket 中有更详细的解释。
我正在尝试在模拟器和 CPUs 的 CLaSH 实现之间共享尽可能多的代码。作为其中的一部分,我正在按照
的方式编写指令获取和解码fetchInstr :: (Monad m) => m Word8 -> m Instr
这对于模拟器中的 运行 来说是微不足道的,使用在其状态中具有程序计数器并直接访问内存的 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
这个想法是,这将通过在指令获取期间将 Buffer n dat
存储在 CPU 的状态中来使用,并且 remember
ing 从内存中获取值时运行:
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
有什么不对,内衬会卡住?
原来真正的罪魁祸首不是 FetchM
,而是我的代码的其他部分需要内联很多函数(我的主 CPU
monad 中每个 monadic 绑定一个!) , FetchM
只是增加了绑定的数量。
真正的问题是我的 CPU
monad was, among other things, a Writer (Endo CPUOut)
和所有那些 CPUOut -> CPUOut
函数都需要完全内联,因为 CLaSH 不能将函数表示为信号。
所有这些都在 the related CLaSH bug ticket 中有更详细的解释。