-
Notifications
You must be signed in to change notification settings - Fork 75
Expand file tree
/
Copy pathCodeGen.hs
More file actions
349 lines (298 loc) · 13.5 KB
/
CodeGen.hs
File metadata and controls
349 lines (298 loc) · 13.5 KB
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
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
{-# LANGUAGE GADTs #-}
-- | High-level translation of Copilot Core into C99.
module Copilot.Compile.C99.CodeGen
(
-- * Externs
mkExtCpyDecln
, mkExtDecln
-- * Type declarations
, mkStructDecln
, mkStructForwDecln
-- * Ring buffers
, mkBuffDecln
, mkIndexDecln
, mkAccessDecln
-- * Stream generators
, mkGenFun
, mkGenFunArray
-- * Monitor processing
, mkStep
)
where
-- External imports
import Control.Monad.State ( runState )
import Data.List ( unzip4 )
import qualified Data.List.NonEmpty as NonEmpty
import qualified Language.C99.Simple as C
-- Internal imports: Copilot
import Copilot.Core ( Expr (..), Id, Stream (..), Struct (..), Trigger (..),
Type (..), UExpr (..), Value (..), fieldName, typeSize )
-- Internal imports
import Copilot.Compile.C99.Error ( impossible )
import Copilot.Compile.C99.Expr ( constArray, transExpr )
import Copilot.Compile.C99.External ( External (..) )
import Copilot.Compile.C99.Name ( argNames, argTempNames,
generatorName, guardName,
indexName, streamAccessorName,
streamName )
import Copilot.Compile.C99.Settings ( CSettings,
cSettingsStepFunctionName )
import Copilot.Compile.C99.Type ( transType )
import Copilot.Compile.C99.Representation ( UniqueTrigger (..) )
-- * Externs
-- | Make a extern declaration of a variable.
mkExtDecln :: External -> C.Decln
mkExtDecln (External name _ ty) = decln
where
decln = C.VarDecln (Just C.Extern) cTy name Nothing
cTy = transType ty
-- | Make a declaration for a copy of an external variable.
mkExtCpyDecln :: External -> C.Decln
mkExtCpyDecln (External _name cpyName ty) = decln
where
decln = C.VarDecln (Just C.Static) cTy cpyName Nothing
cTy = transType ty
-- * Type declarations
-- | Write a struct declaration based on its definition.
mkStructDecln :: Struct a => Type a -> C.Decln
mkStructDecln (Struct x) = C.TypeDecln struct
where
struct = C.TypeSpec $ C.StructDecln (Just $ typeName x) fields
fields = NonEmpty.fromList $ map mkField (toValues x)
mkField :: Value a -> C.FieldDecln
mkField (Value ty field) = C.FieldDecln (transType ty) (fieldName field)
mkStructDecln _ = error "Unhandled case"
-- | Write a forward struct declaration.
mkStructForwDecln :: Struct a => Type a -> C.Decln
mkStructForwDecln (Struct x) = C.TypeDecln struct
where
struct = C.TypeSpec $ C.Struct (typeName x)
mkStructForwDecln _ = error "Unhandled case"
-- * Ring buffers
-- | Make a C buffer variable and initialise it with the stream buffer.
mkBuffDecln :: Id -> Type a -> [a] -> C.Decln
mkBuffDecln sId ty xs = C.VarDecln (Just C.Static) cTy name initVals
where
name = streamName sId
cTy = C.Array (transType ty) (Just $ C.LitInt $ fromIntegral buffSize)
buffSize = length xs
initVals = Just $ C.InitList $ constArray ty xs
-- | Make a C index variable and initialise it to 0.
mkIndexDecln :: Id -> C.Decln
mkIndexDecln sId = C.VarDecln (Just C.Static) cTy name initVal
where
name = indexName sId
cTy = C.TypeSpec $ C.TypedefName "size_t"
initVal = Just $ C.InitExpr $ C.LitInt 0
-- | Define an accessor functions for the ring buffer associated with a stream.
mkAccessDecln :: Id -> Type a -> [a] -> C.FunDef
mkAccessDecln sId ty xs =
C.FunDef static cTy name params [] [C.Return (Just expr)]
where
static = Just C.Static
cTy = C.decay $ transType ty
name = streamAccessorName sId
-- We cast the buffer length to a size_t to make sure that there are no
-- implicit conversions. This is a requirement for compliance with MISRA C
-- (Rule 10.4).
buffLength = C.Cast sizeT $ C.LitInt $ fromIntegral $ length xs
sizeT = C.TypeName $ C.TypeSpec $ C.TypedefName "size_t"
params = [C.Param (C.TypeSpec $ C.TypedefName "size_t") "x"]
index = (C.Ident (indexName sId) C..+ C.Ident "x") C..% buffLength
expr = C.Index (C.Ident (streamName sId)) index
-- * Stream generators
-- | Write a generator function for a stream.
mkGenFun :: String -> Expr a -> Type a -> C.FunDef
mkGenFun name expr ty =
C.FunDef static cTy name [] cVars stmts
where
static = Just C.Static
cTy = C.decay $ transType ty
(cExpr, state') = runState (transExpr expr) (0, mempty, mempty)
(_, cVars, stmts') = state'
stmts = stmts' ++ [C.Return $ Just cExpr]
-- | Write a generator function for a stream that returns an array.
mkGenFunArray :: String -> String -> Expr a -> Type a -> C.FunDef
mkGenFunArray name nameArg expr ty@(Array _) =
C.FunDef static funType name [ outputParam ] varDecls stmts
where
static = Just C.Static
funType = C.TypeSpec C.Void
-- The output value is an array
outputParam = C.Param cArrayType nameArg
cArrayType = transType ty
-- Statements to execute as part of the generator. They include statements
-- related to calculating the values of intermediate expressions, and
-- statements related to copying the final value to the destination array.
stmts = stmts' ++ [ copyStmt ]
-- Output value, variable declarations needed, and other statements that
-- must be executed to calculate intermediate values.
(cExpr, state') = runState (transExpr expr) (0, mempty, mempty)
(_, varDecls, stmts') = state'
-- Copy expression to output argument.
copyStmt = C.Expr $ memcpy (C.Ident nameArg) cExpr size
size = C.LitInt (fromIntegral $ typeSize ty)
C..* C.SizeOfType (C.TypeName $ tyElemName ty)
mkGenFunArray _name _nameArg _expr _ty =
impossible "mkGenFunArray" "copilot-c99"
-- * Monitor processing
-- | Define the step function that updates all streams.
mkStep :: CSettings -> [Stream] -> [UniqueTrigger] -> [External] -> C.FunDef
mkStep cSettings streams triggers exts =
C.FunDef Nothing void (cSettingsStepFunctionName cSettings) [] declns stmts
where
void = C.TypeSpec C.Void
declns = streamDeclns
++ concat triggerDeclns
stmts = map mkExCopy exts
++ triggerStmts
++ tmpAssigns
++ bufferUpdates
++ indexUpdates
(streamDeclns, tmpAssigns, bufferUpdates, indexUpdates) =
unzip4 $ map mkUpdateGlobals streams
(triggerDeclns, triggerStmts) =
unzip $ map mkTriggerCheck triggers
-- Update the value of a variable with the result of calling a function that
-- generates the next value in a stream expression. If the type of the
-- variable is an array, then we cannot perform a direct C assignment, so
-- we instead pass the variable as an output array to the function.
updateVar :: C.Ident -> C.Ident -> Type a -> C.Expr
updateVar varName genName (Array _) =
C.Funcall (C.Ident genName) [C.Ident varName]
updateVar varName genName _ =
C.AssignOp C.Assign (C.Ident varName) (C.Funcall (C.Ident genName) [])
-- Write code to update global stream buffers and index.
mkUpdateGlobals :: Stream -> (C.Decln, C.Stmt, C.Stmt, C.Stmt)
mkUpdateGlobals (Stream sId buff _expr ty) =
(tmpDecln, tmpAssign, bufferUpdate, indexUpdate)
where
tmpDecln = C.VarDecln Nothing cTy tmpVar Nothing
tmpAssign = C.Expr $ updateVar tmpVar (generatorName sId) ty
bufferUpdate = case ty of
Array _ -> C.Expr $ memcpy dest (C.Ident tmpVar) size
where
dest = C.Index buffVar indexVar
size = C.LitInt
(fromIntegral $ typeSize ty)
C..* C.SizeOfType (C.TypeName (tyElemName ty))
_ -> C.Expr $
C.Index buffVar indexVar C..= C.Ident tmpVar
indexUpdate = C.Expr $ indexVar C..= (incIndex C..% buffLength)
where
-- We cast the buffer length and the literal one to a size_t to
-- make sure that there are no implicit conversions. This is a
-- requirement for compliance with MISRA C (Rule 10.4).
buffLength = C.Cast sizeT $ C.LitInt $ fromIntegral $ length buff
incIndex = indexVar C..+ C.Cast sizeT (C.LitInt 1)
sizeT = C.TypeName $ C.TypeSpec $ C.TypedefName "size_t"
tmpVar = streamName sId ++ "_tmp"
buffVar = C.Ident $ streamName sId
indexVar = C.Ident $ indexName sId
cTy = transType ty
-- Make code that copies an external variable to its local one.
mkExCopy :: External -> C.Stmt
mkExCopy (External name cpyName ty) = C.Expr $ case ty of
Array _ -> memcpy exVar locVar size
where
exVar = C.Ident cpyName
locVar = C.Ident name
size = C.LitInt (fromIntegral $ typeSize ty)
C..* C.SizeOfType (C.TypeName (tyElemName ty))
_ -> C.Ident cpyName C..= C.Ident name
-- Make if-statement to check the guard, call the handler if necessary.
-- This returns two things:
--
-- * A list of Declns for temporary variables, one for each argument that
-- the handler function accepts. For example, if a handler function takes
-- three arguments, the list of Declns might look something like this:
--
-- @
-- int8_t handler_arg_temp0;
-- int16_t handler_arg_temp1;
-- struct s handler_arg_temp2;
-- @
--
-- * A Stmt representing the if-statement. Continuing the example above,
-- the if-statement would look something like this:
--
-- @
-- if (handler_guard()) {
-- handler_arg_temp0 = handler_arg0();
-- handler_arg_temp1 = handler_arg1();
-- handler_arg_temp2 = handler_arg2();
-- handler(handler_arg_temp0, handler_arg_temp1, &handler_arg_temp2);
-- }
-- @
--
-- We create temporary variables because:
--
-- 1. We want to pass structs by reference intead of by value. To this end,
-- we use C's & operator to obtain a reference to a temporary variable
-- of a struct type and pass that to the handler function.
--
-- 2. Assigning a struct to a temporary variable defensively ensures that
-- any modifications that the handler called makes to the struct argument
-- will not affect the internals of the monitoring code.
mkTriggerCheck :: UniqueTrigger -> ([C.Decln], C.Stmt)
mkTriggerCheck (UniqueTrigger uniqueName (Trigger name _guard args)) =
(aTmpDeclns, triggerCheckStmt)
where
aTmpDeclns :: [C.Decln]
aTmpDeclns = zipWith declare args aTempNames
where
declare :: UExpr -> C.Ident -> C.Decln
declare (UExpr ty _) tmpVar =
C.VarDecln Nothing (transType ty) tmpVar Nothing
triggerCheckStmt :: C.Stmt
triggerCheckStmt = C.If guard' fireTrigger
where
guard' = C.Funcall (C.Ident $ guardName uniqueName) []
-- The body of the if-statement. This consists of statements that
-- assign the values of the temporary variables, following by a
-- final statement that passes the temporary variables to the
-- handler function.
-- Note that we call 'name' here instead of 'uniqueName', as 'name'
-- is the name of the actual external function.
fireTrigger = map C.Expr argAssigns
++ [C.Expr $
C.Funcall (C.Ident name)
(zipWith passArg aTempNames args)]
where
-- List of assignments of values of temporary variables.
argAssigns :: [C.Expr]
argAssigns = zipWith3 assign aTempNames aArgNames args
assign :: C.Ident -> C.Ident -> UExpr -> C.Expr
assign aTempName aArgName (UExpr ty _) =
updateVar aTempName aArgName ty
aArgNames :: [C.Ident]
aArgNames = take (length args) (argNames uniqueName)
-- Build an expression to pass a temporary variable as argument
-- to a trigger handler.
--
-- We need to pass a reference to the variable in some cases,
-- so we also need the type of the expression, which is enclosed
-- in the second argument, an UExpr.
passArg :: String -> UExpr -> C.Expr
passArg aTempName (UExpr ty _) =
case ty of
-- Special case for Struct to pass reference to temporary
-- struct variable to handler. (See the comments for
-- mktriggercheck for details.)
Struct _ -> C.UnaryOp C.Ref $ C.Ident aTempName
_ -> C.Ident aTempName
aTempNames :: [String]
aTempNames = take (length args) (argTempNames uniqueName)
-- * Auxiliary functions
-- Write a call to the memcpy function.
memcpy :: C.Expr -> C.Expr -> C.Expr -> C.Expr
memcpy dest src size = C.Funcall (C.Ident "memcpy") [dest, src, size]
-- Translate a Copilot type to a C99 type, handling arrays especially.
--
-- If the given type is an array (including multi-dimensional arrays), the
-- type is that of the elements in the array. Otherwise, it is just the
-- equivalent representation of the given type in C.
tyElemName :: Type a -> C.Type
tyElemName ty = case ty of
Array ty' -> tyElemName ty'
_ -> transType ty