Skip to content

Commit 2ae9aca

Browse files
authored
Merge pull request #1609 from GaloisInc/cryptol-abstract-types
Mr Solver support for monadic Cryptol specs
2 parents 0aa86e9 + e1c225a commit 2ae9aca

File tree

14 files changed

+327
-49
lines changed

14 files changed

+327
-49
lines changed

cryptol-saw-core/saw/CryptolM.sawcore

+12-4
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,20 @@ numAssertEqM n m =
2424
isFinite : Num -> Prop;
2525
isFinite = Num_rec (\ (_:Num) -> Prop) (\ (_:Nat) -> TrueProp) FalseProp;
2626

27+
-- Check whether a Num is finite
28+
checkFinite : (n:Num) -> Maybe (isFinite n);
29+
checkFinite =
30+
Num_rec (\ (n:Num) -> Maybe (isFinite n))
31+
(\ (n:Nat) -> Just (isFinite (TCNum n)) (Refl Bool True))
32+
(Nothing (isFinite TCInf));
33+
2734
-- Assert that a Num is finite, or fail
2835
assertFiniteM : (n:Num) -> CompM (isFinite n);
29-
assertFiniteM =
30-
Num_rec (\ (n:Num) -> CompM (isFinite n))
31-
(\ (_:Nat) -> returnM TrueProp TrueI)
32-
(errorM FalseProp "assertFiniteM: Num not finite");
36+
assertFiniteM n =
37+
maybe (isFinite n) (CompM (isFinite n))
38+
(errorM (isFinite n) "assertFiniteM: Num not finite")
39+
(returnM (isFinite n))
40+
(checkFinite n);
3341

3442
-- Recurse over a Num known to be finite
3543
Num_rec_fin : (p: Num -> sort 1) -> ((n:Nat) -> p (TCNum n)) ->

cryptol-saw-core/src/Verifier/SAW/Cryptol.hs

+14-2
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,13 @@ data Env = Env
8282
, envC :: Map C.Name C.Schema -- ^ Cryptol type environment
8383
, envS :: [Term] -- ^ SAW-Core bound variable environment (for type checking)
8484
, envRefPrims :: Map C.PrimIdent C.Expr
85+
, envPrims :: Map C.PrimIdent Term -- ^ Translations for other primitives
86+
, envPrimTypes :: Map C.PrimIdent Term -- ^ Translations for primitive types
8587
}
8688

8789
emptyEnv :: Env
88-
emptyEnv = Env Map.empty Map.empty Map.empty Map.empty [] Map.empty
90+
emptyEnv =
91+
Env Map.empty Map.empty Map.empty Map.empty [] Map.empty Map.empty Map.empty
8992

9093
liftTerm :: (Term, Int) -> (Term, Int)
9194
liftTerm (t, j) = (t, j + 1)
@@ -102,6 +105,8 @@ liftEnv env =
102105
, envC = envC env
103106
, envS = envS env
104107
, envRefPrims = envRefPrims env
108+
, envPrims = envPrims env
109+
, envPrimTypes = envPrimTypes env
105110
}
106111

107112
bindTParam :: SharedContext -> C.TParam -> Env -> IO Env
@@ -262,7 +267,11 @@ importType sc env ty =
262267
b <- go (tyargs !! 1)
263268
scFun sc a b
264269
C.TCTuple _n -> scTupleType sc =<< traverse go tyargs
265-
C.TCAbstract{} -> panic "importType TODO: abstract type" []
270+
C.TCAbstract (C.UserTC n _)
271+
| Just prim <- C.asPrim n
272+
, Just t <- Map.lookup prim (envPrimTypes env) ->
273+
scApplyAllBeta sc t =<< traverse go tyargs
274+
| True -> panic ("importType: unknown primitive type: " ++ show n) []
266275
C.PC pc ->
267276
case pc of
268277
C.PLiteral -> -- we omit first argument to class Literal
@@ -668,6 +677,9 @@ importPrimitive sc primOpts env n sch
668677
nmi <- importName n
669678
scConstant' sc nmi e t
670679

680+
-- lookup primitive in the extra primitive lookup table
681+
| Just nm <- C.asPrim n, Just t <- Map.lookup nm (envPrims env) = return t
682+
671683
-- Optionally, create an opaque constant representing the primitive
672684
-- if it doesn't match one of the ones we know about.
673685
| Just _ <- C.asPrim n, allowUnknownPrimitives primOpts =

cryptol-saw-core/src/Verifier/SAW/Cryptol/Monadify.hs

+58-2
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,7 @@ monadifyType ctx tp@(asPi -> Just (_, _, tp_out))
398398
monadifyType ctx tp@(asPi -> Just (x, tp_in, tp_out)) =
399399
MTyArrow (monadifyType ctx tp_in)
400400
(monadifyType ((x,tp,Nothing):ctx) tp_out)
401+
monadifyType _ (asTupleType -> Just []) = mkMonType0 unitTypeOpenTerm
401402
monadifyType ctx (asPairType -> Just (tp1, tp2)) =
402403
MTyPair (monadifyType ctx tp1) (monadifyType ctx tp2)
403404
monadifyType ctx (asRecordType -> Just tps) =
@@ -529,6 +530,36 @@ fromCompTerm :: MonType -> OpenTerm -> MonTerm
529530
fromCompTerm mtp t | isBaseType mtp = CompMonTerm mtp t
530531
fromCompTerm mtp t = ArgMonTerm $ fromArgTerm mtp t
531532

533+
-- | Test if a monadification type @tp@ is pure, meaning @MT(tp)=tp@
534+
monTypeIsPure :: MonType -> Bool
535+
monTypeIsPure (MTyForall _ _ _) = False -- NOTE: this could potentially be true
536+
monTypeIsPure (MTyArrow _ _) = False
537+
monTypeIsPure (MTySeq _ _) = False
538+
monTypeIsPure (MTyPair mtp1 mtp2) = monTypeIsPure mtp1 && monTypeIsPure mtp2
539+
monTypeIsPure (MTyRecord fld_mtps) = all (monTypeIsPure . snd) fld_mtps
540+
monTypeIsPure (MTyBase _ _) = True
541+
monTypeIsPure (MTyNum _) = True
542+
543+
-- | Test if a monadification type @tp@ is semi-pure, meaning @SemiP(tp) = tp@,
544+
-- where @SemiP@ is defined in the documentation for 'fromSemiPureTermFun' below
545+
monTypeIsSemiPure :: MonType -> Bool
546+
monTypeIsSemiPure (MTyForall _ k tp_f) =
547+
monTypeIsSemiPure $ tp_f $ MTyBase k $
548+
-- This dummy OpenTerm should never be inspected by the recursive call
549+
error "monTypeIsSemiPure"
550+
monTypeIsSemiPure (MTyArrow tp_in tp_out) =
551+
monTypeIsPure tp_in && monTypeIsSemiPure tp_out
552+
monTypeIsSemiPure (MTySeq _ _) = False
553+
monTypeIsSemiPure (MTyPair mtp1 mtp2) =
554+
-- NOTE: functions in pairs are not semi-pure; only pure types in pairs are
555+
-- semi-pure
556+
monTypeIsPure mtp1 && monTypeIsPure mtp2
557+
monTypeIsSemiPure (MTyRecord fld_mtps) =
558+
-- Same as pairs, record types are only semi-pure if they are pure
559+
all (monTypeIsPure . snd) fld_mtps
560+
monTypeIsSemiPure (MTyBase _ _) = True
561+
monTypeIsSemiPure (MTyNum _) = True
562+
532563
-- | Build a monadification term from a function on terms which, when viewed as
533564
-- a lambda, is a "semi-pure" function of the given monadification type, meaning
534565
-- it maps terms of argument type @MT(tp)@ to an output value of argument type;
@@ -857,8 +888,13 @@ monadifyTerm' _ (asApplyAll -> (asTypedGlobalDef -> Just glob, args)) =
857888
do let (macro_args, reg_args) = splitAt (macroNumArgs macro) args
858889
mtrm_f <- macroApply macro glob macro_args
859890
monadifyApply mtrm_f reg_args
860-
Nothing -> error ("Monadification failed: unhandled constant: "
861-
++ globalDefString glob)
891+
Nothing ->
892+
monadifyTypeM (globalDefType glob) >>= \glob_mtp ->
893+
if monTypeIsSemiPure glob_mtp then
894+
monadifyApply (ArgMonTerm $ fromSemiPureTerm glob_mtp $
895+
globalDefOpenTerm glob) args
896+
else error ("Monadification failed: unhandled constant: "
897+
++ globalDefString glob)
862898
monadifyTerm' _ (asApp -> Just (f, arg)) =
863899
do mtrm_f <- monadifyTerm Nothing f
864900
monadifyApply mtrm_f [arg]
@@ -959,6 +995,25 @@ iteMacro = MonMacro 4 $ \_ args ->
959995
[toCompType mtp, toArgTerm atrm_cond,
960996
toCompTerm mtrm1, toCompTerm mtrm2]
961997

998+
-- | The macro for the either elimination function, which converts the
999+
-- application @either a b c@ to @either a b (CompM c)@
1000+
eitherMacro :: MonMacro
1001+
eitherMacro = MonMacro 3 $ \_ args ->
1002+
do let (tp_a, tp_b, tp_c) =
1003+
case args of
1004+
[t1, t2, t3] -> (t1, t2, t3)
1005+
_ -> error "eitherMacro: wrong number of arguments!"
1006+
mtp_a <- monadifyTypeM tp_a
1007+
mtp_b <- monadifyTypeM tp_b
1008+
mtp_c <- monadifyTypeM tp_c
1009+
let eith_app = applyGlobalOpenTerm "Prelude.either" [toArgType mtp_a,
1010+
toArgType mtp_b,
1011+
toCompType mtp_c]
1012+
let tp_eith = dataTypeOpenTerm "Prelude.Either" [toArgType mtp_a,
1013+
toArgType mtp_b]
1014+
return $ fromCompTerm (MTyArrow (MTyArrow mtp_a mtp_c)
1015+
(MTyArrow (MTyArrow mtp_b mtp_c)
1016+
(MTyArrow (mkMonType0 tp_eith) mtp_c))) eith_app
9621017

9631018
-- | Make a 'MonMacro' that maps a named global whose first argument is @n:Num@
9641019
-- to a global of semi-pure type that takes an additional argument of type
@@ -1048,6 +1103,7 @@ defaultMonEnv =
10481103
mmCustom "Prelude.unsafeAssert" unsafeAssertMacro
10491104
, mmCustom "Prelude.ite" iteMacro
10501105
, mmCustom "Prelude.fix" fixMacro
1106+
, mmCustom "Prelude.either" eitherMacro
10511107

10521108
-- Top-level sequence functions
10531109
, mmArg "Cryptol.seqMap" "CryptolM.seqMapM"

cryptol-saw-core/src/Verifier/SAW/CryptolEnv.hs

+6
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@ data CryptolEnv = CryptolEnv
126126
, eExtraTypes :: Map T.Name T.Schema -- ^ Cryptol types for extra names in scope
127127
, eExtraTSyns :: Map T.Name T.TySyn -- ^ Extra Cryptol type synonyms in scope
128128
, eTermEnv :: Map T.Name Term -- ^ SAWCore terms for *all* names in scope
129+
, ePrims :: Map C.PrimIdent Term -- ^ SAWCore terms for primitives
130+
, ePrimTypes :: Map C.PrimIdent Term -- ^ SAWCore terms for primitive type names
129131
}
130132

131133

@@ -217,6 +219,8 @@ initCryptolEnv sc = do
217219
, eExtraTypes = Map.empty
218220
, eExtraTSyns = Map.empty
219221
, eTermEnv = termEnv
222+
, ePrims = Map.empty
223+
, ePrimTypes = Map.empty
220224
}
221225

222226
-- Parse -----------------------------------------------------------------------
@@ -297,6 +301,8 @@ mkCryEnv env =
297301
let cryEnv = C.emptyEnv
298302
{ C.envE = fmap (\t -> (t, 0)) terms
299303
, C.envC = types'
304+
, C.envPrims = ePrims env
305+
, C.envPrimTypes = ePrimTypes env
300306
}
301307
return cryEnv
302308

heapster-saw/examples/Either.cry

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
/* The definition of the Either type as an abstract type in Cryptol */
3+
4+
module Either where
5+
6+
primitive type Either : * -> * -> *
7+
8+
primitive Left : {a, b} a -> Either a b
9+
primitive Right : {a, b} b -> Either a b
10+
primitive either : {a, b, c} (a -> c) -> (b -> c) -> Either a b -> c

heapster-saw/examples/either.saw

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/* Helper SAW script for defining the Either type in Cryptol */
2+
3+
eith_tp <- parse_core "\\ (a b:sort 0) -> Either a b";
4+
cryptol_add_prim_type "Either" "Either" eith_tp;
5+
6+
left_fun <- parse_core "\\ (a b:sort 0) (x:a) -> Left a b x";
7+
cryptol_add_prim "Either" "Left" left_fun;
8+
9+
right_fun <- parse_core "\\ (a b:sort 0) (x:b) -> Right a b x";
10+
cryptol_add_prim "Either" "Right" right_fun;
11+
12+
either_fun <- parse_core "either";
13+
cryptol_add_prim "Either" "either" either_fun;
14+
15+
import "Either.cry";

heapster-saw/examples/linked_list.cry

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2+
module LinkedList where
3+
4+
import Either
5+
6+
primitive type List : * -> *
7+
8+
primitive foldList : {a} Either () (a, List a) -> List a
9+
primitive unfoldList : {a} List a -> Either () (a, List a)
10+
11+
is_elem_spec : [64] -> List [64] -> [64]
12+
is_elem_spec x l =
13+
either (\ _ -> 0) (\ (y,l') -> if x == y then 1 else is_elem_spec x l')
14+
(unfoldList l)

heapster-saw/examples/linked_list_mr_solver.saw

+30-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
include "linked_list.saw";
22

3+
/***
4+
*** Testing infrastructure
5+
***/
6+
37
let eq_bool b1 b2 =
48
if b1 then
59
if b2 then true else false
@@ -15,12 +19,37 @@ let run_test name test expected =
1519
do { print "Test failed\n"; exit 1; }; };
1620

1721

22+
/***
23+
*** Setup Cryptol environment
24+
***/
25+
26+
include "either.saw";
27+
28+
list_tp <- parse_core "\\ (a:sort 0) -> List a";
29+
cryptol_add_prim_type "LinkedList" "List" list_tp;
30+
31+
fold_fun <- parse_core "foldList";
32+
cryptol_add_prim "LinkedList" "foldList" fold_fun;
33+
34+
unfold_fun <- parse_core "unfoldList";
35+
cryptol_add_prim "LinkedList" "unfoldList" unfold_fun;
36+
37+
import "linked_list.cry";
38+
39+
40+
/***
41+
*** The actual tests
42+
***/
43+
1844
heapster_typecheck_fun env "is_head"
1945
"(). arg0:int64<>, arg1:List<int64<>,always,R> -o \
2046
\ arg0:true, arg1:true, ret:int64<>";
2147

48+
/*
2249
is_head <- parse_core_mod "linked_list" "is_head";
2350
run_test "is_head |= is_head" (mr_solver is_head is_head) true;
51+
*/
2452

2553
is_elem <- parse_core_mod "linked_list" "is_elem";
26-
run_test "is_elem |= is_elem" (mr_solver is_elem is_elem) true;
54+
run_test "is_elem |= is_elem_spec" (mr_solver_debug 2 is_elem {{ is_elem_spec }}) true;
55+
//run_test "is_elem |= is_elem" (mr_solver_debug 1 is_elem is_elem) true;

saw-core/src/Verifier/SAW/SharedTerm.hs

+13
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ module Verifier.SAW.SharedTerm
108108
-- *** Functions and function application
109109
, scApply
110110
, scApplyAll
111+
, scApplyBeta
112+
, scApplyAllBeta
111113
, scGlobalApply
112114
, scFun
113115
, scFunAll
@@ -1283,6 +1285,17 @@ betaNormalize sc t0 =
12831285
scApplyAll :: SharedContext -> Term -> [Term] -> IO Term
12841286
scApplyAll sc = foldlM (scApply sc)
12851287

1288+
-- | Apply a function to an argument, beta-reducing if the function is a lambda
1289+
scApplyBeta :: SharedContext -> Term -> Term -> IO Term
1290+
scApplyBeta sc (asLambda -> Just (_, _, body)) arg =
1291+
instantiateVar sc 0 arg body
1292+
scApplyBeta sc f arg = scApply sc f arg
1293+
1294+
-- | Apply a function 'Term' to zero or more arguments, beta reducing any time
1295+
-- the function is a lambda
1296+
scApplyAllBeta :: SharedContext -> Term -> [Term] -> IO Term
1297+
scApplyAllBeta sc = foldlM (scApplyBeta sc)
1298+
12861299
-- | Returns the defined constant with the given 'Ident'. Fails if no
12871300
-- such constant exists in the module.
12881301
scLookupDef :: SharedContext -> Ident -> IO Term

src/SAWScript/Builtins.hs

+23-1
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,8 @@ import qualified Cryptol.Backend.Monad as C (runEval)
106106
import qualified Cryptol.Eval.Type as C (evalType)
107107
import qualified Cryptol.Eval.Value as C (fromVBit, fromVWord)
108108
import qualified Cryptol.Eval.Concrete as C (Concrete(..), bvVal)
109-
import qualified Cryptol.Utils.Ident as C (mkIdent, packModName)
109+
import qualified Cryptol.Utils.Ident as C (mkIdent, packModName,
110+
textToModName, PrimIdent(..))
110111
import qualified Cryptol.Utils.RecordMap as C (recordFromFields)
111112

112113
import qualified SAWScript.SBVParser as SBV
@@ -1526,6 +1527,27 @@ cryptol_add_path path =
15261527
let rw' = rw { rwCryptol = ce' }
15271528
putTopLevelRW rw'
15281529

1530+
cryptol_add_prim :: String -> String -> TypedTerm -> TopLevel ()
1531+
cryptol_add_prim mnm nm trm =
1532+
do rw <- getTopLevelRW
1533+
let env = rwCryptol rw
1534+
let prim_name =
1535+
C.PrimIdent (C.textToModName $ Text.pack mnm) (Text.pack nm)
1536+
let env' =
1537+
env { CEnv.ePrims =
1538+
Map.insert prim_name (ttTerm trm) (CEnv.ePrims env) }
1539+
putTopLevelRW (rw { rwCryptol = env' })
1540+
1541+
cryptol_add_prim_type :: String -> String -> TypedTerm -> TopLevel ()
1542+
cryptol_add_prim_type mnm nm tp =
1543+
do rw <- getTopLevelRW
1544+
let env = rwCryptol rw
1545+
let prim_name =
1546+
C.PrimIdent (C.textToModName $ Text.pack mnm) (Text.pack nm)
1547+
let env' = env { CEnv.ePrimTypes =
1548+
Map.insert prim_name (ttTerm tp) (CEnv.ePrimTypes env) }
1549+
putTopLevelRW (rw { rwCryptol = env' })
1550+
15291551
-- | Call 'Cryptol.importSchema' using a 'CEnv.CryptolEnv'
15301552
importSchemaCEnv :: SharedContext -> CEnv.CryptolEnv -> Cryptol.Schema ->
15311553
IO Term

src/SAWScript/Interpreter.hs

+14
Original file line numberDiff line numberDiff line change
@@ -1878,6 +1878,20 @@ primitives = Map.fromList
18781878
, "Cryptol source files."
18791879
]
18801880

1881+
, prim "cryptol_add_prim" "String -> String -> Term -> TopLevel ()"
1882+
(pureVal cryptol_add_prim)
1883+
Experimental
1884+
[ "cryptol_add_prim mod nm trm sets the translation of Cryptol primitive"
1885+
, "nm in module mod to trm"
1886+
]
1887+
1888+
, prim "cryptol_add_prim_type" "String -> String -> Term -> TopLevel ()"
1889+
(pureVal cryptol_add_prim_type)
1890+
Experimental
1891+
[ "cryptol_add_prim_type mod nm tp sets the translation of Cryptol"
1892+
, "primitive type nm in module mod to tp"
1893+
]
1894+
18811895
-- Java stuff
18821896

18831897
, prim "java_bool" "JavaType"

0 commit comments

Comments
 (0)