├── .gitignore ├── .travis.yml ├── ChangeLog.md ├── LICENSE ├── README.md ├── Setup.hs ├── docs ├── absint.html ├── baseir.html ├── graph.html ├── index.html ├── ir.html ├── irinterpreter.html ├── language.html ├── main.html ├── mipsasm.html ├── mipsinterpreter.html ├── orderedmap.html ├── pandoc.css ├── parser.html ├── prettyutils.html ├── programtoir.html ├── scev.html ├── transformconstantfolding.html ├── transformirtomips.html ├── transformmem2reg.html └── transformregisterallocate.html ├── imperative-compiler.cabal ├── make-docs.py ├── pandoc.css ├── programs ├── direct-int-if.c ├── direct-int-return.c ├── force-spill.c ├── if.c ├── nontrivial-dom-frontier.c ├── simple-store.c ├── store-load.c ├── test-constant-folding.c ├── while-2-nest.c └── while.c ├── reading ├── STOKE-superoptimizer.pdf └── stochastic-program-optimization.pdf ├── src ├── Absint.lhs ├── BaseIR.lhs ├── Graph.lhs ├── IR.lhs ├── IRInterpreter.lhs ├── ISL │ ├── Native.hs │ ├── Native │ │ ├── C2Hs.chs │ │ ├── Context.hs │ │ └── Types.chs │ └── Types.hs ├── Index.lhs ├── Language.lhs ├── MIPSAsm.lhs ├── MIPSInterpreter.lhs ├── Main.lhs ├── OrderedMap.lhs ├── Parser.lhs ├── PrettyUtils.lhs ├── ProgramToIR.lhs ├── SCEV.lhs ├── TransformConstantFolding.lhs ├── TransformIRToMIPS.lhs ├── TransformMem2Reg.lhs └── TransformRegisterAllocate.lhs ├── stack.yaml └── test └── Spec.hs /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | dist-* 3 | cabal-dev 4 | *.o 5 | *.hi 6 | *.chi 7 | *.chs.h 8 | *.dyn_o 9 | *.dyn_hi 10 | .hpc 11 | .hsenv 12 | .cabal-sandbox/ 13 | cabal.sandbox.config 14 | *.prof 15 | *.aux 16 | *.hp 17 | *.eventlog 18 | .stack-work/ 19 | cabal.project.local 20 | .HTF/ 21 | tags 22 | # emacs ignore 23 | *~ 24 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # This is the simple Travis configuration, which is intended for use 2 | # on applications which do not require cross-platform and 3 | # multiple-GHC-version support. For more information and other 4 | # options, see: 5 | # 6 | # https://docs.haskellstack.org/en/stable/travis_ci/ 7 | # 8 | # Copy these contents into the root directory of your Github project in a file 9 | # named .travis.yml 10 | 11 | # Use new container infrastructure to enable caching 12 | sudo: false 13 | 14 | # Do not choose a language; we provide our own build tools. 15 | language: generic 16 | 17 | # Caching so the next build will be fast too. 18 | cache: 19 | directories: 20 | - $HOME/.stack 21 | 22 | # Ensure necessary system libraries are present 23 | addons: 24 | apt: 25 | packages: 26 | - libgmp-dev 27 | - spim 28 | 29 | before_install: 30 | # Download and unpack the stack executable 31 | - mkdir -p ~/.local/bin 32 | - export PATH=$HOME/.local/bin:$PATH 33 | - travis_retry curl -L https://www.stackage.org/stack/linux-x86_64 | tar xz --wildcards --strip-components=1 -C ~/.local/bin '*/stack' 34 | 35 | install: 36 | # Build dependencies 37 | - stack --no-terminal --install-ghc test --only-dependencies 38 | 39 | script: 40 | # Build the package, its tests, and its docs and run the tests 41 | - stack --no-terminal test --haddock --no-haddock-deps 42 | -------------------------------------------------------------------------------- /ChangeLog.md: -------------------------------------------------------------------------------- 1 | # Revision history for imperative-compiler 2 | 3 | ## 0.1.0.0 -- YYYY-mm-dd 4 | 5 | * First version. Released on an unsuspecting world. 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017, Siddharth Bhat 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of Siddharth Bhat nor the names of other 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A simple end-to-end compiler for an imperative programming language [![Build Status](https://travis-ci.org/bollu/tiny-optimising-compiler.svg?branch=master)](https://travis-ci.org/bollu/tiny-optimising-compiler) 2 | 3 | 4 | Read the docs at [`https://bollu.github.io/tiny-optimising-compiler/`](https://bollu.github.io/tiny-optimising-compiler/) 5 | -------------------------------------------------------------------------------- /Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /docs/graph.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 46 | 47 | 48 | 49 |

50 | Graph 51 |

52 |

In this module, we define a simple graph structure that can be used as:

53 | 58 |

Ideally, we would use some sort of phantom-type mechanism to distinguish between the two, that is Graph Undirected a and Graph Directed a, but oh well :).

59 |
{-# LANGUAGE ViewPatterns #-}
 60 | 
 61 | module Graph where
 62 | import Data.List(nub)
 63 | import Data.Text.Prettyprint.Doc as PP
 64 | import PrettyUtils
 65 | import Data.Maybe (maybeToList)
 66 | import qualified OrderedMap as M
 67 | import qualified Data.Set as S
 68 | 
 69 | -- | Represents a graph with `a` as a vertex ID type
 70 | newtype Graph a = Graph { edges :: [(a, a)] }
 71 | 
 72 | instance Pretty a => Pretty (Graph a) where
 73 |   pretty graph =
 74 |     vcat [pretty "BB graph edges",
 75 |           (vcat . map (indent 4 . pretty) . edges $ graph)]
 76 | 
 77 | -- | returns all edges (H -> T) with a given source H
 78 | getEdgesFromSource :: Eq a => Graph a -> a -> [(a, a)]
 79 | getEdgesFromSource g src = [(src, b) | (a, b) <- edges g, a == src]
 80 | 
 81 | -- | return predecessors of a node
 82 | getPredecessors :: Eq a => Graph a -> a -> [a]
 83 | getPredecessors g bbid = [ src | (src, sink) <- (edges g), sink == bbid]
 84 | 
 85 | -- | Returns the children of an element in a dom tree
 86 | -- | This returns only the immediate children.
 87 | getImmediateChildren :: Eq a => Graph a -> a -> [a]
 88 | getImmediateChildren (Graph edges) a = [dest | (src, dest) <- edges, src==a]
 89 | 
 90 | -- | Return all the vertices of the subgraph
 91 | getAllChildren :: Eq a => Graph a -> a -> [a]
 92 | getAllChildren tree@(Graph edges) a =
 93 |   a:(curChilds >>= (getAllChildren tree)) where
 94 |   curChilds = getImmediateChildren tree a
 95 | 
 96 | -- | Return the set of vertices in DomTree
 97 | vertices :: Eq a => Graph a  -> [a]
 98 | vertices (Graph edges) = nub (map fst edges ++ map snd edges)
 99 | 
100 | -- | Colors are assigned from [1..NGraphColors]
101 | type GraphColor = Int
102 | type NGraphColors = Int
103 | 
104 | _greedyColorGraph :: Ord a => Graph a -- ^ Graph 
105 |                             -> S.Set a -- ^ Set of vertices
106 |                             -> M.OrderedMap a (Maybe GraphColor) -- ^ Mapping from vertices to colors
107 |                             -> NGraphColors -- ^ Total number of graph colors available
108 |                             -> M.OrderedMap a (Maybe GraphColor) -- ^ Final colored graph
109 | _greedyColorGraph _ (null -> True) coloring ncolors = coloring
110 | _greedyColorGraph g vs@(S.elemAt 0 -> v) coloring ncolors  =
111 |     _greedyColorGraph g vs' coloring' ncolors where
112 |         -- adjacent vertices
113 |         adjvs = (getPredecessors g v)
114 | 
115 |         -- colors of adjacent vertices
116 |         adjColors :: [GraphColor]
117 |         adjColors = mconcat $ fmap (\v -> case (v `M.lookup` coloring) of
118 |                                             Just (Just c) -> [c]
119 |                                             _ -> []) adjvs
120 | 
121 |         -- largest color
122 |         largestAdjColor = case adjColors of
123 |                             [] -> 0
124 |                             xs -> maximum xs
125 | 
126 |         -- Leave it uncolored it we can't find a color
127 |         coloring' = if largestAdjColor == ncolors
128 |                 then M.insert v Nothing coloring
129 |                 else M.insert v (Just (largestAdjColor + 1)) coloring
130 | 
131 |         -- remove vertex we currently processed
132 |         vs' = S.deleteAt 0 vs
133 | 
134 | 
135 | -- | Color the graph greedily and return the mapping of colors
136 | greedyColorGraph :: Ord a => NGraphColors -> Graph a -> M.OrderedMap a (Maybe Int)
137 | greedyColorGraph ngraphcolors g =
138 |     _greedyColorGraph g (S.fromList (vertices g))
139 |                       mempty ngraphcolors
140 | 141 | 142 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

13 | Tiny optimising compiler 14 |

15 |

Welcome to the tutorial series that teaches how to write a tiny optimising compiler in haskell!

16 |

Start from:

17 |
    18 |
  1. The source language.

  2. 19 |
  3. The parser for the language.

  4. 20 |
  5. The internal representation.

  6. 21 |
  7. The mem2reg transform that lands us into SSA.

  8. 22 |
  9. The constant folding transform that exploits SSA to "fold away" expressions which can be evaluated at compile time.

  10. 23 |
  11. The register allocation transform which allocates physical registers to the infinite virtual registers of our SSA form.

  12. 24 |
  13. The mipsasm final code generation pass which generates MIPS assembly. 25 |

    26 | Background 27 |

  14. 28 |
29 |

I've wanted to write this for a while: a tiny optimising compiler for a small imperative ish language.

30 |

I want to show off modern compiler ideas, such as:

31 | 37 |

I currently have a parser for the source language, conversion to IR, then to SSA, and a semi-broken MIPS backend.

38 |

39 | Goals 40 |

41 | 46 |

47 | Non goals 48 |

49 |

Shows the correct way of doing a lot of things, in the sense of "engineering". I might pick the slower algorithm to compute a dominator tree, because I wish to emphasize the idea of the dominator tree. When a trade off is presented between simplicity and performance, I will pick simplicity.

50 |

51 | Timeline 52 |

53 | 60 |

At this point, we have a "functioning" compiler. Now, we can extend the compiler or the language. I want to show off optimisations, so I will spend more time implementing optimisations

61 | 69 |

Note that we do not yet have functions in the language! let's add that.

70 | 75 |

If we get here, we can then add polyhedral abilities to the compiler. For this though, we would need to integrate with isl. Someone will need to write haskell bindings :).

76 | 77 | 78 | -------------------------------------------------------------------------------- /docs/language.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 46 | 47 | 48 | 49 |
module Language where
 50 | import Data.Text.Prettyprint.Doc as PP
 51 | 
 52 | newtype Literal = Literal { unLiteral :: String } deriving(Ord, Eq)
 53 | instance Pretty Literal where
 54 |   pretty = pretty . unLiteral
 55 | 
 56 | data BinOp = Plus | Multiply | L | And
 57 | instance Pretty BinOp where
 58 |   pretty Plus = pretty "+"
 59 |   pretty Multiply = pretty "*"
 60 |   pretty L = pretty "<"
 61 |   pretty And = pretty "&&"
 62 | 
 63 | data Expr a = EBinOp a (Expr a) BinOp (Expr a) |
 64 |                   EInt a Int |
 65 |                   ELiteral a Literal
 66 | 
 67 | instance Pretty (Expr a) where
 68 |   pretty (EBinOp _ l op r) = pretty "(" <+> pretty op <+>
 69 |                              pretty l <+> pretty r <+> pretty ")"
 70 |   pretty (EInt _ i) = pretty i
 71 |   pretty (ELiteral _ lit) = pretty lit
 72 | 
 73 | type Expr' = Expr ()
 74 | 
 75 | data Stmt a = If a (Expr a) (Block a) (Block a) |
 76 |               While a (Expr a) (Block a) |
 77 |               Assign a Literal (Expr a) |
 78 |               Define a Literal |
 79 |               Return a (Expr a)
 80 | type Block a = [Stmt a]
 81 | 
 82 | 
 83 | nestDepth :: Int
 84 | nestDepth = 4
 85 | 
 86 | instance Pretty (Stmt a) where
 87 |   pretty (If _ cond then' else') = pretty "if" <+> pretty cond <+>
 88 |                                   PP.braces (nest 4 (pretty then')) <+>
 89 |                                   PP.braces (nest 4 (pretty else'))
 90 | 
 91 |   pretty (While _ cond body) = pretty "while" <+> pretty cond <+> PP.braces (nest 4 (pretty body))
 92 |   pretty (Assign _ lhs rhs) = pretty "assign" <+> pretty lhs <+> pretty ":=" <+> pretty rhs
 93 |   pretty (Define _ lit) = pretty "define" <+> pretty lit
 94 |   pretty (Return _ expr) = pretty "return" <+> pretty expr
 95 | 
 96 | type Stmt' = Stmt ()
 97 | 
 98 | 
 99 | newtype Program a = Program [Stmt a]
100 | type Program' = Program ()
101 | 
102 | instance Pretty (Program a) where
103 |   pretty (Program stmts) = vcat (map pretty stmts)
104 | 105 | 106 | -------------------------------------------------------------------------------- /docs/main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 46 | 47 | 48 | 49 |
module Main where
 50 | import Parser
 51 | import qualified IR as IR
 52 | import IRInterpreter
 53 | import qualified Language as Lang
 54 | import Data.Text.Prettyprint.Doc
 55 | import ProgramToIR
 56 | import System.IO
 57 | import System.Exit (exitSuccess)
 58 | import System.Environment
 59 | import TransformMem2Reg
 60 | import TransformConstantFolding
 61 | import SCEV
 62 | import TransformIRToMIPS
 63 | import PrettyUtils
 64 | import MIPSInterpreter
 65 | import TransformRegisterAllocate
 66 | import qualified OrderedMap as M
 67 | import qualified MIPSAsm as MIPS
 68 | 
 69 | 
 70 | compileProgram :: Lang.Program a ->  IR.IRProgram
 71 | compileProgram p = undefined
 72 | 
 73 | pipeline :: [(String, IR.IRProgram -> IR.IRProgram)]
 74 | pipeline = [("original", id),
 75 |             ("mem2reg", transformMem2Reg),
 76 |             ("constant fold", transformConstantFold)]
 77 | 
 78 | runPasses :: [(String, IR.IRProgram -> IR.IRProgram)] -- ^ Pass pipeline
 79 |     -> IR.IRProgram -- ^ Current program 
 80 |     -> IO IR.IRProgram -- ^ Final program
 81 | runPasses [] p = return p
 82 | runPasses ((name, pass):passes) p = do
 83 |     let p' = pass p
 84 |     putStrLn . docToString $ pretty "#  Running pass " <+> 
 85 |                              pretty name
 86 |     putStrLn . prettyableToString $ p'
 87 |     putStrLn . docToString $ pretty "- Value:" <+> pretty (runProgram p')
 88 |     runPasses passes p'
 89 | 
 90 | 
 91 | 
 92 | main :: IO ()
 93 | main = do
 94 |      args <- getArgs
 95 |      input <- readFile (args !! 0)
 96 |      case parseProgram input of
 97 |         Left err -> putStrLn err
 98 |         Right program -> do
 99 |             putStrLn "*** Program:"
100 |             putStrLn . prettyableToString $  program
101 | 
102 |             let irprogram = programToIR program
103 |             finalProgram <- runPasses pipeline irprogram
104 | 
105 |             putStrLn "*** Loops ***"
106 |             let loops = detectLoops finalProgram
107 |             putStrLn . docToString . vcat . (fmap pretty) $ loops
108 | 
109 |             exitSuccess
110 | 
111 |             putStrLn "*** MIPS assembly *** "
112 |             let mipsasm = transformRegisterAllocate . transformIRToMIPS $ finalProgram
113 |             putStrLn . docToString . MIPS.printMIPSAsm $ mipsasm
114 |             -- putStrLn . docToString . MIPS.unASMDoc . MIPS.generateASM $  finalProgram
115 | 
116 |             putStrLn "*** Output from SPIM *** "
117 |             mProgramOutput <- interpretMIPSWithSPIM mipsasm
118 |             case mProgramOutput of
119 |                 Left err -> putStrLn . docToString $ err
120 |                 Right val -> putStrLn . docToString $ (pretty "final value: " <+> pretty val)
121 | 122 | 123 | -------------------------------------------------------------------------------- /docs/mipsinterpreter.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 46 | 47 | 48 | 49 |
module MIPSInterpreter (
 50 | interpretMIPSWithSPIM) where
 51 | import Data.Text.Prettyprint.Doc
 52 | import System.IO(hPutStr, hFlush, Handle, FilePath)
 53 | import System.IO.Temp(withSystemTempFile)
 54 | import System.Process(readProcessWithExitCode)
 55 | import System.Exit(ExitCode(..))
 56 | import MIPSAsm
 57 | import Text.Read(readMaybe)
 58 | import PrettyUtils
 59 | import Safe(lastMay)
 60 | type ErrorDoc = Doc ()
 61 | 
 62 | -- | Allow for interpreters that try to access state.
 63 | interpretMIPSWithSPIM :: MProgram -> IO (Either ErrorDoc Int)
 64 | interpretMIPSWithSPIM p = 
 65 |     withSystemTempFile "mipsfile" (\filepath handle -> do
 66 |         _writeMIPSIntoFile p handle
 67 |         _runMIPSFromFileWithSPIM filepath)
 68 | 
 69 | 
 70 | -- | Write MIPS code into the file owned by Handle
 71 | _writeMIPSIntoFile :: MProgram -> Handle -> IO ()
 72 | _writeMIPSIntoFile program handle = do
 73 |     hPutStr handle (docToString . printMIPSAsm $ program)
 74 |     hFlush handle
 75 | 
 76 | 
 77 | -- | Run MIPS code through SPIM with the file.
 78 | _runMIPSFromFileWithSPIM :: FilePath -> IO (Either ErrorDoc Int)
 79 | _runMIPSFromFileWithSPIM path = do
 80 |     let stdin = ""
 81 | 
 82 |     (exitcode, stdout, stderr) <- readProcessWithExitCode "spim" ["-f", path] stdin
 83 |     case exitcode of 
 84 |         ExitFailure i ->
 85 |             return $ Left $ 
 86 |                 vcat [pretty "exited with failure code: " <+> pretty i,
 87 |                         pretty "stdout:",
 88 |                         pretty stdout,
 89 |                         pretty "stderr: ",
 90 |                         pretty stderr]
 91 |         ExitSuccess ->
 92 |             case lastMay (lines stdout) >>= readMaybe of
 93 |                 Just val -> return $ Right val
 94 |                 Nothing -> return $ Left $
 95 |                                 vcat [pretty "program returned non-integer output:",
 96 |                                       pretty "stderr:",
 97 |                                       pretty stderr,
 98 |                                       pretty "stdout:",
 99 |                                       pretty stdout]
100 | 101 | 102 | -------------------------------------------------------------------------------- /docs/pandoc.css: -------------------------------------------------------------------------------- 1 | body { 2 | -ms-text-size-adjust: 100%; 3 | -webkit-text-size-adjust: 100%; 4 | line-height: 1.5; 5 | color: #333; 6 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 7 | font-size: 20px; 8 | line-height: 1.8; 9 | word-wrap: break-word; 10 | margin-left: 20%; 11 | margin-right: 20%; 12 | } 13 | 14 | .pl-c { 15 | color: #969896; 16 | } 17 | 18 | .pl-c1, 19 | .pl-s .pl-v { 20 | color: #0086b3; 21 | } 22 | 23 | .pl-e, 24 | .pl-en { 25 | color: #795da3; 26 | } 27 | 28 | .pl-smi, 29 | .pl-s .pl-s1 { 30 | color: #333; 31 | } 32 | 33 | .pl-ent { 34 | color: #63a35c; 35 | } 36 | 37 | .pl-k { 38 | color: #a71d5d; 39 | } 40 | 41 | .pl-s, 42 | .pl-pds, 43 | .pl-s .pl-pse .pl-s1, 44 | .pl-sr, 45 | .pl-sr .pl-cce, 46 | .pl-sr .pl-sre, 47 | .pl-sr .pl-sra { 48 | color: #183691; 49 | } 50 | 51 | .pl-v { 52 | color: #ed6a43; 53 | } 54 | 55 | .pl-id { 56 | color: #b52a1d; 57 | } 58 | 59 | .pl-ii { 60 | color: #f8f8f8; 61 | background-color: #b52a1d; 62 | } 63 | 64 | .pl-sr .pl-cce { 65 | font-weight: bold; 66 | color: #63a35c; 67 | } 68 | 69 | .pl-ml { 70 | color: #693a17; 71 | } 72 | 73 | .pl-mh, 74 | .pl-mh .pl-en, 75 | .pl-ms { 76 | font-weight: bold; 77 | color: #1d3e81; 78 | } 79 | 80 | .pl-mq { 81 | color: #008080; 82 | } 83 | 84 | .pl-mi { 85 | font-style: italic; 86 | color: #333; 87 | } 88 | 89 | .pl-mb { 90 | font-weight: bold; 91 | color: #333; 92 | } 93 | 94 | .pl-md { 95 | color: #bd2c00; 96 | background-color: #ffecec; 97 | } 98 | 99 | .pl-mi1 { 100 | color: #55a532; 101 | background-color: #eaffea; 102 | } 103 | 104 | .pl-mdr { 105 | font-weight: bold; 106 | color: #795da3; 107 | } 108 | 109 | .pl-mo { 110 | color: #1d3e81; 111 | } 112 | 113 | .octicon { 114 | display: inline-block; 115 | vertical-align: text-top; 116 | fill: currentColor; 117 | } 118 | 119 | a { 120 | background-color: transparent; 121 | -webkit-text-decoration-skip: objects; 122 | } 123 | 124 | a:active, 125 | a:hover { 126 | outline-width: 0; 127 | } 128 | 129 | strong { 130 | font-weight: inherit; 131 | } 132 | 133 | strong { 134 | font-weight: bolder; 135 | } 136 | 137 | h1 { 138 | font-size: 2em; 139 | margin: 0.67em 0; 140 | } 141 | 142 | img { 143 | border-style: none; 144 | } 145 | 146 | svg:not(:root) { 147 | overflow: hidden; 148 | } 149 | 150 | code, 151 | kbd, 152 | pre { 153 | font-family: monospace, monospace; 154 | font-size: 1em; 155 | } 156 | 157 | hr { 158 | box-sizing: content-box; 159 | height: 0; 160 | overflow: visible; 161 | } 162 | 163 | input { 164 | font: inherit; 165 | margin: 0; 166 | } 167 | 168 | input { 169 | overflow: visible; 170 | } 171 | 172 | [type="checkbox"] { 173 | box-sizing: border-box; 174 | padding: 0; 175 | } 176 | 177 | * { 178 | box-sizing: border-box; 179 | } 180 | 181 | input { 182 | font-family: inherit; 183 | font-size: inherit; 184 | line-height: inherit; 185 | } 186 | 187 | a { 188 | color: #4078c0; 189 | text-decoration: none; 190 | } 191 | 192 | a:hover, 193 | a:active { 194 | text-decoration: underline; 195 | } 196 | 197 | strong { 198 | font-weight: 600; 199 | } 200 | 201 | hr { 202 | height: 0; 203 | margin: 15px 0; 204 | overflow: hidden; 205 | background: transparent; 206 | border: 0; 207 | border-bottom: 1px solid #ddd; 208 | } 209 | 210 | hr::before { 211 | display: table; 212 | content: ""; 213 | } 214 | 215 | hr::after { 216 | display: table; 217 | clear: both; 218 | content: ""; 219 | } 220 | 221 | table { 222 | border-spacing: 0; 223 | border-collapse: collapse; 224 | } 225 | 226 | td, 227 | th { 228 | padding: 0; 229 | } 230 | 231 | h1, 232 | h2, 233 | h3, 234 | h4, 235 | h5, 236 | h6 { 237 | margin-top: 0; 238 | margin-bottom: 0; 239 | } 240 | 241 | h1 { 242 | font-size: 32px; 243 | font-weight: 600; 244 | } 245 | 246 | h2 { 247 | font-size: 24px; 248 | font-weight: 600; 249 | } 250 | 251 | h3 { 252 | font-size: 20px; 253 | font-weight: 600; 254 | } 255 | 256 | h4 { 257 | font-size: 16px; 258 | font-weight: 600; 259 | } 260 | 261 | h5 { 262 | font-size: 14px; 263 | font-weight: 600; 264 | } 265 | 266 | h6 { 267 | font-size: 12px; 268 | font-weight: 600; 269 | } 270 | 271 | p { 272 | margin-top: 0; 273 | margin-bottom: 10px; 274 | } 275 | 276 | blockquote { 277 | margin: 0; 278 | } 279 | 280 | ul, 281 | ol { 282 | padding-left: 0; 283 | margin-top: 0; 284 | margin-bottom: 0; 285 | } 286 | 287 | ol ol, 288 | ul ol { 289 | list-style-type: lower-roman; 290 | } 291 | 292 | ul ul ol, 293 | ul ol ol, 294 | ol ul ol, 295 | ol ol ol { 296 | list-style-type: lower-alpha; 297 | } 298 | 299 | dd { 300 | margin-left: 0; 301 | } 302 | 303 | code { 304 | font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; 305 | font-size: 12px; 306 | } 307 | 308 | pre { 309 | margin-top: 0; 310 | margin-bottom: 0; 311 | font: 12px Consolas, "Liberation Mono", Menlo, Courier, monospace; 312 | } 313 | 314 | .octicon { 315 | vertical-align: text-bottom; 316 | } 317 | 318 | input { 319 | -webkit-font-feature-settings: "liga" 0; 320 | font-feature-settings: "liga" 0; 321 | } 322 | 323 | .markdown-body::before { 324 | display: table; 325 | content: ""; 326 | } 327 | 328 | .markdown-body::after { 329 | display: table; 330 | clear: both; 331 | content: ""; 332 | } 333 | 334 | .markdown-body>*:first-child { 335 | margin-top: 0 !important; 336 | } 337 | 338 | .markdown-body>*:last-child { 339 | margin-bottom: 0 !important; 340 | } 341 | 342 | a:not([href]) { 343 | color: inherit; 344 | text-decoration: none; 345 | } 346 | 347 | .anchor { 348 | float: left; 349 | padding-right: 4px; 350 | margin-left: -20px; 351 | line-height: 1; 352 | } 353 | 354 | .anchor:focus { 355 | outline: none; 356 | } 357 | 358 | p, 359 | blockquote, 360 | ul, 361 | ol, 362 | dl, 363 | table, 364 | pre { 365 | margin-top: 0; 366 | margin-bottom: 16px; 367 | } 368 | 369 | hr { 370 | height: 0.25em; 371 | padding: 0; 372 | margin: 24px 0; 373 | background-color: #e7e7e7; 374 | border: 0; 375 | } 376 | 377 | blockquote { 378 | padding: 0 1em; 379 | color: #777; 380 | border-left: 0.25em solid #ddd; 381 | } 382 | 383 | blockquote>:first-child { 384 | margin-top: 0; 385 | } 386 | 387 | blockquote>:last-child { 388 | margin-bottom: 0; 389 | } 390 | 391 | kbd { 392 | display: inline-block; 393 | padding: 3px 5px; 394 | font-size: 11px; 395 | line-height: 10px; 396 | color: #555; 397 | vertical-align: middle; 398 | background-color: #fcfcfc; 399 | border: solid 1px #ccc; 400 | border-bottom-color: #bbb; 401 | border-radius: 3px; 402 | box-shadow: inset 0 -1px 0 #bbb; 403 | } 404 | 405 | h1, 406 | h2, 407 | h3, 408 | h4, 409 | h5, 410 | h6 { 411 | margin-top: 24px; 412 | margin-bottom: 16px; 413 | font-weight: 600; 414 | line-height: 1.25; 415 | } 416 | 417 | h1 .octicon-link, 418 | h2 .octicon-link, 419 | h3 .octicon-link, 420 | h4 .octicon-link, 421 | h5 .octicon-link, 422 | h6 .octicon-link { 423 | color: #000; 424 | vertical-align: middle; 425 | visibility: hidden; 426 | } 427 | 428 | h1:hover .anchor, 429 | h2:hover .anchor, 430 | h3:hover .anchor, 431 | h4:hover .anchor, 432 | h5:hover .anchor, 433 | h6:hover .anchor { 434 | text-decoration: none; 435 | } 436 | 437 | h1:hover .anchor .octicon-link, 438 | h2:hover .anchor .octicon-link, 439 | h3:hover .anchor .octicon-link, 440 | h4:hover .anchor .octicon-link, 441 | h5:hover .anchor .octicon-link, 442 | h6:hover .anchor .octicon-link { 443 | visibility: visible; 444 | } 445 | 446 | h1 { 447 | padding-bottom: 0.3em; 448 | font-size: 2em; 449 | border-bottom: 1px solid #eee; 450 | } 451 | 452 | h2 { 453 | padding-bottom: 0.3em; 454 | font-size: 1.5em; 455 | border-bottom: 1px solid #eee; 456 | } 457 | 458 | h3 { 459 | font-size: 1.25em; 460 | } 461 | 462 | h4 { 463 | font-size: 1em; 464 | } 465 | 466 | h5 { 467 | font-size: 0.875em; 468 | } 469 | 470 | h6 { 471 | font-size: 0.85em; 472 | color: #777; 473 | } 474 | 475 | ul, 476 | ol { 477 | padding-left: 2em; 478 | } 479 | 480 | ul ul, 481 | ul ol, 482 | ol ol, 483 | ol ul { 484 | margin-top: 0; 485 | margin-bottom: 0; 486 | } 487 | 488 | li>p { 489 | margin-top: 16px; 490 | } 491 | 492 | li+li { 493 | margin-top: 0.25em; 494 | } 495 | 496 | dl { 497 | padding: 0; 498 | } 499 | 500 | dl dt { 501 | padding: 0; 502 | margin-top: 16px; 503 | font-size: 1em; 504 | font-style: italic; 505 | font-weight: bold; 506 | } 507 | 508 | dl dd { 509 | padding: 0 16px; 510 | margin-bottom: 16px; 511 | } 512 | 513 | table { 514 | display: block; 515 | width: 100%; 516 | overflow: auto; 517 | } 518 | 519 | table th { 520 | font-weight: bold; 521 | } 522 | 523 | table th, 524 | table td { 525 | padding: 6px 13px; 526 | border: 1px solid #ddd; 527 | } 528 | 529 | table tr { 530 | background-color: #fff; 531 | border-top: 1px solid #ccc; 532 | } 533 | 534 | table tr:nth-child(2n) { 535 | background-color: #f8f8f8; 536 | } 537 | 538 | img { 539 | max-width: 100%; 540 | box-sizing: content-box; 541 | background-color: #fff; 542 | } 543 | 544 | code { 545 | padding: 0; 546 | padding-top: 0.2em; 547 | padding-bottom: 0.2em; 548 | margin: 0; 549 | font-size: 85%; 550 | background-color: rgba(0, 0, 0, 0.04); 551 | border-radius: 3px; 552 | } 553 | 554 | code::before, 555 | code::after { 556 | letter-spacing: -0.2em; 557 | content: "\00a0"; 558 | } 559 | 560 | pre { 561 | word-wrap: normal; 562 | } 563 | 564 | pre>code { 565 | padding: 0; 566 | margin: 0; 567 | font-size: 100%; 568 | word-break: normal; 569 | white-space: pre; 570 | background: transparent; 571 | border: 0; 572 | } 573 | 574 | .highlight { 575 | margin-bottom: 16px; 576 | } 577 | 578 | .highlight pre { 579 | margin-bottom: 0; 580 | word-break: normal; 581 | } 582 | 583 | .highlight pre, 584 | pre { 585 | padding: 16px; 586 | overflow: auto; 587 | font-size: 85%; 588 | line-height: 1.45; 589 | background-color: #f7f7f7; 590 | border-radius: 3px; 591 | } 592 | 593 | pre code { 594 | display: inline; 595 | max-width: auto; 596 | padding: 0; 597 | margin: 0; 598 | overflow: visible; 599 | line-height: inherit; 600 | word-wrap: normal; 601 | background-color: transparent; 602 | border: 0; 603 | } 604 | 605 | pre code::before, 606 | pre code::after { 607 | content: normal; 608 | } 609 | 610 | .pl-0 { 611 | padding-left: 0 !important; 612 | } 613 | 614 | .pl-1 { 615 | padding-left: 3px !important; 616 | } 617 | 618 | .pl-2 { 619 | padding-left: 6px !important; 620 | } 621 | 622 | .pl-3 { 623 | padding-left: 12px !important; 624 | } 625 | 626 | .pl-4 { 627 | padding-left: 24px !important; 628 | } 629 | 630 | .pl-5 { 631 | padding-left: 36px !important; 632 | } 633 | 634 | .pl-6 { 635 | padding-left: 48px !important; 636 | } 637 | 638 | .full-commit .btn-outline:not(:disabled):hover { 639 | color: #4078c0; 640 | border: 1px solid #4078c0; 641 | } 642 | 643 | kbd { 644 | display: inline-block; 645 | padding: 3px 5px; 646 | font: 11px Consolas, "Liberation Mono", Menlo, Courier, monospace; 647 | line-height: 10px; 648 | color: #555; 649 | vertical-align: middle; 650 | background-color: #fcfcfc; 651 | border: solid 1px #ccc; 652 | border-bottom-color: #bbb; 653 | border-radius: 3px; 654 | box-shadow: inset 0 -1px 0 #bbb; 655 | } 656 | 657 | :checked+.radio-label { 658 | position: relative; 659 | z-index: 1; 660 | border-color: #4078c0; 661 | } 662 | 663 | .task-list-item { 664 | list-style-type: none; 665 | } 666 | 667 | .task-list-item+.task-list-item { 668 | margin-top: 3px; 669 | } 670 | 671 | .task-list-item input { 672 | margin: 0 0.2em 0.25em -1.6em; 673 | vertical-align: middle; 674 | } 675 | 676 | hr { 677 | border-bottom-color: #eee; 678 | } 679 | 680 | 681 | /** Theming **/ 682 | 683 | body { 684 | color: #333; 685 | background: white; 686 | padding: 0 25px; 687 | } 688 | 689 | .vscode-light, 690 | .vscode-light pre code { 691 | color: #333; 692 | } 693 | 694 | .vscode-dark, 695 | .vscode-dark pre code { 696 | color: #333; 697 | } 698 | 699 | .vscode-high-contrast, 700 | .vscode-high-contrast pre code { 701 | color: #333; 702 | } 703 | 704 | .vscode-light code { 705 | color: #333; 706 | } 707 | 708 | .vscode-dark code { 709 | color: #333; 710 | } 711 | 712 | .vscode-light pre:not(.hljs), 713 | .vscode-light code>div { 714 | background-color: #F6F8FA; 715 | } 716 | 717 | .vscode-dark pre:not(.hljs), 718 | .vscode-dark code>div { 719 | background-color: #F6F8FA; 720 | } 721 | 722 | .vscode-high-contrast pre:not(.hljs), 723 | .vscode-high-contrast code>div { 724 | background-color: #F6F8FA; 725 | } 726 | 727 | .vscode-high-contrast h1 { 728 | border-color: transparent; 729 | } 730 | 731 | .vscode-light table>thead>tr>th { 732 | border-color: #EAECEF; 733 | } 734 | 735 | .vscode-dark table>thead>tr>th { 736 | border-color: #EAECEF; 737 | } 738 | 739 | .vscode-light h1, 740 | .vscode-light hr, 741 | .vscode-light table>tbody>tr+tr>td { 742 | border-color: #EAECEF; 743 | } 744 | 745 | .vscode-dark h1, 746 | .vscode-dark hr, 747 | .vscode-dark table>tbody>tr+tr>td { 748 | border-color: #EAECEF; 749 | } 750 | 751 | .vscode-light blockquote, 752 | .vscode-dark blockquote { 753 | padding: 0 1em; 754 | color: #777; 755 | border-left: 0.25em solid #ddd; 756 | background: transparent; 757 | } 758 | 759 | .vscode-high-contrast blockquote { 760 | padding: 0 1em; 761 | color: #777; 762 | border-left: 0.25em solid #ddd; 763 | background: transparent; 764 | } 765 | -------------------------------------------------------------------------------- /docs/prettyutils.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 46 | 47 | 48 | 49 |
module PrettyUtils where
50 | import Data.Text.Prettyprint.Doc.Render.Text
51 | import Data.Text.Prettyprint.Doc
52 | import qualified Data.Text.Lazy as L
53 | 
54 | docToText :: Doc ann -> L.Text
55 | docToText doc = renderLazy (layoutPretty defaultLayoutOptions doc)
56 | 
57 | docToString :: Doc ann -> String
58 | docToString = L.unpack . docToText
59 | 
60 | prettyableToText :: Pretty a => a -> L.Text
61 | prettyableToText a = docToText (pretty a)
62 | 
63 | prettyableToString :: Pretty a => a -> String
64 | prettyableToString  a = docToString (pretty a)
65 | 66 | 67 | -------------------------------------------------------------------------------- /docs/scev.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 46 | 47 | 48 | 49 |

50 | SCEV, or, how do we analyze loops? 51 |

52 |

53 | Equivalent LLVM passes 54 |

55 | 58 |

59 | Introduction 60 |

61 |

SCEV is an analysis which allows us to understand recurrences across loops.

62 |

63 | References 64 |

65 |

http://www.csd.uwo.ca/moreno/CS447/Lectures/CodeOptimization.html/node6.html http://web.cs.wpi.edu/kal/PLT/PLT8.6.4.html

66 |
{-# LANGUAGE TupleSections #-}
 67 | {-# LANGUAGE RecordWildCards #-}
 68 | {-# LANGUAGE ScopedTypeVariables #-}
 69 | {-# LANGUAGE DeriveFunctor #-}
 70 | 
 71 | module SCEV(analyzeSCEV, detectLoops) where
 72 | 
 73 | import IR
 74 | import BaseIR
 75 | import Data.Tree
 76 | import qualified Data.Set as S
 77 | import qualified OrderedMap as M
 78 | import Data.Text.Prettyprint.Doc as PP
 79 | import PrettyUtils
 80 | import Control.Monad.Reader
 81 | import Data.Traversable
 82 | import qualified Data.Monoid as Monoid
 83 | import qualified Data.Set as S
 84 | import qualified Data.List.NonEmpty as NE
 85 | import Control.Monad.State.Strict
 86 | import TransformMem2Reg
 87 | import Graph
 88 | 
 89 | data Loop = Loop {
 90 |     loopHeader :: IRBBId,
 91 |     loopBackEdges :: [(IRBBId, IRBBId)],
 92 |     loopLatches :: [IRBBId]
 93 | } 
 94 | 
 95 | instance Pretty Loop where
 96 |   pretty Loop{..} = 
 97 |       vsep [pheader, nest 4 platch, nest 4 pbackedges] where
 98 |       pheader = (pretty "header:") <+> (pretty loopHeader)
 99 |       platch =  vcat [pretty  "latches:",
100 |                       nest 4 (vcat (fmap (pretty) loopLatches))]
101 |       pbackedges = vcat [pretty "backedges:",
102 |                          nest 4 $ vcat (fmap pretty loopBackEdges)]
103 | 
104 | 
105 | -- | Returns if the given edge is a back-edge
106 | -- | An edge (Start -> End) is a back edge if End dominates Start
107 | -- | Perform this operation by checking if End belongs to Start's Domset.
108 | isBackEdge :: BBIdToDomSet -> (IRBBId, IRBBId)  -> Bool
109 | isBackEdge bbIdToDomSet (start, end) = end  `S.member` (bbIdToDomSet M.! start)
110 | 
111 | 
112 | 
113 | _detectLoopsRec :: M.OrderedMap IRBBId IRBB  -- ^ Basic Blocks in program
114 |                   -> BBIdToDomSet -- ^ Mapping from basic blocks to nodes
115 |                                   --   that dominate it
116 |                   -> DomTree  -- ^ Domtree of program
117 |                   -> CFG -- ^ CFG of program
118 |                   -> IRBBId  -- ^Current Basic block being inspected
119 |                   -> [Loop] -- ^ List of loops
120 | _detectLoopsRec bbmap bbIdToDomSet domtree cfg curbbid = 
121 |     curloop ++ (domtreechildren  >>= _detectLoopsRec bbmap bbIdToDomSet domtree cfg)
122 |     where
123 |     domtreechildren :: [IRBBId]
124 |     domtreechildren = getImmediateChildren domtree curbbid
125 | 
126 |     -- | next nodes in the CFG from the current node
127 |     cfgnext :: [(IRBBId, IRBBId)]
128 |     cfgnext = getEdgesFromSource cfg curbbid 
129 | 
130 |     -- | backedges from the CFG
131 |     backedges :: [(IRBBId, IRBBId)]
132 |     backedges = filter (isBackEdge bbIdToDomSet) cfgnext
133 | 
134 |     -- | current loop if it exists
135 |     curloop :: [Loop]
136 |     curloop = if null backedges 
137 |               then []
138 |               else [Loop {
139 |                 loopHeader=curbbid,
140 |                 loopLatches= map fst backedges,
141 |                 loopBackEdges=backedges
142 |               }]
143 | 
144 | 
145 | 
146 | detectLoops :: IRProgram -> [Loop]
147 | detectLoops program@Program{programBBMap=bbmap,
148 |                     programEntryBBId=entrybbid} = 
149 |                     _detectLoopsRec bbmap bbIdToDomSet domtree cfg entrybbid where
150 |     bbIdToDomSet :: BBIdToDomSet
151 |     bbIdToDomSet = constructBBDominators program
152 | 
153 |     domtree :: DomTree
154 |     domtree = constructDominatorTree bbIdToDomSet entrybbid
155 | 
156 |     cfg :: CFG
157 |     cfg = mkCFG bbmap
158 |  
159 | 
160 | -- | Chain of recurrences.
161 | data SCEV = SCEV
162 | 
163 | type SCEVMap = M.OrderedMap (Label Inst) SCEV
164 |  
165 | 
166 | analyzeSCEV :: IRProgram -> SCEVMap
167 | analyzeSCEV = undefined
168 | 169 | 170 | -------------------------------------------------------------------------------- /docs/transformconstantfolding.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 46 | 47 | 48 | 49 |

50 | Transform Pass: Constant Folding 51 |

52 | 55 |

56 | Introduction 57 |

58 |

In this pass, we remove all instructions we can evaluate at compile-time. This includes arithmetic and boolean operators.

59 |

The idea is really simple: scan basic blocks, and if an instruction can be immediately evaluated, do so.

60 |

Note that for this pass to be as easy as it is, SSA is crucial.

61 |

Consider this snippet of code:

62 |
define x;
 63 | assign x := 10;
 64 | assign x := x + 42;
 65 | assign x := x * 10
 66 | return x;
67 |

and the associated load/store based IR:

68 |
entry:  default.0
 69 | program:
 70 | default.0:
 71 |     x := alloc
 72 |     _ := store 10# in %x
 73 |     x.load := load %x
 74 |     tmp.0 := add %x.load 42#
 75 |     _.1 := store %tmp.0 in %x
 76 |     x.load.1 := load %x
 77 |     tmp.1 := mul %x.load.1 10#
 78 |     _.2 := store %tmp.1 in %x
 79 |     TERMINAL
80 |

We cannot simply replace x with 10 due to the mutation happening on x!

81 |

Now, consider the SSA form of the same computation:

82 |
entry:  default.0
 83 | program:
 84 | default.0:
 85 |     tmp.0 := add 10# 42#
 86 |     tmp.1 := mul %tmp.0 10#
 87 |     TERMINAL
88 |

Due to the immutable nature of SSA, we are guaranteed that we can replace all occurences of a variable with it's RHS, and the semantics of the program will remain the same! (AKA equational reasoning).

89 |

This is enormously powerful because it allows to replace values with wild abandon :).

90 |

91 | Key Takeaway of this pass 92 |

93 | 97 |
{-# LANGUAGE ViewPatterns #-}
 98 | 
 99 | module TransformConstantFolding where
100 | import qualified OrderedMap as M
101 | import Control.Monad.State.Strict
102 | import Data.Traversable
103 | import Data.Foldable
104 | import Control.Applicative
105 | import qualified Data.List.NonEmpty as NE
106 | import IR
107 | import BaseIR
108 | import Data.Text.Prettyprint.Doc as PP
109 | import PrettyUtils
110 | 
111 | boolToInt :: Bool -> Int
112 | boolToInt False = 0
113 | boolToInt True = 1
114 | 
115 | -- | Fold all possible arithmetic / boolean ops
116 | tryFoldInst :: Inst -> Maybe Value
117 | tryFoldInst (InstAdd (ValueConstInt i) (ValueConstInt j)) = 
118 |     Just $ ValueConstInt (i + j)
119 | tryFoldInst (InstMul (ValueConstInt i) (ValueConstInt j)) = 
120 |     Just $ ValueConstInt (i * j)
121 | tryFoldInst (InstL (ValueConstInt i) (ValueConstInt j)) = 
122 |     Just $ ValueConstInt $ boolToInt (i < j)
123 | 
124 | tryFoldInst (InstAnd (ValueConstInt i) (ValueConstInt j)) = 
125 |     Just $ ValueConstInt (i * j)
126 | tryFoldInst i = Nothing
127 | 
128 | collectFoldableInsts :: Named Inst -> [(Label Inst, Value)]
129 | collectFoldableInsts (Named name (tryFoldInst -> Just v)) = [(name, v)]
130 | collectFoldableInsts _ = []
131 | 
132 | runTillStable :: Eq a => (a -> a) -> a -> a
133 | runTillStable f a = let a' = f a in
134 |     if a' == a
135 |     then a'
136 |     else f a'
137 | 
138 | transformConstantFold :: IRProgram -> IRProgram
139 | transformConstantFold =  dceProgram . (runTillStable foldProgram)  where
140 | 
141 |     -- | Collection of instruction names and values
142 |     foldableInsts :: IRProgram -> [(Label Inst, Value)]
143 |     foldableInsts p = foldMapProgramBBs (foldMapBB (collectFoldableInsts) (const mempty)) p
144 | 
145 |     -- | Program after constant folding
146 |     foldProgram :: IRProgram -> IRProgram
147 |     foldProgram program = foldl (\p (name, v) -> replaceUsesOfInst name v p) program (foldableInsts program)
148 | 
149 |     -- | program after dead code elimination
150 |     dceProgram :: IRProgram -> IRProgram
151 |     dceProgram program =
152 |         foldl (\p name -> filterProgramInsts (not . hasName name) p) program (map fst (foldableInsts program))
153 | 154 | 155 | -------------------------------------------------------------------------------- /imperative-compiler.cabal: -------------------------------------------------------------------------------- 1 | -- Initial imperative-compiler.cabal generated by cabal init. For further 2 | -- documentation, see http://haskell.org/cabal/users-guide/ 3 | 4 | -- The name of the package. 5 | name: imperative-compiler 6 | 7 | -- The package version. See the Haskell package versioning policy (PVP) 8 | -- for standards guiding when and how versions should be incremented. 9 | -- https://wiki.haskell.org/Package_versioning_policy 10 | -- PVP summary: +-+------- breaking API changes 11 | -- | | +----- non-breaking API additions 12 | -- | | | +--- code changes with no API change 13 | version: 0.1.0.0 14 | 15 | -- A short (one-line) description of the package. 16 | -- synopsis: 17 | 18 | -- A longer description of the package. 19 | -- description: 20 | 21 | -- The license under which the package is released. 22 | license: BSD3 23 | 24 | -- The file containing the license text. 25 | license-file: LICENSE 26 | 27 | -- The package author(s). 28 | author: Siddharth Bhat 29 | 30 | -- An email address to which users can send suggestions, bug reports, and 31 | -- patches. 32 | maintainer: siddu.druid@gmail.com 33 | 34 | -- A copyright notice. 35 | -- copyright: 36 | 37 | -- category: 38 | 39 | build-type: Simple 40 | 41 | -- Extra files to be distributed with the package, such as examples or a 42 | -- README. 43 | extra-source-files: ChangeLog.md 44 | 45 | -- Constraint on the version of Cabal needed to build this package. 46 | cabal-version: >=1.10 47 | 48 | 49 | executable imperative-compiler 50 | -- .hs or .lhs file containing the Main module. 51 | main-is: Main.lhs 52 | 53 | -- Modules included in this executable, other than Main. 54 | other-modules: Parser, 55 | IR, 56 | BaseIR, 57 | ProgramToIR, 58 | TransformMem2Reg, 59 | SCEV, 60 | PrettyUtils, 61 | OrderedMap, 62 | IRInterpreter, 63 | MIPSInterpreter, 64 | MIPSAsm, 65 | Graph, 66 | Language, 67 | TransformConstantFolding, 68 | TransformIRToMIPS, 69 | TransformRegisterAllocate, 70 | Absint, 71 | -- ISL 72 | ISL.Native 73 | , ISL.Native.Context 74 | , ISL.Native.C2Hs 75 | , ISL.Native.Types 76 | , ISL.Types 77 | 78 | build-tools: c2hs >= 0.19.1 79 | 80 | -- LANGUAGE extensions used by modules in this package. 81 | -- other-extensions: 82 | 83 | -- Other library packages from which modules are imported. 84 | build-depends: base >=4.9 && <4.10, 85 | parsers, 86 | trifecta, 87 | --trifecta wants this 88 | ansi-wl-pprint, 89 | bytestring, 90 | prettyprinter, 91 | -- To create temporary files. 92 | temporary, 93 | -- To launch SPIM. 94 | process, 95 | text, 96 | containers, 97 | mtl, 98 | -- For safe indexing. 99 | safe, 100 | -- for trifecta (hashset) 101 | unordered-containers, 102 | mono-traversable 103 | 104 | 105 | -- Directories containing source files. 106 | hs-source-dirs: src 107 | 108 | -- Base language which the package is written in. 109 | default-language: Haskell2010 110 | 111 | test-suite test 112 | type: exitcode-stdio-1.0 113 | hs-source-dirs: test, src 114 | main-is: Spec.hs 115 | 116 | build-depends: base 117 | , tasty 118 | , tasty-hunit 119 | , parsers 120 | , trifecta 121 | --trifecta wants this 122 | , ansi-wl-pprint 123 | , bytestring 124 | , prettyprinter 125 | , text 126 | , containers 127 | , mtl 128 | , safe 129 | -- To create temporary files. 130 | , temporary 131 | -- To launch SPIM. 132 | , process 133 | -- for trifecta (hashset) 134 | , unordered-containers 135 | , directory 136 | , mono-traversable 137 | 138 | other-modules: IR 139 | , BaseIR 140 | , IRInterpreter 141 | , MIPSInterpreter 142 | , MIPSAsm 143 | , Language 144 | , OrderedMap 145 | , Parser 146 | , PrettyUtils 147 | , ProgramToIR 148 | , TransformMem2Reg 149 | , TransformIRToMIPS 150 | , TransformRegisterAllocate 151 | , SCEV 152 | , Graph 153 | , TransformConstantFolding 154 | -- ISL 155 | ISL.Native 156 | , ISL.Native.Context 157 | , ISL.Native.C2Hs 158 | , ISL.Native.Types 159 | , ISL.Types 160 | default-language: Haskell2010 161 | ghc-options: -threaded -rtsopts -with-rtsopts=-N 162 | default-language: Haskell2010 163 | -------------------------------------------------------------------------------- /make-docs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from sh import pandoc,rm,cp,mkdir 3 | import glob 4 | import os 5 | 6 | def compile(input_file_path, output_file_path): 7 | print("compiling: %s to %s " % (input_file_path, output_file_path)) 8 | pandoc(input_file_path, c="pandoc.css", f="markdown+lhs", o=output_file_path) 9 | 10 | def mk_output_path(iname): 11 | return "docs/" + iname.lower().split(".lhs")[0] + ".html" 12 | 13 | def mk_input_path(iname): 14 | return "src/" + iname 15 | 16 | def get_input_file_names(): 17 | names = [] 18 | for f in os.listdir("src/"): 19 | if f.endswith("hs"): 20 | names.append(f) 21 | 22 | return names 23 | 24 | 25 | if __name__ == "__main__": 26 | rm("-rf", "docs/") 27 | mkdir("docs") 28 | cp("pandoc.css", "docs/pandoc.css") 29 | for iname in get_input_file_names(): 30 | compile(mk_input_path(iname), mk_output_path(iname)) 31 | 32 | -------------------------------------------------------------------------------- /pandoc.css: -------------------------------------------------------------------------------- 1 | body { 2 | -ms-text-size-adjust: 100%; 3 | -webkit-text-size-adjust: 100%; 4 | line-height: 1.5; 5 | color: #333; 6 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 7 | font-size: 20px; 8 | line-height: 1.8; 9 | word-wrap: break-word; 10 | margin-left: 20%; 11 | margin-right: 20%; 12 | } 13 | 14 | .pl-c { 15 | color: #969896; 16 | } 17 | 18 | .pl-c1, 19 | .pl-s .pl-v { 20 | color: #0086b3; 21 | } 22 | 23 | .pl-e, 24 | .pl-en { 25 | color: #795da3; 26 | } 27 | 28 | .pl-smi, 29 | .pl-s .pl-s1 { 30 | color: #333; 31 | } 32 | 33 | .pl-ent { 34 | color: #63a35c; 35 | } 36 | 37 | .pl-k { 38 | color: #a71d5d; 39 | } 40 | 41 | .pl-s, 42 | .pl-pds, 43 | .pl-s .pl-pse .pl-s1, 44 | .pl-sr, 45 | .pl-sr .pl-cce, 46 | .pl-sr .pl-sre, 47 | .pl-sr .pl-sra { 48 | color: #183691; 49 | } 50 | 51 | .pl-v { 52 | color: #ed6a43; 53 | } 54 | 55 | .pl-id { 56 | color: #b52a1d; 57 | } 58 | 59 | .pl-ii { 60 | color: #f8f8f8; 61 | background-color: #b52a1d; 62 | } 63 | 64 | .pl-sr .pl-cce { 65 | font-weight: bold; 66 | color: #63a35c; 67 | } 68 | 69 | .pl-ml { 70 | color: #693a17; 71 | } 72 | 73 | .pl-mh, 74 | .pl-mh .pl-en, 75 | .pl-ms { 76 | font-weight: bold; 77 | color: #1d3e81; 78 | } 79 | 80 | .pl-mq { 81 | color: #008080; 82 | } 83 | 84 | .pl-mi { 85 | font-style: italic; 86 | color: #333; 87 | } 88 | 89 | .pl-mb { 90 | font-weight: bold; 91 | color: #333; 92 | } 93 | 94 | .pl-md { 95 | color: #bd2c00; 96 | background-color: #ffecec; 97 | } 98 | 99 | .pl-mi1 { 100 | color: #55a532; 101 | background-color: #eaffea; 102 | } 103 | 104 | .pl-mdr { 105 | font-weight: bold; 106 | color: #795da3; 107 | } 108 | 109 | .pl-mo { 110 | color: #1d3e81; 111 | } 112 | 113 | .octicon { 114 | display: inline-block; 115 | vertical-align: text-top; 116 | fill: currentColor; 117 | } 118 | 119 | a { 120 | background-color: transparent; 121 | -webkit-text-decoration-skip: objects; 122 | } 123 | 124 | a:active, 125 | a:hover { 126 | outline-width: 0; 127 | } 128 | 129 | strong { 130 | font-weight: inherit; 131 | } 132 | 133 | strong { 134 | font-weight: bolder; 135 | } 136 | 137 | h1 { 138 | font-size: 2em; 139 | margin: 0.67em 0; 140 | } 141 | 142 | img { 143 | border-style: none; 144 | } 145 | 146 | svg:not(:root) { 147 | overflow: hidden; 148 | } 149 | 150 | code, 151 | kbd, 152 | pre { 153 | font-family: monospace, monospace; 154 | font-size: 1em; 155 | } 156 | 157 | hr { 158 | box-sizing: content-box; 159 | height: 0; 160 | overflow: visible; 161 | } 162 | 163 | input { 164 | font: inherit; 165 | margin: 0; 166 | } 167 | 168 | input { 169 | overflow: visible; 170 | } 171 | 172 | [type="checkbox"] { 173 | box-sizing: border-box; 174 | padding: 0; 175 | } 176 | 177 | * { 178 | box-sizing: border-box; 179 | } 180 | 181 | input { 182 | font-family: inherit; 183 | font-size: inherit; 184 | line-height: inherit; 185 | } 186 | 187 | a { 188 | color: #4078c0; 189 | text-decoration: none; 190 | } 191 | 192 | a:hover, 193 | a:active { 194 | text-decoration: underline; 195 | } 196 | 197 | strong { 198 | font-weight: 600; 199 | } 200 | 201 | hr { 202 | height: 0; 203 | margin: 15px 0; 204 | overflow: hidden; 205 | background: transparent; 206 | border: 0; 207 | border-bottom: 1px solid #ddd; 208 | } 209 | 210 | hr::before { 211 | display: table; 212 | content: ""; 213 | } 214 | 215 | hr::after { 216 | display: table; 217 | clear: both; 218 | content: ""; 219 | } 220 | 221 | table { 222 | border-spacing: 0; 223 | border-collapse: collapse; 224 | } 225 | 226 | td, 227 | th { 228 | padding: 0; 229 | } 230 | 231 | h1, 232 | h2, 233 | h3, 234 | h4, 235 | h5, 236 | h6 { 237 | margin-top: 0; 238 | margin-bottom: 0; 239 | } 240 | 241 | h1 { 242 | font-size: 32px; 243 | font-weight: 600; 244 | } 245 | 246 | h2 { 247 | font-size: 24px; 248 | font-weight: 600; 249 | } 250 | 251 | h3 { 252 | font-size: 20px; 253 | font-weight: 600; 254 | } 255 | 256 | h4 { 257 | font-size: 16px; 258 | font-weight: 600; 259 | } 260 | 261 | h5 { 262 | font-size: 14px; 263 | font-weight: 600; 264 | } 265 | 266 | h6 { 267 | font-size: 12px; 268 | font-weight: 600; 269 | } 270 | 271 | p { 272 | margin-top: 0; 273 | margin-bottom: 10px; 274 | } 275 | 276 | blockquote { 277 | margin: 0; 278 | } 279 | 280 | ul, 281 | ol { 282 | padding-left: 0; 283 | margin-top: 0; 284 | margin-bottom: 0; 285 | } 286 | 287 | ol ol, 288 | ul ol { 289 | list-style-type: lower-roman; 290 | } 291 | 292 | ul ul ol, 293 | ul ol ol, 294 | ol ul ol, 295 | ol ol ol { 296 | list-style-type: lower-alpha; 297 | } 298 | 299 | dd { 300 | margin-left: 0; 301 | } 302 | 303 | code { 304 | font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; 305 | font-size: 12px; 306 | } 307 | 308 | pre { 309 | margin-top: 0; 310 | margin-bottom: 0; 311 | font: 12px Consolas, "Liberation Mono", Menlo, Courier, monospace; 312 | } 313 | 314 | .octicon { 315 | vertical-align: text-bottom; 316 | } 317 | 318 | input { 319 | -webkit-font-feature-settings: "liga" 0; 320 | font-feature-settings: "liga" 0; 321 | } 322 | 323 | .markdown-body::before { 324 | display: table; 325 | content: ""; 326 | } 327 | 328 | .markdown-body::after { 329 | display: table; 330 | clear: both; 331 | content: ""; 332 | } 333 | 334 | .markdown-body>*:first-child { 335 | margin-top: 0 !important; 336 | } 337 | 338 | .markdown-body>*:last-child { 339 | margin-bottom: 0 !important; 340 | } 341 | 342 | a:not([href]) { 343 | color: inherit; 344 | text-decoration: none; 345 | } 346 | 347 | .anchor { 348 | float: left; 349 | padding-right: 4px; 350 | margin-left: -20px; 351 | line-height: 1; 352 | } 353 | 354 | .anchor:focus { 355 | outline: none; 356 | } 357 | 358 | p, 359 | blockquote, 360 | ul, 361 | ol, 362 | dl, 363 | table, 364 | pre { 365 | margin-top: 0; 366 | margin-bottom: 16px; 367 | } 368 | 369 | hr { 370 | height: 0.25em; 371 | padding: 0; 372 | margin: 24px 0; 373 | background-color: #e7e7e7; 374 | border: 0; 375 | } 376 | 377 | blockquote { 378 | padding: 0 1em; 379 | color: #777; 380 | border-left: 0.25em solid #ddd; 381 | } 382 | 383 | blockquote>:first-child { 384 | margin-top: 0; 385 | } 386 | 387 | blockquote>:last-child { 388 | margin-bottom: 0; 389 | } 390 | 391 | kbd { 392 | display: inline-block; 393 | padding: 3px 5px; 394 | font-size: 11px; 395 | line-height: 10px; 396 | color: #555; 397 | vertical-align: middle; 398 | background-color: #fcfcfc; 399 | border: solid 1px #ccc; 400 | border-bottom-color: #bbb; 401 | border-radius: 3px; 402 | box-shadow: inset 0 -1px 0 #bbb; 403 | } 404 | 405 | h1, 406 | h2, 407 | h3, 408 | h4, 409 | h5, 410 | h6 { 411 | margin-top: 24px; 412 | margin-bottom: 16px; 413 | font-weight: 600; 414 | line-height: 1.25; 415 | } 416 | 417 | h1 .octicon-link, 418 | h2 .octicon-link, 419 | h3 .octicon-link, 420 | h4 .octicon-link, 421 | h5 .octicon-link, 422 | h6 .octicon-link { 423 | color: #000; 424 | vertical-align: middle; 425 | visibility: hidden; 426 | } 427 | 428 | h1:hover .anchor, 429 | h2:hover .anchor, 430 | h3:hover .anchor, 431 | h4:hover .anchor, 432 | h5:hover .anchor, 433 | h6:hover .anchor { 434 | text-decoration: none; 435 | } 436 | 437 | h1:hover .anchor .octicon-link, 438 | h2:hover .anchor .octicon-link, 439 | h3:hover .anchor .octicon-link, 440 | h4:hover .anchor .octicon-link, 441 | h5:hover .anchor .octicon-link, 442 | h6:hover .anchor .octicon-link { 443 | visibility: visible; 444 | } 445 | 446 | h1 { 447 | padding-bottom: 0.3em; 448 | font-size: 2em; 449 | border-bottom: 1px solid #eee; 450 | } 451 | 452 | h2 { 453 | padding-bottom: 0.3em; 454 | font-size: 1.5em; 455 | border-bottom: 1px solid #eee; 456 | } 457 | 458 | h3 { 459 | font-size: 1.25em; 460 | } 461 | 462 | h4 { 463 | font-size: 1em; 464 | } 465 | 466 | h5 { 467 | font-size: 0.875em; 468 | } 469 | 470 | h6 { 471 | font-size: 0.85em; 472 | color: #777; 473 | } 474 | 475 | ul, 476 | ol { 477 | padding-left: 2em; 478 | } 479 | 480 | ul ul, 481 | ul ol, 482 | ol ol, 483 | ol ul { 484 | margin-top: 0; 485 | margin-bottom: 0; 486 | } 487 | 488 | li>p { 489 | margin-top: 16px; 490 | } 491 | 492 | li+li { 493 | margin-top: 0.25em; 494 | } 495 | 496 | dl { 497 | padding: 0; 498 | } 499 | 500 | dl dt { 501 | padding: 0; 502 | margin-top: 16px; 503 | font-size: 1em; 504 | font-style: italic; 505 | font-weight: bold; 506 | } 507 | 508 | dl dd { 509 | padding: 0 16px; 510 | margin-bottom: 16px; 511 | } 512 | 513 | table { 514 | display: block; 515 | width: 100%; 516 | overflow: auto; 517 | } 518 | 519 | table th { 520 | font-weight: bold; 521 | } 522 | 523 | table th, 524 | table td { 525 | padding: 6px 13px; 526 | border: 1px solid #ddd; 527 | } 528 | 529 | table tr { 530 | background-color: #fff; 531 | border-top: 1px solid #ccc; 532 | } 533 | 534 | table tr:nth-child(2n) { 535 | background-color: #f8f8f8; 536 | } 537 | 538 | img { 539 | max-width: 100%; 540 | box-sizing: content-box; 541 | background-color: #fff; 542 | } 543 | 544 | code { 545 | padding: 0; 546 | padding-top: 0.2em; 547 | padding-bottom: 0.2em; 548 | margin: 0; 549 | font-size: 85%; 550 | background-color: rgba(0, 0, 0, 0.04); 551 | border-radius: 3px; 552 | } 553 | 554 | code::before, 555 | code::after { 556 | letter-spacing: -0.2em; 557 | content: "\00a0"; 558 | } 559 | 560 | pre { 561 | word-wrap: normal; 562 | } 563 | 564 | pre>code { 565 | padding: 0; 566 | margin: 0; 567 | font-size: 100%; 568 | word-break: normal; 569 | white-space: pre; 570 | background: transparent; 571 | border: 0; 572 | } 573 | 574 | .highlight { 575 | margin-bottom: 16px; 576 | } 577 | 578 | .highlight pre { 579 | margin-bottom: 0; 580 | word-break: normal; 581 | } 582 | 583 | .highlight pre, 584 | pre { 585 | padding: 16px; 586 | overflow: auto; 587 | font-size: 85%; 588 | line-height: 1.45; 589 | background-color: #f7f7f7; 590 | border-radius: 3px; 591 | } 592 | 593 | pre code { 594 | display: inline; 595 | max-width: auto; 596 | padding: 0; 597 | margin: 0; 598 | overflow: visible; 599 | line-height: inherit; 600 | word-wrap: normal; 601 | background-color: transparent; 602 | border: 0; 603 | } 604 | 605 | pre code::before, 606 | pre code::after { 607 | content: normal; 608 | } 609 | 610 | .pl-0 { 611 | padding-left: 0 !important; 612 | } 613 | 614 | .pl-1 { 615 | padding-left: 3px !important; 616 | } 617 | 618 | .pl-2 { 619 | padding-left: 6px !important; 620 | } 621 | 622 | .pl-3 { 623 | padding-left: 12px !important; 624 | } 625 | 626 | .pl-4 { 627 | padding-left: 24px !important; 628 | } 629 | 630 | .pl-5 { 631 | padding-left: 36px !important; 632 | } 633 | 634 | .pl-6 { 635 | padding-left: 48px !important; 636 | } 637 | 638 | .full-commit .btn-outline:not(:disabled):hover { 639 | color: #4078c0; 640 | border: 1px solid #4078c0; 641 | } 642 | 643 | kbd { 644 | display: inline-block; 645 | padding: 3px 5px; 646 | font: 11px Consolas, "Liberation Mono", Menlo, Courier, monospace; 647 | line-height: 10px; 648 | color: #555; 649 | vertical-align: middle; 650 | background-color: #fcfcfc; 651 | border: solid 1px #ccc; 652 | border-bottom-color: #bbb; 653 | border-radius: 3px; 654 | box-shadow: inset 0 -1px 0 #bbb; 655 | } 656 | 657 | :checked+.radio-label { 658 | position: relative; 659 | z-index: 1; 660 | border-color: #4078c0; 661 | } 662 | 663 | .task-list-item { 664 | list-style-type: none; 665 | } 666 | 667 | .task-list-item+.task-list-item { 668 | margin-top: 3px; 669 | } 670 | 671 | .task-list-item input { 672 | margin: 0 0.2em 0.25em -1.6em; 673 | vertical-align: middle; 674 | } 675 | 676 | hr { 677 | border-bottom-color: #eee; 678 | } 679 | 680 | 681 | /** Theming **/ 682 | 683 | body { 684 | color: #333; 685 | background: white; 686 | padding: 0 25px; 687 | } 688 | 689 | .vscode-light, 690 | .vscode-light pre code { 691 | color: #333; 692 | } 693 | 694 | .vscode-dark, 695 | .vscode-dark pre code { 696 | color: #333; 697 | } 698 | 699 | .vscode-high-contrast, 700 | .vscode-high-contrast pre code { 701 | color: #333; 702 | } 703 | 704 | .vscode-light code { 705 | color: #333; 706 | } 707 | 708 | .vscode-dark code { 709 | color: #333; 710 | } 711 | 712 | .vscode-light pre:not(.hljs), 713 | .vscode-light code>div { 714 | background-color: #F6F8FA; 715 | } 716 | 717 | .vscode-dark pre:not(.hljs), 718 | .vscode-dark code>div { 719 | background-color: #F6F8FA; 720 | } 721 | 722 | .vscode-high-contrast pre:not(.hljs), 723 | .vscode-high-contrast code>div { 724 | background-color: #F6F8FA; 725 | } 726 | 727 | .vscode-high-contrast h1 { 728 | border-color: transparent; 729 | } 730 | 731 | .vscode-light table>thead>tr>th { 732 | border-color: #EAECEF; 733 | } 734 | 735 | .vscode-dark table>thead>tr>th { 736 | border-color: #EAECEF; 737 | } 738 | 739 | .vscode-light h1, 740 | .vscode-light hr, 741 | .vscode-light table>tbody>tr+tr>td { 742 | border-color: #EAECEF; 743 | } 744 | 745 | .vscode-dark h1, 746 | .vscode-dark hr, 747 | .vscode-dark table>tbody>tr+tr>td { 748 | border-color: #EAECEF; 749 | } 750 | 751 | .vscode-light blockquote, 752 | .vscode-dark blockquote { 753 | padding: 0 1em; 754 | color: #777; 755 | border-left: 0.25em solid #ddd; 756 | background: transparent; 757 | } 758 | 759 | .vscode-high-contrast blockquote { 760 | padding: 0 1em; 761 | color: #777; 762 | border-left: 0.25em solid #ddd; 763 | background: transparent; 764 | } 765 | -------------------------------------------------------------------------------- /programs/direct-int-if.c: -------------------------------------------------------------------------------- 1 | if (1 < 2) { 2 | return 3; 3 | } else { 4 | return 4; 5 | }; 6 | return -1; 7 | -------------------------------------------------------------------------------- /programs/direct-int-return.c: -------------------------------------------------------------------------------- 1 | return 10 2 | -------------------------------------------------------------------------------- /programs/force-spill.c: -------------------------------------------------------------------------------- 1 | define x1; 2 | define x2; 3 | define x3; 4 | define x4; 5 | define x5; 6 | define x6; 7 | define x7; 8 | define x8; 9 | define x9; 10 | define loop; 11 | 12 | assign x1 := 1; 13 | assign x2 := 1; 14 | assign x3 := 1; 15 | assign x4 := 1; 16 | assign x5 := 1; 17 | assign x6 := 1; 18 | assign x7 := 1; 19 | assign x8 := 1; 20 | assign x9 := 1; 21 | assign loop := 1; 22 | 23 | while loop < 12 { 24 | assign x1 := loop + 1; 25 | assign x2 := loop + 2; 26 | assign x3 := loop + 3; 27 | assign x4 := loop + 4; 28 | assign x5 := loop + 5; 29 | assign x6 := loop + 6; 30 | assign x7 := loop + 7; 31 | assign x8 := loop + 8; 32 | assign x9 := loop + 9; 33 | assign loop := loop + 1; 34 | }; 35 | 36 | return x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8 + x9; 37 | -------------------------------------------------------------------------------- /programs/if.c: -------------------------------------------------------------------------------- 1 | define x; 2 | define y; 3 | assign x := 1; 4 | if x < 1 { 5 | assign y := 2; 6 | } else { 7 | assign y := 3; 8 | }; 9 | 10 | assign x := 2 + y; 11 | return x; 12 | -------------------------------------------------------------------------------- /programs/nontrivial-dom-frontier.c: -------------------------------------------------------------------------------- 1 | define x; 2 | define y; 3 | assign x := 1; 4 | assign y := 2; 5 | 6 | if x < 1 { 7 | assign y := y + 2; 8 | } else { 9 | assign y := y + 3; 10 | }; 11 | 12 | if y < 42 { 13 | assign y := y + 4; 14 | } else { 15 | assign y := y + 5; 16 | }; 17 | 18 | assign x := 2 + y; 19 | return x; 20 | -------------------------------------------------------------------------------- /programs/simple-store.c: -------------------------------------------------------------------------------- 1 | define x; 2 | assign x := 1 + 1 * 2; 3 | return x; 4 | -------------------------------------------------------------------------------- /programs/store-load.c: -------------------------------------------------------------------------------- 1 | define x; 2 | assign x := 10; 3 | assign x := x + 42; 4 | return x; 5 | -------------------------------------------------------------------------------- /programs/test-constant-folding.c: -------------------------------------------------------------------------------- 1 | define x; 2 | assign x := 10; 3 | assign x := x + 42; 4 | assign x := x * 10; 5 | return x; 6 | 7 | -------------------------------------------------------------------------------- /programs/while-2-nest.c: -------------------------------------------------------------------------------- 1 | define x; 2 | define y; 3 | define z; 4 | 5 | assign x := 10; 6 | assign y := 1; 7 | 8 | while (x < 12) { 9 | while (y < 13) { 10 | assign x := x + 1; 11 | assign y := y * x; 12 | }; 13 | }; 14 | return y; 15 | 16 | -------------------------------------------------------------------------------- /programs/while.c: -------------------------------------------------------------------------------- 1 | define x; 2 | define y; 3 | define z; 4 | 5 | assign x := 10; 6 | assign y := 1; 7 | 8 | while (x < 12) { 9 | assign x := x + 1; 10 | assign y := y * x; 11 | }; 12 | return y; 13 | 14 | -------------------------------------------------------------------------------- /reading/STOKE-superoptimizer.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bollu/tiny-optimising-compiler/57565bfc6f0f096241947b805f2a8a85e43fcceb/reading/STOKE-superoptimizer.pdf -------------------------------------------------------------------------------- /reading/stochastic-program-optimization.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bollu/tiny-optimising-compiler/57565bfc6f0f096241947b805f2a8a85e43fcceb/reading/stochastic-program-optimization.pdf -------------------------------------------------------------------------------- /src/Absint.lhs: -------------------------------------------------------------------------------- 1 | 2 | \begin{code} 3 | -- Pretty Utils 4 | -- ============ 5 | instance Pretty a => Pretty (S.Set a) where 6 | pretty s = case S.toList s of 7 | [] -> pretty "emptyset" 8 | xs -> indent 1 $ vcat $ [pretty "{"] ++ (map pretty xs) ++ [pretty "}"] 9 | 10 | instance (Pretty k, Pretty v) => Pretty (M.Map k v) where 11 | pretty m = 12 | if M.null m 13 | then pretty "emptymap" 14 | else (indent 1 (vcat $ [pretty "(" <> pretty k <+> pretty "->" <+> (pretty v) <> pretty ")" | (k, v) <- M.toList m])) 15 | 16 | -- Lattice theory 17 | -- ============== 18 | -- top = join of all elements 19 | class SemiJoin a where 20 | join :: a -> a -> a 21 | top :: a 22 | 23 | -- bottom = meet of all elements 24 | class SemiMeet a where 25 | meet :: a -> a -> a 26 | bottom :: a 27 | 28 | class (SemiJoin a, SemiMeet a) => Lattice a 29 | 30 | instance SemiJoin a => SemiJoin (Maybe a) where 31 | top = Just top 32 | 33 | join Nothing a = a 34 | join a Nothing = a 35 | join (Just a) (Just b) = Just (join a b) 36 | 37 | instance SemiMeet a => SemiMeet (Maybe a) where 38 | bottom = Nothing 39 | 40 | meet Nothing _ = Nothing 41 | meet _ Nothing = Nothing 42 | meet (Just a) (Just b) = Just (meet a b) 43 | 44 | instance (SemiJoin a, SemiJoin b) => SemiJoin (a, b) where 45 | top = (top, top) 46 | join (a, b) (a', b') = (a `join` a', b `join` b') 47 | 48 | instance (SemiMeet a, SemiMeet b) => SemiMeet (a, b) where 49 | bottom = (bottom, bottom) 50 | meet (a, b) (a', b') = (a `meet` a', b `meet` b') 51 | 52 | instance (Lattice a, Lattice b) => Lattice (a, b) 53 | 54 | data LiftedLattice a = LL !a | LLBot | LLTop deriving(Eq, Ord, Functor) 55 | 56 | instance Pretty a => Pretty (LiftedLattice a) where 57 | pretty (LL a) = pretty a 58 | pretty LLBot = pretty "_|_" 59 | pretty LLTop = pretty "T" 60 | 61 | instance Eq a => SemiJoin (LiftedLattice a) where 62 | top = LLTop 63 | 64 | join LLBot a = a 65 | join a LLBot = a 66 | join LLTop _ = LLTop 67 | join _ LLTop = LLTop 68 | join (LL a) (LL b) = if a == b then LL a else LLTop 69 | 70 | instance Eq a => SemiMeet (LiftedLattice a) where 71 | bottom = LLBot 72 | 73 | meet LLBot _ = LLBot 74 | meet _ LLBot = LLBot 75 | meet a LLTop = a 76 | meet LLTop a = a 77 | meet (LL a) (LL b) = if a == b then LL a else LLBot 78 | 79 | instance Eq a => Lattice (LiftedLattice a) 80 | 81 | 82 | 83 | liftLL2 :: (a -> b -> c) -> LiftedLattice a -> LiftedLattice b -> LiftedLattice c 84 | liftLL2 f LLTop _ = LLTop 85 | liftLL2 f _ LLTop = LLTop 86 | liftLL2 f LLBot _ = LLBot 87 | liftLL2 f _ LLBot = LLBot 88 | liftLL2 f (LL a) (LL b) = LL (f a b) 89 | 90 | instance Show a => Show (LiftedLattice a) where 91 | show LLBot = "_|_" 92 | show LLTop = "T" 93 | show (LL a) = show a 94 | 95 | 96 | class Lattice a => BooleanAlgebra a where 97 | complement :: a -> a 98 | 99 | -- implication in the boolean algebra 100 | imply :: BooleanAlgebra a => a -> a -> a 101 | imply a b = (complement a) `join` b 102 | 103 | -- symbol 104 | (===>) :: BooleanAlgebra a => a -> a -> a 105 | (===>) = imply 106 | 107 | 108 | -- Adjoin a top element 109 | data ToppedLattice a = TLTop | TL !a deriving (Eq, Ord, Functor) 110 | 111 | instance Show a => Show (ToppedLattice a) where 112 | show TLTop = "T" 113 | show (TL a) = show a 114 | 115 | data BottomedLattice a = TLBot | TB !a deriving(Eq, Ord, Functor) 116 | 117 | instance Show a => Show (BottomedLattice a) where 118 | show TLBot = "_|_" 119 | show (TB a) = show a 120 | 121 | 122 | -- A map based representation of a function (a -> b), which on partial 123 | -- missing keys returns _|_ 124 | data SemiMeetMap k v = LM !(M.Map k v) deriving (Eq, Ord, Functor) 125 | 126 | -- Insert a regular value into a lattice map 127 | lminsert :: Ord k => k -> v -> SemiMeetMap k v -> SemiMeetMap k v 128 | lminsert k v (LM m) = LM $ M.insert k v m 129 | 130 | -- pointwise produce of two lattice maps 131 | -- If a value is missing in either lattice, put a bottom in its place 132 | lmproduct :: (SemiMeet v, SemiMeet w, Ord k) => SemiMeetMap k v -> SemiMeetMap k w -> SemiMeetMap k (v, w) 133 | lmproduct (LM m) (LM m') = let 134 | missingm' = M.mapMissing (\k w -> bottom) 135 | missingm = M.mapMissing (\k v -> bottom) 136 | merger = M.zipWithMatched (\k tx ty -> (tx, ty)) 137 | in LM $ M.merge missingm' missingm merger m m' 138 | 139 | adjust :: Ord k => k -> (v -> v) -> SemiMeetMap k v -> SemiMeetMap k v 140 | adjust k f (LM m) = LM $ M.adjust f k m 141 | 142 | (!!#!) :: (Ord k, SemiMeet v) => SemiMeetMap k v -> k -> v 143 | (!!#!) (LM m) k = case m M.!? k of 144 | Just v -> v 145 | Nothing -> bottom 146 | 147 | 148 | (!!#?) :: Ord k => SemiMeetMap k v -> k -> Maybe v 149 | (!!#?) (LM m) k = m M.!? k 150 | 151 | lmfromlist :: Ord k => [(k, v)] -> SemiMeetMap k v 152 | lmfromlist kvs = LM $ M.fromList [(k, v) | (k, v) <- kvs] 153 | 154 | lmempty :: SemiMeetMap k v 155 | lmempty = LM $ M.empty 156 | 157 | lmtolist :: Ord k => SemiMeetMap k v -> [(k, v)] 158 | lmtolist (LM m) = M.toList m 159 | 160 | instance (Ord k, Show k, Show v, Pretty k, Pretty v) => Show (SemiMeetMap k v) where 161 | show (LM m) = show $ [(k, m !!# k) | k <- M.keys m] 162 | 163 | 164 | instance (Ord k, Pretty k, Pretty v) => Pretty (SemiMeetMap k v) where 165 | pretty (LM m) = pretty m -- vcat $ [pretty k <+> pretty "->" <+> pretty (m !!# k) | k <- M.keys m] 166 | 167 | instance SemiMeet v => SemiMeet (SemiMeetMap k v) where 168 | bottom = LM M.empty 169 | meet _ _ = error "TODO: define meet" 170 | 171 | \end{code} 172 | 173 |

Helper to repeat till fixpoint

174 | 175 | \begin{code} 176 | repeatTillFix :: (Eq a) => (a -> a) -> a -> a 177 | repeatTillFix f a = 178 | let a' = f a in 179 | if a == a' then a else repeatTillFix f a' 180 | 181 | 182 | -- repeat till fixpoint, or the max count 183 | repeatTillFixDebug :: Eq a => Int -> (a -> a) -> a -> a 184 | repeatTillFixDebug 0 f a = a 185 | repeatTillFixDebug n f a = 186 | let a' = f a in if a' == a then a else repeatTillFixDebug (n - 1) f a' 187 | 188 | 189 | repeatTillFixDebugTrace :: Eq a => Int -> (a -> a) -> a -> [a] 190 | repeatTillFixDebugTrace 0 f a = [a] 191 | repeatTillFixDebugTrace n f a = 192 | let a' = f a in if a' == a then [a] else a:repeatTillFixDebugTrace (n - 1) f a' 193 | 194 | repeatTillFixDebugTraceM :: (Monad m) => Int -> (a -> a -> Bool) -> (a -> m a) -> a -> m [a] 195 | repeatTillFixDebugTraceM 0 eqf f a = return [a] 196 | repeatTillFixDebugTraceM n eqf f a = do 197 | a' <- f a 198 | if eqf a a' 199 | then return [a] 200 | else do 201 | as <- repeatTillFixDebugTraceM (n - 1) eqf f a' 202 | return (a' : as) 203 | \end{code} 204 | -------------------------------------------------------------------------------- /src/BaseIR.lhs: -------------------------------------------------------------------------------- 1 |

BaseIR

2 | 3 | This module contains the building blocks that are shared across the `IR` and 4 | the `MIPSAsm` module. They both use ideas of `Program`, `BasicBlock`, etc, but 5 | with slightly different underlying types. Hence, we unify the common code here. 6 | \begin{code} 7 | {-# LANGUAGE StandaloneDeriving #-} 8 | {-# LANGUAGE DeriveAnyClass #-} 9 | {-# LANGUAGE DeriveFunctor #-} 10 | {-# LANGUAGE DeriveFoldable #-} 11 | {-# LANGUAGE DeriveTraversable #-} 12 | module BaseIR where 13 | 14 | import qualified Control.Arrow as A 15 | import Data.Text.Prettyprint.Doc as PP 16 | import qualified OrderedMap as M 17 | import Data.Functor.Identity 18 | import Data.Traversable 19 | import qualified Data.Monoid as M 20 | import Control.Monad 21 | import Data.Bifunctor 22 | import PrettyUtils 23 | 24 | -- | A label that uses the phantom @a as a type based discriminator 25 | data Label a = Label { unLabel :: String } deriving(Eq, Ord, Functor, Foldable, Traversable) 26 | instance Pretty (Label a) where 27 | pretty (Label s) = pretty s 28 | 29 | 30 | -- | Convert from one type of label to another label. 31 | unsafeTransmuteLabel :: Label a -> Label b 32 | unsafeTransmuteLabel (Label lbl) = Label lbl 33 | 34 | -- | A basic block. Single-entry, multiple-exit. 35 | -- | TODO: remove duplication of information about the bbLabel in both 36 | -- | Program and BasicBlock. 37 | data BasicBlock inst ret = BasicBlock { 38 | bbInsts :: [inst], 39 | bbRetInst :: ret , 40 | bbLabel :: Label (BasicBlock inst ret) 41 | } 42 | 43 | deriving instance (Eq inst, Eq ret) => Eq (BasicBlock inst ret) 44 | 45 | -- | Used to identify basic blocks 46 | type BBId inst retinst = Label (BasicBlock inst retinst) 47 | 48 | -- TODO: replace nest with indent 49 | instance (Pretty inst, Pretty ret) => Pretty (BasicBlock inst ret)where 50 | pretty (BasicBlock insts ret label) = 51 | nest 4 (vsep ([pretty label <> pretty ":"] ++ body)) where 52 | body = map pretty insts ++ [pretty ret] 53 | 54 | 55 | data Program inst ret = Program { 56 | programBBMap :: M.OrderedMap (BBId inst ret) (BasicBlock inst ret), 57 | programEntryBBId :: (BBId inst ret) 58 | } 59 | 60 | 61 | deriving instance (Eq inst, Eq ret) => Eq (Program inst ret) 62 | 63 | instance (Pretty inst, Pretty ret) => Pretty (Program inst ret) where 64 | pretty (Program bbmap entryId) = 65 | vsep $ [pretty "entry: " <+> pretty entryId, pretty "program: "] ++ 66 | fmap pretty (M.elems bbmap) 67 | 68 | 69 | 70 | -- | Run an effect at a particular basic block for a program 71 | traverseProgramAt :: Applicative f => BBId inst ret 72 | -> (BasicBlock inst ret -> f (BasicBlock inst ret)) 73 | -> Program inst ret -> 74 | f (Program inst ret) 75 | traverseProgramAt bbid f (Program bbmap entryId) = Program <$> bbmap' <*> pure entryId 76 | where 77 | bbmap' = (\curbb' -> M.insert bbid curbb' bbmap) <$> (f curbb) 78 | curbb = case M.lookup bbid bbmap of 79 | Just bb -> bb 80 | Nothing -> error . docToString $ pretty "unable to find bbid in program: " <+> pretty bbid 81 | 82 | 83 | mapProgramAt :: BBId inst ret -> (BasicBlock inst ret -> BasicBlock inst ret) 84 | -> Program inst ret -> Program inst ret 85 | mapProgramAt bbid f p = runIdentity $ 86 | traverseProgramAt bbid (Identity . f) p 87 | 88 | 89 | -- | Map an effect over all the BBs of the Program 90 | traverseProgramBBs :: Applicative f => 91 | (BasicBlock inst ret -> f (BasicBlock inst' ret')) 92 | -> Program inst ret 93 | -> f (Program inst' ret') 94 | traverseProgramBBs fbb (Program bbmap entrybbid) = 95 | (Program <$> bbmap' <*> pure (unsafeTransmuteLabel entrybbid)) where 96 | -- bbmap' :: M.OrderedMap (BBId inst' ret') (BasicBlock inst' ret') 97 | bbmap' = traverse fbb bbmapRekeyed 98 | 99 | -- bbmapRekeyed :: M.OrderedMap (BBId inst' ret') (BasicBlock inst ret) 100 | bbmapRekeyed = M.editKeys unsafeTransmuteLabel bbmap 101 | 102 | -- | Map a pure effect over all BBs of the IRPRogram 103 | mapProgramBBs :: (BasicBlock inst ret -> BasicBlock inst' ret') 104 | -> Program inst ret 105 | -> Program inst' ret' 106 | mapProgramBBs fbb program = runIdentity $ traverseProgramBBs (Identity . fbb) program 107 | 108 | -- | Run a monadic effect over the basic blocks throwing away the results 109 | mapMProgramBBs_ :: Monad m => (BasicBlock inst ret -> m ()) -> Program inst ret -> m () 110 | mapMProgramBBs_ fbb (Program bbmap _) = forM_ bbmap fbb 111 | 112 | 113 | -- | Collect results from basic blocks which can be monoidally smashed. 114 | foldMapProgramBBs :: Monoid m => 115 | (BasicBlock inst ret -> m) 116 | -> Program inst ret 117 | -> m 118 | foldMapProgramBBs fbb program = foldMap fbb (programBBMap program) 119 | 120 | -- | Filter instructions in a basic block. 121 | filterBBInsts :: (inst -> Bool) -> BasicBlock inst ret -> BasicBlock inst ret 122 | filterBBInsts pred (BasicBlock insts retinst lbl) = 123 | BasicBlock insts' retinst lbl 124 | where insts' = filter pred insts 125 | 126 | -- | Filter instructions in a Program. 127 | filterProgramInsts :: (inst -> Bool) -> Program inst ret -> Program inst ret 128 | filterProgramInsts pred prog = 129 | mapProgramBBs (filterBBInsts pred) prog 130 | 131 | -- | Run an effect on a basic block. 132 | traverseBB :: Applicative f => (inst -> f inst') 133 | -> (ret -> f ret') 134 | -> BasicBlock inst ret 135 | -> f (BasicBlock inst' ret') 136 | traverseBB finst fretinst (BasicBlock insts retinst lbl) = 137 | BasicBlock <$> insts' <*> retinst' <*> pure (unsafeTransmuteLabel lbl) where 138 | retinst' = fretinst retinst 139 | insts' = for insts finst 140 | 141 | -- | Run an effect over a basic block throwing away the results 142 | mapMBB_ :: Monad f => (inst -> f ()) -> (ret -> f ()) -> BasicBlock inst ret -> f () 143 | mapMBB_ finst fretinst (BasicBlock insts retinst lbl) = do 144 | for insts finst 145 | fretinst retinst 146 | 147 | 148 | weaveEffect_ :: (Traversable f, Applicative f, Monad t, Traversable t) => (a -> f (t b)) 149 | -> t a -> f (t b) 150 | weaveEffect_ f as = join <$> intermediate -- f (t t b) 151 | where 152 | intermediate = for as f 153 | -- join :: t (t b) -> t b 154 | join ttb = ttb >>= (\tb -> tb) 155 | 156 | -- | Run an effect on a basic block, while allowing to create a "locus" around 157 | -- | an instruction. This can be used to delete instructions, or add a sequence 158 | -- | of instructions for one original instruction. 159 | traverseBBInstLocus :: (Applicative f, Traversable f) => 160 | (inst -> f [inst']) 161 | -> BasicBlock inst ret 162 | -> f (BasicBlock inst' ret) 163 | traverseBBInstLocus finst (BasicBlock insts retinst lbl) = 164 | 165 | BasicBlock <$> insts'<*> pure retinst <*> pure (unsafeTransmuteLabel lbl) where 166 | insts' = weaveEffect_ finst insts 167 | 168 | 169 | mapBBInstLocus :: (inst -> [inst']) -> BasicBlock inst ret -> BasicBlock inst' ret 170 | mapBBInstLocus f bb = runIdentity $ traverseBBInstLocus (Identity . f) bb 171 | -- | Fold from the first instruction to the last one, and then on the 172 | -- | RetInst of a BB. 173 | foldlBB :: collect 174 | -> (collect -> inst -> collect) 175 | -> (collect -> ret -> collect) 176 | -> BasicBlock inst ret 177 | -> collect 178 | foldlBB seed finst fretinst (BasicBlock insts retinst lbl) = 179 | fretinst (foldl finst seed insts) retinst 180 | 181 | 182 | -- | produce results on a BB and smash them together with a monoid instance 183 | foldMapBB :: Monoid m => (inst -> m) 184 | -> (ret -> m) 185 | -> BasicBlock inst ret 186 | -> m 187 | foldMapBB finst fretinst bb = 188 | foldlBB mempty (\c i -> c M.<> finst i) (\c ri -> c M.<> fretinst ri) bb 189 | 190 | 191 | -- | Map over the instructions and return values of a basic block 192 | mapBB :: (inst -> inst') 193 | -> (ret -> ret') 194 | -> BasicBlock inst ret 195 | -> BasicBlock inst' ret' 196 | mapBB finst fretinst bb = 197 | runIdentity $ traverseBB (Identity . finst) (Identity . fretinst) bb 198 | 199 | 200 | -- | Insert instructions before the first instruction in a bb. 201 | insertInstsBeginBB :: [inst] -> BasicBlock inst ret -> BasicBlock inst ret 202 | insertInstsBeginBB pre (BasicBlock insts retinst lbl) = 203 | BasicBlock (pre++insts) retinst lbl 204 | 205 | -- | Insert instructions at the end of the last instruction in a bb. 206 | insertInstsEndBB :: [inst] -> BasicBlock inst ret -> BasicBlock inst ret 207 | insertInstsEndBB post (BasicBlock insts retinst lbl) = 208 | BasicBlock (insts++post) retinst lbl 209 | 210 | 211 | \end{code} 212 | 213 | -------------------------------------------------------------------------------- /src/Graph.lhs: -------------------------------------------------------------------------------- 1 |

Graph

2 | In this module, we define a simple `graph` structure that can be used 3 | as: 4 | 5 | - an undirected. 6 | - a directed graph. 7 | - a tree. 8 | 9 | Ideally, we would use some sort of phantom-type mechanism to distinguish 10 | between the two, that is `Graph Undirected a` and `Graph Directed a`, but 11 | oh well `:)`. 12 | 13 | 14 | 15 | \begin{code} 16 | {-# LANGUAGE ViewPatterns #-} 17 | 18 | module Graph where 19 | import Data.List(nub) 20 | import Data.Text.Prettyprint.Doc as PP 21 | import PrettyUtils 22 | import Data.Maybe (maybeToList) 23 | import qualified OrderedMap as M 24 | import qualified Data.Set as S 25 | 26 | -- | Represents a graph with `a` as a vertex ID type 27 | newtype Graph a = Graph { edges :: [(a, a)] } 28 | 29 | instance Pretty a => Pretty (Graph a) where 30 | pretty graph = 31 | vcat [pretty "BB graph edges", 32 | (vcat . map (indent 4 . pretty) . edges $ graph)] 33 | 34 | -- | returns all edges (H -> T) with a given source H 35 | getEdgesFromSource :: Eq a => Graph a -> a -> [(a, a)] 36 | getEdgesFromSource g src = [(src, b) | (a, b) <- edges g, a == src] 37 | 38 | -- | return predecessors of a node 39 | getPredecessors :: Eq a => Graph a -> a -> [a] 40 | getPredecessors g bbid = [ src | (src, sink) <- (edges g), sink == bbid] 41 | 42 | -- | Returns the children of an element in a dom tree 43 | -- | This returns only the immediate children. 44 | getImmediateChildren :: Eq a => Graph a -> a -> [a] 45 | getImmediateChildren (Graph edges) a = [dest | (src, dest) <- edges, src==a] 46 | 47 | -- | Return all the vertices of the subgraph 48 | getAllChildren :: Eq a => Graph a -> a -> [a] 49 | getAllChildren tree@(Graph edges) a = 50 | a:(curChilds >>= (getAllChildren tree)) where 51 | curChilds = getImmediateChildren tree a 52 | 53 | -- | Return the set of vertices in DomTree 54 | vertices :: Eq a => Graph a -> [a] 55 | vertices (Graph edges) = nub (map fst edges ++ map snd edges) 56 | 57 | -- | Colors are assigned from [1..NGraphColors] 58 | type GraphColor = Int 59 | type NGraphColors = Int 60 | 61 | _greedyColorGraph :: Ord a => Graph a -- ^ Graph 62 | -> S.Set a -- ^ Set of vertices 63 | -> M.OrderedMap a (Maybe GraphColor) -- ^ Mapping from vertices to colors 64 | -> NGraphColors -- ^ Total number of graph colors available 65 | -> M.OrderedMap a (Maybe GraphColor) -- ^ Final colored graph 66 | _greedyColorGraph _ (null -> True) coloring ncolors = coloring 67 | _greedyColorGraph g vs@(S.elemAt 0 -> v) coloring ncolors = 68 | _greedyColorGraph g vs' coloring' ncolors where 69 | -- adjacent vertices 70 | adjvs = (getPredecessors g v) 71 | 72 | -- colors of adjacent vertices 73 | adjColors :: [GraphColor] 74 | adjColors = mconcat $ fmap (\v -> case (v `M.lookup` coloring) of 75 | Just (Just c) -> [c] 76 | _ -> []) adjvs 77 | 78 | -- largest color 79 | largestAdjColor = case adjColors of 80 | [] -> 0 81 | xs -> maximum xs 82 | 83 | -- Leave it uncolored it we can't find a color 84 | coloring' = if largestAdjColor == ncolors 85 | then M.insert v Nothing coloring 86 | else M.insert v (Just (largestAdjColor + 1)) coloring 87 | 88 | -- remove vertex we currently processed 89 | vs' = S.deleteAt 0 vs 90 | 91 | 92 | -- | Color the graph greedily and return the mapping of colors 93 | greedyColorGraph :: Ord a => NGraphColors -> Graph a -> M.OrderedMap a (Maybe Int) 94 | greedyColorGraph ngraphcolors g = 95 | _greedyColorGraph g (S.fromList (vertices g)) 96 | mempty ngraphcolors 97 | 98 | \end{code} 99 | -------------------------------------------------------------------------------- /src/IR.lhs: -------------------------------------------------------------------------------- 1 |

Internal Representation

2 | 3 | In this module, we define the LLVM-like IR that we compile our 4 | source code to. 5 | 6 | \begin{code} 7 | {-# LANGUAGE GADTs #-} 8 | {-# LANGUAGE DeriveFunctor #-} 9 | {-# LANGUAGE DeriveFoldable #-} 10 | {-# LANGUAGE DeriveTraversable #-} 11 | {-# LANGUAGE ViewPatterns #-} 12 | {-# LANGUAGE ScopedTypeVariables #-} 13 | 14 | module IR where 15 | import Data.Text.Prettyprint.Doc as PP 16 | import PrettyUtils 17 | import qualified Language as L 18 | import qualified Data.List.NonEmpty as NE 19 | import qualified OrderedMap as M 20 | import Data.Functor.Identity 21 | import qualified Data.Monoid as Monoid 22 | import BaseIR 23 | import Data.Traversable(for) 24 | import Control.Applicative(liftA2) 25 | import Control.Monad.State.Strict(State, execState, modify) 26 | 27 | type IRBB = BasicBlock (Named Inst) RetInst 28 | type IRBBId = BBId (Named Inst) (RetInst) 29 | 30 | -- | Default basic block. 31 | defaultIRBB :: IRBB 32 | defaultIRBB = BasicBlock [] (RetInstTerminal) (Label "undefined") 33 | 34 | -- | Given an IRBB, return a list of Phi nodes. 35 | getIRBBPhis :: IRBB -> [Named Inst] 36 | getIRBBPhis bb = bbInsts $ 37 | filterBBInsts (\(Named _ i) -> case i of 38 | InstPhi _ -> True 39 | _ -> False) bb 40 | 41 | 42 | -- a Value, which can either be a constant, or a reference to an instruction. 43 | data Value = ValueConstInt Int | ValueInstRef (Label Inst) deriving(Eq) 44 | 45 | instance Pretty Value where 46 | pretty (ValueConstInt i) = pretty i <> pretty "#" 47 | pretty (ValueInstRef name) = pretty "%" <> pretty name 48 | 49 | -- | Instructions that we allow within a basic block. 50 | data Inst = InstAlloc 51 | | InstAdd Value Value 52 | | InstMul Value Value 53 | | InstL Value Value 54 | | InstAnd Value Value 55 | | InstLoad Value 56 | | InstStore Value Value 57 | | InstPhi (NE.NonEmpty (IRBBId, Value)) deriving(Eq) 58 | 59 | -- | Given `Inst` (which is known to be a Phi node), get a `Value` which 60 | -- | corresponds to the given `IRBBId` 61 | getPhiValueForBB :: IRBBId -> Inst -> Maybe Value 62 | getPhiValueForBB bbid phi@(InstPhi valList) = 63 | case NE.filter ((==bbid) . fst) valList of 64 | [] -> Nothing 65 | [(_, v)] -> Just v 66 | xs -> error . docToString $ vcat $ 67 | [pretty "Phi node should at most one copy of a predecessor BB, found:", 68 | pretty xs, 69 | pretty "Phi node:", 70 | pretty phi] 71 | getPhiValueForBB _ inst = 72 | error . docToString $ vcat 73 | [pretty "getPhiValueForBB should only be called on Phi. Found:", 74 | pretty inst] 75 | -- | Map over the `Value`s in an Inst 76 | mapInstValue :: (Value -> Value) -> Inst -> Inst 77 | mapInstValue f inst = runIdentity $ forInstValue (Identity . f) inst 78 | 79 | -- | TODO: use Uniplate. 80 | -- | Run an effect `f` over the values of an instruction 81 | forInstValue :: Applicative m => (Value -> m Value) -> Inst -> m Inst 82 | forInstValue _ (InstAlloc) = pure InstAlloc 83 | forInstValue f (InstAdd lhs rhs) = InstAdd <$> (f lhs) <*> (f rhs) 84 | forInstValue f (InstMul lhs rhs) = InstMul <$> (f lhs) <*> (f rhs) 85 | forInstValue f (InstL lhs rhs) = InstL <$> (f lhs) <*> (f rhs) 86 | forInstValue f (InstAnd lhs rhs) = InstAnd <$> (f lhs) <*> (f rhs) 87 | forInstValue f (InstLoad lhs) = InstLoad <$> f lhs 88 | forInstValue f (InstStore lhs rhs) = InstStore <$> (f lhs) <*> (f rhs) 89 | forInstValue f (InstPhi valList) = InstPhi <$> for valList (f' f) where 90 | f' :: Applicative m => (Value -> m Value) 91 | -> (IRBBId, Value) 92 | -> m (IRBBId, Value) 93 | f' f (irbbid, val) = liftA2 (,) (pure irbbid) (f val) 94 | 95 | -- | Collect a monoidal Value over an Inst 96 | foldMapInstValue :: Monoid m => (Value -> m) -> Inst -> m 97 | foldMapInstValue f inst = execState final Monoid.mempty where 98 | -- go :: Value -> State m Value 99 | go v = do 100 | modify (\m -> m Monoid.<> f v) 101 | return v 102 | 103 | -- final :: State m Inst 104 | final = (forInstValue go inst) 105 | 106 | 107 | instance Pretty Inst where 108 | pretty (InstAlloc) = pretty "alloc" 109 | pretty (InstAdd l r) = pretty "add" <+> pretty l <+> pretty r 110 | pretty (InstMul l r) = pretty "mul" <+> pretty l <+> pretty r 111 | pretty (InstL l r) = pretty "lessthan" <+> pretty l <+> pretty r 112 | pretty (InstAnd l r) = pretty "and" <+> pretty l <+> pretty r 113 | pretty (InstLoad op) = pretty "load" <+> pretty op 114 | pretty (InstStore slot val) = pretty "store" <+> pretty val <+> 115 | pretty "in" <+> pretty slot 116 | pretty (InstPhi philist) = 117 | pretty "Phi: " <+> hcat (punctuate comma (NE.toList (fmap (\(bbid, val) -> 118 | brackets (pretty bbid <+> pretty val)) philist))) 119 | 120 | -- | Return instructions are the only ones that can cause control flow 121 | -- | between one basic block to another. 122 | data RetInst = 123 | RetInstConditionalBranch Value IRBBId IRBBId | 124 | RetInstBranch IRBBId | 125 | RetInstTerminal | 126 | RetInstRet Value deriving(Eq) 127 | 128 | instance Pretty RetInst where 129 | pretty (RetInstTerminal) = pretty "TERMINAL" 130 | pretty (RetInstBranch next) = pretty "branch" <+> pretty next 131 | pretty (RetInstConditionalBranch cond then' else') = 132 | pretty "branch if" <+> pretty cond <+> 133 | pretty "then" <+> pretty then' <+> 134 | pretty "else" <+> pretty else' 135 | pretty (RetInstRet val) = pretty "ret" <+> pretty val 136 | 137 | -- | Run an effect `f` over the values of the return instruction 138 | forRetInstValue :: Applicative m => (Value -> m Value) -> RetInst -> m RetInst 139 | forRetInstValue _ RetInstTerminal = pure RetInstTerminal 140 | forRetInstValue _ (RetInstBranch bbid) = pure (RetInstBranch bbid) 141 | forRetInstValue f (RetInstConditionalBranch v t e) = 142 | RetInstConditionalBranch <$> f v <*> pure t <*> pure e 143 | forRetInstValue f (RetInstRet v) = RetInstRet <$> f v 144 | 145 | mapRetInstValue :: (Value -> Value) -> RetInst -> RetInst 146 | mapRetInstValue f ret = runIdentity $ forRetInstValue (Identity . f) ret 147 | 148 | -- | Run an effect `f` over the basic block IDs of the return instruction 149 | forRetInstBBId :: Applicative m => (IRBBId -> m IRBBId) -> RetInst -> m RetInst 150 | forRetInstBBId _ RetInstTerminal = pure RetInstTerminal 151 | forRetInstBBId f (RetInstBranch bbid) = (RetInstBranch <$> f bbid) 152 | forRetInstBBId f (RetInstConditionalBranch v t e) = 153 | RetInstConditionalBranch <$> pure v <*> f t <*> f e 154 | forRetInstBBId _ (RetInstRet v) = pure (RetInstRet v) 155 | 156 | mapRetInstBBId :: (IRBBId -> IRBBId) -> RetInst -> RetInst 157 | mapRetInstBBId f ret = runIdentity $ forRetInstBBId (Identity . f) ret 158 | 159 | 160 | -- | Represents @a that is optionally named by a @Label a 161 | data Named a = Named { namedName :: Label a, namedData :: a } deriving(Functor, Foldable, Traversable, Eq) 162 | 163 | hasName :: (Label a) -> Named a -> Bool 164 | hasName lbl named = namedName named == lbl 165 | 166 | 167 | -- | Infix operator for @Named constructor 168 | (=:=) :: Label a -> a -> Named a 169 | name =:= a = Named name a 170 | 171 | instance Pretty a => Pretty (Named a) where 172 | pretty (Named name data') = pretty name <+> pretty ":=" <+> pretty data' 173 | 174 | 175 | type IRProgram = Program (Named Inst) RetInst 176 | 177 | -- | Replace all uses of an instruction in a program 178 | replaceUsesOfInst :: Label Inst -> Value -> IRProgram -> IRProgram 179 | replaceUsesOfInst instlbl newval program = 180 | mapProgramBBs fbb program where 181 | replaceVal :: Value -> Value 182 | replaceVal (ValueInstRef ((== instlbl) -> True)) = newval 183 | replaceVal v = v 184 | 185 | finst :: Named Inst -> Named Inst 186 | finst = fmap (mapInstValue replaceVal) 187 | 188 | fretinst :: RetInst -> RetInst 189 | fretinst = mapRetInstValue replaceVal 190 | 191 | fbb :: IRBB -> IRBB 192 | fbb = mapBB finst fretinst 193 | 194 | \end{code} 195 | -------------------------------------------------------------------------------- /src/IRInterpreter.lhs: -------------------------------------------------------------------------------- 1 | \begin{code} 2 | {-# LANGUAGE RecordWildCards #-} 3 | module IRInterpreter(runProgram) where 4 | import qualified OrderedMap as M 5 | import Control.Monad.State.Strict 6 | import Data.Traversable 7 | import Data.Foldable 8 | import Control.Applicative 9 | import qualified Data.List.NonEmpty as NE 10 | import IR 11 | import BaseIR 12 | import Data.Text.Prettyprint.Doc as PP 13 | import PrettyUtils 14 | 15 | 16 | data Evaluator = Evaluator { 17 | program :: IRProgram, 18 | prevbbid :: Maybe IRBBId, 19 | valueMap :: M.OrderedMap (Label Inst) Int, 20 | returnval :: Maybe Int 21 | } 22 | instance Pretty Evaluator where 23 | pretty Evaluator{..} =pretty "Evaluator" <+> (braces . indent 2) (vcat [pretty "program: ", 24 | indent 2 . pretty $ program, 25 | pretty "prevbb: ", 26 | indent 2 . pretty $ prevbbid, 27 | pretty "valueMap: ", 28 | indent 2 . pretty $ valueMap, 29 | pretty "returnval: ", 30 | indent 2 . pretty $ returnval]) 31 | 32 | initEvaluator :: IRProgram -> Evaluator 33 | initEvaluator program = Evaluator { 34 | program = program, 35 | prevbbid = Nothing, 36 | valueMap = mempty, 37 | returnval = Nothing 38 | } 39 | 40 | loadName :: Label Inst -> State Evaluator Int 41 | loadName name = gets $ (lookupName name) where 42 | lookupName :: Label Inst -> Evaluator -> Int 43 | lookupName name evaluator@Evaluator{valueMap=vmap} = case M.lookup name vmap of 44 | Just val -> val 45 | Nothing -> error . docToString $ 46 | vcat [pretty "unable to find mapping to variable:" <+> pretty name, 47 | pretty "state: ", 48 | pretty evaluator] 49 | 50 | 51 | setValue :: Label Inst -> Int -> State Evaluator () 52 | setValue name val = 53 | modify (\ctx -> ctx { valueMap=M.insert name val (valueMap ctx) }) 54 | 55 | getValue :: Value -> State Evaluator Int 56 | getValue (ValueConstInt i) = return i 57 | getValue (ValueInstRef name) = loadName name 58 | 59 | getPreviousBBId :: State Evaluator IRBBId 60 | getPreviousBBId = do 61 | prevbb <- gets prevbbid 62 | case prevbb of 63 | Just id' -> return id' 64 | Nothing -> error "no previous BB id found." 65 | 66 | evaluateInst :: Named Inst -> State Evaluator () 67 | evaluateInst namedinst@(Named lhsname inst) = do 68 | case inst of 69 | InstAlloc -> return () 70 | InstLoad slot -> getValue slot >>= setValue lhsname 71 | InstStore (ValueInstRef slotname) val -> getValue val >>= setValue slotname 72 | InstAdd l r -> liftA2 (+) (getValue l) (getValue r) >>= setValue lhsname 73 | InstMul l r -> liftA2 (*) (getValue l) (getValue r) >>= setValue lhsname 74 | InstL l r -> liftA2 (\l r -> if l < r then 1 else 0) (getValue l) (getValue r) >>= setValue lhsname 75 | InstAnd l r -> liftA2 (\l r -> l * r) (getValue l) (getValue r) >>= setValue lhsname 76 | InstPhi bbidValuePairs -> do 77 | prevbbid <- getPreviousBBId 78 | getValue (snd (getCurrentBBIdValue prevbbid)) >>= setValue lhsname 79 | where 80 | pred :: IRBBId -> (IRBBId, Value) -> Bool 81 | pred prevbbid (bbid, _) = bbid == prevbbid 82 | 83 | getCurrentBBIdValue :: IRBBId -> (IRBBId, Value) 84 | getCurrentBBIdValue prevbbid = head . NE.filter (pred prevbbid) $ bbidValuePairs 85 | 86 | 87 | followRetInst :: RetInst -> State Evaluator (Maybe IRBBId) 88 | followRetInst (RetInstTerminal) = return Nothing 89 | followRetInst (RetInstBranch bbid) = return (Just bbid) 90 | followRetInst (RetInstConditionalBranch val t e) = do 91 | valInt <- getValue val 92 | if valInt == 1 93 | then return (Just t) 94 | else return (Just e) 95 | followRetInst (RetInstRet retval) = do 96 | retvalInt <- getValue retval 97 | modify (\evaluator -> evaluator { returnval=Just retvalInt}) 98 | return Nothing 99 | 100 | evaluateBB :: IRBBId -> State Evaluator () 101 | evaluateBB bbid = do 102 | bb <- gets $ (M.! bbid) . programBBMap . program 103 | for (bbInsts bb) evaluateInst 104 | nextid <- followRetInst (bbRetInst bb) 105 | modify (\evaluator -> evaluator {prevbbid=Just bbid}) 106 | 107 | case nextid of 108 | Nothing -> return () 109 | Just nextid -> evaluateBB nextid 110 | 111 | -- | TODO: convert to Either Error (Maybe Int) 112 | -- | The internal monad transformer would need to become EitherT 113 | runProgram :: IRProgram -> Maybe Int 114 | runProgram program = returnval $ execState (evaluateBB (programEntryBBId program)) (initEvaluator program) 115 | \end{code} 116 | -------------------------------------------------------------------------------- /src/ISL/Native.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE QuasiQuotes #-} 2 | {-# LANGUAGE TemplateHaskell #-} 3 | {-# LANGUAGE OverloadedStrings #-} 4 | 5 | -- | An inline-c based low-level interface to isl. 6 | module ISL.Native 7 | ( IslCopy(copy) 8 | , IslFree(free) 9 | 10 | , ctxFree 11 | 12 | , basicSetCopy 13 | , basicSetFree 14 | 15 | , unsafeSetIntersect 16 | , setIntersect 17 | , unsafeSetUnion 18 | , setUnion 19 | , unsafeSetSubtract 20 | , setSubtract 21 | , setCopy 22 | , setEmpty 23 | , unsafeSetUniverse 24 | , setUniverse 25 | , setGetSpace 26 | , setFree 27 | , setNBasicSet 28 | , unsafeSetCoalesce 29 | , setCoalesce 30 | , unsafeSetParams 31 | , setParams 32 | , setComplement 33 | , setGetDimId 34 | , unsafeSetProjectOut 35 | , setProjectOut 36 | 37 | , basicMapCopy 38 | , basicMapFree 39 | 40 | , mapCopy 41 | , mapFree 42 | 43 | , localSpaceCopy 44 | , localSpaceFree 45 | 46 | , spaceCopy 47 | , spaceFree 48 | 49 | , constraintCopy 50 | , constraintFree 51 | 52 | , idCopy 53 | , idFree 54 | ) where 55 | 56 | import Control.Monad (void) 57 | import Foreign.Ptr 58 | import Foreign.C 59 | import qualified Language.C.Inline as C 60 | 61 | import ISL.Native.Context (islCtx) 62 | import ISL.Native.Types 63 | 64 | C.context islCtx 65 | 66 | C.include "" 67 | C.include "" 68 | C.include "" 69 | C.include "" 70 | C.include "" 71 | C.include "" 72 | C.include "" 73 | 74 | class IslCopy a where 75 | copy :: Ptr a -> Ptr a 76 | 77 | class IslFree a where 78 | free :: Ptr a -> IO () 79 | 80 | -- __isl_take: can no longer be used 81 | -- __isl_keep: only used temporarily 82 | 83 | -- * Ctx 84 | 85 | instance IslFree Ctx where free = ctxFree 86 | 87 | ctxFree :: Ptr Ctx -> IO () 88 | ctxFree ctx = [C.block| void { isl_ctx_free($(isl_ctx* ctx)); } |] 89 | 90 | -- * BasicSet 91 | 92 | instance IslCopy BasicSet where copy = basicSetCopy 93 | instance IslFree BasicSet where free = basicSetFree 94 | 95 | basicSetCopy :: Ptr BasicSet -> Ptr BasicSet 96 | basicSetCopy bset = 97 | [C.pure| isl_basic_set* { isl_basic_set_copy($(isl_basic_set* bset)) } |] 98 | 99 | basicSetFree :: Ptr BasicSet -> IO () 100 | basicSetFree bset = void 101 | [C.block| isl_basic_set* { isl_basic_set_free($(isl_basic_set* bset)); } |] 102 | 103 | -- * Set 104 | 105 | instance IslCopy Set where copy = setCopy 106 | instance IslFree Set where free = setFree 107 | 108 | setCopy :: Ptr Set -> Ptr Set 109 | setCopy set = [C.pure| isl_set* { isl_set_copy($(isl_set* set)) } |] 110 | 111 | setFree :: Ptr Set -> IO () 112 | setFree set = void [C.block| isl_set* { isl_set_free($(isl_set* set)); } |] 113 | 114 | unsafeSetIntersect :: Ptr Set -> Ptr Set -> Ptr Set 115 | unsafeSetIntersect set1 set2 = [C.pure| isl_set* { 116 | isl_set_intersect($(isl_set* set1), $(isl_set* set2)) 117 | } |] 118 | 119 | setIntersect :: Ptr Set -> Ptr Set -> Ptr Set 120 | setIntersect set1 set2 = unsafeSetIntersect (setCopy set1) (setCopy set2) 121 | 122 | unsafeSetUnion :: Ptr Set -> Ptr Set -> Ptr Set 123 | unsafeSetUnion set1 set2 = [C.pure| isl_set* { 124 | isl_set_union($(isl_set* set1), $(isl_set* set2)) 125 | } |] 126 | 127 | setUnion :: Ptr Set -> Ptr Set -> Ptr Set 128 | setUnion set1 set2 = unsafeSetUnion (setCopy set1) (setCopy set2) 129 | 130 | unsafeSetSubtract :: Ptr Set -> Ptr Set -> Ptr Set 131 | unsafeSetSubtract set1 set2 = [C.pure| isl_set* { 132 | isl_set_subtract($(isl_set* set1), $(isl_set* set2)) 133 | } |] 134 | 135 | setSubtract :: Ptr Set -> Ptr Set -> Ptr Set 136 | setSubtract set1 set2 = unsafeSetSubtract (setCopy set1) (setCopy set2) 137 | 138 | -- | Create an empty set 139 | setEmpty :: Ptr Space -> Ptr Set 140 | setEmpty space = [C.pure| isl_set* { isl_set_empty($(isl_space* space)) } |] 141 | 142 | -- | Create a universe set 143 | unsafeSetUniverse :: Ptr Space -> Ptr Set 144 | unsafeSetUniverse space = [C.pure| isl_set* { 145 | isl_set_universe($(isl_space* space)) 146 | } |] 147 | 148 | setUniverse :: Ptr Space -> Ptr Set 149 | setUniverse = unsafeSetUniverse . spaceCopy 150 | 151 | -- | It is often useful to create objects that live in the same space as some 152 | -- other object. This can be accomplished by creating the new objects based on 153 | -- the space of the original object. 154 | setGetSpace :: Ptr Set -> Ptr Space 155 | setGetSpace set = [C.pure| isl_space* { 156 | isl_set_get_space($(isl_set* set)) 157 | } |] 158 | 159 | -- | The number of basic sets in a set can be obtained 160 | setNBasicSet :: Ptr Set -> CInt 161 | setNBasicSet set = [C.pure| int { isl_set_n_basic_set($(isl_set* set)) } |] 162 | 163 | unsafeSetCoalesce :: Ptr Set -> Ptr Set 164 | unsafeSetCoalesce set = 165 | [C.pure| isl_set* { isl_set_coalesce($(isl_set* set)) } |] 166 | 167 | -- | Simplify the representation of a set by trying to combine pairs of basic 168 | -- sets into a single basic set. 169 | setCoalesce :: Ptr Set -> Ptr Set 170 | setCoalesce = unsafeSetCoalesce . setCopy 171 | 172 | -- | Projection 173 | unsafeSetParams :: Ptr Set -> Ptr Set 174 | unsafeSetParams set = [C.pure| isl_set* { isl_set_params($(isl_set* set)) } |] 175 | 176 | setParams :: Ptr Set -> Ptr Set 177 | setParams = unsafeSetParams . setCopy 178 | 179 | unsafeSetComplement :: Ptr Set -> Ptr Set 180 | unsafeSetComplement set = 181 | [C.pure| isl_set* { isl_set_complement($(isl_set* set)) } |] 182 | 183 | -- | Projection 184 | setComplement :: Ptr Set -> Ptr Set 185 | setComplement = unsafeSetComplement . setCopy 186 | 187 | setGetDimId :: Ptr Set -> DimType -> CUInt -> Ptr Id 188 | setGetDimId set ty pos = 189 | let ty' :: CInt 190 | ty' = fromDimType ty 191 | in [C.pure| isl_id* { 192 | isl_set_get_dim_id( 193 | $(isl_set* set), 194 | $(int ty'), 195 | $(unsigned int pos) 196 | ) 197 | } |] 198 | 199 | unsafeSetProjectOut :: Ptr Set -> DimType -> CUInt -> CUInt -> Ptr Set 200 | unsafeSetProjectOut set ty first n = 201 | let ty' :: CInt 202 | ty' = fromDimType ty 203 | in [C.pure| isl_set* { 204 | isl_set_project_out( 205 | $(isl_set* set), 206 | $(int ty'), 207 | $(unsigned int first), 208 | $(unsigned int n) 209 | ) 210 | } |] 211 | 212 | setProjectOut :: Ptr Set -> DimType -> CUInt -> CUInt -> Ptr Set 213 | setProjectOut set ty first n = unsafeSetProjectOut (setCopy set) ty first n 214 | 215 | -- * BasicMap 216 | 217 | instance IslCopy BasicMap where copy = basicMapCopy 218 | instance IslFree BasicMap where free = basicMapFree 219 | 220 | basicMapCopy :: Ptr BasicMap -> Ptr BasicMap 221 | basicMapCopy bmap = 222 | [C.pure| isl_basic_map* { isl_basic_map_copy($(isl_basic_map* bmap)) } |] 223 | 224 | basicMapFree :: Ptr BasicMap -> IO () 225 | basicMapFree bmap = void 226 | [C.block| isl_basic_map* { isl_basic_map_free($(isl_basic_map* bmap)); } |] 227 | 228 | -- * Map 229 | 230 | instance IslCopy Map where copy = mapCopy 231 | instance IslFree Map where free = mapFree 232 | 233 | mapCopy :: Ptr Map -> Ptr Map 234 | mapCopy map = [C.pure| isl_map* { isl_map_copy($(isl_map* map)) } |] 235 | 236 | mapFree :: Ptr Map -> IO () 237 | mapFree map = void [C.block| isl_map* { isl_map_free($(isl_map* map)); } |] 238 | 239 | -- * LocalSpace 240 | 241 | instance IslCopy LocalSpace where copy = localSpaceCopy 242 | instance IslFree LocalSpace where free = localSpaceFree 243 | 244 | localSpaceCopy :: Ptr LocalSpace -> Ptr LocalSpace 245 | localSpaceCopy ls = 246 | [C.pure| isl_local_space* { isl_local_space_copy($(isl_local_space* ls)) } |] 247 | 248 | localSpaceFree :: Ptr LocalSpace -> IO () 249 | localSpaceFree ls = void 250 | [C.block| isl_local_space* { isl_local_space_free($(isl_local_space* ls)); } |] 251 | 252 | -- * Space 253 | 254 | instance IslCopy Space where copy = spaceCopy 255 | instance IslFree Space where free = spaceFree 256 | 257 | spaceCopy :: Ptr Space -> Ptr Space 258 | spaceCopy space = 259 | [C.pure| isl_space* { isl_space_copy($(isl_space* space)) } |] 260 | 261 | spaceFree :: Ptr Space -> IO () 262 | spaceFree space = void 263 | [C.block| isl_space* { isl_space_free($(isl_space* space)); } |] 264 | 265 | -- * Constraint 266 | 267 | instance IslCopy Constraint where copy = constraintCopy 268 | instance IslFree Constraint where free = constraintFree 269 | 270 | constraintCopy :: Ptr Constraint -> Ptr Constraint 271 | constraintCopy c = 272 | [C.pure| isl_constraint* { isl_constraint_copy($(isl_constraint* c)) } |] 273 | 274 | constraintFree :: Ptr Constraint -> IO () 275 | constraintFree c = void 276 | [C.block| isl_constraint* { isl_constraint_free($(isl_constraint* c)); } |] 277 | 278 | -- * Id 279 | 280 | instance IslCopy Id where copy = idCopy 281 | instance IslFree Id where free = idFree 282 | 283 | idCopy :: Ptr Id -> Ptr Id 284 | idCopy i = [C.pure| isl_id* { isl_id_copy($(isl_id* i)) } |] 285 | 286 | idFree :: Ptr Id -> IO () 287 | idFree i = void [C.block| isl_id* { isl_id_free($(isl_id* i)); } |] 288 | -------------------------------------------------------------------------------- /src/ISL/Native/Context.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE QuasiQuotes #-} 2 | {-# LANGUAGE TemplateHaskell #-} 3 | {-# LANGUAGE OverloadedStrings #-} 4 | 5 | module ISL.Native.Context (islCtx) where 6 | 7 | import qualified Language.C.Inline as C 8 | import Language.C.Inline.Context 9 | import qualified Language.C.Types as C 10 | import qualified Data.Map as Map 11 | import Data.Monoid ((<>)) 12 | import qualified Language.Haskell.TH as TH 13 | 14 | import ISL.Native.Types 15 | 16 | islCtx :: C.Context 17 | islCtx = baseCtx <> bsCtx <> ctx 18 | where 19 | ctx = mempty 20 | { ctxTypesTable = islTypesTable 21 | } 22 | 23 | islTypesTable :: Map.Map C.TypeSpecifier TH.TypeQ 24 | islTypesTable = Map.fromList 25 | [ (C.TypeName "isl_ctx", [t| Ctx |]) 26 | , (C.TypeName "isl_map", [t| Map |]) 27 | , (C.TypeName "isl_basic_map", [t| BasicMap |]) 28 | , (C.TypeName "isl_set", [t| Set |]) 29 | , (C.TypeName "isl_basic_set", [t| BasicSet |]) 30 | , (C.TypeName "isl_local_space", [t| LocalSpace |]) 31 | , (C.TypeName "isl_space", [t| Space |]) 32 | , (C.TypeName "isl_constraint", [t| Constraint |]) 33 | , (C.TypeName "isl_id", [t| Id |]) 34 | , (C.TypeName "isl_dim_type", [t| DimType |]) 35 | ] 36 | -------------------------------------------------------------------------------- /src/ISL/Native/Types.chs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE ForeignFunctionInterface #-} 2 | 3 | -- | Types for the low-level interface to isl. 4 | module ISL.Native.Types where 5 | 6 | #include 7 | 8 | import Foreign.C (CInt) 9 | 10 | -- | A given context can only be used within a single thread, and all arguments 11 | -- to a function must be allocated within the same context. All objects 12 | -- allocated within a context should be freed before the context is freed. 13 | data Ctx 14 | 15 | -- | A single-space set of tuples that can be described as a conjunction of 16 | -- affine constraints. 17 | data BasicSet 18 | 19 | -- | A union of 'BasicSet's 20 | data Set 21 | 22 | -- | A single-space relation mapping tuples to tuples that can be described as 23 | -- a conjunction of affine constraints. 24 | data BasicMap 25 | 26 | -- | A union of 'BasicMap's 27 | data Map 28 | 29 | -- | A local space is essentially a space with zero or more existentially 30 | -- quantified variables. The local space of various objects can be obtained 31 | -- using the following functions. 32 | data LocalSpace 33 | 34 | -- | Whenever a new set, relation or similar object is created from scratch, 35 | -- the space in which it lives needs to be specified using an isl_space. Each 36 | -- space involves zero or more parameters and zero, one or two tuples of set or 37 | -- input/output dimensions. The parameters and dimensions are identified by an 38 | -- isl_dim_type and a position. The type isl_dim_param refers to parameters, 39 | -- the type isl_dim_set refers to set dimensions (for spaces with a single 40 | -- tuple of dimensions) and the types isl_dim_in and isl_dim_out refer to input 41 | -- and output dimensions (for spaces with two tuples of dimensions). Local 42 | -- spaces (see §1.4.9) also contain dimensions of type isl_dim_div. Note that 43 | -- parameters are only identified by their position within a given object. 44 | -- Across different objects, parameters are (usually) identified by their names 45 | -- or identifiers. Only unnamed parameters are identified by their positions 46 | -- across objects. The use of unnamed parameters is discouraged. 47 | data Space 48 | 49 | -- | An affine constraint. 50 | data Constraint 51 | 52 | -- | Identifiers are used to identify both individual dimensions and tuples of 53 | -- dimensions. They consist of an optional name and an optional user pointer. 54 | -- The name and the user pointer cannot both be NULL, however. Identifiers with 55 | -- the same name but different pointer values are considered to be distinct. 56 | -- Similarly, identifiers with different names but the same pointer value are 57 | -- also considered to be distinct. Equal identifiers are represented using the 58 | -- same object. Pairs of identifiers can therefore be tested for equality using 59 | -- the == operator. Identifiers can be constructed, copied, freed, inspected 60 | -- and printed using the following functions. 61 | data Id 62 | 63 | -- an ISL List of a's 64 | data List a 65 | 66 | data Val 67 | data Aff 68 | data Pwaff 69 | data Pwmultiaff 70 | data Multipwaff 71 | 72 | {#enum isl_dim_type as DimType {underscoreToCase} deriving(Eq, Show) #} 73 | {#enum isl_bool as IslBool {underscoreToCase} deriving(Eq, Show) #} 74 | 75 | fromDimType :: DimType -> CInt 76 | fromDimType = fromIntegral . fromEnum 77 | 78 | fromRawIslBool :: CInt -> Maybe Bool 79 | fromRawIslBool i = 80 | case (fromIntegral i) of 81 | -1 -> Nothing 82 | 0 -> Just False 83 | 1 -> Just True 84 | 85 | 86 | -------------------------------------------------------------------------------- /src/ISL/Types.hs: -------------------------------------------------------------------------------- 1 | -- | Types for the high-level interface to isl. 2 | module ISL.Types where 3 | -------------------------------------------------------------------------------- /src/Index.lhs: -------------------------------------------------------------------------------- 1 |

Tiny optimising compiler

2 | 3 | Welcome to the tutorial series that teaches how to write a tiny optimising 4 | compiler in haskell! 5 | 6 | 7 | 8 | Start from: 9 | 10 | 1. [The source language.](language.html) 11 | 12 | 2. [The parser for the language.](parser.html) 13 | 14 | 3. [The internal representation.](ir.html) 15 | 16 | 4. [The `mem2reg` transform that lands us into `SSA`.](transformmem2reg.html) 17 | 18 | 5. [The `constant folding` transform that exploits `SSA` to "fold away" expressions 19 | which can be evaluated at compile time.](transformconstantfolding.html) 20 | 21 | 6. [The `register allocation` transform which allocates physical registers to 22 | the infinite virtual registers of our SSA form.](transformregisterallocate.html) 23 | 24 | 7. [The MIPS assembly specification](mipsasm.html), and the associated interpreter 25 | which uses [SPIM](mipsinterpreter.hs) 26 | 27 | 7. [The `mipsasm` code generation pass which generates MIPS assembly from our IR.](transformirtomips.html) 28 |

Background

29 | 30 | I've wanted to write this for a while: a tiny *optimising* compiler for 31 | a small imperative ish language. 32 | 33 | I want to show off modern compiler ideas, such as: 34 | 35 | - SSA. 36 | - optimisations enabled by SSA. 37 | - Scalar evolution. 38 | - Polyhedral compilation 39 | 40 | I currently have a parser for the source language, conversion to IR, then 41 | to SSA, and a semi-broken MIPS backend. 42 | 43 |

Goals

44 | - Be readable code. 45 | - Be literate code (preferably). 46 | - Show off real world optimisations. 47 | 48 |

Non goals

49 | Shows the correct way of doing a lot of things, in the sense of "engineering". I 50 | might pick the slower algorithm to compute a dominator tree, because I wish to 51 | emphasize the _idea_ of the dominator tree. When a trade off is presented 52 | between simplicity and performance, I will pick simplicity. 53 | 54 | 55 |

Timeline

56 | 57 | - `[x]` Parse 58 | - `[x]` Generate non-SSA IR 59 | - `[x]` Convert non-SSA to SSA (`Mem2Reg` is the pass where this happens.) 60 | - `[x]` generate MIPS assembly from SSA IR (half-done) 61 | - `[ ]` (Optional) generate LLVM for SSA IR (Can be pulled from [simplexhc](http://github.com/bollu/simplexhc)) 62 | 63 | At this point, we have a "functioning" compiler. Now, we can extend the 64 | compiler or the language. I want to show off optimisations, so I will spend 65 | more time implementing optimisations 66 | 67 | - `[ ]` Loop detection. 68 | - `[ ]` Scalar evolution. 69 | - `[ ]` Global value numbering. 70 | - `[ ]` Dead code elimination. 71 | - `[ ]` Loop unrolling. 72 | - `[ ]` invariant load hoisting. 73 | 74 | Note that we do not yet have functions in the language! let's add that. 75 | 76 | - `[ ]` extend language with functions. 77 | - `[ ]` generate MIPS for functions. 78 | - `[ ]` Inlining. 79 | 80 | 81 | If we get here, we can then add polyhedral abilities to the compiler. For 82 | this though, we would need to integrate with `isl`. **Someone** will need to write 83 | haskell bindings `:)`. 84 | 85 | 86 | -------------------------------------------------------------------------------- /src/Language.lhs: -------------------------------------------------------------------------------- 1 | \begin{code} 2 | module Language where 3 | import Data.Text.Prettyprint.Doc as PP 4 | 5 | newtype Literal = Literal { unLiteral :: String } deriving(Ord, Eq) 6 | instance Pretty Literal where 7 | pretty = pretty . unLiteral 8 | 9 | data BinOp = Plus | Multiply | L | And 10 | instance Pretty BinOp where 11 | pretty Plus = pretty "+" 12 | pretty Multiply = pretty "*" 13 | pretty L = pretty "<" 14 | pretty And = pretty "&&" 15 | 16 | data Expr a = EBinOp a (Expr a) BinOp (Expr a) | 17 | EInt a Int | 18 | ELiteral a Literal 19 | 20 | instance Pretty (Expr a) where 21 | pretty (EBinOp _ l op r) = pretty "(" <+> pretty op <+> 22 | pretty l <+> pretty r <+> pretty ")" 23 | pretty (EInt _ i) = pretty i 24 | pretty (ELiteral _ lit) = pretty lit 25 | 26 | type Expr' = Expr () 27 | 28 | data Stmt a = If a (Expr a) (Block a) (Block a) | 29 | While a (Expr a) (Block a) | 30 | Assign a Literal (Expr a) | 31 | Define a Literal | 32 | Return a (Expr a) 33 | type Block a = [Stmt a] 34 | 35 | 36 | nestDepth :: Int 37 | nestDepth = 4 38 | 39 | instance Pretty (Stmt a) where 40 | pretty (If _ cond then' else') = pretty "if" <+> pretty cond <+> 41 | PP.braces (nest 4 (pretty then')) <+> 42 | PP.braces (nest 4 (pretty else')) 43 | 44 | pretty (While _ cond body) = pretty "while" <+> pretty cond <+> PP.braces (nest 4 (pretty body)) 45 | pretty (Assign _ lhs rhs) = pretty "assign" <+> pretty lhs <+> pretty ":=" <+> pretty rhs 46 | pretty (Define _ lit) = pretty "define" <+> pretty lit 47 | pretty (Return _ expr) = pretty "return" <+> pretty expr 48 | 49 | type Stmt' = Stmt () 50 | 51 | 52 | newtype Program a = Program [Stmt a] 53 | type Program' = Program () 54 | 55 | instance Pretty (Program a) where 56 | pretty (Program stmts) = vcat (map pretty stmts) 57 | \end{code} 58 | -------------------------------------------------------------------------------- /src/MIPSAsm.lhs: -------------------------------------------------------------------------------- 1 | \begin{code} 2 | {-# LANGUAGE DeriveAnyClass #-} 3 | {-# LANGUAGE GADTs #-} 4 | {-# LANGUAGE TypeFamilies #-} 5 | 6 | module MIPSAsm(MReg(..), 7 | MRegLabel, 8 | mkTemporaryReg, 9 | MBBLabel, 10 | MBB, 11 | MProgram, 12 | MInst(..), 13 | mkMov, 14 | regToString, 15 | MTerminatorInst(..), 16 | regZero, 17 | rega0, 18 | regv0, 19 | regsp, 20 | printMIPSAsm, 21 | traverseMInstReg, 22 | mapMInstReg, 23 | foldMapMInstReg, 24 | getMInstRegs, 25 | traverseMTerminatorInstReg, 26 | mapMTerminatorInstReg, 27 | getTerminatorInstSuccessor, 28 | MCFG, 29 | mkMCFG ) where 30 | import qualified OrderedMap as M 31 | import Control.Monad.State.Strict 32 | import Data.Traversable 33 | import Data.Foldable 34 | import Control.Applicative 35 | import qualified Data.List.NonEmpty as NE 36 | import BaseIR 37 | import Data.Text.Prettyprint.Doc as PP 38 | import PrettyUtils 39 | import Data.MonoTraversable 40 | import Data.Functor.Identity(Identity(..), runIdentity) 41 | import qualified Data.Monoid as Monoid 42 | import Graph 43 | import Data.Maybe(maybeToList) 44 | 45 | 46 | 47 | type MRegLabel = Label MReg 48 | 49 | -- A register for our machine instructions. 50 | data MReg = MRegVirtual MRegLabel | MRegReal String deriving(Eq, Ord) 51 | 52 | 53 | -- | Convert from a register to a stringified name. 54 | regToString :: MReg -> String 55 | regToString (MRegVirtual (Label name)) = name 56 | regToString (MRegReal name) = name 57 | 58 | regZero :: MReg 59 | regZero = MRegReal "zero" 60 | 61 | rega0 :: MReg 62 | rega0 = MRegReal "a0" 63 | 64 | regv0 :: MReg 65 | regv0 = MRegReal "v0" 66 | 67 | -- | Stack pointer register 68 | regsp :: MReg 69 | regsp = MRegReal "sp" 70 | 71 | 72 | -- | Count from 0. Make the `n`th temporary register. 73 | -- | There are 8 of these. 74 | mkTemporaryReg :: Int -> MReg 75 | mkTemporaryReg n = 76 | if n > 7 || n < 0 77 | then error . docToString $ pretty "expected 0 <= n <= 7, found:" <+> pretty n 78 | else MRegReal ("t" ++ show n) 79 | 80 | 81 | instance Pretty MReg where 82 | pretty (MRegReal name) = pretty "$" PP.<> pretty name 83 | pretty (MRegVirtual i) = pretty "$virt-" PP.<> pretty i 84 | 85 | 86 | data MInst where 87 | Mli :: MReg -> Int -> MInst 88 | Mmflo :: MReg -> MInst 89 | Madd :: MReg -> MReg -> MReg -> MInst 90 | Maddi :: MReg -> MReg -> Int -> MInst 91 | Mori :: MReg -> MReg -> Int -> MInst 92 | Mslt :: MReg -> MReg -> MReg -> MInst 93 | Mslti :: MReg -> MReg -> Int -> MInst 94 | Mmult :: MReg -> MReg -> MInst 95 | -- | Store a register word with an immediate mode offset and a base register. 96 | Msw :: MReg -> Int -> MReg -> MInst 97 | -- | Load into a register from a base register plus an immediate mode offset 98 | Mlw :: MReg -> Int -> MReg -> MInst 99 | Mcomment :: String -> MInst 100 | Msyscall :: MInst 101 | 102 | -- | Move into `dest` from `src 103 | mkMov :: MReg -- ^ Destination register 104 | -> MReg -- ^ Source register 105 | -> MInst 106 | mkMov dest src = Madd dest regZero src 107 | 108 | type instance Element MInst = MReg 109 | 110 | instance MonoFunctor MInst where 111 | omap f (Mli reg i) = Mli (f reg) i 112 | omap f (Mmflo reg) = Mmflo (f reg) 113 | omap f (Madd r1 r2 r3) = Madd (f r1) (f r2) (f r3) 114 | omap f (Maddi r1 r2 i) = Maddi (f r1) (f r2) i 115 | omap f (Mori r1 r2 i) = Mori (f r1) (f r2) i 116 | omap f (Mslt r1 r2 r3) = Mslt (f r1) (f r2) (f r3) 117 | omap f (Mslti r1 r2 i) = Mori (f r1) (f r2) i 118 | omap f (Mmult r1 r2) = Mmult (f r1) (f r2) 119 | omap f (Msw r1 i r2) = Msw (f r1) i (f r2) 120 | omap f (Mlw r1 i r2) = Mlw (f r1) i (f r2) 121 | omap _ (Mcomment s) = Mcomment s 122 | 123 | omap _ Msyscall = Msyscall 124 | 125 | traverseMInstReg :: Applicative f => (MReg -> f MReg) -> MInst -> f MInst 126 | traverseMInstReg f (Mli reg i) = liftA2 Mli (f reg) (pure i) 127 | traverseMInstReg f (Mmflo reg) = Mmflo <$> (f reg) 128 | traverseMInstReg f (Madd r1 r2 r3) = Madd <$> f r1 <*> f r2 <*> f r3 129 | traverseMInstReg f (Maddi r1 r2 i) = Maddi <$> f r1 <*> f r2 <*> pure i 130 | traverseMInstReg f (Mori r1 r2 i) = Mori <$> f r1 <*> f r2 <*> pure i 131 | traverseMInstReg f (Mslt r1 r2 r3) = Mslt <$> f r1 <*> f r2 <*> f r3 132 | traverseMInstReg f (Mslti r1 r2 i) = Mslti <$> f r1 <*> f r2 <*> pure i 133 | traverseMInstReg f (Mmult r1 r2) = Mmult <$> f r1 <*> f r2 134 | traverseMInstReg f (Msw r1 i r2) = Msw <$> f r1 <*> pure i <*> f r2 135 | traverseMInstReg f (Mlw r1 i r2) = Mlw <$> f r1 <*> pure i <*> f r2 136 | traverseMInstReg _ (Mcomment s) = pure (Mcomment s) 137 | traverseMInstReg f Msyscall = pure Msyscall 138 | 139 | mapMInstReg :: (MReg -> MReg) -> MInst -> MInst 140 | mapMInstReg f inst = runIdentity $ traverseMInstReg (Identity . f) inst 141 | 142 | -- | Collect a monoidal value from MReg over an MInst 143 | foldMapMInstReg :: Monoid m => (MReg -> m) -> MInst -> m 144 | foldMapMInstReg f inst = execState final Monoid.mempty where 145 | -- go :: MReg -> State m MReg 146 | go r = do 147 | modify (\m -> m Monoid.<> f r) 148 | return r 149 | 150 | -- final :: State m Inst 151 | final = (traverseMInstReg go inst) 152 | 153 | 154 | foldlMInstReg :: (seed -> MReg -> seed) -> seed -> MInst -> seed 155 | foldlMInstReg f seed inst = execState final seed where 156 | -- go :: Reg -> State seed Reg 157 | go r = do 158 | modify (\seed -> f seed r) 159 | return r 160 | 161 | -- final :: State m MInst 162 | final = traverseMInstReg go inst 163 | 164 | -- | Get the list of MRegs in a MInst 165 | getMInstRegs :: MInst -> [MReg] 166 | getMInstRegs = foldMapMInstReg (\r -> [r]) 167 | 168 | _prettyMBinOp :: (Pretty a, Pretty b, Pretty c) => 169 | String -> a -> b -> c -> PP.Doc doc 170 | _prettyMBinOp name a b c = pretty name <+> pretty a <+> pretty b <+> pretty c 171 | instance Pretty MInst where 172 | pretty (Mli dest val) = pretty "li" <+> pretty dest <+> pretty val 173 | pretty (Mmflo dest) = pretty "mflo" <+> pretty dest 174 | pretty (Madd dest a b) = _prettyMBinOp "add" dest a b 175 | pretty (Maddi dest a b) = _prettyMBinOp "addi" dest a b 176 | pretty (Mori dest a b) = _prettyMBinOp "ori" dest a b 177 | pretty (Mslt dest a b) = _prettyMBinOp "slt" dest a b 178 | pretty (Mslti dest a b) = _prettyMBinOp "slti" dest a b 179 | pretty (Mmult a b) = pretty "mult" <+> pretty a <+> pretty b 180 | -- | Msw $src 20($s0) 181 | pretty (Msw a i b) = pretty "sw" <+> pretty a <+> pretty i PP.<> parens (pretty b) 182 | -- | Msw $dest 20($s0) 183 | pretty (Mlw a i b) = pretty "lw" <+> pretty a <+> pretty i PP.<> parens (pretty b) 184 | pretty (Mcomment s) = pretty "#" <+> pretty s 185 | pretty (Msyscall) = pretty "syscall" 186 | 187 | data MTerminatorInst = 188 | Mexit | 189 | Mj MBBLabel | 190 | Mbeqz MReg MBBLabel | 191 | Mbgtz MReg MBBLabel deriving (Eq, Ord) 192 | 193 | instance Pretty MTerminatorInst where 194 | pretty (Mexit) = pretty "# " 195 | pretty (Mj dest) = pretty "j" <+> pretty dest 196 | pretty (Mbeqz cond dest) = pretty "beqz" <+> pretty cond <+> pretty dest 197 | pretty (Mbgtz cond dest) = pretty "bgtz" <+> pretty cond <+> pretty dest 198 | 199 | traverseMTerminatorInstReg :: Applicative f => (MReg -> f MReg) -> 200 | MTerminatorInst -> f MTerminatorInst 201 | traverseMTerminatorInstReg f Mexit = pure Mexit 202 | traverseMTerminatorInstReg f (Mj lbl) = pure (Mj lbl) 203 | traverseMTerminatorInstReg f (Mbeqz reg lbl) = Mbeqz <$> f reg <*> pure lbl 204 | traverseMTerminatorInstReg f (Mbgtz reg lbl) = Mbgtz <$> f reg <*> pure lbl 205 | 206 | mapMTerminatorInstReg :: (MReg -> MReg) -> MTerminatorInst -> MTerminatorInst 207 | mapMTerminatorInstReg f t = 208 | runIdentity $ traverseMTerminatorInstReg (Identity . f) t 209 | 210 | 211 | type MBBLabel = Label MBB 212 | type MBB = BasicBlock MInst [MTerminatorInst] 213 | type MProgram = Program MInst [MTerminatorInst] 214 | 215 | 216 | type MLiveRangeBB = BasicBlock (Int, MInst) (Int, MTerminatorInst) 217 | 218 | -- | Get the possible successor this terminator instruction will lead to. 219 | getTerminatorInstSuccessor :: MTerminatorInst -> Maybe MBBLabel 220 | getTerminatorInstSuccessor (Mexit) = Nothing 221 | getTerminatorInstSuccessor (Mj lbl) = Just lbl 222 | getTerminatorInstSuccessor (Mbgtz _ lbl) = Just lbl 223 | getTerminatorInstSuccessor (Mbeqz _ lbl) = Just lbl 224 | 225 | -- | Get the successors of this basic block 226 | getMBBSuccessors :: MBB -> [MBBLabel] 227 | getMBBSuccessors bb = bbRetInst bb >>= maybeToList . getTerminatorInstSuccessor 228 | 229 | 230 | type MCFG = Graph MBBLabel 231 | -- | Make a control flow graph 232 | mkMCFG :: M.OrderedMap MBBLabel MBB -> MCFG 233 | mkMCFG bbMap = Graph (M.foldMapWithKey makeEdges bbMap) where 234 | makeEdges :: MBBLabel -> MBB -> [(MBBLabel, MBBLabel)] 235 | makeEdges bbid bb = map (\succ -> (bbid, succ)) (getMBBSuccessors bb) 236 | 237 | 238 | -- | Print a MIPS program into a Doc. Use this to write it into a file. 239 | -- | **Do not use pretty**, because it prints the entry BB as well. 240 | printMIPSAsm :: MProgram -> Doc () 241 | printMIPSAsm Program{programBBMap=bbmap} = vsep $ fmap printBB (M.elems bbmap) 242 | where 243 | printBB :: MBB -> Doc () 244 | printBB (BasicBlock{bbLabel=label, bbInsts=is, bbRetInst=ris}) = 245 | vcat $ 246 | [pretty label <> pretty ":", indent 4 $ vcat(map pretty is ++ map pretty ris)] 247 | 248 | \end{code} 249 | -------------------------------------------------------------------------------- /src/MIPSInterpreter.lhs: -------------------------------------------------------------------------------- 1 | \begin{code} 2 | module MIPSInterpreter ( 3 | interpretMIPSWithSPIM) where 4 | import Data.Text.Prettyprint.Doc 5 | import System.IO(hPutStr, hFlush, Handle, FilePath) 6 | import System.IO.Temp(withSystemTempFile) 7 | import System.Process(readProcessWithExitCode) 8 | import System.Exit(ExitCode(..)) 9 | import MIPSAsm 10 | import Text.Read(readMaybe) 11 | import PrettyUtils 12 | import Safe(lastMay) 13 | type ErrorDoc = Doc () 14 | 15 | -- | Allow for interpreters that try to access state. 16 | interpretMIPSWithSPIM :: MProgram -> IO (Either ErrorDoc Int) 17 | interpretMIPSWithSPIM p = 18 | withSystemTempFile "mipsfile" (\filepath handle -> do 19 | _writeMIPSIntoFile p handle 20 | _runMIPSFromFileWithSPIM filepath) 21 | 22 | 23 | -- | Write MIPS code into the file owned by Handle 24 | _writeMIPSIntoFile :: MProgram -> Handle -> IO () 25 | _writeMIPSIntoFile program handle = do 26 | hPutStr handle (docToString . printMIPSAsm $ program) 27 | hFlush handle 28 | 29 | 30 | -- | Run MIPS code through SPIM with the file. 31 | _runMIPSFromFileWithSPIM :: FilePath -> IO (Either ErrorDoc Int) 32 | _runMIPSFromFileWithSPIM path = do 33 | let stdin = "" 34 | 35 | (exitcode, stdout, stderr) <- readProcessWithExitCode "spim" ["-f", path] stdin 36 | case exitcode of 37 | ExitFailure i -> 38 | return $ Left $ 39 | vcat [pretty "exited with failure code: " <+> pretty i, 40 | pretty "stdout:", 41 | pretty stdout, 42 | pretty "stderr: ", 43 | pretty stderr] 44 | ExitSuccess -> 45 | case lastMay (lines stdout) >>= readMaybe of 46 | Just val -> return $ Right val 47 | Nothing -> return $ Left $ 48 | vcat [pretty "program returned non-integer output:", 49 | pretty "stderr:", 50 | pretty stderr, 51 | pretty "stdout:", 52 | pretty stdout] 53 | \end{code} 54 | -------------------------------------------------------------------------------- /src/Main.lhs: -------------------------------------------------------------------------------- 1 | \begin{code} 2 | module Main where 3 | import Parser 4 | import qualified IR as IR 5 | import IRInterpreter 6 | import qualified Language as Lang 7 | import Data.Text.Prettyprint.Doc 8 | import ProgramToIR 9 | import System.IO 10 | import System.Exit (exitSuccess) 11 | import System.Environment 12 | import TransformMem2Reg 13 | import TransformConstantFolding 14 | import SCEV 15 | import TransformIRToMIPS 16 | import PrettyUtils 17 | import MIPSInterpreter 18 | import TransformRegisterAllocate 19 | import qualified OrderedMap as M 20 | import qualified MIPSAsm as MIPS 21 | 22 | 23 | compileProgram :: Lang.Program a -> IR.IRProgram 24 | compileProgram p = undefined 25 | 26 | pipeline :: [(String, IR.IRProgram -> IR.IRProgram)] 27 | pipeline = [("original", id), 28 | ("mem2reg", transformMem2Reg), 29 | ("constant fold", transformConstantFold)] 30 | 31 | runPasses :: [(String, IR.IRProgram -> IR.IRProgram)] -- ^ Pass pipeline 32 | -> IR.IRProgram -- ^ Current program 33 | -> IO IR.IRProgram -- ^ Final program 34 | runPasses [] p = return p 35 | runPasses ((name, pass):passes) p = do 36 | let p' = pass p 37 | putStrLn . docToString $ pretty "# Running pass " <+> 38 | pretty name 39 | putStrLn . prettyableToString $ p' 40 | putStrLn . docToString $ pretty "- Value:" <+> pretty (runProgram p') 41 | runPasses passes p' 42 | 43 | 44 | 45 | main :: IO () 46 | main = do 47 | args <- getArgs 48 | input <- readFile (args !! 0) 49 | case parseProgram input of 50 | Left err -> putStrLn err 51 | Right program -> do 52 | putStrLn "*** Program:" 53 | putStrLn . prettyableToString $ program 54 | 55 | let irprogram = programToIR program 56 | finalProgram <- runPasses pipeline irprogram 57 | 58 | putStrLn "*** Loops ***" 59 | let loops = detectLoops finalProgram 60 | putStrLn . docToString . vcat . (fmap pretty) $ loops 61 | 62 | exitSuccess 63 | 64 | putStrLn "*** MIPS assembly *** " 65 | let mipsasm = transformRegisterAllocate . transformIRToMIPS $ finalProgram 66 | putStrLn . docToString . MIPS.printMIPSAsm $ mipsasm 67 | -- putStrLn . docToString . MIPS.unASMDoc . MIPS.generateASM $ finalProgram 68 | 69 | putStrLn "*** Output from SPIM *** " 70 | mProgramOutput <- interpretMIPSWithSPIM mipsasm 71 | case mProgramOutput of 72 | Left err -> putStrLn . docToString $ err 73 | Right val -> putStrLn . docToString $ (pretty "final value: " <+> pretty val) 74 | \end{code} 75 | -------------------------------------------------------------------------------- /src/OrderedMap.lhs: -------------------------------------------------------------------------------- 1 | \begin{code} 2 | {-# LANGUAGE DeriveFunctor #-} 3 | {-# LANGUAGE DeriveFoldable #-} 4 | {-# LANGUAGE DeriveTraversable #-} 5 | {-# LANGUAGE InstanceSigs #-} 6 | {-# LANGUAGE RecordWildCards #-} 7 | module OrderedMap(OrderedMap, 8 | fromList, 9 | size, 10 | adjust, 11 | adjustWithKey, 12 | insert, 13 | insertWith, 14 | elems, 15 | toList, 16 | keys, 17 | editKeys, 18 | (!), 19 | union, 20 | fromListWith, 21 | foldMapWithKey, 22 | foldlWithKey, 23 | mapWithKey, 24 | OrderedMap.lookup, 25 | delete) where 26 | import qualified Data.Map.Strict as M 27 | import Control.Applicative(liftA2) 28 | import qualified Control.Arrow as A 29 | import Data.Monoid 30 | import PrettyUtils 31 | import Data.Text.Prettyprint.Doc 32 | import qualified Data.List as L 33 | 34 | -- At some point, I need this. This is more convenient than overloading the key to store the insertion time. 35 | -- | A dictionary that orders elements by insertion time 36 | data OrderedMap k v = OrderedMap { map' :: M.Map k v, order :: [k] } deriving(Show, Functor, Eq) 37 | 38 | instance (Ord k, Pretty k) => Foldable (OrderedMap k) where 39 | foldMap f omap = foldMap f (map snd . toList $ omap) 40 | 41 | instance (Ord k, Pretty k) => Traversable (OrderedMap k) where 42 | traverse f omap = fmap fromList (traverse ((\(k, v) -> liftA2 (,) (pure k) (f v))) (toList omap)) 43 | 44 | instance (Ord k, Pretty k, Pretty v) => Pretty (OrderedMap k v) where 45 | pretty (OrderedMap _ []) = pretty "empty map" 46 | pretty ok = indent 2 (vcat (map pkv (toList ok))) where 47 | pkv :: (Pretty k, Pretty v) => (k, v) -> Doc ann 48 | pkv (k, v) = pretty k <+> pretty " => " <+> pretty v 49 | 50 | instance Ord k => Monoid (OrderedMap k v) where 51 | mempty :: OrderedMap k v 52 | mempty = OrderedMap mempty mempty 53 | 54 | mappend :: OrderedMap k v -> OrderedMap k v -> OrderedMap k v 55 | mappend (OrderedMap m o) (OrderedMap m' o') = OrderedMap (m `mappend` m') (o `mappend` o') 56 | 57 | liftMapEdit_ :: (M.Map k v -> M.Map k v') -> OrderedMap k v -> OrderedMap k v' 58 | liftMapEdit_ f (OrderedMap map' order) = OrderedMap (f map') order 59 | 60 | liftMapExtract_ :: (M.Map k v -> a) -> OrderedMap k v -> a 61 | liftMapExtract_ f (OrderedMap map' _) = f map' 62 | 63 | -- | NOTE: this will maintain the order of insertion. Elements that are inserted 64 | -- | later are returned later in the `keys`, `elems`. 65 | insert :: Ord k => k -> a -> OrderedMap k a -> OrderedMap k a 66 | insert k a om@OrderedMap{..} = case (liftMapExtract_ (M.lookup k)) om of 67 | Nothing -> OrderedMap (M.insert k a map') (order ++ [k]) 68 | -- If the key already exists, keep the old order 69 | _ -> OrderedMap (M.insert k a map') (order) 70 | 71 | -- | NOTE: this will maintain the order of insertion. Elements that are inserted 72 | -- | later are returned later in the `keys`, `elems`. 73 | insertWith :: Ord k => (a -> a -> a) -> k -> a -> OrderedMap k a -> OrderedMap k a 74 | insertWith combiner k a om@OrderedMap{..} = 75 | case (liftMapExtract_ (M.lookup k)) om of 76 | Nothing -> OrderedMap (M.insertWith combiner k a map') (order ++ [k]) 77 | -- If the key already exists, keep the old order 78 | _ -> OrderedMap (M.insertWith combiner k a map') (order) 79 | 80 | lookup :: Ord k => k -> OrderedMap k a -> Maybe a 81 | lookup k = liftMapExtract_ (M.lookup k) 82 | 83 | fromList :: Ord k => [(k, a)] -> OrderedMap k a 84 | fromList kv = OrderedMap (M.fromList kv) (map fst kv) 85 | 86 | size :: OrderedMap k a -> Int 87 | size = liftMapExtract_ M.size 88 | 89 | keys :: OrderedMap k a -> [k] 90 | keys = order 91 | 92 | index_ :: (Ord k) => OrderedMap k a -> k -> a 93 | index_ omap k = case OrderedMap.lookup k omap of 94 | Just a -> a 95 | Nothing -> error . docToString $ 96 | vcat [pretty "Omap is in inconstent state."] 97 | 98 | elems :: (Ord k, Pretty k, Pretty a) => OrderedMap k a -> [a] 99 | elems omap = map (index_ omap) (keys omap) where 100 | 101 | union :: (Eq k, Ord k) => OrderedMap k a -> OrderedMap k a -> OrderedMap k a 102 | union (OrderedMap{order=o1, map'=m1}) (OrderedMap{order=o2, map'=m2}) = 103 | OrderedMap{map'=m1 `M.union` m2, order=L.nub(o1++o2)} 104 | 105 | -- | Return the list of key value pairs in the order of insertion. 106 | toList :: (Ord k) => OrderedMap k a -> [(k, a)] 107 | toList omap = map (\k -> (k, index_ omap k)) (keys omap) 108 | 109 | adjust :: Ord k => (a -> a) -> k -> OrderedMap k a -> OrderedMap k a 110 | adjust f k = liftMapEdit_ (M.adjust f k) 111 | 112 | adjustWithKey :: Ord k => (k -> a -> a) -> k -> OrderedMap k a -> OrderedMap k a 113 | adjustWithKey f k = liftMapEdit_ (M.adjustWithKey f k) 114 | 115 | (!) :: (Ord k, Pretty k, Pretty a) => OrderedMap k a -> k -> a 116 | ok ! k = 117 | case (OrderedMap.lookup k ok) of 118 | Just a -> a 119 | Nothing -> error . docToString $ 120 | vcat [pretty "key missing, has no value associated with it: " <+> pretty k, 121 | pretty "map:", 122 | indent 4 (pretty ok), 123 | pretty "---"] 124 | 125 | foldMapWithKey :: Monoid m => (k -> a -> m) -> OrderedMap k a -> m 126 | foldMapWithKey f = liftMapExtract_ (M.foldMapWithKey f) 127 | 128 | fromListWith :: Ord k => (a -> a -> a) -> [(k, a)] -> OrderedMap k a 129 | fromListWith f kvs = OrderedMap {order=fmap fst kvs, map'=M.fromListWith f kvs} 130 | 131 | foldlWithKey :: (a -> k -> b -> a) -> a -> OrderedMap k b -> a 132 | foldlWithKey f a = liftMapExtract_ (M.foldlWithKey f a) 133 | 134 | mapWithKey :: (k -> a -> b) -> OrderedMap k a -> OrderedMap k b 135 | mapWithKey f = liftMapEdit_ (M.mapWithKey f) 136 | 137 | -- | Change the keys of the map, without changing the order. 138 | editKeys :: (Ord k, Ord k') => (k -> k') -> OrderedMap k a -> OrderedMap k' a 139 | editKeys f = fromList . map (f A.*** id) . toList 140 | 141 | 142 | delete :: Ord k => k -> OrderedMap k a -> OrderedMap k a 143 | delete key omap@OrderedMap{..} = OrderedMap {order=L.delete key order, map'=M.delete key map' } 144 | \end{code} 145 | -------------------------------------------------------------------------------- /src/Parser.lhs: -------------------------------------------------------------------------------- 1 |

Parser

2 | In this module, we define the parser for our source language. We use 3 | `trifecta` as our parser, and we import all our parser combinators from 4 | the `parsers` package. 5 | 6 | This module can be considered as a quick tutorial to `trifecta`. 7 | \begin{code} 8 | module Parser where 9 | 10 | import Language 11 | import Control.Monad (void) 12 | 13 | import Control.Applicative 14 | import Data.HashSet as HashSet 15 | 16 | import Text.Trifecta as TR 17 | import Text.Parser.Token.Highlight 18 | import Text.Parser.Token.Style 19 | import Text.Trifecta.Delta 20 | 21 | import Text.Parser.Char 22 | import Text.Parser.Combinators 23 | import Text.Parser.Token 24 | import Text.Parser.Expression 25 | import Text.Parser.Token (TokenParsing, natural, parens, reserve) 26 | import Text.Parser.Token.Style (emptyOps) 27 | 28 | 29 | import Data.ByteString.Char8 as BS 30 | import qualified Text.PrettyPrint.ANSI.Leijen as TrifectaPP 31 | \end{code} 32 | 33 | 34 | \begin{code} 35 | -- import Data.Text.Prettyprint.Doc as PP 36 | () = flip () 37 | 38 | \end{code} 39 | 40 |

Parsing identifiers

41 | 42 | `trifecta` needs us to tell it what the reserved keywords of our 43 | language are so it can skip those strings. To parse identifiers, we need 44 | three main components: 45 | 46 | - `_styleStart`, which is the characters that can act as the starting character 47 | of our identifier. 48 | 49 | - `_styleLetter`, which the parser will consume greedily when it sees a 50 | `_styleStart` 51 | 52 | - `_styleReserved`, which are strings that should *not* be considered identifiers 53 | because these are reserved keywords. 54 | 55 | \begin{code} 56 | -- | Syntax rules for parsing variable-looking like identifiers. 57 | identStyle :: IdentifierStyle Parser 58 | identStyle = IdentifierStyle 59 | { _styleName = "variable" 60 | , _styleStart = lower <|> char '_' 61 | , _styleLetter = alphaNum <|> oneOf "_'#" 62 | , _styleReserved = HashSet.fromList ["define", "assign", "if", "else", "return", "*", "+", "<", "&&"] 63 | , _styleHighlight = Identifier 64 | , _styleReservedHighlight = ReservedIdentifier } 65 | \end{code} 66 | 67 |

Standard parsers

68 | 69 | The only point of interest here is that we choose to name our parsers with the 70 | `` combinator, which is used to provide better error messages. 71 | 72 | \begin{code} 73 | -- | Parse a variable identifier. Variables start with a lower-case letter or 74 | -- @_@, followed by a string consisting of alphanumeric characters or @'@, @_@. 75 | litp :: Parser Literal 76 | litp = "varname" (Literal <$> (ident identStyle)) 77 | 78 | 79 | intp :: Parser Int 80 | intp = fromIntegral <$> integer 81 | 82 | boolp :: Parser Bool 83 | boolp = ((const True) <$> symbol "true") <|> ((const False) <$> symbol "false") 84 | 85 | term :: Parser Expr' 86 | term = (Text.Parser.Token.parens exprp 87 | <|> ELiteral () <$> litp <|> EInt () <$> intp) "simple expression" 88 | \end{code} 89 | 90 | 91 |

Expression Parsing

92 | 93 | Expression parsing is also very nice in `trifecta`, as one can create a table 94 | of operators with their priority and associativities and have that "just work". 95 | 96 | \begin{code} 97 | table :: [[Operator Parser Expr']] 98 | table = [[binary "*" Multiply AssocLeft], 99 | [binary "+" Plus AssocLeft], 100 | [binary "<" L AssocLeft], 101 | [binary "&&" And AssocLeft]] 102 | 103 | binary :: String -> BinOp -> Assoc -> Operator Parser Expr' 104 | binary name op assoc = Infix p assoc where 105 | p :: Parser (Expr' -> Expr' -> Expr') 106 | p = do 107 | reserve identStyle name 108 | return $ mkBinopExpr op 109 | mkBinopExpr :: BinOp -> Expr' -> Expr' -> Expr' 110 | mkBinopExpr op lhs rhs = EBinOp () lhs op rhs 111 | 112 | binopp :: Parser Expr' 113 | binopp = buildExpressionParser table term 114 | 115 | exprp :: Parser Expr' 116 | exprp = binopp 117 | 118 | ifp :: Parser Stmt' 119 | ifp = do 120 | symbol "if" 121 | e <- exprp 122 | symbol "{" 123 | thenstmts <- sepEndBy stmtp (symbol ";") 124 | symbol "}" 125 | symbol "else" 126 | 127 | symbol "{" 128 | elsestmts <- sepEndBy stmtp (symbol ";") 129 | symbol "}" 130 | return $ If () e thenstmts elsestmts 131 | 132 | whilep :: Parser Stmt' 133 | whilep = do 134 | symbol "while" 135 | e <- exprp 136 | symbol "{" 137 | stmts <- sepEndBy stmtp (symbol ";") 138 | symbol "}" 139 | return $ While () e stmts 140 | 141 | 142 | assignp :: Parser Stmt' 143 | assignp = do 144 | symbol "assign" 145 | name <- litp 146 | symbol ":=" 147 | rhs <- exprp 148 | return $ Assign () name rhs 149 | 150 | definep :: Parser Stmt' 151 | definep = do 152 | symbol "define" 153 | name <- litp 154 | return $ Define () name 155 | 156 | retp :: Parser Stmt' 157 | retp = do 158 | symbol "return" 159 | retexpr <- exprp 160 | return $ Return () retexpr 161 | 162 | stmtp :: Parser Stmt' 163 | stmtp = ifp <|> whilep <|> assignp <|> definep <|> retp 164 | 165 | programp :: Parser Program' 166 | programp = Program <$> sepEndBy1 stmtp (symbol ";") 167 | 168 | 169 | -- vLow level interface to trifecta 170 | parseProgram_ :: String -> Result Program' 171 | parseProgram_ string = TR.parseString (spaces *> programp) (Directed (BS.pack string) 0 0 0 0) string 172 | 173 | 174 | 175 | -- v High level interface 176 | type ErrorString = String 177 | parseProgram :: String -> Either ErrorString Program' 178 | parseProgram str = case parseProgram_ str of 179 | Success a -> Right a 180 | Failure ErrInfo{ _errDoc = e } -> Left (TrifectaPP.displayS (TrifectaPP.renderPretty 0.8 80 e) "") 181 | \end{code} 182 | -------------------------------------------------------------------------------- /src/PrettyUtils.lhs: -------------------------------------------------------------------------------- 1 | \begin{code} 2 | module PrettyUtils where 3 | import Data.Text.Prettyprint.Doc.Render.Text 4 | import Data.Text.Prettyprint.Doc 5 | import qualified Data.Text.Lazy as L 6 | 7 | docToText :: Doc ann -> L.Text 8 | docToText doc = renderLazy (layoutPretty defaultLayoutOptions doc) 9 | 10 | docToString :: Doc ann -> String 11 | docToString = L.unpack . docToText 12 | 13 | prettyableToText :: Pretty a => a -> L.Text 14 | prettyableToText a = docToText (pretty a) 15 | 16 | prettyableToString :: Pretty a => a -> String 17 | prettyableToString a = docToString (pretty a) 18 | \end{code} 19 | -------------------------------------------------------------------------------- /src/ProgramToIR.lhs: -------------------------------------------------------------------------------- 1 | \begin{code} 2 | module ProgramToIR where 3 | import Language 4 | import IR 5 | import BaseIR 6 | import qualified OrderedMap as M 7 | import Data.Traversable 8 | import Data.Foldable 9 | import Control.Monad.State.Strict 10 | import qualified Data.Tree as T 11 | import PrettyUtils 12 | import Data.Text.Prettyprint.Doc as PP 13 | 14 | data Builder = Builder { 15 | -- | The first BB that is present in the module 16 | entryBBId :: IRBBId, 17 | -- | The BB the builder is currently focused on 18 | currentBBId :: IRBBId, 19 | -- | Mapping from BBId to IRBB 20 | bbIdToBB :: M.OrderedMap IRBBId IRBB, 21 | -- | counter to generate new instruction name 22 | tmpInstNamesCounter :: Int, 23 | -- | Map from name to count of number of times name has occured 24 | instNameCounter :: M.OrderedMap String Int, 25 | -- | Map from literal name to Value 26 | literalToValue :: M.OrderedMap Literal Value 27 | } 28 | 29 | -- | Create a new builder with an empty basic block 30 | newBuilder :: Builder 31 | newBuilder = 32 | execState mkDefaultBB initbuilder 33 | where 34 | mkDefaultBB = do 35 | bbid <- createNewBB (Label "default") 36 | focusBB bbid 37 | -- Set the "entry" basic block so we can later give it to IRProgram 38 | modify (\b -> b { entryBBId = bbid }) 39 | 40 | initbuilder = (Builder { 41 | entryBBId = Label "", 42 | currentBBId = Label "", 43 | bbIdToBB = mempty, 44 | tmpInstNamesCounter=0, 45 | instNameCounter=mempty, 46 | literalToValue=mempty 47 | }) 48 | 49 | -- | Get the current Basic block ID 50 | getCurrentBBId :: State Builder IRBBId 51 | getCurrentBBId = gets currentBBId 52 | 53 | -- | Focus the basic block given by the ID 54 | focusBB :: IRBBId -> State Builder () 55 | focusBB id = modify (\b-> b { currentBBId=id }) 56 | 57 | -- | Append a new basic block. DOES NOT switch the currentBBId to the new basic block. For that, see focusBB 58 | createNewBB :: Label Builder -> State Builder IRBBId 59 | createNewBB name = do 60 | idtobbs <- gets bbIdToBB 61 | let nbbs = M.size idtobbs 62 | let nameunique = Label ((unLabel name) ++ "." ++ show nbbs) 63 | let newbb = defaultIRBB { bbLabel=nameunique } 64 | modify (\b -> b { bbIdToBB = M.insert nameunique newbb idtobbs } ) 65 | return nameunique 66 | 67 | 68 | -- | Create a temporary instruction name. 69 | getTempInstName :: State Builder (Label Inst) 70 | getTempInstName = do 71 | n <- gets tmpInstNamesCounter 72 | modify (\b -> b { tmpInstNamesCounter=n+1 }) 73 | return . Label $ "tmp." ++ show n 74 | 75 | getUniqueInstName :: String -> State Builder (Label Inst) 76 | getUniqueInstName s = do 77 | counts <- gets instNameCounter 78 | let instNameCounter' = M.insertWith (\_ old -> old + 1) s 0 counts 79 | modify (\b -> b {instNameCounter=instNameCounter' }) 80 | 81 | let curcount = case M.lookup s instNameCounter' of 82 | Just count -> count 83 | Nothing -> error . docToString $ pretty "no count present for: " <+> pretty s 84 | if curcount == 0 85 | then return (Label s) 86 | else return (Label (s ++ "." ++ show curcount)) 87 | 88 | 89 | 90 | -- | Create a temporary name for a return instruction 91 | -- | Note that we cheat in the implementation, by just "relabelling" 92 | -- | an instruction label to a ret instruction label. 93 | getTempRetInstName :: State Builder (Label RetInst) 94 | getTempRetInstName = Label . unLabel <$> getTempInstName 95 | 96 | -- | Add a mapping between literal and value. 97 | mapLiteralToValue :: Literal -> Value -> State Builder () 98 | mapLiteralToValue l v = do 99 | ltov <- gets literalToValue 100 | -- TODO: check that we do not repeat literals. 101 | modify (\b -> b { literalToValue=M.insert l v ltov }) 102 | return () 103 | 104 | -- | Get the value that the Literal maps to. 105 | getLiteralValueMapping :: Literal -> State Builder Value 106 | getLiteralValueMapping lit = do 107 | ltov <- gets literalToValue 108 | return $ ltov M.! lit 109 | 110 | -- | lift an edit of a basic block to the current basic block focused 111 | -- | in the Builder. 112 | liftBBEdit :: (IRBB -> IRBB) -> Builder -> Builder 113 | liftBBEdit f builder = builder { 114 | bbIdToBB = M.adjust f (currentBBId builder) (bbIdToBB builder) 115 | } 116 | 117 | -- | Set the builder's current basic block to the i'th basic block 118 | setBB :: Builder -> IRBBId -> Builder 119 | setBB builder i = builder { 120 | currentBBId = i 121 | } 122 | 123 | 124 | -- | Append instruction "I" to the builder 125 | appendInst :: Named Inst -> State Builder Value 126 | appendInst i = do 127 | modify . liftBBEdit $ (appendInstToBB i) 128 | return $ ValueInstRef (namedName i) 129 | where 130 | appendInstToBB :: Named Inst -> IRBB -> IRBB 131 | appendInstToBB i bb = bb { bbInsts=bbInsts bb ++ [i] } 132 | 133 | setRetInst :: RetInst -> State Builder () 134 | setRetInst i = do 135 | modify . liftBBEdit $ (setBBRetInst i) 136 | where 137 | setBBRetInst :: RetInst -> IRBB -> IRBB 138 | setBBRetInst i bb = bb { bbRetInst=i } 139 | 140 | 141 | mkBinOpInst :: Value -> BinOp -> Value -> Inst 142 | mkBinOpInst lhs Plus rhs = InstAdd lhs rhs 143 | mkBinOpInst lhs Multiply rhs = InstMul lhs rhs 144 | mkBinOpInst lhs L rhs = InstL lhs rhs 145 | mkBinOpInst lhs And rhs = InstAnd lhs rhs 146 | 147 | buildExpr :: Expr' -> State Builder Value 148 | buildExpr (EInt _ i) = return $ ValueConstInt i 149 | buildExpr (ELiteral _ lit) = do 150 | name <- getUniqueInstName $ unLiteral lit ++ ".load" 151 | val <- getLiteralValueMapping lit 152 | appendInst $ name =:= InstLoad val 153 | 154 | buildExpr (EBinOp _ lhs op rhs) = do 155 | lhs <- buildExpr lhs 156 | rhs <- buildExpr rhs 157 | let inst = (mkBinOpInst lhs op rhs) 158 | name <- getTempInstName 159 | -- TODO: generate fresh labels 160 | appendInst $ name =:= inst 161 | 162 | -- | Build the IR for the assignment, and return a reference to @InstStore 163 | -- | TODO: technically, store should not return a Value 164 | buildAssign :: Literal -> Expr' -> State Builder Value 165 | buildAssign lit expr = do 166 | exprval <- buildExpr expr 167 | litval <- getLiteralValueMapping lit 168 | name <- getUniqueInstName $ "_" 169 | -- TODO: do not allow Store to be named with type system trickery 170 | appendInst $ name =:= InstStore litval exprval 171 | return $ ValueInstRef name 172 | 173 | -- | Build IR for "define x" 174 | buildDefine :: Literal -> State Builder Value 175 | buildDefine lit = do 176 | name <- getUniqueInstName . unLiteral $ lit 177 | mapLiteralToValue lit (ValueInstRef name) 178 | appendInst $ name =:= InstAlloc 179 | 180 | -- | Build IR for "Return" 181 | buildRet :: Expr' -> State Builder () 182 | buildRet retexpr = do 183 | retval <- buildExpr retexpr 184 | setRetInst $ RetInstRet retval 185 | 186 | -- | Build IR for "Stmt" 187 | buildStmt :: Stmt' -> State Builder () 188 | buildStmt (Define _ lit) = buildDefine lit >> return () 189 | buildStmt (Assign _ lit expr) = buildAssign lit expr >> return () 190 | buildStmt (If _ cond then' else') = do 191 | condval <- buildExpr cond 192 | currbb <- getCurrentBBId 193 | 194 | 195 | bbthen <- createNewBB (Label "then") 196 | focusBB bbthen 197 | stmtsToInsts then' 198 | 199 | bbelse <- createNewBB (Label "else") 200 | focusBB bbelse 201 | stmtsToInsts else' 202 | 203 | bbjoin <- createNewBB (Label "join") 204 | focusBB bbthen 205 | setRetInst $ RetInstBranch bbjoin 206 | 207 | focusBB bbelse 208 | setRetInst $ RetInstBranch bbjoin 209 | 210 | focusBB currbb 211 | setRetInst $ RetInstConditionalBranch condval bbthen bbelse 212 | 213 | focusBB bbjoin 214 | 215 | buildStmt (While _ cond body) = do 216 | curbb <- getCurrentBBId 217 | condbb <- createNewBB (Label "while.cond") 218 | bodybb <- createNewBB (Label "while.body") 219 | endbb <- createNewBB (Label "while.end") 220 | 221 | focusBB condbb 222 | condval <- buildExpr cond 223 | setRetInst $ RetInstConditionalBranch condval bodybb endbb 224 | 225 | focusBB bodybb 226 | stmtsToInsts body 227 | setRetInst $ RetInstBranch condbb 228 | 229 | focusBB curbb 230 | setRetInst $ RetInstBranch condbb 231 | 232 | focusBB endbb 233 | 234 | buildStmt (Return _ retexpr) = buildRet retexpr 235 | 236 | -- Given a collection of statements, create a State that will create these 237 | -- statements in the builder 238 | stmtsToInsts :: [Stmt'] -> State Builder () 239 | stmtsToInsts stmts = (for_ stmts buildStmt) 240 | 241 | 242 | programToIR :: Program' -> IRProgram 243 | programToIR (Language.Program stmts) = 244 | BaseIR.Program { 245 | programBBMap = bbIdToBB builder, 246 | programEntryBBId = entryBBId builder 247 | } where 248 | builder = execState (stmtsToInsts stmts) newBuilder 249 | \end{code} 250 | -------------------------------------------------------------------------------- /src/SCEV.lhs: -------------------------------------------------------------------------------- 1 |

SCEV, or, how do we analyze loops?

2 | 3 |

Equivalent LLVM passes

4 | 5 | - [SCEV](http://llvm.org/doxygen/classllvm_1_1ScalarEvolution.html) 6 | 7 | 8 |

Introduction

9 | 10 | SCEV is an analysis which allows us to understand recurrences across loops. 11 | 12 |

References

13 | 14 | http://www.csd.uwo.ca/~moreno/CS447/Lectures/CodeOptimization.html/node6.html 15 | http://web.cs.wpi.edu/~kal/PLT/PLT8.6.4.html 16 | 17 | \begin{code} 18 | {-# LANGUAGE TupleSections #-} 19 | {-# LANGUAGE RecordWildCards #-} 20 | {-# LANGUAGE ScopedTypeVariables #-} 21 | {-# LANGUAGE DeriveFunctor #-} 22 | 23 | module SCEV(analyzeSCEV, detectLoops) where 24 | 25 | import IR 26 | import BaseIR 27 | import Data.Tree 28 | import qualified Data.Set as S 29 | import qualified OrderedMap as M 30 | import Data.Text.Prettyprint.Doc as PP 31 | import PrettyUtils 32 | import Control.Monad.Reader 33 | import Data.Traversable 34 | import qualified Data.Monoid as Monoid 35 | import qualified Data.Set as S 36 | import qualified Data.List.NonEmpty as NE 37 | import Control.Monad.State.Strict 38 | import TransformMem2Reg 39 | import Graph 40 | 41 | data Loop = Loop { 42 | loopHeader :: IRBBId, 43 | loopBackEdges :: [(IRBBId, IRBBId)], 44 | loopLatches :: [IRBBId] 45 | } 46 | 47 | instance Pretty Loop where 48 | pretty Loop{..} = 49 | vsep [pheader, nest 4 platch, nest 4 pbackedges] where 50 | pheader = (pretty "header:") <+> (pretty loopHeader) 51 | platch = vcat [pretty "latches:", 52 | nest 4 (vcat (fmap (pretty) loopLatches))] 53 | pbackedges = vcat [pretty "backedges:", 54 | nest 4 $ vcat (fmap pretty loopBackEdges)] 55 | 56 | 57 | -- | Returns if the given edge is a back-edge 58 | -- | An edge (Start -> End) is a back edge if End dominates Start 59 | -- | Perform this operation by checking if End belongs to Start's Domset. 60 | isBackEdge :: BBIdToDomSet -> (IRBBId, IRBBId) -> Bool 61 | isBackEdge bbIdToDomSet (start, end) = end `S.member` (bbIdToDomSet M.! start) 62 | 63 | 64 | 65 | _detectLoopsRec :: M.OrderedMap IRBBId IRBB -- ^ Basic Blocks in program 66 | -> BBIdToDomSet -- ^ Mapping from basic blocks to nodes 67 | -- that dominate it 68 | -> DomTree -- ^ Domtree of program 69 | -> CFG -- ^ CFG of program 70 | -> IRBBId -- ^Current Basic block being inspected 71 | -> [Loop] -- ^ List of loops 72 | _detectLoopsRec bbmap bbIdToDomSet domtree cfg curbbid = 73 | curloop ++ (domtreechildren >>= _detectLoopsRec bbmap bbIdToDomSet domtree cfg) 74 | where 75 | domtreechildren :: [IRBBId] 76 | domtreechildren = getImmediateChildren domtree curbbid 77 | 78 | -- | next nodes in the CFG from the current node 79 | cfgnext :: [(IRBBId, IRBBId)] 80 | cfgnext = getEdgesFromSource cfg curbbid 81 | 82 | -- | backedges from the CFG 83 | backedges :: [(IRBBId, IRBBId)] 84 | backedges = filter (isBackEdge bbIdToDomSet) cfgnext 85 | 86 | -- | current loop if it exists 87 | curloop :: [Loop] 88 | curloop = if null backedges 89 | then [] 90 | else [Loop { 91 | loopHeader=curbbid, 92 | loopLatches= map fst backedges, 93 | loopBackEdges=backedges 94 | }] 95 | 96 | 97 | 98 | detectLoops :: IRProgram -> [Loop] 99 | detectLoops program@Program{programBBMap=bbmap, 100 | programEntryBBId=entrybbid} = 101 | _detectLoopsRec bbmap bbIdToDomSet domtree cfg entrybbid where 102 | bbIdToDomSet :: BBIdToDomSet 103 | bbIdToDomSet = constructBBDominators program 104 | 105 | domtree :: DomTree 106 | domtree = constructDominatorTree bbIdToDomSet entrybbid 107 | 108 | cfg :: CFG 109 | cfg = mkCFG bbmap 110 | 111 | 112 | -- | Chain of recurrences. 113 | data SCEV = SCEV 114 | 115 | type SCEVMap = M.OrderedMap (Label Inst) SCEV 116 | 117 | 118 | analyzeSCEV :: IRProgram -> SCEVMap 119 | analyzeSCEV = undefined 120 | \end{code} 121 | -------------------------------------------------------------------------------- /src/TransformConstantFolding.lhs: -------------------------------------------------------------------------------- 1 |

Transform Pass: Constant Folding

2 | 3 | - [Equivalent LLVM pass](https://llvm.org/docs/Passes.html#constprop-simple-constant-propagation) 4 | 5 |

Introduction

6 | 7 | In this pass, we remove all instructions we can evaluate at compile-time. 8 | This includes arithmetic and boolean operators. 9 | 10 | The idea is really simple: scan basic blocks, and if an instruction can be 11 | immediately evaluated, do so. 12 | 13 | Note that for this pass to be as easy as it is, **SSA is crucial**. 14 | 15 | Consider this snippet of code: 16 | 17 | ``` 18 | define x; 19 | assign x := 10; 20 | assign x := x + 42; 21 | assign x := x * 10 22 | return x; 23 | ``` 24 | 25 | and the associated `load/store` based IR: 26 | 27 | ``` 28 | entry: default.0 29 | program: 30 | default.0: 31 | x := alloc 32 | _ := store 10# in %x 33 | x.load := load %x 34 | tmp.0 := add %x.load 42# 35 | _.1 := store %tmp.0 in %x 36 | x.load.1 := load %x 37 | tmp.1 := mul %x.load.1 10# 38 | _.2 := store %tmp.1 in %x 39 | TERMINAL 40 | ``` 41 | 42 | We cannot simply replace `x` with `10` due to the mutation happening on x! 43 | 44 | Now, consider the SSA form of the same computation: 45 | 46 | ``` 47 | entry: default.0 48 | program: 49 | default.0: 50 | tmp.0 := add 10# 42# 51 | tmp.1 := mul %tmp.0 10# 52 | TERMINAL 53 | ``` 54 | 55 | Due to the *immutable* nature of SSA, we are guaranteed that we can replace all 56 | occurences of a variable with it's RHS, and the semantics of the program will 57 | remain the same! (AKA [equational reasoning](https://wiki.haskell.org/Equational_reasoning_examples)). 58 | 59 | This is enormously powerful because it allows to replace values with wild abandon `:)`. 60 | 61 |

Key Takeaway of this pass

62 | 63 | - SSA, due to immutability enables equational reasoning. 64 | - This allows us to perform transformations such as 65 | constant folding very easily. 66 | 67 | 68 | 69 | \begin{code} 70 | {-# LANGUAGE ViewPatterns #-} 71 | 72 | module TransformConstantFolding where 73 | import qualified OrderedMap as M 74 | import Control.Monad.State.Strict 75 | import Data.Traversable 76 | import Data.Foldable 77 | import Control.Applicative 78 | import qualified Data.List.NonEmpty as NE 79 | import IR 80 | import BaseIR 81 | import Data.Text.Prettyprint.Doc as PP 82 | import PrettyUtils 83 | 84 | boolToInt :: Bool -> Int 85 | boolToInt False = 0 86 | boolToInt True = 1 87 | 88 | -- | Fold all possible arithmetic / boolean ops 89 | tryFoldInst :: Inst -> Maybe Value 90 | tryFoldInst (InstAdd (ValueConstInt i) (ValueConstInt j)) = 91 | Just $ ValueConstInt (i + j) 92 | tryFoldInst (InstMul (ValueConstInt i) (ValueConstInt j)) = 93 | Just $ ValueConstInt (i * j) 94 | tryFoldInst (InstL (ValueConstInt i) (ValueConstInt j)) = 95 | Just $ ValueConstInt $ boolToInt (i < j) 96 | 97 | tryFoldInst (InstAnd (ValueConstInt i) (ValueConstInt j)) = 98 | Just $ ValueConstInt (i * j) 99 | tryFoldInst i = Nothing 100 | 101 | collectFoldableInsts :: Named Inst -> [(Label Inst, Value)] 102 | collectFoldableInsts (Named name (tryFoldInst -> Just v)) = [(name, v)] 103 | collectFoldableInsts _ = [] 104 | 105 | runTillStable :: Eq a => (a -> a) -> a -> a 106 | runTillStable f a = let a' = f a in 107 | if a' == a 108 | then a' 109 | else f a' 110 | 111 | transformConstantFold :: IRProgram -> IRProgram 112 | transformConstantFold = dceProgram . (runTillStable foldProgram) where 113 | 114 | -- | Collection of instruction names and values 115 | foldableInsts :: IRProgram -> [(Label Inst, Value)] 116 | foldableInsts p = foldMapProgramBBs (foldMapBB (collectFoldableInsts) (const mempty)) p 117 | 118 | -- | Program after constant folding 119 | foldProgram :: IRProgram -> IRProgram 120 | foldProgram program = foldl (\p (name, v) -> replaceUsesOfInst name v p) program (foldableInsts program) 121 | 122 | -- | program after dead code elimination 123 | dceProgram :: IRProgram -> IRProgram 124 | dceProgram program = 125 | foldl (\p name -> filterProgramInsts (not . hasName name) p) program (map fst (foldableInsts program)) 126 | 127 | \end{code} 128 | -------------------------------------------------------------------------------- /src/TransformIRToMIPS.lhs: -------------------------------------------------------------------------------- 1 |

Transform Pass: IR Canonicalization for MIPS

2 | 3 | 4 |

Introduction

5 | 6 |

In this pass, we rewrite binary instructions of the form:

7 | - ` ` 8 | - ` ` 9 | to 10 | ``` 11 | 12 | ``` 13 | 14 | 15 |

We assume that constant folding has already taken place, 16 | so we cannot have:

17 | ``` 18 | 19 | ``` 20 | 21 |

We will leave:

22 | - ` ` 23 | as-is. 24 | 25 | 26 | \begin{code} 27 | {-# LANGUAGE ViewPatterns #-} 28 | 29 | module TransformIRToMIPS where 30 | import qualified OrderedMap as M 31 | import TransformMem2Reg (mkCFG, CFG) 32 | import Control.Monad.State.Strict 33 | import Data.Traversable 34 | import Data.Foldable 35 | import Control.Applicative 36 | import qualified Data.List.NonEmpty as NE 37 | import IR 38 | import Graph 39 | import BaseIR 40 | import Data.Text.Prettyprint.Doc as PP 41 | import PrettyUtils 42 | import Debug.Trace(trace) 43 | import MIPSAsm 44 | 45 | -- | Convert a label of an instruction to a virtual register. 46 | lblToReg :: Label Inst -> MReg 47 | lblToReg lbl = MRegVirtual (unsafeTransmuteLabel lbl) 48 | 49 | -- | Create a MachineInst for those IR instructions which have two equivalent 50 | -- | MachineInsts: 51 | -- | One that can take an immediate mode `Int` value, and another that takes 52 | -- | two registers 53 | mkMInstForBinOpFromVariants :: 54 | Label Inst -- ^ Destination name 55 | -> Value -- ^ 1st binary operand `a' 56 | -> Value -- ^ 2nd binary operand 57 | -> (MReg -> MReg -> MReg -> MInst) -- ^ Constructor for the instruction 58 | -- that uses two registers as operands. 59 | -> (MReg -> MReg -> Int -> MInst) -- ^ Constructor for the instruction that 60 | -- uses a register and an immediate value. 61 | -> MInst 62 | mkMInstForBinOpFromVariants dstlbl (ValueConstInt i) (ValueInstRef v) _ cimm = 63 | cimm (lblToReg dstlbl) (lblToReg v) i 64 | 65 | mkMInstForBinOpFromVariants dstlbl (ValueInstRef v) (ValueConstInt i) _ cimm = 66 | cimm (lblToReg dstlbl) (lblToReg v) i 67 | 68 | mkMInstForBinOpFromVariants dstlbl (ValueInstRef v) (ValueInstRef v') creg _ = 69 | creg (lblToReg dstlbl) (lblToReg v) (lblToReg v') 70 | 71 | mkMInstForBinOpFromVariants dstlbl (ValueConstInt i) (ValueConstInt i') creg _= 72 | error . docToString $ vcat 73 | [pretty "expected instruction to be constant folded", 74 | pretty "Found illegal operands:", 75 | pretty dstlbl <+> pretty ":= f(" <+> 76 | pretty i <+> pretty "," <+> pretty i <+> pretty ")"] 77 | 78 | 79 | -- | Transform an `Inst` to a sequence of `MInst` 80 | transformInst :: Named Inst -> [MInst] 81 | transformInst (Named dest (InstAdd a b)) = 82 | [mkMInstForBinOpFromVariants dest a b Madd Maddi] 83 | 84 | transformInst (Named dest (InstL a b)) = 85 | [mkMInstForBinOpFromVariants dest a b Mslt Mslti] 86 | 87 | -- | Note that for now, we assume that multiplication never happens between 88 | -- | constants. 89 | transformInst (Named dest (InstMul (ValueInstRef a) (ValueInstRef b))) = 90 | [Mmult (lblToReg a) (lblToReg b), Mmflo (lblToReg dest)] 91 | 92 | -- | A phi node is simply "coalesced" in the preceding basic blocks. 93 | -- | @see emitFusePhi, transformBB. 94 | transformInst (Named _ (InstPhi _)) = [] 95 | 96 | transformInst inst = 97 | error . docToString $ pretty "unimplemented lowering for Inst: " <+> 98 | pretty inst 99 | 100 | 101 | -- | Make a MInst that sets a MReg to a value. 102 | mkMInstSetRegToVal :: MReg -- ^ Register to set 103 | -> Value -- ^ Value to use the register to 104 | -> MInst 105 | mkMInstSetRegToVal reg (ValueConstInt i) = 106 | Mli reg i 107 | mkMInstSetRegToVal reg (ValueInstRef lbl) = 108 | mkMov reg (lblToReg lbl) 109 | 110 | -- | Code needed in $v0 to issue "print integer". 111 | codePrintInt :: Int 112 | codePrintInt = 1 113 | 114 | -- | Code needed in $v0 to issue exit. 115 | codeExit :: Int 116 | codeExit = 10 117 | 118 | -- | Transform a `RetInst` into possible `MInsts` and a terminator inst. 119 | transformRetInst :: RetInst -> ([MInst], [MTerminatorInst]) 120 | transformRetInst (RetInstRet v) = 121 | ([mkMInstSetRegToVal rega0 v, 122 | Mli regv0 codePrintInt, 123 | Msyscall, 124 | Mli regv0 codeExit, 125 | Msyscall], 126 | [Mexit]) 127 | 128 | transformRetInst (RetInstTerminal) = 129 | ([Mli regv0 codeExit, 130 | Msyscall], [Mexit]) 131 | 132 | transformRetInst (RetInstBranch lbl) = 133 | ([], [Mbeqz regZero (unsafeTransmuteLabel lbl)]) 134 | 135 | transformRetInst (RetInstConditionalBranch 136 | (ValueInstRef (unsafeTransmuteLabel -> condlbl)) 137 | (unsafeTransmuteLabel -> thenlbl) 138 | (unsafeTransmuteLabel -> elselbl)) = 139 | ([], 140 | [Mbgtz (MRegVirtual condlbl) thenlbl, 141 | Mj elselbl]) 142 | 143 | -- | Shortcut a jump from a branch of "0" to a direct jump 144 | -- | Note that these should ideally be fused in a previous pass 145 | -- | TODO: implement BB fusion. 146 | transformRetInst (RetInstConditionalBranch 147 | (ValueConstInt 0) _ (unsafeTransmuteLabel -> elselbl)) = 148 | ([], [Mj elselbl]) 149 | 150 | -- | Shortcut a jump from a branch of "1" to a direct jump 151 | -- | Note that these should ideally be fused in a previous pass 152 | -- | TODO: implement BB fusion. 153 | 154 | transformRetInst (RetInstConditionalBranch 155 | (ValueConstInt 1) (unsafeTransmuteLabel -> thenlbl) _) = 156 | ([], [Mj thenlbl]) 157 | 158 | 159 | transformRetInst retinst = 160 | error . docToString $ pretty "unimplemented lowering for RetInst: " <+> 161 | pretty retinst 162 | 163 | -- | Emit code such that if the current basic block jumps to a basic block 164 | -- | that has a phi node, we write to a register that the phi node would have 165 | -- | occupied. 166 | emitFusePhi :: CFG -> IRProgram -> 167 | IRBBId -- ^ The basic block to emit code to handle phi nodes of successors 168 | -> [MInst] -- ^ Instructions that store values into phi nodes. 169 | emitFusePhi cfg Program{programBBMap=bbmap} curbbid = 170 | let 171 | -- | Make an instruction that stores a Value into the phi node, 172 | mkStoreForPhi :: Label Inst -- ^ Label of the Phi node 173 | -> Value -- ^ Value to store in the Phi node 174 | -> MInst 175 | mkStoreForPhi (unsafeTransmuteLabel -> phiname) val = 176 | mkMInstSetRegToVal (MRegVirtual phiname) val 177 | in 178 | trace (docToString $ pretty "successors(" <+> pretty curbbid <+> pretty "): " 179 | <+> hcat (map pretty succphis) ) 180 | (map (uncurry mkStoreForPhi) succPhiReferences) 181 | where 182 | -- | BBs that are successors in the CFG 183 | succbbs :: [IRBB] 184 | succbbs = fmap (bbmap M.!) (getImmediateChildren cfg curbbid) 185 | -- | Phi nodes of all successor basic blocks 186 | succphis :: [Named Inst] 187 | succphis = succbbs >>= getIRBBPhis 188 | -- | Names of variables referred to by successors of current BB 189 | -- | LHS is the phi node name. 190 | -- | RHS is the source inst name. 191 | succPhiReferences :: [(Label Inst, Value)] 192 | succPhiReferences = succphis >>= \(Named phiname phi) -> 193 | case getPhiValueForBB curbbid phi of 194 | Just val -> [(phiname, val)] 195 | _ -> [] 196 | 197 | 198 | 199 | -- | Transform an IR basic block to a machine Basic Block 200 | transformBB :: CFG -> IRProgram -> IRBB -> MBB 201 | transformBB cfg program (bb@BasicBlock { 202 | bbInsts=insts, 203 | bbRetInst=retinst, 204 | bbLabel=curbbid 205 | }) = BasicBlock { 206 | bbLabel=unsafeTransmuteLabel curbbid, 207 | bbInsts=insts' ++ instsFromSucceedingPhi ++ instsFromRet, 208 | bbRetInst=retinst' 209 | } where 210 | insts' = insts >>= transformInst 211 | (instsFromRet, retinst') = transformRetInst retinst 212 | instsFromSucceedingPhi = emitFusePhi cfg program curbbid 213 | 214 | \end{code} 215 | 216 | SPIM assumes that our entry label is called `main`. To stick to the convention, 217 | we re-label our entry basic block to `main`. 218 | 219 | \begin{code} 220 | addJumpToEntry :: IRBBId -> MProgram -> MProgram 221 | addJumpToEntry entrybbid mprogram@Program{ 222 | programBBMap=bbmap 223 | } = mprogram { 224 | programBBMap = M.insert (Label "main") mainBB bbmap, 225 | programEntryBBId = Label "main" 226 | } where 227 | -- | Have a basic block whose only job is to jump to the actual entry 228 | mainBB = BasicBlock { 229 | bbInsts=[], 230 | bbRetInst=[Mj (unsafeTransmuteLabel entrybbid)], 231 | bbLabel=Label "main" 232 | } 233 | \end{code} 234 | 235 | Finally, we write the interface to our transformation as a function 236 | `transformIRToMIPS`. 237 | \begin{code} 238 | transformIRToMIPS :: IRProgram -> MProgram 239 | transformIRToMIPS p = 240 | addJumpToEntry (programEntryBBId p) (mapProgramBBs (transformBB cfg p) p) where 241 | cfg = mkCFG (programBBMap p) 242 | \end{code} 243 | 244 | -------------------------------------------------------------------------------- /stack.yaml: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by 'stack init' 2 | # 3 | # Some commonly used options have been documented as comments in this file. 4 | # For advanced use and comprehensive documentation of the format, please see: 5 | # http://docs.haskellstack.org/en/stable/yaml_configuration/ 6 | 7 | # Resolver to choose a 'specific' stackage snapshot or a compiler version. 8 | # A snapshot resolver dictates the compiler version and the set of packages 9 | # to be used for project dependencies. For example: 10 | # 11 | # resolver: lts-3.5 12 | # resolver: nightly-2015-09-21 13 | # resolver: ghc-7.10.2 14 | # resolver: ghcjs-0.1.0_ghc-7.10.2 15 | # resolver: 16 | # name: custom-snapshot 17 | # location: "./custom-snapshot.yaml" 18 | resolver: lts-8.18 19 | 20 | # User packages to be built. 21 | # Various formats can be used as shown in the example below. 22 | # 23 | # packages: 24 | # - some-directory 25 | # - https://example.com/foo/bar/baz-0.0.2.tar.gz 26 | # - location: 27 | # git: https://github.com/commercialhaskell/stack.git 28 | # commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a 29 | # - location: https://github.com/commercialhaskell/stack/commit/e7b331f14bcffb8367cd58fbfc8b40ec7642100a 30 | # extra-dep: true 31 | # subdirs: 32 | # - auto-update 33 | # - wai 34 | # 35 | # A package marked 'extra-dep: true' will only be built if demanded by a 36 | # non-dependency (i.e. a user package), and its test suites and benchmarks 37 | # will not be run. This is useful for tweaking upstream packages. 38 | packages: 39 | - '.' 40 | # Dependency packages to be pulled from upstream that are not in the resolver 41 | # (e.g., acme-missiles-0.3) 42 | extra-deps: [prettyprinter-1.2.1] 43 | 44 | # Override default flag values for local packages and extra-deps 45 | flags: {} 46 | 47 | # Extra package databases containing global packages 48 | extra-package-dbs: [] 49 | 50 | # Control whether we use the GHC we find on the path 51 | # system-ghc: true 52 | # 53 | # Require a specific version of stack, using version ranges 54 | # require-stack-version: -any # Default 55 | # require-stack-version: ">=1.4" 56 | # 57 | # Override the architecture used by stack, especially useful on Windows 58 | # arch: i386 59 | # arch: x86_64 60 | # 61 | # Extra directories used by stack for building 62 | # extra-include-dirs: [/path/to/dir] 63 | # extra-lib-dirs: [/path/to/dir] 64 | # 65 | # Allow a newer minor version of GHC than the snapshot specifies 66 | # compiler-check: newer-minor 67 | -------------------------------------------------------------------------------- /test/Spec.hs: -------------------------------------------------------------------------------- 1 | import Test.Tasty 2 | import Control.Monad 3 | import Test.Tasty.HUnit 4 | import Test.Tasty.Runners 5 | 6 | import Data.Ord 7 | import qualified Data.Map as M 8 | import System.IO 9 | import Data.Text.Prettyprint.Doc 10 | import PrettyUtils 11 | 12 | 13 | import IR 14 | import Parser 15 | import IRInterpreter 16 | import Language 17 | import ProgramToIR 18 | import TransformMem2Reg 19 | import TransformConstantFolding 20 | import TransformIRToMIPS 21 | import TransformRegisterAllocate 22 | import MIPSInterpreter(interpretMIPSWithSPIM) 23 | 24 | import Control.Monad(filterM) 25 | import Data.List(permutations) 26 | 27 | import Data.Foldable(forM_) 28 | 29 | import System.Directory 30 | import Data.Either 31 | 32 | -- | collection of all program paths and their contents 33 | resources :: IO [(FilePath, String)] 34 | resources = do 35 | programFiles <- listDirectory "./programs/" 36 | forM programFiles $ \f -> do 37 | contents <- readFile $ "./programs/" ++ f 38 | return (f, contents) 39 | 40 | -- | Make a test case for a *single* pass 41 | mkPassTest :: String -> Pass -> TestTree 42 | mkPassTest contents pass@(passname, _) = 43 | -- | use testCaseSteps so we can print errors on parsing and reference program evaluation. 44 | testCaseSteps (passname) $ \step -> do 45 | parseSourceToIR step contents $ \seedir -> do 46 | v <- runReferenceProgram step seedir 47 | verifyPass pass seedir v 48 | 49 | -- | Make a test case for all passes to run on this particular file 50 | mkAllPassesTests :: FilePath -> String -> TestTree 51 | mkAllPassesTests filepath contents = 52 | let 53 | tests = map (mkPassTest contents) allPasses 54 | in testGroup (filepath) tests 55 | 56 | 57 | -- | Make a test case that checks that MIPS output is the same as the 58 | -- | interpreter output. 59 | mkMIPSCodegenTest :: FilePath -> String -> TestTree 60 | mkMIPSCodegenTest filepath contents = 61 | -- | use testCaseSteps so we can print errors on parsing and reference program evaluation. 62 | testCaseSteps (filepath) $ \step -> do 63 | parseSourceToIR step contents $ \seedir -> do 64 | v <- runReferenceProgram step seedir 65 | let mipsIR = (transformRegisterAllocate . 66 | transformIRToMIPS . 67 | transformConstantFold . 68 | transformMem2Reg) $ seedir 69 | step "running IR on SPIM..." 70 | mipsv <- interpretMIPSWithSPIM mipsIR 71 | case mipsv of 72 | Left err -> assertFailure . docToString $ 73 | pretty "SPIM error: " <+> err 74 | Right mipsv -> v @=? Just mipsv 75 | 76 | main :: IO () 77 | main = do 78 | filepathsWithContents <- resources 79 | let passesTests = fmap (uncurry mkAllPassesTests) filepathsWithContents 80 | let mipsTests = fmap (uncurry mkMIPSCodegenTest) filepathsWithContents 81 | 82 | defaultMain $ testGroup "All tests" 83 | [testGroup "All passes on all files" passesTests, 84 | testGroup "MIPS codegen & execution in SPIM" mipsTests] 85 | 86 | -- | An IR pass 87 | type Pass = (String, IRProgram -> IRProgram) 88 | 89 | -- | A list of basic passes. 90 | basicPasses :: [Pass] 91 | basicPasses = [("mem2reg", transformMem2Reg), 92 | ("constant fold", transformConstantFold)] 93 | 94 | 95 | -- | Get all powersets which are not null. 96 | nonNullPowerset_ :: [a] -> [[a]] 97 | nonNullPowerset_ xs = filter (not . null) (filterM (const [True, False]) xs) 98 | 99 | -- | Compose a list of passes into one pass 100 | composePassList_ :: [Pass] -> Pass 101 | composePassList_ ((xs, xf):ls) = 102 | let (ys, yf) = composePassList_ ls 103 | name = if ys == "" then xs else xs ++ ", " ++ ys 104 | in (name, yf . xf) 105 | composePassList_ [] = ("", id) 106 | 107 | -- | Compose all passes in all possible orders of all subsets. 108 | allPasses :: [Pass] 109 | allPasses = nonNullPowerset_ basicPasses >>= \subset -> 110 | composePassList_ <$> permutations subset 111 | 112 | -- | Parse a program 113 | parseSourceToIR :: (String -> IO ()) -- Function to log messages 114 | -> String -- Source program 115 | -> (IRProgram -> Assertion) -- Parse success handler 116 | -> Assertion 117 | parseSourceToIR step raw cont = do 118 | step $ "Parsing source program..." 119 | case parseProgram raw of 120 | Left e -> assertFailure $ "parse error: " ++ e 121 | Right p -> cont (programToIR p) 122 | 123 | -- | Run the reference program 124 | -- | TODO: once the IR interpreter can throw errors, catch the error here. 125 | runReferenceProgram :: (String -> IO ()) -> IRProgram -> IO (Maybe Int) 126 | runReferenceProgram step seedir = do 127 | step $ "Running source program IR..." 128 | return $ runProgram seedir 129 | 130 | 131 | -- | Verify that a pass is correct 132 | verifyPass :: Pass -- The pass to run 133 | -> IRProgram -- The source program 134 | -> Maybe Int -- The correct value expected 135 | -> Assertion 136 | verifyPass (passname, passfn) seedir expectedVal = do 137 | expectedVal @=? runProgram (passfn $ seedir) 138 | --------------------------------------------------------------------------------