packages feed

llvm-codegen-0.1.0.0: lib/LLVM/Codegen/ModuleBuilder.hs

{-# LANGUAGE TypeFamilies, MultiParamTypeClasses, UndecidableInstances, TypeOperators #-}

module LLVM.Codegen.ModuleBuilder
  ( ModuleBuilderT
  , ModuleBuilder
  , runModuleBuilderT
  , runModuleBuilder
  , MonadModuleBuilder
  , Module(..)
  , Definition(..)
  , ParameterName(..)
  , FunctionAttribute(..)
  , function
  , global
  , globalUtf8StringPtr
  , extern
  , typedef
  , opaqueTypedef
  , getTypedefs
  , lookupType
  , withFunctionAttributes
  , renderModule
  ) where

import GHC.Stack
import Control.Monad.State.Lazy (StateT(..), MonadState, State, execStateT, modify, gets)
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.Reader
import Control.Monad.Writer
import Control.Monad.Except
import Control.Monad.Morph
import Control.Monad.Fix
import Data.DList (DList)
import Data.Map (Map)
import Data.String
import qualified Data.DList as DList
import qualified Data.Map as Map
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import qualified Data.ByteString as BS
import Data.Functor.Identity
import LLVM.Codegen.IRBuilder.Monad
import LLVM.Codegen.Operand
import LLVM.Codegen.Type
import LLVM.Codegen.Name
import LLVM.Codegen.Flag
import LLVM.Codegen.IR
import LLVM.Pretty


newtype Module
  = Module [Definition]

data ParameterName
  = ParameterName !T.Text
  | NoParameterName
  deriving Show

instance IsString ParameterName where
  fromString = ParameterName . fromString

data FunctionAttribute
  = WasmExportName !T.Text
  | AlwaysInline
  -- Add more as needed..
  deriving Show

data Global
  = GlobalVariable !Name !Type !Constant
  | Function !Name !Type ![(Type, ParameterName)] ![FunctionAttribute] ![BasicBlock]
  deriving Show

data Typedef
  = Opaque
  | Clear !Type
  deriving Show

data Definition
  = GlobalDefinition !Global
  | TypeDefinition !Name !Typedef
  deriving Show

data ModuleBuilderState
  = ModuleBuilderState
  { definitions :: !(DList Definition)
  , types :: !(Map Name Type)
  , defaultFunctionAttributes :: ![FunctionAttribute]
  }

newtype ModuleBuilderT m a
  = ModuleBuilderT { unModuleBuilderT :: StateT ModuleBuilderState m a }
  deriving ( Functor, Applicative, Monad, MonadFix, MonadIO
           , MonadError e
           )
  via StateT ModuleBuilderState m

type ModuleBuilder = ModuleBuilderT Identity

instance MonadTrans ModuleBuilderT where
  lift = ModuleBuilderT . lift
  {-# INLINEABLE lift #-}

instance MonadReader r m => MonadReader r (ModuleBuilderT m) where
  ask = lift ask
  {-# INLINEABLE ask #-}
  local = mapModuleBuilderT . local
  {-# INLINEABLE local #-}

mapModuleBuilderT :: (Functor m, Monad n) => (m a -> n a) -> ModuleBuilderT m a -> ModuleBuilderT n a
mapModuleBuilderT f (ModuleBuilderT inner) =
  ModuleBuilderT $ do
    s <- LazyState.get
    LazyState.mapStateT (g s) inner
  where
    g s = fmap (,s) . f . fmap fst
{-# INLINEABLE mapModuleBuilderT #-}

instance MonadState s m => MonadState s (ModuleBuilderT m) where
  state = lift . LazyState.state
  {-# INLINEABLE state #-}

instance MFunctor ModuleBuilderT where
  hoist nat = ModuleBuilderT . hoist nat . unModuleBuilderT
  {-# INLINEABLE hoist #-}

class Monad m => MonadModuleBuilder m where
  liftModuleBuilderState :: State ModuleBuilderState a -> m a

  default liftModuleBuilderState
    :: (MonadTrans t, MonadModuleBuilder m1, m ~ t m1)
    => State ModuleBuilderState a
    -> m a
  liftModuleBuilderState = lift . liftModuleBuilderState
  {-# INLINEABLE liftModuleBuilderState #-}

instance Monad m => MonadModuleBuilder (ModuleBuilderT m) where
  liftModuleBuilderState (StateT s) =
    ModuleBuilderT $ StateT $ pure . runIdentity . s
  {-# INLINEABLE liftModuleBuilderState #-}

instance MonadModuleBuilder m => MonadModuleBuilder (IRBuilderT m)
instance MonadModuleBuilder m => MonadModuleBuilder (StrictState.StateT s m)
instance MonadModuleBuilder m => MonadModuleBuilder (LazyState.StateT s m)
instance (MonadModuleBuilder m, Monoid w) => MonadModuleBuilder (StrictRWS.RWST r w s m)
instance (MonadModuleBuilder m, Monoid w) => MonadModuleBuilder (LazyRWS.RWST r w s m)
instance MonadModuleBuilder m => MonadModuleBuilder (ReaderT r m)
instance (MonadModuleBuilder m, Monoid w) => MonadModuleBuilder (WriterT w m)
instance MonadModuleBuilder m => MonadModuleBuilder (ExceptT e m)

runModuleBuilderT :: Monad m => ModuleBuilderT m a -> m Module
runModuleBuilderT (ModuleBuilderT m) =
  Module . DList.toList . definitions <$> execStateT m beginState
  where
    beginState = ModuleBuilderState mempty mempty []
{-# INLINEABLE runModuleBuilderT #-}

withFunctionAttributes
  :: MonadModuleBuilder m
  => ([FunctionAttribute] -> [FunctionAttribute])
  -> m a -> m a
withFunctionAttributes f m = do
  fnAttrs <- liftModuleBuilderState (gets defaultFunctionAttributes)
  liftModuleBuilderState $
    modify $ \s -> s { defaultFunctionAttributes = f fnAttrs }
  result <- m
  liftModuleBuilderState $
    modify $ \s -> s { defaultFunctionAttributes = fnAttrs }
  pure result
{-# INLINEABLE withFunctionAttributes #-}

resetFunctionAttributes :: MonadModuleBuilder m => m ()
resetFunctionAttributes =
  liftModuleBuilderState $
    modify $ \s -> s { defaultFunctionAttributes = mempty }
{-# INLINEABLE resetFunctionAttributes #-}

getDefaultFunctionAttributes :: MonadModuleBuilder m => m [FunctionAttribute]
getDefaultFunctionAttributes =
  liftModuleBuilderState $ gets defaultFunctionAttributes
{-# INLINEABLE getDefaultFunctionAttributes #-}

runModuleBuilder :: ModuleBuilder a -> Module
runModuleBuilder = runIdentity . runModuleBuilderT
{-# INLINEABLE runModuleBuilder #-}

function :: (HasCallStack, MonadModuleBuilder m)
         => Name -> [(Type, ParameterName)] -> Type -> ([Operand] -> IRBuilderT m a) -> m Operand
function name args retTy fnBody = do
  fnAttrs <- getDefaultFunctionAttributes

  (names, instrs) <- runIRBuilderT $ do
    (names, operands) <- unzip <$> traverse (uncurry mkOperand) args
    resetFunctionAttributes  -- This is done to avoid functions emitted in the body that not automatically copy the same attributes
    _ <- fnBody operands
    pure names

  liftModuleBuilderState $
    modify $ \s -> s { defaultFunctionAttributes = fnAttrs }
  let args' = zipWith (\argName (ty, _) -> (ty, ParameterName $ unName argName)) names args
  emitDefinition $ GlobalDefinition $ Function name retTy args' fnAttrs instrs
  pure $ ConstantOperand $ GlobalRef (ptr (FunctionType retTy $ map fst args)) name
{-# INLINEABLE function #-}

emitDefinition :: MonadModuleBuilder m => Definition -> m ()
emitDefinition def =
  liftModuleBuilderState $ modify $ \s -> s { definitions = DList.snoc (definitions s) def }
{-# INLINEABLE emitDefinition #-}

getTypedefs :: MonadModuleBuilder m => m (Map Name Type)
getTypedefs =
  liftModuleBuilderState $ gets types
{-# INLINEABLE getTypedefs #-}

lookupType :: MonadModuleBuilder m => Name -> m (Maybe Type)
lookupType name =
  liftModuleBuilderState $ gets (Map.lookup name . types)
{-# INLINEABLE lookupType #-}

addType :: MonadModuleBuilder m => Name -> Type -> m ()
addType name ty =
  liftModuleBuilderState $ modify $ \s -> s { types = Map.insert name ty (types s) }
{-# INLINEABLE addType #-}

global :: MonadModuleBuilder m => Name -> Type -> Constant -> m Operand
global name ty constant = do
  emitDefinition $ GlobalDefinition $ GlobalVariable name ty constant
  pure $ ConstantOperand $ GlobalRef (ptr ty) name
{-# INLINEABLE global #-}

globalUtf8StringPtr :: (HasCallStack, MonadModuleBuilder m, MonadIRBuilder m)
                    => T.Text -> Name -> m Operand
globalUtf8StringPtr txt name = do
  let utf8Bytes = BS.snoc (TE.encodeUtf8 txt) 0  -- 0-terminated UTF8 string
      llvmValues = map (Int 8 . toInteger) $ BS.unpack utf8Bytes
      arrayValue = Array i8 llvmValues
      constant = ConstantOperand arrayValue
      ty = typeOf constant
  -- This definition will end up before the function this is used in
  addr <- global name ty arrayValue
  let instr = GetElementPtr On addr [ ConstantOperand $ Int 32 0
                                    , ConstantOperand $ Int 32 0
                                    ]
  emitInstr (ptr i8) instr
{-# INLINEABLE globalUtf8StringPtr #-}

-- NOTE: typedefs are only allowed for structs, even though clang also allows it
-- for primitive types. This is done to avoid weird inconsistencies with the LLVM JIT
-- (where this is not allowed).
typedef :: MonadModuleBuilder m => Name -> Flag Packed -> [Type] -> m Type
typedef name packed tys = do
  let ty = StructureType packed tys
  emitDefinition $ TypeDefinition name (Clear ty)
  addType name ty
  pure $ NamedTypeReference name
{-# INLINEABLE typedef #-}

opaqueTypedef :: MonadModuleBuilder m => Name -> m Type
opaqueTypedef name = do
  emitDefinition $ TypeDefinition name Opaque
  pure $ NamedTypeReference name
{-# INLINEABLE opaqueTypedef #-}

extern :: MonadModuleBuilder m => Name -> [Type] -> Type -> m Operand
extern name argTys retTy = do
  let args = [(argTy, ParameterName "") | argTy <- argTys]
  fnAttrs <- getDefaultFunctionAttributes
  emitDefinition $ GlobalDefinition $ Function name retTy args fnAttrs []
  let fnTy = ptr $ FunctionType retTy argTys
  pure $ ConstantOperand $ GlobalRef fnTy name
{-# INLINEABLE extern #-}

-- NOTE: Only used internally, this creates an unassigned operand
mkOperand :: Monad m => Type -> ParameterName -> IRBuilderT m (Name, Operand)
mkOperand ty paramName = do
  name <- case paramName of
    NoParameterName -> freshName Nothing
    ParameterName name -> freshName (Just name)
  pure (name, LocalRef ty name)
{-# INLINEABLE mkOperand #-}

renderModule :: Renderer Module
renderModule buf (Module defs) =
  sepBy "\n\n"# buf defs renderDefinition
{-# INLINEABLE renderModule #-}

renderDefinition :: Renderer Definition
renderDefinition buf = \case
  GlobalDefinition g ->
    renderGlobal buf g
  TypeDefinition name typeDef ->
    case typeDef of
      Opaque ->
        (buf |>. '%') `renderName` name |># " = type opaque"#
      Clear ty ->
        ((buf |>. '%') `renderName` name |># " = type "#) `renderType` ty
{-# INLINEABLE renderDefinition #-}

renderGlobal :: Renderer Global
renderGlobal buf = \case
  GlobalVariable name ty constant ->
    (((((buf |>. '@') `renderName` name) |># " = global "#) `renderType` ty) |>. ' ') `renderConstant` constant
  Function name retTy args attrs body
    | null body ->
      hsep (tupled ((((buf |># "declare external ccc "#) `renderType` retTy) |># " @"#) `renderName` name) argTys renderType
        |># (if null attrs then ""# else " "#)) attrs renderFunctionAttr
    | otherwise ->
      vsep (hsep (tupled ((((buf |># "define external ccc "#) `renderType` retTy) |># " @"#) `renderName` name) (zip [0..] args) renderArg |>. ' ') attrs renderFunctionAttr
        |># (if null attrs then "{\n"# else " {\n"#)) body renderBasicBlock |># "\n}"#
    where
      argTys = map fst args
      renderArg :: Renderer (Int, (Type, ParameterName))
      renderArg buf' (i, (argTy, nm)) =
        let localRef = case nm of
              NoParameterName ->
                LocalRef argTy $ Name $ T.pack $ show i
              ParameterName paramName ->
                LocalRef argTy $ Name paramName
         in ((buf' `renderType` argTy) |>. ' ') `renderOperand` localRef
{-# INLINEABLE renderGlobal #-}

renderFunctionAttr :: Renderer FunctionAttribute
renderFunctionAttr buf = \case
  AlwaysInline ->
    buf |># "alwaysinline"#
  WasmExportName name ->
    dquotes
      (dquotes buf (|># "wasm-export-name"#) |>. '=')
      (|> name)
{-# INLINEABLE renderFunctionAttr #-}