├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── kaleidoscope.cabal ├── src ├── Codegen.hs ├── JIT.hs └── Main.hs ├── stack.yaml └── stack.yaml.lock /.gitignore: -------------------------------------------------------------------------------- 1 | *.sw[po] 2 | *.o 3 | *.so 4 | cabal.sandbox.config 5 | .cabal-sandbox 6 | dist/ 7 | *.hi 8 | *.o 9 | includes 10 | *.html 11 | *.epub 12 | *.agdai 13 | .stack-work 14 | dist-newstyle 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | dist: xenial 3 | language: generic 4 | 5 | env: 6 | global: 7 | - GCC=gcc-5 8 | - GXX=g++-5 9 | 10 | cache: 11 | directories: 12 | - $HOME/.stack/ 13 | 14 | addons: 15 | apt: 16 | packages: 17 | - gcc-5 18 | - g++-5 19 | - libgmp-dev 20 | sources: 21 | - llvm-toolchain-xenial-9 22 | - ubuntu-toolchain-r-test 23 | 24 | before_install: 25 | - mkdir -p ~/.local/bin 26 | - export PATH=~/.local/bin:$PATH 27 | - travis_retry curl -L https://www.stackage.org/stack/linux-x86_64 | tar xz --wildcards --strip-components=1 -C ~/.local/bin '*/stack' 28 | - export CC=/usr/bin/$GCC 29 | - export CXX=/usr/bin/$GXX 30 | - wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add - 31 | - echo "deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-9 main" | sudo tee -a /etc/apt/sources.list 32 | - sudo add-apt-repository --yes ppa:ubuntu-toolchain-r/ppa 33 | - sudo apt-get update 34 | - sudo apt-get --yes install llvm-9 llvm-9-dev 35 | 36 | install: 37 | - stack --no-terminal --install-ghc test --only-dependencies 38 | 39 | script: 40 | - stack --no-terminal test --haddock --no-haddock-deps 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2020, Stephen Diehl 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to 5 | deal in the Software without restriction, including without limitation the 6 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | sell copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | llvm-tutorial-standalone 2 | ------------------------ 3 | 4 | [![Build Status](https://travis-ci.org/llvm-hs/llvm-hs-kaleidoscope.svg)](https://travis-ci.org/llvm-hs/llvm-hs-kaleidoscope) 5 | [![MIT License](http://img.shields.io/badge/license-mit-blue.svg)](https://github.com/sdiehl/llvm-tutorial-standalone/blob/master/LICENSE) 6 | 7 | A minimal LLVM builder. Basically the same code as the [Haskell Kaleidoscope 8 | tutorial](http://www.stephendiehl.com/llvm/) uses but without going through a 9 | frontend AST. 10 | 11 | If you want to roll a custom LLVM compiler backend this might be a good starting 12 | point for the backend. 13 | 14 | Install 15 | ------- 16 | 17 | Check that your installed LLVM version is precisely 9.0. 18 | 19 | ```bash 20 | $ llvm-config --version 21 | 9.0 22 | ``` 23 | 24 | To build using stack: 25 | 26 | ```bash 27 | $ stack build 28 | $ stack exec main 29 | ``` 30 | 31 | To build using cabal: 32 | 33 | ```bash 34 | $ cabal new-build 35 | ``` 36 | 37 | To run: 38 | 39 | ```bash 40 | $ stack run 41 | $ cabal run 42 | Preprocessing executable 'standalone' for tutorial-0.2.0.0... 43 | ; ModuleID = 'my cool jit' 44 | 45 | define double @main() { 46 | entry: 47 | ret double 3.000000e+01 48 | } 49 | 50 | Evaluated to: 30.0 51 | ``` 52 | 53 | Usage 54 | ----- 55 | 56 | Code is split across 57 | 58 | * [Codegen](https://github.com/llvm-hs/llvm-hs-kaleidoscope/blob/master/src/Codegen.hs) 59 | * [JIT](https://github.com/llvm-hs/llvm-hs-kaleidoscope/blob/master/src/JIT.hs) 60 | * [Main](https://github.com/llvm-hs/llvm-hs-kaleidoscope/blob/master/src/Main.hs) 61 | 62 | The main program will use the embedded LLVM Monad to define a small program 63 | which will add two constants together. 64 | 65 | ```haskell 66 | initModule :: AST.Module 67 | initModule = emptyModule "my cool jit" 68 | 69 | logic :: LLVM () 70 | logic = do 71 | define double "main" [] $ \ptrToMain -> do 72 | let a = cons $ C.Float (F.Double 10) 73 | let b = cons $ C.Float (F.Double 20) 74 | res <- fadd a b 75 | ret res 76 | 77 | main :: IO (AST.Module) 78 | main = do 79 | let ast = runLLVM initModule logic 80 | runJIT ast 81 | return ast 82 | ``` 83 | 84 | This will generate and JIT compile into the following IR and use the LLVM 85 | execution engine to JIT it to machine code. 86 | 87 | ```llvm 88 | ; ModuleID = 'my cool jit' 89 | 90 | define double @main() { 91 | entry: 92 | %1 = fadd double 1.000000e+01, 2.000000e+01 93 | ret double %1 94 | } 95 | ``` 96 | -------------------------------------------------------------------------------- /kaleidoscope.cabal: -------------------------------------------------------------------------------- 1 | name: kaleidoscope 2 | version: 0.1.0.0 3 | synopsis: Standalone LLVM JIT Tutorial 4 | license: MIT 5 | category: Compilers 6 | description: LLVM Kaleidoscope tutorial 7 | synopsis: LLVM Kaleidoscope tutorial 8 | license-file: LICENSE 9 | author: Stephen Diehl 10 | maintainer: stephen.m.diehl@gmail.com 11 | build-type: Simple 12 | extra-source-files: README.md 13 | cabal-version: >=1.10 14 | 15 | executable main 16 | build-depends: 17 | base >= 4.6 && <5.0, 18 | bytestring >= 0.10, 19 | mtl >= 2.2, 20 | containers >= 0.5, 21 | llvm-hs >= 9.0 && < 10.0, 22 | llvm-hs-pure >= 9.0 && < 10.0 23 | 24 | other-modules: 25 | Codegen 26 | JIT 27 | 28 | default-language: Haskell2010 29 | hs-source-dirs: src 30 | main-is: Main.hs 31 | -------------------------------------------------------------------------------- /src/Codegen.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | 4 | module Codegen where 5 | 6 | import Control.Applicative 7 | import Control.Monad.State 8 | import Data.ByteString.Short 9 | import Data.Function 10 | import Data.List 11 | import qualified Data.Map as Map 12 | import Data.Monoid ((<>)) 13 | import Data.String 14 | import Data.Word 15 | import LLVM.AST 16 | import qualified LLVM.AST as AST 17 | import LLVM.AST.AddrSpace 18 | import qualified LLVM.AST.Attribute as A 19 | import qualified LLVM.AST.CallingConvention as CC 20 | import qualified LLVM.AST.Constant as C 21 | import qualified LLVM.AST.FloatingPointPredicate as FP 22 | import LLVM.AST.Global 23 | import qualified LLVM.AST.Linkage as L 24 | import LLVM.AST.Type 25 | import LLVM.AST.Typed (typeOf) 26 | 27 | ------------------------------------------------------------------------------- 28 | -- Module Level 29 | ------------------------------------------------------------------------------- 30 | 31 | newtype LLVM a = LLVM (State AST.Module a) 32 | deriving (Functor, Applicative, Monad, MonadState AST.Module) 33 | 34 | runLLVM :: AST.Module -> LLVM a -> AST.Module 35 | runLLVM mod (LLVM m) = execState m mod 36 | 37 | emptyModule :: ShortByteString -> AST.Module 38 | emptyModule label = defaultModule {moduleName = label} 39 | 40 | addDefn :: Definition -> LLVM () 41 | addDefn d = do 42 | defs <- gets moduleDefinitions 43 | modify $ \s -> s {moduleDefinitions = defs ++ [d]} 44 | 45 | define :: Type -> ShortByteString -> [(Type, Name)] -> (Type -> Codegen a) -> LLVM () 46 | define retty label argtys body = 47 | addDefn 48 | $ GlobalDefinition 49 | $ functionDefaults 50 | { name = Name label, 51 | parameters = ([Parameter ty nm [] | (ty, nm) <- argtys], False), 52 | returnType = retty, 53 | basicBlocks = bls 54 | } 55 | where 56 | bls = createBlocks $ execCodegen $ do 57 | enter <- addBlock entryBlockName 58 | _ <- setBlock enter 59 | body ptrThisType 60 | ptrThisType = PointerType 61 | { pointerReferent = FunctionType 62 | { resultType = retty, 63 | argumentTypes = map fst argtys, 64 | isVarArg = False 65 | }, 66 | pointerAddrSpace = AddrSpace 0 67 | } 68 | 69 | external :: Type -> ShortByteString -> [(Type, Name)] -> LLVM () 70 | external retty label argtys = 71 | addDefn 72 | $ GlobalDefinition 73 | $ functionDefaults 74 | { name = Name label, 75 | linkage = L.External, 76 | parameters = ([Parameter ty nm [] | (ty, nm) <- argtys], False), 77 | returnType = retty, 78 | basicBlocks = [] 79 | } 80 | 81 | fnPtr :: Name -> LLVM Type 82 | fnPtr nm = findType <$> gets moduleDefinitions 83 | where 84 | findType defs = 85 | case fnDefByName of 86 | [] -> error $ "Undefined function: " ++ show nm 87 | [fn] -> PointerType (typeOf fn) (AddrSpace 0) 88 | _ -> error $ "Ambiguous function name: " ++ show nm 89 | where 90 | globalDefs = [g | GlobalDefinition g <- defs] 91 | fnDefByName = [f | f@(Function {name = nm'}) <- globalDefs, nm' == nm] 92 | 93 | --------------------------------------------------------------------------------- 94 | -- Types 95 | ------------------------------------------------------------------------------- 96 | 97 | -- IEEE 754 double 98 | double :: Type 99 | double = FloatingPointType DoubleFP 100 | 101 | void :: Type 102 | void = AST.VoidType 103 | 104 | ------------------------------------------------------------------------------- 105 | -- Names 106 | ------------------------------------------------------------------------------- 107 | 108 | type Names = Map.Map ShortByteString Int 109 | 110 | uniqueName :: ShortByteString -> Names -> (ShortByteString, Names) 111 | uniqueName nm ns = 112 | case Map.lookup nm ns of 113 | Nothing -> (nm, Map.insert nm 1 ns) 114 | Just ix -> (nm <> fromString (show ix), Map.insert nm (ix + 1) ns) 115 | 116 | ------------------------------------------------------------------------------- 117 | -- Codegen State 118 | ------------------------------------------------------------------------------- 119 | 120 | type SymbolTable = [(ShortByteString, Operand)] 121 | 122 | data CodegenState 123 | = CodegenState 124 | { currentBlock :: Name, -- Name of the active block to append to 125 | blocks :: Map.Map Name BlockState, -- Blocks for function 126 | symtab :: SymbolTable, -- Function scope symbol table 127 | blockCount :: Int, -- Count of basic blocks 128 | count :: Word, -- Count of unnamed instructions 129 | names :: Names -- Name Supply 130 | } 131 | deriving (Show) 132 | 133 | data BlockState 134 | = BlockState 135 | { idx :: Int, -- Block index 136 | stack :: [Named Instruction], -- Stack of instructions 137 | term :: Maybe (Named Terminator) -- Block terminator 138 | } 139 | deriving (Show) 140 | 141 | ------------------------------------------------------------------------------- 142 | -- Codegen Operations 143 | ------------------------------------------------------------------------------- 144 | 145 | newtype Codegen a = Codegen {runCodegen :: State CodegenState a} 146 | deriving (Functor, Applicative, Monad, MonadState CodegenState) 147 | 148 | sortBlocks :: [(Name, BlockState)] -> [(Name, BlockState)] 149 | sortBlocks = sortBy (compare `on` (idx . snd)) 150 | 151 | createBlocks :: CodegenState -> [BasicBlock] 152 | createBlocks m = map makeBlock $ sortBlocks $ Map.toList (blocks m) 153 | 154 | makeBlock :: (Name, BlockState) -> BasicBlock 155 | makeBlock (l, (BlockState _ s t)) = BasicBlock l (reverse s) (maketerm t) 156 | where 157 | maketerm (Just x) = x 158 | maketerm Nothing = error $ "Block has no terminator: " ++ (show l) 159 | 160 | entryBlockName :: ShortByteString 161 | entryBlockName = "entry" 162 | 163 | emptyBlock :: Int -> BlockState 164 | emptyBlock i = BlockState i [] Nothing 165 | 166 | emptyCodegen :: CodegenState 167 | emptyCodegen = CodegenState (Name entryBlockName) Map.empty [] 1 0 Map.empty 168 | 169 | execCodegen :: Codegen a -> CodegenState 170 | execCodegen m = execState (runCodegen m) emptyCodegen 171 | 172 | fresh :: Codegen Word 173 | fresh = do 174 | i <- gets count 175 | modify $ \s -> s {count = 1 + i} 176 | return $ i + 1 177 | 178 | instr :: Type -> Instruction -> Codegen (Operand) 179 | instr ty ins = do 180 | n <- fresh 181 | let ref = (UnName n) 182 | blk <- current 183 | let i = stack blk 184 | modifyBlock (blk {stack = (ref := ins) : i}) 185 | return $ local ty ref 186 | 187 | unnminstr :: Instruction -> Codegen () 188 | unnminstr ins = do 189 | blk <- current 190 | let i = stack blk 191 | modifyBlock (blk {stack = (Do ins) : i}) 192 | 193 | terminator :: Named Terminator -> Codegen (Named Terminator) 194 | terminator trm = do 195 | blk <- current 196 | modifyBlock (blk {term = Just trm}) 197 | return trm 198 | 199 | ------------------------------------------------------------------------------- 200 | -- Block Stack 201 | ------------------------------------------------------------------------------- 202 | 203 | entry :: Codegen Name 204 | entry = gets currentBlock 205 | 206 | addBlock :: ShortByteString -> Codegen Name 207 | addBlock bname = do 208 | bls <- gets blocks 209 | ix <- gets blockCount 210 | nms <- gets names 211 | let new = emptyBlock ix 212 | (qname, supply) = uniqueName bname nms 213 | modify $ \s -> 214 | s 215 | { blocks = Map.insert (Name qname) new bls, 216 | blockCount = ix + 1, 217 | names = supply 218 | } 219 | return (Name qname) 220 | 221 | setBlock :: Name -> Codegen Name 222 | setBlock bname = do 223 | modify $ \s -> s {currentBlock = bname} 224 | return bname 225 | 226 | getBlock :: Codegen Name 227 | getBlock = gets currentBlock 228 | 229 | modifyBlock :: BlockState -> Codegen () 230 | modifyBlock new = do 231 | active <- gets currentBlock 232 | modify $ \s -> s {blocks = Map.insert active new (blocks s)} 233 | 234 | current :: Codegen BlockState 235 | current = do 236 | c <- gets currentBlock 237 | blks <- gets blocks 238 | case Map.lookup c blks of 239 | Just x -> return x 240 | Nothing -> error $ "No such block: " ++ show c 241 | 242 | ------------------------------------------------------------------------------- 243 | -- Symbol Table 244 | ------------------------------------------------------------------------------- 245 | 246 | assign :: ShortByteString -> Operand -> Codegen () 247 | assign var x = do 248 | lcls <- gets symtab 249 | modify $ \s -> s {symtab = [(var, x)] ++ lcls} 250 | 251 | getvar :: ShortByteString -> Codegen Operand 252 | getvar var = do 253 | syms <- gets symtab 254 | case lookup var syms of 255 | Just x -> return x 256 | Nothing -> error $ "Local variable not in scope: " ++ show var 257 | 258 | ------------------------------------------------------------------------------- 259 | 260 | -- References 261 | local :: Type -> Name -> Operand 262 | local = LocalReference 263 | 264 | global :: Type -> Name -> C.Constant 265 | global = C.GlobalReference 266 | 267 | externf :: Type -> Name -> Operand 268 | externf ty nm = ConstantOperand (C.GlobalReference ty nm) 269 | 270 | -- Arithmetic and Constants 271 | fadd :: Operand -> Operand -> Codegen Operand 272 | fadd a b = instr float $ FAdd noFastMathFlags a b [] 273 | 274 | fsub :: Operand -> Operand -> Codegen Operand 275 | fsub a b = instr float $ FSub noFastMathFlags a b [] 276 | 277 | fmul :: Operand -> Operand -> Codegen Operand 278 | fmul a b = instr float $ FMul noFastMathFlags a b [] 279 | 280 | fdiv :: Operand -> Operand -> Codegen Operand 281 | fdiv a b = instr float $ FDiv noFastMathFlags a b [] 282 | 283 | fcmp :: FP.FloatingPointPredicate -> Operand -> Operand -> Codegen Operand 284 | fcmp cond a b = instr float $ FCmp cond a b [] 285 | 286 | cons :: C.Constant -> Operand 287 | cons = ConstantOperand 288 | 289 | uitofp :: Type -> Operand -> Codegen Operand 290 | uitofp ty a = instr float $ UIToFP a ty [] 291 | 292 | toArgs :: [Operand] -> [(Operand, [A.ParameterAttribute])] 293 | toArgs = map (\x -> (x, [])) 294 | 295 | -- Effects 296 | call :: Operand -> [Operand] -> Codegen Operand 297 | call fn args = instr float $ Call Nothing CC.C [] (Right fn) (toArgs args) [] [] 298 | 299 | alloca :: Type -> Codegen Operand 300 | alloca ty = instr float $ Alloca ty Nothing 0 [] 301 | 302 | store :: Operand -> Operand -> Codegen () 303 | store ptr val = unnminstr $ Store False ptr val Nothing 0 [] 304 | 305 | load :: Operand -> Codegen Operand 306 | load ptr = instr float $ Load False ptr Nothing 0 [] 307 | 308 | -- Control Flow 309 | br :: Name -> Codegen (Named Terminator) 310 | br val = terminator $ Do $ Br val [] 311 | 312 | cbr :: Operand -> Name -> Name -> Codegen (Named Terminator) 313 | cbr cond tr fl = terminator $ Do $ CondBr cond tr fl [] 314 | 315 | phi :: Type -> [(Operand, Name)] -> Codegen Operand 316 | phi ty incoming = instr float $ Phi ty incoming [] 317 | 318 | ret :: Operand -> Codegen (Named Terminator) 319 | ret val = terminator $ Do $ Ret (Just val) [] 320 | 321 | retvoid :: Codegen (Named Terminator) 322 | retvoid = terminator $ Do $ Ret Nothing [] 323 | -------------------------------------------------------------------------------- /src/JIT.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | 3 | module JIT where 4 | 5 | import Control.Monad.Except 6 | import qualified Data.ByteString.Char8 as ByteString 7 | import Data.Int 8 | import Data.Word 9 | import Foreign.Ptr (FunPtr, castFunPtr) 10 | import qualified LLVM.AST as AST 11 | import LLVM.Analysis 12 | import LLVM.CodeModel 13 | import LLVM.Context 14 | import qualified LLVM.ExecutionEngine as EE 15 | import LLVM.Module as Mod 16 | import LLVM.PassManager 17 | import LLVM.Target 18 | import LLVM.Transforms 19 | 20 | foreign import ccall "dynamic" haskFun :: FunPtr (IO Double) -> (IO Double) 21 | 22 | run :: FunPtr a -> IO Double 23 | run fn = haskFun (castFunPtr fn :: FunPtr (IO Double)) 24 | 25 | jit :: Context -> (EE.MCJIT -> IO a) -> IO a 26 | jit c = EE.withMCJIT c optlevel model ptrelim fastins 27 | where 28 | optlevel = Just 0 -- optimization level 29 | model = Nothing -- code model ( Default ) 30 | ptrelim = Nothing -- frame pointer elimination 31 | fastins = Nothing -- fast instruction selection 32 | 33 | passes :: PassSetSpec 34 | passes = defaultCuratedPassSetSpec {optLevel = Just 3} 35 | 36 | runJIT :: AST.Module -> IO AST.Module 37 | runJIT mod = do 38 | withContext $ \context -> 39 | jit context $ \executionEngine -> 40 | withModuleFromAST context mod $ \m -> 41 | withPassManager passes $ \pm -> do 42 | -- Optimization Pass 43 | {-runPassManager pm m-} 44 | optmod <- moduleAST m 45 | s <- moduleLLVMAssembly m 46 | ByteString.putStrLn s 47 | EE.withModuleInEngine executionEngine m $ \ee -> do 48 | mainfn <- EE.getFunction ee "main" 49 | case mainfn of 50 | Just fn -> do 51 | res <- run fn 52 | putStrLn $ "Evaluated to: " ++ show res 53 | Nothing -> return () 54 | -- Return the optimized module 55 | return optmod 56 | -------------------------------------------------------------------------------- /src/Main.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | 3 | import Codegen 4 | import JIT 5 | import qualified LLVM.AST as AST 6 | import qualified LLVM.AST.Constant as C 7 | import qualified LLVM.AST.Float as F 8 | 9 | {- 10 | 11 | ; ModuleID = 'my cool jit' 12 | 13 | define double @main() { 14 | entry: 15 | %1 = fadd double 1.000000e+01, 2.000000e+01 16 | ret double %1 17 | } 18 | 19 | -} 20 | 21 | initModule :: AST.Module 22 | initModule = emptyModule "my cool jit" 23 | 24 | logic :: LLVM () 25 | logic = do 26 | define double "main" [] $ \ptrToMain -> do 27 | let a = cons $ C.Float (F.Double 10) 28 | let b = cons $ C.Float (F.Double 20) 29 | res <- fadd a b 30 | ret res 31 | 32 | main :: IO AST.Module 33 | main = do 34 | let ast = runLLVM initModule logic 35 | rc <- runJIT ast 36 | return ast 37 | -------------------------------------------------------------------------------- /stack.yaml: -------------------------------------------------------------------------------- 1 | resolver: lts-14.0 2 | packages: 3 | - '.' 4 | extra-deps: 5 | - llvm-hs-9.0.1 6 | - llvm-hs-pure-9.0.0 7 | 8 | flags: 9 | llvm-hs: 10 | shared-llvm: true 11 | -------------------------------------------------------------------------------- /stack.yaml.lock: -------------------------------------------------------------------------------- 1 | # This file was autogenerated by Stack. 2 | # You should not edit this file by hand. 3 | # For more information, please see the documentation at: 4 | # https://docs.haskellstack.org/en/stable/lock_files 5 | 6 | packages: 7 | - completed: 8 | hackage: llvm-hs-9.0.1@sha256:ee6ec2eb8cba4daf2a43586388a87dbfd6a2ec6d81d1f9965896ac187acad286,8700 9 | pantry-tree: 10 | size: 13599 11 | sha256: 537616dec1351bd9f7905182f82f132359cf3058ebbb059c03b4edcef04b2689 12 | original: 13 | hackage: llvm-hs-9.0.1 14 | - completed: 15 | hackage: llvm-hs-pure-9.0.0@sha256:134779f40086a366279f678e670b610d7a7618b28dd43f65894078fd20e04629,2945 16 | pantry-tree: 17 | size: 2600 18 | sha256: f1a83aa959b69b72342997fc4fdc352086bc3b9f306cfb6a4c8bbc850d36b4fb 19 | original: 20 | hackage: llvm-hs-pure-9.0.0 21 | snapshots: 22 | - completed: 23 | size: 523443 24 | url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/14/0.yaml 25 | sha256: 283773e7120f5446d961eab35ea95c9af9c24187cc178537bd29273200a05171 26 | original: lts-14.0 27 | --------------------------------------------------------------------------------