nova-nix-0.4.0.0: src/Nix/Eval/CAttrSet.hs
-- | C-backed sorted attribute set via FFI.
--
-- Wraps @cbits/nn_attrset.c@ — a contiguous sorted array of
-- @(nn_symbol_t, void*)@ pairs. Replaces Haskell's @Data.Map.Strict@
-- for attribute sets, cutting per-entry overhead from ~48 bytes
-- (Map.Bin node) to ~12 bytes (4-byte symbol + 8-byte pointer).
--
-- Construction is two-phase: insert entries, then freeze (sort + dedup).
-- After freeze, lookup is O(log n) binary search over contiguous memory.
--
-- Values are opaque @CThunkPtr@ handles stored as @void*@ on the C side.
-- The C side never dereferences them. Thunk lifetimes are managed by
-- the thunk arena; attribute set cleanup only frees the key\/value arrays.
module Nix.Eval.CAttrSet
( -- * Opaque handle
CAttrSet,
-- * Lifecycle
cattrsetNew,
-- * Construction
cattrsetInsert,
cattrsetFreeze,
-- * Query
cattrsetLookup,
cattrsetIndex,
cattrsetGetValue,
cattrsetSetValue,
cattrsetGetKey,
-- * Bulk access
cattrsetSize,
cattrsetKeys,
-- * Set operations
cattrsetUnion,
cattrsetRemoveKeys,
)
where
import Data.Word (Word32)
import Foreign.C.Types (CInt (..))
import Foreign.Marshal.Array (peekArray, withArray)
import Foreign.Ptr (Ptr, castPtr, nullPtr)
import Nix.Eval.CThunk (CThunkPtr)
import Nix.Eval.Symbol (Symbol (..))
-- | Opaque handle to a C-allocated attribute set.
-- The Haskell side holds a raw pointer; the C side owns the memory.
data NnAttrSet
-- | Convenience alias.
type CAttrSet = Ptr NnAttrSet
-- ---------------------------------------------------------------------------
-- FFI imports (all unsafe — no callbacks, fast data access)
-- ---------------------------------------------------------------------------
foreign import ccall unsafe "nn_attrset_new"
c_nn_attrset_new :: Word32 -> IO CAttrSet
foreign import ccall unsafe "nn_attrset_insert"
c_nn_attrset_insert :: CAttrSet -> Word32 -> Ptr () -> IO ()
foreign import ccall unsafe "nn_attrset_freeze"
c_nn_attrset_freeze :: CAttrSet -> IO ()
foreign import ccall unsafe "nn_attrset_lookup"
c_nn_attrset_lookup :: CAttrSet -> Word32 -> IO (Ptr ())
foreign import ccall unsafe "nn_attrset_index"
c_nn_attrset_index :: CAttrSet -> Word32 -> IO CInt
foreign import ccall unsafe "nn_attrset_set_value"
c_nn_attrset_set_value :: CAttrSet -> Word32 -> Ptr () -> IO ()
foreign import ccall unsafe "nn_attrset_get_value"
c_nn_attrset_get_value :: CAttrSet -> Word32 -> IO (Ptr ())
foreign import ccall unsafe "nn_attrset_get_key"
c_nn_attrset_get_key :: CAttrSet -> Word32 -> IO Word32
foreign import ccall unsafe "nn_attrset_size"
c_nn_attrset_size :: CAttrSet -> IO Word32
foreign import ccall unsafe "nn_attrset_keys_ptr"
c_nn_attrset_keys_ptr :: CAttrSet -> IO (Ptr Word32)
foreign import ccall unsafe "nn_attrset_union"
c_nn_attrset_union :: CAttrSet -> CAttrSet -> IO CAttrSet
foreign import ccall unsafe "nn_attrset_remove_keys"
c_nn_attrset_remove_keys :: CAttrSet -> Ptr Word32 -> Word32 -> IO CAttrSet
-- ---------------------------------------------------------------------------
-- Lifecycle
-- ---------------------------------------------------------------------------
-- | Allocate a new empty attribute set with the given capacity hint.
cattrsetNew :: Word32 -> IO CAttrSet
cattrsetNew = c_nn_attrset_new
-- ---------------------------------------------------------------------------
-- Construction
-- ---------------------------------------------------------------------------
-- | Insert a key-value pair. Call before freeze.
-- The value is a CThunkPtr cast to Ptr () — C never dereferences it.
cattrsetInsert :: CAttrSet -> Symbol -> CThunkPtr -> IO ()
cattrsetInsert set (Symbol sym) ptr =
c_nn_attrset_insert set sym (castPtr ptr)
-- | Sort and deduplicate. Must be called once before any queries.
cattrsetFreeze :: CAttrSet -> IO ()
cattrsetFreeze = c_nn_attrset_freeze
-- ---------------------------------------------------------------------------
-- Query
-- ---------------------------------------------------------------------------
-- | Look up a key. Returns 'Nothing' if not found.
-- The returned 'CThunkPtr' is borrowed — the thunk arena owns it.
cattrsetLookup :: CAttrSet -> Symbol -> IO (Maybe CThunkPtr)
cattrsetLookup set (Symbol sym) = do
ptr <- c_nn_attrset_lookup set sym
pure (if ptr == nullPtr then Nothing else Just (castPtr ptr))
-- | Find the index of a key, or 'Nothing' if not found.
cattrsetIndex :: CAttrSet -> Symbol -> IO (Maybe Word32)
cattrsetIndex set (Symbol sym) = do
idx <- c_nn_attrset_index set sym
pure (if idx < 0 then Nothing else Just (fromIntegral idx))
-- | Get the value at a known index.
cattrsetGetValue :: CAttrSet -> Word32 -> IO CThunkPtr
cattrsetGetValue set idx = castPtr <$> c_nn_attrset_get_value set idx
-- | Update the value at a known index (for two-phase construction).
cattrsetSetValue :: CAttrSet -> Word32 -> CThunkPtr -> IO ()
cattrsetSetValue set idx ptr = c_nn_attrset_set_value set idx (castPtr ptr)
-- | Get the key symbol at a known index.
cattrsetGetKey :: CAttrSet -> Word32 -> IO Symbol
cattrsetGetKey set idx = Symbol <$> c_nn_attrset_get_key set idx
-- ---------------------------------------------------------------------------
-- Bulk access
-- ---------------------------------------------------------------------------
-- | Number of entries (unique keys after freeze).
cattrsetSize :: CAttrSet -> IO Word32
cattrsetSize = c_nn_attrset_size
-- | Read all keys as a list of symbols (sorted).
cattrsetKeys :: CAttrSet -> IO [Symbol]
cattrsetKeys set = do
n <- cattrsetSize set
ptr <- c_nn_attrset_keys_ptr set
raw <- peekArray (fromIntegral n) ptr
pure (map Symbol raw)
-- ---------------------------------------------------------------------------
-- Set operations
-- ---------------------------------------------------------------------------
-- | Right-biased union (// semantics). Result is frozen.
cattrsetUnion :: CAttrSet -> CAttrSet -> IO CAttrSet
cattrsetUnion = c_nn_attrset_union
-- | Remove a list of keys. Result is frozen.
cattrsetRemoveKeys :: CAttrSet -> [Symbol] -> IO CAttrSet
cattrsetRemoveKeys set syms = do
let raw = map unSymbol syms
n = fromIntegral (length raw)
withArray raw $ \ptr ->
c_nn_attrset_remove_keys set ptr n