packages feed

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

module LLVM.Codegen.IR
  ( IR(..)
  , Terminator(..)
  , ComparisonType(..)
  , CallingConvention(..)
  , TailCallAttribute(..)
  , SynchronizationScope(..)
  , MemoryOrdering(..)
  , Alignment
  , Flag(..)
  , NUW
  , NSW
  , Exact
  , Inbounds
  , Volatile
  , renderIR
  ) where

import Prelude hiding (EQ)
import LLVM.Codegen.Name
import LLVM.Codegen.Operand
import LLVM.Codegen.Type
import LLVM.Codegen.Flag
import LLVM.Pretty
import Data.Word
import Data.List.NonEmpty (NonEmpty(..))
import qualified Data.List.NonEmpty as NE


data NUW
data NSW
data Exact
data Inbounds
data Volatile

type Alignment = Word32

data SynchronizationScope
  = SingleThread
  | System
  deriving Show

data MemoryOrdering
  = Unordered
  | Monotonic
  | Acquire
  | Release
  | AcquireRelease
  | SequentiallyConsistent
  deriving Show

type Atomicity = (SynchronizationScope, MemoryOrdering)

data ComparisonType
  = EQ
  | NE
  | UGT
  | UGE
  | ULT
  | ULE
  | SGT
  | SGE
  | SLT
  | SLE
  deriving (Eq, Show)

data TailCallAttribute
  = Tail
  | MustTail
  | NoTail
  deriving Show

data CallingConvention
  = C
  | Fast
  -- TODO add others as needed
  deriving Show

data IR
  = Add !(Flag NUW) !(Flag NSW) !Operand !Operand
  | Mul !(Flag NUW) !(Flag NSW) !Operand !Operand
  | Sub !(Flag NUW) !(Flag NSW) !Operand !Operand
  | Udiv !(Flag Exact) !Operand !Operand
  | And !Operand !Operand
  | Or !Operand !Operand
  | Trunc !Operand !Type
  | Zext !Operand !Type
  | Bitcast !Operand !Type
  | ICmp !ComparisonType !Operand !Operand
  | PtrToInt !Operand !Type
  | Alloca !Type !(Maybe Operand) !Int
  | GetElementPtr !(Flag Inbounds) !Operand ![Operand]
  | Load !(Flag Volatile) !Operand !(Maybe Atomicity) !Alignment
  | Store !(Flag Volatile) !Operand !Operand !(Maybe Atomicity) !Alignment
  | Phi !(NonEmpty (Operand, Name))
  | Call !(Maybe TailCallAttribute) !CallingConvention !Operand ![Operand]  -- TODO support param attributes
  -- Terminators
  | Ret !(Maybe Operand)
  | Br !Name
  | CondBr !Operand !Name !Name
  | Switch !Operand !Name ![(Operand, Name)]
  | Select !Operand !Operand !Operand
  deriving Show

newtype Terminator
  = Terminator IR
  deriving Show

renderTCA :: Renderer TailCallAttribute
renderTCA buf = \case
  Tail -> buf |># "tail"#
  NoTail -> buf |># "notail"#
  MustTail -> buf |># "musttail"#
{-# INLINABLE renderTCA #-}

renderCC :: Renderer CallingConvention
renderCC buf = \case
  C -> buf |># "ccc"#
  Fast -> buf |># "fastcc"#
{-# INLINABLE renderCC #-}

renderComparisonType :: Renderer ComparisonType
renderComparisonType buf = \case
  EQ  -> buf |># "eq"#
  NE  -> buf |># "ne"#
  UGT -> buf |># "ugt"#
  UGE -> buf |># "uge"#
  ULT -> buf |># "ult"#
  ULE -> buf |># "ule"#
  SGT -> buf |># "sgt"#
  SGE -> buf |># "sge"#
  SLT -> buf |># "slt"#
  SLE -> buf |># "sle"#
{-# INLINABLE renderComparisonType #-}

renderMemoryOrdering :: Renderer MemoryOrdering
renderMemoryOrdering buf = \case
  Unordered              -> buf |># "unordered"#
  Monotonic              -> buf |># "monotonic"#
  Acquire                -> buf |># "acquire"#
  Release                -> buf |># "release"#
  AcquireRelease         -> buf |># "acq_rel"#
  SequentiallyConsistent -> buf |># "seq_cst"#
{-# INLINABLE renderMemoryOrdering #-}

renderSyncScope :: Renderer SynchronizationScope
renderSyncScope buf = \case
  SingleThread ->
    buf |># "syncscope(\"singlethread\")"#
  System ->
    buf
{-# INLINABLE renderSyncScope #-}

renderIR :: Renderer IR
renderIR buf = \case
  Add nuw nsw a b ->
    renderArithBinOp buf "add "# nuw nsw a b
  Mul nuw nsw a b ->
    renderArithBinOp buf "mul "# nuw nsw a b
  Sub nuw nsw a b ->
    renderArithBinOp buf "sub "# nuw nsw a b
  Udiv exact a b ->
    ((((optional exact (buf |># "udiv "#) (|># "exact "#) `renderType` typeOf a)
    |>. ' ') `renderOperand` a) |># ", "#) `renderOperand` b
  And a b ->
    ((((buf |># "and "#) `renderType` typeOf a) |>. ' ') `renderOperand` a |># ", "#) `renderOperand` b
  Or a b ->
    ((((buf |># "or "#) `renderType` typeOf a) |>. ' ') `renderOperand` a |># ", "#) `renderOperand` b
  ICmp cmp a b ->
    (((((buf |># "icmp "#) `renderComparisonType` cmp |>. ' ') `renderType` typeOf a) |>. ' ') `renderOperand` a |># ", "#) `renderOperand` b
  Trunc val to ->
    renderConvertOp buf "trunc "# val to
  Zext val to ->
    renderConvertOp buf "zext "# val to
  Bitcast val to ->
    renderConvertOp buf "bitcast "# val to
  PtrToInt val to ->
    renderConvertOp buf "ptrtoint "# val to
  Alloca ty mNumElems alignment ->
    renderMaybe
      (renderMaybe ((buf |># "alloca "#) `renderType` ty) mNumElems
        (\buf' count -> ((buf' |># ", "#) `renderType` typeOf count |>. ' ') `renderOperand` count))
      (if alignment == 0 then Nothing else Just alignment)
      (\buf' align -> buf' |># ", align "# |>$ align)
  GetElementPtr inbounds pointer indices ->
    case typeOf pointer of
      ty@(PointerType innerTy) ->
        commas (((optional inbounds (buf |># "getelementptr "#) (|># "inbounds "#) `renderType` innerTy |># ", "#) `renderType` ty |>. ' ')
        `renderOperand` pointer |># ", "#) indices prettyIndex
      _ ->
        buf |> error "Operand given to `getelementptr` that is not a pointer!"
    where
      prettyIndex :: Buffer %1 -> Operand -> Buffer
      prettyIndex buf' i = (renderType buf' (typeOf i) |>. ' ') `renderOperand` i
  Load volatile addr atomicity alignment ->
    case atomicity of
      Nothing ->
        withAlignment alignment
          ((((optional volatile (buf |># "load "#) (|># "volatile "#)
            `renderType` resultTy) |># ", "#) `renderType` ptrTy |>. ' ') `renderOperand` addr)
      Just (syncScope, memoryOrdering) ->
        withAlignment alignment
          ((((((optional volatile (buf |># "load atomic "#) (|># "volatile "#)
            `renderType` resultTy) |># ", "#) `renderType` ptrTy |>. ' ') `renderOperand` addr |>. ' ')
            `renderSyncScope` syncScope |>. ' ') `renderMemoryOrdering` memoryOrdering)
    where
      ptrTy = typeOf addr
      resultTy = case ptrTy of
        PointerType ty -> ty
        _ -> error "Malformed AST, expected pointer type."
  Store volatile addr value atomicity alignment ->
    case atomicity of
      Nothing ->
        withAlignment alignment
          ((((optional volatile (buf |># "store "#) (|># "volatile "#) `renderType` ty |>. ' ') `renderOperand` value |># ", "#)
            `renderType` ptrTy |>. ' ') `renderOperand` addr)
      Just (syncScope, memoryOrdering) ->
        withAlignment alignment
          ((((((optional volatile (buf |># "store atomic "#) (|># "volatile "#) `renderType` ty |>. ' ') `renderOperand` value |># ", "#)
            `renderType` ptrTy |>. ' ') `renderOperand` addr |>. ' ') `renderSyncScope` syncScope |>. ' ') `renderMemoryOrdering` memoryOrdering)
    where
      ty = typeOf value
      ptrTy = PointerType ty
  Phi cases@((val, _) :| _) ->
    commas ((buf |># "phi "#) `renderType` typeOf val |>. ' ') (NE.toList cases) renderPhiCase
    where
      renderPhiCase :: Renderer (Operand, Name)
      renderPhiCase buf' (value, name) =
        brackets buf' (\buf'' -> (renderOperand buf'' value |># ", %"#) `renderName` name)
  Call tcAttr cc fn args ->
    (((renderMaybe buf tcAttr (\buf' tca -> renderTCA buf' tca |>. ' ')
      |># "call "#) `renderCC` cc |>. ' ') `renderType` resultType |>. ' ')
      `renderOperand` fn `renderArgs` args
    where
      resultType = case typeOf fn of
        PointerType (FunctionType retTy _) -> retTy
        FunctionType retTy _ -> retTy
        _ -> error "Malformed AST, expected function type."
      renderArgs :: Renderer [Operand]
      renderArgs buf' args' = tupled buf' args' renderArg
      renderArg :: Renderer Operand
      renderArg buf' arg =
        (renderType buf' (typeOf arg) |>. ' ') `renderOperand` arg
  Ret term -> case term of
    Nothing ->
      buf |># "ret void"#
    Just operand ->
      ((buf |># "ret "#) `renderType` typeOf operand |>. ' ') `renderOperand` operand
  Br blockName ->
    (buf |># "br label %"#) `renderName` blockName
  CondBr cond trueLabel falseLabel ->
    (((buf |># "br i1 "#) `renderOperand` cond
      |># ", label %"#) `renderName` trueLabel
      |># ", label %"#) `renderName` falseLabel
  Switch val defaultLabel cases ->
    brackets ((((buf |># "switch "#) `renderType` typeOf val |>. ' ') `renderOperand` val |># ", label %"#) `renderName` defaultLabel |>. ' ')
      (\buf' -> hsep buf' cases renderCase)
    where
      renderCase :: Renderer (Operand, Name)
      renderCase buf' (caseVal, label) =
        ((renderType buf' (typeOf caseVal) |>. ' ') `renderOperand` caseVal |># ", label %"#) `renderName` label
  Select c t f ->
    ((((((buf |># "select "#) `renderType` typeOf c |>. ' ') `renderOperand` c |># ", "#)
      `renderType` typeOf t |>. ' ') `renderOperand` t |># ", "#)
      `renderType` typeOf f |>. ' ') `renderOperand` f
  where
    withAlignment :: Word32 -> Buffer %1 -> Buffer
    withAlignment alignment buf' =
      if alignment == 0
        then buf'
        else buf' |># ", align "# |>$ alignment
{-# INLINABLE renderIR #-}

renderArithBinOp :: Buffer %1 -> Addr# -> Flag NUW -> Flag NSW -> Operand -> Operand -> Buffer
renderArithBinOp buf opName nuw nsw a b =
  (((optional nsw (optional nuw
    (buf |># opName) (|># "nuw "#)) (|># "nsw "#) `renderType` typeOf a) |>. ' ') `renderOperand` a |># ", "#) `renderOperand` b
{-# INLINABLE renderArithBinOp #-}

renderConvertOp :: Buffer %1 -> Addr# -> Operand -> Type -> Buffer
renderConvertOp buf opName val to =
  ((((buf |># opName) `renderType` typeOf val) |>. ' ') `renderOperand` val |># " to "#) `renderType` to
{-# INLINABLE renderConvertOp #-}