packages feed

haskell-gi 0.26.18 → 0.26.19

raw patch · 8 files changed

+598/−19 lines, 8 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

+ Data.GI.CodeGen.API: [fieldAnonymous] :: Field -> Maybe AnonymousMember
+ Data.GI.CodeGen.API: [fieldArrayOfPointers] :: Field -> Bool
+ Data.GI.CodeGen.API: [fieldBitShift] :: Field -> Maybe Int
+ Data.GI.CodeGen.API: [fieldBits] :: Field -> Maybe Int
+ Data.GI.CodeGen.StructLayout: AggPointer :: Aggregate
+ Data.GI.CodeGen.StructLayout: AggScalar :: Int -> Aggregate
+ Data.GI.CodeGen.StructLayout: AggStruct :: Struct -> Aggregate
+ Data.GI.CodeGen.StructLayout: AggUnion :: Union -> Aggregate
+ Data.GI.CodeGen.StructLayout: data Aggregate
+ Data.GI.CodeGen.StructLayout: fixupStructLayouts :: Map Name Aggregate -> Map Name Aggregate
+ Data.GI.CodeGen.StructLayout: instance GHC.Internal.Classes.Eq Data.GI.CodeGen.StructLayout.Mode
+ Data.GI.GIR.Field: AnonymousStruct :: [Field] -> AnonymousMember
+ Data.GI.GIR.Field: AnonymousUnion :: [Field] -> AnonymousMember
+ Data.GI.GIR.Field: [fieldAnonymous] :: Field -> Maybe AnonymousMember
+ Data.GI.GIR.Field: [fieldArrayOfPointers] :: Field -> Bool
+ Data.GI.GIR.Field: [fieldBitShift] :: Field -> Maybe Int
+ Data.GI.GIR.Field: [fieldBits] :: Field -> Maybe Int
+ Data.GI.GIR.Field: data AnonymousMember
+ Data.GI.GIR.Field: instance GHC.Internal.Show.Show Data.GI.GIR.Field.AnonymousMember
+ Data.GI.GIR.Parser: parseAllChildrenWithLocalNames :: [Text] -> (Text -> Parser a) -> Parser [a]
- Data.GI.CodeGen.API: Field :: Text -> Bool -> Type -> Maybe Bool -> Maybe Callback -> Int -> [FieldInfoFlag] -> Documentation -> Maybe DeprecationInfo -> Field
+ Data.GI.CodeGen.API: Field :: Text -> Bool -> Type -> Maybe Bool -> Bool -> Maybe Callback -> Int -> Maybe Int -> Maybe Int -> Maybe AnonymousMember -> [FieldInfoFlag] -> Documentation -> Maybe DeprecationInfo -> Field
- Data.GI.GIR.Field: Field :: Text -> Bool -> Type -> Maybe Bool -> Maybe Callback -> Int -> [FieldInfoFlag] -> Documentation -> Maybe DeprecationInfo -> Field
+ Data.GI.GIR.Field: Field :: Text -> Bool -> Type -> Maybe Bool -> Bool -> Maybe Callback -> Int -> Maybe Int -> Maybe Int -> Maybe AnonymousMember -> [FieldInfoFlag] -> Documentation -> Maybe DeprecationInfo -> Field

Files

haskell-gi.cabal view
@@ -1,5 +1,5 @@ name:                haskell-gi-version:             0.26.18+version:             0.26.19 synopsis:            Generate Haskell bindings for GObject Introspection capable libraries description:         Generate Haskell bindings for GObject Introspection capable libraries. This includes most notably                      GTK, but many other libraries in the GObject ecosystem provide introspection data too.@@ -81,6 +81,7 @@                        Data.GI.GIR.Union,                        Data.GI.GIR.XMLUtils,                        Data.GI.CodeGen.API,+                       Data.GI.CodeGen.StructLayout,                        Data.GI.CodeGen.CabalHooks,                        Data.GI.CodeGen.Callable,                        Data.GI.CodeGen.Code,
lib/Data/GI/CodeGen/API.hs view
@@ -104,6 +104,8 @@                         lookupAttrWithNamespace, GIRXMLNamespace(..),                         xmlLocalName) +import Data.GI.CodeGen.StructLayout (Aggregate(..), fixupStructLayouts)+ import Data.GI.Base.BasicConversions (unpackStorableArrayWithLength) import Data.GI.Base.BasicTypes (GType(..), CGType, gtypeName) import Data.GI.Base.Utils (allocMem, freeMem)@@ -329,6 +331,7 @@   (fixup (fixupInterface typelibMap ctypes) >=>     fixup (fixupStruct typelibMap) >=>     fixup fixupUnion >=>+    (return . fixupLayouts) >=>     fixup (fixupMissingSymbols verbose typelibMap)   ) (doc, deps)   where fixup :: ((Name, API) -> IO (Name, API))@@ -347,6 +350,42 @@         ctypes :: M.Map Text Name         ctypes = M.unions (map girCTypes (doc:deps)) +-- | The typelib compiler ignores the widths of bitfields, and+-- anonymous unions and structs, when computing the layout of+-- structs, so we fix the layout of structs and unions (directly or+-- indirectly) containing these using the information in the GIR+-- file. See "Data.GI.CodeGen.StructLayout" for the details. This+-- needs to be done for all the loaded namespaces at once, since+-- structs may embed structs defined in other namespaces.+fixupLayouts :: (GIRInfo, [GIRInfo]) -> (GIRInfo, [GIRInfo])+fixupLayouts (doc, deps) = (fixInfo doc, map fixInfo deps)+  where aggregates :: M.Map Name Aggregate+        aggregates = M.fromList [(n, agg) | info <- doc : deps+                                          , (n, api) <- girAPIs info+                                          , Just agg <- [toAggregate api]]++        toAggregate :: API -> Maybe Aggregate+        toAggregate (APIStruct s) = Just (AggStruct s)+        toAggregate (APIUnion u) = Just (AggUnion u)+        toAggregate (APIEnum e) = Just (AggScalar (enumStorageBytes e))+        toAggregate (APIFlags (Flags e)) = Just (AggScalar (enumStorageBytes e))+        toAggregate (APIObject _) = Just AggPointer+        toAggregate (APIInterface _) = Just AggPointer+        toAggregate (APICallback _) = Just AggPointer+        toAggregate _ = Nothing++        fixed :: M.Map Name Aggregate+        fixed = fixupStructLayouts aggregates++        fixInfo :: GIRInfo -> GIRInfo+        fixInfo info = info {girAPIs = map fixAPI (girAPIs info)}++        fixAPI :: (Name, API) -> (Name, API)+        fixAPI (n, api) = case M.lookup n fixed of+          Just (AggStruct s) -> (n, APIStruct s)+          Just (AggUnion u) -> (n, APIUnion u)+          _ -> (n, api)+ foreign import ccall "g_type_interface_prerequisites" g_type_interface_prerequisites :: CGType -> Ptr CUInt -> IO (Ptr CGType)  -- | List the prerequisites for a 'GType' corresponding to an interface.@@ -434,6 +473,10 @@  -- | Fixup the offsets of fields using the given offset map. fixupField :: M.Map Text FieldInfo -> Field -> Field+fixupField _ f@(Field {fieldAnonymous = Just _}) =+  -- Not present in the typelib, the offset will be computed in+  -- 'fixupLayouts'.+  f fixupField offsetMap f =     f {fieldOffset = case M.lookup (fieldName f) offsetMap of                        Nothing -> error $ "Could not find field "
lib/Data/GI/CodeGen/Code.hs view
@@ -965,6 +965,7 @@                 , "import qualified GHC.Records as R"                 , "import qualified Data.Word as DW"                 , "import qualified Data.Int as DI"+                , "import qualified Data.Bits as DB"                 , "import qualified System.Posix.Types as SPT"                 , "import qualified Foreign.C.Types as FCT"] 
lib/Data/GI/CodeGen/Conversions.hsc view
@@ -703,11 +703,13 @@                         | (size == 0) = "unpackPtrArrayWithLength"                         | boxed       = "unpackBoxedArrayWithLength " <> tshow size                         | otherwise   = "unpackBlockArrayWithLength " <> tshow size-               -- We always make a copy of the elements when unpacking-               -- boxed types.-           let transfer' | boxed      = if transfer == TransferContainer-                                        then TransferEverything-                                        else transfer+           -- When unpacking structs/unions stored inline in the+           -- array (size > 0) the unpacker always makes a copy of+           -- each element, which we then own, so we wrap it+           -- directly instead of making a second copy (which would+           -- leak the first one).+           let transfer' | isScalar   = transfer+                         | size > 0   = TransferEverything                          | otherwise  = transfer            innerConstructor <- fToH' t a hType fType transfer'            return $ do
lib/Data/GI/CodeGen/Struct.hs view
@@ -135,6 +135,56 @@   , "'Data.GI.Base.Attributes.get' " <> lowerName n <> " #" <> labelName field   , "@"] +-- | If the given field is a C array whose length is given by another+-- field in the same struct/union, return the field holding the+-- length.+arrayLengthField :: [Field] -> Field -> ExcCodeGen (Maybe Field)+arrayLengthField fields field =+  case fieldType field of+    TCArray False (-1) idx _+      | idx < 0 -> return Nothing+      | idx >= length fields ->+        badIntroError $ "Array length field index " <> tshow idx+                        <> " for field “" <> fieldName field+                        <> "” is out of range."+      | otherwise -> do+          let lengthField = fields !! idx+          case fieldType lengthField of+            TBasicType t | t `elem` [TInt, TUInt, TLong, TULong,+                                     TInt8, TUInt8, TInt16, TUInt16,+                                     TInt32, TUInt32, TInt64, TUInt64,+                                     TSize, TSSize] ->+                             return (Just lengthField)+            t -> badIntroError $ "Array length field “"+                                 <> fieldName lengthField+                                 <> "” for field “" <> fieldName field+                                 <> "” has non-integral type " <> tshow t+    _ -> return Nothing++-- | If the field is a bitfield, return its width and the shift of+-- its least significant bit inside the storage unit.+bitfieldInfo :: Field -> ExcCodeGen (Maybe (Int, Int))+bitfieldInfo field = case fieldBits field of+  Nothing -> return Nothing+  Just width -> case fieldBitShift field of+    Nothing -> notImplementedError "Could not determine the layout of the bitfield"+    Just shift -> do+      supported <- case fieldType field of+        TBasicType t -> return $ t `elem` [TBoolean, TInt, TUInt, TLong, TULong,+                                           TInt8, TUInt8, TInt16, TUInt16,+                                           TInt32, TUInt32, TInt64, TUInt64,+                                           TShort, TUShort, TSize, TSSize,+                                           TUniChar]+        t@(TInterface _) -> findAPI t >>= \case+          Just (APIEnum _) -> return True+          Just (APIFlags _) -> return True+          _ -> return False+        _ -> return False+      if supported+        then return (Just (width, shift))+        else notImplementedError $ "Bitfield of unsupported type "+                                   <> tshow (fieldType field)+ -- Notice that when reading the field we return a copy of any embedded -- structs, so modifications of the returned struct will not affect -- the original struct. This is on purpose, in order to increase@@ -142,12 +192,13 @@ -- parent structure while we are modifying the embedded one, and havoc -- will ensue). -- | Extract a field from a struct.-buildFieldReader :: Name -> Field -> ExcCodeGen ()-buildFieldReader n field = group $ do+buildFieldReader :: Name -> [Field] -> Field -> ExcCodeGen ()+buildFieldReader n fields field = group $ do   let name' = upperName n       getter = fieldGetter n field    embedded <- isEmbedded field+  bitfield <- bitfieldInfo field   nullConvert <- if embedded                  then return Nothing                  else maybeNullConvert (fieldType field)@@ -172,12 +223,31 @@              <> " :: " <> peekedType     else line $ "val <- peek (ptr `plusPtr` " <> tshow (fieldOffset field)              <> ") :: IO " <> peekedType+    -- For bitfields, we read the whole storage unit, and then+    -- extract the relevant bits (with sign extension for signed+    -- types).+    valName <- case bitfield of+      Nothing -> return "val"+      Just (width, shift) -> do+        line $ "let bitfield = DB.shiftR (DB.shiftL val (DB.finiteBitSize val P.- "+               <> tshow (shift + width) <> ")) (DB.finiteBitSize val P.- "+               <> tshow width <> ")"+        return "bitfield"+    -- C arrays whose length is given by another field in the struct+    -- need the length in order to be unpacked.+    converter <- arrayLengthField fields field >>= \case+      Nothing -> return $ fToH (fieldType field) TransferNothing+      Just lengthField -> do+        lType <- typeShow <$> foreignType (fieldType lengthField)+        line $ "len <- peek (ptr `plusPtr` " <> tshow (fieldOffset lengthField)+               <> ") :: IO " <> lType+        return $ unpackCArray "len" (fieldType field) TransferNothing     result <- case nullConvert of-              Nothing -> convert "val" $ fToH (fieldType field) TransferNothing+              Nothing -> convert valName converter               Just nullConverter -> do                 line $ "result <- " <> nullConverter <> " val $ \\val' -> do"                 indent $ do-                  val' <- convert "val'" $ fToH (fieldType field) TransferNothing+                  val' <- convert "val'" converter                   line $ "return " <> val'                 return "result"     line $ "return " <> result@@ -220,12 +290,25 @@   line $ setter <> " :: MonadIO m => " <> name' <> " -> "            <> hType <> " -> m ()"   line $ setter <> " s val = liftIO $ withManagedPtr s $ \\ptr -> do"+  bitfield <- bitfieldInfo field+   indent $ do     converted <- if isPtr                  then return "val"                  else convert "val" $ hToF (fieldType field) TransferNothing-    line $ "poke (ptr `plusPtr` " <> tshow (fieldOffset field)-         <> ") (" <> converted <> " :: " <> fType <> ")"+    let location = "(ptr `plusPtr` " <> tshow (fieldOffset field) <> ")"+    case bitfield of+      Nothing ->+        line $ "poke " <> location <> " (" <> converted <> " :: " <> fType <> ")"+      Just (width, shift) -> do+        -- Only modify the bits corresponding to the bitfield in the+        -- storage unit.+        line $ "old <- peek " <> location <> " :: IO " <> parenthesize fType+        line $ "let mask = DB.shiftL (DB.shiftL 1 " <> tshow width+               <> " P.- 1) " <> tshow shift <> " :: " <> fType+        line $ "poke " <> location <> " ((old DB..&. DB.complement mask) DB..|. "+               <> "(DB.shiftL (" <> converted <> " :: " <> fType <> ") "+               <> tshow shift <> " DB..&. mask))"  -- | Name for the clear function fieldClear :: Name -> Field -> Text@@ -398,8 +481,8 @@   return $ "'(\"" <> labelName field <> "\", " <> it <> ")"  -- | Build code for a single field.-buildFieldAttributes :: Name -> Field -> ExcCodeGen (Maybe Text)-buildFieldAttributes n field+buildFieldAttributes :: Name -> [Field] -> Field -> ExcCodeGen (Maybe Text)+buildFieldAttributes n fields field     | not (fieldVisible field) = return Nothing     | privateType (fieldType field) = return Nothing     | otherwise = group $ do@@ -417,7 +500,7 @@       addSectionDocumentation docSection (fieldDocumentation field) -     buildFieldReader n field+     buildFieldReader n fields field      export docSection (fieldGetter n field)       when (not embedded) $ do@@ -449,7 +532,7 @@                              ":" <> fieldName field <> "\"")                       printCGError e                       return Nothing)-                  (buildFieldAttributes n field)+                  (buildFieldAttributes n fields field)    blank @@ -567,6 +650,8 @@        line $ "-- XXX Wrapping a foreign struct/union with no known destructor or size, leak?"    copy <- case allocCopy info of+            -- Do not copy, the copy refers to the same memory.+            AllocationOp "none" -> return "return"             AllocationOp op -> do                 copy <- prefixedForeignImport (prefix "copy") op "Ptr a -> IO (Ptr a)"                 return ("\\p -> B.ManagedPtr.withManagedPtr p (" <> copy <>@@ -579,6 +664,8 @@                 else return "return"    free <- case allocFree info of+            -- Never free the memory.+            AllocationOp "none" -> return "\\_x -> return ()"             AllocationOp op -> do               free <- prefixedForeignImport (prefix "free") op "Ptr a -> IO ()"               return $ "\\p -> B.ManagedPtr.withManagedPtr p " <> free
+ lib/Data/GI/CodeGen/StructLayout.hsc view
@@ -0,0 +1,365 @@+-- | Fix the memory layout of structs and unions.+--+-- When compiling the typelib, the typelib compiler (both+-- @gi-compile-repository@ in GLib and @g-ir-compiler@ in+-- gobject-introspection) makes two simplifications when computing+-- the layout of structs and unions:+--+-- * It ignores the width of bitfields: each bitfield is laid out as+--   if it was a full member of its declared type.+--+-- * It ignores unions and structs declared inline (such as+--   @union { ... } u;@, or anonymous unions).+--+-- This leads to wrong struct sizes, and wrong offsets for the fields+-- following the affected members. This affects the struct itself,+-- and any struct or union embedding it. See+-- <https://github.com/haskell-gi/haskell-gi/issues/485>.+--+-- The GIR files do contain the required information, so here we+-- recompute the layout ourselves, following the rules of the C+-- ABI. To make sure that we are not missing any information (fields+-- that we could not parse, types of unknown size, ...), we first+-- check that by laying out the struct in the same way that the+-- typelib compiler does we reproduce the typelib data exactly. If+-- this is not the case we keep the data from the typelib.+module Data.GI.CodeGen.StructLayout+  ( Aggregate(..)+  , fixupStructLayouts+  ) where++#include <glib-object.h>++#if !MIN_VERSION_base(4,13,0)+import Data.Monoid ((<>))+#endif++import Control.Monad (forM, unless, when)+import qualified Data.Map.Lazy as ML+import Data.Maybe (isJust)+import Data.Text (Text)+import qualified Data.Text as T+import Foreign.C.Types (CTime)+import Foreign.Storable (alignment, sizeOf)+import qualified System.Info++import Data.GI.GIR.BasicTypes (BasicType(..), Name(..), Type(..))+import Data.GI.GIR.Field (Field(..), AnonymousMember(..))+import Data.GI.GIR.Struct (Struct(..))+import Data.GI.GIR.Union (Union(..))++-- | The information about a named type that we need in order to+-- compute layouts.+data Aggregate = AggStruct Struct+               | AggUnion Union+               | AggScalar Int  -- ^ Enums and flags, with their+                                -- storage size in bytes.+               | AggPointer     -- ^ Types which are always stored+                                -- as pointers in structs (objects,+                                -- interfaces, callbacks).++-- | How to lay out the struct.+data Mode = TypelibMode -- ^ Mimic the typelib compiler.+          | ABIMode     -- ^ Follow the platform C ABI.+  deriving (Eq)++-- | Position of a field in a struct.+data FieldLayout = FieldLayout {+      flOffset   :: Int       -- ^ Offset in bytes.+    , flBitShift :: Maybe Int -- ^ Bit shift, for bitfields.+    }++-- | Layout of a struct or union.+data Layout = Layout {+      lSize   :: Int+    , lAlign  :: Int+    , lFields :: [FieldLayout]+    }++-- | Size and alignment of a C type.+type SizeAlign = (Int, Int)++-- | Given the structs, unions, and other named types in scope,+-- return the structs and unions whose layout has been+-- corrected. Bitfields in the corrected structs and unions will+-- also have their `fieldBitShift` set.+fixupStructLayouts :: ML.Map Name Aggregate -> ML.Map Name Aggregate+fixupStructLayouts aggs = ML.mapMaybeWithKey fix aggs+  where+    fix :: Name -> Aggregate -> Maybe Aggregate+    fix n (AggStruct s) | needsFixing (structFields s) = do+      layout <- either (const Nothing) Just (abiLayout n)+      return $ AggStruct (s { structSize = lSize layout+                            , structFields = applyLayout layout+                                                         (structFields s) })+    fix n (AggUnion u) | needsFixing (unionFields u) = do+      layout <- either (const Nothing) Just (abiLayout n)+      return $ AggUnion (u { unionSize = lSize layout+                           , unionFields = applyLayout layout+                                                       (unionFields u) })+    fix _ _ = Nothing++    -- Structs whose layout may be wrong in the typelib: those+    -- containing bitfields or inline unions or structs, and those+    -- embedding other structs or unions (which may be wrong).+    needsFixing :: [Field] -> Bool+    needsFixing = any (\f -> isJust (fieldBits f) || embedsAggregate f)++    embedsAggregate :: Field -> Bool+    embedsAggregate f+      | isJust (fieldAnonymous f) = True+      | isPointerField f = False+      | otherwise = case elementType (fieldType f) of+          TInterface n -> case ML.lookup n aggs of+                            Just (AggStruct _) -> True+                            Just (AggUnion _) -> True+                            _ -> False+          TGValue -> True+          TGClosure _ -> True+          _ -> False++    elementType :: Type -> Type+    elementType (TCArray _ fixed _ t) | fixed > 0 = elementType t+    elementType t = t++    applyLayout :: Layout -> [Field] -> [Field]+    applyLayout layout =+      zipWith (\fl f -> f { fieldOffset = flOffset fl+                          , fieldBitShift = flBitShift fl })+              (lFields layout)++    -- Layouts computed by mimicking the typelib compiler, keeping+    -- only those that reproduce exactly the data in the typelib. The+    -- map is lazy, so we only compute what we need, and each layout+    -- is computed at most once.+    typelibLayouts :: ML.Map Name (Either Text Layout)+    typelibLayouts = ML.mapMaybe verified aggs+      where verified (AggStruct s) =+              Just (verify (structSize s) (structFields s)+                    =<< structLayout TypelibMode (structFields s))+            verified (AggUnion u) =+              Just (verify (unionSize u) (unionFields u)+                    =<< unionLayout TypelibMode (unionFields u))+            verified _ = Nothing++            verify :: Int -> [Field] -> Layout -> Either Text Layout+            verify size fields layout = do+              when (lSize layout /= size) $+                Left $ "Computed size " <> tshow (lSize layout)+                       <> " does not match typelib size " <> tshow size+              let offsets = [(flOffset fl, fieldOffset f)+                            | (fl, f) <- zip (lFields layout) fields+                            , not (isJust (fieldAnonymous f))]+              unless (all (uncurry (==)) offsets) $+                Left "Computed offsets do not match the typelib offsets"+              return layout++    -- Layouts following the C ABI, for those types whose typelib+    -- layout we can reproduce.+    abiLayouts :: ML.Map Name (Either Text Layout)+    abiLayouts = ML.mapMaybeWithKey abi aggs+      where abi n (AggStruct s) =+              Just (typelibLayout n >> structLayout ABIMode (structFields s))+            abi n (AggUnion u) =+              Just (typelibLayout n >> unionLayout ABIMode (unionFields u))+            abi _ _ = Nothing++    typelibLayout :: Name -> Either Text Layout+    typelibLayout n = case ML.lookup n typelibLayouts of+      Just l -> l+      Nothing -> Left ("No layout for " <> tshow n)++    abiLayout :: Name -> Either Text Layout+    abiLayout n = case ML.lookup n abiLayouts of+      Just l -> l+      Nothing -> Left ("No layout for " <> tshow n)++    -- Size and alignment of an embedded struct or union.+    embeddedSizeAlign :: Mode -> Name -> Either Text SizeAlign+    embeddedSizeAlign mode n = do+      layout <- case mode of+                  TypelibMode -> typelibLayout n+                  ABIMode -> abiLayout n+      return (lSize layout, lAlign layout)++    structLayout :: Mode -> [Field] -> Either Text Layout+    structLayout mode fields = do+      sizes <- forM fields (fieldSizeAlign mode)+      let bitfields = case mode of+                        TypelibMode -> map (const Nothing) fields+                        ABIMode -> map fieldBits fields+          placed = zip3 fields sizes bitfields+      if msBitfields+        then msStructLayout placed+        else sysvStructLayout placed++    unionLayout :: Mode -> [Field] -> Either Text Layout+    unionLayout mode fields = do+      sizes <- forM fields (fieldSizeAlign mode)+      let align = maximum (1 : map snd sizes)+          size = roundUp (maximum (0 : map fst sizes)) align+          fl (f, (s, _)) = case (mode, fieldBits f) of+            (ABIMode, Just w) -> FieldLayout 0 (Just (bitShift s 0 w))+            _ -> FieldLayout 0 Nothing+      return $ Layout size align (map fl (zip fields sizes))++    fieldSizeAlign :: Mode -> Field -> Either Text SizeAlign+    fieldSizeAlign mode f = case fieldAnonymous f of+      -- These are ignored by the typelib compiler.+      Just _ | mode == TypelibMode -> Right (0, 1)+      Just (AnonymousUnion members) -> do+        layout <- unionLayout mode members+        return (lSize layout, lAlign layout)+      Just (AnonymousStruct members) -> do+        layout <- structLayout mode members+        return (lSize layout, lAlign layout)+      Nothing+        | isPointerField f -> Right pointerSizeAlign+        | TCArray _ fixed _ _ <- fieldType f, fixed > 0, fieldArrayOfPointers f ->+            let (size, align) = pointerSizeAlign+            in Right (fixed * size, align)+        | otherwise -> case typeSizeAlign mode (fieldType f) of+            Left err -> Left ("Field “" <> fieldName f <> "”: " <> err)+            r -> r++    -- Size and alignment of a type stored inline.+    typeSizeAlign :: Mode -> Type -> Either Text SizeAlign+    typeSizeAlign mode t = case t of+      TBasicType b -> basicSizeAlign b+      TInterface n -> case ML.lookup n aggs of+        Just (AggStruct _) -> embeddedSizeAlign mode n+        Just (AggUnion _) -> embeddedSizeAlign mode n+        Just (AggScalar size) -> Right (size, size)+        Just AggPointer -> Right pointerSizeAlign+        Nothing -> Left ("Unknown type " <> tshow n)+      TGValue -> embeddedSizeAlign mode (Name "GObject" "Value")+      TGClosure _ -> embeddedSizeAlign mode (Name "GObject" "Closure")+      TCArray _ fixed _ elemType+        | fixed > 0 -> do+            (size, align) <- typeSizeAlign mode elemType+            return (fixed * size, align)+        | otherwise -> Right pointerSizeAlign+      _ -> Right pointerSizeAlign++-- | Whether the field is a pointer (as opposed to being stored inline+-- in the struct).+isPointerField :: Field -> Bool+isPointerField f = isJust (fieldCallback f) || fieldIsPointer f == Just True++-- | Lay out the fields in a struct following the rules of the System+-- V (and Itanium) ABIs: a bitfield is placed at the next available+-- bit, unless it would straddle a storage unit of its declared type,+-- in which case it is placed at the start of the next such unit.+sysvStructLayout :: [(Field, SizeAlign, Maybe Int)] -> Either Text Layout+sysvStructLayout = go 0 1 []+  where+    -- We keep track of the position in bits.+    go :: Int -> Int -> [FieldLayout] -> [(Field, SizeAlign, Maybe Int)]+       -> Either Text Layout+    go pos align acc [] =+      Right $ Layout (roundUp (roundUp pos 8 `div` 8) align) align (reverse acc)+    go pos align acc ((f, (size, falign), bits) : rest) = case bits of+      Nothing ->+        let offset = roundUp (roundUp pos 8 `div` 8) falign+        in go ((offset + size) * 8) (max align falign)+              (FieldLayout offset Nothing : acc) rest+      Just w -> do+        let unitBits = size * 8+        when (w > unitBits) $+          Left ("Bitfield “" <> fieldName f <> "” is wider than its type")+        let start = if pos `mod` unitBits + w > unitBits+                    then roundUp pos unitBits+                    else pos+            unit = (start `div` unitBits) * size+        go (start + w) (max align falign)+           (FieldLayout unit (Just (bitShift size (start - unit * 8) w)) : acc)+           rest++-- | Lay out the fields in a struct following the rules used by MSVC+-- (and by GCC with @-mms-bitfields@, the default on Windows): a+-- bitfield shares the storage unit of the previous bitfield only if+-- their declared types have the same size and there is enough space+-- left, otherwise a new storage unit is allocated.+msStructLayout :: [(Field, SizeAlign, Maybe Int)] -> Either Text Layout+msStructLayout = go 0 Nothing 1 []+  where+    -- We keep track of the end of the struct so far in bytes, and of+    -- the current storage unit (offset, size, bits used), if any.+    go :: Int -> Maybe (Int, Int, Int) -> Int -> [FieldLayout]+       -> [(Field, SizeAlign, Maybe Int)] -> Either Text Layout+    go end _ align acc [] =+      Right $ Layout (roundUp end align) align (reverse acc)+    go end unit align acc ((f, (size, falign), bits) : rest) = case bits of+      Nothing ->+        let offset = roundUp end falign+        in go (offset + size) Nothing (max align falign)+              (FieldLayout offset Nothing : acc) rest+      Just w -> do+        when (w > size * 8) $+          Left ("Bitfield “" <> fieldName f <> "” is wider than its type")+        case unit of+          Just (uOffset, uSize, used) | uSize == size && used + w <= size * 8 ->+            go end (Just (uOffset, uSize, used + w)) (max align falign)+               (FieldLayout uOffset (Just (bitShift size used w)) : acc) rest+          _ -> do+            let offset = roundUp end falign+            go (offset + size) (Just (offset, size, w)) (max align falign)+               (FieldLayout offset (Just (bitShift size 0 w)) : acc) rest++-- | Shift of the least significant bit of a bitfield of the given+-- width, starting at the given bit position in a storage unit of+-- the given size in bytes. On big endian machines bitfields are+-- allocated starting from the most significant bit.+bitShift :: Int -> Int -> Int -> Int+bitShift unitSize pos width+  | bigEndian = unitSize * 8 - pos - width+  | otherwise = pos++-- | Whether we are running on a big endian machine.+bigEndian :: Bool+bigEndian = (#{const G_BYTE_ORDER} :: Int) == #{const G_BIG_ENDIAN}++-- | Whether bitfields are laid out following the MSVC rules.+msBitfields :: Bool+msBitfields = System.Info.os == "mingw32"++pointerSizeAlign :: SizeAlign+pointerSizeAlign = (#{size gpointer}, #{alignment gpointer})++-- | Size and alignment of the basic types.+basicSizeAlign :: BasicType -> Either Text SizeAlign+basicSizeAlign t = case t of+  TBoolean -> Right (#{size gboolean}, #{alignment gboolean})+  TInt -> Right (#{size gint}, #{alignment gint})+  TUInt -> Right (#{size guint}, #{alignment guint})+  TLong -> Right (#{size glong}, #{alignment glong})+  TULong -> Right (#{size gulong}, #{alignment gulong})+  TInt8 -> Right (#{size gint8}, #{alignment gint8})+  TUInt8 -> Right (#{size guint8}, #{alignment guint8})+  TInt16 -> Right (#{size gint16}, #{alignment gint16})+  TUInt16 -> Right (#{size guint16}, #{alignment guint16})+  TInt32 -> Right (#{size gint32}, #{alignment gint32})+  TUInt32 -> Right (#{size guint32}, #{alignment guint32})+  TInt64 -> Right (#{size gint64}, #{alignment gint64})+  TUInt64 -> Right (#{size guint64}, #{alignment guint64})+  TFloat -> Right (#{size gfloat}, #{alignment gfloat})+  TDouble -> Right (#{size gdouble}, #{alignment gdouble})+  TUniChar -> Right (#{size gunichar}, #{alignment gunichar})+  TGType -> Right (#{size GType}, #{alignment GType})+  TUTF8 -> Right pointerSizeAlign+  TFileName -> Right pointerSizeAlign+  TPtr -> Right pointerSizeAlign+  TIntPtr -> Right (#{size gintptr}, #{alignment gintptr})+  TUIntPtr -> Right (#{size guintptr}, #{alignment guintptr})+  TShort -> Right (#{size gshort}, #{alignment gshort})+  TUShort -> Right (#{size gushort}, #{alignment gushort})+  TSize -> Right (#{size gsize}, #{alignment gsize})+  TSSize -> Right (#{size gssize}, #{alignment gssize})+  Ttime_t -> Right (sizeOf (0 :: CTime), alignment (0 :: CTime))+  _ -> Left ("Unsupported basic type " <> tshow t)++roundUp :: Int -> Int -> Int+roundUp n a = ((n + a - 1) `div` a) * a++tshow :: Show a => a -> Text+tshow = T.pack . show
lib/Data/GI/GIR/Field.hs view
@@ -1,6 +1,7 @@ -- | Parsing of object/struct/union fields. module Data.GI.GIR.Field     ( Field(..)+    , AnonymousMember(..)     , FieldInfoFlag     , parseFields     ) where@@ -13,7 +14,7 @@ #endif import Data.Text (Text, isSuffixOf) -import Data.GI.GIR.BasicTypes (Type(..))+import Data.GI.GIR.BasicTypes (Type(..), BasicType(..)) import Data.GI.GIR.Callback (Callback, parseCallback) import Data.GI.GIR.Type (parseType, queryElementCType) import Data.GI.GIR.Parser@@ -23,8 +24,27 @@       fieldVisible :: Bool,       fieldType :: Type,       fieldIsPointer :: Maybe Bool, -- ^ `Nothing` if not known.+      fieldArrayOfPointers :: Bool,+      -- ^ For C arrays stored inline in the struct (that is, with a+      -- fixed size), whether the elements are pointers.       fieldCallback :: Maybe Callback,       fieldOffset :: Int,+      -- ^ For bitfields, this is the offset of the storage unit+      -- containing the bitfield.+      fieldBits :: Maybe Int,+      -- ^ The width in bits, if the field is a bitfield.+      fieldBitShift :: Maybe Int,+      -- ^ For bitfields, the position of the least significant bit+      -- of the bitfield inside its storage unit (a value of the+      -- declared type of the field, stored at `fieldOffset`). This+      -- is `Nothing` if the layout of the bitfield could not be+      -- determined.+      fieldAnonymous :: Maybe AnonymousMember,+      -- ^ If this member is an (anonymous type) union or struct+      -- declared inline, its contents. In this case `fieldType`+      -- is meaningless, and the field is never visible. We keep+      -- track of these members since they affect the layout of+      -- the containing struct or union.       fieldFlags :: [FieldInfoFlag],       fieldDocumentation :: Documentation,       fieldDeprecated :: Maybe DeprecationInfo }@@ -33,6 +53,12 @@ data FieldInfoFlag = FieldIsReadable | FieldIsWritable                    deriving Show +-- | Contents of a union or struct declared inline in a struct or+-- union.+data AnonymousMember = AnonymousUnion [Field]+                     | AnonymousStruct [Field]+                     deriving Show+ -- | Parse a single field in a struct or union. We parse -- non-introspectable fields too (but set fieldVisible = False for -- them), this is necessary since they affect the computation of@@ -41,6 +67,7 @@ parseField = do   name <- getAttr "name"   deprecated <- parseDeprecation+  bits <- optionalAttr "bits" Nothing (fmap Just . parseIntegral)   readable <- optionalAttr "readable" True parseBool   writable <- optionalAttr "writable" False parseBool   let flags = if readable then [FieldIsReadable] else []@@ -79,6 +106,11 @@           [n] -> return (TInterface n, Just True, Nothing)           _ -> parseError "Multiple callbacks in field" +    elementCTypes <- parseAllChildrenWithLocalName "array" queryElementCType+    let arrayOfPointers = case elementCTypes of+          [Just ct] -> "*" `isSuffixOf` ct+          _ -> False+     return $ Just $ Field {                fieldName = name              , fieldVisible = introspectable && not private@@ -86,12 +118,50 @@              , fieldIsPointer = if isJust callback                                 then Just True                                 else isPtr+             , fieldArrayOfPointers = arrayOfPointers              , fieldCallback = callback              , fieldOffset = error ("unfixed field offset " ++ show name)+             , fieldBits = if bits == Just 0 then Nothing else bits+             , fieldBitShift = Nothing+             , fieldAnonymous = Nothing              , fieldFlags = flags              , fieldDocumentation = doc              , fieldDeprecated = deprecated           } +-- | Parse an union or struct declared inline in a struct or union.+parseAnonymousMember :: Bool -> Parser Field+parseAnonymousMember isUnion = do+  name <- optionalAttr "name" (if isUnion+                               then "<anonymous union>"+                               else "<anonymous struct>") return+  -- We do not keep track of the offsets of the members of the+  -- anonymous union or struct, only of its total size.+  members <- map (\f -> f {fieldOffset = 0}) <$> parseFields+  doc <- parseDocumentation+  return $ Field {+               fieldName = name+             , fieldVisible = False+             , fieldType = TBasicType TPtr -- Not meaningful+             , fieldIsPointer = Just False+             , fieldArrayOfPointers = False+             , fieldCallback = Nothing+             , fieldOffset = 0+             , fieldBits = Nothing+             , fieldBitShift = Nothing+             , fieldAnonymous = Just (if isUnion+                                      then AnonymousUnion members+                                      else AnonymousStruct members)+             , fieldFlags = []+             , fieldDocumentation = doc+             , fieldDeprecated = Nothing+          }++-- | Parse the members of a struct or union, in order. parseFields :: Parser [Field]-parseFields = catMaybes <$> parseAllChildrenWithLocalName "field" parseField+parseFields = catMaybes <$>+  parseAllChildrenWithLocalNames ["field", "union", "record"] parseMember+  where parseMember :: Text -> Parser (Maybe Field)+        parseMember "union" = Just <$> parseAnonymousMember True+        parseMember "record" = Just <$> parseAnonymousMember False+        parseMember _ = parseField
lib/Data/GI/GIR/Parser.hs view
@@ -14,6 +14,7 @@     , parseBool     , parseChildrenWithLocalName     , parseAllChildrenWithLocalName+    , parseAllChildrenWithLocalNames     , parseChildrenWithNSName      , getAttr@@ -53,7 +54,7 @@ import Data.GI.GIR.Documentation (Documentation, queryDocumentation) import Data.GI.GIR.XMLUtils (localName, GIRXMLNamespace(..),                         childElemsWithLocalName, childElemsWithNSName,-                        lookupAttr, lookupAttrWithNamespace)+                        lookupAttr, lookupAttrWithNamespace, subelements)  -- | Info to carry around when parsing. data ParseContext = ParseContext {@@ -204,6 +205,15 @@ parseAllChildrenWithLocalName n parser = do   ctx <- ask   mapM (withElement parser) (childElemsWithLocalName n (currentElement ctx))++-- | Parse all subelements whose local name is one of the given+-- ones, in document order. The parser is passed the local name of+-- the subelement being parsed.+parseAllChildrenWithLocalNames :: [Text] -> (Text -> Parser a) -> Parser [a]+parseAllChildrenWithLocalNames names parser = do+  ctx <- ask+  mapM (\e -> withElement (parser (localName e)) e)+       (filter ((`elem` names) . localName) (subelements (currentElement ctx)))  -- | Parse all introspectable children with the given namespace and -- local name.