{-# LANGUAGE CPP #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE UndecidableInstances #-}
module System.Plugins.MultiStage
(
loadFunWithConfig
, loadFunType
, Config(..)
, defaultConfig
, defaultBuilder
, CallConv(..)
, buildType
, applyTF
, expandTF
, pack
, unpack
, Reference(..)
, Marshal(..)
)
where
import Debug.Trace
import BasicTypes (failed)
#if defined(__GLASGOW_HASKELL__) && __GLASGOW_HASKELL__ >= 800
import GHCi.ObjLink (initObjLinker,loadObj,resolveObjs)
#if defined(__GLASGOW_HASKELL__) && __GLASGOW_HASKELL__ >= 802
import GHCi.ObjLink (ShouldRetainCAFs(..))
#endif
#else
import ObjLink (initObjLinker,loadObj,resolveObjs)
#endif
import Language.Haskell.TH
import Language.Haskell.TH.Desugar
import Data.Int
import Data.Word
import Data.Maybe (mapMaybe)
import Control.Monad
import Control.Applicative
import Foreign.Ptr
import Foreign.C.String (CString,withCString)
import Foreign.Marshal (new,with)
import Foreign.Marshal.Unsafe (unsafeLocalState)
import Foreign.Storable
import System.Info (os)
import System.Process (readProcessWithExitCode)
import System.Directory (doesFileExist, removeFile, createDirectoryIfMissing)
data Config = Config { declWorker :: Config -> Name -> Name -> [Name] -> Type -> [DecQ]
, builder :: Config -> Name -> Q Body
, worker :: Name -> [Name] -> Q Body
, typeFromName :: Name -> Q Type
, mkHSig :: Type -> Q Type
, mkCSig :: Type -> Q Type
, prefix :: String
, suffix :: String
, wdir :: String
, opts :: [String]
, safety :: Safety
}
defaultConfig :: Config
defaultConfig = Config { declWorker = declareWorker
, builder = noBuilder
, worker = noWorker
, typeFromName = loadFunType
, mkHSig = buildType resultInIO
, mkCSig = buildType resultInIO
, prefix = "c_"
, suffix = ""
, wdir = "tmp"
, opts = []
, safety = unsafe
}
noBuilder :: Config -> Name -> Q Body
noBuilder _ _ = normalB [| nullPtr |]
noWorker :: Name -> [Name] -> Q Body
noWorker fun as = normalB $ appsE $ map varE $ fun:as
defaultBuilder :: Config -> Name -> Q Body
defaultBuilder Config{..} name =
normalB [|unsafeLocalState $ do
createDirectoryIfMissing True wdir
compileAndLoad srcname objname []
lookupSymbol symbol
|]
where
base = nameBase name ++ suffix
srcname = wdir ++ "/" ++ base ++ ".c"
objname = wdir ++ "/" ++ base ++ ".o"
symbol = ldprefix ++ base
ldprefix = case os of
"darwin" -> "_"
_ -> ""
resultInIO :: CallConv
resultInIO = CallConv{..}
where
arg = return
res t = [t| IO $(return t) |]
loadFunWithConfig :: Config -> [Name] -> Q [Dec]
loadFunWithConfig conf@Config{..} names = concat <$> mapM go names
where
go name = do
typ <- typeFromName name
let base = prefix ++ nameBase name ++ suffix
let cname = mkName base
let wname = mkName $ base ++ "_worker"
let args = [mkName $ 'v' : show i | i <- [1..(arity typ)]]
sequence $ declWorker conf wname name args typ
++ declareWrapper cname wname args typ
arity :: Type -> Int
arity (AppT (AppT ArrowT _) r) = 1 + arity r
arity _ = 0
loadFunType :: Name -> Q Type
loadFunType name = do
info <- reify name
case info of
#if defined(__GLASGOW_HASKELL__) && __GLASGOW_HASKELL__ >= 800
(VarI _ t _) -> return t
#else
(VarI _ t _ _) -> return t
#endif
_ -> error $ unwords ["loadFun:",show (nameBase name)
,"is not a function:",show info]
declareWorker :: Config -> Name -> Name -> [Name] -> Type -> [DecQ]
declareWorker conf@Config{..} wname name as typ =
[ declareImport conf factory csig
, sigD bname $ appT [t|Ptr|] csig
, funD bname [clause [] (builder conf name) []]
, pragInlD bname NoInline FunLike AllPhases
, sigD rname csig
, funD rname [clause [] (normalB [|$(varE factory) $ castPtrToFunPtr $(varE bname)|]) []]
, sigD wname hsig
, funD wname [clause (map varP as) (worker rname as) []]
, pragInlD wname NoInline FunLike AllPhases
]
where
base = prefix ++ nameBase name ++ suffix
bname = mkName $ base ++ "_builder"
factory = mkName $ base ++ "_factory"
rname = mkName $ base ++ "_raw"
hsig = mkHSig typ
csig = mkCSig typ
declareWrapper :: Name -> Name -> [Name] -> Type -> [DecQ]
declareWrapper cname wname as typ =
[ sigD cname (return typ)
, funD cname [clause (map varP as) (wrapper wname as) [] ]
]
declareImport :: Config -> Name -> TypeQ -> DecQ
declareImport Config{..} name csig =
forImpD cCall safety "dynamic" name [t|FunPtr $(csig) -> $(csig)|]
wrapper :: Name -> [Name] -> Q Body
wrapper workername args = normalB
[|unsafeLocalState $(appsE $ map varE $ workername : args) |]
data CallConv = CallConv { arg :: Type -> Q Type
, res :: Type -> Q Type
}
buildType :: CallConv -> Type -> Q Type
buildType CallConv{..} typ = go typ >>= expandTF
where
go (AppT (AppT ArrowT t) r) = arg t `arrT` go r
go r = res r
arrT t = appT (appT arrowT t)
compileAndLoad :: FilePath -> FilePath -> [String] -> IO ()
compileAndLoad cname oname opts = do
exists <- doesFileExist oname
when exists $ removeFile oname
compileC cname oname opts
#if defined(__GLASGOW_HASKELL__) && __GLASGOW_HASKELL__ >= 802
initObjLinker RetainCAFs
#else
initObjLinker
#endif
_ <- loadObj oname
res <- resolveObjs
#if defined(__GLASGOW_HASKELL__) && __GLASGOW_HASKELL__ >= 800
when (not res) $ error $ "Symbols in " ++ oname ++ " could not be resolved"
#else
when (failed res) $ error $ "Symbols in " ++ oname ++ " could not be resolved"
#endif
compileC :: String -> String -> [String] -> IO ()
compileC srcfile objfile opts = do
let args = [ "-optc -std=c99"
, "-optc -Wall"
, "-w"
, "-c"
]
(_,stdout,stderr) <- readProcessWithExitCode "ghc" (args ++ opts ++ ["-o",objfile,srcfile]) ""
let output = stdout ++ stderr
unless (null output) $ putStrLn output
lookupSymbol :: String -> IO (Ptr a)
lookupSymbol symbol = do
mptr <- withCString symbol _lookupSymbol
when (mptr == nullPtr) $ error $ "Symbol " ++ symbol ++ " not found"
return mptr
foreign import ccall safe "lookupSymbol"
_lookupSymbol :: CString -> IO (Ptr a)
applyTF :: Name -> Type -> Q Type
applyTF tf = expandTF . AppT (ConT tf)
expandTF :: Type -> Q Type
expandTF t = sweeten <$> (desugar t >>= expandType)
pack :: (Reference (Rep a), Marshal a) => a -> IO (Ref (Rep a))
pack a = to a >>= ref
unpack :: (Reference (Rep a), Marshal a) => Ref (Rep a) -> IO a
unpack a = deref a >>= from
class Reference a
where
type Ref a :: *
ref :: a -> IO (Ref a)
default ref :: (a ~ Ref a) => a -> IO (Ref a)
{-# INLINE ref #-}
ref = return
deref :: Ref a -> IO a
default deref :: (a ~ Ref a) => Ref a -> IO a
{-# INLINE deref #-}
deref = return
instance Reference Bool where type Ref Bool = Bool
instance Reference Int8 where type Ref Int8 = Int8
instance Reference Int16 where type Ref Int16 = Int16
instance Reference Int32 where type Ref Int32 = Int32
instance Reference Int64 where type Ref Int64 = Int64
instance Reference Word8 where type Ref Word8 = Word8
instance Reference Word16 where type Ref Word16 = Word16
instance Reference Word32 where type Ref Word32 = Word32
instance Reference Word64 where type Ref Word64 = Word64
instance Reference Float where type Ref Float = Float
instance Reference Double where type Ref Double = Double
class Marshal a
where
type Rep a :: *
to :: a -> IO (Rep a)
default to :: (a ~ Rep a) => a -> IO (Rep a)
{-# INLINE to #-}
to = return
from :: Rep a -> IO a
default from :: (a ~ Rep a) => Rep a -> IO a
{-# INLINE from #-}
from = return
instance Marshal Bool where type Rep Bool = Bool
instance Marshal Int8 where type Rep Int8 = Int8
instance Marshal Int16 where type Rep Int16 = Int16
instance Marshal Int32 where type Rep Int32 = Int32
instance Marshal Int64 where type Rep Int64 = Int64
instance Marshal Word8 where type Rep Word8 = Word8
instance Marshal Word16 where type Rep Word16 = Word16
instance Marshal Word32 where type Rep Word32 = Word32
instance Marshal Word64 where type Rep Word64 = Word64
instance Marshal Float where type Rep Float = Float
instance Marshal Double where type Rep Double = Double