llvm-codegen-0.1.0.0: lib/LLVM/Codegen/IRBuilder/Monad.hs
{-# LANGUAGE TypeFamilies, RankNTypes, MultiParamTypeClasses, UndecidableInstances, BangPatterns, TypeOperators #-}
module LLVM.Codegen.IRBuilder.Monad
( IRBuilderT
, IRBuilder
, runIRBuilderT
, runIRBuilder
, MonadIRBuilder(..)
, BasicBlock(..)
, block
, blockNamed
, emitBlockStart
, emitInstr
, emitInstrVoid
, emitTerminator
, renderBasicBlock
, freshName
) where
-- NOTE: this module only exists to solve a cyclic import
import Prelude hiding (and)
import Control.Arrow hiding ((<+>))
import Control.Monad.State.Lazy (StateT(..), MonadState, modify)
import qualified Control.Monad.State.Strict as StrictState
import qualified Control.Monad.State.Lazy as LazyState
import qualified Control.Monad.RWS.Lazy as LazyRWS
import qualified Control.Monad.RWS.Strict as StrictRWS
import Control.Monad
import Control.Monad.Reader
import Control.Monad.Writer
import Control.Monad.Except
import Control.Monad.Morph
import Control.Monad.Fix
import Data.Functor.Identity
import qualified Data.Text as T
import qualified Data.DList as DList
import Data.DList (DList)
import qualified Data.Map as M
import Data.Map (Map)
import Data.Maybe
import Data.Monoid
import LLVM.Codegen.Operand
import LLVM.Codegen.IR
import LLVM.Codegen.Type
import LLVM.Codegen.Name
import LLVM.Pretty
data BasicBlock
= BB
{ bbName :: !Name
, bbInstructions :: !(DList (Maybe Operand, IR))
, bbTerminator :: !Terminator
} deriving Show
data PartialBlock
= PartialBlock
{ pbName :: !Name
, pbInstructions :: !(DList (Maybe Operand, IR))
, pbTerminator :: !(First Terminator)
, pbNumInstrs :: !Int
}
data IRBuilderState
= IRBuilderState
{ allocas :: !(DList (Maybe Operand, IR))
, basicBlocks :: !(DList BasicBlock)
, currentPartialBlock :: !PartialBlock
, operandCounter :: !Int
, nameMap :: !(Map T.Text Int)
}
newtype IRBuilderT m a
= IRBuilderT { unIRBuilderT :: StateT IRBuilderState m a }
deriving ( Functor, Applicative, Monad, MonadFix, MonadIO
, MonadError e
)
via StateT IRBuilderState m
instance MonadReader r m => MonadReader r (IRBuilderT m) where
ask = lift ask
{-# INLINEABLE ask #-}
local = mapIRBuilderT . local
{-# INLINEABLE local #-}
-- TODO MonadWriter
mapIRBuilderT :: (Monad m, Monad n) => (m a -> n a) -> IRBuilderT m a -> IRBuilderT n a
mapIRBuilderT f (IRBuilderT inner) =
IRBuilderT $ do
s <- LazyState.get
LazyState.mapStateT (g s) inner
where
g s = fmap (,s) . f . fmap fst
{-# INLINEABLE mapIRBuilderT #-}
instance MonadState s m => MonadState s (IRBuilderT m) where
state = lift . StrictState.state
{-# INLINEABLE state #-}
instance MonadTrans IRBuilderT where
lift = IRBuilderT . lift
{-# INLINEABLE lift #-}
instance MFunctor IRBuilderT where
hoist nat = IRBuilderT . hoist nat . unIRBuilderT
{-# INLINEABLE hoist #-}
type IRBuilder = IRBuilderT Identity
runIRBuilderT :: Monad m => IRBuilderT m a -> m (a, [BasicBlock])
runIRBuilderT (IRBuilderT m) = do
let partialBlock = PartialBlock (Name "start") mempty mempty 0
result = runStateT m (IRBuilderState mempty mempty partialBlock 0 mempty)
fmap (second getBlocks) result
where
getBlocks irState =
case blocks of
[] -> []
(firstBlk:restBlks) ->
let firstBlk' = firstBlk { bbInstructions = DList.append allocations (bbInstructions firstBlk) }
in (firstBlk':restBlks)
where
previousBlocks = DList.apply (basicBlocks irState) mempty
currentBlk = currentPartialBlock irState
blocks = previousBlocks <> [partialBlockToBasicBlock currentBlk]
allocations = allocas irState
{-# INLINEABLE runIRBuilderT #-}
runIRBuilder :: IRBuilder a -> (a, [BasicBlock])
runIRBuilder = runIdentity . runIRBuilderT
{-# INLINEABLE runIRBuilder #-}
partialBlockToBasicBlock :: PartialBlock -> BasicBlock
partialBlockToBasicBlock pb =
let currentTerm = fromMaybe (Terminator $ Ret Nothing) $ getFirst $ pbTerminator pb
in BB (pbName pb) (pbInstructions pb) currentTerm
{-# INLINEABLE partialBlockToBasicBlock #-}
block :: (MonadIRBuilder m) => m Name
block = do
blockName <- freshName (Just "block")
emitBlockStart blockName
pure blockName
{-# INLINEABLE block #-}
blockNamed :: (MonadIRBuilder m) => T.Text -> m Name
blockNamed blkName = do
blockName <- freshName (Just blkName)
emitBlockStart blockName
pure blockName
{-# INLINEABLE blockNamed #-}
emitBlockStart :: (MonadIRBuilder m) => Name -> m ()
emitBlockStart blockName =
modifyIRBuilderState $ \s ->
let currBlock = currentPartialBlock s
hasntStartedBlock = (pbNumInstrs currBlock == 0) && isNothing (getFirst (pbTerminator currBlock))
blocks = basicBlocks s
-- If the current block is empty:
-- Insert a dummy basic block that jumps directly to the next block, to avoid continuity errors.
-- Normally, LLVM should optimize this away since it is semantically a no-op.
-- Otherwise:
-- Append the current block to the existing list of blocks.
--
-- NOTE: This is different behavior compared to the llvm-hs-pure library,
-- but this avoids a lot of partial functions!
newBlock =
if hasntStartedBlock
then BB (pbName currBlock) mempty (Terminator $ Br blockName)
else partialBlockToBasicBlock currBlock
in s { basicBlocks = DList.snoc blocks newBlock
, currentPartialBlock = PartialBlock blockName mempty mempty 0
}
{-# INLINEABLE emitBlockStart #-}
-- NOTE: Only used internally, this creates an unassigned operand
mkOperand :: (MonadIRBuilder m) => Type -> m Operand
mkOperand ty = LocalRef ty <$!> freshUnnamed
{-# INLINEABLE mkOperand #-}
freshName :: MonadIRBuilder m => Maybe T.Text -> m Name
freshName = \case
Nothing -> freshUnnamed
Just suggestion -> do
nameMapping <- nameMap <$> getIRBuilderState
let !mCount = M.lookup suggestion nameMapping
!count = fromMaybe 0 mCount
!newMapping = M.insert suggestion (count + 1) nameMapping
modifyIRBuilderState $ \s -> s { nameMap = newMapping }
pure $! Name $! suggestion <> "_" <> T.pack (show count)
{-# INLINEABLE freshName #-}
freshUnnamed :: MonadIRBuilder m => m Name
freshUnnamed = do
!ctr <- operandCounter <$> getIRBuilderState
let !newCount = ctr + 1
modifyIRBuilderState $ \s -> s { operandCounter = newCount }
pure $! Generated ctr
{-# INLINEABLE freshUnnamed #-}
emitInstr :: (MonadIRBuilder m) => Type -> IR -> m Operand
emitInstr ty = \case
instr@(Alloca {}) -> do
-- For performant code, all alloca instructions should be at the start of the function!
-- https://llvm.org/docs/Frontend/PerformanceTips.html#use-of-allocas
-- (A custom operand name is only used here to avoid having to re-number all operands.)
operand <- LocalRef ty <$!> freshName (Just "stack.ptr")
addAlloca operand instr
pure operand
instr -> do
operand <- mkOperand ty
addInstrToCurrentBlock (Just operand) instr
pure operand
{-# INLINABLE emitInstr #-}
emitInstrVoid :: MonadIRBuilder m => IR -> m ()
emitInstrVoid =
addInstrToCurrentBlock Nothing
{-# INLINABLE emitInstrVoid #-}
addInstrToCurrentBlock :: MonadIRBuilder m => Maybe Operand -> IR -> m ()
addInstrToCurrentBlock operand instr =
modifyCurrentBlock $ \blk ->
let instrs = DList.snoc (pbInstructions blk) (operand, instr)
in blk { pbInstructions = instrs, pbNumInstrs = pbNumInstrs blk + 1 }
{-# INLINEABLE addInstrToCurrentBlock #-}
addAlloca :: MonadIRBuilder m => Operand -> IR -> m ()
addAlloca operand instr =
modifyIRBuilderState $ \s ->
s { allocas = DList.snoc (allocas s) (Just operand, instr) }
{-# INLINEABLE addAlloca #-}
emitTerminator :: MonadIRBuilder m => Terminator -> m ()
emitTerminator term =
modifyCurrentBlock $ \blk ->
blk { pbTerminator = pbTerminator blk <> First (Just term) }
{-# INLINABLE emitTerminator #-}
modifyCurrentBlock :: MonadIRBuilder m => (PartialBlock -> PartialBlock) -> m ()
modifyCurrentBlock f =
modifyIRBuilderState $ \s ->
s { currentPartialBlock = f (currentPartialBlock s) }
{-# INLINEABLE modifyCurrentBlock #-}
class Monad m => MonadIRBuilder m where
getIRBuilderState :: m IRBuilderState
modifyIRBuilderState :: (IRBuilderState -> IRBuilderState) -> m ()
currentBlock :: m Name
default getIRBuilderState
:: (MonadTrans t, MonadIRBuilder m1, m ~ t m1)
=> m IRBuilderState
getIRBuilderState = lift getIRBuilderState
{-# INLINEABLE getIRBuilderState #-}
default modifyIRBuilderState
:: (MonadTrans t, MonadIRBuilder m1, m ~ t m1)
=> (IRBuilderState -> IRBuilderState)
-> m ()
modifyIRBuilderState = lift . modifyIRBuilderState
{-# INLINEABLE modifyIRBuilderState #-}
default currentBlock
:: (MonadTrans t, MonadIRBuilder m1, m ~ t m1)
=> m Name
currentBlock = lift currentBlock
{-# INLINEABLE currentBlock #-}
instance Monad m => MonadIRBuilder (IRBuilderT m) where
getIRBuilderState = IRBuilderT LazyState.get
{-# INLINEABLE getIRBuilderState #-}
modifyIRBuilderState = IRBuilderT . modify
{-# INLINEABLE modifyIRBuilderState #-}
currentBlock =
IRBuilderT $ LazyState.gets (pbName . currentPartialBlock)
{-# INLINEABLE currentBlock #-}
instance MonadIRBuilder m => MonadIRBuilder (StrictState.StateT s m)
instance MonadIRBuilder m => MonadIRBuilder (LazyState.StateT s m)
instance (MonadIRBuilder m, Monoid w) => MonadIRBuilder (StrictRWS.RWST r w s m)
instance (MonadIRBuilder m, Monoid w) => MonadIRBuilder (LazyRWS.RWST r w s m)
instance MonadIRBuilder m => MonadIRBuilder (ReaderT r m)
instance (MonadIRBuilder m, Monoid w) => MonadIRBuilder (WriterT w m)
instance MonadIRBuilder m => MonadIRBuilder (ExceptT e m)
renderBasicBlock :: Renderer BasicBlock
renderBasicBlock buf (BB name stmts (Terminator term)) =
if null stmts
then (renderName buf name |># ":\n "#) `renderIR` term
else (vsep (renderName buf name |># ":\n"#) stmts' renderStmt |># "\n "#) `renderIR` term
where
stmts' = DList.apply stmts []
renderStmt :: Buffer %1 -> (Maybe Operand, IR) -> Buffer
renderStmt buf' (mOperand, instr) =
withIndent buf' (\buf'' -> renderStmt' buf'' mOperand instr)
renderStmt' :: Buffer %1 -> Maybe Operand -> IR -> Buffer
renderStmt' buf' mOperand instr =
renderMaybe buf' mOperand (\buf'' operand -> buf'' `renderOperand` operand |># " = "#) `renderIR` instr
{-# INLINABLE renderBasicBlock #-}