ghc-9.12.1: GHC/CmmToAsm/RV64/Instr.hs
-- All instructions will be rendered eventually. Thus, there's no benefit in
-- being lazy in data types.
{-# LANGUAGE StrictData #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module GHC.CmmToAsm.RV64.Instr where
import Data.Maybe
import GHC.Cmm
import GHC.Cmm.BlockId
import GHC.Cmm.CLabel
import GHC.Cmm.Dataflow.Label
import GHC.CmmToAsm.Config
import GHC.CmmToAsm.Format
import GHC.CmmToAsm.Instr (RegUsage (..))
import GHC.CmmToAsm.RV64.Cond
import GHC.CmmToAsm.RV64.Regs
import GHC.CmmToAsm.Types
import GHC.CmmToAsm.Utils
import GHC.Data.FastString (LexicalFastString)
import GHC.Platform
import GHC.Platform.Reg
import GHC.Platform.Regs
import GHC.Platform.Reg.Class.Separate
import GHC.Prelude
import GHC.Stack
import GHC.Types.Unique.DSM
import GHC.Utils.Outputable
import GHC.Utils.Panic
-- | Stack frame header size in bytes.
--
-- The stack frame header is made of the values that are always saved
-- (regardless of the context.) It consists of the saved return address and a
-- pointer to the previous frame. Thus, its size is two stack frame slots which
-- equals two addresses/words (2 * 8 byte).
stackFrameHeaderSize :: Int
stackFrameHeaderSize = 2 * spillSlotSize
-- | All registers are 8 byte wide.
spillSlotSize :: Int
spillSlotSize = 8
-- | The number of bytes that the stack pointer should be aligned to.
stackAlign :: Int
stackAlign = 16
-- | The number of spill slots available without allocating more.
maxSpillSlots :: NCGConfig -> Int
maxSpillSlots config =
( (ncgSpillPreallocSize config - stackFrameHeaderSize)
`div` spillSlotSize
)
- 1
-- | Convert a spill slot number to a *byte* offset.
spillSlotToOffset :: Int -> Int
spillSlotToOffset slot =
stackFrameHeaderSize + spillSlotSize * slot
instance Outputable RegUsage where
ppr (RU reads writes) = text "RegUsage(reads:" <+> ppr reads <> comma <+> text "writes:" <+> ppr writes <> char ')'
-- | 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.
--
-- RegUsage = RU [<read regs>] [<write regs>]
regUsageOfInstr :: Platform -> Instr -> RegUsage
regUsageOfInstr platform instr = case instr of
ANN _ i -> regUsageOfInstr platform i
COMMENT {} -> usage ([], [])
MULTILINE_COMMENT {} -> usage ([], [])
PUSH_STACK_FRAME -> usage ([], [])
POP_STACK_FRAME -> usage ([], [])
LOCATION {} -> usage ([], [])
DELTA {} -> usage ([], [])
ADD dst src1 src2 -> usage (regOp src1 ++ regOp src2, regOp dst)
MUL dst src1 src2 -> usage (regOp src1 ++ regOp src2, regOp dst)
NEG dst src -> usage (regOp src, regOp dst)
MULH dst src1 src2 -> usage (regOp src1 ++ regOp src2, regOp dst)
DIV dst src1 src2 -> usage (regOp src1 ++ regOp src2, regOp dst)
REM dst src1 src2 -> usage (regOp src1 ++ regOp src2, regOp dst)
REMU dst src1 src2 -> usage (regOp src1 ++ regOp src2, regOp dst)
SUB dst src1 src2 -> usage (regOp src1 ++ regOp src2, regOp dst)
DIVU dst src1 src2 -> usage (regOp src1 ++ regOp src2, regOp dst)
AND dst src1 src2 -> usage (regOp src1 ++ regOp src2, regOp dst)
OR dst src1 src2 -> usage (regOp src1 ++ regOp src2, regOp dst)
SRA dst src1 src2 -> usage (regOp src1 ++ regOp src2, regOp dst)
XOR dst src1 src2 -> usage (regOp src1 ++ regOp src2, regOp dst)
SLL dst src1 src2 -> usage (regOp src1 ++ regOp src2, regOp dst)
SRL dst src1 src2 -> usage (regOp src1 ++ regOp src2, regOp dst)
MOV dst src -> usage (regOp src, regOp dst)
-- ORI's third operand is always an immediate
ORI dst src1 _ -> usage (regOp src1, regOp dst)
XORI dst src1 _ -> usage (regOp src1, regOp dst)
J_TBL _ _ t -> usage ([t], [])
B t -> usage (regTarget t, [])
BCOND _ l r t -> usage (regTarget t ++ regOp l ++ regOp r, [])
BL t ps -> usage (t : ps, callerSavedRegisters)
CSET dst l r _ -> usage (regOp l ++ regOp r, regOp dst)
STR _ src dst -> usage (regOp src ++ regOp dst, [])
LDR _ dst src -> usage (regOp src, regOp dst)
LDRU _ dst src -> usage (regOp src, regOp dst)
FENCE _ _ -> usage ([], [])
FCVT _variant dst src -> usage (regOp src, regOp dst)
FABS dst src -> usage (regOp src, regOp dst)
FMIN dst src1 src2 -> usage (regOp src1 ++ regOp src2, regOp dst)
FMAX dst src1 src2 -> usage (regOp src1 ++ regOp src2, regOp dst)
FMA _ dst src1 src2 src3 ->
usage (regOp src1 ++ regOp src2 ++ regOp src3, regOp dst)
_ -> panic $ "regUsageOfInstr: " ++ instrCon instr
where
-- filtering the usage is necessary, otherwise the register
-- allocator will try to allocate pre-defined fixed stg
-- registers as well, as they show up.
usage :: ([Reg], [Reg]) -> RegUsage
usage (srcRegs, dstRegs) =
RU
(map mkFmt $ filter (interesting platform) srcRegs)
(map mkFmt $ filter (interesting platform) dstRegs)
-- SIMD NCG TODO: the format here is used for register spilling/unspilling.
-- As the RISCV64 NCG does not currently support SIMD registers,
-- this simple logic is OK.
mkFmt r = RegWithFormat r fmt
where
fmt = case cls of
RcInteger -> II64
RcFloat -> FF64
RcVector -> sorry "The RISCV64 NCG does not (yet) support vectors; please use -fllvm."
cls = case r of
RegVirtual vr -> classOfVirtualReg (platformArch platform) vr
RegReal rr -> classOfRealReg rr
regAddr :: AddrMode -> [Reg]
regAddr (AddrRegImm r1 _imm) = [r1]
regAddr (AddrReg r1) = [r1]
regOp :: Operand -> [Reg]
regOp (OpReg _w r1) = [r1]
regOp (OpAddr a) = regAddr a
regOp (OpImm _imm) = []
regTarget :: Target -> [Reg]
regTarget (TBlock _bid) = []
regTarget (TReg r1) = [r1]
-- Is this register interesting for the register allocator?
interesting :: Platform -> Reg -> Bool
interesting _ (RegVirtual _) = True
interesting platform (RegReal (RealRegSingle i)) = freeReg platform i
-- | Caller-saved registers (according to calling convention)
--
-- These registers may be clobbered after a jump.
callerSavedRegisters :: [Reg]
callerSavedRegisters =
[regSingle raRegNo]
++ map regSingle [t0RegNo .. t2RegNo]
++ map regSingle [a0RegNo .. a7RegNo]
++ map regSingle [t3RegNo .. t6RegNo]
++ map regSingle [ft0RegNo .. ft7RegNo]
++ map regSingle [fa0RegNo .. fa7RegNo]
-- | Apply a given mapping to all the register references in this instruction.
patchRegsOfInstr :: Instr -> (Reg -> Reg) -> Instr
patchRegsOfInstr instr env = case instr of
ANN d i -> ANN d (patchRegsOfInstr i env)
COMMENT {} -> instr
MULTILINE_COMMENT {} -> instr
PUSH_STACK_FRAME -> instr
POP_STACK_FRAME -> instr
LOCATION {} -> instr
DELTA {} -> instr
ADD o1 o2 o3 -> ADD (patchOp o1) (patchOp o2) (patchOp o3)
MUL o1 o2 o3 -> MUL (patchOp o1) (patchOp o2) (patchOp o3)
NEG o1 o2 -> NEG (patchOp o1) (patchOp o2)
MULH o1 o2 o3 -> MULH (patchOp o1) (patchOp o2) (patchOp o3)
DIV o1 o2 o3 -> DIV (patchOp o1) (patchOp o2) (patchOp o3)
REM o1 o2 o3 -> REM (patchOp o1) (patchOp o2) (patchOp o3)
REMU o1 o2 o3 -> REMU (patchOp o1) (patchOp o2) (patchOp o3)
SUB o1 o2 o3 -> SUB (patchOp o1) (patchOp o2) (patchOp o3)
DIVU o1 o2 o3 -> DIVU (patchOp o1) (patchOp o2) (patchOp o3)
AND o1 o2 o3 -> AND (patchOp o1) (patchOp o2) (patchOp o3)
OR o1 o2 o3 -> OR (patchOp o1) (patchOp o2) (patchOp o3)
SRA o1 o2 o3 -> SRA (patchOp o1) (patchOp o2) (patchOp o3)
XOR o1 o2 o3 -> XOR (patchOp o1) (patchOp o2) (patchOp o3)
SLL o1 o2 o3 -> SLL (patchOp o1) (patchOp o2) (patchOp o3)
SRL o1 o2 o3 -> SRL (patchOp o1) (patchOp o2) (patchOp o3)
MOV o1 o2 -> MOV (patchOp o1) (patchOp o2)
-- o3 cannot be a register for ORI (always an immediate)
ORI o1 o2 o3 -> ORI (patchOp o1) (patchOp o2) (patchOp o3)
XORI o1 o2 o3 -> XORI (patchOp o1) (patchOp o2) (patchOp o3)
J_TBL ids mbLbl t -> J_TBL ids mbLbl (env t)
B t -> B (patchTarget t)
BL t ps -> BL (patchReg t) ps
BCOND c o1 o2 t -> BCOND c (patchOp o1) (patchOp o2) (patchTarget t)
CSET o l r c -> CSET (patchOp o) (patchOp l) (patchOp r) c
STR f o1 o2 -> STR f (patchOp o1) (patchOp o2)
LDR f o1 o2 -> LDR f (patchOp o1) (patchOp o2)
LDRU f o1 o2 -> LDRU f (patchOp o1) (patchOp o2)
FENCE o1 o2 -> FENCE o1 o2
FCVT variant o1 o2 -> FCVT variant (patchOp o1) (patchOp o2)
FABS o1 o2 -> FABS (patchOp o1) (patchOp o2)
FMIN o1 o2 o3 -> FMIN (patchOp o1) (patchOp o2) (patchOp o3)
FMAX o1 o2 o3 -> FMAX (patchOp o1) (patchOp o2) (patchOp o3)
FMA s o1 o2 o3 o4 ->
FMA s (patchOp o1) (patchOp o2) (patchOp o3) (patchOp o4)
_ -> panic $ "patchRegsOfInstr: " ++ instrCon instr
where
patchOp :: Operand -> Operand
patchOp (OpReg w r) = OpReg w (env r)
patchOp (OpAddr a) = OpAddr (patchAddr a)
patchOp opImm = opImm
patchTarget :: Target -> Target
patchTarget (TReg r) = TReg (env r)
patchTarget tBlock = tBlock
patchAddr :: AddrMode -> AddrMode
patchAddr (AddrRegImm r1 imm) = AddrRegImm (env r1) imm
patchAddr (AddrReg r) = AddrReg (env r)
patchReg :: Reg -> Reg
patchReg = env
-- | 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
isJumpishInstr instr = case instr of
ANN _ i -> isJumpishInstr i
J_TBL {} -> True
B {} -> True
BL {} -> True
BCOND {} -> True
_ -> False
canFallthroughTo :: Instr -> BlockId -> Bool
canFallthroughTo insn bid =
case insn of
B (TBlock target) -> bid == target
BCOND _ _ _ (TBlock target) -> bid == target
J_TBL targets _ _ -> all isTargetBid targets
_ -> False
where
isTargetBid target = case target of
Nothing -> True
Just target -> target == bid
-- | Get the `BlockId`s of the jump destinations (if any)
jumpDestsOfInstr :: Instr -> [BlockId]
jumpDestsOfInstr (ANN _ i) = jumpDestsOfInstr i
jumpDestsOfInstr (J_TBL ids _mbLbl _r) = catMaybes ids
jumpDestsOfInstr (B t) = [id | TBlock id <- [t]]
jumpDestsOfInstr (BCOND _ _ _ t) = [id | TBlock id <- [t]]
jumpDestsOfInstr _ = []
-- | Change the destination of this (potential) jump instruction.
--
-- Used in the linear allocator when adding fixup blocks for join
-- points.
patchJumpInstr :: Instr -> (BlockId -> BlockId) -> Instr
patchJumpInstr instr patchF =
case instr of
ANN d i -> ANN d (patchJumpInstr i patchF)
J_TBL ids mbLbl r -> J_TBL (map (fmap patchF) ids) mbLbl r
B (TBlock bid) -> B (TBlock (patchF bid))
BCOND c o1 o2 (TBlock bid) -> BCOND c o1 o2 (TBlock (patchF bid))
_ -> panic $ "patchJumpInstr: " ++ instrCon instr
-- -----------------------------------------------------------------------------
-- Note [RISCV64 Spills and Reloads]
-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
--
-- We reserve @RESERVED_C_STACK_BYTES@ on the C stack for spilling and reloading
-- registers. The load and store instructions of RISCV64 address with a signed
-- 12-bit immediate + a register; machine stackpointer (sp/x2) in this case.
--
-- The @RESERVED_C_STACK_BYTES@ is 16k, so we can't always address into it in a
-- single load/store instruction. There are offsets to sp (not to be confused
-- with STG's SP!) which need a register to be calculated.
--
-- Using sp to compute the offset would violate assumptions about the stack pointer
-- pointing to the top of the stack during signal handling. As we can't force
-- every signal to use its own stack, we have to ensure that the stack pointer
-- always points to the top of the stack, and we can't use it for computation.
--
-- So, we reserve one register (TMP) for this purpose (and other, unrelated
-- intermediate operations.) See Note [The made-up RISCV64 TMP (IP) register]
-- | Generate instructions to spill a register into a spill slot.
mkSpillInstr ::
(HasCallStack) =>
NCGConfig ->
-- | register to spill
RegWithFormat ->
-- | current stack delta
Int ->
-- | spill slot to use
Int ->
[Instr]
mkSpillInstr _config (RegWithFormat reg _fmt) delta slot =
case off - delta of
imm | fitsIn12bitImm imm -> [mkStrSpImm imm]
imm ->
[ movImmToTmp imm,
addSpToTmp,
mkStrTmp
]
where
fmt = case reg of
RegReal (RealRegSingle n) | n < d0RegNo -> II64
_ -> FF64
mkStrSpImm imm =
ANN (text "Spill@" <> int (off - delta))
$ STR fmt (OpReg W64 reg) (OpAddr (AddrRegImm spMachReg (ImmInt imm)))
movImmToTmp imm =
ANN (text "Spill: TMP <- " <> int imm)
$ MOV tmp (OpImm (ImmInt imm))
addSpToTmp =
ANN (text "Spill: TMP <- SP + TMP ")
$ ADD tmp tmp sp
mkStrTmp =
ANN (text "Spill@" <> int (off - delta))
$ STR fmt (OpReg W64 reg) (OpAddr (AddrReg tmpReg))
off = spillSlotToOffset slot
-- | Generate instructions to load a register from a spill slot.
mkLoadInstr ::
NCGConfig ->
-- | register to load
RegWithFormat ->
-- | current stack delta
Int ->
-- | spill slot to use
Int ->
[Instr]
mkLoadInstr _config (RegWithFormat reg _fmt) delta slot =
case off - delta of
imm | fitsIn12bitImm imm -> [mkLdrSpImm imm]
imm ->
[ movImmToTmp imm,
addSpToTmp,
mkLdrTmp
]
where
fmt = case reg of
RegReal (RealRegSingle n) | n < d0RegNo -> II64
_ -> FF64
mkLdrSpImm imm =
ANN (text "Reload@" <> int (off - delta))
$ LDR fmt (OpReg W64 reg) (OpAddr (AddrRegImm spMachReg (ImmInt imm)))
movImmToTmp imm =
ANN (text "Reload: TMP <- " <> int imm)
$ MOV tmp (OpImm (ImmInt imm))
addSpToTmp =
ANN (text "Reload: TMP <- SP + TMP ")
$ ADD tmp tmp sp
mkLdrTmp =
ANN (text "Reload@" <> int (off - delta))
$ LDR fmt (OpReg W64 reg) (OpAddr (AddrReg tmpReg))
off = spillSlotToOffset slot
-- | See if this instruction is telling us the current C stack delta
takeDeltaInstr :: Instr -> Maybe Int
takeDeltaInstr (ANN _ i) = takeDeltaInstr i
takeDeltaInstr (DELTA i) = Just i
takeDeltaInstr _ = Nothing
-- | Not real instructions. Just meta data
isMetaInstr :: Instr -> Bool
isMetaInstr instr =
case instr of
ANN _ i -> isMetaInstr i
COMMENT {} -> True
MULTILINE_COMMENT {} -> True
LOCATION {} -> True
LDATA {} -> True
NEWBLOCK {} -> True
DELTA {} -> True
PUSH_STACK_FRAME -> True
POP_STACK_FRAME -> True
_ -> False
-- | Copy the value in a register to another one.
--
-- Must work for all register classes.
mkRegRegMoveInstr :: Reg -> Reg -> Instr
mkRegRegMoveInstr src dst = ANN desc instr
where
desc = text "Reg->Reg Move: " <> ppr src <> text " -> " <> ppr dst
instr = MOV (operandFromReg dst) (operandFromReg src)
-- | Take the source and destination from this (potential) reg -> reg move instruction
--
-- We have to be a bit careful here: A `MOV` can also mean an implicit
-- conversion. This case is filtered out.
takeRegRegMoveInstr :: Instr -> Maybe (Reg, Reg)
takeRegRegMoveInstr (MOV (OpReg width dst) (OpReg width' src))
| width == width' && (isFloatReg dst == isFloatReg src) = pure (src, dst)
takeRegRegMoveInstr _ = Nothing
-- | Make an unconditional jump instruction.
mkJumpInstr :: BlockId -> [Instr]
mkJumpInstr = pure . B . TBlock
-- | Decrement @sp@ to allocate stack space.
--
-- The stack grows downwards, so we decrement the stack pointer by @n@ (bytes).
-- This is dual to `mkStackDeallocInstr`. @sp@ is the RISCV stack pointer, not
-- to be confused with the STG stack pointer.
mkStackAllocInstr :: Platform -> Int -> [Instr]
mkStackAllocInstr _platform = moveSp . negate
-- | Increment SP to deallocate stack space.
--
-- The stack grows downwards, so we increment the stack pointer by @n@ (bytes).
-- This is dual to `mkStackAllocInstr`. @sp@ is the RISCV stack pointer, not to
-- be confused with the STG stack pointer.
mkStackDeallocInstr :: Platform -> Int -> [Instr]
mkStackDeallocInstr _platform = moveSp
moveSp :: Int -> [Instr]
moveSp n
| n == 0 = []
| n /= 0 && fitsIn12bitImm n = pure . ANN desc $ ADD sp sp (OpImm (ImmInt n))
| otherwise =
-- This ends up in three effective instructions. We could get away with
-- two for intMax12bit < n < 3 * intMax12bit by recursing once. However,
-- this way is likely less surprising.
[ ANN desc (MOV tmp (OpImm (ImmInt n))),
ADD sp sp tmp
]
where
desc = text "Move SP:" <+> int n
--
-- See Note [extra spill slots] in X86/Instr.hs
--
allocMoreStack ::
Platform ->
Int ->
NatCmmDecl statics GHC.CmmToAsm.RV64.Instr.Instr ->
UniqDSM (NatCmmDecl statics GHC.CmmToAsm.RV64.Instr.Instr, [(BlockId, BlockId)])
allocMoreStack _ _ top@(CmmData _ _) = return (top, [])
allocMoreStack platform slots proc@(CmmProc info lbl live (ListGraph code)) = do
let entries = entryBlocks proc
retargetList <- mapM (\e -> (e,) <$> newBlockId) entries
let delta = ((x + stackAlign - 1) `quot` stackAlign) * stackAlign -- round up
where
x = slots * spillSlotSize -- sp delta
alloc = mkStackAllocInstr platform delta
dealloc = mkStackDeallocInstr platform delta
new_blockmap :: LabelMap BlockId
new_blockmap = mapFromList retargetList
insert_stack_insn (BasicBlock id insns)
| Just new_blockid <- mapLookup id new_blockmap =
[ BasicBlock id $ alloc ++ [B (TBlock new_blockid)],
BasicBlock new_blockid block'
]
| otherwise =
[BasicBlock id block']
where
block' = foldr insert_dealloc [] insns
insert_dealloc insn r = case insn of
J_TBL {} -> dealloc ++ (insn : r)
ANN _ e -> insert_dealloc e r
_other
| jumpDestsOfInstr insn /= [] ->
patchJumpInstr insn retarget : r
_other -> insn : r
where
retarget b = fromMaybe b (mapLookup b new_blockmap)
new_code = concatMap insert_stack_insn code
return (CmmProc info lbl live (ListGraph new_code), retargetList)
data Instr
= -- | Comment pseudo-op
COMMENT SDoc
| -- | Multi-line comment pseudo-op
MULTILINE_COMMENT SDoc
| -- | Annotated instruction. Should print <instr> # <doc>
ANN SDoc Instr
| -- | Location pseudo-op @.loc@ (file, line, col, name)
LOCATION Int Int Int LexicalFastString
| -- | Static data spat out during code generation.
LDATA Section RawCmmStatics
| -- | Start a new basic block.
--
-- Useful during codegen, removed later. Preceding instruction should be a
-- jump, as per the invariants for a BasicBlock (see Cmm).
NEWBLOCK BlockId
| -- | Specify current stack offset for benefit of subsequent passes
DELTA Int
| -- | Push a minimal stack frame consisting of the return address (RA) and the frame pointer (FP).
PUSH_STACK_FRAME
| -- | Pop the minimal stack frame of prior `PUSH_STACK_FRAME`.
POP_STACK_FRAME
| -- | Arithmetic addition (both integer and floating point)
--
-- @rd = rs1 + rs2@
ADD Operand Operand Operand
| -- | Arithmetic subtraction (both integer and floating point)
--
-- @rd = rs1 - rs2@
SUB Operand Operand Operand
| -- | Logical AND (integer only)
--
-- @rd = rs1 & rs2@
AND Operand Operand Operand
| -- | Logical OR (integer only)
--
-- @rd = rs1 | rs2@
OR Operand Operand Operand
| -- | Logical left shift (zero extened, integer only)
--
-- @rd = rs1 << rs2@
SLL Operand Operand Operand
| -- | Logical right shift (zero extened, integer only)
--
-- @rd = rs1 >> rs2@
SRL Operand Operand Operand
| -- | Arithmetic right shift (sign-extened, integer only)
--
-- @rd = rs1 >> rs2@
SRA Operand Operand Operand
| -- | Store to memory (both, integer and floating point)
STR Format Operand Operand
| -- | Load from memory (sign-extended, integer and floating point)
LDR Format Operand Operand
| -- | Load from memory (unsigned, integer and floating point)
LDRU Format Operand Operand
| -- | Arithmetic multiplication (both, integer and floating point)
--
-- @rd = rn × rm@
MUL Operand Operand Operand
| -- | Negation (both, integer and floating point)
--
-- @rd = -op2@
NEG Operand Operand
| -- | Division (both, integer and floating point)
--
-- @rd = rn ÷ rm@
DIV Operand Operand Operand
| -- | Remainder (integer only, signed)
--
-- @rd = rn % rm@
REM Operand Operand Operand --
| -- | Remainder (integer only, unsigned)
--
-- @rd = |rn % rm|@
REMU Operand Operand Operand
| -- | High part of a multiplication that doesn't fit into 64bits (integer only)
--
-- E.g. for a multiplication with 64bits width: @rd = (rs1 * rs2) >> 64@.
MULH Operand Operand Operand
| -- | Unsigned division (integer only)
--
-- @rd = |rn ÷ rm|@
DIVU Operand Operand Operand
| -- | XOR (integer only)
--
-- @rd = rn ⊕ op2@
XOR Operand Operand Operand
| -- | ORI with immediate (integer only)
--
-- @rd = rn | op2@
ORI Operand Operand Operand
| -- | OR with immediate (integer only)
--
-- @rd = rn ⊕ op2@
XORI Operand Operand Operand
| -- | Move to register (integer and floating point)
--
-- @rd = rn@ or @rd = #imm@
MOV Operand Operand
| -- | Pseudo-op for conditional setting of a register.
--
-- @if(o2 cond o3) op <- 1 else op <- 0@
CSET Operand Operand Operand Cond
| -- | A jump instruction with data for switch/jump tables
J_TBL [Maybe BlockId] (Maybe CLabel) Reg
| -- | Unconditional jump (no linking)
B Target
| -- | Unconditional jump, links return address (sets @ra@/@x1@)
BL Reg [Reg]
| -- | branch with condition (integer only)
BCOND Cond Operand Operand Target
| -- | Fence instruction
--
-- Memory barrier.
FENCE FenceType FenceType
| -- | Floating point conversion
FCVT FcvtVariant Operand Operand
| -- | Floating point ABSolute value
FABS Operand Operand
| -- | Min
-- dest = min(r1)
FMIN Operand Operand Operand
| -- | Max
FMAX Operand Operand Operand
| -- | Floating-point fused multiply-add instructions
--
-- - fmadd : d = r1 * r2 + r3
-- - fnmsub: d = r1 * r2 - r3
-- - fmsub : d = - r1 * r2 + r3
-- - fnmadd: d = - r1 * r2 - r3
FMA FMASign Operand Operand Operand Operand
-- | Operand of a FENCE instruction (@r@, @w@ or @rw@)
data FenceType = FenceRead | FenceWrite | FenceReadWrite
-- | Variant of a floating point conversion instruction
data FcvtVariant = FloatToFloat | IntToFloat | FloatToInt
instrCon :: Instr -> String
instrCon i =
case i of
COMMENT {} -> "COMMENT"
MULTILINE_COMMENT {} -> "COMMENT"
ANN {} -> "ANN"
LOCATION {} -> "LOCATION"
LDATA {} -> "LDATA"
NEWBLOCK {} -> "NEWBLOCK"
DELTA {} -> "DELTA"
PUSH_STACK_FRAME {} -> "PUSH_STACK_FRAME"
POP_STACK_FRAME {} -> "POP_STACK_FRAME"
ADD {} -> "ADD"
OR {} -> "OR"
MUL {} -> "MUL"
NEG {} -> "NEG"
DIV {} -> "DIV"
REM {} -> "REM"
REMU {} -> "REMU"
MULH {} -> "MULH"
SUB {} -> "SUB"
DIVU {} -> "DIVU"
AND {} -> "AND"
SRA {} -> "SRA"
XOR {} -> "XOR"
SLL {} -> "SLL"
SRL {} -> "SRL"
MOV {} -> "MOV"
ORI {} -> "ORI"
XORI {} -> "ORI"
STR {} -> "STR"
LDR {} -> "LDR"
LDRU {} -> "LDRU"
CSET {} -> "CSET"
J_TBL {} -> "J_TBL"
B {} -> "B"
BL {} -> "BL"
BCOND {} -> "BCOND"
FENCE {} -> "FENCE"
FCVT {} -> "FCVT"
FABS {} -> "FABS"
FMIN {} -> "FMIN"
FMAX {} -> "FMAX"
FMA variant _ _ _ _ ->
case variant of
FMAdd -> "FMADD"
FMSub -> "FMSUB"
FNMAdd -> "FNMADD"
FNMSub -> "FNMSUB"
data Target
= TBlock BlockId
| TReg Reg
data Operand
= -- | register
OpReg Width Reg
| -- | immediate value
OpImm Imm
| -- | memory reference
OpAddr AddrMode
deriving (Eq, Show)
operandFromReg :: Reg -> Operand
operandFromReg = OpReg W64
operandFromRegNo :: RegNo -> Operand
operandFromRegNo = operandFromReg . regSingle
zero, ra, sp, gp, tp, fp, tmp :: Operand
zero = operandFromReg zeroReg
ra = operandFromReg raReg
sp = operandFromReg spMachReg
gp = operandFromRegNo 3
tp = operandFromRegNo 4
fp = operandFromRegNo 8
tmp = operandFromReg tmpReg
x0, x1, x2, x3, x4, x5, x6, x7 :: Operand
x8, x9, x10, x11, x12, x13, x14, x15 :: Operand
x16, x17, x18, x19, x20, x21, x22, x23 :: Operand
x24, x25, x26, x27, x28, x29, x30, x31 :: Operand
x0 = operandFromRegNo x0RegNo
x1 = operandFromRegNo 1
x2 = operandFromRegNo 2
x3 = operandFromRegNo 3
x4 = operandFromRegNo 4
x5 = operandFromRegNo x5RegNo
x6 = operandFromRegNo 6
x7 = operandFromRegNo x7RegNo
x8 = operandFromRegNo 8
x9 = operandFromRegNo 9
x10 = operandFromRegNo x10RegNo
x11 = operandFromRegNo 11
x12 = operandFromRegNo 12
x13 = operandFromRegNo 13
x14 = operandFromRegNo 14
x15 = operandFromRegNo 15
x16 = operandFromRegNo 16
x17 = operandFromRegNo x17RegNo
x18 = operandFromRegNo 18
x19 = operandFromRegNo 19
x20 = operandFromRegNo 20
x21 = operandFromRegNo 21
x22 = operandFromRegNo 22
x23 = operandFromRegNo 23
x24 = operandFromRegNo 24
x25 = operandFromRegNo 25
x26 = operandFromRegNo 26
x27 = operandFromRegNo 27
x28 = operandFromRegNo x28RegNo
x29 = operandFromRegNo 29
x30 = operandFromRegNo 30
x31 = operandFromRegNo x31RegNo
d0, d1, d2, d3, d4, d5, d6, d7 :: Operand
d8, d9, d10, d11, d12, d13, d14, d15 :: Operand
d16, d17, d18, d19, d20, d21, d22, d23 :: Operand
d24, d25, d26, d27, d28, d29, d30, d31 :: Operand
d0 = operandFromRegNo d0RegNo
d1 = operandFromRegNo 33
d2 = operandFromRegNo 34
d3 = operandFromRegNo 35
d4 = operandFromRegNo 36
d5 = operandFromRegNo 37
d6 = operandFromRegNo 38
d7 = operandFromRegNo d7RegNo
d8 = operandFromRegNo 40
d9 = operandFromRegNo 41
d10 = operandFromRegNo d10RegNo
d11 = operandFromRegNo 43
d12 = operandFromRegNo 44
d13 = operandFromRegNo 45
d14 = operandFromRegNo 46
d15 = operandFromRegNo 47
d16 = operandFromRegNo 48
d17 = operandFromRegNo d17RegNo
d18 = operandFromRegNo 50
d19 = operandFromRegNo 51
d20 = operandFromRegNo 52
d21 = operandFromRegNo 53
d22 = operandFromRegNo 54
d23 = operandFromRegNo 55
d24 = operandFromRegNo 56
d25 = operandFromRegNo 57
d26 = operandFromRegNo 58
d27 = operandFromRegNo 59
d28 = operandFromRegNo 60
d29 = operandFromRegNo 61
d30 = operandFromRegNo 62
d31 = operandFromRegNo d31RegNo
fitsIn12bitImm :: (Num a, Ord a) => a -> Bool
fitsIn12bitImm off = off >= intMin12bit && off <= intMax12bit
intMin12bit :: (Num a) => a
intMin12bit = -2048
intMax12bit :: (Num a) => a
intMax12bit = 2047
fitsIn32bits :: (Num a, Ord a, Bits a) => a -> Bool
fitsIn32bits i = (-1 `shiftL` 31) <= i && i <= (1 `shiftL` 31 - 1)
isNbitEncodeable :: Int -> Integer -> Bool
isNbitEncodeable n i = let shift = n - 1 in (-1 `shiftL` shift) <= i && i < (1 `shiftL` shift)
isEncodeableInWidth :: Width -> Integer -> Bool
isEncodeableInWidth = isNbitEncodeable . widthInBits
isIntOp :: Operand -> Bool
isIntOp = not . isFloatOp
isFloatOp :: Operand -> Bool
isFloatOp (OpReg _ reg) | isFloatReg reg = True
isFloatOp _ = False
isFloatReg :: Reg -> Bool
isFloatReg (RegReal (RealRegSingle i)) | i > 31 = True
isFloatReg (RegVirtual (VirtualRegD _)) = True
isFloatReg _ = False