module LLVM.Internal.OrcJIT.IRCompileLayer where

import LLVM.Prelude

import Control.Exception
import Control.Monad.AnyCont
import Control.Monad.IO.Class
import Data.IORef
import Foreign.Ptr

import qualified LLVM.Internal.FFI.DataLayout as FFI
import qualified LLVM.Internal.FFI.OrcJIT.CompileLayer as FFI
import qualified LLVM.Internal.FFI.OrcJIT.IRCompileLayer as FFI
import qualified LLVM.Internal.FFI.PtrHierarchy as FFI
import LLVM.Internal.OrcJIT
import LLVM.Internal.OrcJIT.CompileLayer
import LLVM.Internal.OrcJIT.LinkingLayer (LinkingLayer(..), getLinkingLayer)
import LLVM.Internal.Target

-- | 'IRCompileLayer' compiles modules immediately when they are
-- added. It parametrized by a 'LinkingLayer' which handles linking of
-- the generated object files.
data IRCompileLayer linkingLayer =
  IRCompileLayer {
    compileLayer :: !(Ptr FFI.IRCompileLayer),
    dataLayout :: !(Ptr FFI.DataLayout),
    cleanupActions :: !(IORef [IO ()])
  }
  deriving Eq

instance CompileLayer (IRCompileLayer l) where
  getCompileLayer = FFI.upCast . compileLayer
  getDataLayout = dataLayout
  getCleanups = cleanupActions

-- | Create a new 'IRCompileLayer'.
--
-- When the layer is no longer needed, it should be disposed using 'disposeCompileLayer.
newIRCompileLayer :: LinkingLayer l => l -> TargetMachine -> IO (IRCompileLayer l)
newIRCompileLayer linkingLayer (TargetMachine tm) = flip runAnyContT return $ do
  cleanups <- liftIO (newIORef [])
  dl <- createRegisteredDataLayout (TargetMachine tm) cleanups
  cl <- anyContToM $
    bracketOnError
      (FFI.createIRCompileLayer (getLinkingLayer linkingLayer) tm)
      (FFI.disposeCompileLayer . FFI.upCast)
  return (IRCompileLayer cl dl cleanups)

-- | 'bracket'-style wrapper around 'newIRCompileLayer' and 'disposeCompileLayer'.
withIRCompileLayer :: LinkingLayer l => l -> TargetMachine -> (IRCompileLayer l -> IO a) -> IO a
withIRCompileLayer linkingLayer tm =
  bracket (newIRCompileLayer linkingLayer tm) disposeCompileLayer