parsley-0.1.0.1: src/ghc/Parsley/Internal/Backend/CodeGenerator.hs
{-# OPTIONS_GHC -Wno-incomplete-patterns #-}
{-# LANGUAGE PatternSynonyms #-}
module Parsley.Internal.Backend.CodeGenerator (codeGen) where
import Data.Maybe (isJust)
import Data.Set (Set, elems)
import Control.Monad.Trans (lift)
import Parsley.Internal.Backend.Machine (Defunc(USER, SAME), LetBinding, makeLetBinding, Instr(..),
pattern Fmap, pattern App, _Modify, _Get, _Put, _Make, pattern If,
addCoins, refundCoins, drainCoins, PositionOps,
IMVar, IΦVar, IΣVar, MVar(..), ΦVar(..), ΣVar(..))
import Parsley.Internal.Backend.InstructionAnalyser (coinsNeeded)
import Parsley.Internal.Common.Fresh (VFreshT, HFresh, evalFreshT, evalFresh, construct, MonadFresh(..), mapVFreshT)
import Parsley.Internal.Common.Indexed (Fix, Fix4(In4), Cofree(..), Nat(..), imap, histo, extract, (|>))
import Parsley.Internal.Core.CombinatorAST (Combinator(..), MetaCombinator(..))
import Parsley.Internal.Core.Defunc (Defunc(COMPOSE, ID), pattern FLIP_H, pattern UNIT)
import Parsley.Internal.Trace (Trace(trace))
import Parsley.Internal.Core.Defunc as Core (Defunc)
type CodeGenStack a = VFreshT IΦVar (VFreshT IMVar (HFresh IΣVar)) a
runCodeGenStack :: CodeGenStack a -> IMVar -> IΦVar -> IΣVar -> a
runCodeGenStack m μ0 φ0 σ0 =
(flip evalFresh σ0 .
flip evalFreshT μ0 .
flip evalFreshT φ0) m
newtype CodeGen o a x =
CodeGen {runCodeGen :: forall xs n r. Fix4 (Instr o) (x : xs) (Succ n) r a -> CodeGenStack (Fix4 (Instr o) xs (Succ n) r a)}
{-# INLINEABLE codeGen #-}
codeGen :: (Trace, PositionOps o) => Maybe (MVar x) -> Fix Combinator x -> Set IΣVar -> IMVar -> IΣVar -> LetBinding o a x
codeGen letBound p rs μ0 σ0 = trace ("GENERATING " ++ name ++ ": " ++ show p ++ "\nMACHINE: " ++ show (map ΣVar (elems rs)) ++ " => " ++ show m) $ makeLetBinding m rs
where
name = maybe "TOP LEVEL" show letBound
m = finalise (histo alg p)
alg :: PositionOps o => Combinator (Cofree Combinator (CodeGen o a)) x -> CodeGen o a x
alg = peephole |> (\x -> CodeGen (direct (imap extract x)))
finalise cg =
let m = runCodeGenStack (runCodeGen cg (In4 Ret)) μ0 0 σ0
in if isJust letBound then m else addCoins (coinsNeeded m) m
pattern (:<$>:) :: Core.Defunc (a -> b) -> k a -> Combinator (Cofree Combinator k) b
pattern f :<$>: p <- (_ :< Pure f) :<*>: (p :< _)
pattern (:$>:) :: Combinator (Cofree Combinator k) a -> Core.Defunc b -> Combinator (Cofree Combinator k) b
pattern p :$>: x <- (_ :< p) :*>: (_ :< Pure x)
pattern LiftA2 :: Core.Defunc (a -> b -> c) -> k a -> k b -> Combinator (Cofree Combinator k) c
pattern LiftA2 f p q <- (_ :< ((_ :< Pure f) :<*>: (p :< _))) :<*>: (q :< _)
pattern TryOrElse :: k a -> k a -> Combinator (Cofree Combinator k) a
pattern TryOrElse p q <- (_ :< Try (p :< _)) :<|>: (q :< _)
rollbackHandler :: Fix4 (Instr o) (o : xs) (Succ n) r a
rollbackHandler = In4 (Seek (In4 Empt))
parsecHandler :: PositionOps o => Fix4 (Instr o) xs (Succ n) r a -> Fix4 (Instr o) (o : xs) (Succ n) r a
parsecHandler k = In4 (Tell (In4 (Lift2 SAME (In4 (If k (In4 Empt))))))
peephole :: PositionOps o => Combinator (Cofree Combinator (CodeGen o a)) x -> Maybe (CodeGen o a x)
peephole (f :<$>: p) = Just $ CodeGen $ \m -> runCodeGen p (In4 (Fmap (USER f) m))
peephole (LiftA2 f p q) = Just $ CodeGen $ \m ->
do qc <- runCodeGen q (In4 (Lift2 (USER f) m))
runCodeGen p qc
peephole (TryOrElse p q) = Just $ CodeGen $ \m -> -- FIXME!
do (binder, φ) <- makeΦ m
pc <- freshΦ (runCodeGen p (deadCommitOptimisation φ))
fmap (binder . In4 . Catch pc . In4 . Seek) (freshΦ (runCodeGen q φ))
peephole ((_ :< (Try (p :< _) :$>: x)) :<|>: (q :< _)) = Just $ CodeGen $ \m ->
do (binder, φ) <- makeΦ m
pc <- freshΦ (runCodeGen p (deadCommitOptimisation (In4 (Pop (In4 (Push (USER x) φ))))))
fmap (binder . In4 . Catch pc . In4 . Seek) (freshΦ (runCodeGen q φ))
peephole (MetaCombinator RequiresCut (_ :< ((p :< _) :<|>: (q :< _)))) = Just $ CodeGen $ \m ->
do (binder, φ) <- makeΦ m
pc <- freshΦ (runCodeGen p (deadCommitOptimisation φ))
qc <- freshΦ (runCodeGen q φ)
return $! binder (In4 (Catch pc (parsecHandler qc)))
peephole (MetaCombinator RequiresCut (_ :< ChainPre (op :< _) (p :< _))) = Just $ CodeGen $ \m ->
do μ <- askM
σ <- freshΣ
opc <- freshM (runCodeGen op (In4 (Fmap (USER (FLIP_H COMPOSE)) (In4 (_Modify σ (In4 (Jump μ)))))))
pc <- freshM (runCodeGen p (In4 (App m)))
return $! In4 (Push (USER ID) (In4 (_Make σ (In4 (Iter μ opc (parsecHandler (In4 (_Get σ (addCoins (coinsNeeded pc) pc)))))))))
peephole (MetaCombinator RequiresCut (_ :< ChainPost (p :< _) (op :< _))) = Just $ CodeGen $ \m ->
do μ <- askM
σ <- freshΣ
let nm = coinsNeeded m
opc <- freshM (runCodeGen op (In4 (_Modify σ (In4 (Jump μ)))))
freshM (runCodeGen p (In4 (_Make σ (In4 (Iter μ opc (parsecHandler (In4 (_Get σ (addCoins nm m)))))))))
peephole (MetaCombinator Cut (_ :< ChainPre (op :< _) (p :< _))) = Just $ CodeGen $ \m ->
do μ <- askM
σ <- freshΣ
opc <- freshM (runCodeGen op (In4 (Fmap (USER (FLIP_H COMPOSE)) (In4 (_Modify σ (In4 (Jump μ)))))))
let nop = coinsNeeded opc
pc <- freshM (runCodeGen p (In4 (App m)))
return $! In4 (Push (USER ID) (In4 (_Make σ (In4 (Iter μ (addCoins nop opc) (parsecHandler (In4 (_Get σ (addCoins (coinsNeeded pc) pc)))))))))
-- TODO: One more for fmap try
peephole _ = Nothing
direct :: PositionOps o => Combinator (CodeGen o a) x -> Fix4 (Instr o) (x : xs) (Succ n) r a -> CodeGenStack (Fix4 (Instr o) xs (Succ n) r a)
direct (Pure x) m = do return $! In4 (Push (USER x) m)
direct (Satisfy p) m = do return $! In4 (Sat (USER p) m)
direct (pf :<*>: px) m = do pxc <- runCodeGen px (In4 (App m)); runCodeGen pf pxc
direct (p :*>: q) m = do qc <- runCodeGen q m; runCodeGen p (In4 (Pop qc))
direct (p :<*: q) m = do qc <- runCodeGen q (In4 (Pop m)); runCodeGen p qc
direct Empty _ = do return $! In4 Empt
direct (p :<|>: q) m =
do (binder, φ) <- makeΦ m
pc <- freshΦ (runCodeGen p (deadCommitOptimisation φ))
qc <- freshΦ (runCodeGen q φ)
let np = coinsNeeded pc
let nq = coinsNeeded qc
let dp = np - min np nq
let dq = nq - min np nq
return $! binder (In4 (Catch (addCoins dp pc) (parsecHandler (addCoins dq qc))))
direct (Try p) m = do fmap (In4 . flip Catch rollbackHandler) (runCodeGen p (deadCommitOptimisation m))
direct (LookAhead p) m =
do n <- fmap coinsNeeded (runCodeGen p (In4 Empt)) -- Dodgy hack, but oh well
fmap (In4 . Tell) (runCodeGen p (In4 (Swap (In4 (Seek (refundCoins n m))))))
direct (NotFollowedBy p) m =
do pc <- runCodeGen p (In4 (Pop (In4 (Seek (In4 (Commit (In4 Empt)))))))
let np = coinsNeeded pc
let nm = coinsNeeded m
return $! In4 (Catch (addCoins (max (np - nm) 0) (In4 (Tell pc))) (In4 (Seek (In4 (Push (USER UNIT) m)))))
direct (Branch b p q) m =
do (binder, φ) <- makeΦ m
pc <- freshΦ (runCodeGen p (In4 (Swap (In4 (App φ)))))
qc <- freshΦ (runCodeGen q (In4 (Swap (In4 (App φ)))))
let minc = coinsNeeded (In4 (Case pc qc))
let dp = max 0 (coinsNeeded pc - minc)
let dq = max 0 (coinsNeeded qc - minc)
fmap binder (runCodeGen b (In4 (Case (addCoins dp pc) (addCoins dq qc))))
direct (Match p fs qs def) m =
do (binder, φ) <- makeΦ m
qcs <- traverse (\q -> freshΦ (runCodeGen q φ)) qs
defc <- freshΦ (runCodeGen def φ)
let minc = coinsNeeded (In4 (Choices (map USER fs) qcs defc))
let defc':qcs' = map (max 0 . subtract minc . coinsNeeded >>= addCoins) (defc:qcs)
fmap binder (runCodeGen p (In4 (Choices (map USER fs) qcs' defc')))
direct (Let _ μ _) m = return $! tailCallOptimise μ m
direct (ChainPre op p) m =
do μ <- askM
σ <- freshΣ
opc <- freshM (runCodeGen op (In4 (Fmap (USER (FLIP_H COMPOSE)) (In4 (_Modify σ (In4 (Jump μ)))))))
let nop = coinsNeeded opc
pc <- freshM (runCodeGen p (In4 (App m)))
return $! In4 (Push (USER ID) (In4 (_Make σ (In4 (Iter μ (addCoins nop opc) (parsecHandler (In4 (_Get σ pc))))))))
direct (ChainPost p op) m =
do μ <- askM
σ <- freshΣ
opc <- freshM (runCodeGen op (In4 (_Modify σ (In4 (Jump μ)))))
let nop = coinsNeeded opc
freshM (runCodeGen p (In4 (_Make σ (In4 (Iter μ (addCoins nop opc) (parsecHandler (In4 (_Get σ m))))))))
direct (MakeRegister σ p q) m = do qc <- runCodeGen q m; runCodeGen p (In4 (_Make σ qc))
direct (GetRegister σ) m = do return $! In4 (_Get σ m)
direct (PutRegister σ p) m = do runCodeGen p (In4 (_Put σ (In4 (Push (USER UNIT) m))))
direct (Debug name p) m = do fmap (In4 . LogEnter name) (runCodeGen p (In4 (Commit (In4 (LogExit name m)))))
direct (MetaCombinator Cut p) m = do runCodeGen p (addCoins (coinsNeeded m) m)
tailCallOptimise :: MVar x -> Fix4 (Instr o) (x : xs) (Succ n) r a -> Fix4 (Instr o) xs (Succ n) r a
tailCallOptimise μ (In4 Ret) = In4 (Jump μ)
tailCallOptimise μ k = In4 (Call μ k)
-- Thanks to the optimisation applied to the K stack, commit is deadcode before Ret
-- However, I'm not yet sure about the interactions with try yet...
deadCommitOptimisation :: Fix4 (Instr o) xs n r a -> Fix4 (Instr o) xs (Succ n) r a
deadCommitOptimisation (In4 Ret) = In4 Ret
deadCommitOptimisation m = In4 (Commit m)
-- Refactor with asks
askM :: CodeGenStack (MVar a)
askM = lift (construct MVar)
askΦ :: CodeGenStack (ΦVar a)
askΦ = construct ΦVar
freshM :: CodeGenStack a -> CodeGenStack a
freshM = mapVFreshT newScope
freshΦ :: CodeGenStack a -> CodeGenStack a
freshΦ = newScope
makeΦ :: Fix4 (Instr o) (x ': xs) (Succ n) r a -> CodeGenStack (Fix4 (Instr o) xs (Succ n) r a -> Fix4 (Instr o) xs (Succ n) r a, Fix4 (Instr o) (x : xs) (Succ n) r a)
makeΦ m | elidable m = return $! (id, m)
where
-- This is double-φ optimisation: If a φ-node points directly to another φ-node, then it can be elided
elidable (In4 (Join _)) = True
-- This is terminal-φ optimisation: If a φ-node points directly to a terminal operation, then it can be elided
elidable (In4 Ret) = True
elidable _ = False
makeΦ m = let n = coinsNeeded m in fmap (\φ -> (In4 . MkJoin φ (addCoins n m), drainCoins n (In4 (Join φ)))) askΦ
freshΣ :: CodeGenStack (ΣVar a)
freshΣ = lift (lift (construct ΣVar))