llvm-party-12.1.0: src/LLVM/Internal/OrcJIT.hs
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
module LLVM.Internal.OrcJIT where
import LLVM.Prelude
import Control.Exception
import Control.Monad.AnyCont
import Control.Monad.IO.Class
import Data.Bits
import Data.IORef
import Foreign.C.Types
import Foreign.C.String
import Foreign.Ptr
import LLVM.Internal.Coding
import LLVM.Internal.Module (Module, readModule)
import LLVM.Internal.Target (TargetMachine(..))
import qualified LLVM.Internal.FFI.LLVMCTypes as FFI
import qualified LLVM.Internal.FFI.ShortByteString as SBS
import qualified LLVM.Internal.FFI.DataLayout as FFI
import qualified LLVM.Internal.FFI.OrcJIT as FFI
import qualified LLVM.Internal.FFI.Target as FFI
--------------------------------------------------------------------------------
-- ExecutionSession
--------------------------------------------------------------------------------
data ExecutionSession = ExecutionSession {
sessionPtr :: !(Ptr FFI.ExecutionSession),
sessionCleanups :: !(IORef [IO ()])
}
-- | Create a new `ExecutionSession`.
createExecutionSession :: IO ExecutionSession
createExecutionSession = ExecutionSession <$> FFI.createExecutionSession <*> newIORef []
-- | Dispose of an `ExecutionSession`. This should be called when the
-- `ExecutionSession` is not needed anymore.
disposeExecutionSession :: ExecutionSession -> IO ()
disposeExecutionSession (ExecutionSession es cleanups) = do
FFI.endSession es
sequence_ =<< readIORef cleanups
FFI.disposeExecutionSession es
-- | `bracket`-style wrapper around `createExecutionSession` and
-- `disposeExecutionSession`.
withExecutionSession :: (ExecutionSession -> IO a) -> IO a
withExecutionSession = bracket createExecutionSession disposeExecutionSession
--------------------------------------------------------------------------------
-- JITSymbol
--------------------------------------------------------------------------------
-- | Contrary to the C++ interface, we do not store the HasError flag
-- here. Instead decoding a JITSymbol produces a sumtype based on
-- whether that flag is set or not.
data JITSymbolFlags =
JITSymbolFlags {
jitSymbolWeak :: !Bool -- ^ Is this a weak symbol?
, jitSymbolCommon :: !Bool -- ^ Is this a common symbol?
, jitSymbolAbsolute :: !Bool
-- ^ Is this an absolute symbol? This will cause LLVM to use
-- absolute relocations for the symbol even in position
-- independent code.
, jitSymbolExported :: !Bool -- ^ Is this symbol exported?
, jitSymbolCallable :: !Bool
, jitSymbolMaterializationSideEffectsOnly :: !Bool
}
deriving (Show, Eq, Ord)
defaultJITSymbolFlags :: JITSymbolFlags
defaultJITSymbolFlags = JITSymbolFlags False False False False False False
data JITSymbol =
JITSymbol {
jitSymbolAddress :: !WordPtr, -- ^ The address of the symbol. If
-- you’ve looked up a function, you
-- need to cast this to a 'FunPtr'.
jitSymbolFlags :: !JITSymbolFlags -- ^ The flags of this symbol.
}
deriving (Show, Eq, Ord)
data JITSymbolError = JITSymbolError ShortByteString
deriving (Show, Eq)
instance Monad m => EncodeM m JITSymbolFlags FFI.JITSymbolFlags where
encodeM f = return $ foldr1 (.|.) [
if a f
then b
else 0
| (a,b) <- [
(jitSymbolWeak, FFI.jitSymbolFlagsWeak),
(jitSymbolCommon, FFI.jitSymbolFlagsCommon),
(jitSymbolAbsolute, FFI.jitSymbolFlagsAbsolute),
(jitSymbolExported, FFI.jitSymbolFlagsExported),
(jitSymbolCallable, FFI.jitSymbolFlagsCallable),
(jitSymbolMaterializationSideEffectsOnly, FFI.jitSymbolFlagsMaterializationSideEffectsOnly)
]
]
instance Monad m => DecodeM m JITSymbolFlags FFI.JITSymbolFlags where
decodeM f =
return $ JITSymbolFlags {
jitSymbolWeak = FFI.jitSymbolFlagsWeak .&. f /= 0,
jitSymbolCommon = FFI.jitSymbolFlagsCommon .&. f /= 0,
jitSymbolAbsolute = FFI.jitSymbolFlagsAbsolute .&. f /= 0,
jitSymbolExported = FFI.jitSymbolFlagsExported .&. f /= 0,
jitSymbolCallable = FFI.jitSymbolFlagsCallable .&. f /= 0,
jitSymbolMaterializationSideEffectsOnly = FFI.jitSymbolFlagsMaterializationSideEffectsOnly .&. f /= 0
}
instance Monad m => EncodeM m MangledSymbol (Ptr FFI.SymbolStringPtr) where
encodeM (MangledSymbol p) = return p
instance (Monad m, MonadIO m, MonadAnyCont IO m) => EncodeM m JITSymbol (Ptr FFI.JITEvaluatedSymbol) where
encodeM (JITSymbol addr flags) = do
flags' <- encodeM flags
anyContToM $ bracket
(FFI.createJITEvaluatedSymbol (FFI.TargetAddress $ fromIntegral addr) flags')
FFI.disposeJITEvaluatedSymbol
instance (MonadIO m, MonadAnyCont IO m) =>
DecodeM m (Either JITSymbolError JITSymbol) (Ptr FFI.ExpectedJITEvaluatedSymbol) where
decodeM expectedSym = do
errMsg <- alloca
FFI.TargetAddress addr <- liftIO $ FFI.getExpectedSymbolAddress expectedSym errMsg
rawFlags <- liftIO (FFI.getExpectedSymbolFlags expectedSym)
if addr == 0 || (rawFlags .&. FFI.jitSymbolFlagsHasError /= 0)
then do
errMsg <- decodeM errMsg
pure (Left (JITSymbolError errMsg))
else do
flags <- decodeM rawFlags
pure (Right (JITSymbol (fromIntegral addr) flags))
--------------------------------------------------------------------------------
-- JITDylib
--------------------------------------------------------------------------------
newtype JITDylib = JITDylib (Ptr FFI.JITDylib)
-- | Create a new 'JITDylib' with the given name.
createJITDylib :: ExecutionSession -> ShortByteString -> IO JITDylib
createJITDylib (ExecutionSession es _) name =
SBS.useAsCString name $ fmap JITDylib . FFI.createJITDylib es
-- NB: JITDylib unloading is WIP (at least according to some old-looking docs)
-- | Adds a 'JITDylib' definition generator that looks up missing symbols in
-- the namespace of the current process.
addDynamicLibrarySearchGeneratorForCurrentProcess :: IRLayer l => l -> JITDylib -> IO ()
addDynamicLibrarySearchGeneratorForCurrentProcess compileLayer (JITDylib dylib) =
FFI.addDynamicLibrarySearchGeneratorForCurrentProcess dylib (getDataLayout compileLayer)
-- | Adds a 'JITDylib' definition generator that looks up missing symbols in
-- the namespace of a shared library located at the specified 'FilePath'.
addDynamicLibrarySearchGenerator :: IRLayer l => l -> JITDylib -> FilePath -> IO ()
addDynamicLibrarySearchGenerator compileLayer (JITDylib dylib) s = withCString s $ \cStr ->
FFI.addDynamicLibrarySearchGenerator dylib (getDataLayout compileLayer) cStr
defineAbsoluteSymbols :: JITDylib -> [(MangledSymbol, JITSymbol)] -> IO ()
defineAbsoluteSymbols (JITDylib dylib) symList =
runAnyContT' return $ do
(nsyms, symStrPtrPtr) :: (CUInt, Ptr (Ptr FFI.SymbolStringPtr)) <- encodeM symNames
(_, symValPtrPtr) :: (CUInt, Ptr (Ptr FFI.JITEvaluatedSymbol)) <- encodeM symVals
liftIO $ FFI.defineAbsoluteSymbols dylib nsyms symStrPtrPtr symValPtrPtr
where (symNames, symVals) = unzip symList
-- | Looks up an (unmangled) symbol name in the given 'JITDylib'.
--
-- The symbol is expected to have been added to the 'JITDylib' by the same 'IRLayer'
-- as specified in this function. Using a different 'IRLayer' can cause the lookup
-- to fail due to differences in mangling schemes.
lookupSymbol :: IRLayer l => ExecutionSession -> l -> JITDylib -> ShortByteString -> IO (Either JITSymbolError JITSymbol)
lookupSymbol (ExecutionSession es _) irl (JITDylib dylib) name = SBS.useAsCString name $ \nameStr ->
runAnyContT' return $ do
symbol <- anyContToM $ bracket
(FFI.lookupSymbol es dylib (getMangler irl) nameStr) FFI.disposeExpectedJITEvaluatedSymbol
decodeM symbol
--------------------------------------------------------------------------------
-- ThreadSafeContext
--------------------------------------------------------------------------------
newtype ThreadSafeContext = ThreadSafeContext (Ptr FFI.ThreadSafeContext)
-- | Create a 'ThreadSafeContext'
createThreadSafeContext :: IO ThreadSafeContext
createThreadSafeContext = ThreadSafeContext <$> FFI.createThreadSafeContext
-- | Dispose of a 'ThreadSafeContext'
disposeThreadSafeContext :: ThreadSafeContext -> IO ()
disposeThreadSafeContext (ThreadSafeContext ctx) = FFI.disposeThreadSafeContext ctx
-- | 'bracket'-style wrapper around 'createThreadSafeContext'
-- and 'disposeThreadSafeContext'.
withThreadSafeContext :: (ThreadSafeContext -> IO a) -> IO a
withThreadSafeContext = bracket createThreadSafeContext disposeThreadSafeContext
--------------------------------------------------------------------------------
-- ThreadSafeModule
--------------------------------------------------------------------------------
newtype ThreadSafeModule = ThreadSafeModule (Ptr FFI.ThreadSafeModule)
-- | Create a 'ThreadSafeModule' with the same content as the input 'Module'.
--
-- The module will get cloned into a fresh LLVM context. The lifetime of the
-- new context is bound to the lifetime of the returned 'ThreadSafeModule'.
cloneAsThreadSafeModule :: Module -> IO ThreadSafeModule
cloneAsThreadSafeModule m = do
mPtr <- readModule m
ThreadSafeModule <$> FFI.cloneAsThreadSafeModule mPtr
-- | Dispose of a 'ThreadSafeModule'.
disposeThreadSafeModule :: ThreadSafeModule -> IO ()
disposeThreadSafeModule (ThreadSafeModule m) = FFI.disposeThreadSafeModule m
-- | 'bracket'-style wrapper around 'cloneAsThreadSafeModule'
-- and 'disposeThreadSafeModule'.
withClonedThreadSafeModule :: Module -> (ThreadSafeModule -> IO a) -> IO a
withClonedThreadSafeModule m = bracket (cloneAsThreadSafeModule m) disposeThreadSafeModule
--------------------------------------------------------------------------------
-- ObjectLayer + RTDyldObjectLinkingLayer
--------------------------------------------------------------------------------
-- | A type class implemented by the different OrcJIT object layers.
--
-- See e.g. 'RTDyldObjectLinkingLayer'.
class ObjectLayer l where
getObjectLayer :: l -> Ptr FFI.ObjectLayer
data RTDyldObjectLinkingLayer = RTDyldObjectLinkingLayer !(Ptr FFI.ObjectLayer)
instance ObjectLayer RTDyldObjectLinkingLayer where
getObjectLayer (RTDyldObjectLinkingLayer ol) = ol
-- | Create a new 'RTDyldObjectLinkingLayer'.
--
-- The layer will get automatically disposed along with its ExecutionSession.
createRTDyldObjectLinkingLayer :: ExecutionSession -> IO RTDyldObjectLinkingLayer
createRTDyldObjectLinkingLayer (ExecutionSession es cleanups) = do
ol <- FFI.createRTDyldObjectLinkingLayer es
modifyIORef' cleanups (FFI.disposeObjectLayer ol :)
return $ RTDyldObjectLinkingLayer ol
data ObjectLinkingLayer = ObjectLinkingLayer !(Ptr FFI.ObjectLayer)
instance ObjectLayer ObjectLinkingLayer where
getObjectLayer (ObjectLinkingLayer ol) = ol
createObjectLinkingLayer :: ExecutionSession -> IO ObjectLinkingLayer
createObjectLinkingLayer (ExecutionSession es cleanups) = do
ol <- FFI.createObjectLinkingLayer es
modifyIORef' cleanups (FFI.disposeObjectLayer ol :)
return $ ObjectLinkingLayer ol
addObjectFile :: ObjectLayer l => l -> JITDylib -> FilePath -> IO ()
addObjectFile ol (JITDylib dylib) path = do
withCString path $ \cStr ->
FFI.objectLayerAddObjectFile (getObjectLayer ol) dylib cStr
--------------------------------------------------------------------------------
-- IRLayer + IRCompileLayer
--------------------------------------------------------------------------------
-- | A mangled symbol name. Valid only for as long as the IRLayer that created it.
newtype MangledSymbol = MangledSymbol (Ptr FFI.SymbolStringPtr)
-- | A type class implemented by the different OrcJIT IR layers.
--
-- See e.g. 'IRCompileLayer'.
class IRLayer l where
getIRLayer :: l -> Ptr FFI.IRLayer
getDataLayout :: l -> Ptr FFI.DataLayout
getMangler :: l -> Ptr FFI.MangleAndInterner
-- | Add a 'Module' to the specified 'JITDylib'.
--
-- The specified 'IRLayer' will be responsible for compiling the symbols
-- present in the module. The module itself is consumed and __should not be used again__.
addModule :: IRLayer l => ThreadSafeModule -> JITDylib -> l -> IO ()
addModule (ThreadSafeModule m) (JITDylib dylib) irl =
FFI.irLayerAddModule m dylib (getDataLayout irl) (getIRLayer irl)
mangleSymbol :: IRLayer l => l -> ShortByteString -> IO MangledSymbol
mangleSymbol irl name = SBS.useAsCString name $ \namePtr ->
MangledSymbol <$> FFI.mangleSymbol (getMangler irl) namePtr
disposeMangledSymbol :: MangledSymbol -> IO ()
disposeMangledSymbol (MangledSymbol symbol) = FFI.disposeMangledSymbol symbol
withMangledSymbol :: IRLayer l => l -> ShortByteString -> (MangledSymbol -> IO a) -> IO a
withMangledSymbol irl name = bracket (mangleSymbol irl name) disposeMangledSymbol
-- | An IR layer that compiles the symbols in a module eagerly.
data IRCompileLayer = IRCompileLayer !(Ptr FFI.IRLayer) !(Ptr FFI.DataLayout) !(Ptr FFI.MangleAndInterner)
instance IRLayer IRCompileLayer where
getIRLayer (IRCompileLayer cl _ _) = cl
getDataLayout (IRCompileLayer _ dl _) = dl
getMangler (IRCompileLayer _ _ mg) = mg
-- | Create a new 'IRCompileLayer'.
--
-- The layer will get automatically disposed along with its ExecutionSession.
createIRCompileLayer :: ObjectLayer l => ExecutionSession -> l -> TargetMachine -> IO IRCompileLayer
createIRCompileLayer (ExecutionSession es cleanups) ol (TargetMachine tm) = do
dl <- FFI.createTargetDataLayout tm
modifyIORef' cleanups (FFI.disposeDataLayout dl :)
mg <- FFI.createMangleAndInterner es dl
modifyIORef' cleanups (FFI.disposeMangleAndInterner mg :)
cl <- FFI.createIRCompileLayer es (getObjectLayer ol) tm
modifyIORef' cleanups (FFI.disposeIRLayer cl :)
return $ IRCompileLayer cl dl mg