All Downloads are FREE. Search and download functionalities are using the official Maven repository.

main.io.ksmt.solver.z3.KZ3Solver.kt Maven / Gradle / Ivy

There is a newer version: 0.5.29
Show newest version
package io.ksmt.solver.z3

import com.microsoft.z3.Solver
import com.microsoft.z3.Status
import com.microsoft.z3.Z3Exception
import com.microsoft.z3.solverAssert
import com.microsoft.z3.solverAssertAndTrack
import com.microsoft.z3.solverCheckAssumptions
import com.microsoft.z3.solverGetUnsatCore
import io.ksmt.KContext
import io.ksmt.expr.KExpr
import io.ksmt.solver.KModel
import io.ksmt.solver.KSolver
import io.ksmt.solver.KSolverException
import io.ksmt.solver.KSolverStatus
import io.ksmt.solver.model.KNativeSolverModel
import io.ksmt.sort.KBoolSort
import io.ksmt.utils.library.NativeLibraryLoaderUtils
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
import java.lang.ref.PhantomReference
import java.lang.ref.ReferenceQueue
import java.util.*
import kotlin.time.Duration
import kotlin.time.DurationUnit

open class KZ3Solver(private val ctx: KContext) : KSolver {
    private val z3Ctx = KZ3Context(ctx)

    private var lazyConfiguration: KZ3SolverLazyConfiguration? = null
    private var solverInitialized: Boolean = false
    private val solver by lazy { createSolver() }
    fun nativeSolver(): Solver = solver

    private var lastCheckStatus = KSolverStatus.UNKNOWN
    private var lastReasonOfUnknown: String? = null
    private var lastModel: KModel? = null
    private var lastUnsatCore: List>? = null

    private var currentScope: UInt = 0u

    @Suppress("LeakingThis")
    private val contextCleanupActionHandler = registerContextForCleanup(this, z3Ctx)

    private val exprInternalizer by lazy {
        createExprInternalizer(z3Ctx)
    }
    private val exprConverter by lazy {
        createExprConverter(z3Ctx)
    }

    open fun createExprInternalizer(z3Ctx: KZ3Context): KZ3ExprInternalizer = KZ3ExprInternalizer(ctx, z3Ctx)

    open fun createExprConverter(z3Ctx: KZ3Context) = KZ3ExprConverter(ctx, z3Ctx)

    private fun createSolver(): Solver {
        solverInitialized = true
        val config = lazyConfiguration
        lazyConfiguration = null

        val logic = config?.logicConfiguration

        val solver = if (logic != null) {
            z3Ctx.nativeContext.mkSolver(logic)
        } else {
            z3Ctx.nativeContext.mkSolver()
        }

        config?.params?.let { solver.setParameters(it) }

        return solver
    }

    override fun configure(configurator: KZ3SolverConfiguration.() -> Unit) {
        if (!solverInitialized) {
            val config = lazyConfiguration
                ?: KZ3SolverLazyConfiguration(z3Ctx.nativeContext.mkParams())
                    .also { lazyConfiguration = it }

            config.configurator()
        } else {
            val config = KZ3SolverParamsConfiguration(z3Ctx.nativeContext.mkParams())
            config.configurator()
            solver.setParameters(config.params)
        }
    }

    override fun push() {
        solver.push()
        z3Ctx.pushAssertionLevel()
        currentScope++
    }

    override fun pop(n: UInt) {
        require(n <= currentScope) {
            "Can not pop $n scope levels because current scope level is $currentScope"
        }
        if (n == 0u) return

        solver.pop(n.toInt())
        repeat(n.toInt()) { z3Ctx.popAssertionLevel() }

        currentScope -= n
    }

    override fun assert(expr: KExpr) = z3Try {
        ctx.ensureContextMatch(expr)

        val z3Expr = with(exprInternalizer) { expr.internalizeExpr() }
        solver.solverAssert(z3Expr)

        z3Ctx.assertPendingAxioms(solver)
    }

    private val trackedAssertions = Long2ObjectOpenHashMap>()

    override fun assertAndTrack(expr: KExpr) = z3Try {
        ctx.ensureContextMatch(expr)

        val trackExpr = ctx.mkFreshConst("track", ctx.boolSort)
        val z3Expr = with(exprInternalizer) { expr.internalizeExpr() }
        val z3TrackVar = with(exprInternalizer) { trackExpr.internalizeExpr() }

        trackedAssertions.put(z3TrackVar, expr)

        solver.solverAssertAndTrack(z3Expr, z3TrackVar)

        z3Ctx.assertPendingAxioms(solver)
    }

    override fun check(timeout: Duration): KSolverStatus = z3TryCheck {
        solver.updateTimeout(timeout)
        solver.check().processCheckResult()
    }

    override fun checkWithAssumptions(
        assumptions: List>,
        timeout: Duration
    ): KSolverStatus = z3TryCheck {
        ctx.ensureContextMatch(assumptions)

        val z3Assumptions = with(exprInternalizer) {
            LongArray(assumptions.size) {
                val assumption = assumptions[it]

                /**
                 * Assumptions are trivially unsat and no check-sat is required.
                 * */
                if (assumption == ctx.falseExpr) {
                    lastUnsatCore = listOf(ctx.falseExpr)
                    lastCheckStatus = KSolverStatus.UNSAT
                    return KSolverStatus.UNSAT
                }

                assumption.internalizeExpr()
            }
        }

        solver.updateTimeout(timeout)

        solver.solverCheckAssumptions(z3Assumptions).processCheckResult()
    }

    override fun model(): KModel = z3Try {
        require(lastCheckStatus == KSolverStatus.SAT) {
            "Model are only available after SAT checks, current solver status: $lastCheckStatus"
        }
        lastModel?.let { return it }

        val z3Model = KZ3Model(solver.model, ctx, z3Ctx, exprInternalizer)
        return KNativeSolverModel(z3Model).also {
            lastModel = it
        }
    }

    override fun unsatCore(): List> = z3Try {
        require(lastCheckStatus == KSolverStatus.UNSAT) { "Unsat cores are only available after UNSAT checks" }

        val unsatCore = lastUnsatCore ?: with(exprConverter) {
            val solverUnsatCore = solver.solverGetUnsatCore()
            solverUnsatCore.map { trackedAssertions.get(it) ?: it.convertExpr() }
        }
        lastUnsatCore = unsatCore

        unsatCore
    }

    override fun reasonOfUnknown(): String = z3Try {
        require(lastCheckStatus == KSolverStatus.UNKNOWN) { "Unknown reason is only available after UNKNOWN checks" }
        lastReasonOfUnknown ?: solver.reasonUnknown
    }

    override fun interrupt() = z3Try {
        solver.interrupt()
    }

    override fun close() {
        unregisterContextCleanup(contextCleanupActionHandler)
        z3Ctx.close()
    }

    private fun Status?.processCheckResult() = when (this) {
        Status.SATISFIABLE -> KSolverStatus.SAT
        Status.UNSATISFIABLE -> KSolverStatus.UNSAT
        Status.UNKNOWN -> KSolverStatus.UNKNOWN
        null -> KSolverStatus.UNKNOWN
    }.also { lastCheckStatus = it }

    private fun Solver.updateTimeout(timeout: Duration) {
        val z3Timeout = if (timeout == Duration.INFINITE) {
            UInt.MAX_VALUE.toInt()
        } else {
            timeout.toInt(DurationUnit.MILLISECONDS)
        }
        val params = z3Ctx.nativeContext.mkParams().apply {
            add("timeout", z3Timeout)
        }
        setParameters(params)
    }

    private inline fun  z3Try(body: () -> T): T = try {
        body()
    } catch (ex: Z3Exception) {
        throw KSolverException(ex)
    }

    private fun invalidateSolverState() {
        lastReasonOfUnknown = null
        lastCheckStatus = KSolverStatus.UNKNOWN
        lastModel = null
        lastUnsatCore = null
    }

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

    companion object {
        init {
            System.setProperty("z3.skipLibraryLoad", "true")
            NativeLibraryLoaderUtils.load()
        }

        private val cleanupHandlers = ReferenceQueue()
        private val contextForCleanup = IdentityHashMap, KZ3Context>()

        /** Ensure Z3 native context is closed and all native memory is released.
         * */
        private fun registerContextForCleanup(solver: KZ3Solver, context: KZ3Context): PhantomReference {
            cleanupStaleContexts()
            val cleanupHandler = PhantomReference(solver, cleanupHandlers)
            contextForCleanup[cleanupHandler] = context

            return cleanupHandler
        }

        private fun unregisterContextCleanup(handler: PhantomReference) {
            contextForCleanup.remove(handler)
            handler.clear()
            cleanupStaleContexts()
        }

        private fun cleanupStaleContexts() {
            while (true) {
                val handler = cleanupHandlers.poll() ?: break
                contextForCleanup.remove(handler)?.close()
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy