-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathCompile.hs
329 lines (271 loc) · 10.5 KB
/
Compile.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
{- | Module containing utilities for compiling an 'E' program into C code.
Note that the generated C disregards portability, and is dependent on certain
libraries (that may or may not need to be linked):
* \<stdint.h\>
* \<math.h\>
-}
module E.Compile
( compile
, printProg
, writeProg
) where
import Control.Monad (when)
import Data.Functor ((<&>))
import Data.List (sortOn)
import Data.Proxy (Proxy (..))
import Lens.Micro ((^.), _head)
import Lens.Micro.Mtl (assign, modifying, use)
import E.Core
import E.CTypes (CType (..))
import qualified Control.Monad.State.Strict as St
import qualified Data.Map.Strict as M
import qualified Lens.Micro.TH
--
-- * Compilation utils
--
newtype Name = Name
{ unName :: String
} deriving newtype (Show)
deriving stock (Eq, Ord)
{- | 'EC' is a wrapper for 'E' for use where homogeneity is needed and only
the 'CType' methods are required.
-}
data EC = forall a . CType a => EC (E a)
{- | Global variable declaration representations are parameterized by their
C-representable type.
-}
data Global a = forall t . CType t => Global (Proxy t) a
type Compile = St.State CompileState
-- | State during compilation.
data CompileState = CompileState
{ -- | Counter for generating unique names for stuff.
_csCounter :: Int
-- | Function definitions as whole strings. These are accumulated
-- during compilation.
, _csDefs :: [String]
-- | Global variables for scrutinees.
, _csGlobalScrutIds :: [Global ScrutId]
-- | Global variables for constructor fields. It's a bit silly having
-- separate lists for different global things; can probably be unified
-- into a single field (TODO).
, _csGlobalArgIds :: [Global ArgId]
-- | Contexts for case-of's/pattern matches, modelled as a stack. Each
-- field of a constructor is represented by a unique ID, along with the
-- ID of the scrutinee it originated from. A new map is used for each
-- new pattern match construct. The mapping to a (wrapped) 'E' allows us
-- to bind the expressions of constructor fields to variables, so they
-- can be reused internally.
, _csCtxts :: [M.Map ArgId EC]
}
$(Lens.Micro.TH.makeLenses ''CompileState)
-- ** Other utilities
-- | Return a unique identifier and increment the counter in state.
freshCid :: Compile Name
freshCid = do
newId <- ('v' :) . show <$> use csCounter
modifying csCounter (+ 1)
pure $ Name newId
showArgId :: ArgId -> String
showArgId (ArgId scrutId (FieldId fid) (Just prettyName)) =
concat [showScrutId scrutId, "_", show fid, "_", prettyName]
-- TODO: Make the `show fid` ^above^ not needed, do smarter compilation
showArgId (ArgId scrutId (FieldId fid) Nothing) =
concat [showScrutId scrutId, "_field", show fid]
showScrutId :: ScrutId -> String
showScrutId (ScrutId sid) = "_scrut" ++ show sid
showGlobalScrutId :: Global ScrutId -> String
showGlobalScrutId (Global (Proxy :: Proxy t) x) =
concat [ctype @t, " ", showScrutId x, ";"]
showGlobalArgId :: Global ArgId -> String
showGlobalArgId (Global (_ :: Proxy t) x) =
concat [ctype @t, " ", showArgId x, ";"]
--
-- * Compilation
--
-- | Compile and write the generated C code to file.
writeProg :: CType a => FilePath -> Estate (E a) -> IO ()
writeProg fp = writeFile fp . compile . runEstate
-- | Compile and output the generated C code to stdout.
printProg :: CType a => Estate (E a) -> IO ()
printProg = putStrLn . compile . runEstate
{- | Main entry point for compilation. Generate code for an @'E' a@ expression
where @a@ is a type that can be represented in C (indicated by the 'CType')
constraint.
-}
compile :: forall a. CType a => E a -> String
compile expr =
let (code, st) = runCompile (ce expr)
in mconcat
[ "\n// Code generated from E program \n\n"
-- #include lines
, concatMap ((++ "\n") . includeWrap)
["stdbool.h", "stdlib.h", "stdio.h", "stdint.h", "math.h"], "\n"
-- Insert global variable declarations.
, "// Global variables for scrutinees.\n"
, unlines . map showGlobalScrutId . sortOn (\ (Global _ x) -> x)
$ st ^. csGlobalScrutIds
, "\n"
, "// Global variables for constructor fields.\n"
, unlines . map showGlobalArgId . sortOn (\ (Global _ x) -> x)
$ st ^. csGlobalArgIds
, "\n"
-- Function definitions are added in the wrong order (for the C code),
-- so we reverse the list of definitions before printing them.
, unlines . reverse $ (st ^. csDefs)
-- Create the main() function entry point.
-- NOTE: Doesn't handle any input currently.
, mainWrap code
]
where
runCompile :: Compile a1 -> (a1, CompileState)
runCompile = flip St.runState initCompileState
where
initCompileState :: CompileState
initCompileState = CompileState
{ _csCounter = 0
, _csDefs = []
, _csGlobalScrutIds = []
, _csGlobalArgIds = []
, _csCtxts = []
}
includeWrap :: String -> String
includeWrap s = "#include <" ++ s ++ ">"
mainWrap :: String -> String
mainWrap body = mconcat
[ "int main() {\n"
, " ", ctype @a, " output = ", body, ";\n"
, " printf(\"Output: %", cformat @a : "\\n\", output);\n"
, " return 0;\n"
, "}\n"
]
-- | Add a new empty map to the constructor field context stack.
pushCtxt :: Compile ()
pushCtxt = modifying csCtxts (M.empty :)
-- | Return the top constructor field context and decrease the stack.
popCtxt :: Compile (M.Map ArgId EC)
popCtxt = use csCtxts >>= \case
[] -> error "popEnv: Empty stack"
x : xs -> assign csCtxts xs >> pure x
-- | Serialize an 'E' expression and affect the compilation state as necessary.
ce :: forall rt . CType rt => E rt -> Compile String -- "rt" for "return type"
ce expr = case expr of
EVal v -> pure $ cval v
EVar s -> pure s
EField argId e -> do
-- Add the ArgId of the expression we encountered to the current
-- context, so that the outer 'ECase' construct knows to bind it.
modifying (csCtxts . _head) (M.insert argId (EC e))
-- And if it's the first time we made use of this field, we need to
-- add add a global variable declaration for it.
isNew <- not <$> globalArgIdExists argId
when isNew (newGlobalArgId @rt $ argId)
pure $ showArgId argId
ESym s -> pure $ showScrutId s
ECase scrut@(Scrut e _s) matches -> do
fName <- newCaseDef scrut matches
scrutStr <- ce e
pure $ concat [unName fName, "(", scrutStr, ")"]
EAdd e1 e2 -> binOp e1 e2 "+"
EMul e1 e2 -> binOp e1 e2 "*"
ESub e1 e2 -> binOp e1 e2 "-"
EDiv e1 e2 -> binOp e1 e2 "/"
EGt e1 e2 -> binOp e1 e2 ">"
ELt e1 e2 -> binOp e1 e2 "<"
EGte e1 e2 -> binOp e1 e2 ">="
ELte e1 e2 -> binOp e1 e2 "<="
EEq e1 e2 -> binOp e1 e2 "=="
ENeq e1 e2 -> binOp e1 e2 "!="
EAnd b1 b2 -> binOp b1 b2 "&&"
EOr b1 b2 -> binOp b1 b2 "||"
ENot b -> ce b <&> \ b' -> concat ["!(", b', ")"]
ECFloorInt d -> ce d <&> \ d' -> concat ["((int) floor(", d', "))"]
ECFloorDouble d -> ce d <&> \ d' -> concat ["(floor(", d', "))"]
-- Unsafe bit twiddling below, subject to change
ECast e (Proxy :: Proxy t) -> do
e' <- ce e
pure $ concat ["((", ctype @t, ") ", e', ")"]
EShiftL e n -> binOp e n "<<"
EShiftR e n -> binOp e n ">>"
EBitAnd e1 e2 -> binOp e1 e2 "&"
where
binOp :: (CType a1, CType a2)
=> E a1
-> E a2
-> String -- ^ Operator as a string
-> Compile String
binOp e1 e2 op = do
e1' <- ce e1
e2' <- ce e2
pure $ concat ["(", e1', " ", op, " ", e2', ")"]
{- | Create a function definition representing a case-of use and add
it to the compilation state. Return the function name.
-}
newCaseDef :: forall p a b . (CType a, CType b)
=> Scrut a
-> [Match p b]
-> Compile Name
newCaseDef (Scrut _scrutExp scrutId) matches = do
pushCtxt
newGlobalScrutId @a scrutId
fName <- freshCid
ifs <- cMatches matches
-- At this point, the top of the csCtxts stack should contain the variables
-- and expressions we need to bind.
-- TODO: Still creates redundant assignments when using nested cases where
-- the inner case refers to a bound variable from the outer case.
bindings <- mapM cScrutBinding . M.assocs =<< popCtxt
let def = concat
[ ctype @b, " ", unName fName, "(", ctype @a, " ", argName, ") {\n"
, " ", showScrutId scrutId, " = ", argName, ";\n"
, concatMap (\ x -> " " ++ x ++ "\n") bindings
, concatMap (\ x -> " " ++ x ++ "\n") ifs
, "}\n"
]
modifying csDefs (def :)
pure fName
where
argName :: String
argName = "arg"
cScrutBinding :: (ArgId, EC) -> Compile String
cScrutBinding (argId, EC (e :: E t)) = do
e' <- ce e
pure $ concat [showArgId argId, " = ", e', ";"]
cMatches :: [Match p b] -> Compile [String]
cMatches xs = do
ifs <- mapM cMatch xs
pure $ ifs ++ [nonMatch]
where
nonMatch :: String
nonMatch = concat
[ "{ fprintf(stderr, \"No match on: `", showScrutId scrutId
, "`\\n\"); exit(1); }"]
cMatch :: Match p b -> Compile String
cMatch (Match cond body) = do
cond' <- ce cond
body' <- ce body
pure $ concat
["if (", cond', ") { return ", body', "; } else "]
{- | Add a global variable to the compilation state, for holding the value
of a constructor field.
-}
newGlobalArgId :: forall a . CType a => ArgId -> Compile ()
newGlobalArgId aid = modifying csGlobalArgIds (Global (Proxy @a) aid :)
{- | Add a global variable to the compilation state, for holding the value
of a scrutinee.
-}
newGlobalScrutId :: forall a . CType a => ScrutId -> Compile ()
newGlobalScrutId sid = modifying csGlobalScrutIds (Global (Proxy @a) sid :)
{- | Check if an ArgId is already a declared global varaible, regardless
of variable type.
-}
globalArgIdExists :: ArgId -> Compile Bool
globalArgIdExists aid =
(aid `elem`) . map (\ (Global _ a) -> a) <$> use csGlobalArgIds