-- | Layout of algebraic data.
module DDC.Core.Lite.Layout
        ( -- * Heap Objects
          HeapObject(..)
        , heapObjectOfDataCtor

          -- * Fields
        , payloadSizeOfDataCtor
        , fieldOffsetsOfDataCtor)
where
import DDC.Core.Lite.Name
import DDC.Core.Lite.Env
import DDC.Core.Salt.Platform
import DDC.Type.DataDef
import DDC.Type.Exp
import Control.Monad
import Data.Maybe
import qualified DDC.Core.Salt.Name     as A


-- HeapObject -----------------------------------------------------------------
-- | Enumerates the heap object formats that can be used to store
--   algebraic data.
--
--   The layout of these is defined in the @ObjectNN.dce@ file of the runtime
--   system, where @NN@ is the word size of the machine.
data HeapObject
        = HeapObjectBoxed
        | HeapObjectMixed
        | HeapObjectRaw
        | HeapObjectRawSmall
        deriving (Eq, Show)


-- | Decide which heap object to use to represent a data constructor.
heapObjectOfDataCtor :: Platform -> DataCtor Name -> Maybe HeapObject
heapObjectOfDataCtor pp ctor

        -- If all the fields are boxed objects then used a Boxed heap object,
        -- as these just contain pointer fields.
        | tsFields              <- dataCtorFieldTypes ctor
        , all isBoxedType tsFields
        = Just HeapObjectBoxed

        -- All of the fixed size primitive types will fit in a RawSmall object.
        --   Each field needs to be non-abstract, and have a real width.
        | [TCon tc]                <- dataCtorFieldTypes ctor
        , TyConBound (UPrim n _) _ <- tc
        , NamePrimTyCon ptc        <- n
        , isJust $ A.primTyConWidth pp ptc
        = Just HeapObjectRawSmall

        | otherwise
        = Nothing


-- Field Layout ---------------------------------------------------------------
-- | Get the size of the payload for this data constructor.
--   The payload holds all the fields, but does not include
--   header information such as the constructor tag.
--
--   This doesn't add any padding for misaligned fields.
payloadSizeOfDataCtor :: Platform -> DataCtor Name -> Maybe Integer
payloadSizeOfDataCtor platform ctor
        = liftM sum
        $ sequence
        $ map (fieldSizeOfType platform)
        $ dataCtorFieldTypes ctor


-- | Given a constructor definition,
--   get the offset of each field in the payload of a heap object.
--
--   We don't know the absolute offset from the beginning of the heap
--   object, because the size of the header is only known by the runtime
--   system.
--
--   This doesn't add any padding for misaligned fields.
fieldOffsetsOfDataCtor :: Platform -> DataCtor Name -> Maybe [Integer]
fieldOffsetsOfDataCtor platform ctor
        = liftM (init . scanl (+) 0)
        $ sequence
        $ map (fieldSizeOfType platform)
        $ dataCtorFieldTypes ctor


-- | Get the raw size of a field of this type, without padding.
fieldSizeOfType    :: Platform -> Type Name -> Maybe Integer
fieldSizeOfType platform tt
 = case tt of
        TVar{}          -> Just $ platformAddrBytes platform

        TCon tc
         -> case tc of
                TyConBound (UPrim n _) _ -> fieldSizeOfPrim platform n
                TyConBound _ _           -> Just $ platformAddrBytes platform
                _                        -> Nothing

        -- We're not supporting polymorphic fields yet.
        TForall{}       -> Nothing

        -- Assume anything that isn't a primitive constructor is
        -- represented by a pointer.
        TApp{}          -> Just $ platformAddrBytes platform

        -- We shouldn't find any TSums, because field types always have
        -- kind data.
        TSum{}          -> Nothing


fieldSizeOfPrim :: Platform -> Name -> Maybe Integer
fieldSizeOfPrim platform nn
 = case nn of
        NameDataTyCon{}         -> Just $ platformAddrBytes platform
        NamePrimTyCon tc        -> fieldSizeOfPrimTyCon platform tc
        _                       -> Nothing

fieldSizeOfPrimTyCon :: Platform -> PrimTyCon -> Maybe Integer
fieldSizeOfPrimTyCon platform tc
 = case tc of
        -- It might make sense to represent these as zero bytes,
        -- but I can't think of reason to have them in data type definitions.
        PrimTyConVoid        -> Nothing

        -- Pointer tycon shouldn't appear by itself.
        PrimTyConPtr         -> Nothing

        PrimTyConAddr        -> Just $ platformAddrBytes platform
        PrimTyConNat         -> Just $ platformNatBytes  platform
        PrimTyConInt         -> Just $ platformNatBytes  platform
        PrimTyConTag         -> Just $ platformTagBytes  platform
        PrimTyConBool        -> Just $ 1

        PrimTyConWord bits
         | bits `rem` 8 == 0 -> Just $ fromIntegral $ bits `div` 8
         | otherwise         -> Nothing

        PrimTyConFloat bits
         | bits `rem` 8 == 0 -> Just $ fromIntegral $ bits `div` 8
         | otherwise         -> Nothing

        -- Vectors don't appear as raw fields.
        PrimTyConVec{}       -> Nothing

        -- Strings shouldn't appear as raw fields, only pointers to them.
        PrimTyConString      -> Nothing