packages feed

ddc-core-llvm-0.4.3.1: DDC/Core/Llvm/Convert/Exp/Case.hs

module DDC.Core.Llvm.Convert.Exp.Case
        (convertCase)
where
import DDC.Core.Llvm.Convert.Exp.Atom
import DDC.Core.Llvm.Convert.Context
import DDC.Core.Llvm.Convert.Base
import DDC.Llvm.Syntax
import DDC.Core.Salt.Platform
import DDC.Data.ListUtils
import Control.Monad
import Data.Maybe
import Data.Sequence                    (Seq, (<|), (|>), (><))
import qualified DDC.Core.Salt          as A
import qualified DDC.Core.Exp           as C
import qualified Data.Sequence          as Seq


-- Case -------------------------------------------------------------------------------------------
convertCase
        :: Context              -- ^ Context of the conversion.
        -> ExpContext           -- ^ Expression context.
        -> Label                -- ^ Label of current block
        -> Seq AnnotInstr       -- ^ Instructions to prepend to initial block.
        -> A.Exp                -- ^ Scrutinee of case expression.
        -> [A.Alt]              -- ^ Alternatives of case expression.
        -> ConvertM (Seq Block)

convertCase ctx ectx label instrs xScrut alts 
 | Just mVar    <- takeLocalV ctx xScrut
 = do
        vScrut' <- mVar

        -- Convert all the alternatives.
        -- If we're in a nested context we'll also get a block to join the 
        -- results of each alternative.
        (alts', blocksJoin) 
         <- convertAlts ctx ectx alts

        -- Determine what default alternative to use for the instruction. 
        (lDefault, blocksDefault)
         <- case last alts' of
                AltDefault l bs -> return (l, bs)
                AltCase _  l bs -> return (l, bs)

        -- Get the alternatives before the default one.
        -- This will fail if there are no alternatives at all.
        altsTable       
         <- case takeInit alts' of
                Nothing -> throw $ ErrorInvalidExp (A.XCase xScrut alts) Nothing
                Just as -> return as

        -- Build the jump table of non-default alts.
        let table       = mapMaybe takeAltCase altsTable
        let blocksTable = join $ fmap altResultBlocks $ Seq.fromList altsTable

        let switchBlock 
                =  Block label
                $  instrs 
                |> (annotNil $ ISwitch (XVar vScrut') lDefault table)

        return  $  switchBlock 
                <| (blocksTable >< blocksDefault >< blocksJoin)

 | otherwise 
 = throw $ ErrorInvalidExp (A.XCase xScrut alts) Nothing


-- Alts -------------------------------------------------------------------------------------------
-- | Convert some case alternatives to LLVM.
convertAlts
        :: Context -> ExpContext
        -> [A.Alt]
        -> ConvertM ([AltResult], Seq Block)

-- Alternatives are at top level.
convertAlts ctx ectx@ExpTop{} alts
 = do   
        alts'   <- mapM (convertAlt ctx ectx) alts
        return  (alts', Seq.empty)

-- If we're doing a branch inside a let-binding we need to add a join
-- point to collect the results from each altenative before continuing
-- on to evaluate the rest.
convertAlts ctx (ExpNest ectx vDst lCont) alts
 = do
        -- Label of the block that does the join.
        lJoin     <- newUniqueLabel "join"

        -- Convert all the alternatives,
        -- assiging their results into separate vars.
        (vDstAlts, alts'@(_:_))
                <- liftM unzip 
                $  mapM (\alt -> do
                        vDst'   <- newUniqueNamedVar "alt" (typeOfVar vDst)
                        alt'    <- convertAlt ctx (ExpNest ectx vDst' lJoin) alt
                        lAlt    <- return (altResultLabel alt')
                        return ((XVar vDst', lAlt), alt'))
                $  alts

        -- A block to join the result from each alternative.
        let blockJoin   
                = Block lJoin
                $ Seq.fromList $ map annotNil
                [ IPhi vDst vDstAlts
                , IBranch lCont ]

        return (alts', Seq.singleton blockJoin)

-- Cannot convert alternative in this context.
convertAlts _ ExpAssign{} alts
 = throw $ ErrorInvalidAlt alts
         $ Just "Cannot convert alternative in this context."


-- Alt --------------------------------------------------------------------------------------------
-- | Convert a case alternative to LLVM.
--
--   This only works for zero-arity constructors.
--   The client should extract the fields of algebraic data objects manually.
convertAlt
        :: Context -> ExpContext
        -> A.Alt
        -> ConvertM AltResult

convertAlt ctx ectx aa
 = let  pp              = contextPlatform ctx
        convBodyM       = contextConvertBody ctx
   in case aa of
        A.AAlt A.PDefault x
         -> do  label   <- newUniqueLabel "default"
                blocks  <- convBodyM ctx ectx Seq.empty label Seq.empty x
                return  $  AltDefault label blocks

        A.AAlt (A.PData C.DaConUnit []) x
         -> do  label   <- newUniqueLabel "alt"
                blocks  <- convBodyM ctx ectx Seq.empty label Seq.empty x
                return  $  AltDefault label blocks

        A.AAlt (A.PData dc []) x
         | Just n       <- A.takeNameOfDaCon dc
         , Just lit     <- convPatName pp n
         -> do  label   <- newUniqueLabel "alt"
                blocks  <- convBodyM ctx ectx Seq.empty label Seq.empty x
                return  $  AltCase lit label blocks

        _ -> throw $ ErrorInvalidAlt [aa] Nothing


-- | Convert a constructor name from a pattern to a LLVM literal.
--
--   Only integral-ish types can be used as patterns, for others 
--   such as Floats we rely on the Lite transform to have expanded
--   cases on float literals into a sequence of boolean checks.
convPatName :: Platform -> A.Name -> Maybe Lit
convPatName pp (A.NamePrimLit lit)
 = case lit of
        A.PrimLitBool True
         -> Just $ LitInt (TInt 1) 1

        A.PrimLitBool False
         -> Just $ LitInt (TInt 1) 0

        A.PrimLitNat  i
         -> Just $ LitInt (TInt (8 * platformAddrBytes pp)) i

        A.PrimLitInt  i
         -> Just $ LitInt (TInt (8 * platformAddrBytes pp)) i

        A.PrimLitWord i bits 
         | elem bits [8, 16, 32, 64]
         -> Just $ LitInt (TInt $ fromIntegral bits) i

        A.PrimLitTag  i
         -> Just $ LitInt (TInt (8 * platformTagBytes pp))  i

        _ -> Nothing

convPatName _ _ 
 = Nothing


-- | Take the label from an `AltResult`.
altResultLabel :: AltResult -> Label
altResultLabel aa
 = case aa of
        AltDefault label _      -> label
        AltCase  _ label _      -> label


-- | Take the blocks from an `AltResult`.
altResultBlocks :: AltResult -> Seq Block
altResultBlocks aa
 = case aa of
        AltDefault _ blocks     -> blocks
        AltCase _ _  blocks     -> blocks


-- | Take the `Lit` and `Label` from an `AltResult`
takeAltCase :: AltResult -> Maybe (Lit, Label)
takeAltCase ac
 = case ac of
        AltCase lit label _     -> Just (lit, label)
        _                       -> Nothing