Skip to content

Commit a8fc084

Browse files
Bitwuzla (#16)
* add bitwuzla support * add bitwuzla in the CI * Update install solvers script to the one from Inox (+bitwuzla) * Define bitwuzla test executors * Filter tests for bitwuzla by compatibility --------- Co-authored-by: Sankalp Gambhir <[email protected]>
1 parent 1432172 commit a8fc084

File tree

6 files changed

+175
-20
lines changed

6 files changed

+175
-20
lines changed

.github/workflows/scala-smtlib-CI.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ jobs:
1616
Z3_VER: 4.15.1
1717
CVC4_VER: 1.8
1818
CVC5_VER: 1.2.1
19+
BITWUZLA_VER: 0.8.2
20+
INSTALL_SOLVERS: 1
1921
steps:
2022
- name: Checkout
2123
uses: actions/checkout@v4
@@ -31,11 +33,11 @@ jobs:
3133
./scripts/install_sbt.sh
3234
echo "PATH=./sbt/bin/:$PATH" >> "$GITHUB_ENV"
3335
- name: Install solvers
34-
run: ./scripts/install_solvers.sh $GITHUB_WORKSPACE/.local/bin $Z3_VER $CVC4_VER $CVC5_VER
36+
run: ./scripts/install_solvers.sh $GITHUB_WORKSPACE/.local/bin $Z3_VER $CVC4_VER $CVC5_VER $BITWUZLA_VER
3537
- name: Add solvers to PATH
3638
run: echo "$GITHUB_WORKSPACE/.local/bin" >> $GITHUB_PATH
3739
- name: Test solvers availability
38-
run: cvc5 --version && z3 --version && cvc4 --version
40+
run: cvc5 --version && z3 --version && cvc4 --version && bitwuzla --version
3941
- name: Run Tests
4042
run: sbt -Dtest-parallelism=10 -batch test
4143
- name: Run integration tests

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ target
1010
.bsp
1111
project/.bloop
1212
project/metals.sbt
13-
project/project
13+
project/project
14+
.DS_Store

scripts/install_solvers.sh

Lines changed: 68 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,102 @@
11
#!/usr/bin/env sh
22

3+
if [ "$INSTALL_SOLVERS" != "1" ]; then
4+
echo "Skipping solver installation"
5+
exit 0
6+
fi
7+
8+
INFO_MSG="[INFO]"
9+
WARN_MSG="\033[33m[WARNING]\033[0m"
10+
ERROR_MSG="\033[1;31m[ERROR]\033[0m"
11+
12+
# curl command with default flags
13+
CURL='curl -L -s -C -'
14+
315
TEMP_DIR="$(pwd)/temp"
416
SOLVERS_DIR=${1:-"$(pwd)/solvers"}
517

18+
# check for set version or default to known working (for script) versions
619
Z3_VER=${2:-"4.15.1"}
720
CVC4_VER=${3:-"1.8"}
821
CVC5_VER=${4:-"1.2.1"}
22+
BITWUZLA_VER=${5:-"0.8.2"}
923

1024
ARCH=$(uname -m)
1125
# short arch name as used by z3 builds
1226
SHORT_ARCH=$(uname -m | sed 's/x86_64/x64/;s/aarch64/arm64/;')
1327

1428
# libc linked against z3
1529
# (glibc for Linux, osx for macOS)
16-
LIBC_NAME=$(uname -s | tr '[:upper:]' '[:lower:]' | sed 's/darwin/osx/;s/linux/glibc/;')
30+
Z3_LIBC_NAME=$(uname -s | tr '[:upper:]' '[:lower:]' | sed 's/darwin/osx/;s/linux/glibc/;')
31+
32+
# os names as used by CVC5 builds
33+
CVC5_OS_NAME=$(uname -s | tr '[:upper:]' '[:lower:]' | sed 's/darwin/macOS/;s/linux/Linux/;')
1734

1835
mkdir -p "$SOLVERS_DIR"
1936
mkdir -p "$TEMP_DIR"
37+
38+
reset_temp_dir() {
39+
rm -rf "$TEMP_DIR"
40+
mkdir -p "$TEMP_DIR"
41+
}
42+
43+
echo "$INFO_MSG Installing Solvers to $SOLVERS_DIR"
44+
2045
# cvc5
21-
wget -c "https://github.com/cvc5/cvc5/releases/download/cvc5-${CVC5_VER}/cvc5-Linux-${ARCH}-static.zip" -O "$TEMP_DIR/cvc5.zip" -q
22-
unzip "$TEMP_DIR/cvc5.zip" -d "$TEMP_DIR"
46+
echo "$INFO_MSG Installing cvc5 (v${CVC5_VER})"
47+
48+
$CURL "https://github.com/cvc5/cvc5/releases/download/cvc5-${CVC5_VER}/cvc5-${CVC5_OS_NAME}-${ARCH}-static.zip" --output "$TEMP_DIR/cvc5.zip"
49+
unzip -q "$TEMP_DIR/cvc5.zip" -d "$TEMP_DIR"
2350
CVC5_DIR=$(find "$TEMP_DIR" -mindepth 1 -maxdepth 1 -type d -name "*cvc5*")
2451
mv "$CVC5_DIR/bin/cvc5" "$SOLVERS_DIR/cvc5"
2552
chmod +x "$SOLVERS_DIR/cvc5"
26-
rm -rf "$TEMP_DIR"
2753

28-
# # CVC4
29-
wget -c "https://cvc4.cs.stanford.edu/downloads/builds/x86_64-linux-opt/cvc4-${CVC4_VER}-x86_64-linux-opt" -O "$SOLVERS_DIR/cvc4" -q
54+
reset_temp_dir
55+
56+
# CVC4
57+
echo "$INFO_MSG Installing CVC4 (v${CVC4_VER})"
58+
59+
if [ "$CVC5_OS_NAME" = "macOS" ]; then
60+
echo "$WARN_MSG CVC4 installation on macOS; defaulting to only build available (x86_64 v1.8)"
61+
$CURL "https://github.com/CVC4/CVC4-archived/releases/download/1.8/cvc4-1.8-macos-opt" --output "$SOLVERS_DIR/cvc4"
62+
elif [ "$CVC5_OS_NAME" = "Linux" ]; then
63+
$CURL "https://cvc4.cs.stanford.edu/downloads/builds/x86_64-linux-opt/cvc4-${CVC4_VER}-x86_64-linux-opt" --output "$SOLVERS_DIR/cvc4"
64+
else
65+
echo "$ERROR_MSG CVC4 installation not supported on unknown OS: $CVC5_OS_NAME"
66+
exit 1
67+
fi
3068
chmod +x "$SOLVERS_DIR/cvc4"
3169

70+
reset_temp_dir
71+
3272
# z3
33-
mkdir -p "$TEMP_DIR"
34-
Z3_FILE_PREFIX="z3-${Z3_VER}-${SHORT_ARCH}-${LIBC_NAME}"
73+
echo "$INFO_MSG Installing Z3 (v${Z3_VER})"
74+
75+
# z3 release file names contain libc versions in them, which makes them hard to
76+
# guess. Just get the correct one via the GitHub release API.
77+
Z3_FILE_PREFIX="z3-${Z3_VER}-${SHORT_ARCH}-${Z3_LIBC_NAME}" # followed by -<libcversion>.zip
3578
Z3_URL=$(
36-
curl -s "https://api.github.com/repos/Z3Prover/z3/releases/tags/z3-${Z3_VER}" | # get release info
37-
grep "browser_download_url.*${Z3_FILE_PREFIX}" | #| # find url for the correct build
79+
$CURL "https://api.github.com/repos/Z3Prover/z3/releases/tags/z3-${Z3_VER}" | # get release info
80+
grep "browser_download_url.*${Z3_FILE_PREFIX}" | # find url for the correct build
3881
sed 's/^.*: //;s/^"//;s/"$//' # strip non-url
3982
)
40-
wget -c "${Z3_URL}" -O "$TEMP_DIR/z3.zip" -q
41-
unzip "$TEMP_DIR/z3.zip" -d "$TEMP_DIR"
83+
$CURL "${Z3_URL}" --output "$TEMP_DIR/z3.zip"
84+
unzip -q "$TEMP_DIR/z3.zip" -d "$TEMP_DIR"
4285
Z3_DIR=$(find "$TEMP_DIR" -mindepth 1 -maxdepth 1 -type d -name "*z3*")
4386
mv "$Z3_DIR/bin/z3" "$SOLVERS_DIR/z3"
4487
chmod +x "$SOLVERS_DIR/z3"
4588
rm -rf "$TEMP_DIR"
4689

47-
echo "************** Solvers Installed **************"
48-
exit 0
90+
# Bitwuzla
91+
echo "$INFO_MSG Installing Bitwuzla (v${BITWUZLA_VER})"
92+
93+
mkdir -p "$TEMP_DIR"
94+
$CURL -L https://github.com/bitwuzla/bitwuzla/releases/download/${BITWUZLA_VER}/Bitwuzla-Linux-x86_64-static.zip --output "$TEMP_DIR/bitwuzla.zip"
95+
unzip -q "$TEMP_DIR/bitwuzla.zip" -d "$TEMP_DIR"
96+
BITWUZLA_DIR=$(find "$TEMP_DIR" -mindepth 1 -maxdepth 1 -type d -name "*Bitwuzla*")
97+
mv "$BITWUZLA_DIR/bin/bitwuzla" "$SOLVERS_DIR/bitwuzla"
98+
chmod +x "$SOLVERS_DIR/bitwuzla"
99+
rm -rf "$TEMP_DIR"
100+
101+
echo "$INFO_MSG Solvers Installed"
102+
exit 0

src/it/scala/smtlib/it/TestHelpers.scala

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ import parser.Parser
1212
import lexer.Lexer
1313
import printer.RecursivePrinter
1414
import interpreters._
15+
import smtlib.trees.Commands.*
16+
import smtlib.trees.Terms.Term
17+
import smtlib.trees.Tree
18+
import smtlib.trees.TreesOps
19+
import smtlib.theories.FixedSizeBitVectors.BitVectorSort
20+
import smtlib.theories.FloatingPoint.FloatingPointSort
1521

1622
/** Provide helper functions to test solver interfaces and files.
1723
*
@@ -43,6 +49,7 @@ trait TestHelpers {
4349
def getZ3Interpreter: Interpreter = Z3Interpreter.buildDefault
4450
def getCVC4Interpreter: Interpreter = CVC4Interpreter.buildDefault
4551
def getCVC5Interpreter: Interpreter = CVC5Interpreter.buildDefault
52+
def getBitwuzlaInterpreter: Interpreter = BitwuzlaInterpreter.buildDefault
4653

4754
def isZ3Available: Boolean = try {
4855
val _: String = "z3 -help".!!
@@ -67,6 +74,13 @@ trait TestHelpers {
6774
case _: Exception => false
6875
}
6976

77+
def isBitwuzlaAvailable: Boolean = try {
78+
val _: String = "bitwuzla --version".!!
79+
true
80+
} catch {
81+
case _: Exception => false
82+
}
83+
7084
def executeZ3(file: File)(f: (String) => Unit): Unit = {
7185
Seq("z3", "-smt2", file.getPath) ! ProcessLogger(f, s => ())
7286
}
@@ -79,5 +93,47 @@ trait TestHelpers {
7993
Seq("cvc5", "--lang", "smt", file.getPath) ! ProcessLogger(f, s => ())
8094
}
8195

96+
def executeBitwuzla(file: File)(f: (String) => Unit): Unit = {
97+
Seq("bitwuzla", "-v", "0", "--lang", "--smt2", file.getPath) ! ProcessLogger(f, s => ())
98+
}
99+
100+
////////////////////////////////////////////////////////////////
101+
// Solver specific helpers
102+
103+
/**
104+
* Is test in the fragment supported by bitwuzla?
105+
*
106+
* May not contain ints, reals, arrays, ADTs
107+
*
108+
*/
109+
def testIsBitwuzlaCompatible(cmds: Seq[Command], formula: Term): Boolean =
110+
def isCompat(term: Term): Boolean =
111+
def violatingTerm(term: Tree, children: Seq[Boolean]): Boolean = {
112+
import smtlib.theories.*
113+
term match
114+
case FixedSizeBitVectors.BitVectorConstant(_, _) => false // internally contains IntNumeral, but in a valid place
115+
case Ints.NumeralLit(_) => true
116+
case Reals.DecimalLit(_) => true
117+
case FixedSizeBitVectors.Int2BV(_, _) => true
118+
case FixedSizeBitVectors.BV2Nat(_) => true
119+
// this is enough to rule out the current problematic tests,
120+
// but may need refinement
121+
case _ => children.contains(true)
122+
}
123+
!TreesOps.fold(violatingTerm)(term)
124+
125+
def compatibleCommand(cmd: Command): Boolean =
126+
cmd match
127+
case DeclareFun(_, paramSorts, sort) => (sort +: paramSorts).forall {
128+
case BitVectorSort(_) => true
129+
case FloatingPointSort(_, _) => true
130+
case _ => false
131+
}
132+
case DeclareSort(_, _) => false
133+
case DeclareDatatypes(_) => false
134+
case Assert(term) => isCompat(term)
135+
case _ => true // may need refinement
136+
cmds.forall(compatibleCommand) && (isCompat(formula))
137+
82138
}
83139

src/it/scala/smtlib/it/TheoriesBuilderTests.scala

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import smtlib.trees.Commands._
66
import smtlib.trees.CommandsResponses._
77
import smtlib.trees.Terms._
88

9-
109
/** Checks that formula build with theories module are correctly handled by solvers */
1110
class TheoriesBuilderTests extends AnyFunSuite with TestHelpers {
1211

@@ -57,6 +56,15 @@ class TheoriesBuilderTests extends AnyFunSuite with TestHelpers {
5756
} else {
5857
ignore(cvc5Test) {}
5958
}
59+
60+
val bitwuzlaTest = prefix + ": with Bitwuzla"
61+
if (isBitwuzlaAvailable && testIsBitwuzlaCompatible(cmds, formula)) {
62+
test(bitwuzlaTest) {
63+
runAndCheck(getBitwuzlaInterpreter)
64+
}
65+
} else {
66+
ignore(bitwuzlaTest) {}
67+
}
6068
}
6169

6270

@@ -221,8 +229,8 @@ class TheoriesBuilderTests extends AnyFunSuite with TestHelpers {
221229
val f18 = Equals(Add(BitVectorConstant(1, 32), BitVectorConstant(2, 32), BitVectorConstant(3, 32), BitVectorConstant(4, 32)), BitVectorConstant(10, 32))
222230
mkTest(f18, SatStatus, uniqueName())
223231

224-
val f20 = Equals(Mul(BitVectorConstant(1, 32), BitVectorConstant(2, 32), BitVectorConstant(3, 32), BitVectorConstant(4, 32)), BitVectorConstant(24, 32))
225-
mkTest(f20, SatStatus, uniqueName())
232+
val f19 = Equals(Mul(BitVectorConstant(1, 32), BitVectorConstant(2, 32), BitVectorConstant(3, 32), BitVectorConstant(4, 32)), BitVectorConstant(24, 32))
233+
mkTest(f19, SatStatus, uniqueName())
226234
}
227235

228236
{
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package smtlib
2+
package interpreters
3+
4+
import smtlib.lexer.Lexer
5+
import smtlib.parser.Parser
6+
import smtlib.printer.{Printer, RecursivePrinter}
7+
import trees.Commands._
8+
9+
import java.io.BufferedReader
10+
11+
class BitwuzlaInterpreter(executable: String,
12+
args: Array[String],
13+
printer: Printer = RecursivePrinter,
14+
parserCtor: BufferedReader => Parser = out => new Parser(new Lexer(out)))
15+
extends ProcessInterpreter(executable, args, printer, parserCtor) {
16+
17+
printer.printCommand(SetOption(PrintSuccess(true)), in)
18+
in.write("\n")
19+
in.flush()
20+
parser.parseGenResponse
21+
22+
}
23+
24+
object BitwuzlaInterpreter {
25+
26+
def buildDefault: BitwuzlaInterpreter = {
27+
val executable = "bitwuzla"
28+
val args = Array("-v", "0",
29+
"--produce-models",
30+
"--lang", "smt2")
31+
new BitwuzlaInterpreter(executable, args)
32+
}
33+
34+
}

0 commit comments

Comments
 (0)