packages feed

keel-dyn-0.1.0.0: src/Keel/Dyn.hs

-- | Cross-platform runtime loading of native shared libraries.
--
-- This module is the keystone of keel: every native capability (OpenBLAS,
-- ONNX Runtime, ...) is resolved at run time through it, so no keel package
-- carries a build-time C dependency and @cabal install@ can never fail on a
-- missing native library.
--
-- Search-path behaviour:
--
-- * Windows: 'loadLibrary' first tries @LoadLibraryExW@ with
--   @LOAD_LIBRARY_SEARCH_DEFAULT_DIRS@ (application dir, System32, and any
--   directory registered via 'addSearchDir'; for absolute paths also the
--   library's own directory, so multi-DLL packages find their siblings),
--   then falls back to the legacy @LoadLibraryW@ search (PATH, CWD) so
--   bare names on PATH keep working.
-- * POSIX: @dlopen@ semantics (rpath, @LD_LIBRARY_PATH@\/@DYLD_*@, system
--   default dirs). 'addSearchDir' is a documented no-op returning 'False' —
--   POSIX search paths must be set before process start.
--
-- For the full env-var → data-dir → system policy that capability packages
-- use, see "Keel.Dyn.Locate".
--
-- == Building a capability record
--
-- Resolve each function once at load time into a record of 'FunPtr's,
-- invoked through @foreign import ccall \"dynamic\"@ wrappers. Required
-- symbols use 'requireSym' (throws 'DynError'); symbols that may be absent
-- in older library builds use 'resolveOptional', so a missing symbol
-- degrades that one operation instead of failing the whole library:
--
-- > data BlasOps = BlasOps
-- >   { ddot   :: FunPtr CblasDdotT           -- required
-- >   , dgemm  :: FunPtr CblasDgemmT          -- required
-- >   , sbgemm :: Maybe (FunPtr CblasSbgemmT) -- bfloat16: newer builds only
-- >   }
-- >
-- > openBlas :: Library -> IO (Capability BlasOps)
-- > openBlas lib = do
-- >   ops <- BlasOps
-- >     <$> requireSym lib "cblas_ddot"
-- >     <*> requireSym lib "cblas_dgemm"
-- >     <*> resolveOptional lib "cblas_sbgemm"
-- >   version <- queryVersion lib   -- e.g. via openblas_get_config
-- >   pure (Capability lib version ops)
module Keel.Dyn
  ( -- * Libraries
    Library
  , libraryPath
  , loadLibrary
  , loadLibraryGlobal
  , closeLibrary
  , withLibrary

    -- * Symbols
  , resolveSym
  , resolveOptional
  , requireSym

    -- * Capability records
  , Capability (..)

    -- * Search path
  , addSearchDir

    -- * Errors
  , DynError (..)
  ) where

import Control.Exception (throwIO)
import Foreign.Ptr (FunPtr)

import Keel.Dyn.Platform

-- | Like 'resolveSym' but throws the 'DynError' as an exception. Intended
-- for assembling capability records applicatively (see the module header).
requireSym :: Library -> String -> IO (FunPtr a)
requireSym lib name = resolveSym lib name >>= either throwIO pure

-- | A loaded native capability: the library it came from, a version tag
-- queried from the library itself (shown by @keel doctor@), and a
-- caller-defined record of resolved 'FunPtr's.
data Capability ops = Capability
  { capLibrary :: Library
  , capVersion :: String
  , capOps :: ops
  }