Code Generation Setup
We start with a new Haskell module Codegen.hs
which will hold the pure code generation logic that we’ll use to drive building the llvm-hs AST. For simplicity’s sake we’ll insist that all variables be of a single type, the double
type.
double :: Type
double = FloatingPointType 64 IEEE
To start we create a new record type to hold the internal state of our code generator as we walk the AST. We’ll use two records, one for the toplevel module code generation and one for basic blocks inside of function definitions.
type SymbolTable = [(String, Operand)]
data CodegenState
= CodegenState {
currentBlock :: Name -- Name of the active block to append to
, blocks :: Map.Map Name BlockState -- Blocks for function
, symtab :: SymbolTable -- Function scope symbol table
, blockCount :: Int -- Count of basic blocks
, count :: Word -- Count of unnamed instructions
, names :: Names -- Name Supply
} deriving Show
data BlockState
= BlockState {
idx :: Int -- Block index
, stack :: [Named Instruction] -- Stack of instructions
, term :: Maybe (Named Terminator) -- Block terminator
} deriving Show
We’ll hold the state of the code generator inside of Codegen
State monad, the Codegen monad contains a map of block names to their BlockState
representation.
newtype Codegen a = Codegen { runCodegen :: State CodegenState a }
deriving (Functor, Applicative, Monad, MonadState CodegenState )
At the top level we’ll create a LLVM
State monad which will hold all code a for the LLVM module and upon evaluation will emit an llvm-hs Module containing the AST. We’ll append to the list of definitions in the AST.Module
field moduleDefinitions
.
newtype LLVM a = LLVM (State AST.Module a)
deriving (Functor, Applicative, Monad, MonadState AST.Module )
runLLVM :: AST.Module -> LLVM a -> AST.Module
runLLVM mod (LLVM m) = execState m mod
emptyModule :: String -> AST.Module
emptyModule label = defaultModule { moduleName = label }
addDefn :: Definition -> LLVM ()
addDefn d = do
defs <- gets moduleDefinitions
modify $ \s -> s { moduleDefinitions = defs ++ [d] }
Inside of our module we’ll need to insert our toplevel definitions. For our purposes this will consist entirely of local functions and external function declarations.
define :: Type -> String -> [(Type, Name)] -> [BasicBlock] -> LLVM ()
define retty label argtys body = addDefn $
GlobalDefinition $ functionDefaults {
name = Name label
, parameters = ([Parameter ty nm [] | (ty, nm) <- argtys], False)
, returnType = retty
, basicBlocks = body
}
external :: Type -> String -> [(Type, Name)] -> LLVM ()
external retty label argtys = addDefn $
GlobalDefinition $ functionDefaults {
name = Name label
, linkage = L.External
, parameters = ([Parameter ty nm [] | (ty, nm) <- argtys], False)
, returnType = retty
, basicBlocks = []
}