Skip to content

Commit e69dd81

Browse files
committed
cvc5 forking solver, tests
1 parent 86fc1cf commit e69dd81

11 files changed

Lines changed: 962 additions & 292 deletions

File tree

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package io.ksmt.solver
2+
3+
/**
4+
* A solver capable of creating forks (copies) of itself, preserving assertions and assertion scopes
5+
*
6+
* @see KForkingSolverManager
7+
*/
8+
interface KForkingSolver<Config : KSolverConfiguration> : KSolver<Config> {
9+
10+
/**
11+
* Creates forked solver
12+
*/
13+
fun fork(): KForkingSolver<Config>
14+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package io.ksmt.solver
2+
3+
/**
4+
* Responsible for creation of [KForkingSolver] and managing its lifetime
5+
*
6+
* @see KForkingSolver
7+
*/
8+
interface KForkingSolverManager <Config : KSolverConfiguration> : AutoCloseable {
9+
10+
fun mkForkingSolver(): KForkingSolver<Config>
11+
12+
/**
13+
* Closes the manager and all opened solvers ([KForkingSolver]) managed by this
14+
*/
15+
override fun close()
16+
}

ksmt-cvc5/src/main/kotlin/io/ksmt/solver/cvc5/KCvc5Context.kt

Lines changed: 122 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,28 @@ import io.ksmt.sort.KSortVisitor
3232
import io.ksmt.sort.KUninterpretedSort
3333
import java.util.TreeMap
3434

35-
class KCvc5Context(
35+
class KCvc5Context private constructor(
3636
private val solver: Solver,
37-
private val ctx: KContext
37+
private val ctx: KContext,
38+
parent: KCvc5Context?,
39+
isForking: Boolean
3840
) : AutoCloseable {
41+
constructor(solver: Solver, ctx: KContext, isForking: Boolean = false) : this(solver, ctx, null, isForking)
42+
3943
private var isClosed = false
44+
private val isChild = parent != null
4045

4146
private val uninterpretedSortCollector = KUninterpretedSortCollector(this)
4247
private var exprCurrentLevelCacheRestorer = KCurrentScopeExprCacheRestorer(uninterpretedSortCollector, ctx)
4348

49+
private val uninterpretedSorts: ScopedFrame<HashSet<KUninterpretedSort>>
50+
private val declarations: ScopedFrame<HashSet<KDecl<*>>>
51+
52+
4453
/**
4554
* We use double-scoped expression internalization cache:
46-
* * current (before pop operation) - [currentScopeExpressions]
47-
* * global (after pop operation) - [expressions]
55+
* * current accumulated (before pop operation) - [currentAccumulatedScopeExpressions]
56+
* * global (current + all previously met)- [expressions]
4857
*
4958
* Due to incremental collect of declarations and uninterpreted sorts for the model,
5059
* we collect them during internalizing.
@@ -57,98 +66,137 @@ class KCvc5Context(
5766
*
5867
* **solution**: Recollect sorts / decls for each expression
5968
* that is in global cache, but whose sorts / decls have been erased after pop()
60-
* (and put this expr to the current scope cache)
69+
* (and put this expr to the cache of current accumulated scope)
6170
*/
62-
private val currentScopeExpressions = HashMap<KExpr<*>, Term>()
63-
private val expressions = HashMap<KExpr<*>, Term>()
64-
// we can't use HashMap with Term and Sort (hashcode is not implemented)
65-
private val cvc5Expressions = TreeMap<Term, KExpr<*>>()
66-
private val sorts = HashMap<KSort, Sort>()
67-
private val cvc5Sorts = TreeMap<Sort, KSort>()
68-
private val decls = HashMap<KDecl<*>, Term>()
69-
private val cvc5Decls = TreeMap<Term, KDecl<*>>()
71+
private val currentAccumulatedScopeExpressions: HashMap<KExpr<*>, Term>
72+
private val expressions: HashMap<KExpr<*>, Term>
7073

71-
private var currentLevelUninterpretedSorts = hashSetOf<KUninterpretedSort>()
72-
private val uninterpretedSorts = mutableListOf(currentLevelUninterpretedSorts)
73-
74-
private var currentLevelDeclarations = hashSetOf<KDecl<*>>()
75-
private val declarations = mutableListOf(currentLevelDeclarations)
74+
// we can't use HashMap with Term and Sort (hashcode is not implemented)
75+
private val cvc5Expressions: TreeMap<Term, KExpr<*>>
76+
private val sorts: HashMap<KSort, Sort>
77+
private val cvc5Sorts: TreeMap<Sort, KSort>
78+
private val decls: HashMap<KDecl<*>, Term>
79+
private val cvc5Decls: TreeMap<Term, KDecl<*>>
7680

77-
fun addUninterpretedSort(sort: KUninterpretedSort) { currentLevelUninterpretedSorts += sort }
81+
private val uninterpretedSortValueDescriptors: ArrayList<UninterpretedSortValueDescriptor>
82+
private val uninterpretedSortValueInterpreter: HashMap<KUninterpretedSort, Term>
7883

7984
/**
80-
* uninterpreted sorts of active push-levels
85+
* Uninterpreted sort values and universe are shared for whole forking hierarchy (from parent to children)
86+
* due to shared expressions cache,
87+
* that's why once [registerUninterpretedSortValue] and [saveUninterpretedSortValue] are called,
88+
* each solver in hierarchy should assert newly internalized uninterpreted sort values via [assertPendingAxioms]
89+
*
90+
* @see KCvc5Model.uninterpretedSortUniverse
8191
*/
82-
fun uninterpretedSorts(): List<Set<KUninterpretedSort>> = uninterpretedSorts
92+
private val uninterpretedSortValues: HashMap<KUninterpretedSort, MutableList<Pair<Term, KUninterpretedSortValue>>>
93+
94+
init {
95+
if (isForking) {
96+
uninterpretedSorts = (parent?.uninterpretedSorts as? ScopedLinkedFrame)?.fork()
97+
?: ScopedLinkedFrame(::HashSet, ::HashSet)
98+
declarations = (parent?.declarations as? ScopedLinkedFrame)?.fork()
99+
?: ScopedLinkedFrame(::HashSet, ::HashSet)
100+
} else {
101+
uninterpretedSorts = ScopedArrayFrame(::HashSet)
102+
declarations = ScopedArrayFrame(::HashSet)
103+
}
104+
105+
if (parent != null) {
106+
currentAccumulatedScopeExpressions = parent.currentAccumulatedScopeExpressions.toMap(HashMap())
107+
expressions = parent.expressions
108+
cvc5Expressions = parent.cvc5Expressions
109+
sorts = parent.sorts
110+
cvc5Sorts = parent.cvc5Sorts
111+
decls = parent.decls
112+
cvc5Decls = parent.cvc5Decls
113+
uninterpretedSortValueDescriptors = parent.uninterpretedSortValueDescriptors
114+
uninterpretedSortValueInterpreter = parent.uninterpretedSortValueInterpreter
115+
uninterpretedSortValues = parent.uninterpretedSortValues
116+
} else {
117+
currentAccumulatedScopeExpressions = HashMap()
118+
expressions = HashMap()
119+
cvc5Expressions = TreeMap()
120+
sorts = HashMap()
121+
cvc5Sorts = TreeMap()
122+
decls = HashMap()
123+
cvc5Decls = TreeMap()
124+
uninterpretedSortValueDescriptors = arrayListOf()
125+
uninterpretedSortValueInterpreter = hashMapOf()
126+
uninterpretedSortValues = hashMapOf()
127+
}
128+
}
129+
130+
fun addUninterpretedSort(sort: KUninterpretedSort) {
131+
uninterpretedSorts.currentFrame += sort
132+
}
133+
134+
fun uninterpretedSorts(): Set<KUninterpretedSort> = uninterpretedSorts.flatten { this += it }
83135

84136
fun addDeclaration(decl: KDecl<*>) {
85-
currentLevelDeclarations += decl
137+
declarations.currentFrame += decl
86138
uninterpretedSortCollector.collect(decl)
87139
}
88140

89-
/**
90-
* declarations of active push-levels
91-
*/
92-
fun declarations(): List<Set<KDecl<*>>> = declarations
141+
fun declarations(): Set<KDecl<*>> = declarations.flatten { this += it }
93142

94143
val nativeSolver: Solver
95144
get() = solver
96145

97146
val isActive: Boolean
98147
get() = !isClosed
99148

149+
fun fork(solver: Solver): KCvc5Context = KCvc5Context(solver, ctx, this, true).also { forkCtx ->
150+
repeat(assertedConstraintLevels.size) {
151+
forkCtx.pushAssertionLevel()
152+
}
153+
}
154+
100155
fun push() {
101-
currentLevelDeclarations = hashSetOf()
102-
declarations.add(currentLevelDeclarations)
103-
currentLevelUninterpretedSorts = hashSetOf()
104-
uninterpretedSorts.add(currentLevelUninterpretedSorts)
156+
declarations.push()
157+
uninterpretedSorts.push()
105158

106159
pushAssertionLevel()
107160
}
108161

109162
fun pop(n: UInt) {
110-
repeat(n.toInt()) {
111-
declarations.removeLast()
112-
uninterpretedSorts.removeLast()
163+
declarations.pop(n)
164+
uninterpretedSorts.pop(n)
113165

114-
popAssertionLevel()
115-
}
166+
repeat(n.toInt()) { popAssertionLevel() }
116167

117-
currentLevelDeclarations = declarations.last()
118-
currentLevelUninterpretedSorts = uninterpretedSorts.last()
119-
120-
expressions += currentScopeExpressions
121-
currentScopeExpressions.clear()
168+
currentAccumulatedScopeExpressions.clear()
122169
// recreate cache restorer to avoid KNonRecursiveTransformer cache
123170
exprCurrentLevelCacheRestorer = KCurrentScopeExprCacheRestorer(uninterpretedSortCollector, ctx)
124171
}
125172

126-
// expr
127-
fun findInternalizedExpr(expr: KExpr<*>): Term? = currentScopeExpressions[expr]
173+
fun findInternalizedExpr(expr: KExpr<*>): Term? = currentAccumulatedScopeExpressions[expr]
128174
?: expressions[expr]?.also {
129175
/*
130-
* expr is not in current scope cache, but in global cache.
176+
* expr is not in cache of current accumulated scope, but in global cache.
131177
* Recollect declarations and uninterpreted sorts
132-
* and add entire expression tree to the current scope cache from the global
133-
* to avoid re-internalizing with native calls
178+
* and add entire expression tree to the current accumulated scope cache from the global
179+
* to avoid re-internalization
134180
*/
135181
exprCurrentLevelCacheRestorer.apply(expr)
136182
}
137183

138184
fun findConvertedExpr(expr: Term): KExpr<*>? = cvc5Expressions[expr]
139185

140186
fun saveInternalizedExpr(expr: KExpr<*>, internalized: Term): Term =
141-
internalizeAst(currentScopeExpressions, cvc5Expressions, expr) { internalized }
187+
internalizeAst(currentAccumulatedScopeExpressions, cvc5Expressions, expr) { internalized }
188+
.also { expressions[expr] = internalized }
142189

143190
/**
144191
* save expr, which is in global cache, to the current scope cache
145192
*/
146-
fun savePreviouslyInternalizedExpr(expr: KExpr<*>): Term = saveInternalizedExpr(expr, expressions[expr]!!)
193+
fun saveInternalizedExprToCurrentAccumulatedScope(expr: KExpr<*>): Term =
194+
currentAccumulatedScopeExpressions.getOrPut(expr) { expressions[expr]!! }
147195

148196
fun saveConvertedExpr(expr: Term, converted: KExpr<*>): KExpr<*> =
149-
convertAst(currentScopeExpressions, cvc5Expressions, expr) { converted }
197+
convertAst(currentAccumulatedScopeExpressions, cvc5Expressions, expr) { converted }
198+
.also { expressions[converted] = expr }
150199

151-
// sort
152200
fun findInternalizedSort(sort: KSort): Sort? = sorts[sort]
153201

154202
fun findConvertedSort(sort: Sort): KSort? = cvc5Sorts[sort]
@@ -165,7 +213,6 @@ class KCvc5Context(
165213
inline fun convertSort(sort: Sort, converter: () -> KSort): KSort =
166214
findOrSave(sort, converter, ::findConvertedSort, ::saveConvertedSort)
167215

168-
// decl
169216
fun findInternalizedDecl(decl: KDecl<*>): Term? = decls[decl]
170217

171218
fun findConvertedDecl(decl: Term): KDecl<*>? = cvc5Decls[decl]
@@ -211,17 +258,20 @@ class KCvc5Context(
211258
val nativeValueTerm: Term
212259
)
213260

261+
/**
262+
* Uninterpreted sort value axioms will not be lost for [KCvc5ForkingSolver] on [fork].
263+
*
264+
* On child initialization, "[currentValueConstraintsLevel] = 0"
265+
* will be pushed to [assertedConstraintLevels] for each push-level ([currentValueConstraintsLevel] times).
266+
* At the first call of [assertPendingAxioms] each descriptor from [uninterpretedSortValueDescriptors]
267+
* will be asserted to the child [KCvc5ForkingSolver]
268+
*/
214269
private var currentValueConstraintsLevel = 0
215270
private val assertedConstraintLevels = arrayListOf<Int>()
216-
private val uninterpretedSortValueDescriptors = arrayListOf<UninterpretedSortValueDescriptor>()
217-
private val uninterpretedSortValueInterpreter = hashMapOf<KUninterpretedSort, Term>()
218-
219-
private val uninterpretedSortValues =
220-
hashMapOf<KUninterpretedSort, MutableList<Pair<Term, KUninterpretedSortValue>>>()
221271

222272
fun saveUninterpretedSortValue(nativeValue: Term, value: KUninterpretedSortValue): Term {
223273
val sortValues = uninterpretedSortValues.getOrPut(value.sort) { arrayListOf() }
224-
sortValues.add(nativeValue to value)
274+
sortValues += nativeValue to value
225275
return nativeValue
226276
}
227277

@@ -266,7 +316,7 @@ class KCvc5Context(
266316
uninterpretedSortValues[sort] ?: emptyList()
267317

268318
private fun pushAssertionLevel() {
269-
assertedConstraintLevels.add(currentValueConstraintsLevel)
319+
assertedConstraintLevels += currentValueConstraintsLevel
270320
}
271321

272322
private fun popAssertionLevel() {
@@ -354,20 +404,18 @@ class KCvc5Context(
354404
if (isClosed) return
355405
isClosed = true
356406

357-
currentScopeExpressions.clear()
358-
expressions.clear()
359-
cvc5Expressions.clear()
407+
currentAccumulatedScopeExpressions.clear()
360408

361-
uninterpretedSorts.clear()
362-
currentLevelUninterpretedSorts.clear()
409+
if (isChild) {
410+
expressions.clear()
411+
cvc5Expressions.clear()
363412

364-
declarations.clear()
365-
currentLevelDeclarations.clear()
413+
sorts.clear()
414+
cvc5Sorts.clear()
366415

367-
sorts.clear()
368-
cvc5Sorts.clear()
369-
decls.clear()
370-
cvc5Decls.clear()
416+
decls.clear()
417+
cvc5Decls.clear()
418+
}
371419
}
372420

373421
class KUninterpretedSortCollector(private val cvc5Ctx: KCvc5Context) : KSortVisitor<Unit> {
@@ -416,7 +464,8 @@ class KCvc5Context(
416464
ctx: KContext
417465
) : KNonRecursiveTransformer(ctx) {
418466

419-
override fun <T : KSort> exprTransformationRequired(expr: KExpr<T>): Boolean = expr !in currentScopeExpressions
467+
override fun <T : KSort> exprTransformationRequired(expr: KExpr<T>): Boolean =
468+
expr !in currentAccumulatedScopeExpressions
420469

421470
override fun <T : KSort> transform(expr: KFunctionApp<T>): KExpr<T> = cacheIfNeed(expr) {
422471
this@KCvc5Context.addDeclaration(expr.decl)
@@ -426,7 +475,7 @@ class KCvc5Context(
426475
override fun <T : KSort> transform(expr: KConst<T>): KExpr<T> = cacheIfNeed(expr) {
427476
this@KCvc5Context.addDeclaration(expr.decl)
428477
uninterpretedSortCollector.collect(expr.decl)
429-
this@KCvc5Context.savePreviouslyInternalizedExpr(expr)
478+
saveInternalizedExprToCurrentAccumulatedScope(expr)
430479
}
431480

432481
override fun <A : KArraySortBase<R>, R : KSort> transform(expr: KFunctionAsArray<A, R>): KExpr<A> =
@@ -446,7 +495,7 @@ class KCvc5Context(
446495

447496
override fun transform(expr: KUniversalQuantifier): KExpr<KBoolSort> = cacheIfNeed(expr) {
448497
transformQuantifier(expr.bounds.toSet(), expr.body)
449-
this@KCvc5Context.savePreviouslyInternalizedExpr(expr)
498+
saveInternalizedExprToCurrentAccumulatedScope(expr)
450499
}
451500

452501
private fun transformQuantifier(bounds: Set<KDecl<*>>, body: KExpr<*>) {
@@ -455,11 +504,11 @@ class KCvc5Context(
455504
}
456505

457506
private fun <S : KSort, E : KExpr<S>> cacheIfNeed(expr: E, transform: E.() -> Unit): KExpr<S> {
458-
if (expr in currentScopeExpressions)
507+
if (expr in currentAccumulatedScopeExpressions)
459508
return expr
460509

461510
expr.transform()
462-
this@KCvc5Context.savePreviouslyInternalizedExpr(expr)
511+
saveInternalizedExprToCurrentAccumulatedScope(expr)
463512
return expr
464513
}
465514
}

0 commit comments

Comments
 (0)