packages feed

signals-0.0.0.1: Backend/C.hs

{-# LANGUAGE GADTs                 #-}
{-# LANGUAGE TypeFamilies          #-}
{-# LANGUAGE QuasiQuotes           #-}
{-# LANGUAGE FlexibleContexts      #-}
{-# LANGUAGE FlexibleInstances     #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables   #-}
{-# LANGUAGE ConstraintKinds       #-}
{-# LANGUAGE UndecidableInstances  #-}

module Backend.C where

import Control.Applicative
import Control.Monad.State
import Control.Monad.Exception
import Control.Monad.Operational
import Data.Typeable
import Data.IORef
import Data.Array.IO.Safe
import qualified System.IO as IO
import qualified Text.Printf as Printf

import Language.C.Quote.C
import qualified Language.C.Syntax as C
import qualified Data.Set          as Set

import Text.PrettyPrint.Mainland

import Core
import Interpretation

import Backend.C.Monad
import Examples.Simple.Expr

--------------------------------------------------------------------------------
-- * Compilation of Commands
--------------------------------------------------------------------------------

compile :: CompCMD cmd => Program cmd a -> C a
compile = interpretWithMonad compCMD

--------------------------------------------------------------------------------

instance CompCMD (CMD Expr)
  where
    compCMD = compCMD'

compCMD' :: CMD Expr a -> C a

-- ^ File handling
compCMD' (Open path) = do
  addInclude "<stdio.h>"
  addInclude "<stdlib.h>"
  sym <- gensym "v"
  addLocal [cdecl| typename FILE * $id:sym; |]
  addStm   [cstm| $id:sym = fopen($id:path', "r+"); |]
  return $ HandleComp sym
  where path' = show path
compCMD' (Close (HandleComp h)) = do
  addStm [cstm| fclose($id:h); |]
compCMD' (Put (HandleComp h) exp) = do
  v <- compExp exp
  addStm [cstm| fprintf($id:h, "%f ", $v); |]
compCMD' (Get (HandleComp h)) = do
  sym <- gensym "v"
  addLocal [cdecl| float $id:sym; |]
  addStm   [cstm| fscanf($id:h, "%f", &$id:sym); |]
  return $ varExp sym
compCMD' (Eof (HandleComp h)) = do
  addInclude "<stdbool.h>"
  sym <- gensym "v"
  addLocal [cdecl| int $id:sym; |]
  addStm   [cstm| $id:sym = feof($id:h); |]
  return $ varExp sym

-- ^ Mutable refrences
compCMD' cmd@(InitRef) = do
  let t = compTypeRep (typeOfP3 cmd)
  sym <- gensym "r"
  addLocal [cdecl| $ty:t $id:sym; |]
  return $ RefComp sym
compCMD' cmd@(NewRef exp) = do
  let t = compTypeRep (typeOfP3 cmd)
  sym <- gensym "r"
  v   <- compExp exp
  addLocal [cdecl| $ty:t $id:sym; |]
  addStm   [cstm| $id:sym = $v; |]
  return $ RefComp sym
compCMD' cmd@(GetRef (RefComp ref)) = do
  let t = compTypeRep (typeOfP2 cmd)
  sym <- gensym "r"
  addLocal [cdecl| $ty:t $id:sym; |]
  addStm   [cstm| $id:sym = $id:ref; |]
  return $ varExp sym
compCMD' (SetRef (RefComp ref) exp) = do
  v <- compExp exp
  addStm [cstm| $id:ref = $v; |]

-- ^ Mutable arrays
compCMD' (NewArr size init) = do
  addInclude "<string.h>"
  sym <- gensym "a"
  v   <- compExp size
  i   <- compExp init -- todo: use this with memset
  addLocal [cdecl| float $id:sym[ $v ]; |] -- todo: get real type
  addStm   [cstm| memset($id:sym, $i, sizeof( $id:sym )); |]
  return $ ArrComp sym
compCMD' (GetArr expi (ArrComp arr)) = do
  sym <- gensym "a"
  i   <- compExp expi
  addLocal [cdecl| float $id:sym; |] -- todo: get real type
  addStm   [cstm| $id:sym = $id:arr[ $i ]; |]
  return $ varExp sym
compCMD' (SetArr expi expv (ArrComp arr)) = do
  v <- compExp expv
  i <- compExp expi
  addStm [cstm| $id:arr[ $i ] = $v; |]

-- ^ Unsafe
compCMD' (UnsafeGetRef (RefComp ref)) =
  return $ varExp ref
compCMD' (UnsafeGetArr expi (ArrComp arr)) =
  undefined

-- ^ Control structures
compCMD' (If b t f) = do
  b' <- compExp b :: C C.Exp
  ct <- inNewBlock_ $ compile t
  cf <- inNewBlock_ $ compile f
  case null cf of
    True  -> addStm [cstm| if ($(b')) {$items:ct} |]
    False -> addStm [cstm| if ($(b')) {$items:ct} else {$items:cf} |]
  return ()
compCMD' (While b t) = do
  b'  <- compile b  :: C (Expr Bool)
  b'' <- compExp b' :: C C.Exp
  ct <- inNewBlock_ $ compile t
  addStm [cstm| while ($(b'')) {$items:ct} |]
  return ()
    -- todo: the b program should be re-executed at the end of each iteration
compCMD' Break = addStm [cstm| break; |]
compCMD' GetTime = do
    addInclude "<sys/time.h>"
    addInclude "<sys/resource.h>"
    addGlobal getTimeDef
    sym <- gensym "t"
    addLocal [cdecl| double $id:sym; |]
    addStm   [cstm| $id:sym = get_time(); |]
    return $ varExp sym
compCMD' (Printf format a) = do
    addInclude "<stdio.h>"
    let format' = show format
    a' <- compExp a
    addStm [cstm| printf($id:format', $exp:a'); |]

--------------------------------------------------------------------------------
-- ** Helpers

compTypeRep :: TypeRep -> C.Type
compTypeRep trep = case show trep of
    "Bool"  -> [cty| int   |]
    "Int"   -> [cty| int   |]  -- todo: should only use fix-width Haskell ints
    "Float" -> [cty| float |]
    x       -> error x

typeOfP1 :: forall proxy a. Typeable a => proxy a -> TypeRep
typeOfP1 _ = typeOf (undefined :: a)

typeOfP2 :: forall proxy1 proxy2 a. Typeable a => proxy1 (proxy2 a) -> TypeRep
typeOfP2 p = typeOf (undefined :: a)

typeOfP3 :: forall proxy1 proxy2 proxy3 a. Typeable a => proxy1 (proxy2 (proxy3 a)) -> TypeRep
typeOfP3 p = typeOf (undefined :: a)

getTimeDef :: C.Definition
getTimeDef = [cedecl|
// From http://stackoverflow.com/questions/2349776/how-can-i-benchmark-c-code-easily
double get_time()
{
    struct timeval t;
    struct timezone tzp;
    gettimeofday(&t, &tzp);
    return t.tv_sec + t.tv_usec*1e-6;
}
|]

--------------------------------------------------------------------------------
-- * Compilation of constructs
--------------------------------------------------------------------------------

instance CompCMD cmd => CompCMD (Construct cmd)
  where
    compCMD = compConstruct
    
compConstruct :: CompCMD cmd => Construct cmd a -> C a
compConstruct (Function fun body) = inFunction fun $ compile body

--------------------------------------------------------------------------------
-- ** Run Functions

cgen :: CompCMD cmd => Program cmd a -> IO Doc
cgen = cgen' Flags

cgen' :: CompCMD cmd => Flags -> Program cmd a -> IO Doc
cgen' flags ma = do
    (_,cenv) <- runC (compile ma) (defaultCEnv flags)
    return $ ppr $ cenvToCUnit cenv

genMain :: CompCMD cmd => Program cmd a -> IO Doc
genMain prog = do
    (_,cenv) <- runC main (defaultCEnv Flags)
    return $ ppr $ cenvToCUnit cenv
  where
    main = do
      (params,items) <- inNewFunction $ compile prog >> addStm [cstm| return 0; |]
      addGlobal [cedecl| int main($params:params){ $items:items }|]

--------------------------------------------------------------------------------
-- * Evaluation of programs
--------------------------------------------------------------------------------

runProgram :: ( EvalExp exp
              , LitPred exp Bool
              , LitPred exp Float)
           => Program (CMD exp) a
           -> IO a
runProgram = interpretWithMonad runCMD

--------------------------------------------------------------------------------

readWord :: IO.Handle -> IO String
readWord h = do
    eof <- IO.hIsEOF h
    if eof
      then return ""
      else do
          c  <- IO.hGetChar h
          cs <- readWord h
          return (c:cs)

--------------------------------------------------------------------------------

runCMD :: (EvalExp exp, LitPred exp Bool, LitPred exp Float) => CMD exp a -> IO a
runCMD (Open path)            = fmap HandleEval $ IO.openFile path IO.ReadWriteMode
runCMD (Close (HandleEval h)) = IO.hClose h
runCMD (Put (HandleEval h) a) = IO.hPrint h (evalExp a)
runCMD (Get (HandleEval h))   = do
    w <- readWord h
    case reads w of
        [(f,"")] -> return $ litExp f
        _        -> error "runCMD: Get: no parse"
runCMD (Eof (HandleEval h)) = fmap litExp $ IO.hIsEOF h

runCMD (InitRef)              = fmap RefEval $ newIORef undefined
runCMD (NewRef a)             = fmap RefEval $ newIORef a
runCMD (GetRef (RefEval r))   = readIORef r
runCMD (SetRef (RefEval r) a) = writeIORef r a

runCMD (NewArr i a)               = fmap ArrEval $ newArray (0, fromIntegral (evalExp i) - 1) a
runCMD (GetArr i (ArrEval arr))   = readArray arr (fromIntegral (evalExp i))
runCMD (SetArr i a (ArrEval arr)) = writeArray arr (fromIntegral (evalExp i)) a

runCMD (UnsafeGetRef (RefEval r)) = readIORef r

runCMD (If c t f)
    | evalExp c = runProgram t
    | otherwise = runProgram f
runCMD (While cond body) = do
    cond' <- runProgram cond
    if evalExp cond'
      then runProgram body >> runCMD (While cond body)
      else return ()
runCMD Break = error "runCMD: Break not implemented"

runCMD (Printf format a) = Printf.printf format (show $ evalExp a)