kempe-0.2.0.0: src/Kempe/IR.hs
module Kempe.IR ( writeModule
, runTempM
, TempM
, prettyIR
, WriteSt (..)
, size
) where
import Data.Foldable (toList, traverse_)
import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.List.NonEmpty as NE
-- strict b/c it's faster according to benchmarks
import Control.Monad.State.Strict (State, gets, modify, runState)
import Data.Bifunctor (second)
import Data.Foldable.Ext
import Data.Int (Int64)
import qualified Data.IntMap as IM
import Data.Text.Encoding (encodeUtf8)
import Kempe.AST
import Kempe.AST.Size
import Kempe.IR.Type
import Kempe.Name
import Kempe.Unique
import Lens.Micro (Lens')
import Lens.Micro.Mtl (modifying)
import Prettyprinter (Doc, Pretty (pretty))
import Prettyprinter.Ext
data TempSt = TempSt { labels :: [Label]
, tempSupply :: [Int]
, atLabels :: IM.IntMap Label
-- TODO: type sizes in state
}
asWriteSt :: TempSt -> WriteSt
asWriteSt (TempSt ls ts _) = WriteSt ls ts
runTempM :: TempM a -> (a, WriteSt)
runTempM = second asWriteSt . flip runState (TempSt [1..] [1..] mempty)
atLabelsLens :: Lens' TempSt (IM.IntMap Label)
atLabelsLens f s = fmap (\x -> s { atLabels = x }) (f (atLabels s))
nextLabels :: TempSt -> TempSt
nextLabels (TempSt ls ts ats) = TempSt (tail ls) ts ats
nextTemps :: TempSt -> TempSt
nextTemps (TempSt ls ts ats) = TempSt ls (tail ts) ats
type TempM = State TempSt
getTemp :: TempM Int
getTemp = gets (head . tempSupply) <* modify nextTemps
getTemp64 :: TempM Temp
getTemp64 = Temp64 <$> getTemp
getTemp8 :: TempM Temp
getTemp8 = Temp8 <$> getTemp
newLabel :: TempM Label
newLabel = gets (head . labels) <* modify nextLabels
broadcastName :: Unique -> TempM ()
broadcastName (Unique i) = do
l <- newLabel
modifying atLabelsLens (IM.insert i l)
lookupName :: Name a -> TempM Label
lookupName (Name _ (Unique i) _) =
gets
(IM.findWithDefault (error "Internal error in IR phase: could not look find label for name") i . atLabels)
prettyIR :: [Stmt] -> Doc ann
prettyIR = prettyLines . fmap pretty
writeModule :: SizeEnv -> Declarations () (ConsAnn MonoStackType) MonoStackType -> TempM [Stmt]
writeModule env m = traverse_ assignName m *> foldMapA (writeDecl env) m
-- optimize tail-recursion, if possible
-- This is a little slow
tryTCO :: Bool -- ^ Can it be optimized here?
-> [Stmt]
-> [Stmt]
tryTCO _ [] = []
tryTCO False stmts = stmts
tryTCO True stmts =
let end = last stmts
in
case end of
KCall l' -> init stmts ++ [Jump l']
_ -> stmts
assignName :: KempeDecl a c b -> TempM ()
assignName (FunDecl _ (Name _ u _) _ _ _) = broadcastName u
assignName (ExtFnDecl _ (Name _ u _) _ _ _) = broadcastName u
assignName Export{} = pure ()
assignName TyDecl{} = error "Internal error: type declarations should not exist at this stage"
-- FIXME: Current broadcast + write approach fails mutually recursive functions
writeDecl :: SizeEnv -> KempeDecl () (ConsAnn MonoStackType) MonoStackType -> TempM [Stmt]
writeDecl env (FunDecl _ n _ _ as) = do
bl <- lookupName n
(++ [Ret]) . (Labeled bl:) . tryTCO True <$> writeAtoms env True as
writeDecl _ (ExtFnDecl ty n _ _ cName) = do
bl <- lookupName n
pure [Labeled bl, CCall ty cName, Ret]
writeDecl _ (Export sTy abi n) = pure . WrapKCall abi sTy (encodeUtf8 $ name n) <$> lookupName n
writeDecl _ TyDecl{} = error "Internal error: type declarations should not exist at this stage"
writeAtoms :: SizeEnv -> Bool -> [Atom (ConsAnn MonoStackType) MonoStackType] -> TempM [Stmt]
writeAtoms _ _ [] = pure []
writeAtoms env False stmts = foldMapA (writeAtom env False) stmts
writeAtoms env l stmts =
let end = last stmts
in (++) <$> foldMapA (writeAtom env False) (init stmts) <*> writeAtom env l end
intShift :: IntBinOp -> TempM [Stmt]
intShift cons = do
t0 <- getTemp64
t1 <- getTemp64
pure $
pop 8 t0 ++ pop 8 t1 ++ push 8 (ExprIntBinOp cons (Reg t1) (Reg t0))
boolOp :: BoolBinOp -> TempM [Stmt]
boolOp op = do
t0 <- getTemp8
t1 <- getTemp8
pure $
pop 1 t0 ++ pop 1 t1 ++ push 1 (BoolBinOp op (Reg t1) (Reg t0))
intOp :: IntBinOp -> TempM [Stmt]
intOp cons = do
t0 <- getTemp64 -- registers are 64 bits for integers
t1 <- getTemp64
pure $
pop 8 t0 ++ pop 8 t1 ++ push 8 (ExprIntBinOp cons (Reg t1) (Reg t0))
-- | Push bytes onto the Kempe data pointer
push :: Int64 -> Exp -> [Stmt]
push off e =
[ MovMem (Reg DataPointer) off e
, dataPointerInc off -- increment instead of decrement b/c this is the Kempe ABI
]
pop :: Int64 -> Temp -> [Stmt]
pop sz t =
[ dataPointerDec sz
, MovTemp t (Mem sz (Reg DataPointer))
]
-- FIXME: just use expressions from memory accesses
intRel :: RelBinOp -> TempM [Stmt]
intRel cons = do
t0 <- getTemp64
t1 <- getTemp64
pure $
pop 8 t0 ++ pop 8 t1 ++ push 1 (ExprIntRel cons (Reg t1) (Reg t0))
intNeg :: TempM [Stmt]
intNeg = do
t0 <- getTemp64
pure $
pop 8 t0 ++ push 8 (IntNegIR (Reg t0))
wordCount :: TempM [Stmt]
wordCount = do
t0 <- getTemp64
pure $
pop 8 t0 ++ push 8 (PopcountIR (Reg t0))
-- | This throws exceptions on nonsensical input.
writeAtom :: SizeEnv
-> Bool -- ^ Can we do TCO?
-> Atom (ConsAnn MonoStackType) MonoStackType
-> TempM [Stmt]
writeAtom _ _ (IntLit _ i) = pure $ push 8 (ConstInt $ fromInteger i)
writeAtom _ _ (Int8Lit _ i) = pure $ push 1 (ConstInt8 i)
writeAtom _ _ (WordLit _ w) = pure $ push 8 (ConstWord $ fromIntegral w)
writeAtom _ _ (BoolLit _ b) = pure $ push 1 (ConstBool b)
writeAtom _ _ (AtName _ n) = pure . KCall <$> lookupName n
writeAtom _ _ (AtBuiltin ([], _) Drop) = error "Internal error: Ill-typed drop!"
writeAtom _ _ (AtBuiltin ([], _) Dup) = error "Internal error: Ill-typed dup!"
writeAtom _ _ (Dip ([], _) _) = error "Internal error: Ill-typed dip()!"
writeAtom _ _ (AtBuiltin _ IntPlus) = intOp IntPlusIR
writeAtom _ _ (AtBuiltin _ IntMinus) = intOp IntMinusIR
writeAtom _ _ (AtBuiltin _ IntTimes) = intOp IntTimesIR
writeAtom _ _ (AtBuiltin _ IntDiv) = intOp IntDivIR -- what to do on failure?
writeAtom _ _ (AtBuiltin _ IntMod) = intOp IntModIR
writeAtom _ _ (AtBuiltin _ IntXor) = intOp IntXorIR
writeAtom _ _ (AtBuiltin _ IntShiftR) = intShift WordShiftRIR -- FIXME: shr or sar?
writeAtom _ _ (AtBuiltin _ IntShiftL) = intShift WordShiftLIR
writeAtom _ _ (AtBuiltin _ IntEq) = intRel IntEqIR
writeAtom _ _ (AtBuiltin _ IntLt) = intRel IntLtIR
writeAtom _ _ (AtBuiltin _ IntLeq) = intRel IntLeqIR
writeAtom _ _ (AtBuiltin _ WordPlus) = intOp IntPlusIR
writeAtom _ _ (AtBuiltin _ WordTimes) = intOp IntTimesIR
writeAtom _ _ (AtBuiltin _ WordXor) = intOp IntXorIR
writeAtom _ _ (AtBuiltin _ WordMinus) = intOp IntMinusIR
writeAtom _ _ (AtBuiltin _ IntNeq) = intRel IntNeqIR
writeAtom _ _ (AtBuiltin _ IntGeq) = intRel IntGeqIR
writeAtom _ _ (AtBuiltin _ IntGt) = intRel IntGtIR
writeAtom _ _ (AtBuiltin _ WordShiftL) = intShift WordShiftLIR
writeAtom _ _ (AtBuiltin _ WordShiftR) = intShift WordShiftRIR
writeAtom _ _ (AtBuiltin _ WordDiv) = intOp WordDivIR
writeAtom _ _ (AtBuiltin _ WordMod) = intOp WordModIR
writeAtom _ _ (AtBuiltin _ And) = boolOp BoolAnd
writeAtom _ _ (AtBuiltin _ Or) = boolOp BoolOr
writeAtom _ _ (AtBuiltin _ Xor) = boolOp BoolXor
writeAtom _ _ (AtBuiltin _ IntNeg) = intNeg
writeAtom _ _ (AtBuiltin _ Popcount) = wordCount
writeAtom env _ (AtBuiltin (is, _) Drop) =
let sz = size' env (last is) in
pure [ dataPointerDec sz ]
writeAtom env _ (AtBuiltin (is, _) Dup) =
let sz = size' env (last is) in
pure $
copyBytes 0 (-sz) sz
++ [ dataPointerInc sz ] -- move data pointer over sz bytes
writeAtom env l (If _ as as') = do
l0 <- newLabel
l1 <- newLabel
let ifIR = CJump (Mem 1 (Reg DataPointer)) l0 l1
asIR <- tryTCO l <$> writeAtoms env l as
asIR' <- tryTCO l <$> writeAtoms env l as'
l2 <- newLabel
pure $ dataPointerDec 1 : ifIR : (Labeled l0 : asIR ++ [Jump l2]) ++ (Labeled l1 : asIR') ++ [Labeled l2]
writeAtom env _ (Dip (is, _) as) =
let sz = size' env (last is)
in foldMapA (dipify env sz) as
writeAtom env _ (AtBuiltin ([i0, i1], _) Swap) =
let sz0 = size' env i0
sz1 = size' env i1
in
pure $
copyBytes 0 (-sz0 - sz1) sz0 -- copy i0 to end of the stack
++ copyBytes (-sz0 - sz1) (-sz1) sz1 -- copy i1 to where i0 used to be
++ copyBytes (-sz0) 0 sz0 -- copy i0 at end of stack to its new place
writeAtom _ _ (AtBuiltin _ Swap) = error "Ill-typed swap!"
writeAtom env _ (AtCons ann@(ConsAnn _ tag' _) _) =
pure $ dataPointerInc (padBytes env ann) : push 1 (ConstTag tag')
writeAtom _ _ (Case ([], _) _) = error "Internal error: Ill-typed case statement?!"
-- single-case leaf
writeAtom env l (Case (is, _) ((_, as) :| [])) =
let decSz = size' env (last is)
in do
nextAs <- writeAtoms env l as
pure $ dataPointerDec decSz : nextAs
writeAtom env l (Case (is, _) ls) =
let (ps, ass) = NE.unzip ls
decSz = size' env (last is)
in do
leaves <- zipWithM (mkLeaf env l) ps ass
let (switches, meat) = NE.unzip leaves
ret <- newLabel
let meat' = (++ [Jump ret]) . toList <$> meat
pure $ dataPointerDec decSz : concatMap toList switches ++ concat meat' ++ [Labeled ret]
zipWithM :: (Applicative m) => (a -> b -> m c) -> NonEmpty a -> NonEmpty b -> m (NonEmpty c)
zipWithM f xs ys = sequenceA (NE.zipWith f xs ys)
mkLeaf :: SizeEnv -> Bool -> Pattern (ConsAnn MonoStackType) MonoStackType -> [Atom (ConsAnn MonoStackType) MonoStackType] -> TempM ([Stmt], [Stmt])
mkLeaf env l p as = do
l' <- newLabel
as' <- writeAtoms env l as
let s = patternSwitch env p l'
pure (s, Labeled l' : as')
patternSwitch :: SizeEnv -> Pattern (ConsAnn MonoStackType) MonoStackType -> Label -> [Stmt]
patternSwitch _ (PatternBool _ True) l = [MJump (Mem 1 (Reg DataPointer)) l]
patternSwitch _ (PatternBool _ False) l = [MJump (EqByte (Mem 1 (Reg DataPointer)) (ConstTag 0)) l]
patternSwitch _ (PatternWildcard _) l = [Jump l]
patternSwitch _ (PatternInt _ i) l = [MJump (ExprIntRel IntEqIR (Mem 8 (Reg DataPointer)) (ConstInt $ fromInteger i)) l]
patternSwitch env (PatternCons ann@(ConsAnn _ tag' _) _) l =
let padAt = padBytes env ann + 1
-- decrement by padAt bytes (to discard padding), then we need to access
-- the tag at [datapointer+padAt] when we check
in [ dataPointerDec padAt, MJump (EqByte (Mem 1 (ExprIntBinOp IntPlusIR (Reg DataPointer) (ConstInt padAt))) (ConstTag tag')) l]
-- | Constructors may need to be padded, this computes the number of bytes of
-- padding
padBytes :: SizeEnv -> ConsAnn MonoStackType -> Int64
padBytes env (ConsAnn sz _ (is, _)) = sz - sizeStack env is - 1
dipify :: SizeEnv -> Int64 -> Atom (ConsAnn MonoStackType) MonoStackType -> TempM [Stmt]
dipify _ _ (AtBuiltin ([], _) Drop) = error "Internal error: Ill-typed drop!"
dipify env sz (AtBuiltin (is, _) Drop) =
let sz' = size' env (last is)
shift = dataPointerDec sz' -- shift data pointer over by sz' bytes
-- copy sz bytes over (-sz') bytes from the data pointer
copyBytes' = copyBytes (-sz - sz') (-sz) sz
in pure $ copyBytes' ++ [shift]
dipify env sz (AtBuiltin ([i0, i1], _) Swap) =
let sz0 = size' env i0
sz1 = size' env i1
in
pure $
copyBytes 0 (-sz - sz0 - sz1) sz0 -- copy i0 to end of the stack
++ copyBytes (-sz - sz0 - sz1) (-sz - sz1) sz1 -- copy i1 to where i0 used to be
++ copyBytes (-sz - sz0) 0 sz0 -- copy i0 at end of stack to its new place
dipify _ _ (Dip ([], _) _) = error "Internal error: Ill-typed dip()!"
dipify env sz (Dip (is, _) as) =
let sz' = size' env (last is)
in foldMapA (dipify env (sz + sz')) as
dipify _ _ (AtBuiltin _ Swap) = error "Internal error: Ill-typed swap!"
dipify _ sz (AtBuiltin _ IntTimes) = dipOp sz IntTimesIR
dipify _ sz (AtBuiltin _ IntPlus) = dipOp sz IntPlusIR
dipify _ sz (AtBuiltin _ IntMinus) = dipOp sz IntMinusIR
dipify _ sz (AtBuiltin _ IntDiv) = dipOp sz IntDivIR
dipify _ sz (AtBuiltin _ IntMod) = dipOp sz IntModIR
dipify _ sz (AtBuiltin _ IntXor) = dipOp sz IntXorIR
dipify _ sz (AtBuiltin _ IntEq) = dipRel sz IntEqIR
dipify _ sz (AtBuiltin _ IntLt) = dipRel sz IntLtIR
dipify _ sz (AtBuiltin _ IntLeq) = dipRel sz IntLeqIR
dipify _ sz (AtBuiltin _ IntShiftL) = dipShift sz WordShiftLIR
dipify _ sz (AtBuiltin _ IntShiftR) = dipShift sz WordShiftRIR
dipify _ sz (AtBuiltin _ WordXor) = dipOp sz IntXorIR
dipify _ sz (AtBuiltin _ WordShiftL) = dipShift sz WordShiftLIR
dipify _ sz (AtBuiltin _ WordShiftR) = dipShift sz WordShiftRIR
dipify _ sz (AtBuiltin _ WordPlus) = dipOp sz IntPlusIR
dipify _ sz (AtBuiltin _ WordTimes) = dipOp sz IntTimesIR
dipify _ sz (AtBuiltin _ IntGeq) = dipRel sz IntGeqIR
dipify _ sz (AtBuiltin _ IntGt) = dipRel sz IntGtIR
dipify _ sz (AtBuiltin _ IntNeq) = dipRel sz IntNeqIR
dipify _ sz (AtBuiltin _ IntNeg) = plainShift sz <$> intNeg
dipify _ sz (AtBuiltin _ Popcount) = plainShift sz <$> wordCount
dipify _ sz (AtBuiltin _ And) = dipBoolOp sz BoolAnd
dipify _ sz (AtBuiltin _ Or) = dipBoolOp sz BoolOr
dipify _ sz (AtBuiltin _ Xor) = dipBoolOp sz BoolXor
dipify _ sz (AtBuiltin _ WordMinus) = dipOp sz IntMinusIR
dipify _ sz (AtBuiltin _ WordDiv) = dipOp sz WordDivIR
dipify _ sz (AtBuiltin _ WordMod) = dipOp sz WordModIR
dipify _ _ (AtBuiltin ([], _) Dup) = error "Internal error: Ill-typed dup!"
dipify env sz (AtBuiltin (is, _) Dup) = do
let sz' = size' env (last is) in
pure $
copyBytes 0 (-sz) sz -- copy sz bytes over to the end of the stack
++ copyBytes (-sz) (-sz - sz') sz' -- copy sz' bytes over (duplicate)
++ copyBytes (-sz + sz') 0 sz -- copy sz bytes back
++ [ dataPointerInc sz' ] -- move data pointer over sz' bytes
dipify _ sz (IntLit _ i) = pure $ dipPush sz 8 (ConstInt $ fromInteger i)
dipify _ sz (WordLit _ w) = pure $ dipPush sz 8 (ConstWord $ fromIntegral w)
dipify _ sz (Int8Lit _ i) = pure $ dipPush sz 1 (ConstInt8 i)
dipify _ sz (BoolLit _ b) = pure $ dipPush sz 1 (ConstBool b)
dipify env sz (AtCons ann@(ConsAnn _ tag' _) _) =
pure $
copyBytes 0 (-sz) sz
++ dataPointerInc (padBytes env ann) : push 1 (ConstTag tag')
++ copyBytes (-sz) 0 sz
dipify env sz a@(If sty _ _) =
dipSupp env sz sty <$> writeAtom env False a
dipify env sz (AtName sty n) =
dipSupp env sz sty . pure . KCall <$> lookupName n
dipify env sz a@(Case sty _) =
dipSupp env sz sty <$> writeAtom env False a
dipSupp :: SizeEnv -> Int64 -> MonoStackType -> [Stmt] -> [Stmt]
dipSupp env sz (is, os) stmts =
let excessSz = sizeStack env os - sizeStack env is -- how much the atom(s) grow the stack
in case compare excessSz 0 of
EQ -> plainShift sz stmts
LT -> dipDo sz stmts
GT -> dipHelp excessSz sz stmts
dipHelp :: Int64 -> Int64 -> [Stmt] -> [Stmt]
dipHelp excessSz dipSz stmts =
let shiftNext = dataPointerDec dipSz
shiftBack = dataPointerInc dipSz
in
shiftNext
: copyBytes excessSz (-dipSz) dipSz -- copy bytes past end of stack
++ stmts
++ copyBytes (-dipSz) 0 dipSz -- copy bytes back (now from 0 of stack; data pointer has been set)
++ [shiftBack]
dipPush :: Int64 -> Int64 -> Exp -> [Stmt]
dipPush sz sz' e =
-- FIXME: is this right?
copyBytes 0 (-sz) sz
++ push sz' e
++ copyBytes (-sz) 0 sz -- copy bytes back (data pointer has been incremented already by push)
-- for e.g. negation where the stack size stays the same
plainShift :: Int64 -> [Stmt] -> [Stmt]
plainShift sz stmt =
let shiftNext = dataPointerDec sz
shiftBack = dataPointerInc sz
in
(shiftNext : stmt ++ [shiftBack])
-- works in general because relations, shifts, operations shrink the size of the
-- stack.
dipDo :: Int64 -> [Stmt] -> [Stmt]
dipDo sz stmt =
let shiftNext = dataPointerDec sz
shiftBack = dataPointerInc sz
copyBytes' = copyBytes 0 sz sz
in
(shiftNext : stmt ++ copyBytes' ++ [shiftBack])
dipShift :: Int64 -> IntBinOp -> TempM [Stmt]
dipShift sz op = dipDo sz <$> intShift op
dipRel :: Int64 -> RelBinOp -> TempM [Stmt]
dipRel sz rel = dipDo sz <$> intRel rel
dipOp :: Int64 -> IntBinOp -> TempM [Stmt]
dipOp sz op = dipDo sz <$> intOp op
dipBoolOp :: Int64 -> BoolBinOp -> TempM [Stmt]
dipBoolOp sz op = dipDo sz <$> boolOp op
copyBytes :: Int64 -- ^ dest offset
-> Int64 -- ^ src offset
-> Int64 -- ^ Number of bytes to copy
-> [Stmt]
copyBytes off1 off2 b
| b `mod` 8 == 0 =
let is = fmap (8*) [0..(b `div` 8 - 1)] in
[ MovMem (dataPointerPlus (i + off1)) 8 (Mem 8 $ dataPointerPlus (i + off2)) | i <- is ]
-- TODO: 4 byte chunks, &c. (would require more registers).
| otherwise =
[ MovMem (dataPointerPlus (i + off1)) 1 (Mem 1 $ dataPointerPlus (i + off2)) | i <- [0..(b-1)] ]
dataPointerDec :: Int64 -> Stmt
dataPointerDec i = MovTemp DataPointer (ExprIntBinOp IntMinusIR (Reg DataPointer) (ConstInt i))
dataPointerInc :: Int64 -> Stmt
dataPointerInc i = MovTemp DataPointer (ExprIntBinOp IntPlusIR (Reg DataPointer) (ConstInt i))
dataPointerPlus :: Int64 -> Exp
dataPointerPlus off =
if off > 0
then ExprIntBinOp IntPlusIR (Reg DataPointer) (ConstInt off)
else ExprIntBinOp IntMinusIR (Reg DataPointer) (ConstInt (negate off))