diff --git a/buildSrc/src/main/kotlin/io.ksmt.ksmt-base.gradle.kts b/buildSrc/src/main/kotlin/io.ksmt.ksmt-base.gradle.kts index cfb3207ef..a56559257 100644 --- a/buildSrc/src/main/kotlin/io.ksmt.ksmt-base.gradle.kts +++ b/buildSrc/src/main/kotlin/io.ksmt.ksmt-base.gradle.kts @@ -11,7 +11,7 @@ plugins { } group = "io.ksmt" -version = "0.6.7" +version = project.findProperty("ksmtVersion")?.toString()?.takeIf { it.isNotBlank() } ?: "0.6.7" repositories { mavenCentral() diff --git a/jitpack.yml b/jitpack.yml new file mode 100644 index 000000000..32c369436 --- /dev/null +++ b/jitpack.yml @@ -0,0 +1,4 @@ +jdk: + - openjdk17 +install: + - ./gradlew :ksmt-core:publishToMavenLocal :ksmt-bitwuzla:ksmt-bitwuzla-core:publishToMavenLocal -PksmtVersion=$VERSION -x test -x dokkaJavadoc -Dorg.gradle.jvmargs=-Xmx1g diff --git a/ksmt-bitwuzla/ksmt-bitwuzla-core/src/main/kotlin/io/ksmt/solver/bitwuzla/KBitwuzlaExprConverter.kt b/ksmt-bitwuzla/ksmt-bitwuzla-core/src/main/kotlin/io/ksmt/solver/bitwuzla/KBitwuzlaExprConverter.kt index 164ad78a8..f9425f560 100644 --- a/ksmt-bitwuzla/ksmt-bitwuzla-core/src/main/kotlin/io/ksmt/solver/bitwuzla/KBitwuzlaExprConverter.kt +++ b/ksmt-bitwuzla/ksmt-bitwuzla-core/src/main/kotlin/io/ksmt/solver/bitwuzla/KBitwuzlaExprConverter.kt @@ -15,6 +15,17 @@ import io.ksmt.expr.KConst import io.ksmt.expr.KEqExpr import io.ksmt.expr.KExpr import io.ksmt.expr.KFpRoundingMode +import io.ksmt.expr.KApp +import io.ksmt.expr.KArray2Lambda +import io.ksmt.expr.KArray2Store +import io.ksmt.expr.KArray3Lambda +import io.ksmt.expr.KArray3Store +import io.ksmt.expr.KArrayLambda +import io.ksmt.expr.KArrayLambdaBase +import io.ksmt.expr.KArrayNLambda +import io.ksmt.expr.KArrayNStore +import io.ksmt.expr.KArrayStore +import io.ksmt.expr.KArrayStoreBase import io.ksmt.expr.KInterpretedValue import io.ksmt.expr.KIteExpr import io.ksmt.expr.printer.ExpressionPrinter @@ -1186,6 +1197,11 @@ open class KBitwuzlaExprConverter( return@with expr.arg.uncheckedCast() } + val retypedArray = ArrayDomainRetyper(fromSort, toSort).retype(expr.arg) + if (retypedArray != null) { + return@with AdapterTermRewriter(ctx).apply(retypedArray).uncheckedCast() + } + val indices = toSort.domainSorts.map { mkFreshConst("i", it) } @@ -1216,6 +1232,135 @@ open class KBitwuzlaExprConverter( } } + private inner class ArrayDomainRetyper( + private val fromSort: KArraySortBase<*>, + private val toSort: KArraySortBase<*> + ) : KNonRecursiveTransformer(ctx) { + private val retypedIndices = hashMapOf, KDecl<*>>() + private var failed = false + + fun retype(array: KExpr<*>): KExpr<*>? { + val result = apply(array) + return if (failed || result.sort != toSort) null else result + } + + override fun exprTransformationRequired(expr: KExpr): Boolean = + expr !is KInterpretedValue && !expr.isAdapterTerm() + + private fun KExpr<*>.isAdapterTerm(): Boolean = + this is BoolToBv1AdapterExpr || + this is Bv1ToBoolAdapterExpr || + this is Bv32ToUninterpretedSortAdapterExpr || + this is ArrayAdapterExpr<*, *> + + override fun transformApp(expr: KApp): KExpr { + if (expr.sort == fromSort || expr.args.any { it.isRetypedIndex() }) failed = true + return expr + } + + override fun transform(expr: KEqExpr): KExpr = + transformExprAfterTransformed(expr, expr.lhs, expr.rhs) { lhs, rhs -> + retypedEquality(lhs, rhs) ?: retypedEquality(rhs, lhs) ?: run { + if (lhs.isRetypedIndex() || rhs.isRetypedIndex()) failed = true + ctx.mkEq(lhs, rhs) + } + } + + override fun , R : KSort> transform(expr: KArrayConst): KExpr = + if (expr.sort != fromSort) super.transform(expr) else retypedConstArray(expr) + + override fun transform(expr: KArrayStore): KExpr> = + if (expr.sort != fromSort) super.transform(expr) else retypedStore(expr) + + override fun transform( + expr: KArray2Store + ): KExpr> = + if (expr.sort != fromSort) super.transform(expr) else retypedStore(expr) + + override fun transform( + expr: KArray3Store + ): KExpr> = + if (expr.sort != fromSort) super.transform(expr) else retypedStore(expr) + + override fun transform(expr: KArrayNStore): KExpr> = + if (expr.sort != fromSort) super.transform(expr) else retypedStore(expr) + + override fun transform(expr: KArrayLambda): KExpr> = + if (expr.sort != fromSort) super.transform(expr) else retypedLambda(expr) + + override fun transform( + expr: KArray2Lambda + ): KExpr> = + if (expr.sort != fromSort) super.transform(expr) else retypedLambda(expr) + + override fun transform( + expr: KArray3Lambda + ): KExpr> = + if (expr.sort != fromSort) super.transform(expr) else retypedLambda(expr) + + override fun transform(expr: KArrayNLambda): KExpr> = + if (expr.sort != fromSort) super.transform(expr) else retypedLambda(expr) + + private fun retypedConstArray(expr: KArrayConst<*, *>): KExpr { + val array: KExpr = expr.uncheckedCast() + return transformExprAfterTransformed(array, expr.value) { value -> + val wellSortedValue: KExpr = value.convertToExpectedIfNeeded(toSort.range) + val arraySort: KArraySortBase = toSort.uncheckedCast() + ctx.mkArrayConst(arraySort, wellSortedValue).uncheckedCast() + }.uncheckedCast() + } + + private fun retypedStore(expr: KArrayStoreBase<*, *>): KExpr { + val store: KExpr = expr.uncheckedCast() + return transformExprAfterTransformed(store, expr.args) { args -> + val indices = args.subList(1, args.lastIndex) + if (indices.any { it !is KInterpretedValue<*> }) failed = true + val array: KExpr> = args.first().uncheckedCast() + with(ctx) { mkAnyArrayStore(array, indices, args.last()) }.uncheckedCast() + }.uncheckedCast() + } + + private fun retypedLambda(expr: KArrayLambdaBase<*, *>): KExpr { + val oldDecls = expr.indexVarDeclarations + if (oldDecls.size != toSort.domainSorts.size) failed = true + val newDecls = oldDecls.zip(toSort.domainSorts) { old, sort -> retypedIndexDecl(old, sort) } + + val lambda: KExpr = expr.uncheckedCast() + val body: KExpr = expr.body.uncheckedCast() + return transformExprAfterTransformed(lambda, body) { transformedBody -> + val wellSortedBody: KExpr = transformedBody.convertToExpectedIfNeeded(toSort.range) + with(ctx) { mkAnyArrayLambda(newDecls, wellSortedBody) }.uncheckedCast() + }.uncheckedCast() + } + + private fun retypedIndexDecl(old: KDecl<*>, sort: KSort): KDecl<*> = when { + old.sort == sort -> old + old.sort == ctx.bv32Sort && sort is KUninterpretedSort -> + retypedIndices.getOrPut(old) { ctx.mkFreshConstDecl(old.name, sort) } + else -> { + failed = true + old + } + } + + private fun retypedEquality(index: KExpr, other: KExpr): KExpr? { + val newIndex = retypedConst(index) ?: return null + val newOther: KExpr = when { + other is KBitVec32Value -> Bv32ToUninterpretedSortAdapterExpr(other, newIndex.sort) + else -> retypedConst(other) ?: return null + } + return ctx.mkEq(newIndex, newOther) + } + + private fun KExpr<*>.isRetypedIndex(): Boolean = this is KConst<*> && decl in retypedIndices + + private fun retypedConst(expr: KExpr<*>): KExpr? { + if (expr !is KConst<*>) return null + val newDecl = retypedIndices[expr.decl] ?: return null + return ctx.mkConstApp(newDecl).uncheckedCast() + } + } + inline fun BitwuzlaTerm.convertBv( op: (KExpr, KExpr) -> KExpr ) = convert { a0: KExpr, a1: KExpr -> diff --git a/ksmt-bitwuzla/ksmt-bitwuzla-core/src/test/kotlin/io/ksmt/solver/bitwuzla/UninterpretedSortArrayModelTest.kt b/ksmt-bitwuzla/ksmt-bitwuzla-core/src/test/kotlin/io/ksmt/solver/bitwuzla/UninterpretedSortArrayModelTest.kt new file mode 100644 index 000000000..4fe58dd7d --- /dev/null +++ b/ksmt-bitwuzla/ksmt-bitwuzla-core/src/test/kotlin/io/ksmt/solver/bitwuzla/UninterpretedSortArrayModelTest.kt @@ -0,0 +1,56 @@ +package io.ksmt.solver.bitwuzla + +import io.ksmt.KContext +import io.ksmt.solver.KSolverStatus +import kotlin.test.Test +import kotlin.test.assertEquals + +class UninterpretedSortArrayModelTest { + private val ctx = KContext() + private val solver = KBitwuzlaSolver(ctx) + + @Test + fun detachModelOfSelectedTwoIndexArrayOverUninterpretedSort(): Unit = with(ctx) { + val address = mkUninterpretedSort("Address") + val array = mkConst("input", mkArraySort(address, bv32Sort, bv8Sort)) + val a = mkConst("a", address) + val i = mkConst("i", bv32Sort) + val selected = array.select(a, i) + + solver.assert(selected eq mkBv(5.toByte())) + assertEquals(KSolverStatus.SAT, solver.check()) + + val model = solver.model().detach() + assertEquals(mkBv(5.toByte()), model.eval(selected)) + } + + @Test + fun detachModelOfEqualTwoIndexArraysOverUninterpretedSort(): Unit = with(ctx) { + val address = mkUninterpretedSort("Address") + val sort = mkArraySort(address, bv32Sort, bv8Sort) + val array = mkConst("input", sort) + val other = mkConst("other", sort) + + solver.assert(array eq other) + assertEquals(KSolverStatus.SAT, solver.check()) + + val model = solver.model().detach() + assertEquals(model.eval(array), model.eval(other)) + } + + @Test + fun detachModelOfStoredTwoIndexArrayOverUninterpretedSort(): Unit = with(ctx) { + val address = mkUninterpretedSort("Address") + val sort = mkArraySort(address, bv32Sort, bv8Sort) + val array = mkConst("input", sort) + val other = mkConst("other", sort) + val a = mkConst("a", address) + val i = mkConst("i", bv32Sort) + + solver.assert(array eq other.store(a, i, mkBv(7.toByte()))) + assertEquals(KSolverStatus.SAT, solver.check()) + + val model = solver.model().detach() + assertEquals(mkBv(7.toByte()), model.eval(array.select(a, i))) + } +}