Skip to content

Commit

Permalink
bitwuzla - forking solver, scoped uninterpreted values tracking fix, …
Browse files Browse the repository at this point in the history
…yices - forking solver init exceptions wrapper, forking solver close fix,

all solvers - ScopedLinkedFrame.pop fix
  • Loading branch information
dee-tree committed Aug 14, 2023
1 parent 92a96da commit 837c567
Show file tree
Hide file tree
Showing 12 changed files with 482 additions and 211 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
package io.ksmt.solver.bitwuzla

import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap
import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap
import io.ksmt.KContext
import io.ksmt.decl.KDecl
import io.ksmt.decl.KFuncDecl
Expand All @@ -15,13 +12,10 @@ import io.ksmt.expr.KExistentialQuantifier
import io.ksmt.expr.KExpr
import io.ksmt.expr.KFunctionApp
import io.ksmt.expr.KFunctionAsArray
import io.ksmt.expr.KUninterpretedSortValue
import io.ksmt.expr.KUniversalQuantifier
import io.ksmt.expr.transformer.KNonRecursiveTransformer
import io.ksmt.solver.KSolverException
import org.ksmt.solver.bitwuzla.bindings.BitwuzlaNativeException
import org.ksmt.solver.bitwuzla.bindings.BitwuzlaSort
import org.ksmt.solver.bitwuzla.bindings.BitwuzlaTerm
import org.ksmt.solver.bitwuzla.bindings.Native
import io.ksmt.solver.util.KExprLongInternalizerBase.Companion.NOT_INTERNALIZED
import io.ksmt.sort.KArray2Sort
import io.ksmt.sort.KArray3Sort
Expand All @@ -37,6 +31,13 @@ import io.ksmt.sort.KRealSort
import io.ksmt.sort.KSort
import io.ksmt.sort.KSortVisitor
import io.ksmt.sort.KUninterpretedSort
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap
import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap
import org.ksmt.solver.bitwuzla.bindings.BitwuzlaNativeException
import org.ksmt.solver.bitwuzla.bindings.BitwuzlaSort
import org.ksmt.solver.bitwuzla.bindings.BitwuzlaTerm
import org.ksmt.solver.bitwuzla.bindings.Native

open class KBitwuzlaContext(val ctx: KContext) : AutoCloseable {
private var isClosed = false
Expand Down Expand Up @@ -433,6 +434,11 @@ open class KBitwuzlaContext(val ctx: KContext) : AutoCloseable {
return super.transform(expr)
}

override fun transform(expr: KUninterpretedSortValue): KExpr<KUninterpretedSort> {
registerDeclIfNotIgnored(expr.decl)
return super.transform(expr)
}

private val quantifiedVarsScopeOwner = arrayListOf<KExpr<*>>()
private val quantifiedVarsScope = arrayListOf<Set<KDecl<*>>?>()

Expand Down Expand Up @@ -474,7 +480,7 @@ open class KBitwuzlaContext(val ctx: KContext) : AutoCloseable {
override fun transform(expr: KExistentialQuantifier): KExpr<KBoolSort> =
expr.transformQuantifier(expr.bounds, expr.body)

override fun transform(expr: KUniversalQuantifier): KExpr<KBoolSort> =
override fun transform(expr: KUniversalQuantifier): KExpr<KBoolSort> =
expr.transformQuantifier(expr.bounds, expr.body)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package io.ksmt.solver.bitwuzla

import io.ksmt.KContext
import io.ksmt.expr.KExpr
import io.ksmt.solver.KForkingSolver
import io.ksmt.solver.KSolverStatus
import io.ksmt.sort.KBoolSort
import kotlin.time.Duration

class KBitwuzlaForkingSolver(
private val ctx: KContext,
private val manager: KBitwuzlaForkingSolverManager,
parent: KBitwuzlaForkingSolver?
) : KBitwuzlaSolverBase(ctx),
KForkingSolver<KBitwuzlaSolverConfiguration> {

private val assertions = ScopedLinkedFrame<MutableList<KExpr<KBoolSort>>>(::ArrayList, ::ArrayList)
private val trackToExprFrames =
ScopedLinkedFrame<MutableList<Pair<KExpr<KBoolSort>, KExpr<KBoolSort>>>>(::ArrayList, ::ArrayList)

private val config: KBitwuzlaForkingSolverConfigurationImpl

init {
if (parent != null) {
config = parent.config.fork(bitwuzlaCtx.bitwuzla)
assertions.fork(parent.assertions)
trackToExprFrames.fork(parent.trackToExprFrames)
} else {
config = KBitwuzlaForkingSolverConfigurationImpl(bitwuzlaCtx.bitwuzla)
}
}

override fun configure(configurator: KBitwuzlaSolverConfiguration.() -> Unit) {
config.configurator()
}

override fun fork(): KForkingSolver<KBitwuzlaSolverConfiguration> = manager.mkForkingSolver(this)

private var assertionsInitiated = parent == null

private fun ensureAssertionsInitiated() {
if (assertionsInitiated) return

assertions.stacked().zip(trackToExprFrames.stacked())
.asReversed()
.forEachIndexed { scope, (assertionsFrame, trackedExprsFrame) ->
if (scope > 0) super.push()

assertionsFrame.forEach { assertion ->
internalizeAndAssertWithAxioms(assertion)
}

trackedExprsFrame.forEach { (track, trackedExpr) ->
super.registerTrackForExpr(trackedExpr, track)
}
}
assertionsInitiated = true
}

override fun assert(expr: KExpr<KBoolSort>) = bitwuzlaCtx.bitwuzlaTry {
ctx.ensureContextMatch(expr)
ensureAssertionsInitiated()

internalizeAndAssertWithAxioms(expr)
assertions.currentFrame += expr
}

override fun assertAndTrack(expr: KExpr<KBoolSort>) {
bitwuzlaCtx.bitwuzlaTry { ensureAssertionsInitiated() }
super.assertAndTrack(expr)
}

override fun registerTrackForExpr(expr: KExpr<KBoolSort>, track: KExpr<KBoolSort>) {
super.registerTrackForExpr(expr, track)
trackToExprFrames.currentFrame += track to expr
}

override fun push() {
bitwuzlaCtx.bitwuzlaTry { ensureAssertionsInitiated() }
super.push()
assertions.push()
trackToExprFrames.push()
}

override fun pop(n: UInt) {
bitwuzlaCtx.bitwuzlaTry { ensureAssertionsInitiated() }
super.pop(n)
assertions.pop(n)
trackToExprFrames.pop(n)
}

override fun check(timeout: Duration): KSolverStatus {
bitwuzlaCtx.bitwuzlaTry { ensureAssertionsInitiated() }
return super.check(timeout)
}

override fun checkWithAssumptions(assumptions: List<KExpr<KBoolSort>>, timeout: Duration): KSolverStatus {
bitwuzlaCtx.bitwuzlaTry { ensureAssertionsInitiated() }
return super.checkWithAssumptions(assumptions, timeout)
}

override fun close() {
super.close()
manager.close(this)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.ksmt.solver.bitwuzla

import io.ksmt.KContext
import io.ksmt.solver.KForkingSolver
import io.ksmt.solver.KForkingSolverManager
import java.util.concurrent.ConcurrentHashMap

class KBitwuzlaForkingSolverManager(private val ctx: KContext) : KForkingSolverManager<KBitwuzlaSolverConfiguration> {
private val solvers = ConcurrentHashMap.newKeySet<KBitwuzlaForkingSolver>()

override fun mkForkingSolver(): KForkingSolver<KBitwuzlaSolverConfiguration> {
return KBitwuzlaForkingSolver(ctx, this, null).also {
solvers += it
}
}

internal fun mkForkingSolver(parent: KBitwuzlaForkingSolver) = KBitwuzlaForkingSolver(ctx, this, parent).also {
solvers += it
}

internal fun close(solver: KBitwuzlaForkingSolver) {
solvers -= solver
}

override fun close() {
solvers.forEach(KBitwuzlaForkingSolver::close)
}
}
Original file line number Diff line number Diff line change
@@ -1,199 +1,10 @@
package io.ksmt.solver.bitwuzla

import it.unimi.dsi.fastutil.longs.LongOpenHashSet
import io.ksmt.KContext
import io.ksmt.expr.KExpr
import io.ksmt.solver.KModel
import io.ksmt.solver.KSolver
import io.ksmt.solver.KSolverStatus
import org.ksmt.solver.bitwuzla.bindings.BitwuzlaNativeException
import org.ksmt.solver.bitwuzla.bindings.BitwuzlaOption
import org.ksmt.solver.bitwuzla.bindings.BitwuzlaResult
import org.ksmt.solver.bitwuzla.bindings.BitwuzlaTerm
import org.ksmt.solver.bitwuzla.bindings.BitwuzlaTermArray
import org.ksmt.solver.bitwuzla.bindings.Native
import io.ksmt.sort.KBoolSort
import kotlin.time.Duration

open class KBitwuzlaSolver(private val ctx: KContext) : KSolver<KBitwuzlaSolverConfiguration> {
open val bitwuzlaCtx = KBitwuzlaContext(ctx)
open val exprInternalizer: KBitwuzlaExprInternalizer by lazy {
KBitwuzlaExprInternalizer(bitwuzlaCtx)
}
open val exprConverter: KBitwuzlaExprConverter by lazy {
KBitwuzlaExprConverter(ctx, bitwuzlaCtx)
}

private var lastCheckStatus = KSolverStatus.UNKNOWN
private var lastReasonOfUnknown: String? = null
private var lastAssumptions: TrackedAssumptions? = null
private var lastModel: KBitwuzlaModel? = null

init {
Native.bitwuzlaSetOption(bitwuzlaCtx.bitwuzla, BitwuzlaOption.BITWUZLA_OPT_INCREMENTAL, value = 1)
Native.bitwuzlaSetOption(bitwuzlaCtx.bitwuzla, BitwuzlaOption.BITWUZLA_OPT_PRODUCE_MODELS, value = 1)
}

private var trackedAssertions = mutableListOf<Pair<KExpr<KBoolSort>, BitwuzlaTerm>>()
private val trackVarsAssertionFrames = arrayListOf(trackedAssertions)
open class KBitwuzlaSolver(ctx: KContext) : KBitwuzlaSolverBase(ctx) {

override fun configure(configurator: KBitwuzlaSolverConfiguration.() -> Unit) {
KBitwuzlaSolverConfigurationImpl(bitwuzlaCtx.bitwuzla).configurator()
}

override fun assert(expr: KExpr<KBoolSort>) = bitwuzlaCtx.bitwuzlaTry {
ctx.ensureContextMatch(expr)

val assertionWithAxioms = with(exprInternalizer) { expr.internalizeAssertion() }

assertionWithAxioms.axioms.forEach {
Native.bitwuzlaAssert(bitwuzlaCtx.bitwuzla, it)
}
Native.bitwuzlaAssert(bitwuzlaCtx.bitwuzla, assertionWithAxioms.assertion)
}

override fun assertAndTrack(expr: KExpr<KBoolSort>) = bitwuzlaCtx.bitwuzlaTry {
ctx.ensureContextMatch(expr)

val trackVarExpr = ctx.mkFreshConst("track", ctx.boolSort)
val trackedExpr = with(ctx) { !trackVarExpr or expr }

assert(trackedExpr)

val trackVarTerm = with(exprInternalizer) { trackVarExpr.internalize() }
trackedAssertions += expr to trackVarTerm
}

override fun push(): Unit = bitwuzlaCtx.bitwuzlaTry {
Native.bitwuzlaPush(bitwuzlaCtx.bitwuzla, nlevels = 1)

trackedAssertions = trackedAssertions.toMutableList()
trackVarsAssertionFrames.add(trackedAssertions)

bitwuzlaCtx.createNestedDeclarationScope()
}

override fun pop(n: UInt): Unit = bitwuzlaCtx.bitwuzlaTry {
val currentLevel = trackVarsAssertionFrames.lastIndex.toUInt()
require(n <= currentLevel) {
"Cannot pop $n scope levels because current scope level is $currentLevel"
}

if (n == 0u) return

repeat(n.toInt()) {
trackVarsAssertionFrames.removeLast()
bitwuzlaCtx.popDeclarationScope()
}

trackedAssertions = trackVarsAssertionFrames.last()

Native.bitwuzlaPop(bitwuzlaCtx.bitwuzla, n.toInt())
}

override fun check(timeout: Duration): KSolverStatus =
checkWithAssumptions(emptyList(), timeout)

override fun checkWithAssumptions(assumptions: List<KExpr<KBoolSort>>, timeout: Duration): KSolverStatus =
bitwuzlaTryCheck {
ctx.ensureContextMatch(assumptions)

val currentAssumptions = TrackedAssumptions().also { lastAssumptions = it }

trackedAssertions.forEach {
currentAssumptions.assumeTrackedAssertion(it)
}

with(exprInternalizer) {
assumptions.forEach {
currentAssumptions.assumeAssumption(it, it.internalize())
}
}

checkWithTimeout(timeout).processCheckResult()
}

private fun checkWithTimeout(timeout: Duration): BitwuzlaResult = if (timeout.isInfinite()) {
Native.bitwuzlaCheckSatResult(bitwuzlaCtx.bitwuzla)
} else {
Native.bitwuzlaCheckSatTimeoutResult(bitwuzlaCtx.bitwuzla, timeout.inWholeMilliseconds)
}

override fun model(): KModel = bitwuzlaCtx.bitwuzlaTry {
require(lastCheckStatus == KSolverStatus.SAT) { "Model are only available after SAT checks" }
val model = lastModel ?: KBitwuzlaModel(
ctx, bitwuzlaCtx, exprConverter,
bitwuzlaCtx.declarations(),
bitwuzlaCtx.uninterpretedSortsWithRelevantDecls()
)
lastModel = model
model
}

override fun unsatCore(): List<KExpr<KBoolSort>> = bitwuzlaCtx.bitwuzlaTry {
require(lastCheckStatus == KSolverStatus.UNSAT) { "Unsat cores are only available after UNSAT checks" }
val unsatAssumptions = Native.bitwuzlaGetUnsatAssumptions(bitwuzlaCtx.bitwuzla)
lastAssumptions?.resolveUnsatCore(unsatAssumptions) ?: emptyList()
}

override fun reasonOfUnknown(): String = bitwuzlaCtx.bitwuzlaTry {
require(lastCheckStatus == KSolverStatus.UNKNOWN) {
"Unknown reason is only available after UNKNOWN checks"
}

// There is no way to retrieve reason of unknown from Bitwuzla in general case.
return lastReasonOfUnknown ?: "unknown"
}

override fun interrupt() = bitwuzlaCtx.bitwuzlaTry {
Native.bitwuzlaForceTerminate(bitwuzlaCtx.bitwuzla)
}

override fun close() = bitwuzlaCtx.bitwuzlaTry {
bitwuzlaCtx.close()
}

private fun BitwuzlaResult.processCheckResult() = when (this) {
BitwuzlaResult.BITWUZLA_SAT -> KSolverStatus.SAT
BitwuzlaResult.BITWUZLA_UNSAT -> KSolverStatus.UNSAT
BitwuzlaResult.BITWUZLA_UNKNOWN -> KSolverStatus.UNKNOWN
}.also { lastCheckStatus = it }

private fun invalidateSolverState() {
/**
* Bitwuzla model is only valid until the next check-sat call.
* */
lastModel?.markInvalid()
lastModel = null

lastCheckStatus = KSolverStatus.UNKNOWN
lastReasonOfUnknown = null

lastAssumptions = null
}

private inline fun bitwuzlaTryCheck(body: () -> KSolverStatus): KSolverStatus = try {
invalidateSolverState()
body()
} catch (ex: BitwuzlaNativeException) {
lastReasonOfUnknown = ex.message
KSolverStatus.UNKNOWN.also { lastCheckStatus = it }
}

private inner class TrackedAssumptions {
private val assumedExprs = arrayListOf<Pair<KExpr<KBoolSort>, BitwuzlaTerm>>()

fun assumeTrackedAssertion(trackedAssertion: Pair<KExpr<KBoolSort>, BitwuzlaTerm>) {
assumedExprs.add(trackedAssertion)
Native.bitwuzlaAssume(bitwuzlaCtx.bitwuzla, trackedAssertion.second)
}

fun assumeAssumption(expr: KExpr<KBoolSort>, term: BitwuzlaTerm) =
assumeTrackedAssertion(expr to term)

fun resolveUnsatCore(unsatAssumptions: BitwuzlaTermArray): List<KExpr<KBoolSort>> {
val unsatCoreTerms = LongOpenHashSet(unsatAssumptions)
return assumedExprs.mapNotNull { (expr, term) -> expr.takeIf { unsatCoreTerms.contains(term) } }
}
}
}
Loading

0 comments on commit 837c567

Please sign in to comment.