Skip to content

Commit 08f0cbd

Browse files
jbertholdgithub-actions
and
github-actions
authored
HOTFIX avoid caching extra predicates, only simplify term during rewrite (#4103)
The change in #3953 introduced additional arguments to evaluation functions which allow callers to supply some "known true" predicates for the simplification and evaluation. However, doing so means that the cache will get populated with associations that might only be true if this known truth does not change. The case in point was a predicate being cached as "true" because of an earlier evaluation, and then _removed_ from the path condition (see added integration test). This change * refactors `checkConstraint` functions to fix the pattern constraints * skips the cache update if any additional "known truth" arguments were supplied to `checkConstraint`, and * runs internal computations that manipulate the predicates with a fresh empty cache. Furthermore, to avoid a significant performance penalty, we * simplify the pattern constraints once before starting to rewrite * only simplify the pattern _term_ during rewriting - the pattern constraints are already evaluated and stay the same - if a new constraint is added (by way of an `ensures`), this constraint is evaluated before adding it * At the end of the rewrite, we still simplify everything again (including the entire path condition). We are not using the SMT solver to detect a vacuous state at the start, though, could add that separately if desired. At the moment, the behaviour is largely the same as before (integration test expectations identical except for some evaluation in vacuous states). --------- Co-authored-by: github-actions <[email protected]>
1 parent d3c36c1 commit 08f0cbd

17 files changed

+194759
-218
lines changed

booster/library/Booster/Pattern/ApplyEquations.hs

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,7 @@ runEquationT definition llvmApi smtSolver sCache known (EquationT m) = do
333333
, logger
334334
, prettyModifiers
335335
}
336+
-- NB the returned cache assumes the known predicates
336337
pure (res, endState.cache)
337338

338339
iterateEquations ::
@@ -423,19 +424,24 @@ llvmSimplify term = do
423424
----------------------------------------
424425
-- Interface functions
425426

426-
-- | Evaluate and simplify a term.
427+
{- | Evaluate and simplify a term.
428+
429+
The returned cache should only be reused with the same known predicates.
430+
-}
427431
evaluateTerm ::
428432
LoggerMIO io =>
429433
Direction ->
430434
KoreDefinition ->
431435
Maybe LLVM.API ->
432436
SMT.SMTContext ->
437+
SimplifierCache ->
433438
Set Predicate ->
434439
Term ->
435440
io (Either EquationFailure Term, SimplifierCache)
436-
evaluateTerm direction def llvmApi smtSolver knownPredicates =
437-
runEquationT def llvmApi smtSolver mempty knownPredicates
438-
. evaluateTerm' direction
441+
evaluateTerm direction def llvmApi smtSolver cache knownPredicates term =
442+
runEquationT def llvmApi smtSolver cache knownPredicates $
443+
withTermContext term $
444+
evaluateTerm' direction term
439445

440446
-- version for internal nested evaluation
441447
evaluateTerm' ::
@@ -447,6 +453,9 @@ evaluateTerm' direction = iterateEquations direction PreferFunctions
447453

448454
{- | Simplify a Pattern, processing its constraints independently.
449455
Returns either the first failure or the new pattern if no failure was encountered
456+
457+
The returned cache may only be reused if pat.constraints are known
458+
to remain true in the next usage context.
450459
-}
451460
evaluatePattern ::
452461
LoggerMIO io =>
@@ -510,35 +519,28 @@ evaluatePattern' pat@Pattern{term, ceilConditions} = withPatternContext pat $ do
510519
err -> throw err
511520

512521
-- evaluate the given predicate assuming all others
522+
-- This manipulates the known predicates so it should run without cache
513523
simplifyAssumedPredicate :: LoggerMIO io => Predicate -> EquationT io ()
514524
simplifyAssumedPredicate p = do
515-
allPs <- predicates <$> getState
516-
let otherPs = Set.delete p allPs
517-
eqState $ modify $ \s -> s{predicates = otherPs}
525+
prior <- getState
526+
let otherPs = Set.delete p (prior.predicates)
527+
eqState $ modify $ \s -> s{predicates = otherPs, cache = mempty}
518528
newP <- simplifyConstraint' True $ coerce p
519-
pushConstraints $ Set.singleton $ coerce newP
529+
eqState $ modify $ \s -> s{cache = prior.cache, predicates = otherPs <> Set.singleton (coerce newP)}
520530

521531
evaluateConstraints ::
522532
LoggerMIO io =>
523533
KoreDefinition ->
524534
Maybe LLVM.API ->
525535
SMT.SMTContext ->
526-
SimplifierCache ->
527536
Set Predicate ->
528-
io (Either EquationFailure (Set Predicate), SimplifierCache)
529-
evaluateConstraints def mLlvmLibrary smtSolver cache =
530-
runEquationT def mLlvmLibrary smtSolver cache mempty . evaluateConstraints'
531-
532-
evaluateConstraints' ::
533-
LoggerMIO io =>
534-
Set Predicate ->
535-
EquationT io (Set Predicate)
536-
evaluateConstraints' constraints = do
537-
pushConstraints constraints
538-
-- evaluate all existing constraints, once
539-
traverse_ simplifyAssumedPredicate . predicates =<< getState
540-
-- this may yield additional new constraints, left unevaluated
541-
predicates <$> getState
537+
io (Either EquationFailure (Set Predicate))
538+
evaluateConstraints def mLlvmLibrary smtSolver constraints =
539+
fmap fst $ runEquationT def mLlvmLibrary smtSolver mempty constraints $ do
540+
-- evaluate all existing constraints, once
541+
traverse_ simplifyAssumedPredicate . predicates =<< getState
542+
-- this may yield additional new constraints, left unevaluated
543+
predicates <$> getState
542544

543545
----------------------------------------
544546

@@ -1075,9 +1077,8 @@ applyEquation term rule =
10751077
(to decide whether or not a rule can apply, not to retain the
10761078
ensured conditions).
10771079
1078-
If and as soon as this function is used inside equation
1079-
application, it needs to run within the same 'EquationT' context
1080-
so we can detect simplification loops and avoid monad nesting.
1080+
Consistency of the returned SimplifierCache must be tracked by the
1081+
caller, the cache incorporates the given knownPredicates.
10811082
-}
10821083
simplifyConstraint ::
10831084
LoggerMIO io =>
@@ -1088,8 +1089,11 @@ simplifyConstraint ::
10881089
Set Predicate ->
10891090
Predicate ->
10901091
io (Either EquationFailure Predicate, SimplifierCache)
1091-
simplifyConstraint def mbApi smt cache knownPredicates (Predicate p) = do
1092-
runEquationT def mbApi smt cache knownPredicates $ (coerce <$>) . simplifyConstraint' True $ p
1092+
simplifyConstraint def mbApi smt cache knownPredicates =
1093+
runEquationT def mbApi smt cache knownPredicates
1094+
. (coerce <$>)
1095+
. simplifyConstraint' True
1096+
. coerce
10931097

10941098
simplifyConstraints ::
10951099
LoggerMIO io =>

booster/library/Booster/Pattern/Implies.hs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,8 @@ runImplies def mLlvmLibrary mSMTOptions antecedent consequent =
150150
subst
151151
else -- FIXME This is incomplete because patL.constraints are not assumed in the check.
152152

153-
ApplyEquations.evaluateConstraints def mLlvmLibrary solver mempty filteredConsequentPreds >>= \case
154-
(Right newPreds, _) ->
153+
ApplyEquations.evaluateConstraints def mLlvmLibrary solver filteredConsequentPreds >>= \case
154+
Right newPreds ->
155155
if all (== Predicate TrueBool) newPreds
156156
then
157157
implies
@@ -161,7 +161,7 @@ runImplies def mLlvmLibrary mSMTOptions antecedent consequent =
161161
subst
162162
else -- here we conservatively abort (incomplete)
163163
pure . Left . RpcError.backendError $ RpcError.Aborted "unknown constraints"
164-
(Left other, _) ->
164+
Left other ->
165165
pure . Left . RpcError.backendError $ RpcError.Aborted (Text.pack . constructorName $ other)
166166

167167
case (internalised antecedent.term, internalised consequent.term) of

booster/library/Booster/Pattern/Rewrite.hs

Lines changed: 60 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import Data.List.NonEmpty (NonEmpty (..), toList)
3838
import Data.List.NonEmpty qualified as NE
3939
import Data.Map qualified as Map
4040
import Data.Maybe (catMaybes, fromMaybe)
41-
import Data.Sequence (Seq, (|>))
41+
import Data.Sequence as Seq (Seq, fromList, (|>))
4242
import Data.Set qualified as Set
4343
import Data.Text as Text (Text, pack)
4444
import Numeric.Natural
@@ -55,9 +55,12 @@ import Booster.LLVM as LLVM (API)
5555
import Booster.Log
5656
import Booster.Pattern.ApplyEquations (
5757
CacheTag (Equations),
58+
Direction (..),
5859
EquationFailure (..),
5960
SimplifierCache (..),
61+
evaluateConstraints,
6062
evaluatePattern,
63+
evaluateTerm,
6164
simplifyConstraint,
6265
)
6366
import Booster.Pattern.Base
@@ -516,9 +519,16 @@ applyRule pat@Pattern{ceilConditions} rule =
516519
, rulePredicate = Just rulePredicate
517520
}
518521
where
519-
filterOutKnownConstraints :: Set.Set Predicate -> [Predicate] -> RewriteT io [Predicate]
520-
filterOutKnownConstraints priorKnowledge constraitns = do
521-
let (knownTrue, toCheck) = partition (`Set.member` priorKnowledge) constraitns
522+
-- These predicates are known (and do not change) during the
523+
-- entire rewrite step. The simplifier cache cannot be retained
524+
-- when additional predicates are used (see 'checkConstraint').
525+
knownPatternPredicates =
526+
pat.constraints <> (Set.fromList . asEquations $ pat.substitution)
527+
528+
filterOutKnownConstraints :: [Predicate] -> RewriteT io [Predicate]
529+
filterOutKnownConstraints constraints = do
530+
let (knownTrue, toCheck) =
531+
partition (`Set.member` knownPatternPredicates) constraints
522532
unless (null knownTrue) $
523533
getPrettyModifiers >>= \case
524534
ModifiersRep (_ :: FromModifiersT mods => Proxy mods) ->
@@ -537,14 +547,16 @@ applyRule pat@Pattern{ceilConditions} rule =
537547
Set.Set Predicate ->
538548
Predicate ->
539549
RewriteRuleAppT (RewriteT io) (Maybe a)
540-
checkConstraint onUnclear onBottom knownPredicates p = do
550+
checkConstraint onUnclear onBottom extraPredicates p = do
541551
RewriteConfig{definition, llvmApi, smtSolver} <- lift $ RewriteT ask
542-
RewriteState{cache = oldCache} <- lift . RewriteT . lift $ get
543-
(simplified, cache) <-
552+
RewriteState{cache} <- lift . RewriteT . lift $ get
553+
let knownPredicates = knownPatternPredicates <> extraPredicates
554+
(simplified, newCache) <-
544555
withContext CtxConstraint $
545-
simplifyConstraint definition llvmApi smtSolver oldCache knownPredicates p
546-
-- update cache
547-
lift $ updateRewriterCache cache
556+
simplifyConstraint definition llvmApi smtSolver cache knownPredicates p
557+
-- Important: only retain new cache if no extraPredicates were supplied!
558+
when (Set.null extraPredicates) $
559+
lift (updateRewriterCache newCache)
548560
case simplified of
549561
Right (Predicate FalseBool) -> onBottom
550562
Right (Predicate TrueBool) -> pure Nothing
@@ -559,14 +571,9 @@ applyRule pat@Pattern{ceilConditions} rule =
559571
-- apply substitution to rule requires
560572
let ruleRequires =
561573
concatMap (splitBoolPredicates . substituteInPredicate matchingSubst) rule.requires
562-
knownConstraints = pat.constraints <> (Set.fromList . asEquations $ pat.substitution)
563574

564575
-- filter out any predicates known to be _syntactically_ present in the known prior
565-
toCheck <-
566-
lift $
567-
filterOutKnownConstraints
568-
knownConstraints
569-
ruleRequires
576+
toCheck <- lift $ filterOutKnownConstraints ruleRequires
570577

571578
-- simplify the constraints (one by one in isolation). Stop if false, abort rewrite if indeterminate.
572579
unclearRequires <-
@@ -575,17 +582,13 @@ applyRule pat@Pattern{ceilConditions} rule =
575582
( checkConstraint
576583
id
577584
returnNotApplied
578-
knownConstraints
585+
mempty -- checkConstraint already considers knownConstraints
579586
)
580587
toCheck
581588

582589
-- unclear conditions may have been simplified and
583590
-- could now be syntactically present in the path constraints, filter again
584-
stillUnclear <-
585-
lift $
586-
filterOutKnownConstraints
587-
knownConstraints
588-
unclearRequires
591+
stillUnclear <- lift $ filterOutKnownConstraints unclearRequires
589592

590593
-- check unclear requires-clauses in the context of known constraints (priorKnowledge)
591594
solver <- lift $ RewriteT $ (.smtSolver) <$> ask
@@ -614,17 +617,14 @@ applyRule pat@Pattern{ceilConditions} rule =
614617
-- apply substitution to rule ensures
615618
let ruleEnsures =
616619
concatMap (splitBoolPredicates . coerce . substituteInTerm matchingSubst . coerce) rule.ensures
617-
knownConstraints =
618-
pat.constraints
619-
<> (Set.fromList . asEquations $ pat.substitution)
620-
<> Set.fromList unclearRequiresAfterSmt
621620
newConstraints <-
622621
catMaybes
623622
<$> mapM
624623
( checkConstraint
625624
id
626625
returnTrivial
627-
knownConstraints
626+
-- supply required path conditions as extra constraints
627+
(Set.fromList unclearRequiresAfterSmt)
628628
)
629629
ruleEnsures
630630

@@ -672,7 +672,7 @@ applyRule pat@Pattern{ceilConditions} rule =
672672
let ruleRequires =
673673
concatMap (splitBoolPredicates . coerce . substituteInTerm matchingSubst . coerce) rule.requires
674674
collapseAndBools . catMaybes
675-
<$> mapM (checkConstraint id returnNotApplied pat.constraints) ruleRequires
675+
<$> mapM (checkConstraint id returnNotApplied mempty) ruleRequires
676676

677677
ruleGroupPriority :: [RewriteRule a] -> Maybe Priority
678678
ruleGroupPriority = \case
@@ -1001,9 +1001,16 @@ performRewrite ::
10011001
Pattern ->
10021002
io (Natural, Seq (RewriteTrace ()), RewriteResult Pattern)
10031003
performRewrite rewriteConfig pat = do
1004-
(rr, RewriteStepsState{counter, traces}) <-
1005-
flip runStateT rewriteStart $ doSteps False pat
1006-
pure (counter, traces, rr)
1004+
simplifiedConstraints <-
1005+
withContext CtxSimplify $ evaluateConstraints definition llvmApi smtSolver pat.constraints
1006+
case simplifiedConstraints of
1007+
Right constraints ->
1008+
(flip runStateT rewriteStart $ doSteps False pat{constraints})
1009+
>>= \(rr, RewriteStepsState{counter, traces}) -> pure (counter, traces, rr)
1010+
Left r@(SideConditionFalse{}) ->
1011+
pure (0, fromList [RewriteSimplified (Just r)], error "Just return #Bottom here")
1012+
Left err ->
1013+
error (show err)
10071014
where
10081015
RewriteConfig
10091016
{ definition
@@ -1034,6 +1041,27 @@ performRewrite rewriteConfig pat = do
10341041

10351042
updateCache simplifierCache = modify $ \rss -> (rss :: RewriteStepsState){simplifierCache}
10361043

1044+
-- only simplifies the _term_ of the pattern
1045+
simplifyT :: Pattern -> StateT RewriteStepsState io (Maybe Pattern)
1046+
simplifyT p = withContext CtxSimplify $ do
1047+
cache <- simplifierCache <$> get
1048+
evaluateTerm BottomUp definition llvmApi smtSolver cache p.constraints p.term >>= \(res, newCache) -> do
1049+
updateCache newCache
1050+
case res of
1051+
Right newTerm -> do
1052+
emitRewriteTrace $ RewriteSimplified Nothing
1053+
pure $ Just p{term = newTerm}
1054+
Left r@SideConditionFalse{} -> do
1055+
emitRewriteTrace $ RewriteSimplified (Just r)
1056+
pure Nothing
1057+
Left r@UndefinedTerm{} -> do
1058+
emitRewriteTrace $ RewriteSimplified (Just r)
1059+
pure Nothing
1060+
Left other -> do
1061+
emitRewriteTrace $ RewriteSimplified (Just other)
1062+
pure $ Just p
1063+
1064+
-- simplifies term and constraints of the pattern
10371065
simplifyP :: Pattern -> StateT RewriteStepsState io (Maybe Pattern)
10381066
simplifyP p = withContext CtxSimplify $ do
10391067
st <- get
@@ -1228,7 +1256,7 @@ performRewrite rewriteConfig pat = do
12281256
else withSimplified pat' msg (pure . RewriteAborted failure)
12291257
where
12301258
withSimplified p msg cont = do
1231-
(withPatternContext p $ simplifyP p) >>= \case
1259+
(withPatternContext p $ simplifyT p) >>= \case
12321260
Nothing -> do
12331261
logMessage ("Rewrite stuck after simplification." :: Text)
12341262
pure $ RewriteStuck p

booster/test/rpc-integration/resources/3934-smt.kompile

Lines changed: 0 additions & 48 deletions
This file was deleted.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
kompile-from-double-definition.sh

0 commit comments

Comments
 (0)