ghc-9.12.1: GHC/CmmToAsm/RV64/Ppr.hs
{-# LANGUAGE ScopedTypeVariables #-}
module GHC.CmmToAsm.RV64.Ppr (pprNatCmmDecl, pprInstr) where
import GHC.Cmm hiding (topInfoTable)
import GHC.Cmm.BlockId
import GHC.Cmm.CLabel
import GHC.Cmm.Dataflow.Label
import GHC.CmmToAsm.Config
import GHC.CmmToAsm.Format
import GHC.CmmToAsm.Ppr
import GHC.CmmToAsm.RV64.Cond
import GHC.CmmToAsm.RV64.Instr
import GHC.CmmToAsm.RV64.Regs
import GHC.CmmToAsm.Types
import GHC.CmmToAsm.Utils
import GHC.Platform
import GHC.Platform.Reg
import GHC.Prelude hiding (EQ)
import GHC.Types.Basic (Alignment, alignmentBytes, mkAlignment)
import GHC.Types.Unique (getUnique, pprUniqueAlways)
import GHC.Utils.Outputable
import GHC.Utils.Panic
pprNatCmmDecl :: forall doc. (IsDoc doc) => NCGConfig -> NatCmmDecl RawCmmStatics Instr -> doc
pprNatCmmDecl config (CmmData section dats) =
pprSectionAlign config section $$ pprDatas config dats
pprNatCmmDecl config proc@(CmmProc top_info lbl _ (ListGraph blocks)) =
let platform = ncgPlatform config
pprProcAlignment :: doc
pprProcAlignment = maybe empty (pprAlign . mkAlignment) (ncgProcAlignment config)
in pprProcAlignment
$$ case topInfoTable proc of
Nothing ->
-- special case for code without info table:
pprSectionAlign config (Section Text lbl)
$$
-- do not
-- pprProcAlignment config $$
pprLabel platform lbl
$$ vcat (map (pprBasicBlock config top_info) blocks) -- blocks guaranteed not null, so label needed
$$ ppWhen
(ncgDwarfEnabled config)
(line (pprBlockEndLabel platform lbl) $$ line (pprProcEndLabel platform lbl))
$$ pprSizeDecl platform lbl
Just (CmmStaticsRaw info_lbl _) ->
pprSectionAlign config (Section Text info_lbl)
$$
-- pprProcAlignment config $$
( if platformHasSubsectionsViaSymbols platform
then line (pprAsmLabel platform (mkDeadStripPreventer info_lbl) <> char ':')
else empty
)
$$ vcat (map (pprBasicBlock config top_info) blocks)
$$ ppWhen (ncgDwarfEnabled config) (line (pprProcEndLabel platform info_lbl))
$$
-- above: Even the first block gets a label, because with branch-chain
-- elimination, it might be the target of a goto.
( if platformHasSubsectionsViaSymbols platform
then -- See Note [Subsections Via Symbols]
line
$ text "\t.long "
<+> pprAsmLabel platform info_lbl
<+> char '-'
<+> pprAsmLabel platform (mkDeadStripPreventer info_lbl)
else empty
)
$$ pprSizeDecl platform info_lbl
{-# SPECIALIZE pprNatCmmDecl :: NCGConfig -> NatCmmDecl RawCmmStatics Instr -> SDoc #-}
{-# SPECIALIZE pprNatCmmDecl :: NCGConfig -> NatCmmDecl RawCmmStatics Instr -> HDoc #-} -- see Note [SPECIALIZE to HDoc] in GHC.Utils.Outputable
pprLabel :: (IsDoc doc) => Platform -> CLabel -> doc
pprLabel platform lbl =
pprGloblDecl platform lbl
$$ pprTypeDecl platform lbl
$$ line (pprAsmLabel platform lbl <> char ':')
pprAlign :: (IsDoc doc) => Alignment -> doc
pprAlign alignment =
-- "The .align directive for RISC-V is an alias to .p2align, which aligns to a
-- power of two, so .align 2 means align to 4 bytes. Because the definition of
-- the .align directive varies by architecture, it is recommended to use the
-- unambiguous .p2align or .balign directives instead."
-- (https://github.com/riscv-non-isa/riscv-asm-manual/blob/main/riscv-asm.md#-align)
line $ text "\t.balign " <> int (alignmentBytes alignment)
-- | Print appropriate alignment for the given section type.
--
-- Currently, this always aligns to a full machine word (8 byte.) A future
-- improvement could be to really do this per section type (though, it's
-- probably not a big gain.)
pprAlignForSection :: (IsDoc doc) => SectionType -> doc
pprAlignForSection _seg = pprAlign . mkAlignment $ 8
-- | Print section header and appropriate alignment for that section.
--
-- This will e.g. emit a header like:
--
-- .section .text
-- .balign 8
pprSectionAlign :: (IsDoc doc) => NCGConfig -> Section -> doc
pprSectionAlign _config (Section (OtherSection _) _) =
panic "RV64.Ppr.pprSectionAlign: unknown section"
pprSectionAlign config sec@(Section seg _) =
line (pprSectionHeader config sec)
$$ pprAlignForSection seg
pprProcEndLabel ::
(IsLine doc) =>
Platform ->
-- | Procedure name
CLabel ->
doc
pprProcEndLabel platform lbl =
pprAsmLabel platform (mkAsmTempProcEndLabel lbl) <> colon
pprBlockEndLabel ::
(IsLine doc) =>
Platform ->
-- | Block name
CLabel ->
doc
pprBlockEndLabel platform lbl =
pprAsmLabel platform (mkAsmTempEndLabel lbl) <> colon
-- | Output the ELF .size directive (if needed.)
pprSizeDecl :: (IsDoc doc) => Platform -> CLabel -> doc
pprSizeDecl platform lbl
| osElfTarget (platformOS platform) =
line $ text "\t.size" <+> asmLbl <> text ", .-" <> asmLbl
where
asmLbl = pprAsmLabel platform lbl
pprSizeDecl _ _ = empty
pprBasicBlock ::
(IsDoc doc) =>
NCGConfig ->
LabelMap RawCmmStatics ->
NatBasicBlock Instr ->
doc
pprBasicBlock config info_env (BasicBlock blockid instrs) =
maybe_infotable
$ pprLabel platform asmLbl
$$ vcat (map (pprInstr platform) (id {-detectTrivialDeadlock-} optInstrs))
$$ ppWhen
(ncgDwarfEnabled config)
( -- Emit both end labels since this may end up being a standalone
-- top-level block
line
( pprBlockEndLabel platform asmLbl
<> pprProcEndLabel platform asmLbl
)
)
where
-- TODO: Check if we can filter more instructions here.
-- TODO: Shouldn't this be a more general check on a higher level?
-- Filter out identity moves. E.g. mov x18, x18 will be dropped.
optInstrs = filter f instrs
where
f (MOV o1 o2) | o1 == o2 = False
f _ = True
asmLbl = blockLbl blockid
platform = ncgPlatform config
maybe_infotable c = case mapLookup blockid info_env of
Nothing -> c
Just (CmmStaticsRaw info_lbl info) ->
-- pprAlignForSection platform Text $$
infoTableLoc
$$ vcat (map (pprData config) info)
$$ pprLabel platform info_lbl
$$ c
$$ ppWhen
(ncgDwarfEnabled config)
(line (pprBlockEndLabel platform info_lbl))
-- Make sure the info table has the right .loc for the block
-- coming right after it. See Note [Info Offset]
infoTableLoc = case instrs of
(l@LOCATION {} : _) -> pprInstr platform l
_other -> empty
pprDatas :: (IsDoc doc) => NCGConfig -> RawCmmStatics -> doc
-- See Note [emit-time elimination of static indirections] in "GHC.Cmm.CLabel".
pprDatas config (CmmStaticsRaw alias [CmmStaticLit (CmmLabel lbl), CmmStaticLit ind, _, _])
| lbl == mkIndStaticInfoLabel,
let labelInd (CmmLabelOff l _) = Just l
labelInd (CmmLabel l) = Just l
labelInd _ = Nothing,
Just ind' <- labelInd ind,
alias `mayRedirectTo` ind' =
pprGloblDecl (ncgPlatform config) alias
$$ line (text ".equiv" <+> pprAsmLabel (ncgPlatform config) alias <> comma <> pprAsmLabel (ncgPlatform config) ind')
pprDatas config (CmmStaticsRaw lbl dats) =
vcat (pprLabel platform lbl : map (pprData config) dats)
where
platform = ncgPlatform config
pprData :: (IsDoc doc) => NCGConfig -> CmmStatic -> doc
pprData _config (CmmString str) = line (pprString str)
pprData _config (CmmFileEmbed path _) = line (pprFileEmbed path)
-- TODO: AFAIK there no Darwin for RISCV, so we may consider to simplify this.
pprData config (CmmUninitialised bytes) =
line
$ let platform = ncgPlatform config
in if platformOS platform == OSDarwin
then text ".space " <> int bytes
else text ".skip " <> int bytes
pprData config (CmmStaticLit lit) = pprDataItem config lit
pprGloblDecl :: (IsDoc doc) => Platform -> CLabel -> doc
pprGloblDecl platform lbl
| not (externallyVisibleCLabel lbl) = empty
| otherwise = line (text "\t.globl " <> pprAsmLabel platform lbl)
-- Note [Always use objects for info tables]
-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- See discussion in X86.Ppr for why this is necessary. Essentially we need to
-- ensure that we never pass function symbols when we might want to lookup the
-- info table. If we did, we could end up with procedure linking tables
-- (PLT)s, and thus the lookup wouldn't point to the function, but into the
-- jump table.
--
-- Fun fact: The LLVMMangler exists to patch this issue on the LLVM side as
-- well.
pprLabelType' :: (IsLine doc) => Platform -> CLabel -> doc
pprLabelType' platform lbl =
if isCFunctionLabel lbl || functionOkInfoTable
then text "@function"
else text "@object"
where
functionOkInfoTable =
platformTablesNextToCode platform
&& isInfoTableLabel lbl
&& not (isCmmInfoTableLabel lbl)
&& not (isConInfoTableLabel lbl)
-- this is called pprTypeAndSizeDecl in PPC.Ppr
pprTypeDecl :: (IsDoc doc) => Platform -> CLabel -> doc
pprTypeDecl platform lbl =
if osElfTarget (platformOS platform) && externallyVisibleCLabel lbl
then line (text ".type " <> pprAsmLabel platform lbl <> text ", " <> pprLabelType' platform lbl)
else empty
pprDataItem :: (IsDoc doc) => NCGConfig -> CmmLit -> doc
pprDataItem config lit =
lines_ (ppr_item (cmmTypeFormat $ cmmLitType platform lit) lit)
where
platform = ncgPlatform config
imm = litToImm lit
ppr_item II8 _ = [text "\t.byte\t" <> pprDataImm platform imm]
ppr_item II16 _ = [text "\t.short\t" <> pprDataImm platform imm]
ppr_item II32 _ = [text "\t.long\t" <> pprDataImm platform imm]
ppr_item II64 _ = [text "\t.quad\t" <> pprDataImm platform imm]
ppr_item FF32 (CmmFloat r _) =
let bs = floatToBytes (fromRational r)
in map (\b -> text "\t.byte\t" <> int (fromIntegral b)) bs
ppr_item FF64 (CmmFloat r _) =
let bs = doubleToBytes (fromRational r)
in map (\b -> text "\t.byte\t" <> int (fromIntegral b)) bs
ppr_item _ _ = pprPanic "pprDataItem:ppr_item" (text $ show lit)
-- | Pretty print an immediate value in the @data@ section
--
-- This does not include any checks. We rely on the Assembler to check for
-- errors. Use `pprOpImm` for immediates in instructions (operands.)
pprDataImm :: (IsLine doc) => Platform -> Imm -> doc
pprDataImm _ (ImmInt i) = int i
pprDataImm _ (ImmInteger i) = integer i
pprDataImm p (ImmCLbl l) = pprAsmLabel p l
pprDataImm p (ImmIndex l i) = pprAsmLabel p l <> char '+' <> int i
pprDataImm _ (ImmLit s) = ftext s
pprDataImm _ (ImmFloat f) = float (fromRational f)
pprDataImm _ (ImmDouble d) = double (fromRational d)
pprDataImm p (ImmConstantSum a b) = pprDataImm p a <> char '+' <> pprDataImm p b
pprDataImm p (ImmConstantDiff a b) =
pprDataImm p a
<> char '-'
<> lparen
<> pprDataImm p b
<> rparen
-- | Comment @c@ with @# c@
asmComment :: SDoc -> SDoc
asmComment c = text "#" <+> c
-- | Commen @c@ with @// c@
asmDoubleslashComment :: SDoc -> SDoc
asmDoubleslashComment c = text "//" <+> c
-- | Comment @c@ with @/* c */@ (multiline comment)
asmMultilineComment :: SDoc -> SDoc
asmMultilineComment c = text "/*" $+$ c $+$ text "*/"
-- | Pretty print an immediate operand of an instruction
--
-- The kinds of immediates we can use here is pretty limited: RISCV doesn't
-- support index expressions (as e.g. Aarch64 does.) Floating points need to
-- fit in range. As we don't need them, forbit them to save us from future
-- troubles.
pprOpImm :: (IsLine doc) => Platform -> Imm -> doc
pprOpImm platform im = case im of
ImmInt i -> int i
ImmInteger i -> integer i
ImmCLbl l -> char '=' <> pprAsmLabel platform l
_ -> pprPanic "RV64.Ppr.pprOpImm" (text "Unsupported immediate for instruction operands" <> colon <+> (text . show) im)
-- | Negate integer immediate operand
--
-- This function is partial and will panic if the operand is not an integer.
negOp :: Operand -> Operand
negOp (OpImm (ImmInt i)) = OpImm (ImmInt (negate i))
negOp (OpImm (ImmInteger i)) = OpImm (ImmInteger (negate i))
negOp op = pprPanic "RV64.negOp" (text $ show op)
-- | Pretty print an operand
pprOp :: (IsLine doc) => Platform -> Operand -> doc
pprOp plat op = case op of
OpReg w r -> pprReg w r
OpImm im -> pprOpImm plat im
OpAddr (AddrRegImm r1 im) -> pprOpImm plat im <> char '(' <> pprReg W64 r1 <> char ')'
OpAddr (AddrReg r1) -> text "0(" <+> pprReg W64 r1 <+> char ')'
-- | Pretty print register with calling convention name
--
-- This representation makes it easier to reason about the emitted assembly
-- code.
pprReg :: forall doc. (IsLine doc) => Width -> Reg -> doc
pprReg w r = case r of
RegReal (RealRegSingle i) -> ppr_reg_no i
-- virtual regs should not show up, but this is helpful for debugging.
RegVirtual (VirtualRegI u) -> text "%vI_" <> pprUniqueAlways u
RegVirtual (VirtualRegD u) -> text "%vD_" <> pprUniqueAlways u
_ -> pprPanic "RiscV64.pprReg" (text (show r) <+> ppr w)
where
ppr_reg_no :: Int -> doc
-- General Purpose Registers
ppr_reg_no 0 = text "zero"
ppr_reg_no 1 = text "ra"
ppr_reg_no 2 = text "sp"
ppr_reg_no 3 = text "gp"
ppr_reg_no 4 = text "tp"
ppr_reg_no 5 = text "t0"
ppr_reg_no 6 = text "t1"
ppr_reg_no 7 = text "t2"
ppr_reg_no 8 = text "s0"
ppr_reg_no 9 = text "s1"
ppr_reg_no 10 = text "a0"
ppr_reg_no 11 = text "a1"
ppr_reg_no 12 = text "a2"
ppr_reg_no 13 = text "a3"
ppr_reg_no 14 = text "a4"
ppr_reg_no 15 = text "a5"
ppr_reg_no 16 = text "a6"
ppr_reg_no 17 = text "a7"
ppr_reg_no 18 = text "s2"
ppr_reg_no 19 = text "s3"
ppr_reg_no 20 = text "s4"
ppr_reg_no 21 = text "s5"
ppr_reg_no 22 = text "s6"
ppr_reg_no 23 = text "s7"
ppr_reg_no 24 = text "s8"
ppr_reg_no 25 = text "s9"
ppr_reg_no 26 = text "s10"
ppr_reg_no 27 = text "s11"
ppr_reg_no 28 = text "t3"
ppr_reg_no 29 = text "t4"
ppr_reg_no 30 = text "t5"
ppr_reg_no 31 = text "t6"
-- Floating Point Registers
ppr_reg_no 32 = text "ft0"
ppr_reg_no 33 = text "ft1"
ppr_reg_no 34 = text "ft2"
ppr_reg_no 35 = text "ft3"
ppr_reg_no 36 = text "ft4"
ppr_reg_no 37 = text "ft5"
ppr_reg_no 38 = text "ft6"
ppr_reg_no 39 = text "ft7"
ppr_reg_no 40 = text "fs0"
ppr_reg_no 41 = text "fs1"
ppr_reg_no 42 = text "fa0"
ppr_reg_no 43 = text "fa1"
ppr_reg_no 44 = text "fa2"
ppr_reg_no 45 = text "fa3"
ppr_reg_no 46 = text "fa4"
ppr_reg_no 47 = text "fa5"
ppr_reg_no 48 = text "fa6"
ppr_reg_no 49 = text "fa7"
ppr_reg_no 50 = text "fs2"
ppr_reg_no 51 = text "fs3"
ppr_reg_no 52 = text "fs4"
ppr_reg_no 53 = text "fs5"
ppr_reg_no 54 = text "fs6"
ppr_reg_no 55 = text "fs7"
ppr_reg_no 56 = text "fs8"
ppr_reg_no 57 = text "fs9"
ppr_reg_no 58 = text "fs10"
ppr_reg_no 59 = text "fs11"
ppr_reg_no 60 = text "ft8"
ppr_reg_no 61 = text "ft9"
ppr_reg_no 62 = text "ft10"
ppr_reg_no 63 = text "ft11"
ppr_reg_no i
| i < 0 = pprPanic "Unexpected register number (min is 0)" (ppr w <+> int i)
| i > 63 = pprPanic "Unexpected register number (max is 63)" (ppr w <+> int i)
-- no support for widths > W64.
| otherwise = pprPanic "Unsupported width in register (max is 64)" (ppr w <+> int i)
-- | Single precission `Operand` (floating-point)
isSingleOp :: Operand -> Bool
isSingleOp (OpReg W32 _) = True
isSingleOp _ = False
-- | Double precission `Operand` (floating-point)
isDoubleOp :: Operand -> Bool
isDoubleOp (OpReg W64 _) = True
isDoubleOp _ = False
-- | `Operand` is an immediate value
isImmOp :: Operand -> Bool
isImmOp (OpImm _) = True
isImmOp _ = False
-- | `Operand` is an immediate @0@ value
isImmZero :: Operand -> Bool
isImmZero (OpImm (ImmFloat 0)) = True
isImmZero (OpImm (ImmDouble 0)) = True
isImmZero (OpImm (ImmInt 0)) = True
isImmZero _ = False
-- | `Target` represents a label
isLabel :: Target -> Bool
isLabel (TBlock _) = True
isLabel _ = False
-- | Get the pretty-printed label from a `Target`
--
-- This function is partial and will panic if the `Target` is not a label.
getLabel :: (IsLine doc) => Platform -> Target -> doc
getLabel platform (TBlock bid) = pprBlockId platform bid
where
pprBlockId :: (IsLine doc) => Platform -> BlockId -> doc
pprBlockId platform bid = pprAsmLabel platform (mkLocalBlockLabel (getUnique bid))
getLabel _platform _other = panic "Cannot turn this into a label"
-- | Pretty-print an `Instr`
--
-- This function is partial and will panic if the `Instr` is not supported. This
-- can happen due to invalid operands or unexpected meta instructions.
pprInstr :: (IsDoc doc) => Platform -> Instr -> doc
pprInstr platform instr = case instr of
-- see Note [dualLine and dualDoc] in GHC.Utils.Outputable
COMMENT s -> dualDoc (asmComment s) empty
MULTILINE_COMMENT s -> dualDoc (asmMultilineComment s) empty
ANN d i -> dualDoc (pprInstr platform i <+> asmDoubleslashComment d) (pprInstr platform i)
LOCATION file line' col _name ->
line (text "\t.loc" <+> int file <+> int line' <+> int col)
DELTA d -> dualDoc (asmComment $ text "\tdelta = " <> int d) empty
NEWBLOCK _ -> panic "PprInstr: NEWBLOCK"
LDATA _ _ -> panic "pprInstr: LDATA"
PUSH_STACK_FRAME ->
lines_
[ text "\taddi sp, sp, -16",
text "\tsd x1, 8(sp)", -- store RA
text "\tsd x8, 0(sp)", -- store FP/s0
text "\taddi x8, sp, 16"
]
POP_STACK_FRAME ->
lines_
[ text "\tld x8, 0(sp)", -- restore FP/s0
text "\tld x1, 8(sp)", -- restore RA
text "\taddi sp, sp, 16"
]
ADD o1 o2 o3
| isFloatOp o1 && isFloatOp o2 && isFloatOp o3 -> op3 (text "\tfadd." <> if isSingleOp o1 then text "s" else text "d") o1 o2 o3
-- This case is used for sign extension: SEXT.W op
| OpReg W64 _ <- o1, OpReg W32 _ <- o2, isImmOp o3 -> op3 (text "\taddiw") o1 o2 o3
| otherwise -> op3 (text "\tadd") o1 o2 o3
MUL o1 o2 o3
| isFloatOp o1 && isFloatOp o2 && isFloatOp o3 -> op3 (text "\tfmul." <> if isSingleOp o1 then text "s" else text "d") o1 o2 o3
| otherwise -> op3 (text "\tmul") o1 o2 o3
MULH o1 o2 o3 -> op3 (text "\tmulh") o1 o2 o3
NEG o1 o2 | isFloatOp o1 && isFloatOp o2 && isSingleOp o2 -> op2 (text "\tfneg.s") o1 o2
NEG o1 o2 | isFloatOp o1 && isFloatOp o2 && isDoubleOp o2 -> op2 (text "\tfneg.d") o1 o2
NEG o1 o2 -> op2 (text "\tneg") o1 o2
DIV o1 o2 o3
| isFloatOp o1 && isFloatOp o2 && isFloatOp o3 ->
-- TODO: This must (likely) be refined regarding width
op3 (text "\tfdiv." <> if isSingleOp o1 then text "s" else text "d") o1 o2 o3
DIV o1 o2 o3 -> op3 (text "\tdiv") o1 o2 o3
REM o1 o2 o3
| isFloatOp o1 || isFloatOp o2 || isFloatOp o3 ->
panic "pprInstr - REM not implemented for floats (yet)"
REM o1 o2 o3 -> op3 (text "\trem") o1 o2 o3
REMU o1 o2 o3 -> op3 (text "\tremu") o1 o2 o3
SUB o1 o2 o3
| isFloatOp o1 && isFloatOp o2 && isFloatOp o3 -> op3 (text "\tfsub." <> if isSingleOp o1 then text "s" else text "d") o1 o2 o3
| isImmOp o3 -> op3 (text "\taddi") o1 o2 (negOp o3)
| otherwise -> op3 (text "\tsub") o1 o2 o3
DIVU o1 o2 o3 -> op3 (text "\tdivu") o1 o2 o3
AND o1 o2 o3
| isImmOp o3 -> op3 (text "\tandi") o1 o2 o3
| otherwise -> op3 (text "\tand") o1 o2 o3
OR o1 o2 o3 -> op3 (text "\tor") o1 o2 o3
SRA o1 o2 o3 | isImmOp o3 -> op3 (text "\tsrai") o1 o2 o3
SRA o1 o2 o3 -> op3 (text "\tsra") o1 o2 o3
XOR o1 o2 o3 -> op3 (text "\txor") o1 o2 o3
SLL o1 o2 o3 -> op3 (text "\tsll") o1 o2 o3
SRL o1 o2 o3 -> op3 (text "\tsrl") o1 o2 o3
MOV o1 o2
| isFloatOp o1 && isFloatOp o2 && isDoubleOp o2 -> op2 (text "\tfmv.d") o1 o2 -- fmv.d rd, rs is pseudo op fsgnj.d rd, rs, rs
| isFloatOp o1 && isFloatOp o2 && isSingleOp o2 -> op2 (text "\tfmv.s") o1 o2 -- fmv.s rd, rs is pseudo op fsgnj.s rd, rs, rs
| isFloatOp o1 && isImmZero o2 && isDoubleOp o1 -> op2 (text "\tfcvt.d.w") o1 zero
| isFloatOp o1 && isImmZero o2 && isSingleOp o1 -> op2 (text "\tfcvt.s.w") o1 zero
| isFloatOp o1 && not (isFloatOp o2) && isSingleOp o1 -> op2 (text "\tfmv.w.x") o1 o2
| isFloatOp o1 && not (isFloatOp o2) && isDoubleOp o1 -> op2 (text "\tfmv.d.x") o1 o2
| not (isFloatOp o1) && isFloatOp o2 && isSingleOp o2 -> op2 (text "\tfmv.x.w") o1 o2
| not (isFloatOp o1) && isFloatOp o2 && isDoubleOp o2 -> op2 (text "\tfmv.x.d") o1 o2
| (OpImm (ImmInteger i)) <- o2,
fitsIn12bitImm i ->
lines_ [text "\taddi" <+> pprOp platform o1 <> comma <+> pprOp platform x0 <> comma <+> pprOp platform o2]
| (OpImm (ImmInt i)) <- o2,
fitsIn12bitImm i ->
lines_ [text "\taddi" <+> pprOp platform o1 <> comma <+> pprOp platform x0 <> comma <+> pprOp platform o2]
| (OpImm (ImmInteger i)) <- o2,
fitsIn32bits i ->
lines_
[ text "\tlui" <+> pprOp platform o1 <> comma <+> text "%hi(" <> pprOp platform o2 <> text ")",
text "\taddw" <+> pprOp platform o1 <> comma <+> pprOp platform o1 <> comma <+> text "%lo(" <> pprOp platform o2 <> text ")"
]
| (OpImm (ImmInt i)) <- o2,
fitsIn32bits i ->
lines_
[ text "\tlui" <+> pprOp platform o1 <> comma <+> text "%hi(" <> pprOp platform o2 <> text ")",
text "\taddw" <+> pprOp platform o1 <> comma <+> pprOp platform o1 <> comma <+> text "%lo(" <> pprOp platform o2 <> text ")"
]
| isImmOp o2 ->
-- Surrender! Let the assembler figure out the right expressions with pseudo-op LI.
lines_ [text "\tli" <+> pprOp platform o1 <> comma <+> pprOp platform o2]
| otherwise -> op3 (text "\taddi") o1 o2 (OpImm (ImmInt 0))
ORI o1 o2 o3 -> op3 (text "\tori") o1 o2 o3
XORI o1 o2 o3 -> op3 (text "\txori") o1 o2 o3
J_TBL _ _ r -> pprInstr platform (B (TReg r))
B l | isLabel l -> line $ text "\tjal" <+> pprOp platform x0 <> comma <+> getLabel platform l
B (TReg r) -> line $ text "\tjalr" <+> pprOp platform x0 <> comma <+> pprReg W64 r <> comma <+> text "0"
BL r _ -> line $ text "\tjalr" <+> text "x1" <> comma <+> pprReg W64 r <> comma <+> text "0"
BCOND c l r t
| isLabel t ->
line $ text "\t" <> pprBcond c <+> pprOp platform l <> comma <+> pprOp platform r <> comma <+> getLabel platform t
BCOND _ _ _ (TReg _) -> panic "RV64.ppr: No conditional branching to registers!"
CSET o l r c -> case c of
EQ
| isIntOp l && isIntOp r ->
lines_
[ subFor l r,
text "\tseqz" <+> pprOp platform o <> comma <+> pprOp platform o
]
EQ | isFloatOp l && isFloatOp r -> line $ binOp ("\tfeq." ++ floatOpPrecision platform l r)
NE
| isIntOp l && isIntOp r ->
lines_
[ subFor l r,
text "\tsnez" <+> pprOp platform o <> comma <+> pprOp platform o
]
NE
| isFloatOp l && isFloatOp r ->
lines_
[ binOp ("\tfeq." ++ floatOpPrecision platform l r),
text "\txori" <+> pprOp platform o <> comma <+> pprOp platform o <> comma <+> text "1"
]
SLT -> lines_ [sltFor l r <+> pprOp platform o <> comma <+> pprOp platform l <> comma <+> pprOp platform r]
SLE ->
lines_
[ sltFor l r <+> pprOp platform o <> comma <+> pprOp platform r <> comma <+> pprOp platform l,
text "\txori" <+> pprOp platform o <> comma <+> pprOp platform o <> comma <+> text "1"
]
SGE ->
lines_
[ sltFor l r <+> pprOp platform o <> comma <+> pprOp platform l <> comma <+> pprOp platform r,
text "\txori" <+> pprOp platform o <> comma <+> pprOp platform o <> comma <+> text "1"
]
SGT -> lines_ [sltFor l r <+> pprOp platform o <> comma <+> pprOp platform r <> comma <+> pprOp platform l]
ULT -> lines_ [sltuFor l r <+> pprOp platform o <> comma <+> pprOp platform l <> comma <+> pprOp platform r]
ULE ->
lines_
[ sltuFor l r <+> pprOp platform o <> comma <+> pprOp platform r <> comma <+> pprOp platform l,
text "\txori" <+> pprOp platform o <> comma <+> pprOp platform o <> comma <+> text "1"
]
UGE ->
lines_
[ sltuFor l r <+> pprOp platform o <> comma <+> pprOp platform l <> comma <+> pprOp platform r,
text "\txori" <+> pprOp platform o <> comma <+> pprOp platform o <> comma <+> text "1"
]
UGT -> lines_ [sltuFor l r <+> pprOp platform o <> comma <+> pprOp platform r <> comma <+> pprOp platform l]
FLT | isFloatOp l && isFloatOp r -> line $ binOp ("\tflt." ++ floatOpPrecision platform l r)
FLE | isFloatOp l && isFloatOp r -> line $ binOp ("\tfle." ++ floatOpPrecision platform l r)
FGT | isFloatOp l && isFloatOp r -> line $ binOp ("\tfgt." ++ floatOpPrecision platform l r)
FGE | isFloatOp l && isFloatOp r -> line $ binOp ("\tfge." ++ floatOpPrecision platform l r)
x -> pprPanic "RV64.ppr: unhandled CSET conditional" (text (show x) <+> pprOp platform o <> comma <+> pprOp platform r <> comma <+> pprOp platform l)
where
subFor l r
| (OpImm _) <- r = text "\taddi" <+> pprOp platform o <> comma <+> pprOp platform l <> comma <+> pprOp platform (negOp r)
| (OpImm _) <- l = panic "RV64.ppr: Cannot SUB IMM _"
| otherwise = text "\tsub" <+> pprOp platform o <> comma <+> pprOp platform l <> comma <+> pprOp platform r
sltFor l r
| (OpImm _) <- r = text "\tslti"
| (OpImm _) <- l = panic "PV64.ppr: Cannot SLT IMM _"
| otherwise = text "\tslt"
sltuFor l r
| (OpImm _) <- r = text "\tsltui"
| (OpImm _) <- l = panic "PV64.ppr: Cannot SLTU IMM _"
| otherwise = text "\tsltu"
binOp :: (IsLine doc) => String -> doc
binOp op = text op <+> pprOp platform o <> comma <+> pprOp platform l <> comma <+> pprOp platform r
STR II8 o1 o2 -> op2 (text "\tsb") o1 o2
STR II16 o1 o2 -> op2 (text "\tsh") o1 o2
STR II32 o1 o2 -> op2 (text "\tsw") o1 o2
STR II64 o1 o2 -> op2 (text "\tsd") o1 o2
STR FF32 o1 o2 -> op2 (text "\tfsw") o1 o2
STR FF64 o1 o2 -> op2 (text "\tfsd") o1 o2
LDR _f o1 (OpImm (ImmIndex lbl off)) ->
lines_
[ text "\tla" <+> pprOp platform o1 <> comma <+> pprAsmLabel platform lbl,
text "\taddi" <+> pprOp platform o1 <> comma <+> pprOp platform o1 <> comma <+> int off
]
LDR _f o1 (OpImm (ImmCLbl lbl)) ->
line $ text "\tla" <+> pprOp platform o1 <> comma <+> pprAsmLabel platform lbl
LDR II8 o1 o2 -> op2 (text "\tlb") o1 o2
LDR II16 o1 o2 -> op2 (text "\tlh") o1 o2
LDR II32 o1 o2 -> op2 (text "\tlw") o1 o2
LDR II64 o1 o2 -> op2 (text "\tld") o1 o2
LDR FF32 o1 o2 -> op2 (text "\tflw") o1 o2
LDR FF64 o1 o2 -> op2 (text "\tfld") o1 o2
LDRU II8 o1 o2 -> op2 (text "\tlbu") o1 o2
LDRU II16 o1 o2 -> op2 (text "\tlhu") o1 o2
LDRU II32 o1 o2 -> op2 (text "\tlwu") o1 o2
-- double words (64bit) cannot be sign extended by definition
LDRU II64 o1 o2 -> op2 (text "\tld") o1 o2
LDRU FF32 o1 o2@(OpAddr (AddrReg _)) -> op2 (text "\tflw") o1 o2
LDRU FF32 o1 o2@(OpAddr (AddrRegImm _ _)) -> op2 (text "\tflw") o1 o2
LDRU FF64 o1 o2@(OpAddr (AddrReg _)) -> op2 (text "\tfld") o1 o2
LDRU FF64 o1 o2@(OpAddr (AddrRegImm _ _)) -> op2 (text "\tfld") o1 o2
LDRU f o1 o2 -> pprPanic "Unsupported unsigned load" ((text . show) f <+> pprOp platform o1 <+> pprOp platform o2)
FENCE r w -> line $ text "\tfence" <+> pprFenceType r <> char ',' <+> pprFenceType w
FCVT FloatToFloat o1@(OpReg W32 _) o2@(OpReg W64 _) -> op2 (text "\tfcvt.s.d") o1 o2
FCVT FloatToFloat o1@(OpReg W64 _) o2@(OpReg W32 _) -> op2 (text "\tfcvt.d.s") o1 o2
FCVT FloatToFloat o1 o2 ->
pprPanic "RV64.pprInstr - impossible float to float conversion"
$ line (pprOp platform o1 <> text "->" <> pprOp platform o2)
FCVT IntToFloat o1@(OpReg W32 _) o2@(OpReg W32 _) -> op2 (text "\tfcvt.s.w") o1 o2
FCVT IntToFloat o1@(OpReg W32 _) o2@(OpReg W64 _) -> op2 (text "\tfcvt.s.l") o1 o2
FCVT IntToFloat o1@(OpReg W64 _) o2@(OpReg W32 _) -> op2 (text "\tfcvt.d.w") o1 o2
FCVT IntToFloat o1@(OpReg W64 _) o2@(OpReg W64 _) -> op2 (text "\tfcvt.d.l") o1 o2
FCVT IntToFloat o1 o2 ->
pprPanic "RV64.pprInstr - impossible integer to float conversion"
$ line (pprOp platform o1 <> text "->" <> pprOp platform o2)
FCVT FloatToInt o1@(OpReg W32 _) o2@(OpReg W32 _) -> op2 (text "\tfcvt.w.s") o1 o2
FCVT FloatToInt o1@(OpReg W32 _) o2@(OpReg W64 _) -> op2 (text "\tfcvt.w.d") o1 o2
FCVT FloatToInt o1@(OpReg W64 _) o2@(OpReg W32 _) -> op2 (text "\tfcvt.l.s") o1 o2
FCVT FloatToInt o1@(OpReg W64 _) o2@(OpReg W64 _) -> op2 (text "\tfcvt.l.d") o1 o2
FCVT FloatToInt o1 o2 ->
pprPanic "RV64.pprInstr - impossible float to integer conversion"
$ line (pprOp platform o1 <> text "->" <> pprOp platform o2)
FABS o1 o2 | isSingleOp o2 -> op2 (text "\tfabs.s") o1 o2
FABS o1 o2 | isDoubleOp o2 -> op2 (text "\tfabs.d") o1 o2
FMIN o1 o2 o3 | isSingleOp o1 -> op3 (text "\tfmin.s") o1 o2 o3
| isDoubleOp o2 -> op3 (text "\tfmin.d") o1 o2 o3
FMAX o1 o2 o3 | isSingleOp o1 -> op3 (text "\tfmax.s") o1 o2 o3
| isDoubleOp o2 -> op3 (text "\tfmax.d") o1 o2 o3
FMA variant d r1 r2 r3 ->
let fma = case variant of
FMAdd -> text "\tfmadd" <> dot <> floatPrecission d
FMSub -> text "\tfmsub" <> dot <> floatPrecission d
FNMAdd -> text "\tfnmadd" <> dot <> floatPrecission d
FNMSub -> text "\tfnmsub" <> dot <> floatPrecission d
in op4 fma d r1 r2 r3
instr -> panic $ "RV64.pprInstr - Unknown instruction: " ++ instrCon instr
where
op2 op o1 o2 = line $ op <+> pprOp platform o1 <> comma <+> pprOp platform o2
op3 op o1 o2 o3 = line $ op <+> pprOp platform o1 <> comma <+> pprOp platform o2 <> comma <+> pprOp platform o3
op4 op o1 o2 o3 o4 = line $ op <+> pprOp platform o1 <> comma <+> pprOp platform o2 <> comma <+> pprOp platform o3 <> comma <+> pprOp platform o4
pprFenceType FenceRead = text "r"
pprFenceType FenceWrite = text "w"
pprFenceType FenceReadWrite = text "rw"
floatPrecission o
| isSingleOp o = text "s"
| isDoubleOp o = text "d"
| otherwise = pprPanic "Impossible floating point precission: " (pprOp platform o)
floatOpPrecision :: Platform -> Operand -> Operand -> String
floatOpPrecision _p l r | isFloatOp l && isFloatOp r && isSingleOp l && isSingleOp r = "s" -- single precision
floatOpPrecision _p l r | isFloatOp l && isFloatOp r && isDoubleOp l && isDoubleOp r = "d" -- double precision
floatOpPrecision p l r = pprPanic "Cannot determine floating point precission" (text "op1" <+> pprOp p l <+> text "op2" <+> pprOp p r)
-- | Pretty print a conditional branch
--
-- This function is partial and will panic if the conditional is not supported;
-- i.e. if its floating point related.
pprBcond :: (IsLine doc) => Cond -> doc
pprBcond c = text "b" <> pprCond c
where
pprCond :: (IsLine doc) => Cond -> doc
pprCond c = case c of
EQ -> text "eq"
NE -> text "ne"
SLT -> text "lt"
SLE -> text "le"
SGE -> text "ge"
SGT -> text "gt"
ULT -> text "ltu"
ULE -> text "leu"
UGE -> text "geu"
UGT -> text "gtu"
-- BCOND cannot handle floating point comparisons / registers
_ -> panic $ "RV64.ppr: unhandled BCOND conditional: " ++ show c