Skip to content

Commit 9f7010f

Browse files
authored
Implement --smt-retry-limit option (#3166)
1 parent ac11645 commit 9f7010f

File tree

3 files changed

+81
-1
lines changed

3 files changed

+81
-1
lines changed

kore/src/Kore/Rewrite/SMT/Evaluator.hs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import Control.Lens qualified as Lens
2222
import Control.Monad.Counter qualified as Counter
2323
import Control.Monad.State.Strict qualified as State
2424
import Data.Generics.Product.Fields
25+
import Data.Limit
2526
import Data.Map.Strict qualified as Map
2627
import Data.Set qualified as Set
2728
import Data.Text qualified as Text
@@ -171,6 +172,7 @@ decidePredicate sideCondition predicates =
171172

172173
whenUnknown f Unknown = f
173174
whenUnknown _ result = return result
175+
174176
query :: MaybeT simplifier Result
175177
query =
176178
SMT.withSolver . evalTranslator $ do
@@ -182,9 +184,33 @@ decidePredicate sideCondition predicates =
182184
traverse_ SMT.assert predicates'
183185
SMT.check
184186

187+
retry :: MaybeT simplifier Result
185188
retry = do
189+
SMT.RetryLimit limit <- SMT.askRetryLimit
190+
-- Use the same timeout for the first retry, since sometimes z3
191+
-- decides it doesn't want to work today and all we need is to
192+
-- retry it once.
193+
let timeoutScales = takeWithin limit [1 ..]
194+
let retryActions = map retryOnceWithScaledTimeout timeoutScales
195+
let combineRetries r1 r2 = r1 >>= whenUnknown r2
196+
-- This works even if 'retryActions' is infinite, because the second
197+
-- argument to 'whenUnknown' will be the 'combineRetries' of all of
198+
-- the tail of the list. As soon as a result is not 'Unknown', the
199+
-- rest of the fold is discarded.
200+
foldr combineRetries (pure Unknown) retryActions
201+
202+
retryOnceWithScaledTimeout :: Integer -> MaybeT simplifier Result
203+
retryOnceWithScaledTimeout scale =
204+
-- scale the timeout _inside_ 'retryOnce' so that we override the
205+
-- call to 'SMT.reinit'.
206+
retryOnce $ SMT.localTimeOut (scaleTimeOut scale) query
207+
208+
scaleTimeOut _ (SMT.TimeOut Unlimited) = SMT.TimeOut Unlimited
209+
scaleTimeOut n (SMT.TimeOut (Limit r)) = SMT.TimeOut (Limit (n * r))
210+
211+
retryOnce actionToRetry = do
186212
SMT.reinit
187-
result <- query
213+
result <- actionToRetry
188214
debugRetrySolverQuery predicates
189215
return result
190216

kore/src/Options/SMT.hs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import System.FilePath (
5252
-- | Command line options for the SMT solver.
5353
data KoreSolverOptions = KoreSolverOptions
5454
{ timeOut :: !TimeOut
55+
, retryLimit :: !RetryLimit
5556
, rLimit :: !RLimit
5657
, resetInterval :: !ResetInterval
5758
, prelude :: !Prelude
@@ -62,6 +63,7 @@ parseKoreSolverOptions :: Parser KoreSolverOptions
6263
parseKoreSolverOptions =
6364
KoreSolverOptions
6465
<$> parseTimeOut
66+
<*> parseRetryLimit
6567
<*> parseRLimit
6668
<*> parseResetInterval
6769
<*> parsePrelude
@@ -76,6 +78,15 @@ parseKoreSolverOptions =
7678
<> value defaultTimeOut
7779
)
7880

81+
parseRetryLimit =
82+
option
83+
readRetryLimit
84+
( metavar "SMT_RETRY_LIMIT"
85+
<> long "smt-retry-limit"
86+
<> help "Limit how many times an SMT query can be retried (with scaling timeouts)"
87+
<> value defaultRetryLimit
88+
)
89+
7990
parseRLimit =
8091
option
8192
readRLimit
@@ -106,12 +117,14 @@ parseKoreSolverOptions =
106117

107118
SMT.Config
108119
{ timeOut = defaultTimeOut
120+
, retryLimit = defaultRetryLimit
109121
, rLimit = defaultRLimit
110122
, resetInterval = defaultResetInterval
111123
} =
112124
SMT.defaultConfig
113125

114126
readTimeOut = readPositiveIntegral (SMT.TimeOut . Limit) "smt-timeout"
127+
readRetryLimit = readPositiveIntegral (SMT.RetryLimit . Limit) "smt-retry-limit"
115128
readRLimit = readPositiveIntegral (SMT.RLimit . Limit) "smt-rlimit"
116129
readResetInterval =
117130
readPositiveIntegral SMT.ResetInterval "smt-reset-interval"
@@ -120,6 +133,7 @@ unparseKoreSolverOptions :: KoreSolverOptions -> [String]
120133
unparseKoreSolverOptions
121134
KoreSolverOptions
122135
{ timeOut = TimeOut unwrappedTimeOut
136+
, retryLimit = RetryLimit unwrappedRetryLimit
123137
, rLimit = RLimit unwrappedRLimit
124138
, resetInterval
125139
, prelude = Prelude unwrappedPrelude
@@ -128,6 +142,8 @@ unparseKoreSolverOptions
128142
catMaybes
129143
[ (\limit -> unwords ["--smt-timeout", show limit])
130144
<$> maybeLimit Nothing Just unwrappedTimeOut
145+
, (\limit -> unwords ["--smt-retry-limit", show limit])
146+
<$> maybeLimit Nothing Just unwrappedRetryLimit
131147
, (\limit -> unwords ["--smt-rlimit", show limit])
132148
<$> maybeLimit Nothing Just unwrappedRLimit
133149
, pure $

kore/src/SMT.hs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,12 @@ module SMT (
2626
ackCommand,
2727
loadFile,
2828
reinit,
29+
askRetryLimit,
30+
localTimeOut,
2931
Config (..),
3032
defaultConfig,
3133
TimeOut (..),
34+
RetryLimit (..),
3235
RLimit (..),
3336
ResetInterval (..),
3437
Prelude (..),
@@ -216,6 +219,11 @@ reinit :: MonadSMT m => m ()
216219
reinit = liftSMT reinitSMT
217220
{-# INLINE reinit #-}
218221

222+
-- | Get the retry limit from the SMT
223+
askRetryLimit :: MonadSMT m => m RetryLimit
224+
askRetryLimit = liftSMT askRetryLimitSMT
225+
{-# INLINE askRetryLimit #-}
226+
219227
-- * Implementation
220228

221229
data SolverSetup = SolverSetup
@@ -376,6 +384,10 @@ instance MonadSMT m => MonadSMT (RWST r () s m)
376384
newtype TimeOut = TimeOut {getTimeOut :: Limit Integer}
377385
deriving stock (Eq, Ord, Read, Show)
378386

387+
-- | Retry-limit for SMT queries.
388+
newtype RetryLimit = RetryLimit {getRetryLimit :: Limit Integer}
389+
deriving stock (Eq, Ord, Read, Show)
390+
379391
-- | Resource-limit for SMT queries.
380392
newtype RLimit = RLimit {getRLimit :: Limit Integer}
381393
deriving stock (Eq, Ord, Read, Show)
@@ -399,6 +411,8 @@ data Config = Config
399411
logFile :: !(Maybe FilePath)
400412
, -- | query time limit
401413
timeOut :: !TimeOut
414+
, -- | query retry limit
415+
retryLimit :: !RetryLimit
402416
, -- | query resource limit
403417
rLimit :: !RLimit
404418
, -- | reset solver after this number of queries
@@ -417,6 +431,7 @@ defaultConfig =
417431
, prelude = Prelude Nothing
418432
, logFile = Nothing
419433
, timeOut = TimeOut (Limit 40)
434+
, retryLimit = RetryLimit (Limit 1)
420435
, rLimit = RLimit Unlimited
421436
, resetInterval = ResetInterval 100
422437
}
@@ -605,6 +620,29 @@ reinitSMT =
605620
Nothing -> return ()
606621
Just solverSetup -> reinitSMT' solverSetup
607622

623+
{- | Run a solver action with an adjusted timeout,
624+
and reset the timeout when it's done.
625+
-}
626+
localTimeOut :: MonadSMT m => (TimeOut -> TimeOut) -> m a -> m a
627+
localTimeOut adjust isolated = do
628+
originalTimeOut <- liftSMT extractTimeOut
629+
setTimeOut $ adjust originalTimeOut
630+
isolated <* setTimeOut originalTimeOut
631+
where
632+
extractTimeOut =
633+
SMT $
634+
pure . \case
635+
Nothing -> TimeOut Unlimited
636+
Just setup -> timeOut $ config setup
637+
638+
-- | Get the retry limit for SMT queries.
639+
askRetryLimitSMT :: SMT RetryLimit
640+
askRetryLimitSMT =
641+
SMT $
642+
pure . \case
643+
Nothing -> RetryLimit (Limit 0)
644+
Just setup -> retryLimit $ config setup
645+
608646
-- --------------------------------
609647
-- Internal
610648

0 commit comments

Comments
 (0)