diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml new file mode 100644 index 000000000..843b1c75e --- /dev/null +++ b/.github/workflows/docker-image.yml @@ -0,0 +1,61 @@ +name: Publish Docker image + +on: + schedule: + - cron: "0 1 * * 0" # every Sunday at 1 am + workflow_dispatch: # on button click + +jobs: + push_to_registry: + name: Push Docker image to GitHub Docker registry + runs-on: ubuntu-latest + + steps: + - name: Check out the repo + uses: actions/checkout@v2 + + - name: Log in to GitHub Docker registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + # ------- + # BARE Z3 + # ------- + - name: Extract metadata (tags, labels) for Bare Z3 Docker Image + id: meta + uses: docker/metadata-action@v3 + with: + images: ghcr.io/z3prover/z3 + flavor: | + latest=auto + prefix=ubuntu-20.04-bare-z3- + tags: | + type=schedule,pattern={{date 'YYYYMMDD'}} + type=ref,event=tag + type=edge + type=sha,prefix=ubuntu-20.04-bare-z3-sha- + - name: Build and push Bare Z3 Docker Image + uses: docker/build-push-action@v2.7.0 + with: + context: . + push: true + target: bare-z3 + file: ./docker/ubuntu-20-04.Dockerfile + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + # ------------------------------ + # Repo description on GHCR + # ------------------------------ +# - name: Update repo description +# uses: peter-evans/dockerhub-description@v2 +# with: +# registry: ghcr.io +# repository: z3prover/z3 +# username: ${{ secrets.DOCKER_USERNAME }} +# password: ${{ secrets.DOCKER_PASSWORD }} +# short-description: ${{ github.event.repository.description }} +# readme-filepath: ./docker/README.md diff --git a/.github/workflows/wasm-release.yml b/.github/workflows/wasm-release.yml new file mode 100644 index 000000000..b91e06a80 --- /dev/null +++ b/.github/workflows/wasm-release.yml @@ -0,0 +1,61 @@ +name: WebAssembly Publish + +on: + workflow_dispatch: + +defaults: + run: + working-directory: src/api/js + +env: + EM_VERSION: 3.1.0 + +jobs: + publish: + name: Publish + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup node + uses: actions/setup-node@v2 + with: + node-version: 'lts/*' + registry-url: 'https://registry.npmjs.org' + + - name: Prepare for publish + run: | + npm version $(node -e 'console.log(fs.readFileSync("../../../scripts/release.yml", "utf8").match(/ReleaseVersion:\s*\x27(\S+)\x27/)[1])') + mv PUBLISHED_README.md README.md + cp ../../../LICENSE.txt . + + - name: Setup emscripten + uses: mymindstorm/setup-emsdk@v11 + with: + no-install: true + version: ${{env.EM_VERSION}} + actions-cache-folder: 'emsdk-cache' + + - name: Install dependencies + run: npm ci + + - name: Build TypeScript + run: npm run build-ts + + - name: Build wasm + run: | + emsdk install ${EM_VERSION} + emsdk activate ${EM_VERSION} + source $(dirname $(which emsdk))/emsdk_env.sh + which node + which clang++ + npm run build-wasm + + - name: Test + run: npm test + + - name: Publish + run: npm publish + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml index 0a2ebc3d2..6ad00cc47 100644 --- a/.github/workflows/wasm.yml +++ b/.github/workflows/wasm.yml @@ -1,42 +1,51 @@ -name: WASM Build +name: WebAssembly Build on: push: branches: [ master ] + pull_request: + +defaults: + run: + working-directory: src/api/js env: - BUILD_TYPE: Release + EM_VERSION: 3.1.0 jobs: - build: + check: + name: Check runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v2 + - name: Checkout + uses: actions/checkout@v2 - - name: Import emscripten - uses: mymindstorm/setup-emsdk@v9 - - - name: Configure CMake and build - run: | - mkdir build - cd build + - name: Setup node + uses: actions/setup-node@v2 + with: + node-version: 'lts/*' - emcmake cmake \ - -DCMAKE_BUILD_TYPE=MinSizeRel \ - -DZ3_BUILD_LIBZ3_SHARED=OFF \ - -DZ3_ENABLE_EXAMPLE_TARGETS=OFF \ - -DZ3_BUILD_TEST_EXECUTABLES=OFF \ - -DZ3_BUILD_EXECUTABLE=OFF \ - -DZ3_SINGLE_THREADED=ON \ - -DCMAKE_CXX_FLAGS="-s DISABLE_EXCEPTION_CATCHING=0" \ - ..; - make - tar -cvf z3-build-wasm.tar *.a - - - name: Archive production artifacts - uses: actions/upload-artifact@v2 - with: - name: z3-build-wasm - path: build/z3-build-wasm.tar + - name: Setup emscripten + uses: mymindstorm/setup-emsdk@v11 + with: + no-install: true + version: ${{env.EM_VERSION}} + actions-cache-folder: 'emsdk-cache' + + - name: Install dependencies + run: npm ci + + - name: Build TypeScript + run: npm run build-ts + + - name: Build wasm + run: | + emsdk install ${EM_VERSION} + emsdk activate ${EM_VERSION} + source $(dirname $(which emsdk))/emsdk_env.sh + which node + which clang++ + npm run build-wasm + + - name: Test + run: npm test diff --git a/.gitignore b/.gitignore index 1bf820b7e..ac83a2775 100644 --- a/.gitignore +++ b/.gitignore @@ -75,6 +75,13 @@ src/api/ml/z3enums.ml src/api/ml/z3native.mli src/api/ml/z3enums.mli src/api/ml/z3.mllib +src/api/js/node_modules/ +src/api/js/*.js +src/api/js/build/ +src/api/js/**/*.d.ts +!src/api/js/scripts/*.js +!src/api/js/src/*.js + out/** *.bak doc/api diff --git a/CMakeLists.txt b/CMakeLists.txt index 4a6871451..0a265bb6c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.4) set(CMAKE_USER_MAKE_RULES_OVERRIDE_CXX "${CMAKE_CURRENT_SOURCE_DIR}/cmake/cxx_compiler_flags_overrides.cmake") -project(Z3 VERSION 4.8.14.0 LANGUAGES CXX C) +project(Z3 VERSION 4.8.15.0 LANGUAGES CXX C) ################################################################################ # Project version diff --git a/README.md b/README.md index 5c5af803b..643e49823 100644 --- a/README.md +++ b/README.md @@ -14,10 +14,12 @@ See the [release notes](RELEASE_NOTES) for notes on various stable releases of Z ## Build status -| Azure Pipelines | Code Coverage | Open Bugs | Android Build | WASM Build | +| Azure Pipelines | Code Coverage | Open Bugs | Android Build | WASM Build | | --------------- | --------------|-----------|---------------|------------| | [![Build Status](https://dev.azure.com/Z3Public/Z3/_apis/build/status/Z3Prover.z3?branchName=master)](https://dev.azure.com/Z3Public/Z3/_build/latest?definitionId=1&branchName=master) | [![CodeCoverage](https://github.com/Z3Prover/z3/actions/workflows/coverage.yml/badge.svg)](https://github.com/Z3Prover/z3/actions/workflows/coverage.yml) | [![Open Issues](https://github.com/Z3Prover/z3/actions/workflows/wip.yml/badge.svg)](https://github.com/Z3Prover/z3/actions/workflows/wip.yml) |[![Android Build](https://github.com/Z3Prover/z3/actions/workflows/android-build.yml/badge.svg)](https://github.com/Z3Prover/z3/actions/workflows/android-build.yml) | [![WASM Build](https://github.com/Z3Prover/z3/actions/workflows/wasm.yml/badge.svg)](https://github.com/Z3Prover/z3/actions/workflows/wasm.yml) | +Docker image. + [1]: #building-z3-on-windows-using-visual-studio-command-prompt [2]: #building-z3-using-make-and-gccclang [3]: #building-z3-using-cmake @@ -193,9 +195,9 @@ See [``examples/python``](examples/python) for examples. The Julia package [Z3.jl](https://github.com/ahumenberger/Z3.jl) wraps the C++ API of Z3. Information about updating and building the Julia bindings can be found in [src/api/julia](src/api/julia). -### ``Web Assembly`` +### ``Web Assembly`` / ``TypeScript`` / ``JavaScript`` -[WebAssembly](https://github.com/cpitclaudel/z3.wasm) bindings are provided by ClĂ©ment Pit-Claudel. +A WebAssembly build with associated TypeScript typings is published on npm as [z3-solver](https://www.npmjs.com/package/z3-solver). Information about building these bindings can be found in [src/api/js](src/api/js). ## System Overview diff --git a/RELEASE_NOTES b/RELEASE_NOTES index 790691c3d..78cd70fb0 100644 --- a/RELEASE_NOTES +++ b/RELEASE_NOTES @@ -11,6 +11,13 @@ Version 4.8.next - introduction of simple induction lemmas to handle a limited repertoire of induction proofs. +Version 4.8.14 +============== + - fixes Antimirov derivatives for intersections and unions required + required for solving non-emptiness constraints. + - includes x86 dll in nuget package for Windows. + - exposes additional user propagator functionality + Version 4.8.13 ============== The release integrates various bug fixes and tuning. diff --git a/azure-pipelines.yml b/azure-pipelines.yml index ea894a93a..bff70c859 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -288,6 +288,7 @@ jobs: - job: "MacOSOCaml" displayName: "MacOS build with OCaml" + condition: eq(0,1) pool: vmImage: "macOS-latest" steps: @@ -310,9 +311,9 @@ jobs: eval `opam config env` make -j3 make -j3 _ex_ml_example_post_install - ./ml_example_shared.byte - ./ml_example_shared_custom.byte - ./ml_example_shared + # ./ml_example_shared.byte + # ./ml_example_shared_custom.byte + # ./ml_example_shared cd .. # Skip as dead-slow in debug mode: # - template: scripts/test-z3.yml diff --git a/docker/ubuntu-20-04.Dockerfile b/docker/ubuntu-20-04.Dockerfile new file mode 100644 index 000000000..6a37d05c0 --- /dev/null +++ b/docker/ubuntu-20-04.Dockerfile @@ -0,0 +1,45 @@ +# ------------- +# OS Base image +# ------------- +# >> Includes system-wide dependencies +FROM ubuntu:20.04 as lib-base +ARG DEBIAN_FRONTEND=noninteractive +RUN apt-get update && \ + apt-get -y --no-install-recommends install \ + cmake \ + make \ + clang \ + g++ \ + curl \ + default-jdk \ + python3 \ + python3-setuptools \ + python-is-python3 \ + sudo + +# ---------------- +# Z3 Builder Image +# ---------------- +# >> Includes build files and compiles the basic z3 sources +FROM lib-base as builder +COPY ./ /z3-source/ +WORKDIR /z3-source/ +RUN python scripts/mk_make.py +WORKDIR /z3-source/build/ +RUN make +RUN sudo make install +WORKDIR /z3-source/ + +# ------- +# Bare z3 +# ------- +# >> Includes only stnadard z3 installations. +# >> Can be used as a standalone interface to z3. +FROM builder as bare-z3 +ENTRYPOINT [ "z3" ] + +# TODO: introduce Python-binding stage +# ... + +# TODO(optional): introduce C/C++ -binding stage +# ... diff --git a/scripts/mk_nuget_task.py b/scripts/mk_nuget_task.py index 69b67abc9..75d26a1c5 100644 --- a/scripts/mk_nuget_task.py +++ b/scripts/mk_nuget_task.py @@ -21,17 +21,17 @@ def mk_dir(d): if not os.path.exists(d): os.makedirs(d) +os_info = { 'ubuntu-18' : ('so', 'linux-x64'), + 'ubuntu-20' : ('so', 'linux-x64'), + 'glibc-2.31' : ('so', 'linux-x64'), + 'x64-win' : ('dll', 'win-x64'), + 'x86-win' : ('dll', 'win-x86'), + 'osx' : ('dylib', 'osx-x64'), + 'debian' : ('so', 'linux-x64') } -os_info = {"z64-ubuntu-14" : ('so', 'linux-x64'), - 'ubuntu-18' : ('so', 'linux-x64'), - 'ubuntu-20' : ('so', 'linux-x64'), - 'glibc-2.31' : ('so', 'linux-x64'), - 'x64-win' : ('dll', 'win-x64'), - 'x86-win' : ('dll', 'win-x86'), - 'osx' : ('dylib', 'osx-x64'), - 'debian' : ('so', 'linux-x64') } + -def classify_package(f): +def classify_package(f, arch): for os_name in os_info: if os_name in f: ext, dst = os_info[os_name] @@ -45,7 +45,7 @@ def replace(src, dst): except: shutil.move(src, dst) -def unpack(packages, symbols): +def unpack(packages, symbols, arch): # unzip files in packages # out # +- runtimes @@ -57,15 +57,15 @@ def unpack(packages, symbols): tmp = "tmp" if not symbols else "tmpsym" for f in os.listdir(packages): print(f) - if f.endswith(".zip") and classify_package(f): - os_name, package_dir, ext, dst = classify_package(f) + if f.endswith(".zip") and classify_package(f, arch): + os_name, package_dir, ext, dst = classify_package(f, arch) path = os.path.abspath(os.path.join(packages, f)) zip_ref = zipfile.ZipFile(path, 'r') zip_ref.extract(f"{package_dir}/bin/libz3.{ext}", f"{tmp}") mk_dir(f"out/runtimes/{dst}/native") replace(f"{tmp}/{package_dir}/bin/libz3.{ext}", f"out/runtimes/{dst}/native/libz3.{ext}") - if "x64-win" in f: - mk_dir("out/lib/netstandard1.4/") + if "x64-win" in f or "x86-win" in f: + mk_dir("out/lib/netstandard2.0/") if symbols: zip_ref.extract(f"{package_dir}/bin/libz3.pdb", f"{tmp}") replace(f"{tmp}/{package_dir}/bin/libz3.pdb", f"out/runtimes/{dst}/native/libz3.pdb") @@ -74,7 +74,7 @@ def unpack(packages, symbols): files += ["Microsoft.Z3.pdb", "Microsoft.Z3.xml"] for b in files: zip_ref.extract(f"{package_dir}/bin/{b}", f"{tmp}") - replace(f"{tmp}/{package_dir}/bin/{b}", f"out/lib/netstandard1.4/{b}") + replace(f"{tmp}/{package_dir}/bin/{b}", f"out/lib/netstandard2.0/{b}") def mk_targets(source_root): mk_dir("out/build") @@ -85,11 +85,12 @@ def mk_icon(source_root): shutil.copy(f"{source_root}/resources/icon.jpg", "out/content/icon.jpg") -def create_nuget_spec(version, repo, branch, commit, symbols): +def create_nuget_spec(version, repo, branch, commit, symbols, arch): + arch = f".{arch}" if arch == "x86" else "" contents = """ - Microsoft.Z3 + Microsoft.Z3{4} {0} Microsoft @@ -107,34 +108,42 @@ Linux Dependencies: true en - + -""".format(version, repo, branch, commit) +""".format(version, repo, branch, commit, arch) print(contents) sym = "sym." if symbols else "" - file = f"out/Microsoft.Z3.{sym}nuspec" + file = f"out/Microsoft.Z3{arch}.{sym}nuspec" print(file) with open(file, 'w') as f: f.write(contents) + +class Env: + def __init__(self, argv): + self.packages = argv[1] + self.version = argv[2] + self.repo = argv[3] + self.branch = argv[4] + self.commit = argv[5] + self.source_root = argv[6] + self.symbols = False + self.arch = "x64" + if len(argv) > 7 and "symbols" == argv[7]: + self.symbols = True + if len(argv) > 8: + self.arch = argv[8] + + def create(self): + mk_dir(self.packages) + unpack(self.packages, self.symbols, self.arch) + mk_targets(self.source_root) + mk_icon(self.source_root) + create_nuget_spec(self.version, self.repo, self.branch, self.commit, self.symbols, self.arch) def main(): - packages = sys.argv[1] - version = sys.argv[2] - repo = sys.argv[3] - branch = sys.argv[4] - commit = sys.argv[5] - source_root = sys.argv[6] - symbols = False - if len(sys.argv) > 7: - print(sys.argv[7]) - if len(sys.argv) > 7 and "symbols" == sys.argv[7]: - symbols = True - print(packages) - mk_dir(packages) - unpack(packages, symbols) - mk_targets(source_root) - mk_icon(source_root) - create_nuget_spec(version, repo, branch, commit, symbols) + env = Env(sys.argv) + print(env.packages) + env.create() main() diff --git a/scripts/mk_project.py b/scripts/mk_project.py index e3d181b41..c46b40f1d 100644 --- a/scripts/mk_project.py +++ b/scripts/mk_project.py @@ -8,7 +8,7 @@ from mk_util import * def init_version(): - set_version(4, 8, 14, 0) + set_version(4, 8, 15, 0) # Z3 Project definition def init_project_def(): @@ -42,7 +42,6 @@ def init_project_def(): add_lib('cmd_context', ['solver', 'rewriter', 'params']) add_lib('smt2parser', ['cmd_context', 'parser_util'], 'parsers/smt2') add_lib('pattern', ['normal_forms', 'smt2parser', 'rewriter'], 'ast/pattern') - add_lib('solver_assertions', ['pattern','smt_params','cmd_context'], 'solver/assertions') add_lib('aig_tactic', ['tactic'], 'tactic/aig') add_lib('ackermannization', ['model', 'rewriter', 'ast', 'solver', 'tactic'], 'ackermannization') add_lib('fpa', ['ast', 'util', 'rewriter', 'model'], 'ast/fpa') @@ -50,7 +49,9 @@ def init_project_def(): add_lib('core_tactics', ['tactic', 'macros', 'normal_forms', 'rewriter', 'pattern'], 'tactic/core') add_lib('arith_tactics', ['core_tactics', 'sat'], 'tactic/arith') add_lib('mbp', ['model', 'simplex'], 'qe/mbp') - add_lib('sat_smt', ['sat', 'euf', 'tactic', 'solver', 'smt_params', 'bit_blaster', 'fpa', 'mbp', 'normal_forms', 'lp', 'pattern'], 'sat/smt') + add_lib('qe_lite', ['tactic', 'mbp'], 'qe/lite') + add_lib('solver_assertions', ['pattern','smt_params','cmd_context','qe_lite'], 'solver/assertions') + add_lib('sat_smt', ['sat', 'euf', 'tactic', 'solver', 'smt_params', 'bit_blaster', 'fpa', 'mbp', 'normal_forms', 'lp', 'pattern', 'qe_lite'], 'sat/smt') add_lib('sat_tactic', ['tactic', 'sat', 'solver', 'sat_smt'], 'sat/tactic') add_lib('nlsat_tactic', ['nlsat', 'sat_tactic', 'arith_tactics'], 'nlsat/tactic') add_lib('subpaving_tactic', ['core_tactics', 'subpaving'], 'math/subpaving/tactic') @@ -62,7 +63,7 @@ def init_project_def(): add_lib('fuzzing', ['ast'], 'test/fuzzing') add_lib('smt_tactic', ['smt'], 'smt/tactic') add_lib('sls_tactic', ['tactic', 'normal_forms', 'core_tactics', 'bv_tactics'], 'tactic/sls') - add_lib('qe', ['smt', 'mbp', 'nlsat', 'tactic', 'nlsat_tactic'], 'qe') + add_lib('qe', ['smt', 'mbp', 'qe_lite', 'nlsat', 'tactic', 'nlsat_tactic'], 'qe') add_lib('sat_solver', ['solver', 'core_tactics', 'aig_tactic', 'bv_tactics', 'arith_tactics', 'sat_tactic'], 'sat/sat_solver') add_lib('fd_solver', ['core_tactics', 'arith_tactics', 'sat_solver', 'smt'], 'tactic/fd_solver') add_lib('muz', ['smt', 'sat', 'smt2parser', 'aig_tactic', 'qe'], 'muz/base') diff --git a/scripts/mk_util.py b/scripts/mk_util.py index e6d34dc3e..042e6af46 100644 --- a/scripts/mk_util.py +++ b/scripts/mk_util.py @@ -15,7 +15,7 @@ import shutil from mk_exception import * import mk_genfile_common from fnmatch import fnmatch -import distutils.sysconfig +import sysconfig import compileall import subprocess @@ -48,7 +48,7 @@ CXX_COMPILERS=['g++', 'clang++'] C_COMPILERS=['gcc', 'clang'] JAVAC=None JAR=None -PYTHON_PACKAGE_DIR=distutils.sysconfig.get_python_lib(prefix=getenv("PREFIX", None)) +PYTHON_PACKAGE_DIR=sysconfig.get_path('purelib') BUILD_DIR='build' REV_BUILD_DIR='..' SRC_DIR='src' @@ -92,7 +92,6 @@ DOTNET_CORE_ENABLED=False DOTNET_KEY_FILE=getenv("Z3_DOTNET_KEY_FILE", None) JAVA_ENABLED=False ML_ENABLED=False -JS_ENABLED=False PYTHON_INSTALL_ENABLED=False STATIC_LIB=False STATIC_BIN=False @@ -681,7 +680,7 @@ def display_help(exit_code): # Parse configuration option for mk_make script def parse_options(): global VERBOSE, DEBUG_MODE, IS_WINDOWS, VS_X64, ONLY_MAKEFILES, SHOW_CPPS, VS_PROJ, TRACE, VS_PAR, VS_PAR_NUM - global DOTNET_CORE_ENABLED, DOTNET_KEY_FILE, JAVA_ENABLED, ML_ENABLED, JS_ENABLED, STATIC_LIB, STATIC_BIN, PREFIX, GMP, PYTHON_PACKAGE_DIR, GPROF, GIT_HASH, GIT_DESCRIBE, PYTHON_INSTALL_ENABLED, PYTHON_ENABLED + global DOTNET_CORE_ENABLED, DOTNET_KEY_FILE, JAVA_ENABLED, ML_ENABLED, STATIC_LIB, STATIC_BIN, PREFIX, GMP, PYTHON_PACKAGE_DIR, GPROF, GIT_HASH, GIT_DESCRIBE, PYTHON_INSTALL_ENABLED, PYTHON_ENABLED global LINUX_X64, SLOW_OPTIMIZE, LOG_SYNC, SINGLE_THREADED global GUARD_CF, ALWAYS_DYNAMIC_BASE try: @@ -749,8 +748,6 @@ def parse_options(): GIT_DESCRIBE = True elif opt in ('', '--ml'): ML_ENABLED = True - elif opt == "--js": - JS_ENABLED = True elif opt in ('', '--log-sync'): LOG_SYNC = True elif opt == '--single-threaded': @@ -813,16 +810,6 @@ def set_build_dir(d): BUILD_DIR = norm_path(d) REV_BUILD_DIR = reverse_path(d) -def set_z3js_dir(p): - global SRC_DIR, Z3JS_SRC_DIR - p = norm_path(p) - full = os.path.join(SRC_DIR, p) - if not os.path.exists(full): - raise MKException("Python bindings directory '%s' does not exist" % full) - Z3JS_SRC_DIR = full - if VERBOSE: - print("Js bindings directory was detected.") - def set_z3py_dir(p): global SRC_DIR, Z3PY_SRC_DIR p = norm_path(p) @@ -858,9 +845,6 @@ def get_components(): def get_z3py_dir(): return Z3PY_SRC_DIR -# Return directory where the js bindings are located -def get_z3js_dir(): - return Z3JS_SRC_DIR # Return true if in verbose mode def is_verbose(): @@ -872,9 +856,6 @@ def is_java_enabled(): def is_ml_enabled(): return ML_ENABLED -def is_js_enabled(): - return JS_ENABLED - def is_dotnet_core_enabled(): return DOTNET_CORE_ENABLED @@ -1611,7 +1592,7 @@ class PythonInstallComponent(Component): os.path.join(self.pythonPkgDir,'z3'), in_prefix=self.in_prefix_install) - if PYTHON_PACKAGE_DIR != distutils.sysconfig.get_python_lib(): + if PYTHON_PACKAGE_DIR != sysconfig.get_path('purelib'): out.write('\t@echo Z3Py was installed at \'%s\', make sure this directory is in your PYTHONPATH environment variable.' % PYTHON_PACKAGE_DIR) def mk_uninstall(self, out): @@ -2692,7 +2673,7 @@ def mk_config(): print("Python pkg dir: %s" % PYTHON_PACKAGE_DIR) if GPROF: print('gprof: enabled') - print('Python version: %s' % distutils.sysconfig.get_python_version()) + print('Python version: %s' % sysconfig.get_python_version()) if is_java_enabled(): print('JNI Bindings: %s' % JNI_HOME) print('Java Compiler: %s' % JAVAC) @@ -3025,17 +3006,14 @@ def mk_bindings(api_files): ml_output_dir = None if is_ml_enabled(): ml_output_dir = get_component('ml').src_dir - if is_js_enabled(): - set_z3js_dir("api/js") - js_output_dir = get_component('js').src_dir # Get the update_api module to do the work for us + update_api.VERBOSE = is_verbose() update_api.generate_files(api_files=new_api_files, api_output_dir=get_component('api').src_dir, z3py_output_dir=get_z3py_dir(), dotnet_output_dir=dotnet_output_dir, java_output_dir=java_output_dir, java_package_name=java_package_name, - js_output_dir=get_z3js_dir(), ml_output_dir=ml_output_dir, ml_src_dir=ml_output_dir ) diff --git a/scripts/nightly.yaml b/scripts/nightly.yaml index d26f77934..08b3501a0 100644 --- a/scripts/nightly.yaml +++ b/scripts/nightly.yaml @@ -1,5 +1,5 @@ variables: - ReleaseVersion: '4.8.13' + ReleaseVersion: '4.8.15' stages: - stage: Build @@ -132,8 +132,8 @@ stages: - stage: Package jobs: - - job: NuGet - displayName: "NuGet packaging" + - job: NuGet64 + displayName: "NuGet 64 packaging" pool: vmImage: "windows-latest" steps: @@ -148,11 +148,6 @@ stages: inputs: artifact: 'Windows64' path: $(Agent.TempDirectory)\package - - task: DownloadPipelineArtifact@2 - displayName: 'Download Win32 Build' - inputs: - artifact: 'Windows32' - path: $(Agent.TempDirectory)\package - task: DownloadPipelineArtifact@2 displayName: 'Download Ubuntu Build' inputs: @@ -245,6 +240,105 @@ stages: targetPath: $(Build.ArtifactStagingDirectory) artifactName: 'NuGet' + - job: NuGet32 + displayName: "NuGet 32 packaging" + pool: + vmImage: "windows-latest" + steps: + - powershell: write-host $(System.DefinitionId) + displayName: 'System.DefinitionId' + - powershell: write-host $(Build.BuildId) + displayName: 'Build.BuildId' + - powershell: write-host $(System.TeamProjectId) + displayName: 'System.TeamProjectId' + - task: DownloadPipelineArtifact@2 + displayName: 'Download Win32 Build' + inputs: + artifact: 'Windows32' + path: $(Agent.TempDirectory)\package + - task: NuGetToolInstaller@0 + inputs: + versionSpec: 5.x + checkLatest: false + - task: PythonScript@0 + displayName: 'Python: assemble files' + inputs: + scriptSource: 'filepath' + scriptPath: scripts\mk_nuget_task.py + workingDirectory: $(Agent.TempDirectory)\package + arguments: + $(Agent.TempDirectory)\package + $(ReleaseVersion) + $(Build.Repository.Uri) + $(Build.SourceBranchName) + $(Build.SourceVersion) + $(Build.SourcesDirectory) + symbols + x86 + - task: NugetCommand@2 + displayName: 'NuGet Pack Symbols' + inputs: + command: custom + arguments: 'pack $(Agent.TempDirectory)\package\out\Microsoft.Z3.x86.sym.nuspec -OutputDirectory $(Build.ArtifactStagingDirectory) -Verbosity detailed -Symbols -SymbolPackageFormat snupkg -BasePath $(Agent.TempDirectory)\package\out' + - task: EsrpCodeSigning@1 + displayName: 'Sign Package' + inputs: + ConnectedServiceName: 'z3-esrp-signing-2' + FolderPath: $(Build.ArtifactStagingDirectory) + Pattern: Microsoft.Z3.x86.$(ReleaseVersion).nupkg + signConfigType: 'inlineSignParams' + inlineOperation: | + [ + { + "KeyCode" : "CP-401405", + "OperationCode" : "NuGetSign", + "Parameters" : {}, + "ToolName" : "sign", + "ToolVersion" : "1.0" + }, + { + "KeyCode" : "CP-401405", + "OperationCode" : "NuGetVerify", + "Parameters" : {}, + "ToolName" : "sign", + "ToolVersion" : "1.0" + } + ] + SessionTimeout: '60' + MaxConcurrency: '50' + MaxRetryAttempts: '5' + - task: EsrpCodeSigning@1 + displayName: 'Sign Symbol Package' + inputs: + ConnectedServiceName: 'z3-esrp-signing-2' + FolderPath: $(Build.ArtifactStagingDirectory) + Pattern: Microsoft.Z3.x86.$(ReleaseVersion).snupkg + signConfigType: 'inlineSignParams' + inlineOperation: | + [ + { + "KeyCode" : "CP-401405", + "OperationCode" : "NuGetSign", + "Parameters" : {}, + "ToolName" : "sign", + "ToolVersion" : "1.0" + }, + { + "KeyCode" : "CP-401405", + "OperationCode" : "NuGetVerify", + "Parameters" : {}, + "ToolName" : "sign", + "ToolVersion" : "1.0" + } + ] + SessionTimeout: '60' + MaxConcurrency: '50' + MaxRetryAttempts: '5' + - task: PublishPipelineArtifact@1 + inputs: + targetPath: $(Build.ArtifactStagingDirectory) + artifactName: 'NuGet32' + - job: Python displayName: "Python packaging" @@ -287,6 +381,8 @@ stages: jobs: - job: Deploy displayName: "Deploy into GitHub" + pool: + vmImage: "ubuntu-latest" steps: - task: DownloadPipelineArtifact@2 displayName: "Download windows32" @@ -323,6 +419,11 @@ stages: inputs: artifactName: 'NuGet' targetPath: tmp + - task: DownloadPipelineArtifact@2 + displayName: "Download NuGet32" + inputs: + artifactName: 'NuGet32' + targetPath: tmp - task: GitHubRelease@0 inputs: gitHubConnection: Z3GitHub diff --git a/scripts/release.yml b/scripts/release.yml index 9a1982744..8e69be9b0 100644 --- a/scripts/release.yml +++ b/scripts/release.yml @@ -6,7 +6,7 @@ trigger: none variables: - ReleaseVersion: '4.8.13' + ReleaseVersion: '4.8.15' stages: @@ -171,6 +171,11 @@ stages: inputs: artifact: 'WindowsBuild-x64' path: $(Agent.TempDirectory)\package + - task: DownloadPipelineArtifact@2 + displayName: 'Download Win32 Build' + inputs: + artifact: 'WindowsBuild-x86' + path: $(Agent.TempDirectory)\package - task: DownloadPipelineArtifact@2 displayName: 'Download Ubuntu Build' inputs: @@ -309,7 +314,7 @@ stages: jobs: - job: GitHubPublish - condition: eq(0,1) + condition: eq(1,1) displayName: "Publish to GitHub" pool: vmImage: "windows-latest" diff --git a/scripts/update_api.py b/scripts/update_api.py index 6392e439e..2daf0a7a7 100755 --- a/scripts/update_api.py +++ b/scripts/update_api.py @@ -15,7 +15,7 @@ several API header files. It can also optionally emit some of the files required for Z3's different language bindings. """ -import mk_util + import mk_exception import argparse import logging @@ -23,6 +23,10 @@ import re import os import sys +VERBOSE = True +def is_verbose(): + return VERBOSE + ########################################################## # TODO: rewrite this file without using global variables. # This file is a big HACK. @@ -78,42 +82,44 @@ Type2Dotnet = { VOID : 'void', VOID_PTR : 'IntPtr', INT : 'int', UINT : 'uint', FLOAT : 'float', STRING : 'string', STRING_PTR : 'byte**', BOOL : 'byte', SYMBOL : 'IntPtr', PRINT_MODE : 'uint', ERROR_CODE : 'uint', CHAR : 'char', CHAR_PTR : 'IntPtr' } -# Mapping to Java types -Type2Java = { VOID : 'void', VOID_PTR : 'long', INT : 'int', UINT : 'int', INT64 : 'long', UINT64 : 'long', DOUBLE : 'double', - FLOAT : 'float', STRING : 'String', STRING_PTR : 'StringPtr', - BOOL : 'boolean', SYMBOL : 'long', PRINT_MODE : 'int', ERROR_CODE : 'int', CHAR : 'char', CHAR_PTR : 'long' } - -Type2JavaW = { VOID : 'void', VOID_PTR : 'jlong', INT : 'jint', UINT : 'jint', INT64 : 'jlong', UINT64 : 'jlong', DOUBLE : 'jdouble', - FLOAT : 'jfloat', STRING : 'jstring', STRING_PTR : 'jobject', - BOOL : 'jboolean', SYMBOL : 'jlong', PRINT_MODE : 'jint', ERROR_CODE : 'jint', CHAR : 'jchar', CHAR_PTR : 'jlong'} # Mapping to ML types Type2ML = { VOID : 'unit', VOID_PTR : 'VOIDP', INT : 'int', UINT : 'int', INT64 : 'int', UINT64 : 'int', DOUBLE : 'float', FLOAT : 'float', STRING : 'string', STRING_PTR : 'char**', BOOL : 'bool', SYMBOL : 'z3_symbol', PRINT_MODE : 'int', ERROR_CODE : 'int', CHAR : 'char', CHAR_PTR : 'string' } -next_type_id = FIRST_OBJ_ID +class APITypes: + def __init__(self): + self.next_type_id = FIRST_OBJ_ID -def def_Type(var, c_type, py_type): - global next_type_id - exec('%s = %s' % (var, next_type_id), globals()) - Type2Str[next_type_id] = c_type - Type2PyStr[next_type_id] = py_type - next_type_id = next_type_id + 1 - -def def_Types(api_files): - pat1 = re.compile(" *def_Type\(\'(.*)\',[^\']*\'(.*)\',[^\']*\'(.*)\'\)[ \t]*") - for api_file in api_files: - api = open(api_file, 'r') - for line in api: - m = pat1.match(line) - if m: - def_Type(m.group(1), m.group(2), m.group(3)) - for k in Type2Str: - v = Type2Str[k] - if is_obj(k): - Type2Dotnet[k] = v - Type2ML[k] = v.lower() + def def_Type(self, var, c_type, py_type): + """Process type definitions of the form def_Type(var, c_type, py_type) + The variable 'var' is set to a unique number and recorded globally using exec + It is used by 'def_APIs' to that uses the unique numbers to access the + corresponding C and Python types. + """ + id = self.next_type_id + exec('%s = %s' % (var, id), globals()) + Type2Str[id] = c_type + Type2PyStr[id] = py_type + self.next_type_id += 1 + + def def_Types(self, api_files): + pat1 = re.compile(" *def_Type\(\'(.*)\',[^\']*\'(.*)\',[^\']*\'(.*)\'\)[ \t]*") + for api_file in api_files: + with open(api_file, 'r') as api: + for line in api: + m = pat1.match(line) + if m: + self.def_Type(m.group(1), m.group(2), m.group(3)) + # + # Populate object type entries in dotnet and ML bindings. + # + for k in Type2Str: + v = Type2Str[k] + if is_obj(k): + Type2Dotnet[k] = v + Type2ML[k] = v.lower() def type2str(ty): global Type2Str @@ -127,20 +133,6 @@ def type2dotnet(ty): global Type2Dotnet return Type2Dotnet[ty] -def type2java(ty): - global Type2Java - if (ty >= FIRST_OBJ_ID): - return 'long' - else: - return Type2Java[ty] - -def type2javaw(ty): - global Type2JavaW - if (ty >= FIRST_OBJ_ID): - return 'jlong' - else: - return Type2JavaW[ty] - def type2ml(ty): global Type2ML q = Type2ML[ty] @@ -214,42 +206,8 @@ def param2dotnet(p): else: return type2dotnet(param_type(p)) -def param2java(p): - k = param_kind(p) - if k == OUT: - if param_type(p) == INT or param_type(p) == UINT: - return "IntPtr" - elif param_type(p) == INT64 or param_type(p) == UINT64 or param_type(p) == VOID_PTR or param_type(p) >= FIRST_OBJ_ID: - return "LongPtr" - elif param_type(p) == STRING: - return "StringPtr" - else: - print("ERROR: unreachable code") - assert(False) - exit(1) - elif k == IN_ARRAY or k == INOUT_ARRAY or k == OUT_ARRAY: - return "%s[]" % type2java(param_type(p)) - elif k == OUT_MANAGED_ARRAY: - if param_type(p) == UINT: - return "UIntArrayPtr" - else: - return "ObjArrayPtr" - else: - return type2java(param_type(p)) -def param2javaw(p): - k = param_kind(p) - if k == OUT: - return "jobject" - elif k == IN_ARRAY or k == INOUT_ARRAY or k == OUT_ARRAY: - if param_type(p) == INT or param_type(p) == UINT or param_type(p) == BOOL: - return "jintArray" - else: - return "jlongArray" - elif k == OUT_MANAGED_ARRAY: - return "jlong" - else: - return type2javaw(param_type(p)) +# -------------- def param2pystr(p): if param_kind(p) == IN_ARRAY or param_kind(p) == OUT_ARRAY or param_kind(p) == IN_ARRAY or param_kind(p) == INOUT_ARRAY or param_kind(p) == OUT: @@ -257,6 +215,9 @@ def param2pystr(p): else: return type2pystr(param_type(p)) +# -------------- +# ML + def param2ml(p): k = param_kind(p) if k == OUT: @@ -507,6 +468,68 @@ def mk_dotnet_wrappers(dotnet): dotnet.write(" }\n\n") dotnet.write("}\n\n") +# ---------------------- +# Java + +Type2Java = { VOID : 'void', VOID_PTR : 'long', INT : 'int', UINT : 'int', INT64 : 'long', UINT64 : 'long', DOUBLE : 'double', + FLOAT : 'float', STRING : 'String', STRING_PTR : 'StringPtr', + BOOL : 'boolean', SYMBOL : 'long', PRINT_MODE : 'int', ERROR_CODE : 'int', CHAR : 'char', CHAR_PTR : 'long' } + +Type2JavaW = { VOID : 'void', VOID_PTR : 'jlong', INT : 'jint', UINT : 'jint', INT64 : 'jlong', UINT64 : 'jlong', DOUBLE : 'jdouble', + FLOAT : 'jfloat', STRING : 'jstring', STRING_PTR : 'jobject', + BOOL : 'jboolean', SYMBOL : 'jlong', PRINT_MODE : 'jint', ERROR_CODE : 'jint', CHAR : 'jchar', CHAR_PTR : 'jlong'} + +def type2java(ty): + global Type2Java + if (ty >= FIRST_OBJ_ID): + return 'long' + else: + return Type2Java[ty] + +def type2javaw(ty): + global Type2JavaW + if (ty >= FIRST_OBJ_ID): + return 'jlong' + else: + return Type2JavaW[ty] + +def param2java(p): + k = param_kind(p) + if k == OUT: + if param_type(p) == INT or param_type(p) == UINT: + return "IntPtr" + elif param_type(p) == INT64 or param_type(p) == UINT64 or param_type(p) == VOID_PTR or param_type(p) >= FIRST_OBJ_ID: + return "LongPtr" + elif param_type(p) == STRING: + return "StringPtr" + else: + print("ERROR: unreachable code") + assert(False) + exit(1) + elif k == IN_ARRAY or k == INOUT_ARRAY or k == OUT_ARRAY: + return "%s[]" % type2java(param_type(p)) + elif k == OUT_MANAGED_ARRAY: + if param_type(p) == UINT: + return "UIntArrayPtr" + else: + return "ObjArrayPtr" + else: + return type2java(param_type(p)) + +def param2javaw(p): + k = param_kind(p) + if k == OUT: + return "jobject" + elif k == IN_ARRAY or k == INOUT_ARRAY or k == OUT_ARRAY: + if param_type(p) == INT or param_type(p) == UINT or param_type(p) == BOOL: + return "jintArray" + else: + return "jlongArray" + elif k == OUT_MANAGED_ARRAY: + return "jlong" + else: + return type2javaw(param_type(p)) + def java_method_name(name): result = '' name = name[3:] # Remove Z3_ @@ -776,62 +799,9 @@ def mk_java(java_dir, package_name): java_wrapper.write('#ifdef __cplusplus\n') java_wrapper.write('}\n') java_wrapper.write('#endif\n') - if mk_util.is_verbose(): + if is_verbose(): print("Generated '%s'" % java_nativef) - -Type2Napi = { VOID : '', VOID_PTR : '', INT : 'number', UINT : 'number', INT64 : 'number', UINT64 : 'number', DOUBLE : 'number', - FLOAT : 'number', STRING : 'string', STRING_PTR : 'array', - BOOL : 'number', SYMBOL : 'external', PRINT_MODE : 'number', ERROR_CODE : 'number', CHAR : 'number' } - -def type2napi(t): - try: - return Type2Napi[t] - except: - return "external" - -Type2NapiBuilder = { VOID : '', VOID_PTR : '', INT : 'int32', UINT : 'uint32', INT64 : 'int64', UINT64 : 'uint64', DOUBLE : 'double', - FLOAT : 'float', STRING : 'string', STRING_PTR : 'array', - BOOL : 'bool', SYMBOL : 'external', PRINT_MODE : 'int32', ERROR_CODE : 'int32', CHAR : 'char' } - -def type2napibuilder(t): - try: - return Type2NapiBuilder[t] - except: - return "external" - - -def mk_js(js_output_dir): - with open(os.path.join(js_output_dir, "z3.json"), 'w') as ous: - ous.write("{\n") - ous.write(" \"api\": [\n") - for name, result, params in _dotnet_decls: - ous.write(" {\n") - ous.write(" \"name\": \"%s\",\n" % name) - ous.write(" \"c_type\": \"%s\",\n" % Type2Str[result]) - ous.write(" \"napi_type\": \"%s\",\n" % type2napi(result)) - ous.write(" \"arg_list\": [") - first = True - for p in params: - if first: - first = False - ous.write("\n {\n") - else: - ous.write(",\n {\n") - t = param_type(p) - k = t - ous.write(" \"name\": \"%s\",\n" % "") # TBD - ous.write(" \"c_type\": \"%s\",\n" % type2str(t)) - ous.write(" \"napi_type\": \"%s\",\n" % type2napi(t)) - ous.write(" \"napi_builder\": \"%s\"\n" % type2napibuilder(t)) - ous.write( " }") - ous.write("],\n") - ous.write(" \"napi_builder\": \"%s\"\n" % type2napibuilder(result)) - ous.write(" },\n") - ous.write(" ]\n") - ous.write("}\n") - - def mk_log_header(file, name, params): file.write("void log_%s(" % name) i = 0 @@ -842,6 +812,10 @@ def mk_log_header(file, name, params): i = i + 1 file.write(")") +# --------------------------------- +# Logging + + def log_param(p): kind = param_kind(p) ty = param_type(p) @@ -1364,7 +1338,7 @@ def mk_ml(ml_src_dir, ml_output_dir): ml_native.write('(**/**)\n') ml_native.close() - if mk_util.is_verbose(): + if is_verbose(): print ('Generated "%s"' % ml_nativef) mk_z3native_stubs_c(ml_src_dir, ml_output_dir) @@ -1689,7 +1663,7 @@ def mk_z3native_stubs_c(ml_src_dir, ml_output_dir): # C interface ml_wrapper.write('}\n') ml_wrapper.write('#endif\n') - if mk_util.is_verbose(): + if is_verbose(): print ('Generated "%s"' % ml_wrapperf) # Collect API(...) commands from @@ -1889,7 +1863,6 @@ def generate_files(api_files, dotnet_output_dir=None, java_output_dir=None, java_package_name=None, - js_output_dir=None, ml_output_dir=None, ml_src_dir=None): """ @@ -1933,6 +1906,7 @@ def generate_files(api_files, import tempfile return tempfile.TemporaryFile(mode=mode) + apiTypes = APITypes() with mk_file_or_temp(api_output_dir, 'api_log_macros.h') as log_h: with mk_file_or_temp(api_output_dir, 'api_log_macros.cpp') as log_c: with mk_file_or_temp(api_output_dir, 'api_commands.cpp') as exe_c: @@ -1944,13 +1918,13 @@ def generate_files(api_files, write_core_py_preamble(core_py) # FIXME: these functions are awful - def_Types(api_files) + apiTypes.def_Types(api_files) def_APIs(api_files) mk_bindings(exe_c) mk_py_wrappers() write_core_py_post(core_py) - if mk_util.is_verbose(): + if is_verbose(): print("Generated '{}'".format(log_h.name)) print("Generated '{}'".format(log_c.name)) print("Generated '{}'".format(exe_c.name)) @@ -1960,7 +1934,7 @@ def generate_files(api_files, with open(os.path.join(dotnet_output_dir, 'Native.cs'), 'w') as dotnet_file: mk_dotnet(dotnet_file) mk_dotnet_wrappers(dotnet_file) - if mk_util.is_verbose(): + if is_verbose(): print("Generated '{}'".format(dotnet_file.name)) if java_output_dir: @@ -1970,8 +1944,6 @@ def generate_files(api_files, assert not ml_src_dir is None mk_ml(ml_src_dir, ml_output_dir) - if js_output_dir: - mk_js(js_output_dir) def main(args): logging.basicConfig(level=logging.INFO) @@ -2006,10 +1978,6 @@ def main(args): dest="ml_output_dir", default=None, help="Directory to emit OCaml files. If not specified no files are emitted.") - parser.add_argument("--js_output_dir", - dest="js_output_dir", - default=None, - help="Directory to emit js bindings. If not specified no files are emitted.") pargs = parser.parse_args(args) if pargs.java_output_dir: @@ -2033,7 +2001,6 @@ def main(args): dotnet_output_dir=pargs.dotnet_output_dir, java_output_dir=pargs.java_output_dir, java_package_name=pargs.java_package_name, - js_output_dir=pargs.js_output_dir, ml_output_dir=pargs.ml_output_dir, ml_src_dir=pargs.ml_src_dir) return 0 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 88b149e0f..355277aef 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -67,11 +67,12 @@ add_subdirectory(solver) add_subdirectory(cmd_context) add_subdirectory(cmd_context/extra_cmds) add_subdirectory(parsers/smt2) +add_subdirectory(qe/mbp) +add_subdirectory(qe/lite) add_subdirectory(solver/assertions) add_subdirectory(ast/pattern) add_subdirectory(ast/rewriter/bit_blaster) add_subdirectory(math/lp) -add_subdirectory(qe/mbp) add_subdirectory(sat/smt) add_subdirectory(sat/tactic) add_subdirectory(nlsat/tactic) diff --git a/src/api/api_ast.cpp b/src/api/api_ast.cpp index 7f67879bc..87e218d99 100644 --- a/src/api/api_ast.cpp +++ b/src/api/api_ast.cpp @@ -1187,6 +1187,9 @@ extern "C" { case OP_SEQ_CONTAINS: return Z3_OP_SEQ_CONTAINS; case OP_SEQ_EXTRACT: return Z3_OP_SEQ_EXTRACT; case OP_SEQ_REPLACE: return Z3_OP_SEQ_REPLACE; + case OP_SEQ_REPLACE_RE: return Z3_OP_SEQ_REPLACE_RE; + case OP_SEQ_REPLACE_RE_ALL: return Z3_OP_SEQ_REPLACE_RE_ALL; + case OP_SEQ_REPLACE_ALL: return Z3_OP_SEQ_REPLACE_ALL; case OP_SEQ_AT: return Z3_OP_SEQ_AT; case OP_SEQ_NTH: return Z3_OP_SEQ_NTH; case OP_SEQ_LENGTH: return Z3_OP_SEQ_LENGTH; @@ -1210,6 +1213,9 @@ extern "C" { case OP_STRING_STOI: return Z3_OP_STR_TO_INT; case OP_STRING_ITOS: return Z3_OP_INT_TO_STR; + case OP_STRING_TO_CODE: return Z3_OP_STR_TO_CODE; + case OP_STRING_FROM_CODE: return Z3_OP_STR_FROM_CODE; + case OP_STRING_UBVTOS: return Z3_OP_UBV_TO_STR; case OP_STRING_SBVTOS: return Z3_OP_SBV_TO_STR; case OP_STRING_LT: return Z3_OP_STRING_LT; @@ -1220,9 +1226,11 @@ extern "C" { case OP_RE_OPTION: return Z3_OP_RE_OPTION; case OP_RE_CONCAT: return Z3_OP_RE_CONCAT; case OP_RE_UNION: return Z3_OP_RE_UNION; + case OP_RE_DIFF: return Z3_OP_RE_DIFF; + case OP_RE_POWER: return Z3_OP_RE_POWER; case OP_RE_INTERSECT: return Z3_OP_RE_INTERSECT; case OP_RE_LOOP: return Z3_OP_RE_LOOP; - case OP_RE_FULL_SEQ_SET: return Z3_OP_RE_FULL_SET; + case OP_RE_FULL_SEQ_SET: return Z3_OP_RE_FULL_SET; //case OP_RE_FULL_CHAR_SET: return Z3_OP_RE_FULL_SET; case OP_RE_EMPTY_SET: return Z3_OP_RE_EMPTY_SET; default: diff --git a/src/api/api_config_params.cpp b/src/api/api_config_params.cpp index fd7c94f22..1b551bd73 100644 --- a/src/api/api_config_params.cpp +++ b/src/api/api_config_params.cpp @@ -47,7 +47,7 @@ extern "C" { env_params::updt_params(); } - Z3_bool_opt Z3_API Z3_global_param_get(Z3_string param_id, Z3_string_ptr param_value) { + Z3_bool Z3_API Z3_global_param_get(Z3_string param_id, Z3_string_ptr param_value) { memory::initialize(UINT_MAX); LOG_Z3_global_param_get(param_id, param_value); *param_value = nullptr; diff --git a/src/api/api_datalog.cpp b/src/api/api_datalog.cpp index 3a6e8e56f..8c6227d7d 100644 --- a/src/api/api_datalog.cpp +++ b/src/api/api_datalog.cpp @@ -585,7 +585,7 @@ extern "C" { to_fixedpoint_ref(d)->collect_param_descrs(descrs); to_params(p)->m_params.validate(descrs); to_fixedpoint_ref(d)->updt_params(to_param_ref(p)); - to_fixedpoint(d)->m_params = to_param_ref(p); + to_fixedpoint(d)->m_params.append(to_param_ref(p)); Z3_CATCH; } diff --git a/src/api/api_qe.cpp b/src/api/api_qe.cpp index e3c51c7b5..d1e5d195b 100644 --- a/src/api/api_qe.cpp +++ b/src/api/api_qe.cpp @@ -26,7 +26,7 @@ Notes: #include "api/api_model.h" #include "api/api_ast_map.h" #include "api/api_ast_vector.h" -#include "qe/qe_lite.h" +#include "qe/lite/qe_lite.h" #include "muz/spacer/spacer_util.h" extern "C" diff --git a/src/api/api_seq.cpp b/src/api/api_seq.cpp index a7eb237b3..a7c42df4f 100644 --- a/src/api/api_seq.cpp +++ b/src/api/api_seq.cpp @@ -55,7 +55,6 @@ extern "C" { Z3_CATCH_RETURN(nullptr); } - Z3_ast Z3_API Z3_mk_lstring(Z3_context c, unsigned sz, Z3_string str) { Z3_TRY; LOG_Z3_mk_lstring(c, sz, str); @@ -80,6 +79,16 @@ extern "C" { Z3_CATCH_RETURN(nullptr); } + Z3_ast Z3_API Z3_mk_char(Z3_context c, unsigned ch) { + Z3_TRY; + LOG_Z3_mk_char(c, ch); + RESET_ERROR_CODE(); + app* a = mk_c(c)->sutil().str.mk_char(ch); + mk_c(c)->save_ast_trail(a); + RETURN_Z3(of_ast(a)); + Z3_CATCH_RETURN(nullptr); + } + Z3_sort Z3_API Z3_mk_string_sort(Z3_context c) { Z3_TRY; LOG_Z3_mk_string_sort(c); @@ -277,6 +286,8 @@ extern "C" { MK_BINARY(Z3_mk_seq_contains, mk_c(c)->get_seq_fid(), OP_SEQ_CONTAINS, SKIP); MK_BINARY(Z3_mk_str_lt, mk_c(c)->get_seq_fid(), OP_STRING_LT, SKIP); MK_BINARY(Z3_mk_str_le, mk_c(c)->get_seq_fid(), OP_STRING_LE, SKIP); + MK_UNARY(Z3_mk_string_to_code, mk_c(c)->get_seq_fid(), OP_STRING_TO_CODE, SKIP); + MK_UNARY(Z3_mk_string_from_code, mk_c(c)->get_seq_fid(), OP_STRING_FROM_CODE, SKIP); MK_TERNARY(Z3_mk_seq_extract, mk_c(c)->get_seq_fid(), OP_SEQ_EXTRACT, SKIP); MK_TERNARY(Z3_mk_seq_replace, mk_c(c)->get_seq_fid(), OP_SEQ_REPLACE, SKIP); @@ -308,6 +319,7 @@ extern "C" { MK_UNARY(Z3_mk_re_star, mk_c(c)->get_seq_fid(), OP_RE_STAR, SKIP); MK_UNARY(Z3_mk_re_option, mk_c(c)->get_seq_fid(), OP_RE_OPTION, SKIP); MK_UNARY(Z3_mk_re_complement, mk_c(c)->get_seq_fid(), OP_RE_COMPLEMENT, SKIP); + MK_BINARY(Z3_mk_re_diff, mk_c(c)->get_seq_fid(), OP_RE_DIFF, SKIP); MK_NARY(Z3_mk_re_union, mk_c(c)->get_seq_fid(), OP_RE_UNION, SKIP); MK_NARY(Z3_mk_re_intersect, mk_c(c)->get_seq_fid(), OP_RE_INTERSECT, SKIP); MK_NARY(Z3_mk_re_concat, mk_c(c)->get_seq_fid(), OP_RE_CONCAT, SKIP); diff --git a/src/api/api_solver.cpp b/src/api/api_solver.cpp index ea009b1bc..8e8360cd3 100644 --- a/src/api/api_solver.cpp +++ b/src/api/api_solver.cpp @@ -944,7 +944,7 @@ extern "C" { Z3_TRY; LOG_Z3_solver_propagate_register(c, s, e); RESET_ERROR_CODE(); - return to_solver_ref(s)->user_propagate_register(to_expr(e)); + return to_solver_ref(s)->user_propagate_register_expr(to_expr(e)); Z3_CATCH_RETURN(0); } @@ -964,4 +964,28 @@ extern "C" { Z3_CATCH; } + void Z3_API Z3_solver_propagate_created(Z3_context c, Z3_solver s, Z3_created_eh created_eh) { + Z3_TRY; + RESET_ERROR_CODE(); + user_propagator::created_eh_t c = (void(*)(void*, user_propagator::callback*, expr*, unsigned))created_eh; + to_solver_ref(s)->user_propagate_register_created(c); + Z3_CATCH; + } + + Z3_func_decl Z3_API Z3_solver_propagate_declare(Z3_context c, Z3_symbol name, unsigned n, Z3_sort* domain, Z3_sort range) { + Z3_TRY; + LOG_Z3_solver_propagate_declare(c, name, n, domain, range); + RESET_ERROR_CODE(); + ast_manager& m = mk_c(c)->m(); + family_id fid = m.mk_family_id(user_propagator::plugin::name()); + if (!m.has_plugin(fid)) + m.register_plugin(fid, alloc(user_propagator::plugin)); + func_decl_info info(fid, user_propagator::plugin::kind_t::OP_USER_PROPAGATE); + func_decl* f = m.mk_func_decl(to_symbol(name), n, to_sorts(domain), to_sort(range), info); + mk_c(c)->save_ast_trail(f); + RETURN_Z3(of_func_decl(f)); + Z3_CATCH_RETURN(nullptr); + } + + }; diff --git a/src/api/c++/z3++.h b/src/api/c++/z3++.h index 95c09e47a..80432450d 100644 --- a/src/api/c++/z3++.h +++ b/src/api/c++/z3++.h @@ -334,6 +334,7 @@ namespace z3 { func_decl recfun(char const * name, sort const & d1, sort const & d2, sort const & range); void recdef(func_decl, expr_vector const& args, expr const& body); + func_decl user_propagate_function(symbol const& name, sort_vector const& domain, sort const& range); expr constant(symbol const & name, sort const & s); expr constant(char const * name, sort const & s); @@ -700,6 +701,9 @@ namespace z3 { sort array_range() const { assert(is_array()); Z3_sort s = Z3_get_array_sort_range(ctx(), *this); check_error(); return sort(ctx(), s); } friend std::ostream & operator<<(std::ostream & out, sort const & s) { return out << Z3_sort_to_string(s.ctx(), Z3_sort(s.m_ast)); } + + func_decl_vector constructors(); + func_decl_vector recognizers(); }; /** @@ -740,6 +744,9 @@ namespace z3 { expr operator()(expr const & a1, expr const & a2, expr const & a3) const; expr operator()(expr const & a1, expr const & a2, expr const & a3, expr const & a4) const; expr operator()(expr const & a1, expr const & a2, expr const & a3, expr const & a4, expr const & a5) const; + + func_decl_vector accessors(); + }; /** @@ -834,7 +841,7 @@ namespace z3 { double as_double() const { double d = 0; is_numeral(d); return d; } uint64_t as_uint64() const { uint64_t r = 0; is_numeral_u64(r); return r; } - uint64_t as_int64() const { int64_t r = 0; is_numeral_i64(r); return r; } + int64_t as_int64() const { int64_t r = 0; is_numeral_i64(r); return r; } /** @@ -1333,9 +1340,9 @@ namespace z3 { friend expr bvmul_no_overflow(expr const& a, expr const& b, bool is_signed); friend expr bvmul_no_underflow(expr const& a, expr const& b); - expr rotate_left(unsigned i) { Z3_ast r = Z3_mk_rotate_left(ctx(), i, *this); ctx().check_error(); return expr(ctx(), r); } - expr rotate_right(unsigned i) { Z3_ast r = Z3_mk_rotate_right(ctx(), i, *this); ctx().check_error(); return expr(ctx(), r); } - expr repeat(unsigned i) { Z3_ast r = Z3_mk_repeat(ctx(), i, *this); ctx().check_error(); return expr(ctx(), r); } + expr rotate_left(unsigned i) const { Z3_ast r = Z3_mk_rotate_left(ctx(), i, *this); ctx().check_error(); return expr(ctx(), r); } + expr rotate_right(unsigned i) const { Z3_ast r = Z3_mk_rotate_right(ctx(), i, *this); ctx().check_error(); return expr(ctx(), r); } + expr repeat(unsigned i) const { Z3_ast r = Z3_mk_repeat(ctx(), i, *this); ctx().check_error(); return expr(ctx(), r); } friend expr bvredor(expr const & a); friend expr bvredand(expr const & a); @@ -3424,6 +3431,14 @@ namespace z3 { Z3_add_rec_def(f.ctx(), f, vars.size(), vars.ptr(), body); } + inline func_decl context::user_propagate_function(symbol const& name, sort_vector const& domain, sort const& range) { + check_context(domain, range); + array domain1(domain); + Z3_func_decl f = Z3_solver_propagate_declare(range.ctx(), name, domain1.size(), domain1.ptr(), range); + check_error(); + return func_decl(*this, f); + } + inline expr context::constant(symbol const & name, sort const & s) { Z3_ast r = Z3_mk_const(m_ctx, name, s); check_error(); @@ -3788,6 +3803,13 @@ namespace z3 { ctx.check_error(); return expr(ctx, r); } + inline expr re_diff(expr const& a, expr const& b) { + check_context(a, b); + context& ctx = a.ctx(); + Z3_ast r = Z3_mk_re_diff(ctx, a, b); + ctx.check_error(); + return expr(ctx, r); + } inline expr re_complement(expr const& a) { MK_EXPR1(Z3_mk_re_complement, a); } @@ -3847,6 +3869,42 @@ namespace z3 { return expr_vector(*this, r); } + inline func_decl_vector sort::constructors() { + assert(is_datatype()); + func_decl_vector cs(ctx()); + unsigned n = Z3_get_datatype_sort_num_constructors(ctx(), *this); + for (unsigned i = 0; i < n; ++i) + cs.push_back(func_decl(ctx(), Z3_get_datatype_sort_constructor(ctx(), *this, i))); + return cs; + } + + inline func_decl_vector sort::recognizers() { + assert(is_datatype()); + func_decl_vector rs(ctx()); + unsigned n = Z3_get_datatype_sort_num_constructors(ctx(), *this); + for (unsigned i = 0; i < n; ++i) + rs.push_back(func_decl(ctx(), Z3_get_datatype_sort_recognizer(ctx(), *this, i))); + return rs; + } + + inline func_decl_vector func_decl::accessors() { + sort s = range(); + assert(s.is_datatype()); + unsigned n = Z3_get_datatype_sort_num_constructors(ctx(), s); + unsigned idx = 0; + for (; idx < n; ++idx) { + func_decl f(ctx(), Z3_get_datatype_sort_constructor(ctx(), s, idx)); + if (id() == f.id()) + break; + } + assert(idx < n); + n = arity(); + func_decl_vector as(ctx()); + for (unsigned i = 0; i < n; ++i) + as.push_back(func_decl(ctx(), Z3_get_datatype_sort_constructor_accessor(ctx(), s, idx, i))); + return as; + } + inline expr expr::substitute(expr_vector const& src, expr_vector const& dst) { assert(src.size() == dst.size()); @@ -3877,10 +3935,12 @@ namespace z3 { typedef std::function fixed_eh_t; typedef std::function final_eh_t; typedef std::function eq_eh_t; + typedef std::function created_eh_t; final_eh_t m_final_eh; eq_eh_t m_eq_eh; fixed_eh_t m_fixed_eh; + created_eh_t m_created_eh; solver* s; Z3_context c; Z3_solver_callback cb { nullptr }; @@ -3929,6 +3989,14 @@ namespace z3 { static_cast(p)->m_final_eh(); } + static void created_eh(void* _p, Z3_solver_callback cb, Z3_ast _e, unsigned id) { + user_propagator_base* p = static_cast(_p); + scoped_cb _cb(p, cb); + scoped_context ctx(p->ctx()); + expr e(ctx(), _e); + static_cast(p)->m_created_eh(id, e); + } + public: user_propagator_base(Z3_context c) : s(nullptr), c(c) {} @@ -4008,6 +4076,18 @@ namespace z3 { Z3_solver_propagate_final(ctx(), *s, final_eh); } + void register_created(created_eh_t& c) { + assert(s); + m_created_eh = c; + Z3_solver_propagate_created(ctx(), *s, created_eh); + } + + void register_created() { + m_created_eh = [this](unsigned id, expr const& e) { + created(id, e); + }; + Z3_solver_propagate_created(ctx(), *s, created_eh); + } virtual void fixed(unsigned /*id*/, expr const& /*e*/) { } @@ -4015,6 +4095,8 @@ namespace z3 { virtual void final() { } + virtual void created(unsigned /*id*/, expr const& /*e*/) {} + /** \brief tracks \c e by a unique identifier that is returned by the call. diff --git a/src/api/dotnet/CMakeLists.txt b/src/api/dotnet/CMakeLists.txt index 98d4b9503..bab8ac982 100644 --- a/src/api/dotnet/CMakeLists.txt +++ b/src/api/dotnet/CMakeLists.txt @@ -18,8 +18,6 @@ add_custom_command(OUTPUT "${Z3_DOTNET_NATIVE_FILE}" ${Z3_FULL_PATH_API_HEADER_FILES_TO_SCAN} "${PROJECT_SOURCE_DIR}/scripts/update_api.py" ${Z3_GENERATED_FILE_EXTRA_DEPENDENCIES} - # FIXME: When update_api.py no longer uses ``mk_util`` drop this dependency - "${PROJECT_SOURCE_DIR}/scripts/mk_util.py" COMMENT "Generating ${Z3_DOTNET_NATIVE_FILE}" ${ADD_CUSTOM_COMMAND_USES_TERMINAL_ARG} ) diff --git a/src/api/dotnet/Context.cs b/src/api/dotnet/Context.cs index 07759b1fa..214411053 100644 --- a/src/api/dotnet/Context.cs +++ b/src/api/dotnet/Context.cs @@ -980,7 +980,8 @@ namespace Microsoft.Z3 Debug.Assert(t != null); Debug.Assert(t.All(a => a != null)); CheckContextMatch(t); - return new BoolExpr(this, Native.Z3_mk_and(nCtx, (uint)t.Count(), AST.EnumToNative(t))); + var ands = t.ToArray(); + return new BoolExpr(this, Native.Z3_mk_and(nCtx, (uint)t.Count(), AST.ArrayToNative(ands))); } /// @@ -1005,7 +1006,8 @@ namespace Microsoft.Z3 Debug.Assert(t.All(a => a != null)); CheckContextMatch(t); - return new BoolExpr(this, Native.Z3_mk_or(nCtx, (uint)t.Count(), AST.EnumToNative(t))); + var ts = t.ToArray(); + return new BoolExpr(this, Native.Z3_mk_or(nCtx, (uint)ts.Length, AST.ArrayToNative(ts))); } #endregion @@ -1032,7 +1034,8 @@ namespace Microsoft.Z3 Debug.Assert(t.All(a => a != null)); CheckContextMatch(t); - return (ArithExpr)Expr.Create(this, Native.Z3_mk_add(nCtx, (uint)t.Count(), AST.EnumToNative(t))); + var ts = t.ToArray(); + return (ArithExpr)Expr.Create(this, Native.Z3_mk_add(nCtx, (uint)ts.Length, AST.ArrayToNative(ts))); } /// @@ -1044,7 +1047,8 @@ namespace Microsoft.Z3 Debug.Assert(t.All(a => a != null)); CheckContextMatch(t); - return (ArithExpr)Expr.Create(this, Native.Z3_mk_mul(nCtx, (uint)t.Length, AST.ArrayToNative(t))); + var ts = t.ToArray(); + return (ArithExpr)Expr.Create(this, Native.Z3_mk_mul(nCtx, (uint)ts.Length, AST.ArrayToNative(ts))); } /// @@ -1056,7 +1060,8 @@ namespace Microsoft.Z3 Debug.Assert(t.All(a => a != null)); CheckContextMatch(t); - return (ArithExpr)Expr.Create(this, Native.Z3_mk_mul(nCtx, (uint)t.Count(), AST.EnumToNative(t))); + var ts = t.ToArray(); + return (ArithExpr)Expr.Create(this, Native.Z3_mk_mul(nCtx, (uint)ts.Length, AST.ArrayToNative(ts))); } /// @@ -2662,6 +2667,17 @@ namespace Microsoft.Z3 return new ReExpr(this, Native.Z3_mk_re_intersect(nCtx, (uint)t.Length, AST.ArrayToNative(t))); } + /// + /// Create a difference regular expression. + /// + public ReExpr MkDiff(ReExpr a, ReExpr b) + { + Debug.Assert(a != null); + Debug.Assert(b != null); + CheckContextMatch(a, b); + return new ReExpr(this, Native.Z3_mk_re_diff(nCtx, a.NativeObject, b.NativeObject)); + } + /// /// Create the empty regular expression. /// The sort s should be a regular expression. @@ -2749,10 +2765,11 @@ namespace Microsoft.Z3 /// public BoolExpr MkAtMost(IEnumerable args, uint k) { - Debug.Assert(args != null); - CheckContextMatch(args); - return new BoolExpr(this, Native.Z3_mk_atmost(nCtx, (uint) args.Count(), - AST.EnumToNative(args), k)); + Debug.Assert(args != null); + CheckContextMatch(args); + var ts = args.ToArray(); + return new BoolExpr(this, Native.Z3_mk_atmost(nCtx, (uint) ts.Length, + AST.ArrayToNative(ts), k)); } /// @@ -2760,10 +2777,11 @@ namespace Microsoft.Z3 /// public BoolExpr MkAtLeast(IEnumerable args, uint k) { - Debug.Assert(args != null); - CheckContextMatch(args); - return new BoolExpr(this, Native.Z3_mk_atleast(nCtx, (uint) args.Count(), - AST.EnumToNative(args), k)); + Debug.Assert(args != null); + CheckContextMatch(args); + var ts = args.ToArray(); + return new BoolExpr(this, Native.Z3_mk_atleast(nCtx, (uint) ts.Length, + AST.ArrayToNative(ts), k)); } /// diff --git a/src/api/dotnet/Z3Object.cs b/src/api/dotnet/Z3Object.cs index 9a61a0119..d25dfc25a 100644 --- a/src/api/dotnet/Z3Object.cs +++ b/src/api/dotnet/Z3Object.cs @@ -131,24 +131,10 @@ namespace Microsoft.Z3 return an; } - internal static IntPtr[] EnumToNative(IEnumerable a) where T : Z3Object - { - - if (a == null) return null; - IntPtr[] an = new IntPtr[a.Count()]; - int i = 0; - foreach (var ai in a) - { - if (ai != null) an[i] = ai.NativeObject; - ++i; - } - return an; - } - internal static uint ArrayLength(Z3Object[] a) { return (a == null)?0:(uint)a.Length; } - #endregion +#endregion } } diff --git a/src/api/java/Context.java b/src/api/java/Context.java index 9fcdfb1b5..b22bedef7 100644 --- a/src/api/java/Context.java +++ b/src/api/java/Context.java @@ -2016,7 +2016,15 @@ public class Context implements AutoCloseable { */ public SeqExpr mkString(String s) { - return (SeqExpr) Expr.create(this, Native.mkString(nCtx(), s)); + StringBuilder buf = new StringBuilder(); + for (int i = 0; i < s.length(); ++i) { + int code = s.codePointAt(i); + if (code <= 32 || 127 < code) + buf.append(String.format("\\u{%x}", code)); + else + buf.append(s.charAt(i)); + } + return (SeqExpr) Expr.create(this, Native.mkString(nCtx(), buf.toString())); } /** @@ -2246,8 +2254,19 @@ public class Context implements AutoCloseable { return (ReExpr) Expr.create(this, Native.mkReIntersect(nCtx(), t.length, AST.arrayToNative(t))); } + /** + * Create a difference regular expression. + */ + public ReExpr mkDiff(Expr> a, Expr> b) + { + checkContextMatch(a, b); + return (ReExpr) Expr.create(this, Native.mkReDiff(nCtx(), a.getNativeObject(), b.getNativeObject())); + } + + /** * Create the empty regular expression. + * Coresponds to re.none */ public ReExpr mkEmptyRe(R s) { @@ -2256,12 +2275,22 @@ public class Context implements AutoCloseable { /** * Create the full regular expression. + * Corresponds to re.all */ public ReExpr mkFullRe(R s) { return (ReExpr) Expr.create(this, Native.mkReFull(nCtx(), s.getNativeObject())); } + /** + * Create regular expression that accepts all characters + * Corresponds to re.allchar + */ + public ReExpr mkAllcharRe(R s) + { + return (ReExpr) Expr.create(this, Native.mkReAllchar(nCtx(), s.getNativeObject())); + } + /** * Create a range expression. */ diff --git a/src/api/js/PUBLISHED_README.md b/src/api/js/PUBLISHED_README.md new file mode 100644 index 000000000..897edabf5 --- /dev/null +++ b/src/api/js/PUBLISHED_README.md @@ -0,0 +1,124 @@ +# Z3 TypeScript Bindings + +This project provides low-level TypeScript bindings for the [Z3 theorem prover](https://github.com/Z3Prover/z3). It is available on npm as [z3-solver](https://www.npmjs.com/package/z3-solver). + +Z3 itself is distributed as a wasm artifact as part of this package. You can find the documentation for the Z3 API [here](https://z3prover.github.io/api/html/z3__api_8h.html), though note the differences below. + + +## Using + +This requires threads, which means you'll need to be running in an environment which supports `SharedArrayBuffer`. In browsers, in addition to ensuring the browser has implemented `SharedArrayBuffer`, you'll need to serve your page with [special headers](https://web.dev/coop-coep/). There's a [neat trick](https://github.com/gzuidhof/coi-serviceworker) for doing that client-side on e.g. Github Pages, though you shouldn't use that trick in more complex applications. + +Other than the differences below, the bindings can be used exactly as you'd use the C library. Because this is a wrapper around a C library, most of the values you'll use are just numbers representing pointers. For this reason you are strongly encouraged to make use of the TypeScript types to differentiate among the different kinds of value. + +The module exports an `init` function, is an async function which initializes the library and returns `{ em, Z3 }` - `em` contains the underlying emscripten module, which you can use to e.g. kill stray threads, and `Z3` contains the actual bindings. The other module exports are the enums defined in the Z3 API. + +[`test-ts-api.ts`](./test-ts-api.ts) contains a couple real cases translated very mechanically from [this file](https://github.com/Z3Prover/z3/blob/90fd3d82fce20d45ed2eececdf65545bab769503/examples/c/test_capi.c). + + +## Differences from the C API + +### Integers + +JavaScript numbers are IEEE double-precisions floats. These can be used wherever the C API expects an `int`, `unsigned int`, `float`, or `double`. + +`int64_t` and `uint64_t` cannot be precisely represented by JS numbers, so in the TS bindings they are represented by [BigInts](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#bigint_type). + +### Out parameters + +In C, to return multiple values a function will take an address to write to, conventionally referred to as an "out parameter". Sometimes the function returns a boolean to indicate success; other times it may return nothing or some other value. + +In JS the convention when returning multiple values is to return records containing the values of interest. + +The wrapper translates between the two conventions. For example, the C declaration + +```c +void Z3_rcf_get_numerator_denominator(Z3_context c, Z3_rcf_num a, Z3_rcf_num * n, Z3_rcf_num * d); +``` + +is represented in the TS bindings as + +```ts +function rcf_get_numerator_denominator(c: Z3_context, a: Z3_rcf_num): { n: Z3_rcf_num; d: Z3_rcf_num } { + // ... +} +``` + +When there is only a single out parameter, and the return value is not otherwise of interest, the parameter is not wrapped. For example, the C declaration + +```c +Z3_bool Z3_model_eval(Z3_context c, Z3_model m, Z3_ast t, bool model_completion, Z3_ast * v); +``` + +is represented in the TS bindings as + +```ts +function model_eval(c: Z3_context, m: Z3_model, t: Z3_ast, model_completion: boolean): Z3_ast | null { + // ... +} +``` + +Note that the boolean return type of the C function is translated into a nullable return type for the TS binding. + +When the return value is of interest it is included in the returned record under the key `rv`. + + +### Arrays + +The when the C API takes an array as an argument it will also require a parameter which specifies the length of the array (since arrays do not carry their own lengths in C). In the TS bindings this is automatically inferred. + +For example, the C declaration +```js +unsigned Z3_rcf_mk_roots(Z3_context c, unsigned n, Z3_rcf_num const a[], Z3_rcf_num roots[]); +``` + +is represented in the TS bindings as + +```ts +function rcf_mk_roots(c: Z3_context, a: Z3_rcf_num[]): { rv: number; roots: Z3_rcf_num[] } { + // ... +} +``` + +When there are multiple arrays which the C API expects to be of the same length, the TS bindings will enforce that this is the case. + + +### Null pointers + +Some of the C APIs accept or return null pointers (represented by types whose name end in `_opt`). These are represented in the TS bindings as `| null`. For example, the C declaration + +```js +Z3_ast_opt Z3_model_get_const_interp(Z3_context c, Z3_model m, Z3_func_decl a); +``` + +is represented in the TS bindings as + +```ts +function model_get_const_interp(c: Z3_context, m: Z3_model, a: Z3_func_decl): Z3_ast | null { + // ... +} +``` + + +### Async functions + +Certain long-running APIs are not appropriate to call on the main thread. In the TS bindings those APIs are marked as `async` and are automatically called on a different thread. This includes the following APIs: + +- `Z3_simplify` +- `Z3_simplify_ex` +- `Z3_solver_check` +- `Z3_solver_check_assumptions` +- `Z3_solver_cube` +- `Z3_solver_get_consequences` +- `Z3_tactic_apply` +- `Z3_tactic_apply_ex` +- `Z3_optimize_check` +- `Z3_algebraic_roots` +- `Z3_algebraic_eval` +- `Z3_fixedpoint_query` +- `Z3_fixedpoint_query_relations` +- `Z3_fixedpoint_query_from_lvl` +- `Z3_polynomial_subresultants` + +Note that these are not thread-safe, and so only one call can be running at a time. Trying to call a second async API before the first completes will throw. + diff --git a/src/api/js/README.md b/src/api/js/README.md new file mode 100644 index 000000000..27ff5bdfe --- /dev/null +++ b/src/api/js/README.md @@ -0,0 +1,17 @@ +# TypeScript Bindings + +This directory contains JavaScript code to automatically derive TypeScript bindings for the C API, which are published on npm as [z3-solver](https://www.npmjs.com/package/z3-solver). + +The readme for the bindings themselves is located in [`PUBLISHED_README.md`](./PUBLISHED_README.md). + + +## Building + +You'll need to have emscripten set up, along with all of its dependencies. The easiest way to do that is with [emsdk](https://github.com/emscripten-core/emsdk). + +Then run `npm i` to install dependencies, `npm run build-ts` to build the TypeScript wrapper, and `npm run build-wasm` to build the wasm artifact. + + +## Tests + +Current tests are very minimal: [`test-ts-api.ts`](./test-ts-api.ts) contains a couple real cases translated very mechanically from [this file](https://github.com/Z3Prover/z3/blob/90fd3d82fce20d45ed2eececdf65545bab769503/examples/c/test_capi.c). diff --git a/src/api/js/build-wasm.sh b/src/api/js/build-wasm.sh new file mode 100755 index 000000000..e89b70862 --- /dev/null +++ b/src/api/js/build-wasm.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +set -euxo pipefail + + +export ROOT=$PWD + +cd ../../.. +export CXXFLAGS="-pthread -s USE_PTHREADS=1 -s DISABLE_EXCEPTION_CATCHING=0" +export LDFLAGS="-s WASM_BIGINT -s -pthread -s USE_PTHREADS=1" +if [ ! -f "build/Makefile" ]; then + emconfigure python scripts/mk_make.py --staticlib --single-threaded +fi + +cd build +emmake make -j$(nproc) libz3.a + +cd $ROOT + +export EM_CACHE=$HOME/.emscripten/ +export FNS=$(node scripts/list-exports.js) +export METHODS='["ccall","FS","allocate","UTF8ToString","intArrayFromString","ALLOC_NORMAL"]' +emcc build/async-fns.cc ../../../build/libz3.a --std=c++20 --pre-js src/async-wrapper.js -g2 -pthread -fexceptions -s WASM_BIGINT -s USE_PTHREADS=1 -s PTHREAD_POOL_SIZE=0 -s PTHREAD_POOL_SIZE_STRICT=0 -s MODULARIZE=1 -s 'EXPORT_NAME="initZ3"' -s EXPORTED_RUNTIME_METHODS=$METHODS -s EXPORTED_FUNCTIONS=$FNS -s DISABLE_EXCEPTION_CATCHING=0 -s SAFE_HEAP=0 -s DEMANGLE_SUPPORT=1 -s TOTAL_MEMORY=1GB -I z3/src/api/ -o build/z3-built.js diff --git a/src/api/js/example-raw.ts b/src/api/js/example-raw.ts new file mode 100644 index 000000000..9b19e782c --- /dev/null +++ b/src/api/js/example-raw.ts @@ -0,0 +1,57 @@ +import { init } from './build/wrapper'; + +// demonstrates use of the raw API + +(async () => { + let { em, Z3 } = await init(); + + Z3.global_param_set('verbose', '10'); + console.log('verbosity:', Z3.global_param_get('verbose')); + + let config = Z3.mk_config(); + let ctx = Z3.mk_context_rc(config); + Z3.del_config(config); + + let unicodeStr = [...'hello™'].map(x => x.codePointAt(0)!); + let strAst = Z3.mk_u32string(ctx, unicodeStr); + Z3.inc_ref(ctx, strAst); + + console.log(Z3.is_string(ctx, strAst)); + console.log(Z3.get_string(ctx, strAst)); + console.log(Z3.get_string_contents(ctx, strAst, unicodeStr.length)); + + let bv = Z3.mk_bv_numeral(ctx, [true, true, false]); + let bs = Z3.mk_ubv_to_str(ctx, bv); + console.log(Z3.ast_to_string(ctx, bs)); + + let intSort = Z3.mk_int_sort(ctx); + let big = Z3.mk_int64(ctx, 42n, intSort); + console.log(Z3.get_numeral_string(ctx, big)); + console.log(Z3.get_numeral_int64(ctx, big)); + + console.log(Z3.get_version()); + + let head_tail = [Z3.mk_string_symbol(ctx, 'car'), Z3.mk_string_symbol(ctx, 'cdr')]; + + let nil_con = Z3.mk_constructor(ctx, Z3.mk_string_symbol(ctx, 'nil'), Z3.mk_string_symbol(ctx, 'is_nil'), [], [], []); + let cons_con = Z3.mk_constructor( + ctx, + Z3.mk_string_symbol(ctx, 'cons'), + Z3.mk_string_symbol(ctx, 'is_cons'), + head_tail, + [null, null], + [0, 0], + ); + + let cell = Z3.mk_datatype(ctx, Z3.mk_string_symbol(ctx, 'cell'), [nil_con, cons_con]); + console.log(Z3.query_constructor(ctx, nil_con, 0)); + console.log(Z3.query_constructor(ctx, cons_con, 2)); + + Z3.dec_ref(ctx, strAst); + Z3.del_context(ctx); + + em.PThread.terminateAllThreads(); +})().catch(e => { + console.error('error', e); + process.exit(1); +}); diff --git a/src/api/js/package-lock.json b/src/api/js/package-lock.json new file mode 100644 index 000000000..56bb70753 --- /dev/null +++ b/src/api/js/package-lock.json @@ -0,0 +1,30 @@ +{ + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "@types/node": { + "version": "17.0.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.8.tgz", + "integrity": "sha512-YofkM6fGv4gDJq78g4j0mMuGMkZVxZDgtU0JRdx6FgiJDG+0fY0GKVolOV8WqVmEhLCXkQRjwDdKyPxJp/uucg==", + "dev": true + }, + "prettier": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", + "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", + "dev": true + }, + "sprintf-js": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", + "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", + "dev": true + }, + "typescript": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz", + "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==", + "dev": true + } + } +} diff --git a/src/api/js/package.json b/src/api/js/package.json new file mode 100644 index 000000000..85fd2d24c --- /dev/null +++ b/src/api/js/package.json @@ -0,0 +1,27 @@ +{ + "name": "z3-solver", + "keywords": ["Z3", "theorem", "prover", "solver", "satisfiability", "smt", "satisfiability modulo theories"], + "homepage": "https://github.com/Z3Prover/z3/tree/master/src/api/js", + "repository": "github:Z3Prover/z3", + "engines": { + "node": ">=16" + }, + "main": "build/wrapper.js", + "types": "build/wrapper.d.ts", + "files": [ + "build/*.{js,d.ts,wasm}" + ], + "scripts": { + "build-ts": "mkdir -p build && node scripts/make-ts-wrapper.js > build/wrapper.ts && tsc", + "build-wasm": "mkdir -p build && node scripts/make-cc-wrapper.js > build/async-fns.cc && ./build-wasm.sh", + "format": "prettier --write --single-quote --arrow-parens avoid --print-width 120 --trailing-comma all '{,src/,scripts/}*.{js,ts}'", + "test": "node test-ts-api.js" + }, + "devDependencies": { + "@types/node": "^17.0.8", + "prettier": "^2.5.1", + "sprintf-js": "^1.1.2", + "typescript": "^4.5.4" + }, + "license": "MIT" +} diff --git a/src/api/js/scripts/async-fns.js b/src/api/js/scripts/async-fns.js new file mode 100644 index 000000000..515126b94 --- /dev/null +++ b/src/api/js/scripts/async-fns.js @@ -0,0 +1,22 @@ +'use strict'; + +// things which you probably want to do off-thread +// from https://github.com/Z3Prover/z3/issues/5746#issuecomment-1006289146 +module.exports = [ + 'Z3_eval_smtlib2_string', + 'Z3_simplify', + 'Z3_simplify_ex', + 'Z3_solver_check', + 'Z3_solver_check_assumptions', + 'Z3_solver_cube', + 'Z3_solver_get_consequences', + 'Z3_tactic_apply', + 'Z3_tactic_apply_ex', + 'Z3_optimize_check', + 'Z3_algebraic_roots', + 'Z3_algebraic_eval', + 'Z3_fixedpoint_query', + 'Z3_fixedpoint_query_relations', + 'Z3_fixedpoint_query_from_lvl', + 'Z3_polynomial_subresultants', +]; diff --git a/src/api/js/scripts/list-exports.js b/src/api/js/scripts/list-exports.js new file mode 100644 index 000000000..4513b7e58 --- /dev/null +++ b/src/api/js/scripts/list-exports.js @@ -0,0 +1,11 @@ +'use strict'; + +// this is called by build.sh to generate the names of the bindings to export + +let { functions } = require('./parse-api.js'); +let asyncFns = require('./async-fns.js'); + +let extras = asyncFns.map(f => '_async_' + f); +let fns = functions.filter(f => !asyncFns.includes(f.name)); + +console.log(JSON.stringify([...extras, ...functions.map(f => '_' + f.name)])); diff --git a/src/api/js/scripts/make-cc-wrapper.js b/src/api/js/scripts/make-cc-wrapper.js new file mode 100644 index 000000000..b0860e349 --- /dev/null +++ b/src/api/js/scripts/make-cc-wrapper.js @@ -0,0 +1,82 @@ +'use strict'; + +// generates c wrappers with off-thread versions of specified functions + +let path = require('path'); + +let { functions } = require('./parse-api.js'); +let asyncFns = require('./async-fns.js'); + +let wrappers = []; + +for (let fnName of asyncFns) { + let fn = functions.find(f => f.name === fnName); + if (fn == null) { + throw new Error(`could not find definition for ${fnName}`); + } + let wrapper; + if (fn.cRet === 'Z3_string') { + wrapper = `wrapper_str`; + } else if (['int', 'unsigned', 'void'].includes(fn.cRet) || fn.cRet.startsWith('Z3_')) { + wrapper = `wrapper`; + } else { + throw new Error(`async function with unknown return type ${fn.cRet}`); + } + + wrappers.push( + ` +extern "C" void async_${fn.name}(${fn.params + .map(p => `${p.isConst ? 'const ' : ''}${p.cType}${p.isPtr ? '*' : ''} ${p.name}${p.isArray ? '[]' : ''}`) + .join(', ')}) { + ${wrapper}(${fn.params.map(p => `${p.name}`).join(', ')}); +} +`.trim(), + ); +} + +console.log(`// THIS FILE IS AUTOMATICALLY GENERATED BY ${path.basename(__filename)} +// DO NOT EDIT IT BY HAND + +#include + +#include + +#include "../../z3.h" + +template +void wrapper(Args&&... args) { + std::thread t([...args = std::forward(args)] { + try { + auto result = fn(args...); + MAIN_THREAD_ASYNC_EM_ASM({ + resolve_async($0); + }, result); + } catch (...) { + MAIN_THREAD_ASYNC_EM_ASM({ + reject_async('failed with unknown exception'); + }); + throw; + } + }); + t.detach(); +} + +template +void wrapper_str(Args&&... args) { + std::thread t([...args = std::forward(args)] { + try { + auto result = fn(args...); + MAIN_THREAD_ASYNC_EM_ASM({ + resolve_async(UTF8ToString($0)); + }, result); + } catch (...) { + MAIN_THREAD_ASYNC_EM_ASM({ + reject_async('failed with unknown exception'); + }); + throw; + } + }); + t.detach(); +} + +${wrappers.join('\n\n')}`); diff --git a/src/api/js/scripts/make-ts-wrapper.js b/src/api/js/scripts/make-ts-wrapper.js new file mode 100644 index 000000000..4a51c5ec9 --- /dev/null +++ b/src/api/js/scripts/make-ts-wrapper.js @@ -0,0 +1,422 @@ +'use strict'; + +let path = require('path'); +let prettier = require('prettier'); + +let { primitiveTypes, types, enums, functions } = require('./parse-api.js'); +let asyncFns = require('./async-fns.js'); + +let subtypes = { + __proto__: null, + Z3_sort: 'Z3_ast', + Z3_func_decl: 'Z3_ast', +}; + +let makePointerType = t => + `export type ${t} = ` + (t in subtypes ? `Subpointer<'${t}', '${subtypes[t]}'>;` : `Pointer<'${t}'>;`); + +// this supports a up to 6 out intergers/pointers +// or up to 3 out int64s +const BYTES_TO_ALLOCATE_FOR_OUT_PARAMS = 24; + +function toEmType(type) { + if (type in primitiveTypes) { + type = primitiveTypes[type]; + } + if (['boolean', 'number', 'string', 'bigint', 'void'].includes(type)) { + return type; + } + if (type.startsWith('Z3_')) { + return 'number'; + } + throw new Error(`unknown parameter type ${type}`); +} + +function isZ3PointerType(type) { + return type.startsWith('Z3_'); +} + +function toEm(p) { + if (typeof p === 'string') { + // we've already set this, e.g. by replacing it with an expression + return p; + } + let { type } = p; + if (p.kind === 'out') { + throw new Error(`unknown out parameter type ${JSON.stringify(p)}`); + } + if (p.isArray) { + if (isZ3PointerType(type) || type === 'unsigned' || type === 'int') { + // this works for nullables also because null coerces to 0 + return `intArrayToByteArr(${p.name} as unknown as number[])`; + } else if (type === 'boolean') { + return `boolArrayToByteArr(${p.name})`; + } else { + throw new Error(`only know how to deal with arrays of int/bool (got ${type})`); + } + } + if (type in primitiveTypes) { + type = primitiveTypes[type]; + } + + if (['boolean', 'number', 'bigint', 'string'].includes(type)) { + return p.name; + } + if (type.startsWith('Z3_')) { + return p.name; + } + throw new Error(`unknown parameter type ${JSON.stringify(p)}`); +} + +let isInParam = p => ['in', 'in_array'].includes(p.kind); +function wrapFunction(fn) { + let inParams = fn.params.filter(isInParam); + let outParams = fn.params.map((p, idx) => ({ ...p, idx })).filter(p => !isInParam(p)); + + // we'll figure out how to deal with these cases later + let unknownInParam = inParams.find( + p => + p.isPtr || + p.type === 'Z3_char_ptr' || + (p.isArray && !(isZ3PointerType(p.type) || p.type === 'unsigned' || p.type === 'int' || p.type === 'boolean')), + ); + if (unknownInParam) { + console.error(`skipping ${fn.name} - unknown in parameter ${JSON.stringify(unknownInParam)}`); + return null; + } + + if (fn.ret === 'Z3_char_ptr') { + console.error(`skipping ${fn.name} - returns a string or char pointer`); + return null; + } + // console.error(fn.name); + + let isAsync = asyncFns.includes(fn.name); + let trivial = + !['string', 'boolean'].includes(fn.ret) && + !fn.nullableRet && + outParams.length === 0 && + !inParams.some(p => p.type === 'string' || p.isArray || p.nullable); + + let name = fn.name.startsWith('Z3_') ? fn.name.substring(3) : fn.name; + + let params = inParams.map(p => { + let type = p.type; + if (p.isArray && p.nullable) { + type = `(${type} | null)[]`; + } else if (p.isArray) { + type = `${type}[]`; + } else if (p.nullable) { + type = `${type} | null`; + } + return `${p.name}: ${type}`; + }); + + if (trivial && isAsync) { + // i.e. and async + return `${name}: function (${params.join(', ')}): Promise<${fn.ret}> { + return Mod.async_call(Mod._async_${fn.name}, ${fn.params.map(toEm).join(', ')}); + }`; + } + + if (trivial) { + return `${name}: Mod._${fn.name} as ((${params.join(', ')}) => ${fn.ret})`; + } + + // otherwise fall back to ccall + + let ctypes = fn.params.map(p => + p.kind === 'in_array' ? 'array' : p.kind === 'out_array' ? 'number' : p.isPtr ? 'number' : toEmType(p.type), + ); + + let prefix = ''; + let infix = ''; + let rv = 'ret'; + let suffix = ''; + + let args = fn.params; + + let arrayLengthParams = new Map(); + for (let p of inParams) { + if (p.nullable && !p.isArray) { + // this would be easy to implement - just map null to 0 - but nothing actually uses nullable non-array input parameters, so we can't ensure we've done it right + console.error(`skipping ${fn.name} - nullable input parameter`); + return null; + } + if (!p.isArray) { + continue; + } + let { sizeIndex } = p; + if (arrayLengthParams.has(sizeIndex)) { + let otherParam = arrayLengthParams.get(sizeIndex); + prefix += ` + if (${otherParam}.length !== ${p.name}.length) { + throw new TypeError(\`${otherParam} and ${p.name} must be the same length (got \${${otherParam}.length} and \{${p.name}.length})\`); + } + `.trim(); + continue; + } + arrayLengthParams.set(sizeIndex, p.name); + + let sizeParam = fn.params[sizeIndex]; + if (!(sizeParam.kind === 'in' && sizeParam.type === 'unsigned' && !sizeParam.isPtr && !sizeParam.isArray)) { + throw new Error( + `size index is not unsigned int (for fn ${fn.name} parameter ${sizeIndex} got ${sizeParam.type})`, + ); + } + args[sizeIndex] = `${p.name}.length`; + params[sizeIndex] = null; + } + + let returnType = fn.ret; + let cReturnType = toEmType(fn.ret); + if (outParams.length > 0) { + let mapped = []; + let memIdx = 0; // offset from `outAddress` where the data should get written, in units of 4 bytes + + for (let outParam of outParams) { + if (outParam.isArray) { + if (isZ3PointerType(outParam.type) || outParam.type === 'unsigned') { + let { sizeIndex } = outParam; + + let count; + if (arrayLengthParams.has(sizeIndex)) { + // i.e. this is also the length of an input array + count = args[sizeIndex]; + } else { + let sizeParam = fn.params[sizeIndex]; + if (!(sizeParam.kind === 'in' && sizeParam.type === 'unsigned' && !sizeParam.isPtr && !sizeParam.isArray)) { + throw new Error( + `size index is not unsigned int (for fn ${fn.name} parameter ${sizeIndex} got ${sizeParam.type})`, + ); + } + count = sizeParam.name; + } + let outArrayAddress = `outArray_${outParam.name}`; + prefix += ` + let ${outArrayAddress} = Mod._malloc(4 * ${count}); + try { + `.trim(); + suffix = + ` + } finally { + Mod._free(${outArrayAddress}); + } + `.trim() + suffix; + args[outParam.idx] = outArrayAddress; + mapped.push({ + name: outParam.name, + read: + `readUintArray(${outArrayAddress}, ${count})` + + (outParam.type === 'unsigned' ? '' : `as unknown as ${outParam.type}[]`), + type: `${outParam.type}[]`, + }); + } else { + console.error(`skipping ${fn.name} - out array of ${outParam.type}`); + return null; + } + } else if (outParam.isPtr) { + function setArg() { + args[outParam.idx] = memIdx === 0 ? 'outAddress' : `outAddress + ${memIdx * 4}`; + } + let read, type; + if (outParam.type === 'string') { + read = `Mod.UTF8ToString(getOutUint(${memIdx}))`; + setArg(); + ++memIdx; + } else if (isZ3PointerType(outParam.type)) { + read = `getOutUint(${memIdx}) as unknown as ${outParam.type}`; + setArg(); + ++memIdx; + } else if (outParam.type === 'unsigned') { + read = `getOutUint(${memIdx})`; + setArg(); + ++memIdx; + } else if (outParam.type === 'int') { + read = `getOutInt(${memIdx})`; + setArg(); + ++memIdx; + } else if (outParam.type === 'uint64_t') { + if (memIdx % 2 === 1) { + ++memIdx; + } + read = `getOutUint64(${memIdx / 2})`; + setArg(); + memIdx += 2; + } else if (outParam.type === 'int64_t') { + if (memIdx % 2 === 1) { + ++memIdx; + } + read = `getOutInt64(${memIdx / 2})`; + setArg(); + memIdx += 2; + } else { + console.error(`skipping ${fn.name} - unknown out parameter type ${outParam.type}`); + return null; + } + if (memIdx > Math.floor(BYTES_TO_ALLOCATE_FOR_OUT_PARAMS / 4)) { + // prettier-ignore + console.error(`skipping ${fn.name} - out parameter sizes sum to ${memIdx * 4}, which is > ${BYTES_TO_ALLOCATE_FOR_OUT_PARAMS}`); + return null; + } + mapped.push({ + name: outParam.name, + read, + type: outParam.type, + }); + } else { + console.error(`skipping ${fn.name} - out param is neither pointer nor array`); + return null; + } + } + + let ignoreReturn = fn.ret === 'boolean' || fn.ret === 'void'; + if (outParams.length === 1) { + let outParam = mapped[0]; + if (ignoreReturn) { + returnType = outParam.type; + rv = outParam.read; + } else { + returnType = `{ rv: ${fn.ret}, ${outParam.name} : ${outParam.type} }`; + rv = `{ rv: ret, ${outParam.name} : ${outParam.read} }`; + } + } else { + if (ignoreReturn) { + returnType = `{ ${mapped.map(p => `${p.name} : ${p.type}`).join(', ')} }`; + rv = `{ ${mapped.map(p => `${p.name}: ${p.read}`).join(', ')} }`; + } else { + returnType = `{ rv: ${fn.ret}, ${mapped.map(p => `${p.name} : ${p.type}`).join(', ')} }`; + rv = `{ rv: ret, ${mapped.map(p => `${p.name}: ${p.read}`).join(', ')} }`; + } + } + + if (fn.ret === 'boolean') { + // assume the boolean indicates success + infix += ` + if (!ret) { + return null; + } + `.trim(); + cReturnType = 'boolean'; + returnType += ' | null'; + } else if (fn.ret === 'void') { + cReturnType = 'void'; + } else if (isZ3PointerType(fn.ret) || fn.ret === 'unsigned') { + cReturnType = 'number'; + } else { + console.error(`skipping ${fn.name} - out parameter for function which returns non-boolean`); + return null; + } + } + + if (fn.nullableRet) { + returnType += ' | null'; + infix += ` + if (ret === 0) { + return null; + } + `.trim(); + } + + if (isAsync) { + } + + // prettier-ignore + let invocation = `Mod.ccall('${isAsync ? 'async_' : ''}${fn.name}', '${cReturnType}', ${JSON.stringify(ctypes)}, [${args.map(toEm).join(', ')}])`; + + if (isAsync) { + invocation = `await Mod.async_call(() => ${invocation})`; + returnType = `Promise<${returnType}>`; + } + + let out = `${name}: ${isAsync ? 'async' : ''} function(${params.filter(p => p != null).join(', ')}): ${returnType} { + ${prefix}`; + if (infix === '' && suffix === '' && rv === 'ret') { + out += `return ${invocation};`; + } else { + out += ` + let ret = ${invocation}; + ${infix}return ${rv};${suffix} + `.trim(); + } + out += '}'; + return out; +} + +function wrapEnum(name, values) { + let enumEntries = Object.entries(values); + return `export enum ${name} { + ${enumEntries.map(([k, v], i) => k + (v === (enumEntries[i - 1]?.[1] ?? -1) + 1 ? '' : ` = ${v}`) + ',').join('\n')} + };`; +} + +function getValidOutArrayIndexes(size) { + return Array.from({ length: Math.floor(BYTES_TO_ALLOCATE_FOR_OUT_PARAMS / size) }, (_, i) => i).join(' | '); +} + +let out = ` +// THIS FILE IS AUTOMATICALLY GENERATED BY ${path.basename(__filename)} +// DO NOT EDIT IT BY HAND + +// @ts-ignore no-implicit-any +import initModule = require('./z3-built.js'); +interface Pointer extends Number { + readonly __typeName: T; +} +interface Subpointer extends Pointer { + readonly __typeName2: T; +} + +${Object.entries(primitiveTypes) + .filter(e => e[0] !== 'void') + .map(e => `type ${e[0]} = ${e[1]};`) + .join('\n')} + +${Object.keys(types) + .filter(k => k.startsWith('Z3')) + .map(makePointerType) + .join('\n')} + +${Object.entries(enums) + .map(e => wrapEnum(e[0], e[1])) + .join('\n\n')} + +export async function init() { + let Mod = await initModule(); + + // this works for both signed and unsigned, because JS will wrap for you when constructing the Uint32Array + function intArrayToByteArr(ints: number[]) { + return new Uint8Array((new Uint32Array(ints)).buffer); + } + + function boolArrayToByteArr(bools: boolean[]) { + return bools.map(b => b ? 1 : 0); + } + + function readUintArray(address: number, count: number) { + return Array.from(new Uint32Array(Mod.HEAPU32.buffer, address, count)); + } + + let outAddress = Mod._malloc(${BYTES_TO_ALLOCATE_FOR_OUT_PARAMS}); + let outUintArray = (new Uint32Array(Mod.HEAPU32.buffer, outAddress, 4)); + let getOutUint = (i: ${getValidOutArrayIndexes(4)}) => outUintArray[i]; + let outIntArray = (new Int32Array(Mod.HEAPU32.buffer, outAddress, 4)); + let getOutInt = (i: ${getValidOutArrayIndexes(4)}) => outIntArray[i]; + let outUint64Array = (new BigUint64Array(Mod.HEAPU32.buffer, outAddress, 2)); + let getOutUint64 = (i: ${getValidOutArrayIndexes(8)}) => outUint64Array[i]; + let outInt64Array = (new BigInt64Array(Mod.HEAPU32.buffer, outAddress, 2)); + let getOutInt64 = (i: ${getValidOutArrayIndexes(8)}) => outInt64Array[i]; + + return { + em: Mod, + Z3: { + ${functions + .map(wrapFunction) + .filter(f => f != null) + .join(',\n')} + } + }; +} +`; + +console.log(prettier.format(out, { singleQuote: true, parser: 'typescript' })); diff --git a/src/api/js/scripts/parse-api.js b/src/api/js/scripts/parse-api.js new file mode 100644 index 000000000..53655424f --- /dev/null +++ b/src/api/js/scripts/parse-api.js @@ -0,0 +1,362 @@ +'use strict'; + +let fs = require('fs'); +let path = require('path'); + +let files = [ + 'z3_api.h', + 'z3_algebraic.h', + 'z3_ast_containers.h', + 'z3_fixedpoint.h', + 'z3_fpa.h', + 'z3_optimization.h', + 'z3_polynomial.h', + 'z3_rcf.h', + 'z3_spacer.h', +]; + +let aliases = { + __proto__: null, + Z3_bool: 'boolean', + Z3_string: 'string', + bool: 'boolean', + signed: 'int', +}; + +let primitiveTypes = { + __proto__: null, + Z3_char_ptr: 'string', + unsigned: 'number', + int: 'number', + uint64_t: 'bigint', + int64_t: 'bigint', + double: 'number', + float: 'number', +}; + +let optTypes = { + __proto__: null, + + Z3_sort_opt: 'Z3_sort', + Z3_ast_opt: 'Z3_ast', + Z3_func_interp_opt: 'Z3_func_interp', +}; + +// parse type declarations +let types = { + __proto__: null, + + // these are function types I can't be bothered to parse + Z3_error_handler: 'Z3_error_handler', + Z3_push_eh: 'Z3_push_eh', + Z3_pop_eh: 'Z3_pop_eh', + Z3_fresh_eh: 'Z3_fresh_eh', + Z3_fixed_eh: 'Z3_fixed_eh', + Z3_eq_eh: 'Z3_eq_eh', + Z3_final_eh: 'Z3_final_eh', + Z3_created_eh: 'Z3_created_eh', +}; + +let defApis = Object.create(null); +let functions = []; +let enums = Object.create(null); +for (let file of files) { + let contents = fs.readFileSync(path.join(__dirname, '..', '..', file), 'utf8'); + + // we _could_ use an actual C++ parser, which accounted for macros and everything + // but that's super painful + // and the files are regular enough that we can get away without it + + // we could also do this by modifying the `update_api.py` script + // which we should probably do eventually + // but this is easier while this remains not upstreamed + + // we need to parse the `def_API` stuff so we know which things are out parameters + // unfortunately we also need to parse the actual declarations so we know the parameter names also + let pytypes = Object.create(null); + + let typeMatches = contents.matchAll( + /def_Type\(\s*'(?[A-Za-z0-9_]+)',\s*'(?[A-Za-z0-9_]+)',\s*'(?[A-Za-z0-9_]+)'\)/g, + ); + for (let { groups } of typeMatches) { + pytypes[groups.name] = groups.cname; + } + + // we filter first to ensure our regex isn't too strict + let apiLines = contents.split('\n').filter(l => /def_API|extra_API/.test(l)); + for (let line of apiLines) { + let match = line.match( + /^\s*(?def_API|extra_API) *\(\s*'(?[A-Za-z0-9_]+)'\s*,\s*(?[A-Za-z0-9_]+)\s*,\s*\((?((_in|_out|_in_array|_out_array|_inout_array)\([^)]+\)\s*,?\s*)*)\)\s*\)\s*$/, + ); + if (match == null) { + throw new Error(`failed to match def_API call ${JSON.stringify(line)}`); + } + let { name, ret, def } = match.groups; + let params = match.groups.params.trim(); + let text = params; + let parsedParams = []; + while (true) { + text = eatWs(text); + ({ text, match } = eat(text, /^_(?in|out|in_array|out_array|inout_array)\(/)); + if (match == null) { + break; + } + let kind = match.groups.kind; + if (kind === 'inout_array') kind = 'in_array'; // https://github.com/Z3Prover/z3/discussions/5761 + if (kind === 'in' || kind === 'out') { + ({ text, match } = expect(text, /^[A-Za-z0-9_]+/)); + parsedParams.push({ kind, type: match[0] }); + } else { + ({ text, match } = expect(text, /^(\d+),/)); + let sizeIndex = Number(match[1]); + text = eatWs(text); + ({ text, match } = expect(text, /^[A-Za-z0-9_]+/)); + parsedParams.push({ kind, sizeIndex, type: match[0] }); + } + ({ text, match } = expect(text, /^\)/)); + text = eatWs(text); + ({ text, match } = eat(text, /^,/)); + } + if (text !== '') { + throw new Error(`extra text in parameter list ${JSON.stringify(text)}`); + } + + + if (name in defApis) { + throw new Error(`multiple defApi calls for ${name}`); + } + defApis[name] = { params: parsedParams, ret, extra: def === 'extra_API' }; + } + + for (let match of contents.matchAll(/DEFINE_TYPE\((?[A-Za-z0-9_]+)\)/g)) { + types[match.groups.type] = match.groups.type; + } + + // we don't have to pre-populate the types map with closure types + // use the Z3_DECLARE_CLOSURE to identify closure types + // for (let match of contents.matchAll(/Z3_DECLARE_CLOSURE\((?[A-Za-z0-9_]+),/g)) { + // types[match.groups.type] = match.groups.type + // } + + // parse enum declarations + for (let idx = 0; idx < contents.length; ) { + let nextIdx = contents.indexOf('typedef enum', idx); + if (nextIdx === -1) { + break; + } + let lineStart = contents.lastIndexOf('\n', nextIdx); + let lineEnd = contents.indexOf(';', nextIdx); + if (lineStart === -1 || lineEnd === -1) { + throw new Error(`could not parse enum at index ${nextIdx}`); + } + idx = lineEnd; + let slice = contents.substring(lineStart, lineEnd); + let { match, text } = eat(slice, /^\s*typedef enum\s*\{/); + if (match === null) { + throw new Error(`could not parse enum ${JSON.stringify(slice)}`); + } + let vals = Object.create(null); + let next = 0; + while (true) { + let blank = true; + while (blank) { + ({ match, text } = eat(text, /^\s*(\/\/[^\n]*\n)?/)); + blank = match[0].length > 0; + } + ({ match, text } = eat(text, /^[A-Za-z0-9_]+/)); + if (match === null) { + throw new Error(`could not parse enum value in ${slice}`); + } + let name = match[0]; + text = eatWs(text); + + ({ match, text } = eat(text, /^= *(?[^\n,\s]+)/)); + if (match !== null) { + let parsedVal = Number(match.groups.val); + if (Object.is(parsedVal, NaN)) { + throw new Error('unknown value ' + match.groups.val); + } + vals[name] = parsedVal; + next = parsedVal; + } else { + vals[name] = next; + } + text = eatWs(text); + ({ match, text } = eat(text, /^,?\s*}/)); + if (match !== null) { + break; + } + + ({ match, text } = expect(text, /^,/)); + + ++next; + } + text = eatWs(text); + ({ match, text } = expect(text, /^[A-Za-z0-9_]+/)); + if (match[0] in enums) { + throw new Error(`duplicate enum definition ${match[0]}`); + } + enums[match[0]] = vals; + text = eatWs(text); + if (text !== '') { + throw new Error('expected end of definition, got ' + text); + } + } + + // parse function declarations + for (let idx = 0; idx < contents.length; ) { + let nextIdx = contents.indexOf(' Z3_API ', idx); + if (nextIdx === -1) { + break; + } + let lineStart = contents.lastIndexOf('\n', nextIdx); + let lineEnd = contents.indexOf(';', nextIdx); + if (lineStart === -1 || lineEnd === -1) { + throw new Error(`could not parse definition at index ${nextIdx}`); + } + idx = lineEnd; + + let slice = contents.substring(lineStart, lineEnd); + let match = slice.match(/^\s*(?[A-Za-z0-9_]+) +Z3_API +(?[A-Za-z0-9_]+)\s*\((?[^)]*)\)/); + if (match == null) { + throw new Error(`failed to match c definition: ${JSON.stringify(slice)}`); + } + let { ret, name, params } = match.groups; + let parsedParams = []; + + if (params.trim() !== 'void') { + for (let param of params.split(',')) { + let paramType, paramName, isConst, isPtr, isArray; + + let { match, text } = eat(param, /^\s*/); + ({ match, text } = eat(text, /^[A-Za-z0-9_]+/)); + if (match === null) { + throw new Error(`failed to parse param type in ${JSON.stringify(slice)} for param ${JSON.stringify(param)}`); + } + paramType = match[0]; + + text = eatWs(text); + + ({ match, text } = eat(text, /^const(?![A-Za-z0-9_])/)); + isConst = match !== null; + + ({ match, text } = eat(text, /^\s*\*/)); + isPtr = match !== null; + + text = eatWs(text); + + if (text === '') { + paramName = 'UNNAMED'; + isArray = false; + } else { + ({ match, text } = eat(text, /^[A-Za-z0-9_]+/)); + if (match === null) { + throw new Error( + `failed to parse param name in ${JSON.stringify(slice)} for param ${JSON.stringify(param)}`, + ); + } + paramName = match[0]; + text = eatWs(text); + + ({ match, text } = eat(text, /^\[\]/)); + isArray = match !== null; + + text = eatWs(text); + + if (text !== '') { + throw new Error(`excess text in param in ${JSON.stringify(slice)} for param ${JSON.stringify(param)}`); + } + } + + if (paramType === 'Z3_string_ptr' && !isPtr) { + paramType = 'Z3_string'; + isPtr = true; + } + + let nullable = false; + if (paramType in optTypes) { + nullable = true; + paramType = optTypes[paramType]; + } + + let cType = paramType; + paramType = aliases[paramType] ?? paramType; + + parsedParams.push({ type: paramType, cType, name: paramName, isConst, isPtr, isArray, nullable }); + } + } + + let nullableRet = false; + if (ret in optTypes) { + nullableRet = true; + ret = optTypes[ret]; + } + + let cRet = ret; + ret = aliases[ret] ?? ret; + + if (name in defApis) { + functions.push({ ret, cRet, name, params: parsedParams, nullableRet }); + } + // only a few things are missing `def_API`; we'll skip those + } +} + +function isKnownType(t) { + return t in enums || t in types || t in primitiveTypes || ['string', 'boolean', 'void'].includes(t); +} + +for (let fn of functions) { + if (!isKnownType(fn.ret)) { + throw new Error(`unknown type ${fn.ret}`); + } + let defParams = defApis[fn.name].params; + if (fn.params.length !== defParams.length) { + throw new Error(`parameter length mismatch for ${fn.name}`); + } + let idx = 0; + for (let param of fn.params) { + if (!isKnownType(param.type)) { + throw new Error(`unknown type ${param.type}`); + } + param.kind = defParams[idx].kind; + if (param.kind === 'in_array' || param.kind === 'out_array') { + if (defParams[idx].sizeIndex == null) { + throw new Error(`function ${fn.name} parameter ${idx} is marked as ${param.kind} but has no index`); + } + param.sizeIndex = defParams[idx].sizeIndex; + if (!param.isArray && param.isPtr) { + // not clear why some things are written as `int * x` and others `int x[]` + // but we can jsut cast + param.isArray = true; + param.isPtr = false; + } + if (!param.isArray) { + throw new Error(`function ${fn.name} parameter ${idx} is marked as ${param.kind} but not typed as array`); + } + } + ++idx; + } +} + +function eat(str, regex) { + const match = str.match(regex); + if (match == null) { + return { match, text: str }; + } + return { match, text: str.substring(match[0].length) }; +} + +function eatWs(text) { + return eat(text, /^\s*/).text; +} + +function expect(str, regex) { + let { text, match } = eat(str, regex); + if (match === null) { + throw new Error(`expected ${regex}, got ${JSON.stringify(text)}`); + } + return { text, match }; +} + +module.exports = { primitiveTypes, types, enums, functions }; diff --git a/src/api/js/src/async-wrapper.js b/src/api/js/src/async-wrapper.js new file mode 100644 index 000000000..7a1b95c4f --- /dev/null +++ b/src/api/js/src/async-wrapper.js @@ -0,0 +1,38 @@ +// this wrapper works with async-fns to provide promise-based off-thread versions of some functions + +let capability = null; +function resolve_async(val) { + // setTimeout is a workaround for https://github.com/emscripten-core/emscripten/issues/15900 + if (capability == null) { + return; + } + let cap = capability; + capability = null; + + setTimeout(() => { + cap.resolve(val); + }, 0); +} + +function reject_async(val) { + if (capability == null) { + return; + } + let cap = capability; + capability = null; + + setTimeout(() => { + cap.reject(val); + }, 0); +} + +Module.async_call = function (f, ...args) { + if (capability !== null) { + throw new Error(`you can't execute multiple async functions at the same time; let the previous one finish first`); + } + let promise = new Promise((resolve, reject) => { + capability = { resolve, reject }; + }); + f(...args); + return promise; +}; diff --git a/src/api/js/test-ts-api.ts b/src/api/js/test-ts-api.ts new file mode 100644 index 000000000..0e371fc6e --- /dev/null +++ b/src/api/js/test-ts-api.ts @@ -0,0 +1,392 @@ +// Some of the examples from test_capi.c. +// Note that none of the type annotations on variable declarations are necessary: +// TypeScript can infer all of them. +// They're just here so readers can see what types things are. + +import type { + Z3_config, + Z3_context, + Z3_solver, + Z3_sort, + Z3_ast, + Z3_app, + Z3_model, + Z3_symbol, + Z3_ast_vector, + Z3_func_decl, + Z3_func_interp, + Z3_func_entry, +} from './build/wrapper'; +import { init, Z3_lbool, Z3_ast_kind, Z3_sort_kind, Z3_symbol_kind } from './build/wrapper'; + +// @ts-ignore we're not going to bother with types for this +import { sprintf } from 'sprintf-js'; + +let printf = (str: string, ...args: unknown[]) => console.log(sprintf(str.replace(/\n$/, ''), ...args)); + +(async () => { + let { em, Z3 } = await init(); + + function mk_context(): Z3_context { + let cfg: Z3_config = Z3.mk_config(); + Z3.set_param_value(cfg, 'model', 'true'); + let ctx: Z3_context = Z3.mk_context(cfg); + Z3.del_config(cfg); + return ctx; + } + + function mk_proof_context(): Z3_context { + let cfg: Z3_config = Z3.mk_config(); + let ctx: Z3_context; + Z3.set_param_value(cfg, 'proof', 'true'); + ctx = Z3.mk_context(cfg); + Z3.del_config(cfg); + return ctx; + } + + function mk_solver(ctx: Z3_context): Z3_solver { + let s: Z3_solver = Z3.mk_solver(ctx); + Z3.solver_inc_ref(ctx, s); + return s; + } + + function del_solver(ctx: Z3_context, s: Z3_solver) { + Z3.solver_dec_ref(ctx, s); + } + + function mk_var(ctx: Z3_context, name: string, ty: Z3_sort): Z3_ast { + let s: Z3_symbol = Z3.mk_string_symbol(ctx, name); + return Z3.mk_const(ctx, s, ty); + } + + function mk_bool_var(ctx: Z3_context, name: string): Z3_ast { + let ty: Z3_sort = Z3.mk_bool_sort(ctx); + return mk_var(ctx, name, ty); + } + + function exitf(m: string) { + console.error(`BUG: ${m}`); + process.exit(1); + } + + function display_symbol(c: Z3_context, s: Z3_symbol) { + switch (Z3.get_symbol_kind(c, s)) { + case Z3_symbol_kind.Z3_INT_SYMBOL: + process.stdout.write(sprintf('#%d', Z3.get_symbol_int(c, s))); + break; + case Z3_symbol_kind.Z3_STRING_SYMBOL: + process.stdout.write(sprintf('%s', Z3.get_symbol_string(c, s))); + break; + default: + throw new Error('unreachable'); + } + } + + function display_sort(c: Z3_context, ty: Z3_sort) { + switch (Z3.get_sort_kind(c, ty)) { + case Z3_sort_kind.Z3_UNINTERPRETED_SORT: + display_symbol(c, Z3.get_sort_name(c, ty)); + break; + case Z3_sort_kind.Z3_BOOL_SORT: + process.stdout.write('bool'); + break; + case Z3_sort_kind.Z3_INT_SORT: + process.stdout.write('int'); + break; + case Z3_sort_kind.Z3_REAL_SORT: + process.stdout.write('real'); + break; + case Z3_sort_kind.Z3_BV_SORT: + process.stdout.write(sprintf('bv%d', Z3.get_bv_sort_size(c, ty))); + break; + case Z3_sort_kind.Z3_ARRAY_SORT: + process.stdout.write('['); + display_sort(c, Z3.get_array_sort_domain(c, ty)); + process.stdout.write('->'); + display_sort(c, Z3.get_array_sort_range(c, ty)); + process.stdout.write(']'); + break; + case Z3_sort_kind.Z3_DATATYPE_SORT: + if (Z3.get_datatype_sort_num_constructors(c, ty) !== 1) { + process.stdout.write(sprintf('%s', Z3.sort_to_string(c, ty))); + break; + } + { + let num_fields: number = Z3.get_tuple_sort_num_fields(c, ty); + let i: number; + process.stdout.write('('); + for (i = 0; i < num_fields; i++) { + let field: Z3_func_decl = Z3.get_tuple_sort_field_decl(c, ty, i); + if (i > 0) { + process.stdout.write(', '); + } + display_sort(c, Z3.get_range(c, field)); + } + process.stdout.write(')'); + break; + } + default: + process.stdout.write('unknown['); + display_symbol(c, Z3.get_sort_name(c, ty)); + process.stdout.write(']'); + break; + } + } + + function display_ast(c: Z3_context, v: Z3_ast) { + switch (Z3.get_ast_kind(c, v)) { + case Z3_ast_kind.Z3_NUMERAL_AST: { + let t: Z3_sort; + process.stdout.write(sprintf('%s', Z3.get_numeral_string(c, v))); + t = Z3.get_sort(c, v); + process.stdout.write(':'); + display_sort(c, t); + break; + } + case Z3_ast_kind.Z3_APP_AST: { + let i: number; + let app: Z3_app = Z3.to_app(c, v); + let num_fields: number = Z3.get_app_num_args(c, app); + let d: Z3_func_decl = Z3.get_app_decl(c, app); + process.stdout.write(sprintf('%s', Z3.func_decl_to_string(c, d))); + if (num_fields > 0) { + process.stdout.write('['); + for (i = 0; i < num_fields; i++) { + if (i > 0) { + process.stdout.write(', '); + } + display_ast(c, Z3.get_app_arg(c, app, i)); + } + process.stdout.write(']'); + } + break; + } + case Z3_ast_kind.Z3_QUANTIFIER_AST: { + process.stdout.write('quantifier'); + } + default: + process.stdout.write('#unknown'); + } + } + + function display_function_interpretations(c: Z3_context, m: Z3_model) { + let num_functions: number; + let i: number; + + process.stdout.write('function interpretations:\n'); + + num_functions = Z3.model_get_num_funcs(c, m); + for (i = 0; i < num_functions; i++) { + let fdecl: Z3_func_decl; + let name: Z3_symbol; + let func_else: Z3_ast; + let num_entries = 0; + let j: number; + let finterp: Z3_func_interp | null; + + fdecl = Z3.model_get_func_decl(c, m, i); + finterp = Z3.model_get_func_interp(c, m, fdecl); + if (finterp) Z3.func_interp_inc_ref(c, finterp); + name = Z3.get_decl_name(c, fdecl); + display_symbol(c, name); + process.stdout.write(' = {'); + if (finterp) num_entries = Z3.func_interp_get_num_entries(c, finterp); + for (j = 0; j < num_entries; j++) { + let num_args: number; + let k: number; + let fentry: Z3_func_entry = Z3.func_interp_get_entry(c, finterp!, j); + Z3.func_entry_inc_ref(c, fentry); + if (j > 0) { + process.stdout.write(', '); + } + num_args = Z3.func_entry_get_num_args(c, fentry); + process.stdout.write('('); + for (k = 0; k < num_args; k++) { + if (k > 0) { + process.stdout.write(', '); + } + display_ast(c, Z3.func_entry_get_arg(c, fentry, k)); + } + process.stdout.write('|->'); + display_ast(c, Z3.func_entry_get_value(c, fentry)); + process.stdout.write(')'); + Z3.func_entry_dec_ref(c, fentry); + } + if (num_entries > 0) { + process.stdout.write(', '); + } + if (finterp) { + process.stdout.write('(else|->'); + func_else = Z3.func_interp_get_else(c, finterp); + display_ast(c, func_else); + Z3.func_interp_dec_ref(c, finterp); + } + process.stdout.write(')}\n'); + } + } + function display_model(c: Z3_context, m: Z3_model | null) { + let num_constants: number; + let i: number; + + if (!m) return; + + num_constants = Z3.model_get_num_consts(c, m); + for (i = 0; i < num_constants; i++) { + let name: Z3_symbol; + let cnst: Z3_func_decl = Z3.model_get_const_decl(c, m, i); + let a: Z3_ast; + let v: Z3_ast; + let ok: boolean; + name = Z3.get_decl_name(c, cnst); + display_symbol(c, name); + process.stdout.write(' = '); + a = Z3.mk_app(c, cnst, []); + v = a; + let result = Z3.model_eval(c, m, a, true); + ok = result != null; + if (result != null) { + v = result; + } + display_ast(c, v); + process.stdout.write('\n'); + } + display_function_interpretations(c, m); + } + + async function check(ctx: Z3_context, s: Z3_solver, expected_result: Z3_lbool) { + let m: Z3_model | null = null; + let result: Z3_lbool = await Z3.solver_check(ctx, s); + switch (result) { + case Z3_lbool.Z3_L_FALSE: + printf('unsat\n'); + break; + case Z3_lbool.Z3_L_UNDEF: + printf('unknown\n'); + m = Z3.solver_get_model(ctx, s); + if (m) Z3.model_inc_ref(ctx, m); + printf('potential model:\n%s\n', Z3.model_to_string(ctx, m)); + break; + case Z3_lbool.Z3_L_TRUE: + m = Z3.solver_get_model(ctx, s); + if (m) Z3.model_inc_ref(ctx, m); + printf('sat\n%s\n', Z3.model_to_string(ctx, m)); + break; + } + if (result !== expected_result) { + exitf('unexpected result'); + } + if (m) Z3.model_dec_ref(ctx, m); + } + + // https://github.com/Z3Prover/z3/blob/174889ad5ea8b1e1127aeec8a4121a5687ac9a2b/examples/c/test_capi.c#L1440 + async function bitvector_example2() { + let ctx: Z3_context = mk_context(); + let s: Z3_solver = mk_solver(ctx); + + /* construct x ^ y - 103 == x * y */ + let bv_sort: Z3_sort = Z3.mk_bv_sort(ctx, 32); + let x: Z3_ast = mk_var(ctx, 'x', bv_sort); + let y: Z3_ast = mk_var(ctx, 'y', bv_sort); + let x_xor_y: Z3_ast = Z3.mk_bvxor(ctx, x, y); + let c103: Z3_ast = Z3.mk_numeral(ctx, '103', bv_sort); + let lhs: Z3_ast = Z3.mk_bvsub(ctx, x_xor_y, c103); + let rhs: Z3_ast = Z3.mk_bvmul(ctx, x, y); + let ctr: Z3_ast = Z3.mk_eq(ctx, lhs, rhs); + + printf('\nbitvector_example2\n'); + // LOG_MSG("bitvector_example2"); + printf('find values of x and y, such that x ^ y - 103 == x * y\n'); + + // /* add the constraint x ^ y - 103 == x * y<\tt> to the logical context */ + Z3.solver_assert(ctx, s, ctr); + + // /* find a model (i.e., values for x an y that satisfy the constraint */ + await check(ctx, s, Z3_lbool.Z3_L_TRUE); + + del_solver(ctx, s); + Z3.del_context(ctx); + } + + // https://github.com/Z3Prover/z3/blob/174889ad5ea8b1e1127aeec8a4121a5687ac9a2b/examples/c/test_capi.c#L2230 + async function unsat_core_and_proof_example() { + let ctx: Z3_context = mk_proof_context(); + let s: Z3_solver = mk_solver(ctx); + let pa: Z3_ast = mk_bool_var(ctx, 'PredA'); + let pb: Z3_ast = mk_bool_var(ctx, 'PredB'); + let pc: Z3_ast = mk_bool_var(ctx, 'PredC'); + let pd: Z3_ast = mk_bool_var(ctx, 'PredD'); + let p1: Z3_ast = mk_bool_var(ctx, 'P1'); + let p2: Z3_ast = mk_bool_var(ctx, 'P2'); + let p3: Z3_ast = mk_bool_var(ctx, 'P3'); + let p4: Z3_ast = mk_bool_var(ctx, 'P4'); + let assumptions: Z3_ast[] = [Z3.mk_not(ctx, p1), Z3.mk_not(ctx, p2), Z3.mk_not(ctx, p3), Z3.mk_not(ctx, p4)]; + let args1: Z3_ast[] = [pa, pb, pc]; + let f1 = Z3.mk_and(ctx, args1); + let args2: Z3_ast[] = [pa, Z3.mk_not(ctx, pb), pc]; + let f2 = Z3.mk_and(ctx, args2); + let args3: Z3_ast[] = [Z3.mk_not(ctx, pa), Z3.mk_not(ctx, pc)]; + let f3 = Z3.mk_or(ctx, args3); + let f4 = pd; + let g1: Z3_ast[] = [f1, p1]; + let g2: Z3_ast[] = [f2, p2]; + let g3: Z3_ast[] = [f3, p3]; + let g4: Z3_ast[] = [f4, p4]; + let result: Z3_lbool; + let proof: Z3_ast; + let m: Z3_model | null = null; + let i: number; + let core: Z3_ast_vector; + + printf('\nunsat_core_and_proof_example\n'); + // LOG_MSG("unsat_core_and_proof_example"); + + Z3.solver_assert(ctx, s, Z3.mk_or(ctx, g1)); + Z3.solver_assert(ctx, s, Z3.mk_or(ctx, g2)); + Z3.solver_assert(ctx, s, Z3.mk_or(ctx, g3)); + Z3.solver_assert(ctx, s, Z3.mk_or(ctx, g4)); + + result = await Z3.solver_check_assumptions(ctx, s, assumptions); + + switch (result) { + case Z3_lbool.Z3_L_FALSE: + core = Z3.solver_get_unsat_core(ctx, s); + proof = Z3.solver_get_proof(ctx, s); + printf('unsat\n'); + printf('proof: %s\n', Z3.ast_to_string(ctx, proof)); + + printf('\ncore:\n'); + for (i = 0; i < Z3.ast_vector_size(ctx, core); ++i) { + printf('%s\n', Z3.ast_to_string(ctx, Z3.ast_vector_get(ctx, core, i))); + } + printf('\n'); + break; + case Z3_lbool.Z3_L_UNDEF: + printf('unknown\n'); + printf('potential model:\n'); + m = Z3.solver_get_model(ctx, s); + if (m) Z3.model_inc_ref(ctx, m); + display_model(ctx, m); + throw new Error('result was undef, should have been unsat'); + case Z3_lbool.Z3_L_TRUE: + printf('sat\n'); + m = Z3.solver_get_model(ctx, s); + if (m) Z3.model_inc_ref(ctx, m); + display_model(ctx, m); + throw new Error('result was sat, should have been unsat'); + } + + /* delete logical context */ + if (m) Z3.model_dec_ref(ctx, m); + del_solver(ctx, s); + Z3.del_context(ctx); + } + + await bitvector_example2(); + await unsat_core_and_proof_example(); + + // shut down + em.PThread.terminateAllThreads(); +})().catch(e => { + console.error('error', e); + process.exit(1); +}); diff --git a/src/api/js/tsconfig.json b/src/api/js/tsconfig.json new file mode 100644 index 000000000..73406c691 --- /dev/null +++ b/src/api/js/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "es2021", + "module": "commonjs", + "esModuleInterop": true, + "declaration": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true + }, + "exclude": [ + "src" + ] +} diff --git a/src/api/ml/z3.ml b/src/api/ml/z3.ml index 1448a9ea5..b904937a9 100644 --- a/src/api/ml/z3.ml +++ b/src/api/ml/z3.ml @@ -203,7 +203,7 @@ end = struct let equal = (=) (* The standard comparison uses the custom operations of the C layer *) - let compare = Pervasives.compare + let compare = Stdlib.compare let translate (x:ast) (to_ctx:context) = if gc x = to_ctx then diff --git a/src/api/python/CMakeLists.txt b/src/api/python/CMakeLists.txt index 8e50ad255..e791a2f11 100644 --- a/src/api/python/CMakeLists.txt +++ b/src/api/python/CMakeLists.txt @@ -99,7 +99,7 @@ if (Z3_INSTALL_PYTHON_BINDINGS) message(STATUS "CMAKE_INSTALL_PYTHON_PKG_DIR not set. Trying to guess") execute_process( COMMAND "${PYTHON_EXECUTABLE}" "-c" - "import distutils.sysconfig; print(distutils.sysconfig.get_python_lib())" + "import sysconfig; print(sysconfig.get_path('purelib'))" RESULT_VARIABLE exit_code OUTPUT_VARIABLE CMAKE_INSTALL_PYTHON_PKG_DIR OUTPUT_STRIP_TRAILING_WHITESPACE diff --git a/src/api/python/z3/z3.py b/src/api/python/z3/z3.py index 8e6a86cf1..13c922f85 100644 --- a/src/api/python/z3/z3.py +++ b/src/api/python/z3/z3.py @@ -1150,6 +1150,8 @@ def _to_expr_ref(a, ctx): return FPRMRef(a, ctx) if sk == Z3_SEQ_SORT: return SeqRef(a, ctx) + if sk == Z3_CHAR_SORT: + return CharRef(a, ctx) if sk == Z3_RE_SORT: return ReRef(a, ctx) return ExprRef(a, ctx) @@ -4809,7 +4811,7 @@ def Ext(a, b): """ ctx = a.ctx if z3_debug(): - _z3_assert(is_array_sort(a) and is_array(b), "arguments must be arrays") + _z3_assert(is_array_sort(a) and (is_array(b) or b.is_lambda()), "arguments must be arrays") return _to_expr_ref(Z3_mk_array_ext(ctx.ref(), a.as_ast(), b.as_ast()), ctx) @@ -8789,6 +8791,10 @@ def Product(*args): _args, sz = _to_ast_array(args) return ArithRef(Z3_mk_mul(ctx.ref(), sz, _args), ctx) +def Abs(arg): + """Create the absolute value of an arithmetic expression""" + return If(arg > 0, arg, -arg) + def AtMost(*args): """Create an at-most Pseudo-Boolean k constraint. @@ -10580,7 +10586,6 @@ class CharSortRef(SortRef): """Character sort.""" - def StringSort(ctx=None): """Create a string sort >>> s = StringSort() @@ -10646,18 +10651,68 @@ class SeqRef(ExprRef): return Z3_ast_to_string(self.ctx_ref(), self.as_ast()) def __le__(self, other): - return SeqRef(Z3_mk_str_le(self.ctx_ref(), self.as_ast(), other.as_ast()), self.ctx) + return _to_expr_ref(Z3_mk_str_le(self.ctx_ref(), self.as_ast(), other.as_ast()), self.ctx) def __lt__(self, other): - return SeqRef(Z3_mk_str_lt(self.ctx_ref(), self.as_ast(), other.as_ast()), self.ctx) + return _to_expr_ref(Z3_mk_str_lt(self.ctx_ref(), self.as_ast(), other.as_ast()), self.ctx) def __ge__(self, other): - return SeqRef(Z3_mk_str_le(self.ctx_ref(), other.as_ast(), self.as_ast()), self.ctx) + return _to_expr_ref(Z3_mk_str_le(self.ctx_ref(), other.as_ast(), self.as_ast()), self.ctx) def __gt__(self, other): - return SeqRef(Z3_mk_str_lt(self.ctx_ref(), other.as_ast(), self.as_ast()), self.ctx) + return _to_expr_ref(Z3_mk_str_lt(self.ctx_ref(), other.as_ast(), self.as_ast()), self.ctx) +def _coerce_char(ch, ctx=None): + if isinstance(ch, str): + ctx = _get_ctx(ctx) + ch = CharVal(ch, ctx) + if not is_expr(ch): + raise Z3Exception("Character expression expected") + return ch + +class CharRef(ExprRef): + """Character expression.""" + + def __le__(self, other): + other = _coerce_char(other, self.ctx) + return _to_expr_ref(Z3_mk_char_le(self.ctx_ref(), self.as_ast(), other.as_ast()), self.ctx) + + def to_int(self): + return _to_expr_ref(Z3_mk_char_to_int(self.ctx_ref(), self.as_ast()), self.ctx) + + def to_bv(self): + return _to_expr_ref(Z3_mk_char_to_bv(self.ctx_ref(), self.as_ast()), self.ctx) + + def is_digit(self): + return _to_expr_ref(Z3_mk_char_is_digit(self.ctx_ref(), self.as_ast()), self.ctx) + + +def CharVal(ch, ctx=None): + ctx = _get_ctx(ctx) + if isinstance(ch, str): + ch = ord(ch) + if not isinstance(ch, int): + raise Z3Exception("character value should be an ordinal") + return _to_expr_ref(Z3_mk_char(ctx.ref(), ch), ctx) + +def CharFromBv(ch, ctx=None): + if not is_expr(ch): + raise Z3Expression("Bit-vector expression needed") + return _to_expr_ref(Z3_mk_char_from_bv(ch.ctx_ref(), ch.as_ast()), ch.ctx) + +def CharToBv(ch, ctx=None): + ch = _coerce_char(ch, ctx) + return ch.to_bv() + +def CharToInt(ch, ctx=None): + ch = _coerce_char(ch, ctx) + return ch.to_int() + +def CharIsDigit(ch, ctx=None): + ch = _coerce_char(ch, ctx) + return ch.is_digit() + def _coerce_seq(s, ctx=None): if isinstance(s, str): ctx = _get_ctx(ctx) @@ -10774,6 +10829,7 @@ def Full(s): raise Z3Exception("Non-sequence, non-regular expression sort passed to Full") + def Unit(a): """Create a singleton sequence""" return SeqRef(Z3_mk_seq_unit(a.ctx_ref(), a.as_ast()), a.ctx) @@ -10905,6 +10961,18 @@ def IntToStr(s): return SeqRef(Z3_mk_int_to_str(s.ctx_ref(), s.as_ast()), s.ctx) +def StrToCode(s): + """Convert a unit length string to integer code""" + if not is_expr(s): + s = _py2expr(s) + return ArithRef(Z3_mk_string_to_code(s.ctx_ref(), s.as_ast()), s.ctx) + +def StrFromCode(c): + """Convert code to a string""" + if not is_expr(c): + c = _py2expr(c) + return SeqRef(Z3_mk_string_from_code(c.ctx_ref(), c.as_ast()), c.ctx) + def Re(s, ctx=None): """The regular expression that accepts sequence 's' >>> s1 = Re("ab") @@ -11065,6 +11133,11 @@ def Range(lo, hi, ctx=None): hi = _coerce_seq(hi, ctx) return ReRef(Z3_mk_re_range(lo.ctx_ref(), lo.ast, hi.ast), lo.ctx) +def Diff(a, b, ctx=None): + """Create the difference regular epression + """ + return ReRef(Z3_mk_re_diff(a.ctx_ref(), a.ast, b.ast), a.ctx) + def AllChar(regex_sort, ctx=None): """Create a regular expression that accepts all single character strings """ diff --git a/src/api/z3_api.h b/src/api/z3_api.h index 767eebc43..0b0d286d1 100644 --- a/src/api/z3_api.h +++ b/src/api/z3_api.h @@ -1192,6 +1192,9 @@ typedef enum { Z3_OP_SEQ_CONTAINS, Z3_OP_SEQ_EXTRACT, Z3_OP_SEQ_REPLACE, + Z3_OP_SEQ_REPLACE_RE, + Z3_OP_SEQ_REPLACE_RE_ALL, + Z3_OP_SEQ_REPLACE_ALL, Z3_OP_SEQ_AT, Z3_OP_SEQ_NTH, Z3_OP_SEQ_LENGTH, @@ -1205,6 +1208,8 @@ typedef enum { Z3_OP_INT_TO_STR, Z3_OP_UBV_TO_STR, Z3_OP_SBV_TO_STR, + Z3_OP_STR_TO_CODE, + Z3_OP_STR_FROM_CODE, Z3_OP_STRING_LT, Z3_OP_STRING_LE, @@ -1216,7 +1221,9 @@ typedef enum { Z3_OP_RE_UNION, Z3_OP_RE_RANGE, Z3_OP_RE_LOOP, + Z3_OP_RE_POWER, Z3_OP_RE_INTERSECT, + Z3_OP_RE_DIFF, Z3_OP_RE_EMPTY_SET, Z3_OP_RE_FULL_SET, Z3_OP_RE_COMPLEMENT, @@ -1418,18 +1425,18 @@ typedef enum /** \brief Z3 custom error handler (See #Z3_set_error_handler). */ -typedef void Z3_error_handler(Z3_context c, Z3_error_code e); - +Z3_DECLARE_CLOSURE(Z3_error_handler, void, (Z3_context c, Z3_error_code e)); /** \brief callback functions for user propagator. */ -typedef void Z3_push_eh(void* ctx); -typedef void Z3_pop_eh(void* ctx, unsigned num_scopes); -typedef void* Z3_fresh_eh(void* ctx, Z3_context new_context); -typedef void Z3_fixed_eh(void* ctx, Z3_solver_callback cb, unsigned id, Z3_ast value); -typedef void Z3_eq_eh(void* ctx, Z3_solver_callback cb, unsigned x, unsigned y); -typedef void Z3_final_eh(void* ctx, Z3_solver_callback cb); +Z3_DECLARE_CLOSURE(Z3_push_eh, void, (void* ctx)); +Z3_DECLARE_CLOSURE(Z3_pop_eh, void, (void* ctx, unsigned num_scopes)); +Z3_DECLARE_CLOSURE(Z3_fresh_eh, void*, (void* ctx, Z3_context new_context)); +Z3_DECLARE_CLOSURE(Z3_fixed_eh, void, (void* ctx, Z3_solver_callback cb, unsigned id, Z3_ast value)); +Z3_DECLARE_CLOSURE(Z3_eq_eh, void, (void* ctx, Z3_solver_callback cb, unsigned x, unsigned y)); +Z3_DECLARE_CLOSURE(Z3_final_eh, void, (void* ctx, Z3_solver_callback cb)); +Z3_DECLARE_CLOSURE(Z3_created_eh, void, (void* ctx, Z3_solver_callback cb, Z3_ast e, unsigned id)); /** @@ -1510,7 +1517,7 @@ extern "C" { def_API('Z3_global_param_get', BOOL, (_in(STRING), _out(STRING))) */ - Z3_bool_opt Z3_API Z3_global_param_get(Z3_string param_id, Z3_string_ptr param_value); + Z3_bool Z3_API Z3_global_param_get(Z3_string param_id, Z3_string_ptr param_value); /**@}*/ @@ -3702,6 +3709,21 @@ extern "C" { */ Z3_ast Z3_API Z3_mk_int_to_str(Z3_context c, Z3_ast s); + + /** + \brief String to code conversion. + + def_API('Z3_mk_string_to_code', AST, (_in(CONTEXT), _in(AST))) + */ + Z3_ast Z3_API Z3_mk_string_to_code(Z3_context c, Z3_ast a); + + /** + \brief Code to string conversion. + + def_API('Z3_mk_string_from_code', AST, (_in(CONTEXT), _in(AST))) + */ + Z3_ast Z3_API Z3_mk_string_from_code(Z3_context c, Z3_ast a); + /** \brief Unsigned bit-vector to string conversion. @@ -3811,6 +3833,13 @@ extern "C" { */ Z3_ast Z3_API Z3_mk_re_complement(Z3_context c, Z3_ast re); + /** + \brief Create the difference of regular expressions. + + def_API('Z3_mk_re_diff', AST ,(_in(CONTEXT), _in(AST), _in(AST))) + */ + Z3_ast Z3_API Z3_mk_re_diff(Z3_context c, Z3_ast re1, Z3_ast re2); + /** \brief Create an empty regular expression of sort \c re. @@ -3830,6 +3859,13 @@ extern "C" { */ Z3_ast Z3_API Z3_mk_re_full(Z3_context c, Z3_sort re); + + /** + \brief Create a character literal + def_API('Z3_mk_char', AST, (_in(CONTEXT), _in(UINT))) + */ + Z3_ast Z3_API Z3_mk_char(Z3_context c, unsigned ch); + /** \brief Create less than or equal to between two characters. @@ -4312,7 +4348,7 @@ extern "C" { def_API('Z3_get_finite_domain_sort_size', BOOL, (_in(CONTEXT), _in(SORT), _out(UINT64))) */ - Z3_bool_opt Z3_API Z3_get_finite_domain_sort_size(Z3_context c, Z3_sort s, uint64_t* r); + Z3_bool Z3_API Z3_get_finite_domain_sort_size(Z3_context c, Z3_sort s, uint64_t* r); /** \brief Return the domain of the given array sort. @@ -5251,7 +5287,7 @@ extern "C" { def_API('Z3_model_eval', BOOL, (_in(CONTEXT), _in(MODEL), _in(AST), _in(BOOL), _out(AST))) */ - Z3_bool_opt Z3_API Z3_model_eval(Z3_context c, Z3_model m, Z3_ast t, bool model_completion, Z3_ast * v); + Z3_bool Z3_API Z3_model_eval(Z3_context c, Z3_model m, Z3_ast t, bool model_completion, Z3_ast * v); /** \brief Return the interpretation (i.e., assignment) of constant \c a in the model \c m. @@ -6676,6 +6712,24 @@ extern "C" { */ void Z3_API Z3_solver_propagate_diseq(Z3_context c, Z3_solver s, Z3_eq_eh eq_eh); + /** + * \brief register a callback when a new expression with a registered function is used by the solver + * The registered function appears at the top level and is created using \ref Z3_propagate_solver_declare. + */ + void Z3_API Z3_solver_propagate_created(Z3_context c, Z3_solver s, Z3_created_eh created_eh); + + /** + Create uninterpreted function declaration for the user propagator. + When expressions using the function are created by the solver invoke a callback + to \ref \Z3_solver_progate_created with arguments + 1. context and callback solve + 2. declared_expr: expression using function that was used as the top-level symbol + 3. declared_id: a unique identifier (unique within the current scope) to track the expression. + + def_API('Z3_solver_propagate_declare', FUNC_DECL, (_in(CONTEXT), _in(SYMBOL), _in(UINT), _in_array(2, SORT), _in(SORT))) + */ + Z3_func_decl Z3_API Z3_solver_propagate_declare(Z3_context c, Z3_symbol name, unsigned n, Z3_sort* domain, Z3_sort range); + /** \brief register an expression to propagate on with the solver. Only expressions of type Bool and type Bit-Vector can be registered for propagation. diff --git a/src/api/z3_macros.h b/src/api/z3_macros.h index d1ac18804..08756cf15 100644 --- a/src/api/z3_macros.h +++ b/src/api/z3_macros.h @@ -4,9 +4,6 @@ Copyright (c) 2015 Microsoft Corporation --*/ -#ifndef Z3_bool_opt -#define Z3_bool_opt Z3_bool -#endif #ifndef Z3_API # ifdef __GNUC__ @@ -19,3 +16,7 @@ Copyright (c) 2015 Microsoft Corporation #ifndef DEFINE_TYPE #define DEFINE_TYPE(T) typedef struct _ ## T *T #endif + +#ifndef Z3_DECLARE_CLOSURE +#define Z3_DECLARE_CLOSURE(_NAME_,_RETURN_,_ARGS_) typedef _RETURN_ _NAME_ _ARGS_ +#endif diff --git a/src/ast/arith_decl_plugin.h b/src/ast/arith_decl_plugin.h index 399aa76f2..781996662 100644 --- a/src/ast/arith_decl_plugin.h +++ b/src/ast/arith_decl_plugin.h @@ -273,6 +273,17 @@ public: bool is_rem0(func_decl const * n) const { return is_decl_of(n, arith_family_id, OP_REM0); } bool is_mod0(func_decl const * n) const { return is_decl_of(n, arith_family_id, OP_MOD0); } bool is_power0(func_decl const * n) const { return is_decl_of(n, arith_family_id, OP_POWER0); } + bool is_power(func_decl const * n) const { return is_decl_of(n, arith_family_id, OP_POWER); } + bool is_add(func_decl const* f) const { return is_decl_of(f, arith_family_id, OP_ADD); } + bool is_mul(func_decl const* f) const { return is_decl_of(f, arith_family_id, OP_MUL); } + bool is_sub(func_decl const* f) const { return is_decl_of(f, arith_family_id, OP_SUB); } + bool is_uminus(func_decl const* f) const { return is_decl_of(f, arith_family_id, OP_UMINUS); } + bool is_div(func_decl const* f) const { return is_decl_of(f, arith_family_id, OP_DIV); } + bool is_rem(func_decl const* f) const { return is_decl_of(f, arith_family_id, OP_REM); } + bool is_mod(func_decl const* f) const { return is_decl_of(f, arith_family_id, OP_MOD); } + bool is_to_real(func_decl const* f) const { return is_decl_of(f, arith_family_id, OP_TO_REAL); } + bool is_to_int(func_decl const* f) const { return is_decl_of(f, arith_family_id, OP_TO_INT); } + bool is_is_int(func_decl const* f) const { return is_decl_of(f, arith_family_id, OP_IS_INT); } bool is_add(expr const * n) const { return is_app_of(n, arith_family_id, OP_ADD); } bool is_sub(expr const * n) const { return is_app_of(n, arith_family_id, OP_SUB); } @@ -291,6 +302,7 @@ public: bool is_is_int(expr const * n) const { return is_app_of(n, arith_family_id, OP_IS_INT); } bool is_power(expr const * n) const { return is_app_of(n, arith_family_id, OP_POWER); } bool is_power0(expr const * n) const { return is_app_of(n, arith_family_id, OP_POWER0); } + bool is_abs(expr const* n) const { return is_app_of(n, arith_family_id, OP_ABS); } bool is_int(sort const * s) const { return is_sort_of(s, arith_family_id, INT_SORT); } bool is_int(expr const * n) const { return is_int(n->get_sort()); } @@ -330,6 +342,7 @@ public: MATCH_UNARY(is_to_real); MATCH_UNARY(is_to_int); MATCH_UNARY(is_is_int); + MATCH_UNARY(is_abs); MATCH_BINARY(is_sub); MATCH_BINARY(is_add); MATCH_BINARY(is_mul); diff --git a/src/ast/array_decl_plugin.h b/src/ast/array_decl_plugin.h index f986eb46a..7298a0e47 100644 --- a/src/ast/array_decl_plugin.h +++ b/src/ast/array_decl_plugin.h @@ -149,7 +149,12 @@ public: bool is_store(expr* n) const { return is_app_of(n, m_fid, OP_STORE); } bool is_const(expr* n) const { return is_app_of(n, m_fid, OP_CONST_ARRAY); } bool is_ext(expr* n) const { return is_app_of(n, m_fid, OP_ARRAY_EXT); } + bool is_ext(func_decl const* f) const { return is_decl_of(f, m_fid, OP_ARRAY_EXT); } bool is_map(expr* n) const { return is_app_of(n, m_fid, OP_ARRAY_MAP); } + bool is_union(expr* n) const { return is_app_of(n, m_fid, OP_SET_UNION); } + bool is_intersect(expr* n) const { return is_app_of(n, m_fid, OP_SET_INTERSECT); } + bool is_difference(expr* n) const { return is_app_of(n, m_fid, OP_SET_DIFFERENCE); } + bool is_complement(expr* n) const { return is_app_of(n, m_fid, OP_SET_COMPLEMENT); } bool is_as_array(expr * n) const { return is_app_of(n, m_fid, OP_AS_ARRAY); } bool is_as_array(expr * n, func_decl*& f) const { return is_as_array(n) && (f = get_as_array_func_decl(n), true); } bool is_set_has_size(expr* e) const { return is_app_of(e, m_fid, OP_SET_HAS_SIZE); } @@ -158,11 +163,14 @@ public: bool is_store(func_decl* f) const { return is_decl_of(f, m_fid, OP_STORE); } bool is_const(func_decl* f) const { return is_decl_of(f, m_fid, OP_CONST_ARRAY); } bool is_map(func_decl* f) const { return is_decl_of(f, m_fid, OP_ARRAY_MAP); } + bool is_union(func_decl* f) const { return is_decl_of(f, m_fid, OP_SET_UNION); } + bool is_intersect(func_decl* f) const { return is_decl_of(f, m_fid, OP_SET_INTERSECT); } bool is_as_array(func_decl* f) const { return is_decl_of(f, m_fid, OP_AS_ARRAY); } bool is_set_has_size(func_decl* f) const { return is_decl_of(f, m_fid, OP_SET_HAS_SIZE); } bool is_set_card(func_decl* f) const { return is_decl_of(f, m_fid, OP_SET_CARD); } bool is_default(func_decl* f) const { return is_decl_of(f, m_fid, OP_ARRAY_DEFAULT); } bool is_default(expr* n) const { return is_app_of(n, m_fid, OP_ARRAY_DEFAULT); } + bool is_subset(expr const* n) const { return is_app_of(n, m_fid, OP_SET_SUBSET); } bool is_as_array(func_decl* f, func_decl*& g) const { return is_decl_of(f, m_fid, OP_AS_ARRAY) && (g = get_as_array_func_decl(f), true); } func_decl * get_as_array_func_decl(expr * n) const; func_decl * get_as_array_func_decl(func_decl* f) const; @@ -172,6 +180,8 @@ public: bool is_const(expr* e, expr*& v) const; bool is_store_ext(expr* e, expr_ref& a, expr_ref_vector& args, expr_ref& value); + + MATCH_BINARY(is_subset); }; class array_util : public array_recognizers { diff --git a/src/ast/ast.h b/src/ast/ast.h index b04e67003..13898c2a6 100644 --- a/src/ast/ast.h +++ b/src/ast/ast.h @@ -845,7 +845,7 @@ class quantifier : public expr { char m_patterns_decls[0]; static unsigned get_obj_size(unsigned num_decls, unsigned num_patterns, unsigned num_no_patterns) { - return sizeof(quantifier) + num_decls * (sizeof(sort *) + sizeof(symbol)) + (num_patterns + num_no_patterns) * sizeof(expr*); + return (unsigned)(sizeof(quantifier) + num_decls * (sizeof(sort *) + sizeof(symbol)) + (num_patterns + num_no_patterns) * sizeof(expr*)); } quantifier(quantifier_kind k, unsigned num_decls, sort * const * decl_sorts, symbol const * decl_names, expr * body, sort* s, diff --git a/src/ast/ast_util.cpp b/src/ast/ast_util.cpp index 993831d64..090a2ff9d 100644 --- a/src/ast/ast_util.cpp +++ b/src/ast/ast_util.cpp @@ -260,39 +260,52 @@ expr_ref mk_distinct(expr_ref_vector const& args) { void flatten_and(expr_ref_vector& result) { ast_manager& m = result.get_manager(); expr* e1, *e2, *e3; + expr_ref_vector pin(m); + expr_fast_mark1 seen; for (unsigned i = 0; i < result.size(); ++i) { - if (m.is_and(result.get(i))) { - app* a = to_app(result.get(i)); - for (expr* arg : *a) result.push_back(arg); + expr* e = result.get(i); + if (seen.is_marked(e)) { + result[i] = result.back(); + result.pop_back(); + --i; + continue; + } + seen.mark(e); + pin.push_back(e); + if (m.is_and(e)) { + app* a = to_app(e); + for (expr* arg : *a) + result.push_back(arg); result[i] = result.back(); result.pop_back(); --i; } - else if (m.is_not(result.get(i), e1) && m.is_not(e1, e2)) { + else if (m.is_not(e, e1) && m.is_not(e1, e2)) { result[i] = e2; --i; } - else if (m.is_not(result.get(i), e1) && m.is_or(e1)) { + else if (m.is_not(e, e1) && m.is_or(e1)) { app* a = to_app(e1); - for (expr* arg : *a) result.push_back(m.mk_not(arg)); + for (expr* arg : *a) + result.push_back(mk_not(m, arg)); result[i] = result.back(); result.pop_back(); --i; } - else if (m.is_not(result.get(i), e1) && m.is_implies(e1,e2,e3)) { + else if (m.is_not(e, e1) && m.is_implies(e1, e2, e3)) { result.push_back(e2); - result[i] = m.mk_not(e3); + result[i] = mk_not(m, e3); --i; } - else if (m.is_true(result.get(i)) || - (m.is_not(result.get(i), e1) && + else if (m.is_true(e) || + (m.is_not(e, e1) && m.is_false(e1))) { result[i] = result.back(); result.pop_back(); --i; } - else if (m.is_false(result.get(i)) || - (m.is_not(result.get(i), e1) && + else if (m.is_false(e) || + (m.is_not(e, e1) && m.is_true(e1))) { result.reset(); result.push_back(m.mk_false()); @@ -317,39 +330,52 @@ void flatten_and(expr_ref& fml) { void flatten_or(expr_ref_vector& result) { ast_manager& m = result.get_manager(); expr* e1, *e2, *e3; + expr_ref_vector pin(m); + expr_fast_mark1 seen; for (unsigned i = 0; i < result.size(); ++i) { - if (m.is_or(result.get(i))) { - app* a = to_app(result.get(i)); - for (expr* arg : *a) result.push_back(arg); + expr* e = result.get(i); + if (seen.is_marked(e)) { + result[i] = result.back(); + result.pop_back(); + --i; + continue; + } + seen.mark(e); + pin.push_back(e); + if (m.is_or(e)) { + app* a = to_app(e); + for (expr* arg : *a) + result.push_back(arg); result[i] = result.back(); result.pop_back(); --i; } - else if (m.is_not(result.get(i), e1) && m.is_not(e1, e2)) { + else if (m.is_not(e, e1) && m.is_not(e1, e2)) { result[i] = e2; --i; } - else if (m.is_not(result.get(i), e1) && m.is_and(e1)) { + else if (m.is_not(e, e1) && m.is_and(e1)) { app* a = to_app(e1); - for (expr* arg : *a) result.push_back(m.mk_not(arg)); + for (expr* arg : *a) + result.push_back(mk_not(m, arg)); result[i] = result.back(); result.pop_back(); --i; } - else if (m.is_implies(result.get(i),e2,e3)) { + else if (m.is_implies(e,e2,e3)) { result.push_back(e3); - result[i] = m.mk_not(e2); + result[i] = mk_not(m, e2); --i; } - else if (m.is_false(result.get(i)) || - (m.is_not(result.get(i), e1) && + else if (m.is_false(e) || + (m.is_not(e, e1) && m.is_true(e1))) { result[i] = result.back(); result.pop_back(); --i; } - else if (m.is_true(result.get(i)) || - (m.is_not(result.get(i), e1) && + else if (m.is_true(e) || + (m.is_not(e, e1) && m.is_false(e1))) { result.reset(); result.push_back(m.mk_true()); diff --git a/src/ast/datatype_decl_plugin.cpp b/src/ast/datatype_decl_plugin.cpp index ce183ce6b..ace0cb567 100644 --- a/src/ast/datatype_decl_plugin.cpp +++ b/src/ast/datatype_decl_plugin.cpp @@ -580,7 +580,7 @@ namespace datatype { m_defs.remove(s); } - bool plugin::is_value_visit(expr * arg, ptr_buffer & todo) const { + bool plugin::is_value_visit(bool unique, expr * arg, ptr_buffer & todo) const { if (!is_app(arg)) return false; family_id fid = to_app(arg)->get_family_id(); @@ -592,12 +592,13 @@ namespace datatype { todo.push_back(to_app(arg)); return true; } - else { + else if (unique) + return m_manager->is_unique_value(arg); + else return m_manager->is_value(arg); - } } - bool plugin::is_value(app * e) const { + bool plugin::is_value_aux(bool unique, app * e) const { TRACE("dt_is_value", tout << "checking\n" << mk_ismt2_pp(e, *m_manager) << "\n";); if (!u().is_constructor(e)) return false; @@ -608,7 +609,7 @@ namespace datatype { ptr_buffer todo; // potentially expensive check for common sub-expressions. for (expr* arg : *e) { - if (!is_value_visit(arg, todo)) { + if (!is_value_visit(unique, arg, todo)) { TRACE("dt_is_value", tout << "not-value:\n" << mk_ismt2_pp(arg, *m_manager) << "\n";); return false; } @@ -618,7 +619,7 @@ namespace datatype { SASSERT(u().is_constructor(curr)); todo.pop_back(); for (expr* arg : *curr) { - if (!is_value_visit(arg, todo)) { + if (!is_value_visit(unique, arg, todo)) { TRACE("dt_is_value", tout << "not-value:\n" << mk_ismt2_pp(arg, *m_manager) << "\n";); return false; } diff --git a/src/ast/datatype_decl_plugin.h b/src/ast/datatype_decl_plugin.h index d4d38a757..0172d0b1d 100644 --- a/src/ast/datatype_decl_plugin.h +++ b/src/ast/datatype_decl_plugin.h @@ -230,9 +230,9 @@ namespace datatype { bool is_fully_interp(sort * s) const override; - bool is_value(app* e) const override; + bool is_value(app* e) const override { return is_value_aux(false, e); } - bool is_unique_value(app * e) const override { return is_value(e); } + bool is_unique_value(app * e) const override { return is_value_aux(true, e); } void get_op_names(svector & op_names, symbol const & logic) override; @@ -257,7 +257,8 @@ namespace datatype { bool has_nested_arrays() const { return m_has_nested_arrays; } private: - bool is_value_visit(expr * arg, ptr_buffer & todo) const; + bool is_value_visit(bool unique, expr * arg, ptr_buffer & todo) const; + bool is_value_aux(bool unique, app * arg) const; func_decl * mk_update_field( unsigned num_parameters, parameter const * parameters, diff --git a/src/ast/euf/euf_egraph.cpp b/src/ast/euf/euf_egraph.cpp index 1f8e14565..8a3a4e002 100644 --- a/src/ast/euf/euf_egraph.cpp +++ b/src/ast/euf/euf_egraph.cpp @@ -25,6 +25,8 @@ namespace euf { enode* egraph::mk_enode(expr* f, unsigned generation, unsigned num_args, enode * const* args) { enode* n = enode::mk(m_region, f, generation, num_args, args); + if (m_default_relevant) + n->set_relevant(true); m_nodes.push_back(n); m_exprs.push_back(f); if (is_app(f) && num_args > 0) { @@ -187,10 +189,10 @@ namespace euf { add_th_diseq(id, v1, v2, n->get_expr()); return; } - for (auto p : euf::enode_th_vars(r1)) { + for (auto const& p : euf::enode_th_vars(r1)) { if (!th_propagates_diseqs(p.get_id())) continue; - for (auto q : euf::enode_th_vars(r2)) + for (auto const& q : euf::enode_th_vars(r2)) if (p.get_id() == q.get_id()) add_th_diseq(p.get_id(), p.get_var(), q.get_var(), n->get_expr()); } @@ -269,6 +271,13 @@ namespace euf { } } + void egraph::set_relevant(enode* n) { + if (n->is_relevant()) + return; + n->set_relevant(true); + m_updates.push_back(update_record(n, update_record::set_relevant())); + } + void egraph::toggle_merge_enabled(enode* n, bool backtracking) { bool enable_merge = !n->merge_enabled(); n->set_merge_enabled(enable_merge); @@ -380,6 +389,10 @@ namespace euf { case update_record::tag_t::is_lbl_set: p.r1->m_lbls.set(p.m_lbls); break; + case update_record::tag_t::is_set_relevant: + SASSERT(p.r1->is_relevant()); + p.r1->set_relevant(false); + break; case update_record::tag_t::is_update_children: for (unsigned i = 0; i < p.r1->num_args(); ++i) { SASSERT(p.r1->m_args[i]->get_root()->m_parents.back() == p.r1); @@ -446,8 +459,8 @@ namespace euf { r2->inc_class_size(r1->class_size()); merge_th_eq(r1, r2); reinsert_parents(r1, r2); - if (m_on_merge) - m_on_merge(r2, r1); + for (auto& cb : m_on_merge) + cb(r2, r1); } void egraph::remove_parents(enode* r1, enode* r2) { @@ -493,7 +506,7 @@ namespace euf { void egraph::merge_th_eq(enode* n, enode* root) { SASSERT(n != root); - for (auto iv : enode_th_vars(n)) { + for (auto const& iv : enode_th_vars(n)) { theory_id id = iv.get_id(); theory_var v = root->get_th_var(id); if (v == null_theory_var) { @@ -754,6 +767,8 @@ namespace euf { } std::ostream& egraph::display(std::ostream& out, unsigned max_args, enode* n) const { + if (!n->is_relevant()) + out << "n"; out << "#" << n->get_expr_id() << " := "; expr* f = n->get_expr(); if (is_app(f)) @@ -770,11 +785,18 @@ namespace euf { out << " " << p->get_expr_id(); out << "] "; } - if (n->value() != l_undef) - out << "[b" << n->bool_var() << " := " << (n->value() == l_true ? "T":"F") << (n->merge_tf()?"":" no merge") << "] "; + auto value_of = [&]() { + switch (n->value()) { + case l_true: return "T"; + case l_false: return "F"; + default: return "?"; + } + }; + if (n->bool_var() != sat::null_bool_var) + out << "[b" << n->bool_var() << " := " << value_of() << (n->merge_tf() ? "" : " no merge") << "] "; if (n->has_th_vars()) { out << "[t"; - for (auto v : enode_th_vars(n)) + for (auto const& v : enode_th_vars(n)) out << " " << v.get_id() << ":" << v.get_var(); out << "] "; } diff --git a/src/ast/euf/euf_egraph.h b/src/ast/euf/euf_egraph.h index 7c1f9e566..a91dbf4a4 100644 --- a/src/ast/euf/euf_egraph.h +++ b/src/ast/euf/euf_egraph.h @@ -32,6 +32,7 @@ Notes: #include "ast/euf/euf_enode.h" #include "ast/euf/euf_etable.h" #include "ast/ast_ll_pp.h" +#include namespace euf { @@ -105,10 +106,11 @@ namespace euf { struct lbl_hash {}; struct lbl_set {}; struct update_children {}; + struct set_relevant {}; enum class tag_t { is_set_parent, is_add_node, is_toggle_merge, is_update_children, is_add_th_var, is_replace_th_var, is_new_lit, is_new_th_eq, is_lbl_hash, is_new_th_eq_qhead, is_new_lits_qhead, - is_inconsistent, is_value_assignment, is_lbl_set }; + is_inconsistent, is_value_assignment, is_lbl_set, is_set_relevant }; tag_t tag; enode* r1; enode* n1; @@ -151,6 +153,8 @@ namespace euf { tag(tag_t::is_lbl_set), r1(n), n1(nullptr), m_lbls(n->m_lbls.get()) {} update_record(enode* n, update_children) : tag(tag_t::is_update_children), r1(n), n1(nullptr), r2_num_parents(UINT_MAX) {} + update_record(enode* n, set_relevant) : + tag(tag_t::is_set_relevant), r1(n), n1(nullptr), r2_num_parents(UINT_MAX) {} }; ast_manager& m; svector m_to_merge; @@ -181,7 +185,8 @@ namespace euf { enode_vector m_todo; stats m_stats; bool m_uses_congruence = false; - std::function m_on_merge; + bool m_default_relevant = true; + std::vector> m_on_merge; std::function m_on_make; std::function m_used_eq; std::function m_used_cc; @@ -292,8 +297,10 @@ namespace euf { void set_merge_enabled(enode* n, bool enable_merge); void set_value(enode* n, lbool value); void set_bool_var(enode* n, unsigned v) { n->set_bool_var(v); } + void set_relevant(enode* n); + void set_default_relevant(bool b) { m_default_relevant = b; } - void set_on_merge(std::function& on_merge) { m_on_merge = on_merge; } + void set_on_merge(std::function& on_merge) { m_on_merge.push_back(on_merge); } void set_on_make(std::function& on_make) { m_on_make = on_make; } void set_used_eq(std::function& used_eq) { m_used_eq = used_eq; } void set_used_cc(std::function& used_cc) { m_used_cc = used_cc; } diff --git a/src/ast/euf/euf_enode.h b/src/ast/euf/euf_enode.h index 8498c9790..850e183e8 100644 --- a/src/ast/euf/euf_enode.h +++ b/src/ast/euf/euf_enode.h @@ -45,10 +45,12 @@ namespace euf { expr* m_expr = nullptr; bool m_mark1 = false; bool m_mark2 = false; + bool m_mark3 = false; bool m_commutative = false; bool m_interpreted = false; bool m_merge_enabled = true; bool m_is_equality = false; // Does the expression represent an equality + bool m_is_relevant = false; lbool m_value = l_undef; // Assignment by SAT solver for Boolean node sat::bool_var m_bool_var = sat::null_bool_var; // SAT solver variable associated with Boolean node unsigned m_class_size = 1; // Size of the equivalence class if the enode is the root. @@ -145,6 +147,8 @@ namespace euf { unsigned num_parents() const { return m_parents.size(); } bool interpreted() const { return m_interpreted; } bool is_equality() const { return m_is_equality; } + bool is_relevant() const { return m_is_relevant; } + void set_relevant(bool b) { m_is_relevant = b; } lbool value() const { return m_value; } bool value_conflict() const { return value() != l_undef && get_root()->value() != l_undef && value() != get_root()->value(); } sat::bool_var bool_var() const { return m_bool_var; } @@ -170,6 +174,9 @@ namespace euf { void mark2() { m_mark2 = true; } void unmark2() { m_mark2 = false; } bool is_marked2() { return m_mark2; } + void mark3() { m_mark3 = true; } + void unmark3() { m_mark3 = false; } + bool is_marked3() { return m_mark3; } template void mark1_targets() { enode* n = this; diff --git a/src/ast/recfun_decl_plugin.cpp b/src/ast/recfun_decl_plugin.cpp index 96ddc7162..9a8c59ae9 100644 --- a/src/ast/recfun_decl_plugin.cpp +++ b/src/ast/recfun_decl_plugin.cpp @@ -410,10 +410,7 @@ namespace recfun { promise_def plugin::ensure_def(symbol const& name, unsigned n, sort *const * params, sort * range, bool is_generated) { def* d = u().decl_fun(name, n, params, range, is_generated); - def* d2 = nullptr; - if (m_defs.find(d->get_decl(), d2)) { - dealloc(d2); - } + erase_def(d->get_decl()); m_defs.insert(d->get_decl(), d); return promise_def(&u(), d); } diff --git a/src/ast/recfun_decl_plugin.h b/src/ast/recfun_decl_plugin.h index 1146b7aaf..70447ff67 100644 --- a/src/ast/recfun_decl_plugin.h +++ b/src/ast/recfun_decl_plugin.h @@ -305,7 +305,7 @@ namespace recfun { m_pred(pred), m_cdef(&d), m_args(args) {} body_expansion(body_expansion const & from): m_pred(from.m_pred), m_cdef(from.m_cdef), m_args(from.m_args) {} - body_expansion(body_expansion && from) : + body_expansion(body_expansion && from) noexcept : m_pred(from.m_pred), m_cdef(from.m_cdef), m_args(std::move(from.m_args)) {} std::ostream& display(std::ostream& out) const; diff --git a/src/ast/rewriter/bool_rewriter.cpp b/src/ast/rewriter/bool_rewriter.cpp index 4c2ed7e12..1e58db340 100644 --- a/src/ast/rewriter/bool_rewriter.cpp +++ b/src/ast/rewriter/bool_rewriter.cpp @@ -722,12 +722,12 @@ br_status bool_rewriter::mk_eq_core(expr * lhs, expr * rhs, expr_ref & result) { return BR_DONE; } - if (m().is_not(rhs)) + if (m().is_not(rhs)) std::swap(lhs, rhs); - if (m().is_not(lhs, lhs)) { - result = m().mk_not(m().mk_eq(lhs, rhs)); - return BR_REWRITE2; + if (m().is_not(lhs, lhs)) { + result = m().mk_not(m().mk_eq(lhs, rhs)); + return BR_REWRITE2; } if (unfolded) { diff --git a/src/ast/rewriter/bv_rewriter.cpp b/src/ast/rewriter/bv_rewriter.cpp index e120f7baf..356e30d1a 100644 --- a/src/ast/rewriter/bv_rewriter.cpp +++ b/src/ast/rewriter/bv_rewriter.cpp @@ -2818,14 +2818,15 @@ br_status bv_rewriter::mk_bvsmul_no_overflow(unsigned num, expr * const * args, return BR_DONE; } - if (is_num1 && is_num2) { - rational mr = a0_val * a1_val; - rational lim = rational::power_of_two(bv_sz-1); - result = m().mk_bool_val(mr < lim); - return BR_DONE; - } - - return BR_FAILED; + if (!is_num1 || !is_num2) + return BR_FAILED; + + rational lim = rational::power_of_two(bv_sz); + rational r = a0_val * a1_val; + bool sign1 = m_util.has_sign_bit(a0_val, bv_sz); + bool sign2 = m_util.has_sign_bit(a1_val, bv_sz); + result = m().mk_bool_val((sign1 != sign2) || r < lim); + return BR_DONE; } br_status bv_rewriter::mk_bvumul_no_overflow(unsigned num, expr * const * args, expr_ref & result) { diff --git a/src/ast/rewriter/maximize_ac_sharing.h b/src/ast/rewriter/maximize_ac_sharing.h index 53e102b74..a2741aa89 100644 --- a/src/ast/rewriter/maximize_ac_sharing.h +++ b/src/ast/rewriter/maximize_ac_sharing.h @@ -46,7 +46,7 @@ class maximize_ac_sharing : public default_rewriter_cfg { entry(func_decl * d = nullptr, expr * arg1 = nullptr, expr * arg2 = nullptr):m_decl(d), m_arg1(arg1), m_arg2(arg2) { SASSERT((d == 0 && arg1 == 0 && arg2 == 0) || (d != 0 && arg1 != 0 && arg2 != 0)); - if (arg1->get_id() > arg2->get_id()) + if (arg1 && arg2 && arg1->get_id() > arg2->get_id()) std::swap(m_arg1, m_arg2); } diff --git a/src/ast/rewriter/seq_axioms.cpp b/src/ast/rewriter/seq_axioms.cpp index 55d58e3f7..930abe4e6 100644 --- a/src/ast/rewriter/seq_axioms.cpp +++ b/src/ast/rewriter/seq_axioms.cpp @@ -21,8 +21,11 @@ Revision History: #include "ast/ast_pp.h" #include "ast/ast_ll_pp.h" +#include "ast/recfun_decl_plugin.h" +#include "ast/rewriter/recfun_replace.h" #include "ast/rewriter/seq_axioms.h" + namespace seq { axioms::axioms(th_rewriter& r): @@ -1052,6 +1055,81 @@ namespace seq { NOT_IMPLEMENTED_YET(); } + // A basic strategy for supporting replace_all and other + // similar functions is to use recursive relations. + // Then the features for recursive functions that allow expanding definitions + // using iterative deepening can be re-used. + // + // create recursive relation 'ra' with properties: + // ra(i, j, s, p, t, r) <- len(s) = i && len(r) = j + // ra(i, j, s, p, t, r) <- len(s) > i = 0 && p = "" && r = t + s + // ra(i, j, s, p, t, r) <- len(s) > i && p != "" && s = extract(s, 0, i) + p + extract(s, i + len(p), len(s)) && r = extract(r, 0, i) + t + extract(r, i + len(p), len(r)) && ra(i + len(p), j + len(t), s, p, t, r) + // ra(i, s, p, t, r) <- ~prefix(p, extract(s, i, len(s)) && at(s,i) = at(r,j) && ra(i + 1, j + 1, s, p, t, r) + // which amounts to: + // + // + // Then assert + // ra(s, p, t, replace_all(s, p, t)) + // + void axioms::replace_all_axiom(expr* r) { + expr* s = nullptr, *p = nullptr, *t = nullptr; + VERIFY(seq.str.is_replace_all(r, s, p, t)); + recfun::util rec(m); + recfun::decl::plugin& plugin = rec.get_plugin(); + recfun_replace replace(m); + sort* srt = s->get_sort(); + sort* domain[4] = { srt, srt, srt, srt }; + auto d = plugin.ensure_def(symbol("ra"), 4, domain, m.mk_bool_sort(), true); + func_decl* ra = d.get_def()->get_decl(); + (void)ra; + sort* isrt = a.mk_int(); + var_ref vi(m.mk_var(5, isrt), m); + var_ref vj(m.mk_var(4, isrt), m); + var_ref vs(m.mk_var(3, srt), m); + var_ref vp(m.mk_var(2, srt), m); + var_ref vt(m.mk_var(1, srt), m); + var_ref vr(m.mk_var(0, srt), m); + var* vars[6] = { vi, vj, vs, vp, vt, vr }; + (void)vars; + expr_ref len_s(seq.str.mk_length(vs), m); + expr_ref len_r(seq.str.mk_length(vr), m); + expr_ref test1(m.mk_eq(len_s, vi), m); + expr_ref branch1(m.mk_eq(len_r, vj), m); + expr_ref test2(m.mk_and(a.mk_gt(len_s, vi), m.mk_eq(vi, a.mk_int(0)), seq.str.mk_is_empty(vp)), m); + expr_ref branch2(m.mk_eq(vr, seq.str.mk_concat(vt, vs)), m); + NOT_IMPLEMENTED_YET(); +#if 0 + expr_ref test3(, m); + expr_ref s1(m_sk.mk_prefix_inv(vp, vs), m); + expr_ref r1(m_sk.mk_prefix_inv(vp, vr), m); + expr* args1[4] = { s1, vp, vt, r1 }; + expr_ref branch3(m.mk_and(m.mk_eq(seq.str.mk_concat(vp, s1), vs), + m.mk_eq(seq.str.mk_concat(vr, r1), vr), + m.mk_app(ra, 4, args1) + ), m); + expr_ref s0(m), r0(m); + m_sk.decompose(vs, s0, s1); + m_sk.decompose(vr, r0, r1); + expr* args2[4] = { s1, vp, vt, r1 }; + expr_ref branch4(m.mk_and(m.mk_eq(vs, seq.str.mk_concat(s0, s1)), + m.mk_eq(vr, seq.str.mk_concat(s0, r1)), + m.mk_app(ra, 4, args2)), m); + // s = [s0] + s' && r = [s0] + r' && ra(s', p, t, r') + + expr_ref body(m.mk_ite(test1, branch1, m.mk_ite(test2, branch2, m.mk_ite(test3, branch3, branch4))), m); + plugin.set_definition(replace, d, true, 4, vars, body); + expr* args3[4] = { s, p, t, r }; + expr_ref lit(m.mk_app(ra, 4, args3), m); + add_clause(lit); +#endif + } + + void axioms::replace_re_all_axiom(expr* e) { + expr* s = nullptr, *p = nullptr, *t = nullptr; + VERIFY(seq.str.is_replace_re_all(e, s, p, t)); + NOT_IMPLEMENTED_YET(); + } + /** diff --git a/src/ast/rewriter/seq_axioms.h b/src/ast/rewriter/seq_axioms.h index 0a2eb2ed2..583898562 100644 --- a/src/ast/rewriter/seq_axioms.h +++ b/src/ast/rewriter/seq_axioms.h @@ -107,6 +107,8 @@ namespace seq { void length_axiom(expr* n); void unroll_not_contains(expr* e); void replace_re_axiom(expr* e); + void replace_all_axiom(expr* e); + void replace_re_all_axiom(expr* e); expr_ref length_limit(expr* s, unsigned k); expr_ref is_digit(expr* ch); diff --git a/src/ast/rewriter/seq_rewriter.cpp b/src/ast/rewriter/seq_rewriter.cpp index 588979534..adaf3ec0f 100644 --- a/src/ast/rewriter/seq_rewriter.cpp +++ b/src/ast/rewriter/seq_rewriter.cpp @@ -552,9 +552,9 @@ br_status seq_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * con st = mk_re_concat(args[0], args[1], result); } break; - case _OP_RE_ANTIMOROV_UNION: + case _OP_RE_ANTIMIROV_UNION: SASSERT(num_args == 2); - // Rewrite Antimorov union to real union + // Rewrite antimirov union to real union result = re().mk_union(args[0], args[1]); st = BR_REWRITE1; break; @@ -2723,7 +2723,7 @@ expr_ref seq_rewriter::is_nullable_rec(expr* r) { re().is_intersection(r, r1, r2)) { m_br.mk_and(is_nullable(r1), is_nullable(r2), result); } - else if (re().is_union(r, r1, r2) || re().is_antimorov_union(r, r1, r2)) { + else if (re().is_union(r, r1, r2) || re().is_antimirov_union(r, r1, r2)) { m_br.mk_or(is_nullable(r1), is_nullable(r2), result); } else if (re().is_diff(r, r1, r2)) { @@ -2857,7 +2857,7 @@ br_status seq_rewriter::mk_re_reverse(expr* r, expr_ref& result) { return BR_REWRITE2; } else if (re().is_loop(r, r1, lo, hi)) { - result = re().mk_loop(re().mk_reverse(r1), lo, hi); + result = re().mk_loop_proper(re().mk_reverse(r1), lo, hi); return BR_REWRITE2; } else if (re().is_reverse(r, r1)) { @@ -2908,7 +2908,7 @@ br_status seq_rewriter::mk_re_reverse(expr* r, expr_ref& result) { Once the derivative is built, we return BR_REWRITE_FULL so that any remaining possible simplification is performed from the bottom up. - Rewriting also replaces _OP_RE_ANTIMOROV_UNION, which is produced + Rewriting also replaces _OP_RE_antimirov_UNION, which is produced by is_derivative, with real union. */ br_status seq_rewriter::mk_re_derivative(expr* ele, expr* r, expr_ref& result) { @@ -2924,16 +2924,16 @@ br_status seq_rewriter::mk_re_derivative(expr* ele, expr* r, expr_ref& result) { When computing derivatives recursively, we preserve the following BDD normal form: - - At the top level, the derivative is a union of Antimorov derivatives + - At the top level, the derivative is a union of antimirov derivatives (Conceptually each element of the union is a different derivative). We currently express this derivative using an internal op code: - _OP_RE_ANTIMOROV_UNION - - An Antimorov derivative is a nested if-then-else term. + _OP_RE_antimirov_UNION + - An antimirov derivative is a nested if-then-else term. if-then-elses are pushed outwards and sorted by condition ID (cond->get_id()), from largest on the outside to smallest on the inside. Duplicate nested conditions are eliminated. - The leaves of the if-then-else BDD can have unions themselves, - but these are interpreted as Regex union, not as separate Antimorov + but these are interpreted as Regex union, not as separate antimirov derivatives. To debug the normal form, call Z3 with -dbg:seq_regex: @@ -2962,7 +2962,7 @@ bool seq_rewriter::check_deriv_normal_form(expr* r, int level) { unsigned lo = 0, hi = 0; STRACE("seq_verbose", tout << " (level " << level << ")";); int new_level = 0; - if (re().is_antimorov_union(r)) { + if (re().is_antimirov_union(r)) { SASSERT(level >= 2); new_level = 2; } @@ -2975,7 +2975,7 @@ bool seq_rewriter::check_deriv_normal_form(expr* r, int level) { SASSERT(!re().is_opt(r)); SASSERT(!re().is_plus(r)); - if (re().is_antimorov_union(r, r1, r2) || + if (re().is_antimirov_union(r, r1, r2) || re().is_concat(r, r1, r2) || re().is_union(r, r1, r2) || re().is_intersection(r, r1, r2) || @@ -3115,8 +3115,8 @@ void seq_rewriter::mk_antimirov_deriv_rec(expr* e, expr* r, expr* path, expr_ref result = mk_antimirov_deriv_union(c1, re().mk_ite_simplify(r1nullable, mk_antimirov_deriv(e, r2, path), nothing())); } else if (m().is_ite(r, c, r1, r2)) { - c1 = simplify_path(m().mk_and(c, path)); - c2 = simplify_path(m().mk_and(m().mk_not(c), path)); + c1 = simplify_path(e, m().mk_and(c, path)); + c2 = simplify_path(e, m().mk_and(m().mk_not(c), path)); if (m().is_false(c1)) result = mk_antimirov_deriv(e, r2, c2); else if (m().is_false(c2)) @@ -3131,8 +3131,8 @@ void seq_rewriter::mk_antimirov_deriv_rec(expr* e, expr* r, expr* path, expr_ref SASSERT(u().is_char(c1)); SASSERT(u().is_char(c2)); // case: c1 <= e <= c2 - range = simplify_path(m().mk_and(u().mk_le(c1, e), u().mk_le(e, c2))); - psi = simplify_path(m().mk_and(path, range)); + range = simplify_path(e, m().mk_and(u().mk_le(c1, e), u().mk_le(e, c2))); + psi = simplify_path(e, m().mk_and(path, range)); } else if (!str().is_string(r1) && str().is_unit_string(r2, c2)) { SASSERT(u().is_char(c2)); @@ -3141,8 +3141,8 @@ void seq_rewriter::mk_antimirov_deriv_rec(expr* e, expr* r, expr* path, expr_ref expr_ref zero(m_autil.mk_int(0), m()); expr_ref r1_length_eq_one(m().mk_eq(str().mk_length(r1), one), m()); expr_ref r1_0(str().mk_nth_i(r1, zero), m()); - range = simplify_path(m().mk_and(r1_length_eq_one, m().mk_and(u().mk_le(r1_0, e), u().mk_le(e, c2)))); - psi = simplify_path(m().mk_and(path, range)); + range = simplify_path(e, m().mk_and(r1_length_eq_one, m().mk_and(u().mk_le(r1_0, e), u().mk_le(e, c2)))); + psi = simplify_path(e, m().mk_and(path, range)); } else if (!str().is_string(r2) && str().is_unit_string(r1, c1)) { SASSERT(u().is_char(c1)); @@ -3151,8 +3151,8 @@ void seq_rewriter::mk_antimirov_deriv_rec(expr* e, expr* r, expr* path, expr_ref expr_ref zero(m_autil.mk_int(0), m()); expr_ref r2_length_eq_one(m().mk_eq(str().mk_length(r2), one), m()); expr_ref r2_0(str().mk_nth_i(r2, zero), m()); - range = simplify_path(m().mk_and(r2_length_eq_one, m().mk_and(u().mk_le(c1, e), u().mk_le(e, r2_0)))); - psi = simplify_path(m().mk_and(path, range)); + range = simplify_path(e, m().mk_and(r2_length_eq_one, m().mk_and(u().mk_le(c1, e), u().mk_le(e, r2_0)))); + psi = simplify_path(e, m().mk_and(path, range)); } else if (!str().is_string(r1) && !str().is_string(r2)) { // both r1 and r2 nonground: |r1|=1 & |r2|=1 & r1[0] <= e <= r2[0] @@ -3162,8 +3162,8 @@ void seq_rewriter::mk_antimirov_deriv_rec(expr* e, expr* r, expr* path, expr_ref expr_ref r1_0(str().mk_nth_i(r1, zero), m()); expr_ref r2_length_eq_one(m().mk_eq(str().mk_length(r2), one), m()); expr_ref r2_0(str().mk_nth_i(r2, zero), m()); - range = simplify_path(m().mk_and(r1_length_eq_one, m().mk_and(r2_length_eq_one, m().mk_and(u().mk_le(r1_0, e), u().mk_le(e, r2_0))))); - psi = simplify_path(m().mk_and(path, range)); + range = simplify_path(e, m().mk_and(r1_length_eq_one, m().mk_and(r2_length_eq_one, m().mk_and(u().mk_le(r1_0, e), u().mk_le(e, r2_0))))); + psi = simplify_path(e, m().mk_and(path, range)); } if (m().is_false(psi)) result = nothing(); @@ -3173,7 +3173,7 @@ void seq_rewriter::mk_antimirov_deriv_rec(expr* e, expr* r, expr* path, expr_ref else if (re().is_union(r, r1, r2)) result = mk_antimirov_deriv_union(mk_antimirov_deriv(e, r1, path), mk_antimirov_deriv(e, r2, path)); else if (re().is_intersection(r, r1, r2)) - result = mk_antimirov_deriv_intersection( + result = mk_antimirov_deriv_intersection(e, mk_antimirov_deriv(e, r1, path), mk_antimirov_deriv(e, r2, path), m().mk_true()); else if (re().is_star(r, r1) || re().is_plus(r, r1) || (re().is_loop(r, r1, lo) && 0 <= lo && lo <= 1)) @@ -3184,17 +3184,17 @@ void seq_rewriter::mk_antimirov_deriv_rec(expr* e, expr* r, expr* path, expr_ref if ((lo == 0 && hi == 0) || hi < lo) result = nothing(); else - result = mk_antimirov_deriv_concat(mk_antimirov_deriv(e, r1, path), re().mk_loop(r1, (lo == 0 ? 0 : lo - 1), hi - 1)); + result = mk_antimirov_deriv_concat(mk_antimirov_deriv(e, r1, path), re().mk_loop_proper(r1, (lo == 0 ? 0 : lo - 1), hi - 1)); } else if (re().is_opt(r, r1)) result = mk_antimirov_deriv(e, r1, path); else if (re().is_complement(r, r1)) // D(e,~r1) = ~D(e,r1) - result = mk_antimirov_deriv_negate(mk_antimirov_deriv(e, r1, path)); + result = mk_antimirov_deriv_negate(e, mk_antimirov_deriv(e, r1, path)); else if (re().is_diff(r, r1, r2)) - result = mk_antimirov_deriv_intersection( + result = mk_antimirov_deriv_intersection(e, mk_antimirov_deriv(e, r1, path), - mk_antimirov_deriv_negate(mk_antimirov_deriv(e, r2, path)), m().mk_true()); + mk_antimirov_deriv_negate(e, mk_antimirov_deriv(e, r2, path)), m().mk_true()); else if (re().is_of_pred(r, r1)) { array_util array(m()); expr* args[2] = { r1, e }; @@ -3207,40 +3207,44 @@ void seq_rewriter::mk_antimirov_deriv_rec(expr* e, expr* r, expr* path, expr_ref result = re().mk_derivative(e, r); } -expr_ref seq_rewriter::mk_antimirov_deriv_intersection(expr* d1, expr* d2, expr* path) { +expr_ref seq_rewriter::mk_antimirov_deriv_intersection(expr* e, expr* d1, expr* d2, expr* path) { sort* seq_sort = nullptr, * ele_sort = nullptr; VERIFY(m_util.is_re(d1, seq_sort)); VERIFY(m_util.is_seq(seq_sort, ele_sort)); expr_ref result(m()); expr* c, * a, * b; - if (d1 == d2 || re().is_full_seq(d2) || re().is_empty(d1)) + if (re().is_empty(d1)) result = d1; - else if (re().is_full_seq(d1) || re().is_empty(d2)) + else if (re().is_empty(d2)) result = d2; else if (m().is_ite(d1, c, a, b)) { - expr_ref path_and_c(simplify_path(m().mk_and(path, c)), m()); - expr_ref path_and_notc(simplify_path(m().mk_and(path, m().mk_not(c))), m()); + expr_ref path_and_c(simplify_path(e, m().mk_and(path, c)), m()); + expr_ref path_and_notc(simplify_path(e, m().mk_and(path, m().mk_not(c))), m()); if (m().is_false(path_and_c)) - result = mk_antimirov_deriv_intersection(b, d2, path); + result = mk_antimirov_deriv_intersection(e, b, d2, path); else if (m().is_false(path_and_notc)) - result = mk_antimirov_deriv_intersection(a, d2, path); + result = mk_antimirov_deriv_intersection(e, a, d2, path); else - result = m().mk_ite(c, mk_antimirov_deriv_intersection(a, d2, path_and_c), - mk_antimirov_deriv_intersection(b, d2, path_and_notc)); + result = m().mk_ite(c, mk_antimirov_deriv_intersection(e, a, d2, path_and_c), + mk_antimirov_deriv_intersection(e, b, d2, path_and_notc)); } else if (m().is_ite(d2)) // swap d1 and d2 - result = mk_antimirov_deriv_intersection(d2, d1, path); + result = mk_antimirov_deriv_intersection(e, d2, d1, path); + else if (d1 == d2 || re().is_full_seq(d2)) + result = mk_antimirov_deriv_restrict(e, d1, path); + else if (re().is_full_seq(d1)) + result = mk_antimirov_deriv_restrict(e, d2, path); else if (re().is_union(d1, a, b)) // distribute intersection over the union in d1 - result = mk_antimirov_deriv_union(mk_antimirov_deriv_intersection(a, d2, path), mk_antimirov_deriv_intersection(b, d2, path)); + result = mk_antimirov_deriv_union(mk_antimirov_deriv_intersection(e, a, d2, path), + mk_antimirov_deriv_intersection(e, b, d2, path)); else if (re().is_union(d2, a, b)) // distribute intersection over the union in d2 - result = mk_antimirov_deriv_union(mk_antimirov_deriv_intersection(d1, a, path), mk_antimirov_deriv_intersection(d1, b, path)); + result = mk_antimirov_deriv_union(mk_antimirov_deriv_intersection(e, d1, a, path), + mk_antimirov_deriv_intersection(e, d1, b, path)); else - // in all other cases create the intersection regex - // TODO: flatten, order and merge d1 and d2 to maintain equality under similarity - result = (d1->get_id() <= d2->get_id() ? re().mk_inter(d1, d2) : re().mk_inter(d2, d1)); + result = mk_regex_inter_normalize(d1, d2); return result; } @@ -3252,13 +3256,13 @@ expr_ref seq_rewriter::mk_antimirov_deriv_concat(expr* d, expr* r) { if (m().is_ite(d, c, t, e)) result = m().mk_ite(c, mk_antimirov_deriv_concat(t, r), mk_antimirov_deriv_concat(e, r)); else if (re().is_union(d, t, e)) - result = re().mk_union(mk_antimirov_deriv_concat(t, r), mk_antimirov_deriv_concat(e, r)); + result = mk_antimirov_deriv_union(mk_antimirov_deriv_concat(t, r), mk_antimirov_deriv_concat(e, r)); else result = mk_re_append(d, r); return result; } -expr_ref seq_rewriter::mk_antimirov_deriv_negate(expr* d) { +expr_ref seq_rewriter::mk_antimirov_deriv_negate(expr* elem, expr* d) { sort* seq_sort = nullptr; VERIFY(m_util.is_re(d, seq_sort)); auto nothing = [&]() { return expr_ref(re().mk_empty(d->get_sort()), m()); }; @@ -3276,11 +3280,11 @@ expr_ref seq_rewriter::mk_antimirov_deriv_negate(expr* d) { else if (re().is_dot_plus(d)) result = epsilon(); else if (m().is_ite(d, c, t, e)) - result = m().mk_ite(c, mk_antimirov_deriv_negate(t), mk_antimirov_deriv_negate(e)); + result = m().mk_ite(c, mk_antimirov_deriv_negate(elem, t), mk_antimirov_deriv_negate(elem, e)); else if (re().is_union(d, t, e)) - result = re().mk_inter(mk_antimirov_deriv_negate(t), mk_antimirov_deriv_negate(e)); + result = mk_antimirov_deriv_intersection(elem, mk_antimirov_deriv_negate(elem, t), mk_antimirov_deriv_negate(elem, e), m().mk_true()); else if (re().is_intersection(d, t, e)) - result = re().mk_union(mk_antimirov_deriv_negate(t), mk_antimirov_deriv_negate(e)); + result = mk_antimirov_deriv_union(mk_antimirov_deriv_negate(elem, t), mk_antimirov_deriv_negate(elem, e)); else if (re().is_complement(d, t)) result = t; else @@ -3289,21 +3293,205 @@ expr_ref seq_rewriter::mk_antimirov_deriv_negate(expr* d) { } expr_ref seq_rewriter::mk_antimirov_deriv_union(expr* d1, expr* d2) { + sort* seq_sort = nullptr, * ele_sort = nullptr; + VERIFY(m_util.is_re(d1, seq_sort)); + VERIFY(m_util.is_seq(seq_sort, ele_sort)); expr_ref result(m()); - if (re().is_empty(d1) || re().is_full_seq(d2)) - result = d2; - else if (re().is_empty(d2) || re().is_full_seq(d1)) - result = d1; - else if (re().is_dot_plus(d1) && re().get_info(d2).min_length > 0) - result = d1; - else if (re().is_dot_plus(d2) && re().get_info(d1).min_length > 0) - result = d2; + expr* c1, * t1, * e1, * c2, * t2, * e2; + if (m().is_ite(d1, c1, t1, e1) && m().is_ite(d2, c2, t2, e2) && c1 == c2) + // eliminate duplicate branching on exactly the same condition + result = m().mk_ite(c1, mk_antimirov_deriv_union(t1, t2), mk_antimirov_deriv_union(e1, e2)); else - // TODO: flatten, order and merge d1 and d2 to maintain equality under similarity - result = (d1->get_id() <= d2->get_id() ? re().mk_union(d1, d2) : re().mk_union(d2, d1)); + result = mk_regex_union_normalize(d1, d2); return result; } +// restrict the guards of all conditionals id d and simplify the resulting derivative +// restrict(if(c, a, b), cond) = if(c, restrict(a, cond & c), restrict(b, cond & ~c)) +// restrict(a U b, cond) = restrict(a, cond) U restrict(b, cond) +// where {} U X = X, X U X = X +// restrict(R, cond) = R +// +// restrict(d, false) = [] +// +// it is already assumed that the restriction takes place witin a branch +// so the condition is not added explicitly but propagated down in order to eliminate +// infeasible cases +expr_ref seq_rewriter::mk_antimirov_deriv_restrict(expr* e, expr* d, expr* cond) { + expr_ref result(d, m()); + expr_ref _cond(cond, m()); + expr* c, * a, * b; + if (m().is_false(cond)) + result = re().mk_empty(d->get_sort()); + else if (re().is_empty(d) || m().is_true(cond)) + result = d; + else if (m().is_ite(d, c, a, b)) { + expr_ref path_and_c(simplify_path(e, m().mk_and(cond, c)), m()); + expr_ref path_and_notc(simplify_path(e, m().mk_and(cond, m().mk_not(c))), m()); + result = re().mk_ite_simplify(c, mk_antimirov_deriv_restrict(e, a, path_and_c), + mk_antimirov_deriv_restrict(e, b, path_and_notc)); + } + else if (re().is_union(d, a, b)) { + expr_ref a1(mk_antimirov_deriv_restrict(e, a, cond), m()); + expr_ref b1(mk_antimirov_deriv_restrict(e, b, cond), m()); + result = mk_antimirov_deriv_union(a1, b1); + } + return result; +} + +expr_ref seq_rewriter::mk_regex_union_normalize(expr* r1, expr* r2) { + expr_ref _r1(r1, m()), _r2(r2, m()); + SASSERT(m_util.is_re(r1)); + SASSERT(m_util.is_re(r2)); + expr_ref result(m()); + std::function test = [&](expr* t, expr*& a, expr*& b) { return re().is_union(t, a, b); }; + std::function compose = [&](expr* r1, expr* r2) { return (is_subset(r1, r2) ? r2 : (is_subset(r2, r1) ? r1 : re().mk_union(r1, r2))); }; + if (r1 == r2 || re().is_empty(r2) || re().is_full_seq(r1)) + result = r1; + else if (re().is_empty(r1) || re().is_full_seq(r2)) + result = r2; + else if (re().is_dot_plus(r1) && re().get_info(r2).min_length > 0) + result = r1; + else if (re().is_dot_plus(r2) && re().get_info(r1).min_length > 0) + result = r2; + else + result = merge_regex_sets(r1, r2, re().mk_full_seq(r1->get_sort()), test, compose); + return result; +} + +expr_ref seq_rewriter::mk_regex_inter_normalize(expr* r1, expr* r2) { + expr_ref _r1(r1, m()), _r2(r2, m()); + SASSERT(m_util.is_re(r1)); + SASSERT(m_util.is_re(r2)); + expr_ref result(m()); + if (re().is_epsilon(r2)) + std::swap(r1, r2); + std::function test = [&](expr* t, expr*& a, expr*& b) { return re().is_intersection(t, a, b); }; + std::function compose = [&](expr* r1, expr* r2) { return (is_subset(r1, r2) ? r1 : (is_subset(r2, r1) ? r2 : re().mk_inter(r1, r2))); }; + if (r1 == r2 || re().is_empty(r1) || re().is_full_seq(r2)) + result = r1; + else if (re().is_empty(r2) || re().is_full_seq(r1)) + result = r2; + else if (re().is_epsilon(r1)) { + if (re().get_info(r2).nullable == l_true) + result = r1; + else if (re().get_info(r2).nullable == l_false) + result = re().mk_empty(r1->get_sort()); + else + result = merge_regex_sets(r1, r2, re().mk_empty(r1->get_sort()), test, compose); + } + else if (re().is_dot_plus(r1) && re().get_info(r2).min_length > 0) + result = r2; + else if (re().is_dot_plus(r2) && re().get_info(r1).min_length > 0) + result = r1; + else + result = merge_regex_sets(r1, r2, re().mk_empty(r1->get_sort()), test, compose); + return result; +} + +expr_ref seq_rewriter::merge_regex_sets(expr* r1, expr* r2, expr* unit, + std::function& test, + std::function& compose) { + sort* seq_sort; + expr_ref result(unit, m()); + expr_ref_vector prefix(m()); + VERIFY(m_util.is_re(r1, seq_sort)); + SASSERT(m_util.is_re(r2)); + SASSERT(r2->get_sort() == r1->get_sort()); + // Ordering of expressions used by merging, 0 means unordered, -1 means e1 < e2, 1 means e2 < e1 + auto compare = [&](expr* x, expr* y) { + expr* z = nullptr; + // TODO: consider also the case of A{0,l}++B having the same id as A*++B + // in which case return 0 + if (x == y) + return 0; + + unsigned xid = (re().is_complement(x, z) ? z->get_id() : x->get_id()); + unsigned yid = (re().is_complement(y, z) ? z->get_id() : y->get_id()); + SASSERT(xid != yid); + return (xid < yid ? -1 : 1); + }; + auto composeresult = [&](expr* suffix) { + result = suffix; + while (!prefix.empty()) { + result = compose(prefix.back(), result); + prefix.pop_back(); + } + return result; + }; + expr* ar = r1; + expr* br = r2; + while (true) { + if (ar == br) + return composeresult(ar); + + if (are_complements(ar, br)) + return expr_ref(unit, m()); + + expr* a, * ar1, * b, * br1; + if (test(br, b, br1) && !test(ar, a, ar1)) + std::swap(ar, br); + + // both ar, br are decomposable + if (test(br, b, br1)) { + VERIFY(test(ar, a, ar1)); + if (are_complements(a, b)) + return expr_ref(unit, m()); + switch (compare(a, b)) { + case 0: + // a == b + prefix.push_back(a); + ar = ar1; + br = br1; + break; + case -1: + // a < b + prefix.push_back(a); + ar = ar1; + break; + case 1: + // b < a + prefix.push_back(b); + br = br1; + break; + default: + UNREACHABLE(); + } + continue; + } + + // ar is decomposable, br is not decomposable + if (test(ar, a, ar1)) { + if (are_complements(a, br)) + return expr_ref(unit, m()); + switch (compare(a, br)) { + case 0: + // result = prefix ++ ar + return composeresult(ar); + case -1: + // a < br + prefix.push_back(a); + ar = ar1; + break; + case 1: + // br < a, result = prefix ++ (br) ++ ar + prefix.push_back(br); + return composeresult(ar); + default: + UNREACHABLE(); + } + continue; + } + + // neither ar nor br is decomposable + if (compare(ar, br) == -1) + std::swap(ar, br); + // br < ar, result = prefix ++ (br) ++ (ar) + prefix.push_back(br); + return composeresult(ar); + } +} + expr_ref seq_rewriter::mk_regex_reverse(expr* r) { expr* r1 = nullptr, * r2 = nullptr, * c = nullptr; unsigned lo = 0, hi = 0; @@ -3332,7 +3520,7 @@ expr_ref seq_rewriter::mk_regex_reverse(expr* r) { else if (re().is_loop(r, r1, lo)) result = re().mk_loop(mk_regex_reverse(r1), lo); else if (re().is_loop(r, r1, lo, hi)) - result = re().mk_loop(mk_regex_reverse(r1), lo, hi); + result = re().mk_loop_proper(mk_regex_reverse(r1), lo, hi); else if (re().is_opt(r, r1)) result = re().mk_opt(mk_regex_reverse(r1)); else if (re().is_complement(r, r1)) @@ -3345,8 +3533,9 @@ expr_ref seq_rewriter::mk_regex_reverse(expr* r) { } expr_ref seq_rewriter::mk_regex_concat(expr* r, expr* s) { - sort* seq_sort = nullptr; + sort* seq_sort = nullptr, * ele_sort = nullptr; VERIFY(m_util.is_re(r, seq_sort)); + VERIFY(u().is_seq(seq_sort, ele_sort)); SASSERT(r->get_sort() == s->get_sort()); expr_ref result(m()); expr* r1, * r2; @@ -3356,11 +3545,30 @@ expr_ref seq_rewriter::mk_regex_concat(expr* r, expr* s) { result = r; else if (re().is_full_seq(r) && re().is_full_seq(s)) result = r; + else if (re().is_full_char(r) && re().is_full_seq(s)) + // ..* = .+ + result = re().mk_plus(re().mk_full_char(ele_sort)); + else if (re().is_full_seq(r) && re().is_full_char(s)) + // .*. = .+ + result = re().mk_plus(re().mk_full_char(ele_sort)); else if (re().is_concat(r, r1, r2)) - //create the resulting concatenation in right-associative form + // create the resulting concatenation in right-associative form except for the following case + // TODO: maintain the followig invariant for A ++ B{m,n} + C + // concat(concat(A, B{m,n}), C) (if A != () and C != ()) + // concat(B{m,n}, C) (if A == () and C != ()) + // where A, B, C are regexes + // Using & below for Intersection and | for Union + // In other words, do not make A ++ B{m,n} into right-assoc form, but keep B{m,n} at the top + // This will help to identify this situation in the merge routine: + // concat(concat(A, B{0,m}), C) | concat(concat(A, B{0,n}), C) + // simplies to + // concat(concat(A, B{0,max(m,n)}), C) + // analogously: + // concat(concat(A, B{0,m}), C) & concat(concat(A, B{0,n}), C) + // simplies to + // concat(concat(A, B{0,min(m,n)}), C) result = mk_regex_concat(r1, mk_regex_concat(r2, s)); else { - //TODO: perhaps simplifiy some further cases such as .*. = ..* = .*.+ = .+.* = .+ result = re().mk_concat(r, s); } return result; @@ -3390,28 +3598,17 @@ expr_ref seq_rewriter::mk_in_antimirov_rec(expr* s, expr* d) { } /* -path is typically a conjunction of (negated) character equations or constraints that can potentially be simplified -the first element of each equation is assumed to be the element parameter, for example x = (:var 0), -for example a constraint x='a' & x='b' is simplified to false +* calls elim_condition */ -expr_ref seq_rewriter::simplify_path(expr* path) { - //TODO: more systematic simplifications +expr_ref seq_rewriter::simplify_path(expr* elem, expr* path) { expr_ref result(path, m()); - expr* h = nullptr, * t = nullptr, * lhs = nullptr, * rhs = nullptr, * h1 = nullptr; - if (m().is_and(path, h, t)) { - if (m().is_true(h)) - result = simplify_path(t); - else if (m().is_true(t)) - result = simplify_path(h); - else if (m().is_eq(h, lhs, rhs) || (m().is_not(h, h1) && m().is_eq(h1, lhs, rhs))) - elim_condition(lhs, result); - } + elim_condition(elem, result); return result; } -expr_ref seq_rewriter::mk_der_antimorov_union(expr* r1, expr* r2) { - return mk_der_op(_OP_RE_ANTIMOROV_UNION, r1, r2); +expr_ref seq_rewriter::mk_der_antimirov_union(expr* r1, expr* r2) { + return mk_der_op(_OP_RE_ANTIMIROV_UNION, r1, r2); } expr_ref seq_rewriter::mk_der_union(expr* r1, expr* r2) { @@ -3512,7 +3709,7 @@ bool seq_rewriter::ite_bdds_compatabile(expr* a, expr* b) { OP_RE_INTERSECT OP_RE_UNION OP_RE_CONCAT - _OP_RE_ANTIMOROV_UNION + _OP_RE_antimirov_UNION - a and b are in normal form (check_deriv_normal_form) Postcondition: @@ -3542,44 +3739,44 @@ expr_ref seq_rewriter::mk_der_op_rec(decl_kind k, expr* a, expr* b) { }; // Choose when to lift a union to the top level, by converting - // it to an Antimorov union - // This implements a restricted form of Antimorov derivatives + // it to an antimirov union + // This implements a restricted form of antimirov derivatives if (k == OP_RE_UNION) { - if (re().is_antimorov_union(a) || re().is_antimorov_union(b)) { - k = _OP_RE_ANTIMOROV_UNION; + if (re().is_antimirov_union(a) || re().is_antimirov_union(b)) { + k = _OP_RE_ANTIMIROV_UNION; } #if 0 - // Disabled: eager Antimorov lifting unless BDDs are compatible + // Disabled: eager antimirov lifting unless BDDs are compatible // Note: the check for BDD compatibility could be made more - // sophisticated: in an Antimorov union of n terms, we really + // sophisticated: in an antimirov union of n terms, we really // want to check if any pair of them is compatible. else if (m().is_ite(a) && m().is_ite(b) && !ite_bdds_compatabile(a, b)) { - k = _OP_RE_ANTIMOROV_UNION; + k = _OP_RE_ANTIMIROV_UNION; } #endif } - if (k == _OP_RE_ANTIMOROV_UNION) { - result = re().mk_antimorov_union(a, b); + if (k == _OP_RE_ANTIMIROV_UNION) { + result = re().mk_antimirov_union(a, b); return result; } - if (re().is_antimorov_union(a, a1, a2)) { + if (re().is_antimirov_union(a, a1, a2)) { expr_ref r1(m()), r2(m()); r1 = mk_der_op(k, a1, b); r2 = mk_der_op(k, a2, b); - result = re().mk_antimorov_union(r1, r2); + result = re().mk_antimirov_union(r1, r2); return result; } - if (re().is_antimorov_union(b, b1, b2)) { + if (re().is_antimirov_union(b, b1, b2)) { expr_ref r1(m()), r2(m()); r1 = mk_der_op(k, a, b1); r2 = mk_der_op(k, a, b2); - result = re().mk_antimorov_union(r1, r2); + result = re().mk_antimirov_union(r1, r2); return result; } // Remaining non-union case: combine two if-then-else BDDs - // (underneath top-level Antimorov unions) + // (underneath top-level antimirov unions) if (m().is_ite(a, ca, a1, a2)) { expr_ref r1(m()), r2(m()); expr_ref notca(m().mk_not(ca), m()); @@ -3688,9 +3885,9 @@ expr_ref seq_rewriter::mk_der_compl(expr* r) { expr_ref result(m_op_cache.find(OP_RE_COMPLEMENT, r, nullptr, nullptr), m()); if (!result) { expr* c = nullptr, * r1 = nullptr, * r2 = nullptr; - if (re().is_antimorov_union(r, r1, r2)) { + if (re().is_antimirov_union(r, r1, r2)) { // Convert union to intersection - // Result: Antimorov union at top level is lost, pushed inside ITEs + // Result: antimirov union at top level is lost, pushed inside ITEs expr_ref res1(m()), res2(m()); res1 = mk_der_compl(r1); res2 = mk_der_compl(r2); @@ -3723,7 +3920,7 @@ expr_ref seq_rewriter::mk_der_compl(expr* r) { */ expr_ref seq_rewriter::mk_der_cond(expr* cond, expr* ele, sort* seq_sort) { STRACE("seq_verbose", tout << "mk_der_cond: " - << mk_pp(cond, m()) << ", " << mk_pp(ele, m()) << std::endl;); + << mk_pp(cond, m()) << ", " << mk_pp(ele, m()) << std::endl;); sort *ele_sort = nullptr; VERIFY(u().is_seq(seq_sort, ele_sort)); SASSERT(ele_sort == ele->get_sort()); @@ -3796,11 +3993,11 @@ expr_ref seq_rewriter::mk_derivative_rec(expr* ele, expr* r) { return result; } else { - // Instead of mk_der_union here, we use mk_der_antimorov_union to + // Instead of mk_der_union here, we use mk_der_antimirov_union to // force the two cases to be considered separately and lifted to // the top level. This avoids blowup in cases where determinization // is expensive. - return mk_der_antimorov_union(result, mk_der_concat(is_n, dr2)); + return mk_der_antimirov_union(result, mk_der_concat(is_n, dr2)); } } else if (re().is_star(r, r1)) { @@ -3858,7 +4055,7 @@ expr_ref seq_rewriter::mk_derivative_rec(expr* ele, expr* r) { return result; } else { - return mk_der_concat(result, re().mk_loop(r1, lo, hi)); + return mk_der_concat(result, re().mk_loop_proper(r1, lo, hi)); } } else if (re().is_full_seq(r) || @@ -4296,6 +4493,7 @@ br_status seq_rewriter::mk_str_to_regexp(expr* a, expr_ref& result) { r ++ [] -> [] r ++ "" -> r "" ++ r -> r + . ++ .* -> .+ to_re and star: (str.to_re s1) ++ (str.to_re s2) -> (str.to_re (s1 ++ s2)) @@ -4323,6 +4521,14 @@ br_status seq_rewriter::mk_re_concat(expr* a, expr* b, expr_ref& result) { result = a; return BR_DONE; } + if (re().is_full_char(a) && re().is_full_seq(b)) { + result = re().mk_plus(a); + return BR_DONE; + } + if (re().is_full_char(b) && re().is_full_seq(a)) { + result = re().mk_plus(b); + return BR_DONE; + } expr_ref a_str(m()); expr_ref b_str(m()); if (lift_str_from_to_re(a, a_str) && lift_str_from_to_re(b, b_str)) { @@ -4345,7 +4551,7 @@ br_status seq_rewriter::mk_re_concat(expr* a, expr* b, expr_ref& result) { unsigned lo1, hi1, lo2, hi2; if (re().is_loop(a, a1, lo1, hi1) && lo1 <= hi1 && re().is_loop(b, b1, lo2, hi2) && lo2 <= hi2 && a1 == b1) { - result = re().mk_loop(a1, lo1 + lo2, hi1 + hi2); + result = re().mk_loop_proper(a1, lo1 + lo2, hi1 + hi2); return BR_DONE; } if (re().is_loop(a, a1, lo1) && re().is_loop(b, b1, lo2) && a1 == b1) { @@ -4394,6 +4600,7 @@ bool seq_rewriter::is_subset(expr* r1, expr* r2) const { // return false; expr* ra1 = nullptr, *ra2 = nullptr, *ra3 = nullptr; expr* rb1 = nullptr, *rb2 = nullptr, *rb3 = nullptr; + unsigned la, ua, lb, ub; if (re().is_complement(r1, ra1) && re().is_complement(r2, rb1)) { return is_subset(rb1, ra1); @@ -4406,6 +4613,8 @@ bool seq_rewriter::is_subset(expr* r1, expr* r2) const { return true; if (re().is_full_seq(r2)) return true; + if (re().is_dot_plus(r2) && re().get_info(r1).nullable == l_false) + return true; if (is_concat(r1, ra1, ra2, ra3) && is_concat(r2, rb1, rb2, rb3) && ra1 == rb1 && ra2 == rb2) { r1 = ra3; @@ -4417,6 +4626,20 @@ bool seq_rewriter::is_subset(expr* r1, expr* r2) const { r1 = ra2; continue; } + // r1=ra3{la,ua}ra2, r2=rb3{lb,ub}rb2, ra3=rb3, lb<=la, ua<=ub + if (re().is_concat(r1, ra1, ra2) && re().is_loop(ra1, ra3, la, ua) && + re().is_concat(r2, rb1, rb2) && re().is_loop(rb1, rb3, lb, ub) && + ra3 == rb3 && lb <= la && ua <= ub) { + r1 = ra2; + r2 = rb2; + continue; + } + // ra1=ra3{la,ua}, r2=rb3{lb,ub}, ra3=rb3, lb<=la, ua<=ub + if (re().is_loop(r1, ra3, la, ua) && + re().is_loop(r2, rb3, lb, ub) && + ra3 == rb3 && lb <= la && ua <= ub) { + return true; + } return false; } } @@ -4453,74 +4676,13 @@ br_status seq_rewriter::mk_re_union0(expr* a, expr* b, expr_ref& result) { return BR_FAILED; } -/* - (a + a) = a - (a + eps) = a - (eps + a) = a -*/ +/* Creates a normalized union. */ br_status seq_rewriter::mk_re_union(expr* a, expr* b, expr_ref& result) { - br_status st = mk_re_union0(a, b, result); - if (st != BR_FAILED) - return st; - auto mk_full = [&]() { return re().mk_full_seq(a->get_sort()); }; - if (are_complements(a, b)) { - result = mk_full(); - return BR_DONE; - } - - expr* a1 = nullptr, *a2 = nullptr; - expr* b1 = nullptr, *b2 = nullptr; - // ensure union is right-associative - // and swap-sort entries - if (re().is_union(a, a1, a2)) { - result = re().mk_union(a1, re().mk_union(a2, b)); - return BR_REWRITE2; - } - - auto get_id = [&](expr* e) { re().is_complement(e, e); return e->get_id(); }; - if (re().is_union(b, b1, b2)) { - if (is_subset(a, b1)) { - result = b; - return BR_DONE; - } - if (is_subset(b1, a)) { - result = re().mk_union(a, b2); - return BR_REWRITE1; - } - if (are_complements(a, b1)) { - result = mk_full(); - return BR_DONE; - } - if (get_id(a) > get_id(b1)) { - result = re().mk_union(b1, re().mk_union(a, b2)); - return BR_REWRITE2; - } - } - else { - if (is_subset(a, b)) { - result = b; - return BR_DONE; - } - if (is_subset(b, a)) { - result = a; - return BR_DONE; - } - if (get_id(a) > get_id(b)) { - result = re().mk_union(b, a); - return BR_DONE; - } - } - return BR_FAILED; + result = mk_regex_union_normalize(a, b); + return BR_DONE; } -/* - comp(intersect e1 e2) -> union comp(e1) comp(e2) - comp(union e1 e2) -> intersect comp(e1) comp(e2) - comp(none) -> all - comp(all) -> none - comp(comp(e1)) -> e1 - comp(epsilon) -> .+ -*/ +/* Creates a normalized complement */ br_status seq_rewriter::mk_re_complement(expr* a, expr_ref& result) { expr *e1 = nullptr, *e2 = nullptr; if (re().is_intersection(a, e1, e2)) { @@ -4575,80 +4737,14 @@ br_status seq_rewriter::mk_re_inter0(expr* a, expr* b, expr_ref& result) { return BR_FAILED; } -/** - (r n r) = r - (emp n r) = emp - (r n emp) = emp - (all n r) = r - (r n all) = r - (r n comp(r)) = emp - (comp(r) n r) = emp - (r n to_re(s)) = ite (s in r) to_re(s) emp - (to_re(s) n r) = " - */ +/* Creates a normalized intersection. */ br_status seq_rewriter::mk_re_inter(expr* a, expr* b, expr_ref& result) { - br_status st = mk_re_inter0(a, b, result); - if (st != BR_FAILED) - return st; - auto mk_empty = [&]() { return re().mk_empty(a->get_sort()); }; - if (are_complements(a, b)) { - result = mk_empty(); - return BR_DONE; - } - expr* a1 = nullptr, *a2 = nullptr; - expr* b1 = nullptr, *b2 = nullptr; - - // ensure intersection is right-associative - // and swap-sort entries - if (re().is_intersection(a, a1, a2)) { - result = re().mk_inter(a1, re().mk_inter(a2, b)); - return BR_REWRITE2; - } - auto get_id = [&](expr* e) { re().is_complement(e, e); return e->get_id(); }; - if (re().is_intersection(b, b1, b2)) { - if (is_subset(b1, a)) { - result = b; - return BR_DONE; - } - if (is_subset(a, b1)) { - result = re().mk_inter(a, b2); - return BR_REWRITE1; - } - if (are_complements(a, b1)) { - result = mk_empty(); - return BR_DONE; - } - if (get_id(a) > get_id(b1)) { - result = re().mk_inter(b1, re().mk_inter(a, b2)); - return BR_REWRITE2; - } - } - else { - if (get_id(a) > get_id(b)) { - result = re().mk_inter(b, a); - return BR_DONE; - } - if (is_subset(a, b)) { - result = a; - return BR_DONE; - } - if (is_subset(b, a)) { - result = b; - return BR_DONE; - } - } - if (re().is_to_re(b)) - std::swap(a, b); - expr* s = nullptr; - if (re().is_to_re(a, s)) { - result = m().mk_ite(re().mk_in_re(s, b), a, re().mk_empty(a->get_sort())); - return BR_REWRITE2; - } - return BR_FAILED; + result = mk_regex_inter_normalize(a, b); + return BR_DONE; } br_status seq_rewriter::mk_re_diff(expr* a, expr* b, expr_ref& result) { - result = re().mk_inter(a, re().mk_complement(b)); + result = mk_regex_inter_normalize(a, re().mk_complement(b)); return BR_REWRITE2; } @@ -4682,7 +4778,7 @@ br_status seq_rewriter::mk_re_loop(func_decl* f, unsigned num_args, expr* const* } // (loop (loop a l l) h h) = (loop a l*h l*h) if (re().is_loop(args[0], a, lo, hi) && np == 2 && lo == hi && lo2 == hi2) { - result = re().mk_loop(a, lo2 * lo, hi2 * hi); + result = re().mk_loop_proper(a, lo2 * lo, hi2 * hi); return BR_REWRITE1; } // (loop a 1 1) = a @@ -4709,7 +4805,7 @@ br_status seq_rewriter::mk_re_loop(func_decl* f, unsigned num_args, expr* const* case 3: if (m_autil.is_numeral(args[1], n1) && n1.is_unsigned() && m_autil.is_numeral(args[2], n2) && n2.is_unsigned()) { - result = re().mk_loop(args[0], n1.get_unsigned(), n2.get_unsigned()); + result = re().mk_loop_proper(args[0], n1.get_unsigned(), n2.get_unsigned()); return BR_REWRITE1; } break; @@ -4721,7 +4817,7 @@ br_status seq_rewriter::mk_re_loop(func_decl* f, unsigned num_args, expr* const* br_status seq_rewriter::mk_re_power(func_decl* f, expr* a, expr_ref& result) { unsigned p = f->get_parameter(0).get_int(); - result = re().mk_loop(a, p, p); + result = re().mk_loop_proper(a, p, p); return BR_REWRITE1; } @@ -4888,61 +4984,68 @@ void seq_rewriter::intersect(unsigned lo, unsigned hi, svector> ranges, ranges1; ranges.push_back(std::make_pair(0, u().max_char())); - auto exclude_char = [&](unsigned ch) { - if (ch == 0) { - intersect(1, u().max_char(), ranges); - } - else if (ch == u().max_char()) { - intersect(0, ch-1, ranges); + auto exclude_range = [&](unsigned lower, unsigned upper) { + SASSERT(lower <= upper); + if (lower == 0) { + if (upper == u().max_char()) + ranges.reset(); + else + intersect(upper + 1, u().max_char(), ranges); } + else if (upper == u().max_char()) + intersect(0, lower - 1, ranges); else { + // not(lower <= e <= upper) iff ((0 <= e <= lower-1) or (upper+1 <= e <= max)) + // Note that this transformation is correct only when lower <= upper ranges1.reset(); ranges1.append(ranges); - intersect(0, ch - 1, ranges); - intersect(ch + 1, u().max_char(), ranges1); + intersect(0, lower - 1, ranges); + intersect(upper + 1, u().max_char(), ranges1); ranges.append(ranges1); } }; - bool all_ranges = true; + bool negated = false; for (expr* e : conds) { - if (m().is_eq(e, lhs, rhs) && elem == lhs && u().is_const_char(rhs, ch)) { - intersect(ch, ch, ranges); - } - else if (m().is_eq(e, lhs, rhs) && elem == rhs && u().is_const_char(lhs, ch)) { - intersect(ch, ch, ranges); - } - else if (u().is_char_le(e, lhs, rhs) && elem == lhs && u().is_const_char(rhs, ch)) { - intersect(0, ch, ranges); - } - else if (u().is_char_le(e, lhs, rhs) && elem == rhs && u().is_const_char(lhs, ch)) { - intersect(ch, u().max_char(), ranges); - } - else if (m().is_not(e, e1) && m().is_eq(e1, lhs, rhs) && elem == lhs && u().is_const_char(rhs, ch)) { - exclude_char(ch); - } - else if (m().is_not(e, e1) && m().is_eq(e1, lhs, rhs) && elem == rhs && u().is_const_char(lhs, ch)) { - exclude_char(ch); - } - else if (m().is_not(e, e1) && u().is_char_le(e1, lhs, rhs) && elem == lhs && u().is_const_char(rhs, ch)) { - // not (e <= ch) - if (ch == u().max_char()) - ranges.reset(); - else - intersect(ch+1, u().max_char(), ranges); - } - else if (m().is_not(e, e1) && u().is_char_le(e1, lhs, rhs) && elem == rhs && u().is_const_char(lhs, ch)) { - // not (ch <= e) - if (ch == 0) - ranges.reset(); - else - intersect(0, ch-1, ranges); + if (u().is_char_const_range(elem, e, ch, ch2, negated)) { + if (ch > ch2) { + if (negated) + // !(ch <= elem <= ch2) is trivially true + continue; + else + // (ch <= elem <= ch2) is trivially false + ranges.reset(); + } + else if (negated) + exclude_range(ch, ch2); + else + intersect(ch, ch2, ranges); + conds_range.push_back(e); } + // trivially true conditions + else if (m().is_true(e) || (m().is_eq(e, lhs, rhs) && lhs == rhs)) + continue; + else if (m().is_not(e, e1) && m().is_eq(e1, lhs, rhs) && u().is_const_char(lhs, ch) && u().is_const_char(rhs, ch2) && ch != ch2) + continue; + else if (u().is_char_le(e, lhs, rhs) && u().is_const_char(lhs, ch) && u().is_const_char(rhs, ch2) && ch <= ch2) + continue; + else if (m().is_not(e, e1) && u().is_char_le(e1, lhs, rhs) && u().is_const_char(lhs, ch) && u().is_const_char(rhs, ch2) && ch > ch2) + continue; + // trivially false conditions + else if (m().is_false(e) || (m().is_not(e, e1) && m().is_eq(e1, lhs, rhs) && lhs == rhs)) + ranges.reset(); + else if (u().is_char_le(e, lhs, rhs) && u().is_const_char(lhs, ch) && u().is_const_char(rhs, ch2) && ch > ch2) + ranges.reset(); + else if (m().is_not(e, e1) && u().is_char_le(e1, lhs, rhs) && u().is_const_char(lhs, ch) && u().is_const_char(rhs, ch2) && ch <= ch2) + ranges.reset(); else { all_ranges = false; break; @@ -4959,6 +5062,7 @@ void seq_rewriter::elim_condition(expr* elem, expr_ref& cond) { cond = m().mk_true(); return; } + conds.set(conds_range); } } @@ -4981,6 +5085,15 @@ void seq_rewriter::elim_condition(expr* elem, expr_ref& cond) { cond = m().mk_and(m().mk_eq(elem, solution), cond); } } + else if (all_ranges) { + if (conds.empty()) + // all ranges were removed as trivially true + cond = m().mk_true(); + else if (conds.size() == 1) + cond = conds.get(0); + else + cond = m().mk_and(conds); + } } @@ -5459,42 +5572,37 @@ bool seq_rewriter::min_length(expr_ref_vector const& es, unsigned& len) { } bool seq_rewriter::max_length(expr* e, rational& len) { - if (str().is_unit(e)) { - len = 1; - return true; - } - if (str().is_at(e)) { - len = 1; - return true; - } + ptr_buffer es; + es.push_back(e); + len = 0; zstring s; - if (str().is_string(e, s)) { - len = rational(s.length()); - return true; - } - if (str().is_empty(e)) { - len = 0; - return true; - } expr* s1 = nullptr, *i = nullptr, *l = nullptr; rational n; - if (str().is_extract(e, s1, i, l) && m_autil.is_numeral(l, n) && !n.is_neg()) { - len = n; - return true; - } - if (str().is_concat(e)) { - rational l(0); - len = 0; - for (expr* arg : *to_app(e)) { - if (!max_length(arg, l)) - return false; - len += l; + + while (!es.empty()) { + e = es.back(); + es.pop_back(); + if (str().is_unit(e)) + len += 1; + else if (str().is_at(e)) + len += 1; + else if (str().is_string(e, s)) + len += rational(s.length()); + else if (str().is_extract(e, s1, i, l) && m_autil.is_numeral(l, n) && !n.is_neg()) + len += n; + else if (str().is_empty(e)) + continue; + else if (str().is_concat(e)) { + for (expr* arg : *to_app(e)) + es.push_back(arg); } - return true; + else + return false; } - return false; + return true; } + bool seq_rewriter::is_string(unsigned n, expr* const* es, zstring& s) const { zstring s1; expr* e; diff --git a/src/ast/rewriter/seq_rewriter.h b/src/ast/rewriter/seq_rewriter.h index 20978c279..0d8ac029c 100644 --- a/src/ast/rewriter/seq_rewriter.h +++ b/src/ast/rewriter/seq_rewriter.h @@ -200,7 +200,7 @@ class seq_rewriter { expr_ref mk_der_inter(expr* a, expr* b); expr_ref mk_der_compl(expr* a); expr_ref mk_der_cond(expr* cond, expr* ele, sort* seq_sort); - expr_ref mk_der_antimorov_union(expr* r1, expr* r2); + expr_ref mk_der_antimirov_union(expr* r1, expr* r2); bool ite_bdds_compatabile(expr* a, expr* b); /* if r has the form deriv(en..deriv(e1,to_re(s))..) returns 's = [e1..en]' else returns '() in r'*/ expr_ref is_nullable_symbolic_regex(expr* r, sort* seq_sort); @@ -214,14 +214,19 @@ class seq_rewriter { expr_ref mk_in_antimirov_rec(expr* s, expr* d); expr_ref mk_in_antimirov(expr* s, expr* d); - expr_ref mk_antimirov_deriv_intersection(expr* d1, expr* d2, expr* path); + expr_ref mk_antimirov_deriv_intersection(expr* elem, expr* d1, expr* d2, expr* path); expr_ref mk_antimirov_deriv_concat(expr* d, expr* r); - expr_ref mk_antimirov_deriv_negate(expr* d); + expr_ref mk_antimirov_deriv_negate(expr* elem, expr* d); expr_ref mk_antimirov_deriv_union(expr* d1, expr* d2); + expr_ref mk_antimirov_deriv_restrict(expr* elem, expr* d1, expr* cond); expr_ref mk_regex_reverse(expr* r); expr_ref mk_regex_concat(expr* r1, expr* r2); - expr_ref simplify_path(expr* path); + expr_ref merge_regex_sets(expr* r1, expr* r2, expr* unit, std::function& decompose, std::function& compose); + + // elem is (:var 0) and path a condition that may have (:var 0) as a free variable + // simplify path, e.g., (:var 0) = 'a' & (:var 0) = 'b' is simplified to false + expr_ref simplify_path(expr* elem, expr* path); bool lt_char(expr* ch1, expr* ch2); bool eq_char(expr* ch1, expr* ch2); @@ -414,5 +419,10 @@ public: // heuristic elimination of element from condition that comes form a derivative. // special case optimization for conjunctions of equalities, disequalities and ranges. void elim_condition(expr* elem, expr_ref& cond); + + /* Apply simplifications to the union to keep the union normalized (r1 and r2 are not normalized)*/ + expr_ref mk_regex_union_normalize(expr* r1, expr* r2); + /* Apply simplifications to the intersection to keep it normalized (r1 and r2 are not normalized)*/ + expr_ref mk_regex_inter_normalize(expr* r1, expr* r2); }; diff --git a/src/ast/rewriter/seq_skolem.h b/src/ast/rewriter/seq_skolem.h index 913942b8a..088a00eeb 100644 --- a/src/ast/rewriter/seq_skolem.h +++ b/src/ast/rewriter/seq_skolem.h @@ -36,7 +36,8 @@ namespace seq { symbol m_indexof_left, m_indexof_right; // inverse of indexof: (indexof_left s t) + s + (indexof_right s t) = t, for s in t. symbol m_aut_step; // regex unfolding state symbol m_accept; // regex - symbol m_is_empty, m_is_non_empty; // regex emptiness check + symbol m_is_empty; // regex emptiness check + symbol m_is_non_empty; symbol m_pre, m_post; // inverse of at: (pre s i) + (at s i) + (post s i) = s if 0 <= i < (len s) symbol m_postp; symbol m_eq; // equality atom @@ -73,8 +74,8 @@ namespace seq { } expr_ref mk_accept(expr_ref_vector const& args) { return expr_ref(seq.mk_skolem(m_accept, args.size(), args.data(), m.mk_bool_sort()), m); } expr_ref mk_accept(expr* s, expr* i, expr* r) { return mk(m_accept, s, i, r, nullptr, m.mk_bool_sort()); } - expr_ref mk_is_non_empty(expr* r, expr* u, expr* n) { return mk(m_is_non_empty, r, u, n, m.mk_bool_sort(), false); } expr_ref mk_is_empty(expr* r, expr* u, expr* n) { return mk(m_is_empty, r, u, n, m.mk_bool_sort(), false); } + expr_ref mk_is_non_empty(expr* r, expr* u, expr* n) { return mk(m_is_non_empty, r, u, n, m.mk_bool_sort(), false); } expr_ref mk_indexof_left(expr* t, expr* s, expr* offset = nullptr) { return mk(m_indexof_left, t, s, offset); } expr_ref mk_indexof_right(expr* t, expr* s, expr* offset = nullptr) { return mk(m_indexof_right, t, s, offset); } @@ -153,12 +154,12 @@ namespace seq { bool is_length_limit(expr* e) const { return is_skolem(m_length_limit, e); } bool is_length_limit(expr* p, unsigned& lim, expr*& s) const; bool is_is_empty(expr* e) const { return is_skolem(m_is_empty, e); } - bool is_is_non_empty(expr* e) const { return is_skolem(m_is_non_empty, e); } bool is_is_empty(expr* e, expr*& r, expr*& u, expr*& n) const { return is_skolem(m_is_empty, e) && (r = to_app(e)->get_arg(0), u = to_app(e)->get_arg(1), n = to_app(e)->get_arg(2), true); } - bool is_is_non_empty(expr* e, expr*& r, expr*& u, expr*& n) const { - return is_skolem(m_is_non_empty, e) && (r = to_app(e)->get_arg(0), u = to_app(e)->get_arg(1), n = to_app(e)->get_arg(2), true); + bool is_is_non_empty(expr* e) const { return is_skolem(m_is_non_empty, e); } + bool is_is_non_empty(expr* e, expr*& r, expr*& u, expr*& n) const { + return is_skolem(m_is_non_empty, e) && (r = to_app(e)->get_arg(0), u = to_app(e)->get_arg(1), n = to_app(e)->get_arg(2), true); } void decompose(expr* e, expr_ref& head, expr_ref& tail); diff --git a/src/ast/rewriter/th_rewriter.cpp b/src/ast/rewriter/th_rewriter.cpp index 0c2461dd6..2aaf4626c 100644 --- a/src/ast/rewriter/th_rewriter.cpp +++ b/src/ast/rewriter/th_rewriter.cpp @@ -627,7 +627,7 @@ struct th_rewriter_cfg : public default_rewriter_cfg { count_down_subterm_references(result, reference_map); // Any term that was newly introduced by the rewrite step is only referenced within / reachable from the result term. - for (auto kv : reference_map) { + for (auto const& kv : reference_map) { if (kv.m_value == 0) { m().trace_stream() << "[attach-enode] #" << kv.m_key->get_id() << " 0\n"; } @@ -691,7 +691,7 @@ struct th_rewriter_cfg : public default_rewriter_cfg { return; if (m_new_subst) { m_rep.reset(); - for (auto kv : m_subst->sub()) + for (auto const& kv : m_subst->sub()) m_rep.insert(kv.m_key, kv.m_value); m_new_subst = false; } @@ -859,8 +859,8 @@ ast_manager & th_rewriter::m() const { } void th_rewriter::updt_params(params_ref const & p) { - m_params = p; - m_imp->cfg().updt_params(p); + m_params.append(p); + m_imp->cfg().updt_params(m_params); } void th_rewriter::get_param_descrs(param_descrs & r) { diff --git a/src/ast/rewriter/var_subst.h b/src/ast/rewriter/var_subst.h index 84318c9b5..e9d39740d 100644 --- a/src/ast/rewriter/var_subst.h +++ b/src/ast/rewriter/var_subst.h @@ -48,6 +48,7 @@ public: Otherwise, (VAR 0) is stored in the first position, (VAR 1) in the second, and so on. */ expr_ref operator()(expr * n, unsigned num_args, expr * const * args); + inline expr_ref operator()(expr* n, expr* arg) { return (*this)(n, 1, &arg); } inline expr_ref operator()(expr * n, expr_ref_vector const& args) { return (*this)(n, args.size(), args.data()); } inline expr_ref operator()(expr * n, var_ref_vector const& args) { return (*this)(n, args.size(), (expr*const*)args.data()); } inline expr_ref operator()(expr * n, app_ref_vector const& args) { return (*this)(n, args.size(), (expr*const*)args.data()); } diff --git a/src/ast/seq_decl_plugin.cpp b/src/ast/seq_decl_plugin.cpp index 0829aa09f..091f77f5c 100644 --- a/src/ast/seq_decl_plugin.cpp +++ b/src/ast/seq_decl_plugin.cpp @@ -243,7 +243,7 @@ void seq_decl_plugin::init() { m_sigs[OP_RE_OF_PRED] = alloc(psig, m, "re.of.pred", 1, 1, &predA, reA); m_sigs[OP_RE_REVERSE] = alloc(psig, m, "re.reverse", 1, 1, &reA, reA); m_sigs[OP_RE_DERIVATIVE] = alloc(psig, m, "re.derivative", 1, 2, AreA, reA); - m_sigs[_OP_RE_ANTIMOROV_UNION] = alloc(psig, m, "re.union", 1, 2, reAreA, reA); + m_sigs[_OP_RE_ANTIMIROV_UNION] = alloc(psig, m, "re.union", 1, 2, reAreA, reA); m_sigs[OP_SEQ_TO_RE] = alloc(psig, m, "seq.to.re", 1, 1, &seqA, reA); m_sigs[OP_SEQ_IN_RE] = alloc(psig, m, "seq.in.re", 1, 2, seqAreA, boolT); m_sigs[OP_SEQ_REPLACE_RE_ALL] = alloc(psig, m, "str.replace_re_all", 1, 3, seqAreAseqA, seqA); @@ -414,9 +414,9 @@ func_decl* seq_decl_plugin::mk_func_decl(decl_kind k, unsigned num_parameters, p case OP_RE_COMPLEMENT: case OP_RE_REVERSE: case OP_RE_DERIVATIVE: - case _OP_RE_ANTIMOROV_UNION: + case _OP_RE_ANTIMIROV_UNION: m_has_re = true; - // fall-through + Z3_fallthrough; case OP_SEQ_UNIT: case OP_STRING_ITOS: case OP_STRING_STOI: @@ -516,6 +516,7 @@ func_decl* seq_decl_plugin::mk_func_decl(decl_kind k, unsigned num_parameters, p case OP_SEQ_REPLACE_RE_ALL: case OP_SEQ_REPLACE_RE: m_has_re = true; + Z3_fallthrough; case OP_SEQ_REPLACE_ALL: return mk_str_fun(k, arity, domain, range, k); @@ -830,6 +831,39 @@ app* seq_util::mk_lt(expr* ch1, expr* ch2) const { return m.mk_not(mk_le(ch2, ch1)); } +bool seq_util::is_char_const_range(expr const* x, expr* e, unsigned& l, unsigned& u, bool& negated) const { + expr* a, * b, * e0, * e1, * e2, * lb, * ub; + e1 = e; + negated = (m.is_not(e, e1)) ? true : false; + if (m.is_eq(e1, a, b) && (a == x && is_const_char(b, l))) { + u = l; + return true; + } + if (is_char_le(e1, a, b) && a == x && is_const_char(b, u)) { + // (x <= u) + l = 0; + return true; + } + if (is_char_le(e1, a, b) && b == x && is_const_char(a, l)) { + // (l <= x) + u = max_char(); + return true; + } + if (m.is_and(e1, e0, e2) && is_char_le(e0, lb, a) && a == x && is_const_char(lb, l) && + is_char_le(e2, b, ub) && b == x && is_const_char(ub, u)) + // (l <= x) & (x <= u) + return true; + if (m.is_eq(e1, a, b) && b == x && is_const_char(a, l)) { + u = l; + return true; + } + if (m.is_and(e1, e0, e2) && is_char_le(e0, a, ub) && a == x && is_const_char(ub, u) && + is_char_le(e2, lb, b) && b == x && is_const_char(lb, l)) + // (x <= u) & (l <= x) + return true; + return false; +} + bool seq_util::str::is_string(func_decl const* f, zstring& s) const { if (is_string(f)) { s = f->get_parameter(0).get_zstring(); @@ -1029,6 +1063,23 @@ app* seq_util::rex::mk_loop(expr* r, unsigned lo, unsigned hi) { return m.mk_app(m_fid, OP_RE_LOOP, 2, params, 1, &r); } +expr* seq_util::rex::mk_loop_proper(expr* r, unsigned lo, unsigned hi) +{ + if (lo == 0 && hi == 0) { + sort* seq_sort = nullptr; + VERIFY(u.is_re(r, seq_sort)); + // avoid creating a loop with both bounds 0 + // such an expression is invalid as a loop + // it is BY DEFINITION = epsilon + return mk_epsilon(seq_sort); + } + if (lo == 1 && hi == 1) + // do not create a loop unless it actually is a loop + return r; + parameter params[2] = { parameter(lo), parameter(hi) }; + return m.mk_app(m_fid, OP_RE_LOOP, 2, params, 1, &r); +} + app* seq_util::rex::mk_loop(expr* r, expr* lo) { expr* rs[2] = { r, lo }; return m.mk_app(m_fid, OP_RE_LOOP, 0, nullptr, 2, rs); @@ -1283,7 +1334,7 @@ std::ostream& seq_util::rex::pp::print(std::ostream& out, expr* e) const { print(out, r1); print(out, r2); } - else if (re.is_antimorov_union(e, r1, r2) || re.is_union(e, r1, r2)) { + else if (re.is_antimirov_union(e, r1, r2) || re.is_union(e, r1, r2)) { out << "("; print(out, r1); out << (html_encode ? "⋃" : "|"); @@ -1502,9 +1553,9 @@ seq_util::rex::info seq_util::rex::mk_info_rec(app* e) const { if (e->get_family_id() == u.get_family_id()) { switch (e->get_decl()->get_decl_kind()) { case OP_RE_EMPTY_SET: - return info(true, true, true, true, true, true, false, l_false, UINT_MAX, 0); + return info(true, l_false, UINT_MAX); case OP_RE_FULL_SEQ_SET: - return info(true, true, true, true, true, true, false, l_true, 0, 1); + return info(true, l_true, 0); case OP_RE_STAR: i1 = get_info_rec(e->get_arg(0)); return i1.star(); @@ -1516,7 +1567,7 @@ seq_util::rex::info seq_util::rex::mk_info_rec(app* e) const { case OP_RE_OF_PRED: //TBD: check if the character predicate contains uninterpreted symbols or is nonground or is unsat //TBD: check if the range is unsat - return info(true, true, true, true, true, true, true, l_false, 1, 0); + return info(true, l_false, 1); case OP_RE_CONCAT: i1 = get_info_rec(e->get_arg(0)); i2 = get_info_rec(e->get_arg(1)); @@ -1533,7 +1584,7 @@ seq_util::rex::info seq_util::rex::mk_info_rec(app* e) const { min_length = u.str.min_length(e->get_arg(0)); is_value = m.is_value(e->get_arg(0)); nullable = (is_value && min_length == 0 ? l_true : (min_length > 0 ? l_false : l_undef)); - return info(true, true, is_value, true, true, true, (min_length == 1 && u.str.max_length(e->get_arg(0)) == 1), nullable, min_length, 0); + return info(is_value, nullable, min_length); case OP_RE_REVERSE: return get_info_rec(e->get_arg(0)); case OP_RE_PLUS: @@ -1569,14 +1620,7 @@ std::ostream& seq_util::rex::info::display(std::ostream& out) const { if (is_known()) { out << "info(" << "nullable=" << (nullable == l_true ? "T" : (nullable == l_false ? "F" : "U")) << ", " - << "classical=" << (classical ? "T" : "F") << ", " - << "standard=" << (standard ? "T" : "F") << ", " - << "nonbranching=" << (nonbranching ? "T" : "F") << ", " - << "normalized=" << (normalized ? "T" : "F") << ", " - << "monadic=" << (monadic ? "T" : "F") << ", " - << "singleton=" << (singleton ? "T" : "F") << ", " - << "min_length=" << min_length << ", " - << "star_height=" << star_height << ")"; + << "min_length=" << min_length << ")"; } else if (is_valid()) out << "UNKNOWN"; @@ -1596,13 +1640,13 @@ std::string seq_util::rex::info::str() const { seq_util::rex::info seq_util::rex::info::star() const { //if is_known() is false then all mentioned properties will remain false - return seq_util::rex::info(classical, classical, interpreted, nonbranching, normalized, monadic, false, l_true, 0, star_height + 1); + return seq_util::rex::info(interpreted, l_true, 0); } seq_util::rex::info seq_util::rex::info::plus() const { if (is_known()) { //plus never occurs in a normalized regex - return info(classical, classical, interpreted, nonbranching, false, monadic, false, nullable, min_length, star_height + 1); + return info(interpreted, nullable, min_length); } else return *this; @@ -1611,14 +1655,14 @@ seq_util::rex::info seq_util::rex::info::plus() const { seq_util::rex::info seq_util::rex::info::opt() const { // if is_known() is false then all mentioned properties will remain false // optional construct never occurs in a normalized regex - return seq_util::rex::info(classical, classical, interpreted, nonbranching, false, monadic, false, l_true, 0, star_height); + return seq_util::rex::info(interpreted, l_true, 0); } seq_util::rex::info seq_util::rex::info::complement() const { if (is_known()) { lbool compl_nullable = (nullable == l_true ? l_false : (nullable == l_false ? l_true : l_undef)); unsigned compl_min_length = (compl_nullable == l_false ? 1 : 0); - return info(false, standard, interpreted, nonbranching, normalized, monadic, false, compl_nullable, compl_min_length, star_height); + return info(interpreted, compl_nullable, compl_min_length); } else return *this; @@ -1630,16 +1674,9 @@ seq_util::rex::info seq_util::rex::info::concat(seq_util::rex::info const& rhs, unsigned m = min_length + rhs.min_length; if (m < min_length || m < rhs.min_length) m = UINT_MAX; - return info(classical & rhs.classical, - classical && rhs.classical, //both args of concat must be classical for it to be standard - interpreted && rhs.interpreted, - nonbranching && rhs.nonbranching, - (normalized && !lhs_is_concat && rhs.normalized), - monadic && rhs.monadic, - false, + return info(interpreted && rhs.interpreted, ((nullable == l_false || rhs.nullable == l_false) ? l_false : ((nullable == l_true && rhs.nullable == l_true) ? l_true : l_undef)), - m, - std::max(star_height, rhs.star_height)); + m); } else return rhs; @@ -1651,16 +1688,9 @@ seq_util::rex::info seq_util::rex::info::concat(seq_util::rex::info const& rhs, seq_util::rex::info seq_util::rex::info::disj(seq_util::rex::info const& rhs) const { if (is_known() || rhs.is_known()) { //works correctly if one of the arguments is unknown - return info(classical & rhs.classical, - standard && rhs.standard, - interpreted && rhs.interpreted, - nonbranching && rhs.nonbranching, - normalized && rhs.normalized, - monadic && rhs.monadic, - singleton && rhs.singleton, + return info(interpreted && rhs.interpreted, ((nullable == l_true || rhs.nullable == l_true) ? l_true : ((nullable == l_false && rhs.nullable == l_false) ? l_false : l_undef)), - std::min(min_length, rhs.min_length), - std::max(star_height, rhs.star_height)); + std::min(min_length, rhs.min_length)); } else return rhs; @@ -1669,16 +1699,9 @@ seq_util::rex::info seq_util::rex::info::disj(seq_util::rex::info const& rhs) co seq_util::rex::info seq_util::rex::info::conj(seq_util::rex::info const& rhs) const { if (is_known()) { if (rhs.is_known()) { - return info(false, - standard && rhs.standard, - interpreted && rhs.interpreted, - nonbranching && rhs.nonbranching, - normalized && rhs.normalized, - monadic && rhs.monadic, - singleton && rhs.singleton, + return info(interpreted && rhs.interpreted, ((nullable == l_true && rhs.nullable == l_true) ? l_true : ((nullable == l_false || rhs.nullable == l_false) ? l_false : l_undef)), - std::max(min_length, rhs.min_length), - std::max(star_height, rhs.star_height)); + std::max(min_length, rhs.min_length)); } else return rhs; @@ -1690,16 +1713,9 @@ seq_util::rex::info seq_util::rex::info::conj(seq_util::rex::info const& rhs) co seq_util::rex::info seq_util::rex::info::diff(seq_util::rex::info const& rhs) const { if (is_known()) { if (rhs.is_known()) { - return info(false, - standard & rhs.standard, - interpreted & rhs.interpreted, - nonbranching & rhs.nonbranching, - normalized & rhs.normalized, - monadic & rhs.monadic, - false, + return info(interpreted & rhs.interpreted, ((nullable == l_true && rhs.nullable == l_false) ? l_true : ((nullable == l_false || rhs.nullable == l_false) ? l_false : l_undef)), - std::max(min_length, rhs.min_length), - std::max(star_height, rhs.star_height)); + std::max(min_length, rhs.min_length)); } else return rhs; @@ -1714,13 +1730,9 @@ seq_util::rex::info seq_util::rex::info::orelse(seq_util::rex::info const& i) co // unsigned ite_min_length = std::min(min_length, i.min_length); // lbool ite_nullable = (nullable == i.nullable ? nullable : l_undef); // TBD: whether ite is interpreted or not depends on whether the condition is interpreted and both branches are interpreted - return info(false, false, false, false, - normalized && i.normalized, - monadic && i.monadic, - singleton && i.singleton, + return info(false, ((nullable == l_true && i.nullable == l_true) ? l_true : ((nullable == l_false && i.nullable == l_false) ? l_false : l_undef)), - std::min(min_length, i.min_length), - std::max(star_height, i.star_height)); + std::min(min_length, i.min_length)); } else return i; @@ -1736,24 +1748,22 @@ seq_util::rex::info seq_util::rex::info::loop(unsigned lower, unsigned upper) co if (m > 0 && (m < min_length || m < lower)) m = UINT_MAX; lbool loop_nullable = (nullable == l_true || lower == 0 ? l_true : nullable); - if (upper == UINT_MAX) { - // this means the loop is r{lower,*} and is therefore not normalized - // normalized regex would be r{lower,lower}r* and would in particular not use r{0,} for r* - return info(classical, classical, interpreted, nonbranching, false, singleton, false, loop_nullable, m, star_height + 1); - } - else { - bool loop_normalized = normalized; - // r{lower,upper} is not normalized if r is nullable but lower > 0 - // r{0,1} is not normalized: it should be ()|r - // r{1,1} is not normalized: it should be r - // r{lower,upper} is not normalized if lower > upper it should then be [] (empty) - if ((nullable == l_true && lower > 0) || upper == 1 || lower > upper) - loop_normalized = false; - return info(classical, classical, interpreted, nonbranching, loop_normalized, singleton, false, loop_nullable, m, star_height); - } + return info(interpreted, loop_nullable, m); } else return *this; } +seq_util::rex::info& seq_util::rex::info::operator=(info const& other) { + if (this == &other) { + return *this; + } + + known = other.known; + interpreted = other.interpreted; + nullable = other.nullable; + min_length = other.min_length; + return *this; +} + diff --git a/src/ast/seq_decl_plugin.h b/src/ast/seq_decl_plugin.h index 09a10ee9a..9c76298b0 100644 --- a/src/ast/seq_decl_plugin.h +++ b/src/ast/seq_decl_plugin.h @@ -103,7 +103,7 @@ enum seq_op_kind { _OP_REGEXP_EMPTY, _OP_REGEXP_FULL_CHAR, _OP_RE_IS_NULLABLE, - _OP_RE_ANTIMOROV_UNION, // Lifted union for antimorov-style derivatives + _OP_RE_ANTIMIROV_UNION, // Lifted union for antimirov-style derivatives _OP_SEQ_SKOLEM, LAST_SEQ_OP }; @@ -252,6 +252,12 @@ public: unsigned max_char() const { return seq.max_char(); } unsigned num_bits() const { return seq.num_bits(); } + /* + e has a form that is equivalent to l <= x <= u (then negated = false) + or e is equivalent to !(l <= x <= u) (then negated = true) + */ + bool is_char_const_range(expr const* x, expr * e, unsigned& l, unsigned& u, bool& negated) const; + app* mk_skolem(symbol const& name, unsigned n, expr* const* args, sort* range); bool is_skolem(expr const* e) const { return is_app_of(e, m_fid, _OP_SEQ_SKOLEM); } @@ -411,26 +417,11 @@ public: struct info { /* Value is either undefined (known=l_undef) or defined and known (l_true) or defined but unknown (l_false)*/ lbool known { l_undef }; - /* No complement, no intersection, no difference, and no if-then-else is used. Reverse is allowed. */ - bool classical { false }; - /* Boolean-reverse combination of classical regexes (using reverse, union, complement, intersection or difference). */ - bool standard { false }; - /* There are no uninterpreted symbols. */ bool interpreted { false }; - /* No if-then-else is used. */ - bool nonbranching { false }; - /* Concatenations are right associative and if a loop body is nullable then the lower bound is zero. */ - bool normalized { false }; - /* All bounded loops have a body that is a singleton. */ - bool monadic { false }; - /* Positive Boolean combination of ranges or predicates or singleton sequences. */ - bool singleton { false }; /* If l_true then empty word is accepted, if l_false then empty word is not accepted. */ lbool nullable { l_undef }; /* Lower bound on the length of all accepted words. */ unsigned min_length { 0 }; - /* Maximum nesting depth of Kleene stars. */ - unsigned star_height { 0 }; /* Default constructor of invalid info. @@ -445,19 +436,13 @@ public: /* General info constructor. */ - info(bool is_classical, - bool is_standard, - bool is_interpreted, - bool is_nonbranching, - bool is_normalized, - bool is_monadic, - bool is_singleton, + info(bool is_interpreted, lbool is_nullable, - unsigned min_l, - unsigned star_h) : - known(l_true), classical(is_classical), standard(is_standard), interpreted(is_interpreted), nonbranching(is_nonbranching), - normalized(is_normalized), monadic(is_monadic), singleton(is_singleton), nullable(is_nullable), - min_length(min_l), star_height(star_h) {} + unsigned min_l) : + known(l_true), + interpreted(is_interpreted), + nullable(is_nullable), + min_length(min_l) {} /* Appends a string representation of the info into the stream. @@ -483,6 +468,8 @@ public: info diff(info const& rhs) const; info orelse(info const& rhs) const; info loop(unsigned lower, unsigned upper) const; + + info& operator=(info const& other); }; private: seq_util& u; @@ -517,6 +504,7 @@ public: app* mk_opt(expr* r) { return m.mk_app(m_fid, OP_RE_OPTION, r); } app* mk_loop(expr* r, unsigned lo); app* mk_loop(expr* r, unsigned lo, unsigned hi); + expr* mk_loop_proper(expr* r, unsigned lo, unsigned hi); app* mk_loop(expr* r, expr* lo); app* mk_loop(expr* r, expr* lo, expr* hi); app* mk_full_char(sort* s); @@ -525,7 +513,7 @@ public: app* mk_of_pred(expr* p); app* mk_reverse(expr* r) { return m.mk_app(m_fid, OP_RE_REVERSE, r); } app* mk_derivative(expr* ele, expr* r) { return m.mk_app(m_fid, OP_RE_DERIVATIVE, ele, r); } - app* mk_antimorov_union(expr* r1, expr* r2) { return m.mk_app(m_fid, _OP_RE_ANTIMOROV_UNION, r1, r2); } + app* mk_antimirov_union(expr* r1, expr* r2) { return m.mk_app(m_fid, _OP_RE_ANTIMIROV_UNION, r1, r2); } bool is_to_re(expr const* n) const { return is_app_of(n, m_fid, OP_SEQ_TO_RE); } bool is_concat(expr const* n) const { return is_app_of(n, m_fid, OP_RE_CONCAT); } @@ -557,7 +545,7 @@ public: bool is_of_pred(expr const* n) const { return is_app_of(n, m_fid, OP_RE_OF_PRED); } bool is_reverse(expr const* n) const { return is_app_of(n, m_fid, OP_RE_REVERSE); } bool is_derivative(expr const* n) const { return is_app_of(n, m_fid, OP_RE_DERIVATIVE); } - bool is_antimorov_union(expr const* n) const { return is_app_of(n, m_fid, _OP_RE_ANTIMOROV_UNION); } + bool is_antimirov_union(expr const* n) const { return is_app_of(n, m_fid, _OP_RE_ANTIMIROV_UNION); } MATCH_UNARY(is_to_re); MATCH_BINARY(is_concat); MATCH_BINARY(is_union); @@ -571,7 +559,7 @@ public: MATCH_UNARY(is_of_pred); MATCH_UNARY(is_reverse); MATCH_BINARY(is_derivative); - MATCH_BINARY(is_antimorov_union); + MATCH_BINARY(is_antimirov_union); bool is_loop(expr const* n, expr*& body, unsigned& lo, unsigned& hi) const; bool is_loop(expr const* n, expr*& body, unsigned& lo) const; bool is_loop(expr const* n, expr*& body, expr*& lo, expr*& hi) const; diff --git a/src/ast/static_features.cpp b/src/ast/static_features.cpp index c58ff254c..ec1a73e9b 100644 --- a/src/ast/static_features.cpp +++ b/src/ast/static_features.cpp @@ -18,6 +18,7 @@ Revision History: --*/ #include "ast/static_features.h" #include "ast/ast_pp.h" +#include "ast/ast_ll_pp.h" #include "ast/for_each_expr.h" static_features::static_features(ast_manager & m): @@ -39,7 +40,8 @@ static_features::static_features(ast_manager & m): } void static_features::reset() { - m_already_visited .reset(); + m_pre_processed .reset(); + m_post_processed.reset(); m_cnf = true; m_num_exprs = 0; m_num_roots = 0; @@ -343,8 +345,8 @@ void static_features::update_core(expr * e) { } func_decl * d = to_app(e)->get_decl(); inc_num_apps(d); - if (d->get_arity() > 0 && !is_marked(d)) { - mark(d); + if (d->get_arity() > 0 && !is_marked_pre(d)) { + mark_pre(d); if (fid == null_family_id) m_num_uninterpreted_functions++; } @@ -396,73 +398,68 @@ void static_features::update_core(sort * s) { check_array(s); } -void static_features::process(expr * e, bool form_ctx, bool or_and_ctx, bool ite_ctx, unsigned stack_depth) { - TRACE("static_features", tout << "processing\n" << mk_pp(e, m) << "\n";); - if (is_var(e)) - return; - if (is_marked(e)) { - m_num_sharing++; - return; - } +bool static_features::pre_process(expr * e, bool form_ctx, bool or_and_ctx, bool ite_ctx) { + if (is_marked_post(e)) + return true; - if (stack_depth > m_max_stack_depth) { - ptr_vector todo; - todo.push_back(e); - for (unsigned i = 0; i < todo.size(); ++i) { - e = todo[i]; - if (is_marked(e)) - continue; - if (is_basic_expr(e)) { - mark(e); - todo.append(to_app(e)->get_num_args(), to_app(e)->get_args()); - } - else { - process(e, form_ctx, or_and_ctx, ite_ctx, stack_depth - 10); - } - } - return; + if (is_marked_pre(e)) + return true; + + if (is_var(e)) { + mark_pre(e); + mark_post(e); + return true; } - mark(e); + mark_pre(e); update_core(e); if (is_quantifier(e)) { expr * body = to_quantifier(e)->get_expr(); - process(body, false, false, false, stack_depth+1); + if (is_marked_post(body)) + return true; + add_process(body, false, false, false); + return false; + } + + auto [form_ctx_new, or_and_ctx_new, ite_ctx_new] = new_ctx(e); + + bool all_processed = true; + for (expr* arg : *to_app(e)) { + m.is_not(arg, arg); + if (is_marked_post(arg)) + ++m_num_sharing; + else { + add_process(arg, form_ctx_new, or_and_ctx_new, ite_ctx_new); + all_processed = false; + } + } + return all_processed; +} + +void static_features::post_process(expr * e, bool form_ctx, bool or_and_ctx, bool ite_ctx) { + if (is_marked_post(e)) + return; + + mark_post(e); + + if (is_quantifier(e)) { + expr * body = to_quantifier(e)->get_expr(); set_depth(e, get_depth(body)+1); return; } - - bool form_ctx_new = false; - bool or_and_ctx_new = false; - bool ite_ctx_new = false; - if (is_basic_expr(e)) { - switch (to_app(e)->get_decl_kind()) { - case OP_ITE: - form_ctx_new = m.is_bool(e); - ite_ctx_new = true; - break; - case OP_AND: - case OP_OR: - form_ctx_new = true; - or_and_ctx_new = true; - break; - case OP_EQ: - form_ctx_new = true; - break; - } - } - unsigned depth = 0; unsigned form_depth = 0; unsigned or_and_depth = 0; unsigned ite_depth = 0; + auto [form_ctx_new, or_and_ctx_new, ite_ctx_new] = new_ctx(e); + for (expr* arg : *to_app(e)) { m.is_not(arg, arg); - process(arg, form_ctx_new, or_and_ctx_new, ite_ctx_new, stack_depth+1); + SASSERT(is_marked_post(arg)); depth = std::max(depth, get_depth(arg)); if (form_ctx_new) form_depth = std::max(form_depth, get_form_depth(arg)); @@ -507,16 +504,58 @@ void static_features::process(expr * e, bool form_ctx, bool or_and_ctx, bool ite } set_ite_depth(e, ite_depth); } + } +std::tuple static_features::new_ctx(expr* e) { + bool form_ctx_new = false; + bool or_and_ctx_new = false; + bool ite_ctx_new = false; + + if (is_basic_expr(e)) { + switch (to_app(e)->get_decl_kind()) { + case OP_ITE: + form_ctx_new = m.is_bool(e); + ite_ctx_new = true; + break; + case OP_AND: + case OP_OR: + form_ctx_new = true; + or_and_ctx_new = true; + break; + case OP_EQ: + form_ctx_new = true; + break; + } + } + + return std::tuple(form_ctx_new, or_and_ctx_new, ite_ctx_new); +} + +void static_features::process_all() { + while (!m_to_process.empty()) { + auto const& [e, form, or_and, ite] = m_to_process.back(); + if (is_marked_post(e)) { + m_to_process.pop_back(); + ++m_num_sharing; + continue; + } + if (!pre_process(e, form, or_and, ite)) + continue; + post_process(e, form, or_and, ite); + m_to_process.pop_back(); + } +} + + void static_features::process_root(expr * e) { - if (is_marked(e)) { + if (is_marked_post(e)) { m_num_sharing++; return; } m_num_roots++; if (m.is_or(e)) { - mark(e); + mark_post(e); m_num_clauses++; m_num_bool_exprs++; unsigned num_args = to_app(e)->get_num_args(); @@ -530,7 +569,8 @@ void static_features::process_root(expr * e) { expr * arg = to_app(e)->get_arg(i); if (m.is_not(arg)) arg = to_app(arg)->get_arg(0); - process(arg, true, true, false, 0); + add_process(arg, true, true, false); + process_all(); depth = std::max(depth, get_depth(arg)); form_depth = std::max(form_depth, get_form_depth(arg)); or_and_depth = std::max(or_and_depth, get_or_and_depth(arg)); @@ -558,7 +598,8 @@ void static_features::process_root(expr * e) { m_num_units++; m_num_clauses++; } - process(e, false, false, false, 0); + add_process(e, false, false, false); + process_all(); } void static_features::collect(unsigned num_formulas, expr * const * formulas) { diff --git a/src/ast/static_features.h b/src/ast/static_features.h index 8408c08ef..40532f939 100644 --- a/src/ast/static_features.h +++ b/src/ast/static_features.h @@ -28,6 +28,12 @@ Revision History: #include "util/map.h" struct static_features { + struct to_process { + expr* e; + bool form_ctx; + bool and_or_ctx; + bool ite_ctx; + }; ast_manager & m; arith_util m_autil; bv_util m_bvutil; @@ -39,7 +45,7 @@ struct static_features { family_id m_lfid; family_id m_arrfid; family_id m_srfid; - ast_mark m_already_visited; + ast_mark m_pre_processed, m_post_processed; bool m_cnf; unsigned m_num_exprs; // unsigned m_num_roots; // @@ -111,14 +117,17 @@ struct static_features { u_map m_expr2formula_depth; unsigned m_num_theories; - bool_vector m_theories; // mapping family_id -> bool + bool_vector m_theories; // mapping family_id -> bool symbol m_label_sym; symbol m_pattern_sym; symbol m_expr_list_sym; + svector m_to_process; - bool is_marked(ast * e) const { return m_already_visited.is_marked(e); } - void mark(ast * e) { m_already_visited.mark(e, true); } + bool is_marked_pre(ast * e) const { return m_pre_processed.is_marked(e); } + void mark_pre(ast * e) { m_pre_processed.mark(e, true); } + bool is_marked_post(ast * e) const { return m_post_processed.is_marked(e); } + void mark_post(ast * e) { m_post_processed.mark(e, true); } bool is_bool(expr const * e) const { return m.is_bool(e); } bool is_basic_expr(expr const * e) const { return is_app(e) && to_app(e)->get_family_id() == m_bfid; } bool is_arith_expr(expr const * e) const { return is_app(e) && to_app(e)->get_family_id() == m_afid; } @@ -161,7 +170,12 @@ struct static_features { void inc_num_aliens(family_id fid) { m_num_aliens_per_family.reserve(fid+1, 0); m_num_aliens_per_family[fid]++; } void update_core(expr * e); void update_core(sort * s); - void process(expr * e, bool form_ctx, bool or_and_ctx, bool ite_ctx, unsigned stack_depth); + // void process(expr * e, bool form_ctx, bool or_and_ctx, bool ite_ctx, unsigned stack_depth); + bool pre_process(expr * e, bool form_ctx, bool or_and_ctx, bool ite_ctx); + void post_process(expr * e, bool form_ctx, bool or_and_ctx, bool ite_ctx); + void add_process(expr * e, bool form_ctx, bool or_and_ctx, bool ite_ctx) { m_to_process.push_back({e, form_ctx, or_and_ctx, ite_ctx}); } + void process_all(); + std::tuple new_ctx(expr* ); void process_root(expr * e); unsigned get_depth(expr const * e) const { return m_expr2depth.get(e->get_id(), 1); } void set_depth(expr const * e, unsigned d) { m_expr2depth.setx(e->get_id(), d, 1); } diff --git a/src/cmd_context/basic_cmds.cpp b/src/cmd_context/basic_cmds.cpp index 23915f7fe..823f48e01 100644 --- a/src/cmd_context/basic_cmds.cpp +++ b/src/cmd_context/basic_cmds.cpp @@ -301,10 +301,23 @@ UNARY_CMD(pp_cmd, "display", "", "display the given term.", CPK_EXPR, expr ctx.regular_stream() << std::endl; }); -UNARY_CMD(echo_cmd, "echo", "", "display the given string", CPK_STRING, char const *, - bool smt2c = ctx.params().m_smtlib2_compliant; - ctx.regular_stream() << (smt2c ? "\"" : "") << arg << (smt2c ? "\"" : "") << std::endl;); +static std::string escape_string(char const* arg) { + std::string result; + while (*arg) { + auto ch = *arg++; + if (ch == '"') + result.push_back(ch); + result.push_back(ch); + } + return result; +} +UNARY_CMD(echo_cmd, "echo", "", "display the given string", CPK_STRING, char const *, + bool smt2c = ctx.params().m_smtlib2_compliant; + if (smt2c) + ctx.regular_stream() << "\"" << escape_string(arg) << "\"" << std::endl; + else + ctx.regular_stream() << arg << std::endl;); class set_get_option_cmd : public cmd { protected: diff --git a/src/cmd_context/cmd_context.cpp b/src/cmd_context/cmd_context.cpp index 845ac645c..968dcacd8 100644 --- a/src/cmd_context/cmd_context.cpp +++ b/src/cmd_context/cmd_context.cpp @@ -2107,6 +2107,21 @@ void cmd_context::analyze_failure(expr_mark& seen, model_evaluator& ev, expr* a, << (expected_value?"true":"false") << "\n";); IF_VERBOSE(11, display_detailed_analysis(verbose_stream(), ev, a)); + + if (m().is_iff(a)) { + ptr_vector todo; + todo.push_back(a); + for (unsigned i = 0; i < todo.size(); ++i) { + e = todo[i]; + if (m().is_and(e) || m().is_or(e) || m().is_iff(e) || m().is_implies(e) || m().is_not(e)) + for (expr* arg : *to_app(e)) + todo.push_back(arg); + else + IF_VERBOSE(10, verbose_stream() << "#" << e->get_id() << " " << mk_bounded_pp(e, m()) << " " << (ev.is_true(e)?"true":"false") << "\n"); + } + return; + } + } void cmd_context::display_detailed_analysis(std::ostream& out, model_evaluator& ev, expr* e) { diff --git a/src/cmd_context/extra_cmds/dbg_cmds.cpp b/src/cmd_context/extra_cmds/dbg_cmds.cpp index 5b648741c..5a628ce58 100644 --- a/src/cmd_context/extra_cmds/dbg_cmds.cpp +++ b/src/cmd_context/extra_cmds/dbg_cmds.cpp @@ -199,12 +199,12 @@ void tst_params(cmd_context & ctx) { params_ref p1; params_ref p2; p1.set_uint("val", 100); - p2 = p1; + p2.append(p1); SASSERT(p2.get_uint("val", 0) == 100); p2.set_uint("val", 200); SASSERT(p2.get_uint("val", 0) == 200); SASSERT(p1.get_uint("val", 0) == 100); - p2 = p1; + p2.append(p1); SASSERT(p2.get_uint("val", 0) == 100); SASSERT(p1.get_uint("val", 0) == 100); ctx.regular_stream() << "worked" << std::endl; diff --git a/src/cmd_context/pdecl.cpp b/src/cmd_context/pdecl.cpp index e34c7e38a..7a93382e9 100644 --- a/src/cmd_context/pdecl.cpp +++ b/src/cmd_context/pdecl.cpp @@ -416,9 +416,9 @@ void psort_builtin_decl::display(std::ostream & out) const { void ptype::display(std::ostream & out, pdatatype_decl const * const * dts) const { switch (kind()) { - case PTR_PSORT: get_psort()->display(out); break; - case PTR_REC_REF: out << dts[get_idx()]->get_name(); break; - case PTR_MISSING_REF: out << get_missing_ref(); break; + case ptype_kind::PTR_PSORT: get_psort()->display(out); break; + case ptype_kind::PTR_REC_REF: out << dts[get_idx()]->get_name(); break; + case ptype_kind::PTR_MISSING_REF: out << get_missing_ref(); break; } } @@ -426,19 +426,19 @@ paccessor_decl::paccessor_decl(unsigned id, unsigned num_params, pdecl_manager & pdecl(id, num_params), m_name(n), m_type(r) { - if (m_type.kind() == PTR_PSORT) { + if (m_type.kind() == ptype_kind::PTR_PSORT) { m.inc_ref(r.get_psort()); } } void paccessor_decl::finalize(pdecl_manager & m) { - if (m_type.kind() == PTR_PSORT) { + if (m_type.kind() == ptype_kind::PTR_PSORT) { m.lazy_dec_ref(m_type.get_psort()); } } bool paccessor_decl::has_missing_refs(symbol & missing) const { - if (m_type.kind() == PTR_MISSING_REF) { + if (m_type.kind() == ptype_kind::PTR_MISSING_REF) { missing = m_type.get_missing_ref(); return true; } @@ -446,14 +446,14 @@ bool paccessor_decl::has_missing_refs(symbol & missing) const { } bool paccessor_decl::fix_missing_refs(dictionary const & symbol2idx, symbol & missing) { - TRACE("fix_missing_refs", tout << "m_type.kind(): " << m_type.kind() << "\n"; - if (m_type.kind() == PTR_MISSING_REF) tout << m_type.get_missing_ref() << "\n";); - if (m_type.kind() != PTR_MISSING_REF) + TRACE("fix_missing_refs", tout << "m_type.kind(): " << (int)m_type.kind() << "\n"; + if (m_type.kind() == ptype_kind::PTR_MISSING_REF) tout << m_type.get_missing_ref() << "\n";); + if (m_type.kind() != ptype_kind::PTR_MISSING_REF) return true; int idx; if (symbol2idx.find(m_type.get_missing_ref(), idx)) { m_type = ptype(idx); - SASSERT(m_type.kind() == PTR_REC_REF); + SASSERT(m_type.kind() == ptype_kind::PTR_REC_REF); return true; } missing = m_type.get_missing_ref(); @@ -462,8 +462,8 @@ bool paccessor_decl::fix_missing_refs(dictionary const & symbol2idx, symbol accessor_decl * paccessor_decl::instantiate_decl(pdecl_manager & m, unsigned n, sort * const * s) { switch (m_type.kind()) { - case PTR_REC_REF: return mk_accessor_decl(m.m(), m_name, type_ref(m_type.get_idx())); - case PTR_PSORT: return mk_accessor_decl(m.m(), m_name, type_ref(m_type.get_psort()->instantiate(m, n, s))); + case ptype_kind::PTR_REC_REF: return mk_accessor_decl(m.m(), m_name, type_ref(m_type.get_idx())); + case ptype_kind::PTR_PSORT: return mk_accessor_decl(m.m(), m_name, type_ref(m_type.get_psort()->instantiate(m, n, s))); default: // missing refs must have been eliminated. UNREACHABLE(); diff --git a/src/cmd_context/pdecl.h b/src/cmd_context/pdecl.h index e5b630902..3a1db06c1 100644 --- a/src/cmd_context/pdecl.h +++ b/src/cmd_context/pdecl.h @@ -157,7 +157,7 @@ class pdatatype_decl; class pconstructor_decl; class paccessor_decl; -enum ptype_kind { +enum class ptype_kind { PTR_PSORT, // psort PTR_REC_REF, // recursive reference PTR_MISSING_REF // a symbol, it is useful for building parsers. @@ -171,14 +171,14 @@ class ptype { }; symbol m_missing_ref; public: - ptype():m_kind(PTR_PSORT), m_sort(nullptr) {} - ptype(int idx):m_kind(PTR_REC_REF), m_idx(idx) {} - ptype(psort * s):m_kind(PTR_PSORT), m_sort(s) {} - ptype(symbol const & s):m_kind(PTR_MISSING_REF), m_missing_ref(s) {} + ptype():m_kind(ptype_kind::PTR_PSORT), m_sort(nullptr) {} + ptype(int idx):m_kind(ptype_kind::PTR_REC_REF), m_idx(idx) {} + ptype(psort * s):m_kind(ptype_kind::PTR_PSORT), m_sort(s) {} + ptype(symbol const & s):m_kind(ptype_kind::PTR_MISSING_REF), m_sort(nullptr), m_missing_ref(s) {} ptype_kind kind() const { return m_kind; } - psort * get_psort() const { SASSERT(kind() == PTR_PSORT); return m_sort; } - int get_idx() const { SASSERT(kind() == PTR_REC_REF); return m_idx; } - symbol const & get_missing_ref() const { SASSERT(kind() == PTR_MISSING_REF); return m_missing_ref; } + psort * get_psort() const { SASSERT(kind() == ptype_kind::PTR_PSORT); return m_sort; } + int get_idx() const { SASSERT(kind() == ptype_kind::PTR_REC_REF); return m_idx; } + symbol const & get_missing_ref() const { SASSERT(kind() == ptype_kind::PTR_MISSING_REF); return m_missing_ref; } void display(std::ostream & out, pdatatype_decl const * const * dts) const; }; diff --git a/src/math/polynomial/algebraic_numbers.cpp b/src/math/polynomial/algebraic_numbers.cpp index 06fbe228e..5244de82c 100644 --- a/src/math/polynomial/algebraic_numbers.cpp +++ b/src/math/polynomial/algebraic_numbers.cpp @@ -730,9 +730,9 @@ namespace algebraic_numbers { } else { algebraic_cell * c = a.to_algebraic(); - if (!upm().normalize_interval_core(c->m_p_sz, c->m_p, sign_lower(c), bqm(), lower(c), upper(c))) + if (!upm().normalize_interval_core(c->m_p_sz, c->m_p, sign_lower(c), bqm(), lower(c), upper(c))) reset(a); - SASSERT(acell_inv(*c)); + SASSERT(is_zero(a) || acell_inv(*a.to_algebraic())); } } diff --git a/src/math/subpaving/tactic/subpaving_tactic.cpp b/src/math/subpaving/tactic/subpaving_tactic.cpp index 1bc0cb630..6124e726e 100644 --- a/src/math/subpaving/tactic/subpaving_tactic.cpp +++ b/src/math/subpaving/tactic/subpaving_tactic.cpp @@ -227,8 +227,8 @@ public: } void updt_params(params_ref const & p) override { - m_params = p; - m_imp->updt_params(p); + m_params.append(p); + m_imp->updt_params(m_params); } void collect_param_descrs(param_descrs & r) override { diff --git a/src/model/model_core.cpp b/src/model/model_core.cpp index da0cfb883..a23fc9800 100644 --- a/src/model/model_core.cpp +++ b/src/model/model_core.cpp @@ -111,6 +111,7 @@ void model_core::unregister_decl(func_decl * d) { m_interp[m_const_decls.back()].first = v.first; m_const_decls.pop_back(); m_interp.remove(d); + m_decls.erase(d); m.dec_ref(k); m.dec_ref(v.second); return; @@ -122,6 +123,7 @@ void model_core::unregister_decl(func_decl * d) { auto v = ef->get_data().m_value; m_finterp.remove(d); m_func_decls.erase(d); + m_decls.erase(d); m.dec_ref(k); dealloc(v); } diff --git a/src/muz/base/dl_rule.cpp b/src/muz/base/dl_rule.cpp index 2baaf57b9..d0c872c3c 100644 --- a/src/muz/base/dl_rule.cpp +++ b/src/muz/base/dl_rule.cpp @@ -178,7 +178,7 @@ namespace datalog { m_ctx.register_predicate(m_hnf.get_fresh_predicates()[i], false); } for (unsigned i = 0; i < fmls.size(); ++i) { - mk_horn_rule(fmls[i].get(), prs[i].get(), rules, name); + mk_horn_rule(fmls.get(i), prs.get(i), rules, name); } } @@ -190,12 +190,10 @@ namespace datalog { hoist_compound_predicates(index, m_head, m_body); TRACE("dl_rule", tout << mk_pp(m_head, m) << " :- "; - for (unsigned i = 0; i < m_body.size(); ++i) { - tout << mk_pp(m_body[i].get(), m) << " "; - } + for (expr* b : m_body) + tout << mk_pp(b, m) << " "; tout << "\n";); - mk_negations(m_body, m_neg); check_valid_rule(m_head, m_body.size(), m_body.data()); @@ -241,9 +239,8 @@ namespace datalog { m_args.reset(); head = ensure_app(e2); flatten_and(e1, m_args); - for (unsigned i = 0; i < m_args.size(); ++i) { - body.push_back(ensure_app(m_args[i].get())); - } + for (expr* a : m_args) + body.push_back(ensure_app(a)); } else { head = ensure_app(fml); @@ -255,7 +252,7 @@ namespace datalog { unsigned sz = body.size(); hoist_compound(index, head, body); for (unsigned i = 0; i < sz; ++i) { - app_ref b(body[i].get(), m); + app_ref b(body.get(i), m); hoist_compound(index, b, body); body[i] = b; } @@ -781,7 +778,7 @@ namespace datalog { tail_neg.push_back(false); } - SASSERT(tail.size()==tail_neg.size()); + SASSERT(tail.size() == tail_neg.size()); rule_ref old_r = r; r = mk(head, tail.size(), tail.data(), tail_neg.data(), old_r->name()); r->set_accounting_parent_object(m_ctx, old_r); @@ -949,7 +946,8 @@ namespace datalog { } bool rule::has_negation() const { - for (unsigned i = 0; i < get_uninterpreted_tail_size(); ++i) { + unsigned sz = get_uninterpreted_tail_size(); + for (unsigned i = 0; i < sz; ++i) { if (is_neg_tail(i)) { return true; } diff --git a/src/muz/base/dl_rule.h b/src/muz/base/dl_rule.h index 044260f12..c9fa2f6b3 100644 --- a/src/muz/base/dl_rule.h +++ b/src/muz/base/dl_rule.h @@ -28,7 +28,7 @@ Revision History: #include "ast/rewriter/ast_counter.h" #include "ast/rewriter/rewriter.h" #include "muz/base/hnf.h" -#include "qe/qe_lite.h" +#include "qe/lite/qe_lite.h" #include "ast/rewriter/var_subst.h" #include "ast/datatype_decl_plugin.h" #include "ast/rewriter/label_rewriter.h" @@ -287,13 +287,12 @@ namespace datalog { class rule : public accounted_object { friend class rule_manager; - app* m_head{ nullptr }; - proof* m_proof{ nullptr }; - unsigned m_tail_size:20; - // unsigned m_reserve:12; - unsigned m_ref_cnt{ 0 }; - unsigned m_positive_cnt{ 0 }; - unsigned m_uninterp_cnt{ 0 }; + app* m_head = nullptr; + proof* m_proof = nullptr; + unsigned m_tail_size = 0; + unsigned m_ref_cnt = 0; + unsigned m_positive_cnt = 0; + unsigned m_uninterp_cnt = 0; symbol m_name; /** The following field is an array of tagged pointers. diff --git a/src/muz/base/rule_properties.cpp b/src/muz/base/rule_properties.cpp index 3e5a51196..991ca716e 100644 --- a/src/muz/base/rule_properties.cpp +++ b/src/muz/base/rule_properties.cpp @@ -204,6 +204,72 @@ void rule_properties::operator()(var* n) { void rule_properties::operator()(quantifier* n) { m_quantifiers.insert(n, m_rule); } + +bool rule_properties::check_accessor(app* n) { + sort* s = n->get_arg(0)->get_sort(); + SASSERT(m_dt.is_datatype(s)); + if (m_dt.get_datatype_constructors(s)->size() <= 1) + return true; + + + func_decl* f = n->get_decl(); + func_decl * c = m_dt.get_accessor_constructor(f); + unsigned ut_size = m_rule->get_uninterpreted_tail_size(); + unsigned t_size = m_rule->get_tail_size(); + + auto is_recognizer_base = [&](expr* t) { + return m_dt.is_recognizer(t) && + to_app(t)->get_arg(0) == n->get_arg(0) && + m_dt.get_recognizer_constructor(to_app(t)->get_decl()) == c; + }; + + auto is_recognizer = [&](expr* t) { + if (m.is_and(t)) + for (expr* arg : *to_app(t)) + if (is_recognizer_base(arg)) + return true; + return is_recognizer_base(t); + }; + + + for (unsigned i = ut_size; i < t_size; ++i) + if (is_recognizer(m_rule->get_tail(i))) + return true; + + + // create parent use list for every sub-expression in the rule + obj_map> use_list; + for (unsigned i = ut_size; i < t_size; ++i) { + app* t = m_rule->get_tail(i); + use_list.insert_if_not_there(t, ptr_vector()).push_back(nullptr); // add marker for top-level expression. + for (expr* sub : subterms::all(expr_ref(t, m))) + if (is_app(sub)) + for (expr* arg : *to_app(sub)) + use_list.insert_if_not_there(arg, ptr_vector()).push_back(sub); + } + + // walk parents of n to check that each path is guarded by a recognizer. + ptr_vector todo; + todo.push_back(n); + for (unsigned i = 0; i < todo.size(); ++i) { + expr* e = todo[i]; + if (!use_list.contains(e)) + return false; + for (expr* parent : use_list[e]) { + if (!parent) + return false; // top-level expressions are not guarded + if (is_recognizer(parent)) + continue; + if (m.is_ite(parent) && to_app(parent)->get_arg(1) == e && is_recognizer(to_app(parent)->get_arg(0))) + continue; + todo.push_back(parent); + } + } + + return true; + +} + void rule_properties::operator()(app* n) { func_decl_ref f_out(m); expr* n1 = nullptr, *n2 = nullptr; @@ -216,23 +282,9 @@ void rule_properties::operator()(app* n) { m_uninterp_funs.insert(f, m_rule); } else if (m_dt.is_accessor(n)) { - sort* s = n->get_arg(0)->get_sort(); - SASSERT(m_dt.is_datatype(s)); - if (m_dt.get_datatype_constructors(s)->size() > 1) { - bool found = false; - func_decl * c = m_dt.get_accessor_constructor(f); - unsigned ut_size = m_rule->get_uninterpreted_tail_size(); - unsigned t_size = m_rule->get_tail_size(); - for (unsigned i = ut_size; !found && i < t_size; ++i) { - app* t = m_rule->get_tail(i); - if (m_dt.is_recognizer(t) && t->get_arg(0) == n->get_arg(0) && m_dt.get_recognizer_constructor(t->get_decl()) == c) { - found = true; - } - } - if (!found) { - m_uninterp_funs.insert(f, m_rule); - } - } + if (!check_accessor(n)) + m_uninterp_funs.insert(f, m_rule); + } else if (m_a.is_considered_uninterpreted(f, n->get_num_args(), n->get_args(), f_out)) { m_uninterp_funs.insert(f, m_rule); diff --git a/src/muz/base/rule_properties.h b/src/muz/base/rule_properties.h index 863635960..896b1bb16 100644 --- a/src/muz/base/rule_properties.h +++ b/src/muz/base/rule_properties.h @@ -44,7 +44,7 @@ namespace datalog { bool m_generate_proof; rule* m_rule; obj_map m_quantifiers; - obj_map m_uninterp_funs; + obj_map m_uninterp_funs; ptr_vector m_interp_pred; ptr_vector m_negative_rules; ptr_vector m_inf_sort; @@ -55,6 +55,7 @@ namespace datalog { void check_sort(sort* s); void visit_rules(expr_sparse_mark& visited, rule_set const& rules); bool evaluates_to_numeral(expr * n, rational& val); + bool check_accessor(app* n); public: rule_properties(ast_manager & m, rule_manager& rm, context& ctx, i_expr_pred& is_predicate); ~rule_properties(); diff --git a/src/muz/fp/horn_tactic.cpp b/src/muz/fp/horn_tactic.cpp index b66ddf275..6ffd0f745 100644 --- a/src/muz/fp/horn_tactic.cpp +++ b/src/muz/fp/horn_tactic.cpp @@ -395,8 +395,8 @@ public: char const* name() const override { return "horn"; } void updt_params(params_ref const & p) override { - m_params = p; - m_imp->updt_params(p); + m_params.append(p); + m_imp->updt_params(m_params); } diff --git a/src/muz/spacer/spacer_legacy_mbp.cpp b/src/muz/spacer/spacer_legacy_mbp.cpp index 76b543f04..324368cec 100644 --- a/src/muz/spacer/spacer_legacy_mbp.cpp +++ b/src/muz/spacer/spacer_legacy_mbp.cpp @@ -35,7 +35,7 @@ Notes: #include "ast/rewriter/expr_replacer.h" #include "model/model_smt2_pp.h" #include "ast/scoped_proof.h" -#include "qe/qe_lite.h" +#include "qe/lite/qe_lite.h" #include "muz/spacer/spacer_qe_project.h" #include "model/model_pp.h" #include "ast/rewriter/expr_safe_replace.h" diff --git a/src/muz/spacer/spacer_legacy_mev.cpp b/src/muz/spacer/spacer_legacy_mev.cpp index 26752beda..e6da53e32 100644 --- a/src/muz/spacer/spacer_legacy_mev.cpp +++ b/src/muz/spacer/spacer_legacy_mev.cpp @@ -23,7 +23,7 @@ Copyright (c) 2017 Arie Gurfinkel #include "ast/rewriter/expr_replacer.h" #include "model/model_smt2_pp.h" #include "ast/scoped_proof.h" -#include "qe/qe_lite.h" +#include "qe/lite/qe_lite.h" #include "muz/spacer/spacer_qe_project.h" #include "model/model_pp.h" #include "ast/rewriter/expr_safe_replace.h" diff --git a/src/muz/spacer/spacer_qe_project.cpp b/src/muz/spacer/spacer_qe_project.cpp index 5acbab9aa..b170a59d0 100644 --- a/src/muz/spacer/spacer_qe_project.cpp +++ b/src/muz/spacer/spacer_qe_project.cpp @@ -36,7 +36,7 @@ Revision History: #include "model/model_pp.h" #include "qe/qe.h" -#include "qe/qe_lite.h" +#include "qe/lite/qe_lite.h" #include "muz/spacer/spacer_mev_array.h" #include "muz/spacer/spacer_qe_project.h" diff --git a/src/muz/spacer/spacer_util.cpp b/src/muz/spacer/spacer_util.cpp index e284d6424..d16b37972 100644 --- a/src/muz/spacer/spacer_util.cpp +++ b/src/muz/spacer/spacer_util.cpp @@ -52,7 +52,7 @@ Notes: #include "model/model_smt2_pp.h" #include "model/model_pp.h" -#include "qe/qe_lite.h" +#include "qe/lite/qe_lite.h" #include "qe/qe_mbp.h" #include "qe/mbp/mbp_term_graph.h" #include "qe/mbp/mbp_plugin.h" diff --git a/src/muz/tab/tab_context.cpp b/src/muz/tab/tab_context.cpp index 847110beb..b8ca9babb 100644 --- a/src/muz/tab/tab_context.cpp +++ b/src/muz/tab/tab_context.cpp @@ -23,7 +23,7 @@ Revision History: #include "muz/base/dl_context.h" #include "muz/transforms/dl_mk_rule_inliner.h" #include "smt/smt_kernel.h" -#include "qe/qe_lite.h" +#include "qe/lite/qe_lite.h" #include "ast/rewriter/bool_rewriter.h" #include "ast/rewriter/th_rewriter.h" #include "ast/datatype_decl_plugin.h" diff --git a/src/nlsat/tactic/nlsat_tactic.cpp b/src/nlsat/tactic/nlsat_tactic.cpp index ddf85ce19..3baf9da87 100644 --- a/src/nlsat/tactic/nlsat_tactic.cpp +++ b/src/nlsat/tactic/nlsat_tactic.cpp @@ -55,8 +55,8 @@ class nlsat_tactic : public tactic { } void updt_params(params_ref const & p) { - m_params = p; - m_solver.updt_params(p); + m_params.append(p); + m_solver.updt_params(m_params); } bool contains_unsupported(expr_ref_vector & b2a, expr_ref_vector & x2t) { @@ -226,7 +226,7 @@ public: char const* name() const override { return "nlsat"; } void updt_params(params_ref const & p) override { - m_params = p; + m_params.append(p); } void collect_param_descrs(param_descrs & r) override { diff --git a/src/opt/opt_solver.cpp b/src/opt/opt_solver.cpp index c1a4c68a9..01975464a 100644 --- a/src/opt/opt_solver.cpp +++ b/src/opt/opt_solver.cpp @@ -259,6 +259,12 @@ namespace opt { if (!m_models[i]) m_models.set(i, m_last_model.get()); + if (val > m_objective_values[i]) + m_objective_values[i] = val; + + if (!m_last_model) + return true; + // // retrieve value of objective from current model and update // current optimal. @@ -267,7 +273,7 @@ namespace opt { rational r; expr_ref value = (*m_last_model)(m_objective_terms.get(i)); if (arith_util(m).is_numeral(value, r) && r > m_objective_values[i]) - m_objective_values[i] = inf_eps(r); + m_objective_values[i] = inf_eps(r); }; update_objective(); @@ -342,6 +348,11 @@ namespace opt { } void opt_solver::get_model_core(model_ref & m) { + if (m_last_model.get()) { + m = m_last_model.get(); + return; + } + for (unsigned i = m_models.size(); i-- > 0; ) { auto* mdl = m_models[i]; if (mdl) { diff --git a/src/opt/optsmt.cpp b/src/opt/optsmt.cpp index 21489be58..58cdd0cda 100644 --- a/src/opt/optsmt.cpp +++ b/src/opt/optsmt.cpp @@ -49,14 +49,14 @@ namespace opt { dst[i] = src[i]; m_models.set(i, m_s->get_model_idx(i)); m_s->get_labels(m_labels); - m_lower_fmls[i] = fmls[i].get(); + m_lower_fmls[i] = fmls.get(i); if (dst[i].is_pos() && !dst[i].is_finite()) { // review: likely done already. m_lower_fmls[i] = m.mk_false(); fmls[i] = m.mk_false(); } } - else if (src[i] < dst[i] && !m.is_true(m_lower_fmls[i].get())) { - fmls[i] = m_lower_fmls[i].get(); + else if (src[i] < dst[i] && !m.is_true(m_lower_fmls.get(i))) { + fmls[i] = m_lower_fmls.get(i); } } } @@ -202,6 +202,9 @@ namespace opt { for (unsigned i = 0; i < obj_index; ++i) commit_assignment(i); +// m_s->maximize_objective(obj_index, bound); +// m_s->assert_expr(bound); + unsigned steps = 0; unsigned step_incs = 0; rational delta_per_step(1); @@ -282,7 +285,7 @@ namespace opt { bool optsmt::can_increment_delta(vector const& lower, unsigned i) { arith_util arith(m); inf_eps max_delta; - if (m_lower[i] < m_upper[i] && arith.is_int(m_objs[i].get())) { + if (m_lower[i] < m_upper[i] && arith.is_int(m_objs.get(i))) { inf_eps delta = m_lower[i] - lower[i]; if (m_lower[i].is_finite() && delta > max_delta) { return true; @@ -435,7 +438,7 @@ namespace opt { bool progress = false; for (unsigned i = 0; i < m_lower.size() && m.inc(); ++i) { if (m_lower[i] <= mid[i] && mid[i] <= m_upper[i] && m_lower[i] < m_upper[i]) { - th.enable_record_conflict(bounds[i].get()); + th.enable_record_conflict(bounds.get(i)); lbool is_sat = m_s->check_sat(1, bounds.data() + i); switch(is_sat) { case l_true: @@ -484,10 +487,10 @@ namespace opt { } for (unsigned i = 0; i < m_objs.size(); ++i) { - smt::theory_var v = solver.add_objective(m_objs[i].get()); + smt::theory_var v = solver.add_objective(m_objs.get(i)); if (v == smt::null_theory_var) { std::ostringstream out; - out << "Objective function '" << mk_pp(m_objs[i].get(), m) << "' is not supported"; + out << "Objective function '" << mk_pp(m_objs.get(i), m) << "' is not supported"; throw default_exception(out.str()); } m_vars.push_back(v); @@ -547,7 +550,7 @@ namespace opt { // force lower_bound(i) <= objective_value(i) void optsmt::commit_assignment(unsigned i) { inf_eps lo = m_lower[i]; - TRACE("opt", tout << "set lower bound of " << mk_pp(m_objs[i].get(), m) << " to: " << lo << "\n"; + TRACE("opt", tout << "set lower bound of " << mk_pp(m_objs.get(i), m) << " to: " << lo << "\n"; tout << get_lower(i) << ":" << get_upper(i) << "\n";); // Only assert bounds for bounded objectives if (lo.is_finite()) { diff --git a/src/params/context_params.cpp b/src/params/context_params.cpp index 4bc37b86b..294c5cbbe 100644 --- a/src/params/context_params.cpp +++ b/src/params/context_params.cpp @@ -190,7 +190,8 @@ void context_params::get_solver_params(params_ref & p, bool & proofs_enabled, bo proofs_enabled &= p.get_bool("proof", m_proof); models_enabled &= p.get_bool("model", m_model); unsat_core_enabled = m_unsat_core || p.get_bool("unsat_core", false); - p = merge_default_params(p); + if (!m_auto_config && !p.contains("auto_config")) + p.set_bool("auto_config", false); } diff --git a/src/parsers/smt2/smt2parser.cpp b/src/parsers/smt2/smt2parser.cpp index 953b1979f..45a9a9cc9 100644 --- a/src/parsers/smt2/smt2parser.cpp +++ b/src/parsers/smt2/smt2parser.cpp @@ -2470,6 +2470,10 @@ namespace smt2 { next(); } + /** + * (declare-fun f (sorts) sort) + * (declare-fun (alphas) (sorts) sort) + */ void parse_declare_fun() { SASSERT(curr_is_identifier()); SASSERT(curr_id() == m_declare_fun); diff --git a/src/qe/CMakeLists.txt b/src/qe/CMakeLists.txt index 49a9f73b4..8539db7a2 100644 --- a/src/qe/CMakeLists.txt +++ b/src/qe/CMakeLists.txt @@ -10,7 +10,6 @@ z3_add_component(qe qe.cpp qe_datatype_plugin.cpp qe_dl_plugin.cpp - qe_lite.cpp qe_mbi.cpp qe_mbp.cpp qe_tactic.cpp @@ -21,9 +20,9 @@ z3_add_component(qe smt tactic mbp + qe_lite TACTIC_HEADERS nlqsat.h - qe_lite.h qe_tactic.h qsat.h ) diff --git a/src/qe/lite/CMakeLists.txt b/src/qe/lite/CMakeLists.txt new file mode 100644 index 000000000..27f9bb09d --- /dev/null +++ b/src/qe/lite/CMakeLists.txt @@ -0,0 +1,9 @@ +z3_add_component(qe_lite + SOURCES + qe_lite.cpp + COMPONENT_DEPENDENCIES + tactic + mbp + TACTIC_HEADERS + qe_lite.h +) diff --git a/src/qe/qe_lite.cpp b/src/qe/lite/qe_lite.cpp similarity index 99% rename from src/qe/qe_lite.cpp rename to src/qe/lite/qe_lite.cpp index f6dd9c712..6d337c12f 100644 --- a/src/qe/qe_lite.cpp +++ b/src/qe/lite/qe_lite.cpp @@ -34,7 +34,7 @@ Revision History: #include "ast/datatype_decl_plugin.h" #include "tactic/tactical.h" #include "qe/mbp/mbp_solve_plugin.h" -#include "qe/qe_lite.h" +#include "qe/lite/qe_lite.h" namespace qel { @@ -2466,7 +2466,7 @@ public: } void updt_params(params_ref const & p) override { - m_params = p; + m_params.append(p); // m_imp->updt_params(p); } diff --git a/src/qe/qe_lite.h b/src/qe/lite/qe_lite.h similarity index 90% rename from src/qe/qe_lite.h rename to src/qe/lite/qe_lite.h index 3251fa3f8..47af8552a 100644 --- a/src/qe/qe_lite.h +++ b/src/qe/lite/qe_lite.h @@ -60,6 +60,10 @@ public: \brief full rewriting based light-weight quantifier elimination round. */ void operator()(expr_ref& fml, proof_ref& pr); + + void operator()(expr* fml, expr_ref& result, proof_ref& pr) { result = fml; (*this)(result, pr); } + + void operator()(expr_ref& fml) { proof_ref pr(fml.m()); (*this)(fml, pr); } }; tactic * mk_qe_lite_tactic(ast_manager & m, params_ref const & p = params_ref()); diff --git a/src/qe/mbp/mbp_arith.cpp b/src/qe/mbp/mbp_arith.cpp index 120931536..377b62ac0 100644 --- a/src/qe/mbp/mbp_arith.cpp +++ b/src/qe/mbp/mbp_arith.cpp @@ -249,8 +249,8 @@ namespace mbp { bool operator()(model& model, app* v, app_ref_vector& vars, expr_ref_vector& lits) { app_ref_vector vs(m); vs.push_back(v); - project(model, vs, lits, false); - return vs.empty(); + vector defs; + return project(model, vs, lits, defs, false) && vs.empty(); } typedef opt::model_based_opt::var var; @@ -265,12 +265,12 @@ namespace mbp { return t; } - vector project(model& model, app_ref_vector& vars, expr_ref_vector& fmls, bool compute_def) { + bool project(model& model, app_ref_vector& vars, expr_ref_vector& fmls, vector& result, bool compute_def) { bool has_arith = false; for (expr* v : vars) has_arith |= is_arith(v); if (!has_arith) - return vector(); + return true; model_evaluator eval(model); TRACE("qe", tout << model;); eval.set_model_completion(true); @@ -294,7 +294,7 @@ namespace mbp { } fmls.shrink(j); TRACE("qe", tout << "formulas\n" << fmls << "\n"; - for (auto [e, id] : tids) + for (auto const& [e, id] : tids) tout << mk_pp(e, m) << " -> " << id << "\n";); // fmls holds residue, @@ -312,7 +312,7 @@ namespace mbp { rational r; expr_ref val = eval(v); if (!m.inc()) - return vector(); + return false; if (!a.is_numeral(val, r)) throw default_exception("evaluation did not produce a numeral"); TRACE("qe", tout << mk_pp(v, m) << " " << val << "\n";); @@ -326,15 +326,13 @@ namespace mbp { if (is_arith(e) && !var_mark.is_marked(e)) mark_rec(fmls_mark, e); } - if (m_check_purified) { for (expr* fml : fmls) mark_rec(fmls_mark, fml); for (auto& kv : tids) { expr* e = kv.m_key; - if (!var_mark.is_marked(e)) { - mark_rec(fmls_mark, e); - } + if (!var_mark.is_marked(e)) + mark_rec(fmls_mark, e); } } @@ -364,16 +362,18 @@ namespace mbp { for (auto const& d : defs) tout << "def: " << d << "\n"; tout << fmls << "\n";); - vector result; if (compute_def) optdefs2mbpdef(defs, index2expr, real_vars, result); - if (m_apply_projection) - apply_projection(result, fmls); + if (m_apply_projection && !apply_projection(eval, result, fmls)) + return false; + TRACE("qe", - for (auto [v, t] : result) + for (auto const& [v, t] : result) tout << v << " := " << t << "\n"; + for (auto* f : fmls) + tout << mk_pp(f, m) << " := " << eval(f) << "\n"; tout << "fmls:" << fmls << "\n";); - return result; + return true; } void optdefs2mbpdef(vector const& defs, ptr_vector const& index2expr, unsigned_vector const& real_vars, vector& result) { @@ -548,10 +548,11 @@ namespace mbp { } } - void apply_projection(vector& defs, expr_ref_vector& fmls) { + bool apply_projection(model_evaluator& eval, vector const& defs, expr_ref_vector& fmls) { if (fmls.empty() || defs.empty()) - return; + return true; expr_safe_replace subst(m); + expr_ref_vector fmls_tmp(m); expr_ref tmp(m); for (unsigned i = defs.size(); i-- > 0; ) { auto const& d = defs[i]; @@ -561,8 +562,11 @@ namespace mbp { unsigned j = 0; for (expr* fml : fmls) { subst(fml, tmp); + if (m.is_false(eval(tmp))) + return false; fmls[j++] = tmp; } + return true; } }; @@ -579,12 +583,13 @@ namespace mbp { return (*m_imp)(model, var, vars, lits); } - void arith_project_plugin::operator()(model& model, app_ref_vector& vars, expr_ref_vector& lits) { - m_imp->project(model, vars, lits, false); + bool arith_project_plugin::operator()(model& model, app_ref_vector& vars, expr_ref_vector& lits) { + vector defs; + return m_imp->project(model, vars, lits, defs, false); } - vector arith_project_plugin::project(model& model, app_ref_vector& vars, expr_ref_vector& lits) { - return m_imp->project(model, vars, lits, true); + bool arith_project_plugin::project(model& model, app_ref_vector& vars, expr_ref_vector& lits, vector& defs) { + return m_imp->project(model, vars, lits, defs, true); } void arith_project_plugin::set_check_purified(bool check_purified) { diff --git a/src/qe/mbp/mbp_arith.h b/src/qe/mbp/mbp_arith.h index 51d80a870..ca4cccb74 100644 --- a/src/qe/mbp/mbp_arith.h +++ b/src/qe/mbp/mbp_arith.h @@ -29,8 +29,8 @@ namespace mbp { bool operator()(model& model, app* var, app_ref_vector& vars, expr_ref_vector& lits) override; bool solve(model& model, app_ref_vector& vars, expr_ref_vector& lits) override { return false; } family_id get_family_id() override; - void operator()(model& model, app_ref_vector& vars, expr_ref_vector& lits) override; - vector project(model& model, app_ref_vector& vars, expr_ref_vector& lits) override; + bool operator()(model& model, app_ref_vector& vars, expr_ref_vector& lits) override; + bool project(model& model, app_ref_vector& vars, expr_ref_vector& lits, vector& defs) override; void saturate(model& model, func_decl_ref_vector const& shared, expr_ref_vector& lits) override { UNREACHABLE(); } opt::inf_eps maximize(expr_ref_vector const& fmls, model& mdl, app* t, expr_ref& ge, expr_ref& gt); diff --git a/src/qe/mbp/mbp_arrays.cpp b/src/qe/mbp/mbp_arrays.cpp index 149d11987..0f4c805b7 100644 --- a/src/qe/mbp/mbp_arrays.cpp +++ b/src/qe/mbp/mbp_arrays.cpp @@ -1624,8 +1624,8 @@ namespace mbp { ); } - vector array_project_plugin::project(model& model, app_ref_vector& vars, expr_ref_vector& lits) { - return vector(); + bool array_project_plugin::project(model& model, app_ref_vector& vars, expr_ref_vector& lits, vector& defs) { + return true; } void array_project_plugin::saturate(model& model, func_decl_ref_vector const& shared, expr_ref_vector& lits) { diff --git a/src/qe/mbp/mbp_arrays.h b/src/qe/mbp/mbp_arrays.h index bb18cde11..7dd904108 100644 --- a/src/qe/mbp/mbp_arrays.h +++ b/src/qe/mbp/mbp_arrays.h @@ -35,7 +35,7 @@ namespace mbp { bool solve(model& model, app_ref_vector& vars, expr_ref_vector& lits) override; void operator()(model& model, app_ref_vector& vars, expr_ref& fml, app_ref_vector& aux_vars, bool reduce_all_selects); family_id get_family_id() override; - vector project(model& model, app_ref_vector& vars, expr_ref_vector& lits) override; + bool project(model& model, app_ref_vector& vars, expr_ref_vector& lits, vector& defs) override; void saturate(model& model, func_decl_ref_vector const& shared, expr_ref_vector& lits) override; }; diff --git a/src/qe/mbp/mbp_datatypes.cpp b/src/qe/mbp/mbp_datatypes.cpp index 798258599..e40a19546 100644 --- a/src/qe/mbp/mbp_datatypes.cpp +++ b/src/qe/mbp/mbp_datatypes.cpp @@ -300,8 +300,8 @@ namespace mbp { return m_imp->solve(model, vars, lits); } - vector datatype_project_plugin::project(model& model, app_ref_vector& vars, expr_ref_vector& lits) { - return vector(); + bool datatype_project_plugin::project(model& model, app_ref_vector& vars, expr_ref_vector& lits, vector& defs) { + return true; } void datatype_project_plugin::saturate(model& model, func_decl_ref_vector const& shared, expr_ref_vector& lits) { diff --git a/src/qe/mbp/mbp_datatypes.h b/src/qe/mbp/mbp_datatypes.h index 345260970..f30aaaa9f 100644 --- a/src/qe/mbp/mbp_datatypes.h +++ b/src/qe/mbp/mbp_datatypes.h @@ -34,7 +34,7 @@ namespace mbp { bool operator()(model& model, app* var, app_ref_vector& vars, expr_ref_vector& lits) override; bool solve(model& model, app_ref_vector& vars, expr_ref_vector& lits) override; family_id get_family_id() override; - vector project(model& model, app_ref_vector& vars, expr_ref_vector& lits) override; + bool project(model& model, app_ref_vector& vars, expr_ref_vector& lits, vector& defs) override; void saturate(model& model, func_decl_ref_vector const& shared, expr_ref_vector& lits) override; }; diff --git a/src/qe/mbp/mbp_plugin.h b/src/qe/mbp/mbp_plugin.h index 741639aaa..12b592960 100644 --- a/src/qe/mbp/mbp_plugin.h +++ b/src/qe/mbp/mbp_plugin.h @@ -67,7 +67,7 @@ namespace mbp { virtual bool solve(model& model, app_ref_vector& vars, expr_ref_vector& lits) { return false; } virtual family_id get_family_id() { return null_family_id; } - virtual void operator()(model& model, app_ref_vector& vars, expr_ref_vector& lits) { }; + virtual bool operator()(model& model, app_ref_vector& vars, expr_ref_vector& lits) { return false; }; /** \brief project vars modulo model, return set of definitions for eliminated variables. @@ -76,7 +76,7 @@ namespace mbp { - returns set of definitions (TBD: in triangular form, the last definition can be substituted into definitions that come before) */ - virtual vector project(model& model, app_ref_vector& vars, expr_ref_vector& lits) { return vector(); } + virtual bool project(model& model, app_ref_vector& vars, expr_ref_vector& lits, vector& defs) { return true; } /** \brief model based saturation. Saturates theory axioms to equi-satisfiable literals over EUF, diff --git a/src/qe/qe_mbi.cpp b/src/qe/qe_mbi.cpp index 1dcbb178f..0261ad979 100644 --- a/src/qe/qe_mbi.cpp +++ b/src/qe/qe_mbi.cpp @@ -266,7 +266,11 @@ namespace qe { vector uflia_mbi::arith_project(model_ref& mdl, app_ref_vector& avars, expr_ref_vector& lits) { mbp::arith_project_plugin ap(m); ap.set_check_purified(false); - return ap.project(*mdl.get(), avars, lits); + vector defs; + bool ok = ap.project(*mdl.get(), avars, lits, defs); + (void)ok; + CTRACE("qe", !ok, tout << "projection failure ignored!!!!\n"); + return defs; } mbi_result uflia_mbi::operator()(expr_ref_vector& lits, model_ref& mdl) { diff --git a/src/qe/qe_mbp.cpp b/src/qe/qe_mbp.cpp index 42c382542..efadc59e6 100644 --- a/src/qe/qe_mbp.cpp +++ b/src/qe/qe_mbp.cpp @@ -30,7 +30,7 @@ Revision History: #include "qe/mbp/mbp_arith.h" #include "qe/mbp/mbp_arrays.h" #include "qe/mbp/mbp_datatypes.h" -#include "qe/qe_lite.h" +#include "qe/lite/qe_lite.h" #include "model/model_pp.h" #include "model/model_evaluator.h" diff --git a/src/qe/qe_tactic.cpp b/src/qe/qe_tactic.cpp index da1ce5efd..76f4f5715 100644 --- a/src/qe/qe_tactic.cpp +++ b/src/qe/qe_tactic.cpp @@ -103,8 +103,8 @@ public: char const* name() const override { return "qe"; } void updt_params(params_ref const & p) override { - m_params = p; - m_imp->updt_params(p); + m_params.append(p); + m_imp->updt_params(m_params); } diff --git a/src/sat/sat_elim_eqs.cpp b/src/sat/sat_elim_eqs.cpp index 928e84ebe..9bd42f1df 100644 --- a/src/sat/sat_elim_eqs.cpp +++ b/src/sat/sat_elim_eqs.cpp @@ -229,11 +229,12 @@ namespace sat { literal r = roots[v]; SASSERT(v != r.var()); - if (m_solver.m_cut_simplifier) m_solver.m_cut_simplifier->set_root(v, r); + if (m_solver.m_cut_simplifier) + m_solver.m_cut_simplifier->set_root(v, r); + bool set_root = m_solver.set_root(l, r); - bool root_ok = !m_solver.is_external(v) || set_root; TRACE("elim_eqs", tout << l << " " << r << "\n";); - if (m_solver.is_assumption(v) || (m_solver.is_external(v) && (m_solver.is_incremental() || !root_ok))) { + if (m_solver.is_assumption(v) || (m_solver.is_external(v) && (m_solver.is_incremental() || !set_root))) { // cannot really eliminate v, since we have to notify extension of future assignments if (m_solver.m_config.m_drat) { m_solver.m_drat.add(~l, r, sat::status::redundant()); diff --git a/src/sat/sat_solver.cpp b/src/sat/sat_solver.cpp index 51f2c3d98..90e31b97a 100644 --- a/src/sat/sat_solver.cpp +++ b/src/sat/sat_solver.cpp @@ -288,6 +288,7 @@ namespace sat { m_free_vars.pop_back(); m_active_vars.push_back(v); reset_var(v, ext, dvar); + SASSERT(v < m_justification.size()); return v; } m_active_vars.push_back(v); @@ -3008,7 +3009,7 @@ namespace sat { svector logits(vars.size(), 0.0); double itau = m_config.m_reorder_itau; double lse = 0; - double mid = m_rand.max_value()/2; + double mid = (double)(m_rand.max_value()/2); double max = 0; for (double& f : logits) { f = itau * (m_rand() - mid)/mid; @@ -3508,7 +3509,6 @@ namespace sat { unsigned old_num_vars = m_vars_lim.pop(num_scopes); if (old_num_vars == m_active_vars.size()) return; - unsigned free_vars_head = m_free_vars.size(); unsigned sz = m_active_vars.size(), j = old_num_vars; unsigned new_lvl = m_scopes.size() - num_scopes; @@ -3531,16 +3531,14 @@ namespace sat { for (unsigned i = old_num_vars; i < sz; ++i) { bool_var v = m_active_vars[i]; - if (is_visited(v) || is_active(v)) { + if (is_external(v) || is_visited(v) || is_active(v)) { m_vars_to_reinit.push_back(v); m_active_vars[j++] = v; m_var_scope[v] = new_lvl; } else { set_eliminated(v, true); - if (!is_external(v) || true) { - m_free_vars.push_back(v); - } + m_vars_to_free.push_back(v); } } m_active_vars.shrink(j); @@ -3550,8 +3548,7 @@ namespace sat { IF_VERBOSE(0, verbose_stream() << "cleanup: " << lit << " " << w.is_binary_clause() << "\n"); } }; - for (unsigned i = m_free_vars.size(); i-- > free_vars_head; ) { - bool_var v = m_free_vars[i]; + for (bool_var v : m_vars_to_free) { cleanup_watch(literal(v, false)); cleanup_watch(literal(v, true)); @@ -3560,7 +3557,7 @@ namespace sat { tout << "clauses to reinit: " << (m_clauses_to_reinit.size() - old_sz) << "\n"; tout << "new level: " << new_lvl << "\n"; tout << "vars to reinit: " << m_vars_to_reinit << "\n"; - tout << "free vars: " << bool_var_vector(m_free_vars.size() - free_vars_head, m_free_vars.data() + free_vars_head) << "\n"; + tout << "free vars: " << bool_var_vector(m_vars_to_free) << "\n"; for (unsigned i = m_clauses_to_reinit.size(); i-- > old_sz; ) tout << "reinit: " << m_clauses_to_reinit[i] << "\n"; display(tout);); @@ -3599,7 +3596,6 @@ namespace sat { void solver::pop(unsigned num_scopes) { if (num_scopes == 0) return; - unsigned free_vars_head = m_free_vars.size(); if (m_ext) { pop_vars(num_scopes); m_ext->pop(num_scopes); @@ -3609,13 +3605,16 @@ namespace sat { scope & s = m_scopes[new_lvl]; m_inconsistent = false; // TBD: use model seems to make this redundant: s.m_inconsistent; unassign_vars(s.m_trail_lim, new_lvl); - for (unsigned i = m_free_vars.size(); i-- > free_vars_head; ) - m_case_split_queue.del_var_eh(m_free_vars[i]); + for (bool_var v : m_vars_to_free) + m_case_split_queue.del_var_eh(v); m_scope_lvl -= num_scopes; reinit_clauses(s.m_clauses_to_reinit_lim); m_scopes.shrink(new_lvl); - if (m_ext) + if (m_ext) { m_ext->pop_reinit(); + m_free_vars.append(m_vars_to_free); + m_vars_to_free.reset(); + } } void solver::unassign_vars(unsigned old_sz, unsigned new_lvl) { @@ -3656,17 +3655,17 @@ namespace sat { clause_wrapper cw = m_clauses_to_reinit[i]; bool reinit = false; if (cw.is_binary()) { - if (propagate_bin_clause(cw[0], cw[1]) && !at_base_lvl()) + if (propagate_bin_clause(cw[0], cw[1]) && !at_base_lvl()) m_clauses_to_reinit[j++] = cw; - else if (has_variables_to_reinit(cw[0], cw[1])) + else if (has_variables_to_reinit(cw[0], cw[1]) && !at_base_lvl()) m_clauses_to_reinit[j++] = cw; } else { clause & c = *(cw.get_clause()); if (ENABLE_TERNARY && c.size() == 3) { - if (!at_base_lvl() && propagate_ter_clause(c)) + if (propagate_ter_clause(c) && !at_base_lvl()) m_clauses_to_reinit[j++] = cw; - else if (has_variables_to_reinit(c)) + else if (has_variables_to_reinit(c) && !at_base_lvl()) m_clauses_to_reinit[j++] = cw; else c.set_reinit_stack(false); @@ -3674,13 +3673,13 @@ namespace sat { } detach_clause(c); attach_clause(c, reinit); - if (!at_base_lvl() && reinit) + if (reinit && !at_base_lvl()) // clause propagated literal, must keep it in the reinit stack. m_clauses_to_reinit[j++] = cw; - else if (has_variables_to_reinit(c)) + else if (has_variables_to_reinit(c) && !at_base_lvl()) m_clauses_to_reinit[j++] = cw; else - c.set_reinit_stack(false); + c.set_reinit_stack(false); } } m_clauses_to_reinit.shrink(j); @@ -3692,6 +3691,7 @@ namespace sat { // void solver::user_push() { + pop_to_base_level(); m_free_var_freeze.push_back(m_free_vars); m_free_vars.reset(); // resetting free_vars forces new variables to be assigned above new_v diff --git a/src/sat/sat_solver.h b/src/sat/sat_solver.h index b6affe4ee..74bb182e8 100644 --- a/src/sat/sat_solver.h +++ b/src/sat/sat_solver.h @@ -124,7 +124,7 @@ namespace sat { clause_vector m_clauses; clause_vector m_learned; unsigned m_num_frozen; - unsigned_vector m_active_vars, m_free_vars, m_vars_to_reinit; + unsigned_vector m_active_vars, m_free_vars, m_vars_to_free, m_vars_to_reinit; vector m_watches; svector m_assignment; svector m_justification; @@ -266,6 +266,11 @@ namespace sat { // // ----------------------- void add_clause(unsigned num_lits, literal * lits, sat::status st) override { mk_clause(num_lits, lits, st); } + void add_clause(literal l1, literal l2, status st) { + literal lits[2] = { l1, l2 }; + add_clause(2, lits, st); + } + void add_clause(literal lit, status st) { literal lits[1] = { lit }; add_clause(1, lits, st); } bool_var add_var(bool ext) override { return mk_var(ext, true); } bool_var mk_var(bool ext = false, bool dvar = true); @@ -444,9 +449,10 @@ namespace sat { void flush_roots(); typedef std::pair bin_clause; struct bin_clause_hash { unsigned operator()(bin_clause const& b) const { return b.first.hash() + 2*b.second.hash(); } }; - protected: - watch_list & get_wlist(literal l) { return m_watches[l.index()]; } - watch_list const & get_wlist(literal l) const { return m_watches[l.index()]; } + + watch_list const& get_wlist(literal l) const { return m_watches[l.index()]; } + watch_list& get_wlist(literal l) { return m_watches[l.index()]; } + protected: watch_list & get_wlist(unsigned l_idx) { return m_watches[l_idx]; } bool is_marked(bool_var v) const { return m_mark[v]; } void mark(bool_var v) { SASSERT(!is_marked(v)); m_mark[v] = true; } diff --git a/src/sat/sat_solver/inc_sat_solver.cpp b/src/sat/sat_solver/inc_sat_solver.cpp index ea81f0c4d..86de919dc 100644 --- a/src/sat/sat_solver/inc_sat_solver.cpp +++ b/src/sat/sat_solver/inc_sat_solver.cpp @@ -280,8 +280,8 @@ public: m_inserted_const2bits.reset(); m_map.pop(n); SASSERT(n <= m_num_scopes); - m_solver.user_pop(n); m_goal2sat.user_pop(n); + m_solver.user_pop(n); m_num_scopes -= n; // ? m_internalized_converted = false; m_has_uninterpreted.pop(n); @@ -683,10 +683,15 @@ public: ensure_euf()->user_propagate_register_diseq(diseq_eh); } - unsigned user_propagate_register(expr* e) override { - return ensure_euf()->user_propagate_register(e); + unsigned user_propagate_register_expr(expr* e) override { + return ensure_euf()->user_propagate_register_expr(e); } + void user_propagate_register_created(user_propagator::created_eh_t& r) { + ensure_euf()->user_propagate_register_created(r); + } + + private: lbool internalize_goal(goal_ref& g) { diff --git a/src/sat/sat_solver_core.h b/src/sat/sat_solver_core.h index ca66003cb..407deae5c 100644 --- a/src/sat/sat_solver_core.h +++ b/src/sat/sat_solver_core.h @@ -35,10 +35,7 @@ namespace sat { // add clauses virtual void add_clause(unsigned n, literal* lits, status st) = 0; - void add_clause(literal l1, literal l2, status st) { - literal lits[2] = {l1, l2}; - add_clause(2, lits, st); - } + void add_clause(literal l1, literal l2, literal l3, status st) { literal lits[3] = {l1, l2, l3}; add_clause(3, lits, st); diff --git a/src/sat/smt/CMakeLists.txt b/src/sat/smt/CMakeLists.txt index 43b91b2f2..a75c0022d 100644 --- a/src/sat/smt/CMakeLists.txt +++ b/src/sat/smt/CMakeLists.txt @@ -38,7 +38,6 @@ z3_add_component(sat_smt q_queue.cpp q_solver.cpp recfun_solver.cpp - sat_dual_solver.cpp sat_th.cpp user_solver.cpp COMPONENT_DEPENDENCIES diff --git a/src/sat/smt/arith_axioms.cpp b/src/sat/smt/arith_axioms.cpp index b1398c836..1a60f597f 100644 --- a/src/sat/smt/arith_axioms.cpp +++ b/src/sat/smt/arith_axioms.cpp @@ -49,6 +49,14 @@ namespace arith { } } + void solver::mk_abs_axiom(app* n) { + expr* x = nullptr; + VERIFY(a.is_abs(n, x)); + literal is_nonneg = mk_literal(a.mk_ge(x, a.mk_numeral(rational::zero(), n->get_sort()))); + add_clause(~is_nonneg, eq_internalize(n, x)); + add_clause(is_nonneg, eq_internalize(n, a.mk_uminus(x))); + } + // t = n^0 void solver::mk_power0_axioms(app* t, app* n) { expr_ref p0(a.mk_power0(n, t->get_arg(1)), m); diff --git a/src/sat/smt/arith_internalize.cpp b/src/sat/smt/arith_internalize.cpp index 882dd23b0..6f4963990 100644 --- a/src/sat/smt/arith_internalize.cpp +++ b/src/sat/smt/arith_internalize.cpp @@ -237,9 +237,10 @@ namespace arith { if (!is_first) { // skip recursive internalization } - else if (a.is_to_int(n, n1)) { - mk_to_int_axiom(t); - } + else if (a.is_to_int(n, n1)) + mk_to_int_axiom(t); + else if (a.is_abs(n)) + mk_abs_axiom(t); else if (a.is_idiv(n, n1, n2)) { if (!a.is_numeral(n2, r) || r.is_zero()) found_underspecified(n); m_idiv_terms.push_back(n); @@ -268,17 +269,16 @@ namespace arith { } else if (!a.is_div0(n) && !a.is_mod0(n) && !a.is_idiv0(n) && !a.is_rem0(n) && !a.is_power0(n)) { found_unsupported(n); + ensure_arg_vars(to_app(n)); } else { - // no-op + ensure_arg_vars(to_app(n)); } } else { if (is_app(n)) { internalize_args(to_app(n)); - for (expr* arg : *to_app(n)) - if (a.is_arith_expr(arg) && !m.is_bool(arg)) - internalize_term(arg); + ensure_arg_vars(to_app(n)); } theory_var v = mk_evar(n); coeffs[vars.size()] = coeffs[index]; @@ -425,6 +425,12 @@ namespace arith { e_internalize(arg); } + void solver::ensure_arg_vars(app* n) { + for (expr* arg : *to_app(n)) + if (a.is_real(arg) || a.is_int(arg)) + internalize_term(arg); + } + theory_var solver::internalize_power(app* t, app* n, unsigned p) { internalize_args(t, true); bool _has_var = has_var(t); @@ -545,11 +551,13 @@ namespace arith { } m_left_side.clear(); // reset the coefficients after they have been used. - for (unsigned i = 0; i < vars.size(); ++i) { - theory_var var = vars[i]; + for (theory_var var : vars) { rational const& r = m_columns[var]; if (!r.is_zero()) { - m_left_side.push_back(std::make_pair(r, register_theory_var_in_lar_solver(var))); + auto vi = register_theory_var_in_lar_solver(var); + if (lp::tv::is_term(vi)) + vi = lp().map_term_index_to_column_index(vi); + m_left_side.push_back(std::make_pair(r, vi)); m_columns[var].reset(); } } diff --git a/src/sat/smt/arith_solver.cpp b/src/sat/smt/arith_solver.cpp index da68691c7..5a102fa14 100644 --- a/src/sat/smt/arith_solver.cpp +++ b/src/sat/smt/arith_solver.cpp @@ -561,6 +561,8 @@ namespace arith { } void solver::dbg_finalize_model(model& mdl) { + if (m_not_handled) + return; bool found_bad = false; for (unsigned v = 0; v < get_num_vars(); ++v) { if (!is_bool(v)) @@ -953,6 +955,7 @@ namespace arith { if (n1->get_root() == n2->get_root()) continue; literal eq = eq_internalize(n1, n2); + ctx.mark_relevant(eq); if (s().value(eq) != l_true) return true; } @@ -1446,21 +1449,19 @@ namespace arith { TRACE("arith", tout << "canceled\n";); return l_undef; } - if (!m_nla) { - TRACE("arith", tout << "no nla\n";); + CTRACE("arith", !m_nla, tout << "no nla\n";); + if (!m_nla) return l_true; - } if (!m_nla->need_check()) return l_true; m_a1 = nullptr; m_a2 = nullptr; lbool r = m_nla->check(m_nla_lemma_vector); switch (r) { - case l_false: { + case l_false: for (const nla::lemma& l : m_nla_lemma_vector) false_case_of_check_nla(l); break; - } case l_true: if (assume_eqs()) return l_false; @@ -1477,11 +1478,28 @@ namespace arith { } bool solver::include_func_interp(func_decl* f) const { - return - a.is_div0(f) || - a.is_idiv0(f) || - a.is_power0(f) || - a.is_rem0(f) || - a.is_mod0(f); + SASSERT(f->get_family_id() == get_id()); + switch (f->get_decl_kind()) { + case OP_ADD: + case OP_SUB: + case OP_UMINUS: + case OP_MUL: + case OP_LE: + case OP_LT: + case OP_GE: + case OP_GT: + case OP_MOD: + case OP_REM: + case OP_DIV: + case OP_IDIV: + case OP_POWER: + case OP_IS_INT: + case OP_TO_INT: + case OP_TO_REAL: + case OP_NUM: + return false; + default: + return true; + } } } diff --git a/src/sat/smt/arith_solver.h b/src/sat/smt/arith_solver.h index 759c85fae..d8405ea10 100644 --- a/src/sat/smt/arith_solver.h +++ b/src/sat/smt/arith_solver.h @@ -239,6 +239,7 @@ namespace arith { void add_def_constraint(lp::constraint_index index, theory_var v); void add_def_constraint_and_equality(lpvar vi, lp::lconstraint_kind kind, const rational& bound); void internalize_args(app* t, bool force = false); + void ensure_arg_vars(app* t); theory_var internalize_power(app* t, app* n, unsigned p); theory_var internalize_mul(app* t); theory_var internalize_def(expr* term); @@ -263,6 +264,7 @@ namespace arith { // axioms void mk_div_axiom(expr* p, expr* q); void mk_to_int_axiom(app* n); + void mk_abs_axiom(app* n); void mk_is_int_axiom(expr* n); void mk_idiv_mod_axioms(expr* p, expr* q); void mk_rem_axiom(expr* dividend, expr* divisor); @@ -445,6 +447,8 @@ namespace arith { bool is_shared(theory_var v) const override; lbool get_phase(bool_var v) override; bool include_func_interp(func_decl* f) const override; + bool enable_ackerman_axioms(euf::enode* n) const override { return !a.is_add(n->get_expr()); } + bool has_unhandled() const override { return m_not_handled != nullptr; } // bounds and equality propagation callbacks lp::lar_solver& lp() { return *m_solver; } diff --git a/src/sat/smt/array_axioms.cpp b/src/sat/smt/array_axioms.cpp index d362dacb6..296c35b8c 100644 --- a/src/sat/smt/array_axioms.cpp +++ b/src/sat/smt/array_axioms.cpp @@ -57,8 +57,6 @@ namespace array { bool solver::assert_axiom(unsigned idx) { axiom_record& r = m_axiom_trail[idx]; - if (!is_relevant(r)) - return false; switch (r.m_kind) { case axiom_record::kind_t::is_store: return assert_store_axiom(to_app(r.n->get_expr())); @@ -86,35 +84,12 @@ namespace array { return assert_default_const_axiom(to_app(child)); else if (a.is_store(child)) return assert_default_store_axiom(to_app(child)); - else if (a.is_map(child)) + else if (is_map_combinator(child)) return assert_default_map_axiom(to_app(child)); else return false; } - - bool solver::is_relevant(axiom_record const& r) const { - return true; -#if 0 - // relevancy propagation is currently incomplete on terms - - expr* child = r.n->get_expr(); - switch (r.m_kind) { - case axiom_record::kind_t::is_select: { - app* select = r.select->get_app(); - for (unsigned i = 1; i < select->get_num_args(); ++i) - if (!ctx.is_relevant(select->get_arg(i))) - return false; - return ctx.is_relevant(child); - } - case axiom_record::kind_t::is_default: - return ctx.is_relevant(child); - default: - return true; - } -#endif - } - bool solver::assert_select(unsigned idx, axiom_record& r) { expr* child = r.n->get_expr(); app* select = r.select->get_app(); @@ -140,7 +115,7 @@ namespace array { return assert_select_as_array_axiom(select, to_app(child)); else if (a.is_store(child)) return assert_select_store_axiom(select, to_app(child)); - else if (a.is_map(child)) + else if (is_map_combinator(child)) return assert_select_map_axiom(select, to_app(child)); else if (is_lambda(child)) return assert_select_lambda_axiom(select, child); @@ -215,10 +190,17 @@ namespace array { return new_prop; sat::literal sel_eq = sat::null_literal; + auto ensure_relevant = [&](sat::literal lit) { + if (ctx.is_relevant(lit)) + return; + new_prop = true; + ctx.mark_relevant(lit); + }; auto init_sel_eq = [&]() { if (sel_eq != sat::null_literal) return true; sel_eq = mk_literal(sel_eq_e); + ensure_relevant(sel_eq); return s().value(sel_eq) != l_true; }; @@ -235,6 +217,7 @@ namespace array { break; } sat::literal idx_eq = eq_internalize(idx1, idx2); + ensure_relevant(idx_eq); if (s().value(idx_eq) == l_true) continue; if (s().value(idx_eq) == l_undef) @@ -253,8 +236,7 @@ namespace array { * Assert * select(const(v), i) = v */ - bool solver::assert_select_const_axiom(app* select, app* cnst) { - + bool solver::assert_select_const_axiom(app* select, app* cnst) { ++m_stats.m_num_select_const_axiom; expr* val = nullptr; VERIFY(a.is_const(cnst, val)); @@ -291,16 +273,19 @@ namespace array { return add_clause(lit1, ~lit2); } + bool solver::is_map_combinator(expr* map) const { + return a.is_map(map) || a.is_union(map) || a.is_intersect(map) || a.is_difference(map) || a.is_complement(map); + } + /** * Assert axiom: * select(map[f](a, ... d), i) = f(select(a,i),...,select(d,i)) */ bool solver::assert_select_map_axiom(app* select, app* map) { ++m_stats.m_num_select_map_axiom; - SASSERT(a.is_map(map)); SASSERT(a.is_select(select)); + SASSERT(is_map_combinator(map)); SASSERT(map->get_num_args() > 0); - func_decl* f = a.get_map_func_decl(map); unsigned num_args = select->get_num_args(); ptr_buffer args1, args2; vector > args2l; @@ -321,7 +306,8 @@ namespace array { expr_ref sel1(m), sel2(m); sel1 = a.mk_select(args1); - sel2 = m.mk_app(f, args2); + sel2 = apply_map(map, args2.size(), args2.data()); + rewrite(sel2); euf::enode* n1 = e_internalize(sel1); euf::enode* n2 = e_internalize(sel2); @@ -348,21 +334,44 @@ namespace array { return ctx.propagate(n1, n2, array_axiom()); } + expr_ref solver::apply_map(app* map, unsigned n, expr* const* args) { + expr_ref result(m); + if (a.is_map(map)) + result = m.mk_app(a.get_map_func_decl(map), n, args); + else if (a.is_union(map)) + result = m.mk_or(n, args); + else if (a.is_intersect(map)) + result = m.mk_and(n, args); + else if (a.is_difference(map)) { + SASSERT(n > 0); + result = args[0]; + for (unsigned i = 1; i < n; ++i) + result = m.mk_and(result, m.mk_not(args[i])); + } + else if (a.is_complement(map)) { + SASSERT(n == 1); + result = m.mk_not(args[0]); + } + else { + UNREACHABLE(); + } + rewrite(result); + return result; + } + + /** * Assert: * default(map[f](a,..,d)) = f(default(a),..,default(d)) */ bool solver::assert_default_map_axiom(app* map) { ++m_stats.m_num_default_map_axiom; - SASSERT(a.is_map(map)); - func_decl* f = a.get_map_func_decl(map); - SASSERT(map->get_num_args() == f->get_arity()); + SASSERT(is_map_combinator(map)); expr_ref_vector args2(m); for (expr* arg : *map) args2.push_back(a.mk_default(arg)); expr_ref def1(a.mk_default(map), m); - expr_ref def2(m.mk_app(f, args2), m); - rewrite(def2); + expr_ref def2 = apply_map(map, args2.size(), args2.data()); return ctx.propagate(e_internalize(def1), e_internalize(def2), array_axiom()); } @@ -534,11 +543,14 @@ namespace array { unsigned num_vars = get_num_vars(); bool change = false; for (unsigned v = 0; v < num_vars; v++) { - propagate_parent_select_axioms(v); auto& d = get_var_data(v); if (!d.m_prop_upward) continue; euf::enode* n = var2enode(v); + if (!ctx.is_relevant(n)) + continue; + for (euf::enode* lambda : d.m_parent_lambdas) + propagate_select_axioms(d, lambda); if (add_as_array_eqs(n)) change = true; bool has_default = false; @@ -552,8 +564,8 @@ namespace array { m_delay_qhead = 0; for (; m_delay_qhead < sz; ++m_delay_qhead) - if (m_axiom_trail[m_delay_qhead].is_delayed() && assert_axiom(m_delay_qhead)) - change = true; + if (m_axiom_trail[m_delay_qhead].is_delayed() && assert_axiom(m_delay_qhead)) + change = true; flet _enable_delay(m_enable_delay, false); if (unit_propagate()) change = true; @@ -567,6 +579,8 @@ namespace array { return false; for (unsigned i = 0; i < ctx.get_egraph().enodes_of(f).size(); ++i) { euf::enode* p = ctx.get_egraph().enodes_of(f)[i]; + if (!ctx.is_relevant(p)) + continue; expr_ref_vector select(m); select.push_back(n->get_expr()); for (expr* arg : *to_app(p->get_expr())) @@ -574,7 +588,8 @@ namespace array { expr_ref _e(a.mk_select(select.size(), select.data()), m); euf::enode* e = e_internalize(_e); if (e->get_root() != p->get_root()) { - add_unit(eq_internalize(_e, p->get_expr())); + sat::literal eq = eq_internalize(_e, p->get_expr()); + add_unit(eq); change = true; } } @@ -594,13 +609,12 @@ namespace array { expr* e2 = var2expr(v2); if (e1->get_sort() != e2->get_sort()) continue; - if (must_have_different_model_values(v1, v2)) { - continue; - } - if (ctx.get_egraph().are_diseq(var2enode(v1), var2enode(v2))) { - continue; - } + if (must_have_different_model_values(v1, v2)) + continue; + if (ctx.get_egraph().are_diseq(var2enode(v1), var2enode(v2))) + continue; sat::literal lit = eq_internalize(e1, e2); + ctx.mark_relevant(lit); if (s().value(lit) == l_undef) prop = true; } @@ -612,10 +626,11 @@ namespace array { ptr_buffer to_unmark; unsigned num_vars = get_num_vars(); for (unsigned i = 0; i < num_vars; i++) { - euf::enode * n = var2enode(i); - + euf::enode * n = var2enode(i); if (!is_array(n)) - continue; + continue; + if (!ctx.is_relevant(n)) + continue; euf::enode * r = n->get_root(); if (r->is_marked1()) continue; diff --git a/src/sat/smt/array_internalize.cpp b/src/sat/smt/array_internalize.cpp index aa94f12a7..e1d0d4b33 100644 --- a/src/sat/smt/array_internalize.cpp +++ b/src/sat/smt/array_internalize.cpp @@ -49,7 +49,7 @@ namespace array { if (v == euf::null_theory_var) { mk_var(n); if (is_lambda(n->get_expr())) - internalize_lambda(n); + internalize_lambda_eh(n); } } @@ -57,45 +57,6 @@ namespace array { ensure_var(n); } - void solver::internalize_store(euf::enode* n) { - add_parent_lambda(n->get_arg(0)->get_th_var(get_id()), n); - push_axiom(store_axiom(n)); - add_lambda(n->get_th_var(get_id()), n); - SASSERT(!get_var_data(n->get_th_var(get_id())).m_prop_upward); - } - - void solver::internalize_map(euf::enode* n) { - for (auto* arg : euf::enode_args(n)) { - add_parent_lambda(arg->get_th_var(get_id()), n); - set_prop_upward(arg); - } - push_axiom(default_axiom(n)); - add_lambda(n->get_th_var(get_id()), n); - SASSERT(!get_var_data(n->get_th_var(get_id())).m_prop_upward); - } - - void solver::internalize_lambda(euf::enode* n) { - SASSERT(is_lambda(n->get_expr()) || a.is_const(n->get_expr()) || a.is_as_array(n->get_expr())); - theory_var v = n->get_th_var(get_id()); - push_axiom(default_axiom(n)); - add_lambda(v, n); - set_prop_upward(v); - } - - void solver::internalize_select(euf::enode* n) { - add_parent_select(n->get_arg(0)->get_th_var(get_id()), n); - } - - void solver::internalize_ext(euf::enode* n) { - SASSERT(is_array(n->get_arg(0))); - push_axiom(extensionality_axiom(n->get_arg(0), n->get_arg(1))); - } - - void solver::internalize_default(euf::enode* n) { - add_parent_default(n->get_arg(0)->get_th_var(get_id()), n); - set_prop_upward(n); - } - bool solver::visited(expr* e) { euf::enode* n = expr2enode(e); return n && n->is_attached_to(get_id()); @@ -116,7 +77,8 @@ namespace array { bool solver::post_visit(expr* e, bool sign, bool root) { euf::enode* n = expr2enode(e); - app* a = to_app(e); + app *a = to_app(e); + (void)a; SASSERT(!n || !n->is_attached_to(get_id())); if (!n) n = mk_enode(e, false); @@ -124,40 +86,111 @@ namespace array { mk_var(n); for (auto* arg : euf::enode_args(n)) ensure_var(arg); - switch (a->get_decl_kind()) { - case OP_STORE: - internalize_store(n); + internalize_eh(n); + if (ctx.is_relevant(n)) + relevant_eh(n); + return true; + } + + void solver::internalize_lambda_eh(euf::enode* n) { + push_axiom(default_axiom(n)); + auto& d = get_var_data(find(n)); + ctx.push_vec(d.m_lambdas, n); + } + + void solver::internalize_eh(euf::enode* n) { + switch (n->get_decl()->get_decl_kind()) { + case OP_STORE: + ctx.push_vec(get_var_data(find(n)).m_lambdas, n); + push_axiom(store_axiom(n)); break; - case OP_SELECT: - internalize_select(n); + case OP_SELECT: break; case OP_AS_ARRAY: - case OP_CONST_ARRAY: - internalize_lambda(n); + case OP_CONST_ARRAY: + internalize_lambda_eh(n); break; - case OP_ARRAY_EXT: - internalize_ext(n); + case OP_ARRAY_EXT: + SASSERT(is_array(n->get_arg(0))); + push_axiom(extensionality_axiom(n->get_arg(0), n->get_arg(1))); break; - case OP_ARRAY_DEFAULT: - internalize_default(n); + case OP_ARRAY_DEFAULT: + add_parent_default(find(n->get_arg(0)), n); break; - case OP_ARRAY_MAP: - internalize_map(n); + case OP_ARRAY_MAP: + case OP_SET_UNION: + case OP_SET_INTERSECT: + case OP_SET_DIFFERENCE: + case OP_SET_COMPLEMENT: + for (auto* arg : euf::enode_args(n)) + add_parent_lambda(find(arg), n); + internalize_lambda_eh(n); break; - case OP_SET_UNION: - case OP_SET_INTERSECT: - case OP_SET_DIFFERENCE: - case OP_SET_COMPLEMENT: - case OP_SET_SUBSET: - case OP_SET_HAS_SIZE: - case OP_SET_CARD: - ctx.unhandled_function(a->get_decl()); + case OP_SET_SUBSET: { + expr* x, *y; + VERIFY(a.is_subset(n->get_expr(), x, y)); + expr_ref diff(a.mk_setminus(x, y), m); + expr_ref emp(a.mk_empty_set(x->get_sort()), m); + sat::literal eq = eq_internalize(diff, emp); + sat::literal sub = expr2literal(n->get_expr()); + add_equiv(eq, sub); + break; + } + case OP_SET_HAS_SIZE: + case OP_SET_CARD: + ctx.unhandled_function(n->get_decl()); break; default: UNREACHABLE(); - break; + break; + } + } + + void solver::relevant_eh(euf::enode* n) { + if (is_lambda(n->get_expr())) { + set_prop_upward(find(n)); + return; + } + if (!is_app(n->get_expr())) + return; + if (n->get_decl()->get_family_id() != a.get_family_id()) + return; + switch (n->get_decl()->get_decl_kind()) { + case OP_STORE: + add_parent_lambda(find(n->get_arg(0)), n); + break; + case OP_SELECT: + add_parent_select(find(n->get_arg(0)), n); + break; + case OP_CONST_ARRAY: + case OP_AS_ARRAY: + set_prop_upward(find(n)); + propagate_parent_default(find(n)); + break; + case OP_ARRAY_EXT: + break; + case OP_ARRAY_DEFAULT: + set_prop_upward(find(n->get_arg(0))); + break; + case OP_ARRAY_MAP: + case OP_SET_UNION: + case OP_SET_INTERSECT: + case OP_SET_DIFFERENCE: + case OP_SET_COMPLEMENT: + for (auto* arg : euf::enode_args(n)) + set_prop_upward_store(arg); + set_prop_upward(find(n)); + break; + case OP_SET_SUBSET: + break; + case OP_SET_HAS_SIZE: + case OP_SET_CARD: + ctx.unhandled_function(n->get_decl()); + break; + default: + UNREACHABLE(); + break; } - return true; } /** diff --git a/src/sat/smt/array_model.cpp b/src/sat/smt/array_model.cpp index 92a368095..1f1066121 100644 --- a/src/sat/smt/array_model.cpp +++ b/src/sat/smt/array_model.cpp @@ -60,6 +60,11 @@ namespace array { ptr_vector args; sort* srt = n->get_sort(); n = n->get_root(); + if (a.is_as_array(n->get_expr())) { + values.set(n->get_expr_id(), n->get_expr()); + return; + } + unsigned arity = get_array_arity(srt); func_decl * f = mk_aux_decl_for_array_sort(m, srt); func_interp * fi = alloc(func_interp, m, arity); diff --git a/src/sat/smt/array_solver.cpp b/src/sat/smt/array_solver.cpp index 15978747c..f2836d9aa 100644 --- a/src/sat/smt/array_solver.cpp +++ b/src/sat/smt/array_solver.cpp @@ -186,17 +186,16 @@ namespace array { if (should_set_prop_upward(d)) set_prop_upward(d); ctx.push_vec(d.m_lambdas, lambda); - if (should_set_prop_upward(d)) { - set_prop_upward(lambda); - propagate_select_axioms(d, lambda); - } + propagate_select_axioms(d, lambda); + if (should_set_prop_upward(d)) + set_prop_upward_store(lambda); } void solver::add_parent_lambda(theory_var v_child, euf::enode* lambda) { SASSERT(can_beta_reduce(lambda)); auto& d = get_var_data(find(v_child)); ctx.push_vec(d.m_parent_lambdas, lambda); - if (should_set_prop_upward(d)) + if (should_prop_upward(d)) propagate_select_axioms(d, lambda); } @@ -231,6 +230,9 @@ namespace array { for (euf::enode* lambda : d.m_lambdas) propagate_select_axioms(d, lambda); + if (!should_prop_upward(d)) + return; + for (euf::enode* lambda : d.m_parent_lambdas) propagate_select_axioms(d, lambda); } @@ -246,14 +248,14 @@ namespace array { set_prop_upward(d); } - void solver::set_prop_upward(euf::enode* n) { + void solver::set_prop_upward_store(euf::enode* n) { if (a.is_store(n->get_expr())) set_prop_upward(n->get_arg(0)->get_th_var(get_id())); } void solver::set_prop_upward(var_data& d) { for (auto* p : d.m_lambdas) - set_prop_upward(p); + set_prop_upward_store(p); } /** @@ -273,6 +275,6 @@ namespace array { } bool solver::can_beta_reduce(expr* c) const { - return a.is_const(c) || a.is_as_array(c) || a.is_store(c) || is_lambda(c) || a.is_map(c); + return a.is_const(c) || a.is_as_array(c) || a.is_store(c) || is_lambda(c) || is_map_combinator(c); } } diff --git a/src/sat/smt/array_solver.h b/src/sat/smt/array_solver.h index 0c0fc82d6..31bdba4a1 100644 --- a/src/sat/smt/array_solver.h +++ b/src/sat/smt/array_solver.h @@ -66,6 +66,7 @@ namespace array { array_union_find m_find; theory_var find(theory_var v) { return m_find.find(v); } + theory_var find(euf::enode* n) { return find(n->get_th_var(get_id())); } func_decl_ref_vector const& sort2diff(sort* s); // internalize @@ -73,12 +74,8 @@ namespace array { bool visited(expr* e) override; bool post_visit(expr* e, bool sign, bool root) override; void ensure_var(euf::enode* n); - void internalize_store(euf::enode* n); - void internalize_select(euf::enode* n); - void internalize_lambda(euf::enode* n); - void internalize_ext(euf::enode* n); - void internalize_default(euf::enode* n); - void internalize_map(euf::enode* n); + void internalize_eh(euf::enode* n); + void internalize_lambda_eh(euf::enode* n); // axioms struct axiom_record { @@ -157,7 +154,6 @@ namespace array { bool assert_axiom(unsigned idx); bool assert_select(unsigned idx, axiom_record & r); bool assert_default(axiom_record & r); - bool is_relevant(axiom_record const& r) const; void set_applied(unsigned idx) { m_axiom_trail[idx].set_applied(); } bool is_applied(unsigned idx) const { return m_axiom_trail[idx].is_applied(); } bool is_delayed(unsigned idx) const { return m_axiom_trail[idx].is_delayed(); } @@ -185,7 +181,9 @@ namespace array { bool assert_congruent_axiom(expr* e1, expr* e2); bool add_delayed_axioms(); bool add_as_array_eqs(euf::enode* n); - + expr_ref apply_map(app* map, unsigned n, expr* const* args); + bool is_map_combinator(expr* e) const; + bool has_unitary_domain(app* array_term); bool has_large_domain(expr* array_term); std::pair mk_epsilon(sort* s); @@ -197,7 +195,7 @@ namespace array { // solving void add_parent_select(theory_var v_child, euf::enode* select); void add_parent_default(theory_var v_child, euf::enode* def); - void add_lambda(theory_var v, euf::enode* lambda); + void add_lambda(theory_var v, euf::enode* lambda); void add_parent_lambda(theory_var v_child, euf::enode* lambda); void propagate_select_axioms(var_data const& d, euf::enode* a); @@ -206,7 +204,7 @@ namespace array { void set_prop_upward(theory_var v); void set_prop_upward(var_data& d); - void set_prop_upward(euf::enode* n); + void set_prop_upward_store(euf::enode* n); unsigned get_lambda_equiv_size(var_data const& d) const; bool should_set_prop_upward(var_data const& d) const; bool should_prop_upward(var_data const& d) const; @@ -256,6 +254,7 @@ namespace array { void new_diseq_eh(euf::th_eq const& eq) override; bool unit_propagate() override; void init_model() override; + bool include_func_interp(func_decl* f) const override { return a.is_ext(f); } void add_value(euf::enode* n, model& mdl, expr_ref_vector& values) override; bool add_dep(euf::enode* n, top_sort& dep) override; sat::literal internalize(expr* e, bool sign, bool root, bool learned) override; @@ -264,7 +263,9 @@ namespace array { void apply_sort_cnstr(euf::enode* n, sort* s) override; bool is_shared(theory_var v) const override; bool enable_self_propagate() const override { return true; } - + void relevant_eh(euf::enode* n) override; + bool enable_ackerman_axioms(euf::enode* n) const override { return !a.is_array(n->get_sort()); } + void merge_eh(theory_var, theory_var, theory_var v1, theory_var v2); void after_merge_eh(theory_var r1, theory_var r2, theory_var v1, theory_var v2) {} void unmerge_eh(theory_var v1, theory_var v2) {} diff --git a/src/sat/smt/bv_delay_internalize.cpp b/src/sat/smt/bv_delay_internalize.cpp index ca4841656..796d0bda1 100644 --- a/src/sat/smt/bv_delay_internalize.cpp +++ b/src/sat/smt/bv_delay_internalize.cpp @@ -21,7 +21,10 @@ Author: namespace bv { bool solver::check_delay_internalized(expr* e) { - if (!ctx.is_relevant(e)) + euf::enode* n = expr2enode(e); + if (!n) + return true; + if (!ctx.is_relevant(n)) return true; if (get_internalize_mode(e) != internalize_mode::delay_i) return true; @@ -247,7 +250,7 @@ namespace bv { return; expr_ref tmp = literal2expr(bits.back()); for (unsigned i = bits.size() - 1; i-- > 0; ) { - auto b = bits[i]; + sat::literal b = bits[i]; tmp = m.mk_or(literal2expr(b), tmp); xs.push_back(tmp); } @@ -356,10 +359,28 @@ namespace bv { solver::internalize_mode solver::get_internalize_mode(expr* e) { if (!bv.is_bv(e)) return internalize_mode::no_delay_i; - if (!get_config().m_bv_delay) - return internalize_mode::no_delay_i; if (!reflect()) return internalize_mode::no_delay_i; + if (get_config().m_bv_polysat) { + switch (to_app(e)->get_decl_kind()) { + case OP_BMUL: + case OP_BUMUL_NO_OVFL: + case OP_BSMOD_I: + case OP_BUREM_I: + case OP_BUDIV_I: + case OP_BADD: + return internalize_mode::polysat_i; + case OP_BSMUL_NO_OVFL: + case OP_BSMUL_NO_UDFL: + case OP_BSDIV_I: + case OP_BSREM_I: + default: + return internalize_mode::no_delay_i; + } + } + + if (!get_config().m_bv_delay) + return internalize_mode::no_delay_i; internalize_mode mode; switch (to_app(e)->get_decl_kind()) { case OP_BMUL: diff --git a/src/sat/smt/bv_internalize.cpp b/src/sat/smt/bv_internalize.cpp index 439764515..33bede1bd 100644 --- a/src/sat/smt/bv_internalize.cpp +++ b/src/sat/smt/bv_internalize.cpp @@ -144,10 +144,16 @@ namespace bv { SASSERT(!n->is_attached_to(get_id())); mk_var(n); SASSERT(n->is_attached_to(get_id())); - if (internalize_mode::no_delay_i != get_internalize_mode(a)) - mk_bits(n->get_th_var(get_id())); - else + switch (get_internalize_mode(a)) { + case internalize_mode::no_delay_i: internalize_circuit(a); + break; + case internalize_mode::polysat_i: + NOT_IMPLEMENTED_YET(); + break; + default: + mk_bits(n->get_th_var(get_id())); + } return true; } @@ -166,6 +172,7 @@ namespace bv { #define internalize_pun(F) pun = [&](unsigned sz, expr* const* xs, unsigned p, expr_ref_vector& bits) { m_bb.F(sz, xs, p, bits);}; internalize_par_unary(a, pun); #define internalize_nfl(F) ebin = [&](unsigned sz, expr* const* xs, expr* const* ys, expr_ref& out) { m_bb.F(sz, xs, ys, out);}; internalize_novfl(a, ebin); #define internalize_int(B, U) ibin = [&](expr* x, expr* y) { return B(x, y); }; iun = [&](expr* x) { return U(x); }; internalize_interp(a, ibin, iun); +#define if_unary(F) if (a->get_num_args() == 1) { internalize_un(F); break; } switch (a->get_decl_kind()) { case OP_BV_NUM: internalize_num(a); break; @@ -190,7 +197,7 @@ namespace bv { case OP_BXOR: internalize_ac(mk_xor); break; case OP_BNAND: internalize_bin(mk_nand); break; case OP_BNOR: internalize_bin(mk_nor); break; - case OP_BXNOR: internalize_bin(mk_xnor); break; + case OP_BXNOR: if_unary(mk_not); internalize_bin(mk_xnor); break; case OP_BCOMP: internalize_bin(mk_comp); break; case OP_SIGN_EXT: internalize_pun(mk_sign_extend); break; case OP_ZERO_EXT: internalize_pun(mk_zero_extend); break; @@ -370,6 +377,7 @@ namespace bv { if (m_true == sat::null_literal) { ctx.push(value_trail(m_true)); m_true = ctx.internalize(m.mk_true(), false, true, false); + s().assign_unit(m_true); } return m_true; } @@ -426,7 +434,6 @@ namespace bv { expr_ref sum(m_autil.mk_add(sz, args.data()), m); sat::literal lit = eq_internalize(n, sum); add_unit(lit); - ctx.add_root(lit); } void solver::internalize_int2bv(app* n) { @@ -456,7 +463,6 @@ namespace bv { rhs = m_autil.mk_mod(e, m_autil.mk_int(mod)); sat::literal eq_lit = eq_internalize(lhs, rhs); add_unit(eq_lit); - ctx.add_root(eq_lit); expr_ref_vector n_bits(m); get_bits(n_enode, n_bits); @@ -469,7 +475,6 @@ namespace bv { lhs = n_bits.get(i); eq_lit = eq_internalize(lhs, rhs); add_unit(eq_lit); - ctx.add_root(eq_lit); } } @@ -535,7 +540,6 @@ namespace bv { if (p.hi_div0()) { eq_lit = eq_internalize(n, ibin(arg1, arg2)); add_unit(eq_lit); - ctx.add_root(eq_lit); } else { unsigned sz = bv.get_bv_size(n); @@ -653,7 +657,6 @@ namespace bv { mk_bits(get_th_var(e)); sat::literal eq_lit = eq_internalize(e, r); add_unit(eq_lit); - ctx.add_root(eq_lit); } void solver::internalize_bit2bool(app* n) { diff --git a/src/sat/smt/bv_solver.cpp b/src/sat/smt/bv_solver.cpp index fe6d7e0f7..bc435d7eb 100644 --- a/src/sat/smt/bv_solver.cpp +++ b/src/sat/smt/bv_solver.cpp @@ -58,6 +58,15 @@ namespace bv { m_bb.set_flat(false); } + bool solver::is_fixed(euf::theory_var v, expr_ref& val, sat::literal_vector& lits) { + numeral n; + if (!get_fixed_value(v, n)) + return false; + val = bv.mk_numeral(n, m_bits[v].size()); + lits.append(m_bits[v]); + return true; + } + void solver::fixed_var_eh(theory_var v1) { numeral val1, val2; VERIFY(get_fixed_value(v1, val1)); @@ -158,7 +167,7 @@ namespace bv { SASSERT(m_bits[v1][idx] == ~m_bits[v2][idx]); TRACE("bv", tout << "found new diseq axiom\n" << pp(v1) << pp(v2);); m_stats.m_num_diseq_static++; - expr_ref eq = mk_var_eq(v1, v2); + expr_ref eq(m.mk_eq(var2expr(v1), var2expr(v2)), m); add_unit(~ctx.internalize(eq, false, false, m_is_redundant)); } @@ -740,6 +749,7 @@ namespace bv { void solver::merge_eh(theory_var r1, theory_var r2, theory_var v1, theory_var v2) { TRACE("bv", tout << "merging: v" << v1 << " #" << var2enode(v1)->get_expr_id() << " v" << v2 << " #" << var2enode(v2)->get_expr_id() << "\n";); + if (!merge_zero_one_bits(r1, r2)) { TRACE("bv", tout << "conflict detected\n";); return; // conflict was detected diff --git a/src/sat/smt/bv_solver.h b/src/sat/smt/bv_solver.h index f699a8030..5453871b4 100644 --- a/src/sat/smt/bv_solver.h +++ b/src/sat/smt/bv_solver.h @@ -226,6 +226,7 @@ namespace bv { void get_bits(euf::enode* n, expr_ref_vector& r); void get_arg_bits(app* n, unsigned idx, expr_ref_vector& r); void fixed_var_eh(theory_var v); + bool is_fixed(euf::theory_var v, expr_ref& val, sat::literal_vector& lits) override; bool is_bv(theory_var v) const { return bv.is_bv(var2expr(v)); } void register_true_false_bit(theory_var v, unsigned i); void add_bit(theory_var v, sat::literal lit); @@ -266,6 +267,7 @@ namespace bv { enum class internalize_mode { delay_i, no_delay_i, + polysat_i, init_bits_only_i }; diff --git a/src/sat/smt/euf_ackerman.cpp b/src/sat/smt/euf_ackerman.cpp index 941b1e0ac..c8639e302 100644 --- a/src/sat/smt/euf_ackerman.cpp +++ b/src/sat/smt/euf_ackerman.cpp @@ -99,14 +99,38 @@ namespace euf { m_tmp_inference->init(m_tmp_inference); } + bool ackerman::enable_cc(app* a, app* b) { + if (!s.enable_ackerman_axioms(a)) + return false; + if (!s.enable_ackerman_axioms(b)) + return false; + for (expr* arg : *a) + if (!s.enable_ackerman_axioms(arg)) + return false; + for (expr* arg : *b) + if (!s.enable_ackerman_axioms(arg)) + return false; + return true; + } + + bool ackerman::enable_eq(expr* a, expr* b, expr* c) { + return s.enable_ackerman_axioms(a) && + s.enable_ackerman_axioms(b) && + s.enable_ackerman_axioms(c); + } + void ackerman::cg_conflict_eh(expr * n1, expr * n2) { if (!is_app(n1) || !is_app(n2)) return; + if (!s.enable_ackerman_axioms(n1)) + return; SASSERT(!s.m_drating); app* a = to_app(n1); app* b = to_app(n2); if (a->get_decl() != b->get_decl() || a->get_num_args() != b->get_num_args()) return; + if (!enable_cc(a, b)) + return; TRACE("ack", tout << "conflict eh: " << mk_pp(a, m) << " == " << mk_pp(b, m) << "\n";); insert(a, b); gc(); @@ -117,6 +141,8 @@ namespace euf { return; if (s.m_drating) return; + if (!enable_eq(a, b, c)) + return; TRACE("ack", tout << mk_pp(a, m) << " " << mk_pp(b, m) << " " << mk_pp(c, m) << "\n";); insert(a, b, c); gc(); @@ -128,6 +154,8 @@ namespace euf { TRACE("ack", tout << "used cc: " << mk_pp(a, m) << " == " << mk_pp(b, m) << "\n";); SASSERT(a->get_decl() == b->get_decl()); SASSERT(a->get_num_args() == b->get_num_args()); + if (!enable_cc(a, b)) + return; insert(a, b); gc(); } @@ -173,13 +201,13 @@ namespace euf { app* b = to_app(_b); TRACE("ack", tout << mk_pp(a, m) << " " << mk_pp(b, m) << "\n";); sat::literal_vector lits; - unsigned sz = a->get_num_args(); + unsigned sz = a->get_num_args(); for (unsigned i = 0; i < sz; ++i) { - expr_ref eq(m.mk_eq(a->get_arg(i), b->get_arg(i)), m); + expr_ref eq = s.mk_eq(a->get_arg(i), b->get_arg(i)); lits.push_back(~s.mk_literal(eq)); } - expr_ref eq(m.mk_eq(a, b), m); + expr_ref eq = s.mk_eq(a, b); lits.push_back(s.mk_literal(eq)); s.s().mk_clause(lits, sat::status::th(true, m.get_basic_family_id())); } @@ -187,9 +215,9 @@ namespace euf { void ackerman::add_eq(expr* a, expr* b, expr* c) { flet _is_redundant(s.m_is_redundant, true); sat::literal lits[3]; - expr_ref eq1(m.mk_eq(a, c), m); - expr_ref eq2(m.mk_eq(b, c), m); - expr_ref eq3(m.mk_eq(a, b), m); + expr_ref eq1(s.mk_eq(a, c), m); + expr_ref eq2(s.mk_eq(b, c), m); + expr_ref eq3(s.mk_eq(a, b), m); TRACE("ack", tout << mk_pp(a, m) << " " << mk_pp(b, m) << " " << mk_pp(c, m) << "\n";); lits[0] = ~s.mk_literal(eq1); lits[1] = ~s.mk_literal(eq2); diff --git a/src/sat/smt/euf_ackerman.h b/src/sat/smt/euf_ackerman.h index 7b9c4c3d9..479ad2933 100644 --- a/src/sat/smt/euf_ackerman.h +++ b/src/sat/smt/euf_ackerman.h @@ -70,6 +70,9 @@ namespace euf { void add_cc(expr* a, expr* b); void add_eq(expr* a, expr* b, expr* c); void gc(); + bool enable_cc(app* a, app* b); + bool enable_eq(expr* a, expr* b, expr* c); + public: ackerman(solver& s, ast_manager& m); diff --git a/src/sat/smt/euf_internalize.cpp b/src/sat/smt/euf_internalize.cpp index 90a268661..6cc72eba5 100644 --- a/src/sat/smt/euf_internalize.cpp +++ b/src/sat/smt/euf_internalize.cpp @@ -162,31 +162,34 @@ namespace euf { sat::literal lit2 = literal(v, false); s().mk_clause(~lit, lit2, sat::status::th(m_is_redundant, m.get_basic_family_id())); s().mk_clause(lit, ~lit2, sat::status::th(m_is_redundant, m.get_basic_family_id())); - if (relevancy_enabled()) { - add_aux(~lit, lit2); - add_aux(lit, ~lit2); - } + add_aux(~lit, lit2); + add_aux(lit, ~lit2); lit = lit2; } + TRACE("euf", tout << "attach " << v << " " << mk_bounded_pp(e, m) << "\n";); m_bool_var2expr.reserve(v + 1, nullptr); if (m_bool_var2expr[v] && m_egraph.find(e)) { + if (m_egraph.find(e)->bool_var() != v) { + IF_VERBOSE(0, verbose_stream() + << "var " << v << "\n" + << "found var " << m_egraph.find(e)->bool_var() << "\n" + << mk_pp(m_bool_var2expr[v], m) << "\n" + << mk_pp(e, m) << "\n"); + } SASSERT(m_egraph.find(e)->bool_var() == v); return lit; } - TRACE("euf", tout << "attach " << v << " " << mk_bounded_pp(e, m) << "\n";); + m_bool_var2expr[v] = e; - m_var_trail.push_back(v); + m_var_trail.push_back(v); enode* n = m_egraph.find(e); - if (!n) { + if (!n) n = mk_enode(e, 0, nullptr); - } SASSERT(n->bool_var() == sat::null_bool_var || n->bool_var() == v); m_egraph.set_bool_var(n, v); if (m.is_eq(e) || m.is_or(e) || m.is_and(e) || m.is_not(e)) m_egraph.set_merge_enabled(n, false); - if (!si.is_bool_op(e)) - track_relevancy(lit.var()); if (s().value(lit) != l_undef) m_egraph.set_value(n, s().value(lit)); return lit; @@ -224,9 +227,8 @@ namespace euf { lits.push_back(lit); } } + add_root(lits); s().mk_clause(lits, st); - if (relevancy_enabled()) - add_root(lits); } else { // g(f(x_i)) = x_i @@ -244,15 +246,13 @@ namespace euf { expr_ref gapp(m.mk_app(g, fapp.get()), m); expr_ref eq = mk_eq(gapp, arg); sat::literal lit = mk_literal(eq); - s().add_clause(1, &lit, st); + s().add_clause(lit, st); eqs.push_back(mk_eq(fapp, a)); } pb_util pb(m); expr_ref at_least2(pb.mk_at_least_k(eqs.size(), eqs.data(), 2), m); sat::literal lit = si.internalize(at_least2, m_is_redundant); - s().mk_clause(1, &lit, st); - if (relevancy_enabled()) - add_root(1, &lit); + s().add_clause(lit, st); } } @@ -268,9 +268,7 @@ namespace euf { for (unsigned j = i + 1; j < sz; ++j) { expr_ref eq = mk_eq(args[i]->get_expr(), args[j]->get_expr()); sat::literal lit = ~mk_literal(eq); - s().add_clause(1, &lit, st); - if (relevancy_enabled()) - add_root(1, &lit); + s().add_clause(lit, st); } } } @@ -287,9 +285,7 @@ namespace euf { n->mark_interpreted(); expr_ref eq = mk_eq(fapp, fresh); sat::literal lit = mk_literal(eq); - s().add_clause(1, &lit, st); - if (relevancy_enabled()) - add_root(1, &lit); + s().add_clause(lit, st); } } } @@ -302,16 +298,16 @@ namespace euf { expr_ref eq_th = mk_eq(e, th); sat::literal lit_th = mk_literal(eq_th); if (th == el) { - s().add_clause(1, &lit_th, st); + s().add_clause(lit_th, st); } else { sat::literal lit_c = mk_literal(c); expr_ref eq_el = mk_eq(e, el); sat::literal lit_el = mk_literal(eq_el); - literal lits1[2] = { ~lit_c, lit_th }; - literal lits2[2] = { lit_c, lit_el }; - s().add_clause(2, lits1, st); - s().add_clause(2, lits2, st); + add_root(~lit_c, lit_th); + add_root(lit_c, lit_el); + s().add_clause(~lit_c, lit_th, st); + s().add_clause(lit_c, lit_el, st); } } else if (m.is_distinct(e)) { @@ -326,10 +322,10 @@ namespace euf { expr_ref fml(m.mk_or(eqs), m); sat::literal dist(si.to_bool_var(e), false); sat::literal some_eq = si.internalize(fml, m_is_redundant); - sat::literal lits1[2] = { ~dist, ~some_eq }; - sat::literal lits2[2] = { dist, some_eq }; - s().add_clause(2, lits1, st); - s().add_clause(2, lits2, st); + add_root(~dist, ~some_eq); + add_root(dist, some_eq); + s().add_clause(~dist, ~some_eq, st); + s().add_clause(dist, some_eq, st); } else if (m.is_eq(e, th, el) && !m.is_iff(e)) { sat::literal lit1 = expr2literal(e); @@ -338,10 +334,10 @@ namespace euf { enode* n2 = m_egraph.find(e2); if (n2) { sat::literal lit2 = expr2literal(e2); - sat::literal lits1[2] = { ~lit1, lit2 }; - sat::literal lits2[2] = { lit1, ~lit2 }; - s().add_clause(2, lits1, st); - s().add_clause(2, lits2, st); + add_root(~lit1, lit2); + add_root(lit1, ~lit2); + s().add_clause(~lit1, lit2, st); + s().add_clause(lit1, ~lit2, st); } } } @@ -357,7 +353,7 @@ namespace euf { // contains a parent application. family_id th_id = m.get_basic_family_id(); - for (auto p : euf::enode_th_vars(n)) { + for (auto const& p : euf::enode_th_vars(n)) { family_id id = p.get_id(); if (m.get_basic_family_id() != id) { if (th_id != m.get_basic_family_id()) @@ -402,7 +398,7 @@ namespace euf { // Remark: The inconsistency is not going to be detected if they are // not marked as shared. - for (auto p : euf::enode_th_vars(n)) + for (auto const& p : euf::enode_th_vars(n)) if (fid2solver(p.get_id())->is_shared(p.get_var())) return true; diff --git a/src/sat/smt/euf_model.cpp b/src/sat/smt/euf_model.cpp index ea5bfe274..fff7e5989 100644 --- a/src/sat/smt/euf_model.cpp +++ b/src/sat/smt/euf_model.cpp @@ -35,7 +35,7 @@ namespace euf { s(s), m(s.m), mdl(mdl), values(values), factory(m) {} ~user_sort() { - for (auto kv : sort2values) + for (auto const& kv : sort2values) mdl->register_usort(kv.m_key, kv.m_value->size(), kv.m_value->data()); } @@ -161,10 +161,9 @@ namespace euf { default: break; } - if (is_app(e) && to_app(e)->get_family_id() == m.get_basic_family_id()) - continue; sat::bool_var v = get_enode(e)->bool_var(); - SASSERT(v != sat::null_bool_var); + if (v == sat::null_bool_var) + continue; switch (s().value(v)) { case l_true: m_values.set(id, m.mk_true()); @@ -200,6 +199,8 @@ namespace euf { expr* e = n->get_expr(); if (!is_app(e)) continue; + if (!is_relevant(n)) + continue; app* a = to_app(e); func_decl* f = a->get_decl(); if (!include_func_interp(f)) @@ -224,7 +225,7 @@ namespace euf { enode* earg = get_enode(arg); expr* val = m_values.get(earg->get_root_id()); args.push_back(val); - CTRACE("euf", !val, tout << "no value for " << bpp(earg) << "\n";); + CTRACE("euf", !val, tout << "no value for " << bpp(earg) << "\n" << bpp(n) << "\n"; display(tout);); SASSERT(val); } SASSERT(args.size() == arity); @@ -259,7 +260,7 @@ namespace euf { if (n->is_root() && m_values.get(n->get_expr_id())) m_values2root.insert(m_values.get(n->get_expr_id()), n); TRACE("model", - for (auto kv : m_values2root) + for (auto const& kv : m_values2root) tout << mk_bounded_pp(kv.m_key, m) << "\n -> " << bpp(kv.m_value) << "\n";); return m_values2root; @@ -271,6 +272,7 @@ namespace euf { void solver::display_validation_failure(std::ostream& out, model& mdl, enode* n) { out << "Failed to validate " << n->bool_var() << " " << bpp(n) << " " << mdl(n->get_expr()) << "\n"; + s().display(out); euf::enode_vector nodes; nodes.push_back(n); for (unsigned i = 0; i < nodes.size(); ++i) { @@ -289,14 +291,20 @@ namespace euf { for (euf::enode* r : nodes) r->unmark1(); out << mdl << "\n"; - s().display(out); } void solver::validate_model(model& mdl) { + if (!m_unhandled_functions.empty()) + return; + for (auto* s : m_solvers) + if (s && s->has_unhandled()) + return; model_evaluator ev(mdl); ev.set_model_completion(true); TRACE("model", for (enode* n : m_egraph.nodes()) { + if (!is_relevant(n)) + continue; unsigned id = n->get_root_id(); expr* val = m_values.get(id, nullptr); if (!val) @@ -322,8 +330,8 @@ namespace euf { IF_VERBOSE(0, display_validation_failure(verbose_stream(), mdl, n);); CTRACE("euf", first, display_validation_failure(tout, mdl, n);); (void)first; - exit(1); first = false; + exit(1); } } diff --git a/src/sat/smt/euf_relevancy.cpp b/src/sat/smt/euf_relevancy.cpp index 40dff962a..73fe49506 100644 --- a/src/sat/smt/euf_relevancy.cpp +++ b/src/sat/smt/euf_relevancy.cpp @@ -3,157 +3,326 @@ Copyright (c) 2020 Microsoft Corporation Module Name: - euf_relevancy.cpp + smt_relevant.cpp Abstract: - Features for relevancy tracking. - A reduced (minimal) implicant is extracted by running a dual solver. - Then the literals in the implicant are used as a basis for marking - subterms relevant. + Relevancy propagation Author: - Nikolaj Bjorner (nbjorner) 2020-09-22 + Nikolaj Bjorner (nbjorner) 2021-12-27 --*/ - +#include "sat/sat_solver.h" #include "sat/smt/euf_solver.h" +#include "sat/smt/euf_relevancy.h" + namespace euf { - - void solver::add_auto_relevant(expr* e) { - if (!relevancy_enabled()) - return; - for (; m_auto_relevant_scopes > 0; --m_auto_relevant_scopes) - m_auto_relevant_lim.push_back(m_auto_relevant.size()); - // std::cout << "add-auto " << e->get_id() << " " << mk_bounded_pp(e, m) << "\n"; - m_auto_relevant.push_back(e); - } - void solver::pop_relevant(unsigned n) { - if (m_auto_relevant_scopes >= n) { - m_auto_relevant_scopes -= n; + void relevancy::pop(unsigned n) { + if (!m_enabled) + return; + if (n <= m_num_scopes) { + m_num_scopes -= n; return; } - n -= m_auto_relevant_scopes; - m_auto_relevant_scopes = 0; - unsigned top = m_auto_relevant_lim.size() - n; - unsigned lim = m_auto_relevant_lim[top]; - m_auto_relevant_lim.shrink(top); - m_auto_relevant.shrink(lim); + else if (m_num_scopes > 0) { + n -= m_num_scopes; + m_num_scopes = 0; + } + SASSERT(n > 0); + unsigned sz = m_lim[m_lim.size() - n]; + for (unsigned i = m_trail.size(); i-- > sz; ) { + auto const& [u, idx] = m_trail[i]; + switch (u) { + case update::relevant_var: + m_relevant_var_ids[idx] = false; + break; + case update::add_queue: + m_queue.pop_back(); + break; + case update::add_clause: { + sat::clause* c = m_clauses.back(); + for (sat::literal lit : *c) { + SASSERT(m_occurs[lit.index()].back() == m_clauses.size() - 1); + m_occurs[lit.index()].pop_back(); + } + m_clauses.pop_back(); + m_roots.pop_back(); + m_alloc.del_clause(c); + break; + } + case update::set_root: + m_roots[idx] = false; + break; + case update::set_qhead: + m_qhead = idx; + break; + default: + UNREACHABLE(); + break; + } + } + m_trail.shrink(sz); + m_lim.shrink(m_lim.size() - n); } - void solver::push_relevant() { - ++m_auto_relevant_scopes; - } - - bool solver::is_relevant(expr* e) const { - return m_relevant_expr_ids.get(e->get_id(), true); - } - - bool solver::is_relevant(enode* n) const { - return m_relevant_expr_ids.get(n->get_expr_id(), true); - } - - void solver::ensure_dual_solver() { - if (m_dual_solver) + void relevancy::add_root(unsigned n, sat::literal const* lits) { + if (!m_enabled) return; - m_dual_solver = alloc(sat::dual_solver, s().rlimit()); - for (unsigned i = s().num_user_scopes(); i-- > 0; ) - m_dual_solver->push(); + flush(); + TRACE("relevancy", tout << "root " << sat::literal_vector(n, lits) << "\n"); + sat::literal true_lit = sat::null_literal; + for (unsigned i = 0; i < n; ++i) { + if (ctx.s().value(lits[i]) == l_true) { + if (is_relevant(lits[i])) + return; + true_lit = lits[i]; + } + } + if (true_lit != sat::null_literal) { + mark_relevant(true_lit); + return; + } + + sat::clause* cl = m_alloc.mk_clause(n, lits, false); + unsigned sz = m_clauses.size(); + m_clauses.push_back(cl); + m_roots.push_back(true); + m_trail.push_back(std::make_pair(update::add_clause, 0)); + for (sat::literal lit : *cl) { + ctx.s().set_external(lit.var()); + occurs(lit).push_back(sz); + } + } + + void relevancy::add_def(unsigned n, sat::literal const* lits) { + if (!m_enabled) + return; + flush(); + TRACE("relevancy", tout << "def " << sat::literal_vector(n, lits) << "\n"); + for (unsigned i = 0; i < n; ++i) { + if (ctx.s().value(lits[i]) == l_false && is_relevant(lits[i])) { + add_root(n, lits); + return; + } + } + sat::clause* cl = m_alloc.mk_clause(n, lits, false); + unsigned sz = m_clauses.size(); + m_clauses.push_back(cl); + m_roots.push_back(false); + m_trail.push_back(std::make_pair(update::add_clause, 0)); + for (sat::literal lit : *cl) { + ctx.s().set_external(lit.var()); + occurs(lit).push_back(sz); + } + } + + void relevancy::add_to_propagation_queue(sat::literal lit) { + m_trail.push_back(std::make_pair(update::add_queue, lit.var())); + m_queue.push_back(std::make_pair(lit, nullptr)); + } + + void relevancy::set_relevant(sat::literal lit) { + euf::enode* n = ctx.bool_var2enode(lit.var()); + if (n) + mark_relevant(n); + m_relevant_var_ids.setx(lit.var(), true, false); + m_trail.push_back(std::make_pair(update::relevant_var, lit.var())); + } + + void relevancy::set_asserted(sat::literal lit) { + SASSERT(!is_relevant(lit)); + SASSERT(ctx.s().value(lit) == l_true); + set_relevant(lit); + add_to_propagation_queue(lit); + ctx.asserted(lit); } /** - * Add a root clause. Root clauses must all be satisfied by the - * final assignment. If a clause is added above search level it - * is subject to removal on backtracking. These clauses are therefore - * not tracked. - */ - void solver::add_root(unsigned n, sat::literal const* lits) { - if (!relevancy_enabled()) + * Boolean variable is set relevant because an E-node is relevant. + * + */ + void relevancy::relevant_eh(sat::bool_var v) { + if (is_relevant(v)) return; - ensure_dual_solver(); - m_dual_solver->add_root(n, lits); - } - - void solver::add_aux(unsigned n, sat::literal const* lits) { - if (!relevancy_enabled()) - return; - ensure_dual_solver(); - m_dual_solver->add_aux(n, lits); - } - - void solver::track_relevancy(sat::bool_var v) { - ensure_dual_solver(); - m_dual_solver->track_relevancy(v); - } - - bool solver::init_relevancy() { - m_relevant_expr_ids.reset(); - if (!relevancy_enabled()) - return true; - if (!m_dual_solver) - return true; - if (!(*m_dual_solver)(s())) - return false; - unsigned max_id = 0; - for (enode* n : m_egraph.nodes()) - max_id = std::max(max_id, n->get_expr_id()); - m_relevant_expr_ids.resize(max_id + 1, false); - ptr_vector& todo = m_relevant_todo; - bool_vector& visited = m_relevant_visited; - auto const& core = m_dual_solver->core(); - todo.reset(); - for (auto lit : core) { - expr* e = m_bool_var2expr.get(lit.var(), nullptr); - if (e) - todo.push_back(e); + sat::literal lit(v); + switch (ctx.s().value(lit)) { + case l_undef: + set_relevant(lit); + break; + case l_true: + set_asserted(lit); + break; + case l_false: + set_asserted(~lit); + break; } -#if 0 - std::cout << "init-relevant\n"; - for (expr* e : m_auto_relevant) - std::cout << "auto-relevant " << e->get_id() << " " << mk_bounded_pp(e, m) << "\n"; -#endif - todo.append(m_auto_relevant); - for (unsigned i = 0; i < todo.size(); ++i) { - expr* e = todo[i]; - if (visited.get(e->get_id(), false)) + } + + void relevancy::asserted(sat::literal lit) { + TRACE("relevancy", tout << "asserted " << lit << " relevant " << is_relevant(lit) << "\n"); + if (!m_enabled) + return; + flush(); + if (is_relevant(lit)) { + add_to_propagation_queue(lit); + return; + } + if (ctx.s().lvl(lit) <= ctx.s().search_lvl()) { + set_relevant(lit); + add_to_propagation_queue(lit); + return; + } + + for (auto idx : occurs(lit)) { + if (!m_roots[idx]) continue; - visited.setx(e->get_id(), true, false); - if (!si.is_bool_op(e)) - m_relevant_expr_ids.setx(e->get_id(), true, false); - if (!is_app(e)) + for (sat::literal lit2 : *m_clauses[idx]) + if (lit2 != lit && ctx.s().value(lit2) == l_true && is_relevant(lit2)) + goto next; + set_relevant(lit); + add_to_propagation_queue(lit); + return; + next: + ; + } + } + + void relevancy::propagate() { + if (!m_enabled) + return; + flush(); + if (m_qhead == m_queue.size()) + return; + m_trail.push_back(std::make_pair(update::set_qhead, m_qhead)); + while (m_qhead < m_queue.size() && !ctx.s().inconsistent() && ctx.get_manager().inc()) { + auto const& [lit, n] = m_queue[m_qhead++]; + SASSERT(n || lit != sat::null_literal); + SASSERT(!n || lit == sat::null_literal); + if (n) + propagate_relevant(n); + else + propagate_relevant(lit); + } + } + + void relevancy::merge(euf::enode* root, euf::enode* other) { + TRACE("relevancy", tout << "merge #" << ctx.bpp(root) << " " << is_relevant(root) << " #" << ctx.bpp(other) << " " << is_relevant(other) << "\n"); + if (is_relevant(root)) + mark_relevant(other); + else if (is_relevant(other)) + mark_relevant(root); + } + + void relevancy::mark_relevant(euf::enode* n) { + if (!m_enabled) + return; + flush(); + if (is_relevant(n)) + return; + TRACE("relevancy", tout << "mark #" << ctx.bpp(n) << "\n"); + m_trail.push_back(std::make_pair(update::add_queue, 0)); + m_queue.push_back(std::make_pair(sat::null_literal, n)); + } + + void relevancy::mark_relevant(sat::literal lit) { + TRACE("relevancy", tout << "mark " << lit << " " << is_relevant(lit) << " " << ctx.s().value(lit) << " lim: " << m_lim.size() << "\n"); + if (!m_enabled) + return; + flush(); + if (is_relevant(lit)) + return; + set_relevant(lit); + switch (ctx.s().value(lit)) { + case l_true: + break; + case l_false: + lit.neg(); + break; + default: + return; + } + add_to_propagation_queue(lit); + } + + void relevancy::propagate_relevant(sat::literal lit) { + SASSERT(m_num_scopes == 0); + TRACE("relevancy", tout << "propagate " << lit << " lim: " << m_lim.size() << "\n"); + SASSERT(ctx.s().value(lit) == l_true); + SASSERT(is_relevant(lit)); + euf::enode* n = ctx.bool_var2enode(lit.var()); + if (n && !ctx.get_si().is_bool_op(n->get_expr())) + return; + for (auto idx : occurs(~lit)) { + if (m_roots[idx]) continue; - expr* c = nullptr, *th = nullptr, *el = nullptr; - if (m.is_ite(e, c, th, el) && get_enode(c)) { - sat::literal lit = expr2literal(c); - todo.push_back(c); - switch (s().value(lit)) { - case l_true: - todo.push_back(th); - break; - case l_false: - todo.push_back(el); - break; - default: - todo.push_back(th); - todo.push_back(el); - break; + sat::clause* cl = m_clauses[idx]; + sat::literal true_lit = sat::null_literal; + for (sat::literal lit2 : *cl) { + if (ctx.s().value(lit2) == l_true) { + if (is_relevant(lit2)) + goto next; + true_lit = lit2; } - continue; } - for (expr* arg : *to_app(e)) - todo.push_back(arg); + + if (true_lit != sat::null_literal) + set_asserted(true_lit); + else { + m_trail.push_back(std::make_pair(update::set_root, idx)); + m_roots[idx] = true; + } + next: + TRACE("relevancy", tout << "propagate " << lit << " " << true_lit << " " << m_roots[idx] << "\n"); + ; } - - for (auto * e : todo) - visited[e->get_id()] = false; - - TRACE("euf", - for (enode* n : m_egraph.nodes()) - if (is_relevant(n)) - tout << "relevant " << n->get_expr_id() << " [r" << n->get_root_id() << "]: " << mk_bounded_pp(n->get_expr(), m) << "\n";); - return true; } + + void relevancy::propagate_relevant(euf::enode* n) { + m_todo.push_back(n); + while (!m_todo.empty()) { + n = m_todo.back(); + m_todo.pop_back(); + TRACE("relevancy", tout << "propagate #" << ctx.bpp(n) << " lim: " << m_lim.size() << "\n"); + if (n->is_relevant()) + continue; + m_stack.push_back(n); + while (!m_stack.empty()) { + n = m_stack.back(); + unsigned sz = m_stack.size(); + bool is_bool_op = ctx.get_si().is_bool_op(n->get_expr()); + if (!is_bool_op) + for (euf::enode* arg : euf::enode_args(n)) + if (!arg->is_relevant()) + m_stack.push_back(arg); + if (sz != m_stack.size()) + continue; + if (!n->is_relevant()) { + ctx.get_egraph().set_relevant(n); + ctx.relevant_eh(n); + sat::bool_var v = n->bool_var(); + if (v != sat::null_bool_var) + relevant_eh(v); + for (euf::enode* sib : euf::enode_class(n)) + if (!sib->is_relevant()) + m_todo.push_back(sib); + } + if (!ctx.get_manager().inc()) { + m_todo.reset(); + m_stack.reset(); + return; + } + m_stack.pop_back(); + } + } + } + + void relevancy::set_enabled(bool e) { + m_enabled = e; + ctx.get_egraph().set_default_relevant(!e); + } + } diff --git a/src/sat/smt/euf_relevancy.h b/src/sat/smt/euf_relevancy.h new file mode 100644 index 000000000..382b054fc --- /dev/null +++ b/src/sat/smt/euf_relevancy.h @@ -0,0 +1,173 @@ +/*++ +Copyright (c) 2020 Microsoft Corporation + +Module Name: + + smt_relevant.h + +Abstract: + + Relevancy propagation + +Author: + + Nikolaj Bjorner (nbjorner) 2021-12-27 + + +Clauses are split into two parts: + +- Roots +- Defs + +The goal is to establish a labeling of literals as "relevant" such that +- the set of relevant literals satisfies Roots +- there is a set of blocked literals that can be used to satisfy the clauses in Defs + independent of their real assignment. + +The idea is that the Defs clauses are obtained from Tseitin transformation so they can be +grouped by the blocked literal that was used to introduce them. +For example, when clausifying (and a b) we have the clauses +(=> (and a b) a) +(=> (and a b) b) +(or (not a) (not b) (and a b)) +then the literal for "(and a b)" is blocked. +And recursively for clauses introduced for a, b if they use a Boolean connectives +at top level. + +The state transitions are: + +- A literal lit is assigned: + lit appears positively in a Root clause R and no other literal in R are relevant. + -> + lit is set relevant + + lit is justified at search level + -> + lit is set relevant + +- An equality n1 = n2 is assigned: + n1 is relevant + -> + n2 is marked as relevant + +- A lit is set relevant: + -> + all clauses D in Defs where lit appears negatively are added to Roots + +- When a clause R is added to Roots: + R contains a positive literal lit that is relevant + -> + skip adding R to Roots + +- When a clause R is added to Roots: + R contains a positive literal lit, no positive literal in R are relevant + -> + lit is set relevant + +- When a clause D is added to Defs: + D contains a negative literal that is relevant + -> + Add D to Roots + +- When an expression is set relevant: + All non-relevant children above Boolean connectives are set relevant + If nodes are treated as Boolean connectives because they are clausified + to (=> cond (= n then)) and (=> (not cond) (= n else)) + +Replay: + - literals that are replayed in clauses that are marked relevant are + marked relevant again. + + - expressions corresponding to auxiliary clauses are added as auxiliary clauses. + + - TBD: Are root clauses added under a scope discarded? + The SAT solver re-initializes clauses on its own, should we just use this mechanism? + +Can a literal that is not in a root be set relevant? + - yes, if we propagate over expressions + +Do we need full watch lists instead of 2-watch lists? + - probably, but unclear. The dual SAT solver only uses 2-watch lists, but uses a large clause for tracking + roots. + + + State machine for literals: relevant(lit), assigned(lit) + +relevant(lit) transitions false -> true + if assigned(lit): add to propagation queue + if not assigned(lit): no-op (or mark enodes as relevant) + +assigned(lit) transitions false -> true + if relevant(lit): add to propagation queue + if not relevant(lit): set relevant if member of root, add to propagation queue + + +--*/ +#pragma once +#include "sat/sat_solver.h" +#include "sat/smt/sat_th.h" + + +namespace euf { + + class solver; + + class relevancy { + euf::solver& ctx; + + enum class update { relevant_var, add_queue, add_clause, set_root, set_qhead }; + + bool m_enabled = false; + svector> m_trail; + unsigned_vector m_lim; + unsigned m_num_scopes = 0; + bool_vector m_relevant_var_ids; // identifiers of relevant Boolean variables + sat::clause_allocator m_alloc; + sat::clause_vector m_clauses; // clauses + bool_vector m_roots; // indicate if clause is a root + vector m_occurs; // where do literals occur + unsigned m_qhead = 0; // queue head for relevancy + svector> m_queue; // propagation queue for relevancy + euf::enode_vector m_stack, m_todo; + + void push_core() { m_lim.push_back(m_trail.size()); } + void flush() { for (; m_num_scopes > 0; --m_num_scopes) push_core(); } + + unsigned_vector& occurs(sat::literal lit) { m_occurs.reserve(lit.index() + 1); return m_occurs[lit.index()]; } + + void propagate_relevant(sat::literal lit); + + void add_to_propagation_queue(sat::literal lit); + + void set_relevant(sat::literal lit); + + void set_asserted(sat::literal lit); + + void relevant_eh(sat::bool_var v); + + void propagate_relevant(euf::enode* n); + + public: + relevancy(euf::solver& ctx): ctx(ctx) {} + + void push() { if (m_enabled) ++m_num_scopes; } + void pop(unsigned n); + + void add_root(unsigned n, sat::literal const* lits); + void add_def(unsigned n, sat::literal const* lits); + void asserted(sat::literal lit); + void propagate(); + bool can_propagate() const { return m_qhead < m_queue.size(); } + + void mark_relevant(euf::enode* n); + void mark_relevant(sat::literal lit); + void merge(euf::enode* n1, euf::enode* n2); + + bool is_relevant(sat::bool_var v) const { return !m_enabled || m_relevant_var_ids.get(v, false); } + bool is_relevant(sat::literal lit) const { return is_relevant(lit.var()); } + bool is_relevant(euf::enode* n) const { return !m_enabled || n->is_relevant(); } + + bool enabled() const { return m_enabled; } + void set_enabled(bool e); + }; +} diff --git a/src/sat/smt/euf_solver.cpp b/src/sat/smt/euf_solver.cpp index bbdd6ce0e..3c7342136 100644 --- a/src/sat/smt/euf_solver.cpp +++ b/src/sat/smt/euf_solver.cpp @@ -41,6 +41,7 @@ namespace euf { extension(symbol("euf"), m.mk_family_id("euf")), m(m), si(si), + m_relevancy(*this), m_egraph(m), m_trail(), m_rewriter(m), @@ -51,12 +52,21 @@ namespace euf { m_values(m) { updt_params(p); + m_relevancy.set_enabled(get_config().m_relevancy_lvl > 2); std::function disp = [&](std::ostream& out, void* j) { display_justification_ptr(out, reinterpret_cast(j)); }; m_egraph.set_display_justification(disp); + + if (m_relevancy.enabled()) { + std::function on_merge = + [&](enode* root, enode* other) { + m_relevancy.merge(root, other); + }; + m_egraph.set_on_merge(on_merge); + } } void solver::updt_params(params_ref const& p) { @@ -183,7 +193,7 @@ namespace euf { } void solver::propagate(literal lit, ext_justification_idx idx) { - add_auto_relevant(bool_var2expr(lit.var())); + mark_relevant(lit); s().assign(lit, sat::justification::mk_ext_justification(s().scope_lvl(), idx)); } @@ -233,11 +243,11 @@ namespace euf { m_egraph.explain_eq(m_explain, a, b); } - void solver::add_diseq_antecedent(enode* a, enode* b) { - sat::bool_var v = get_egraph().explain_diseq(m_explain, a, b); + void solver::add_diseq_antecedent(ptr_vector& ex, enode* a, enode* b) { + sat::bool_var v = get_egraph().explain_diseq(ex, a, b); SASSERT(v == sat::null_bool_var || s().value(v) == l_false); if (v != sat::null_bool_var) - m_explain.push_back(to_ptr(sat::literal(v, true))); + ex.push_back(to_ptr(sat::literal(v, true))); } bool solver::propagate(enode* a, enode* b, ext_justification_idx idx) { @@ -286,6 +296,11 @@ namespace euf { } void solver::asserted(literal l) { + + m_relevancy.asserted(l); + if (!m_relevancy.is_relevant(l)) + return; + expr* e = m_bool_var2expr.get(l.var(), nullptr); TRACE("euf", tout << "asserted: " << l << "@" << s().scope_lvl() << " := " << mk_bounded_pp(e, m) << "\n";); if (!e) @@ -329,6 +344,8 @@ namespace euf { bool solver::unit_propagate() { bool propagated = false; while (!s().inconsistent()) { + if (m_relevancy.enabled()) + m_relevancy.propagate(); if (m_egraph.inconsistent()) { unsigned lvl = s().scope_lvl(); s().set_conflict(sat::justification::mk_ext_justification(lvl, conflict_constraint().to_index())); @@ -345,9 +362,13 @@ namespace euf { if (m_solvers[i]->unit_propagate()) propagated1 = true; - if (!propagated1) - break; - propagated = true; + if (propagated1) { + propagated = true; + continue; + } + if (m_relevancy.enabled() && m_relevancy.can_propagate()) + continue; + break; } DEBUG_CODE(if (!propagated && !s().inconsistent()) check_missing_eq_propagation();); return propagated; @@ -422,12 +443,10 @@ namespace euf { void solver::propagate_th_eqs() { for (; m_egraph.has_th_eq() && !s().inconsistent() && !m_egraph.inconsistent(); m_egraph.next_th_eq()) { th_eq eq = m_egraph.get_th_eq(); - if (eq.is_eq()) { - if (!is_self_propagated(eq)) - m_id2solver[eq.id()]->new_eq_eh(eq); - } - else + if (!eq.is_eq()) m_id2solver[eq.id()]->new_diseq_eh(eq); + else if (!is_self_propagated(eq)) + m_id2solver[eq.id()]->new_eq_eh(eq); } } @@ -458,9 +477,6 @@ namespace euf { if (unit_propagate()) return sat::check_result::CR_CONTINUE; - - if (!init_relevancy()) - give_up = true; unsigned num_nodes = m_egraph.num_nodes(); auto apply_solver = [&](th_solver* e) { @@ -525,9 +541,7 @@ namespace euf { for (auto* e : m_solvers) e->push(); m_egraph.push(); - if (m_dual_solver) - m_dual_solver->push(); - push_relevant(); + m_relevancy.push(); } void solver::pop(unsigned n) { @@ -537,7 +551,7 @@ namespace euf { e->pop(n); si.pop(n); m_egraph.pop(n); - pop_relevant(n); + m_relevancy.pop(n); scope const & sc = m_scopes[m_scopes.size() - n]; for (unsigned i = m_var_trail.size(); i-- > sc.m_var_lim; ) { bool_var v = m_var_trail[i]; @@ -546,8 +560,6 @@ namespace euf { } m_var_trail.shrink(sc.m_var_lim); m_scopes.shrink(m_scopes.size() - n); - if (m_dual_solver) - m_dual_solver->pop(n); SASSERT(m_egraph.num_scopes() == m_scopes.size()); TRACE("euf_verbose", display(tout << "pop to: " << m_scopes.size() << "\n");); } @@ -720,6 +732,58 @@ namespace euf { } } + bool solver::is_relevant(bool_var v) const { + if (m_relevancy.enabled()) + return m_relevancy.is_relevant(v); + auto* e = bool_var2enode(v); + return !e || is_relevant(e); + } + + void solver::relevant_eh(euf::enode* n) { + if (m_qsolver) + m_qsolver->relevant_eh(n); + for (auto const& thv : enode_th_vars(n)) { + auto* th = m_id2solver.get(thv.get_id(), nullptr); + if (th && th != m_qsolver) + th->relevant_eh(n); + } + } + + bool solver::enable_ackerman_axioms(expr* e) const { + euf::enode* n = get_enode(e); + if (!n) + return false; + for (auto const& thv : enode_th_vars(n)) { + auto* th = m_id2solver.get(thv.get_id(), nullptr); + if (th && !th->enable_ackerman_axioms(n)) + return false; + } + return true; + } + + bool solver::is_fixed(euf::enode* n, expr_ref& val, sat::literal_vector& explain) { + if (n->bool_var() != sat::null_bool_var) { + switch (s().value(n->bool_var())) { + case l_true: + val = m.mk_true(); + explain.push_back(sat::literal(n->bool_var())); + return true; + case l_false: + val = m.mk_false(); + explain.push_back(~sat::literal(n->bool_var())); + return true; + default: + return false; + } + } + for (auto const& thv : enode_th_vars(n)) { + auto* th = m_id2solver.get(thv.get_id(), nullptr); + if (th && !th->is_fixed(thv.get_var(), val, explain)) + return true; + } + return false; + } + void solver::pre_simplify() { for (auto* e : m_solvers) e->pre_simplify(); @@ -765,13 +829,18 @@ namespace euf { } bool solver::set_root(literal l, literal r) { - expr* e = bool_var2expr(l.var()); - if (!e) - return true; + if (m_relevancy.enabled()) + return false; bool ok = true; for (auto* s : m_solvers) if (!s->set_root(l, r)) ok = false; + + if (!ok) + return false; + expr* e = bool_var2expr(l.var()); + if (!e) + return true; if (m.is_eq(e) && !m.is_iff(e)) ok = false; euf::enode* n = get_enode(e); @@ -794,7 +863,7 @@ namespace euf { out << "bool-vars\n"; for (unsigned v : m_var_trail) { expr* e = m_bool_var2expr[v]; - out << v << ": " << e->get_id() << " " << m_solver->value(v) << " " << mk_bounded_pp(e, m, 1) << "\n"; + out << v << (is_relevant(v)?"":"n") << ": " << e->get_id() << " " << m_solver->value(v) << " " << mk_bounded_pp(e, m, 1) << "\n"; } for (auto* e : m_solvers) e->display(out); diff --git a/src/sat/smt/euf_solver.h b/src/sat/smt/euf_solver.h index d70206d11..d299c92d0 100644 --- a/src/sat/smt/euf_solver.h +++ b/src/sat/smt/euf_solver.h @@ -25,11 +25,12 @@ Author: #include "sat/sat_extension.h" #include "sat/smt/atom2bool_var.h" #include "sat/smt/sat_th.h" -#include "sat/smt/sat_dual_solver.h" #include "sat/smt/euf_ackerman.h" #include "sat/smt/user_solver.h" +#include "sat/smt/euf_relevancy.h" #include "smt/params/smt_params.h" + namespace euf { typedef sat::literal literal; typedef sat::ext_constraint_idx ext_constraint_idx; @@ -91,6 +92,7 @@ namespace euf { std::function<::solver*(void)> m_mk_solver; ast_manager& m; sat::sat_internalizer& si; + relevancy m_relevancy; smt_params m_config; euf::egraph m_egraph; trail_stack m_trail; @@ -181,17 +183,17 @@ namespace euf { void init_drat(); // relevancy - bool_vector m_relevant_expr_ids; - bool_vector m_relevant_visited; - ptr_vector m_relevant_todo; - void ensure_dual_solver(); - bool init_relevancy(); - - + //bool_vector m_relevant_expr_ids; + //bool_vector m_relevant_visited; + //ptr_vector m_relevant_todo; + //void init_relevant_expr_ids(); + //void push_relevant(sat::bool_var v); + bool is_propagated(sat::literal lit); // invariant void check_eqc_bool_assignment() const; void check_missing_bool_enode_propagation() const; void check_missing_eq_propagation() const; + // diagnosis std::ostream& display_justification_ptr(std::ostream& out, size_t* j) const; @@ -250,6 +252,10 @@ namespace euf { sat::sat_internalizer& get_si() { return si; } ast_manager& get_manager() { return m; } enode* get_enode(expr* e) const { return m_egraph.find(e); } + enode* bool_var2enode(sat::bool_var b) const { + expr* e = m_bool_var2expr.get(b, nullptr); + return e ? get_enode(e) : nullptr; + } sat::literal expr2literal(expr* e) const { return enode2literal(get_enode(e)); } sat::literal enode2literal(enode* n) const { return sat::literal(n->bool_var(), false); } lbool value(enode* n) const { return s().value(enode2literal(n)); } @@ -299,7 +305,9 @@ namespace euf { void get_antecedents(literal l, ext_justification_idx idx, literal_vector& r, bool probing) override; void get_antecedents(literal l, th_explain& jst, literal_vector& r, bool probing); void add_antecedent(enode* a, enode* b); - void add_diseq_antecedent(enode* a, enode* b); + void add_diseq_antecedent(ptr_vector& ex, enode* a, enode* b); + void add_explain(size_t* p) { m_explain.push_back(p); } + void reset_explain() { m_explain.reset(); } void set_eliminated(bool_var v) override; void asserted(literal l) override; sat::check_result check() override; @@ -362,32 +370,29 @@ namespace euf { th_rewriter& get_rewriter() { return m_rewriter; } void rewrite(expr_ref& e) { m_rewriter(e); } bool is_shared(euf::enode* n) const; + bool enable_ackerman_axioms(expr* n) const; + bool is_fixed(euf::enode* n, expr_ref& val, sat::literal_vector& explain); // relevancy - bool m_relevancy = true; - scoped_ptr m_dual_solver; - ptr_vector m_auto_relevant; - unsigned_vector m_auto_relevant_lim; - unsigned m_auto_relevant_scopes = 0; - bool relevancy_enabled() const { return m_relevancy && get_config().m_relevancy_lvl > 0; } - void disable_relevancy(expr* e) { IF_VERBOSE(0, verbose_stream() << "disabling relevancy " << mk_pp(e, m) << "\n"); m_relevancy = false; } - void add_root(unsigned n, sat::literal const* lits); + bool relevancy_enabled() const { return m_relevancy.enabled(); } + void disable_relevancy(expr* e) { IF_VERBOSE(0, verbose_stream() << "disabling relevancy " << mk_pp(e, m) << "\n"); m_relevancy.set_enabled(false); } + void add_root(unsigned n, sat::literal const* lits) { m_relevancy.add_root(n, lits); } void add_root(sat::literal_vector const& lits) { add_root(lits.size(), lits.data()); } void add_root(sat::literal lit) { add_root(1, &lit); } - void add_root(sat::literal a, sat::literal b) { sat::literal lits[2] = {a, b}; add_root(2, lits); } + void add_root(sat::literal lit1, sat::literal lit2) { sat::literal lits[2] = { lit1, lit2, }; add_root(2, lits); } void add_aux(sat::literal_vector const& lits) { add_aux(lits.size(), lits.data()); } - void add_aux(unsigned n, sat::literal const* lits); + void add_aux(unsigned n, sat::literal const* lits) { m_relevancy.add_def(n, lits); } void add_aux(sat::literal a) { sat::literal lits[1] = { a }; add_aux(1, lits); } void add_aux(sat::literal a, sat::literal b) { sat::literal lits[2] = {a, b}; add_aux(2, lits); } void add_aux(sat::literal a, sat::literal b, sat::literal c) { sat::literal lits[3] = { a, b, c }; add_aux(3, lits); } - void track_relevancy(sat::bool_var v); - bool is_relevant(expr* e) const; - bool is_relevant(enode* n) const; - void add_auto_relevant(expr* e); - void pop_relevant(unsigned n); - void push_relevant(); + void mark_relevant(sat::literal lit) { m_relevancy.mark_relevant(lit); } + bool is_relevant(enode* n) const { return m_relevancy.is_relevant(n); } + bool is_relevant(bool_var v) const; + bool is_relevant(sat::literal lit) const { return is_relevant(lit.var()); } + void relevant_eh(euf::enode* n); + relevancy& get_relevancy() { return m_relevancy; } // model construction void update_model(model_ref& mdl); @@ -425,7 +430,11 @@ namespace euf { check_for_user_propagator(); m_user_propagator->register_diseq(diseq_eh); } - unsigned user_propagate_register(expr* e) { + void user_propagate_register_created(user_propagator::created_eh_t& ceh) { + check_for_user_propagator(); + m_user_propagator->register_created(ceh); + } + unsigned user_propagate_register_expr(expr* e) { check_for_user_propagator(); return m_user_propagator->add_expr(e); } diff --git a/src/sat/smt/fpa_solver.cpp b/src/sat/smt/fpa_solver.cpp index f655a0241..55e41fea4 100644 --- a/src/sat/smt/fpa_solver.cpp +++ b/src/sat/smt/fpa_solver.cpp @@ -269,7 +269,7 @@ namespace fpa { expr* xe = e_x->get_expr(); expr* ye = e_y->get_expr(); - if (m_fpa_util.is_bvwrap(xe) || m_fpa_util.is_bvwrap(ye)) + if (fu.is_bvwrap(xe) || fu.is_bvwrap(ye)) return; expr_ref xc = convert(xe); diff --git a/src/sat/smt/pb_internalize.cpp b/src/sat/smt/pb_internalize.cpp index f30d25b31..eae1bbe5a 100644 --- a/src/sat/smt/pb_internalize.cpp +++ b/src/sat/smt/pb_internalize.cpp @@ -18,6 +18,7 @@ Author: #include "sat/smt/pb_solver.h" #include "ast/pb_decl_plugin.h" +#include "sat/smt/euf_solver.h" namespace pb { @@ -27,8 +28,12 @@ namespace pb { literal solver::internalize(expr* e, bool sign, bool root, bool redundant) { flet _redundant(m_is_redundant, redundant); - if (m_pb.is_pb(e)) - return internalize_pb(e, sign, root); + if (m_pb.is_pb(e)) { + sat::literal lit = internalize_pb(e, sign, root); + if (m_ctx && !root && lit != sat::null_literal) + m_ctx->attach_lit(lit, e); + return lit; + } UNREACHABLE(); return sat::null_literal; } diff --git a/src/sat/smt/pb_solver.cpp b/src/sat/smt/pb_solver.cpp index 33fd1c48d..5c67a05c5 100644 --- a/src/sat/smt/pb_solver.cpp +++ b/src/sat/smt/pb_solver.cpp @@ -1338,7 +1338,9 @@ namespace pb { } solver::solver(euf::solver& ctx, euf::theory_id id) : - solver(ctx.get_manager(), ctx.get_si(), id) {} + solver(ctx.get_manager(), ctx.get_si(), id) { + m_ctx = &ctx; + } solver::solver(ast_manager& m, sat::sat_internalizer& si, euf::theory_id id) : euf::th_solver(m, symbol("ba"), id), @@ -1370,6 +1372,21 @@ namespace pb { s().mk_clause(_lits.size(), _lits.data(), sat::status::th(learned, get_id())); return nullptr; } + + if (k == 0) { + if (lit != sat::null_literal) + s().add_clause(lit, sat::status::th(false, get_id())); + return nullptr; + } + + if (k > lits.size()) { + if (lit == sat::null_literal) + s().add_clause(0, nullptr, sat::status::th(false, get_id())); + else + s().add_clause(~lit, sat::status::th(false, get_id())); + return nullptr; + } + if (!learned && clausify(lit, lits.size(), lits.data(), k)) { return nullptr; } @@ -1403,8 +1420,10 @@ namespace pb { if (m_solver) m_solver->set_external(lit.var()); c->watch_literal(*this, lit); c->watch_literal(*this, ~lit); - } - SASSERT(c->well_formed()); + } + if (!c->well_formed()) + std::cout << *c << "\n"; + VERIFY(c->well_formed()); if (m_solver && m_solver->get_config().m_drat) { std::function fn = [&](std::ostream& out) { out << "c ba constraint " << *c << " 0\n"; @@ -1429,12 +1448,27 @@ namespace pb { constraint* solver::add_pb_ge(literal lit, svector const& wlits, unsigned k, bool learned) { bool units = true; - for (wliteral wl : wlits) units &= wl.first == 1; - if (k == 0 && lit == sat::null_literal) { + for (wliteral wl : wlits) + units &= wl.first == 1; + + if (k == 0) { + if (lit != sat::null_literal) + s().add_clause(lit, sat::status::th(false, get_id())); + return nullptr; + } + rational weight(0); + for (auto const [w, l] : wlits) + weight += w; + if (weight < k) { + if (lit == sat::null_literal) + s().add_clause(0, nullptr, sat::status::th(false, get_id())); + else + s().add_clause(~lit, sat::status::th(false, get_id())); return nullptr; } if (!learned) { - for (wliteral wl : wlits) s().set_external(wl.second.var()); + for (wliteral wl : wlits) + s().set_external(wl.second.var()); } if (units || k == 1) { literal_vector lits; @@ -2006,7 +2040,7 @@ namespace pb { s().pop_to_base_level(); if (s().inconsistent()) return; - unsigned trail_sz, count = 0; + unsigned trail_sz = 0, count = 0; do { trail_sz = s().init_trail_size(); m_simplify_change = false; @@ -2159,12 +2193,13 @@ namespace pb { } bool solver::set_root(literal l, literal r) { - if (s().is_assumption(l.var())) { + if (s().is_assumption(l.var())) return false; - } reserve_roots(); m_roots[l.index()] = r; m_roots[(~l).index()] = ~r; + m_roots[r.index()] = r; + m_roots[(~r).index()] = ~r; m_root_vars[l.var()] = true; return true; } @@ -2180,7 +2215,6 @@ namespace pb { flush_roots(*m_learned[i]); cleanup_constraints(); // validate(); - // validate_eliminated(); } @@ -2191,7 +2225,8 @@ namespace pb { void solver::validate_eliminated(ptr_vector const& cs) { for (constraint const* c : cs) { - if (c->learned()) continue; + if (c->learned()) + continue; for (auto l : constraint::literal_iterator(*c)) VERIFY(!s().was_eliminated(l.var())); } @@ -2415,9 +2450,10 @@ namespace pb { for (unsigned i = 0; !found && i < c.size(); ++i) { found = m_root_vars[c.get_lit(i).var()]; } - if (!found) return; + if (!found) + return; clear_watch(c); - + // this could create duplicate literals for (unsigned i = 0; i < c.size(); ++i) { literal lit = m_roots[c.get_lit(i).index()]; diff --git a/src/sat/smt/pb_solver.h b/src/sat/smt/pb_solver.h index c19facf34..09c0e47e0 100644 --- a/src/sat/smt/pb_solver.h +++ b/src/sat/smt/pb_solver.h @@ -80,7 +80,8 @@ namespace pb { sat::sat_internalizer& si; pb_util m_pb; - sat::lookahead* m_lookahead{ nullptr }; + sat::lookahead* m_lookahead = nullptr; + euf::solver* m_ctx = nullptr; stats m_stats; small_object_allocator m_allocator; diff --git a/src/sat/smt/q_clause.h b/src/sat/smt/q_clause.h index 0c89b2ade..e1e31b199 100644 --- a/src/sat/smt/q_clause.h +++ b/src/sat/smt/q_clause.h @@ -122,18 +122,17 @@ namespace q { return out << "]"; } - struct justification { - expr* m_lhs, *m_rhs; + expr* m_lhs, * m_rhs; bool m_sign; - unsigned m_num_ev; - euf::enode_pair* m_evidence; - clause& m_clause; + unsigned m_num_ex; + size_t** m_explain; + clause& m_clause; euf::enode* const* m_binding; - justification(lit const& l, clause& c, euf::enode* const* b, unsigned n, euf::enode_pair* ev): - m_lhs(l.lhs), m_rhs(l.rhs), m_sign(l.sign), m_num_ev(n), m_evidence(ev), m_clause(c), m_binding(b) {} - sat::ext_constraint_idx to_index() const { - return sat::constraint_base::mem2base(this); + justification(lit const& l, clause& c, euf::enode* const* b, unsigned n, size_t** ev) : + m_lhs(l.lhs), m_rhs(l.rhs), m_sign(l.sign), m_num_ex(n), m_explain(ev), m_clause(c), m_binding(b) {} + sat::ext_constraint_idx to_index() const { + return sat::constraint_base::mem2base(this); } static justification& from_index(size_t idx) { return *reinterpret_cast(sat::constraint_base::from_index(idx)->mem()); diff --git a/src/sat/smt/q_ematch.cpp b/src/sat/smt/q_ematch.cpp index e4e01b964..5f2d4a50e 100644 --- a/src/sat/smt/q_ematch.cpp +++ b/src/sat/smt/q_ematch.cpp @@ -31,6 +31,7 @@ Done: #include "ast/ast_util.h" #include "ast/rewriter/var_subst.h" #include "ast/rewriter/rewriter_def.h" +#include "ast/normal_forms/pull_quant.h" #include "solver/solver.h" #include "sat/smt/sat_th.h" #include "sat/smt/euf_solver.h" @@ -69,10 +70,16 @@ namespace q { m_mam->add_node(n, false); }; ctx.get_egraph().set_on_merge(_on_merge); - ctx.get_egraph().set_on_make(_on_make); + if (!ctx.relevancy_enabled()) + ctx.get_egraph().set_on_make(_on_make); m_mam = mam::mk(ctx, *this); } + void ematch::relevant_eh(euf::enode* n) { + if (ctx.relevancy_enabled()) + m_mam->add_node(n, false); + } + void ematch::ensure_ground_enodes(expr* e) { mam::ground_subterms(e, m_ground); for (expr* g : m_ground) @@ -90,32 +97,58 @@ namespace q { } } + /** + * Create a justification for binding b. + * The justification involves equalities in the E-graph that have + * explanations. Retrieve the explanations while the justification + * is created to ensure the justification trail is well-founded + * during conflict resolution. + */ sat::ext_justification_idx ematch::mk_justification(unsigned idx, clause& c, euf::enode* const* b) { void* mem = ctx.get_region().allocate(justification::get_obj_size()); sat::constraint_base::initialize(mem, &m_qs); bool sign = false; - expr* l = nullptr, *r = nullptr; - lit lit(expr_ref(l, m), expr_ref(r, m), sign); + expr* l = nullptr, * r = nullptr; + lit lit(expr_ref(l, m), expr_ref(r, m), sign); if (idx != UINT_MAX) lit = c[idx]; - auto* ev = static_cast(ctx.get_region().allocate(sizeof(euf::enode_pair) * m_evidence.size())); - for (unsigned i = m_evidence.size(); i-- > 0; ) - ev[i] = m_evidence[i]; - auto* constraint = new (sat::constraint_base::ptr2mem(mem)) justification(lit, c, b, m_evidence.size(), ev); + m_explain.reset(); + ctx.get_egraph().begin_explain(); + ctx.reset_explain(); + for (auto const& [a, b] : m_evidence) { + SASSERT(a->get_root() == b->get_root() || ctx.get_egraph().are_diseq(a, b)); + if (a->get_root() == b->get_root()) + ctx.get_egraph().explain_eq(m_explain, a, b); + else + ctx.add_diseq_antecedent(m_explain, a, b); + } + ctx.get_egraph().end_explain(); + + size_t** ev = static_cast(ctx.get_region().allocate(sizeof(size_t*) * m_explain.size())); + for (unsigned i = m_explain.size(); i-- > 0; ) + ev[i] = m_explain[i]; + auto* constraint = new (sat::constraint_base::ptr2mem(mem)) justification(lit, c, b, m_explain.size(), ev); return constraint->to_index(); } void ematch::get_antecedents(sat::literal l, sat::ext_justification_idx idx, sat::literal_vector& r, bool probing) { - m_eval.explain(l, justification::from_index(idx), r, probing); + justification& j = justification::from_index(idx); + for (unsigned i = 0; i < j.m_num_ex; ++i) + ctx.add_explain(j.m_explain[i]); + r.push_back(j.m_clause.m_literal); } quantifier_ref ematch::nnf_skolem(quantifier* q) { + SASSERT(is_forall(q)); expr_ref r(m); proof_ref p(m); m_new_defs.reset(); m_new_proofs.reset(); m_nnf(q, m_new_defs, m_new_proofs, r, p); - SASSERT(is_quantifier(r)); + SASSERT(is_forall(r)); + pull_quant pull(m); + pull(r, r, p); + SASSERT(is_forall(r)); for (expr* d : m_new_defs) m_qs.add_unit(m_qs.mk_literal(d)); CTRACE("q", r != q, tout << mk_pp(q, m) << " -->\n" << r << "\n" << m_new_defs << "\n";); @@ -295,7 +328,7 @@ namespace q { TRACE("q", b->display(ctx, tout << "on-binding " << mk_pp(q, m) << "\n") << "\n";); - if (false && propagate(false, _binding, max_generation, c, new_propagation)) + if (propagate(false, _binding, max_generation, c, new_propagation)) return; binding::push_to_front(c.m_bindings, b); @@ -304,6 +337,10 @@ namespace q { } bool ematch::propagate(bool is_owned, euf::enode* const* binding, unsigned max_generation, clause& c, bool& propagated) { + if (!m_enable_propagate) + return false; + if (ctx.s().inconsistent()) + return true; TRACE("q", c.display(ctx, tout) << "\n";); unsigned idx = UINT_MAX; m_evidence.reset(); @@ -331,7 +368,7 @@ namespace q { propagate(ev == l_false, idx, j_idx); else m_prop_queue.push_back(prop(ev == l_false, idx, j_idx)); - propagated = true; + propagated = true; return true; } @@ -352,7 +389,7 @@ namespace q { if (m_prop_queue.empty()) return false; for (unsigned i = 0; i < m_prop_queue.size(); ++i) { - auto [is_conflict, idx, j_idx] = m_prop_queue[i]; + auto const& [is_conflict, idx, j_idx] = m_prop_queue[i]; propagate(is_conflict, idx, j_idx); } m_prop_queue.reset(); @@ -527,7 +564,7 @@ namespace q { }; void ematch::add(quantifier* _q) { - TRACE("q", tout << "add " << mk_pp(_q, m) << "\n";); + TRACE("q", tout << "add " << mk_pp(_q, m) << "\n"); clause* c = clausify(_q); quantifier* q = c->q(); if (m_q2clauses.contains(q)) { @@ -551,7 +588,7 @@ namespace q { app * mp = to_app(q->get_pattern(i)); SASSERT(m.is_pattern(mp)); bool unary = (mp->get_num_args() == 1); - TRACE("q", tout << "adding:\n" << expr_ref(mp, m) << "\n";); + TRACE("q", tout << "adding:\n" << expr_ref(mp, m) << "\n"); if (!unary && j >= num_eager_multi_patterns) { TRACE("q", tout << "delaying (too many multipatterns):\n" << mk_ismt2_pp(mp, m) << "\n";); if (!m_lazy_mam) @@ -570,7 +607,7 @@ namespace q { bool ematch::unit_propagate() { - return false; + // return false; return ctx.get_config().m_ematching && propagate(false); } @@ -581,7 +618,7 @@ namespace q { return m_inst_queue.propagate() || propagated; ctx.push(value_trail(m_qhead)); ptr_buffer to_remove; - for (; m_qhead < m_clause_queue.size(); ++m_qhead) { + for (; m_qhead < m_clause_queue.size() && m.inc(); ++m_qhead) { unsigned idx = m_clause_queue[m_qhead]; clause& c = *m_clauses[idx]; binding* b = c.m_bindings; @@ -589,7 +626,7 @@ namespace q { continue; do { - if (false && propagate(true, b->m_nodes, b->m_max_generation, c, propagated)) + if (propagate(true, b->m_nodes, b->m_max_generation, c, propagated)) to_remove.push_back(b); else if (flush) { instantiate(*b); @@ -642,7 +679,7 @@ namespace q { void ematch::collect_statistics(statistics& st) const { m_inst_queue.collect_statistics(st); st.update("q redundant", m_stats.m_num_redundant); - st.update("q units", m_stats.m_num_propagations); + st.update("q unit propagations", m_stats.m_num_propagations); st.update("q conflicts", m_stats.m_num_conflicts); st.update("q delayed bindings", m_stats.m_num_delayed_bindings); } diff --git a/src/sat/smt/q_ematch.h b/src/sat/smt/q_ematch.h index 3cdcfc80e..98ee26fc4 100644 --- a/src/sat/smt/q_ematch.h +++ b/src/sat/smt/q_ematch.h @@ -89,11 +89,13 @@ namespace q { unsigned m_qhead = 0; unsigned_vector m_clause_queue; euf::enode_pair_vector m_evidence; + bool m_enable_propagate = true; euf::enode* const* copy_nodes(clause& c, euf::enode* const* _binding); binding* tmp_binding(clause& c, app* pat, euf::enode* const* _binding); binding* alloc_binding(clause& c, app* pat, euf::enode* const* _binding, unsigned max_generation, unsigned min_top, unsigned max_top); - + + ptr_vector m_explain; sat::ext_justification_idx mk_justification(unsigned idx, clause& c, euf::enode* const* b); void ensure_ground_enodes(expr* e); @@ -135,9 +137,10 @@ namespace q { bool unit_propagate(); - void add(quantifier* q); + void relevant_eh(euf::enode* n); + void collect_statistics(statistics& st) const; void get_antecedents(sat::literal l, sat::ext_justification_idx idx, sat::literal_vector& r, bool probing); diff --git a/src/sat/smt/q_eval.cpp b/src/sat/smt/q_eval.cpp index a01bb9d9d..a87c5ec0d 100644 --- a/src/sat/smt/q_eval.cpp +++ b/src/sat/smt/q_eval.cpp @@ -230,13 +230,17 @@ namespace q { if (!n) return nullptr; for (unsigned i = args.size(); i-- > 0; ) { - if (args[i] != n->get_arg(i)) { - // roots could be different when using commutativity - // instead of compensating for this, we just bail out - if (args[i]->get_root() != n->get_arg(i)->get_root()) - return nullptr; - evidence.push_back(euf::enode_pair(args[i], n->get_arg(i))); - } + euf::enode* a = args[i], *b = n->get_arg(i); + if (a == b) + continue; + + // roots could be different when using commutativity + // instead of compensating for this, we just bail out + if (a->get_root() != b->get_root()) + return nullptr; + + TRACE("q", tout << "evidence " << ctx.bpp(a) << " " << ctx.bpp(b) << "\n"); + evidence.push_back(euf::enode_pair(a, b)); } m_indirect_nodes.push_back(n); m_eval.setx(t->get_id(), n, nullptr); @@ -246,20 +250,5 @@ namespace q { } return m_eval[e->get_id()]; } - - void eval::explain(sat::literal l, justification& j, sat::literal_vector& r, bool probing) { - clause& c = j.m_clause; - for (unsigned i = 0; i < j.m_num_ev; ++i) { - auto [a, b] = j.m_evidence[i]; - SASSERT(a->get_root() == b->get_root() || ctx.get_egraph().are_diseq(a, b)); - if (a->get_root() == b->get_root()) - ctx.add_antecedent(a, b); - else - ctx.add_diseq_antecedent(a, b); - } - r.push_back(c.m_literal); - (void)probing; // ignored - } - } diff --git a/src/sat/smt/q_eval.h b/src/sat/smt/q_eval.h index ef016407a..6d2f09501 100644 --- a/src/sat/smt/q_eval.h +++ b/src/sat/smt/q_eval.h @@ -43,7 +43,6 @@ namespace q { public: eval(euf::solver& ctx); - void explain(sat::literal l, justification& j, sat::literal_vector& r, bool probing); lbool operator()(euf::enode* const* binding, clause& c, euf::enode_pair_vector& evidence); lbool operator()(euf::enode* const* binding, clause& c, unsigned& idx, euf::enode_pair_vector& evidence); diff --git a/src/sat/smt/q_mam.cpp b/src/sat/smt/q_mam.cpp index a968314c2..b029b0618 100644 --- a/src/sat/smt/q_mam.cpp +++ b/src/sat/smt/q_mam.cpp @@ -424,15 +424,19 @@ namespace q { friend class compiler; friend class code_tree_manager; - void display_seq(std::ostream & out, instruction * head, unsigned indent) const { - for (unsigned i = 0; i < indent; i++) { + void spaces(std::ostream& out, unsigned indent) const { + for (unsigned i = 0; i < indent; i++) out << " "; - } + } + + void display_seq(std::ostream & out, instruction * head, unsigned indent) const { + spaces(out, indent); instruction * curr = head; out << *curr; curr = curr->m_next; while (curr != nullptr && curr->m_opcode != CHOOSE && curr->m_opcode != NOOP) { out << "\n"; + spaces(out, indent); out << *curr; curr = curr->m_next; } @@ -549,8 +553,8 @@ namespace q { void unmark(unsigned head) { for (unsigned i = m_candidates.size(); i-- > head; ) { enode* app = m_candidates[i]; - if (app->is_marked1()) - app->unmark1(); + if (app->is_marked3()) + app->unmark3(); } } @@ -598,7 +602,7 @@ namespace q { ast_manager & m = m_egraph->get_manager(); out << "patterns:\n"; for (auto [q, a] : m_patterns) - out << mk_pp(q, m) << " " << mk_pp(a, m) << "\n"; + out << q->get_id() << ": " << mk_pp(q, m) << " " << mk_pp(a, m) << "\n"; } #endif out << "function: " << m_root_lbl->get_name(); @@ -942,9 +946,9 @@ namespace q { void linearise_core() { m_aux.reset(); app * first_app = nullptr; - unsigned first_app_reg; - unsigned first_app_sz; - unsigned first_app_num_unbound_vars; + unsigned first_app_reg = 0; + unsigned first_app_sz = 0; + unsigned first_app_num_unbound_vars = 0; // generate first the non-BIND operations for (unsigned reg : m_todo) { expr * p = m_registers[reg]; @@ -2018,9 +2022,9 @@ namespace q { code_tree::scoped_unmark _unmark(t); while ((app = t->next_candidate()) && !ctx.resource_limits_exceeded()) { TRACE("trigger_bug", tout << "candidate\n" << ctx.bpp(app) << "\n";); - if (!app->is_marked1() && app->is_cgr()) { + if (!app->is_marked3() && app->is_cgr()) { execute_core(t, app); - app->mark1(); + app->mark3(); } } } @@ -2878,8 +2882,10 @@ namespace q { if (tree->expected_num_args() == p->get_num_args()) m_compiler.insert(tree, qa, mp, first_idx, false); } - DEBUG_CODE(m_trees[lbl_id]->get_patterns().push_back(std::make_pair(qa, mp)); - ctx.push(push_back_trail, false>(m_trees[lbl_id]->get_patterns()));); + DEBUG_CODE(if (first_idx == 0) { + m_trees[lbl_id]->get_patterns().push_back(std::make_pair(qa, mp)); + ctx.push(push_back_trail, false>(m_trees[lbl_id]->get_patterns())); + }); TRACE("trigger_bug", tout << "after add_pattern, first_idx: " << first_idx << "\n"; m_trees[lbl_id]->display(tout);); } @@ -3089,7 +3095,7 @@ namespace q { void add_candidate(code_tree * t, enode * app) { if (!t) return; - TRACE("q", tout << "candidate " << t << " " << ctx.bpp(app) << "\n";); + TRACE("q", tout << "candidate " << ctx.bpp(app) << "\n";); if (!t->has_candidates()) { ctx.push(push_back_trail(m_to_match)); m_to_match.push_back(t); diff --git a/src/sat/smt/q_mbi.cpp b/src/sat/smt/q_mbi.cpp index a7a2d67d7..e7b7c2a01 100644 --- a/src/sat/smt/q_mbi.cpp +++ b/src/sat/smt/q_mbi.cpp @@ -51,7 +51,7 @@ namespace q { m_instantiations.reset(); for (sat::literal lit : m_qs.m_universal) { quantifier* q = to_quantifier(ctx.bool_var2expr(lit.var())); - if (!ctx.is_relevant(q)) + if (!ctx.is_relevant(lit.var())) continue; init_model(); switch (check_forall(q)) { @@ -67,11 +67,10 @@ namespace q { } } m_max_cex += ctx.get_config().m_mbqi_max_cexs; - for (auto [qlit, fml, generation] : m_instantiations) { + for (auto const& [qlit, fml, generation] : m_instantiations) { euf::solver::scoped_generation sg(ctx, generation + 1); sat::literal lit = ctx.mk_literal(fml); m_qs.add_clause(~qlit, ~lit); - ctx.add_root(~qlit, ~lit); } m_instantiations.reset(); return result; @@ -130,7 +129,7 @@ namespace q { r = n; } else if (n->generation() == gen) { - if ((++count) % m_qs.random() == 0) + if ((m_qs.random() % ++count) == 0) r = n; } if (count > m_max_choose_candidates) @@ -156,7 +155,7 @@ namespace q { unsigned inc = 1; while (true) { ::solver::scoped_push _sp(*m_solver); - add_universe_restriction(q, *qb); + add_universe_restriction(*qb); m_solver->assert_expr(qb->mbody); ++m_stats.m_num_checks; lbool r = m_solver->check_sat(0, nullptr); @@ -218,17 +217,17 @@ namespace q { qlit.neg(); ctx.rewrite(proj); TRACE("q", tout << "project: " << proj << "\n";); + IF_VERBOSE(11, verbose_stream() << "mbi:\n" << mk_pp(q, m) << "\n" << proj << "\n"); ++m_stats.m_num_instantiations; unsigned generation = ctx.get_max_generation(proj); m_instantiations.push_back(instantiation_t(qlit, proj, generation)); } - void mbqi::add_universe_restriction(quantifier* q, q_body& qb) { - unsigned sz = q->get_num_decls(); - for (unsigned i = 0; i < sz; ++i) { - sort* s = q->get_decl_sort(i); + void mbqi::add_universe_restriction(q_body& qb) { + for (app* v : qb.vars) { + sort* s = v->get_sort(); if (m_model->has_uninterpreted_sort(s)) - restrict_to_universe(qb.vars.get(i), m_model->get_universe(s)); + restrict_to_universe(v, m_model->get_universe(s)); } } @@ -309,8 +308,10 @@ namespace q { proj.extract_literals(*m_model, vars, fmls); fmls_extracted = true; } - if (p) - (*p)(*m_model, vars, fmls); + if (!p) + continue; + if (!(*p)(*m_model, vars, fmls)) + return expr_ref(nullptr, m); } for (app* v : vars) { expr_ref term(m); diff --git a/src/sat/smt/q_mbi.h b/src/sat/smt/q_mbi.h index 1920baa52..c7dc4a551 100644 --- a/src/sat/smt/q_mbi.h +++ b/src/sat/smt/q_mbi.h @@ -83,7 +83,7 @@ namespace q { q_body* specialize(quantifier* q); q_body* q2body(quantifier* q); expr_ref solver_project(model& mdl, q_body& qb, expr_ref_vector& eqs, bool use_inst); - void add_universe_restriction(quantifier* q, q_body& qb); + void add_universe_restriction(q_body& qb); void add_domain_eqs(model& mdl, q_body& qb); void add_domain_bounds(model& mdl, q_body& qb); void eliminate_nested_vars(expr_ref_vector& fmls, q_body& qb); diff --git a/src/sat/smt/q_model_fixer.cpp b/src/sat/smt/q_model_fixer.cpp index 3339daf2d..456525c42 100644 --- a/src/sat/smt/q_model_fixer.cpp +++ b/src/sat/smt/q_model_fixer.cpp @@ -66,7 +66,7 @@ namespace q { ptr_vector univ; for (sat::literal lit : m_qs.universal()) { quantifier* q = to_quantifier(ctx.bool_var2expr(lit.var())); - if (ctx.is_relevant(q)) + if (ctx.is_relevant(lit.var())) univ.push_back(q); } if (univ.empty()) @@ -256,7 +256,7 @@ namespace q { tout << "invert-app " << mk_pp(t, m) << " = " << mk_pp(value, m) << "\n"; if (v2r.find(value, r)) tout << "inverse " << mk_pp(r->get_expr(), m) << "\n"; - ctx.display(tout); + /*ctx.display(tout); */ ); if (v2r.find(value, r)) return r->get_expr(); diff --git a/src/sat/smt/q_queue.cpp b/src/sat/smt/q_queue.cpp index d74ee4e39..95aff5e4d 100644 --- a/src/sat/smt/q_queue.cpp +++ b/src/sat/smt/q_queue.cpp @@ -146,15 +146,10 @@ namespace q { unsigned gen = get_new_gen(f, ent.m_cost); bool new_propagation = false; - if (false && em.propagate(true, f.nodes(), gen, *f.c, new_propagation)) + if (em.propagate(true, f.nodes(), gen, *f.c, new_propagation)) return; -#if 0 - std::cout << mk_pp(q, m) << "\n"; - std::cout << num_bindings << "\n"; - for (unsigned i = 0; i < num_bindings; ++i) - std::cout << mk_pp(f[i]->get_expr(), m) << " " << mk_pp(f[i]->get_sort(), m) << "\n"; -#endif + auto* ebindings = m_subst(q, num_bindings); for (unsigned i = 0; i < num_bindings; ++i) ebindings[i] = f[i]->get_expr(); @@ -168,7 +163,15 @@ namespace q { m_stats.m_num_instances++; - // f.display(ctx, std::cout << mk_pp(f.q(), m) << "\n" << instance << "\n") << "\n"; +#if 0 + std::cout << "instantiate\n"; + for (unsigned i = 0; i < num_bindings; ++i) + std::cout << ctx.bpp(f[i]) << " "; + std::cout << "\n"; + std::cout << mk_pp(q, m) << "\n"; +#endif + +// f.display(ctx, std::cout << mk_pp(f.q(), m) << "\n" << instance << "\n") << "\n"; euf::solver::scoped_generation _sg(ctx, gen); diff --git a/src/sat/smt/q_solver.cpp b/src/sat/smt/q_solver.cpp index 148383c2b..2bb7e2a30 100644 --- a/src/sat/smt/q_solver.cpp +++ b/src/sat/smt/q_solver.cpp @@ -19,9 +19,11 @@ Author: #include "ast/well_sorted.h" #include "ast/rewriter/var_subst.h" #include "ast/normal_forms/pull_quant.h" +#include "ast/rewriter/inj_axiom.h" #include "sat/smt/q_solver.h" #include "sat/smt/euf_solver.h" #include "sat/smt/sat_th.h" +#include "qe/lite/qe_lite.h" namespace q { @@ -44,19 +46,23 @@ namespace q { if (l.sign() == is_forall(e)) { sat::literal lit = skolemize(q); add_clause(~l, lit); - ctx.add_root(~l, lit); + return; } - else if (expand(q)) { - for (expr* e : m_expanded) { - sat::literal lit = ctx.internalize(e, l.sign(), false, false); - add_clause(~l, lit); - ctx.add_root(~l, lit); + quantifier* q_flat = nullptr; + if (!m_flat.find(q, q_flat)) { + if (expand(q)) { + for (expr* e : m_expanded) { + sat::literal lit = ctx.internalize(e, l.sign(), false, false); + add_clause(~l, lit); + } + return; } + q_flat = flatten(q); } - else if (is_ground(q->get_expr())) { - auto lit = ctx.internalize(q->get_expr(), l.sign(), false, false); + + if (is_ground(q_flat->get_expr())) { + auto lit = ctx.internalize(q_flat->get_expr(), l.sign(), false, false); add_clause(~l, lit); - ctx.add_root(~l, lit); } else { ctx.push_vec(m_universal, l); @@ -72,9 +78,12 @@ namespace q { if (ctx.get_config().m_mbqi) { switch (m_mbqi()) { - case l_true: return sat::check_result::CR_DONE; - case l_false: return sat::check_result::CR_CONTINUE; - case l_undef: break; + case l_true: + return sat::check_result::CR_DONE; + case l_false: + return sat::check_result::CR_CONTINUE; + case l_undef: + break; } } return sat::check_result::CR_GIVEUP; @@ -169,13 +178,18 @@ namespace q { quantifier* solver::flatten(quantifier* q) { quantifier* q_flat = nullptr; - if (!has_quantifiers(q->get_expr())) - return q; if (m_flat.find(q, q_flat)) return q_flat; + + expr_ref new_q(q, m), r(m); proof_ref pr(m); - expr_ref new_q(m); - if (is_forall(q)) { + if (!has_quantifiers(q->get_expr())) { + if (!ctx.get_config().m_refine_inj_axiom) + return q; + if (!simplify_inj_axiom(m, q, new_q)) + return q; + } + else if (is_forall(q)) { pull_quant pull(m); pull(q, new_q, pr); SASSERT(is_well_sorted(m, new_q)); @@ -221,15 +235,34 @@ namespace q { return val; } + /** + * Expand returns true if it was able to rewrite the formula. + * If the rewrite results in a quantifier, the rewritten quantifier + * is stored in m_flat to avoid repeated expansions. + * + */ bool solver::expand(quantifier* q) { - expr_ref r(m); + expr_ref r(q, m); proof_ref pr(m); - m_der(q, r, pr); + ctx.rewrite(r); + m_der(r, r, pr); + if (ctx.get_config().m_qe_lite) { + qe_lite qe(m, ctx.s().params()); + qe(r); + } m_expanded.reset(); - if (r != q) { - ctx.get_rewriter()(r); - m_expanded.push_back(r); - return true; + bool updated = q != r; + if (updated) { + ctx.rewrite(r); + if (!is_quantifier(r)) { + m_expanded.push_back(r); + return true; + } + if (is_forall(q) != is_forall(r)) { + m_expanded.push_back(r); + return true; + } + q = to_quantifier(r); } if (is_forall(q)) flatten_and(q->get_expr(), m_expanded); @@ -253,6 +286,11 @@ namespace q { idx = i; } } + if (!e1 && updated) { + m_expanded.reset(); + m_expanded.push_back(r); + return true; + } if (!e1) return false; @@ -267,12 +305,19 @@ namespace q { if (m_expanded.size() > 1) { for (unsigned i = m_expanded.size(); i-- > 0; ) { expr_ref tmp(m.update_quantifier(q, m_expanded.get(i)), m); - ctx.get_rewriter()(tmp); + ctx.rewrite(tmp); m_expanded[i] = tmp; } return true; } - return false; + else if (m_expanded.size() == 1 && updated) { + m_expanded[0] = r; + flatten(to_quantifier(r)); + return true; + } + else { + return false; + } } bool solver::split(expr* arg, expr_ref& e1, expr_ref& e2) { diff --git a/src/sat/smt/q_solver.h b/src/sat/smt/q_solver.h index 934864669..5d6a52c8f 100644 --- a/src/sat/smt/q_solver.h +++ b/src/sat/smt/q_solver.h @@ -83,6 +83,7 @@ namespace q { void init_search() override; void finalize_model(model& mdl) override; bool is_shared(euf::theory_var v) const override { return true; } + void relevant_eh(euf::enode* n) override { m_ematch.relevant_eh(n); } ast_manager& get_manager() { return m; } sat::literal_vector const& universal() const { return m_universal; } diff --git a/src/sat/smt/recfun_solver.cpp b/src/sat/smt/recfun_solver.cpp index be7322a75..b908297ad 100644 --- a/src/sat/smt/recfun_solver.cpp +++ b/src/sat/smt/recfun_solver.cpp @@ -68,8 +68,8 @@ namespace recfun { TRACEFN("case expansion " << e); SASSERT(e.m_def->is_fun_macro()); auto & vars = e.m_def->get_vars(); - auto lhs = e.m_lhs; - auto rhs = apply_args(vars, e.m_args, e.m_def->get_rhs()); + app_ref lhs = e.m_lhs; + expr_ref rhs = apply_args(vars, e.m_args, e.m_def->get_rhs()); unsigned generation = std::max(ctx.get_max_generation(lhs), ctx.get_max_generation(rhs)); euf::solver::scoped_generation _sgen(ctx, generation + 1); auto eq = eq_internalize(lhs, rhs); diff --git a/src/sat/smt/sat_dual_solver.cpp b/src/sat/smt/sat_dual_solver.cpp deleted file mode 100644 index 86d73a2d8..000000000 --- a/src/sat/smt/sat_dual_solver.cpp +++ /dev/null @@ -1,185 +0,0 @@ -/*++ -Copyright (c) 2020 Microsoft Corporation - -Module Name: - - sat_dual_solver.cpp - -Abstract: - - Solver for obtaining implicant. - -Author: - - Nikolaj Bjorner (nbjorner) 2020-08-25 - ---*/ -#include "sat/smt/sat_dual_solver.h" - -namespace sat { - - dual_solver::dual_solver(reslimit& l): - m_solver(m_params, l) - { - SASSERT(!m_solver.get_config().m_drat); - m_solver.set_incremental(true); - } - - void dual_solver::flush() { - while (m_num_scopes > 0) { - m_solver.user_push(); - m_roots.push_scope(); - m_tracked_vars.push_scope(); - m_units.push_scope(); - m_vars.push_scope(); - --m_num_scopes; - } - } - - void dual_solver::push() { - ++m_num_scopes; - } - - void dual_solver::pop(unsigned num_scopes) { - if (m_num_scopes >= num_scopes) { - m_num_scopes -= num_scopes; - return; - } - num_scopes -= m_num_scopes; - m_num_scopes = 0; - m_solver.user_pop(num_scopes); - unsigned old_sz = m_tracked_vars.old_size(num_scopes); - for (unsigned i = m_tracked_vars.size(); i-- > old_sz; ) - m_is_tracked[m_tracked_vars[i]] = false; - old_sz = m_vars.old_size(num_scopes); - for (unsigned i = m_vars.size(); i-- > old_sz; ) { - unsigned v = m_vars[i]; - unsigned w = m_ext2var[v]; - m_ext2var[v] = null_bool_var; - m_var2ext[w] = null_bool_var; - } - m_vars.pop_scope(num_scopes); - m_units.pop_scope(num_scopes); - m_roots.pop_scope(num_scopes); - m_tracked_vars.pop_scope(num_scopes); - } - - bool_var dual_solver::ext2var(bool_var v) { - bool_var w = m_ext2var.get(v, null_bool_var); - if (null_bool_var == w) { - w = m_solver.mk_var(); - m_solver.set_external(w); - m_ext2var.setx(v, w, null_bool_var); - m_var2ext.setx(w, v, null_bool_var); - m_vars.push_back(v); - } - return w; - } - - void dual_solver::track_relevancy(bool_var w) { - flush(); - bool_var v = ext2var(w); - if (!m_is_tracked.get(v, false)) { - m_is_tracked.setx(v, true, false); - m_tracked_vars.push_back(v); - } - } - - literal dual_solver::ext2lit(literal lit) { - return literal(ext2var(lit.var()), lit.sign()); - } - - literal dual_solver::lit2ext(literal lit) { - return literal(m_var2ext[lit.var()], lit.sign()); - } - - void dual_solver::add_root(unsigned sz, literal const* clause) { - flush(); - if (false && sz == 1) { - TRACE("dual", tout << "unit: " << clause[0] << "\n";); - m_units.push_back(clause[0]); - return; - } - literal root; - if (sz == 1) - root = ext2lit(clause[0]); - else { - root = literal(m_solver.mk_var(), false); - for (unsigned i = 0; i < sz; ++i) - m_solver.mk_clause(root, ~ext2lit(clause[i]), status::input()); - } - m_solver.set_external(root.var()); - m_roots.push_back(~root); - TRACE("dual", tout << "root: " << ~root << " => " << literal_vector(sz, clause) << "\n";); - } - - void dual_solver::add_aux(unsigned sz, literal const* clause) { - flush(); - m_lits.reset(); - for (unsigned i = 0; i < sz; ++i) - m_lits.push_back(ext2lit(clause[i])); - TRACE("dual", tout << "aux: " << literal_vector(sz, clause) << " -> " << m_lits << "\n";); - m_solver.mk_clause(sz, m_lits.data(), status::input()); - } - - void dual_solver::add_assumptions(solver const& s) { - flush(); - m_lits.reset(); - for (bool_var v : m_tracked_vars) - m_lits.push_back(literal(v, l_false == s.value(m_var2ext[v]))); - for (auto lit : m_units) { - bool_var w = m_ext2var.get(lit.var(), null_bool_var); - if (w != null_bool_var) - m_lits.push_back(ext2lit(lit)); - } - } - - bool dual_solver::operator()(solver const& s) { - m_core.reset(); - m_core.append(m_units); - if (m_roots.empty()) - return true; - m_solver.user_push(); - m_solver.add_clause(m_roots.size(), m_roots.data(), status::input()); - add_assumptions(s); - lbool is_sat = m_solver.check(m_lits.size(), m_lits.data()); - if (is_sat == l_false) - for (literal lit : m_solver.get_core()) - m_core.push_back(lit2ext(lit)); - if (is_sat == l_true) { - TRACE("dual", display(s, tout); s.display(tout);); - IF_VERBOSE(0, verbose_stream() << "unexpected SAT\n"); - UNREACHABLE(); - return false; - } - TRACE("dual", m_solver.display(tout << "is-sat: " << is_sat << " core: " << m_core << "\n");); - m_solver.user_pop(1); - return is_sat == l_false; - } - - std::ostream& dual_solver::display(solver const& s, std::ostream& out) const { - for (unsigned v = 0; v < m_solver.num_vars(); ++v) { - bool_var w = m_var2ext.get(v, null_bool_var); - if (w == null_bool_var) - continue; - lbool v1 = m_solver.value(v); - lbool v2 = s.value(w); - if (v1 == v2 || v2 == l_undef) - continue; - out << "ext: " << w << " " << v2 << "\n"; - out << "int: " << v << " " << v1 << "\n"; - } - literal_vector lits; - for (bool_var v : m_tracked_vars) - lits.push_back(literal(m_var2ext[v], l_false == s.value(m_var2ext[v]))); - out << "tracked: " << lits << "\n"; - lits.reset(); - for (literal r : m_roots) - if (m_solver.value(r) == l_true) - lits.push_back(r); - out << "roots: " << lits << "\n"; - m_solver.display(out); - - return out; - } -} diff --git a/src/sat/smt/sat_dual_solver.h b/src/sat/smt/sat_dual_solver.h deleted file mode 100644 index 08a31084e..000000000 --- a/src/sat/smt/sat_dual_solver.h +++ /dev/null @@ -1,83 +0,0 @@ -/*++ -Copyright (c) 2020 Microsoft Corporation - -Module Name: - - sat_dual_solver.h - -Abstract: - - Solver for obtaining implicant. - Based on an idea by Armin Biere to use dual propagation - for representation of negated goal. - -Author: - - Nikolaj Bjorner (nbjorner) 2020-08-25 - ---*/ -#pragma once -#include "util/lim_vector.h" -#include "sat/sat_solver.h" - -namespace sat { - - class dual_solver { - class dual_params : public no_drat_params { - public: - dual_params() { - set_bool("core.minimize", false); - } - }; - dual_params m_params; - solver m_solver; - lim_svector m_units, m_roots; - lim_svector m_tracked_vars; - literal_vector m_lits, m_core; - bool_var_vector m_is_tracked; - unsigned_vector m_ext2var; - unsigned_vector m_var2ext; - lim_svector m_vars; - unsigned m_num_scopes = 0; - void add_literal(literal lit); - - bool_var ext2var(bool_var v); - bool_var var2ext(bool_var v); - literal ext2lit(literal lit); - literal lit2ext(literal lit); - - void add_assumptions(solver const& s); - - std::ostream& display(solver const& s, std::ostream& out) const; - - void flush(); - - public: - dual_solver(reslimit& l); - void push(); - void pop(unsigned num_scopes); - - /* - * track model relevancy for variable - */ - void track_relevancy(bool_var v); - - /* - * Add a root clause from the input problem. - * At least one literal has to be satisfied in every root. - */ - void add_root(unsigned sz, literal const* clause); - - /* - * Add auxiliary clauses that originate from compiling definitions. - */ - void add_aux(unsigned sz, literal const* clause); - - /* - * Extract a minimized subset of relevant literals from a model for s. - */ - bool operator()(solver const& s); - - literal_vector const& core() const { return m_core; } - }; -} diff --git a/src/sat/smt/sat_th.cpp b/src/sat/smt/sat_th.cpp index 06efccaf0..f72ff601a 100644 --- a/src/sat/smt/sat_th.cpp +++ b/src/sat/smt/sat_th.cpp @@ -132,6 +132,7 @@ namespace euf { bool th_euf_solver::add_unit(sat::literal lit) { bool was_true = is_true(lit); ctx.s().add_clause(1, &lit, mk_status()); + ctx.add_root(lit); return !was_true; } @@ -143,32 +144,27 @@ namespace euf { return is_new; } - bool th_euf_solver::add_clause(sat::literal a, sat::literal b) { - bool was_true = is_true(a, b); + bool th_euf_solver::add_clause(sat::literal a, sat::literal b) { sat::literal lits[2] = { a, b }; - ctx.s().add_clause(2, lits, mk_status()); - return !was_true; + return add_clause(2, lits); } - bool th_euf_solver::add_clause(sat::literal a, sat::literal b, sat::literal c) { - bool was_true = is_true(a, b, c); + bool th_euf_solver::add_clause(sat::literal a, sat::literal b, sat::literal c) { sat::literal lits[3] = { a, b, c }; - ctx.s().add_clause(3, lits, mk_status()); - return !was_true; + return add_clause(3, lits); } bool th_euf_solver::add_clause(sat::literal a, sat::literal b, sat::literal c, sat::literal d) { - bool was_true = is_true(a, b, c, d); sat::literal lits[4] = { a, b, c, d }; - ctx.s().add_clause(4, lits, mk_status()); - return !was_true; + return add_clause(4, lits); } - bool th_euf_solver::add_clause(sat::literal_vector const& lits) { + bool th_euf_solver::add_clause(unsigned n, sat::literal* lits) { bool was_true = false; - for (auto lit : lits) - was_true |= is_true(lit); - s().add_clause(lits.size(), lits.data(), mk_status()); + for (unsigned i = 0; i < n; ++i) + was_true |= is_true(lits[i]); + ctx.add_root(n, lits); + s().add_clause(n, lits, mk_status()); return !was_true; } diff --git a/src/sat/smt/sat_th.h b/src/sat/smt/sat_th.h index 7e66a7156..b2d8d85b7 100644 --- a/src/sat/smt/sat_th.h +++ b/src/sat/smt/sat_th.h @@ -95,6 +95,11 @@ namespace euf { \brief conclude model building */ virtual void finalize_model(model& mdl) {} + + /** + * \brief does solver have an unhandled function. + */ + virtual bool has_unhandled() const { return false; } }; class th_solver : public sat::extension, public th_model_builder, public th_decompile, public th_internalizer { @@ -111,6 +116,12 @@ namespace euf { virtual void new_diseq_eh(euf::th_eq const& eq) {} + virtual bool enable_ackerman_axioms(euf::enode* n) const { return true; } + + virtual bool is_fixed(euf::theory_var v, expr_ref& val, sat::literal_vector& lits) { return false; } + + virtual void relevant_eh(euf::enode* n) {} + /** \brief Parametric theories (e.g. Arrays) should implement this method. */ @@ -139,7 +150,8 @@ namespace euf { bool add_clause(sat::literal a, sat::literal b); bool add_clause(sat::literal a, sat::literal b, sat::literal c); bool add_clause(sat::literal a, sat::literal b, sat::literal c, sat::literal d); - bool add_clause(sat::literal_vector const& lits); + bool add_clause(sat::literal_vector const& lits) { return add_clause(lits.size(), lits.data()); } + bool add_clause(unsigned n, sat::literal* lits); void add_equiv(sat::literal a, sat::literal b); void add_equiv_and(sat::literal a, sat::literal_vector const& bs); diff --git a/src/sat/smt/user_solver.cpp b/src/sat/smt/user_solver.cpp index edbaf6d8d..febbe9383 100644 --- a/src/sat/smt/user_solver.cpp +++ b/src/sat/smt/user_solver.cpp @@ -36,6 +36,10 @@ namespace user_solver { return n->get_th_var(get_id()); euf::theory_var v = mk_var(n); ctx.attach_th_var(n, this, v); + expr_ref r(m); + sat::literal_vector explain; + if (ctx.is_fixed(n, r, explain)) + m_prop.push_back(prop_info(explain, v, r)); return v; } @@ -93,6 +97,18 @@ namespace user_solver { m_pop_eh(m_user_context, num_scopes); } + void solver::propagate_consequence(prop_info const& prop) { + sat::literal lit = ctx.internalize(prop.m_conseq, false, false, true); + if (s().value(lit) != l_true) { + s().assign(lit, mk_justification(m_qhead)); + ++m_stats.m_num_propagations; + } + } + + void solver::propagate_new_fixed(prop_info const& prop) { + new_fixed_eh(prop.m_var, prop.m_conseq, prop.m_lits.size(), prop.m_lits.data()); + } + bool solver::unit_propagate() { if (m_qhead == m_prop.size()) return false; @@ -100,12 +116,11 @@ namespace user_solver { ctx.push(value_trail(m_qhead)); unsigned np = m_stats.m_num_propagations; for (; m_qhead < m_prop.size() && !s().inconsistent(); ++m_qhead) { - auto const& prop = m_prop[m_qhead]; - sat::literal lit = ctx.internalize(prop.m_conseq, false, false, true); - if (s().value(lit) != l_true) { - s().assign(lit, mk_justification(m_qhead)); - ++m_stats.m_num_propagations; - } + auto const& prop = m_prop[m_qhead]; + if (prop.m_var == euf::null_theory_var) + propagate_consequence(prop); + else + propagate_new_fixed(prop); } return np < m_stats.m_num_propagations; } @@ -171,5 +186,51 @@ namespace user_solver { return result; } + sat::literal solver::internalize(expr* e, bool sign, bool root, bool redundant) { + if (!visit_rec(m, e, sign, root, redundant)) { + TRACE("array", tout << mk_pp(e, m) << "\n";); + return sat::null_literal; + } + sat::literal lit = ctx.expr2literal(e); + if (sign) + lit.neg(); + if (root) + add_unit(lit); + return lit; + } + + void solver::internalize(expr* e, bool redundant) { + visit_rec(m, e, false, false, redundant); + } + + bool solver::visit(expr* e) { + if (visited(e)) + return true; + if (!is_app(e) || to_app(e)->get_family_id() != get_id()) { + ctx.internalize(e, m_is_redundant); + return true; + } + m_stack.push_back(sat::eframe(e)); + return false; + } + + bool solver::visited(expr* e) { + euf::enode* n = expr2enode(e); + return n && n->is_attached_to(get_id()); + } + + bool solver::post_visit(expr* e, bool sign, bool root) { + euf::enode* n = expr2enode(e); + SASSERT(!n || !n->is_attached_to(get_id())); + if (!n) + n = mk_enode(e, false); + auto v = add_expr(e); + if (m_created_eh) + m_created_eh(m_user_context, this, e, v); + return true; + } + + + } diff --git a/src/sat/smt/user_solver.h b/src/sat/smt/user_solver.h index b11742608..a30bc6a6d 100644 --- a/src/sat/smt/user_solver.h +++ b/src/sat/smt/user_solver.h @@ -32,6 +32,9 @@ namespace user_solver { unsigned_vector m_ids; expr_ref m_conseq; svector> m_eqs; + sat::literal_vector m_lits; + euf::theory_var m_var = euf::null_theory_var; + prop_info(unsigned num_fixed, unsigned const* fixed_ids, unsigned num_eqs, unsigned const* eq_lhs, unsigned const* eq_rhs, expr_ref const& c): m_ids(num_fixed, fixed_ids), m_conseq(c) @@ -39,6 +42,12 @@ namespace user_solver { for (unsigned i = 0; i < num_eqs; ++i) m_eqs.push_back(std::make_pair(eq_lhs[i], eq_rhs[i])); } + + prop_info(sat::literal_vector const& lits, euf::theory_var v, expr_ref const& val): + m_conseq(val), + m_lits(lits), + m_var(v) {} + }; struct stats { @@ -55,6 +64,7 @@ namespace user_solver { user_propagator::fixed_eh_t m_fixed_eh; user_propagator::eq_eh_t m_eq_eh; user_propagator::eq_eh_t m_diseq_eh; + user_propagator::created_eh_t m_created_eh; user_propagator::context_obj* m_api_context = nullptr; unsigned m_qhead = 0; vector m_prop; @@ -80,8 +90,15 @@ namespace user_solver { sat::justification mk_justification(unsigned propagation_index); + void propagate_consequence(prop_info const& prop); + void propagate_new_fixed(prop_info const& prop); + void validate_propagation(); + bool visit(expr* e) override; + bool visited(expr* e) override; + bool post_visit(expr* e, bool sign, bool root) override; + public: solver(euf::solver& ctx); @@ -107,6 +124,7 @@ namespace user_solver { void register_fixed(user_propagator::fixed_eh_t& fixed_eh) { m_fixed_eh = fixed_eh; } void register_eq(user_propagator::eq_eh_t& eq_eh) { m_eq_eh = eq_eh; } void register_diseq(user_propagator::eq_eh_t& diseq_eh) { m_diseq_eh = diseq_eh; } + void register_created(user_propagator::created_eh_t& created_eh) { m_created_eh = created_eh; } bool has_fixed() const { return (bool)m_fixed_eh; } @@ -122,8 +140,8 @@ namespace user_solver { bool unit_propagate() override; void get_antecedents(sat::literal l, sat::ext_justification_idx idx, sat::literal_vector & r, bool probing) override; void collect_statistics(statistics& st) const override; - sat::literal internalize(expr* e, bool sign, bool root, bool learned) override { UNREACHABLE(); return sat::null_literal; } - void internalize(expr* e, bool redundant) override { UNREACHABLE(); } + sat::literal internalize(expr* e, bool sign, bool root, bool learned) override; + void internalize(expr* e, bool redundant) override; std::ostream& display(std::ostream& out) const override; std::ostream& display_justification(std::ostream& out, sat::ext_justification_idx idx) const override; std::ostream& display_constraint(std::ostream& out, sat::ext_constraint_idx idx) const override; diff --git a/src/sat/tactic/goal2sat.cpp b/src/sat/tactic/goal2sat.cpp index bdb37bc79..30d73a21a 100644 --- a/src/sat/tactic/goal2sat.cpp +++ b/src/sat/tactic/goal2sat.cpp @@ -125,20 +125,6 @@ struct goal2sat::imp : public sat::sat_internalizer { bool top_level_relevant() { return m_top_level && relevancy_enabled(); } - - void add_dual_def(unsigned n, sat::literal const* lits) { - if (relevancy_enabled()) - ensure_euf()->add_aux(n, lits); - } - - void add_dual_root(unsigned n, sat::literal const* lits) { - if (relevancy_enabled()) - ensure_euf()->add_root(n, lits); - } - - void add_dual_root(sat::literal lit) { - add_dual_root(1, &lit); - } void mk_clause(sat::literal l) { mk_clause(1, &l); @@ -156,7 +142,8 @@ struct goal2sat::imp : public sat::sat_internalizer { void mk_clause(unsigned n, sat::literal * lits) { TRACE("goal2sat", tout << "mk_clause: "; for (unsigned i = 0; i < n; i++) tout << lits[i] << " "; tout << "\n";); - add_dual_def(n, lits); + if (relevancy_enabled()) + ensure_euf()->add_aux(n, lits); m_solver.add_clause(n, lits, mk_status()); } @@ -176,7 +163,8 @@ struct goal2sat::imp : public sat::sat_internalizer { void mk_root_clause(unsigned n, sat::literal * lits) { TRACE("goal2sat", tout << "mk_root_clause: "; for (unsigned i = 0; i < n; i++) tout << lits[i] << " "; tout << "\n";); - add_dual_root(n, lits); + if (relevancy_enabled()) + ensure_euf()->add_root(n, lits); m_solver.add_clause(n, lits, m_is_redundant ? mk_status() : sat::status::input()); } @@ -186,8 +174,6 @@ struct goal2sat::imp : public sat::sat_internalizer { return v; v = m_solver.add_var(is_ext); log_def(v, n); - if (top_level_relevant() && !is_bool_op(n)) - ensure_euf()->track_relevancy(v); return v; } @@ -216,14 +202,11 @@ struct goal2sat::imp : public sat::sat_internalizer { if (!m_expr2var_replay || !m_expr2var_replay->find(t, v)) v = add_var(true, t); m_map.insert(t, v); - if (relevancy_enabled() && (m.is_true(t) || m.is_false(t))) { - add_dual_root(sat::literal(v, m.is_false(t))); - ensure_euf()->track_relevancy(v); - } return v; } sat::bool_var add_bool_var(expr* t) override { + force_push(); sat::bool_var v = m_map.to_bool_var(t); if (v == sat::null_bool_var) v = mk_bool_var(t); @@ -238,11 +221,11 @@ struct goal2sat::imp : public sat::sat_internalizer { for (; m_num_scopes > 0; --m_num_scopes) { m_map.push(); m_cache_lim.push_back(m_cache_trail.size()); - } + } } void push() override { - ++m_num_scopes; + ++m_num_scopes; } void pop(unsigned n) override { @@ -263,7 +246,7 @@ struct goal2sat::imp : public sat::sat_internalizer { } } m_cache_trail.shrink(k); - m_cache_lim.shrink(m_cache_lim.size() - n); + m_cache_lim.shrink(m_cache_lim.size() - n); } // remove non-external literals from cache. @@ -677,8 +660,6 @@ struct goal2sat::imp : public sat::sat_internalizer { } if (lit == sat::null_literal) return; - if (top_level_relevant()) - euf->track_relevancy(lit.var()); if (root) mk_root_clause(lit); else diff --git a/src/sat/tactic/sat_tactic.cpp b/src/sat/tactic/sat_tactic.cpp index 38c25eae3..892a88f89 100644 --- a/src/sat/tactic/sat_tactic.cpp +++ b/src/sat/tactic/sat_tactic.cpp @@ -201,8 +201,8 @@ public: char const* name() const override { return "sat"; } void updt_params(params_ref const & p) override { - m_params = p; - if (m_imp) m_imp->updt_params(p); + m_params.append(p); + if (m_imp) m_imp->updt_params(m_params); } void collect_param_descrs(param_descrs & r) override { diff --git a/src/smt/params/preprocessor_params.cpp b/src/smt/params/preprocessor_params.cpp index a87d4959d..9fcb09843 100644 --- a/src/smt/params/preprocessor_params.cpp +++ b/src/smt/params/preprocessor_params.cpp @@ -40,8 +40,8 @@ void preprocessor_params::display(std::ostream & out) const { pattern_inference_params::display(out); bit_blaster_params::display(out); - DISPLAY_PARAM(m_lift_ite); - DISPLAY_PARAM(m_ng_lift_ite); + DISPLAY_PARAM((int)m_lift_ite); + DISPLAY_PARAM((int)m_ng_lift_ite); DISPLAY_PARAM(m_pull_cheap_ite); DISPLAY_PARAM(m_pull_nested_quantifiers); DISPLAY_PARAM(m_eliminate_term_ite); diff --git a/src/smt/params/preprocessor_params.h b/src/smt/params/preprocessor_params.h index a0d56182a..53568c366 100644 --- a/src/smt/params/preprocessor_params.h +++ b/src/smt/params/preprocessor_params.h @@ -21,7 +21,7 @@ Revision History: #include "params/pattern_inference_params.h" #include "params/bit_blaster_params.h" -enum lift_ite_kind { +enum class lift_ite_kind { LI_NONE, LI_CONSERVATIVE, LI_FULL @@ -50,8 +50,8 @@ struct preprocessor_params : public pattern_inference_params, public: preprocessor_params(params_ref const & p = params_ref()): - m_lift_ite(LI_NONE), - m_ng_lift_ite(LI_NONE), + m_lift_ite(lift_ite_kind::LI_NONE), + m_ng_lift_ite(lift_ite_kind::LI_NONE), m_pull_cheap_ite(false), m_pull_nested_quantifiers(false), m_eliminate_term_ite(false), diff --git a/src/smt/params/qi_params.cpp b/src/smt/params/qi_params.cpp index 91f354eda..387df4dd5 100644 --- a/src/smt/params/qi_params.cpp +++ b/src/smt/params/qi_params.cpp @@ -28,6 +28,7 @@ void qi_params::updt_params(params_ref const & _p) { m_mbqi_trace = p.mbqi_trace(); m_mbqi_force_template = p.mbqi_force_template(); m_mbqi_id = p.mbqi_id(); + m_qe_lite = p.q_lite(); m_qi_profile = p.qi_profile(); m_qi_profile_freq = p.qi_profile_freq(); m_qi_max_instances = p.qi_max_instances(); diff --git a/src/smt/params/qi_params.h b/src/smt/params/qi_params.h index 8ad7a8a90..8f59667a0 100644 --- a/src/smt/params/qi_params.h +++ b/src/smt/params/qi_params.h @@ -30,26 +30,27 @@ enum quick_checker_mode { struct qi_params { std::string m_qi_cost; std::string m_qi_new_gen; - double m_qi_eager_threshold; - double m_qi_lazy_threshold; - unsigned m_qi_max_eager_multipatterns; - unsigned m_qi_max_lazy_multipattern_matching; - bool m_qi_profile; - unsigned m_qi_profile_freq; - quick_checker_mode m_qi_quick_checker; - bool m_qi_lazy_quick_checker; - bool m_qi_promote_unsat; - unsigned m_qi_max_instances; - bool m_qi_lazy_instantiation; - bool m_qi_conservative_final_check; + double m_qi_eager_threshold = 10.0; + double m_qi_lazy_threshold = 20.0; + unsigned m_qi_max_eager_multipatterns = 0; + unsigned m_qi_max_lazy_multipattern_matching = 2; + bool m_qi_profile = false; + unsigned m_qi_profile_freq = UINT_MAX; + quick_checker_mode m_qi_quick_checker = MC_NO; + bool m_qi_lazy_quick_checker = true; + bool m_qi_promote_unsat = true; + unsigned m_qi_max_instances = UINT_MAX; + bool m_qi_lazy_instantiation = false; + bool m_qi_conservative_final_check = false; + bool m_qe_lite = false; - bool m_mbqi; - unsigned m_mbqi_max_cexs; - unsigned m_mbqi_max_cexs_incr; - unsigned m_mbqi_max_iterations; - bool m_mbqi_trace; - unsigned m_mbqi_force_template; - const char * m_mbqi_id; + bool m_mbqi = true; + unsigned m_mbqi_max_cexs = 1; + unsigned m_mbqi_max_cexs_incr = 1; + unsigned m_mbqi_max_iterations = 1000; + bool m_mbqi_trace = false; + unsigned m_mbqi_force_template = 10; + const char * m_mbqi_id = nullptr; qi_params(params_ref const & p = params_ref()): /* @@ -78,26 +79,7 @@ struct qi_params { matching loop detection. */ m_qi_cost("(+ weight generation)"), - m_qi_new_gen("cost"), - m_qi_eager_threshold(10.0), - m_qi_lazy_threshold(20.0), // reduced to give a chance to MBQI - m_qi_max_eager_multipatterns(0), - m_qi_max_lazy_multipattern_matching(2), - m_qi_profile(false), - m_qi_profile_freq(UINT_MAX), - m_qi_quick_checker(MC_NO), - m_qi_lazy_quick_checker(true), - m_qi_promote_unsat(true), - m_qi_max_instances(UINT_MAX), - m_qi_lazy_instantiation(false), - m_qi_conservative_final_check(false), - m_mbqi(true), // enabled by default - m_mbqi_max_cexs(1), - m_mbqi_max_cexs_incr(1), - m_mbqi_max_iterations(1000), - m_mbqi_trace(false), - m_mbqi_force_template(10), - m_mbqi_id(nullptr) + m_qi_new_gen("cost") { updt_params(p); } diff --git a/src/smt/params/smt_params_helper.pyg b/src/smt/params/smt_params_helper.pyg index 20839ea8a..c0ec6086c 100644 --- a/src/smt/params/smt_params_helper.pyg +++ b/src/smt/params/smt_params_helper.pyg @@ -35,6 +35,7 @@ def_module_params(module_name='smt', ('mbqi.force_template', UINT, 10, 'some quantifiers can be used as templates for building interpretations for functions. Z3 uses heuristics to decide whether a quantifier will be used as a template or not. Quantifiers with weight >= mbqi.force_template are forced to be used as a template'), ('mbqi.id', STRING, '', 'Only use model-based instantiation for quantifiers with id\'s beginning with string'), ('q.lift_ite', UINT, 0, '0 - don not lift non-ground if-then-else, 1 - use conservative ite lifting, 2 - use full lifting of if-then-else under quantifiers'), + ('q.lite', BOOL, False, 'Use cheap quantifier elimination during pre-processing'), ('qi.profile', BOOL, False, 'profile quantifier instantiation'), ('qi.profile_freq', UINT, UINT_MAX, 'how frequent results are reported by qi.profile'), ('qi.max_instances', UINT, UINT_MAX, 'maximum number of quantifier instantiations'), @@ -49,6 +50,7 @@ def_module_params(module_name='smt', ('bv.eq_axioms', BOOL, True, 'add dynamic equality axioms'), ('bv.watch_diseq', BOOL, False, 'use watch lists instead of eager axioms for bit-vectors'), ('bv.delay', BOOL, True, 'delay internalize expensive bit-vector operations'), + ('bv.polysat', BOOL, True, 'use polysat bit-vector solver'), ('arith.random_initial_value', BOOL, False, 'use random initial values in the simplex-based procedure for linear arithmetic'), ('arith.solver', UINT, 6, 'arithmetic solver: 0 - no solver, 1 - bellman-ford based solver (diff. logic only), 2 - simplex based solver, 3 - floyd-warshall based solver (diff. logic only) and no theory combination 4 - utvpi, 5 - infinitary lra, 6 - lra solver'), ('arith.nl', BOOL, True, '(incomplete) nonlinear arithmetic support based on Groebner basis and interval propagation, relevant only if smt.arith.solver=2'), diff --git a/src/smt/params/theory_bv_params.cpp b/src/smt/params/theory_bv_params.cpp index 337af657f..ea73702f7 100644 --- a/src/smt/params/theory_bv_params.cpp +++ b/src/smt/params/theory_bv_params.cpp @@ -28,6 +28,7 @@ void theory_bv_params::updt_params(params_ref const & _p) { m_bv_enable_int2bv2int = p.bv_enable_int2bv(); m_bv_eq_axioms = p.bv_eq_axioms(); m_bv_delay = p.bv_delay(); + m_bv_polysat = p.bv_polysat(); } #define DISPLAY_PARAM(X) out << #X"=" << X << std::endl; @@ -42,4 +43,5 @@ void theory_bv_params::display(std::ostream & out) const { DISPLAY_PARAM(m_bv_blast_max_size); DISPLAY_PARAM(m_bv_enable_int2bv2int); DISPLAY_PARAM(m_bv_delay); + DISPLAY_PARAM(m_bv_polysat); } diff --git a/src/smt/params/theory_bv_params.h b/src/smt/params/theory_bv_params.h index 57ec16b6a..4fa62e664 100644 --- a/src/smt/params/theory_bv_params.h +++ b/src/smt/params/theory_bv_params.h @@ -36,6 +36,7 @@ struct theory_bv_params { bool m_bv_enable_int2bv2int = true; bool m_bv_watch_diseq = false; bool m_bv_delay = true; + bool m_bv_polysat = true; theory_bv_params(params_ref const & p = params_ref()) { updt_params(p); } diff --git a/src/smt/qi_queue.cpp b/src/smt/qi_queue.cpp index f359136af..1cef2a0f1 100644 --- a/src/smt/qi_queue.cpp +++ b/src/smt/qi_queue.cpp @@ -226,6 +226,7 @@ namespace smt { ebindings[i] = bindings[i]->get_expr(); expr_ref instance = m_subst(); + TRACE("qi_queue", tout << "new instance:\n" << mk_pp(instance, m) << "\n";); TRACE("qi_queue_instance", tout << "new instance:\n" << mk_pp(instance, m) << "\n";); expr_ref s_instance(m); @@ -244,6 +245,15 @@ namespace smt { return; } +#if 0 + std::cout << "instantiate\n"; + enode_vector _bindings(num_bindings, bindings); + for (auto * b : _bindings) + std::cout << enode_pp(b, m_context) << " "; + std::cout << "\n"; + std::cout << mk_pp(q, m) << "\n"; +#endif + TRACE("qi_queue", tout << "simplified instance:\n" << s_instance << "\n";); stat->inc_num_instances(); if (stat->get_num_instances() % m_params.m_qi_profile_freq == 0) { diff --git a/src/smt/seq_axioms.h b/src/smt/seq_axioms.h index e9fc3cb53..525f6b3db 100644 --- a/src/smt/seq_axioms.h +++ b/src/smt/seq_axioms.h @@ -73,6 +73,7 @@ namespace smt { void add_indexof_axiom(expr* n) { m_ax.indexof_axiom(n); } void add_last_indexof_axiom(expr* n) { m_ax.last_indexof_axiom(n); } void add_replace_axiom(expr* n) { m_ax.replace_axiom(n); } + void add_replace_all_axiom(expr* n) { m_ax.replace_all_axiom(n); } void add_at_axiom(expr* n) { m_ax.at_axiom(n); } void add_nth_axiom(expr* n) { m_ax.nth_axiom(n); } void add_itos_axiom(expr* n) { m_ax.itos_axiom(n); } diff --git a/src/smt/seq_eq_solver.cpp b/src/smt/seq_eq_solver.cpp index a54ebdac9..7ac82eff7 100644 --- a/src/smt/seq_eq_solver.cpp +++ b/src/smt/seq_eq_solver.cpp @@ -375,6 +375,15 @@ bool theory_seq::split_lengths(dependency* dep, SASSERT(X != Y); // |b| < |X| <= |b| + |Y| => x = bY1, Y = Y1Y2 + // at this point |b| = lenB - |Y| which is less than |X| + // given how bs is constructed: + // bs is constructed as a vector of strings with length >= |X| + // but when the last element it removed the length is < |X| + // We also have |X| <= |b| + |Y|, also by how bs is constructed. + // Therefore the antecedent is true in the current model. + // It could be the antecendet is not justified, so we create + // literals to justify the antecedent and ensure they are relevant. + // If the literals are relevant and assigned they should be true. expr_ref lenXE = mk_len(X); expr_ref lenYE = mk_len(Y); expr_ref lenb = mk_len(b); @@ -383,7 +392,7 @@ bool theory_seq::split_lengths(dependency* dep, literal_vector lits; lits.push_back(lit1); lits.push_back(lit2); - + if (ctx.get_assignment(lit1) != l_true || ctx.get_assignment(lit2) != l_true) { ctx.mark_as_relevant(lit1); diff --git a/src/smt/seq_regex.cpp b/src/smt/seq_regex.cpp index aa1faa131..55c3d7edd 100644 --- a/src/smt/seq_regex.cpp +++ b/src/smt/seq_regex.cpp @@ -19,6 +19,9 @@ Author: #include "smt/seq_regex.h" #include "smt/theory_seq.h" #include "ast/expr_abstract.h" +#include "ast/ast_util.h" +#include "ast/for_each_expr.h" +#include namespace smt { @@ -144,38 +147,6 @@ namespace smt { } } - /* - //if r is uninterpreted then taking a derivative may diverge try to obtain the - //value from equations providing r a definition - if (is_uninterp(r)) { - if (m_const_to_expr.contains(r)) { - proof* _not_used = nullptr; - m_const_to_expr.get(r, r, _not_used); - if (is_uninterp(r)) { - if (m_const_to_expr.contains(r)) { - m_const_to_expr.get(r, r, _not_used); - } - } - } - else { - //add the literal back - expr_ref r_alias(m.mk_fresh_const(symbol(r->get_id()), r->get_sort(), false), m); - expr_ref s_in_r_alias(re().mk_in_re(s, r_alias), m); - literal s_in_r_alias_lit = th.mk_literal(s_in_r_alias); - m_const_to_expr.insert(r_alias, r, nullptr); - th.add_axiom(s_in_r_alias_lit); - return; - } - } - */ - - /* - if (is_uninterp(r)) { - th.add_unhandled_expr(e); - return; - } - */ - expr_ref zero(a().mk_int(0), m); expr_ref acc(sk().mk_accept(s, zero, r), m); literal acc_lit = th.mk_literal(acc); @@ -235,6 +206,28 @@ namespace smt { } + bool seq_regex::block_if_empty(expr* r, literal lit) { + auto info = re().get_info(r); + + //if the minlength of the regex is UINT_MAX then the regex is a deadend + if (re().is_empty(r) || info.min_length == UINT_MAX) { + STRACE("seq_regex_brief", tout << "(empty) ";); + th.add_axiom(~lit); + return true; + } + + if (info.interpreted) { + update_state_graph(r); + if (m_state_graph.is_dead(get_state_id(r))) { + STRACE("seq_regex_brief", tout << "(dead) ";); + th.add_axiom(~lit); + return true; + } + } + return false; + } + + /** * Propagate the atom (accept s i r) * @@ -271,24 +264,8 @@ namespace smt { << "PA(" << mk_pp(s, m) << "@" << idx << "," << state_str(r) << ") ";); - auto info = re().get_info(r); - - //if the minlength of the regex is UINT_MAX then the regex is a deadend - if (re().is_empty(r) || info.min_length == UINT_MAX) { - STRACE("seq_regex_brief", tout << "(empty) ";); - th.add_axiom(~lit); + if (block_if_empty(r, lit)) return; - } - - if (info.interpreted) { - update_state_graph(r); - - if (m_state_graph.is_dead(get_state_id(r))) { - STRACE("seq_regex_brief", tout << "(dead) ";); - th.add_axiom(~lit); - return; - } - } if (block_unfolding(lit, idx)) { STRACE("seq_regex_brief", tout << "(blocked) ";); @@ -459,13 +436,10 @@ namespace smt { STRACE("seq_regex", tout << "derivative(" << mk_pp(ele, m) << "): " << mk_pp(r, m) << std::endl;); // Uses canonical variable (:var 0) for the derivative element - expr_ref der(seq_rw().mk_derivative(r), m); - // Substitute (:var 0) with the actual element + expr_ref der = seq_rw().mk_derivative(r); var_subst subst(m); - expr_ref_vector sub(m); - sub.push_back(ele); - der = subst(der, sub); + der = subst(der, ele); STRACE("seq_regex", tout << "derivative result: " << mk_pp(der, m) << std::endl;); STRACE("seq_regex_brief", tout << "d(" << state_str(r) << ")=" @@ -480,17 +454,6 @@ namespace smt { TRACE("seq_regex", tout << "propagate EQ: " << mk_pp(r1, m) << ", " << mk_pp(r2, m) << std::endl;); STRACE("seq_regex_brief", tout << "PEQ ";); - /* - if (is_uninterp(r1) || is_uninterp(r2)) { - th.add_axiom(th.mk_eq(r1, r2, false)); - if (is_uninterp(r1)) - m_const_to_expr.insert(r1, r2, nullptr); - else - m_const_to_expr.insert(r2, r1, nullptr); - - } - */ - sort* seq_sort = nullptr; VERIFY(u().is_re(r1, seq_sort)); expr_ref r = symmetric_diff(r1, r2); @@ -512,13 +475,11 @@ namespace smt { void seq_regex::propagate_ne(expr* r1, expr* r2) { TRACE("seq_regex", tout << "propagate NEQ: " << mk_pp(r1, m) << ", " << mk_pp(r2, m) << std::endl;); STRACE("seq_regex_brief", tout << "PNEQ ";); - // TBD: rewrite to use state_graph - // why is is_non_empty even needed, why not just not(in_empty) sort* seq_sort = nullptr; VERIFY(u().is_re(r1, seq_sort)); expr_ref r = symmetric_diff(r1, r2); expr_ref emp(re().mk_empty(r->get_sort()), m); - expr_ref n(m.mk_fresh_const("re.char", seq_sort), m); + expr_ref n(m.mk_fresh_const("re.char", seq_sort), m); expr_ref is_non_empty = sk().mk_is_non_empty(r, r, n); th.add_axiom(th.mk_eq(r1, r2, false), th.mk_literal(is_non_empty)); } @@ -544,6 +505,10 @@ namespace smt { expr* e = ctx.bool_var2expr(lit.var()), *r = nullptr, *u = nullptr, *n = nullptr; VERIFY(sk().is_is_non_empty(e, r, u, n)); + if (block_if_empty(r, lit)) + return; + + TRACE("seq_regex", tout << "propagate nonempty: " << mk_pp(e, m) << std::endl;); STRACE("seq_regex_brief", tout << std::endl << "PNE(" << expr_id_str(e) << "," << state_str(r) @@ -553,6 +518,7 @@ namespace smt { if (m.is_true(is_nullable)) return; + literal null_lit = th.mk_literal(is_nullable); expr_ref hd = mk_first(r, n); expr_ref d(m); @@ -566,8 +532,8 @@ namespace smt { expr_ref_pair_vector cofactors(m); get_cofactors(d, cofactors); for (auto const& p : cofactors) { - if (is_member(p.second, u)) - continue; + if (is_member(p.second, u)) + continue; expr_ref cond(p.first, m); seq_rw().elim_condition(hd, cond); rewrite(cond); @@ -641,23 +607,6 @@ namespace smt { // s[i..] in .* <==> true, also: s[i..] in .+ <==> true when |s|>i re_to_accept.find(e) = m.mk_true(); } - /* - else if (re().is_epsilon(e)) - { - expr* one = a().mk_int(1); - _temp_bool_owner.push_back(one); - //the substring starting after position i must be empty - expr* s_end = str().mk_substr(s, i_int, one); - expr* s_end_is_epsilon = m.mk_eq(s_end, str().mk_empty(m.get_sort(s))); - - _temp_bool_owner.push_back(s_end_is_epsilon); - re_to_accept.find(e) = s_end_is_epsilon; - - STRACE("seq_regex_verbose", tout - << "added empty sequence leaf: " - << mk_pp(s_end_is_epsilon, m) << std::endl;); - } - */ else if (re().is_union(e, e1, e2)) { expr* b1 = re_to_accept.find(e1); expr* b2 = re_to_accept.find(e2); @@ -728,39 +677,47 @@ namespace smt { Return a list of all (cond, leaf) pairs in a given derivative expression r. - Note: this recursive implementation is inefficient, since if nodes - are repeated often in the expression DAG, they may be visited - many times. For this reason, prefer mk_deriv_accept and - get_all_derivatives when possible. + Note: this implementation is inefficient: it simply collects all expressions under an if and + iterates over all combinations. This method is still used by: propagate_is_empty propagate_is_non_empty */ void seq_regex::get_cofactors(expr* r, expr_ref_pair_vector& result) { - expr_ref_vector conds(m); - get_cofactors_rec(r, conds, result); - STRACE("seq_regex", tout << "Number of derivatives: " - << result.size() << std::endl;); - STRACE("seq_regex_brief", tout << "#derivs=" << result.size() << " ";); - } - void seq_regex::get_cofactors_rec(expr* r, expr_ref_vector& conds, - expr_ref_pair_vector& result) { - expr* cond = nullptr, *r1 = nullptr, *r2 = nullptr; - if (m.is_ite(r, cond, r1, r2)) { - conds.push_back(cond); - get_cofactors_rec(r1, conds, result); - conds.pop_back(); - conds.push_back(mk_not(m, cond)); - get_cofactors_rec(r2, conds, result); - conds.pop_back(); + obj_hashtable ifs; + expr* cond = nullptr, * r1 = nullptr, * r2 = nullptr; + for (expr* e : subterms::ground(expr_ref(r, m))) + if (m.is_ite(e, cond, r1, r2)) + ifs.insert(cond); + + expr_ref_vector rs(m); + vector conds; + conds.push_back(expr_ref_vector(m)); + rs.push_back(r); + for (expr* c : ifs) { + unsigned sz = conds.size(); + expr_safe_replace rep1(m); + expr_safe_replace rep2(m); + rep1.insert(c, m.mk_true()); + rep2.insert(c, m.mk_false()); + expr_ref r2(m); + for (unsigned i = 0; i < sz; ++i) { + expr_ref_vector cs = conds[i]; + cs.push_back(mk_not(m, c)); + conds.push_back(cs); + conds[i].push_back(c); + expr_ref r1(rs.get(i), m); + rep1(r1, r2); + rs[i] = r2; + rep2(r1, r2); + rs.push_back(r2); + } } - else if (re().is_union(r, r1, r2)) { - get_cofactors_rec(r1, conds, result); - get_cofactors_rec(r2, conds, result); - } - else { - expr_ref conj = mk_and(conds); + for (unsigned i = 0; i < conds.size(); ++i) { + expr_ref conj = mk_and(conds[i]); + expr_ref r(rs.get(i), m); + ctx.get_rewriter()(r); if (!m.is_false(conj) && !re().is_empty(r)) result.push_back(conj, r); } @@ -845,7 +802,6 @@ namespace smt { return m_state_to_expr.get(id - 1); } - bool seq_regex::can_be_in_cycle(expr *r1, expr *r2) { // TBD: This can be used to optimize the state graph: // return false here if it is known that r1 -> r2 can never be @@ -904,6 +860,7 @@ namespace smt { } m_state_graph.mark_done(r_id); } + STRACE("seq_regex", m_state_graph.display(tout);); STRACE("seq_regex_brief", tout << std::endl;); STRACE("seq_regex_brief", m_state_graph.display(tout);); diff --git a/src/smt/seq_regex.h b/src/smt/seq_regex.h index dcf81dbc3..5c3fddd25 100644 --- a/src/smt/seq_regex.h +++ b/src/smt/seq_regex.h @@ -165,8 +165,6 @@ namespace smt { expr_ref mk_deriv_accept(expr* s, unsigned i, expr* r); void get_derivative_targets(expr* r, expr_ref_vector& targets); void get_cofactors(expr* r, expr_ref_pair_vector& result); - void get_cofactors_rec(expr* r, expr_ref_vector& conds, - expr_ref_pair_vector& result); /* Pretty print the regex of the state id to the out stream, @@ -184,6 +182,8 @@ namespace smt { } } + bool block_if_empty(expr* r, literal lit); + public: seq_regex(theory_seq& th); @@ -201,11 +201,11 @@ namespace smt { void propagate_eq(expr* r1, expr* r2); - void propagate_ne(expr* r1, expr* r2); - - void propagate_is_non_empty(literal lit); + void propagate_ne(expr* r1, expr* r2); void propagate_is_empty(literal lit); + + void propagate_is_non_empty(literal lit); }; diff --git a/src/smt/smt_context.cpp b/src/smt/smt_context.cpp index 48eb23b4f..d659329ca 100644 --- a/src/smt/smt_context.cpp +++ b/src/smt/smt_context.cpp @@ -2906,6 +2906,30 @@ namespace smt { m_user_propagator->new_fixed_eh(v, val, sz, explain); } + bool context::is_fixed(enode* n, expr_ref& val, literal_vector& explain) { + if (m.is_bool(n->get_expr())) { + literal lit = get_literal(n->get_expr()); + switch (get_assignment(lit)) { + case l_true: + val = m.mk_true(); explain.push_back(lit); return true; + case l_false: + val = m.mk_false(); explain.push_back(~lit); return true; + default: + return false; + } + } + theory_var_list * l = n->get_th_var_list(); + while (l) { + theory_id tid = l->get_id(); + auto* p = m_theories.get_plugin(tid); + if (p && p->is_fixed_propagated(l->get_var(), val, explain)) + return true; + l = l->get_next(); + } + return false; + } + + void context::push() { pop_to_base_lvl(); setup_context(false); diff --git a/src/smt/smt_context.h b/src/smt/smt_context.h index 700c2f211..fd6c12482 100644 --- a/src/smt/smt_context.h +++ b/src/smt/smt_context.h @@ -1723,12 +1723,18 @@ namespace smt { m_user_propagator->register_diseq(diseq_eh); } - unsigned user_propagate_register(expr* e) { + unsigned user_propagate_register_expr(expr* e) { if (!m_user_propagator) throw default_exception("user propagator must be initialized"); return m_user_propagator->add_expr(e); } - + + void user_propagate_register_created(user_propagator::created_eh_t& r) { + if (!m_user_propagator) + throw default_exception("user propagator must be initialized"); + m_user_propagator->register_created(r); + } + bool watches_fixed(enode* n) const; void assign_fixed(enode* n, expr* val, unsigned sz, literal const* explain); @@ -1741,6 +1747,8 @@ namespace smt { assign_fixed(n, val, 1, &explain); } + bool is_fixed(enode* n, expr_ref& val, literal_vector& explain); + void display(std::ostream & out) const; void display_unsat_core(std::ostream & out) const; diff --git a/src/smt/smt_enode.h b/src/smt/smt_enode.h index 6e739a499..8995f7fba 100644 --- a/src/smt/smt_enode.h +++ b/src/smt/smt_enode.h @@ -158,7 +158,6 @@ namespace smt { void mark_as_interpreted() { SASSERT(!m_interpreted); - SASSERT(m_owner->get_num_args() == 0); SASSERT(m_class_size == 1); m_interpreted = true; } diff --git a/src/smt/smt_internalizer.cpp b/src/smt/smt_internalizer.cpp index 77b3f14d5..1c899ef18 100644 --- a/src/smt/smt_internalizer.cpp +++ b/src/smt/smt_internalizer.cpp @@ -974,7 +974,7 @@ namespace smt { } enode * e = enode::mk(m, m_region, m_app2enode, n, generation, suppress_args, merge_tf, m_scope_lvl, cgc_enabled, true); TRACE("mk_enode_detail", tout << "e.get_num_args() = " << e->get_num_args() << "\n";); - if (n->get_num_args() == 0 && m.is_unique_value(n)) + if (m.is_unique_value(n)) e->mark_as_interpreted(); TRACE("mk_var_bug", tout << "mk_enode: " << id << "\n";); TRACE("generation", tout << "mk_enode: " << id << " " << generation << "\n";); diff --git a/src/smt/smt_kernel.cpp b/src/smt/smt_kernel.cpp index 4b321fd4a..4a2c8bae9 100644 --- a/src/smt/smt_kernel.cpp +++ b/src/smt/smt_kernel.cpp @@ -32,31 +32,11 @@ namespace smt { m_kernel(m, fp, p), m_params(p) { } - - static void copy(imp& src, imp& dst) { - context::copy(src.m_kernel, dst.m_kernel); - } - - smt_params & fparams() { - return m_kernel.get_fparams(); - } - - params_ref const & params() { - return m_params; - } ast_manager & m() const { return m_kernel.get_manager(); } - bool set_logic(symbol logic) { - return m_kernel.set_logic(logic); - } - - void set_progress_callback(progress_callback * callback) { - return m_kernel.set_progress_callback(callback); - } - void display(std::ostream & out) const { // m_kernel.display(out); <<< for external users it is just junk // TODO: it will be replaced with assertion_stack.display @@ -67,187 +47,7 @@ namespace smt { out << "\n " << mk_ismt2_pp(f, m(), 2); } out << ")"; - } - - void assert_expr(expr * e) { - TRACE("smt_kernel", tout << "assert:\n" << mk_ismt2_pp(e, m()) << "\n";); - m_kernel.assert_expr(e); - } - - void assert_expr(expr * e, proof * pr) { - m_kernel.assert_expr(e, pr); - } - - unsigned size() const { - return m_kernel.get_num_asserted_formulas(); - } - - void get_formulas(ptr_vector& fmls) const { - m_kernel.get_asserted_formulas(fmls); - } - - expr* get_formula(unsigned i) const { - return m_kernel.get_asserted_formula(i); - } - - void push() { - TRACE("smt_kernel", tout << "push()\n";); - m_kernel.push(); - } - - void pop(unsigned num_scopes) { - TRACE("smt_kernel", tout << "pop()\n";); - m_kernel.pop(num_scopes); - } - - unsigned get_scope_level() const { - return m_kernel.get_scope_level(); - } - - lbool setup_and_check() { - return m_kernel.setup_and_check(); - } - - bool inconsistent() { - return m_kernel.inconsistent(); - } - - lbool check(unsigned num_assumptions, expr * const * assumptions) { - return m_kernel.check(num_assumptions, assumptions); - } - - lbool check(expr_ref_vector const& cube, vector const& clause) { - return m_kernel.check(cube, clause); - } - - lbool get_consequences(expr_ref_vector const& assumptions, expr_ref_vector const& vars, expr_ref_vector& conseq, expr_ref_vector& unfixed) { - return m_kernel.get_consequences(assumptions, vars, conseq, unfixed); - } - - lbool preferred_sat(expr_ref_vector const& asms, vector& cores) { - return m_kernel.preferred_sat(asms, cores); - } - - lbool find_mutexes(expr_ref_vector const& vars, vector& mutexes) { - return m_kernel.find_mutexes(vars, mutexes); - } - - void get_model(model_ref & m) { - m_kernel.get_model(m); - } - - proof * get_proof() { - return m_kernel.get_proof(); - } - - unsigned get_unsat_core_size() const { - return m_kernel.get_unsat_core_size(); - } - - expr * get_unsat_core_expr(unsigned idx) const { - return m_kernel.get_unsat_core_expr(idx); - } - - void get_levels(ptr_vector const& vars, unsigned_vector& depth) { - m_kernel.get_levels(vars, depth); - } - - expr_ref_vector get_trail() { - return m_kernel.get_trail(); - } - - failure last_failure() const { - return m_kernel.get_last_search_failure(); - } - - std::string last_failure_as_string() const { - return m_kernel.last_failure_as_string(); - } - - void set_reason_unknown(char const* msg) { - m_kernel.set_reason_unknown(msg); - } - - void get_assignments(expr_ref_vector & result) { - m_kernel.get_assignments(result); - } - - void get_relevant_labels(expr * cnstr, buffer & result) { - m_kernel.get_relevant_labels(cnstr, result); - } - - void get_relevant_labeled_literals(bool at_lbls, expr_ref_vector & result) { - m_kernel.get_relevant_labeled_literals(at_lbls, result); - } - - void get_relevant_literals(expr_ref_vector & result) { - m_kernel.get_relevant_literals(result); - } - - void get_guessed_literals(expr_ref_vector & result) { - m_kernel.get_guessed_literals(result); - } - - expr_ref next_cube() { - lookahead lh(m_kernel); - return lh.choose(); - } - - expr_ref_vector cubes(unsigned depth) { - lookahead lh(m_kernel); - return lh.choose_rec(depth); - } - - void collect_statistics(::statistics & st) const { - m_kernel.collect_statistics(st); - } - - void reset_statistics() { - } - - void display_statistics(std::ostream & out) const { - m_kernel.display_statistics(out); - } - - void display_istatistics(std::ostream & out) const { - m_kernel.display_istatistics(out); - } - - bool canceled() { - return m_kernel.get_cancel_flag(); - } - - void updt_params(params_ref const & p) { - m_kernel.updt_params(p); - } - - void user_propagate_init( - void* ctx, - user_propagator::push_eh_t& push_eh, - user_propagator::pop_eh_t& pop_eh, - user_propagator::fresh_eh_t& fresh_eh) { - m_kernel.user_propagate_init(ctx, push_eh, pop_eh, fresh_eh); - } - - void user_propagate_register_final(user_propagator::final_eh_t& final_eh) { - m_kernel.user_propagate_register_final(final_eh); - } - - void user_propagate_register_fixed(user_propagator::fixed_eh_t& fixed_eh) { - m_kernel.user_propagate_register_fixed(fixed_eh); - } - - void user_propagate_register_eq(user_propagator::eq_eh_t& eq_eh) { - m_kernel.user_propagate_register_eq(eq_eh); - } - - void user_propagate_register_diseq(user_propagator::eq_eh_t& diseq_eh) { - m_kernel.user_propagate_register_diseq(diseq_eh); - } - - unsigned user_propagate_register(expr* e) { - return m_kernel.user_propagate_register(e); - } + } }; @@ -260,148 +60,148 @@ namespace smt { } ast_manager & kernel::m() const { - return m_imp->m(); + return m_imp->m_kernel.get_manager(); } void kernel::copy(kernel& src, kernel& dst) { - imp::copy(*src.m_imp, *dst.m_imp); + context::copy(src.m_imp->m_kernel, dst.m_imp->m_kernel); } bool kernel::set_logic(symbol logic) { - return m_imp->set_logic(logic); + return m_imp->m_kernel.set_logic(logic); } void kernel::set_progress_callback(progress_callback * callback) { - m_imp->set_progress_callback(callback); + m_imp->m_kernel.set_progress_callback(callback); } void kernel::assert_expr(expr * e) { - m_imp->assert_expr(e); + m_imp->m_kernel.assert_expr(e); } void kernel::assert_expr(expr_ref_vector const& es) { - for (unsigned i = 0; i < es.size(); ++i) { - m_imp->assert_expr(es[i]); - } + for (unsigned i = 0; i < es.size(); ++i) + m_imp->m_kernel.assert_expr(es[i]); } void kernel::assert_expr(expr * e, proof * pr) { - m_imp->assert_expr(e, pr); + m_imp->m_kernel.assert_expr(e, pr); } unsigned kernel::size() const { - return m_imp->size(); + return m_imp->m_kernel.get_num_asserted_formulas(); } expr* kernel::get_formula(unsigned i) const { - return m_imp->get_formula(i); + return m_imp->m_kernel.get_asserted_formula(i); } - void kernel::push() { - m_imp->push(); + m_imp->m_kernel.push(); } void kernel::pop(unsigned num_scopes) { - m_imp->pop(num_scopes); + m_imp->m_kernel.pop(num_scopes); } unsigned kernel::get_scope_level() const { - return m_imp->get_scope_level(); + return m_imp->m_kernel.get_scope_level(); } void kernel::reset() { ast_manager & _m = m(); - smt_params & fps = m_imp->fparams(); - params_ref ps = m_imp->params(); + smt_params& fps = m_imp->m_kernel.get_fparams(); + params_ref ps = m_imp->m_params; m_imp->~imp(); m_imp = new (m_imp) imp(_m, fps, ps); } bool kernel::inconsistent() { - return m_imp->inconsistent(); + return m_imp->m_kernel.inconsistent(); } lbool kernel::setup_and_check() { - return m_imp->setup_and_check(); + return m_imp->m_kernel.setup_and_check(); } lbool kernel::check(unsigned num_assumptions, expr * const * assumptions) { - lbool r = m_imp->check(num_assumptions, assumptions); + lbool r = m_imp->m_kernel.check(num_assumptions, assumptions); TRACE("smt_kernel", tout << "check result: " << r << "\n";); return r; } lbool kernel::check(expr_ref_vector const& cube, vector const& clauses) { - return m_imp->check(cube, clauses); + return m_imp->m_kernel.check(cube, clauses); } lbool kernel::get_consequences(expr_ref_vector const& assumptions, expr_ref_vector const& vars, expr_ref_vector& conseq, expr_ref_vector& unfixed) { - return m_imp->get_consequences(assumptions, vars, conseq, unfixed); + return m_imp->m_kernel.get_consequences(assumptions, vars, conseq, unfixed); } lbool kernel::preferred_sat(expr_ref_vector const& asms, vector& cores) { - return m_imp->preferred_sat(asms, cores); + return m_imp->m_kernel.preferred_sat(asms, cores); } lbool kernel::find_mutexes(expr_ref_vector const& vars, vector& mutexes) { - return m_imp->find_mutexes(vars, mutexes); + return m_imp->m_kernel.find_mutexes(vars, mutexes); } void kernel::get_model(model_ref & m) { - m_imp->get_model(m); + m_imp->m_kernel.get_model(m); } proof * kernel::get_proof() { - return m_imp->get_proof(); + return m_imp->m_kernel.get_proof(); } unsigned kernel::get_unsat_core_size() const { - return m_imp->get_unsat_core_size(); + return m_imp->m_kernel.get_unsat_core_size(); } expr * kernel::get_unsat_core_expr(unsigned idx) const { - return m_imp->get_unsat_core_expr(idx); + return m_imp->m_kernel.get_unsat_core_expr(idx); } failure kernel::last_failure() const { - return m_imp->last_failure(); + return m_imp->m_kernel.get_last_search_failure(); } std::string kernel::last_failure_as_string() const { - return m_imp->last_failure_as_string(); + return m_imp->m_kernel.last_failure_as_string(); } void kernel::set_reason_unknown(char const* msg) { - m_imp->set_reason_unknown(msg); + m_imp->m_kernel.set_reason_unknown(msg); } void kernel::get_assignments(expr_ref_vector & result) { - m_imp->get_assignments(result); + m_imp->m_kernel.get_assignments(result); } void kernel::get_relevant_labels(expr * cnstr, buffer & result) { - m_imp->get_relevant_labels(cnstr, result); + m_imp->m_kernel.get_relevant_labels(cnstr, result); } void kernel::get_relevant_labeled_literals(bool at_lbls, expr_ref_vector & result) { - m_imp->get_relevant_labeled_literals(at_lbls, result); + m_imp->m_kernel.get_relevant_labeled_literals(at_lbls, result); } void kernel::get_relevant_literals(expr_ref_vector & result) { - m_imp->get_relevant_literals(result); + m_imp->m_kernel.get_relevant_literals(result); } void kernel::get_guessed_literals(expr_ref_vector & result) { - m_imp->get_guessed_literals(result); + m_imp->m_kernel.get_guessed_literals(result); } expr_ref kernel::next_cube() { - return m_imp->next_cube(); + lookahead lh(m_imp->m_kernel); + return lh.choose(); } expr_ref_vector kernel::cubes(unsigned depth) { - return m_imp->cubes(depth); + lookahead lh(m_imp->m_kernel); + return lh.choose_rec(depth); } std::ostream& kernel::display(std::ostream & out) const { @@ -410,27 +210,26 @@ namespace smt { } void kernel::collect_statistics(::statistics & st) const { - m_imp->collect_statistics(st); + m_imp->m_kernel.collect_statistics(st); } void kernel::reset_statistics() { - m_imp->reset_statistics(); } void kernel::display_statistics(std::ostream & out) const { - m_imp->display_statistics(out); + m_imp->m_kernel.display_statistics(out); } void kernel::display_istatistics(std::ostream & out) const { - m_imp->display_istatistics(out); + m_imp->m_kernel.display_istatistics(out); } bool kernel::canceled() const { - return m_imp->canceled(); + return m_imp->m_kernel.get_cancel_flag(); } void kernel::updt_params(params_ref const & p) { - return m_imp->updt_params(p); + return m_imp->m_kernel.updt_params(p); } void kernel::collect_param_descrs(param_descrs & d) { @@ -442,11 +241,11 @@ namespace smt { } void kernel::get_levels(ptr_vector const& vars, unsigned_vector& depth) { - m_imp->get_levels(vars, depth); + m_imp->m_kernel.get_levels(vars, depth); } expr_ref_vector kernel::get_trail() { - return m_imp->get_trail(); + return m_imp->m_kernel.get_trail(); } void kernel::user_propagate_init( @@ -454,27 +253,31 @@ namespace smt { user_propagator::push_eh_t& push_eh, user_propagator::pop_eh_t& pop_eh, user_propagator::fresh_eh_t& fresh_eh) { - m_imp->user_propagate_init(ctx, push_eh, pop_eh, fresh_eh); + m_imp->m_kernel.user_propagate_init(ctx, push_eh, pop_eh, fresh_eh); } void kernel::user_propagate_register_fixed(user_propagator::fixed_eh_t& fixed_eh) { - m_imp->user_propagate_register_fixed(fixed_eh); + m_imp->m_kernel.user_propagate_register_fixed(fixed_eh); } void kernel::user_propagate_register_final(user_propagator::final_eh_t& final_eh) { - m_imp->user_propagate_register_final(final_eh); + m_imp->m_kernel.user_propagate_register_final(final_eh); } void kernel::user_propagate_register_eq(user_propagator::eq_eh_t& eq_eh) { - m_imp->user_propagate_register_eq(eq_eh); + m_imp->m_kernel.user_propagate_register_eq(eq_eh); } void kernel::user_propagate_register_diseq(user_propagator::eq_eh_t& diseq_eh) { - m_imp->user_propagate_register_diseq(diseq_eh); + m_imp->m_kernel.user_propagate_register_diseq(diseq_eh); } - unsigned kernel::user_propagate_register(expr* e) { - return m_imp->user_propagate_register(e); + unsigned kernel::user_propagate_register_expr(expr* e) { + return m_imp->m_kernel.user_propagate_register_expr(e); } -}; + void kernel::user_propagate_register_created(user_propagator::created_eh_t& r) { + m_imp->m_kernel.user_propagate_register_created(r); + } + +}; \ No newline at end of file diff --git a/src/smt/smt_kernel.h b/src/smt/smt_kernel.h index 2259dd997..681bdd55b 100644 --- a/src/smt/smt_kernel.h +++ b/src/smt/smt_kernel.h @@ -301,22 +301,19 @@ namespace smt { void user_propagate_register_diseq(user_propagator::eq_eh_t& diseq_eh); - - /** - \brief register an expression to be tracked fro user propagation. - */ - unsigned user_propagate_register(expr* e); + unsigned user_propagate_register_expr(expr* e); + void user_propagate_register_created(user_propagator::created_eh_t& r); /** \brief Return a reference to smt::context. - This is a temporary hack to support user theories. - TODO: remove this hack. - We need to revamp user theories too. + This breaks abstractions. + + It is currently used by the opt-solver + to access optimization services from arithmetic solvers + and to ensure that the solver has registered PB theory solver. - This method breaks the abstraction barrier. - - \warning We should not use this method + \warning This method should not be used in new code. */ context & get_context(); }; diff --git a/src/smt/smt_relevancy.cpp b/src/smt/smt_relevancy.cpp index 6d20072cb..4b787561d 100644 --- a/src/smt/smt_relevancy.cpp +++ b/src/smt/smt_relevancy.cpp @@ -134,11 +134,11 @@ namespace smt { obj_map m_relevant_ehs; obj_map m_watches[2]; struct eh_trail { - enum kind { POS_WATCH, NEG_WATCH, HANDLER }; + enum class kind { POS_WATCH, NEG_WATCH, HANDLER }; kind m_kind; expr * m_node; - eh_trail(expr * n):m_kind(HANDLER), m_node(n) {} - eh_trail(expr * n, bool val):m_kind(val ? POS_WATCH : NEG_WATCH), m_node(n) {} + eh_trail(expr * n):m_kind(kind::HANDLER), m_node(n) {} + eh_trail(expr * n, bool val):m_kind(val ? kind::POS_WATCH : kind::NEG_WATCH), m_node(n) {} kind get_kind() const { return m_kind; } expr * get_node() const { return m_node; } }; @@ -292,9 +292,9 @@ namespace smt { expr * n = t.get_node(); relevancy_ehs * ehs; switch (t.get_kind()) { - case eh_trail::POS_WATCH: ehs = get_watches(n, true); SASSERT(ehs); set_watches(n, true, ehs->tail()); break; - case eh_trail::NEG_WATCH: ehs = get_watches(n, false); SASSERT(ehs); set_watches(n, false, ehs->tail()); break; - case eh_trail::HANDLER: ehs = get_handlers(n); SASSERT(ehs); set_handlers(n, ehs->tail()); break; + case eh_trail::kind::POS_WATCH: ehs = get_watches(n, true); SASSERT(ehs); set_watches(n, true, ehs->tail()); break; + case eh_trail::kind::NEG_WATCH: ehs = get_watches(n, false); SASSERT(ehs); set_watches(n, false, ehs->tail()); break; + case eh_trail::kind::HANDLER: ehs = get_handlers(n); SASSERT(ehs); set_handlers(n, ehs->tail()); break; default: UNREACHABLE(); break; } m.dec_ref(n); @@ -378,9 +378,7 @@ namespace smt { break; case l_true: { expr * true_arg = nullptr; - unsigned num_args = n->get_num_args(); - for (unsigned i = 0; i < num_args; i++) { - expr * arg = n->get_arg(i); + for (expr* arg : *n) { if (m_context.find_assignment(arg) == l_true) { if (is_relevant_core(arg)) return; @@ -402,9 +400,7 @@ namespace smt { switch (val) { case l_false: { expr * false_arg = nullptr; - unsigned num_args = n->get_num_args(); - for (unsigned i = 0; i < num_args; i++) { - expr * arg = n->get_arg(i); + for (expr* arg : *n) { if (m_context.find_assignment(arg) == l_false) { if (is_relevant_core(arg)) return; diff --git a/src/smt/smt_setup.cpp b/src/smt/smt_setup.cpp index ba601272d..2d131c6ab 100644 --- a/src/smt/smt_setup.cpp +++ b/src/smt/smt_setup.cpp @@ -643,8 +643,8 @@ namespace smt { // It destroys the existing patterns. // m_params.m_macro_finder = true; - if (m_params.m_ng_lift_ite == LI_NONE) - m_params.m_ng_lift_ite = LI_CONSERVATIVE; + if (m_params.m_ng_lift_ite == lift_ite_kind::LI_NONE) + m_params.m_ng_lift_ite = lift_ite_kind::LI_CONSERVATIVE; TRACE("setup", tout << "max_eager_multipatterns: " << m_params.m_qi_max_eager_multipatterns << "\n";); m_context.register_plugin(alloc(smt::theory_i_arith, m_context)); setup_arrays(); @@ -668,8 +668,8 @@ namespace smt { m_params.m_qi_lazy_threshold = 20; // m_params.m_macro_finder = true; - if (m_params.m_ng_lift_ite == LI_NONE) - m_params.m_ng_lift_ite = LI_CONSERVATIVE; + if (m_params.m_ng_lift_ite == lift_ite_kind::LI_NONE) + m_params.m_ng_lift_ite = lift_ite_kind::LI_CONSERVATIVE; m_params.m_pi_max_multi_patterns = 10; //<< it was used for SMT-COMP m_params.m_array_lazy_ieq = true; m_params.m_array_lazy_ieq_delay = 4; diff --git a/src/smt/smt_solver.cpp b/src/smt/smt_solver.cpp index 9352e33f4..03f55585a 100644 --- a/src/smt/smt_solver.cpp +++ b/src/smt/smt_solver.cpp @@ -124,7 +124,7 @@ namespace { smt_params m_smt_params_save; void push_params() override { - m_params_save = params_ref(); + m_params_save.reset(); m_params_save.copy(solver::get_params()); m_smt_params_save = m_smt_params; } @@ -200,7 +200,6 @@ namespace { return m_context.check(num_assumptions, assumptions); } - lbool check_sat_cc_core(expr_ref_vector const& cube, vector const& clauses) override { return m_context.check(cube, clauses); } @@ -237,8 +236,12 @@ namespace { m_context.user_propagate_register_diseq(diseq_eh); } - unsigned user_propagate_register(expr* e) override { - return m_context.user_propagate_register(e); + unsigned user_propagate_register_expr(expr* e) override { + return m_context.user_propagate_register_expr(e); + } + + void user_propagate_register_created(user_propagator::created_eh_t& c) override { + m_context.user_propagate_register_created(c); } struct scoped_minimize_core { diff --git a/src/smt/smt_theory.h b/src/smt/smt_theory.h index 500c47f5e..d723bfee8 100644 --- a/src/smt/smt_theory.h +++ b/src/smt/smt_theory.h @@ -615,6 +615,12 @@ namespace smt { bool is_relevant_and_shared(enode * n) const; bool assume_eq(enode * n1, enode * n2); + + + /** + * \brief theory plugin for fixed values. + */ + virtual bool is_fixed_propagated(theory_var v, expr_ref& val, literal_vector & explain) { return false; } }; }; diff --git a/src/smt/tactic/smt_tactic_core.cpp b/src/smt/tactic/smt_tactic_core.cpp index 5cbe469fd..bf2ea9bd6 100644 --- a/src/smt/tactic/smt_tactic_core.cpp +++ b/src/smt/tactic/smt_tactic_core.cpp @@ -127,7 +127,8 @@ public: scoped_init_ctx(smt_tactic & o, ast_manager & m):m_owner(o) { m_params = o.fparams(); - m_params_ref = o.params(); + m_params_ref.reset(); + m_params_ref.append(o.params()); smt::kernel * new_ctx = alloc(smt::kernel, m, m_params, m_params_ref); TRACE("smt_tactic", tout << "logic: " << o.m_logic << "\n";); new_ctx->set_logic(o.m_logic); @@ -319,15 +320,21 @@ public: user_propagator::final_eh_t m_final_eh; user_propagator::eq_eh_t m_eq_eh; user_propagator::eq_eh_t m_diseq_eh; + user_propagator::created_eh_t m_created_eh; + expr_ref_vector m_vars; unsigned_vector m_var2internal; unsigned_vector m_internal2var; + unsigned_vector m_limit; + + user_propagator::push_eh_t i_push_eh; + user_propagator::pop_eh_t i_pop_eh; user_propagator::fixed_eh_t i_fixed_eh; user_propagator::final_eh_t i_final_eh; user_propagator::eq_eh_t i_eq_eh; user_propagator::eq_eh_t i_diseq_eh; - + user_propagator::created_eh_t i_created_eh; struct callback : public user_propagator::callback { @@ -403,19 +410,48 @@ public: m_ctx->user_propagate_register_diseq(i_diseq_eh); } + void init_i_created_eh() { + if (!m_created_eh) + return; + i_created_eh = [this](void* ctx, user_propagator::callback* cb, expr* e, unsigned i) { + unsigned j = m_vars.size(); + m_vars.push_back(e); + m_internal2var.setx(i, j, 0); + m_var2internal.setx(j, i, 0); + m_created_eh(ctx, cb, e, j); + }; + m_ctx->user_propagate_register_created(i_created_eh); + } + + void init_i_push_pop() { + i_push_eh = [this](void* ctx) { + m_limit.push_back(m_vars.size()); + m_push_eh(ctx); + }; + i_pop_eh = [this](void* ctx, unsigned n) { + unsigned old_sz = m_limit.size() - n; + unsigned num_vars = m_limit[old_sz]; + m_vars.shrink(num_vars); + m_limit.shrink(old_sz); + m_pop_eh(ctx, n); + }; + } + void user_propagate_delay_init() { if (!m_user_ctx) return; - m_ctx->user_propagate_init(m_user_ctx, m_push_eh, m_pop_eh, m_fresh_eh); + init_i_push_pop(); + m_ctx->user_propagate_init(m_user_ctx, i_push_eh, i_pop_eh, m_fresh_eh); init_i_fixed_eh(); init_i_final_eh(); init_i_eq_eh(); init_i_diseq_eh(); + init_i_created_eh(); unsigned i = 0; for (expr* v : m_vars) { - unsigned j = m_ctx->user_propagate_register(v); + unsigned j = m_ctx->user_propagate_register_expr(v); m_var2internal.setx(i, j, 0); m_internal2var.setx(j, i, 0); ++i; @@ -429,6 +465,7 @@ public: m_final_eh = nullptr; m_eq_eh = nullptr; m_diseq_eh = nullptr; + m_created_eh = nullptr; } void user_propagate_init( @@ -459,10 +496,14 @@ public: m_diseq_eh = diseq_eh; } - unsigned user_propagate_register(expr* e) override { + unsigned user_propagate_register_expr(expr* e) override { m_vars.push_back(e); return m_vars.size() - 1; } + + void user_propagate_register_created(user_propagator::created_eh_t& created_eh) override { + m_created_eh = created_eh; + } }; static tactic * mk_seq_smt_tactic(ast_manager& m, params_ref const & p) { diff --git a/src/smt/tactic/unit_subsumption_tactic.cpp b/src/smt/tactic/unit_subsumption_tactic.cpp index 97ce4a1c1..e0197d15d 100644 --- a/src/smt/tactic/unit_subsumption_tactic.cpp +++ b/src/smt/tactic/unit_subsumption_tactic.cpp @@ -50,7 +50,7 @@ struct unit_subsumption_tactic : public tactic { } void updt_params(params_ref const& p) override { - m_params = p; + m_params.append(p); // m_context.updt_params(p); does not exist. } diff --git a/src/smt/theory_bv.cpp b/src/smt/theory_bv.cpp index c714b17f3..28ffebb1d 100644 --- a/src/smt/theory_bv.cpp +++ b/src/smt/theory_bv.cpp @@ -519,6 +519,21 @@ namespace smt { } } + bool theory_bv::is_fixed_propagated(theory_var v, expr_ref& val, literal_vector& lits) { + numeral r; + enode* n = get_enode(v); + if (!get_fixed_value(v, r)) + return false; + val = m_util.mk_numeral(r, n->get_sort()); + for (literal b : m_bits[v]) { + if (ctx.get_assignment(b) == l_false) + b.neg(); + lits.push_back(b); + } + return true; + } + + bool theory_bv::get_fixed_value(theory_var v, numeral & result) const { result.reset(); unsigned i = 0; diff --git a/src/smt/theory_bv.h b/src/smt/theory_bv.h index 931e3041a..ebca3fa83 100644 --- a/src/smt/theory_bv.h +++ b/src/smt/theory_bv.h @@ -282,6 +282,7 @@ namespace smt { void collect_statistics(::statistics & st) const override; bool get_fixed_value(app* x, numeral & result) const; + bool is_fixed_propagated(theory_var v, expr_ref& val, literal_vector& explain) override; bool check_assignment(theory_var v); bool check_invariant(); diff --git a/src/smt/theory_recfun.cpp b/src/smt/theory_recfun.cpp index 1bf987008..782510681 100644 --- a/src/smt/theory_recfun.cpp +++ b/src/smt/theory_recfun.cpp @@ -55,7 +55,7 @@ namespace smt { ctx.internalize(arg, false); } if (!ctx.e_internalized(atom)) { - ctx.mk_enode(atom, false, true, false); + ctx.mk_enode(atom, false, true, true); } if (!ctx.b_internalized(atom)) { bool_var v = ctx.mk_bool_var(atom); @@ -214,7 +214,7 @@ namespace smt { void theory_recfun::assign_eh(bool_var v, bool is_true) { expr* e = ctx.bool_var2expr(v); if (is_true && u().is_case_pred(e)) { - TRACEFN("assign_case_pred_true " << mk_pp(e, m)); + TRACEFN("assign_case_pred_true " << v << " " << mk_pp(e, m)); // body-expand push_body_expand(e); } @@ -343,6 +343,7 @@ namespace smt { activate_guard(pred_applied, guards); } + TRACEFN("assert core " << preds); // the disjunction of branches is asserted // to close the available cases. diff --git a/src/smt/theory_seq.cpp b/src/smt/theory_seq.cpp index 1564a2015..498cfbd1e 100644 --- a/src/smt/theory_seq.cpp +++ b/src/smt/theory_seq.cpp @@ -349,7 +349,7 @@ final_check_status theory_seq::final_check_eh() { TRACEFIN("propagate_contains"); return FC_CONTINUE; } - if (fixed_length(true)) { + if (check_fixed_length(true, false)) { ++m_stats.m_fixed_length; TRACEFIN("zero_length"); return FC_CONTINUE; @@ -359,7 +359,7 @@ final_check_status theory_seq::final_check_eh() { TRACEFIN("split_based_on_length"); return FC_CONTINUE; } - if (fixed_length()) { + if (check_fixed_length(false, false)) { ++m_stats.m_fixed_length; TRACEFIN("fixed_length"); return FC_CONTINUE; @@ -413,6 +413,11 @@ final_check_status theory_seq::final_check_eh() { TRACEFIN("branch_itos"); return FC_CONTINUE; } + if (check_fixed_length(false, true)) { + ++m_stats.m_fixed_length; + TRACEFIN("fixed_length"); + return FC_CONTINUE; + } if (m_unhandled_expr) { TRACEFIN("give_up"); TRACE("seq", tout << "unhandled: " << mk_pp(m_unhandled_expr, m) << "\n";); @@ -461,18 +466,18 @@ bool theory_seq::enforce_length(expr_ref_vector const& es, vector & le } -bool theory_seq::fixed_length(bool is_zero) { +bool theory_seq::check_fixed_length(bool is_zero, bool check_long_strings) { bool found = false; for (unsigned i = 0; i < m_length.size(); ++i) { expr* e = m_length.get(i); - if (fixed_length(e, is_zero)) { + if (fixed_length(e, is_zero, check_long_strings)) { found = true; } } return found; } -bool theory_seq::fixed_length(expr* len_e, bool is_zero) { +bool theory_seq::fixed_length(expr* len_e, bool is_zero, bool check_long_strings) { rational lo, hi; expr* e = nullptr; VERIFY(m_util.str.is_length(len_e, e)); @@ -493,12 +498,21 @@ bool theory_seq::fixed_length(expr* len_e, bool is_zero) { expr_ref seq(e, m), head(m), tail(m); + + TRACE("seq", tout << "Fixed: " << mk_bounded_pp(e, m, 2) << " " << lo << "\n";); + literal a = mk_eq(len_e, m_autil.mk_numeral(lo, true), false); + if (ctx.get_assignment(a) == l_false) + return false; + + if (!check_long_strings && lo > 20 && !is_zero) + return false; + if (lo.is_zero()) { seq = m_util.str.mk_empty(e->get_sort()); } else if (!is_zero) { unsigned _lo = lo.get_unsigned(); - expr_ref_vector elems(m); + expr_ref_vector elems(m); for (unsigned j = 0; j < _lo; ++j) { m_sk.decompose(seq, head, tail); elems.push_back(head); @@ -506,10 +520,6 @@ bool theory_seq::fixed_length(expr* len_e, bool is_zero) { } seq = mk_concat(elems.size(), elems.data()); } - TRACE("seq", tout << "Fixed: " << mk_bounded_pp(e, m, 2) << " " << lo << "\n";); - literal a = mk_eq(len_e, m_autil.mk_numeral(lo, true), false); - if (ctx.get_assignment(a) == l_false) - return false; literal b = mk_seq_eq(seq, e); if (ctx.get_assignment(b) == l_true) return false; @@ -2636,6 +2646,9 @@ void theory_seq::deque_axiom(expr* n) { else if (m_util.str.is_replace(n)) { m_ax.add_replace_axiom(n); } + else if (m_util.str.is_replace_all(n)) { + m_ax.add_replace_all_axiom(n); + } else if (m_util.str.is_extract(n)) { m_ax.add_extract_axiom(n); } @@ -2873,7 +2886,7 @@ void theory_seq::add_axiom(literal_vector & lits) { for (literal lit : lits) ctx.mark_as_relevant(lit); - IF_VERBOSE(10, verbose_stream() << "ax "; + IF_VERBOSE(10, verbose_stream() << "ax"; for (literal l : lits) ctx.display_literal_smt2(verbose_stream() << " ", l); verbose_stream() << "\n"); m_new_propagation = true; @@ -3014,10 +3027,6 @@ void theory_seq::assign_eh(bool_var v, bool is_true) { if (is_true) m_regex.propagate_is_empty(lit); } - else if (m_sk.is_is_non_empty(e)) { - if (is_true) - m_regex.propagate_is_non_empty(lit); - } else if (m_sk.is_eq(e, e1, e2)) { if (is_true) { propagate_eq(lit, e1, e2, true); @@ -3037,6 +3046,10 @@ void theory_seq::assign_eh(bool_var v, bool is_true) { propagate_length_limit(e); } } + else if (m_sk.is_is_non_empty(e)) { + if (is_true) + m_regex.propagate_is_non_empty(lit); + } else if (m_util.str.is_lt(e) || m_util.str.is_le(e)) { m_lts.push_back(e); } @@ -3180,6 +3193,7 @@ void theory_seq::relevant_eh(app* n) { m_util.str.is_to_code(n) || m_util.str.is_unit(n) || m_util.str.is_length(n) || + /* m_util.str.is_replace_all(n) || uncomment to enable axiomatization */ m_util.str.is_le(n)) { enque_axiom(n); } diff --git a/src/smt/theory_seq.h b/src/smt/theory_seq.h index 68e9f3762..4b4c1d80b 100644 --- a/src/smt/theory_seq.h +++ b/src/smt/theory_seq.h @@ -243,7 +243,7 @@ namespace smt { replay_fixed_length(ast_manager& m, expr* e) : m_e(e, m) {} ~replay_fixed_length() override {} void operator()(theory_seq& th) override { - th.fixed_length(m_e); + th.fixed_length(m_e, false, false); m_e.reset(); } }; @@ -436,8 +436,8 @@ namespace smt { bool check_length_coherence(); bool check_length_coherence0(expr* e); bool check_length_coherence(expr* e); - bool fixed_length(bool is_zero = false); - bool fixed_length(expr* e, bool is_zero); + bool check_fixed_length(bool is_zero, bool check_long_strings); + bool fixed_length(expr* e, bool is_zero, bool check_long_strings); bool branch_variable_eq(depeq const& e); bool branch_binary_variable(depeq const& e); bool can_align_from_lhs(expr_ref_vector const& ls, expr_ref_vector const& rs); diff --git a/src/smt/theory_user_propagator.cpp b/src/smt/theory_user_propagator.cpp index 2b50e07ab..5e5bd1e1d 100644 --- a/src/smt/theory_user_propagator.cpp +++ b/src/smt/theory_user_propagator.cpp @@ -23,7 +23,7 @@ Author: using namespace smt; theory_user_propagator::theory_user_propagator(context& ctx): - theory(ctx, ctx.get_manager().mk_family_id("user_propagator")) + theory(ctx, ctx.get_manager().mk_family_id(user_propagator::plugin::name())) {} theory_user_propagator::~theory_user_propagator() { @@ -40,11 +40,31 @@ void theory_user_propagator::force_push() { unsigned theory_user_propagator::add_expr(expr* e) { force_push(); + expr_ref r(m); + ctx.get_rewriter()(e, r); + if (r != e) { + r = m.mk_fresh_const("aux-expr", e->get_sort()); + expr_ref eq(m.mk_eq(r, e), m); + ctx.assert_expr(eq); + ctx.internalize_assertions(); + e = r; + ctx.mark_as_relevant(eq.get()); + } enode* n = ensure_enode(e); if (is_attached_to_var(n)) return n->get_th_var(get_id()); theory_var v = mk_var(n); + if (m.is_bool(e) && !ctx.b_internalized(e)) { + bool_var bv = ctx.mk_bool_var(e); + ctx.set_var_theory(bv, get_id()); + ctx.set_enode_flag(bv, true); + } + SASSERT(!m.is_bool(e) || ctx.b_internalized(e)); + ctx.attach_th_var(n, this, v); + literal_vector explain; + if (ctx.is_fixed(n, r, explain)) + m_prop.push_back(prop_info(explain, v, r)); return v; } @@ -118,54 +138,66 @@ bool theory_user_propagator::can_propagate() { return m_qhead < m_prop.size(); } +void theory_user_propagator::propagate_consequence(prop_info const& prop) { + justification* js; + m_lits.reset(); + m_eqs.reset(); + for (unsigned id : prop.m_ids) + m_lits.append(m_id2justification[id]); + for (auto const& p : prop.m_eqs) + m_eqs.push_back(enode_pair(get_enode(p.first), get_enode(p.second))); + DEBUG_CODE(for (auto const& p : m_eqs) VERIFY(p.first->get_root() == p.second->get_root());); + DEBUG_CODE(for (unsigned id : prop.m_ids) VERIFY(m_fixed.contains(id));); + DEBUG_CODE(for (literal lit : m_lits) VERIFY(ctx.get_assignment(lit) == l_true);); + + TRACE("user_propagate", tout << "propagating #" << prop.m_conseq->get_id() << ": " << prop.m_conseq << "\n"); + + if (m.is_false(prop.m_conseq)) { + js = ctx.mk_justification( + ext_theory_conflict_justification( + get_id(), ctx.get_region(), m_lits.size(), m_lits.data(), m_eqs.size(), m_eqs.data(), 0, nullptr)); + ctx.set_conflict(js); + } + else { + for (auto& lit : m_lits) + lit.neg(); + for (auto const& [a,b] : m_eqs) + m_lits.push_back(~mk_eq(a->get_expr(), b->get_expr(), false)); + + literal lit; + if (has_quantifiers(prop.m_conseq)) { + expr_ref fn(m.mk_fresh_const("aux-literal", m.mk_bool_sort()), m); + expr_ref eq(m.mk_eq(fn, prop.m_conseq), m); + ctx.assert_expr(eq); + ctx.internalize_assertions(); + lit = mk_literal(fn); + } + else + lit = mk_literal(prop.m_conseq); + ctx.mark_as_relevant(lit); + m_lits.push_back(lit); + ctx.mk_th_lemma(get_id(), m_lits); + TRACE("user_propagate", ctx.display(tout);); + } +} + +void theory_user_propagator::propagate_new_fixed(prop_info const& prop) { + new_fixed_eh(prop.m_var, prop.m_conseq, prop.m_lits.size(), prop.m_lits.data()); +} + + void theory_user_propagator::propagate() { TRACE("user_propagate", tout << "propagating queue head: " << m_qhead << " prop queue: " << m_prop.size() << "\n"); if (m_qhead == m_prop.size()) return; force_push(); unsigned qhead = m_qhead; - justification* js; while (qhead < m_prop.size() && !ctx.inconsistent()) { auto const& prop = m_prop[qhead]; - m_lits.reset(); - m_eqs.reset(); - for (unsigned id : prop.m_ids) - m_lits.append(m_id2justification[id]); - for (auto const& p : prop.m_eqs) - m_eqs.push_back(enode_pair(get_enode(p.first), get_enode(p.second))); - DEBUG_CODE(for (auto const& p : m_eqs) VERIFY(p.first->get_root() == p.second->get_root());); - DEBUG_CODE(for (unsigned id : prop.m_ids) VERIFY(m_fixed.contains(id));); - DEBUG_CODE(for (literal lit : m_lits) VERIFY(ctx.get_assignment(lit) == l_true);); - - TRACE("user_propagate", tout << "propagating #" << prop.m_conseq->get_id() << ": " << prop.m_conseq << "\n"); - - if (m.is_false(prop.m_conseq)) { - js = ctx.mk_justification( - ext_theory_conflict_justification( - get_id(), ctx.get_region(), m_lits.size(), m_lits.data(), m_eqs.size(), m_eqs.data(), 0, nullptr)); - ctx.set_conflict(js); - } - else { - for (auto& lit : m_lits) - lit.neg(); - for (auto const& [a,b] : m_eqs) - m_lits.push_back(~mk_eq(a->get_expr(), b->get_expr(), false)); - - literal lit; - if (has_quantifiers(prop.m_conseq)) { - expr_ref fn(m.mk_fresh_const("aux-literal", m.mk_bool_sort()), m); - expr_ref eq(m.mk_eq(fn, prop.m_conseq), m); - ctx.assert_expr(eq); - ctx.internalize_assertions(); - lit = mk_literal(fn); - } - else - lit = mk_literal(prop.m_conseq); - ctx.mark_as_relevant(lit); - m_lits.push_back(lit); - ctx.mk_th_lemma(get_id(), m_lits); - TRACE("user_propagate", ctx.display(tout);); - } + if (prop.m_var == null_theory_var) + propagate_consequence(prop); + else + propagate_new_fixed(prop); ++m_stats.m_num_propagations; ++qhead; } @@ -173,6 +205,26 @@ void theory_user_propagator::propagate() { m_qhead = qhead; } + +bool theory_user_propagator::internalize_atom(app* atom, bool gate_ctx) { + return internalize_term(atom); +} + +bool theory_user_propagator::internalize_term(app* term) { + for (auto arg : *term) + ensure_enode(arg); + if (term->get_family_id() == get_id() && !ctx.e_internalized(term)) + ctx.mk_enode(term, true, false, true); + + unsigned v = add_expr(term); + + if (!m_created_eh && (m_fixed_eh || m_eq_eh || m_diseq_eh)) + throw default_exception("You have to register a created event handler for new terms if you track them"); + if (m_created_eh) + m_created_eh(m_user_context, this, term, v); + return true; +} + void theory_user_propagator::collect_statistics(::statistics & st) const { st.update("user-propagations", m_stats.m_num_propagations); st.update("user-watched", get_num_vars()); diff --git a/src/smt/theory_user_propagator.h b/src/smt/theory_user_propagator.h index d007de6a0..f1e558256 100644 --- a/src/smt/theory_user_propagator.h +++ b/src/smt/theory_user_propagator.h @@ -30,16 +30,24 @@ namespace smt { class theory_user_propagator : public theory, public user_propagator::callback { struct prop_info { - unsigned_vector m_ids; - expr_ref m_conseq; + unsigned_vector m_ids; + expr_ref m_conseq; svector> m_eqs; - prop_info(unsigned num_fixed, unsigned const* fixed_ids, unsigned num_eqs, unsigned const* eq_lhs, unsigned const* eq_rhs, expr_ref const& c): + literal_vector m_lits; + theory_var m_var = null_theory_var; + prop_info(unsigned num_fixed, unsigned const* fixed_ids, + unsigned num_eqs, unsigned const* eq_lhs, unsigned const* eq_rhs, expr_ref const& c): m_ids(num_fixed, fixed_ids), - m_conseq(c) - { + m_conseq(c) { for (unsigned i = 0; i < num_eqs; ++i) m_eqs.push_back(std::make_pair(eq_lhs[i], eq_rhs[i])); } + + prop_info(literal_vector const& lits, theory_var v, expr_ref const& val): + m_conseq(val), + m_lits(lits), + m_var(v) {} + }; struct stats { @@ -56,6 +64,8 @@ namespace smt { user_propagator::fixed_eh_t m_fixed_eh; user_propagator::eq_eh_t m_eq_eh; user_propagator::eq_eh_t m_diseq_eh; + user_propagator::created_eh_t m_created_eh; + user_propagator::context_obj* m_api_context = nullptr; unsigned m_qhead = 0; uint_set m_fixed; @@ -69,6 +79,9 @@ namespace smt { void force_push(); + void propagate_consequence(prop_info const& prop); + void propagate_new_fixed(prop_info const& prop); + public: theory_user_propagator(context& ctx); @@ -94,6 +107,7 @@ namespace smt { void register_fixed(user_propagator::fixed_eh_t& fixed_eh) { m_fixed_eh = fixed_eh; } void register_eq(user_propagator::eq_eh_t& eq_eh) { m_eq_eh = eq_eh; } void register_diseq(user_propagator::eq_eh_t& diseq_eh) { m_diseq_eh = diseq_eh; } + void register_created(user_propagator::created_eh_t& created_eh) { m_created_eh = created_eh; } bool has_fixed() const { return (bool)m_fixed_eh; } @@ -103,8 +117,8 @@ namespace smt { void new_fixed_eh(theory_var v, expr* value, unsigned num_lits, literal const* jlits); theory * mk_fresh(context * new_ctx) override; - bool internalize_atom(app * atom, bool gate_ctx) override { UNREACHABLE(); return false; } - bool internalize_term(app * term) override { UNREACHABLE(); return false; } + bool internalize_atom(app* atom, bool gate_ctx) override; + bool internalize_term(app* term) override; void new_eq_eh(theory_var v1, theory_var v2) override { if (m_eq_eh) m_eq_eh(m_user_context, this, v1, v2); } void new_diseq_eh(theory_var v1, theory_var v2) override { if (m_diseq_eh) m_diseq_eh(m_user_context, this, v1, v2); } bool use_diseqs() const override { return ((bool)m_diseq_eh); } @@ -119,7 +133,7 @@ namespace smt { void collect_statistics(::statistics & st) const override; model_value_proc * mk_value(enode * n, model_generator & mg) override { return nullptr; } void init_model(model_generator & m) override {} - bool include_func_interp(func_decl* f) override { return false; } + bool include_func_interp(func_decl* f) override { return true; } bool can_propagate() override; void propagate() override; void display(std::ostream& out) const override {} diff --git a/src/solver/assertions/CMakeLists.txt b/src/solver/assertions/CMakeLists.txt index 7cc320458..6deffd3a5 100644 --- a/src/solver/assertions/CMakeLists.txt +++ b/src/solver/assertions/CMakeLists.txt @@ -4,4 +4,5 @@ z3_add_component(solver_assertions COMPONENT_DEPENDENCIES smt2parser smt_params + qe_lite ) diff --git a/src/solver/assertions/asserted_formulas.cpp b/src/solver/assertions/asserted_formulas.cpp index b3be7e8ab..d852d9383 100644 --- a/src/solver/assertions/asserted_formulas.cpp +++ b/src/solver/assertions/asserted_formulas.cpp @@ -49,6 +49,7 @@ asserted_formulas::asserted_formulas(ast_manager & m, smt_params & sp, params_re m_refine_inj_axiom(*this), m_max_bv_sharing_fn(*this), m_elim_term_ite(*this), + m_qe_lite(*this), m_pull_nested_quantifiers(*this), m_elim_bvs_from_quantifiers(*this), m_cheap_quant_fourier_motzkin(*this), @@ -71,12 +72,12 @@ asserted_formulas::asserted_formulas(ast_manager & m, smt_params & sp, params_re void asserted_formulas::setup() { switch (m_smt_params.m_lift_ite) { - case LI_FULL: - m_smt_params.m_ng_lift_ite = LI_NONE; + case lift_ite_kind::LI_FULL: + m_smt_params.m_ng_lift_ite = lift_ite_kind::LI_NONE; break; - case LI_CONSERVATIVE: - if (m_smt_params.m_ng_lift_ite == LI_CONSERVATIVE) - m_smt_params.m_ng_lift_ite = LI_NONE; + case lift_ite_kind::LI_CONSERVATIVE: + if (m_smt_params.m_ng_lift_ite == lift_ite_kind::LI_CONSERVATIVE) + m_smt_params.m_ng_lift_ite = lift_ite_kind::LI_NONE; break; default: break; @@ -281,10 +282,11 @@ void asserted_formulas::reduce() { if (!invoke(m_reduce_asserted_formulas)) return; if (!invoke(m_pull_nested_quantifiers)) return; if (!invoke(m_lift_ite)) return; - m_lift_ite.m_functor.set_conservative(m_smt_params.m_lift_ite == LI_CONSERVATIVE); - m_ng_lift_ite.m_functor.set_conservative(m_smt_params.m_ng_lift_ite == LI_CONSERVATIVE); + m_lift_ite.m_functor.set_conservative(m_smt_params.m_lift_ite == lift_ite_kind::LI_CONSERVATIVE); + m_ng_lift_ite.m_functor.set_conservative(m_smt_params.m_ng_lift_ite == lift_ite_kind::LI_CONSERVATIVE); if (!invoke(m_ng_lift_ite)) return; if (!invoke(m_elim_term_ite)) return; + if (!invoke(m_qe_lite)) return; if (!invoke(m_refine_inj_axiom)) return; if (!invoke(m_distribute_forall)) return; if (!invoke(m_find_macros)) return; diff --git a/src/solver/assertions/asserted_formulas.h b/src/solver/assertions/asserted_formulas.h index 95848133c..77c670f78 100644 --- a/src/solver/assertions/asserted_formulas.h +++ b/src/solver/assertions/asserted_formulas.h @@ -37,6 +37,7 @@ Revision History: #include "ast/normal_forms/elim_term_ite.h" #include "ast/pattern/pattern_inference.h" #include "smt/params/smt_params.h" +#include "qe/lite/qe_lite.h" class asserted_formulas { @@ -154,7 +155,7 @@ class asserted_formulas { public: elim_term_ite_fn(asserted_formulas& af): simplify_fmls(af, "elim-term-ite"), m_elim(af.m, af.m_defined_names) {} void simplify(justified_expr const& j, expr_ref& n, proof_ref& p) override { m_elim(j.get_fml(), n, p); } - bool should_apply() const override { return af.m_smt_params.m_eliminate_term_ite && af.m_smt_params.m_lift_ite != LI_FULL; } + bool should_apply() const override { return af.m_smt_params.m_eliminate_term_ite && af.m_smt_params.m_lift_ite != lift_ite_kind::LI_FULL; } void post_op() override { af.m_formulas.append(m_elim.new_defs()); af.reduce_and_solve(); m_elim.reset(); } void push() { m_elim.push(); } void pop(unsigned n) { m_elim.pop(n); } @@ -187,8 +188,10 @@ class asserted_formulas { MK_SIMPLIFIERF(cheap_quant_fourier_motzkin, elim_bounds_rw, "cheap-fourier-motzkin", af.m_smt_params.m_eliminate_bounds && af.has_quantifiers(), true); MK_SIMPLIFIERF(elim_bvs_from_quantifiers, bv_elim_rw, "eliminate-bit-vectors-from-quantifiers", af.m_smt_params.m_bb_quantifiers, true); MK_SIMPLIFIERF(apply_bit2int, bit2int, "propagate-bit-vector-over-integers", af.m_smt_params.m_simplify_bit2int, true); - MK_SIMPLIFIERF(lift_ite, push_app_ite_rw, "lift-ite", af.m_smt_params.m_lift_ite != LI_NONE, true); - MK_SIMPLIFIERF(ng_lift_ite, ng_push_app_ite_rw, "lift-ite", af.m_smt_params.m_ng_lift_ite != LI_NONE, true); + MK_SIMPLIFIERF(lift_ite, push_app_ite_rw, "lift-ite", af.m_smt_params.m_lift_ite != lift_ite_kind::LI_NONE, true); + MK_SIMPLIFIERF(ng_lift_ite, ng_push_app_ite_rw, "lift-ite", af.m_smt_params.m_ng_lift_ite != lift_ite_kind::LI_NONE, true); + + MK_SIMPLIFIERA(qe_lite_fn, qe_lite, "qe-lite", af.m_smt_params.m_qe_lite && af.has_quantifiers(), (af.m, af.m_params), true); reduce_asserted_formulas_fn m_reduce_asserted_formulas; @@ -197,6 +200,7 @@ class asserted_formulas { refine_inj_axiom_fn m_refine_inj_axiom; max_bv_sharing_fn m_max_bv_sharing_fn; elim_term_ite_fn m_elim_term_ite; + qe_lite_fn m_qe_lite; pull_nested_quantifiers m_pull_nested_quantifiers; elim_bvs_from_quantifiers m_elim_bvs_from_quantifiers; cheap_quant_fourier_motzkin m_cheap_quant_fourier_motzkin; diff --git a/src/solver/check_sat_result.cpp b/src/solver/check_sat_result.cpp index b772f21ae..d29e0f2bd 100644 --- a/src/solver/check_sat_result.cpp +++ b/src/solver/check_sat_result.cpp @@ -20,7 +20,10 @@ Notes: void check_sat_result::set_reason_unknown(event_handler& eh) { switch (eh.caller_id()) { - case UNSET_EH_CALLER: break; + case UNSET_EH_CALLER: + if (reason_unknown() == "") + set_reason_unknown("unclassifed exception"); + break; case CTRL_C_EH_CALLER: set_reason_unknown("interrupted from keyboard"); break; diff --git a/src/solver/solver.cpp b/src/solver/solver.cpp index b53e3daed..7dcca2ecc 100644 --- a/src/solver/solver.cpp +++ b/src/solver/solver.cpp @@ -226,7 +226,7 @@ void solver::collect_param_descrs(param_descrs & r) { } void solver::reset_params(params_ref const & p) { - m_params = p; + m_params.append(p); solver_params sp(m_params); m_cancel_backup_file = sp.cancel_backup_file(); } diff --git a/src/solver/tactic2solver.cpp b/src/solver/tactic2solver.cpp index 94f7fb336..6ed570297 100644 --- a/src/solver/tactic2solver.cpp +++ b/src/solver/tactic2solver.cpp @@ -108,8 +108,12 @@ public: m_tactic->user_propagate_register_diseq(diseq_eh); } - unsigned user_propagate_register(expr* e) override { - return m_tactic->user_propagate_register(e); + unsigned user_propagate_register_expr(expr* e) override { + return m_tactic->user_propagate_register_expr(e); + } + + void user_propagate_register_created(user_propagator::created_eh_t& created_eh) override { + m_tactic->user_propagate_register_created(created_eh); } void user_propagate_clear() override { diff --git a/src/tactic/aig/aig.cpp b/src/tactic/aig/aig.cpp index 37027e63a..b689a53e2 100644 --- a/src/tactic/aig/aig.cpp +++ b/src/tactic/aig/aig.cpp @@ -559,7 +559,7 @@ struct aig_manager::imp { aig_lit r; switch (num) { case 0: - r = m.m_true; + r = m.m_false; break; case 1: r = m_result_stack[spos]; diff --git a/src/tactic/arith/add_bounds_tactic.cpp b/src/tactic/arith/add_bounds_tactic.cpp index 4493568bf..33ffbf282 100644 --- a/src/tactic/arith/add_bounds_tactic.cpp +++ b/src/tactic/arith/add_bounds_tactic.cpp @@ -149,8 +149,8 @@ public: char const* name() const override { return "add_bounds"; } void updt_params(params_ref const & p) override { - m_params = p; - m_imp->updt_params(p); + m_params.append(p); + m_imp->updt_params(m_params); } void collect_param_descrs(param_descrs & r) override { diff --git a/src/tactic/arith/card2bv_tactic.cpp b/src/tactic/arith/card2bv_tactic.cpp index fce096f23..3cc7436d1 100644 --- a/src/tactic/arith/card2bv_tactic.cpp +++ b/src/tactic/arith/card2bv_tactic.cpp @@ -45,7 +45,7 @@ public: char const* name() const override { return "card2bv"; } void updt_params(params_ref const & p) override { - m_params = p; + m_params.append(p); } void collect_param_descrs(param_descrs & r) override { diff --git a/src/tactic/arith/diff_neq_tactic.cpp b/src/tactic/arith/diff_neq_tactic.cpp index 92ce7cd29..4269aff85 100644 --- a/src/tactic/arith/diff_neq_tactic.cpp +++ b/src/tactic/arith/diff_neq_tactic.cpp @@ -360,8 +360,8 @@ public: char const* name() const override { return "diff_neq"; } void updt_params(params_ref const & p) override { - m_params = p; - m_imp->updt_params(p); + m_params.append(p); + m_imp->updt_params(m_params); } void collect_param_descrs(param_descrs & r) override { diff --git a/src/tactic/arith/factor_tactic.cpp b/src/tactic/arith/factor_tactic.cpp index 07b59a3f9..6b9b226b3 100644 --- a/src/tactic/arith/factor_tactic.cpp +++ b/src/tactic/arith/factor_tactic.cpp @@ -297,8 +297,8 @@ public: char const* name() const override { return "factor"; } void updt_params(params_ref const & p) override { - m_params = p; - m_imp->m_rw.cfg().updt_params(p); + m_params.append(p); + m_imp->m_rw.cfg().updt_params(m_params); } void collect_param_descrs(param_descrs & r) override { diff --git a/src/tactic/arith/fix_dl_var_tactic.cpp b/src/tactic/arith/fix_dl_var_tactic.cpp index f2039c378..87061b189 100644 --- a/src/tactic/arith/fix_dl_var_tactic.cpp +++ b/src/tactic/arith/fix_dl_var_tactic.cpp @@ -303,8 +303,8 @@ public: char const* name() const override { return "fix_dl_var"; } void updt_params(params_ref const & p) override { - m_params = p; - m_imp->updt_params(p); + m_params.append(p); + m_imp->updt_params(m_params); } void collect_param_descrs(param_descrs & r) override { diff --git a/src/tactic/arith/fm_tactic.cpp b/src/tactic/arith/fm_tactic.cpp index ef8109de0..569dbff21 100644 --- a/src/tactic/arith/fm_tactic.cpp +++ b/src/tactic/arith/fm_tactic.cpp @@ -1645,8 +1645,8 @@ public: char const* name() const override { return "fm"; } void updt_params(params_ref const & p) override { - m_params = p; - m_imp->updt_params(p); + m_params.append(p); + m_imp->updt_params(m_params); } void collect_param_descrs(param_descrs & r) override { diff --git a/src/tactic/arith/lia2card_tactic.cpp b/src/tactic/arith/lia2card_tactic.cpp index 77786e267..5f6edf6ea 100644 --- a/src/tactic/arith/lia2card_tactic.cpp +++ b/src/tactic/arith/lia2card_tactic.cpp @@ -135,8 +135,8 @@ public: char const* name() const override { return "lia2card"; } void updt_params(params_ref const & p) override { - m_params = p; - m_compile_equality = p.get_bool("compile_equality", true); + m_params.append(p); + m_compile_equality = m_params.get_bool("compile_equality", true); } expr_ref mk_bounded(expr_ref_vector& axioms, app* x, unsigned lo, unsigned hi) { diff --git a/src/tactic/arith/lia2pb_tactic.cpp b/src/tactic/arith/lia2pb_tactic.cpp index d67f49a37..46404ffb0 100644 --- a/src/tactic/arith/lia2pb_tactic.cpp +++ b/src/tactic/arith/lia2pb_tactic.cpp @@ -308,8 +308,8 @@ public: char const* name() const override { return "lia2pb"; } void updt_params(params_ref const & p) override { - m_params = p; - m_imp->updt_params(p); + m_params.append(p); + m_imp->updt_params(m_params); } void collect_param_descrs(param_descrs & r) override { diff --git a/src/tactic/arith/pb2bv_tactic.cpp b/src/tactic/arith/pb2bv_tactic.cpp index b63c85f1b..2d405895f 100644 --- a/src/tactic/arith/pb2bv_tactic.cpp +++ b/src/tactic/arith/pb2bv_tactic.cpp @@ -1010,8 +1010,8 @@ public: char const* name() const override { return "pb2bv"; } void updt_params(params_ref const & p) override { - m_params = p; - m_imp->updt_params(p); + m_params.append(p); + m_imp->updt_params(m_params); } void collect_param_descrs(param_descrs & r) override { diff --git a/src/tactic/arith/propagate_ineqs_tactic.cpp b/src/tactic/arith/propagate_ineqs_tactic.cpp index d55ea4ecf..0f62b45f4 100644 --- a/src/tactic/arith/propagate_ineqs_tactic.cpp +++ b/src/tactic/arith/propagate_ineqs_tactic.cpp @@ -543,8 +543,8 @@ propagate_ineqs_tactic::~propagate_ineqs_tactic() { } void propagate_ineqs_tactic::updt_params(params_ref const & p) { - m_params = p; - m_imp->updt_params(p); + m_params.append(p); + m_imp->updt_params(m_params); } void propagate_ineqs_tactic::operator()(goal_ref const & g, diff --git a/src/tactic/arith/purify_arith_tactic.cpp b/src/tactic/arith/purify_arith_tactic.cpp index db43a3ee6..843d32828 100644 --- a/src/tactic/arith/purify_arith_tactic.cpp +++ b/src/tactic/arith/purify_arith_tactic.cpp @@ -854,7 +854,10 @@ struct purify_arith_proc { for (auto const& p : mods) { body = m().mk_ite(m().mk_and(m().mk_eq(v0, p.x), m().mk_eq(v1, p.y)), p.d, body); } + fmc->add(u().mk_mod0(), body); + body = m().mk_ite(u().mk_ge(v1, u().mk_int(0)), body, u().mk_uminus(body)); + fmc->add(u().mk_rem0(), body); } if (!idivs.empty()) { expr_ref body(u().mk_int(0), m()); @@ -906,7 +909,7 @@ public: char const* name() const override { return "purify_arith"; } void updt_params(params_ref const & p) override { - m_params = p; + m_params.append(p); } void collect_param_descrs(param_descrs & r) override { diff --git a/src/tactic/arith/recover_01_tactic.cpp b/src/tactic/arith/recover_01_tactic.cpp index a7cef82e3..251d78e72 100644 --- a/src/tactic/arith/recover_01_tactic.cpp +++ b/src/tactic/arith/recover_01_tactic.cpp @@ -401,8 +401,8 @@ public: char const* name() const override { return "recover_01"; } void updt_params(params_ref const & p) override { - m_params = p; - m_imp->updt_params(p); + m_params.append(p); + m_imp->updt_params(m_params); } void collect_param_descrs(param_descrs & r) override { diff --git a/src/tactic/bv/bit_blaster_tactic.cpp b/src/tactic/bv/bit_blaster_tactic.cpp index 744f207f3..978a0a9a6 100644 --- a/src/tactic/bv/bit_blaster_tactic.cpp +++ b/src/tactic/bv/bit_blaster_tactic.cpp @@ -122,8 +122,8 @@ public: char const* name() const override { return "bit_blaster"; } void updt_params(params_ref const & p) override { - m_params = p; - m_imp->updt_params(p); + m_params.append(p); + m_imp->updt_params(m_params); } void collect_param_descrs(param_descrs & r) override { diff --git a/src/tactic/bv/bv1_blaster_tactic.cpp b/src/tactic/bv/bv1_blaster_tactic.cpp index 3af58bf66..0fc38f2db 100644 --- a/src/tactic/bv/bv1_blaster_tactic.cpp +++ b/src/tactic/bv/bv1_blaster_tactic.cpp @@ -434,8 +434,8 @@ public: char const* name() const override { return "bv1_blaster"; } void updt_params(params_ref const & p) override { - m_params = p; - m_imp->m_rw.cfg().updt_params(p); + m_params.append(p); + m_imp->m_rw.cfg().updt_params(m_params); } void collect_param_descrs(param_descrs & r) override { diff --git a/src/tactic/bv/bv_bound_chk_tactic.cpp b/src/tactic/bv/bv_bound_chk_tactic.cpp index e0b6e4d81..7ea7c0a69 100644 --- a/src/tactic/bv/bv_bound_chk_tactic.cpp +++ b/src/tactic/bv/bv_bound_chk_tactic.cpp @@ -206,8 +206,8 @@ tactic * bv_bound_chk_tactic::translate(ast_manager & m) { void bv_bound_chk_tactic::updt_params(params_ref const & p) { - m_params = p; - m_imp->updt_params(p); + m_params.append(p); + m_imp->updt_params(m_params); } void bv_bound_chk_tactic::cleanup() { diff --git a/src/tactic/bv/bvarray2uf_tactic.cpp b/src/tactic/bv/bvarray2uf_tactic.cpp index d0bd6538b..da86ed663 100644 --- a/src/tactic/bv/bvarray2uf_tactic.cpp +++ b/src/tactic/bv/bvarray2uf_tactic.cpp @@ -116,8 +116,8 @@ public: char const* name() const override { return "bvarray2uf"; } void updt_params(params_ref const & p) override { - m_params = p; - m_imp->updt_params(p); + m_params.append(p); + m_imp->updt_params(m_params); } void collect_param_descrs(param_descrs & r) override { diff --git a/src/tactic/bv/elim_small_bv_tactic.cpp b/src/tactic/bv/elim_small_bv_tactic.cpp index 7c72abde9..02ec522c6 100644 --- a/src/tactic/bv/elim_small_bv_tactic.cpp +++ b/src/tactic/bv/elim_small_bv_tactic.cpp @@ -145,11 +145,13 @@ class elim_small_bv_tactic : public tactic { "; sort = " << mk_ismt2_pp(s, m) << "; body = " << mk_ismt2_pp(body, m) << std::endl;); - if (bv_sz >= 31ul || ((unsigned)(1ul << bv_sz)) + num_steps > m_max_steps) { + if (bv_sz >= 31) + return false; + unsigned max_num = 1u << bv_sz; + if (max_num > m_max_steps || max_num + num_steps > m_max_steps) return false; - } - for (unsigned j = 0; j < (1ul << bv_sz) && !max_steps_exceeded(num_steps); j++) { + for (unsigned j = 0; j < max_num && !max_steps_exceeded(num_steps); j++) { expr_ref n(m_util.mk_numeral(j, bv_sz), m); new_bodies.push_back(replace_var(uv, num_decls, max_var_idx_p1, i, s, body, n)); num_steps++; @@ -207,10 +209,10 @@ class elim_small_bv_tactic : public tactic { } void updt_params(params_ref const & p) { - m_params = p; - m_max_memory = megabytes_to_bytes(p.get_uint("max_memory", UINT_MAX)); - m_max_steps = p.get_uint("max_steps", UINT_MAX); - m_max_bits = p.get_uint("max_bits", 4); + m_params.append(p); + m_max_memory = megabytes_to_bytes(m_params.get_uint("max_memory", UINT_MAX)); + m_max_steps = m_params.get_uint("max_steps", UINT_MAX); + m_max_bits = m_params.get_uint("max_bits", 4); } }; @@ -241,8 +243,8 @@ public: } void updt_params(params_ref const & p) override { - m_params = p; - m_rw.cfg().updt_params(p); + m_params.append(p); + m_rw.cfg().updt_params(m_params); } void collect_param_descrs(param_descrs & r) override { diff --git a/src/tactic/bv/max_bv_sharing_tactic.cpp b/src/tactic/bv/max_bv_sharing_tactic.cpp index 0efe4f58c..2bc99806e 100644 --- a/src/tactic/bv/max_bv_sharing_tactic.cpp +++ b/src/tactic/bv/max_bv_sharing_tactic.cpp @@ -280,8 +280,8 @@ public: char const* name() const override { return "max_bv_sharing"; } void updt_params(params_ref const & p) override { - m_params = p; - m_imp->m_rw.cfg().updt_params(p); + m_params.append(p); + m_imp->m_rw.cfg().updt_params(m_params); } void collect_param_descrs(param_descrs & r) override { diff --git a/src/tactic/core/blast_term_ite_tactic.cpp b/src/tactic/core/blast_term_ite_tactic.cpp index fe1269732..38b4e172e 100644 --- a/src/tactic/core/blast_term_ite_tactic.cpp +++ b/src/tactic/core/blast_term_ite_tactic.cpp @@ -174,8 +174,8 @@ public: char const* name() const override { return "blast_term_ite"; } void updt_params(params_ref const & p) override { - m_params = p; - m_imp->m_rw.m_cfg.updt_params(p); + m_params.append(p); + m_imp->m_rw.m_cfg.updt_params(m_params); } void collect_param_descrs(param_descrs & r) override { diff --git a/src/tactic/core/cofactor_term_ite_tactic.cpp b/src/tactic/core/cofactor_term_ite_tactic.cpp index d1b8f5b1b..234b7262c 100644 --- a/src/tactic/core/cofactor_term_ite_tactic.cpp +++ b/src/tactic/core/cofactor_term_ite_tactic.cpp @@ -52,7 +52,7 @@ public: ~cofactor_term_ite_tactic() override {} char const* name() const override { return "cofactor"; } - void updt_params(params_ref const & p) override { m_params = p; m_elim_ite.updt_params(p); } + void updt_params(params_ref const & p) override { m_params.append(p); m_elim_ite.updt_params(m_params); } void collect_param_descrs(param_descrs & r) override { m_elim_ite.collect_param_descrs(r); } void operator()(goal_ref const & g, goal_ref_buffer& result) override { diff --git a/src/tactic/core/collect_statistics_tactic.cpp b/src/tactic/core/collect_statistics_tactic.cpp index 3d8ebfb12..5c7ec827c 100644 --- a/src/tactic/core/collect_statistics_tactic.cpp +++ b/src/tactic/core/collect_statistics_tactic.cpp @@ -60,7 +60,7 @@ public: } void updt_params(params_ref const & p) override { - m_params = p; + m_params.append(p); } void collect_param_descrs(param_descrs & r) override {} @@ -128,12 +128,10 @@ protected: if (m_stats.find("max-quantification-depth") == m_stats.end() || m_stats["max-quantification-depth"] < m_qdepth) m_stats["max-quantification-depth"] = m_qdepth; - this->operator()(body); m_qdepth--; } void operator()(app * n) { - m_stats["function-applications"]++; this->operator()(n->get_decl()); } diff --git a/src/tactic/core/ctx_simplify_tactic.cpp b/src/tactic/core/ctx_simplify_tactic.cpp index c8aa1a4c4..47b2fa389 100644 --- a/src/tactic/core/ctx_simplify_tactic.cpp +++ b/src/tactic/core/ctx_simplify_tactic.cpp @@ -605,8 +605,8 @@ ctx_simplify_tactic::~ctx_simplify_tactic() { } void ctx_simplify_tactic::updt_params(params_ref const & p) { - m_params = p; - m_imp->updt_params(p); + m_params.append(p); + m_imp->updt_params(m_params); } void ctx_simplify_tactic::get_param_descrs(param_descrs & r) { diff --git a/src/tactic/core/elim_term_ite_tactic.cpp b/src/tactic/core/elim_term_ite_tactic.cpp index 42adf04a9..2a0593ade 100644 --- a/src/tactic/core/elim_term_ite_tactic.cpp +++ b/src/tactic/core/elim_term_ite_tactic.cpp @@ -143,8 +143,8 @@ public: } void updt_params(params_ref const & p) override { - m_params = p; - m_imp->m_rw.cfg().updt_params(p); + m_params.append(p); + m_imp->m_rw.cfg().updt_params(m_params); } void collect_param_descrs(param_descrs & r) override { diff --git a/src/tactic/core/elim_uncnstr_tactic.cpp b/src/tactic/core/elim_uncnstr_tactic.cpp index 915a0f3c0..26a69fd4a 100644 --- a/src/tactic/core/elim_uncnstr_tactic.cpp +++ b/src/tactic/core/elim_uncnstr_tactic.cpp @@ -38,6 +38,7 @@ class elim_uncnstr_tactic : public tactic { struct rw_cfg : public default_rewriter_cfg { bool m_produce_proofs; obj_hashtable & m_vars; + obj_hashtable& m_nonvars; ref m_mc; arith_util m_a_util; bv_util m_bv_util; @@ -49,10 +50,11 @@ class elim_uncnstr_tactic : public tactic { unsigned long long m_max_memory; unsigned m_max_steps; - rw_cfg(ast_manager & m, bool produce_proofs, obj_hashtable & vars, mc * _m, - unsigned long long max_memory, unsigned max_steps): + rw_cfg(ast_manager & m, bool produce_proofs, obj_hashtable & vars, obj_hashtable & nonvars, mc * _m, + unsigned long long max_memory, unsigned max_steps): m_produce_proofs(produce_proofs), m_vars(vars), + m_nonvars(nonvars), m_mc(_m), m_a_util(m), m_bv_util(m), @@ -73,7 +75,7 @@ class elim_uncnstr_tactic : public tactic { } bool uncnstr(expr * arg) const { - return m_vars.contains(arg); + return m_vars.contains(arg) && !m_nonvars.contains(arg); } bool uncnstr(unsigned num, expr * const * args) const { @@ -749,16 +751,17 @@ class elim_uncnstr_tactic : public tactic { class rw : public rewriter_tpl { rw_cfg m_cfg; public: - rw(ast_manager & m, bool produce_proofs, obj_hashtable & vars, mc * _m, + rw(ast_manager & m, bool produce_proofs, obj_hashtable & vars, obj_hashtable& nonvars, mc * _m, unsigned long long max_memory, unsigned max_steps): rewriter_tpl(m, produce_proofs, m_cfg), - m_cfg(m, produce_proofs, vars, _m, max_memory, max_steps) { + m_cfg(m, produce_proofs, vars, nonvars, _m, max_memory, max_steps) { } }; ast_manager & m_manager; ref m_mc; obj_hashtable m_vars; + obj_hashtable m_nonvars; scoped_ptr m_rw; unsigned m_num_elim_apps = 0; unsigned long long m_max_memory; @@ -774,12 +777,11 @@ class elim_uncnstr_tactic : public tactic { } void init_rw(bool produce_proofs) { - m_rw = alloc(rw, m(), produce_proofs, m_vars, m_mc.get(), m_max_memory, m_max_steps); + m_rw = alloc(rw, m(), produce_proofs, m_vars, m_nonvars, m_mc.get(), m_max_memory, m_max_steps); } void run(goal_ref const & g, goal_ref_buffer & result) { bool produce_proofs = g->proofs_enabled(); - TRACE("goal", g->display(tout);); tactic_report report("elim-uncnstr", *g); m_vars.reset(); @@ -860,9 +862,9 @@ public: } void updt_params(params_ref const & p) override { - m_params = p; - m_max_memory = megabytes_to_bytes(p.get_uint("max_memory", UINT_MAX)); - m_max_steps = p.get_uint("max_steps", UINT_MAX); + m_params.append(p); + m_max_memory = megabytes_to_bytes(m_params.get_uint("max_memory", UINT_MAX)); + m_max_steps = m_params.get_uint("max_steps", UINT_MAX); } void collect_param_descrs(param_descrs & r) override { @@ -890,6 +892,16 @@ public: m_num_elim_apps = 0; } + unsigned user_propagate_register_expr(expr* e) override { + m_nonvars.insert(e); + return 0; + } + + void user_propagate_clear() override { + m_nonvars.reset(); + } + + }; } diff --git a/src/tactic/core/injectivity_tactic.cpp b/src/tactic/core/injectivity_tactic.cpp index 485eda4dd..dfcb152a2 100644 --- a/src/tactic/core/injectivity_tactic.cpp +++ b/src/tactic/core/injectivity_tactic.cpp @@ -258,8 +258,8 @@ public: char const* name() const override { return "injectivity"; } void updt_params(params_ref const & p) override { - m_params = p; - m_finder->updt_params(p); + m_params.append(p); + m_finder->updt_params(m_params); } void collect_param_descrs(param_descrs & r) override { diff --git a/src/tactic/core/nnf_tactic.cpp b/src/tactic/core/nnf_tactic.cpp index 164cf9311..fccfdf53e 100644 --- a/src/tactic/core/nnf_tactic.cpp +++ b/src/tactic/core/nnf_tactic.cpp @@ -51,7 +51,7 @@ public: char const* name() const override { return "nnf"; } - void updt_params(params_ref const & p) override { m_params = p; } + void updt_params(params_ref const & p) override { m_params.append(p); } void collect_param_descrs(param_descrs & r) override { nnf::get_param_descrs(r); } diff --git a/src/tactic/core/pb_preprocess_tactic.cpp b/src/tactic/core/pb_preprocess_tactic.cpp index ef3db804b..1d40a91c7 100644 --- a/src/tactic/core/pb_preprocess_tactic.cpp +++ b/src/tactic/core/pb_preprocess_tactic.cpp @@ -605,7 +605,7 @@ private: sub.insert(e, v); expr_ref tmp(m); m_r.set_substitution(&sub); - for (unsigned i = 0; i < positions.size(); ++i) { + for (unsigned i = 0; !g->inconsistent() && i < positions.size(); ++i) { unsigned idx = positions[i]; expr_ref f(m); proof_ref new_pr(m); diff --git a/src/tactic/core/propagate_values_tactic.cpp b/src/tactic/core/propagate_values_tactic.cpp index b735de4b7..b2ed7cab9 100644 --- a/src/tactic/core/propagate_values_tactic.cpp +++ b/src/tactic/core/propagate_values_tactic.cpp @@ -219,9 +219,9 @@ public: char const* name() const override { return "propagate_values"; } void updt_params(params_ref const & p) override { - m_params = p; + m_params.append(p); m_r.updt_params(p); - updt_params_core(p); + updt_params_core(m_params); } void collect_param_descrs(param_descrs & r) override { diff --git a/src/tactic/core/reduce_args_tactic.cpp b/src/tactic/core/reduce_args_tactic.cpp index 436ee3272..607928f64 100644 --- a/src/tactic/core/reduce_args_tactic.cpp +++ b/src/tactic/core/reduce_args_tactic.cpp @@ -78,7 +78,7 @@ public: void operator()(goal_ref const & g, goal_ref_buffer & result) override; void cleanup() override; - unsigned user_propagate_register(expr* e) override; + unsigned user_propagate_register_expr(expr* e) override; void user_propagate_clear() override; }; @@ -502,7 +502,7 @@ void reduce_args_tactic::cleanup() { m_imp->m_vars.append(vars); } -unsigned reduce_args_tactic::user_propagate_register(expr* e) { +unsigned reduce_args_tactic::user_propagate_register_expr(expr* e) { m_imp->m_vars.push_back(e); return 0; } diff --git a/src/tactic/core/simplify_tactic.cpp b/src/tactic/core/simplify_tactic.cpp index ca76cafc7..8d9ff759f 100644 --- a/src/tactic/core/simplify_tactic.cpp +++ b/src/tactic/core/simplify_tactic.cpp @@ -80,8 +80,8 @@ simplify_tactic::~simplify_tactic() { } void simplify_tactic::updt_params(params_ref const & p) { - m_params = p; - m_imp->m_r.updt_params(p); + m_params.append(p); + m_imp->m_r.updt_params(m_params); } void simplify_tactic::get_param_descrs(param_descrs & r) { diff --git a/src/tactic/core/solve_eqs_tactic.cpp b/src/tactic/core/solve_eqs_tactic.cpp index 9d844b7ed..a73fe88b6 100644 --- a/src/tactic/core/solve_eqs_tactic.cpp +++ b/src/tactic/core/solve_eqs_tactic.cpp @@ -1092,8 +1092,8 @@ public: char const* name() const override { return "solve_eqs"; } void updt_params(params_ref const & p) override { - m_params = p; - m_imp->updt_params(p); + m_params.append(p); + m_imp->updt_params(m_params); } void collect_param_descrs(param_descrs & r) override { diff --git a/src/tactic/core/special_relations_tactic.h b/src/tactic/core/special_relations_tactic.h index a1cafb7c0..dae38304a 100644 --- a/src/tactic/core/special_relations_tactic.h +++ b/src/tactic/core/special_relations_tactic.h @@ -49,7 +49,7 @@ public: ~special_relations_tactic() override {} - void updt_params(params_ref const & p) override { m_params = p; } + void updt_params(params_ref const & p) override { m_params.append(p); } void collect_param_descrs(param_descrs & r) override { } diff --git a/src/tactic/core/tseitin_cnf_tactic.cpp b/src/tactic/core/tseitin_cnf_tactic.cpp index e26fded8f..c141aaa3b 100644 --- a/src/tactic/core/tseitin_cnf_tactic.cpp +++ b/src/tactic/core/tseitin_cnf_tactic.cpp @@ -894,8 +894,8 @@ public: char const* name() const override { return "tseitin_cnf"; } void updt_params(params_ref const & p) override { - m_params = p; - m_imp->updt_params(p); + m_params.append(p); + m_imp->updt_params(m_params); } void collect_param_descrs(param_descrs & r) override { diff --git a/src/tactic/fpa/fpa2bv_tactic.cpp b/src/tactic/fpa/fpa2bv_tactic.cpp index be98dea00..282439228 100644 --- a/src/tactic/fpa/fpa2bv_tactic.cpp +++ b/src/tactic/fpa/fpa2bv_tactic.cpp @@ -123,8 +123,8 @@ public: char const* name() const override { return "fp2bv"; } void updt_params(params_ref const & p) override { - m_params = p; - m_imp->updt_params(p); + m_params.append(p); + m_imp->updt_params(m_params); } void collect_param_descrs(param_descrs & r) override { diff --git a/src/tactic/goal.h b/src/tactic/goal.h index f7f927bb3..f32e4a66b 100644 --- a/src/tactic/goal.h +++ b/src/tactic/goal.h @@ -119,7 +119,7 @@ public: unsigned num_exprs() const; - expr * form(unsigned i) const { return m().get(m_forms, i); } + expr * form(unsigned i) const { return inconsistent() ? m().mk_false() : m().get(m_forms, i); } proof * pr(unsigned i) const { return m().size(m_proofs) > i ? static_cast(m().get(m_proofs, i)) : nullptr; } expr_dependency * dep(unsigned i) const { return unsat_core_enabled() ? m().get(m_dependencies, i) : nullptr; } diff --git a/src/tactic/portfolio/solver_subsumption_tactic.cpp b/src/tactic/portfolio/solver_subsumption_tactic.cpp index 6b275508b..c92968c8c 100644 --- a/src/tactic/portfolio/solver_subsumption_tactic.cpp +++ b/src/tactic/portfolio/solver_subsumption_tactic.cpp @@ -136,8 +136,8 @@ public: char const* name() const override { return "solver_subsumption"; } void updt_params(params_ref const& p) override { - m_params = p; - unsigned max_conflicts = p.get_uint("max_conflicts", 2); + m_params.append(p); + unsigned max_conflicts = m_params.get_uint("max_conflicts", 2); m_params.set_uint("sat.max_conflicts", max_conflicts); m_params.set_uint("smt.max_conflicts", max_conflicts); } diff --git a/src/tactic/sls/sls_tactic.cpp b/src/tactic/sls/sls_tactic.cpp index ece6940f3..e631c23e9 100644 --- a/src/tactic/sls/sls_tactic.cpp +++ b/src/tactic/sls/sls_tactic.cpp @@ -53,8 +53,8 @@ public: char const* name() const override { return "sls"; } void updt_params(params_ref const & p) override { - m_params = p; - m_engine->updt_params(p); + m_params.append(p); + m_engine->updt_params(m_params); } void collect_param_descrs(param_descrs & r) override { diff --git a/src/tactic/smtlogics/nra_tactic.cpp b/src/tactic/smtlogics/nra_tactic.cpp index c139321f9..e29694bc2 100644 --- a/src/tactic/smtlogics/nra_tactic.cpp +++ b/src/tactic/smtlogics/nra_tactic.cpp @@ -24,7 +24,7 @@ Notes: #include "tactic/smtlogics/smt_tactic.h" #include "qe/qe_tactic.h" #include "qe/nlqsat.h" -#include "qe/qe_lite.h" +#include "qe/lite/qe_lite.h" #include "nlsat/tactic/qfnra_nlsat_tactic.h" tactic * mk_nra_tactic(ast_manager & m, params_ref const& p) { diff --git a/src/tactic/smtlogics/quant_tactics.cpp b/src/tactic/smtlogics/quant_tactics.cpp index eef9ae77a..daf020a14 100644 --- a/src/tactic/smtlogics/quant_tactics.cpp +++ b/src/tactic/smtlogics/quant_tactics.cpp @@ -21,7 +21,7 @@ Revision History: #include "tactic/core/propagate_values_tactic.h" #include "tactic/core/solve_eqs_tactic.h" #include "tactic/core/elim_uncnstr_tactic.h" -#include "qe/qe_lite.h" +#include "qe/lite/qe_lite.h" #include "qe/qsat.h" #include "tactic/core/ctx_simplify_tactic.h" #include "tactic/core/elim_term_ite_tactic.h" diff --git a/src/tactic/tactic.h b/src/tactic/tactic.h index 4c4b9358d..437c7f804 100644 --- a/src/tactic/tactic.h +++ b/src/tactic/tactic.h @@ -85,7 +85,7 @@ public: throw default_exception("tactic does not support user propagation"); } - unsigned user_propagate_register(expr* e) override { return 0; } + unsigned user_propagate_register_expr(expr* e) override { return 0; } virtual char const* name() const = 0; protected: diff --git a/src/tactic/tactical.cpp b/src/tactic/tactical.cpp index 19009aa44..c5a9e6984 100644 --- a/src/tactic/tactical.cpp +++ b/src/tactic/tactical.cpp @@ -190,9 +190,9 @@ public: m_t2->user_propagate_register_diseq(diseq_eh); } - unsigned user_propagate_register(expr* e) override { - m_t1->user_propagate_register(e); - return m_t2->user_propagate_register(e); + unsigned user_propagate_register_expr(expr* e) override { + m_t1->user_propagate_register_expr(e); + return m_t2->user_propagate_register_expr(e); } void user_propagate_clear() override { @@ -200,6 +200,10 @@ public: m_t2->user_propagate_clear(); } + void user_propagate_register_created(user_propagator::created_eh_t& created_eh) override { + m_t2->user_propagate_register_created(created_eh); + } + }; tactic * and_then(tactic * t1, tactic * t2) { @@ -332,6 +336,25 @@ public: catch (tactic_exception &) { result.reset(); } + catch (z3_error & ex) { + IF_VERBOSE(10, verbose_stream() << "z3 error: " << ex.error_code() << " in or-else\n"); + throw; + } + catch (z3_exception& ex) { + IF_VERBOSE(10, verbose_stream() << ex.msg() << " in or-else\n"); + throw; + } + catch (const std::exception &ex) { + IF_VERBOSE(10, verbose_stream() << ex.what() << " in or-else\n"); + throw; + } + catch (...) { + IF_VERBOSE(10, verbose_stream() << " unclassified exception in or-else\n"); + // std::current_exception returns a std::exception_ptr, which apparently + // needs to be re-thrown to extract type information. + // typeid(ex).name() would be nice. + throw; + } } else { t->operator()(in, result); @@ -825,7 +848,7 @@ public: void reset() override { m_t->reset(); } void set_logic(symbol const& l) override { m_t->set_logic(l); } void set_progress_callback(progress_callback * callback) override { m_t->set_progress_callback(callback); } - unsigned user_propagate_register(expr* e) override { return m_t->user_propagate_register(e); } + unsigned user_propagate_register_expr(expr* e) override { return m_t->user_propagate_register_expr(e); } void user_propagate_clear() override { m_t->user_propagate_clear(); } protected: diff --git a/src/tactic/ufbv/macro_finder_tactic.cpp b/src/tactic/ufbv/macro_finder_tactic.cpp index c5745d85c..2358abcd1 100644 --- a/src/tactic/ufbv/macro_finder_tactic.cpp +++ b/src/tactic/ufbv/macro_finder_tactic.cpp @@ -108,8 +108,8 @@ public: char const* name() const override { return "macro_finder"; } void updt_params(params_ref const & p) override { - m_params = p; - m_imp->updt_params(p); + m_params.append(p); + m_imp->updt_params(m_params); } void collect_param_descrs(param_descrs & r) override { diff --git a/src/tactic/ufbv/quasi_macros_tactic.cpp b/src/tactic/ufbv/quasi_macros_tactic.cpp index 0e0cb7cb2..b0eb113b8 100644 --- a/src/tactic/ufbv/quasi_macros_tactic.cpp +++ b/src/tactic/ufbv/quasi_macros_tactic.cpp @@ -103,8 +103,8 @@ public: char const* name() const override { return "quasi_macros"; } void updt_params(params_ref const & p) override { - m_params = p; - m_imp->updt_params(p); + m_params.append(p); + m_imp->updt_params(m_params); } void collect_param_descrs(param_descrs & r) override { diff --git a/src/tactic/ufbv/ufbv_rewriter_tactic.cpp b/src/tactic/ufbv/ufbv_rewriter_tactic.cpp index d5bfec8fe..e254523c0 100644 --- a/src/tactic/ufbv/ufbv_rewriter_tactic.cpp +++ b/src/tactic/ufbv/ufbv_rewriter_tactic.cpp @@ -35,7 +35,7 @@ public: } void updt_params(params_ref const & p) override { - m_params = p; + m_params.append(p); } void collect_param_descrs(param_descrs & r) override { diff --git a/src/tactic/user_propagator_base.h b/src/tactic/user_propagator_base.h index 899722c2a..403df8af5 100644 --- a/src/tactic/user_propagator_base.h +++ b/src/tactic/user_propagator_base.h @@ -14,7 +14,7 @@ namespace user_propagator { class context_obj { public: - virtual ~context_obj() {} + virtual ~context_obj() = default; }; typedef std::function final_eh_t; @@ -23,8 +23,31 @@ namespace user_propagator { typedef std::function fresh_eh_t; typedef std::function push_eh_t; typedef std::function pop_eh_t; + typedef std::function created_eh_t; + class plugin : public decl_plugin { + public: + + static symbol name() { return symbol("user_propagator"); } + + enum kind_t { OP_USER_PROPAGATE }; + + decl_plugin* mk_fresh() override { return alloc(plugin); } + + sort* mk_sort(decl_kind k, unsigned num_parameters, parameter const* parameters) override { + UNREACHABLE(); + return nullptr; + } + + func_decl* mk_func_decl(decl_kind k, unsigned num_parameters, parameter const* parameters, + unsigned arity, sort* const* domain, sort* range) override { + UNREACHABLE(); + return nullptr; + } + + }; + class core { public: @@ -54,12 +77,17 @@ namespace user_propagator { throw default_exception("user-propagators are only supported on the SMT solver"); } - virtual unsigned user_propagate_register(expr* e) { + virtual unsigned user_propagate_register_expr(expr* e) { + throw default_exception("user-propagators are only supported on the SMT solver"); + } + + virtual void user_propagate_register_created(created_eh_t& r) { throw default_exception("user-propagators are only supported on the SMT solver"); } virtual void user_propagate_clear() { } + }; diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 2a23ac4ca..56f9d53e2 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -104,6 +104,7 @@ add_executable(test-z3 sat_local_search.cpp sat_lookahead.cpp sat_user_scope.cpp + scoped_timer.cpp simple_parser.cpp simplex.cpp simplifier.cpp diff --git a/src/test/main.cpp b/src/test/main.cpp index c95eae815..954fdd860 100644 --- a/src/test/main.cpp +++ b/src/test/main.cpp @@ -259,6 +259,7 @@ int main(int argc, char ** argv) { TST(bdd); TST(pdd); TST(pdd_solver); + TST(scoped_timer); TST(solver_pool); //TST_ARGV(hs); TST(finder); diff --git a/src/test/scoped_timer.cpp b/src/test/scoped_timer.cpp new file mode 100644 index 000000000..ff9d52b06 --- /dev/null +++ b/src/test/scoped_timer.cpp @@ -0,0 +1,52 @@ +// test driver for scoped timer. +// fixes are required to be fuzzed +// with single and multi-threaded mode and short timeouts. +// run it with app-verifier (becuzz yes ...) + +#include "util/scoped_timer.h" +#include "util/util.h" +#include "util/vector.h" +#include "util/trace.h" +#include +#include + +class test_scoped_eh : public event_handler { + std::atomic m_called = false; +public: + void operator()(event_handler_caller_t id) override { + m_caller_id = id; + m_called = true; + } + bool called() const { return m_called; } +}; + +static void worker_thread(unsigned tid) { + for (unsigned i = 0; i < 100; ++i) { + test_scoped_eh eh; + scoped_timer sc(1, &eh); + unsigned_vector v; + for (unsigned j = 0; j < (2 << 25); ++j) { + v.push_back(j); + if (eh.called()) { + // IF_VERBOSE(0, verbose_stream() << tid << " " << i << " " << j << "\n"); + break; + } + } + } +} + +void tst_scoped_timer() { + + std::cout << "sequential test\n"; + worker_thread(0); + + std::cout << "thread test\n"; + unsigned num_threads = 3; + vector threads(num_threads); + for (unsigned i = 0; i < num_threads; ++i) + threads[i] = std::thread([&, i]() { worker_thread(i); }); + + for (auto& th : threads) + th.join(); + +} diff --git a/src/util/params.cpp b/src/util/params.cpp index 1819684e0..aefe4e074 100644 --- a/src/util/params.cpp +++ b/src/util/params.cpp @@ -520,7 +520,7 @@ params_ref::~params_ref() { params_ref::params_ref(params_ref const & p): m_params(nullptr) { - operator=(p); + set(p); } void params_ref::display(std::ostream & out) const { @@ -553,18 +553,20 @@ void params_ref::validate(param_descrs const & p) { m_params->validate(p); } -params_ref & params_ref::operator=(params_ref const & p) { + +void params_ref::set(params_ref const & p) { if (p.m_params) p.m_params->inc_ref(); if (m_params) m_params->dec_ref(); m_params = p.m_params; - return *this; } void params_ref::copy(params_ref const & src) { - if (m_params == nullptr) - operator=(src); + if (m_params == nullptr || m_params->empty()) + set(src); + else if (src.empty()) + return; else { init(); copy_core(src.m_params); diff --git a/src/util/params.h b/src/util/params.h index 989d112bd..da05dff90 100644 --- a/src/util/params.h +++ b/src/util/params.h @@ -35,6 +35,8 @@ class params_ref { params * m_params; void init(); void copy_core(params const * p); + params_ref& operator=(params_ref const& p) = delete; + void set(params_ref const& p); public: params_ref():m_params(nullptr) {} params_ref(params_ref const & p); @@ -42,8 +44,7 @@ public: static params_ref const & get_empty() { return g_empty_params_ref; } - params_ref & operator=(params_ref const & p); - + // copy params from src void copy(params_ref const & src); void append(params_ref const & src) { copy(src); } diff --git a/src/util/scoped_timer.cpp b/src/util/scoped_timer.cpp index e144cf636..a36e762aa 100644 --- a/src/util/scoped_timer.cpp +++ b/src/util/scoped_timer.cpp @@ -33,12 +33,14 @@ Revision History: #include #endif +enum scoped_timer_work_state { IDLE = 0, WORKING = 1, EXITING = 2 }; + struct scoped_timer_state { std::thread m_thread; std::timed_mutex m_mutex; event_handler * eh; unsigned ms; - std::atomic work; + std::atomic work; std::condition_variable_any cv; }; @@ -49,11 +51,11 @@ static atomic num_workers(0); static void thread_func(scoped_timer_state *s) { workers.lock(); while (true) { - s->cv.wait(workers, [=]{ return s->work > 0; }); + s->cv.wait(workers, [=]{ return s->work > IDLE; }); workers.unlock(); // exiting.. - if (s->work == 2) + if (s->work == EXITING) return; auto end = std::chrono::steady_clock::now() + std::chrono::milliseconds(s->ms); @@ -68,9 +70,8 @@ static void thread_func(scoped_timer_state *s) { s->m_mutex.unlock(); next: - s->work = 0; + s->work = IDLE; workers.lock(); - available_workers.push_back(s); } } @@ -97,7 +98,7 @@ public: s->ms = ms; s->eh = eh; s->m_mutex.lock(); - s->work = 1; + s->work = WORKING; if (new_worker) { s->m_thread = std::thread(thread_func, s); } @@ -108,8 +109,11 @@ public: ~imp() { s->m_mutex.unlock(); - while (s->work == 1) + while (s->work == WORKING) std::this_thread::yield(); + workers.lock(); + available_workers.push_back(s); + workers.unlock(); } }; @@ -139,7 +143,7 @@ void scoped_timer::finalize() { while (deleted < num_workers) { workers.lock(); for (auto w : available_workers) { - w->work = 2; + w->work = EXITING; w->cv.notify_one(); } decltype(available_workers) cleanup_workers; diff --git a/src/util/state_graph.cpp b/src/util/state_graph.cpp index 2cc4158c7..55de6d515 100644 --- a/src/util/state_graph.cpp +++ b/src/util/state_graph.cpp @@ -445,7 +445,7 @@ bool state_graph::write_dgml() { } r = m_state_ufind.next(r); } while (r != s); - dgml << " Category=\"State\">" << std::endl; + dgml << " Category=\"State\" Group=\"Collapsed\">" << std::endl; if (m_dead.contains(s)) dgml << "" << std::endl; if (m_live.contains(s)) @@ -453,18 +453,35 @@ bool state_graph::write_dgml() { if (m_unexplored.contains(s)) dgml << "" << std::endl; dgml << "" << std::endl; + dgml << "" << std::endl; + dgml << "" << std::endl; } } dgml << "" << std::endl; dgml << "" << std::endl; for (auto s : m_seen) { - if (m_state_ufind.is_root(s)) + if (m_state_ufind.is_root(s)) { for (auto t : m_targets[s]) { dgml << "" << std::endl; if (!m_sources_maybecycle[t].contains(s)) dgml << "" << std::endl; dgml << "" << std::endl; } + dgml << "" << std::endl; + dgml << "" << std::endl; + } } dgml << "" << std::endl; dgml << "" << std::endl @@ -494,6 +511,11 @@ bool state_graph::write_dgml() { << "" << std::endl << "" << std::endl << "" << std::endl + << "" << std::endl + << "" << std::endl + << "" << std::endl << "