-
Notifications
You must be signed in to change notification settings - Fork 77
Expand file tree
/
Copy pathTranslate.hs
More file actions
1504 lines (1386 loc) · 62.5 KB
/
Translate.hs
File metadata and controls
1504 lines (1386 loc) · 62.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
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
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}
-- |
-- Module : Copilot.Theorem.What4.Translate
-- Description : Translate Copilot specifications into What4
-- Copyright : (c) Galois Inc., 2021-2022
-- Maintainer : robdockins@galois.com
-- Stability : experimental
-- Portability : POSIX
--
-- Translate Copilot specifications to What4 formulas using the 'TransM' monad.
module Copilot.Theorem.What4.Translate
( -- * Translation into What4
TransState(..)
, TransM
, runTransM
, LocalEnv
, translateExpr
, translateConstExpr
, getStreamValue
, getExternConstant
-- * What4 representations of Copilot expressions
, XExpr(..)
-- * Stream offsets
, StreamOffset(..)
-- * Auxiliary functions
, panic
) where
import Control.Monad (forM, zipWithM)
import qualified Control.Monad.Fail as Fail
import Control.Monad.IO.Class (MonadIO (..))
import Control.Monad.State (MonadState (..), StateT (..),
gets, modify)
import qualified Data.BitVector.Sized as BV
import Data.IORef (newIORef, modifyIORef,
readIORef)
import Data.List (elemIndex, genericIndex,
genericLength)
import qualified Data.Map as Map
import Data.Maybe (fromJust)
import Data.Parameterized.Classes (KnownRepr (..))
import Data.Parameterized.Context (EmptyCtx, type (::>))
import Data.Parameterized.NatRepr (LeqProof (..), NatCases (..),
NatRepr, decNat, incNat,
intValue, isZeroOrGT1,
knownNat, minusPlusCancel,
mkNatRepr, testNatCases,
testStrictLeq)
import Data.Parameterized.Some (Some (..))
import Data.Parameterized.SymbolRepr (SymbolRepr, knownSymbol)
import qualified Data.Parameterized.Vector as V
import Data.Type.Equality (TestEquality (..), (:~:) (..))
import Data.Word (Word32)
import GHC.TypeLits (KnownSymbol)
import GHC.TypeNats (KnownNat, type (<=), type (+))
import qualified Panic as Panic
import qualified What4.BaseTypes as WT
import qualified What4.Interface as WI
import qualified What4.InterpretedFloatingPoint as WFP
import qualified What4.SpecialFunctions as WSF
import qualified Copilot.Core.Expr as CE
import qualified Copilot.Core.Operators as CE
import qualified Copilot.Core.Spec as CS
import qualified Copilot.Core.Type as CT
import qualified Copilot.Core.Type.Array as CT
import qualified Copilot.PrettyPrint as CP
-- Translation into What4
-- | The state for translating Copilot expressions into What4 expressions. As we
-- translate, we generate fresh symbolic constants for external variables and
-- for stream variables. We need to only generate one constant per variable, so
-- we allocate them in a map. When we need the constant for a particular
-- variable, we check if it is already in the map, and return it if it is; if it
-- isn't, we generate a fresh constant at that point, store it in the map, and
-- return it.
--
-- We also store 'streams', an immutable field, in this state, rather than wrap
-- it up in another monad transformer layer. This is initialized prior to
-- translation and is never modified. This maps from stream ids to the
-- core stream definitions.
data TransState sym = TransState {
-- | Map keeping track of all external variables encountered during
-- translation.
mentionedExternals :: Map.Map CE.Name (Some CT.Type),
-- | Memo table for external variables, indexed by the external stream name
-- and a stream offset.
externVars :: Map.Map (CE.Name, StreamOffset) (XExpr sym),
-- | Memo table for stream values, indexed by the stream 'CE.Id' and offset.
streamValues :: Map.Map (CE.Id, StreamOffset) (XExpr sym),
-- | Map from stream ids to the streams themselves. This value is never
-- modified, but I didn't want to make this an RWS, so it's represented as a
-- stateful value.
streams :: Map.Map CE.Id CS.Stream,
-- | A list of side conditions that must be true in order for all applications
-- of partial functions (e.g., 'CE.Div') to be well defined.
sidePreds :: [WI.Pred sym]
}
newtype TransM sym a = TransM { unTransM :: StateT (TransState sym) IO a }
deriving ( Functor
, Applicative
, Monad
, Fail.MonadFail
, MonadIO
, MonadState (TransState sym)
)
-- | Translate a Copilot specification using the given 'TransM' computation.
runTransM :: CS.Spec -> TransM sym a -> IO a
runTransM spec m = do
-- Build up initial translation state
let streamMap = Map.fromList $
(\stream -> (CS.streamId stream, stream)) <$> CS.specStreams spec
st = TransState
{ mentionedExternals = mempty
, externVars = mempty
, streamValues = mempty
, streams = streamMap
, sidePreds = []
}
(res, _) <- runStateT (unTransM m) st
return res
-- | An environment used to translate local Copilot variables to What4.
type LocalEnv sym = Map.Map CE.Name (StreamOffset -> TransM sym (XExpr sym))
-- | Compute the value of a stream expression at the given offset in the given
-- local environment.
translateExpr :: forall sym a.
WFP.IsInterpretedFloatSymExprBuilder sym
=> sym
-> LocalEnv sym
-- ^ Environment for local variables
-> CE.Expr a
-- ^ Expression to translate
-> StreamOffset
-- ^ Offset to compute
-> TransM sym (XExpr sym)
translateExpr sym localEnv e offset = case e of
CE.Const tp a -> liftIO $ translateConstExpr sym tp a
CE.Drop _tp ix streamId -> getStreamValue sym streamId (addOffset offset ix)
CE.Local _tpa _tpb nm e1 body -> do
ref <- liftIO (newIORef mempty)
-- Look up a stream value by offset, using an IORef to cache values that
-- have already been looked up previously. Caching values in this way avoids
-- exponential blowup.
--
-- Note that using a single IORef to store all local variables means that it
-- is possible for local variables to escape their lexical scope. See issue
-- #253 for more information. This is an issue that is shared in common with
-- `copilot-c99` and the Copilot interpreter.
let f :: StreamOffset -> TransM sym (XExpr sym)
f offset' = do
m <- liftIO (readIORef ref)
case Map.lookup offset' m of
-- If we have looked up this value before, return the cached value.
Just x -> return x
-- Otherwise, translate the expression and cache it for subsequent
-- lookups.
Nothing ->
do x <- translateExpr sym localEnv e1 offset'
liftIO (modifyIORef ref (Map.insert offset' x))
return x
let localEnv' = Map.insert nm f localEnv
translateExpr sym localEnv' body offset
CE.Var _tp nm ->
case Map.lookup nm localEnv of
Nothing -> panic ["translateExpr: unknown var " ++ show nm]
Just f -> f offset
CE.ExternVar tp nm _prefix -> getExternConstant sym tp nm offset
CE.Op1 op e1 -> do
xe1 <- translateExpr sym localEnv e1 offset
translateOp1 sym e op xe1
CE.Op2 op e1 e2 -> do
xe1 <- translateExpr sym localEnv e1 offset
xe2 <- translateExpr sym localEnv e2 offset
translateOp2 sym e op xe1 xe2
CE.Op3 op e1 e2 e3 -> do
xe1 <- translateExpr sym localEnv e1 offset
xe2 <- translateExpr sym localEnv e2 offset
xe3 <- translateExpr sym localEnv e3 offset
translateOp3 sym e op xe1 xe2 xe3
CE.Label _ _ e1 ->
translateExpr sym localEnv e1 offset
-- | Compute and cache the value of a stream with the given identifier at the
-- given offset.
getStreamValue :: WFP.IsInterpretedFloatSymExprBuilder sym
=> sym
-> CE.Id
-> StreamOffset
-> TransM sym (XExpr sym)
getStreamValue sym streamId offset = do
svs <- gets streamValues
case Map.lookup (streamId, offset) svs of
Just xe -> return xe
Nothing -> do
streamDef <- getStreamDef streamId
xe <- computeStreamValue streamDef
modify $ \st ->
st { streamValues =
Map.insert (streamId, offset) xe (streamValues st) }
return xe
where
computeStreamValue
(CS.Stream
{ CS.streamId = id, CS.streamBuffer = buf,
CS.streamExpr = ex, CS.streamExprType = tp }) =
let len = genericLength buf in
case offset of
AbsoluteOffset i
| i < 0 -> panic ["Invalid absolute offset " ++ show i ++
" for stream " ++ show id]
| i < len -> liftIO (translateConstExpr sym tp (genericIndex buf i))
| otherwise -> translateExpr sym mempty ex (AbsoluteOffset (i - len))
RelativeOffset i
| i < 0 -> panic ["Invalid relative offset " ++ show i ++
" for stream " ++ show id]
| i < len -> let nm = "s" ++ show id ++ "_r" ++ show i
in liftIO (freshCPConstant sym nm tp)
| otherwise -> translateExpr sym mempty ex (RelativeOffset (i - len))
-- | Compute and cache the value of an external stream with the given name at
-- the given offset.
getExternConstant :: WFP.IsInterpretedFloatSymExprBuilder sym
=> sym
-> CT.Type a
-> CE.Name
-> StreamOffset
-> TransM sym (XExpr sym)
getExternConstant sym tp nm offset = do
es <- gets externVars
case Map.lookup (nm, offset) es of
Just xe -> return xe
Nothing -> do
xe <- computeExternConstant
modify $ \st ->
st { externVars = Map.insert (nm, offset) xe (externVars st)
, mentionedExternals =
Map.insert nm (Some tp) (mentionedExternals st)
}
return xe
where
computeExternConstant =
case offset of
AbsoluteOffset i
| i < 0 -> panic ["Invalid absolute offset " ++ show i ++
" for external stream " ++ nm]
| otherwise -> let nm' = nm ++ "_a" ++ show i
in liftIO (freshCPConstant sym nm' tp)
RelativeOffset i
| i < 0 -> panic ["Invalid relative offset " ++ show i ++
" for external stream " ++ nm]
| otherwise -> let nm' = nm ++ "_r" ++ show i
in liftIO (freshCPConstant sym nm' tp)
-- | A view of an XExpr as a bitvector expression, a natrepr for its width, its
-- signed/unsigned status, and the constructor used to reconstruct an XExpr from
-- it. This is a useful view for translation, as many of the operations can be
-- grouped together for all words\/ints\/floats.
data SomeBVExpr sym where
SomeBVExpr :: 1 <= w
=> WI.SymBV sym w
-> NatRepr w
-> BVSign
-> (WI.SymBV sym w -> XExpr sym)
-> SomeBVExpr sym
-- | The sign of a bitvector -- this indicates whether it is to be interpreted
-- as a signed 'Int' or an unsigned 'Word'.
data BVSign = Signed | Unsigned
deriving Eq
-- | If the inner expression can be viewed as a bitvector, we project out a view
-- of it as such.
asBVExpr :: XExpr sym -> Maybe (SomeBVExpr sym)
asBVExpr xe = case xe of
XInt8 e -> Just (SomeBVExpr e knownNat Signed XInt8)
XInt16 e -> Just (SomeBVExpr e knownNat Signed XInt16)
XInt32 e -> Just (SomeBVExpr e knownNat Signed XInt32)
XInt64 e -> Just (SomeBVExpr e knownNat Signed XInt64)
XWord8 e -> Just (SomeBVExpr e knownNat Unsigned XWord8)
XWord16 e -> Just (SomeBVExpr e knownNat Unsigned XWord16)
XWord32 e -> Just (SomeBVExpr e knownNat Unsigned XWord32)
XWord64 e -> Just (SomeBVExpr e knownNat Unsigned XWord64)
_ -> Nothing
-- | If an 'XExpr' is a bitvector expression, use it to generate a side
-- condition involving an application of a partial function. Otherwise, do
-- nothing.
addBVSidePred1 :: WI.IsExprBuilder sym
=> XExpr sym
-> (forall w.
1 <= w
=> WI.SymBV sym w
-> NatRepr w
-> BVSign
-> IO (WI.Pred sym))
-> TransM sym ()
addBVSidePred1 xe makeSidePred =
case asBVExpr xe of
Just (SomeBVExpr e w sgn _) -> do
sidePred <- liftIO $ makeSidePred e w sgn
addSidePred sidePred
Nothing -> pure ()
-- | If two 'XExpr's are both bitvector expressions of the same type and
-- signedness, use them to generate a side condition involving an application of
-- a partial function. Otherwise, do nothing.
addBVSidePred2 :: WI.IsExprBuilder sym
=> XExpr sym
-> XExpr sym
-> (forall w.
1 <= w
=> WI.SymBV sym w
-> WI.SymBV sym w
-> NatRepr w
-> BVSign
-> IO (WI.Pred sym))
-> TransM sym ()
addBVSidePred2 xe1 xe2 makeSidePred =
case (asBVExpr xe1, asBVExpr xe2) of
(Just (SomeBVExpr e1 w1 sgn1 _), Just (SomeBVExpr e2 w2 sgn2 _))
| Just Refl <- testEquality w1 w2
, sgn1 == sgn2
-> do sidePred <- liftIO $ makeSidePred e1 e2 w1 sgn1
addSidePred sidePred
_ -> pure ()
-- | Translate a constant expression by creating a what4 literal and packaging
-- it up into an 'XExpr'.
translateConstExpr :: forall sym a.
WFP.IsInterpretedFloatExprBuilder sym
=> sym
-> CT.Type a
-> a
-> IO (XExpr sym)
translateConstExpr sym tp a = case tp of
CT.Bool -> case a of
True -> return $ XBool (WI.truePred sym)
False -> return $ XBool (WI.falsePred sym)
CT.Int8 -> XInt8 <$> WI.bvLit sym knownNat (BV.int8 a)
CT.Int16 -> XInt16 <$> WI.bvLit sym knownNat (BV.int16 a)
CT.Int32 -> XInt32 <$> WI.bvLit sym knownNat (BV.int32 a)
CT.Int64 -> XInt64 <$> WI.bvLit sym knownNat (BV.int64 a)
CT.Word8 -> XWord8 <$> WI.bvLit sym knownNat (BV.word8 a)
CT.Word16 -> XWord16 <$> WI.bvLit sym knownNat (BV.word16 a)
CT.Word32 -> XWord32 <$> WI.bvLit sym knownNat (BV.word32 a)
CT.Word64 -> XWord64 <$> WI.bvLit sym knownNat (BV.word64 a)
CT.Float -> XFloat <$> WFP.iFloatLitSingle sym a
CT.Double -> XDouble <$> WFP.iFloatLitDouble sym a
CT.Array tp -> do
elts <- traverse (translateConstExpr sym tp) (CT.arrayElems a)
Some n <- return $ mkNatRepr (genericLength elts)
case isZeroOrGT1 n of
Left Refl -> return XEmptyArray
Right LeqProof -> do
let Just v = V.fromList n elts
return $ XArray v
CT.Struct _ -> do
elts <- forM (CT.toValues a) $ \(CT.Value tp (CT.Field a)) ->
translateConstExpr sym tp a
return $ XStruct elts
arrayLen :: KnownNat n => CT.Type (CT.Array n t) -> NatRepr n
arrayLen _ = knownNat
-- | Generate a fresh constant for a given copilot type. This will be called
-- whenever we attempt to get the constant for a given external variable or
-- stream variable, but that variable has not been accessed yet and therefore
-- has no constant allocated.
freshCPConstant :: forall sym a .
WFP.IsInterpretedFloatSymExprBuilder sym
=> sym
-> String
-> CT.Type a
-> IO (XExpr sym)
freshCPConstant sym nm tp = case tp of
CT.Bool -> XBool <$> WI.freshConstant sym (WI.safeSymbol nm) knownRepr
CT.Int8 -> XInt8 <$> WI.freshConstant sym (WI.safeSymbol nm) knownRepr
CT.Int16 -> XInt16 <$> WI.freshConstant sym (WI.safeSymbol nm) knownRepr
CT.Int32 -> XInt32 <$> WI.freshConstant sym (WI.safeSymbol nm) knownRepr
CT.Int64 -> XInt64 <$> WI.freshConstant sym (WI.safeSymbol nm) knownRepr
CT.Word8 -> XWord8 <$> WI.freshConstant sym (WI.safeSymbol nm) knownRepr
CT.Word16 -> XWord16 <$> WI.freshConstant sym (WI.safeSymbol nm) knownRepr
CT.Word32 -> XWord32 <$> WI.freshConstant sym (WI.safeSymbol nm) knownRepr
CT.Word64 -> XWord64 <$> WI.freshConstant sym (WI.safeSymbol nm) knownRepr
CT.Float -> XFloat <$>
WFP.freshFloatConstant sym (WI.safeSymbol nm) WFP.SingleFloatRepr
CT.Double -> XDouble <$>
WFP.freshFloatConstant sym (WI.safeSymbol nm) WFP.DoubleFloatRepr
atp@(CT.Array itp) -> do
let n = arrayLen atp
case isZeroOrGT1 n of
Left Refl -> return XEmptyArray
Right LeqProof -> do
Refl <- return $ minusPlusCancel n (knownNat @1)
elts :: V.Vector n (XExpr t) <-
V.generateM (decNat n) (const (freshCPConstant sym "" itp))
return $ XArray elts
CT.Struct stp -> do
elts <- forM (CT.toValues stp) $ \(CT.Value ftp _) ->
freshCPConstant sym "" ftp
return $ XStruct elts
-- | Retrieve a stream definition given its id.
getStreamDef :: CE.Id -> TransM sym CS.Stream
getStreamDef streamId = fromJust <$> gets (Map.lookup streamId . streams)
-- | Add a side condition originating from an application of a partial function.
addSidePred :: WI.Pred sym -> TransM sym ()
addSidePred newPred = modify (\st -> st { sidePreds = newPred : sidePreds st })
-- * Translate Ops
-- Note [Side conditions for floating-point operations]
-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- We do not currently track side conditions for floating-point operations, as
-- they are unlikely to matter. A typical client of copilot-theorem will likely
-- treat floating-point operations as uninterpreted functions, and side
-- conditions involving uninterpreted functions are very unlikely to be helpful
-- except in very specific circumstances. In case we revisit this decision
-- later, we make a note of which floating-point operations could potentially
-- track side conditions as comments (but without implementing them).
type BVOp1 sym w = (KnownNat w, 1 <= w) => WI.SymBV sym w -> IO (WI.SymBV sym w)
type FPOp1 sym fi =
WFP.FloatInfoRepr fi
-> WI.SymExpr sym (WFP.SymInterpretedFloatType sym fi)
-> IO (WI.SymExpr sym (WFP.SymInterpretedFloatType sym fi))
fieldName :: KnownSymbol s => CT.Field s a -> SymbolRepr s
fieldName _ = knownSymbol
valueName :: CT.Value a -> Some SymbolRepr
valueName (CT.Value _ f) = Some (fieldName f)
translateOp1 :: forall sym a b .
WFP.IsInterpretedFloatExprBuilder sym
=> sym
-> CE.Expr b
-- ^ Original value we are translating (only used for error
-- messages)
-> CE.Op1 a b
-> XExpr sym
-> TransM sym (XExpr sym)
translateOp1 sym origExpr op xe = case (op, xe) of
(CE.Not, XBool e) -> liftIO $ fmap XBool $ WI.notPred sym e
(CE.Not, _) -> panic ["Expected bool", show xe]
(CE.Abs _, xe) -> translateAbs xe
(CE.Sign _, xe) -> translateSign xe
-- We do not track any side conditions for floating-point operations
-- (see Note [Side conditions for floating-point operations]), but we will
-- make a note of which operations have partial inputs.
-- The argument should not be zero
(CE.Recip _, xe) -> liftIO $ fpOp recip xe
where
recip :: forall fi . FPOp1 sym fi
recip fiRepr e = do
one <- fpLit fiRepr 1.0
WFP.iFloatDiv @_ @fi sym fpRM one e
-- The argument should not cause the result to overflow or underflow
(CE.Exp _, xe) -> liftIO $ fpSpecialOp WSF.Exp xe
-- The argument should not be less than -0
(CE.Sqrt _, xe) ->
liftIO $
fpOp (\(_ :: WFP.FloatInfoRepr fi) -> WFP.iFloatSqrt @_ @fi sym fpRM) xe
-- The argument should not be negative or zero
(CE.Log _, xe) -> liftIO $ fpSpecialOp WSF.Log xe
-- The argument should not be infinite
(CE.Sin _, xe) -> liftIO $ fpSpecialOp WSF.Sin xe
-- The argument should not be infinite
(CE.Cos _, xe) -> liftIO $ fpSpecialOp WSF.Cos xe
-- The argument should not be infinite, nor should it cause the result to
-- overflow
(CE.Tan _, xe) -> liftIO $ fpSpecialOp WSF.Tan xe
-- The argument should not cause the result to overflow
(CE.Sinh _, xe) -> liftIO $ fpSpecialOp WSF.Sinh xe
-- The argument should not cause the result to overflow
(CE.Cosh _, xe) -> liftIO $ fpSpecialOp WSF.Cosh xe
(CE.Tanh _, xe) -> liftIO $ fpSpecialOp WSF.Tanh xe
-- The argument should not be outside the range [-1, 1]
(CE.Asin _, xe) -> liftIO $ fpSpecialOp WSF.Arcsin xe
-- The argument should not be outside the range [-1, 1]
(CE.Acos _, xe) -> liftIO $ fpSpecialOp WSF.Arccos xe
(CE.Atan _, xe) -> liftIO $ fpSpecialOp WSF.Arctan xe
(CE.Asinh _, xe) -> liftIO $ fpSpecialOp WSF.Arcsinh xe
-- The argument should not be less than 1
(CE.Acosh _, xe) -> liftIO $ fpSpecialOp WSF.Arccosh xe
-- The argument should not be less than or equal to -1,
-- nor should it be greater than or equal to +1
(CE.Atanh _, xe) -> liftIO $ fpSpecialOp WSF.Arctanh xe
-- The argument should not cause the result to overflow
(CE.Ceiling _, xe) ->
liftIO $
fpOp (\(_ :: WFP.FloatInfoRepr fi) -> WFP.iFloatRound @_ @fi sym WI.RTP) xe
-- The argument should not cause the result to overflow
(CE.Floor _, xe) ->
liftIO $
fpOp (\(_ :: WFP.FloatInfoRepr fi) -> WFP.iFloatRound @_ @fi sym WI.RTN) xe
(CE.BwNot _, xe) -> liftIO $ case xe of
XBool e -> XBool <$> WI.notPred sym e
_ -> bvOp (WI.bvNotBits sym) xe
(CE.Cast _ tp, xe) -> liftIO $ castOp sym origExpr tp xe
(CE.GetField atp _ftp extractor, xe) -> translateGetField atp extractor xe
where
-- Translate an 'CE.Abs' operation and its argument into a what4
-- representation of the appropriate type.
translateAbs :: XExpr sym -> TransM sym (XExpr sym)
translateAbs xe = do
addBVSidePred1 xe $ \e w _ -> do
-- The argument should not be INT_MIN
bvIntMin <- liftIO $ WI.bvLit sym w (BV.minSigned w)
eqIntMin <- liftIO $ WI.bvEq sym e bvIntMin
WI.notPred sym eqIntMin
liftIO $ numOp bvAbs fpAbs xe
where
bvAbs :: BVOp1 sym w
bvAbs e = do
zero <- WI.bvLit sym knownNat (BV.zero knownNat)
e_neg <- WI.bvSlt sym e zero
neg_e <- WI.bvSub sym zero e
WI.bvIte sym e_neg neg_e e
fpAbs :: forall fi . FPOp1 sym fi
fpAbs _ e = WFP.iFloatAbs @_ @fi sym e
-- Translate a 'CE.GetField' operation and its argument into a what4
-- representation. If the argument is not a struct, panic.
translateGetField :: forall struct s.
KnownSymbol s
=> CT.Type struct
-- ^ The type of the argument
-> (struct -> CT.Field s b)
-- ^ Extract a struct field
-> XExpr sym
-- ^ The argument value (should be a struct)
-> TransM sym (XExpr sym)
translateGetField tp extractor xe = case (tp, xe) of
(CT.Struct s, XStruct xes) ->
case mIx s of
Just ix -> return $ xes !! ix
Nothing -> panic [ "Could not find field " ++ show fieldNameRepr
, show s
]
_ -> unexpectedValue "get-field operation"
where
fieldNameRepr :: SymbolRepr s
fieldNameRepr = fieldName (extractor undefined)
structFieldNameReprs :: CT.Struct struct => struct -> [Some SymbolRepr]
structFieldNameReprs s = valueName <$> CT.toValues s
mIx :: CT.Struct struct => struct -> Maybe Int
mIx s = elemIndex (Some fieldNameRepr) (structFieldNameReprs s)
-- Translate a 'CE.Sign' operation (i.e, 'signum') and its argument into a
-- what4 representation of the appropriate type. We translate @signum x@ as
-- @x > 0 ? 1 : (x < 0 ? -1 : x)@. This matches how copilot-c99 translates
-- 'CE.Sign' to C code.
translateSign :: XExpr sym -> TransM sym (XExpr sym)
translateSign xe = liftIO $ numOp bvSign fpSign xe
where
bvSign :: BVOp1 sym w
bvSign e = do
zero <- WI.bvLit sym knownRepr (BV.zero knownNat)
neg_one <- WI.bvLit sym knownNat (BV.mkBV knownNat (-1))
pos_one <- WI.bvLit sym knownNat (BV.mkBV knownNat 1)
e_neg <- WI.bvSlt sym e zero
e_pos <- WI.bvSgt sym e zero
t <- WI.bvIte sym e_neg neg_one e
WI.bvIte sym e_pos pos_one t
fpSign :: forall fi . FPOp1 sym fi
fpSign fiRepr e = do
zero <- fpLit fiRepr 0.0
neg_one <- fpLit fiRepr (-1.0)
pos_one <- fpLit fiRepr 1.0
e_neg <- WFP.iFloatLt @_ @fi sym e zero
e_pos <- WFP.iFloatGt @_ @fi sym e zero
t <- WFP.iFloatIte @_ @fi sym e_neg neg_one e
WFP.iFloatIte @_ @fi sym e_pos pos_one t
-- Check the type of the argument. If the argument is a bitvector value,
-- apply the 'BVOp1'. If the argument is a floating-point value, apply the
-- 'FPOp1'. Otherwise, 'panic'.
numOp :: (forall w . BVOp1 sym w)
-> (forall fpp . FPOp1 sym fpp)
-> XExpr sym
-> IO (XExpr sym)
numOp bvOp fpOp xe = case xe of
XInt8 e -> XInt8 <$> bvOp e
XInt16 e -> XInt16 <$> bvOp e
XInt32 e -> XInt32 <$> bvOp e
XInt64 e -> XInt64 <$> bvOp e
XWord8 e -> XWord8 <$> bvOp e
XWord16 e -> XWord16 <$> bvOp e
XWord32 e -> XWord32 <$> bvOp e
XWord64 e -> XWord64 <$> bvOp e
XFloat e -> XFloat <$> fpOp WFP.SingleFloatRepr e
XDouble e -> XDouble <$> fpOp WFP.DoubleFloatRepr e
_ -> unexpectedValue "numOp"
bvOp :: (forall w . BVOp1 sym w) -> XExpr sym -> IO (XExpr sym)
bvOp f xe = case xe of
XInt8 e -> XInt8 <$> f e
XInt16 e -> XInt16 <$> f e
XInt32 e -> XInt32 <$> f e
XInt64 e -> XInt64 <$> f e
XWord8 e -> XWord8 <$> f e
XWord16 e -> XWord16 <$> f e
XWord32 e -> XWord32 <$> f e
XWord64 e -> XWord64 <$> f e
_ -> unexpectedValue "bvOp"
fpOp :: (forall fi . FPOp1 sym fi) -> XExpr sym -> IO (XExpr sym)
fpOp g xe = case xe of
XFloat e -> XFloat <$> g WFP.SingleFloatRepr e
XDouble e -> XDouble <$> g WFP.DoubleFloatRepr e
_ -> unexpectedValue "fpOp"
-- Translate a special-floating operation to the corresponding what4
-- operation. These operations will be treated as uninterpreted functions in
-- the solver.
fpSpecialOp :: WSF.SpecialFunction (EmptyCtx ::> WSF.R)
-> XExpr sym -> IO (XExpr sym)
fpSpecialOp fn = fpOp (\fiRepr -> WFP.iFloatSpecialFunction1 sym fiRepr fn)
-- Construct a floating-point literal value of the appropriate type.
fpLit :: forall fi.
WFP.FloatInfoRepr fi
-> (forall frac. Fractional frac => frac)
-> IO (WI.SymExpr sym (WFP.SymInterpretedFloatType sym fi))
fpLit fiRepr fracLit =
case fiRepr of
WFP.SingleFloatRepr -> WFP.iFloatLitSingle sym fracLit
WFP.DoubleFloatRepr -> WFP.iFloatLitDouble sym fracLit
_ -> panic ["Expected single- or double-precision float", show fiRepr]
-- A catch-all error message to use when translation cannot proceed.
unexpectedValue :: forall m x.
(Panic.HasCallStack, MonadIO m)
=> String
-> m x
unexpectedValue op =
panic [ "Unexpected value in " ++ op ++ ": " ++ show (CP.ppExpr origExpr)
, show xe
]
type BVOp2 sym w =
(KnownNat w, 1 <= w)
=> WI.SymBV sym w
-> WI.SymBV sym w
-> IO (WI.SymBV sym w)
type FPOp2 sym fi =
WFP.FloatInfoRepr fi
-> WI.SymExpr sym (WFP.SymInterpretedFloatType sym fi)
-> WI.SymExpr sym (WFP.SymInterpretedFloatType sym fi)
-> IO (WI.SymExpr sym (WFP.SymInterpretedFloatType sym fi))
type BoolCmp2 sym =
WI.Pred sym
-> WI.Pred sym
-> IO (WI.Pred sym)
type BVCmp2 sym w =
(KnownNat w, 1 <= w)
=> WI.SymBV sym w
-> WI.SymBV sym w
-> IO (WI.Pred sym)
type FPCmp2 sym fi =
WFP.FloatInfoRepr fi
-> WI.SymExpr sym (WFP.SymInterpretedFloatType sym fi)
-> WI.SymExpr sym (WFP.SymInterpretedFloatType sym fi)
-> IO (WI.Pred sym)
translateOp2 :: forall sym a b c .
WFP.IsInterpretedFloatExprBuilder sym
=> sym
-> CE.Expr c
-- ^ Original value we are translating (only used for error
-- messages)
-> CE.Op2 a b c
-> XExpr sym
-> XExpr sym
-> TransM sym (XExpr sym)
translateOp2 sym origExpr op xe1 xe2 = case (op, xe1, xe2) of
(CE.And, XBool e1, XBool e2) -> liftIO $ fmap XBool $ WI.andPred sym e1 e2
(CE.And, _, _) -> unexpectedValues "and operation"
(CE.Or, XBool e1, XBool e2) -> liftIO $ fmap XBool $ WI.orPred sym e1 e2
(CE.Or, _, _) -> unexpectedValues "or operation"
(CE.Add _, xe1, xe2) -> translateAdd xe1 xe2
(CE.Sub _, xe1, xe2) -> translateSub xe1 xe2
(CE.Mul _, xe1, xe2) -> translateMul xe1 xe2
(CE.Mod _, xe1, xe2) -> do
-- The second argument should not be zero
addBVSidePred1 xe2 $ \e2 _ _ -> WI.bvIsNonzero sym e2
liftIO $ bvOp (WI.bvSrem sym) (WI.bvUrem sym) xe1 xe2
(CE.Div _, xe1, xe2) -> do
-- The second argument should not be zero
addBVSidePred1 xe2 $ \e2 _ _ -> WI.bvIsNonzero sym e2
liftIO $ bvOp (WI.bvSdiv sym) (WI.bvUdiv sym) xe1 xe2
-- We do not track any side conditions for floating-point operations
-- (see Note [Side conditions for floating-point operations]), but we will
-- make a note of which operations have partial inputs.
-- The second argument should not be zero
(CE.Fdiv _, xe1, xe2) ->
liftIO $
fpOp (\(_ :: WFP.FloatInfoRepr fi) -> WFP.iFloatDiv @_ @fi sym fpRM)
xe1
xe2
-- None of the following should happen:
--
-- * The first argument is negative, and the second argument is a finite
-- noninteger
--
-- * The first argument is zero, and the second argument is negative
--
-- * The arguments cause the result to overflow
--
-- * The arguments cause the result to underflow
(CE.Pow _, xe1, xe2) -> liftIO $ fpSpecialOp WSF.Pow xe1 xe2
-- The second argument should not be negative or zero
(CE.Logb _, xe1, xe2) -> liftIO $ fpOp logbFn xe1 xe2
where
logbFn :: forall fi . FPOp2 sym fi
-- Implement logb(e1,e2) as log(e2)/log(e1). This matches how copilot-c99
-- translates Logb to C code.
logbFn fiRepr e1 e2 = do
re1 <- WFP.iFloatSpecialFunction1 sym fiRepr WSF.Log e1
re2 <- WFP.iFloatSpecialFunction1 sym fiRepr WSF.Log e2
WFP.iFloatDiv @_ @fi sym fpRM re2 re1
(CE.Atan2 _, xe1, xe2) -> liftIO $ fpSpecialOp WSF.Arctan2 xe1 xe2
(CE.Eq _, xe1, xe2) ->
liftIO $
cmp (WI.eqPred sym)
(WI.bvEq sym)
(\(_ :: WFP.FloatInfoRepr fi) -> WFP.iFloatEq @_ @fi sym)
xe1
xe2
(CE.Ne _, xe1, xe2) -> translateNe xe1 xe2
(CE.Le _, xe1, xe2) ->
liftIO $
numCmp (WI.bvSle sym)
(WI.bvUle sym)
(\(_ :: WFP.FloatInfoRepr fi) -> WFP.iFloatLe @_ @fi sym)
xe1
xe2
(CE.Ge _, xe1, xe2) ->
liftIO $
numCmp (WI.bvSge sym)
(WI.bvUge sym)
(\(_ :: WFP.FloatInfoRepr fi) -> WFP.iFloatGe @_ @fi sym)
xe1
xe2
(CE.Lt _, xe1, xe2) ->
liftIO $
numCmp (WI.bvSlt sym)
(WI.bvUlt sym)
(\(_ :: WFP.FloatInfoRepr fi) -> WFP.iFloatLt @_ @fi sym)
xe1
xe2
(CE.Gt _, xe1, xe2) ->
liftIO $
numCmp (WI.bvSgt sym)
(WI.bvUgt sym)
(\(_ :: WFP.FloatInfoRepr fi) -> WFP.iFloatGt @_ @fi sym)
xe1
xe2
(CE.BwAnd _, xe1, xe2) ->
liftIO $ bvOp (WI.bvAndBits sym) (WI.bvAndBits sym) xe1 xe2
(CE.BwOr _, xe1, xe2) ->
liftIO $ bvOp (WI.bvOrBits sym) (WI.bvOrBits sym) xe1 xe2
(CE.BwXor _, xe1, xe2) ->
liftIO $ bvOp (WI.bvXorBits sym) (WI.bvXorBits sym) xe1 xe2
(CE.BwShiftL _ _, xe1, xe2) -> translateBwShiftL xe1 xe2
(CE.BwShiftR _ _, xe1, xe2) -> translateBwShiftR xe1 xe2
(CE.Index _, xe1, xe2) -> translateIndex xe1 xe2
(CE.UpdateField atp _ftp extractor, structXe, fieldXe) ->
translateUpdateField atp extractor structXe fieldXe
where
-- Translate an 'CE.Add' operation and its arguments into a what4
-- representation of the appropriate type.
translateAdd :: XExpr sym -> XExpr sym -> TransM sym (XExpr sym)
translateAdd xe1 xe2 = do
addBVSidePred2 xe1 xe2 $ \e1 e2 _ sgn ->
-- The arguments should not result in signed overflow or underflow
case sgn of
Signed -> do
(wrap, _) <- WI.addSignedOF sym e1 e2
WI.notPred sym wrap
Unsigned -> pure $ WI.truePred sym
liftIO $
numOp (WI.bvAdd sym)
(\(_ :: WFP.FloatInfoRepr fi) -> WFP.iFloatAdd @_ @fi sym fpRM)
xe1
xe2
-- Translate a 'CE.Sub' operation and its arguments into a what4
-- representation of the appropriate type.
translateSub :: XExpr sym -> XExpr sym -> TransM sym (XExpr sym)
translateSub xe1 xe2 = do
addBVSidePred2 xe1 xe2 $ \e1 e2 _ sgn ->
-- The arguments should not result in signed overflow or underflow
case sgn of
Signed -> do
(wrap, _) <- WI.subSignedOF sym e1 e2
WI.notPred sym wrap
Unsigned -> pure $ WI.truePred sym
liftIO $
numOp (WI.bvSub sym)
(\(_ :: WFP.FloatInfoRepr fi) -> WFP.iFloatSub @_ @fi sym fpRM)
xe1
xe2
-- Translate a 'CE.Mul' operation and its arguments into a what4
-- representation of the appropriate type.
translateMul :: XExpr sym -> XExpr sym -> TransM sym (XExpr sym)
translateMul xe1 xe2 = do
addBVSidePred2 xe1 xe2 $ \e1 e2 _ sgn ->
-- The arguments should not result in signed overflow or underflow
case sgn of
Signed -> do
(wrap, _) <- WI.mulSignedOF sym e1 e2
WI.notPred sym wrap
Unsigned -> pure $ WI.truePred sym
liftIO $
numOp (WI.bvMul sym)
(\(_ :: WFP.FloatInfoRepr fi) -> WFP.iFloatMul @_ @fi sym fpRM)
xe1
xe2
-- Translate an 'CE.Ne' operation and its arguments into a what4
-- representation of the appropriate type.
translateNe :: XExpr sym -> XExpr sym -> TransM sym (XExpr sym)
translateNe xe1 xe2 = liftIO $ cmp neqPred bvNeq fpNeq xe1 xe2
where
neqPred :: BoolCmp2 sym
neqPred e1 e2 = do
e <- WI.eqPred sym e1 e2
WI.notPred sym e
bvNeq :: forall w . BVCmp2 sym w
bvNeq e1 e2 = do
e <- WI.bvEq sym e1 e2
WI.notPred sym e
fpNeq :: forall fi . FPCmp2 sym fi
fpNeq _ e1 e2 = do
e <- WFP.iFloatEq @_ @fi sym e1 e2
WI.notPred sym e
-- Translate a 'CE.BwShiftL' operation and its arguments into a what4
-- representation.
--
-- Note: we are interpreting the shifter as an unsigned bitvector regardless
-- of whether it is a word or an int.
translateBwShiftL :: XExpr sym -> XExpr sym -> TransM sym (XExpr sym)
translateBwShiftL xe1 xe2 = do
-- These partial pattern matches on Just should always succeed because
-- BwShiftL should always have bitvectors as arguments.
Just (SomeBVExpr e1 w1 sgn1 ctor1) <- return $ asBVExpr xe1
Just (SomeBVExpr e2 w2 _ _ ) <- return $ asBVExpr xe2
e2' <- liftIO $ case testNatCases w1 w2 of
NatCaseLT LeqProof -> WI.bvTrunc sym w1 e2
NatCaseEQ -> return e2
NatCaseGT LeqProof -> WI.bvZext sym w1 e2
res <- liftIO $ WI.bvShl sym e1 e2'
-- The second argument should not be greater than or equal to the bit
-- width
wBV <- liftIO $ WI.bvLit sym w1 $ BV.width w1
notTooLarge <- liftIO $ WI.bvUlt sym e2' wBV
addSidePred notTooLarge
case sgn1 of
Unsigned -> do
-- Non-zero bits should not be shifted out
otherDirection <- liftIO $ WI.bvLshr sym res e2'
noWrap <- liftIO $ WI.bvEq sym e1 otherDirection
addSidePred noWrap
Signed -> do
-- Bits that disagree with the sign bit should not be shifted out
otherDirection <- liftIO $ WI.bvAshr sym res e2'
noWrap <- liftIO $ WI.bvEq sym e1 otherDirection
addSidePred noWrap
return $ ctor1 res
-- Translate a 'CE.BwShiftL' operation and its arguments into a what4
-- representation.
--
-- Note: we are interpreting the shifter as an unsigned bitvector regardless
-- of whether it is a word or an int.
translateBwShiftR :: XExpr sym -> XExpr sym -> TransM sym (XExpr sym)
translateBwShiftR xe1 xe2 = do
-- These partial pattern matches on Just should always succeed because
-- BwShiftL should always have bitvectors as arguments.
Just (SomeBVExpr e1 w1 sgn1 ctor1) <- return $ asBVExpr xe1
Just (SomeBVExpr e2 w2 _ _ ) <- return $ asBVExpr xe2
e2' <- liftIO $ case testNatCases w1 w2 of
NatCaseLT LeqProof -> WI.bvTrunc sym w1 e2
NatCaseEQ -> return e2
NatCaseGT LeqProof -> WI.bvZext sym w1 e2
-- The second argument should not be greater than or equal to the bit
-- width
wBV <- liftIO $ WI.bvLit sym w1 $ BV.width w1
notTooLarge <- liftIO $ WI.bvUlt sym e2' wBV
addSidePred notTooLarge
liftIO $ fmap ctor1 $ case sgn1 of
Signed -> WI.bvAshr sym e1 e2'
Unsigned -> WI.bvLshr sym e1 e2'
-- Translate an 'CE.Index' operation and its arguments into a what4
-- representation. This checks that the first argument is an 'XArray' and
-- the second argument is an 'XWord32', invoking 'panic' is this invariant
-- is not upheld.
--
-- Note: Currently, copilot only checks if array indices are out of bounds
-- as a side condition. The method of translation we are using simply
-- creates a nest of if-then-else expression to check the index expression
-- against all possible indices. If the index expression is known by the
-- solver to be out of bounds (for instance, if it is a constant 5 for an
-- array of 5 elements), then the if-then-else will trivially resolve to
-- true.
translateIndex :: XExpr sym -> XExpr sym -> TransM sym (XExpr sym)
translateIndex xe1 xe2 = case (xe1, xe2) of
(XArray xes, XWord32 ix) -> do
-- The second argument should not be out of bounds (i.e., greater than
-- or equal to the length of the array)
xesLenBV <- liftIO $ WI.bvLit sym knownNat $ BV.mkBV knownNat
$ toInteger $ V.lengthInt xes
inRange <- liftIO $ WI.bvUlt sym ix xesLenBV
addSidePred inRange
liftIO $ buildIndexExpr sym ix xes
_ -> unexpectedValues "index operation"
-- Translate an 'CE.UpdateField' operation and its arguments into a what4
-- representation. This function will panic if one of the following does not
-- hold:
--
-- - The argument is not a struct.
--
-- - The struct's field cannot be found.
translateUpdateField :: forall struct s.
KnownSymbol s
=> CT.Type struct
-- ^ The type of the struct argument
-> (struct -> CT.Field s b)
-- ^ Extract a struct field
-> XExpr sym
-- ^ The first argument value (should be a struct)
-> XExpr sym
-- ^ The second argument value (should be the same type
-- as the struct field)
-> TransM sym (XExpr sym)
-- ^ The first argument value, but with an updated
-- value for the supplied field.
translateUpdateField structTp extractor structXe newFieldXe =
case (structTp, structXe) of
(CT.Struct s, XStruct structFieldXes) ->
case mIx s of
Just ix -> return $ XStruct $ updateAt ix newFieldXe structFieldXes
Nothing ->
panic [ "Could not find field " ++ show fieldNameRepr
, show s
]