-
Notifications
You must be signed in to change notification settings - Fork 75
Expand file tree
/
Copy pathEval.hs
More file actions
406 lines (359 loc) · 14.8 KB
/
Eval.hs
File metadata and controls
406 lines (359 loc) · 14.8 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
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
-- Copyright © 2011 National Institute of Aerospace / Galois, Inc.
-- | A tagless interpreter for Copilot specifications.
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE Safe #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Copilot.Interpret.Eval
( Env
, Output
, ExecTrace (..)
, eval
, ShowType (..)
) where
import Copilot.Core (Expr (..), Field (..), Id, Name, Observer (..),
Op1 (..), Op2 (..), Op3 (..), Spec, Stream (..),
Trigger (..), Type (..), UExpr (..), Value (..),
arrayElems, arrayUpdate, specObservers,
specStreams, specTriggers, updateField)
import Copilot.Interpret.Error (badUsage)
import Prelude hiding (id)
import qualified Prelude as P
import Control.Exception (Exception, throw)
import Data.Bits (complement, shiftL, shiftR, xor, (.&.), (.|.))
import Data.Dynamic (Dynamic, fromDynamic, toDyn)
import Data.List (transpose)
import Data.Maybe (fromJust)
import Data.Typeable (Typeable)
import GHC.TypeLits (KnownNat, Nat, natVal)
-- | Exceptions that may be thrown during interpretation of a Copilot
-- specification.
data InterpException
= ArrayWrongSize Name Int -- ^ Extern array has incorrect size.
| ArrayIdxOutofBounds Name Int Int -- ^ Index out-of-bounds exception.
| DivideByZero -- ^ Division by zero.
| NotEnoughValues Name Int -- ^ For one or more streams, not enough
-- values are available to simulate the
-- number of steps requested.
| NoExtsInterp Name -- ^ One of the externs used by the
-- specification does not declare
-- sample values to be used during
-- simulation.
deriving Typeable
-- | Show a descriptive message of the exception.
instance Show InterpException where
---------------------------------------
show (ArrayWrongSize name expectedSize) =
badUsage $ "in the environment for external array " ++ name
++ ", we expect a list of length " ++ show expectedSize
++ ", but the length of the array you supplied is of a different length."
---------------------------------------
show (ArrayIdxOutofBounds name index size) =
badUsage $ "in the environment for external array " ++ name
++ ", you gave an index of " ++ show index
++ " where the size of the array is " ++ show size ++ "; the size must "
++ " be strictly greater than the index."
---------------------------------------
show DivideByZero =
badUsage "divide by zero."
---------------------------------------
show (NotEnoughValues name k) =
badUsage $ "on the " ++ show k ++ "th iteration, we ran out of "
++ "values for simulating the external element " ++ name ++ "."
---------------------------------------
show (NoExtsInterp name) =
badUsage $ "in a call of external symbol " ++ name ++ ", you did not "
++ "provide an expression for interpretation. In your external "
++ "declaration, you need to provide a 'Just strm', where 'strm' is "
++ "some stream with which to simulate the function."
---------------------------------------
-- | Allow throwing and catching 'InterpException' using Haskell's standard
-- exception mechanisms.
instance Exception InterpException
-- | An environment that contains an association between (stream or extern)
-- names and their values.
type Env nm = [(nm, Dynamic)]
-- | The simulation output is defined as a string. Different backends may
-- choose to format their results differently.
type Output = String
-- | An execution trace, containing the traces associated to each individual
-- monitor trigger and observer.
data ExecTrace = ExecTrace
{ interpTriggers :: [(String, [Maybe [Output]])]
-- ^ Map from trigger names to their optional output, which is a list of
-- strings representing their values. The output may be 'Nothing' if the
-- guard for the trigger was false. The order is important, since we
-- compare the arg lists between the interpreter and backends.
, interpObservers :: [(String, [Output])]
-- ^ Map from observer names to their outputs.
}
deriving Show
-- We could write this in a beautiful lazy style like above, but that creates a
-- space leak in the interpreter that is hard to fix while maintaining laziness.
-- We take a more brute-force approach below.
-- | Evaluate a specification for a number of steps.
eval :: ShowType -- ^ Show booleans as @0@\/@1@ (C) or @True@\/@False@
-- (Haskell).
-> Int -- ^ Number of steps to evaluate.
-> Spec -- ^ Specification to evaluate.
-> ExecTrace
eval showType k spec =
let initStrms = map initStrm (specStreams spec) in
let strms = evalStreams k (specStreams spec) initStrms in
let trigs = map (evalTrigger showType k strms)
(specTriggers spec) in
let obsvs = map (evalObserver showType k strms)
(specObservers spec) in
strms `seq` ExecTrace
{ interpTriggers =
zip (map triggerName (specTriggers spec)) trigs
, interpObservers =
zip (map observerName (specObservers spec)) obsvs
}
-- | An environment that contains an association between (stream or extern)
-- names and their values.
type LocalEnv = [(Name, Dynamic)]
-- | Evaluate an expression for a number of steps, obtaining the value
-- of the sample at that time.
evalExpr_ :: Typeable a => Int -> Expr a -> LocalEnv -> Env Id -> a
evalExpr_ k e0 locs strms = case e0 of
Const _ x -> x
Drop t i id ->
let Just buff = lookup id strms >>= fromDynamic in
reverse buff !! (fromIntegral i + k)
Local t1 _ name e1 e2 ->
let x = evalExpr_ k e1 locs strms in
let locs' = (name, toDyn x) : locs in
x `seq` locs' `seq` evalExpr_ k e2 locs' strms
Var t name -> fromJust $ lookup name locs >>= fromDynamic
ExternVar _ name xs -> evalExternVar k name xs
Op1 op e1 ->
let ev1 = evalExpr_ k e1 locs strms in
let op1 = evalOp1 op in
ev1 `seq` op1 `seq` op1 ev1
Op2 op e1 e2 ->
let ev1 = evalExpr_ k e1 locs strms in
let ev2 = evalExpr_ k e2 locs strms in
let op2 = evalOp2 op in
ev1 `seq` ev2 `seq` op2 `seq` op2 ev1 ev2
Op3 op e1 e2 e3 ->
let ev1 = evalExpr_ k e1 locs strms in
let ev2 = evalExpr_ k e2 locs strms in
let ev3 = evalExpr_ k e3 locs strms in
let op3 = evalOp3 op in
ev1 `seq` ev2 `seq` ev3 `seq` op3 `seq` op3 ev1 ev2 ev3
Label _ _ e1 ->
let ev1 = evalExpr_ k e1 locs strms in
ev1
-- | Evaluate an extern stream for a number of steps, obtaining the value of
-- the sample at that time.
evalExternVar :: Int -> Name -> Maybe [a] -> a
evalExternVar k name exts =
case exts of
Nothing -> throw (NoExtsInterp name)
Just xs ->
case safeIndex k xs of
Nothing -> throw (NotEnoughValues name k)
Just x -> x
-- | Evaluate an 'Copilot.Core.Operators.Op1' by producing an equivalent
-- Haskell function operating on the same types as the
-- 'Copilot.Core.Operators.Op1'.
evalOp1 :: Op1 a b -> (a -> b)
evalOp1 op = case op of
Not -> P.not
Abs _ -> P.abs
Sign _ -> P.signum
Recip _ -> P.recip
Exp _ -> P.exp
Sqrt _ -> P.sqrt
Log _ -> P.log
Sin _ -> P.sin
Tan _ -> P.tan
Cos _ -> P.cos
Asin _ -> P.asin
Atan _ -> P.atan
Acos _ -> P.acos
Sinh _ -> P.sinh
Tanh _ -> P.tanh
Cosh _ -> P.cosh
Asinh _ -> P.asinh
Atanh _ -> P.atanh
Acosh _ -> P.acosh
Ceiling _ -> P.fromIntegral . idI . P.ceiling
Floor _ -> P.fromIntegral . idI . P.floor
BwNot _ -> complement
Cast _ _ -> P.fromIntegral
GetField (Struct _) _ f -> unfield . f
where
-- Used to help GHC pick a return type for ceiling/floor
idI :: Integer -> Integer
idI = P.id
-- Extract value from field
unfield (Field v) = v
-- | Evaluate an 'Copilot.Core.Operators.Op2' by producing an equivalent
-- Haskell function operating on the same types as the
-- 'Copilot.Core.Operators.Op2'.
evalOp2 :: Op2 a b c -> (a -> b -> c)
evalOp2 op = case op of
And -> (&&)
Or -> (||)
Add _ -> (+)
Sub _ -> (-)
Mul _ -> (*)
Mod _ -> (catchZero P.mod)
Div _ -> (catchZero P.quot)
Fdiv _ -> (P./)
Pow _ -> (P.**)
Logb _ -> P.logBase
Atan2 _ -> P.atan2
Eq _ -> (==)
Ne _ -> (/=)
Le _ -> (<=)
Ge _ -> (>=)
Lt _ -> (<)
Gt _ -> (>)
BwAnd _ -> (.&.)
BwOr _ -> (.|.)
BwXor _ -> (xor)
BwShiftL _ _ -> ( \ !a !b -> shiftL a $! fromIntegral b )
BwShiftR _ _ -> ( \ !a !b -> shiftR a $! fromIntegral b )
Index _ -> \xs n -> (arrayElems xs) !! (fromIntegral n)
UpdateField (Struct _) ty (fieldAccessor :: a -> Field s b) ->
\stream fieldValue ->
let newField :: Field s b
newField = Field fieldValue
in updateField stream (Value ty newField)
-- | Apply a function to two numbers, so long as the second one is
-- not zero.
--
-- Used to detect attempts at dividing by zero and produce the appropriate
-- 'InterpException'.
catchZero :: Integral a => (a -> a -> a) -> (a -> a -> a)
catchZero _ _ 0 = throw DivideByZero
catchZero f x y = f x y
-- | Evaluate an 'Copilot.Core.Operators.Op3' by producing an equivalent
-- Haskell function operating on the same types as the
-- 'Copilot.Core.Operators.Op3'.
evalOp3 :: Op3 a b c d -> (a -> b -> c -> d)
evalOp3 (Mux _) = \ !v !x !y -> if v then x else y
evalOp3 (UpdateArray ty) = \xs n x -> arrayUpdate xs (fromIntegral n) x
-- | Turn a stream into a key-value pair that can be added to an 'Env' for
-- simulation.
initStrm :: Stream -> (Id, Dynamic)
initStrm Stream { streamId = id
, streamBuffer = buffer
, streamExprType = t } =
(id, toDyn (reverse buffer))
-- | Evaluate several streams for a number of steps, producing the environment
-- at the end of the evaluation.
evalStreams :: Int -> [Stream] -> Env Id -> Env Id
evalStreams top specStrms initStrms =
-- XXX actually only need to compute until shortest stream is of length k
-- XXX this should just be a foldl' over [0,1..k]
evalStreams_ 0 initStrms
where
evalStreams_ :: Int -> Env Id -> Env Id
evalStreams_ k strms | k == top = strms
evalStreams_ k strms | otherwise =
evalStreams_ (k+1) $! strms_
where
strms_ = map evalStream specStrms
evalStream Stream { streamId = id
, streamExpr = e
, streamExprType = t } =
let xs = fromJust $ lookup id strms >>= fromDynamic in
let x = evalExpr_ k e [] strms in
let ls = x `seq` (x:xs) in
(id, toDyn ls)
-- | Evaluate a trigger for a number of steps.
evalTrigger :: ShowType -- ^ Show booleans as @0@/@1@ (C) or
-- @True@/@False@ (Haskell).
-> Int -- ^ Number of steps to evaluate.
-> Env Id -- ^ Environment to use with known
-- stream-value associations.
-> Trigger -- ^ Trigger to evaluate.
-> [Maybe [Output]]
evalTrigger showType k strms
Trigger
{ triggerGuard = e
, triggerArgs = args
} = map tag (zip bs vs)
where
tag :: (Bool, a) -> Maybe a
tag (True, x) = Just x
tag (False, _) = Nothing
-- Is the guard true?
bs :: [Bool]
bs = evalExprs_ k e strms
-- The argument outputs.
vs :: [[Output]]
vs = if null args then replicate k [] -- might be 0 args.
else transpose $ map evalUExpr args
evalUExpr :: UExpr -> [Output]
evalUExpr (UExpr t e1) =
map (showWithType showType t) (evalExprs_ k e1 strms)
-- | Evaluate an observer for a number of steps.
evalObserver :: ShowType -- ^ Show booleans as @0@/@1@ (C) or @True@/@False@
-- (Haskell).
-> Int -- ^ Number of steps to evaluate.
-> Env Id -- ^ Environment to use with known stream-value
-- associations.
-> Observer -- ^ Observer to evaluate.
-> [Output]
evalObserver showType k strms
Observer
{ observerExpr = e
, observerExprType = t }
= map (showWithType showType t) (evalExprs_ k e strms)
-- | Evaluate an expression for a number of steps, producing a list with the
-- changing value of the expression until that time.
evalExprs_ :: Typeable a => Int -> Expr a -> Env Id -> [a]
evalExprs_ k e strms =
map (\i -> evalExpr_ i e [] strms) [0..(k-1)]
-- | Safe indexing (!!) on possibly infininite lists.
safeIndex :: Int -> [a] -> Maybe a
safeIndex i ls =
let ls' = take (i+1) ls in
if length ls' > i then Just (ls' !! i)
else Nothing
-- * Auxiliary
-- Are we proving equivalence with a C backend, in which case we want to show
-- Booleans as '0' and '1'.
-- | Target language for showing a typed value. Used to adapt the
-- representation of booleans.
data ShowType = C | Haskell
-- | Show a value. The representation depends on the type and the target
-- language. Booleans are represented differently depending on the backend.
showWithType :: ShowType -> Type a -> a -> String
showWithType showT t x =
case showT of
C -> case t of
Bool -> if x then "1" else "0"
_ -> sw
Haskell -> case t of
Bool -> if x then "true" else "false"
_ -> sw
where
sw = case showWit t of
ShowWit -> show x
-- * Auxiliary show instance
-- | Witness datatype for showing a value, used by 'showWithType'.
data ShowWit a = Show a => ShowWit
-- | Turn a type into a show witness.
showWit :: Type a -> ShowWit a
showWit t =
case t of
Bool -> ShowWit
Int8 -> ShowWit
Int16 -> ShowWit
Int32 -> ShowWit
Int64 -> ShowWit
Word8 -> ShowWit
Word16 -> ShowWit
Word32 -> ShowWit
Word64 -> ShowWit
Float -> ShowWit
Double -> ShowWit
Array t -> ShowWit
Struct t -> ShowWit