Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for translating Cryptol constraint guards #1911

Merged
merged 10 commits into from
Aug 18, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions cryptol-saw-core/saw/Cryptol.sawcore
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ Num_rec : (p: Num -> sort 1) -> ((n:Nat) -> p (TCNum n)) -> p TCInf ->
(n:Num) -> p n;
Num_rec p f1 f2 n = Num#rec p f1 f2 n;

-- Check whether a 'Num' is finite
tcFin : Num -> Bool;
tcFin n = Num#rec (\ (n:Num) -> Bool) (\ (n:Nat) -> True) False n;

-- Helper function: take a Num that we expect to be finite, and extract its Nat,
-- raising an error if that Num is not finite
getFinNat : (n:Num) -> Nat;
Expand Down Expand Up @@ -182,6 +186,33 @@ tcLenFromThenTo_Nat x y z =
tcLenFromThenTo : Num -> Num -> Num -> Num;
tcLenFromThenTo = ternaryNumFun tcLenFromThenTo_Nat TCInf;

-- Build a binary predicate on Nums by lifting a binary predicate on Nats (the
-- first argument) and using additional cases for: when the first argument is a
-- Nat and the second is infinite; when the second is a Nat and the first is
-- infinite; and when both are infinite
binaryNumPred : (Nat -> Nat -> Bool) ->
(Nat -> Bool) ->
(Nat -> Bool) ->
Bool ->
Num -> Num -> Bool;
binaryNumPred f1 f2 f3 f4 num1 num2 =
Num#rec (\ (num1':Num) -> Bool)
(\ (n1:Nat) ->
Num#rec (\ (num2':Num) -> Bool)
(\ (n2:Nat) -> f1 n1 n2)
(f2 n1)
num2)
(Num#rec (\ (num2':Num) -> Bool) f3 f4 num2)
num1;

-- Check two 'Num's for equality.
tcEqual : Num -> Num -> Bool;
tcEqual =
binaryNumPred equalNat (\ (x:Nat) -> False) (\ (y:Nat) -> False) True;

-- Check that the first 'Num' is strictly less than the second 'Num'.
tcLt : Num -> Num -> Bool;
tcLt = binaryNumPred ltNat (\ (x:Nat) -> True) (\ (y:Nat) -> False) True;

--------------------------------------------------------------------------------
-- Possibly infinite sequences
Expand Down
80 changes: 75 additions & 5 deletions cryptol-saw-core/src/Verifier/SAW/Cryptol.hs
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,44 @@ isErasedProp prop =
C.TCon (C.PC C.PLiteralLessThan) _ -> False
_ -> True

-- | Translate a 'Prop' containing a numeric constraint to a 'Term' that tests
-- if the 'Prop' holds. This function will 'panic' for 'Prop's that are not
-- numeric constraints, such as @Integral@. In other words, this function
-- supports the same set of 'Prop's that constraint guards do.
importNumericConstraintAsBool :: SharedContext -> Env -> C.Prop -> IO Term
importNumericConstraintAsBool sc env prop =
case prop of
C.TCon (C.PC C.PEqual) [lhs, rhs] -> eqTerm lhs rhs
C.TCon (C.PC C.PNeq) [lhs, rhs] -> eqTerm lhs rhs >>= scNot sc
C.TCon (C.PC C.PGeq) [lhs, rhs] -> do
bboston7 marked this conversation as resolved.
Show resolved Hide resolved
-- Convert 'lhs >= rhs' into '(rhs < lhs) \/ (rhs == lhs)'
lhs' <- importType sc env lhs
rhs' <- importType sc env rhs
lt <- scGlobalApply sc "Cryptol.tcLt" [rhs', lhs']
eq <- scGlobalApply sc "Cryptol.tcEqual" [rhs', lhs']
scOr sc lt eq
C.TCon (C.PC C.PFin) [x] -> do
x' <- importType sc env x
scGlobalApply sc "Cryptol.tcFin" [x']
C.TCon (C.PC C.PAnd) [lhs, rhs] -> do
lhs' <- importType sc env lhs
rhs' <- importType sc env rhs
scAnd sc lhs' rhs'
C.TCon (C.PC C.PTrue) [] -> scBool sc True
C.TUser _ _ t -> importNumericConstraintAsBool sc env t
_ -> panic
"importNumericConstraintAsBool"
[ "importNumericConstraintAsBool called with non-numeric constraint:"
, pretty prop
]
where
-- | Construct a term for equality of two types
eqTerm :: C.Type -> C.Type -> IO Term
eqTerm lhs rhs = do
lhs' <- importType sc env lhs
rhs' <- importType sc env rhs
scGlobalApply sc "Cryptol.tcEqual" [lhs', rhs']

importPropsType :: SharedContext -> Env -> [C.Prop] -> C.Type -> IO Term
importPropsType sc env [] ty = importType sc env ty
importPropsType sc env (prop : props) ty
Expand Down Expand Up @@ -1076,25 +1114,56 @@ importExpr sc env expr =
C.ELocated _ e ->
importExpr sc env e

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

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

-- | Translate an erased 'Prop' to a term and return the conjunction of the
-- translated term and 'mt' if 'mt' is 'Just'. Otherwise, return the
-- translated 'Prop'. This function is intended to be used in a fold,
-- taking a 'Maybe' in the first argument to avoid creating an unnecessary
-- conjunction over singleton lists.
conjoinErasedProps :: Maybe Term -> C.Prop -> IO (Maybe Term)
conjoinErasedProps mt p = do
p' <- importNumericConstraintAsBool sc env p
case mt of
Just t -> Just <$> scAnd sc t p'
Nothing -> pure $ Just p'

-- | A helper function to be used in a fold converting a prop guard to an
-- if-then-else. In order, the arguments of the function are:
-- 1. The type of the prop guard
-- 2. An arm of the prop guard
-- 3. A term representing the else branch of the if-then-else
propGuardToIte :: Term -> ([C.Prop], C.Expr) -> Term -> IO Term
propGuardToIte typ (props, body) falseBranch = do
mCondition <- Fold.foldlM conjoinErasedProps Nothing props
condition <- maybe (scBool sc True) pure mCondition
trueBranch <- importExpr sc env body
scGlobalApply sc "Prelude.ite" [typ, condition, trueBranch, falseBranch]


-- | Convert a Cryptol expression with the given type schema to a
-- SAW-Core term. Calling 'scTypeOf' on the result of @'importExpr''
-- sc env schema expr@ must yield a type that is equivalent (i.e.
-- convertible) with the one returned by @'importSchema' sc env
-- schema@.
--
-- Essentially, this function should be used when the expression's type is known
-- (such as with a type annotation), and 'importExpr' should be used when the
-- type must be inferred.
importExpr' :: SharedContext -> Env -> C.Schema -> C.Expr -> IO Term
importExpr' sc env schema expr =
case expr of
C.EPropGuards {} ->
error "Using contsraint guards is not yet supported by SAW."

C.ETuple es ->
do ty <- the "Expected a mono type in ETuple" (C.isMono schema)
ts <- the "Expected a tuple type in ETuple" (C.tIsTuple ty)
Expand Down Expand Up @@ -1168,6 +1237,7 @@ importExpr' sc env schema expr =
C.EApp {} -> fallback
C.ETApp {} -> fallback
C.EProofApp {} -> fallback
C.EPropGuards {} -> fallback

where
go :: C.Type -> C.Expr -> IO Term
Expand Down
21 changes: 21 additions & 0 deletions intTests/test_constraint_guards/Test.cry
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module Test where

// Exhaustive constraint guards with some overlapping branches
guard : {w} [w] -> Integer
guard x
| (w == 32) => 0
| (w >= 32) => 1
| (w < 8) => 2
| (w != 8, w != 9) => 3
| () => 4

// Non-exhaustive constraint guard
incomplete : {w} [w] -> Bool
incomplete x
| (w == 32) => True

// More dependently typed case
dependent : {n} [n]
dependent
| n == 1 => [True]
| () => repeat False
18 changes: 18 additions & 0 deletions intTests/test_constraint_guards/test.saw
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import "Test.cry";

// Test exhaustive constraint guards
prove_print z3 {{ \(x : [32]) -> guard x == 0 }};
prove_print z3 {{ \(x : [34]) -> guard x == 1 }};
prove_print z3 {{ \(x : [4]) -> guard x == 2 }};
prove_print z3 {{ \(x : [16]) -> guard x == 3 }};
prove_print z3 {{ \(x : [8]) -> guard x == 4}};
prove_print z3 {{ \(x : [9]) -> guard x == 4}};

// Test non-exhaustive constraint guards
prove_print z3 {{ \(x : [32]) -> incomplete x }};
fails (prove_print z3 {{ \(x : [64]) -> incomplete x }});

// Test more dependently typed constraint guards
prove_print z3 {{ dependent`{1} == [True] }};
prove_print z3 {{ dependent`{3} == [False, False, False] }};
prove_print z3 {{ dependent`{0} == [] }};
3 changes: 3 additions & 0 deletions intTests/test_constraint_guards/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
set -e

$SAW test.saw