3Description : Coordinates the compilation process.
6Maintainer : The Idris Community.
8{-# LANGUAGE CPP, PatternGuards, TypeSynonymInstances #-}
10module IRTS.Compiler(compile, generate) where
13import Idris.AbsSyntaxTree
15import Idris.Core.CaseTree
16import Idris.Core.Evaluate
23import IRTS.CodegenCommon
24import IRTS.CodegenJavaScript
25import IRTS.Defunctionalise
34import Prelude hiding (id, (.))
36import Control.Applicative
37import Control.Category
38import Control.Monad.State
39import Data.IntSet (IntSet)
40import qualified Data.IntSet as IS
42import qualified Data.Map as M
45import qualified Data.Set as S
47import System.Directory
48import System.Environment
50import System.FilePath (addTrailingPathSeparator, (</>))
54-- | Compile to simplified forms and return CodegenInfo
55compile :: Codegen -> FilePath -> Maybe Term -> Idris CodegenInfo
56compile codegen f mtm = do
57 logCodeGen 1 "Compiling Output."
58 iReport 2 "Compiling Output."
59 checkMVs -- check for undefined metavariables
60 checkTotality -- refuse to compile if there are totality problems
61 exports <- findExports
62 let rootNames = case mtm of
66 reachableNames <- performUsageAnalysis
67 (rootNames ++ getExpNames exports)
68 maindef <- case mtm of
70 Just tm -> do md <- irMain tm
71 logCodeGen 1 $ "MAIN: " ++ show md
72 return [(sMN 0 "runMain", md)]
73 objs <- getObjectFiles codegen
74 libs <- getLibs codegen
75 flags <- getFlags codegen
76 hdrs <- getHdrs codegen
77 impdirs <- allImportDirs
78 ttDeclarations <- getDeclarations reachableNames
79 defsIn <- mkDecls reachableNames
80 -- if no 'main term' given, generate interface files
81 let iface = case mtm of
85 let defs = defsIn ++ maindef
87 -- Inlined top level LDecl made here
88 let defsInlined = inlineAll defs
89 let defsUniq = map (allocUnique (addAlist defsInlined emptyContext))
92 let (nexttag, tagged) = addTags 65536 (liftAll defsUniq)
93 let ctxtIn = addAlist tagged emptyContext
95 logCodeGen 1 "Defunctionalising"
96 iReport 3 "Defunctionalising"
97 let defuns_in = defunctionalise nexttag ctxtIn
98 logCodeGen 5 $ show defuns_in
99 logCodeGen 1 "Inlining"
101 let defuns = inline defuns_in
102 logCodeGen 5 $ show defuns
103 logCodeGen 1 "Resolving variables for CG"
105 let checked = simplifyDefs defuns (toAlist defuns)
107 dumpCases <- getDumpCases
108 dumpDefun <- getDumpDefun
111 Just f -> runIO $ writeFile f (showCaseTrees defs)
114 Just f -> runIO $ writeFile f (dumpDefuns defuns)
115 triple <- Idris.AbsSyntax.targetTriple
116 cpu <- Idris.AbsSyntax.targetCPU
117 logCodeGen 1 "Generating Code."
118 iReport 2 "Generating Code."
120 OK c -> do return $ CodegenInfo f outty triple cpu
121 hdrs impdirs objs libs flags
122 NONE c (toAlist defuns)
126 where checkMVs = do i <- getIState
127 case map fst (idris_metavars i) \\ primDefs of
129 ms -> do iputStrLn $ "WARNING: There are incomplete holes:\n " ++ show ms
130 iputStrLn "\nEvaluation of any of these will crash at run time."
132 checkTotality = do i <- getIState
133 case idris_totcheckfail i of
135 ((fc, msg):fs) -> ierror . At fc . Msg $ "Cannot compile:\n " ++ msg
137generate :: Codegen -> FilePath -> CodegenInfo -> IO ()
138generate codegen mainmod ir
140 -- Built-in code generators (FIXME: lift these out!)
141 Via _ "c" -> codegenC ir
142 -- Any external code generator
143 Via fm cg -> do input <- case fm of
144 IBCFormat -> return mainmod
146 tempdir <- getTemporaryDirectory
147 (fn, h) <- openTempFile tempdir "idris-cg.json"
151 let cmd = "idris-codegen-" ++ cg
152 args = [input, "-o", outputFile ir] ++ compilerFlags ir
153 exit <- rawSystem cmd args
154 when (exit /= ExitSuccess) $
155 putStrLn ("FAILURE: " ++ show cmd ++ " " ++ show args)
156 Bytecode -> dumpBC (simpleDecls ir) (outputFile ir)
158irMain :: TT Name -> Idris LDecl
160 i <- irTerm (sMN 0 "runMain") M.empty [] tm
161 return $ LFun [] (sMN 0 "runMain") [] (LForce i)
163mkDecls :: [Name] -> Idris [(Name, LDecl)]
166 let ds = filter (\(n, d) -> n `elem` used || isCon d) $ ctxtAlist (tt_ctxt i)
167 decls <- mapM build ds
170getDeclarations :: [Name] -> Idris ([(Name, TTDecl)])
173 let ds = filter (\(n, (d,_,_,_,_,_)) -> n `elem` used || isCon d) $ ((toAlist . definitions . tt_ctxt) i)
176showCaseTrees :: [(Name, LDecl)] -> String
177showCaseTrees = showSep "\n\n" . map showCT . sortBy (comparing defnRank)
179 showCT (n, LFun _ f args lexp)
180 = show n ++ " " ++ showSep " " (map show args) ++ " =\n\t"
182 showCT (n, LConstructor c t a) = "data " ++ show n ++ " " ++ show a
184 defnRank :: (Name, LDecl) -> String
185 defnRank (n, LFun _ _ _ _) = "1" ++ nameRank n
186 defnRank (n, LConstructor _ _ _) = "2" ++ nameRank n
188 nameRank :: Name -> String
189 nameRank (UN s) = "1" ++ show s
190 nameRank (MN i s) = "2" ++ show s ++ show i
191 nameRank (NS n ns) = "3" ++ concatMap show (reverse ns) ++ nameRank n
192 nameRank (SN sn) = "4" ++ snRank sn
193 nameRank n = "5" ++ show n
195 snRank :: SpecialName -> String
196 snRank (WhereN i n n') = "1" ++ nameRank n' ++ nameRank n ++ show i
197 snRank (ImplementationN n args) = "2" ++ nameRank n ++ concatMap show args
198 snRank (ParentN n s) = "3" ++ nameRank n ++ show s
199 snRank (MethodN n) = "4" ++ nameRank n
200 snRank (CaseN _ n) = "5" ++ nameRank n
201 snRank (ElimN n) = "6" ++ nameRank n
202 snRank (ImplementationCtorN n) = "7" ++ nameRank n
203 snRank (WithN i n) = "8" ++ nameRank n ++ show i
205isCon (TyDecl _ _) = True
208build :: (Name, Def) -> Idris (Name, LDecl)
213 let args = map (\x -> sMN x "op") [0..] in
214 return (n, (LFun [] n (take ar args)
215 (LOp op (map (LV . Glob) (take ar args)))))
216 _ -> do def <- mkLDecl n d
217 logCodeGen 3 $ "Compiled " ++ show n ++ " =\n\t" ++ show def
220 | Just (ar, op) <- lookup n (idris_scprims i)
222 | Just ar <- lookup n (S.toList (idris_externs i))
223 = Just (ar, LExternal n)
224 getPrim n i = Nothing
226declArgs args inl n (LLam xs x) = declArgs (args ++ xs) inl n x
227declArgs args inl n x = LFun (if inl then [Inline] else []) n args x
229mkLDecl n (Function tm _)
230 = declArgs [] True n <$> irTerm n M.empty [] tm
232mkLDecl n (CaseOp ci _ _ _ pats cd)
233 = declArgs [] (case_inlinable ci || caseName n) n <$> irTree n args sc
235 (args, sc) = cases_runtime cd
237 -- Always attempt to inline functions arising from 'case' expressions
238 caseName (SN (CaseN _ _)) = True
239 caseName (SN (WithN _ _)) = True
240 caseName (NS n _) = caseName n
243mkLDecl n (TyDecl (DCon tag arity _) _) =
244 LConstructor n tag . length <$> fgetState (cg_usedpos . ist_callgraph n)
246mkLDecl n (TyDecl (TCon t a) _) = return $ LConstructor n (-1) a
247mkLDecl n _ = return $ (declArgs [] True n LNothing) -- postulate, never run
250 { viMethod :: Maybe Name
254type Vars = M.Map Name VarInfo
256irTerm :: Name -> Vars -> [Name] -> Term -> Idris LExp
257irTerm top vs env tm@(App _ f a) = do
261 | n `elem` map fst (idris_metavars ist) \\ primDefs
262 -> return $ LError $ "ABORT: Attempt to evaluate hole " ++ show n
264 | m == txt "mkForeignPrim"
265 -> doForeign vs env (reverse (drop 4 args)) -- drop implicits
267 (P _ (UN u) _, [_, arg])
268 | u == txt "unsafePerformPrimIO"
269 -> irTerm top vs env arg
272 | u == txt "assert_unreachable"
273 -> return $ LError $ "ABORT: Reached an unreachable case in " ++ show top
275 (P _ (UN u) _, [_, msg])
276 | u == txt "idris_crash"
277 -> do msg' <- irTerm top vs env msg
278 return $ LOp LCrash [msg']
280 -- TMP HACK - until we get inlining.
281 (P _ (UN r) _, [_, _, _, _, _, arg])
283 -> irTerm top vs env arg
285 -- 'void' doesn't have any pattern clauses and only gets called on
286 -- erased things in higher order contexts (also a TMP HACK...)
291 -- Laziness, the old way
292 (P _ (UN l) _, [_, arg])
294 -> error "lazy has crept in somehow"
296 (P _ (UN l) _, [_, arg])
298 -> LForce <$> irTerm top vs env arg
300 -- Laziness, the new way
301 (P _ (UN l) _, [_, _, arg])
303 -> LLazyExp <$> irTerm top vs env arg
305 (P _ (UN l) _, [_, _, arg])
307 -> LForce <$> irTerm top vs env arg
309 (P _ (UN a) _, [_, _, _, arg])
310 | a == txt "assert_smaller"
311 -> irTerm top vs env arg
313 (P _ (UN a) _, [_, arg])
314 | a == txt "assert_total"
315 -> irTerm top vs env arg
317 (P _ (UN p) _, [_, arg])
319 -> do arg' <- irTerm top vs env arg
320 return $ LOp LPar [LLazyExp arg']
322 (P _ (UN pf) _, [arg])
323 | pf == txt "prim_fork"
324 -> do arg' <- irTerm top vs env arg
325 return $ LOp LFork [LLazyExp arg']
327 (P _ (UN m) _, [_,size,t])
329 -> irTerm top vs env t
331 (P _ (UN tm) _, [_,t])
332 | tm == txt "trace_malloc"
333 -> irTerm top vs env t -- TODO
335 -- This case is here until we get more general inlining. It's just
336 -- a really common case, and the laziness hurts...
337 (P _ (NS (UN be) [b,p]) _, [_,x,(App _ (App _ (App _ (P _ (UN d) _) _) _) t),
338 (App _ (App _ (App _ (P _ (UN d') _) _) _) e)])
339 | be == txt "ifThenElse"
345 x' <- irTerm top vs env x
346 t' <- irTerm top vs env t
347 e' <- irTerm top vs env e
348 return (LCase Shared x'
349 [LConCase 0 (sNS (sUN "False") ["Bool","Prelude"]) [] e'
350 ,LConCase 1 (sNS (sUN "True" ) ["Bool","Prelude"]) [] t'
354 (P (DCon t arity _) n _, args) -> do
355 detag <- fgetState (opt_detaggable . ist_optimisation n)
356 used <- map fst <$> fgetState (cg_usedpos . ist_callgraph n)
358 let isNewtype = length used == 1 && detag
359 let argsPruned = [a | (i,a) <- zip [0..] args, i `elem` used]
361 -- The following code removes fields from data constructors
362 -- and performs the newtype optimisation.
364 -- The general rule here is:
365 -- Everything we get as input is not touched by erasure,
366 -- so it conforms to the official arities and types
367 -- and we can reason about it like it's plain TT.
369 -- It's only the data that leaves this point that's erased
370 -- and possibly no longer typed as the original TT version.
372 -- Especially, underapplied constructors must yield functions
373 -- even if all the remaining arguments are erased
374 -- (the resulting function *will* be applied, to NULLs).
376 -- This will probably need rethinking when we get erasure from functions.
378 -- "padLams" will wrap our term in LLam-bdas and give us
379 -- the "list of future unerased args" coming from these lambdas.
381 -- We can do whatever we like with the list of unerased args,
382 -- hence it takes a lambda: \unerased_argname_list -> resulting_LExp.
383 let padLams = padLambdas used (length args) arity
385 case compare (length args) arity of
388 GT -> ifail ("overapplied data constructor: " ++ show tm ++
390 "Arity: " ++ show arity ++ "\n" ++
391 "Arguments: " ++ show args ++ "\n" ++
392 "Pruned arguments: " ++ show argsPruned)
396 -> irTerm top vs env (head argsPruned)
398 | otherwise -- not newtype, plain data ctor
399 -> buildApp (LV $ Glob n) argsPruned
401 -- not saturated, underapplied
402 LT | isNewtype -- newtype
403 , length argsPruned == 1 -- and we already have the value
404 -> padLams . (\tm [] -> tm) -- the [] asserts there are no unerased args
405 <$> irTerm top vs env (head argsPruned)
407 | isNewtype -- newtype but the value is not among args yet
408 -> return . padLams $ \[vn] -> LApp False (LV $ Glob n) [LV $ Glob vn]
410 -- not a newtype, just apply to a constructor
412 -> padLams . applyToNames <$> buildApp (LV $ Glob n) argsPruned
415 (P (TCon t a) n _, args) -> return LNothing
417 -- an external name applied to arguments
418 (P _ n _, args) | S.member (n, length args) (idris_externs ist) -> do
419 LOp (LExternal n) <$> mapM (irTerm top vs env) args
421 -- a name applied to arguments
422 (P _ n _, args) -> do
423 case lookup n (idris_scprims ist) of
424 -- if it's a primitive that is already saturated,
425 -- compile to the corresponding op here already to save work
426 Just (arity, op) | length args == arity
427 -> LOp op <$> mapM (irTerm top vs env) args
429 -- otherwise, just apply the name
430 _ -> applyName n ist args
432 -- turn de bruijn vars into regular named references and try again
433 (V i, args) -> irTerm top vs env $ mkApp (P Bound (env !! i) Erased) args
437 <$> irTerm top vs env f
438 <*> mapM (irTerm top vs env) args
441 buildApp :: LExp -> [Term] -> Idris LExp
442 buildApp e [] = return e
443 buildApp e xs = LApp False e <$> mapM (irTerm top vs env) xs
445 applyToNames :: LExp -> [Name] -> LExp
446 applyToNames tm [] = tm
447 applyToNames tm ns = LApp False tm $ map (LV . Glob) ns
449 padLambdas :: [Int] -> Int -> Int -> ([Name] -> LExp) -> LExp
450 padLambdas used startIdx endSIdx mkTerm
451 = LLam allNames $ mkTerm nonerasedNames
453 allNames = [sMN i "sat" | i <- [startIdx .. endSIdx-1]]
454 nonerasedNames = [sMN i "sat" | i <- [startIdx .. endSIdx-1], i `elem` used]
456 applyName :: Name -> IState -> [Term] -> Idris LExp
457 applyName n ist args =
458 LApp False (LV $ Glob n) <$> mapM (irTerm top vs env . erase) (zip [0..] args)
461 | i >= arity || i `elem` used = x
464 arity = case fst4 <$> lookupCtxtExact n (definitions . tt_ctxt $ ist) of
465 Just (CaseOp ci ty tys def tot cdefs) -> length tys
466 Just (TyDecl (DCon tag ar _) _) -> ar
467 Just (TyDecl Ref ty) -> length $ getArgTys ty
468 Just (Operator ty ar op) -> ar
469 Just def -> error $ "unknown arity: " ++ show (n, def)
470 Nothing -> 0 -- no definition, probably local name => can't erase anything
472 -- name for purposes of usage info lookup
474 | Just n' <- viMethod =<< M.lookup n vs = n'
477 used = maybe [] (map fst . usedpos) $ lookupCtxtExact uName (idris_callgraph ist)
478 fst4 (x,_,_,_,_,_) = x
480irTerm top vs env (P _ n _) = return $ LV (Glob n)
481irTerm top vs env (V i)
482 | i >= 0 && i < length env = return $ LV (Glob (env!!i))
483 | otherwise = ifail $ "bad de bruijn index: " ++ show i
485irTerm top vs env (Bind n (Lam _ _) sc) = LLam [n'] <$> irTerm top vs (n':env) sc
487 n' = uniqueName n env
489irTerm top vs env (Bind n (Let _ v) sc)
490 = LLet n <$> irTerm top vs env v <*> irTerm top vs (n : env) sc
492irTerm top vs env (Bind _ _ _) = return $ LNothing
494irTerm top vs env (Proj t (-1)) = do
495 t' <- irTerm top vs env t
496 return $ LOp (LMinus (ATInt ITBig))
499irTerm top vs env (Proj t i) = LProj <$> irTerm top vs env t <*> pure i
500irTerm top vs env (Constant TheWorld) = return LNothing
501irTerm top vs env (Constant c) = return (LConst c)
502irTerm top vs env (TType _) = return LNothing
503irTerm top vs env Erased = return LNothing
504irTerm top vs env Impossible = return LNothing
506doForeign :: Vars -> [Name] -> [Term] -> Idris LExp
507doForeign vs env (ret : fname : world : args)
508 = do args' <- mapM splitArg args
509 let fname' = toFDesc fname
510 let ret' = toFDesc ret
511 return $ LForeign ret' fname' args'
513 splitArg tm | (_, [_,_,l,r]) <- unApply tm -- pair, two implicits
514 = do let l' = toFDesc l
515 r' <- irTerm (sMN 0 "__foreignCall") vs env r
517 splitArg _ = ifail "Badly formed foreign function call"
519 toFDesc (Constant (Str str)) = FStr str
521 | (P _ n _, []) <- unApply tm = FCon (deNS n)
522 | (P _ n _, as) <- unApply tm = FApp (deNS n) (map toFDesc as)
527doForeign vs env xs = ifail "Badly formed foreign function call"
529irTree :: Name -> [Name] -> SC -> Idris LExp
530irTree top args tree = do
531 logCodeGen 3 $ "Compiling " ++ show args ++ "\n" ++ show tree
532 LLam args <$> irSC top M.empty tree
534irSC :: Name -> Vars -> SC -> Idris LExp
535irSC top vs (STerm t) = irTerm top vs [] t
536irSC top vs (UnmatchedCase str) = return $ LError str
538irSC top vs (ProjCase tm alts) = do
539 tm' <- irTerm top vs [] tm
540 alts' <- mapM (irAlt top vs tm') alts
541 return $ LCase Shared tm' alts'
543-- Transform matching on Delay to applications of Force.
544irSC top vs (Case up n [ConCase (UN delay) i [_, _, n'] sc])
545 | delay == txt "Delay"
546 = do sc' <- irSC top vs sc -- mkForce n' n sc
547 return $ lsubst n' (LForce (LV (Glob n))) sc'
549-- There are two transformations in this case:
551-- 1. Newtype-case elimination:
553-- wrap({e1}) -> P({e1}) ==> P({e0})
555-- This is important because newtyped constructors are compiled away entirely
556-- and we need to do that everywhere.
558-- 2. Unused-case elimination (only valid for singleton branches):
560-- C(x,y) -> P[... x,y not used ...]
562-- This is important for runtime because sometimes we case on irrelevant data:
564-- In the example above, {e0} will most probably have been erased
565-- so this vain projection would make the resulting program segfault
566-- because the code generator still emits a PROJECT(...) G-machine instruction.
568-- Hence, we check whether the variables are used at all
569-- and erase the casesplit if they are not.
571irSC top vs (Case up n [alt]) = do
572 replacement <- case alt of
573 ConCase cn a ns sc -> do
574 detag <- fgetState (opt_detaggable . ist_optimisation cn)
575 used <- map fst <$> fgetState (cg_usedpos . ist_callgraph cn)
576 if detag && length used == 1
577 then return . Just $ substSC (ns !! head used) n sc
582 Just sc -> irSC top vs sc
584 alt' <- irAlt top vs (LV (Glob n)) alt
585 return $ case namesBoundIn alt' `usedIn` subexpr alt' of
586 [] -> subexpr alt' -- strip the unused top-most case
587 _ -> LCase up (LV (Glob n)) [alt']
589 namesBoundIn :: LAlt -> [Name]
590 namesBoundIn (LConCase cn i ns sc) = ns
591 namesBoundIn (LConstCase c sc) = []
592 namesBoundIn (LDefaultCase sc) = []
594 subexpr :: LAlt -> LExp
595 subexpr (LConCase _ _ _ e) = e
596 subexpr (LConstCase _ e) = e
597 subexpr (LDefaultCase e) = e
599-- FIXME: When we have a non-singleton case-tree of the form
605-- and C is detaggable (the only constructor of the family), we can be sure
606-- that the first branch will be always taken -- so we add special handling
607-- to remove the dead default branch.
609-- If we don't do so and C is newtype-optimisable, we will miss this newtype
610-- transformation and the resulting code will probably segfault.
612-- This work-around is not entirely optimal; the best approach would be
613-- to ensure that such case trees don't arise in the first place.
615irSC top vs (Case up n alts@[ConCase cn a ns sc, DefaultCase sc']) = do
616 detag <- fgetState (opt_detaggable . ist_optimisation cn)
618 then irSC top vs (Case up n [ConCase cn a ns sc])
619 else LCase up (LV (Glob n)) <$> mapM (irAlt top vs (LV (Glob n))) alts
621irSC top vs sc@(Case up n alts) = do
622 -- check that neither alternative needs the newtype optimisation,
624 goneWrong <- or <$> mapM isDetaggable alts
626 $ ifail ("irSC: non-trivial case-match on detaggable data: " ++ show sc)
629 LCase up (LV (Glob n)) <$> mapM (irAlt top vs (LV (Glob n))) alts
631 isDetaggable (ConCase cn _ _ _) = fgetState $ opt_detaggable . ist_optimisation cn
632 isDetaggable _ = return False
634irSC top vs ImpossibleCase = return LNothing
636irAlt :: Name -> Vars -> LExp -> CaseAlt -> Idris LAlt
638-- this leaves out all unused arguments of the constructor
639irAlt top vs _ (ConCase n t args sc) = do
640 used <- map fst <$> fgetState (cg_usedpos . ist_callgraph n)
641 let usedArgs = [a | (i,a) <- zip [0..] args, i `elem` used]
642 LConCase (-1) n usedArgs <$> irSC top (methodVars `M.union` vs) sc
644 methodVars = case n of
645 SN (ImplementationCtorN interfaceName)
646 -> M.fromList [(v, VI
647 { viMethod = Just $ mkFieldName n i
648 }) | (v,i) <- zip args [0..]]
650 -> M.empty -- not an implementation constructor
652irAlt top vs _ (ConstCase x rhs)
653 | matchable x = LConstCase x <$> irSC top vs rhs
654 | matchableTy x = LDefaultCase <$> irSC top vs rhs
656 matchable (I _) = True
657 matchable (BI _) = True
658 matchable (Ch _) = True
659 matchable (Str _) = True
660 matchable (B8 _) = True
661 matchable (B16 _) = True
662 matchable (B32 _) = True
663 matchable (B64 _) = True
666 matchableTy (AType (ATInt ITNative)) = True
667 matchableTy (AType (ATInt ITBig)) = True
668 matchableTy (AType (ATInt ITChar)) = True
669 matchableTy StrType = True
671 matchableTy (AType (ATInt (ITFixed IT8))) = True
672 matchableTy (AType (ATInt (ITFixed IT16))) = True
673 matchableTy (AType (ATInt (ITFixed IT32))) = True
674 matchableTy (AType (ATInt (ITFixed IT64))) = True
676 matchableTy _ = False
678irAlt top vs tm (SucCase n rhs) = do
679 rhs' <- irSC top vs rhs
680 return $ LDefaultCase (LLet n (LOp (LMinus (ATInt ITBig))
682 LConst (BI 1)]) rhs')
684irAlt top vs _ (ConstCase c rhs)
685 = ifail $ "Can't match on (" ++ show c ++ ")"
687irAlt top vs _ (DefaultCase rhs)
688 = LDefaultCase <$> irSC top vs rhs
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list