Skip to content

Commit 71c3fda

Browse files
committed
CERT-9827: Rerun leaf timeouts with random seeds (#8188)
fcf493b367d992fa19a0aeaa1971a98f61e531d2
1 parent b6bda9a commit 71c3fda

File tree

7 files changed

+85
-33
lines changed

7 files changed

+85
-33
lines changed

lib/Shared/src/main/kotlin/config/Config.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3005,6 +3005,24 @@ object Config {
30053005
}"
30063006
}
30073007

3008+
val retryOnLeafTimeout: ConfigType.BooleanCmdLine = object : ConfigType.BooleanCmdLine(
3009+
false,
3010+
Option(
3011+
"smt_retryOnLeafTimeout",
3012+
true,
3013+
"Reruns a timeouting leaf with a broader portfolio of solvers."
3014+
)
3015+
), TransformationAgnosticConfig {}
3016+
3017+
val retryOnLeafTimeoutSolvers: ConfigType.SolverProgramCmdLine = object : ConfigType.SolverProgramCmdLine(
3018+
AllCommonAvailableSolversWithClOptions.toTypedArray(),
3019+
Option(
3020+
"smt_retryOnLeafTimeoutSolvers",
3021+
true,
3022+
"Solvers used for a rerun on timeouting leafs."
3023+
)
3024+
), TransformationAgnosticConfig {}
3025+
30083026
val NumOfUnsatCores: ConfigType.IntCmdLine = object : ConfigType.IntCmdLine(
30093027
1,
30103028
Option(

src/main/kotlin/verifier/LExpVCStats.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ data class ResplitStatistic(
164164
@Serializable
165165
data class SplitStatistic(
166166
/** The location of the split in the split tree. -1 indicates a failed split */
167-
val address: List<Int>,
167+
val address: String,
168168
/** splitName exactly as it is in Split<CoreTACProgram>.name. Also referred to as vcName */
169169
val splitName: String,
170170
val finalResult: String? = null,

src/main/kotlin/verifier/TACVerifier.kt

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import smtlibutils.statistics.LearnedClausesFilterStatistics
5050
import smtlibutils.statistics.PreExecutionStatistics
5151
import solver.ConfigStatistics
5252
import solver.SMTCounterexampleModel
53+
import solver.SolverChoice
5354
import solver.SolverResult
5455
import spec.cvlast.CVLCmd
5556
import spec.cvlast.CVLExpDeclaredSymbolsCollector
@@ -184,7 +185,7 @@ class TACVerifier private constructor(
184185
) {
185186
updateSplitStatistics(
186187
SplitStatistic(
187-
address = address.asIntList,
188+
address = address.name(),
188189
vcFeatures = listOf(),
189190
solvingEvent = ResplitStatistic(resplitTime),
190191
timeToSolveSinceJarStart = TimeSinceStart(),
@@ -209,7 +210,7 @@ class TACVerifier private constructor(
209210

210211
val splitStats = SplitStatistic(
211212
splitName = vcName,
212-
address = address.asIntList,
213+
address = address.name(),
213214
finalResult = LExpVCStatsLogger.getResultStr(checkerResult.satResult),
214215
timeout = timeout,
215216
smtSolvingWallTime = smtSolvingWallTime,
@@ -623,7 +624,7 @@ class TACVerifier private constructor(
623624
vcGenStopWatch = MilliTimeElapserStopWatch(timeStatsRecorder, vcGenTimeTag),
624625
solverStopWatch,
625626
autoConfig,
626-
localSettings
627+
((subProblem as? SplitTree.NodeWithInfo<*>)?.info as? LocalSettings) ?: localSettings
627628
)
628629
val finalResult = verifierResult.finalResult
629630
subProblem.setRunInfo(finalResult, verifierResult.elapsedTimeSeconds.seconds, timeout)
@@ -671,6 +672,24 @@ class TACVerifier private constructor(
671672
problemQueue += subProblem.split(subProblemTAC)
672673
}
673674
continue
675+
} else if (Config.Smt.retryOnLeafTimeout.get() && (subProblem.address !is SplitAddress.Rerun)) {
676+
val sub = LazySubProgram(
677+
splitTree,
678+
SplitAddress.Rerun(subProblem.lazySub.address)
679+
)
680+
val alreadyTried = smtFormulaCheckerResult.subResultsFlattened
681+
.mapNotNull { it.solverInstanceInfo?.solverConfig }
682+
val settings = localSettings.copy(
683+
solverProgramChoice = SolverChoice(
684+
Config.Smt.retryOnLeafTimeoutSolvers.get()
685+
.filter { it.solverInfo.getOptionForRandomSeed(1).isNotEmpty() }
686+
.flatMap { listOf(1, 2, 3).map { seed -> it.copy(randomSeed = seed) } }
687+
.filter { it !in alreadyTried }
688+
.map { it.copy(canBeSkipped = { _,_ -> false }) }
689+
)
690+
)
691+
logger.info { "Timed out on leaf ${sub.name}, rerunning with random seeds" }
692+
problemQueue.add(splitTree.NodeWithInfo(sub, subProblem.sideScore, settings))
674693
} else {
675694
// we dump timeouting leaves.
676695
smtFormulaCheckerResult.let { last ->

src/main/kotlin/verifier/autoconfig/AutoConfigManager.kt

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,11 @@ class AutoConfigManager(val ruleName: String) : Closeable {
7575
* their parent is also UNSAT. The propagation is recursive.
7676
*/
7777
private fun propagateUNSAT(address: SplitAddress): Unit = synchronized(splitStatAndSolverConfigLock) {
78-
if (address.asIntList.toString() !in stats.splitStatistics) {
78+
if (address.name() !in stats.splitStatistics) {
7979
warn("The split with the address $address should be first added to ${::stats.name} before we propagate its unsat result")
8080
return
8181
}
82-
if (!stats.splitStatistics[address.asIntList.toString()]!!.isEventuallyUNSAT()) {
82+
if (!stats.splitStatistics[address.name()]!!.isEventuallyUNSAT()) {
8383
warn("Trying to propagate an unregistered UNSAT result of the split $address")
8484
return
8585
}
@@ -90,10 +90,10 @@ class AutoConfigManager(val ruleName: String) : Closeable {
9090
if (address !is SplitAddress.Block) {
9191
return
9292
}
93-
val siblingAddressStr = address.sibling().toString()
93+
val siblingAddressStr = address.sibling().name()
9494
if (stats.splitStatistics[siblingAddressStr]?.isEventuallyUNSAT() == true) {
9595
val parentAddress = address.parent
96-
val parentAddressStr = parentAddress.toString()
96+
val parentAddressStr = parentAddress?.name() ?: "null"
9797
if (parentAddressStr !in stats.splitStatistics) {
9898
warn("The parent address $parentAddressStr is not registered in ${::stats.name}")
9999
return
@@ -110,12 +110,12 @@ class AutoConfigManager(val ruleName: String) : Closeable {
110110
* Sets the [finalResult] and [solvers] of the stats of [address] based on the stats of [matchingUnsat].
111111
*/
112112
private fun markMatchingUnsat(address: SplitAddress, matchingUnsat: BasicSplitStatistics) = synchronized(splitStatAndSolverConfigLock) {
113-
if(address.asIntList.toString() !in stats.splitStatistics) {
113+
if(address.name() !in stats.splitStatistics) {
114114
warn("Trying to mark an address UNSAT that is not in ${::stats.name}: $address")
115115
return
116116
}
117-
stats.splitStatistics[address.asIntList.toString()] =
118-
stats.splitStatistics[address.asIntList.toString()]!!.copy(
117+
stats.splitStatistics[address.name()] =
118+
stats.splitStatistics[address.name()]!!.copy(
119119
finalResult = matchingUnsat.finalResult,
120120
solvers = matchingUnsat.solvers
121121
)
@@ -126,27 +126,29 @@ class AutoConfigManager(val ruleName: String) : Closeable {
126126
* it gets computed and stored in [stats].
127127
*/
128128
private fun getDigest(address: SplitAddress, prog: CoreTACProgram): String = synchronized(splitStatAndSolverConfigLock) {
129-
require(address.asIntList.toString() in stats.splitStatistics) {
130-
"Trying to store a digest for a split ${address.asIntList} that is not registered yet"
129+
val addressName = address.name()
130+
require(addressName in stats.splitStatistics) {
131+
"Trying to store a digest for a split ${addressName} that is not registered yet"
131132
}
132-
if (stats.splitStatistics[address.asIntList.toString()]!!.tacStats.digest == null) {
133+
if (stats.splitStatistics[addressName]!!.tacStats.digest == null) {
133134
val digest = BasicTACStatistics.computeDigest(prog)
134-
stats.splitStatistics[address.asIntList.toString()] =
135-
stats.splitStatistics[address.asIntList.toString()]!!.let {
135+
stats.splitStatistics[addressName] =
136+
stats.splitStatistics[addressName]!!.let {
136137
it.copy(tacStats = it.tacStats.copy(digest = digest))
137138
}
138139
}
139-
return stats.splitStatistics[address.asIntList.toString()]!!.tacStats.digest!!
140+
return stats.splitStatistics[addressName]!!.tacStats.digest!!
140141
}
141142

142143
/**
143144
* Register the given [subProblem] in [stats] (storing just address, split name, and basic TAC statistics).
144145
*/
145146
fun registerSplit(address: SplitAddress, name: String, subProblemTAC: CoreTACProgram) = synchronized(splitStatAndSolverConfigLock) {
146-
if (address.asIntList.toString() !in stats.splitStatistics.keys) {
147-
info("registering split ${address.asIntList}")
148-
stats.splitStatistics[address.asIntList.toString()] = BasicSplitStatistics(
149-
address = address.asIntList.toString(),
147+
val addressName = address.name()
148+
if (addressName !in stats.splitStatistics.keys) {
149+
info("registering split ${address.name()}")
150+
stats.splitStatistics[addressName] = BasicSplitStatistics(
151+
address = addressName,
150152
splitName = name,
151153
tacStats = BasicTACStatistics.fromCoreTACProgram(subProblemTAC, Config.AutoconfigUseDigests.get()),
152154
)
@@ -164,7 +166,7 @@ class AutoConfigManager(val ruleName: String) : Closeable {
164166
timeoutForSubProblem: Duration,
165167
solvingDuration: Duration,
166168
) = synchronized(splitStatAndSolverConfigLock) {
167-
val address = splitAddress.asIntList.toString()
169+
val address = splitAddress.name()
168170
info("registering ${verifierResult.finalResult} result for split $address.")
169171
if(address !in stats.splitStatistics) {
170172
warn("Trying to register results for an unregistered split $address.")
@@ -262,7 +264,7 @@ class AutoConfigManager(val ruleName: String) : Closeable {
262264
val loadedMatches = loadedStats?.splitStatistics?.values?.filter { tacStats.match(it.tacStats) } ?: listOf()
263265
//Splits we have already solved in this CVT run and which are matching the current split.
264266
val newMatches: List<BasicSplitStatistics> = if (learnFromCurrent) {
265-
val splitAddressStr = splitAddress.asIntList.toString()
267+
val splitAddressStr = splitAddress.name()
266268
synchronized(splitStatAndSolverConfigLock) {
267269
stats.splitStatistics.values.filter { tacStats.match(it.tacStats) && it.address != splitAddressStr }
268270
}

src/main/kotlin/verifier/splits/SplitAddress.kt

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,15 @@ sealed interface SplitAddress : HasKSerializable {
3737
val parent: SplitAddress?
3838
val isRoot get() = parent == null
3939
val depth: Int get() = (parent?.depth ?: -1) + 1
40-
val asIntList : List<Int>
41-
fun name() = asIntList.joinToString("_") { it.toString() }
40+
val asList : List<String>
41+
fun name() = asList.joinToString("_")
4242

4343
fun fullInfo(): String
4444

4545
@KSerializable
4646
data object Root : SplitAddress {
4747
override val parent = null
48-
override val asIntList = emptyList<Int>()
48+
override val asList = emptyList<String>()
4949
override fun fullInfo() = "ROOT"
5050
override fun toString() = name()
5151
}
@@ -59,18 +59,25 @@ sealed interface SplitAddress : HasKSerializable {
5959
@KSerializable
6060
data class MustPass(override val parent: SplitAddress, override val pivot: NBId) : Block {
6161
override fun sibling() = DontPass(parent, pivot)
62-
override val asIntList = parent.asIntList + 0
62+
override val asList = parent.asList + "0"
6363
override fun toString() = name()
6464
}
6565

6666
@KSerializable
6767
data class DontPass(override val parent: SplitAddress, override val pivot: NBId) : Block {
6868
override fun sibling() = MustPass(parent, pivot)
69-
override val asIntList = parent.asIntList + 1
69+
override val asList = parent.asList + "1"
7070
override fun toString() = name()
7171
}
7272
}
7373

74+
@KSerializable
75+
data class Rerun(override val parent: SplitAddress) : SplitAddress {
76+
override val asList = parent.asList + "R"
77+
override fun toString() = name()
78+
override fun fullInfo() = "${name()}[rerun]"
79+
}
80+
7481
@KSerializable
7582
data class Assumption(
7683
override val parent: SplitAddress,
@@ -101,7 +108,7 @@ sealed interface SplitAddress : HasKSerializable {
101108
}
102109

103110
// the +2 is for not interfering with the 0 and 1 splits of `Block`.
104-
override val asIntList = parent.asIntList + (numericalCode + 2)
111+
override val asList = parent.asList + "A${numericalCode}"
105112
override fun fullInfo() = "${name()}[$assumption]"
106113
override fun toString() = name()
107114
}
@@ -118,7 +125,7 @@ sealed interface SplitAddress : HasKSerializable {
118125
// will be 0000, and second 1000, which are very different splits. That's good for searching (probably)
119126
fun compareRevLexical(a: SplitAddress, b: SplitAddress) = run {
120127
require(a.depth == b.depth) // leaving this decision out of here.
121-
(a.asIntList zip b.asIntList)
128+
(a.asList zip b.asList)
122129
.map { (i1, i2) -> i1.compareTo(i2) }
123130
.lastOrNull { it != 0 } ?: 0
124131
}

src/main/kotlin/verifier/splits/SplitTree.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ class SplitTree(val origCode: CoreTACProgram, private val splittingDepth: Int,
6767
/**
6868
* [Node]s are considered equal if their [address] (given in [lazySub]) is equal.
6969
*/
70-
inner class Node(val lazySub: LazySubProgram, val sideScore: SideScore<*>) {
70+
open inner class Node(val lazySub: LazySubProgram, val sideScore: SideScore<*>) {
7171
val address get() = lazySub.address
7272
val name get() = lazySub.name
7373

@@ -152,6 +152,12 @@ class SplitTree(val origCode: CoreTACProgram, private val splittingDepth: Int,
152152
sequenceOf(this) + children.get().orEmpty().asSequence().flatMap { it.nodeSequence }
153153
}
154154

155+
/**
156+
* Extends [Node] with some arbitrary additional information [info].
157+
*/
158+
inner class NodeWithInfo<T>(lazySub: LazySubProgram, sideScore: SideScore<*>, val info: T)
159+
: Node(lazySub, sideScore)
160+
155161
val nodeSequence get() = rootNode.nodeSequence
156162

157163
companion object {

src/test/kotlin/verifier/SplitterHeuristicTest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ class SplitterHeuristicTest : WithTACSource {
6060
assertTrue(a.split.sideScore >= b.split.sideScore)
6161
} else {
6262
// prefer smaller address (reverse lexicographically)
63-
val aa = a.split.address.asIntList.dropLast(1)
64-
val ba = b.split.address.asIntList.dropLast(1)
63+
val aa = a.split.address.asList.dropLast(1)
64+
val ba = b.split.address.asList.dropLast(1)
6565
val res = aa.zip(ba).map { (i1, i2) -> i1.compareTo(i2) }.lastOrNull { it != 0 } ?: 0
6666

6767
assertTrue(res <= 0)

0 commit comments

Comments
 (0)