Skip to content

Commit 172c0ce

Browse files
authored
Merge pull request #1698 from GaloisInc/rwd/congruence
Term utilities
2 parents 178f9ed + e2fd6d4 commit 172c0ce

File tree

8 files changed

+203
-11
lines changed

8 files changed

+203
-11
lines changed

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

+21-11
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import Verifier.SAW.Cryptol (scCryptolType)
2323
import Verifier.SAW.FiniteValue
2424
import Verifier.SAW.Recognizer (asExtCns)
2525
import Verifier.SAW.SharedTerm
26+
import Verifier.SAW.SCTypeCheck (scTypeCheckError)
2627

2728
-- Typed terms -----------------------------------------------------------------
2829

@@ -67,22 +68,31 @@ mkTypedTerm sc trm = do
6768
Just (Right t) -> TypedTermSchema (C.tMono t)
6869
return (TypedTerm ttt trm)
6970

70-
-- | Apply a function-typed 'TypedTerm' to an argument. This operation
71-
-- fails if the first 'TypedTerm' does not have a monomorphic function
72-
-- type.
71+
-- | Apply a function-typed 'TypedTerm' to an argument.
72+
-- This operation fails if the type of the argument does
73+
-- not match the function.
7374
applyTypedTerm :: SharedContext -> TypedTerm -> TypedTerm -> IO TypedTerm
74-
applyTypedTerm sc (TypedTerm tp t1) (TypedTerm _ t2)
75-
| Just (_,cty') <- C.tIsFun =<< ttIsMono tp
76-
= TypedTerm (TypedTermSchema (C.tMono cty')) <$> scApply sc t1 t2
77-
78-
-- TODO? extend this to allow explicit application of types?
79-
applyTypedTerm _ _ _ = fail "applyTypedTerm: not a (monomorphic) function type"
75+
applyTypedTerm sc x y = applyTypedTerms sc x [y]
8076

8177
-- | Apply a 'TypedTerm' to a list of arguments. This operation fails
8278
-- if the first 'TypedTerm' does not have a function type of
83-
-- sufficient arity.
79+
-- sufficient arity, or if the types of the arguments do not match
80+
-- the type of the function.
8481
applyTypedTerms :: SharedContext -> TypedTerm -> [TypedTerm] -> IO TypedTerm
85-
applyTypedTerms sc = foldM (applyTypedTerm sc)
82+
applyTypedTerms sc (TypedTerm _ fn) args =
83+
do trm <- foldM (scApply sc) fn (map ttTerm args)
84+
ty <- scTypeCheckError sc trm
85+
-- NB, scCryptolType can behave in strange ways due to the non-injectivity
86+
-- of the mapping from Cryptol to SAWCore types. Perhaps we would be better
87+
-- to combine the incoming type information instead of applying and then
88+
-- reconstructing here.
89+
ct <- scCryptolType sc ty
90+
let ttt = case ct of
91+
Nothing -> TypedTermOther ty
92+
Just (Left k) -> TypedTermKind k
93+
Just (Right t) -> TypedTermSchema (C.tMono t)
94+
return (TypedTerm ttt trm)
95+
8696

8797
-- | Create an abstract defined constant with the specified name and body.
8898
defineTypedTerm :: SharedContext -> Text -> TypedTerm -> IO TypedTerm

intTests/test_congruence/README

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
This is a simple test of the "congruence_for" primitive.
2+
3+
We define a simple cryptol function, ask the system to
4+
automatically define a congruence rule for it; prove
5+
it is true, and then use it in another proof.

intTests/test_congruence/test.saw

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
enable_experimental;
2+
3+
let {{
4+
f : [32] -> [100][12] -> Integer
5+
f x ys = foldl (\i y -> i + toInteger y) (toInteger x) ys
6+
7+
}};
8+
9+
f_cong_term <- congruence_for {{ f }};
10+
f_cong_thm <- prove_extcore (w4_unint_z3 ["f"]) f_cong_term;
11+
12+
thm <- prove_print
13+
do {
14+
goal_intro "x";
15+
goal_intro "y";
16+
unfolding ["ecEq"];
17+
simplify (cryptol_ss ());
18+
goal_apply f_cong_thm;
19+
z3;
20+
z3;
21+
}
22+
{{ \x y -> f (x+y) zero == f (y+x) zero }};
23+
24+
print thm;

intTests/test_congruence/test.sh

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#!/bin/sh
2+
$SAW test.saw

saw-core-coq/src/Verifier/SAW/Translation/Coq/SpecialTreatment.hs

+1
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ sawCorePreludeSpecialTreatmentMap configuration =
217217
++
218218
[ ("error", mapsTo sawDefinitionsModule "error")
219219
, ("fix", skip)
220+
, ("fix_unfold", skip)
220221
, ("unsafeAssert", replaceDropArgs 3 $ Coq.Ltac "solveUnsafeAssert")
221222
, ("unsafeAssertBVULt", replaceDropArgs 3 $ Coq.Ltac "solveUnsafeAssertBVULt")
222223
, ("unsafeAssertBVULe", replaceDropArgs 3 $ Coq.Ltac "solveUnsafeAssertBVULe")

saw-core/prelude/Prelude.sawcore

+23
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,29 @@ eq_inv_map a b a1 a2 eq_a f1 f2 eq_f =
158158
(trans b (f1 a2) (f2 a2) (f2 a1) eq_f
159159
(eq_cong a a2 a1 (sym a a1 a2 eq_a) b f2));
160160

161+
162+
-- Basic axiom about the behavior of "fix"
163+
axiom fix_unfold :
164+
(a : sort 1) ->
165+
(f : a -> a) ->
166+
Eq a (fix a f) (f (fix a f));
167+
168+
inverse_eta_rule :
169+
(a : sort 1) ->
170+
(b : sort 1) ->
171+
(f : a -> b) ->
172+
(g : a -> b) ->
173+
(Eq (a -> b) f g) ->
174+
(x : a) ->
175+
Eq b (f x) (g x);
176+
inverse_eta_rule a b f g H =
177+
Eq__rec (a -> b) f
178+
( \ (g' : a -> b) -> \ (H':Eq (a -> b) f g') ->
179+
( x : a ) -> Eq b (f x) (g' x))
180+
(\ (x:a) -> Refl b (f x))
181+
g H;
182+
183+
161184
-- Unchecked assertion that two types are equal.
162185
axiom unsafeAssert : (a : sort 1) -> (x : a) -> (y : a) -> Eq a x y;
163186

src/SAWScript/Builtins.hs

+96
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ import Verifier.SAW.SATQuery
7272
import Verifier.SAW.SCTypeCheck hiding (TypedTerm)
7373
import qualified Verifier.SAW.SCTypeCheck as TC (TypedTerm(..))
7474
import Verifier.SAW.Recognizer
75+
import Verifier.SAW.Prelude (scEq)
7576
import Verifier.SAW.SharedTerm
7677
import Verifier.SAW.TypedTerm
7778
import qualified Verifier.SAW.Simulator.Concrete as Concrete
@@ -636,6 +637,58 @@ extract_uninterp unints opaques tt =
636637
pure (tt', replList)
637638

638639

640+
congruence_for :: TypedTerm -> TopLevel TypedTerm
641+
congruence_for tt =
642+
do sc <- getSharedContext
643+
congTm <- io $ build_congruence sc (ttTerm tt)
644+
io $ mkTypedTerm sc congTm
645+
646+
-- | Given an input term, construct another term that
647+
-- represents a congruence law for that term.
648+
-- This term will be a Curry-Howard style theorem statement
649+
-- that can be dispatched to solvers, and should have
650+
-- type "Prop".
651+
--
652+
-- This will only work for terms that represent non-dependent
653+
-- functions.
654+
build_congruence :: SharedContext -> Term -> IO Term
655+
build_congruence sc tm =
656+
do ty <- scTypeOf sc tm
657+
case asPiList ty of
658+
([],_) -> fail "congruence_for: Term is not a function"
659+
(pis, body) ->
660+
if looseVars body == emptyBitSet then
661+
loop pis []
662+
else
663+
fail "congruence_for: cannot build congruence for dependent functions"
664+
where
665+
loop ((nm,tp):pis) vars =
666+
if looseVars tp == emptyBitSet then
667+
do l <- scFreshEC sc (nm <> "_1") tp
668+
r <- scFreshEC sc (nm <> "_2") tp
669+
loop pis ((l,r):vars)
670+
else
671+
fail "congruence_for: cannot build congruence for dependent functions"
672+
673+
loop [] vars =
674+
do lvars <- mapM (scExtCns sc . fst) (reverse vars)
675+
rvars <- mapM (scExtCns sc . snd) (reverse vars)
676+
let allVars = concat [ [l,r] | (l,r) <- reverse vars ]
677+
678+
basel <- scApplyAll sc tm lvars
679+
baser <- scApplyAll sc tm rvars
680+
baseeq <- scEqTrue sc =<< scEq sc basel baser
681+
682+
let f x (l,r) =
683+
do l' <- scExtCns sc l
684+
r' <- scExtCns sc r
685+
eq <- scEqTrue sc =<< scEq sc l' r'
686+
scFun sc eq x
687+
finalEq <- foldM f baseeq vars
688+
689+
scGeneralizeExts sc allVars finalEq
690+
691+
639692
filterCryTerms :: SharedContext -> [Term] -> IO [TypedTerm]
640693
filterCryTerms sc = loop
641694
where
@@ -1201,6 +1254,11 @@ abstractSymbolicPrim (TypedTerm _ t) = do
12011254
bindAllExts :: SharedContext -> Term -> IO Term
12021255
bindAllExts sc body = scAbstractExts sc (getAllExts body) body
12031256

1257+
term_apply :: TypedTerm -> [TypedTerm] -> TopLevel TypedTerm
1258+
term_apply fn args =
1259+
do sc <- getSharedContext
1260+
io $ applyTypedTerms sc fn args
1261+
12041262
lambda :: TypedTerm -> TypedTerm -> TopLevel TypedTerm
12051263
lambda x = lambdas [x]
12061264

@@ -1464,6 +1522,44 @@ eval_size s =
14641522
Right _ -> fail "eval_size: not a numeric type"
14651523
_ -> fail "eval_size: unsupported polymorphic type"
14661524

1525+
int_to_term :: Int -> TopLevel TypedTerm
1526+
int_to_term i
1527+
| i < 0 =
1528+
do sc <- getSharedContext
1529+
tm <- io (scNat sc (fromInteger (negate (toInteger i))))
1530+
tm' <- io (scIntNeg sc =<< scNatToInt sc tm)
1531+
io (mkTypedTerm sc tm')
1532+
| otherwise =
1533+
do sc <- getSharedContext
1534+
tm <- io (scNat sc (fromIntegral i))
1535+
tm' <- io (scNatToInt sc tm)
1536+
io (mkTypedTerm sc tm')
1537+
1538+
nat_to_term :: Int -> TopLevel TypedTerm
1539+
nat_to_term i
1540+
| i >= 0 =
1541+
do sc <- getSharedContext
1542+
tm <- io $ scNat sc (fromIntegral i)
1543+
io $ mkTypedTerm sc tm
1544+
1545+
| otherwise =
1546+
fail ("nat_to_term: negative value " ++ show i)
1547+
1548+
1549+
size_to_term :: C.Schema -> TopLevel TypedTerm
1550+
size_to_term s =
1551+
do sc <- getSharedContext
1552+
tm <- io $ case s of
1553+
C.Forall [] [] t ->
1554+
case C.evalType mempty t of
1555+
Left (C.Nat x) | x >= 0 ->
1556+
scCtorApp sc "Cryptol.TCNum" =<< sequence [scNat sc (fromInteger x)]
1557+
Left C.Inf -> scCtorApp sc "Cryptol.TCInf" []
1558+
_ -> fail "size_to_term: not a numeric type"
1559+
_ -> fail "size_to_term: unsupported polymorphic type"
1560+
1561+
return (TypedTerm (TypedTermKind C.KNum) tm)
1562+
14671563
nthPrim :: [a] -> Int -> TopLevel a
14681564
nthPrim [] _ = fail "nth: index too large"
14691565
nthPrim (x : _) 0 = return x

src/SAWScript/Interpreter.hs

+31
Original file line numberDiff line numberDiff line change
@@ -1075,6 +1075,13 @@ primitives = Map.fromList
10751075
, "is used only for pretty-printing."
10761076
]
10771077

1078+
, prim "term_apply" "Term -> [Term] -> Term"
1079+
(funVal2 term_apply)
1080+
Current
1081+
[ "Build a term application node that applies the first term"
1082+
, "(which much be a term representing a function) to given list of arguments."
1083+
]
1084+
10781085
, prim "lambda" "Term -> Term -> Term"
10791086
(funVal2 lambda)
10801087
Current
@@ -1090,6 +1097,22 @@ primitives = Map.fromList
10901097
, "variables."
10911098
]
10921099

1100+
, prim "size_to_term" "Type -> Term"
1101+
(funVal1 size_to_term)
1102+
Current
1103+
[ "Convert a Cryptol size type into a Term representation."
1104+
]
1105+
1106+
, prim "int_to_term" "Int -> Term"
1107+
(funVal1 int_to_term)
1108+
Current
1109+
[ "Convert a concrete integer value into an integer term." ]
1110+
1111+
, prim "nat_to_term" "Int -> Term"
1112+
(funVal1 nat_to_term)
1113+
Current
1114+
[ "Convert a non-negative integer value into a natural number term." ]
1115+
10931116
, prim "term_theories" "[String] -> Term -> [String]"
10941117
(funVal2 term_theories)
10951118
Experimental
@@ -1111,6 +1134,14 @@ primitives = Map.fromList
11111134
Experimental
11121135
[ "Apply Cryptol defaulting rules to the given term." ]
11131136

1137+
, prim "congruence_for" "Term -> TopLevel Term"
1138+
(pureVal congruence_for)
1139+
Experimental
1140+
[ "Given a term representing a (nondependent) function, attempt"
1141+
, "to automatically construct the statement of a congruence lemma"
1142+
, "for the function."
1143+
]
1144+
11141145
, prim "extract_uninterp" "[String] -> [String] -> Term -> TopLevel (Term, [(String,[(Term, Term)])])"
11151146
(pureVal extract_uninterp)
11161147
Experimental

0 commit comments

Comments
 (0)