Почему вкладыш задыхается от этой конструкции? - PullRequest
3 голосов
/ 29 мая 2019

Я пытаюсь разделить как можно больше кода между эмуляторами и реализациями CLaSH для процессоров.Как часть этого, я пишу выборку и декодирование инструкций как что-то вроде

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

. Это тривиально для запуска в эмуляторе с использованием монады, в которой находится счетчик программ, и прямой доступ кобъем памяти.Для аппаратной версии я создаю буфер фиксированного размера (поскольку длина байта инструкции ограничена) и в каждом цикле закорачиваю выборку, если в буфере еще недостаточно данных.

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 в состоянии ЦП во время выборки команд и 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 этот коммит начинает использовать вышеописанный FetchM.Перед этим изменением достаточно глубины встраивания всего 100, чтобы пройти через синтезатор CLaSH;после этого изменения 300 недостаточно, а 1000 заставляют CLaSH просто взбалтывать, пока не кончится память.

Что такого плохого в FetchM, что инлайнер задыхается от него?

1 Ответ

2 голосов
/ 17 июня 2019

Оказалось, что настоящий виновник был не FetchM, а другие части моего кода, которые требовали встраивания большого количества функций (по одной на каждое монадное связывание в моей основной CPU монаде!), А FetchM простоувеличил количество привязок.

Реальная проблема заключалась в том, что моя CPU монада была, помимо прочего, Writer (Endo CPUOut), и все эти CPUOut -> CPUOut функции должны были быть полностьювстроенный, поскольку CLaSH не может представлять функции как сигналы.

Все это более подробно объясняется в соответствующем билете об ошибках CLaSH .

...