Почему инлайнер задыхается от этой конструкции?

Я пытаюсь разделить как можно больше кода между эмуляторами и реализациями 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, что вкладыш задыхается от него?

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
0
96
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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

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

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

Другие вопросы по теме