> module Epic.Bytecode where
> import Control.Monad.State
> import List
> import Epic.Language
> type Local = Int
> type TmpVar = Int
Register based - most operations do an action, then put the result in a 'TmpVar' which is basically a numbered register. There are infinite registers at this stage.
> data ByteOp = CALL TmpVar Name [TmpVar]
>             | TAILCALL TmpVar Name [TmpVar]
>             | THUNK TmpVar Int Name [TmpVar]
>             | ADDARGS TmpVar TmpVar [TmpVar]
>             | FOREIGN Type TmpVar String [(TmpVar, Type)]
>             | VAR TmpVar Local
>             | ASSIGN Local TmpVar
>             | CON TmpVar Tag [TmpVar]
>             | UNIT TmpVar
>             | INT TmpVar Int
>             | BIGINT TmpVar Integer
>             | FLOAT TmpVar Float
>             | BIGFLOAT TmpVar Double
>             | STRING TmpVar String
>             | PROJ TmpVar TmpVar Int -- project into a register
>             | PROJVAR Local TmpVar Int -- project into a local variable
>               -- each case branch records which tag it's code for
>             | CASE TmpVar [(Int, Bytecode)] (Maybe Bytecode)
>             | IF TmpVar Bytecode Bytecode
>             | OP TmpVar Op TmpVar TmpVar
>             | LOCALS Int -- allocate space for locals
>             | TMPS Int -- declare temporary variables
>             | EVAL TmpVar
>             -- | LET TmpVar Local TmpVar
>             | RETURN TmpVar
>             | DRETURN -- return dummy value
>             | ERROR String -- Fatal error, exit
>             | TRACE String [TmpVar]
>   deriving Show
> type Bytecode = [ByteOp]
> data FunCode = Code [Type] Bytecode
>   deriving Show
> data CompileState = CS { arg_types :: [Type],
>                          num_locals :: Int,
>                          next_tmp :: Int }
> compile :: Context -> Name -> Func -> FunCode
> compile ctxt fname fn@(Bind args locals def) = 
>     let cs = (CS (map snd args) (length args) 1)
>         code = evalState (scompile ctxt fname fn) cs in
>         Code (map snd args) code
> data TailCall = Tail | Middle
> scompile :: Context -> Name -> Func -> State CompileState Bytecode
> scompile ctxt fname (Bind args locals def) = 
>     do -- put (CS args (length args) 1)
>        code <- ecomp Tail def 0 (length args)
>        cs <- get
>        return $ (LOCALS (num_locals cs)):
>                 (TRACE (show fname) [0..(length args)-1]):
>                 (TMPS (next_tmp cs)):code ++[EVAL 0, RETURN 0]
>   where
>     new_tmp :: State CompileState Int
>     new_tmp = do cs <- get
>                  let reg' = next_tmp cs
>                  put (cs { next_tmp = reg'+1 } )
>                  return reg'
Add some locals, return de Bruijn level of first new one.
>     new_locals :: Int -> State CompileState Int
>     new_locals args = 
>         do cs <- get
>            let loc = num_locals cs
>            put (cs { num_locals = loc+args } )
>            return loc
Take an expression and the register (TmpVar) to put the result into; compile code to do just that. Also carry the number of real variables currently in scope so that, in particular, when we project from a data structure we store it in the right place.
>     ecomp :: TailCall -> Expr -> TmpVar -> Int -> 
>              State CompileState Bytecode
>     ecomp tcall (V v) reg vs = 
>         do return [VAR reg v]
>     ecomp tcall (R x) reg vs = acomp tcall False (R x) [] reg vs
>     ecomp tcall (App f as) reg vs = acomp tcall False f as reg vs
>     ecomp tcall (LazyApp f as) reg vs = acomp tcall True f as reg vs
>     ecomp tcall (Con t as) reg vs = 
>         do (argcode, argregs) <- ecomps as vs
>            return $ argcode ++ [CON reg t argregs]
>     ecomp tcall (Proj con i) reg vs =
>         do reg' <- new_tmp
>            concode <- ecomp Middle con reg' vs
>            return [PROJ reg reg' i]
>     ecomp tcall (Const c) reg vs = ccomp c reg
>     ecomp tcall (Case scrutinee alts) reg vs =
>         do screg <- new_tmp
>            sccode <- ecomp Middle scrutinee screg vs
>            (altcode, def) <- altcomps tcall (order alts) screg reg vs
>            return $ sccode ++ [EVAL screg, CASE screg altcode def]
>     ecomp tcall (If a t e) reg vs =
>         do areg <- new_tmp
>            acode <- ecomp Middle a areg vs
>            tcode <- ecomp tcall t reg vs
>            ecode <- ecomp tcall e reg vs
>            return $ acode ++ [EVAL areg, IF areg tcode ecode]
>     ecomp tcall (Op op l r) reg vs =
>         do lreg <- new_tmp
>            rreg <- new_tmp
>            lcode <- ecomp Middle l lreg vs
>            rcode <- ecomp Middle r rreg vs
>            return $ lcode ++ [EVAL lreg] ++ 
>                     rcode ++ [EVAL rreg, OP reg op lreg rreg]
>     ecomp tcall (Let nm ty val scope) reg vs =
>         do loc <- new_locals 1
>            reg' <- new_tmp
>            valcode <- ecomp Middle val reg' vs
>            scopecode <- ecomp tcall scope reg (vs+1)
>            return $ valcode ++ (EVAL reg'):(ASSIGN vs reg'):scopecode
>     ecomp tcall (Error str) reg vs = return [ERROR str]
>     ecomp tcall Impossible reg vs = return [ERROR "The impossible happened."]
>     ecomp tcall (ForeignCall ty fn argtypes) reg vs = do
>           let (args,types) = unzip argtypes
>           (argcode, argregs) <- ecomps args vs
>           let evalcode = map EVAL argregs
>           return $ argcode ++ evalcode ++ [FOREIGN ty reg fn (zip argregs types)]
>     ecomps :: [Expr] -> Int -> State CompileState (Bytecode, [TmpVar])
>     ecomps e vs = ecomps' [] [] e vs
>     ecomps' code tmps [] vs = return (code, tmps)
>     ecomps' code tmps (e:es) vs =
>         do reg <- new_tmp
>            ecode <- ecomp Middle e reg vs
>            ecomps' (code++ecode) (tmps++[reg]) es vs
Compile case alternatives.
>     order :: [CaseAlt] -> [CaseAlt]
>     order xs = sort xs -- insertError 0 (sort xs)
We don't do this any more, now that we have default cases.
>     insertError t [] = []
>     insertError t (a@(Alt tn _ _):xs)
>         = {- (errors t tn) ++ -} a:(insertError (tn+1) xs)
>     insertError t rest@((DefaultCase _):xs)
>         = rest -- End at defaul case
>     errors x end | x == end = []
>                  | otherwise = (Alt x [] Impossible):(errors (x+1) end)
>     altcomps :: TailCall -> [CaseAlt] -> TmpVar -> TmpVar -> Int ->
>                 State CompileState ([(Int, Bytecode)], Maybe Bytecode)
>     altcomps tc [] _ _ vs = return ([], Nothing)
>     altcomps tc (a:as) scrutinee reg vs = 
>         do (t,acode) <- altcomp tc a scrutinee reg vs
>            (ascode, def) <- altcomps tc as scrutinee reg vs
>            if (t<0) then return (ascode, Just acode)
>                     else return ((t,acode):ascode, def)
Assume that all the tags are in order, and unused constructors have a default inserted (i.e., tag can be ignored). Return the tag and the code - tag is -1 for default case.
>     altcomp :: TailCall -> CaseAlt -> TmpVar -> TmpVar -> Int ->
>                State CompileState (Int, Bytecode)
>     altcomp tc (Alt tag nmargs expr) scrutinee reg vs =
>         do let args = map snd nmargs
>            local <- new_locals (length args)
>            projcode <- project args scrutinee vs 0
>            exprcode <- ecomp tc expr reg (vs+(length args))
>            return (tag, projcode++exprcode)
>     altcomp tc (DefaultCase expr) scrutinee reg vs =
>         do exprcode <- ecomp tc expr reg vs
>            return (-1,exprcode)
>     project [] _ _ _ = return []
>     project (_:as) scr loc arg = 
>         do let acode = PROJVAR loc scr arg
>            ascode <- project as scr (loc+1) (arg+1)
>            return (acode:ascode)
Compile an application of a function to arguments
>     acomp :: TailCall -> Bool -> Expr -> [Expr] -> TmpVar -> Int ->
>              State CompileState Bytecode
>     acomp tc lazy (R x) args reg vs
>           | lazy == False && arity x ctxt == length args =
>               do (argcode, argregs) <- ecomps args vs
>                  return $ argcode {- ++ map EVAL argregs -} ++ [(tcall tc) reg x argregs]
>           | otherwise =
>               do (argcode, argregs) <- ecomps args vs
>                  return $ argcode ++ [THUNK reg (arity x ctxt) x argregs]
>      where tcall Tail = TAILCALL
>            tcall Middle = CALL
>     acomp _ _ f args reg vs
>           = do (argcode, argregs) <- ecomps args vs
>                reg' <- new_tmp
>                fcode <- ecomp Middle f reg' vs
>                return $ fcode ++ argcode ++ [ADDARGS reg reg' argregs]
>     ccomp (MkInt i) reg = return [INT reg i]
>     ccomp (MkBigInt i) reg = return [BIGINT reg i]
>     ccomp (MkChar c) reg = return [INT reg (fromEnum c)]
>     ccomp (MkFloat f) reg = return [FLOAT reg f]
>     ccomp (MkBigFloat f) reg = return [BIGFLOAT reg f]
>     ccomp (MkBool b) reg = return [INT reg (if b then 1 else 0)]
>     ccomp (MkString s) reg = return [STRING reg s]
>     ccomp (MkUnit) reg = return [UNIT reg]