haskell-gi-0.26.19: lib/Data/GI/CodeGen/StructLayout.hsc
-- | 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