packages feed

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

{-# LANGUAGE RecursiveDo, PolyKinds, RoleAnnotations #-}

module LLVM.Codegen.IRBuilder
  ( IRBuilderT
  , IRBuilder
  , block
  , blockNamed
  , emitBlockStart
  , emitInstr
  , emitInstrVoid
  , emitTerminator
  , BasicBlock(..)
  , runIRBuilderT
  , runIRBuilder
  , MonadIRBuilder(..)

  , add
  , mul
  , sub
  , udiv
  , and
  , or
  , trunc
  , zext
  , ptrtoint
  , bitcast
  , ptrcast
  , icmp
  , alloca
  , gep
  , load
  , store
  , phi
  , call
  , ret
  , retVoid
  , br
  , condBr
  , switch
  , select

  , eq
  , ne
  , sge
  , sgt
  , sle
  , slt
  , uge
  , ugt
  , ule
  , ult
  , if'
  , loop
  , loopWhile
  , loopFor
  , pointerDiff
  , not'
  , Signedness(..)
  , minimum'
  , allocate
  , Path(..), (->>), mkPath
  , addr, deref, assign, update, increment, copy, swap

  , bit
  , int8
  , int16
  , int32
  , int64
  , intN
  , nullPtr
  ) where

import Prelude hiding (EQ, and, or)
import GHC.Stack
import Control.Monad.Fix
import qualified Data.List.NonEmpty as NE
import Data.List.NonEmpty (NonEmpty(..))
import Data.Word
import LLVM.Codegen.Name
import LLVM.Codegen.Operand
import LLVM.Codegen.Type
import LLVM.Codegen.IR
import LLVM.Codegen.IRBuilder.Monad
import LLVM.Codegen.ModuleBuilder


-- Helpers for generating instructions:

add :: (MonadIRBuilder m, HasCallStack) => Operand -> Operand -> m Operand
add lhs rhs =
  emitInstr (typeOf lhs) $ Add Off Off lhs rhs
{-# INLINEABLE add #-}

mul :: (MonadIRBuilder m, HasCallStack) => Operand -> Operand -> m Operand
mul lhs rhs =
  emitInstr (typeOf lhs) $ Mul Off Off lhs rhs
{-# INLINEABLE mul #-}

sub :: (MonadIRBuilder m, HasCallStack) => Operand -> Operand -> m Operand
sub lhs rhs =
  emitInstr (typeOf lhs) $ Sub Off Off lhs rhs
{-# INLINEABLE sub #-}

udiv :: (MonadIRBuilder m, HasCallStack) => Operand -> Operand -> m Operand
udiv lhs rhs =
  emitInstr (typeOf lhs) $ Udiv Off lhs rhs
{-# INLINEABLE udiv #-}

and :: (MonadIRBuilder m, HasCallStack) => Operand -> Operand -> m Operand
and lhs rhs =
  emitInstr (typeOf lhs) $ And lhs rhs
{-# INLINEABLE and #-}

or :: (MonadIRBuilder m, HasCallStack) => Operand -> Operand -> m Operand
or lhs rhs =
  emitInstr (typeOf lhs) $ Or lhs rhs
{-# INLINEABLE or #-}

trunc :: (MonadIRBuilder m, HasCallStack) => Operand -> Type -> m Operand
trunc val ty =
  emitInstr ty $ Trunc val ty
{-# INLINEABLE trunc #-}

zext :: (MonadIRBuilder m, HasCallStack) => Operand -> Type -> m Operand
zext val ty =
  emitInstr ty $ Zext val ty
{-# INLINEABLE zext #-}

ptrtoint :: (MonadIRBuilder m, HasCallStack) => Operand -> Type -> m Operand
ptrtoint val ty =
  emitInstr ty $ PtrToInt val ty
{-# INLINEABLE ptrtoint #-}

-- At the moment not useful because of introduction of opaque pointers.
-- Will become more useful once float or vector types are added again.
bitcast :: (MonadIRBuilder m, HasCallStack) => Operand -> Type -> m Operand
bitcast val ty =
  emitInstr ty $ Bitcast val ty
{-# INLINEABLE bitcast #-}

-- Casts a pointer to be a pointer containing type "ty".
-- This helper function is introduced to smooth the transition between
-- LLVM14 -> LLVM15+ (opaque pointer migration).
-- All bitcasts of pointers should be replaced with ptrcasts
ptrcast :: Type -> Operand -> Operand
ptrcast ty = \case
  LocalRef (PointerType _) name ->
    LocalRef (PointerType ty) name
  ConstantOperand (NullPtr _) ->
    ConstantOperand $ NullPtr ty
  _ ->
    error "'ptrcast' is only supported for pointer operands."
{-# INLINEABLE ptrcast #-}

icmp :: (MonadIRBuilder m, HasCallStack) => ComparisonType -> Operand -> Operand -> m Operand
icmp cmp a b =
  emitInstr i1 $ ICmp cmp a b
{-# INLINEABLE icmp #-}

eq, ne, sge, sgt, sle, slt, uge, ugt, ule, ult
  :: (MonadIRBuilder m, HasCallStack) => Operand -> Operand -> m Operand
eq = icmp EQ
ne = icmp NE
sge = icmp SGE
sgt = icmp SGT
sle = icmp SLE
slt = icmp SLT
uge = icmp UGE
ugt = icmp UGT
ule = icmp ULE
ult = icmp ULT
{-# INLINABLE eq #-}
{-# INLINABLE ne #-}
{-# INLINABLE sge #-}
{-# INLINABLE sgt #-}
{-# INLINABLE sle #-}
{-# INLINABLE slt #-}
{-# INLINABLE uge #-}
{-# INLINABLE ugt #-}
{-# INLINABLE ule #-}
{-# INLINABLE ult #-}

alloca :: (MonadIRBuilder m, HasCallStack) => Type -> Maybe Operand -> Int -> m Operand
alloca ty numElems alignment =
  emitInstr (ptr ty) $ Alloca ty numElems alignment
{-# INLINEABLE alloca #-}

gep :: (HasCallStack, MonadModuleBuilder m, MonadIRBuilder m)
    => Operand -> [Operand] -> m Operand
gep operand indices = do
  resultType <- computeGepType (typeOf operand) indices
  case resultType of
    Left err -> error err
    Right ty ->
      emitInstr ty $ GetElementPtr Off operand indices
{-# INLINEABLE gep #-}

computeGepType :: (MonadModuleBuilder m, HasCallStack) => Type -> [Operand] -> m (Either String Type)
computeGepType ty [] = pure $ Right $ PointerType ty
computeGepType (PointerType ty) (_ : idxs) =
  case (ty, null idxs) of
    -- If you want to load something from e.g. i8***, you need to gep + load for each pointer indirection!
    (PointerType{}, False) -> pure $ Left "Opaque pointers support only one gep offset."
    _ -> computeGepType ty idxs
computeGepType (StructureType _ elTys) (ConstantOperand (Int 32 val):is) =
  computeGepType (elTys !! fromIntegral val) is
computeGepType (StructureType _ _) (i:_) =
  pure $ Left $ "Indices into structures should be 32-bit integer constants. (Malformed AST): " <> show i
computeGepType (ArrayType _ elTy) (_:is) = computeGepType elTy is
computeGepType (NamedTypeReference n) is =
  lookupType n >>= \case
    Nothing -> pure $ Left $ "Couldn’t resolve typedef for: " <> show n
    Just ty -> computeGepType ty is
computeGepType ty _ =
  pure $ Left $ "Expecting aggregate type. (Malformed AST): " <> show ty
{-# INLINEABLE computeGepType #-}

load :: (HasCallStack, MonadIRBuilder m) => Operand -> Alignment -> m Operand
load address align =
  case typeOf address of
    PointerType ty ->
      emitInstr ty $ Load Off address Nothing align
    t ->
      error $ "Malformed AST: Expected a pointer type" <> show t
{-# INLINEABLE load #-}

store :: (MonadIRBuilder m, HasCallStack) => Operand -> Alignment -> Operand -> m ()
store address align value =
  emitInstrVoid $ Store Off address value Nothing align
{-# INLINEABLE store #-}

phi :: (HasCallStack, MonadIRBuilder m) => [(Operand, Name)] -> m Operand
phi cases
  | null cases = error "phi instruction should always have > 0 cases!"
  | otherwise =
    let neCases = NE.fromList cases
        ty = typeOf $ fst $ NE.head neCases
     in emitInstr ty $ Phi neCases
{-# INLINEABLE phi #-}

call :: (HasCallStack, MonadIRBuilder m) => Operand -> [Operand] -> m Operand
call fn args = case typeOf fn of
  FunctionType retTy _->
    emitCallInstr retTy
  PointerType (FunctionType retTy _) ->
    emitCallInstr retTy
  _ -> error "Malformed AST, expected function type in 'call' instruction"
  where
    emitCallInstr resultTy =
      if resultTy == VoidType
        then do
          emitInstrVoid $ Call Nothing C fn args
          pure $ ConstantOperand $ Undef void  -- Invalid, but isn't rendered anyway
        else emitInstr resultTy $ Call Nothing C fn args
{-# INLINEABLE call #-}

ret :: (HasCallStack, MonadIRBuilder m) => Operand -> m ()
ret val =
  emitTerminator (Terminator (Ret (Just val)))
{-# INLINEABLE ret #-}

retVoid :: (HasCallStack, MonadIRBuilder m) => m ()
retVoid =
  emitTerminator (Terminator (Ret Nothing))
{-# INLINEABLE retVoid #-}

br :: (MonadIRBuilder m, HasCallStack) => Name -> m ()
br label =
  emitTerminator (Terminator (Br label))
{-# INLINEABLE br #-}

condBr :: (HasCallStack, MonadIRBuilder m) => Operand -> Name -> Name -> m ()
condBr cond trueLabel falseLabel =
  emitTerminator (Terminator (CondBr cond trueLabel falseLabel))
{-# INLINEABLE condBr #-}

switch :: (HasCallStack, MonadIRBuilder m) => Operand -> Name -> [(Operand, Name)] -> m ()
switch value defaultDest dests =
  emitTerminator $ Terminator $ Switch value defaultDest dests
{-# INLINEABLE switch #-}

select :: (HasCallStack, MonadIRBuilder m) => Operand -> Operand -> Operand -> m Operand
select c t f =
  emitInstr (typeOf t) $ Select c t f
{-# INLINEABLE select #-}

if' :: (HasCallStack, MonadIRBuilder m, MonadFix m)
    => Operand -> m a -> m ()
if' condition asm = mdo
  condBr condition ifBlock end
  ifBlock <- blockNamed "if"
  _ <- asm
  br end
  end <- blockNamed "end_if"
  pure ()
{-# INLINEABLE if' #-}

loop :: (HasCallStack, MonadIRBuilder m, MonadFix m) => m a -> m ()
loop asm = mdo
  br begin
  begin <- blockNamed "loop"
  _ <- asm
  br begin
{-# INLINEABLE loop #-}

loopWhile :: (HasCallStack, MonadIRBuilder m, MonadFix m)
          => m Operand -> m a -> m ()
loopWhile condition asm = mdo
  br begin
  begin <- blockNamed "while_begin"
  result <- condition
  condBr result body end
  body <- blockNamed "while_body"
  _ <- asm
  br begin
  end <- blockNamed "while_end"
  pure ()
{-# INLINEABLE loopWhile #-}

loopFor :: (HasCallStack, MonadModuleBuilder m, MonadIRBuilder m, MonadFix m)
        => Operand
        -> (Operand -> m Operand)
        -> (Operand -> m Operand)
        -> (Operand -> m a)
        -> m ()
loopFor beginValue condition post asm = mdo
  start <- currentBlock
  br begin
  begin <- blockNamed "for_begin"
  loopValue <- phi [(beginValue, start), (updatedValue, bodyEnd)]
  result <- condition loopValue
  condBr result bodyStart end
  bodyStart <- blockNamed "for_body"
  _ <- asm loopValue
  updatedValue <- post loopValue
  bodyEnd <- currentBlock
  br begin
  end <- blockNamed "for_end"
  pure ()
{-# INLINEABLE loopFor #-}

-- NOTE: diff is in bytes! (Different compared to C and C++)
pointerDiff :: (HasCallStack, MonadIRBuilder m)
            => Type -> Operand -> Operand -> m Operand
pointerDiff ty a b = do
  a' <- ptrtoint a i64
  b' <- ptrtoint b i64
  result <- sub a' b'
  if ty == i64
    then pure result
    else trunc result ty
{-# INLINEABLE pointerDiff #-}

-- | Calculates the logical not of a boolean 'Operand'.
--   NOTE: This assumes the 'Operand' is of type 'i1', this is not checked!
--   Passing in an argument of another width will lead to a crash in LLVM.
not' :: (HasCallStack, MonadIRBuilder m)
     => Operand -> m Operand
not' bool =
  select bool (bit 0) (bit 1)
{-# INLINEABLE not' #-}

data Signedness = Signed | Unsigned

--   NOTE: No check is made if the 2 operands have the same 'Type'!
minimum' :: (HasCallStack, MonadIRBuilder m)
         => Signedness -> Operand -> Operand -> m Operand
minimum' sign a b = do
  let inst = case sign of
        Signed -> slt
        Unsigned -> ult
  isLessThan <- inst a b
  select isLessThan a b
{-# INLINEABLE minimum' #-}

allocate :: (HasCallStack, MonadIRBuilder m) => Type -> Operand -> m Operand
allocate ty beginValue = do
  value <- alloca ty Nothing 0
  store value 0 beginValue
  pure value
{-# INLINEABLE allocate #-}

newtype Path (a :: k) (b :: k)
  = Path (NonEmpty Operand)
  deriving (Eq, Show)
type role Path nominal nominal

mkPath :: [Operand] -> Path a b
mkPath path = Path (int32 0 :| path)

(->>) :: Path a b -> Path b c -> Path a c
Path a2b ->> Path b2c =
  let b2c' = if NE.head b2c == int32 0
               then NE.tail b2c
               else NE.toList b2c
   in Path $ NE.head a2b :| (NE.tail a2b ++ b2c')

addr :: (MonadModuleBuilder m, MonadIRBuilder m, HasCallStack)
     => Path a b -> Operand -> m Operand
addr path p = gep p (pathToIndices path)
  where
    pathToIndices :: Path a b -> [Operand]
    pathToIndices (Path indices) =
      NE.toList indices
{-# INLINEABLE addr #-}

deref :: (MonadModuleBuilder m, MonadIRBuilder m, HasCallStack)
      => Path a b -> Operand -> m Operand
deref path p = do
  address <- addr path p
  load address 0
{-# INLINEABLE deref #-}

assign :: (MonadModuleBuilder m, MonadIRBuilder m, HasCallStack)
       => Path a b -> Operand -> Operand -> m ()
assign path p value = do
  dstAddr <- addr path p
  store dstAddr 0 value
{-# INLINEABLE assign #-}

update :: (MonadModuleBuilder m, MonadIRBuilder m, HasCallStack)
       => Path a b
       -> Operand
       -> (Operand -> m Operand)
       -> m ()
update path p f = do
  dstAddr <- addr path p
  store dstAddr 0 =<< f =<< load dstAddr 0
{-# INLINEABLE update #-}

increment :: (MonadModuleBuilder m, MonadIRBuilder m, HasCallStack)
          => (Integer -> Operand) -> Path a b -> Operand -> m ()
increment ty path p =
  update path p (add (ty 1))
{-# INLINEABLE increment #-}

copy :: (MonadModuleBuilder m, MonadIRBuilder m, HasCallStack)
     => Path a b -> Operand -> Operand -> m ()
copy path src dst = do
  value <- deref path src
  assign path dst value
{-# INLINEABLE copy #-}

swap :: (MonadModuleBuilder m, MonadIRBuilder m, HasCallStack)
     => Path a b -> Operand -> Operand -> m ()
swap path lhs rhs = do
  tmp <- deref path lhs
  copy path rhs lhs
  assign path rhs tmp
{-# INLINEABLE swap #-}


bit :: Integer -> Operand
bit b =
  intN 1 $ if b == 0 then 0 else 1

int8 :: Integer -> Operand
int8 =
  intN 8

int16 :: Integer -> Operand
int16 =
  intN 16

int32 :: Integer -> Operand
int32 =
  intN 32

int64 :: Integer -> Operand
int64 =
  intN 64

intN :: Word32 -> Integer -> Operand
intN bits value =
  ConstantOperand $ Int bits value

nullPtr :: Type -> Operand
nullPtr =
  ConstantOperand . NullPtr