{-# LANGUAGE MultiParamTypeClasses, OverloadedStrings #-}
module LLVM.Internal.OrcJIT.CompileOnDemandLayer where
import LLVM.Prelude
import Control.Exception
import Control.Monad.AnyCont
import Control.Monad.IO.Class
import Data.IORef
import Foreign.Ptr
import LLVM.Internal.Coding
import LLVM.Internal.OrcJIT
import LLVM.Internal.OrcJIT.CompileLayer
import LLVM.Internal.Target
import qualified LLVM.Internal.FFI.DataLayout as FFI
import qualified LLVM.Internal.FFI.OrcJIT as FFI
import qualified LLVM.Internal.FFI.OrcJIT.CompileOnDemandLayer as FFI
import qualified LLVM.Internal.FFI.PtrHierarchy as FFI
type PartitioningFn = Ptr FFI.Function -> IO [Ptr FFI.Function]
data JITCompileCallbackManager =
  CallbackMgr !(Ptr FFI.JITCompileCallbackManager)
              !(IO ())
newtype IndirectStubsManagerBuilder =
  StubsMgr (Ptr FFI.IndirectStubsManagerBuilder)
data CompileOnDemandLayer baseLayer =
  CompileOnDemandLayer {
    compileLayer :: !(Ptr FFI.CompileOnDemandLayer),
    dataLayout :: !(Ptr FFI.DataLayout),
    cleanupActions :: !(IORef [IO ()])
  }
  deriving Eq
instance CompileLayer (CompileOnDemandLayer l) where
  getCompileLayer = FFI.upCast . compileLayer
  getDataLayout = dataLayout
  getCleanups = cleanupActions
instance MonadIO m =>
  EncodeM m PartitioningFn (IORef [IO ()] -> IO (FunPtr FFI.PartitioningFn)) where
  encodeM partition = return $ \cleanups -> do
    allocFunPtr
      cleanups
      (FFI.wrapPartitioningFn
         (\f set -> do
           fs <- partition f
           traverse_ (FFI.insertFun set) fs
           return ()))
instance (MonadIO m, MonadAnyCont IO m) =>
         EncodeM m (Maybe (IO ())) (FFI.TargetAddress, IO ()) where
  encodeM Nothing = return (FFI.TargetAddress 0, return ())
  encodeM (Just f) = do
    f' <- anyContToM $ bracketOnError (FFI.wrapErrorHandler f) freeHaskellFunPtr
    return
      ( (FFI.TargetAddress . fromIntegral . ptrToWordPtr . castFunPtrToPtr) f'
      , freeHaskellFunPtr f')
newIndirectStubsManagerBuilder ::
  ShortByteString  ->
  IO IndirectStubsManagerBuilder
newIndirectStubsManagerBuilder triple =
  flip runAnyContT return $ do
    triple' <- encodeM triple
    stubsMgr <- liftIO (FFI.createLocalIndirectStubsManagerBuilder triple')
    return (StubsMgr stubsMgr)
disposeIndirectStubsManagerBuilder :: IndirectStubsManagerBuilder -> IO ()
disposeIndirectStubsManagerBuilder (StubsMgr stubsMgr) =
  FFI.disposeIndirectStubsManagerBuilder stubsMgr
withIndirectStubsManagerBuilder ::
  ShortByteString  ->
  (IndirectStubsManagerBuilder -> IO a) ->
  IO a
withIndirectStubsManagerBuilder triple =
  bracket
    (newIndirectStubsManagerBuilder triple)
    disposeIndirectStubsManagerBuilder
newJITCompileCallbackManager ::
  ShortByteString  ->
  Maybe (IO ())  ->
  IO JITCompileCallbackManager
newJITCompileCallbackManager triple errorHandler = flip runAnyContT return $ do
  triple' <- encodeM triple
  (errorHandler', cleanup) <- encodeM errorHandler
  callbackMgr <- liftIO (FFI.createLocalCompileCallbackManager triple' errorHandler')
  return (CallbackMgr callbackMgr cleanup)
disposeJITCompileCallbackManager :: JITCompileCallbackManager -> IO ()
disposeJITCompileCallbackManager (CallbackMgr mgr cleanup) =
  FFI.disposeCallbackManager mgr >> cleanup
withJITCompileCallbackManager ::
  ShortByteString  ->
  Maybe (IO ())  ->
  (JITCompileCallbackManager -> IO a) ->
  IO a
withJITCompileCallbackManager triple errorHandler =
  bracket
    (newJITCompileCallbackManager triple errorHandler)
    disposeJITCompileCallbackManager
newCompileOnDemandLayer :: CompileLayer l =>
  l ->
  TargetMachine ->
  (Ptr FFI.Function -> IO [Ptr FFI.Function])  ->
  JITCompileCallbackManager ->
  IndirectStubsManagerBuilder ->
  Bool  ->
  IO (CompileOnDemandLayer l)
newCompileOnDemandLayer baseLayer tm partition (CallbackMgr callbackMgr _) (StubsMgr stubsMgr) cloneStubs =
  flip runAnyContT return $ do
    cleanups <- liftIO (newIORef [])
    dl <- createRegisteredDataLayout tm cleanups
    partitionAct <- encodeM partition
    partition' <- liftIO $ partitionAct cleanups
    cloneStubs' <- encodeM cloneStubs
    cl <-
      liftIO
        (FFI.createCompileOnDemandLayer
           (getCompileLayer baseLayer)
           partition'
           callbackMgr
           stubsMgr
           cloneStubs')
    return (CompileOnDemandLayer cl dl cleanups)
withCompileOnDemandLayer ::
  CompileLayer l =>
  l ->
  TargetMachine ->
  (Ptr FFI.Function -> IO [Ptr FFI.Function])  ->
  JITCompileCallbackManager ->
  IndirectStubsManagerBuilder ->
  Bool  ->
  (CompileOnDemandLayer l -> IO a) ->
  IO a
withCompileOnDemandLayer l tm partition callbackMgr stubsMgr cloneStubs =
  bracket
    (newCompileOnDemandLayer l tm partition callbackMgr stubsMgr cloneStubs)
    disposeCompileLayer