module GHC.CmmToAsm.Instr
   ( Instruction(..)
   , RegUsage(..)
   , noUsage
   )
where

import GHC.Prelude

import GHC.Platform
import GHC.Platform.Reg
import GHC.Utils.Outputable (SDoc)

import GHC.Cmm.BlockId

import GHC.CmmToAsm.Config

-- | Holds a list of source and destination registers used by a
--      particular instruction.
--
--   Machine registers that are pre-allocated to stgRegs are filtered
--      out, because they are uninteresting from a register allocation
--      standpoint.  (We wouldn't want them to end up on the free list!)
--
--   As far as we are concerned, the fixed registers simply don't exist
--      (for allocation purposes, anyway).
--
data RegUsage
        = RU    {
                RegUsage -> [Reg]
reads :: [Reg],
                RegUsage -> [Reg]
writes :: [Reg]
                }

-- | No regs read or written to.
noUsage :: RegUsage
noUsage :: RegUsage
noUsage  = [Reg] -> [Reg] -> RegUsage
RU [] []

-- | Common things that we can do with instructions, on all architectures.
--      These are used by the shared parts of the native code generator,
--      specifically the register allocators.
--
class Instruction instr where

        -- | Get the registers that are being used by this instruction.
        --      regUsage doesn't need to do any trickery for jumps and such.
        --      Just state precisely the regs read and written by that insn.
        --      The consequences of control flow transfers, as far as register
        --      allocation goes, are taken care of by the register allocator.
        --
        regUsageOfInstr
                :: Platform
                -> instr
                -> RegUsage


        -- | Apply a given mapping to all the register references in this
        --      instruction.
        patchRegsOfInstr
                :: instr
                -> (Reg -> Reg)
                -> instr


        -- | Checks whether this instruction is a jump/branch instruction.
        --      One that can change the flow of control in a way that the
        --      register allocator needs to worry about.
        isJumpishInstr
                :: instr -> Bool


        -- | Give the possible destinations of this jump instruction.
        --      Must be defined for all jumpish instructions.
        jumpDestsOfInstr
                :: instr -> [BlockId]


        -- | Change the destination of this jump instruction.
        --      Used in the linear allocator when adding fixup blocks for join
        --      points.
        patchJumpInstr
                :: instr
                -> (BlockId -> BlockId)
                -> instr


        -- | An instruction to spill a register into a spill slot.
        mkSpillInstr
                :: NCGConfig
                -> Reg          -- ^ the reg to spill
                -> Int          -- ^ the current stack delta
                -> Int          -- ^ spill slot to use
                -> instr


        -- | An instruction to reload a register from a spill slot.
        mkLoadInstr
                :: NCGConfig
                -> Reg          -- ^ the reg to reload.
                -> Int          -- ^ the current stack delta
                -> Int          -- ^ the spill slot to use
                -> instr

        -- | See if this instruction is telling us the current C stack delta
        takeDeltaInstr
                :: instr
                -> Maybe Int

        -- | Check whether this instruction is some meta thing inserted into
        --      the instruction stream for other purposes.
        --
        --      Not something that has to be treated as a real machine instruction
        --      and have its registers allocated.
        --
        --      eg, comments, delta, ldata, etc.
        isMetaInstr
                :: instr
                -> Bool



        -- | Copy the value in a register to another one.
        --      Must work for all register classes.
        mkRegRegMoveInstr
                :: Platform
                -> Reg          -- ^ source register
                -> Reg          -- ^ destination register
                -> instr

        -- | Take the source and destination from this reg -> reg move instruction
        --      or Nothing if it's not one
        takeRegRegMoveInstr
                :: instr
                -> Maybe (Reg, Reg)

        -- | Make an unconditional jump instruction.
        --      For architectures with branch delay slots, its ok to put
        --      a NOP after the jump. Don't fill the delay slot with an
        --      instruction that references regs or you'll confuse the
        --      linear allocator.
        mkJumpInstr
                :: BlockId
                -> [instr]


        -- Subtract an amount from the C stack pointer
        mkStackAllocInstr
                :: Platform
                -> Int
                -> [instr]

        -- Add an amount to the C stack pointer
        mkStackDeallocInstr
                :: Platform
                -> Int
                -> [instr]

        -- | Pretty-print an instruction
        pprInstr :: Platform -> instr -> SDoc