Skip to content

Commit 9bf9546

Browse files
Add new Eldarica interpreter (without IPC)
1 parent d4bb72a commit 9bf9546

File tree

4 files changed

+220
-46
lines changed

4 files changed

+220
-46
lines changed

src/main/scala/inox/solvers/SolverFactory.scala

Lines changed: 5 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,6 @@ object SolverFactory {
4545
case _: java.io.IOException => false
4646
}
4747

48-
lazy val hasEld = try {
49-
new CVC5Interpreter("eld", Array("-hsmt")).interrupt()
50-
true
51-
} catch {
52-
case _: java.io.IOException => false
53-
}
54-
5548
lazy val hasCVC4 = try {
5649
new CVC4Interpreter("cvc4", Array("-q", "--lang", "smt2.6")).interrupt()
5750
true
@@ -90,20 +83,20 @@ object SolverFactory {
9083
"smt-z3:<exec>" -> "Z3 through SMT-LIB with custom executable name",
9184
"princess" -> "Princess with inox unrolling",
9285
"eval" -> "Internal evaluator to discharge ground assertions",
93-
"horn-z3" -> "Horn solver using Z3 / Spacer",
94-
"horn-eld" -> "Horn solver using Eldarica",
86+
"inv-z3" -> "Horn solver using Z3 / Spacer",
87+
"inv-eld" -> "Horn solver using Eldarica"
9588
)
9689

9790
private val fallbacks = Map(
9891
"nativez3" -> (() => hasNativeZ3, Seq("smt-z3", "smt-cvc4", "smt-cvc5", "princess"), "Z3 native interface"),
9992
"nativez3-opt" -> (() => hasNativeZ3, Seq("smt-z3-opt"), "Z3 native interface"),
10093
"unrollz3" -> (() => hasNativeZ3, Seq("smt-z3", "smt-cvc4", "smt-cvc5", "princess"), "Z3 native interface"),
101-
"horn-z3" -> (() => hasNativeZ3, Seq("smt-z3", "smt-cvc4", "smt-cvc5", "princess"), "Z3 native interface"),
102-
"horn-eld" -> (() => hasEld, Seq("smt-z3", "smt-cvc4", "smt-cvc5", "princess"), "Eldarica binary"),
94+
"inv-z3" -> (() => hasZ3, Seq("smt-z3", "smt-cvc4", "smt-cvc5", "princess"), "Z3 native interface"),
10395
"smt-cvc4" -> (() => hasCVC4, Seq("nativez3", "smt-z3", "princess"), "'cvc4' binary"),
10496
"smt-cvc5" -> (() => hasCVC5, Seq("nativez3", "smt-z3", "princess"), "'cvc5' binary"),
10597
"smt-z3" -> (() => hasZ3, Seq("nativez3", "smt-cvc4", "smt-cvc5", "princess"), "'z3' binary"),
10698
"smt-z3-opt" -> (() => hasZ3, Seq("nativez3-opt"), "'z3' binary"),
99+
"inv-eld" -> (() => true, Seq(), "Eldarica solver"),
107100
"princess" -> (() => true, Seq(), "Princess solver"),
108101
"eval" -> (() => true, Seq(), "Internal evaluator")
109102
)
@@ -347,41 +340,7 @@ object SolverFactory {
347340

348341
class Underlying(override val program: targetProgram.type)
349342
extends smtlib.SMTLIBSolver(program, context)
350-
with smtlib.CVC5Solver {
351-
override def targetName = "unmanaged/inter"
352-
import _root_.smtlib.trees.Terms
353-
import _root_.smtlib.trees.CommandsResponses._
354-
import _root_.smtlib.trees.Commands._
355-
import _root_.smtlib.Interpreter
356-
import _root_.smtlib.printer.Printer
357-
import _root_.smtlib.printer.RecursivePrinter
358-
import java.io.BufferedReader
359-
import _root_.smtlib.interpreters.ProcessInterpreter
360-
import _root_.smtlib.parser.Parser
361-
import _root_.smtlib.extensions.tip.Lexer
362-
363-
class HornEldInterpreter(executable: String,
364-
args: Array[String],
365-
printer: Printer = RecursivePrinter,
366-
parserCtor: BufferedReader => Parser = out => new Parser(new Lexer(out)))
367-
extends ProcessInterpreter (executable, args, printer, parserCtor):
368-
printer.printCommand(SetOption(PrintSuccess(true)), in)
369-
in.write("\n")
370-
in.flush()
371-
parser.parseGenResponse
372-
in.write("(set-logic HORN)\n")
373-
in.flush()
374-
parser.parseGenResponse
375-
376-
override def eval(cmd: Terms.SExpr): Terms.SExpr =
377-
super.eval(cmd)
378-
379-
override protected val interpreter = {
380-
val opts = interpreterOpts
381-
// reporter.debug("Invoking solver "+targetName+" with "+opts.mkString(" "))
382-
new HornEldInterpreter(targetName, opts.toArray, parserCtor = out => new Parser(new Lexer(out)))
383-
}
384-
}
343+
with smtlib.EldaricaSolver
385344

386345
override protected val underlyingHorn = Underlying(targetProgram)
387346

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
package inox.solvers.smtlib
2+
3+
import _root_.smtlib.trees.Terms.*
4+
import _root_.smtlib.printer.Printer
5+
import _root_.smtlib.parser.Parser
6+
import _root_.smtlib.Interpreter
7+
import _root_.smtlib.theories.*
8+
import java.io.BufferedReader
9+
import _root_.smtlib.trees.CommandsResponses.*
10+
import _root_.smtlib.trees.Commands.*
11+
import java.io.StringReader
12+
import java.util.concurrent.Future
13+
14+
/**
15+
*
16+
*
17+
* @param printer
18+
* @param parser
19+
*/
20+
class EldaricaInterpreter(val printer: Printer, val parserCtor: BufferedReader => Parser) extends Interpreter {
21+
22+
import collection.mutable.{Stack => MStack, Seq => MSeq}
23+
24+
private val commands = MStack(MSeq.empty[SExpr])
25+
26+
private var lastModelResponse: Option[GetModelResponse] = None
27+
28+
val parser = parserCtor(new BufferedReader(new StringReader(""))) // dummy parser
29+
30+
private class InterruptibleExecutor[T]:
31+
private var task: Option[Future[T]] = None
32+
33+
private val executor = scala.concurrent.ExecutionContext.fromExecutorService(null)
34+
35+
private def asCallable[A](block: => A): java.util.concurrent.Callable[A] =
36+
new java.util.concurrent.Callable[A] { def call(): A = block }
37+
38+
def execute(block: => T): Option[T] =
39+
this.synchronized: // run only one task at a time
40+
task = Some(executor.submit(asCallable(block)))
41+
42+
val res =
43+
try
44+
Some(task.get.get()) // block for result or interrupt
45+
catch
46+
case e: java.util.concurrent.CancellationException => None // externally interrupted
47+
case e: Exception => throw e
48+
49+
task = None
50+
res
51+
52+
def interrupt(): Unit =
53+
task.foreach(_.cancel(true))
54+
55+
private val executor = new InterruptibleExecutor[SExpr]
56+
57+
/**
58+
* args to run Eldarica calls under
59+
*/
60+
private val eldArgs = Array(
61+
"-in", // read input from (simulated) stdin
62+
"-hsmt", // use SMT-LIB2 input format
63+
"-disj" // use disjunctive interpolation
64+
)
65+
66+
def eval(cmd: SExpr): SExpr =
67+
cmd match
68+
case CheckSat() =>
69+
checkSat
70+
case CheckSatAssuming(assumptions) =>
71+
def toAssertion(lit: PropLiteral): SExpr =
72+
val PropLiteral(sym, polarity) = lit
73+
val id = QualifiedIdentifier(SimpleIdentifier(sym), Some(Core.BoolSort()))
74+
val term = if polarity then id else Core.Not(id)
75+
Assert(term)
76+
77+
commands.push(assumptions.map(toAssertion).to(MSeq))
78+
val res = checkSat
79+
commands.pop()
80+
res
81+
case Echo(value) =>
82+
EchoResponseSuccess(value.toString)
83+
case Exit() =>
84+
// equivalent to reset
85+
commands.clear()
86+
trySuccess
87+
case GetInfo(flag) =>
88+
flag match
89+
case VersionInfoFlag() =>
90+
GetInfoResponseSuccess(VersionInfoResponse("0.1"), Seq.empty)
91+
case _ => Unsupported
92+
case GetModel() =>
93+
getModel
94+
case Pop(n) =>
95+
(1 to n).foreach(_ => commands.pop())
96+
trySuccess
97+
case Push(n) =>
98+
(1 to n).foreach(_ => commands.push(MSeq()))
99+
trySuccess
100+
case Reset() =>
101+
commands.clear()
102+
trySuccess
103+
case SetOption(option) =>
104+
// slightly haphazard
105+
// but we always expect that PrintSuccess(true) has been passed as the first command
106+
trySuccess
107+
case _ =>
108+
commands.push(commands.pop() :+ cmd)
109+
trySuccess
110+
111+
//A free method is kind of justified by the need for the IO streams to be closed, and
112+
//there seems to be a decent case in general to have such a method for things like solvers
113+
//note that free can be used even if the solver is currently solving, and act as a sort of interrupt
114+
def free(): Unit =
115+
commands.clear()
116+
117+
def interrupt(): Unit =
118+
executor.interrupt()
119+
120+
private def trySuccess: SExpr = Success
121+
122+
private def collapsedCommands = commands.toSeq.flatten
123+
124+
private def checkSat: SExpr =
125+
this.synchronized {
126+
// reset last model
127+
setLastModelResponse(None)
128+
executor
129+
.execute(seqCheckSat)
130+
.getOrElse(CheckSatStatus(UnknownStatus))
131+
}
132+
133+
private def seqCheckSat: SExpr =
134+
val commands = collapsedCommands :+ CheckSat()
135+
val script = commands.map(printer.toString).mkString("\n")
136+
137+
val inputStream = new java.io.StringReader(script)
138+
139+
val buffer = new java.io.ByteArrayOutputStream
140+
val printStream = new java.io.PrintStream(buffer)
141+
142+
// actually check sat, requesting a model if possible
143+
Console.withIn(inputStream):
144+
Console.withOut(printStream):
145+
lazabs.Main.doMain(eldArgs, false)
146+
147+
val eldRes = new java.io.BufferedReader(new java.io.StringReader(buffer.toString))
148+
149+
val parser = parserCtor(eldRes)
150+
151+
val result = parser.parseCheckSatResponse
152+
153+
result match
154+
case CheckSatStatus(SatStatus) =>
155+
// FIXME: @sg: disabled due to non-SMTLIB compliant model printing from eldarica
156+
// there will be a parser exception if we attemp this
157+
// // if Sat, parse and store model
158+
// val model = parser.parseGetModelResponse
159+
// // could be a model or an error, in either case, this is the response for (get-model)
160+
// setLastModelResponse(Some(model))
161+
setLastModelResponse(Some(GetModelResponseSuccess(Nil))) // empty model
162+
result
163+
case _ =>
164+
// if unsat or unknown, reset the model
165+
setLastModelResponse(None)
166+
result
167+
168+
private def setLastModelResponse(model: Option[GetModelResponse]): Unit =
169+
lastModelResponse = model
170+
171+
private def getModel: SExpr =
172+
lastModelResponse match
173+
case Some(modelResponse) =>
174+
modelResponse
175+
case None =>
176+
Error("No model available")
177+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/* Copyright 2009-2018 EPFL, Lausanne */
2+
3+
package inox
4+
package solvers
5+
package smtlib
6+
7+
import _root_.{smtlib => sl}
8+
import _root_.smtlib.trees.Terms.{Identifier => _, _}
9+
import _root_.smtlib.trees.CommandsResponses._
10+
11+
trait EldaricaSolver extends SMTLIBSolver with EldaricaTarget {
12+
13+
protected val interpreter: sl.Interpreter =
14+
new EldaricaInterpreter(sl.printer.RecursivePrinter, out => sl.parser.Parser(sl.extensions.tip.Lexer(out)))
15+
16+
def targetName = "eldarica"
17+
18+
protected def interpreterOpts: Seq[String] = Seq.empty
19+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/* Copyright 2009-2018 EPFL, Lausanne */
2+
3+
package inox
4+
package solvers
5+
package smtlib
6+
7+
import _root_.smtlib.trees.Terms.{Identifier => SMTIdentifier, _}
8+
import _root_.smtlib.trees.Commands._
9+
import _root_.smtlib.theories._
10+
import _root_.smtlib.theories.cvc._
11+
12+
trait EldaricaTarget extends SMTLIBTarget with SMTLIBDebugger {
13+
import context.{given, _}
14+
import program._
15+
import program.trees._
16+
import program.symbols.{given, _}
17+
18+
override protected def toSMT(e: Expr)(using bindings: Map[Identifier, Term]) = super.toSMT(e)
19+
}

0 commit comments

Comments
 (0)