Skip to content

Commit e0604ca

Browse files
authored
Merge pull request #1911 from GaloisInc/bb/constraint-guards
Add support for translating Cryptol constraint guards
2 parents d4b61c3 + 758fc88 commit e0604ca

File tree

6 files changed

+153
-5
lines changed

6 files changed

+153
-5
lines changed

CHANGES.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
# Nightly
2+
3+
## New Features
4+
* SAW now supports loading and reasoning about Cryptol declarations that make use of numeric constraint guards. For more information on numeric constraint guards, see the [relavent section of the Cryptol reference manual](https://galoisinc.github.io/cryptol/master/BasicSyntax.html#numeric-constraint-guards).
5+
16
# Version 1.0 -- 2023-06-26
27

38
## New Features

cryptol-saw-core/saw/Cryptol.sawcore

+31
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ Num_rec : (p: Num -> sort 1) -> ((n:Nat) -> p (TCNum n)) -> p TCInf ->
3939
(n:Num) -> p n;
4040
Num_rec p f1 f2 n = Num#rec p f1 f2 n;
4141

42+
-- Check whether a 'Num' is finite
43+
tcFin : Num -> Bool;
44+
tcFin n = Num#rec (\ (n:Num) -> Bool) (\ (n:Nat) -> True) False n;
45+
4246
-- Helper function: take a Num that we expect to be finite, and extract its Nat,
4347
-- raising an error if that Num is not finite
4448
getFinNat : (n:Num) -> Nat;
@@ -182,6 +186,33 @@ tcLenFromThenTo_Nat x y z =
182186
tcLenFromThenTo : Num -> Num -> Num -> Num;
183187
tcLenFromThenTo = ternaryNumFun tcLenFromThenTo_Nat TCInf;
184188

189+
-- Build a binary predicate on Nums by lifting a binary predicate on Nats (the
190+
-- first argument) and using additional cases for: when the first argument is a
191+
-- Nat and the second is infinite; when the second is a Nat and the first is
192+
-- infinite; and when both are infinite
193+
binaryNumPred : (Nat -> Nat -> Bool) ->
194+
(Nat -> Bool) ->
195+
(Nat -> Bool) ->
196+
Bool ->
197+
Num -> Num -> Bool;
198+
binaryNumPred f1 f2 f3 f4 num1 num2 =
199+
Num#rec (\ (num1':Num) -> Bool)
200+
(\ (n1:Nat) ->
201+
Num#rec (\ (num2':Num) -> Bool)
202+
(\ (n2:Nat) -> f1 n1 n2)
203+
(f2 n1)
204+
num2)
205+
(Num#rec (\ (num2':Num) -> Bool) f3 f4 num2)
206+
num1;
207+
208+
-- Check two 'Num's for equality.
209+
tcEqual : Num -> Num -> Bool;
210+
tcEqual =
211+
binaryNumPred equalNat (\ (x:Nat) -> False) (\ (y:Nat) -> False) True;
212+
213+
-- Check that the first 'Num' is strictly less than the second 'Num'.
214+
tcLt : Num -> Num -> Bool;
215+
tcLt = binaryNumPred ltNat (\ (x:Nat) -> True) (\ (y:Nat) -> False) True;
185216

186217
--------------------------------------------------------------------------------
187218
-- Possibly infinite sequences

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

+75-5
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,44 @@ isErasedProp prop =
332332
C.TCon (C.PC C.PLiteralLessThan) _ -> False
333333
_ -> True
334334

335+
-- | Translate a 'Prop' containing a numeric constraint to a 'Term' that tests
336+
-- if the 'Prop' holds. This function will 'panic' for 'Prop's that are not
337+
-- numeric constraints, such as @Integral@. In other words, this function
338+
-- supports the same set of 'Prop's that constraint guards do.
339+
importNumericConstraintAsBool :: SharedContext -> Env -> C.Prop -> IO Term
340+
importNumericConstraintAsBool sc env prop =
341+
case prop of
342+
C.TCon (C.PC C.PEqual) [lhs, rhs] -> eqTerm lhs rhs
343+
C.TCon (C.PC C.PNeq) [lhs, rhs] -> eqTerm lhs rhs >>= scNot sc
344+
C.TCon (C.PC C.PGeq) [lhs, rhs] -> do
345+
-- Convert 'lhs >= rhs' into '(rhs < lhs) \/ (rhs == lhs)'
346+
lhs' <- importType sc env lhs
347+
rhs' <- importType sc env rhs
348+
lt <- scGlobalApply sc "Cryptol.tcLt" [rhs', lhs']
349+
eq <- scGlobalApply sc "Cryptol.tcEqual" [rhs', lhs']
350+
scOr sc lt eq
351+
C.TCon (C.PC C.PFin) [x] -> do
352+
x' <- importType sc env x
353+
scGlobalApply sc "Cryptol.tcFin" [x']
354+
C.TCon (C.PC C.PAnd) [lhs, rhs] -> do
355+
lhs' <- importType sc env lhs
356+
rhs' <- importType sc env rhs
357+
scAnd sc lhs' rhs'
358+
C.TCon (C.PC C.PTrue) [] -> scBool sc True
359+
C.TUser _ _ t -> importNumericConstraintAsBool sc env t
360+
_ -> panic
361+
"importNumericConstraintAsBool"
362+
[ "importNumericConstraintAsBool called with non-numeric constraint:"
363+
, pretty prop
364+
]
365+
where
366+
-- | Construct a term for equality of two types
367+
eqTerm :: C.Type -> C.Type -> IO Term
368+
eqTerm lhs rhs = do
369+
lhs' <- importType sc env lhs
370+
rhs' <- importType sc env rhs
371+
scGlobalApply sc "Cryptol.tcEqual" [lhs', rhs']
372+
335373
importPropsType :: SharedContext -> Env -> [C.Prop] -> C.Type -> IO Term
336374
importPropsType sc env [] ty = importType sc env ty
337375
importPropsType sc env (prop : props) ty
@@ -1076,25 +1114,56 @@ importExpr sc env expr =
10761114
C.ELocated _ e ->
10771115
importExpr sc env e
10781116

1079-
C.EPropGuards {} ->
1080-
error "Using contsraint guards is not yet supported by SAW."
1117+
C.EPropGuards arms typ -> do
1118+
-- Convert prop guards to nested if-then-elses
1119+
typ' <- importType sc env typ
1120+
errMsg <- scString sc "No constraints satisfied in constraint guard"
1121+
err <- scGlobalApply sc "Prelude.error" [typ', errMsg]
1122+
-- NOTE: Must use a right fold to maintain order of prop guards in
1123+
-- generated if-then-else
1124+
Fold.foldrM (propGuardToIte typ') err arms
10811125

10821126
where
10831127
the :: String -> Maybe a -> IO a
10841128
the what = maybe (panic "importExpr" ["internal type error", what]) return
10851129

1130+
-- | Translate an erased 'Prop' to a term and return the conjunction of the
1131+
-- translated term and 'mt' if 'mt' is 'Just'. Otherwise, return the
1132+
-- translated 'Prop'. This function is intended to be used in a fold,
1133+
-- taking a 'Maybe' in the first argument to avoid creating an unnecessary
1134+
-- conjunction over singleton lists.
1135+
conjoinErasedProps :: Maybe Term -> C.Prop -> IO (Maybe Term)
1136+
conjoinErasedProps mt p = do
1137+
p' <- importNumericConstraintAsBool sc env p
1138+
case mt of
1139+
Just t -> Just <$> scAnd sc t p'
1140+
Nothing -> pure $ Just p'
1141+
1142+
-- | A helper function to be used in a fold converting a prop guard to an
1143+
-- if-then-else. In order, the arguments of the function are:
1144+
-- 1. The type of the prop guard
1145+
-- 2. An arm of the prop guard
1146+
-- 3. A term representing the else branch of the if-then-else
1147+
propGuardToIte :: Term -> ([C.Prop], C.Expr) -> Term -> IO Term
1148+
propGuardToIte typ (props, body) falseBranch = do
1149+
mCondition <- Fold.foldlM conjoinErasedProps Nothing props
1150+
condition <- maybe (scBool sc True) pure mCondition
1151+
trueBranch <- importExpr sc env body
1152+
scGlobalApply sc "Prelude.ite" [typ, condition, trueBranch, falseBranch]
1153+
10861154

10871155
-- | Convert a Cryptol expression with the given type schema to a
10881156
-- SAW-Core term. Calling 'scTypeOf' on the result of @'importExpr''
10891157
-- sc env schema expr@ must yield a type that is equivalent (i.e.
10901158
-- convertible) with the one returned by @'importSchema' sc env
10911159
-- schema@.
1160+
--
1161+
-- Essentially, this function should be used when the expression's type is known
1162+
-- (such as with a type annotation), and 'importExpr' should be used when the
1163+
-- type must be inferred.
10921164
importExpr' :: SharedContext -> Env -> C.Schema -> C.Expr -> IO Term
10931165
importExpr' sc env schema expr =
10941166
case expr of
1095-
C.EPropGuards {} ->
1096-
error "Using contsraint guards is not yet supported by SAW."
1097-
10981167
C.ETuple es ->
10991168
do ty <- the "Expected a mono type in ETuple" (C.isMono schema)
11001169
ts <- the "Expected a tuple type in ETuple" (C.tIsTuple ty)
@@ -1168,6 +1237,7 @@ importExpr' sc env schema expr =
11681237
C.EApp {} -> fallback
11691238
C.ETApp {} -> fallback
11701239
C.EProofApp {} -> fallback
1240+
C.EPropGuards {} -> fallback
11711241

11721242
where
11731243
go :: C.Type -> C.Expr -> IO Term
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
module Test where
2+
3+
// Exhaustive constraint guards with some overlapping branches
4+
guard : {w} [w] -> Integer
5+
guard x
6+
| (w == 32) => 0
7+
| (w >= 32) => 1
8+
| (w < 8) => 2
9+
| (w != 8, w != 9) => 3
10+
| () => 4
11+
12+
// Non-exhaustive constraint guard
13+
incomplete : {w} [w] -> Bool
14+
incomplete x
15+
| (w == 32) => True
16+
17+
// More dependently typed case
18+
dependent : {n} [n]
19+
dependent
20+
| n == 1 => [True]
21+
| () => repeat False
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import "Test.cry";
2+
3+
// Test exhaustive constraint guards
4+
prove_print z3 {{ \(x : [32]) -> guard x == 0 }};
5+
prove_print z3 {{ \(x : [34]) -> guard x == 1 }};
6+
prove_print z3 {{ \(x : [4]) -> guard x == 2 }};
7+
prove_print z3 {{ \(x : [16]) -> guard x == 3 }};
8+
prove_print z3 {{ \(x : [8]) -> guard x == 4}};
9+
prove_print z3 {{ \(x : [9]) -> guard x == 4}};
10+
11+
// Test non-exhaustive constraint guards
12+
prove_print z3 {{ \(x : [32]) -> incomplete x }};
13+
fails (prove_print z3 {{ \(x : [64]) -> incomplete x }});
14+
15+
// Test more dependently typed constraint guards
16+
prove_print z3 {{ dependent`{1} == [True] }};
17+
prove_print z3 {{ dependent`{3} == [False, False, False] }};
18+
prove_print z3 {{ dependent`{0} == [] }};
+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
set -e
2+
3+
$SAW test.saw

0 commit comments

Comments
 (0)