Skip to content

Commit f1d69a0

Browse files
authored
Merge pull request #1600 from GaloisInc/mr-solver/widening
Mr Solver Widening
2 parents 7c1ee59 + b648dd0 commit f1d69a0

File tree

6 files changed

+262
-118
lines changed

6 files changed

+262
-118
lines changed

heapster-saw/examples/Makefile

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
all: Makefile.coq
1+
all: Makefile.coq mr-solver-tests
22

33
Makefile.coq: _CoqProject
44
coq_makefile -f _CoqProject -o Makefile.coq
@@ -32,3 +32,12 @@ rust_data.bc: rust_data.rs
3232

3333
rust_lifetimes.bc: rust_lifetimes.rs
3434
rustc --crate-type=lib --emit=llvm-bc rust_lifetimes.rs
35+
36+
# Lists all the Mr Solver tests, without their ".saw" suffix
37+
MR_SOLVER_TESTS = arrays_mr_solver
38+
39+
.PHONY: mr-solver-tests $(MR_SOLVER_TESTS)
40+
mr-solver-tests: $(MR_SOLVER_TESTS)
41+
42+
$(MR_SOLVER_TESTS):
43+
$(SAW) $@.saw
+17-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
11
include "arrays.saw";
2+
3+
let eq_bool b1 b2 =
4+
if b1 then
5+
if b2 then true else false
6+
else
7+
if b2 then false else true;
8+
9+
let fail = do { print "Test failed"; exit 1; };
10+
let run_test name test expected =
11+
do { if expected then print (str_concat "Test: " name) else
12+
print (str_concat (str_concat "Test: " name) " (expecting failure)");
13+
actual <- test;
14+
if eq_bool actual expected then print "Success\n" else
15+
do { print "Test failed\n"; exit 1; }; };
16+
17+
// Test that contains0 |= contains0
218
contains0 <- parse_core_mod "arrays" "contains0";
3-
mr_solver_debug 1 contains0 contains0;
19+
run_test "contains0 |= contains0" (mr_solver contains0 contains0) true;

src/SAWScript/Prover/MRSolver/Monad.hs

+102-67
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,6 @@ data MRFailure
6666
| MalformedDefsFun Term
6767
| MalformedComp Term
6868
| NotCompFunType Term
69-
| CoIndHypMismatchWidened FunName FunName CoIndHyp
70-
| CoIndHypMismatchFailure (NormComp, NormComp) (NormComp, NormComp)
7169
-- | A local variable binding
7270
| MRFailureLocalVar LocalName MRFailure
7371
-- | Information about the context of the failure
@@ -81,8 +79,8 @@ ppWithPrefix :: PrettyInCtx a => String -> a -> PPInCtxM SawDoc
8179
ppWithPrefix str a = (pretty str <>) <$> nest 2 <$> (line <>) <$> prettyInCtx a
8280

8381
-- | Pretty-print two objects, prefixed with a 'String' and with a separator
84-
ppWithPrefixSep :: PrettyInCtx a => String -> a -> String -> a ->
85-
PPInCtxM SawDoc
82+
ppWithPrefixSep :: (PrettyInCtx a, PrettyInCtx b) =>
83+
String -> a -> String -> b -> PPInCtxM SawDoc
8684
ppWithPrefixSep d1 t2 d3 t4 =
8785
prettyInCtx t2 >>= \d2 -> prettyInCtx t4 >>= \d4 ->
8886
return $ group (pretty d1 <> nest 2 (line <> d2) <> line <>
@@ -124,13 +122,6 @@ instance PrettyInCtx MRFailure where
124122
ppWithPrefix "Could not handle computation:" t
125123
prettyInCtx (NotCompFunType tp) =
126124
ppWithPrefix "Not a computation or computational function type:" tp
127-
prettyInCtx (CoIndHypMismatchWidened nm1 nm2 _) =
128-
ppWithPrefixSep "[Internal] Trying to widen co-inductive hypothesis on:" nm1 "," nm2
129-
prettyInCtx (CoIndHypMismatchFailure (tm1, tm2) (tm1', tm2')) =
130-
do pp <- ppWithPrefixSep "" tm1 "|=" tm2
131-
pp' <- ppWithPrefixSep "" tm1' "|=" tm2'
132-
return $ "Could not match co-inductive hypothesis:" <> pp' <> line <>
133-
"with goal:" <> pp
134125
prettyInCtx (MRFailureLocalVar x err) =
135126
local (x:) $ prettyInCtx err
136127
prettyInCtx (MRFailureCtx ctx err) =
@@ -184,16 +175,34 @@ data CoIndHyp = CoIndHyp {
184175
-- from outermost to innermost; that is, the uvars as "seen from outside their
185176
-- scope", which is the reverse of the order of 'mrUVars', below
186177
coIndHypCtx :: [(LocalName,Term)],
178+
-- | The LHS function name
179+
coIndHypLHSFun :: FunName,
180+
-- | The RHS function name
181+
coIndHypRHSFun :: FunName,
187182
-- | The LHS argument expressions @y1, ..., ym@ over the 'coIndHypCtx' uvars
188183
coIndHypLHS :: [Term],
189184
-- | The RHS argument expressions @y1, ..., ym@ over the 'coIndHypCtx' uvars
190185
coIndHypRHS :: [Term]
191186
} deriving Show
192187

188+
-- | Extract the @i@th argument on either the left- or right-hand side of a
189+
-- coinductive hypothesis
190+
coIndHypArg :: CoIndHyp -> Either Int Int -> Term
191+
coIndHypArg (CoIndHyp _ _ _ args1 _) (Left i) = args1 !! i
192+
coIndHypArg (CoIndHyp _ _ _ _ args2) (Right i) = args2 !! i
193+
193194
-- | A map from pairs of function names to co-inductive hypotheses over those
194195
-- names
195196
type CoIndHyps = Map (FunName, FunName) CoIndHyp
196197

198+
instance PrettyInCtx CoIndHyp where
199+
prettyInCtx (CoIndHyp ctx f1 f2 args1 args2) =
200+
local (const $ map fst $ reverse ctx) $
201+
prettyAppList [return (ppCtx ctx <> "."),
202+
prettyInCtx (FunBind f1 args1 CompFunReturn),
203+
return "|=",
204+
prettyInCtx (FunBind f2 args2 CompFunReturn)]
205+
197206
-- | An assumption that a named function refines some specificaiton. This has
198207
-- the form
199208
--
@@ -244,14 +253,20 @@ data MRState = MRState {
244253
mrsVars :: MRVarMap
245254
}
246255

256+
-- | The exception type for MR. Solver, which is either a 'MRFailure' or a
257+
-- widening request
258+
data MRExn = MRExnFailure MRFailure
259+
| MRExnWiden FunName FunName [Either Int Int]
260+
deriving Show
261+
247262
-- | Mr. Monad, the monad used by MR. Solver, which has 'MRInfo' as as a
248263
-- shared environment, 'MRState' as state, and 'MRFailure' as an exception
249264
-- type, all over an 'IO' monad
250265
newtype MRM a = MRM { unMRM :: ReaderT MRInfo (StateT MRState
251-
(ExceptT MRFailure IO)) a }
266+
(ExceptT MRExn IO)) a }
252267
deriving (Functor, Applicative, Monad, MonadIO,
253268
MonadReader MRInfo, MonadState MRState,
254-
MonadError MRFailure)
269+
MonadError MRExn)
255270

256271
instance MonadTerm MRM where
257272
mkTermF = liftSC1 scTermF
@@ -301,23 +316,41 @@ runMRM sc timeout debug assumps m =
301316
mriUVars = [], mriCoIndHyps = Map.empty,
302317
mriAssumptions = true_tm }
303318
let init_st = MRState { mrsVars = Map.empty }
304-
runExceptT $ flip evalStateT init_st $ flip runReaderT init_info $ unMRM m
319+
res <- runExceptT $ flip evalStateT init_st $
320+
flip runReaderT init_info $ unMRM m
321+
case res of
322+
Right a -> return $ Right a
323+
Left (MRExnFailure failure) -> return $ Left failure
324+
Left exn -> fail ("runMRM: unexpected internal exception: " ++ show exn)
325+
326+
-- | Throw an 'MRFailure'
327+
throwMRFailure :: MRFailure -> MRM a
328+
throwMRFailure = throwError . MRExnFailure
305329

306330
-- | Apply a function to any failure thrown by an 'MRM' computation
307-
mapFailure :: (MRFailure -> MRFailure) -> MRM a -> MRM a
308-
mapFailure f m = catchError m (throwError . f)
331+
mapMRFailure :: (MRFailure -> MRFailure) -> MRM a -> MRM a
332+
mapMRFailure f m = catchError m $ \case
333+
MRExnFailure failure -> throwError $ MRExnFailure $ f failure
334+
e -> throwError e
335+
336+
-- | Catch any 'MRFailure' raised by a computation
337+
catchFailure :: MRM a -> (MRFailure -> MRM a) -> MRM a
338+
catchFailure m f =
339+
m `catchError` \case
340+
MRExnFailure failure -> f failure
341+
e -> throwError e
309342

310343
-- | Try two different 'MRM' computations, combining their failures if needed.
311344
-- Note that the 'MRState' will reset if the first computation fails.
312345
mrOr :: MRM a -> MRM a -> MRM a
313346
mrOr m1 m2 =
314-
catchError m1 $ \err1 ->
315-
catchError m2 $ \err2 ->
316-
throwError $ MRFailureDisj err1 err2
347+
catchFailure m1 $ \err1 ->
348+
catchFailure m2 $ \err2 ->
349+
throwMRFailure $ MRFailureDisj err1 err2
317350

318351
-- | Run an 'MRM' computation in an extended failure context
319352
withFailureCtx :: FailCtx -> MRM a -> MRM a
320-
withFailureCtx ctx = mapFailure (MRFailureCtx ctx)
353+
withFailureCtx ctx = mapMRFailure (MRFailureCtx ctx)
321354

322355
{-
323356
-- | Catch any errors thrown by a computation and coerce them to a 'Left'
@@ -394,11 +427,20 @@ mrApplyAll f args = liftSC2 scApplyAll f args >>= liftSC1 betaNormalize
394427
-- types as SAW core 'Term's, with the least recently bound uvar first, i.e., in
395428
-- the order as seen "from the outside"
396429
mrUVarCtx :: MRM [(LocalName,Term)]
397-
mrUVarCtx = reverse <$> map (\(nm,Type tp) -> (nm,tp)) <$> mrUVars
430+
mrUVarCtx = reverse <$> mrUVarCtxRev
431+
432+
-- | Get the current context of uvars as a list of variable names and their
433+
-- types as SAW core 'Term's, with the most recently bound uvar first, i.e., in
434+
-- the order as seen "from the inside"
435+
mrUVarCtxRev :: MRM [(LocalName,Term)]
436+
mrUVarCtxRev = map (\(nm,Type tp) -> (nm,tp)) <$> mrUVars
398437

399438
-- | Get the type of a 'Term' in the current uvar context
400439
mrTypeOf :: Term -> MRM Term
401-
mrTypeOf t = mrUVarCtx >>= \ctx -> liftSC2 scTypeOf' (map snd ctx) t
440+
mrTypeOf t =
441+
-- NOTE: scTypeOf' wants the type context in the most recently bound var
442+
-- first, i.e., in the mrUVarCtxRev order
443+
mrUVarCtxRev >>= \ctx -> liftSC2 scTypeOf' (map snd ctx) t
402444

403445
-- | Check if two 'Term's are convertible in the 'MRM' monad
404446
mrConvertible :: Term -> Term -> MRM Bool
@@ -419,7 +461,7 @@ mrFunOutType fname args =
419461
debugPrint 0 ("Expected: " ++ show (length vars) ++
420462
", found: " ++ show (length args))
421463
debugPretty 0 ("For function: " <> pp_fname <> " with type: " <> pp_ftype)
422-
error"mrFunOutType"
464+
error "mrFunOutType"
423465

424466
-- | Turn a 'LocalName' into one not in a list, adding a suffix if necessary
425467
uniquifyName :: LocalName -> [LocalName] -> LocalName
@@ -430,16 +472,19 @@ uniquifyName nm nms =
430472
Just nm' -> nm'
431473
Nothing -> error "uniquifyName"
432474

475+
-- | Turn a list of 'LocalName's into one names not in a list, adding suffixes
476+
-- if necessary
477+
uniquifyNames :: [LocalName] -> [LocalName] -> [LocalName]
478+
uniquifyNames [] _ = []
479+
uniquifyNames (nm:nms) nms_other =
480+
let nm' = uniquifyName nm nms_other in
481+
nm' : uniquifyNames nms (nm' : nms_other)
482+
433483
-- | Run a MR Solver computation in a context extended with a universal
434484
-- variable, which is passed as a 'Term' to the sub-computation. Note that any
435485
-- assumptions made in the sub-computation will be lost when it completes.
436486
withUVar :: LocalName -> Type -> (Term -> MRM a) -> MRM a
437-
withUVar nm tp m =
438-
do nm' <- uniquifyName nm <$> map fst <$> mrUVars
439-
assumps' <- mrAssumptions >>= liftTerm 0 1
440-
local (\info -> info { mriUVars = (nm',tp) : mriUVars info,
441-
mriAssumptions = assumps' }) $
442-
mapFailure (MRFailureLocalVar nm') (liftSC1 scLocalVar 0 >>= m)
487+
withUVar nm (Type tp) m = withUVars [(nm,tp)] (\[v] -> m v)
443488

444489
-- | Run a MR Solver computation in a context extended with a universal variable
445490
-- and pass it the lifting (in the sense of 'incVars') of an MR Solver term
@@ -453,16 +498,25 @@ withUVarLift nm tp t m =
453498
-- The variables are bound "outside in", meaning the first variable in the list
454499
-- is bound outermost, and so will have the highest deBruijn index.
455500
withUVars :: [(LocalName,Term)] -> ([Term] -> MRM a) -> MRM a
456-
withUVars = helper [] where
457-
-- The extra input list gives the variables that have already been bound, in
458-
-- order from most to least recently bound
459-
helper :: [Term] -> [(LocalName,Term)] -> ([Term] -> MRM a) -> MRM a
460-
helper vars [] m = m $ reverse vars
461-
helper vars ((nm,tp):ctx) m =
462-
-- FIXME: I think substituting here is wrong, but works on closed terms, so
463-
-- it's fine to use at the top level at least...
464-
substTerm 0 vars tp >>= \tp' ->
465-
withUVarLift nm (Type tp') vars $ \var vars' -> helper (var:vars') ctx m
501+
withUVars [] f = f []
502+
withUVars ctx f =
503+
do nms <- uniquifyNames (map fst ctx) <$> map fst <$> mrUVars
504+
let ctx_u = zip nms $ map (Type . snd) ctx
505+
assumps' <- mrAssumptions >>= liftTerm 0 (length ctx)
506+
vars <- reverse <$> mapM (liftSC1 scLocalVar) [0 .. length ctx - 1]
507+
local (\info -> info { mriUVars = reverse ctx_u ++ mriUVars info,
508+
mriAssumptions = assumps' }) $
509+
foldr (\nm m -> mapMRFailure (MRFailureLocalVar nm) m) (f vars) nms
510+
511+
-- | Run a MR Solver in a top-level context, i.e., with no uvars or assumptions
512+
withNoUVars :: MRM a -> MRM a
513+
withNoUVars m =
514+
do true_tm <- liftSC1 scBool True
515+
local (\info -> info { mriUVars = [], mriAssumptions = true_tm }) m
516+
517+
-- | Run a MR Solver in a context of only the specified UVars, no others
518+
withOnlyUVars :: [(LocalName,Term)] -> MRM a -> MRM a
519+
withOnlyUVars vars m = withNoUVars $ withUVars vars $ const m
466520

467521
-- | Build 'Term's for all the uvars currently in scope, ordered from least to
468522
-- most recently bound
@@ -699,32 +753,13 @@ _mrSubstEVarsStrict = mrSubstEVarsStrict
699753
mrGetCoIndHyp :: FunName -> FunName -> MRM (Maybe CoIndHyp)
700754
mrGetCoIndHyp nm1 nm2 = Map.lookup (nm1, nm2) <$> mrCoIndHyps
701755

702-
-- | Run a compuation under the additional co-inductive assumption that
703-
-- @forall x1, ..., xn. F y1 ... ym |= G z1 ... zl@, where @F@ and @G@ are
704-
-- the given 'FunName's, @y1, ..., ym@ and @z1, ..., zl@ are the given
705-
-- argument lists, and @x1, ..., xn@ is the current context of uvars. If
706-
-- while running the given computation a 'CoIndHypMismatchWidened' error is
707-
-- reached with the given names, the state is restored and the computation is
708-
-- re-run with the widened hypothesis. This is done recursively, meaning this
709-
-- function will only return once no 'CoIndHypMismatchWidened' errors are
710-
-- raised with the given names.
711-
withCoIndHyp :: FunName -> [Term] -> FunName -> [Term] -> MRM a -> MRM a
712-
withCoIndHyp nm1 args1 nm2 args2 m =
713-
do ctx <- mrUVarCtx
714-
withCoIndHyp' (nm1, nm2) (CoIndHyp ctx args1 args2) m
715-
716-
-- | The main loop of 'withCoIndHyp'
717-
withCoIndHyp' :: (FunName, FunName) -> CoIndHyp -> MRM a -> MRM a
718-
withCoIndHyp' (nm1, nm2) hyp@(CoIndHyp _ args1 args2) m =
719-
do mrDebugPPPrefixSep 1 "withCoIndHyp" (FunBind nm1 args1 CompFunReturn)
720-
"|=" (FunBind nm2 args2 CompFunReturn)
721-
st <- get
722-
hyps' <- Map.insert (nm1, nm2) hyp <$> mrCoIndHyps
723-
(local (\info -> info { mriCoIndHyps = hyps' }) m) `catchError` \case
724-
CoIndHypMismatchWidened nm1' nm2' hyp' | nm1 == nm1' && nm2 == nm2'
725-
-> -- FIXME: Could restoring the state here cause any problems?
726-
put st >> withCoIndHyp' (nm1, nm2) hyp' m
727-
e -> throwError e
756+
-- | Run a compuation under an additional co-inductive assumption
757+
withCoIndHypRaw :: CoIndHyp -> MRM a -> MRM a
758+
withCoIndHypRaw hyp m =
759+
do debugPretty 1 ("withCoIndHyp" <+> ppInEmptyCtx hyp)
760+
hyps' <- Map.insert (coIndHypLHSFun hyp,
761+
coIndHypRHSFun hyp) hyp <$> mrCoIndHyps
762+
local (\info -> info { mriCoIndHyps = hyps' }) m
728763

729764
-- | Generate fresh evars for the context of a 'CoIndHyp' and
730765
-- substitute them into its arguments and right-hand side
@@ -791,8 +826,8 @@ mrPPInCtx a =
791826

792827
-- | Pretty-print the result of 'ppWithPrefixSep' relative to the current uvar
793828
-- context to 'stderr' if the debug level is at least the 'Int' provided
794-
mrDebugPPPrefixSep :: PrettyInCtx a => Int -> String -> a -> String -> a ->
795-
MRM ()
829+
mrDebugPPPrefixSep :: (PrettyInCtx a, PrettyInCtx b) =>
830+
Int -> String -> a -> String -> b -> MRM ()
796831
mrDebugPPPrefixSep i pre a1 sp a2 =
797832
mrUVars >>= \ctx ->
798833
debugPretty i $

src/SAWScript/Prover/MRSolver/SMT.hs

+8-5
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ mrProvableRaw prop_term =
177177
-- | Test if a Boolean term over the current uvars is provable given the current
178178
-- assumptions
179179
mrProvable :: Term -> MRM Bool
180+
mrProvable (asBool -> Just b) = return b
180181
mrProvable bool_tm =
181182
do assumps <- mrAssumptions
182183
prop <- liftSC2 scImplies assumps bool_tm >>= liftSC1 scEqTrue
@@ -276,12 +277,10 @@ mrAssertProveEq :: Term -> Term -> MRM ()
276277
mrAssertProveEq t1 t2 =
277278
do success <- mrProveEq t1 t2
278279
if success then return () else
279-
throwError (TermsNotEq t1 t2)
280+
throwMRFailure (TermsNotEq t1 t2)
280281

281-
-- | The main workhorse for 'prProveEq'. Build a Boolean term expressing that
282-
-- the third and fourth arguments, whose type is given by the second. This is
283-
-- done in a continuation monad so that the output term can be in a context with
284-
-- additional universal variables.
282+
-- | The main workhorse for 'mrProveEq'. Build a Boolean term expressing that
283+
-- the third and fourth arguments, whose type is given by the second.
285284
mrProveEqH :: Map MRVar MRVarInfo -> Term -> Term -> Term -> MRM TermInCtx
286285

287286
{-
@@ -309,6 +308,10 @@ mrProveEqH var_map _tp t1 (asEVarApp var_map -> Just (evar, args, Nothing)) =
309308
success <- mrTrySetAppliedEVar evar args t1'
310309
TermInCtx [] <$> liftSC1 scBool success
311310

311+
-- For unit types, always return true
312+
mrProveEqH _ (asTupleType -> Just []) _ _ =
313+
TermInCtx [] <$> liftSC1 scBool True
314+
312315
-- For the nat, bitvector, Boolean, and integer types, call mrProveEqSimple
313316
mrProveEqH _ (asDataType -> Just (pn, [])) t1 t2
314317
| primName pn == "Prelude.Nat" =

0 commit comments

Comments
 (0)