{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE ViewPatterns #-}

-- | Formats on this architecture
--      A Format is a combination of width and class
--
--      TODO:   Signed vs unsigned?
--
--      TODO:   This module is currently shared by all architectures because
--              NCGMonad need to know about it to make a VReg. It would be better
--              to have architecture specific formats, and do the overloading
--              properly. eg SPARC doesn't care about FF80.
--
module GHC.CmmToAsm.Format (
    Format(.., IntegerFormat),
    ScalarFormat(..),
    intFormat,
    floatFormat,
    isIntFormat,
    isIntScalarFormat,
    intScalarFormat,
    isFloatFormat,
    vecFormat,
    isVecFormat,
    cmmTypeFormat,
    formatToWidth,
    scalarWidth,
    formatInBytes,
    isFloatScalarFormat,
    isFloatOrFloatVecFormat,
    floatScalarFormat,
    scalarFormatFormat,
    VirtualRegWithFormat(..),
    RegWithFormat(..),
    takeVirtualRegs,
    takeRealRegs,
)

where

import GHC.Prelude

import GHC.Cmm
import GHC.Platform.Reg ( Reg(..), RealReg, VirtualReg )
import GHC.Types.Unique ( Uniquable(..) )
import GHC.Types.Unique.Set
import GHC.Utils.Outputable
import GHC.Utils.Panic

{- Note [GHC's data format representations]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
GHC has severals types that represent various aspects of data format.
These include:

 * 'CmmType.CmmType': The data classification used throughout the C--
   pipeline. This is a pair of a CmmCat and a Width.

 * 'CmmType.CmmCat': What the bits in a C-- value mean (e.g. a pointer, integer, or floating-point value)

 * 'CmmType.Width': The width of a C-- value.

 * 'CmmType.Length': The width (measured in number of scalars) of a vector value.

 * 'Format.Format': The data format representation used by much of the backend.

 * 'Format.ScalarFormat': The format of a 'Format.VecFormat'\'s scalar.

 * 'RegClass.RegClass': Whether a register is an integer or a floating point/vector register.
-}

-- It looks very like the old MachRep, but it's now of purely local
-- significance, here in the native code generator.  You can change it
-- without global consequences.
--
-- A major use is as an opcode qualifier; thus the opcode
--      mov.l a b
-- might be encoded
--      MOV II32 a b
-- where the Format field encodes the ".l" part.

-- ToDo: it's not clear to me that we need separate signed-vs-unsigned formats
--        here.  I've removed them from the x86 version, we'll see what happens --SDM

-- ToDo: quite a few occurrences of Format could usefully be replaced by Width

data Format
        = II8
        | II16
        | II32
        | II64
        | FF32
        | FF64
        | VecFormat !Length       -- ^ number of elements (always at least 2)
                    !ScalarFormat -- ^ format of each element
        deriving (Show, Eq, Ord)

pattern IntegerFormat :: Format
pattern IntegerFormat <- ( isIntegerFormat -> True )
{-# COMPLETE IntegerFormat, FF32, FF64, VecFormat #-}

isIntegerFormat :: Format -> Bool
isIntegerFormat = \case
  II8  -> True
  II16 -> True
  II32 -> True
  II64 -> True
  _    -> False


instance Outputable Format where
  ppr fmt = text (show fmt)

data ScalarFormat
  = FmtInt8
  | FmtInt16
  | FmtInt32
  | FmtInt64
  | FmtFloat
  | FmtDouble
  deriving (Show, Eq, Ord)

scalarFormatFormat :: ScalarFormat -> Format
scalarFormatFormat = \case
  FmtInt8 -> II8
  FmtInt16 -> II16
  FmtInt32 -> II32
  FmtInt64 -> II64
  FmtFloat -> FF32
  FmtDouble -> FF64

isFloatScalarFormat :: ScalarFormat -> Bool
isFloatScalarFormat = \case
  FmtFloat -> True
  FmtDouble -> True
  _ -> False

isFloatOrFloatVecFormat :: Format -> Bool
isFloatOrFloatVecFormat = \case
  VecFormat _ sFmt -> isFloatScalarFormat sFmt
  fmt -> isFloatFormat fmt

floatScalarFormat :: Width -> ScalarFormat
floatScalarFormat W32 = FmtFloat
floatScalarFormat W64 = FmtDouble
floatScalarFormat w = pprPanic "floatScalarFormat" (ppr w)

isIntScalarFormat :: ScalarFormat -> Bool
isIntScalarFormat = not . isFloatScalarFormat

intScalarFormat :: Width -> ScalarFormat
intScalarFormat = \case
  W8  -> FmtInt8
  W16 -> FmtInt16
  W32 -> FmtInt32
  W64 -> FmtInt64
  w   -> pprPanic "intScalarFormat" (ppr w)

-- | Get the integer format of this width.
intFormat :: Width -> Format
intFormat width
 = case width of
        W8      -> II8
        W16     -> II16
        W32     -> II32
        W64     -> II64
        other   -> sorry $ "The native code generator cannot " ++
            "produce code for Format.intFormat " ++ show other
            ++ "\n\tConsider using the llvm backend with -fllvm"

-- | Check if a format represent an integer value.
isIntFormat :: Format -> Bool
isIntFormat format =
  case format of
    II8  -> True
    II16 -> True
    II32 -> True
    II64 -> True
    _    -> False

-- | Get the float format of this width.
floatFormat :: Width -> Format
floatFormat width
 = case width of
        W32     -> FF32
        W64     -> FF64
        other   -> pprPanic "Format.floatFormat" (ppr other)

-- | Check if a format represents a floating point value.
isFloatFormat :: Format -> Bool
isFloatFormat format
 = case format of
        FF32    -> True
        FF64    -> True
        _       -> False

vecFormat :: CmmType -> Format
vecFormat ty =
  let l      = vecLength ty
      elemTy = vecElemType ty
   in if isFloatType elemTy
      then case typeWidth elemTy of
             W32 -> VecFormat l FmtFloat
             W64 -> VecFormat l FmtDouble
             _   -> pprPanic "Incorrect vector element width" (ppr elemTy)
      else case typeWidth elemTy of
             W8  -> VecFormat l FmtInt8
             W16 -> VecFormat l FmtInt16
             W32 -> VecFormat l FmtInt32
             W64 -> VecFormat l FmtInt64
             _   -> pprPanic "Incorrect vector element width" (ppr elemTy)

-- | Check if a format represents a vector
isVecFormat :: Format -> Bool
isVecFormat (VecFormat {}) = True
isVecFormat _              = False


-- | Convert a Cmm type to a Format.
cmmTypeFormat :: CmmType -> Format
cmmTypeFormat ty
        | isFloatType ty        = floatFormat (typeWidth ty)
        | isVecType ty          = vecFormat ty
        | otherwise             = intFormat (typeWidth ty)


-- | Get the Width of a Format.
formatToWidth :: Format -> Width
formatToWidth format
 = case format of
        II8  -> W8
        II16 -> W16
        II32 -> W32
        II64 -> W64
        FF32 -> W32
        FF64 -> W64
        VecFormat l s ->
          widthFromBytes (l * widthInBytes (scalarWidth s))

scalarWidth :: ScalarFormat -> Width
scalarWidth = \case
  FmtInt8   -> W8
  FmtInt16  -> W16
  FmtInt32  -> W32
  FmtInt64  -> W64
  FmtFloat  -> W32
  FmtDouble -> W64

formatInBytes :: Format -> Int
formatInBytes = widthInBytes . formatToWidth

--------------------------------------------------------------------------------

-- | A typed virtual register: a virtual register, together with the specific
-- format we are using it at.
data VirtualRegWithFormat
    = VirtualRegWithFormat
    { virtualRegWithFormat_reg :: {-# UNPACK #-} !VirtualReg
    , virtualRegWithFormat_format :: !Format
    }

-- | A typed register: a register, together with the specific format we
-- are using it at.
data RegWithFormat
    = RegWithFormat
    { regWithFormat_reg :: {-# UNPACK #-} !Reg
    , regWithFormat_format :: !Format
    }

instance Show RegWithFormat where
  show (RegWithFormat reg fmt) = show reg ++ "::" ++ show fmt

instance Uniquable RegWithFormat where
  getUnique = getUnique . regWithFormat_reg

instance Outputable VirtualRegWithFormat where
  ppr (VirtualRegWithFormat reg fmt) = ppr reg <+> dcolon <+> ppr fmt

instance Outputable RegWithFormat where
  ppr (RegWithFormat reg fmt) = ppr reg <+> dcolon <+> ppr fmt

-- | Take all the virtual registers from this set.
takeVirtualRegs :: UniqSet RegWithFormat -> UniqSet VirtualReg
takeVirtualRegs = mapMaybeUniqSet_sameUnique $
  \ case { RegWithFormat { regWithFormat_reg = RegVirtual vr } -> Just vr; _ -> Nothing }
  -- See Note [Unique Determinism and code generation]

-- | Take all the real registers from this set.
takeRealRegs :: UniqSet RegWithFormat -> UniqSet RealReg
takeRealRegs = mapMaybeUniqSet_sameUnique $
  \ case { RegWithFormat { regWithFormat_reg = RegReal rr } -> Just rr; _ -> Nothing }
  -- See Note [Unique Determinism and code generation]
