diff --git a/CMakeLists.txt b/CMakeLists.txt index 664f05112..185405490 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,9 +24,8 @@ if (NOT (EXISTS "${CMAKE_SOURCE_DIR}/src/CMakeLists.txt")) "``python contrib/cmake/bootstrap.py create``") endif() -# This overrides the default flags for the different CMAKE_BUILD_TYPEs -set(CMAKE_USER_MAKE_RULES_OVERRIDE "${CMAKE_CURRENT_SOURCE_DIR}/cmake/compiler_flags_override.cmake") -project(Z3 C CXX) +set(CMAKE_USER_MAKE_RULES_OVERRIDE_CXX "${CMAKE_CURRENT_SOURCE_DIR}/cmake/cxx_compiler_flags_overrides.cmake") +project(Z3 CXX) ################################################################################ # Project version @@ -37,16 +36,20 @@ set(Z3_VERSION_PATCH 1) set(Z3_VERSION_TWEAK 0304) set(Z3_FULL_VERSION 0) set(Z3_VERSION "${Z3_VERSION_MAJOR}.${Z3_VERSION_MINOR}.${Z3_VERSION_PATCH}.${Z3_VERSION_TWEAK}") +set(Z3_FULL_VERSION_STR "${Z3_VERSION}") # Note this might be modified message(STATUS "Z3 version ${Z3_VERSION}") ################################################################################ # Set various useful variables depending on CMake version ################################################################################ if (("${CMAKE_VERSION}" VERSION_EQUAL "3.2") OR ("${CMAKE_VERSION}" VERSION_GREATER "3.2")) - # In CMake >= 3.2 add_custom_command() supports a ``USES_TERMINAL`` argument + # In CMake >= 3.2 add_custom_command() and add_custom_target() + # supports a ``USES_TERMINAL`` argument set(ADD_CUSTOM_COMMAND_USES_TERMINAL_ARG "USES_TERMINAL") + set(ADD_CUSTOM_TARGET_USES_TERMINAL_ARG "USES_TERMINAL") else() set(ADD_CUSTOM_COMMAND_USES_TERMINAL_ARG "") + set(ADD_CUSTOM_TARGET_USES_TERMINAL_ARG "") endif() ################################################################################ @@ -75,6 +78,64 @@ endif() ################################################################################ list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules") +################################################################################ +# Handle git hash and description +################################################################################ +include(${CMAKE_SOURCE_DIR}/cmake/git_utils.cmake) +macro(disable_git_describe) + message(WARNING "Disabling INCLUDE_GIT_DESCRIBE") + set(INCLUDE_GIT_DESCRIBE OFF CACHE BOOL "Include git describe output in version output" FORCE) +endmacro() +macro(disable_git_hash) + message(WARNING "Disabling INCLUDE_GIT_HASH") + set(INCLUDE_GIT_HASH OFF CACHE BOOL "Include git hash in version output" FORCE) + unset(Z3GITHASH) # Used in configure_file() +endmacro() +option(INCLUDE_GIT_HASH "Include git hash in version output" ON) +option(INCLUDE_GIT_DESCRIBE "Include git describe output in version output" ON) + +set(GIT_DIR "${CMAKE_SOURCE_DIR}/.git") +if (EXISTS "${GIT_DIR}") + # Try to make CMake configure depend on the current git HEAD so that + # a re-configure is triggered when the HEAD changes. + add_git_dir_dependency("${GIT_DIR}" ADD_GIT_DEP_SUCCESS) + if (ADD_GIT_DEP_SUCCESS) + if (INCLUDE_GIT_HASH) + get_git_head_hash("${GIT_DIR}" Z3GITHASH) + if (NOT Z3GITHASH) + message(WARNING "Failed to get Git hash") + disable_git_hash() + endif() + message(STATUS "Using Git hash in version output: ${Z3GITHASH}") + # This mimics the behaviour of the old build system. + string(APPEND Z3_FULL_VERSION_STR " ${Z3GITHASH}") + else() + message(STATUS "Not using Git hash in version output") + unset(Z3GITHASH) # Used in configure_file() + endif() + if (INCLUDE_GIT_DESCRIBE) + get_git_head_describe("${GIT_DIR}" Z3_GIT_DESCRIPTION) + if (NOT Z3_GIT_DESCRIPTION) + message(WARNING "Failed to get Git description") + disable_git_describe() + endif() + message(STATUS "Using Git description in version output: ${Z3_GIT_DESCRIPTION}") + # This mimics the behaviour of the old build system. + string(APPEND Z3_FULL_VERSION_STR " ${Z3_GIT_DESCRIPTION}") + else() + message(STATUS "Not including git descrption in version") + endif() + else() + message(WARNING "Failed to add git dependency.") + disable_git_describe() + disable_git_hash() + endif() +else() + message(STATUS "Failed to find git directory.") + disable_git_describe() + disable_git_hash() +endif() + ################################################################################ # Useful CMake functions/Macros ################################################################################ @@ -89,19 +150,18 @@ set(Z3_COMPONENT_CXX_FLAGS "") set(Z3_COMPONENT_EXTRA_INCLUDE_DIRS "") set(Z3_DEPENDENT_LIBS "") set(Z3_DEPENDENT_EXTRA_CXX_LINK_FLAGS "") -set(Z3_DEPENDENT_EXTRA_C_LINK_FLAGS "") ################################################################################ # Build type ################################################################################ message(STATUS "CMake generator: ${CMAKE_GENERATOR}") +set(available_build_types Debug Release RelWithDebInfo MinSizeRel) if (DEFINED CMAKE_CONFIGURATION_TYPES) # Multi-configuration build (e.g. Visual Studio and Xcode). Here # CMAKE_BUILD_TYPE doesn't matter message(STATUS "Available configurations: ${CMAKE_CONFIGURATION_TYPES}") else() # Single configuration generator (e.g. Unix Makefiles, Ninja) - set(available_build_types Debug Release RelWithDebInfo MinSizeRel) if(NOT CMAKE_BUILD_TYPE) message(STATUS "CMAKE_BUILD_TYPE is not set. Setting default") message(STATUS "The available build types are: ${available_build_types}") @@ -236,11 +296,7 @@ if (OPENMP_FOUND) # flag by MSVC and breaks the build if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") OR ("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU")) - list(APPEND Z3_DEPENDENT_EXTRA_CXX_LINK_FLAGS ${OpenMP_C_FLAGS}) - endif() - if (("${CMAKE_C_COMPILER_ID}" MATCHES "Clang") OR - ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU")) - list(APPEND Z3_DEPENDENT_EXTRA_C_LINK_FLAGS ${OpenMP_CXX_FLAGS}) + list(APPEND Z3_DEPENDENT_EXTRA_CXX_LINK_FLAGS ${OpenMP_CXX_FLAGS}) endif() unset(CMAKE_REQUIRED_FLAGS) message(STATUS "Using OpenMP") @@ -270,6 +326,15 @@ if (("${TARGET_ARCHITECTURE}" STREQUAL "x86_64") OR ("${TARGET_ARCHITECTURE}" ST unset(SSE_FLAGS) endif() +# FIXME: Remove "x.." when CMP0054 is set to NEW +if ("x${CMAKE_CXX_COMPILER_ID}" STREQUAL "xMSVC") + # This is the default for MSVC already but to replicate the + # python/Makefile build system behaviour this flag is set + # explicitly. + z3_add_cxx_flag("/fp:precise" REQUIRED) +endif() +# There doesn't seem to be an equivalent for clang/gcc + ################################################################################ # Threading support ################################################################################ @@ -320,6 +385,41 @@ if (BUILD_LIBZ3_SHARED) endif() endif() +################################################################################ +# Link time optimization +################################################################################ +include(${CMAKE_SOURCE_DIR}/cmake/compiler_lto.cmake) + +################################################################################ +# MSVC specific flags inherited from old build system +################################################################################ +# FIXME: Remove "x.." when CMP0054 is set to NEW +if ("x${CMAKE_CXX_COMPILER_ID}" STREQUAL "xMSVC") + include(${CMAKE_SOURCE_DIR}/cmake/msvc_legacy_quirks.cmake) +endif() + +################################################################################ +# Report default CMake flags +################################################################################ +# This is mainly for debugging. +message(STATUS "CMAKE_CXX_FLAGS: \"${CMAKE_CXX_FLAGS}\"") +message(STATUS "CMAKE_EXE_LINKER_FLAGS: \"${CMAKE_EXE_LINKER_FLAGS}\"") +message(STATUS "CMAKE_STATIC_LINKER_FLAGS: \"${CMAKE_STATIC_LINKER_FLAGS}\"") +message(STATUS "CMAKE_SHARED_LINKER_FLAGS: \"${CMAKE_SHARED_LINKER_FLAGS}\"") +if (DEFINED CMAKE_CONFIGURATION_TYPES) + # Multi configuration generator + string(TOUPPER "${available_build_types}" build_types_to_report) +else() + # Single configuration generator + string(TOUPPER "${CMAKE_BUILD_TYPE}" build_types_to_report) +endif() +foreach (_build_type ${build_types_to_report}) + message(STATUS "CMAKE_CXX_FLAGS_${_build_type}: \"${CMAKE_CXX_FLAGS_${_build_type}}\"") + message(STATUS "CMAKE_EXE_LINKER_FLAGS_${_build_type}: \"${CMAKE_EXE_LINKER_FLAGS_${_build_type}}\"") + message(STATUS "CMAKE_SHARED_LINKER_FLAGS_${_build_type}: \"${CMAKE_SHARED_LINKER_FLAGS_${_build_type}}\"") + message(STATUS "CMAKE_STATIC_LINKER_FLAGS_${_build_type}: \"${CMAKE_STATIC_LINKER_FLAGS_${_build_type}}\"") +endforeach() + ################################################################################ # Report Z3_COMPONENT flags ################################################################################ @@ -328,7 +428,6 @@ message(STATUS "Z3_COMPONENT_CXX_FLAGS: ${Z3_COMPONENT_CXX_FLAGS}") message(STATUS "Z3_DEPENDENT_LIBS: ${Z3_DEPENDENT_LIBS}") message(STATUS "Z3_COMPONENT_EXTRA_INCLUDE_DIRS: ${Z3_COMPONENT_EXTRA_INCLUDE_DIRS}") message(STATUS "Z3_DEPENDENT_EXTRA_CXX_LINK_FLAGS: ${Z3_DEPENDENT_EXTRA_CXX_LINK_FLAGS}") -message(STATUS "Z3_DEPENDENT_EXTRA_C_LINK_FLAGS: ${Z3_DEPENDENT_EXTRA_C_LINK_FLAGS}") ################################################################################ # Z3 installation locations @@ -340,10 +439,18 @@ set(CMAKE_INSTALL_PKGCONFIGDIR PATH "Directory to install pkgconfig files" ) + +set(CMAKE_INSTALL_Z3_CMAKE_PACKAGE_DIR + "${CMAKE_INSTALL_LIBDIR}/cmake/z3" + CACHE + PATH + "Directory to install Z3 CMake package files" +) message(STATUS "CMAKE_INSTALL_LIBDIR: \"${CMAKE_INSTALL_LIBDIR}\"") message(STATUS "CMAKE_INSTALL_BINDIR: \"${CMAKE_INSTALL_BINDIR}\"") message(STATUS "CMAKE_INSTALL_INCLUDEDIR: \"${CMAKE_INSTALL_INCLUDEDIR}\"") message(STATUS "CMAKE_INSTALL_PKGCONFIGDIR: \"${CMAKE_INSTALL_PKGCONFIGDIR}\"") +message(STATUS "CMAKE_INSTALL_Z3_CMAKE_PACKAGE_DIR: \"${CMAKE_INSTALL_Z3_CMAKE_PACKAGE_DIR}\"") ################################################################################ # Uninstall rule @@ -391,6 +498,76 @@ include(${CMAKE_SOURCE_DIR}/cmake/z3_add_component.cmake) include(${CMAKE_SOURCE_DIR}/cmake/z3_append_linker_flag_list_to_target.cmake) add_subdirectory(src) +################################################################################ +# Create `Z3Config.cmake` and related files for the build tree so clients can +# use Z3 via CMake. +################################################################################ +include(CMakePackageConfigHelpers) +export(EXPORT Z3_EXPORTED_TARGETS + NAMESPACE z3:: + FILE "${CMAKE_BINARY_DIR}/Z3Targets.cmake" +) +set(Z3_FIRST_PACKAGE_INCLUDE_DIR "${CMAKE_BINARY_DIR}/src/api") +set(Z3_SECOND_PACKAGE_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/src/api") +set(Z3_CXX_PACKAGE_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/src/api/c++") +set(AUTO_GEN_MSG "Automatically generated. DO NOT EDIT") +set(CONFIG_FILE_TYPE "build tree") +configure_package_config_file("${CMAKE_SOURCE_DIR}/cmake/Z3Config.cmake.in" + "Z3Config.cmake" + INSTALL_DESTINATION "${CMAKE_BINARY_DIR}" + PATH_VARS + Z3_FIRST_PACKAGE_INCLUDE_DIR + Z3_SECOND_PACKAGE_INCLUDE_DIR + Z3_CXX_PACKAGE_INCLUDE_DIR + INSTALL_PREFIX "${CMAKE_BINARY_DIR}" +) +unset(Z3_FIRST_PACKAGE_INCLUDE_DIR) +unset(Z3_SECOND_PACKAGE_INCLUDE_DIR) +unset(Z3_CXX_PACKAGE_INCLUDE_DIR) +unset(AUTO_GEN_MSG) +unset(CONFIG_FILE_TYPE) +# TODO: Provide a `Z3Version.cmake` file so that clients can specify the version +# of Z3 they want. + +################################################################################ +# Create `Z3Config.cmake` and related files for install tree so clients can use +# Z3 via CMake. +################################################################################ +install(EXPORT + Z3_EXPORTED_TARGETS + FILE "Z3Targets.cmake" + NAMESPACE z3:: + DESTINATION "${CMAKE_INSTALL_Z3_CMAKE_PACKAGE_DIR}" +) +set(Z3_INSTALL_TREE_CMAKE_CONFIG_FILE "${CMAKE_BINARY_DIR}/cmake/Z3Config.cmake") +set(Z3_FIRST_PACKAGE_INCLUDE_DIR "${CMAKE_INSTALL_INCLUDEDIR}") +set(Z3_SECOND_INCLUDE_DIR "") +set(Z3_CXX_PACKAGE_INCLUDE_DIR "") +set(AUTO_GEN_MSG "Automatically generated. DO NOT EDIT") +set(CONFIG_FILE_TYPE "install tree") +# We use `configure_package_config_file()` to try and create CMake files +# that are re-locatable so that it doesn't matter if the files aren't placed +# in the original install prefix. +configure_package_config_file("${CMAKE_SOURCE_DIR}/cmake/Z3Config.cmake.in" + "${Z3_INSTALL_TREE_CMAKE_CONFIG_FILE}" + INSTALL_DESTINATION "${CMAKE_INSTALL_Z3_CMAKE_PACKAGE_DIR}" + PATH_VARS Z3_FIRST_PACKAGE_INCLUDE_DIR +) +unset(Z3_FIRST_PACKAGE_INCLUDE_DIR) +unset(Z3_SECOND_PACKAGE_INCLUDE_DIR) +unset(Z3_CXX_PACKAGE_INCLUDE_DIR) +unset(AUTO_GEN_MSG) +unset(CONFIG_FILE_TYPE) + +# Add install rule to install ${Z3_INSTALL_TREE_CMAKE_CONFIG_FILE} +install( + FILES "${Z3_INSTALL_TREE_CMAKE_CONFIG_FILE}" + DESTINATION "${CMAKE_INSTALL_Z3_CMAKE_PACKAGE_DIR}" +) + +# TODO: Provide a `Z3Version.cmake` file so that clients can specify the version +# of Z3 they want. + ################################################################################ # Examples ################################################################################ @@ -398,3 +575,14 @@ option(ENABLE_EXAMPLE_TARGETS "Build Z3 api examples" ON) if (ENABLE_EXAMPLE_TARGETS) add_subdirectory(examples) endif() + +################################################################################ +# Documentation +################################################################################ +option(BUILD_DOCUMENTATION "Build API documentation" OFF) +if (BUILD_DOCUMENTATION) + message(STATUS "Building documentation enabled") + add_subdirectory(doc) +else() + message(STATUS "Building documentation disabled") +endif() diff --git a/LICENSE.txt b/LICENSE.txt index 91c8070d0..cc90bed74 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -2,6 +2,9 @@ Z3 Copyright (c) Microsoft Corporation All rights reserved. MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ""Software""), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README-CMake.md b/README-CMake.md index 0943e7a7d..9289d40f1 100644 --- a/README-CMake.md +++ b/README-CMake.md @@ -267,6 +267,8 @@ The following useful options can be passed to CMake whilst configuring. * ``CMAKE_INSTALL_PREFIX`` - STRING. The install prefix to use (e.g. ``/usr/local/``). * ``CMAKE_INSTALL_PKGCONFIGDIR`` - STRING. The path to install pkgconfig files. * ``CMAKE_INSTALL_PYTHON_PKG_DIR`` - STRING. The path to install the z3 python bindings. This can be relative (to ``CMAKE_INSTALL_PREFIX``) or absolute. +* ``CMAKE_INSTALL_Z3_CMAKE_PACKAGE_DIR`` - STRING. The path to install CMake package files (e.g. ``/usr/lib/cmake/z3``). +* ``CMAKE_INSTALL_API_BINDINGS_DOC`` - STRING. The path to install documentation for API bindings. * ``ENABLE_TRACING_FOR_NON_DEBUG`` - BOOL. If set to ``TRUE`` enable tracing in non-debug builds, if set to ``FALSE`` disable tracing in non-debug builds. Note in debug builds tracing is always enabled. * ``BUILD_LIBZ3_SHARED`` - BOOL. If set to ``TRUE`` build libz3 as a shared library otherwise build as a static library. * ``ENABLE_EXAMPLE_TARGETS`` - BOOL. If set to ``TRUE`` add the build targets for building the API examples. @@ -283,6 +285,14 @@ The following useful options can be passed to CMake whilst configuring. * ``INSTALL_JAVA_BINDINGS`` - BOOL. If set to ``TRUE`` and ``BUILD_JAVA_BINDINGS`` is ``TRUE`` then running the ``install`` target will install Z3's Java bindings. * ``Z3_JAVA_JAR_INSTALLDIR`` - STRING. The path to directory to install the Z3 Java ``.jar`` file. This path should be relative to ``CMAKE_INSTALL_PREFIX``. * ``Z3_JAVA_JNI_LIB_INSTALLDIRR`` - STRING. The path to directory to install the Z3 Java JNI bridge library. This path should be relative to ``CMAKE_INSTALL_PREFIX``. +* ``INCLUDE_GIT_DESCRIBE`` - BOOL. If set to ``TRUE`` and the source tree of Z3 is a git repository then the output of ``git describe`` will be included in the build. +* ``INCLUDE_GIT_HASH`` - BOOL. If set to ``TRUE`` and the source tree of Z3 is a git repository then the git hash will be included in the build. +* ``BUILD_DOCUMENTATION`` - BOOL. If set to ``TRUE`` then documentation for the API bindings can be built by invoking the ``api_docs`` target. +* ``INSTALL_API_BINDINGS_DOCUMENTATION`` - BOOL. If set to ``TRUE`` and ``BUILD_DOCUMENTATION` is ``TRUE`` then documentation for API bindings will be installed + when running the ``install`` target. +* ``ALWAYS_BUILD_DOCS`` - BOOL. If set to ``TRUE`` and ``BUILD_DOCUMENTATION`` is ``TRUE`` then documentation for API bindings will always be built. + Disabling this is useful for faster incremental builds. The documentation can be manually built by invoking the ``api_docs`` target. +* ``LINK_TIME_OPTIMIZATION`` - BOOL. If set to ``TRUE`` link time optimization will be enabled. On the command line these can be passed to ``cmake`` using the ``-D`` option. In ``ccmake`` and ``cmake-gui`` these can be set in the user interface. @@ -414,6 +424,7 @@ There are a few special targets: * ``clean`` all the built targets in the current directory and below * ``edit_cache`` will invoke one of the CMake tools (depending on which is available) to let you change configuration options. * ``rebuild_cache`` will reinvoke ``cmake`` for the project. +* ``api_docs`` will build the documentation for the API bindings. ### Setting build type specific flags diff --git a/README.md b/README.md index f4c6dd012..f92a5389a 100644 --- a/README.md +++ b/README.md @@ -53,8 +53,8 @@ make sudo make install ``` -Note by default ``gcc`` is used as the C++ compiler if it is available. If you -would prefer to use Clang change the ``mk_make.py`` line to +Note by default ``g++`` is used as the C++ compiler if it is available. If you +would prefer to use Clang change the ``mk_make.py`` invocation to: ```bash CXX=clang++ CC=clang python scripts/mk_make.py diff --git a/contrib/cmake/cmake/Z3Config.cmake.in b/contrib/cmake/cmake/Z3Config.cmake.in new file mode 100644 index 000000000..e7f604591 --- /dev/null +++ b/contrib/cmake/cmake/Z3Config.cmake.in @@ -0,0 +1,30 @@ +################################################################################ +# @AUTO_GEN_MSG@ +# +# This file is intended to be consumed by clients who wish to use Z3 from CMake. +# It can be use by doing `find_package(Z3 config)` from within a +# `CMakeLists.txt` file. If CMake doesn't find this package automatically you +# can give it a hint by passing `-DZ3_DIR=` to the CMake invocation where +# `` is the path to the directory containing this file. +# +# This file was built for the @CONFIG_FILE_TYPE@. +################################################################################ + +# Exported targets +include("${CMAKE_CURRENT_LIST_DIR}/Z3Targets.cmake") + +@PACKAGE_INIT@ + +# Version information +set(Z3_VERSION_MAJOR @Z3_VERSION_MAJOR@) +set(Z3_VERSION_MINOR @Z3_VERSION_MINOR@) +set(Z3_VERSION_PATCH @Z3_VERSION_PATCH@) +set(Z3_VERSION_TWEAK @Z3_VERSION_TWEAK@) +set(Z3_VERSION_STRING "${Z3_VERSION_MAJOR}.${Z3_VERSION_MINOR}.${Z3_VERSION_PATCH}.${Z3_VERSION_TWEAK}") + +# NOTE: We can't use `set_and_check()` here because this a list of paths. +# List of include directories +set(Z3_C_INCLUDE_DIRS @PACKAGE_Z3_FIRST_PACKAGE_INCLUDE_DIR@ @PACKAGE_Z3_SECOND_PACKAGE_INCLUDE_DIR@) +set(Z3_CXX_INCLUDE_DIRS @PACKAGE_Z3_CXX_PACKAGE_INCLUDE_DIR@ ${Z3_C_INCLUDE_DIRS}) +# List of libraries to link against +set(Z3_LIBRARIES "z3::libz3") diff --git a/contrib/cmake/cmake/compiler_flags_override.cmake b/contrib/cmake/cmake/compiler_flags_override.cmake deleted file mode 100644 index c6005396b..000000000 --- a/contrib/cmake/cmake/compiler_flags_override.cmake +++ /dev/null @@ -1,43 +0,0 @@ -# This file overrides the default compiler flags for CMake's built-in -# configurations (CMAKE_BUILD_TYPE). Most compiler flags should not be set here. -# The main purpose is to have very fine grained control of the compiler flags. -if (CMAKE_C_COMPILER_ID) - set(_lang C) -elseif(CMAKE_CXX_COMPILER_ID) - set(_lang CXX) -else() - message(FATAL_ERROR "Unknown language") -endif() - -# TODO: The value of doing this is debatable. The flags set here are pretty -# much the CMake defaults now (they didn't use to be) and makes extra work for -# us when supporting different compilers. Perhaps we should move the remaining -# code that sets non-default flags out into the CMakeLists.txt files and remove -# any overrides here? -if (("${CMAKE_${_lang}_COMPILER_ID}" MATCHES "Clang") OR ("${CMAKE_${_lang}_COMPILER_ID}" MATCHES "GNU")) - # Taken from Modules/Compiler/GNU.cmake - set(CMAKE_${_lang}_FLAGS_INIT "") - set(CMAKE_${_lang}_FLAGS_DEBUG_INIT "-g -O0") - set(CMAKE_${_lang}_FLAGS_MINSIZEREL_INIT "-Os -DNDEBUG") - set(CMAKE_${_lang}_FLAGS_RELEASE_INIT "-O3 -DNDEBUG") - set(CMAKE_${_lang}_FLAGS_RELWITHDEBINFO_INIT "-O2 -g -DNDEBUG") - # FIXME: Remove "x.." when CMP0054 is set to NEW -elseif ("x${CMAKE_${_lang}_COMPILER_ID}" STREQUAL "xMSVC") - # FIXME: Perhaps we should be using /MD instead? - set(CMAKE_${_lang}_FLAGS_DEBUG_INIT "/MTd /Zi /Ob0 /Od /RTC1") - set(CMAKE_${_lang}_FLAGS_MINSIZEREL_INIT "/MT /O1 /Ob1 /D NDEBUG") - set(CMAKE_${_lang}_FLAGS_RELEASE_INIT "/MT /O2 /Ob2 /D NDEBUG") - set(CMAKE_${_lang}_FLAGS_RELWITHDEBINFO_INIT "/MT /Zi /O2 /Ob1 /D NDEBUG") - # Override linker flags (see Windows-MSVC.cmake for CMake's defaults) - # The stack size comes from the Python build system. - set(_msvc_stack_size "8388608") - set(CMAKE_EXE_LINKER_FLAGS_DEBUG_INIT "/debug /INCREMENTAL:NO /STACK:${_msvc_stack_size}") - set(CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO_INIT "/debug /INCREMENTAL:NO /STACK:${_msvc_stack_size}") - set(CMAKE_EXE_LINKER_FLAGS_MINSIZEREL_INIT "/INCREMENTAL:NO /STACK:${_msvc_stack_size}") - set(CMAKE_EXE_LINKER_FLAGS_RELEASE_INIT "/INCREMENTAL:NO /STACK:${_msvc_stack_size}") - unset(_msvc_stack_size) -else() - message(FATAL_ERROR "Overrides not set for ${_lang} compiler \"${CMAKE_${_lang}_COMPILER_ID}\"") -endif() - -unset(_lang) diff --git a/contrib/cmake/cmake/compiler_lto.cmake b/contrib/cmake/cmake/compiler_lto.cmake new file mode 100644 index 000000000..b90890f59 --- /dev/null +++ b/contrib/cmake/cmake/compiler_lto.cmake @@ -0,0 +1,52 @@ +option(LINK_TIME_OPTIMIZATION "Use link time optimiziation" OFF) + +if (LINK_TIME_OPTIMIZATION) + message(STATUS "LTO enabled") + set(build_types_with_lto "RELEASE" "RELWITHDEBINFO") + if (DEFINED CMAKE_CONFIGURATION_TYPES) + # Multi configuration generator + message(STATUS "Note LTO is only enabled for the following configurations: ${build_types_with_lto}") + else() + # Single configuration generator + string(TOUPPER "${CMAKE_BUILD_TYPE}" _build_type_upper) + list(FIND build_types_with_lto "${_build_type_upper}" _index) + if ("${_index}" EQUAL -1) + message(FATAL_ERROR "Configuration ${CMAKE_BUILD_TYPE} does not support LTO." + "You should set LINK_TIME_OPTIMIZATION to OFF.") + endif() + endif() + + set(_lto_compiler_flag "") + set(_lto_linker_flag "") + if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") OR + ("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU")) + set(_lto_compiler_flag "-flto") + set(_lto_linker_flag "-flto") + # FIXME: Remove "x.." when CMP0054 is set to NEW + elseif ("x${CMAKE_CXX_COMPILER_ID}" STREQUAL "xMSVC") + set(_lto_compiler_flag "/GL") + set(_lto_linker_flag "/LTCG") + else() + message(FATAL_ERROR "Can't enable LTO for compiler \"${CMAKE_CXX_COMPILER_ID}\"." + "You should set LINK_TIME_OPTIMIZATION to OFF.") + endif() + CHECK_CXX_COMPILER_FLAG("${_lto_compiler_flag}" HAS_LTO) + if (NOT HAS_LTO) + message(FATAL_ERROR "Compiler does not support LTO") + endif() + + foreach (_config ${build_types_with_lto}) + # Set flags compiler and linker flags globally rather than using + # `Z3_COMPONENT_CXX_FLAGS` and `Z3_DEPENDENT_EXTRA_CXX_LINK_FLAGS` + # respectively. We need per configuration compiler and linker flags. The + # `LINK_FLAGS` property (which we populate with + # `Z3_DEPENDENT_EXTRA_CXX_LINK_FLAGS`) doesn't seem to support generator + # expressions so we can't do `$<$:${_lto_linker_flag}>`. + set(CMAKE_CXX_FLAGS_${_config} "${CMAKE_CXX_FLAGS_${_config}} ${_lto_compiler_flag}") + set(CMAKE_EXE_LINKER_FLAGS_${_config} "${CMAKE_EXE_LINKER_FLAGS_${_config}} ${_lto_linker_flag}") + set(CMAKE_SHARED_LINKER_FLAGS_${_config} "${CMAKE_SHARED_LINKER_FLAGS_${_config}} ${_lto_linker_flag}") + set(CMAKE_STATIC_LINKER_FLAGS_${_config} "${CMAKE_STATIC_LINKER_FLAGS_${_config}} ${_lto_linker_flag}") + endforeach() +else() + message(STATUS "LTO disabled") +endif() diff --git a/contrib/cmake/cmake/compiler_warnings.cmake b/contrib/cmake/cmake/compiler_warnings.cmake index c214e4464..e02b28b2c 100644 --- a/contrib/cmake/cmake/compiler_warnings.cmake +++ b/contrib/cmake/cmake/compiler_warnings.cmake @@ -15,6 +15,13 @@ elseif ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") # FIXME: Remove "x.." when CMP0054 is set to NEW elseif ("x${CMAKE_CXX_COMPILER_ID}" STREQUAL "xMSVC") list(APPEND WARNING_FLAGS_TO_CHECK ${MSVC_WARNINGS}) + + # CMake's default flags include /W3 already so remove them if + # they already exist. + if ("${CMAKE_CXX_FLAGS}" MATCHES "/W3") + string(REPLACE "/W3" "" _cmake_cxx_flags_remove_w3 "${CMAKE_CXX_FLAGS}") + set(CMAKE_CXX_FLAGS "${_cmake_cxx_flags_remove_w3}" CACHE STRING "" FORCE) + endif() else() message(AUTHOR_WARNING "Unknown compiler") endif() @@ -37,4 +44,11 @@ if (WARNINGS_AS_ERRORS) message(STATUS "Treating compiler warnings as errors") else() message(STATUS "Not treating compiler warnings as errors") + # FIXME: Remove "x.." when CMP0054 is set to NEW + if ("x${CMAKE_CXX_COMPILER_ID}" STREQUAL "xMSVC") + # Warnings as errors is off by default for MSVC so setting this + # is not necessary but this duplicates the behaviour of the old + # build system. + list(APPEND Z3_COMPONENT_CXX_FLAGS "/WX-") + endif() endif() diff --git a/contrib/cmake/cmake/cxx_compiler_flags_overrides.cmake b/contrib/cmake/cmake/cxx_compiler_flags_overrides.cmake new file mode 100644 index 000000000..59966f424 --- /dev/null +++ b/contrib/cmake/cmake/cxx_compiler_flags_overrides.cmake @@ -0,0 +1,14 @@ +# This file overrides the default compiler flags for CMake's built-in +# configurations (CMAKE_BUILD_TYPE). Most compiler flags should not be set here. +# The main purpose is to have very fine grained control of the compiler flags. + +# We only override the defaults for Clang and GCC right now. +# CMake's MSVC logic is complicated so for now it's better to just inherit CMake's defaults. +if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") OR ("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU")) + # Taken from Modules/Compiler/GNU.cmake + set(CMAKE_CXX_FLAGS_INIT "") + set(CMAKE_CXX_FLAGS_DEBUG_INIT "-g -O0") + set(CMAKE_CXX_FLAGS_MINSIZEREL_INIT "-Os -DNDEBUG") + set(CMAKE_CXX_FLAGS_RELEASE_INIT "-O3 -DNDEBUG") + set(CMAKE_CXX_FLAGS_RELWITHDEBINFO_INIT "-O2 -g -DNDEBUG") +endif() diff --git a/contrib/cmake/cmake/git_utils.cmake b/contrib/cmake/cmake/git_utils.cmake new file mode 100644 index 000000000..aa7f38825 --- /dev/null +++ b/contrib/cmake/cmake/git_utils.cmake @@ -0,0 +1,173 @@ +# add_git_dir_dependency(GIT_DIR SUCCESS_VAR) +# +# Adds a configure time dependency on the git directory such that if the HEAD +# of the git directory changes CMake will be forced to re-run. This useful +# for fetching the current git hash and including it in the build. +# +# `GIT_DIR` is the path to the git directory (i.e. the `.git` directory) +# `SUCCESS_VAR` is the name of the variable to set. It will be set to TRUE +# if the dependency was successfully added and FALSE otherwise. +function(add_git_dir_dependency GIT_DIR SUCCESS_VAR) + if (NOT "${ARGC}" EQUAL 2) + message(FATAL_ERROR "Invalid number (${ARGC}) of arguments") + endif() + + if (NOT IS_ABSOLUTE "${GIT_DIR}") + message(FATAL_ERROR "GIT_DIR (\"${GIT_DIR}\") is not an absolute path") + endif() + + if (NOT IS_DIRECTORY "${GIT_DIR}") + message(FATAL_ERROR "GIT_DIR (\"${GIT_DIR}\") is not a directory") + endif() + + set(GIT_HEAD_FILE "${GIT_DIR}/HEAD") + if (NOT EXISTS "${GIT_HEAD_FILE}") + message(AUTHOR_WARNING "Git head file \"${GIT_HEAD_FILE}\" cannot be found") + set(${SUCCESS_VAR} FALSE PARENT_SCOPE) + return() + endif() + + # List of files in the git tree that CMake configuration should depend on + set(GIT_FILE_DEPS "${GIT_HEAD_FILE}") + + # Examine the HEAD and workout what additional dependencies there are. + file(READ "${GIT_HEAD_FILE}" GIT_HEAD_DATA LIMIT 128) + string(STRIP "${GIT_HEAD_DATA}" GIT_HEAD_DATA_STRIPPED) + + if ("${GIT_HEAD_DATA_STRIPPED}" MATCHES "^ref:[ ]*(.+)$") + # HEAD points at a reference. + set(GIT_REF "${CMAKE_MATCH_1}") + if (EXISTS "${GIT_DIR}/${GIT_REF}") + # Unpacked reference. The file contains the commit hash + # so add a dependency on this file so that if we stay on this + # reference (i.e. branch) but change commit CMake will be forced + # to reconfigure. + list(APPEND GIT_FILE_DEPS "${GIT_DIR}/${GIT_REF}") + elseif(EXISTS "${GIT_DIR}/packed-refs") + # The ref must be packed (see `man git-pack-refs`). + list(APPEND GIT_FILE_DEPS "${GIT_DIR}/packed-refs") + else() + # Fail + message(AUTHOR_WARNING "Unhandled git reference") + set(${SUCCESS_VAR} FALSE PARENT_SCOPE) + return() + endif() + else() + # Detached HEAD. + # No other dependencies needed + endif() + + # FIXME: + # This is the directory we will copy (via `configure_file()`) git files + # into. This is a hack. It would be better to use the + # `CMAKE_CONFIGURE_DEPENDS` directory property but that feature is not + # available in CMake 2.8.12. So we use `configure_file()` to effectively + # do the same thing. When the source file to `configure_file()` changes + # it will trigger a re-run of CMake. + set(GIT_CMAKE_FILES_DIR "${CMAKE_CURRENT_BINARY_DIR}/git_cmake_files") + file(MAKE_DIRECTORY "${GIT_CMAKE_FILES_DIR}") + + foreach (git_dependency ${GIT_FILE_DEPS}) + message(STATUS "Adding git dependency \"${git_dependency}\"") + configure_file( + "${git_dependency}" + "${GIT_CMAKE_FILES_DIR}" + COPYONLY + ) + endforeach() + + set(${SUCCESS_VAR} TRUE PARENT_SCOPE) +endfunction() + +# get_git_head_hash(GIT_DIR OUTPUT_VAR) +# +# Retrieve the current commit hash for a git working directory where `GIT_DIR` +# is the `.git` directory in the root of the git working directory. +# +# `OUTPUT_VAR` should be the name of the variable to put the result in. If this +# function fails then either a fatal error will be raised or `OUTPUT_VAR` will +# contain a string with the suffix `NOTFOUND` which can be used in CMake `if()` +# commands. +function(get_git_head_hash GIT_DIR OUTPUT_VAR) + if (NOT "${ARGC}" EQUAL 2) + message(FATAL_ERROR "Invalid number of arguments") + endif() + if (NOT IS_DIRECTORY "${GIT_DIR}") + message(FATAL_ERROR "\"${GIT_DIR}\" is not a directory") + endif() + if (NOT IS_ABSOLUTE "${GIT_DIR}") + message(FATAL_ERROR \""${GIT_DIR}\" is not an absolute path") + endif() + find_package(Git) + if (NOT Git_FOUND) + set(${OUTPUT_VAR} "GIT-NOTFOUND" PARENT_SCOPE) + return() + endif() + get_filename_component(GIT_WORKING_DIR "${GIT_DIR}" DIRECTORY) + execute_process( + COMMAND + "${GIT_EXECUTABLE}" + "rev-parse" + "-q" # Quiet + "HEAD" + WORKING_DIRECTORY + "${GIT_WORKING_DIR}" + RESULT_VARIABLE + GIT_EXIT_CODE + OUTPUT_VARIABLE + Z3_GIT_HASH + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if (NOT "${GIT_EXIT_CODE}" EQUAL 0) + message(WARNING "Failed to execute git") + set(${OUTPUT_VAR} NOTFOUND PARENT_SCOPE) + return() + endif() + set(${OUTPUT_VAR} "${Z3_GIT_HASH}" PARENT_SCOPE) +endfunction() + +# get_git_head_describe(GIT_DIR OUTPUT_VAR) +# +# Retrieve the output of `git describe` for a git working directory where +# `GIT_DIR` is the `.git` directory in the root of the git working directory. +# +# `OUTPUT_VAR` should be the name of the variable to put the result in. If this +# function fails then either a fatal error will be raised or `OUTPUT_VAR` will +# contain a string with the suffix `NOTFOUND` which can be used in CMake `if()` +# commands. +function(get_git_head_describe GIT_DIR OUTPUT_VAR) + if (NOT "${ARGC}" EQUAL 2) + message(FATAL_ERROR "Invalid number of arguments") + endif() + if (NOT IS_DIRECTORY "${GIT_DIR}") + message(FATAL_ERROR "\"${GIT_DIR}\" is not a directory") + endif() + if (NOT IS_ABSOLUTE "${GIT_DIR}") + message(FATAL_ERROR \""${GIT_DIR}\" is not an absolute path") + endif() + find_package(Git) + if (NOT Git_FOUND) + set(${OUTPUT_VAR} "GIT-NOTFOUND" PARENT_SCOPE) + return() + endif() + get_filename_component(GIT_WORKING_DIR "${GIT_DIR}" DIRECTORY) + execute_process( + COMMAND + "${GIT_EXECUTABLE}" + "describe" + "--long" + WORKING_DIRECTORY + "${GIT_WORKING_DIR}" + RESULT_VARIABLE + GIT_EXIT_CODE + OUTPUT_VARIABLE + Z3_GIT_DESCRIPTION + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if (NOT "${GIT_EXIT_CODE}" EQUAL 0) + message(WARNING "Failed to execute git") + set(${OUTPUT_VAR} NOTFOUND PARENT_SCOPE) + return() + endif() + set(${OUTPUT_VAR} "${Z3_GIT_DESCRIPTION}" PARENT_SCOPE) +endfunction() diff --git a/contrib/cmake/cmake/msvc_legacy_quirks.cmake b/contrib/cmake/cmake/msvc_legacy_quirks.cmake new file mode 100644 index 000000000..2ca20277c --- /dev/null +++ b/contrib/cmake/cmake/msvc_legacy_quirks.cmake @@ -0,0 +1,194 @@ +# This file ether sets or notes various compiler and linker flags for MSVC that +# were defined by the old python/Makefile based build system but +# don't obviously belong in the other sections in the CMake build system. + +################################################################################ +# Compiler definitions +################################################################################ +# FIXME: All the commented out defines should be removed once +# we are confident it is correct to not set them. +set(Z3_MSVC_LEGACY_DEFINES + # Don't set `_DEBUG`. The old build sytem sets this but this + # is wrong. MSVC will set this depending on which runtime is being used. + # See https://msdn.microsoft.com/en-us/library/b0084kay.aspx + # _DEBUG + + # The old build system only set `UNICODE` and `_UNICODE` for x86_64 release. + # That seems completly wrong so set it for all configurations. + # According to https://blogs.msdn.microsoft.com/oldnewthing/20040212-00/?p=40643/ + # `UNICODE` affects Windows headers and `_UNICODE` affects C runtime header files. + # There is some discussion of this define at https://msdn.microsoft.com/en-us/library/dybsewaf.aspx + UNICODE + _UNICODE +) + +if ("${TARGET_ARCHITECTURE}" STREQUAL "x86_64") + list(APPEND Z3_MSVC_LEGACY_DEFINES "" + # Don't set `_LIB`. The old build system sets this for x86_64 release + # build. This flag doesn't seem to be documented but a stackoverflow + # post hints that this is usually set when building a static library. + # See http://stackoverflow.com/questions/35034683/how-to-tell-if-current-project-is-dll-or-static-lib + # This seems wrong give that the old build system set this regardless + # whether or not libz3 was static or shared so its probably best + # to not set for now. + #$<$:_LIB> + #$<$:_LIB> + + # Don't set `_CONSOLE`. The old build system sets for all configurations + # except x86_64 release. It seems ( https://codeyarns.com/2010/12/02/visual-c-windows-and-console-subsystems/ ) + # that `_CONSOLE` used to be defined by older Visual C++ environments. + # Setting this undocumented option seems like a bad idea so let's not do it. + #$<$ + #$<$ + + # Don't set `ASYNC_COMMANDS`. The old build system sets this for x86_64 + # release but this macro does not appear to be used anywhere and is not + # documented so don't set it for now. + #$<$:ASYNC_COMMANDS> + #$<$:ASYNC_COMMANDS> + ) +else() + list(APPEND Z3_MSVC_LEGACY_DEFINES "" + # Don't set `_CONSOLE`. See reasoning above. + #_CONSOLE + ) +endif() + +# Note we don't set WIN32 or _WINDOWS because +# CMake provides that for us. As a sanity check make sure the option +# is present. +if (NOT "${CMAKE_CXX_FLAGS}" MATCHES "/D[ ]*WIN32") + message(FATAL_ERROR "\"/D WIN32\" is missing") +endif() +if (NOT "${CMAKE_CXX_FLAGS}" MATCHES "/D[ ]*_WINDOWS") + message(FATAL_ERROR "\"/D _WINDOWS\" is missing") +endif() + +list(APPEND Z3_COMPONENT_CXX_DEFINES ${Z3_MSVC_LEGACY_DEFINES}) + +################################################################################ +# Compiler flags +################################################################################ +# By default in MSVC this is on but the old build system set this explicitly so +# for completeness set it too. +# See https://msdn.microsoft.com/en-us/library/dh8che7s.aspx +z3_add_cxx_flag("/Zc:wchar_t" REQUIRED) +# By default in MSVC this on but the old build system set this explicitly so +# for completeness set it too. +z3_add_cxx_flag("/Zc:forScope" REQUIRED) + +# FIXME: We might want to move this out somewhere else if we decide +# we want to set `-fno-omit-frame-pointer` for gcc/clang. +# No omit frame pointer +set(NO_OMIT_FRAME_POINTER_MSVC_FLAG "/Oy-") +CHECK_CXX_COMPILER_FLAG(${NO_OMIT_FRAME_POINTER_MSVC_FLAG} HAS_MSVC_NO_OMIT_FRAME_POINTER) +if (NOT HAS_MSVC_NO_OMIT_FRAME_POINTER) + message(FATAL_ERROR "${NO_OMIT_FRAME_POINTER_MSVC_FLAG} flag not supported") +endif() + +# FIXME: This doesn't make a huge amount of sense but the old +# build system kept the frame pointer for all configurations +# except x86_64 release (I don't know why the frame pointer +# is kept for i686 release). +if ("${TARGET_ARCHITECTURE}" STREQUAL "x86_64") + list(APPEND Z3_COMPONENT_CXX_FLAGS + $<$:${NO_OMIT_FRAME_POINTER_MSVC_FLAG}> + $<$:${NO_OMIT_FRAME_POINTER_MSVC_FLAG}> + ) +else() + list(APPEND Z3_COMPONENT_CXX_FLAGS ${NO_OMIT_FRAME_POINTER_MSVC_FLAG}) +endif() + +if (("${TARGET_ARCHITECTURE}" STREQUAL "x86_64") OR ("${TARGET_ARCHITECTURE}" STREQUAL "i686")) + # Use __cdecl calling convention. Apparently this is MSVC's default + # but the old build system set it so for completeness set it too. + # See https://msdn.microsoft.com/en-us/library/46t77ak2.aspx + z3_add_cxx_flag("/Gd" REQUIRED) +endif() + +# FIXME: The old build system explicitly disables code analysis. +# I don't know why. Duplicate this behaviour for now. +# See https://msdn.microsoft.com/en-us/library/ms173498.aspx +z3_add_cxx_flag("/analyze-" REQUIRED) + +################################################################################ +# Linker flags +################################################################################ + +# By default CMake enables incremental linking for Debug and RelWithDebInfo +# builds. The old build sytem disables it for all builds so try to do the same +# by changing all configurations if necessary +string(TOUPPER "${available_build_types}" _build_types_as_upper) +foreach (_build_type ${_build_types_as_upper}) + foreach (t EXE SHARED STATIC) + set(_replacement "/INCREMENTAL:NO") + # Remove any existing incremental flags + string(REGEX REPLACE + "/INCREMENTAL:YES" + "${_replacement}" + _replaced_linker_flags + "${CMAKE_${t}_LINKER_FLAGS_${_build_type}}") + string(REGEX REPLACE + "(/INCREMENTAL$)|(/INCREMENTAL )" + "${_replacement} " + _replaced_linker_flags + "${_replaced_linker_flags}") + if (NOT "${_replaced_linker_flags}" MATCHES "${_replacement}") + # Flag not present. Add it + string(APPEND _replaced_linker_flags " ${_replacement}") + endif() + set(CMAKE_${t}_LINKER_FLAGS_${_build_type} "${_replaced_linker_flags}") + endforeach() +endforeach() + +# The original build system passes `/STACK:` to the linker. +# This size comes from the original build system. +# FIXME: What is the rationale behind this? +set(STACK_SIZE_MSVC_LINKER 8388608) +# MSVC documentation (https://msdn.microsoft.com/en-us/library/35yc2tc3.aspx) +# says this only matters for executables which is why this is not being +# set for CMAKE_SHARED_LINKER_FLAGS or CMAKE_STATIC_LINKER_FLAGS. +string(APPEND CMAKE_EXE_LINKER_FLAGS " /STACK:${STACK_SIZE_MSVC_LINKER}") + +# The original build system passes `/SUBSYSTEM:` to the linker where `` +# depends on what is being linked. Where `` is `CONSOLE` for executables +# and `WINDOWS` for shard libraries. +# We don't need to pass `/SUBSYSTEM:CONSOLE` because CMake will do this for +# us when building executables because we don't pass the `WIN32` argument to +# `add_executable()`. +# FIXME: We probably don't need this. https://msdn.microsoft.com/en-us/library/fcc1zstk.aspx +# suggests that `/SUBSYSTEM:` only matters for executables. +string(APPEND CMAKE_SHARED_LINKER_FLAGS " /SUBSYSTEM:WINDOWS") + +# FIXME: The following linker flags are weird. They are set in all configurations +# in the old build system except release x86_64. We try to emulate this here but +# this is likely the wrong thing to do. +foreach (_build_type ${_build_types_as_upper}) + if ("${TARGET_ARCHITECTURE}" STREQUAL "x86_64" AND + ("${_build_type}" STREQUAL "RELEASE" OR + "${_build_type}" STREQUAL "RELWITHDEBINFO") + ) + message(AUTHOR_WARNING "Skipping legacy linker MSVC options for x86_64 ${_build_type}") + else() + # Linker optimizations. + # See https://msdn.microsoft.com/en-us/library/bxwfs976.aspx + string(APPEND CMAKE_EXE_LINKER_FLAGS_${_build_type} " /OPT:REF /OPT:ICF") + string(APPEND CMAKE_SHARED_LINKER_FLAGS_${_build_type} " /OPT:REF /OPT:ICF") + + # FIXME: This is not necessary. This is MSVC's default. + # See https://msdn.microsoft.com/en-us/library/b1kw34cb.aspx + string(APPEND CMAKE_EXE_LINKER_FLAGS_${_build_type} " /TLBID:1") + string(APPEND CMAKE_SHARED_LINKER_FLAGS_${_build_type} " /TLBID:1") + + # FIXME: This is not necessary. This is MSVC's default. + # Address space layout randomization + # See https://msdn.microsoft.com/en-us/library/bb384887.aspx + string(APPEND CMAKE_EXE_LINKER_FLAGS_${_build_type} " /DYNAMICBASE") + string(APPEND CMAKE_SHARED_LINKER_FLAGS_${_build_type} " /DYNAMICBASE:NO") + + # FIXME: This is not necessary. This is MSVC's default. + # Indicate that the executable is compatible with DEP + # See https://msdn.microsoft.com/en-us/library/ms235442.aspx + string(APPEND CMAKE_EXE_LINKER_FLAGS_${_build_type} " /NXCOMPAT") + endif() +endforeach() diff --git a/contrib/cmake/cmake/z3_add_cxx_flag.cmake b/contrib/cmake/cmake/z3_add_cxx_flag.cmake index 0c56bb0f6..8bffd7de3 100644 --- a/contrib/cmake/cmake/z3_add_cxx_flag.cmake +++ b/contrib/cmake/cmake/z3_add_cxx_flag.cmake @@ -7,6 +7,7 @@ function(z3_add_cxx_flag flag) string(REPLACE "/" "_" SANITIZED_FLAG_NAME "${SANITIZED_FLAG_NAME}") string(REPLACE "=" "_" SANITIZED_FLAG_NAME "${SANITIZED_FLAG_NAME}") string(REPLACE " " "_" SANITIZED_FLAG_NAME "${SANITIZED_FLAG_NAME}") + string(REPLACE ":" "_" SANITIZED_FLAG_NAME "${SANITIZED_FLAG_NAME}") unset(HAS_${SANITIZED_FLAG_NAME}) CHECK_CXX_COMPILER_FLAG("${flag}" HAS_${SANITIZED_FLAG_NAME}) if (z3_add_flag_REQUIRED AND NOT HAS_${SANITIZED_FLAG_NAME}) diff --git a/contrib/cmake/doc/CMakeLists.txt b/contrib/cmake/doc/CMakeLists.txt new file mode 100644 index 000000000..2f8ee0dc5 --- /dev/null +++ b/contrib/cmake/doc/CMakeLists.txt @@ -0,0 +1,92 @@ +find_package(Doxygen REQUIRED) +message(STATUS "DOXYGEN_EXECUTABLE: \"${DOXYGEN_EXECUTABLE}\"") +message(STATUS "DOXYGEN_VERSION: \"${DOXYGEN_VERSION}\"") + +set(DOC_DEST_DIR "${CMAKE_CURRENT_BINARY_DIR}/api") +set(DOC_TEMP_DIR "${CMAKE_CURRENT_BINARY_DIR}/temp") +set(MK_API_DOC_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/mk_api_doc.py") + +set(PYTHON_API_OPTIONS "") +set(DOTNET_API_OPTIONS "") +set(JAVA_API_OPTIONS "") +SET(DOC_EXTRA_DEPENDS "") + +if (BUILD_PYTHON_BINDINGS) + # FIXME: Don't hard code this path + list(APPEND PYTHON_API_OPTIONS "--z3py-package-path" "${CMAKE_BINARY_DIR}/python/z3") + list(APPEND DOC_EXTRA_DEPENDS "build_z3_python_bindings") +else() + list(APPEND PYTHON_API_OPTIONS "--no-z3py") +endif() + +if (BUILD_DOTNET_BINDINGS) + # FIXME: Don't hard code these paths + list(APPEND DOTNET_API_OPTIONS "--dotnet-search-paths" + "${CMAKE_SOURCE_DIR}/src/api/dotnet" + "${CMAKE_BINARY_DIR}/src/api/dotnet" + ) + list(APPEND DOC_EXTRA_DEPENDS "build_z3_dotnet_bindings") +else() + list(APPEND DOTNET_API_OPTIONS "--no-dotnet") +endif() + +if (BUILD_JAVA_BINDINGS) + # FIXME: Don't hard code these paths + list(APPEND JAVA_API_OPTIONS "--java-search-paths" + "${CMAKE_SOURCE_DIR}/src/api/java" + "${CMAKE_BINARY_DIR}/src/api/java" + ) + list(APPEND DOC_EXTRA_DEPENDS "build_z3_java_bindings") +else() + list(APPEND JAVA_API_OPTIONS "--no-java") +endif() + +option(ALWAYS_BUILD_DOCS "Always build documentation for API bindings" ON) +if (ALWAYS_BUILD_DOCS) + set(ALWAYS_BUILD_DOCS_ARG "ALL") +else() + set(ALWAYS_BUILD_DOCS_ARG "") + # FIXME: This sucks but there doesn't seem to be a way to make the top level + # install target depend on the `api_docs` target. + message(WARNING "Building documentation for API bindings is not part of the" + " all target. This may result in stale files being installed when running" + " the install target. You should run the api_docs target before running" + " the install target. Alternatively Set ALWAYS_BUILD_DOCS to ON to" + " automatically build documentation when running the install target." + ) +endif() + +add_custom_target(api_docs ${ALWAYS_BUILD_DOCS_ARG} + COMMAND + "${PYTHON_EXECUTABLE}" "${MK_API_DOC_SCRIPT}" + --doxygen-executable "${DOXYGEN_EXECUTABLE}" + --output-dir "${DOC_DEST_DIR}" + --temp-dir "${DOC_TEMP_DIR}" + ${PYTHON_API_OPTIONS} + ${DOTNET_API_OPTIONS} + ${JAVA_API_OPTIONS} + DEPENDS + ${DOC_EXTRA_DEPENDS} + COMMENT "Generating documentation" + ${ADD_CUSTOM_TARGET_USES_TERMINAL_ARG} +) + +# Remove generated documentation when running `clean` target. +set_property(DIRECTORY APPEND PROPERTY + ADDITIONAL_MAKE_CLEAN_FILES + "${DOC_DEST_DIR}" +) + +option(INSTALL_API_BINDINGS_DOCUMENTATION "Install documentation for API bindings" ON) +set(CMAKE_INSTALL_API_BINDINGS_DOC + "${CMAKE_INSTALL_DOCDIR}" + CACHE + PATH + "Path to install documentation for API bindings" +) +if (INSTALL_API_BINDINGS_DOCUMENTATION) + install( + DIRECTORY "${DOC_DEST_DIR}" + DESTINATION "${CMAKE_INSTALL_API_BINDINGS_DOC}" + ) +endif() diff --git a/contrib/cmake/examples/CMakeLists.txt b/contrib/cmake/examples/CMakeLists.txt index e596ed3dd..3fa49f9e0 100644 --- a/contrib/cmake/examples/CMakeLists.txt +++ b/contrib/cmake/examples/CMakeLists.txt @@ -1,4 +1,64 @@ -add_subdirectory(c) -add_subdirectory(c++) -add_subdirectory(tptp) -add_subdirectory(python) +include(ExternalProject) +# Unfortunately `BUILD_ALWAYS` only seems to be supported with the version of ExternalProject +# that shipped with CMake >= 3.1. +if (("${CMAKE_VERSION}" VERSION_EQUAL "3.1") OR ("${CMAKE_VERSION}" VERSION_GREATER "3.1")) + set(EXTERNAL_PROJECT_BUILD_ALWAYS_ARG BUILD_ALWAYS 1) +else() + set(EXTERNAL_PROJECT_BUILD_ALWAYS_ARG "") +endif() + +################################################################################ +# Build example project using libz3's C API as an external project +################################################################################ +ExternalProject_Add(c_example + DEPENDS libz3 + # Configure step + SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/c" + CMAKE_ARGS "-DZ3_DIR=${CMAKE_BINARY_DIR}" + # Build step + ${EXTERNAL_PROJECT_BUILD_ALWAYS_ARG} + BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/c_example_build_dir" + # Install Step + INSTALL_COMMAND "${CMAKE_COMMAND}" -E echo "" # Dummy command +) +set_target_properties(c_example PROPERTIES EXCLUDE_FROM_ALL TRUE) + + +################################################################################ +# Build example project using libz3's C++ API as an external project +################################################################################ +ExternalProject_Add(cpp_example + DEPENDS libz3 + # Configure step + SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/c++" + CMAKE_ARGS "-DZ3_DIR=${CMAKE_BINARY_DIR}" + # Build step + ${EXTERNAL_PROJECT_BUILD_ALWAYS_ARG} + BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/cpp_example_build_dir" + # Install Step + INSTALL_COMMAND "${CMAKE_COMMAND}" -E echo "" # Dummy command +) +set_target_properties(cpp_example PROPERTIES EXCLUDE_FROM_ALL TRUE) + +################################################################################ +# Build example tptp5 project using libz3's C++ API as an external project +################################################################################ +ExternalProject_Add(z3_tptp5 + DEPENDS libz3 + # Configure step + SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/tptp" + CMAKE_ARGS "-DZ3_DIR=${CMAKE_BINARY_DIR}" + # Build step + ${EXTERNAL_PROJECT_BUILD_ALWAYS_ARG} + BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/tptp_build_dir" + # Install Step + INSTALL_COMMAND "${CMAKE_COMMAND}" -E echo "" # Dummy command +) +set_target_properties(z3_tptp5 PROPERTIES EXCLUDE_FROM_ALL TRUE) + +################################################################################ +# Build Python examples +################################################################################ +if (BUILD_PYTHON_BINDINGS) + add_subdirectory(python) +endif() diff --git a/contrib/cmake/examples/c++/CMakeLists.txt b/contrib/cmake/examples/c++/CMakeLists.txt index fdc5cf387..d60604924 100644 --- a/contrib/cmake/examples/c++/CMakeLists.txt +++ b/contrib/cmake/examples/c++/CMakeLists.txt @@ -1,9 +1,38 @@ -# FIXME: We should build this as an external project and consume -# Z3 as `find_package(z3 CONFIG)`. -add_executable(cpp_example EXCLUDE_FROM_ALL example.cpp) -target_link_libraries(cpp_example PRIVATE libz3) -target_include_directories(cpp_example PRIVATE "${CMAKE_SOURCE_DIR}/src/api") -target_include_directories(cpp_example PRIVATE "${CMAKE_SOURCE_DIR}/src/api/c++") -if (NOT BUILD_LIBZ3_SHARED) - z3_append_linker_flag_list_to_target(cpp_example ${Z3_DEPENDENT_EXTRA_CXX_LINK_FLAGS}) +################################################################################ +# Example C++ project +################################################################################ +project(Z3_C_EXAMPLE CXX) +cmake_minimum_required(VERSION 2.8.12) +find_package(Z3 + REQUIRED + CONFIG + # `NO_DEFAULT_PATH` is set so that -DZ3_DIR has to be passed to find Z3. + # This should prevent us from accidently picking up an installed + # copy of Z3. This is here to benefit Z3's build sytem when building + # this project. When making your own project you probably shouldn't + # use this option. + NO_DEFAULT_PATH +) +message(STATUS "Z3_FOUND: ${Z3_FOUND}") +message(STATUS "Found Z3 ${Z3_VERSION_STRING}") +message(STATUS "Z3_DIR: ${Z3_DIR}") + +add_executable(cpp_example example.cpp) +target_include_directories(cpp_example PRIVATE ${Z3_CXX_INCLUDE_DIRS}) +target_link_libraries(cpp_example PRIVATE ${Z3_LIBRARIES}) + +if ("${CMAKE_SYSTEM_NAME}" MATCHES "[Ww]indows") + # On Windows we need to copy the Z3 libraries + # into the same directory as the executable + # so that they can be found. + foreach (z3_lib ${Z3_LIBRARIES}) + message(STATUS "Adding copy rule for ${z3_lib}") + add_custom_command(TARGET cpp_example + POST_BUILD + COMMAND + ${CMAKE_COMMAND} -E copy_if_different + $ + $ + ) + endforeach() endif() diff --git a/contrib/cmake/examples/c/CMakeLists.txt b/contrib/cmake/examples/c/CMakeLists.txt index fc6eaee18..dd8fa6328 100644 --- a/contrib/cmake/examples/c/CMakeLists.txt +++ b/contrib/cmake/examples/c/CMakeLists.txt @@ -1,9 +1,42 @@ -# FIXME: We should build this as an external project and consume -# Z3 as `find_package(z3 CONFIG)`. -add_executable(c_example EXCLUDE_FROM_ALL test_capi.c) -target_link_libraries(c_example PRIVATE libz3) -target_include_directories(c_example PRIVATE "${CMAKE_SOURCE_DIR}/src/api") -# This is needed for when libz3 is built as a static library -if (NOT BUILD_LIBZ3_SHARED) - z3_append_linker_flag_list_to_target(c_example ${Z3_DEPENDENT_EXTRA_C_LINK_FLAGS}) +################################################################################ +# Example C project +################################################################################ +# NOTE: Even though this is a C project, libz3 uses C++. When using libz3 +# as a static library if we don't configure this project to also support +# C++ we will use the C linker rather than the C++ linker and will not link +# the C++ standard library in resulting in a link failure. +project(Z3_C_EXAMPLE C CXX) +cmake_minimum_required(VERSION 2.8.12) +find_package(Z3 + REQUIRED + CONFIG + # `NO_DEFAULT_PATH` is set so that -DZ3_DIR has to be passed to find Z3. + # This should prevent us from accidently picking up an installed + # copy of Z3. This is here to benefit Z3's build sytem when building + # this project. When making your own project you probably shouldn't + # use this option. + NO_DEFAULT_PATH +) +message(STATUS "Z3_FOUND: ${Z3_FOUND}") +message(STATUS "Found Z3 ${Z3_VERSION_STRING}") +message(STATUS "Z3_DIR: ${Z3_DIR}") + +add_executable(c_example test_capi.c) +target_include_directories(c_example PRIVATE ${Z3_C_INCLUDE_DIRS}) +target_link_libraries(c_example PRIVATE ${Z3_LIBRARIES}) + +if ("${CMAKE_SYSTEM_NAME}" MATCHES "[Ww]indows") + # On Windows we need to copy the Z3 libraries + # into the same directory as the executable + # so that they can be found. + foreach (z3_lib ${Z3_LIBRARIES}) + message(STATUS "Adding copy rule for ${z3_lib}") + add_custom_command(TARGET c_example + POST_BUILD + COMMAND + ${CMAKE_COMMAND} -E copy_if_different + $ + $ + ) + endforeach() endif() diff --git a/contrib/cmake/examples/tptp/CMakeLists.txt b/contrib/cmake/examples/tptp/CMakeLists.txt index 41fa9cc65..8e8dfb8ea 100644 --- a/contrib/cmake/examples/tptp/CMakeLists.txt +++ b/contrib/cmake/examples/tptp/CMakeLists.txt @@ -1,4 +1,39 @@ -add_executable(z3_tptp5 EXCLUDE_FROM_ALL tptp5.cpp tptp5.lex.cpp) -target_link_libraries(z3_tptp5 PRIVATE libz3) -target_include_directories(z3_tptp5 PRIVATE "${CMAKE_SOURCE_DIR}/src/api") -target_include_directories(z3_tptp5 PRIVATE "${CMAKE_SOURCE_DIR}/src/api/c++") +################################################################################ +# TPTP example +################################################################################ +project(Z3_TPTP5 CXX) +cmake_minimum_required(VERSION 2.8.12) +find_package(Z3 + REQUIRED + CONFIG + # `NO_DEFAULT_PATH` is set so that -DZ3_DIR has to be passed to find Z3. + # This should prevent us from accidently picking up an installed + # copy of Z3. This is here to benefit Z3's build sytem when building + # this project. When making your own project you probably shouldn't + # use this option. + NO_DEFAULT_PATH +) +message(STATUS "Z3_FOUND: ${Z3_FOUND}") +message(STATUS "Found Z3 ${Z3_VERSION_STRING}") +message(STATUS "Z3_DIR: ${Z3_DIR}") + +add_executable(z3_tptp5 tptp5.cpp tptp5.lex.cpp) +target_include_directories(z3_tptp5 PRIVATE ${Z3_CXX_INCLUDE_DIRS}) +target_link_libraries(z3_tptp5 PRIVATE ${Z3_LIBRARIES}) + +if ("${CMAKE_SYSTEM_NAME}" MATCHES "[Ww]indows") + # On Windows we need to copy the Z3 libraries + # into the same directory as the executable + # so that they can be found. + foreach (z3_lib ${Z3_LIBRARIES}) + message(STATUS "Adding copy rule for ${z3_lib}") + add_custom_command(TARGET z3_tptp5 + POST_BUILD + COMMAND + ${CMAKE_COMMAND} -E copy_if_different + $ + $ + ) + endforeach() +endif() + diff --git a/contrib/cmake/src/CMakeLists.txt b/contrib/cmake/src/CMakeLists.txt index 65eef8094..66e34790a 100644 --- a/contrib/cmake/src/CMakeLists.txt +++ b/contrib/cmake/src/CMakeLists.txt @@ -143,7 +143,13 @@ endif() # so that if those are also shared libraries they are referenced by `libz3.so`. target_link_libraries(libz3 PRIVATE ${Z3_DEPENDENT_LIBS}) -z3_append_linker_flag_list_to_target(libz3 ${Z3_DEPENDENT_EXTRA_CXX_LINK_FLAGS}) +# This is currently only for the OpenMP flags. It needs to be set +# via `target_link_libraries()` rather than `z3_append_linker_flag_list_to_target()` +# because when building the `libz3` as a static library when the target is exported +# the link dependencies need to be exported too. +foreach (flag_name ${Z3_DEPENDENT_EXTRA_CXX_LINK_FLAGS}) + target_link_libraries(libz3 PRIVATE ${flag_name}) +endforeach() # Declare which header file are the public header files of libz3 # these will automatically installed when the libz3 target is installed @@ -168,6 +174,7 @@ foreach (header ${libz3_public_headers}) endforeach() install(TARGETS libz3 + EXPORT Z3_EXPORTED_TARGETS LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" # On Windows this installs ``libz3.lib`` which CMake calls the "corresponding import library". Do we want this installed? RUNTIME DESTINATION "${CMAKE_INSTALL_LIBDIR}" # For Windows. DLLs are runtime targets for CMake diff --git a/contrib/cmake/src/api/java/CMakeLists.txt b/contrib/cmake/src/api/java/CMakeLists.txt index b34277266..dce2bc4ea 100644 --- a/contrib/cmake/src/api/java/CMakeLists.txt +++ b/contrib/cmake/src/api/java/CMakeLists.txt @@ -44,7 +44,6 @@ target_link_libraries(z3java PRIVATE libz3) # Z3's components to build ``Native.cpp`` lets do the same for now. target_compile_options(z3java PRIVATE ${Z3_COMPONENT_CXX_FLAGS}) target_compile_definitions(z3java PRIVATE ${Z3_COMPONENT_CXX_DEFINES}) -z3_append_linker_flag_list_to_target(z3java ${Z3_DEPENDENT_EXTRA_CXX_LINK_FLAGS}) target_include_directories(z3java PRIVATE "${CMAKE_SOURCE_DIR}/src/api" "${CMAKE_BINARY_DIR}/src/api" diff --git a/contrib/cmake/src/sat/CMakeLists.txt b/contrib/cmake/src/sat/CMakeLists.txt index 5494049d7..fcfe53e02 100644 --- a/contrib/cmake/src/sat/CMakeLists.txt +++ b/contrib/cmake/src/sat/CMakeLists.txt @@ -3,6 +3,7 @@ z3_add_component(sat card_extension.cpp dimacs.cpp sat_asymm_branch.cpp + sat_ccc.cpp sat_clause.cpp sat_clause_set.cpp sat_clause_use_list.cpp diff --git a/contrib/cmake/src/smt/CMakeLists.txt b/contrib/cmake/src/smt/CMakeLists.txt index bd8ad3f31..c344e936f 100644 --- a/contrib/cmake/src/smt/CMakeLists.txt +++ b/contrib/cmake/src/smt/CMakeLists.txt @@ -58,6 +58,7 @@ z3_add_component(smt theory_opt.cpp theory_pb.cpp theory_seq.cpp + theory_str.cpp theory_utvpi.cpp theory_wmaxsat.cpp uses_theory.cpp diff --git a/contrib/cmake/src/smt/params/CMakeLists.txt b/contrib/cmake/src/smt/params/CMakeLists.txt index 67224a287..500423dcc 100644 --- a/contrib/cmake/src/smt/params/CMakeLists.txt +++ b/contrib/cmake/src/smt/params/CMakeLists.txt @@ -8,6 +8,7 @@ z3_add_component(smt_params theory_array_params.cpp theory_bv_params.cpp theory_pb_params.cpp + theory_str_params.cpp COMPONENT_DEPENDENCIES ast bit_blaster diff --git a/contrib/cmake/src/test/CMakeLists.txt b/contrib/cmake/src/test/CMakeLists.txt index 0f756ce2c..5458344d1 100644 --- a/contrib/cmake/src/test/CMakeLists.txt +++ b/contrib/cmake/src/test/CMakeLists.txt @@ -23,6 +23,7 @@ add_executable(test-z3 bv_simplifier_plugin.cpp chashtable.cpp check_assumptions.cpp + cnf_backbones.cpp datalog_parser.cpp ddnf.cpp diff_logic.cpp diff --git a/contrib/cmake/src/util/CMakeLists.txt b/contrib/cmake/src/util/CMakeLists.txt index c85076531..b76f909d0 100644 --- a/contrib/cmake/src/util/CMakeLists.txt +++ b/contrib/cmake/src/util/CMakeLists.txt @@ -3,7 +3,9 @@ if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/version.h") ${z3_polluted_tree_msg} ) endif() -configure_file(version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h @ONLY) + +set(Z3_FULL_VERSION "\"${Z3_FULL_VERSION_STR}\"") +configure_file(version.h.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/version.h) z3_add_component(util SOURCES diff --git a/contrib/qprofdiff/Makefile b/contrib/qprofdiff/Makefile new file mode 100644 index 000000000..6b90bed51 --- /dev/null +++ b/contrib/qprofdiff/Makefile @@ -0,0 +1,7 @@ +qprofdiff: main.cpp + $(CXX) $(CXXFLAGS) main.cpp -o qprofdiff + +all: qprofdiff + +clean: + rm -f qprofdiff diff --git a/contrib/qprofdiff/main.cpp b/contrib/qprofdiff/main.cpp new file mode 100644 index 000000000..58d21b77d --- /dev/null +++ b/contrib/qprofdiff/main.cpp @@ -0,0 +1,284 @@ +/*++ +Copyright (c) 2017 Microsoft Corporation + +Module Name: + + main.cpp + +Abstract: + + Main file for qprofdiff. + +Author: + + Christoph M. Wintersteiger (cwinter) + +Revision History: +--*/ +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +set options; + +// Profile format: +// [quantifier_instances] qname : num_instances : max_generation : max_cost_s +const string prefix = "[quantifier_instances]"; +unsigned prefix_len = prefix.length(); +typedef struct { unsigned num_instances, max_generation, max_cost; } map_entry; + +string trim(string str) { + size_t linx = str.find_first_not_of(' '); + size_t rinx = str.find_last_not_of(' '); + return str.substr(linx, rinx-linx+1); +} + +int parse(string const & filename, map & data) { + ifstream fs(filename.c_str()); + + if (!fs.is_open()) { + cout << "Can't open file '" << filename << "'" << endl; + return ENOENT; + } + + string qid; + string tokens[4]; + unsigned cur_token = 0; + + while (!fs.eof()) { + string line; + getline(fs, line); + + if (line.substr(0, prefix_len) == prefix) { + line = trim(line.substr(prefix_len)); + size_t from = 0, ti = 0; + for (size_t inx = line.find(':', from); + inx != string::npos; + inx = line.find(':', from)) { + tokens[ti] = trim(line.substr(from, inx-from)); + from = inx+1; + ti++; + } + if (from != line.length() && ti < 4) + tokens[ti] = trim(line.substr(from)); + + qid = tokens[0]; + + if (data.find(qid) == data.end()) { + map_entry & entry = data[qid]; + entry.num_instances = entry.max_generation = entry.max_cost = 0; + } + + map_entry & entry = data[qid]; + entry.num_instances = atoi(tokens[1].c_str()); + entry.max_generation = (unsigned)atoi(tokens[2].c_str()); + entry.max_cost = (unsigned)atoi(tokens[3].c_str()); + } + } + + fs.close(); + + return 0; +} + +void display_data(map & data) { + for (map::iterator it = data.begin(); + it != data.end(); + it++) + cout << it->first << ": " << it->second.num_instances << + ", " << it->second.max_generation << + ", " << it->second.max_cost << endl; +} + + +typedef struct { + int d_num_instances, d_max_generation, d_max_cost; + bool left_only, right_only; +} diff_entry; + +typedef struct { string qid; diff_entry e; } diff_item; + +#define DIFF_LT(X) bool diff_item_lt_ ## X (diff_item const & l, diff_item const & r) { \ + int l_lt_r = l.e.d_ ## X < r.e.d_ ## X; \ + int l_eq_r = l.e.d_ ## X == r.e.d_ ## X; \ + return \ + l.e.left_only ? (r.e.left_only ? ((l_eq_r) ? l.qid < r.qid : l_lt_r) : false) : \ + l.e.right_only ? (r.e.right_only ? ((l_eq_r) ? l.qid < r.qid : l_lt_r) : true) : \ + r.e.right_only ? false : \ + r.e.left_only ? true : \ + l_lt_r; \ +} + +DIFF_LT(num_instances) +DIFF_LT(max_generation) +DIFF_LT(max_cost) + +int indicate(diff_entry const & e, bool suppress_unchanged) { + if (e.left_only) { + cout << "< "; + return INT_MIN; + } + else if (e.right_only) { + cout << "> "; + return INT_MAX; + } + else { + int const & delta = + (options.find("-si") != options.end()) ? e.d_num_instances : + (options.find("-sg") != options.end()) ? e.d_max_generation : + (options.find("-sc") != options.end()) ? e.d_max_cost : + e.d_num_instances; + + if (delta < 0) + cout << "+ "; + else if (delta > 0) + cout << "- "; + else if (delta == 0 && !suppress_unchanged) + cout << "= "; + + return delta; + } +} + +void diff(map & left, map & right) { + map diff_data; + + for (map::const_iterator lit = left.begin(); + lit != left.end(); + lit++) { + string const & qid = lit->first; + map_entry const & lentry = lit->second; + + map::const_iterator rit = right.find(qid); + if (rit != right.end()) { + map_entry const & rentry = rit->second; + diff_entry & de = diff_data[qid]; + + de.left_only = de.right_only = false; + de.d_num_instances = lentry.num_instances - rentry.num_instances; + de.d_max_generation = lentry.max_generation - rentry.max_generation; + de.d_max_cost = lentry.max_cost - rentry.max_cost; + } + else { + diff_entry & de = diff_data[qid]; + de.left_only = true; + de.right_only = false; + de.d_num_instances = lentry.num_instances; + de.d_max_generation = lentry.max_generation; + de.d_max_cost = lentry.max_cost; + } + } + + for (map::const_iterator rit = right.begin(); + rit != right.end(); + rit++) { + string const & qid = rit->first; + map_entry const & rentry = rit->second; + + map::const_iterator lit = left.find(qid); + if (lit == left.end()) { + diff_entry & de = diff_data[qid]; + de.left_only = false; + de.right_only = true; + de.d_num_instances = -(int)rentry.num_instances; + de.d_max_generation = -(int)rentry.max_generation; + de.d_max_cost = -(int)rentry.max_cost; + } + } + + vector flat_data; + for (map::const_iterator it = diff_data.begin(); + it != diff_data.end(); + it++) { + flat_data.push_back(diff_item()); + flat_data.back().qid = it->first; + flat_data.back().e = it->second; + } + + stable_sort(flat_data.begin(), flat_data.end(), + options.find("-si") != options.end() ? diff_item_lt_num_instances: + options.find("-sg") != options.end() ? diff_item_lt_max_generation : + options.find("-sc") != options.end() ? diff_item_lt_max_cost : + diff_item_lt_num_instances); + + bool suppress_unchanged = options.find("-n") != options.end(); + + for (vector::const_iterator it = flat_data.begin(); + it != flat_data.end(); + it++) { + diff_item const & d = *it; + string const & qid = d.qid; + diff_entry const & e = d.e; + + int delta = indicate(e, suppress_unchanged); + + if (!(delta == 0 && suppress_unchanged)) + cout << qid << " (" << + (e.d_num_instances > 0 ? "" : "+") << -e.d_num_instances << " inst., " << + (e.d_max_generation > 0 ? "" : "+") << -e.d_max_generation << " max. gen., " << + (e.d_max_cost > 0 ? "" : "+") << -e.d_max_cost << " max. cost)" << + endl; + } +} + +void display_usage() { + cout << "Usage: qprofdiff [options] " << endl; + cout << "Options:" << endl; + cout << " -n Suppress unchanged items" << endl; + cout << " -si Sort by difference in number of instances" << endl; + cout << " -sg Sort by difference in max. generation" << endl; + cout << " -sc Sort by difference in max. cost" << endl; +} + +int main(int argc, char ** argv) { + char * filename1 = 0; + char * filename2 = 0; + + for (int i = 1; i < argc; i++) { + int len = string(argv[i]).length(); + if (len > 1 && argv[i][0] == '-') { + options.insert(string(argv[i])); + } + else if (filename1 == 0) + filename1 = argv[i]; + else if (filename2 == 0) + filename2 = argv[i]; + else { + cout << "Invalid argument: " << argv[i] << endl << endl; + display_usage(); + return EINVAL; + } + } + + if (filename1 == 0 || filename2 == 0) { + cout << "Two filenames required." << endl << endl; + display_usage(); + return EINVAL; + } + + + cout << "Comparing " << filename1 << " to " << filename2 << endl; + + map data1, data2; + + int r = parse(filename1, data1); + if (r != 0) return r; + r = parse(filename2, data2); + if (r != 0) return r; + + // display_data(data1); + // display_data(data2); + + diff(data1, data2); + + return 0; +} diff --git a/contrib/qprofdiff/maintainers.txt b/contrib/qprofdiff/maintainers.txt new file mode 100644 index 000000000..01167f6d3 --- /dev/null +++ b/contrib/qprofdiff/maintainers.txt @@ -0,0 +1,3 @@ +# Maintainers + +- Christoph M. Wintersteiger (@wintersteiger, cwinter@microsoft.com) diff --git a/contrib/qprofdiff/qprofdiff.vcxproj b/contrib/qprofdiff/qprofdiff.vcxproj new file mode 100644 index 000000000..b6584e126 --- /dev/null +++ b/contrib/qprofdiff/qprofdiff.vcxproj @@ -0,0 +1,137 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {96E7E3EF-4162-474D-BD32-C702632AAF2B} + qprofdiff + 8.1 + + + + Application + true + v141 + NotSet + + + Application + false + v141 + true + MultiByte + + + Application + true + v141 + MultiByte + + + Application + false + v141 + true + MultiByte + + + + + + + + + + + + + + + + + + + + + $(IncludePath) + $(LibraryPath) + + + $(IncludePath) + $(LibraryPath) + + + + Level3 + Disabled + true + MultiThreadedDebugDLL + ..\..\src\util;%(AdditionalIncludeDirectories) + + + ProgramDatabase + + + $(LibraryPath);%(AdditionalLibraryDirectories) + + + + + Level3 + Disabled + true + ..\..\src\util;%(AdditionalIncludeDirectories) + + + + + Level3 + MaxSpeed + true + true + true + ..\..\src\util;%(AdditionalIncludeDirectories) + + + true + true + + + + + Level3 + MaxSpeed + true + true + true + ..\..\src\util;%(AdditionalIncludeDirectories) + + + true + true + + + + + + + + + \ No newline at end of file diff --git a/contrib/qprofdiff/qprofdiff.vcxproj.filters b/contrib/qprofdiff/qprofdiff.vcxproj.filters new file mode 100644 index 000000000..0d8d9e457 --- /dev/null +++ b/contrib/qprofdiff/qprofdiff.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + \ No newline at end of file diff --git a/doc/mk_api_doc.py b/doc/mk_api_doc.py index edabcbd1b..234dd670c 100644 --- a/doc/mk_api_doc.py +++ b/doc/mk_api_doc.py @@ -1,5 +1,9 @@ # Copyright (c) Microsoft Corporation 2015 +""" +Z3 API documentation generator script +""" +import argparse import os import shutil import re @@ -11,124 +15,293 @@ import shutil ML_ENABLED=False BUILD_DIR='../build' - -def display_help(exit_code): - print("mk_api_doc.py: Z3 documentation generator\n") - print("\nOptions:") - print(" -h, --help display this message.") - print(" -b , --build= subdirectory where Z3 is built (default: ../build).") - print(" --ml include ML/OCaml API documentation.") +DOXYGEN_EXE='doxygen' +TEMP_DIR=os.path.join(os.getcwd(), 'tmp') +OUTPUT_DIRECTORY=os.path.join(os.getcwd(), 'api') +Z3PY_PACKAGE_PATH='../src/api/python/z3' +Z3PY_ENABLED=True +DOTNET_ENABLED=True +JAVA_ENABLED=True +DOTNET_API_SEARCH_PATHS=['../src/api/dotnet'] +JAVA_API_SEARCH_PATHS=['../src/api/java'] +SCRIPT_DIR=os.path.abspath(os.path.dirname(__file__)) def parse_options(): - global ML_ENABLED, BUILD_DIR - - try: - options, remainder = getopt.gnu_getopt(sys.argv[1:], - 'b:h', - ['build=', 'help', 'ml']) - except: - print("ERROR: Invalid command line option") - display_help(1) - - for opt, arg in options: - if opt in ('-b', '--build'): - BUILD_DIR = mk_util.norm_path(arg) - elif opt in ('h', '--help'): - display_help() - exit(1) - elif opt in ('--ml'): - ML_ENABLED=True - else: - print("ERROR: Invalid command line option: %s" % opt) - display_help(1) - - + global ML_ENABLED, BUILD_DIR, DOXYGEN_EXE, TEMP_DIR, OUTPUT_DIRECTORY + global Z3PY_PACKAGE_PATH, Z3PY_ENABLED, DOTNET_ENABLED, JAVA_ENABLED + global DOTNET_API_SEARCH_PATHS, JAVA_API_SEARCH_PATHS + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('-b', + '--build', + default=BUILD_DIR, + help='Directory where Z3 is built (default: %(default)s)', + ) + parser.add_argument('--ml', + action='store_true', + default=False, + help='Include ML/OCaml API documentation' + ) + parser.add_argument('--doxygen-executable', + dest='doxygen_executable', + default=DOXYGEN_EXE, + help='Doxygen executable to use (default: %(default)s)', + ) + parser.add_argument('--temp-dir', + dest='temp_dir', + default=TEMP_DIR, + help='Path to directory to use as temporary directory. ' + '(default: %(default)s)', + ) + parser.add_argument('--output-dir', + dest='output_dir', + default=OUTPUT_DIRECTORY, + help='Path to output directory (default: %(default)s)', + ) + parser.add_argument('--z3py-package-path', + dest='z3py_package_path', + default=Z3PY_PACKAGE_PATH, + help='Path to directory containing Z3py package (default: %(default)s)', + ) + # FIXME: I would prefer not to have negative options (i.e. `--z3py` + # instead of `--no-z3py`) but historically these bindings have been on by + # default so we have options to disable generating documentation for these + # bindings rather than enable them. + parser.add_argument('--no-z3py', + dest='no_z3py', + action='store_true', + default=False, + help='Do not generate documentation for Python bindings', + ) + parser.add_argument('--no-dotnet', + dest='no_dotnet', + action='store_true', + default=False, + help='Do not generate documentation for .NET bindings', + ) + parser.add_argument('--no-java', + dest='no_java', + action='store_true', + default=False, + help='Do not generate documentation for Java bindings', + ) + parser.add_argument('--dotnet-search-paths', + dest='dotnet_search_paths', + nargs='+', + default=DOTNET_API_SEARCH_PATHS, + help='Specify one or more path to look for .NET files (default: %(default)s).', + ) + parser.add_argument('--java-search-paths', + dest='java_search_paths', + nargs='+', + default=JAVA_API_SEARCH_PATHS, + help='Specify one or more paths to look for Java files (default: %(default)s).', + ) + pargs = parser.parse_args() + ML_ENABLED = pargs.ml + BUILD_DIR = pargs.build + DOXYGEN_EXE = pargs.doxygen_executable + TEMP_DIR = pargs.temp_dir + OUTPUT_DIRECTORY = pargs.output_dir + Z3PY_PACKAGE_PATH = pargs.z3py_package_path + Z3PY_ENABLED = not pargs.no_z3py + DOTNET_ENABLED = not pargs.no_dotnet + JAVA_ENABLED = not pargs.no_java + DOTNET_API_SEARCH_PATHS = pargs.dotnet_search_paths + JAVA_API_SEARCH_PATHS = pargs.java_search_paths + if Z3PY_ENABLED: + if not os.path.exists(Z3PY_PACKAGE_PATH): + raise Exception('"{}" does not exist'.format(Z3PY_PACKAGE_PATH)) + if not os.path.basename(Z3PY_PACKAGE_PATH) == 'z3': + raise Exception('"{}" does not end with "z3"'.format(Z3PY_PACKAGE_PATH)) + return def mk_dir(d): if not os.path.exists(d): os.makedirs(d) -# Eliminate def_API and extra_API directives from file 'inf'. +# Eliminate def_API, extra_API, and def_Type directives from file 'inf'. # The result is stored in 'outf'. def cleanup_API(inf, outf): pat1 = re.compile(".*def_API.*") pat2 = re.compile(".*extra_API.*") + pat3 = re.compile(r".*def_Type\(.*") _inf = open(inf, 'r') _outf = open(outf, 'w') for line in _inf: - if not pat1.match(line) and not pat2.match(line): + if not pat1.match(line) and not pat2.match(line) and not pat3.match(line): _outf.write(line) +def configure_file(template_file_path, output_file_path, substitutions): + """ + Read a template file ``template_file_path``, perform substitutions + found in the ``substitutions`` dictionary and write the result to + the output file ``output_file_path``. + + The template file should contain zero or more template strings of the + form ``@NAME@``. + + The substitutions dictionary maps old strings (without the ``@`` + symbols) to their replacements. + """ + assert isinstance(template_file_path, str) + assert isinstance(output_file_path, str) + assert isinstance(substitutions, dict) + assert len(template_file_path) > 0 + assert len(output_file_path) > 0 + print("Generating {} from {}".format(output_file_path, template_file_path)) + + if not os.path.exists(template_file_path): + raise Exception('Could not find template file "{}"'.format(template_file_path)) + + # Read whole template file into string + template_string = None + with open(template_file_path, 'r') as f: + template_string = f.read() + + # Do replacements + for (old_string, replacement) in substitutions.items(): + template_string = template_string.replace('@{}@'.format(old_string), replacement) + + # Write the string to the file + with open(output_file_path, 'w') as f: + f.write(template_string) + try: parse_options() - fi = open('website.dox', 'r') - fo = open('website-adj.dox', 'w') + print("Creating temporary directory \"{}\"".format(TEMP_DIR)) + mk_dir(TEMP_DIR) + # Short-hand for path to temporary file + def temp_path(path): + return os.path.join(TEMP_DIR, path) + # Short-hand for path to file in `doc` directory + def doc_path(path): + return os.path.join(SCRIPT_DIR, path) - for line in fi: - if (line != '[ML]\n'): - fo.write(line) - elif (ML_ENABLED): - fo.write(' - ML/OCaml API\n') - fi.close() - fo.close() + # Create configuration file from template + doxygen_config_substitutions = { + 'OUTPUT_DIRECTORY': OUTPUT_DIRECTORY, + 'TEMP_DIR': TEMP_DIR, + 'CXX_API_SEARCH_PATHS': doc_path('../src/api/c++'), + } - mk_dir('api/html') - mk_dir('tmp') - shutil.copyfile('website-adj.dox', 'tmp/website.dox') - os.remove('website-adj.dox') - shutil.copyfile('../src/api/python/z3/z3.py', 'tmp/z3py.py') - cleanup_API('../src/api/z3_api.h', 'tmp/z3_api.h') - cleanup_API('../src/api/z3_ast_containers.h', 'tmp/z3_ast_containers.h') - cleanup_API('../src/api/z3_algebraic.h', 'tmp/z3_algebraic.h') - cleanup_API('../src/api/z3_polynomial.h', 'tmp/z3_polynomial.h') - cleanup_API('../src/api/z3_rcf.h', 'tmp/z3_rcf.h') - cleanup_API('../src/api/z3_fixedpoint.h', 'tmp/z3_fixedpoint.h') - cleanup_API('../src/api/z3_optimization.h', 'tmp/z3_optimization.h') - cleanup_API('../src/api/z3_interp.h', 'tmp/z3_interp.h') - cleanup_API('../src/api/z3_fpa.h', 'tmp/z3_fpa.h') + if Z3PY_ENABLED: + print("Z3Py documentation enabled") + doxygen_config_substitutions['PYTHON_API_FILES'] = 'z3.py' + else: + print("Z3Py documentation disabled") + doxygen_config_substitutions['PYTHON_API_FILES'] = '' + if DOTNET_ENABLED: + print(".NET documentation enabled") + doxygen_config_substitutions['DOTNET_API_FILES'] = '*.cs' + dotnet_api_search_path_str = "" + for p in DOTNET_API_SEARCH_PATHS: + # Quote path so that paths with spaces are handled correctly + dotnet_api_search_path_str += "\"{}\" ".format(p) + doxygen_config_substitutions['DOTNET_API_SEARCH_PATHS'] = dotnet_api_search_path_str + else: + print(".NET documentation disabled") + doxygen_config_substitutions['DOTNET_API_FILES'] = '' + doxygen_config_substitutions['DOTNET_API_SEARCH_PATHS'] = '' + if JAVA_ENABLED: + print("Java documentation enabled") + doxygen_config_substitutions['JAVA_API_FILES'] = '*.java' + java_api_search_path_str = "" + for p in JAVA_API_SEARCH_PATHS: + # Quote path so that paths with spaces are handled correctly + java_api_search_path_str += "\"{}\" ".format(p) + doxygen_config_substitutions['JAVA_API_SEARCH_PATHS'] = java_api_search_path_str + else: + print("Java documentation disabled") + doxygen_config_substitutions['JAVA_API_FILES'] = '' + doxygen_config_substitutions['JAVA_API_SEARCH_PATHS'] = '' + + doxygen_config_file = temp_path('z3api.cfg') + configure_file( + doc_path('z3api.cfg.in'), + doxygen_config_file, + doxygen_config_substitutions) + + website_dox_substitutions = {} + bullet_point_prefix='\n - ' + if Z3PY_ENABLED: + website_dox_substitutions['PYTHON_API'] = ( + '{prefix}Python API ' + '(also available in pydoc format)' + ).format( + prefix=bullet_point_prefix) + else: + website_dox_substitutions['PYTHON_API'] = '' + if DOTNET_ENABLED: + website_dox_substitutions['DOTNET_API'] = ( + '{prefix}' + '' + '.NET API').format( + prefix=bullet_point_prefix) + else: + website_dox_substitutions['DOTNET_API'] = '' + if JAVA_ENABLED: + website_dox_substitutions['JAVA_API'] = ( + '{prefix}' + 'Java API').format( + prefix=bullet_point_prefix) + else: + website_dox_substitutions['JAVA_API'] = '' + if ML_ENABLED: + website_dox_substitutions['OCAML_API'] = ( + 'ML/OCaml API' + ).format( + prefix=bullet_point_prefix) + else: + website_dox_substitutions['OCAML_API'] = '' + configure_file( + doc_path('website.dox.in'), + temp_path('website.dox'), + website_dox_substitutions) + + mk_dir(os.path.join(OUTPUT_DIRECTORY, 'html')) + if Z3PY_ENABLED: + shutil.copyfile(doc_path('../src/api/python/z3/z3.py'), temp_path('z3py.py')) + cleanup_API(doc_path('../src/api/z3_api.h'), temp_path('z3_api.h')) + cleanup_API(doc_path('../src/api/z3_ast_containers.h'), temp_path('z3_ast_containers.h')) + cleanup_API(doc_path('../src/api/z3_algebraic.h'), temp_path('z3_algebraic.h')) + cleanup_API(doc_path('../src/api/z3_polynomial.h'), temp_path('z3_polynomial.h')) + cleanup_API(doc_path('../src/api/z3_rcf.h'), temp_path('z3_rcf.h')) + cleanup_API(doc_path('../src/api/z3_fixedpoint.h'), temp_path('z3_fixedpoint.h')) + cleanup_API(doc_path('../src/api/z3_optimization.h'), temp_path('z3_optimization.h')) + cleanup_API(doc_path('../src/api/z3_interp.h'), temp_path('z3_interp.h')) + cleanup_API(doc_path('../src/api/z3_fpa.h'), temp_path('z3_fpa.h')) print("Removed annotations from z3_api.h.") try: - if subprocess.call(['doxygen', 'z3api.dox']) != 0: + if subprocess.call([DOXYGEN_EXE, doxygen_config_file]) != 0: print("ERROR: doxygen returned nonzero return code") exit(1) except: print("ERROR: failed to execute 'doxygen', make sure doxygen (http://www.doxygen.org) is available in your system.") exit(1) - print("Generated C and .NET API documentation.") - os.remove('tmp/z3_api.h') - os.remove('tmp/z3_ast_containers.h') - os.remove('tmp/z3_algebraic.h') - os.remove('tmp/z3_polynomial.h') - os.remove('tmp/z3_rcf.h') - os.remove('tmp/z3_fixedpoint.h') - os.remove('tmp/z3_optimization.h') - os.remove('tmp/z3_interp.h') - os.remove('tmp/z3_fpa.h') - print("Removed temporary file header files.") - - os.remove('tmp/website.dox') - print("Removed temporary file website.dox") - os.remove('tmp/z3py.py') - print("Removed temporary file z3py.py") - os.removedirs('tmp') - print("Removed temporary directory tmp.") - sys.path.append('../src/api/python/z3') - pydoc.writedoc('z3') - shutil.move('z3.html', 'api/html/z3.html') - print("Generated Python documentation.") + print("Generated Doxygen based documentation") + shutil.rmtree(os.path.realpath(TEMP_DIR)) + print("Removed temporary directory \"{}\"".format(TEMP_DIR)) + if Z3PY_ENABLED: + # Put z3py at the beginning of the search path to try to avoid picking up + # an installed copy of Z3py. + sys.path.insert(0, os.path.dirname(Z3PY_PACKAGE_PATH)) + pydoc.writedoc('z3') + shutil.move('z3.html', os.path.join(OUTPUT_DIRECTORY, 'html', 'z3.html')) + print("Generated pydoc Z3Py documentation.") if ML_ENABLED: - mk_dir('api/html/ml') - if subprocess.call(['ocamldoc', '-html', '-d', 'api\html\ml', '-sort', '-hide', 'Z3', '-I', '%s/api/ml' % BUILD_DIR, '../src/api/ml/z3enums.mli', '../src/api/ml/z3.mli']) != 0: + ml_output_dir = os.path.join(OUTPUT_DIRECTORY, 'html', 'ml') + mk_dir(ml_output_dir) + if subprocess.call(['ocamldoc', '-html', '-d', ml_output_dir, '-sort', '-hide', 'Z3', '-I', '%s/api/ml' % BUILD_DIR, doc_path('../src/api/ml/z3enums.mli'), doc_path('../src/api/ml/z3.mli')]) != 0: print("ERROR: ocamldoc failed.") exit(1) print("Generated ML/OCaml documentation.") - print("Documentation was successfully generated at subdirectory './api/html'.") -except: + print("Documentation was successfully generated at subdirectory '{}'.".format(OUTPUT_DIRECTORY)) +except Exception: exctype, value = sys.exc_info()[:2] print("ERROR: failed to generate documentation: %s" % value) exit(1) diff --git a/doc/website.dox b/doc/website.dox.in similarity index 67% rename from doc/website.dox rename to doc/website.dox.in index 799949752..17a8552d1 100644 --- a/doc/website.dox +++ b/doc/website.dox.in @@ -10,11 +10,7 @@ This website hosts the automatically generated documentation for the Z3 APIs. - - \ref capi - - \ref cppapi - - .NET API - - Java API - - Python API (also available in pydoc format) -[ML] + - \ref capi + - \ref cppapi @DOTNET_API@ @JAVA_API@ @PYTHON_API@ @OCAML_API@ - Try Z3 online at RiSE4Fun. */ diff --git a/doc/z3api.dox b/doc/z3api.cfg.in similarity index 99% rename from doc/z3api.dox rename to doc/z3api.cfg.in index c96a7be73..9e946aa7f 100644 --- a/doc/z3api.dox +++ b/doc/z3api.cfg.in @@ -52,7 +52,7 @@ PROJECT_LOGO = # If a relative path is entered, it will be relative to the location # where doxygen was started. If left blank the current directory will be used. -OUTPUT_DIRECTORY = api +OUTPUT_DIRECTORY = "@OUTPUT_DIRECTORY@" # If the CREATE_SUBDIRS tag is set to YES, then doxygen will create # 4096 sub-directories (in 2 levels) under the output directory of each output @@ -681,10 +681,9 @@ WARN_LOGFILE = # directories like "/usr/src/myproject". Separate the files or directories # with spaces. -INPUT = ../src/api/dotnet \ - ../src/api/java \ - ../src/api/c++ \ - ./tmp +INPUT = "@TEMP_DIR@" \ + "@CXX_API_SEARCH_PATHS@" \ + @DOTNET_API_SEARCH_PATHS@ @JAVA_API_SEARCH_PATHS@ # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is @@ -703,16 +702,14 @@ INPUT_ENCODING = UTF-8 # *.f90 *.f *.for *.vhd *.vhdl FILE_PATTERNS = website.dox \ - z3_api.h \ - z3_algebraic.h \ - z3_polynomial.h \ - z3_rcf.h \ - z3_interp.h \ - z3_fpa.h \ + z3_api.h \ + z3_algebraic.h \ + z3_polynomial.h \ + z3_rcf.h \ + z3_interp.h \ + z3_fpa.h \ z3++.h \ - z3py.py \ - *.cs \ - *.java + @PYTHON_API_FILES@ @DOTNET_API_FILES@ @JAVA_API_FILES@ # The RECURSIVE tag can be used to turn specify whether or not subdirectories # should be searched for input files as well. Possible values are YES and NO. diff --git a/scripts/mk_util.py b/scripts/mk_util.py index 17ab8dea0..d1bbd6ca4 100644 --- a/scripts/mk_util.py +++ b/scripts/mk_util.py @@ -1633,6 +1633,8 @@ class DotNetDLLComponent(Component): if not self.key_file is None: print("%s.dll will be signed using key '%s'." % (self.dll_name, self.key_file)) + if (self.key_file.find(' ') != -1): + self.key_file = '"' + self.key_file + '"' cscCmdLine.append('/keyfile:{}'.format(self.key_file)) cscCmdLine.extend( ['/unsafe+', @@ -2419,6 +2421,7 @@ def mk_config(): FOCI2 = False if GIT_HASH: CPPFLAGS = '%s -DZ3GITHASH=%s' % (CPPFLAGS, GIT_HASH) + CXXFLAGS = '%s -std=c++11' % CXXFLAGS CXXFLAGS = '%s -fvisibility=hidden -c' % CXXFLAGS FPMATH = test_fpmath(CXX) CXXFLAGS = '%s %s' % (CXXFLAGS, FPMATH_FLAGS) @@ -2443,8 +2446,8 @@ def mk_config(): CXXFLAGS = '%s -Wno-unknown-pragmas -Wno-overloaded-virtual -Wno-unused-value' % CXXFLAGS sysname, _, _, _, machine = os.uname() if sysname == 'Darwin': - SO_EXT = '.dylib' - SLIBFLAGS = '-dynamiclib' + SO_EXT = '.dylib' + SLIBFLAGS = '-dynamiclib' elif sysname == 'Linux': CXXFLAGS = '%s -fno-strict-aliasing -D_LINUX_' % CXXFLAGS OS_DEFINES = '-D_LINUX_' @@ -2685,8 +2688,8 @@ def get_full_version_string(major, minor, build, revision): if GIT_HASH: res += " " + GIT_HASH if GIT_DESCRIBE: - branch = check_output(['git', 'rev-parse', '--abbrev-ref', 'HEAD', '--long']) - res += " master " + check_output(['git', 'describe']) + branch = check_output(['git', 'rev-parse', '--abbrev-ref', 'HEAD']) + res += " " + branch + " " + check_output(['git', 'describe']) return '"' + res + '"' # Update files with the version number diff --git a/src/api/api_opt.cpp b/src/api/api_opt.cpp index 9d7e2cbf5..31ac3d103 100644 --- a/src/api/api_opt.cpp +++ b/src/api/api_opt.cpp @@ -128,7 +128,7 @@ extern "C" { lbool r = l_undef; cancel_eh eh(mk_c(c)->m().limit()); unsigned timeout = to_optimize_ptr(o)->get_params().get_uint("timeout", mk_c(c)->get_timeout()); - unsigned rlimit = mk_c(c)->get_rlimit(); + unsigned rlimit = to_optimize_ptr(o)->get_params().get_uint("rlimit", mk_c(c)->get_rlimit()); api::context::set_interruptable si(*(mk_c(c)), eh); { scoped_timer timer(timeout, &eh); diff --git a/src/api/api_pb.cpp b/src/api/api_pb.cpp index f19fd8661..bb4a40c9a 100644 --- a/src/api/api_pb.cpp +++ b/src/api/api_pb.cpp @@ -52,7 +52,7 @@ extern "C" { } Z3_ast Z3_API Z3_mk_pble(Z3_context c, unsigned num_args, - Z3_ast const args[], int _coeffs[], + Z3_ast const args[], int const _coeffs[], int k) { Z3_TRY; LOG_Z3_mk_pble(c, num_args, args, _coeffs, k); @@ -70,7 +70,7 @@ extern "C" { } Z3_ast Z3_API Z3_mk_pbge(Z3_context c, unsigned num_args, - Z3_ast const args[], int _coeffs[], + Z3_ast const args[], int const _coeffs[], int k) { Z3_TRY; LOG_Z3_mk_pble(c, num_args, args, _coeffs, k); @@ -88,7 +88,7 @@ extern "C" { } Z3_ast Z3_API Z3_mk_pbeq(Z3_context c, unsigned num_args, - Z3_ast const args[], int _coeffs[], + Z3_ast const args[], int const _coeffs[], int k) { Z3_TRY; LOG_Z3_mk_pble(c, num_args, args, _coeffs, k); diff --git a/src/api/api_quant.cpp b/src/api/api_quant.cpp index bf64aa571..e87b9446f 100644 --- a/src/api/api_quant.cpp +++ b/src/api/api_quant.cpp @@ -69,14 +69,17 @@ extern "C" { } expr * const* ps = reinterpret_cast(patterns); expr * const* no_ps = reinterpret_cast(no_patterns); - pattern_validator v(mk_c(c)->m()); - for (unsigned i = 0; i < num_patterns; i++) { - if (!v(num_decls, ps[i], 0, 0)) { - SET_ERROR_CODE(Z3_INVALID_PATTERN); - return 0; + symbol qid = to_symbol(quantifier_id); + bool is_rec = mk_c(c)->m().rec_fun_qid() == qid; + if (!is_rec) { + pattern_validator v(mk_c(c)->m()); + for (unsigned i = 0; i < num_patterns; i++) { + if (!v(num_decls, ps[i], 0, 0)) { + SET_ERROR_CODE(Z3_INVALID_PATTERN); + return 0; + } } } - sort* const* ts = reinterpret_cast(sorts); svector names; for (unsigned i = 0; i < num_decls; ++i) { @@ -88,7 +91,7 @@ extern "C" { (0 != is_forall), names.size(), ts, names.c_ptr(), to_expr(body), weight, - to_symbol(quantifier_id), + qid, to_symbol(skolem_id), num_patterns, ps, num_no_patterns, no_ps diff --git a/src/api/c++/z3++.h b/src/api/c++/z3++.h index b176253fd..9d9982523 100644 --- a/src/api/c++/z3++.h +++ b/src/api/c++/z3++.h @@ -86,7 +86,13 @@ namespace z3 { }; inline std::ostream & operator<<(std::ostream & out, exception const & e) { out << e.msg(); return out; } - +#if !defined(Z3_THROW) +#if __cpp_exceptions || _CPPUNWIND +#define Z3_THROW(x) throw x +#else +#define Z3_THROW(x) {} +#endif +#endif // !defined(Z3_THROW) /** \brief Z3 global configuration object. @@ -165,7 +171,7 @@ namespace z3 { Z3_error_code check_error() const { Z3_error_code e = Z3_get_error_code(m_ctx); if (e != Z3_OK && enable_exceptions()) - throw exception(Z3_get_error_msg(m_ctx, e)); + Z3_THROW(exception(Z3_get_error_msg(m_ctx, e))); return e; } @@ -701,7 +707,7 @@ namespace z3 { if (!is_numeral_i(result)) { assert(ctx().enable_exceptions()); if (!ctx().enable_exceptions()) return 0; - throw exception("numeral does not fit in machine int"); + Z3_THROW(exception("numeral does not fit in machine int")); } return result; } @@ -721,7 +727,7 @@ namespace z3 { if (!is_numeral_u(result)) { assert(ctx().enable_exceptions()); if (!ctx().enable_exceptions()) return 0; - throw exception("numeral does not fit in machine uint"); + Z3_THROW(exception("numeral does not fit in machine uint")); } return result; } @@ -738,7 +744,7 @@ namespace z3 { if (!is_numeral_i64(result)) { assert(ctx().enable_exceptions()); if (!ctx().enable_exceptions()) return 0; - throw exception("numeral does not fit in machine __int64"); + Z3_THROW(exception("numeral does not fit in machine __int64")); } return result; } @@ -755,7 +761,7 @@ namespace z3 { if (!is_numeral_u64(result)) { assert(ctx().enable_exceptions()); if (!ctx().enable_exceptions()) return 0; - throw exception("numeral does not fit in machine __uint64"); + Z3_THROW(exception("numeral does not fit in machine __uint64")); } return result; } @@ -890,6 +896,7 @@ namespace z3 { friend expr operator+(expr const & a, expr const & b); friend expr operator+(expr const & a, int b); friend expr operator+(int a, expr const & b); + friend expr sum(expr_vector const& args); friend expr operator*(expr const & a, expr const & b); friend expr operator*(expr const & a, int b); @@ -928,7 +935,6 @@ namespace z3 { friend expr operator>=(expr const & a, expr const & b); - friend expr wasoperator(expr const & a, expr const & b); friend expr operator>=(expr const & a, int b); friend expr operator>=(int a, expr const & b); @@ -940,6 +946,12 @@ namespace z3 { friend expr operator>(expr const & a, int b); friend expr operator>(int a, expr const & b); + friend expr pble(expr_vector const& es, int const * coeffs, int bound); + friend expr pbge(expr_vector const& es, int const * coeffs, int bound); + friend expr pbeq(expr_vector const& es, int const * coeffs, int bound); + friend expr atmost(expr_vector const& es, unsigned bound); + friend expr atleast(expr_vector const& es, unsigned bound); + friend expr operator&(expr const & a, expr const & b); friend expr operator&(expr const & a, int b); friend expr operator&(int a, expr const & b); @@ -1559,7 +1571,54 @@ namespace z3 { array vars(xs); Z3_ast r = Z3_mk_exists_const(b.ctx(), 0, vars.size(), vars.ptr(), 0, 0, b); b.check_error(); return expr(b.ctx(), r); } - + inline expr pble(expr_vector const& es, int const* coeffs, int bound) { + assert(es.size() > 0); + context& ctx = es[0].ctx(); + array _es(es); + Z3_ast r = Z3_mk_pble(ctx, _es.size(), _es.ptr(), coeffs, bound); + ctx.check_error(); + return expr(ctx, r); + } + inline expr pbge(expr_vector const& es, int const* coeffs, int bound) { + assert(es.size() > 0); + context& ctx = es[0].ctx(); + array _es(es); + Z3_ast r = Z3_mk_pbge(ctx, _es.size(), _es.ptr(), coeffs, bound); + ctx.check_error(); + return expr(ctx, r); + } + inline expr pbeq(expr_vector const& es, int const* coeffs, int bound) { + assert(es.size() > 0); + context& ctx = es[0].ctx(); + array _es(es); + Z3_ast r = Z3_mk_pbeq(ctx, _es.size(), _es.ptr(), coeffs, bound); + ctx.check_error(); + return expr(ctx, r); + } + inline expr atmost(expr_vector const& es, unsigned bound) { + assert(es.size() > 0); + context& ctx = es[0].ctx(); + array _es(es); + Z3_ast r = Z3_mk_atmost(ctx, _es.size(), _es.ptr(), bound); + ctx.check_error(); + return expr(ctx, r); + } + inline expr atleast(expr_vector const& es, unsigned bound) { + assert(es.size() > 0); + context& ctx = es[0].ctx(); + array _es(es); + Z3_ast r = Z3_mk_atleast(ctx, _es.size(), _es.ptr(), bound); + ctx.check_error(); + return expr(ctx, r); + } + inline expr sum(expr_vector const& args) { + assert(args.size() > 0); + context& ctx = args[0].ctx(); + array _args(args); + Z3_ast r = Z3_mk_add(ctx, _args.size(), _args.ptr()); + ctx.check_error(); + return expr(ctx, r); + } inline expr distinct(expr_vector const& args) { assert(args.size() > 0); @@ -1699,7 +1758,7 @@ namespace z3 { Z3_bool status = Z3_model_eval(ctx(), m_model, n, model_completion, &r); check_error(); if (status == Z3_FALSE && ctx().enable_exceptions()) - throw exception("failed to evaluate expression"); + Z3_THROW(exception("failed to evaluate expression")); return expr(ctx(), r); } @@ -2023,7 +2082,7 @@ namespace z3 { } inline tactic par_or(unsigned n, tactic const* tactics) { if (n == 0) { - throw exception("a non-zero number of tactics need to be passed to par_or"); + Z3_THROW(exception("a non-zero number of tactics need to be passed to par_or")); } array buffer(n); for (unsigned i = 0; i < n; ++i) buffer[i] = tactics[i]; @@ -2178,14 +2237,14 @@ namespace z3 { class fixedpoint : public object { Z3_fixedpoint m_fp; public: - fixedpoint(context& c):object(c) { mfp = Z3_mk_fixedpoint(c); Z3_fixedpoint_inc_ref(c, m_fp); } + fixedpoint(context& c):object(c) { m_fp = Z3_mk_fixedpoint(c); Z3_fixedpoint_inc_ref(c, m_fp); } ~fixedpoint() { Z3_fixedpoint_dec_ref(ctx(), m_fp); } operator Z3_fixedpoint() const { return m_fp; } void from_string(char const* s) { Z3_fixedpoint_from_string(ctx(), m_fp, s); check_error(); } void from_file(char const* s) { Z3_fixedpoint_from_file(ctx(), m_fp, s); check_error(); } void add_rule(expr& rule, symbol const& name) { Z3_fixedpoint_add_rule(ctx(), m_fp, rule, name); check_error(); } - void add_fact(func_decl& f, unsigned const* args) { Z3_fixedpoint_add_fact(ctx(), m_fp, f, f.num_args(), args); check_error(); } - check_result query(expr& q) { Z3_lbool r = Z3_fixedpoint_query(ctx(), m_fp, q); check_error(); to_check_result(r); } + void add_fact(func_decl& f, unsigned * args) { Z3_fixedpoint_add_fact(ctx(), m_fp, f, f.arity(), args); check_error(); } + check_result query(expr& q) { Z3_lbool r = Z3_fixedpoint_query(ctx(), m_fp, q); check_error(); return to_check_result(r); } check_result query(func_decl_vector& relations) { array rs(relations); Z3_lbool r = Z3_fixedpoint_query_relations(ctx(), m_fp, rs.size(), rs.ptr()); @@ -2194,9 +2253,13 @@ namespace z3 { } expr get_answer() { Z3_ast r = Z3_fixedpoint_get_answer(ctx(), m_fp); check_error(); return expr(ctx(), r); } std::string reason_unknown() { return Z3_fixedpoint_get_reason_unknown(ctx(), m_fp); } - void update_rule(expr& rule, synbol const& name) { Z3_fixedpoint_update_rule(ctx(), m_fp, rule, name); check_error(); } + void update_rule(expr& rule, symbol const& name) { Z3_fixedpoint_update_rule(ctx(), m_fp, rule, name); check_error(); } unsigned get_num_levels(func_decl& p) { unsigned r = Z3_fixedpoint_get_num_levels(ctx(), m_fp, p); check_error(); return r; } - expr get_cover_delta(int level, func_decl& p) { return Z3_fixedpoint_get_cover_delta(ctx(), m_fp, level, p); check_error(); } + expr get_cover_delta(int level, func_decl& p) { + Z3_ast r = Z3_fixedpoint_get_cover_delta(ctx(), m_fp, level, p); + check_error(); + return expr(ctx(), r); + } void add_cover(int level, func_decl& p, expr& property) { Z3_fixedpoint_add_cover(ctx(), m_fp, level, p, property); check_error(); } stats statistics() const { Z3_stats r = Z3_fixedpoint_get_statistics(ctx(), m_fp); check_error(); return stats(ctx(), r); } void register_relation(func_decl& p) { Z3_fixedpoint_register_relation(ctx(), m_fp, p); } @@ -2205,11 +2268,15 @@ namespace z3 { void set(params const & p) { Z3_fixedpoint_set_params(ctx(), m_fp, p); check_error(); } std::string help() const { return Z3_fixedpoint_get_help(ctx(), m_fp); } param_descrs get_param_descrs() { return param_descrs(ctx(), Z3_fixedpoint_get_param_descrs(ctx(), m_fp)); } - std::string to_string() { return Z3_fixedpoint_to_string(ctx(), m_fp); } + std::string to_string() { return Z3_fixedpoint_to_string(ctx(), m_fp, 0, 0); } + std::string to_string(expr_vector const& queries) { + array qs(queries); + return Z3_fixedpoint_to_string(ctx(), m_fp, qs.size(), qs.ptr()); + } void push() { Z3_fixedpoint_push(ctx(), m_fp); check_error(); } void pop() { Z3_fixedpoint_pop(ctx(), m_fp); check_error(); } }; - inline std::ostream & operator<<(std::ostream & out, fixedpoint const & f) { return out << Z3_fixedpoint_to_string(f.ctx(), f); } + inline std::ostream & operator<<(std::ostream & out, fixedpoint const & f) { return out << Z3_fixedpoint_to_string(f.ctx(), f, 0, 0); } inline tactic fail_if(probe const & p) { Z3_tactic r = Z3_tactic_fail_if(p.ctx(), p); diff --git a/src/api/java/Context.java b/src/api/java/Context.java index d50968a32..db7a08711 100644 --- a/src/api/java/Context.java +++ b/src/api/java/Context.java @@ -1890,43 +1890,43 @@ public class Context implements AutoCloseable { /** * Create the empty sequence. */ - public SeqExpr MkEmptySeq(Sort s) + public SeqExpr mkEmptySeq(Sort s) { checkContextMatch(s); - return new SeqExpr(this, Native.mkSeqEmpty(nCtx(), s.getNativeObject())); + return (SeqExpr) Expr.create(this, Native.mkSeqEmpty(nCtx(), s.getNativeObject())); } /** * Create the singleton sequence. */ - public SeqExpr MkUnit(Expr elem) + public SeqExpr mkUnit(Expr elem) { checkContextMatch(elem); - return new SeqExpr(this, Native.mkSeqUnit(nCtx(), elem.getNativeObject())); + return (SeqExpr) Expr.create(this, Native.mkSeqUnit(nCtx(), elem.getNativeObject())); } /** * Create a string constant. */ - public SeqExpr MkString(String s) + public SeqExpr mkString(String s) { - return new SeqExpr(this, Native.mkString(nCtx(), s)); + return (SeqExpr) Expr.create(this, Native.mkString(nCtx(), s)); } /** * Concatentate sequences. */ - public SeqExpr MkConcat(SeqExpr... t) + public SeqExpr mkConcat(SeqExpr... t) { checkContextMatch(t); - return new SeqExpr(this, Native.mkSeqConcat(nCtx(), t.length, AST.arrayToNative(t))); + return (SeqExpr) Expr.create(this, Native.mkSeqConcat(nCtx(), t.length, AST.arrayToNative(t))); } /** * Retrieve the length of a given sequence. */ - public IntExpr MkLength(SeqExpr s) + public IntExpr mkLength(SeqExpr s) { checkContextMatch(s); return (IntExpr) Expr.create(this, Native.mkSeqLength(nCtx(), s.getNativeObject())); @@ -1935,130 +1935,221 @@ public class Context implements AutoCloseable { /** * Check for sequence prefix. */ - public BoolExpr MkPrefixOf(SeqExpr s1, SeqExpr s2) + public BoolExpr mkPrefixOf(SeqExpr s1, SeqExpr s2) { checkContextMatch(s1, s2); - return new BoolExpr(this, Native.mkSeqPrefix(nCtx(), s1.getNativeObject(), s2.getNativeObject())); + return (BoolExpr) Expr.create(this, Native.mkSeqPrefix(nCtx(), s1.getNativeObject(), s2.getNativeObject())); } /** * Check for sequence suffix. */ - public BoolExpr MkSuffixOf(SeqExpr s1, SeqExpr s2) + public BoolExpr mkSuffixOf(SeqExpr s1, SeqExpr s2) { checkContextMatch(s1, s2); - return new BoolExpr(this, Native.mkSeqSuffix(nCtx(), s1.getNativeObject(), s2.getNativeObject())); + return (BoolExpr)Expr.create(this, Native.mkSeqSuffix(nCtx(), s1.getNativeObject(), s2.getNativeObject())); } /** * Check for sequence containment of s2 in s1. */ - public BoolExpr MkContains(SeqExpr s1, SeqExpr s2) + public BoolExpr mkContains(SeqExpr s1, SeqExpr s2) { checkContextMatch(s1, s2); - return new BoolExpr(this, Native.mkSeqContains(nCtx(), s1.getNativeObject(), s2.getNativeObject())); + return (BoolExpr) Expr.create(this, Native.mkSeqContains(nCtx(), s1.getNativeObject(), s2.getNativeObject())); } /** * Retrieve sequence of length one at index. */ - public SeqExpr MkAt(SeqExpr s, IntExpr index) + public SeqExpr mkAt(SeqExpr s, IntExpr index) { checkContextMatch(s, index); - return new SeqExpr(this, Native.mkSeqAt(nCtx(), s.getNativeObject(), index.getNativeObject())); + return (SeqExpr) Expr.create(this, Native.mkSeqAt(nCtx(), s.getNativeObject(), index.getNativeObject())); } /** * Extract subsequence. */ - public SeqExpr MkExtract(SeqExpr s, IntExpr offset, IntExpr length) + public SeqExpr mkExtract(SeqExpr s, IntExpr offset, IntExpr length) { checkContextMatch(s, offset, length); - return new SeqExpr(this, Native.mkSeqExtract(nCtx(), s.getNativeObject(), offset.getNativeObject(), length.getNativeObject())); + return (SeqExpr) Expr.create(this, Native.mkSeqExtract(nCtx(), s.getNativeObject(), offset.getNativeObject(), length.getNativeObject())); } /** * Extract index of sub-string starting at offset. */ - public IntExpr MkIndexOf(SeqExpr s, SeqExpr substr, ArithExpr offset) + public IntExpr mkIndexOf(SeqExpr s, SeqExpr substr, ArithExpr offset) { checkContextMatch(s, substr, offset); - return new IntExpr(this, Native.mkSeqIndex(nCtx(), s.getNativeObject(), substr.getNativeObject(), offset.getNativeObject())); + return (IntExpr)Expr.create(this, Native.mkSeqIndex(nCtx(), s.getNativeObject(), substr.getNativeObject(), offset.getNativeObject())); } /** * Replace the first occurrence of src by dst in s. */ - public SeqExpr MkReplace(SeqExpr s, SeqExpr src, SeqExpr dst) + public SeqExpr mkReplace(SeqExpr s, SeqExpr src, SeqExpr dst) { checkContextMatch(s, src, dst); - return new SeqExpr(this, Native.mkSeqReplace(nCtx(), s.getNativeObject(), src.getNativeObject(), dst.getNativeObject())); + return (SeqExpr) Expr.create(this, Native.mkSeqReplace(nCtx(), s.getNativeObject(), src.getNativeObject(), dst.getNativeObject())); } /** * Convert a regular expression that accepts sequence s. */ - public ReExpr MkToRe(SeqExpr s) + public ReExpr mkToRe(SeqExpr s) { checkContextMatch(s); - return new ReExpr(this, Native.mkSeqToRe(nCtx(), s.getNativeObject())); + return (ReExpr) Expr.create(this, Native.mkSeqToRe(nCtx(), s.getNativeObject())); } /** * Check for regular expression membership. */ - public BoolExpr MkInRe(SeqExpr s, ReExpr re) + public BoolExpr mkInRe(SeqExpr s, ReExpr re) { checkContextMatch(s, re); - return new BoolExpr(this, Native.mkSeqInRe(nCtx(), s.getNativeObject(), re.getNativeObject())); + return (BoolExpr) Expr.create(this, Native.mkSeqInRe(nCtx(), s.getNativeObject(), re.getNativeObject())); } /** * Take the Kleene star of a regular expression. */ - public ReExpr MkStar(ReExpr re) + public ReExpr mkStar(ReExpr re) { checkContextMatch(re); - return new ReExpr(this, Native.mkReStar(nCtx(), re.getNativeObject())); + return (ReExpr) Expr.create(this, Native.mkReStar(nCtx(), re.getNativeObject())); } + + /** + * Take the lower and upper-bounded Kleene star of a regular expression. + */ + public ReExpr mkLoop(ReExpr re, int lo, int hi) + { + return (ReExpr) Expr.create(this, Native.mkReLoop(nCtx(), re.getNativeObject(), lo, hi)); + } + + /** + * Take the lower-bounded Kleene star of a regular expression. + */ + public ReExpr mkLoop(ReExpr re, int lo) + { + return (ReExpr) Expr.create(this, Native.mkReLoop(nCtx(), re.getNativeObject(), lo, 0)); + } + /** * Take the Kleene plus of a regular expression. */ - public ReExpr MPlus(ReExpr re) + public ReExpr mkPlus(ReExpr re) { checkContextMatch(re); - return new ReExpr(this, Native.mkRePlus(nCtx(), re.getNativeObject())); + return (ReExpr) Expr.create(this, Native.mkRePlus(nCtx(), re.getNativeObject())); } /** * Create the optional regular expression. */ - public ReExpr MOption(ReExpr re) + public ReExpr mkOption(ReExpr re) { checkContextMatch(re); - return new ReExpr(this, Native.mkReOption(nCtx(), re.getNativeObject())); + return (ReExpr) Expr.create(this, Native.mkReOption(nCtx(), re.getNativeObject())); } + + /** + * Create the complement regular expression. + */ + public ReExpr mkComplement(ReExpr re) + { + checkContextMatch(re); + return (ReExpr) Expr.create(this, Native.mkReComplement(nCtx(), re.getNativeObject())); + } + /** * Create the concatenation of regular languages. */ - public ReExpr MkConcat(ReExpr... t) + public ReExpr mkConcat(ReExpr... t) { checkContextMatch(t); - return new ReExpr(this, Native.mkReConcat(nCtx(), t.length, AST.arrayToNative(t))); + return (ReExpr) Expr.create(this, Native.mkReConcat(nCtx(), t.length, AST.arrayToNative(t))); } /** * Create the union of regular languages. */ - public ReExpr MkUnion(ReExpr... t) + public ReExpr mkUnion(ReExpr... t) { checkContextMatch(t); - return new ReExpr(this, Native.mkReUnion(nCtx(), t.length, AST.arrayToNative(t))); + return (ReExpr) Expr.create(this, Native.mkReUnion(nCtx(), t.length, AST.arrayToNative(t))); } + + /** + * Create the intersection of regular languages. + */ + public ReExpr mkIntersect(ReExpr... t) + { + checkContextMatch(t); + return (ReExpr) Expr.create(this, Native.mkReIntersect(nCtx(), t.length, AST.arrayToNative(t))); + } + /** + * Create a range expression. + */ + public ReExpr MkRange(SeqExpr lo, SeqExpr hi) + { + checkContextMatch(lo, hi); + return (ReExpr) Expr.create(this, Native.mkReRange(nCtx(), lo.getNativeObject(), hi.getNativeObject())); + } + + + /** + * Create an at-most-k constraint. + */ + public BoolExpr mkAtMost(BoolExpr[] args, int k) + { + checkContextMatch(args); + return (BoolExpr) Expr.create(this, Native.mkAtmost(nCtx(), args.length, AST.arrayToNative(args), k)); + } + + /** + * Create an at-least-k constraint. + */ + public BoolExpr mkAtLeast(BoolExpr[] args, int k) + { + checkContextMatch(args); + return (BoolExpr) Expr.create(this, Native.mkAtleast(nCtx(), args.length, AST.arrayToNative(args), k)); + } + + /** + * Create a pseudo-Boolean less-or-equal constraint. + */ + public BoolExpr mkPBLe(int[] coeffs, BoolExpr[] args, int k) + { + checkContextMatch(args); + return (BoolExpr) Expr.create(this, Native.mkPble(nCtx(), args.length, AST.arrayToNative(args), coeffs, k)); + } + + /** + * Create a pseudo-Boolean greater-or-equal constraint. + */ + public BoolExpr mkPBGe(int[] coeffs, BoolExpr[] args, int k) + { + checkContextMatch(args); + return (BoolExpr) Expr.create(this, Native.mkPbge(nCtx(), args.length, AST.arrayToNative(args), coeffs, k)); + } + + /** + * Create a pseudo-Boolean equal constraint. + */ + public BoolExpr mkPBEq(int[] coeffs, BoolExpr[] args, int k) + { + checkContextMatch(args); + return (BoolExpr) Expr.create(this, Native.mkPbeq(nCtx(), args.length, AST.arrayToNative(args), coeffs, k)); + } + /** * Create a Term of a given sort. diff --git a/src/api/python/z3/z3.py b/src/api/python/z3/z3.py index a0aba95d6..b4cdf834b 100644 --- a/src/api/python/z3/z3.py +++ b/src/api/python/z3/z3.py @@ -3701,12 +3701,8 @@ def Extract(high, low, a): high = StringVal(high) if is_seq(high): s = high - offset = _py2expr(low, high.ctx) - length = _py2expr(a, high.ctx) - - if __debug__: - _z3_assert(is_int(offset) and is_int(length), "Second and third arguments must be integers") - return SeqRef(Z3_mk_seq_extract(s.ctx_ref(), s.as_ast(), offset.as_ast(), length.as_ast()), s.ctx) + offset, length = _coerce_exprs(low, a, s.ctx) + return SeqRef(Z3_mk_seq_extract(s.ctx_ref(), s.as_ast(), offset.as_ast(), length.as_ast()), s.ctx) if __debug__: _z3_assert(low <= high, "First argument must be greater than or equal to second argument") _z3_assert(_is_int(high) and high >= 0 and _is_int(low) and low >= 0, "First and second arguments must be non negative integers") @@ -6198,7 +6194,7 @@ class Solver(Z3PPObject): >>> s.consequences([a],[b,c,d]) (sat, [Implies(a, b), Implies(a, c)]) >>> s.consequences([Not(c),d],[a,b,c,d]) - (sat, [Implies(Not(c), Not(c)), Implies(d, d), Implies(Not(c), Not(b)), Implies(Not(c), Not(a))]) + (sat, [Implies(d, d), Implies(Not(c), Not(c)), Implies(Not(c), Not(b)), Implies(Not(c), Not(a))]) """ if isinstance(assumptions, list): _asms = AstVector(None, self.ctx) @@ -7204,7 +7200,7 @@ def With(t, *args, **keys): >>> t((x + 1)*(y + 2) == 0) [[2*x + y + x*y == -2]] """ - ctx = keys.get('ctx', None) + ctx = keys.pop('ctx', None) t = _to_tactic(t, ctx) p = args2params(args, keys, t.ctx) return Tactic(Z3_tactic_using_params(t.ctx.ref(), t.tactic, p.params), t.ctx) diff --git a/src/api/z3_api.h b/src/api/z3_api.h index ee35c002e..45065f856 100644 --- a/src/api/z3_api.h +++ b/src/api/z3_api.h @@ -1500,7 +1500,7 @@ extern "C" { All main interaction with Z3 happens in the context of a \c Z3_context. In contrast to #Z3_mk_context_rc, the life time of Z3_ast objects - are determined by the scope level of #Z3_push and #Z3_pop. + are determined by the scope level of #Z3_solver_push and #Z3_solver_pop. In other words, a Z3_ast object remains valid until there is a call to Z3_pop that takes the current scope below the level where the object was created. @@ -3091,8 +3091,8 @@ extern "C" { \brief Create a numeral of a given sort. \param c logical context. - \param numeral A string representing the numeral value in decimal notation. The string may be of the form \code{[num]*[.[num]*][E[+|-][num]+]}. - If the given sort is a real, then the numeral can be a rational, that is, a string of the form \ccode{[num]* / [num]*}. + \param numeral A string representing the numeral value in decimal notation. The string may be of the form `[num]*[.[num]*][E[+|-][num]+]`. + If the given sort is a real, then the numeral can be a rational, that is, a string of the form `[num]* / [num]*` . \param ty The sort of the numeral. In the current implementation, the given sort can be an int, real, finite-domain, or bit-vectors of arbitrary size. \sa Z3_mk_int @@ -3306,7 +3306,7 @@ extern "C" { Z3_ast Z3_API Z3_mk_seq_replace(Z3_context c, Z3_ast s, Z3_ast src, Z3_ast dst); /** - \brief Retrieve from \s the unit sequence positioned at position \c index. + \brief Retrieve from \c s the unit sequence positioned at position \c index. def_API('Z3_mk_seq_at' ,AST ,(_in(CONTEXT), _in(AST), _in(AST))) */ @@ -4006,7 +4006,7 @@ extern "C" { def_API('Z3_mk_pble', AST, (_in(CONTEXT), _in(UINT), _in_array(1,AST), _in_array(1,INT), _in(INT))) */ Z3_ast Z3_API Z3_mk_pble(Z3_context c, unsigned num_args, - Z3_ast const args[], int coeffs[], + Z3_ast const args[], int const coeffs[], int k); /** @@ -4017,7 +4017,7 @@ extern "C" { def_API('Z3_mk_pbge', AST, (_in(CONTEXT), _in(UINT), _in_array(1,AST), _in_array(1,INT), _in(INT))) */ Z3_ast Z3_API Z3_mk_pbge(Z3_context c, unsigned num_args, - Z3_ast const args[], int coeffs[], + Z3_ast const args[], int const coeffs[], int k); /** @@ -4028,7 +4028,7 @@ extern "C" { def_API('Z3_mk_pbeq', AST, (_in(CONTEXT), _in(UINT), _in_array(1,AST), _in_array(1,INT), _in(INT))) */ Z3_ast Z3_API Z3_mk_pbeq(Z3_context c, unsigned num_args, - Z3_ast const args[], int coeffs[], + Z3_ast const args[], int const coeffs[], int k); /** diff --git a/src/ast/arith_decl_plugin.cpp b/src/ast/arith_decl_plugin.cpp index 546f037de..1c01496cf 100644 --- a/src/ast/arith_decl_plugin.cpp +++ b/src/ast/arith_decl_plugin.cpp @@ -35,7 +35,7 @@ struct arith_decl_plugin::algebraic_numbers_wrapper { ~algebraic_numbers_wrapper() { } - + unsigned mk_id(algebraic_numbers::anum const & val) { SASSERT(!m_amanager.is_rational(val)); unsigned new_id = m_id_gen.mk(); @@ -121,7 +121,7 @@ void arith_decl_plugin::set_manager(ast_manager * m, family_id id) { m_int_decl = m->mk_sort(symbol("Int"), sort_info(id, INT_SORT)); m->inc_ref(m_int_decl); sort * i = m_int_decl; - + sort * b = m->mk_bool_sort(); #define MK_PRED(FIELD, NAME, KIND, SORT) { \ @@ -140,7 +140,7 @@ void arith_decl_plugin::set_manager(ast_manager * m, family_id id) { MK_PRED(m_i_ge_decl, ">=", OP_GE, i); MK_PRED(m_i_lt_decl, "<", OP_LT, i); MK_PRED(m_i_gt_decl, ">", OP_GT, i); - + #define MK_AC_OP(FIELD, NAME, KIND, SORT) { \ func_decl_info info(id, KIND); \ info.set_associative(); \ @@ -205,7 +205,7 @@ void arith_decl_plugin::set_manager(ast_manager * m, family_id id) { MK_UNARY(m_asinh_decl, "asinh", OP_ASINH, r); MK_UNARY(m_acosh_decl, "acosh", OP_ACOSH, r); MK_UNARY(m_atanh_decl, "atanh", OP_ATANH, r); - + func_decl * pi_decl = m->mk_const_decl(symbol("pi"), r, func_decl_info(id, OP_PI)); m_pi = m->mk_const(pi_decl); m->inc_ref(m_pi); @@ -213,7 +213,7 @@ void arith_decl_plugin::set_manager(ast_manager * m, family_id id) { func_decl * e_decl = m->mk_const_decl(symbol("euler"), r, func_decl_info(id, OP_E)); m_e = m->mk_const(e_decl); m->inc_ref(m_e); - + func_decl * z_pw_z_int = m->mk_const_decl(symbol("0^0-int"), i, func_decl_info(id, OP_0_PW_0_INT)); m_0_pw_0_int = m->mk_const(z_pw_z_int); m->inc_ref(m_0_pw_0_int); @@ -221,7 +221,7 @@ void arith_decl_plugin::set_manager(ast_manager * m, family_id id) { func_decl * z_pw_z_real = m->mk_const_decl(symbol("0^0-real"), r, func_decl_info(id, OP_0_PW_0_REAL)); m_0_pw_0_real = m->mk_const(z_pw_z_real); m->inc_ref(m_0_pw_0_real); - + MK_OP(m_neg_root_decl, "neg-root", OP_NEG_ROOT, r); MK_UNARY(m_div_0_decl, "/0", OP_DIV_0, r); MK_UNARY(m_idiv_0_decl, "div0", OP_IDIV_0, i); @@ -285,7 +285,8 @@ arith_decl_plugin::arith_decl_plugin(): m_idiv_0_decl(0), m_mod_0_decl(0), m_u_asin_decl(0), - m_u_acos_decl(0) { + m_u_acos_decl(0), + m_convert_int_numerals_to_real(false) { } arith_decl_plugin::~arith_decl_plugin() { @@ -418,7 +419,7 @@ app * arith_decl_plugin::mk_numeral(rational const & val, bool is_int) { if (val.is_unsigned()) { unsigned u_val = val.get_unsigned(); if (u_val < MAX_SMALL_NUM_TO_CACHE) { - if (is_int) { + if (is_int && !m_convert_int_numerals_to_real) { app * r = m_small_ints.get(u_val, 0); if (r == 0) { parameter p[2] = { parameter(val), parameter(1) }; @@ -442,7 +443,7 @@ app * arith_decl_plugin::mk_numeral(rational const & val, bool is_int) { } parameter p[2] = { parameter(val), parameter(static_cast(is_int)) }; func_decl * decl; - if (is_int) + if (is_int && !m_convert_int_numerals_to_real) decl = m_manager->mk_const_decl(m_intv_sym, m_int_decl, func_decl_info(m_family_id, OP_NUM, 2, p)); else decl = m_manager->mk_const_decl(m_realv_sym, m_real_decl, func_decl_info(m_family_id, OP_NUM, 2, p)); @@ -479,14 +480,14 @@ static bool has_real_arg(ast_manager * m, unsigned num_args, expr * const * args } static bool is_const_op(decl_kind k) { - return - k == OP_PI || + return + k == OP_PI || k == OP_E || k == OP_0_PW_0_INT || k == OP_0_PW_0_REAL; } - -func_decl * arith_decl_plugin::mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + +func_decl * arith_decl_plugin::mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, unsigned arity, sort * const * domain, sort * range) { if (k == OP_NUM) return mk_num_decl(num_parameters, parameters, arity); @@ -503,7 +504,7 @@ func_decl * arith_decl_plugin::mk_func_decl(decl_kind k, unsigned num_parameters } } -func_decl * arith_decl_plugin::mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, +func_decl * arith_decl_plugin::mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, unsigned num_args, expr * const * args, sort * range) { if (k == OP_NUM) return mk_num_decl(num_parameters, parameters, num_args); @@ -521,9 +522,17 @@ func_decl * arith_decl_plugin::mk_func_decl(decl_kind k, unsigned num_parameters } void arith_decl_plugin::get_sort_names(svector& sort_names, symbol const & logic) { - // TODO: only define Int and Real in the right logics - sort_names.push_back(builtin_name("Int", INT_SORT)); - sort_names.push_back(builtin_name("Real", REAL_SORT)); + if (logic == "NRA" || + logic == "QF_NRA" || + logic == "QF_UFNRA") { + m_convert_int_numerals_to_real = true; + sort_names.push_back(builtin_name("Real", REAL_SORT)); + } + else { + // TODO: only define Int and Real in the right logics + sort_names.push_back(builtin_name("Int", INT_SORT)); + sort_names.push_back(builtin_name("Real", REAL_SORT)); + } } void arith_decl_plugin::get_op_names(svector& op_names, symbol const & logic) { @@ -563,16 +572,16 @@ void arith_decl_plugin::get_op_names(svector& op_names, symbol con } bool arith_decl_plugin::is_value(app * e) const { - return - is_app_of(e, m_family_id, OP_NUM) || + return + is_app_of(e, m_family_id, OP_NUM) || is_app_of(e, m_family_id, OP_IRRATIONAL_ALGEBRAIC_NUM) || is_app_of(e, m_family_id, OP_PI) || is_app_of(e, m_family_id, OP_E); } bool arith_decl_plugin::is_unique_value(app * e) const { - return - is_app_of(e, m_family_id, OP_NUM) || + return + is_app_of(e, m_family_id, OP_NUM) || is_app_of(e, m_family_id, OP_PI) || is_app_of(e, m_family_id, OP_E); } @@ -671,7 +680,7 @@ expr_ref arith_util::mk_mul_simplify(expr_ref_vector const& args) { } expr_ref arith_util::mk_mul_simplify(unsigned sz, expr* const* args) { expr_ref result(m_manager); - + switch (sz) { case 0: result = mk_numeral(rational(1), true); @@ -681,7 +690,7 @@ expr_ref arith_util::mk_mul_simplify(unsigned sz, expr* const* args) { break; default: result = mk_mul(sz, args); - break; + break; } return result; } @@ -692,7 +701,7 @@ expr_ref arith_util::mk_add_simplify(expr_ref_vector const& args) { } expr_ref arith_util::mk_add_simplify(unsigned sz, expr* const* args) { expr_ref result(m_manager); - + switch (sz) { case 0: result = mk_numeral(rational(0), true); @@ -702,7 +711,7 @@ expr_ref arith_util::mk_add_simplify(unsigned sz, expr* const* args) { break; default: result = mk_add(sz, args); - break; + break; } return result; } diff --git a/src/ast/arith_decl_plugin.h b/src/ast/arith_decl_plugin.h index 410c50852..668eebcc9 100644 --- a/src/ast/arith_decl_plugin.h +++ b/src/ast/arith_decl_plugin.h @@ -152,6 +152,8 @@ protected: ptr_vector m_small_ints; ptr_vector m_small_reals; + bool m_convert_int_numerals_to_real; + func_decl * mk_func_decl(decl_kind k, bool is_real); virtual void set_manager(ast_manager * m, family_id id); decl_kind fix_kind(decl_kind k, unsigned arity); diff --git a/src/ast/ast.cpp b/src/ast/ast.cpp index 7be7300a2..5f2de5170 100644 --- a/src/ast/ast.cpp +++ b/src/ast/ast.cpp @@ -17,6 +17,7 @@ Revision History: --*/ #include +#include #include"ast.h" #include"ast_pp.h" #include"ast_ll_pp.h" diff --git a/src/ast/ast.h b/src/ast/ast.h index 9259d5431..6bb3b01c9 100644 --- a/src/ast/ast.h +++ b/src/ast/ast.h @@ -117,6 +117,9 @@ public: explicit parameter(symbol const & s): m_kind(PARAM_SYMBOL) { new (m_symbol) symbol(s); } explicit parameter(rational const & r): m_kind(PARAM_RATIONAL) { new (m_rational) rational(r); } explicit parameter(double d):m_kind(PARAM_DOUBLE), m_dval(d) {} + explicit parameter(const char *s):m_kind(PARAM_SYMBOL) { + new (m_symbol) symbol(s); + } explicit parameter(unsigned ext_id, bool):m_kind(PARAM_EXTERNAL), m_ext_id(ext_id) {} parameter(parameter const&); diff --git a/src/ast/ast_smt2_pp.cpp b/src/ast/ast_smt2_pp.cpp index 98c3b7962..89af8bd3e 100644 --- a/src/ast/ast_smt2_pp.cpp +++ b/src/ast/ast_smt2_pp.cpp @@ -1002,14 +1002,6 @@ class smt2_printer { reset_stacks(); SASSERT(&(r.get_manager()) == &(fm())); m_soccs(n); - TRACE("smt2_pp_shared", - tout << "shared terms for:\n" << mk_pp(n, m()) << "\n"; - tout << "------>\n"; - shared_occs::iterator it = m_soccs.begin_shared(); - shared_occs::iterator end = m_soccs.end_shared(); - for (; it != end; ++it) { - tout << mk_pp(*it, m()) << "\n"; - }); m_root = n; push_frame(n, true); while (!m_frame_stack.empty()) { diff --git a/src/ast/bv_decl_plugin.h b/src/ast/bv_decl_plugin.h index ac0ff7f79..33cf094b9 100644 --- a/src/ast/bv_decl_plugin.h +++ b/src/ast/bv_decl_plugin.h @@ -406,11 +406,11 @@ public: app * mk_bv_not(expr * arg) { return m_manager.mk_app(get_fid(), OP_BNOT, arg); } app * mk_bv_xor(unsigned num, expr * const * args) { return m_manager.mk_app(get_fid(), OP_BXOR, num, args); } app * mk_bv_neg(expr * arg) { return m_manager.mk_app(get_fid(), OP_BNEG, arg); } - app * mk_bv_urem(expr * arg1, expr * arg2) { return m_manager.mk_app(get_fid(), OP_BUREM, arg1, arg2); } - app * mk_bv_srem(expr * arg1, expr * arg2) { return m_manager.mk_app(get_fid(), OP_BSREM, arg1, arg2); } - app * mk_bv_add(expr * arg1, expr * arg2) { return m_manager.mk_app(get_fid(), OP_BADD, arg1, arg2); } - app * mk_bv_sub(expr * arg1, expr * arg2) { return m_manager.mk_app(get_fid(), OP_BSUB, arg1, arg2); } - app * mk_bv_mul(expr * arg1, expr * arg2) { return m_manager.mk_app(get_fid(), OP_BMUL, arg1, arg2); } + app * mk_bv_urem(expr * arg1, expr * arg2) const { return m_manager.mk_app(get_fid(), OP_BUREM, arg1, arg2); } + app * mk_bv_srem(expr * arg1, expr * arg2) const { return m_manager.mk_app(get_fid(), OP_BSREM, arg1, arg2); } + app * mk_bv_add(expr * arg1, expr * arg2) const { return m_manager.mk_app(get_fid(), OP_BADD, arg1, arg2); } + app * mk_bv_sub(expr * arg1, expr * arg2) const { return m_manager.mk_app(get_fid(), OP_BSUB, arg1, arg2); } + app * mk_bv_mul(expr * arg1, expr * arg2) const { return m_manager.mk_app(get_fid(), OP_BMUL, arg1, arg2); } app * mk_zero_extend(unsigned n, expr* e) { parameter p(n); return m_manager.mk_app(get_fid(), OP_ZERO_EXT, 1, &p, 1, &e); diff --git a/src/ast/decl_collector.cpp b/src/ast/decl_collector.cpp index b663a9df3..773ebefc7 100644 --- a/src/ast/decl_collector.cpp +++ b/src/ast/decl_collector.cpp @@ -23,8 +23,21 @@ void decl_collector::visit_sort(sort * n) { family_id fid = n->get_family_id(); if (m().is_uninterp(n)) m_sorts.push_back(n); - if (fid == m_dt_fid) + if (fid == m_dt_fid) { m_sorts.push_back(n); + + unsigned num_cnstr = m_dt_util.get_datatype_num_constructors(n); + for (unsigned i = 0; i < num_cnstr; i++) { + func_decl * cnstr = m_dt_util.get_datatype_constructors(n)->get(i); + m_decls.push_back(cnstr); + ptr_vector const & cnstr_acc = *m_dt_util.get_constructor_accessors(cnstr); + unsigned num_cas = cnstr_acc.size(); + for (unsigned j = 0; j < num_cas; j++) { + func_decl * accsr = cnstr_acc.get(j); + m_decls.push_back(accsr); + } + } + } } bool decl_collector::is_bool(sort * s) { @@ -38,14 +51,15 @@ void decl_collector::visit_func(func_decl * n) { m_preds.push_back(n); else m_decls.push_back(n); - } + } } decl_collector::decl_collector(ast_manager & m, bool preds): m_manager(m), - m_sep_preds(preds) { + m_sep_preds(preds), + m_dt_util(m) { m_basic_fid = m_manager.get_basic_family_id(); - m_dt_fid = m_manager.mk_family_id("datatype"); + m_dt_fid = m_dt_util.get_family_id(); } void decl_collector::visit(ast* n) { @@ -55,7 +69,7 @@ void decl_collector::visit(ast* n) { n = todo.back(); todo.pop_back(); if (!m_visited.is_marked(n)) { - m_visited.mark(n, true); + m_visited.mark(n, true); switch(n->get_kind()) { case AST_APP: { app * a = to_app(n); @@ -64,7 +78,7 @@ void decl_collector::visit(ast* n) { } todo.push_back(a->get_decl()); break; - } + } case AST_QUANTIFIER: { quantifier * q = to_quantifier(n); unsigned num_decls = q->get_num_decls(); @@ -77,7 +91,7 @@ void decl_collector::visit(ast* n) { } break; } - case AST_SORT: + case AST_SORT: visit_sort(to_sort(n)); break; case AST_FUNC_DECL: { diff --git a/src/ast/decl_collector.h b/src/ast/decl_collector.h index 678f3805d..0067d18eb 100644 --- a/src/ast/decl_collector.h +++ b/src/ast/decl_collector.h @@ -21,6 +21,7 @@ Revision History: #define SMT_DECL_COLLECTOR_H_ #include"ast.h" +#include"datatype_decl_plugin.h" class decl_collector { ast_manager & m_manager; @@ -28,9 +29,10 @@ class decl_collector { ptr_vector m_sorts; ptr_vector m_decls; ptr_vector m_preds; - ast_mark m_visited; - family_id m_basic_fid; - family_id m_dt_fid; + ast_mark m_visited; + family_id m_basic_fid; + family_id m_dt_fid; + datatype_util m_dt_util; void visit_sort(sort* n); bool is_bool(sort* s); diff --git a/src/ast/macros/macro_util.cpp b/src/ast/macros/macro_util.cpp index 99732871c..fce6f1b28 100644 --- a/src/ast/macros/macro_util.cpp +++ b/src/ast/macros/macro_util.cpp @@ -19,22 +19,22 @@ Revision History: --*/ #include"macro_util.h" #include"occurs.h" +#include"ast_util.h" #include"arith_simplifier_plugin.h" -#include"basic_simplifier_plugin.h" #include"bv_simplifier_plugin.h" #include"var_subst.h" #include"ast_pp.h" #include"ast_ll_pp.h" -#include"ast_util.h" #include"for_each_expr.h" #include"well_sorted.h" +#include"bool_rewriter.h" macro_util::macro_util(ast_manager & m, simplifier & s): m_manager(m), + m_bv(m), m_simplifier(s), m_arith_simp(0), m_bv_simp(0), - m_basic_simp(0), m_forbidden_set(0), m_curr_clause(0) { } @@ -55,24 +55,17 @@ bv_simplifier_plugin * macro_util::get_bv_simp() const { return m_bv_simp; } -basic_simplifier_plugin * macro_util::get_basic_simp() const { - if (m_basic_simp == 0) { - const_cast(this)->m_basic_simp = static_cast(m_simplifier.get_plugin(m_manager.get_basic_family_id())); - } - SASSERT(m_basic_simp != 0); - return m_basic_simp; -} bool macro_util::is_bv(expr * n) const { - return get_bv_simp()->is_bv(n); + return m_bv.is_bv(n); } bool macro_util::is_bv_sort(sort * s) const { - return get_bv_simp()->is_bv_sort(s); + return m_bv.is_bv_sort(s); } bool macro_util::is_add(expr * n) const { - return get_arith_simp()->is_add(n) || get_bv_simp()->is_add(n); + return get_arith_simp()->is_add(n) || m_bv.is_bv_add(n); } bool macro_util::is_times_minus_one(expr * n, expr * & arg) const { @@ -80,11 +73,11 @@ bool macro_util::is_times_minus_one(expr * n, expr * & arg) const { } bool macro_util::is_le(expr * n) const { - return get_arith_simp()->is_le(n) || get_bv_simp()->is_le(n); + return get_arith_simp()->is_le(n) || m_bv.is_bv_ule(n) || m_bv.is_bv_sle(n); } bool macro_util::is_le_ge(expr * n) const { - return get_arith_simp()->is_le_ge(n) || get_bv_simp()->is_le_ge(n); + return get_arith_simp()->is_le_ge(n) || m_bv.is_bv_ule(n) || m_bv.is_bv_sle(n); } poly_simplifier_plugin * macro_util::get_poly_simp_for(sort * s) const { @@ -102,7 +95,7 @@ app * macro_util::mk_zero(sort * s) const { void macro_util::mk_sub(expr * t1, expr * t2, expr_ref & r) const { if (is_bv(t1)) { - get_bv_simp()->mk_sub(t1, t2, r); + r = m_bv.mk_bv_sub(t1, t2); } else { get_arith_simp()->mk_sub(t1, t2, r); @@ -111,7 +104,7 @@ void macro_util::mk_sub(expr * t1, expr * t2, expr_ref & r) const { void macro_util::mk_add(expr * t1, expr * t2, expr_ref & r) const { if (is_bv(t1)) { - get_bv_simp()->mk_add(t1, t2, r); + r = m_bv.mk_bv_add(t1, t2); } else { get_arith_simp()->mk_add(t1, t2, r); @@ -429,7 +422,7 @@ void macro_util::quasi_macro_head_to_macro_head(app * qhead, unsigned & num_decl new_args.push_back(new_var); new_conds.push_back(new_cond); } - get_basic_simp()->mk_and(new_conds.size(), new_conds.c_ptr(), cond); + bool_rewriter(m_manager).mk_and(new_conds.size(), new_conds.c_ptr(), cond); head = m_manager.mk_app(qhead->get_decl(), new_args.size(), new_args.c_ptr()); num_decls = next_var_idx; } @@ -687,7 +680,7 @@ void macro_util::insert_quasi_macro(app * head, unsigned num_decls, expr * def, if (cond == 0) new_cond = extra_cond; else - get_basic_simp()->mk_and(cond, extra_cond, new_cond); + bool_rewriter(m_manager).mk_and(cond, extra_cond, new_cond); } else { hint_to_macro_head(m_manager, head, num_decls, new_head); @@ -719,20 +712,19 @@ void macro_util::get_rest_clause_as_cond(expr * except_lit, expr_ref & extra_con if (m_curr_clause == 0) return; SASSERT(is_clause(m_manager, m_curr_clause)); - basic_simplifier_plugin * bs = get_basic_simp(); expr_ref_buffer neg_other_lits(m_manager); unsigned num_lits = get_clause_num_literals(m_manager, m_curr_clause); for (unsigned i = 0; i < num_lits; i++) { expr * l = get_clause_literal(m_manager, m_curr_clause, i); if (l != except_lit) { expr_ref neg_l(m_manager); - bs->mk_not(l, neg_l); + bool_rewriter(m_manager).mk_not(l, neg_l); neg_other_lits.push_back(neg_l); } } if (neg_other_lits.empty()) return; - get_basic_simp()->mk_and(neg_other_lits.size(), neg_other_lits.c_ptr(), extra_cond); + bool_rewriter(m_manager).mk_and(neg_other_lits.size(), neg_other_lits.c_ptr(), extra_cond); } void macro_util::collect_poly_args(expr * n, expr * exception, ptr_buffer & args) { diff --git a/src/ast/macros/macro_util.h b/src/ast/macros/macro_util.h index 033f6ecb4..8aa8e550e 100644 --- a/src/ast/macros/macro_util.h +++ b/src/ast/macros/macro_util.h @@ -62,10 +62,10 @@ public: private: ast_manager & m_manager; + bv_util m_bv; simplifier & m_simplifier; arith_simplifier_plugin * m_arith_simp; bv_simplifier_plugin * m_bv_simp; - basic_simplifier_plugin * m_basic_simp; obj_hashtable * m_forbidden_set; bool is_forbidden(func_decl * f) const { return m_forbidden_set != 0 && m_forbidden_set->contains(f); } @@ -99,7 +99,6 @@ public: arith_simplifier_plugin * get_arith_simp() const; bv_simplifier_plugin * get_bv_simp() const; - basic_simplifier_plugin * get_basic_simp() const; bool is_macro_head(expr * n, unsigned num_decls) const; bool is_left_simple_macro(expr * n, unsigned num_decls, app_ref & head, expr_ref & def) const; diff --git a/src/ast/normal_forms/defined_names.cpp b/src/ast/normal_forms/defined_names.cpp index c1d9b36a5..1ac2049ac 100644 --- a/src/ast/normal_forms/defined_names.cpp +++ b/src/ast/normal_forms/defined_names.cpp @@ -28,7 +28,7 @@ struct defined_names::impl { typedef obj_map expr2proof; ast_manager & m_manager; symbol m_z3name; - + /** \brief Mapping from expressions to their names. A name is an application. If the expression does not have free variables, then the name is just a constant. @@ -38,25 +38,25 @@ struct defined_names::impl { \brief Mapping from expressions to the apply-def proof. That is, for each expression e, m_expr2proof[e] is the proof e and m_expr2name[2] are observ. equivalent. - + This mapping is not used if proof production is disabled. */ expr2proof m_expr2proof; - + /** \brief Domain of m_expr2name. It is used to keep the expressions alive and for backtracking */ - expr_ref_vector m_exprs; + expr_ref_vector m_exprs; expr_ref_vector m_names; //!< Range of m_expr2name. It is used to keep the names alive. proof_ref_vector m_apply_proofs; //!< Range of m_expr2proof. It is used to keep the def-intro proofs alive. - - + + unsigned_vector m_lims; //!< Backtracking support. - + impl(ast_manager & m, char const * prefix); virtual ~impl(); - + app * gen_name(expr * e, sort_ref_buffer & var_sorts, buffer & var_names); void cache_new_name(expr * e, app * name); void cache_new_name_intro_proof(expr * e, proof * pr); @@ -106,7 +106,7 @@ app * defined_names::impl::gen_name(expr * e, sort_ref_buffer & var_sorts, buffe for (unsigned i = 0; i < num_vars; i++) { sort * s = uv.get(i); if (s) { - domain.push_back(s); + domain.push_back(s); new_args.push_back(m_manager.mk_var(i, s)); var_sorts.push_back(s); } @@ -162,7 +162,7 @@ void defined_names::impl::bound_vars(sort_ref_buffer const & sorts, buffer var_names; - + n = gen_name(e, var_sorts, var_names); cache_new_name(e, n); - + TRACE("mk_definition_bug", tout << "name: " << mk_ismt2_pp(n, m_manager) << "\n";); // variables are in reverse order in quantifiers std::reverse(var_sorts.c_ptr(), var_sorts.c_ptr() + var_sorts.size()); std::reverse(var_names.c_ptr(), var_names.c_ptr() + var_names.size()); - + mk_definition(e, n, var_sorts, var_names, new_def); - + TRACE("mk_definition_bug", tout << "new_def:\n" << mk_ismt2_pp(new_def, m_manager) << "\n";); - + if (m_manager.proofs_enabled()) { new_def_pr = m_manager.mk_def_intro(new_def); pr = m_manager.mk_apply_def(e, n, new_def_pr); @@ -311,11 +311,11 @@ void defined_names::reset() { m_pos_impl->reset(); } -unsigned defined_names::get_num_names() const { +unsigned defined_names::get_num_names() const { return m_impl->get_num_names() + m_pos_impl->get_num_names(); } -func_decl * defined_names::get_name_decl(unsigned i) const { +func_decl * defined_names::get_name_decl(unsigned i) const { SASSERT(i < get_num_names()); unsigned n1 = m_impl->get_num_names(); return i < n1 ? m_impl->get_name_decl(i) : m_pos_impl->get_name_decl(i - n1); diff --git a/src/ast/rewriter/arith_rewriter.cpp b/src/ast/rewriter/arith_rewriter.cpp index 81385c2af..2b2087e3b 100644 --- a/src/ast/rewriter/arith_rewriter.cpp +++ b/src/ast/rewriter/arith_rewriter.cpp @@ -194,7 +194,7 @@ bool arith_rewriter::is_bound(expr * arg1, expr * arg2, op_kind kind, expr_ref & } } expr* t1, *t2; - bool is_int; + bool is_int = false; if (m_util.is_mod(arg2)) { std::swap(arg1, arg2); switch (kind) { diff --git a/src/ast/rewriter/der.cpp b/src/ast/rewriter/der.cpp index 83ed94ece..aef5d8ddd 100644 --- a/src/ast/rewriter/der.cpp +++ b/src/ast/rewriter/der.cpp @@ -36,7 +36,7 @@ static bool is_neg_var(ast_manager & m, expr * e, unsigned num_decls) { /** \brief Return true if \c e is of the form (not (= VAR t)) or (not (iff VAR t)) or (iff VAR t) or (iff (not VAR) t) or (VAR IDX) or (not (VAR IDX)). - The last case can be viewed + The last case can be viewed */ bool der::is_var_diseq(expr * e, unsigned num_decls, var * & v, expr_ref & t) { // (not (= VAR t)) and (not (iff VAR t)) cases @@ -49,7 +49,7 @@ bool der::is_var_diseq(expr * e, unsigned num_decls, var * & v, expr_ref & t) { return false; if (!is_var(lhs, num_decls)) std::swap(lhs, rhs); - SASSERT(is_var(lhs, num_decls)); + SASSERT(is_var(lhs, num_decls)); // Remark: Occurs check is not necessary here... the top-sort procedure will check for cycles... // if (occurs(lhs, rhs)) { // return false; @@ -67,7 +67,7 @@ bool der::is_var_diseq(expr * e, unsigned num_decls, var * & v, expr_ref & t) { if (is_var(lhs, num_decls) || is_var(rhs, num_decls)) { if (!is_var(lhs, num_decls)) std::swap(lhs, rhs); - SASSERT(is_var(lhs, num_decls)); + SASSERT(is_var(lhs, num_decls)); // Remark: Occurs check is not necessary here... the top-sort procedure will check for cycles... // if (occurs(lhs, rhs)) { // return false; @@ -83,11 +83,11 @@ bool der::is_var_diseq(expr * e, unsigned num_decls, var * & v, expr_ref & t) { if (!is_neg_var(m_manager, lhs, num_decls)) std::swap(lhs, rhs); SASSERT(is_neg_var(m_manager, lhs, num_decls)); - expr * lhs_var = to_app(lhs)->get_arg(0); + expr * lhs_var = to_app(lhs)->get_arg(0); // Remark: Occurs check is not necessary here... the top-sort procedure will check for cycles... // if (occurs(lhs_var, rhs)) { // return false; - // } + // } v = to_var(lhs_var); t = rhs; TRACE("der", tout << mk_pp(e, m_manager) << "\n";); @@ -134,11 +134,11 @@ void der::operator()(quantifier * q, expr_ref & r, proof_ref & pr) { pr = m_manager.mk_transitivity(pr, curr_pr); } } while (q != r && is_quantifier(r)); - + // Eliminate variables that have become unused if (reduced && is_forall(r)) { quantifier * q = to_quantifier(r); - elim_unused_vars(m_manager, q, r); + elim_unused_vars(m_manager, q, params_ref(), r); if (m_manager.proofs_enabled()) { proof * p1 = m_manager.mk_elim_unused_vars(q, r); pr = m_manager.mk_transitivity(pr, p1); @@ -153,24 +153,24 @@ void der::reduce1(quantifier * q, expr_ref & r, proof_ref & pr) { r = q; return; } - + expr * e = q->get_expr(); unsigned num_decls = q->get_num_decls(); var * v = 0; - expr_ref t(m_manager); + expr_ref t(m_manager); if (m_manager.is_or(e)) { unsigned num_args = to_app(e)->get_num_args(); unsigned i = 0; unsigned diseq_count = 0; unsigned largest_vinx = 0; - + m_map.reset(); m_pos2var.reset(); m_inx2var.reset(); - + m_pos2var.reserve(num_args, -1); - + // Find all disequalities for (; i < num_args; i++) { if (is_var_diseq(to_app(e)->get_arg(i), num_decls, v, t)) { @@ -192,7 +192,7 @@ void der::reduce1(quantifier * q, expr_ref & r, proof_ref & pr) { get_elimination_order(); SASSERT(m_order.size() <= diseq_count); // some might be missing because of cycles - if (!m_order.empty()) { + if (!m_order.empty()) { create_substitution(largest_vinx + 1); apply_substitution(q, r); } @@ -202,22 +202,22 @@ void der::reduce1(quantifier * q, expr_ref & r, proof_ref & pr) { r = q; } } - // Remark: get_elimination_order/top-sort checks for cycles, but it is not invoked for unit clauses. + // Remark: get_elimination_order/top-sort checks for cycles, but it is not invoked for unit clauses. // So, we must perform a occurs check here. else if (is_var_diseq(e, num_decls, v, t) && !occurs(v, t)) { r = m_manager.mk_false(); } - else + else r = q; - + if (m_manager.proofs_enabled()) { pr = r == q ? 0 : m_manager.mk_der(q, r); - } + } } void der_sort_vars(ptr_vector & vars, ptr_vector & definitions, unsigned_vector & order) { order.reset(); - + // eliminate self loops, and definitions containing quantifiers. bool found = false; for (unsigned i = 0; i < definitions.size(); i++) { @@ -228,7 +228,7 @@ void der_sort_vars(ptr_vector & vars, ptr_vector & definitions, unsig else found = true; // found at least one candidate } - + if (!found) return; @@ -329,14 +329,14 @@ void der::get_elimination_order() { // der::top_sort ts(m_manager); der_sort_vars(m_inx2var, m_map, m_order); - TRACE("der", + TRACE("der", tout << "Elimination m_order:" << std::endl; for(unsigned i=0; iget_expr(); - unsigned num_args=to_app(e)->get_num_args(); - + unsigned num_args=to_app(e)->get_num_args(); + // get a new expression m_new_args.reset(); for(unsigned i = 0; i < num_args; i++) { int x = m_pos2var[i]; - if (x != -1 && m_map[x] != 0) + if (x != -1 && m_map[x] != 0) continue; // this is a disequality with definition (vanishes) - + m_new_args.push_back(to_app(e)->get_arg(i)); } unsigned sz = m_new_args.size(); expr_ref t(m_manager); t = (sz == 1) ? m_new_args[0] : m_manager.mk_or(sz, m_new_args.c_ptr()); - expr_ref new_e(m_manager); + expr_ref new_e(m_manager); m_subst(t, m_subst_map.size(), m_subst_map.c_ptr(), new_e); - + // don't forget to update the quantifier patterns expr_ref_buffer new_patterns(m_manager); expr_ref_buffer new_no_patterns(m_manager); @@ -392,7 +392,7 @@ void der::apply_substitution(quantifier * q, expr_ref & r) { new_no_patterns.push_back(new_nopat); } - r = m_manager.update_quantifier(q, new_patterns.size(), new_patterns.c_ptr(), + r = m_manager.update_quantifier(q, new_patterns.size(), new_patterns.c_ptr(), new_no_patterns.size(), new_no_patterns.c_ptr(), new_e); } @@ -404,9 +404,9 @@ struct der_rewriter_cfg : public default_rewriter_cfg { ast_manager & m() const { return m_der.m(); } - bool reduce_quantifier(quantifier * old_q, - expr * new_body, - expr * const * new_patterns, + bool reduce_quantifier(quantifier * old_q, + expr * new_body, + expr * const * new_patterns, expr * const * new_no_patterns, expr_ref & result, proof_ref & result_pr) { diff --git a/src/ast/rewriter/pb2bv_rewriter.cpp b/src/ast/rewriter/pb2bv_rewriter.cpp index d85bdec41..5cef19234 100644 --- a/src/ast/rewriter/pb2bv_rewriter.cpp +++ b/src/ast/rewriter/pb2bv_rewriter.cpp @@ -53,6 +53,7 @@ struct pb2bv_rewriter::imp { rational m_k; vector m_coeffs; bool m_keep_cardinality_constraints; + bool m_keep_pb_constraints; unsigned m_min_arity; template @@ -113,12 +114,15 @@ struct pb2bv_rewriter::imp { return expr_ref((is_le == l_false)?m.mk_true():m.mk_false(), m); } +#if 0 expr_ref result(m); switch (is_le) { case l_true: if (mk_le_tot(sz, args, k, result)) return result; else break; case l_false: if (mk_ge_tot(sz, args, k, result)) return result; else break; case l_undef: break; } +#endif + #if 0 expr_ref result(m); switch (is_le) { @@ -562,6 +566,7 @@ struct pb2bv_rewriter::imp { m_trail(m), m_args(m), m_keep_cardinality_constraints(false), + m_keep_pb_constraints(false), m_min_arity(2) {} @@ -698,11 +703,33 @@ struct pb2bv_rewriter::imp { if (m_keep_cardinality_constraints && f->get_arity() >= m_min_arity) return false; result = m_sort.ge(full, pb.get_k(f).get_unsigned(), sz, args); } + else if (pb.is_eq(f) && pb.get_k(f).is_unsigned() && has_small_coefficients(f) && m_keep_pb_constraints) { + return false; + } + else if (pb.is_le(f) && pb.get_k(f).is_unsigned() && has_small_coefficients(f) && m_keep_pb_constraints) { + return false; + } + else if (pb.is_ge(f) && pb.get_k(f).is_unsigned() && has_small_coefficients(f) && m_keep_pb_constraints) { + return false; + } else { result = mk_bv(f, sz, args); } return true; } + + bool has_small_coefficients(func_decl* f) { + unsigned sz = f->get_arity(); + unsigned sum = 0; + for (unsigned i = 0; i < sz; ++i) { + rational c = pb.get_coeff(f, i); + if (!c.is_unsigned()) return false; + unsigned sum1 = sum + c.get_unsigned(); + if (sum1 < sum) return false; + sum = sum1; + } + return true; + } // definitions used for sorting network literal mk_false() { return m.mk_false(); } @@ -730,6 +757,10 @@ struct pb2bv_rewriter::imp { void keep_cardinality_constraints(bool f) { m_keep_cardinality_constraints = f; } + + void keep_pb_constraints(bool f) { + m_keep_pb_constraints = f; + } }; struct card2bv_rewriter_cfg : public default_rewriter_cfg { @@ -742,6 +773,7 @@ struct pb2bv_rewriter::imp { } card2bv_rewriter_cfg(imp& i, ast_manager & m):m_r(i, m) {} void keep_cardinality_constraints(bool f) { m_r.keep_cardinality_constraints(f); } + void keep_pb_constraints(bool f) { m_r.keep_pb_constraints(f); } }; class card_pb_rewriter : public rewriter_tpl { @@ -751,6 +783,7 @@ struct pb2bv_rewriter::imp { rewriter_tpl(m, false, m_cfg), m_cfg(i, m) {} void keep_cardinality_constraints(bool f) { m_cfg.keep_cardinality_constraints(f); } + void keep_pb_constraints(bool f) { m_cfg.keep_pb_constraints(f); } }; card_pb_rewriter m_rw; @@ -761,14 +794,17 @@ struct pb2bv_rewriter::imp { m_num_translated(0), m_rw(*this, m) { m_rw.keep_cardinality_constraints(p.get_bool("keep_cardinality_constraints", false)); + m_rw.keep_pb_constraints(p.get_bool("keep_pb_constraints", false)); } void updt_params(params_ref const & p) { m_params.append(p); m_rw.keep_cardinality_constraints(m_params.get_bool("keep_cardinality_constraints", false)); + m_rw.keep_pb_constraints(m_params.get_bool("keep_pb_constraints", false)); } void collect_param_descrs(param_descrs& r) const { r.insert("keep_cardinality_constraints", CPK_BOOL, "(default: true) retain cardinality constraints (don't bit-blast them) and use built-in cardinality solver"); + r.insert("keep_pb_constraints", CPK_BOOL, "(default: true) retain pb constraints (don't bit-blast them) and use built-in pb solver"); } unsigned get_num_steps() const { return m_rw.get_num_steps(); } diff --git a/src/ast/rewriter/rewriter.h b/src/ast/rewriter/rewriter.h index fc596cabd..2b4f4b14e 100644 --- a/src/ast/rewriter/rewriter.h +++ b/src/ast/rewriter/rewriter.h @@ -315,6 +315,8 @@ protected: template void process_app(app * t, frame & fr); + bool constant_fold(app* t, frame& fr); + template void process_quantifier(quantifier * q, frame & fr); diff --git a/src/ast/rewriter/rewriter.txt b/src/ast/rewriter/rewriter.txt index cdfba9f0f..9eb016af2 100644 --- a/src/ast/rewriter/rewriter.txt +++ b/src/ast/rewriter/rewriter.txt @@ -7,6 +7,7 @@ The following classes implement theory specific rewriting rules: - array_rewriter - datatype_rewriter - fpa_rewriter + - seq_rewriter Each of them provide the method br_status mk_app_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) diff --git a/src/ast/rewriter/rewriter_def.h b/src/ast/rewriter/rewriter_def.h index dddb02dfd..76f149df7 100644 --- a/src/ast/rewriter/rewriter_def.h +++ b/src/ast/rewriter/rewriter_def.h @@ -174,6 +174,37 @@ bool rewriter_tpl::visit(expr * t, unsigned max_depth) { } } +template +bool rewriter_tpl::constant_fold(app * t, frame & fr) { + if (fr.m_i == 1 && m().is_ite(t)) { + expr * cond = result_stack()[fr.m_spos].get(); + expr* arg = 0; + if (m().is_true(cond)) { + arg = t->get_arg(1); + } + else if (m().is_false(cond)) { + arg = t->get_arg(2); + } + if (arg) { + result_stack().shrink(fr.m_spos); + result_stack().push_back(arg); + fr.m_state = REWRITE_BUILTIN; + if (visit(arg, fr.m_max_depth)) { + m_r = result_stack().back(); + result_stack().pop_back(); + result_stack().pop_back(); + result_stack().push_back(m_r); + cache_result(t, m_r, m_pr, fr.m_cache_result); + frame_stack().pop_back(); + set_new_child_flag(t); + } + m_r = 0; + return true; + } + } + return false; +} + template template void rewriter_tpl::process_app(app * t, frame & fr) { @@ -183,16 +214,10 @@ void rewriter_tpl::process_app(app * t, frame & fr) { case PROCESS_CHILDREN: { unsigned num_args = t->get_num_args(); while (fr.m_i < num_args) { - expr * arg = t->get_arg(fr.m_i); - if (fr.m_i >= 1 && m().is_ite(t) && !ProofGen) { - expr * cond = result_stack()[fr.m_spos].get(); - if (m().is_true(cond)) { - arg = t->get_arg(1); - } - else if (m().is_false(cond)) { - arg = t->get_arg(2); - } + if (!ProofGen && constant_fold(t, fr)) { + return; } + expr * arg = t->get_arg(fr.m_i); fr.m_i++; if (!visit(arg, fr.m_max_depth)) return; diff --git a/src/ast/rewriter/rewriter_params.pyg b/src/ast/rewriter/rewriter_params.pyg index 5bd17f556..06500086a 100644 --- a/src/ast/rewriter/rewriter_params.pyg +++ b/src/ast/rewriter/rewriter_params.pyg @@ -8,5 +8,6 @@ def_module_params('rewriter', ("push_ite_bv", BOOL, False, "push if-then-else over bit-vector terms."), ("pull_cheap_ite", BOOL, False, "pull if-then-else terms when cheap."), ("bv_ineq_consistency_test_max", UINT, 0, "max size of conjunctions on which to perform consistency test based on inequalities on bitvectors."), - ("cache_all", BOOL, False, "cache all intermediate results."))) + ("cache_all", BOOL, False, "cache all intermediate results."), + ("ignore_patterns_on_ground_qbody", BOOL, True, "ignores patterns on quantifiers that don't mention their bound variables."))) diff --git a/src/ast/rewriter/seq_rewriter.cpp b/src/ast/rewriter/seq_rewriter.cpp index 26c3e23e4..7aa9329d4 100644 --- a/src/ast/rewriter/seq_rewriter.cpp +++ b/src/ast/rewriter/seq_rewriter.cpp @@ -12,6 +12,7 @@ Abstract: Author: Nikolaj Bjorner (nbjorner) 2015-12-5 + Murphy Berzish 2017-02-21 Notes: @@ -59,7 +60,12 @@ expr_ref sym_expr::accept(expr* e) { } std::ostream& sym_expr::display(std::ostream& out) const { - return out << m_t; + switch (m_ty) { + case t_char: return out << m_t; + case t_range: return out << m_t << ":" << m_s; + case t_pred: return out << m_t; + } + return out << "expression type not recognized"; } struct display_expr1 { @@ -236,6 +242,7 @@ eautomaton* re2automaton::re2aut(expr* e) { unsigned nb = s1.num_bits(); expr_ref _start(bv.mk_numeral(start, nb), m); expr_ref _stop(bv.mk_numeral(stop, nb), m); + TRACE("seq", tout << "Range: " << start << " " << stop << "\n";); a = alloc(eautomaton, sm, sym_expr::mk_range(_start, _stop)); return a.detach(); } @@ -322,7 +329,8 @@ br_status seq_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * con switch(f->get_decl_kind()) { case OP_SEQ_UNIT: - return BR_FAILED; + SASSERT(num_args == 1); + return mk_seq_unit(args[0], result); case OP_SEQ_EMPTY: return BR_FAILED; case OP_RE_PLUS: @@ -344,7 +352,8 @@ br_status seq_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * con SASSERT(num_args == 2); return mk_re_union(args[0], args[1], result); case OP_RE_RANGE: - return BR_FAILED; + SASSERT(num_args == 2); + return mk_re_range(args[0], args[1], result); case OP_RE_INTERSECT: SASSERT(num_args == 2); return mk_re_inter(args[0], args[1], result); @@ -427,6 +436,33 @@ br_status seq_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * con return BR_FAILED; } +/* + * (seq.unit (_ BitVector 8)) ==> String constant + */ +br_status seq_rewriter::mk_seq_unit(expr* e, expr_ref& result) { + sort * s = m().get_sort(e); + bv_util bvu(m()); + + if (is_sort_of(s, bvu.get_family_id(), BV_SORT)) { + // specifically we want (_ BitVector 8) + rational n_val; + unsigned int n_size; + if (bvu.is_numeral(e, n_val, n_size)) { + if (n_size == 8) { + // convert to string constant + char ch = (char)n_val.get_int32(); + TRACE("seq", tout << "rewrite seq.unit of 8-bit value " << n_val.to_string() << " to string constant \"" << ch << "\"" << std::endl;); + char s_tmp[2] = {ch, '\0'}; + symbol s(s_tmp); + result = m_util.str.mk_string(s); + return BR_DONE; + } + } + } + + return BR_FAILED; +} + /* string + string = string a + (b + c) = (a + b) + c @@ -509,15 +545,66 @@ br_status seq_rewriter::mk_seq_length(expr* a, expr_ref& result) { br_status seq_rewriter::mk_seq_extract(expr* a, expr* b, expr* c, expr_ref& result) { zstring s; rational pos, len; - if (m_util.str.is_string(a, s) && m_autil.is_numeral(b, pos) && m_autil.is_numeral(c, len) && - pos.is_unsigned() && len.is_unsigned() && pos.get_unsigned() + len.get_unsigned() <= s.length()) { - unsigned _pos = pos.get_unsigned(); - unsigned _len = len.get_unsigned(); - result = m_util.str.mk_string(s.extract(_pos, _len)); + + bool constantBase = m_util.str.is_string(a, s); + bool constantPos = m_autil.is_numeral(b, pos); + bool constantLen = m_autil.is_numeral(c, len); + + // case 1: pos<0 or len<=0 + // rewrite to "" + if ( (constantPos && pos.is_neg()) || (constantLen && !len.is_pos()) ) { + result = m_util.str.mk_empty(m().get_sort(a)); return BR_DONE; } + // case 1.1: pos >= length(base) + // rewrite to "" + if (constantBase && constantPos && pos >= rational(s.length())) { + result = m_util.str.mk_empty(m().get_sort(a)); + return BR_DONE; + } + + constantPos &= pos.is_unsigned(); + constantLen &= len.is_unsigned(); + + if (constantBase && constantPos && constantLen) { + if (pos.get_unsigned() + len.get_unsigned() >= s.length()) { + // case 2: pos+len goes past the end of the string + unsigned _len = s.length() - pos.get_unsigned() + 1; + result = m_util.str.mk_string(s.extract(pos.get_unsigned(), _len)); + } else { + // case 3: pos+len still within string + result = m_util.str.mk_string(s.extract(pos.get_unsigned(), len.get_unsigned())); + } + return BR_DONE; + } + + if (constantPos && constantLen) { + unsigned _pos = pos.get_unsigned(); + unsigned _len = len.get_unsigned(); + SASSERT(_len > 0); + expr_ref_vector as(m()), bs(m()); + m_util.str.get_concat(a, as); + for (unsigned i = 0; i < as.size() && _len > 0; ++i) { + if (m_util.str.is_unit(as[i].get())) { + if (_pos == 0) { + bs.push_back(as[i].get()); + --_len; + } + else { + --_pos; + } + } + else { + return BR_FAILED; + } + } + result = m_util.str.mk_concat(bs); + return BR_DONE; + } + return BR_FAILED; } + br_status seq_rewriter::mk_seq_contains(expr* a, expr* b, expr_ref& result) { zstring c, d; if (m_util.str.is_string(a, c) && m_util.str.is_string(b, d)) { @@ -594,19 +681,77 @@ br_status seq_rewriter::mk_seq_contains(expr* a, expr* b, expr_ref& result) { result = m().mk_true(); return BR_DONE; } + bool all_units = true; + for (unsigned i = 0; i < bs.size(); ++i) { + all_units = m_util.str.is_unit(bs[i].get()); + } + for (unsigned i = 0; i < as.size(); ++i) { + all_units = m_util.str.is_unit(as[i].get()); + } + if (all_units) { + if (as.size() < bs.size()) { + result = m().mk_false(); + return BR_DONE; + } + expr_ref_vector ors(m()); + for (unsigned i = 0; i < as.size() - bs.size() + 1; ++i) { + expr_ref_vector ands(m()); + for (unsigned j = 0; j < bs.size(); ++j) { + ands.push_back(m().mk_eq(as[i + j].get(), bs[j].get())); + } + ors.push_back(::mk_and(ands)); + } + result = ::mk_or(ors); + return BR_REWRITE_FULL; + } return BR_FAILED; } +/* + * (str.at s i), constants s/i, i < 0 or i >= |s| ==> (str.at s i) = "" + */ br_status seq_rewriter::mk_seq_at(expr* a, expr* b, expr_ref& result) { zstring c; rational r; - if (m_util.str.is_string(a, c) && m_autil.is_numeral(b, r) && r.is_unsigned()) { - unsigned j = r.get_unsigned(); - if (j < c.length()) { - result = m_util.str.mk_string(c.extract(j, 1)); + if (m_autil.is_numeral(b, r)) { + if (r.is_neg()) { + result = m_util.str.mk_empty(m().get_sort(a)); + return BR_DONE; + } + unsigned len = 0; + bool bounded = min_length(1, &a, len); + if (bounded && r >= rational(len)) { + result = m_util.str.mk_empty(m().get_sort(a)); return BR_DONE; } + if (m_util.str.is_string(a, c)) { + if (r.is_unsigned() && r < rational(c.length())) { + result = m_util.str.mk_string(c.extract(r.get_unsigned(), 1)); + } + else { + result = m_util.str.mk_empty(m().get_sort(a)); + } + return BR_DONE; + } + if (r.is_unsigned()) { + len = r.get_unsigned(); + expr_ref_vector as(m()); + m_util.str.get_concat(a, as); + for (unsigned i = 0; i < as.size(); ++i) { + if (m_util.str.is_unit(as[i].get())) { + if (len == 0) { + result = as[i].get(); + return BR_DONE; + } + --len; + } + else { + return BR_FAILED; + } + } + } + } return BR_FAILED; } @@ -664,6 +809,7 @@ br_status seq_rewriter::mk_seq_prefix(expr* a, expr* b, expr_ref& result) { bool isc2 = m_util.str.is_string(b, s2); if (isc1 && isc2) { result = m().mk_bool_val(s1.prefixof(s2)); + TRACE("seq", tout << result << "\n";); return BR_DONE; } if (m_util.str.is_empty(a)) { @@ -677,6 +823,7 @@ br_status seq_rewriter::mk_seq_prefix(expr* a, expr* b, expr_ref& result) { expr_ref_vector as(m()), bs(m()); if (a1 != b1 && isc1 && isc2) { + TRACE("seq", tout << s1 << " " << s2 << "\n";); if (s1.length() <= s2.length()) { if (s1.prefixof(s2)) { if (a == a1) { @@ -721,26 +868,27 @@ br_status seq_rewriter::mk_seq_prefix(expr* a, expr* b, expr_ref& result) { m_util.str.get_concat(a, as); m_util.str.get_concat(b, bs); unsigned i = 0; - bool all_values = true; expr_ref_vector eqs(m()); for (; i < as.size() && i < bs.size(); ++i) { expr* a = as[i].get(), *b = bs[i].get(); if (a == b) { continue; } - all_values &= m().is_value(a) && m().is_value(b); - if (all_values) { - result = m().mk_false(); - return BR_DONE; - } if (m_util.str.is_unit(a) && m_util.str.is_unit(b)) { eqs.push_back(m().mk_eq(a, b)); continue; } + if (m().is_value(a) && m().is_value(b) && m_util.str.is_string(a) && m_util.str.is_string(b)) { + TRACE("seq", tout << mk_pp(a, m()) << " != " << mk_pp(b, m()) << "\n";); + result = m().mk_false(); + return BR_DONE; + } + break; } if (i == as.size()) { result = mk_and(eqs); + TRACE("seq", tout << result << "\n";); if (m().is_true(result)) { return BR_DONE; } @@ -752,6 +900,7 @@ br_status seq_rewriter::mk_seq_prefix(expr* a, expr* b, expr_ref& result) { eqs.push_back(m().mk_eq(m_util.str.mk_empty(m().get_sort(a)), as[j].get())); } result = mk_and(eqs); + TRACE("seq", tout << result << "\n";); return BR_REWRITE3; } if (i > 0) { @@ -759,6 +908,7 @@ br_status seq_rewriter::mk_seq_prefix(expr* a, expr* b, expr_ref& result) { a = m_util.str.mk_concat(as.size() - i, as.c_ptr() + i); b = m_util.str.mk_concat(bs.size() - i, bs.c_ptr() + i); result = m_util.str.mk_prefix(a, b); + TRACE("seq", tout << result << "\n";); return BR_DONE; } else { @@ -1280,6 +1430,40 @@ br_status seq_rewriter::mk_re_star(expr* a, expr_ref& result) { return BR_FAILED; } +/* + * (re.range c_1 c_n) = (re.union (str.to.re c1) (str.to.re c2) ... (str.to.re cn)) + */ +br_status seq_rewriter::mk_re_range(expr* lo, expr* hi, expr_ref& result) { + return BR_FAILED; + TRACE("seq", tout << "rewrite re.range [" << mk_pp(lo, m()) << " " << mk_pp(hi, m()) << "]\n";); + zstring str_lo, str_hi; + if (m_util.str.is_string(lo, str_lo) && m_util.str.is_string(hi, str_hi)) { + if (str_lo.length() == 1 && str_hi.length() == 1) { + unsigned int c1 = str_lo[0]; + unsigned int c2 = str_hi[0]; + if (c1 > c2) { + // exchange c1 and c2 + unsigned int tmp = c1; + c2 = c1; + c1 = tmp; + } + zstring s(c1); + expr_ref acc(m_util.re.mk_to_re(m_util.str.mk_string(s)), m()); + for (unsigned int ch = c1 + 1; ch <= c2; ++ch) { + zstring s_ch(ch); + expr_ref acc2(m_util.re.mk_to_re(m_util.str.mk_string(s_ch)), m()); + acc = m_util.re.mk_union(acc, acc2); + } + result = acc; + return BR_REWRITE2; + } else { + m().raise_exception("string constants in re.range must have length 1"); + } + } + + return BR_FAILED; +} + /* emp+ = emp all+ = all @@ -1309,9 +1493,9 @@ br_status seq_rewriter::mk_re_plus(expr* a, expr_ref& result) { return BR_DONE; } - return BR_FAILED; -// result = m_util.re.mk_concat(a, m_util.re.mk_star(a)); -// return BR_REWRITE2; + //return BR_FAILED; + result = m_util.re.mk_concat(a, m_util.re.mk_star(a)); + return BR_REWRITE2; } br_status seq_rewriter::mk_re_opt(expr* a, expr_ref& result) { @@ -1343,6 +1527,7 @@ bool seq_rewriter::reduce_eq(expr_ref_vector& ls, expr_ref_vector& rs, expr_ref_ zstring s; bool lchange = false; SASSERT(lhs.empty()); + TRACE("seq", tout << ls << "\n"; tout << rs << "\n";); // solve from back while (true) { while (!rs.empty() && m_util.str.is_empty(rs.back())) { @@ -1460,9 +1645,11 @@ bool seq_rewriter::reduce_eq(expr_ref_vector& ls, expr_ref_vector& rs, expr_ref_ head2 < rs.size() && m_util.str.is_string(ls[head1].get(), s1) && m_util.str.is_string(rs[head2].get(), s2)) { + TRACE("seq", tout << s1 << " - " << s2 << " " << s1.length() << " " << s2.length() << "\n";); unsigned l = std::min(s1.length(), s2.length()); for (unsigned i = 0; i < l; ++i) { if (s1[i] != s2[i]) { + TRACE("seq", tout << "different at position " << i << " " << s1[i] << " " << s2[i] << "\n";); return false; } } diff --git a/src/ast/rewriter/seq_rewriter.h b/src/ast/rewriter/seq_rewriter.h index 2b434f475..210b2d72c 100644 --- a/src/ast/rewriter/seq_rewriter.h +++ b/src/ast/rewriter/seq_rewriter.h @@ -98,6 +98,7 @@ class seq_rewriter { re2automaton m_re2aut; expr_ref_vector m_es, m_lhs, m_rhs; + br_status mk_seq_unit(expr* e, expr_ref& result); br_status mk_seq_concat(expr* a, expr* b, expr_ref& result); br_status mk_seq_length(expr* a, expr_ref& result); br_status mk_seq_extract(expr* a, expr* b, expr* c, expr_ref& result); @@ -119,6 +120,7 @@ class seq_rewriter { br_status mk_re_plus(expr* a, expr_ref& result); br_status mk_re_opt(expr* a, expr_ref& result); br_status mk_re_loop(unsigned num_args, expr* const* args, expr_ref& result); + br_status mk_re_range(expr* lo, expr* hi, expr_ref& result); bool set_empty(unsigned sz, expr* const* es, bool all, expr_ref_vector& lhs, expr_ref_vector& rhs); bool is_subsequence(unsigned n, expr* const* l, unsigned m, expr* const* r, diff --git a/src/ast/rewriter/th_rewriter.cpp b/src/ast/rewriter/th_rewriter.cpp index 0c57ea609..b561e02fc 100644 --- a/src/ast/rewriter/th_rewriter.cpp +++ b/src/ast/rewriter/th_rewriter.cpp @@ -54,6 +54,7 @@ struct th_rewriter_cfg : public default_rewriter_cfg { bool m_cache_all; bool m_push_ite_arith; bool m_push_ite_bv; + bool m_ignore_patterns_on_ground_qbody; // substitution support expr_dependency_ref m_used_dependencies; // set of dependencies of used substitutions @@ -70,8 +71,9 @@ struct th_rewriter_cfg : public default_rewriter_cfg { m_cache_all = p.cache_all(); m_push_ite_arith = p.push_ite_arith(); m_push_ite_bv = p.push_ite_bv(); + m_ignore_patterns_on_ground_qbody = p.ignore_patterns_on_ground_qbody(); } - + void updt_params(params_ref const & p) { m_b_rw.updt_params(p); m_a_rw.updt_params(p); @@ -82,7 +84,7 @@ struct th_rewriter_cfg : public default_rewriter_cfg { updt_local_params(p); } - bool flat_assoc(func_decl * f) const { + bool flat_assoc(func_decl * f) const { if (!m_flat) return false; family_id fid = f->get_family_id(); if (fid == null_family_id) @@ -98,10 +100,10 @@ struct th_rewriter_cfg : public default_rewriter_cfg { } bool rewrite_patterns() const { return false; } - + bool cache_all_results() const { return m_cache_all; } - bool max_steps_exceeded(unsigned num_steps) const { + bool max_steps_exceeded(unsigned num_steps) const { cooperate("simplifier"); if (memory::get_allocation_size() > m_max_memory) throw rewriter_exception(Z3_MAX_MEMORY_MSG); @@ -179,13 +181,13 @@ struct th_rewriter_cfg : public default_rewriter_cfg { st = m_ar_rw.mk_eq_core(args[0], args[1], result); else if (s_fid == m_seq_rw.get_fid()) st = m_seq_rw.mk_eq_core(args[0], args[1], result); - + if (st != BR_FAILED) return st; } if (k == OP_EQ || k == OP_IFF) { SASSERT(num == 2); - st = apply_tamagotchi(args[0], args[1], result); + st = apply_tamagotchi(args[0], args[1], result); if (st != BR_FAILED) return st; } @@ -239,13 +241,13 @@ struct th_rewriter_cfg : public default_rewriter_cfg { } else { if (SWAP) { - result = m().mk_ite(ite->get_arg(0), + result = m().mk_ite(ite->get_arg(0), m().mk_app(p, value, ite->get_arg(1)), m().mk_app(p, value, ite->get_arg(2))); return BR_REWRITE2; } else { - result = m().mk_ite(ite->get_arg(0), + result = m().mk_ite(ite->get_arg(0), m().mk_app(p, ite->get_arg(1), value), m().mk_app(p, ite->get_arg(2), value)); return BR_REWRITE2; @@ -257,7 +259,7 @@ struct th_rewriter_cfg : public default_rewriter_cfg { // ite-value-tree := (ite c ) // subtree := value // | (ite c ) - // + // bool is_ite_value_tree(expr * t) { if (!m().is_ite(t)) return false; @@ -281,7 +283,7 @@ struct th_rewriter_cfg : public default_rewriter_cfg { } return true; } - + br_status pull_ite(func_decl * f, unsigned num, expr * const * args, expr_ref & result) { if (num == 2 && m().is_bool(f->get_range()) && !m().is_bool(args[0])) { if (m().is_ite(args[0])) { @@ -325,7 +327,7 @@ struct th_rewriter_cfg : public default_rewriter_cfg { if (!is_app(t)) return false; family_id fid = to_app(t)->get_family_id(); - return ((fid == m_a_rw.get_fid() && m_push_ite_arith) || + return ((fid == m_a_rw.get_fid() && m_push_ite_arith) || (fid == m_bv_rw.get_fid() && m_push_ite_bv)); } @@ -349,7 +351,7 @@ struct th_rewriter_cfg : public default_rewriter_cfg { } return false; } - + /** \brief Try to "unify" t1 and t2 Examples @@ -463,7 +465,7 @@ struct th_rewriter_cfg : public default_rewriter_cfg { } // terms matched... bool is_int = m_a_util.is_int(t1); - if (!new_t1) + if (!new_t1) new_t1 = m_a_util.mk_numeral(rational(0), is_int); if (!new_t2) new_t2 = m_a_util.mk_numeral(rational(0), is_int); @@ -476,7 +478,7 @@ struct th_rewriter_cfg : public default_rewriter_cfg { args.push_back(arg); } SASSERT(!args.empty()); - if (args.size() == 1) + if (args.size() == 1) c = args[0]; else c = m_a_util.mk_add(args.size(), args.c_ptr()); @@ -518,7 +520,7 @@ struct th_rewriter_cfg : public default_rewriter_cfg { // Apply transformations of the form // - // (ite c (+ k1 a) (+ k2 a)) --> (+ (ite c k1 k2) a) + // (ite c (+ k1 a) (+ k2 a)) --> (+ (ite c k1 k2) a) // (ite c (* k1 a) (* k2 a)) --> (* (ite c k1 k2) a) // // These transformations are useful for bit-vector problems, since @@ -536,7 +538,7 @@ struct th_rewriter_cfg : public default_rewriter_cfg { if (unify(t, e, f_prime, new_t, new_e, common, first)) { if (first) result = m().mk_app(f_prime, common, m().mk_ite(c, new_t, new_e)); - else + else result = m().mk_app(f_prime, m().mk_ite(c, new_t, new_e), common); return BR_DONE; } @@ -558,7 +560,7 @@ struct th_rewriter_cfg : public default_rewriter_cfg { result_pr = 0; br_status st = reduce_app_core(f, num, args, result); if (st != BR_DONE && st != BR_FAILED) { - CTRACE("th_rewriter_step", st != BR_FAILED, + CTRACE("th_rewriter_step", st != BR_FAILED, tout << f->get_name() << "\n"; for (unsigned i = 0; i < num; i++) tout << mk_ismt2_pp(args[i], m()) << "\n"; tout << "---------->\n" << mk_ismt2_pp(result, m()) << "\n";); @@ -576,7 +578,7 @@ struct th_rewriter_cfg : public default_rewriter_cfg { else st = pull_ite(result); } - CTRACE("th_rewriter_step", st != BR_FAILED, + CTRACE("th_rewriter_step", st != BR_FAILED, tout << f->get_name() << "\n"; for (unsigned i = 0; i < num; i++) tout << mk_ismt2_pp(args[i], m()) << "\n"; tout << "---------->\n" << mk_ismt2_pp(result, m()) << "\n";); @@ -593,28 +595,28 @@ struct th_rewriter_cfg : public default_rewriter_cfg { } - bool reduce_quantifier(quantifier * old_q, - expr * new_body, - expr * const * new_patterns, + bool reduce_quantifier(quantifier * old_q, + expr * new_body, + expr * const * new_patterns, expr * const * new_no_patterns, expr_ref & result, proof_ref & result_pr) { quantifier_ref q1(m()); proof * p1 = 0; - if (is_quantifier(new_body) && + if (is_quantifier(new_body) && to_quantifier(new_body)->is_forall() == old_q->is_forall() && !old_q->has_patterns() && !to_quantifier(new_body)->has_patterns()) { - + quantifier * nested_q = to_quantifier(new_body); - + ptr_buffer sorts; - buffer names; + buffer names; sorts.append(old_q->get_num_decls(), old_q->get_decl_sorts()); names.append(old_q->get_num_decls(), old_q->get_decl_names()); sorts.append(nested_q->get_num_decls(), nested_q->get_decl_sorts()); names.append(nested_q->get_num_decls(), nested_q->get_decl_names()); - + q1 = m().mk_quantifier(old_q->is_forall(), sorts.size(), sorts.c_ptr(), @@ -624,9 +626,9 @@ struct th_rewriter_cfg : public default_rewriter_cfg { old_q->get_qid(), old_q->get_skid(), 0, 0, 0, 0); - + SASSERT(is_well_sorted(m(), q1)); - + if (m().proofs_enabled()) { SASSERT(old_q->get_expr() == new_body); p1 = m().mk_pull_quant(old_q, q1); @@ -635,24 +637,24 @@ struct th_rewriter_cfg : public default_rewriter_cfg { else { ptr_buffer new_patterns_buf; ptr_buffer new_no_patterns_buf; - + new_patterns_buf.append(old_q->get_num_patterns(), new_patterns); new_no_patterns_buf.append(old_q->get_num_no_patterns(), new_no_patterns); remove_duplicates(new_patterns_buf); remove_duplicates(new_no_patterns_buf); - - q1 = m().update_quantifier(old_q, + + q1 = m().update_quantifier(old_q, new_patterns_buf.size(), new_patterns_buf.c_ptr(), new_no_patterns_buf.size(), new_no_patterns_buf.c_ptr(), new_body); TRACE("reduce_quantifier", tout << mk_ismt2_pp(old_q, m()) << "\n----->\n" << mk_ismt2_pp(q1, m()) << "\n";); SASSERT(is_well_sorted(m(), q1)); } - - elim_unused_vars(m(), q1, result); + + elim_unused_vars(m(), q1, params_ref(), result); TRACE("reduce_quantifier", tout << "after elim_unused_vars:\n" << mk_ismt2_pp(result, m()) << "\n";); - + result_pr = 0; if (m().proofs_enabled()) { proof * p2 = 0; @@ -758,7 +760,7 @@ unsigned th_rewriter::get_num_steps() const { void th_rewriter::cleanup() { ast_manager & m = m_imp->m(); dealloc(m_imp); - m_imp = alloc(imp, m, m_params); + m_imp = alloc(imp, m, m_params); } void th_rewriter::reset() { diff --git a/src/ast/rewriter/var_subst.cpp b/src/ast/rewriter/var_subst.cpp index 37b335a9d..fd290c8fe 100644 --- a/src/ast/rewriter/var_subst.cpp +++ b/src/ast/rewriter/var_subst.cpp @@ -39,10 +39,16 @@ void var_subst::operator()(expr * n, unsigned num_args, expr * const * args, exp tout << mk_ismt2_pp(result, m_reducer.m()) << "\n";); } +unused_vars_eliminator::unused_vars_eliminator(ast_manager & m, params_ref const & params) : + m(m), m_subst(m), m_params(params) +{ + m_ignore_patterns_on_ground_qbody = m_params.get_bool("ignore_patterns_on_ground_qbody", true); +} + void unused_vars_eliminator::operator()(quantifier* q, expr_ref & result) { SASSERT(is_well_sorted(m, q)); - if (is_ground(q->get_expr())) { - // ignore patterns if the body is a ground formula. + if (m_ignore_patterns_on_ground_qbody && is_ground(q->get_expr())) { + // Ignore patterns if the body is a ground formula. result = q->get_expr(); return; } @@ -146,8 +152,8 @@ void unused_vars_eliminator::operator()(quantifier* q, expr_ref & result) { SASSERT(is_well_sorted(m, result)); } -void elim_unused_vars(ast_manager & m, quantifier * q, expr_ref & result) { - unused_vars_eliminator el(m); +void elim_unused_vars(ast_manager & m, quantifier * q, params_ref const & params, expr_ref & result) { + unused_vars_eliminator el(m, params); el(q, result); } diff --git a/src/ast/rewriter/var_subst.h b/src/ast/rewriter/var_subst.h index 9d04cebe3..21aa58399 100644 --- a/src/ast/rewriter/var_subst.h +++ b/src/ast/rewriter/var_subst.h @@ -21,6 +21,7 @@ Notes: #include"rewriter.h" #include"used_vars.h" +#include"params.h" /** \brief Alias for var_shifter class. @@ -31,7 +32,7 @@ typedef var_shifter shift_vars; \brief Variable substitution functor. It substitutes variables by expressions. The expressions may contain variables. */ -class var_subst { +class var_subst { beta_reducer m_reducer; bool m_std_order; public: @@ -39,7 +40,7 @@ public: bool std_order() const { return m_std_order; } /** - When std_order() == true, + When std_order() == true, I'm using the same standard used in quantifier instantiation. (VAR 0) is stored in the last position of the array. ... @@ -55,15 +56,17 @@ public: \brief Eliminate the unused variables from \c q. Store the result in \c r. */ class unused_vars_eliminator { - ast_manager& m; - var_subst m_subst; - used_vars m_used; + ast_manager & m; + var_subst m_subst; + used_vars m_used; + params_ref m_params; + bool m_ignore_patterns_on_ground_qbody; public: - unused_vars_eliminator(ast_manager& m): m(m), m_subst(m) {} + unused_vars_eliminator(ast_manager & m, params_ref const & params); void operator()(quantifier* q, expr_ref& r); }; -void elim_unused_vars(ast_manager & m, quantifier * q, expr_ref & r); +void elim_unused_vars(ast_manager & m, quantifier * q, params_ref const & params, expr_ref & r); /** \brief Instantiate quantifier q using the given exprs. @@ -86,7 +89,7 @@ class expr_free_vars { expr_sparse_mark m_mark; ptr_vector m_sorts; ptr_vector m_todo; -public: +public: void reset(); void operator()(expr* e); void accumulate(expr* e); @@ -96,7 +99,7 @@ public: bool contains(unsigned idx) const { return idx < m_sorts.size() && m_sorts[idx] != 0; } void set_default_sort(sort* s); void reverse() { m_sorts.reverse(); } - sort*const* c_ptr() const { return m_sorts.c_ptr(); } + sort*const* c_ptr() const { return m_sorts.c_ptr(); } }; #endif diff --git a/src/ast/seq_decl_plugin.cpp b/src/ast/seq_decl_plugin.cpp index 9af4687d2..bf238d8c5 100644 --- a/src/ast/seq_decl_plugin.cpp +++ b/src/ast/seq_decl_plugin.cpp @@ -126,13 +126,14 @@ static bool is_escape_char(char const *& s, unsigned& result) { zstring::zstring(encoding enc): m_encoding(enc) {} zstring::zstring(char const* s, encoding enc): m_encoding(enc) { + unsigned mask = 0xFF; // TBD for UTF while (*s) { unsigned ch; if (is_escape_char(s, ch)) { - m_buffer.push_back(ch); + m_buffer.push_back(ch & mask); } else { - m_buffer.push_back(*s); + m_buffer.push_back(*s & mask); ++s; } } @@ -830,7 +831,9 @@ void seq_decl_plugin::get_sort_names(svector & sort_names, symbol init(); sort_names.push_back(builtin_name("Seq", SEQ_SORT)); sort_names.push_back(builtin_name("RegEx", RE_SORT)); + // SMT-LIB 2.5 compatibility sort_names.push_back(builtin_name("String", _STRING_SORT)); + sort_names.push_back(builtin_name("StringSequence", _STRING_SORT)); } app* seq_decl_plugin::mk_string(symbol const& s) { diff --git a/src/ast/seq_decl_plugin.h b/src/ast/seq_decl_plugin.h index a7e534bbb..76b5ebe31 100644 --- a/src/ast/seq_decl_plugin.h +++ b/src/ast/seq_decl_plugin.h @@ -273,6 +273,15 @@ public: bool is_in_re(expr const* n) const { return is_app_of(n, m_fid, OP_SEQ_IN_RE); } bool is_unit(expr const* n) const { return is_app_of(n, m_fid, OP_SEQ_UNIT); } + bool is_string_term(expr const * n) const { + sort * s = get_sort(n); + return u.is_string(s); + } + + bool is_non_string_sequence(expr const * n) const { + sort * s = get_sort(n); + return (u.is_seq(s) && !u.is_string(s)); + } MATCH_BINARY(is_concat); MATCH_UNARY(is_length); @@ -304,6 +313,7 @@ public: app* mk_to_re(expr* s) { return m.mk_app(m_fid, OP_SEQ_TO_RE, 1, &s); } app* mk_in_re(expr* s, expr* r) { return m.mk_app(m_fid, OP_SEQ_IN_RE, s, r); } + app* mk_range(expr* s1, expr* s2) { return m.mk_app(m_fid, OP_RE_RANGE, s1, s2); } app* mk_concat(expr* r1, expr* r2) { return m.mk_app(m_fid, OP_RE_CONCAT, r1, r2); } app* mk_union(expr* r1, expr* r2) { return m.mk_app(m_fid, OP_RE_UNION, r1, r2); } app* mk_inter(expr* r1, expr* r2) { return m.mk_app(m_fid, OP_RE_INTERSECT, r1, r2); } diff --git a/src/ast/simplifier/bv_simplifier_plugin.cpp b/src/ast/simplifier/bv_simplifier_plugin.cpp index 11ed1b9e0..a72e7e117 100644 --- a/src/ast/simplifier/bv_simplifier_plugin.cpp +++ b/src/ast/simplifier/bv_simplifier_plugin.cpp @@ -1517,13 +1517,24 @@ void bv_simplifier_plugin::mk_bv2int(expr * arg, sort* range, expr_ref & result) result = m_arith.mk_add(tmp1, tmp2); } // commented out to reproduce bug in reduction of int2bv/bv2int - else if (m_util.is_concat(arg)) { - expr_ref tmp1(m_manager), tmp2(m_manager); - unsigned sz2 = get_bv_size(to_app(arg)->get_arg(1)); - mk_bv2int(to_app(arg)->get_arg(0), range, tmp1); - mk_bv2int(to_app(arg)->get_arg(1), range, tmp2); - tmp1 = m_arith.mk_mul(m_arith.mk_numeral(power(numeral(2), sz2), true), tmp1); - result = m_arith.mk_add(tmp1, tmp2); + else if (m_util.is_concat(arg) && to_app(arg)->get_num_args() > 0) { + expr_ref_vector args(m_manager); + unsigned num_args = to_app(arg)->get_num_args(); + for (unsigned i = 0; i < num_args; ++i) { + expr_ref tmp(m_manager); + mk_bv2int(to_app(arg)->get_arg(i), range, tmp); + args.push_back(tmp); + } + unsigned sz = get_bv_size(to_app(arg)->get_arg(num_args-1)); + for (unsigned i = num_args - 1; i > 0; ) { + expr_ref tmp(m_manager); + --i; + tmp = args[i].get(); + tmp = m_arith.mk_mul(m_arith.mk_numeral(power(numeral(2), sz), true), tmp); + args[i] = tmp; + sz += get_bv_size(to_app(arg)->get_arg(i)); + } + result = m_arith.mk_add(args.size(), args.c_ptr()); } else { parameter parameter(range); diff --git a/src/ast/simplifier/distribute_forall.cpp b/src/ast/simplifier/distribute_forall.cpp index bd2af5675..78e5d5ded 100644 --- a/src/ast/simplifier/distribute_forall.cpp +++ b/src/ast/simplifier/distribute_forall.cpp @@ -14,7 +14,7 @@ Author: Leonardo de Moura (leonardo) 2010-04-02. Revision History: - + Christoph Wintersteiger 2010-04-06: Added implementation. --*/ @@ -40,7 +40,7 @@ bool distribute_forall::visit_children(expr * n) { bool visited = true; unsigned j; switch(n->get_kind()) { - case AST_VAR: + case AST_VAR: break; case AST_APP: j = to_app(n)->get_num_args(); @@ -86,15 +86,15 @@ void distribute_forall::reduce1_app(app * a) { SASSERT(is_cached(a->get_arg(j))); expr * c = get_cached(a->get_arg(j)); SASSERT(c!=0); - if (c != a->get_arg(j)) + if (c != a->get_arg(j)) reduced = true; m_new_args[j] = c; - } + } if (reduced) { na = m_manager.mk_app(a->get_decl(), num_args, m_new_args.c_ptr()); } - + cache_result(a, na); } @@ -126,11 +126,11 @@ void distribute_forall::reduce1_quantifier(quantifier * q) { quantifier_ref tmp_q(m_manager); tmp_q = m_manager.update_quantifier(q, not_arg); expr_ref new_q(m_manager); - elim_unused_vars(m_manager, tmp_q, new_q); + elim_unused_vars(m_manager, tmp_q, params_ref(), new_q); new_args.push_back(new_q); } expr_ref result(m_manager); - // m_bsimp.mk_and actually constructs a (not (or ...)) formula, + // m_bsimp.mk_and actually constructs a (not (or ...)) formula, // it will also apply basic simplifications. m_bsimp.mk_and(new_args.size(), new_args.c_ptr(), result); cache_result(q, result); @@ -148,15 +148,15 @@ void distribute_forall::operator()(expr * f, expr_ref & result) { while (!m_todo.empty()) { expr * e = m_todo.back(); - if (visit_children(e)) { + if (visit_children(e)) { m_todo.pop_back(); reduce1(e); - } + } } result = get_cached(f); SASSERT(result!=0); - TRACE("distribute_forall", tout << mk_ll_pp(f, m_manager) << "======>\n" + TRACE("distribute_forall", tout << mk_ll_pp(f, m_manager) << "======>\n" << mk_ll_pp(result, m_manager);); } @@ -166,5 +166,5 @@ expr * distribute_forall::get_cached(expr * n) const { void distribute_forall::cache_result(expr * n, expr * r) { SASSERT(r != 0); - m_cache.insert(n, r); + m_cache.insert(n, r); } diff --git a/src/ast/simplifier/elim_bounds.cpp b/src/ast/simplifier/elim_bounds.cpp index a4e145e0a..7a40b8602 100644 --- a/src/ast/simplifier/elim_bounds.cpp +++ b/src/ast/simplifier/elim_bounds.cpp @@ -32,7 +32,7 @@ elim_bounds::elim_bounds(ast_manager & m): (<= x k) (<= (+ x (* -1 y)) k) - (<= (+ x (* -1 t)) k) + (<= (+ x (* -1 t)) k) (<= (+ t (* -1 x)) k) x and y are a bound variables, t is a ground term and k is a numeral @@ -65,14 +65,14 @@ bool elim_bounds::is_bound(expr * n, var * & lower, var * & upper) { if (neg) le = !le; - + if (is_var(n)) { upper = to_var(n); } else if (m_util.is_add(n) && to_app(n)->get_num_args() == 2) { expr * arg1 = to_app(n)->get_arg(0); expr * arg2 = to_app(n)->get_arg(1); - if (is_var(arg1)) + if (is_var(arg1)) upper = to_var(arg1); else if (!is_ground(arg1)) return false; @@ -95,7 +95,7 @@ bool elim_bounds::is_bound(expr * n, var * & lower, var * & upper) { if (!le) std::swap(upper, lower); - + return true; } @@ -188,7 +188,7 @@ void elim_bounds::operator()(quantifier * q, expr_ref & r) { } quantifier_ref new_q(m_manager); new_q = m_manager.update_quantifier(q, new_body); - elim_unused_vars(m_manager, new_q, r); + elim_unused_vars(m_manager, new_q, params_ref(), r); TRACE("elim_bounds", tout << mk_pp(q, m_manager) << "\n" << mk_pp(r, m_manager) << "\n";); } @@ -199,10 +199,10 @@ bool elim_bounds_star::visit_quantifier(quantifier * q) { visit(q->get_expr(), visited); return visited; } - + void elim_bounds_star::reduce1_quantifier(quantifier * q) { if (!q->is_forall() || q->get_num_patterns() != 0) { - cache_result(q, q, 0); + cache_result(q, q, 0); return; } quantifier_ref new_q(m); diff --git a/src/ast/simplifier/poly_simplifier_plugin.h b/src/ast/simplifier/poly_simplifier_plugin.h index 4e6ae9c15..ed6d506c5 100644 --- a/src/ast/simplifier/poly_simplifier_plugin.h +++ b/src/ast/simplifier/poly_simplifier_plugin.h @@ -37,7 +37,7 @@ protected: expr * mk_add(expr * arg1, expr * arg2) { expr * args[2] = { arg1, arg2 }; return mk_add(2, args); } expr * mk_mul(unsigned num_args, expr * const * args); expr * mk_mul(expr * arg1, expr * arg2) { expr * args[2] = { arg1, arg2 }; return mk_mul(2, args); } - expr * mk_sub(unsigned num_args, expr * const * args) { return m_manager.mk_app(m_fid, m_SUB, num_args, args); } + // expr * mk_sub(unsigned num_args, expr * const * args) { return m_manager.mk_app(m_fid, m_SUB, num_args, args); } expr * mk_uminus(expr * arg) { return m_manager.mk_app(m_fid, m_UMINUS, arg); } void process_monomial(unsigned num_args, expr * const * args, numeral & k, ptr_buffer & result); diff --git a/src/ast/simplifier/simplifier.cpp b/src/ast/simplifier/simplifier.cpp index 6f7e62fd4..498244919 100644 --- a/src/ast/simplifier/simplifier.cpp +++ b/src/ast/simplifier/simplifier.cpp @@ -33,8 +33,8 @@ simplifier::simplifier(ast_manager & m): m_ac_support(true) { } -void simplifier::register_plugin(plugin * p) { - m_plugins.register_plugin(p); +void simplifier::register_plugin(plugin * p) { + m_plugins.register_plugin(p); } simplifier::~simplifier() { @@ -46,13 +46,13 @@ void simplifier::enable_ac_support(bool flag) { ptr_vector::const_iterator it = m_plugins.begin(); ptr_vector::const_iterator end = m_plugins.end(); for (; it != end; ++it) { - if (*it != 0) + if (*it != 0) (*it)->enable_ac_support(flag); } } /** - \brief External interface for the simplifier. + \brief External interface for the simplifier. A client will invoke operator()(s, r, p) to simplify s. The result is stored in r. When proof generation is enabled, a proof for the equivalence (or equisatisfiability) @@ -69,14 +69,14 @@ void simplifier::operator()(expr * s, expr_ref & r, proof_ref & p) { proof * result_proof; switch (m.proof_mode()) { case PGM_DISABLED: // proof generation is disabled. - reduce_core(s); + reduce_core(s); // after executing reduce_core, the result of the simplification is in the cache get_cached(s, result, result_proof); r = result; p = m.mk_undef_proof(); break; case PGM_COARSE: // coarse proofs... in this case, we do not produce a step by step (fine grain) proof to show the equivalence (or equisatisfiability) of s an r. - m_subst_proofs.reset(); // m_subst_proofs is an auxiliary vector that is used to justify substitutions. See comment on method get_subst. + m_subst_proofs.reset(); // m_subst_proofs is an auxiliary vector that is used to justify substitutions. See comment on method get_subst. reduce_core(s); get_cached(s, result, result_proof); r = result; @@ -163,7 +163,7 @@ bool simplifier::visit_children(expr * n) { // The method ast_manager::mk_app is used to create the flat version of an AC operator. // In Z3 1.x, we used multi-ary operators. This creates problems for the superposition engine. // So, starting at Z3 2.x, only boolean operators can be multi-ary. - // Example: + // Example: // (and (and a b) (and c d)) --> (and a b c d) // (+ (+ a b) (+ c d)) --> (+ a (+ b (+ c d))) // Remark: The flattening is only applied if m_ac_support is true. @@ -178,7 +178,7 @@ bool simplifier::visit_children(expr * n) { } return visited; } - case AST_QUANTIFIER: + case AST_QUANTIFIER: return visit_quantifier(to_quantifier(n)); default: UNREACHABLE(); @@ -188,7 +188,7 @@ bool simplifier::visit_children(expr * n) { /** \brief Visit the children of n assuming it is an AC (associative-commutative) operator. - + For example, if n is of the form (+ (+ a b) (+ c d)), this method will return true if the nodes a, b, c and d have been already simplified. The nodes (+ a b) and (+ c d) are not really checked. @@ -216,7 +216,7 @@ bool simplifier::visit_ac(app * n) { expr * arg = n->get_arg(i); if (is_app_of(arg, decl)) todo.push_back(to_app(arg)); - else + else visit(arg, visited); } } @@ -319,7 +319,7 @@ void simplifier::reduce1_app_core(app * n) { proof * p; if (n == r) p = 0; - else if (r != s) + else if (r != s) // we use a "theory rewrite generic proof" to justify the step // s = (decl arg_0' ... arg_{n-1}') --> r p = m.mk_transitivity(p1, m.mk_rewrite(s, r)); @@ -368,7 +368,7 @@ void simplifier::reduce1_ac_app_core(app * n) { proof_ref p1(m); mk_ac_congruent_term(n, n_c, p1); TRACE("ac", tout << "expr:\n" << mk_pp(n, m) << "\ncongruent term:\n" << mk_pp(n_c, m) << "\n";); - expr_ref r(m); + expr_ref r(m); func_decl * decl = n->get_decl(); family_id fid = decl->get_family_id(); plugin * p = get_plugin(fid); @@ -415,7 +415,7 @@ void simplifier::reduce1_ac_app_core(app * n) { proof * p; if (n == r.get()) p = 0; - else if (r.get() != n_c.get()) + else if (r.get() != n_c.get()) p = m.mk_transitivity(p1, m.mk_rewrite(n_c, r)); else p = p1; @@ -434,7 +434,7 @@ void simplifier::dump_rewrite_lemma(func_decl * decl, unsigned num_args, expr * sprintf_s(buffer, ARRAYSIZE(buffer), "lemma_%d.smt", g_rewrite_lemma_id); #else sprintf(buffer, "rewrite_lemma_%d.smt", g_rewrite_lemma_id); -#endif +#endif ast_smt_pp pp(m); pp.set_benchmark_name("rewrite_lemma"); pp.set_status("unsat"); @@ -450,7 +450,7 @@ void simplifier::dump_rewrite_lemma(func_decl * decl, unsigned num_args, expr * /** \brief Return in \c result an expression \c e equivalent to (f args[0] ... args[num_args - 1]), and store in \c pr a proof for (= (f args[0] ... args[num_args - 1]) e) - + If e is identical to (f args[0] ... args[num_args - 1]), then pr is set to 0. */ void simplifier::mk_app(func_decl * decl, unsigned num_args, expr * const * args, expr_ref & result) { @@ -474,7 +474,7 @@ void simplifier::mk_app(func_decl * decl, unsigned num_args, expr * const * args //dump_rewrite_lemma(decl, num_args, args, result.get()); return; } - + result = m.mk_app(decl, num_args, args); } @@ -494,17 +494,17 @@ void simplifier::mk_congruent_term(app * n, app_ref & r, proof_ref & p) { proof * arg_proof; get_cached(arg, new_arg, arg_proof); - CTRACE("simplifier_bug", (arg != new_arg) != (arg_proof != 0), + CTRACE("simplifier_bug", (arg != new_arg) != (arg_proof != 0), tout << mk_ll_pp(arg, m) << "\n---->\n" << mk_ll_pp(new_arg, m) << "\n"; tout << "#" << arg->get_id() << " #" << new_arg->get_id() << "\n"; tout << arg << " " << new_arg << "\n";); - - + + if (arg != new_arg) { has_new_args = true; proofs.push_back(arg_proof); SASSERT(arg_proof); - } + } else { SASSERT(arg_proof == 0); } @@ -526,10 +526,10 @@ void simplifier::mk_congruent_term(app * n, app_ref & r, proof_ref & p) { /** \brief Store the new arguments of \c n in result. Store in p a proof for (= n (f result[0] ... result[num_args - 1])), where f is the function symbol of n. - + If there are no new arguments or fine grain proofs are disabled, then p is set to 0. - Return true there are new arguments. + Return true there are new arguments. */ bool simplifier::get_args(app * n, ptr_vector & result, proof_ref & p) { bool has_new_args = false; @@ -565,10 +565,10 @@ bool simplifier::get_args(app * n, ptr_vector & result, proof_ref & p) { void simplifier::mk_ac_congruent_term(app * n, app_ref & r, proof_ref & p) { SASSERT(m_ac_support); func_decl * f = n->get_decl(); - + m_ac_cache.reset(); m_ac_pr_cache.reset(); - + ptr_buffer todo; ptr_buffer new_args; ptr_buffer new_arg_prs; @@ -621,7 +621,7 @@ void simplifier::mk_ac_congruent_term(app * n, app_ref & r, proof_ref & p) { todo.pop_back(); if (!has_new_arg) { m_ac_cache.insert(curr, curr); - if (m.fine_grain_proofs()) + if (m.fine_grain_proofs()) m_ac_pr_cache.insert(curr, 0); } else { @@ -634,7 +634,7 @@ void simplifier::mk_ac_congruent_term(app * n, app_ref & r, proof_ref & p) { } } } - + SASSERT(m_ac_cache.contains(n)); app * new_n = 0; m_ac_cache.find(n, new_n); @@ -646,7 +646,7 @@ void simplifier::mk_ac_congruent_term(app * n, app_ref & r, proof_ref & p) { } } -#define White 0 +#define White 0 #define Grey 1 #define Black 2 @@ -688,7 +688,7 @@ void simplifier::ac_top_sort(app * n, ptr_buffer & result) { while (!todo.empty()) { expr * curr = todo.back(); int color; - obj_map::obj_map_entry * entry = colors.insert_if_not_there2(curr, White); + obj_map::obj_map_entry * entry = colors.insert_if_not_there2(curr, White); SASSERT(entry); color = entry->get_data().m_value; switch (color) { @@ -731,7 +731,7 @@ void simplifier::get_ac_args(app * n, ptr_vector & args, vector ac_top_sort(n, sorted_exprs); SASSERT(!sorted_exprs.empty()); SASSERT(sorted_exprs[sorted_exprs.size()-1] == n); - + TRACE("ac", tout << mk_ll_pp(n, m, true, false) << "#" << n->get_id() << "\nsorted expressions...\n"; for (unsigned i = 0; i < sorted_exprs.size(); i++) { tout << "#" << sorted_exprs[i]->get_id() << " "; @@ -747,7 +747,7 @@ void simplifier::get_ac_args(app * n, ptr_vector & args, vector expr * curr = sorted_exprs[j]; rational mult; m_ac_mults.find(curr, mult); - SASSERT(!mult.is_zero()); + SASSERT(!mult.is_zero()); if (is_app_of(curr, decl)) { unsigned num_args = to_app(curr)->get_num_args(); for (unsigned i = 0; i < num_args; i++) { @@ -772,16 +772,16 @@ void simplifier::reduce1_quantifier(quantifier * q) { quantifier_ref q1(m); proof * p1 = 0; - - if (is_quantifier(new_body) && + + if (is_quantifier(new_body) && to_quantifier(new_body)->is_forall() == q->is_forall() && !to_quantifier(q)->has_patterns() && !to_quantifier(new_body)->has_patterns()) { - + quantifier * nested_q = to_quantifier(new_body); ptr_buffer sorts; - buffer names; + buffer names; sorts.append(q->get_num_decls(), q->get_decl_sorts()); names.append(q->get_num_decls(), q->get_decl_names()); sorts.append(nested_q->get_num_decls(), nested_q->get_decl_sorts()); @@ -797,7 +797,7 @@ void simplifier::reduce1_quantifier(quantifier * q) { q->get_skid(), 0, 0, 0, 0); SASSERT(is_well_sorted(m, q1)); - + if (m.fine_grain_proofs()) { quantifier * q0 = m.update_quantifier(q, new_body); proof * p0 = q == q0 ? 0 : m.mk_quant_intro(q, q0, new_body_pr); @@ -817,13 +817,13 @@ void simplifier::reduce1_quantifier(quantifier * q) { get_cached(q->get_pattern(i), new_pattern, new_pattern_pr); if (m.is_pattern(new_pattern)) { new_patterns.push_back(new_pattern); - } + } } num = q->get_num_no_patterns(); for (unsigned i = 0; i < num; i++) { get_cached(q->get_no_pattern(i), new_pattern, new_pattern_pr); new_no_patterns.push_back(new_pattern); - } + } remove_duplicates(new_patterns); remove_duplicates(new_no_patterns); @@ -833,7 +833,7 @@ void simplifier::reduce1_quantifier(quantifier * q) { q->get_decl_sorts(), q->get_decl_names(), new_body, - q->get_weight(), + q->get_weight(), q->get_qid(), q->get_skid(), new_patterns.size(), @@ -850,10 +850,10 @@ void simplifier::reduce1_quantifier(quantifier * q) { p1 = q == q1 ? 0 : m.mk_quant_intro(q, q1, new_body_pr); } } - + expr_ref r(m); - elim_unused_vars(m, q1, r); - + elim_unused_vars(m, q1, params_ref(), r); + proof * pr = 0; if (m.fine_grain_proofs()) { proof * p2 = 0; @@ -871,7 +871,7 @@ void simplifier::reduce1_quantifier(quantifier * q) { void simplifier::borrow_plugins(simplifier const & s) { ptr_vector::const_iterator it = s.begin_plugins(); ptr_vector::const_iterator end = s.end_plugins(); - for (; it != end; ++it) + for (; it != end; ++it) register_plugin(*it); } @@ -882,7 +882,7 @@ void simplifier::enable_presimp() { enable_ac_support(false); ptr_vector::const_iterator it = begin_plugins(); ptr_vector::const_iterator end = end_plugins(); - for (; it != end; ++it) + for (; it != end; ++it) (*it)->enable_presimp(true); } @@ -905,7 +905,7 @@ bool subst_simplifier::get_subst(expr * n, expr_ref & r, proof_ref & p) { m_subst_map->get(n, _r, _p); r = _r; p = _p; - if (m.coarse_grain_proofs()) + if (m.coarse_grain_proofs()) m_subst_proofs.push_back(p); return true; } @@ -917,7 +917,7 @@ static void push_core(ast_manager & m, expr * e, proof * pr, expr_ref_vector & r TRACE("preprocessor", tout << mk_pp(e, m) << "\n"; if (pr) tout << mk_ll_pp(pr, m) << "\n\n";); - if (m.is_true(e)) + if (m.is_true(e)) return; result.push_back(e); if (m.proofs_enabled()) @@ -952,9 +952,9 @@ void push_assertion(ast_manager & m, expr * e, proof * pr, expr_ref_vector & res CTRACE("push_assertion", !(pr == 0 || m.is_undef_proof(pr) || m.get_fact(pr) == e), tout << mk_pp(e, m) << "\n" << mk_pp(m.get_fact(pr), m) << "\n";); SASSERT(pr == 0 || m.is_undef_proof(pr) || m.get_fact(pr) == e); - if (m.is_and(e)) + if (m.is_and(e)) push_and(m, to_app(e), pr, result, result_prs); - else if (m.is_not(e) && m.is_or(to_app(e)->get_arg(0))) + else if (m.is_not(e) && m.is_or(to_app(e)->get_arg(0))) push_not_or(m, to_app(to_app(e)->get_arg(0)), pr, result, result_prs); else push_core(m, e, pr, result, result_prs); diff --git a/src/ast/static_features.cpp b/src/ast/static_features.cpp index 328128794..daf20e095 100644 --- a/src/ast/static_features.cpp +++ b/src/ast/static_features.cpp @@ -25,6 +25,7 @@ static_features::static_features(ast_manager & m): m_bvutil(m), m_arrayutil(m), m_fpautil(m), + m_sequtil(m), m_bfid(m.get_basic_family_id()), m_afid(m.mk_family_id("arith")), m_lfid(m.mk_family_id("label")), @@ -77,6 +78,8 @@ void static_features::reset() { m_has_real = false; m_has_bv = false; m_has_fpa = false; + m_has_str = false; + m_has_seq_non_str = false; m_has_arrays = false; m_arith_k_sum .reset(); m_num_arith_terms = 0; @@ -279,6 +282,11 @@ void static_features::update_core(expr * e) { m_has_fpa = true; if (!m_has_arrays && m_arrayutil.is_array(e)) m_has_arrays = true; + if (!m_has_str && m_sequtil.str.is_string_term(e)) + m_has_str = true; + if (!m_has_seq_non_str && m_sequtil.str.is_non_string_sequence(e)) { + m_has_seq_non_str = true; + } if (is_app(e)) { family_id fid = to_app(e)->get_family_id(); mark_theory(fid); diff --git a/src/ast/static_features.h b/src/ast/static_features.h index 8b20c5463..e7f69e041 100644 --- a/src/ast/static_features.h +++ b/src/ast/static_features.h @@ -24,6 +24,7 @@ Revision History: #include"bv_decl_plugin.h" #include"array_decl_plugin.h" #include"fpa_decl_plugin.h" +#include"seq_decl_plugin.h" #include"map.h" struct static_features { @@ -32,6 +33,7 @@ struct static_features { bv_util m_bvutil; array_util m_arrayutil; fpa_util m_fpautil; + seq_util m_sequtil; family_id m_bfid; family_id m_afid; family_id m_lfid; @@ -77,6 +79,8 @@ struct static_features { bool m_has_real; // bool m_has_bv; // bool m_has_fpa; // + bool m_has_str; // has String-typed terms + bool m_has_seq_non_str; // has non-String-typed Sequence terms bool m_has_arrays; // rational m_arith_k_sum; // sum of the numerals in arith atoms. unsigned m_num_arith_terms; diff --git a/src/cmd_context/basic_cmds.cpp b/src/cmd_context/basic_cmds.cpp index d951f7710..f09d35158 100644 --- a/src/cmd_context/basic_cmds.cpp +++ b/src/cmd_context/basic_cmds.cpp @@ -100,13 +100,34 @@ public: ATOMIC_CMD(exit_cmd, "exit", "exit.", ctx.print_success(); throw stop_parser_exception();); -ATOMIC_CMD(get_model_cmd, "get-model", "retrieve model for the last check-sat command", { - if (!ctx.is_model_available() || ctx.get_check_sat_result() == 0) - throw cmd_exception("model is not available"); - model_ref m; - ctx.get_check_sat_result()->get_model(m); - ctx.display_model(m); -}); +class get_model_cmd : public cmd { + unsigned m_index; +public: + get_model_cmd(): cmd("get-model"), m_index(0) {} + virtual char const * get_usage() const { return "[]"; } + virtual char const * get_descr(cmd_context & ctx) const { + return "retrieve model for the last check-sat command.\nSupply optional index if retrieving a model corresponding to a box optimization objective"; + } + virtual unsigned get_arity() const { return VAR_ARITY; } + virtual cmd_arg_kind next_arg_kind(cmd_context & ctx) const { return CPK_UINT; } + virtual void set_next_arg(cmd_context & ctx, unsigned index) { m_index = index; } + virtual void execute(cmd_context & ctx) { + if (!ctx.is_model_available() || ctx.get_check_sat_result() == 0) + throw cmd_exception("model is not available"); + model_ref m; + if (m_index > 0 && ctx.get_opt()) { + ctx.get_opt()->get_box_model(m, m_index); + } + else { + ctx.get_check_sat_result()->get_model(m); + } + ctx.display_model(m); + } + virtual void reset(cmd_context& ctx) { + m_index = 0; + } +}; + ATOMIC_CMD(get_assignment_cmd, "get-assignment", "retrieve assignment", { if (!ctx.is_model_available() || ctx.get_check_sat_result() == 0) diff --git a/src/cmd_context/check_logic.cpp b/src/cmd_context/check_logic.cpp index c75c12689..0faddce73 100644 --- a/src/cmd_context/check_logic.cpp +++ b/src/cmd_context/check_logic.cpp @@ -187,6 +187,7 @@ struct check_logic::imp { m_bvs = true; m_uf = true; m_ints = true; + m_nonlinear = true; // non-linear 0-1 variables may get eliminated } else { m_unknown_logic = true; diff --git a/src/cmd_context/cmd_context.cpp b/src/cmd_context/cmd_context.cpp index 67b1df50c..f590725a7 100644 --- a/src/cmd_context/cmd_context.cpp +++ b/src/cmd_context/cmd_context.cpp @@ -249,6 +249,7 @@ protected: array_util m_arutil; fpa_util m_futil; seq_util m_sutil; + datalog::dl_decl_util m_dlutil; format_ns::format * pp_fdecl_name(symbol const & s, func_decls const & fs, func_decl * f, unsigned & len) { @@ -277,6 +278,7 @@ public: virtual array_util & get_arutil() { return m_arutil; } virtual fpa_util & get_futil() { return m_futil; } virtual seq_util & get_sutil() { return m_sutil; } + virtual datalog::dl_decl_util& get_dlutil() { return m_dlutil; } virtual bool uses(symbol const & s) const { return @@ -527,6 +529,9 @@ bool cmd_context::logic_has_fpa() const { return !has_logic() || smt_logics::logic_has_fpa(m_logic); } +bool cmd_context::logic_has_str() const { + return !has_logic() || m_logic == "QF_S"; +} bool cmd_context::logic_has_array() const { return !has_logic() || smt_logics::logic_has_array(m_logic); @@ -568,7 +573,6 @@ void cmd_context::init_manager_core(bool new_manager) { load_plugin(symbol("seq"), logic_has_seq(), fids); load_plugin(symbol("fpa"), logic_has_fpa(), fids); load_plugin(symbol("pb"), logic_has_pb(), fids); - svector::iterator it = fids.begin(); svector::iterator end = fids.end(); for (; it != end; ++it) { @@ -616,7 +620,6 @@ void cmd_context::init_external_manager() { init_manager_core(false); } - bool cmd_context::set_logic(symbol const & s) { if (has_logic()) throw cmd_exception("the logic has already been set"); @@ -739,8 +742,11 @@ void cmd_context::insert_rec_fun(func_decl* f, expr_ref_vector const& binding, s lhs = m().mk_app(f, binding.size(), binding.c_ptr()); eq = m().mk_eq(lhs, e); if (!ids.empty()) { - expr* pat = m().mk_pattern(lhs); - eq = m().mk_forall(ids.size(), f->get_domain(), ids.c_ptr(), eq, 0, m().rec_fun_qid(), symbol::null, 1, &pat); + if (!is_app(e)) { + throw cmd_exception("Z3 only supports recursive definitions that are proper terms (not binders or variables)"); + } + expr* pats[2] = { m().mk_pattern(lhs), m().mk_pattern(to_app(e)) }; + eq = m().mk_forall(ids.size(), f->get_domain(), ids.c_ptr(), eq, 0, m().rec_fun_qid(), symbol::null, 2, pats); } // @@ -1551,6 +1557,7 @@ void cmd_context::validate_model() { p.set_uint("sort_store", true); p.set_bool("completion", true); model_evaluator evaluator(*(md.get()), p); + evaluator.set_expand_array_equalities(false); contains_array_op_proc contains_array(m()); { scoped_rlimit _rlimit(m().limit(), 0); diff --git a/src/cmd_context/cmd_context.h b/src/cmd_context/cmd_context.h index 8eee632dc..8885bc5d6 100644 --- a/src/cmd_context/cmd_context.h +++ b/src/cmd_context/cmd_context.h @@ -124,6 +124,7 @@ public: virtual bool is_pareto() = 0; virtual void set_logic(symbol const& s) = 0; virtual bool print_model() const = 0; + virtual void get_box_model(model_ref& mdl, unsigned index) = 0; virtual void updt_params(params_ref const& p) = 0; }; @@ -256,6 +257,7 @@ protected: bool logic_has_array() const; bool logic_has_datatype() const; bool logic_has_fpa() const; + bool logic_has_str() const; void print_unsupported_msg() { regular_stream() << "unsupported" << std::endl; } void print_unsupported_info(symbol const& s, int line, int pos) { if (s != symbol::null) diagnostic_stream() << "; " << s << " line: " << line << " position: " << pos << std::endl;} diff --git a/src/cmd_context/eval_cmd.cpp b/src/cmd_context/eval_cmd.cpp index 94583001b..86078a13c 100644 --- a/src/cmd_context/eval_cmd.cpp +++ b/src/cmd_context/eval_cmd.cpp @@ -38,6 +38,7 @@ public: virtual void init_pdescrs(cmd_context & ctx, param_descrs & p) { model_evaluator::get_param_descrs(p); insert_timeout(p); + p.insert("model_index", CPK_UINT, "(default: 0) index of model from box optimization objective"); } virtual void prepare(cmd_context & ctx) { @@ -58,9 +59,15 @@ public: if (!ctx.is_model_available()) throw cmd_exception("model is not available"); model_ref md; + unsigned index = m_params.get_uint("model_index", 0); check_sat_result * last_result = ctx.get_check_sat_result(); SASSERT(last_result); - last_result->get_model(md); + if (index == 0 || !ctx.get_opt()) { + last_result->get_model(md); + } + else { + ctx.get_opt()->get_box_model(md, index); + } expr_ref r(ctx.m()); unsigned timeout = m_params.get_uint("timeout", UINT_MAX); unsigned rlimit = m_params.get_uint("rlimit", 0); diff --git a/src/cmd_context/extra_cmds/dbg_cmds.cpp b/src/cmd_context/extra_cmds/dbg_cmds.cpp index 509b5ff2e..7ee1c0aeb 100644 --- a/src/cmd_context/extra_cmds/dbg_cmds.cpp +++ b/src/cmd_context/extra_cmds/dbg_cmds.cpp @@ -29,11 +29,12 @@ Notes: #include"bound_manager.h" #include"used_vars.h" #include"var_subst.h" +#include"gparams.h" #ifndef _EXTERNAL_RELEASE -BINARY_SYM_CMD(get_quantifier_body_cmd, - "dbg-get-qbody", +BINARY_SYM_CMD(get_quantifier_body_cmd, + "dbg-get-qbody", " ", "store the body of the quantifier in the global variable ", CPK_EXPR, @@ -43,8 +44,8 @@ BINARY_SYM_CMD(get_quantifier_body_cmd, store_expr_ref(ctx, m_sym, to_quantifier(arg)->get_expr()); }); -BINARY_SYM_CMD(set_cmd, - "dbg-set", +BINARY_SYM_CMD(set_cmd, + "dbg-set", " ", "store in the global variable ", CPK_EXPR, @@ -57,7 +58,7 @@ UNARY_CMD(pp_var_cmd, "dbg-pp-var", "", "pretty print a global variable expr * t = get_expr_ref(ctx, arg); SASSERT(t != 0); ctx.display(ctx.regular_stream(), t); - ctx.regular_stream() << std::endl; + ctx.regular_stream() << std::endl; }); BINARY_SYM_CMD(shift_vars_cmd, @@ -71,7 +72,7 @@ BINARY_SYM_CMD(shift_vars_cmd, var_shifter s(ctx.m()); s(t, arg, r); store_expr_ref(ctx, m_sym, r.get()); -}); +}); UNARY_CMD(pp_shared_cmd, "dbg-pp-shared", "", "display shared subterms of the given term", CPK_EXPR, expr *, { shared_occs s(ctx.m()); @@ -81,7 +82,7 @@ UNARY_CMD(pp_shared_cmd, "dbg-pp-shared", "", "display shared subterms of shared_occs::iterator end = s.end_shared(); for (; it != end; ++it) { expr * curr = *it; - ctx.regular_stream() << std::endl << " "; + ctx.regular_stream() << std::endl << " "; ctx.display(ctx.regular_stream(), curr, 2); } ctx.regular_stream() << ")" << std::endl; @@ -112,7 +113,7 @@ public: if (m_idx == 1) return CPK_SYMBOL_LIST; return CPK_SYMBOL; } - virtual void set_next_arg(cmd_context & ctx, symbol const & s) { + virtual void set_next_arg(cmd_context & ctx, symbol const & s) { if (m_idx == 0) { m_source = get_expr_ref(ctx, s); } @@ -146,24 +147,24 @@ UNARY_CMD(bool_rewriter_cmd, "dbg-bool-rewriter", "", "apply the Boolean r bool_rewriter_star r(ctx.m(), p); r(arg, t); ctx.display(ctx.regular_stream(), t); - ctx.regular_stream() << std::endl; + ctx.regular_stream() << std::endl; }); UNARY_CMD(bool_frewriter_cmd, "dbg-bool-flat-rewriter", "", "apply the Boolean (flattening) rewriter to the given term", CPK_EXPR, expr *, { expr_ref t(ctx.m()); - { + { params_ref p; p.set_bool("flat", true); bool_rewriter_star r(ctx.m(), p); r(arg, t); } ctx.display(ctx.regular_stream(), t); - ctx.regular_stream() << std::endl; + ctx.regular_stream() << std::endl; }); UNARY_CMD(elim_and_cmd, "dbg-elim-and", "", "apply the Boolean rewriter (eliminating AND operator and flattening) to the given term", CPK_EXPR, expr *, { expr_ref t(ctx.m()); - { + { params_ref p; p.set_bool("flat", true); p.set_bool("elim_and", true); @@ -171,7 +172,7 @@ UNARY_CMD(elim_and_cmd, "dbg-elim-and", "", "apply the Boolean rewriter (e r(arg, t); } ctx.display(ctx.regular_stream(), t); - ctx.regular_stream() << std::endl; + ctx.regular_stream() << std::endl; }); class lt_cmd : public cmd { @@ -192,7 +193,7 @@ public: } virtual void execute(cmd_context & ctx) { bool r = lt(m_t1, m_t2); - ctx.regular_stream() << (r ? "true" : "false") << std::endl; + ctx.regular_stream() << (r ? "true" : "false") << std::endl; } }; @@ -249,7 +250,7 @@ UNARY_CMD(set_next_id, "dbg-set-next-id", "", "set the next expression UNARY_CMD(used_vars_cmd, "dbg-used-vars", "", "test used_vars functor", CPK_EXPR, expr *, { used_vars proc; - if (is_quantifier(arg)) + if (is_quantifier(arg)) arg = to_quantifier(arg)->get_expr(); proc(arg); ctx.regular_stream() << "(vars"; @@ -258,7 +259,7 @@ UNARY_CMD(used_vars_cmd, "dbg-used-vars", "", "test used_vars functor", CP ctx.regular_stream() << "\n (" << std::left << std::setw(6) << i << " "; if (s != 0) ctx.display(ctx.regular_stream(), s, 10); - else + else ctx.regular_stream() << ""; ctx.regular_stream() << ")"; } @@ -271,7 +272,7 @@ UNARY_CMD(elim_unused_vars_cmd, "dbg-elim-unused-vars", "", "eliminate unu return; } expr_ref r(ctx.m()); - elim_unused_vars(ctx.m(), to_quantifier(arg), r); + elim_unused_vars(ctx.m(), to_quantifier(arg), gparams::get(), r); SASSERT(!is_quantifier(r) || !to_quantifier(r)->may_have_unused_vars()); ctx.display(ctx.regular_stream(), r); ctx.regular_stream() << std::endl; @@ -287,18 +288,18 @@ public: virtual char const * get_descr() const { return "instantiate the quantifier using the given expressions."; } virtual unsigned get_arity() const { return 2; } virtual void prepare(cmd_context & ctx) { m_q = 0; m_args.reset(); } - + virtual cmd_arg_kind next_arg_kind(cmd_context & ctx) const { if (m_q == 0) return CPK_EXPR; else return CPK_EXPR_LIST; } - + virtual void set_next_arg(cmd_context & ctx, expr * s) { if (!is_quantifier(s)) throw cmd_exception("invalid command, quantifier expected."); m_q = to_quantifier(s); } - + virtual void set_next_arg(cmd_context & ctx, unsigned num, expr * const * ts) { if (num != m_q->get_num_decls()) throw cmd_exception("invalid command, mismatch between the number of quantified variables and the number of arguments."); @@ -331,7 +332,7 @@ public: class instantiate_nested_cmd : public instantiate_cmd_core { public: instantiate_nested_cmd():instantiate_cmd_core("dbg-instantiate-nested") {} - + virtual char const * get_descr() const { return "instantiate the quantifier nested in the outermost quantifier, this command is used to test the instantiation procedure with quantifiers that contain free variables."; } virtual void set_next_arg(cmd_context & ctx, expr * s) { diff --git a/src/duality/duality_wrapper.cpp b/src/duality/duality_wrapper.cpp index 7ee76d4d6..35033f739 100755 --- a/src/duality/duality_wrapper.cpp +++ b/src/duality/duality_wrapper.cpp @@ -41,8 +41,8 @@ namespace Duality { params_ref p; p.set_bool("proof", true); // this is currently useless if(models) - p.set_bool("model", true); - p.set_bool("unsat_core", true); + p.set_bool("model", true); + p.set_bool("unsat_core", true); bool mbqi = c.get_config().get().get_bool("mbqi",true); p.set_bool("mbqi",mbqi); // just to test p.set_str("mbqi.id","itp"); // use mbqi for quantifiers in interpolants @@ -57,7 +57,7 @@ namespace Duality { m_mode = m().proof_mode(); } - expr context::constant(const std::string &name, const sort &ty){ + expr context::constant(const std::string &name, const sort &ty){ symbol s = str_symbol(name.c_str()); return cook(m().mk_const(m().mk_const_decl(s, ty))); } @@ -111,7 +111,7 @@ namespace Duality { } expr context::mki(family_id fid, ::decl_kind dk, int n, ::expr **args){ - return cook(m().mk_app(fid, dk, 0, 0, n, (::expr **)args)); + return cook(m().mk_app(fid, dk, 0, 0, n, (::expr **)args)); } expr context::make(decl_kind op, const std::vector &args){ @@ -168,9 +168,9 @@ namespace Duality { expr_abstract(m(), 0, num_bound, VEC2PTR(bound_asts), to_expr(body.raw()), abs_body); expr_ref result(m()); result = m().mk_quantifier( - op == Forall, - names.size(), VEC2PTR(types), VEC2PTR(names), abs_body.get(), - 0, + op == Forall, + names.size(), VEC2PTR(types), VEC2PTR(names), abs_body.get(), + 0, ::symbol(), ::symbol(), 0, 0, @@ -194,9 +194,9 @@ namespace Duality { } expr_ref result(m()); result = m().mk_quantifier( - op == Forall, - names.size(), VEC2PTR(types), VEC2PTR(names), to_expr(body.raw()), - 0, + op == Forall, + names.size(), VEC2PTR(types), VEC2PTR(names), to_expr(body.raw()), + 0, ::symbol(), ::symbol(), 0, 0, @@ -273,7 +273,7 @@ namespace Duality { return OtherArray; } } - + return Other; } @@ -340,7 +340,7 @@ namespace Duality { params p; return simplify(p); } - + expr context::make_var(int idx, const sort &s){ ::sort * a = to_sort(s.raw()); return cook(m().mk_var(idx,a)); @@ -348,7 +348,7 @@ namespace Duality { expr expr::qe_lite() const { - ::qe_lite qe(m()); + ::qe_lite qe(m(), params_ref()); expr_ref result(to_expr(raw()),m()); proof_ref pf(m()); qe(result,pf); @@ -356,7 +356,7 @@ namespace Duality { } expr expr::qe_lite(const std::set &idxs, bool index_of_bound) const { - ::qe_lite qe(m()); + ::qe_lite qe(m(), params_ref()); expr_ref result(to_expr(raw()),m()); proof_ref pf(m()); uint_set uis; @@ -412,16 +412,16 @@ namespace Duality { std::vector < ::sort * > _domain(domain.size()); for(unsigned i = 0; i < domain.size(); i++) _domain[i] = to_sort(domain[i].raw()); - ::func_decl* d = m().mk_fresh_func_decl(prefix, - _domain.size(), + ::func_decl* d = m().mk_fresh_func_decl(prefix, + _domain.size(), VEC2PTR(_domain), to_sort(range.raw())); return func_decl(*this,d); } func_decl context::fresh_func_decl(char const * prefix, sort const & range){ - ::func_decl* d = m().mk_fresh_func_decl(prefix, - 0, + ::func_decl* d = m().mk_fresh_func_decl(prefix, + 0, 0, to_sort(range.raw())); return func_decl(*this,d); @@ -462,30 +462,30 @@ namespace Duality { incremental, _theory.size(), VEC2PTR(_theory)); - + if(lb == Z3_L_FALSE){ interpolants.resize(_interpolants.size()); for (unsigned i = 0; i < _interpolants.size(); ++i) { interpolants[i] = expr(ctx(),_interpolants[i]); } - } - + } + if (_model) { model = iz3wrapper::model(ctx(), _model); } - + if(_labels){ labels = _labels; } - + return lb; } #endif - + static int linearize_assumptions(int num, TermTree *assumptions, - std::vector > &linear_assumptions, + std::vector > &linear_assumptions, std::vector &parents){ for(unsigned i = 0; i < assumptions->getChildren().size(); i++) num = linearize_assumptions(num, assumptions->getChildren()[i], linear_assumptions, parents); @@ -501,7 +501,7 @@ namespace Duality { } static int unlinearize_interpolants(int num, - TermTree* assumptions, + TermTree* assumptions, const std::vector &interpolant, TermTree * &tree_interpolant) { @@ -522,7 +522,7 @@ namespace Duality { literals &labels, bool incremental ) - + { int size = assumptions->number(0); std::vector > linear_assumptions(size); @@ -540,36 +540,36 @@ namespace Duality { ptr_vector< ::ast> _theory(theory.size()); for(unsigned i = 0; i < theory.size(); i++) _theory[i] = theory[i]; - - + + if(!incremental){ push(); for(unsigned i = 0; i < linear_assumptions.size(); i++) for(unsigned j = 0; j < linear_assumptions[i].size(); j++) add(linear_assumptions[i][j]); } - + check_result res = unsat; if(!m_solver->get_proof()) res = check(); - + if(res == unsat){ interpolation_options_struct opts; if(weak_mode) - opts.set("weak","1"); - + opts.set("weak","1"); + ::ast *proof = m_solver->get_proof(); try { iz3interpolate(m(),proof,_assumptions,_parents,_interpolants,_theory,&opts); } // If there's an interpolation bug, throw a char * - // exception so duality can catch it and restart. + // exception so duality can catch it and restart. catch (const interpolation_failure &f) { throw f.msg(); } - + std::vector linearized_interpolants(_interpolants.size()); for(unsigned i = 0; i < _interpolants.size(); i++) linearized_interpolants[i] = expr(ctx(),_interpolants[i]); @@ -585,13 +585,13 @@ namespace Duality { model_ref _m; m_solver->get_model(_m); model = Duality::model(ctx(),_m.get()); - + #if 0 if(_labels){ labels = _labels; } #endif - + if(!incremental) pop(); @@ -603,7 +603,7 @@ namespace Duality { void interpolating_solver::SetWeakInterpolants(bool weak){ weak_mode = weak; } - + void interpolating_solver::SetPrintToFile(const std::string &filename){ print_filename = filename; @@ -618,14 +618,14 @@ namespace Duality { void interpolating_solver::RemoveInterpolationAxiom(const expr & t){ // theory.remove(t); } - + const char *interpolating_solver::profile(){ // return Z3_interpolation_profile(ctx()); return ""; } - + static void get_assumptions_rec(stl_ext::hash_set &memo, const proof &pf, std::vector &assumps){ if(memo.find(pf) != memo.end())return; memo.insert(pf); @@ -657,7 +657,7 @@ namespace Duality { model_smt2_pp(std::cout, m(), *m_model, 0); std::cout << std::endl; } - + void model::show_hash() const { std::ostringstream ss; model_smt2_pp(ss, m(), *m_model, 0); diff --git a/src/interp/iz3proof_itp.cpp b/src/interp/iz3proof_itp.cpp index 11cb2f6a2..26ef7386c 100755 --- a/src/interp/iz3proof_itp.cpp +++ b/src/interp/iz3proof_itp.cpp @@ -541,6 +541,7 @@ class iz3proof_itp_impl : public iz3proof_itp { placeholder_arg |= is_placeholder(args[i]); } try { + TRACE("duality", print_expr(tout, e); tout << "\n";); opr f = op(e); if(f == Equal && args[0] == args[1]) res = mk_true(); else if(f == And) res = my_and(args); @@ -853,6 +854,7 @@ class iz3proof_itp_impl : public iz3proof_itp { ast simplify_rotate_eq2leq(const ast &pl, const ast &neg_equality, const ast &pf){ if(pl == arg(pf,1)){ + TRACE("duality", print_expr(tout, pl); print_expr(tout << "\n", neg_equality); print_expr(tout << "\n", pf); tout << "\n";); ast cond = mk_true(); ast equa = sep_cond(arg(pf,0),cond); if(is_equivrel_chain(equa)){ @@ -1870,10 +1872,13 @@ class iz3proof_itp_impl : public iz3proof_itp { ast chain_ineqs(opr comp_op, LitType t, const ast &chain, const ast &lhs, const ast &rhs){ if(is_true(chain)){ - if(lhs != rhs) + if (lhs != rhs) { + TRACE("duality", print_expr(tout, lhs); tout << " "; print_expr(tout, rhs); tout << "\n";); throw bad_ineq_inference(); + } return make(Leq,make_int(rational(0)),make_int(rational(0))); } + TRACE("duality", print_expr(tout, chain); print_expr(tout << "\n", lhs); tout << " "; print_expr(tout, rhs); tout << "\n";); ast last = chain_last(chain); ast rest = chain_rest(chain); ast mid = subst_in_pos(rhs,rewrite_pos(last),rewrite_lhs(last)); diff --git a/src/muz/base/dl_rule.cpp b/src/muz/base/dl_rule.cpp index 56cc6e154..ffb964f47 100644 --- a/src/muz/base/dl_rule.cpp +++ b/src/muz/base/dl_rule.cpp @@ -47,14 +47,14 @@ Revision History: namespace datalog { - rule_manager::rule_manager(context& ctx) + rule_manager::rule_manager(context& ctx) : m(ctx.get_manager()), m_ctx(ctx), m_body(m), m_head(m), m_args(m), m_hnf(m), - m_qe(m), + m_qe(m, params_ref()), m_rwr(m), m_ufproc(m) {} @@ -98,7 +98,7 @@ namespace datalog { var_idx_set& rule_manager::finalize_collect_vars() { unsigned sz = m_free_vars.size(); for (unsigned i = 0; i < sz; ++i) { - if (m_free_vars[i]) m_var_idx.insert(i); + if (m_free_vars[i]) m_var_idx.insert(i); } return m_var_idx; } @@ -139,7 +139,7 @@ namespace datalog { } - void rule_manager::mk_rule(expr* fml, proof* p, rule_set& rules, symbol const& name) { + void rule_manager::mk_rule(expr* fml, proof* p, rule_set& rules, symbol const& name) { scoped_proof_mode _sc(m, m_ctx.generate_proof_trace()?PGM_FINE:PGM_DISABLED); proof_ref pr(p, m); expr_ref fml1(m); @@ -147,7 +147,7 @@ namespace datalog { if (fml1 != fml && pr) { pr = m.mk_asserted(fml1); } - remove_labels(fml1, pr); + remove_labels(fml1, pr); mk_rule_core(fml1, pr, rules, name); } @@ -162,7 +162,7 @@ namespace datalog { else { is_negated.push_back(false); } - } + } } void rule_manager::mk_rule_core(expr* fml, proof* p, rule_set& rules, symbol const& name) { @@ -170,7 +170,7 @@ namespace datalog { proof_ref_vector prs(m); m_hnf.reset(); m_hnf.set_name(name); - + m_hnf(fml, p, fmls, prs); for (unsigned i = 0; i < m_hnf.get_fresh_predicates().size(); ++i) { m_ctx.register_predicate(m_hnf.get_fresh_predicates()[i], false); @@ -181,7 +181,7 @@ namespace datalog { } void rule_manager::mk_horn_rule(expr* fml, proof* p, rule_set& rules, symbol const& name) { - + m_body.reset(); m_neg.reset(); unsigned index = extract_horn(fml, m_body, m_head); @@ -208,13 +208,13 @@ namespace datalog { } else if (is_quantifier(fml1)) { p = m.mk_modus_ponens(p, m.mk_symmetry(m.mk_der(to_quantifier(fml1), fml))); - } + } else { p = m.mk_modus_ponens(p, m.mk_rewrite(fml, fml1)); } } - if (m_ctx.fix_unbound_vars()) { + if (m_ctx.fix_unbound_vars()) { fix_unbound_vars(r, true); } @@ -242,10 +242,10 @@ namespace datalog { for (unsigned i = 0; i < m_args.size(); ++i) { body.push_back(ensure_app(m_args[i].get())); } - } + } else { head = ensure_app(fml); - } + } return index; } @@ -262,12 +262,12 @@ namespace datalog { func_decl* rule_manager::mk_query(expr* query, rule_set& rules) { TRACE("dl", tout << mk_pp(query, m) << "\n";); - + ptr_vector vars; svector names; app_ref_vector body(m); expr_ref q(m); - + // Add implicit variables. // Remove existential prefix. bind_variables(query, false, q); @@ -278,7 +278,7 @@ namespace datalog { m_free_vars(q); vars.append(m_free_vars.size(), m_free_vars.c_ptr()); if (vars.contains(static_cast(0))) { - var_subst sub(m, false); + var_subst sub(m, false); expr_ref_vector args(m); // [s0, 0, s2, ..] // [0 -> 0, 1 -> x, 2 -> 1, ..] @@ -313,7 +313,7 @@ namespace datalog { } } - // we want outermost declared variable first to + // we want outermost declared variable first to // follow order of quantified variables so we reverse vars. while (vars.size() > names.size()) { names.push_back(symbol(names.size())); @@ -321,9 +321,9 @@ namespace datalog { vars.reverse(); names.reverse(); func_decl* qpred = m_ctx.mk_fresh_head_predicate(symbol("query"), symbol(), vars.size(), vars.c_ptr(), body_pred); - m_ctx.register_predicate(qpred, false); + m_ctx.register_predicate(qpred, false); rules.set_output_predicate(qpred); - + if (m_ctx.get_model_converter()) { filter_model_converter* mc = alloc(filter_model_converter, m); mc->insert(qpred); @@ -366,7 +366,7 @@ namespace datalog { for (unsigned i = 0; i < r.size(); ++i) { body.push_back(ensure_app(r[i].get())); } - } + } void rule_manager::hoist_compound(unsigned& num_bound, app_ref& fml, app_ref_vector& body) { @@ -440,7 +440,7 @@ namespace datalog { if (is_quantifier(e)) { q = to_quantifier(e); return q->is_forall(); - } + } return false; } @@ -454,7 +454,7 @@ namespace datalog { return app_ref(m.mk_eq(e, m.mk_true()), m); } } - + void rule_manager::check_app(expr* e) { if (!is_app(e)) { std::ostringstream out; @@ -481,7 +481,7 @@ namespace datalog { bool has_neg = false; for (unsigned i = 0; i < n; i++) { - bool is_neg = (is_negated != 0 && is_negated[i]); + bool is_neg = (is_negated != 0 && is_negated[i]); app * curr = tail[i]; if (is_neg && !m_ctx.is_predicate(curr)) { @@ -571,7 +571,7 @@ namespace datalog { case 1: fml = m.mk_implies(body[0].get(), fml); break; default: fml = m.mk_implies(m.mk_and(body.size(), body.c_ptr()), fml); break; } - + m_free_vars(fml); if (m_free_vars.empty()) { return; @@ -579,7 +579,7 @@ namespace datalog { svector names; used_symbols<> us; m_free_vars.set_default_sort(m.mk_bool_sort()); - + us(fml); m_free_vars.reverse(); for (unsigned j = 0, i = 0; i < m_free_vars.size(); ++j) { @@ -594,8 +594,8 @@ namespace datalog { ++i; } } - } - fml = m.mk_forall(m_free_vars.size(), m_free_vars.c_ptr(), names.c_ptr(), fml); + } + fml = m.mk_forall(m_free_vars.size(), m_free_vars.c_ptr(), names.c_ptr(), fml); } std::ostream& rule_manager::display_smt2(rule const& r, std::ostream & out) { @@ -749,7 +749,7 @@ namespace datalog { quant_tail = m.mk_exists(q_var_cnt, qsorts.c_ptr(), qnames.c_ptr(), unbound_tail_pre_quant); if (try_quantifier_elimination) { - TRACE("dl_rule_unbound_fix_pre_qe", + TRACE("dl_rule_unbound_fix_pre_qe", tout<<"rule: "; r->display(m_ctx, tout); tout<<"tail with unbound vars: "<display(m_ctx, tout); tout<<"tail with unbound vars: "<name(), false); - // keep old variable indices around so we can compose with substitutions. + // keep old variable indices around so we can compose with substitutions. // r->norm_vars(*this); } @@ -835,7 +835,7 @@ namespace datalog { void rule_manager::check_valid_head(expr * head) const { SASSERT(head); - + if (!m_ctx.is_predicate(head)) { std::ostringstream out; out << "Illegal head. The head predicate needs to be uninterpreted and registered (as recursive) " << mk_pp(head, m); @@ -874,14 +874,14 @@ namespace datalog { m.get_allocator().deallocate(get_obj_size(n), this); } - void rule::set_proof(ast_manager& m, proof* p) { + void rule::set_proof(ast_manager& m, proof* p) { if (p) { - m.inc_ref(p); + m.inc_ref(p); } if (m_proof) { - m.dec_ref(m_proof); + m.dec_ref(m_proof); } - m_proof = p; + m_proof = p; } bool rule::is_in_tail(const func_decl * p, bool only_positive) const { @@ -896,7 +896,7 @@ namespace datalog { // - // non-predicates may appear only in the interpreted tail, it is therefore + // non-predicates may appear only in the interpreted tail, it is therefore // sufficient only to check the tail. // bool rule_manager::has_uninterpreted_non_predicates(rule const& r, func_decl*& f) const { @@ -911,7 +911,7 @@ namespace datalog { // - // Quantifiers may appear only in the interpreted tail, it is therefore + // Quantifiers may appear only in the interpreted tail, it is therefore // sufficient only to check the interpreted tail. // void rule_manager::has_quantifiers(rule const& r, bool& existential, bool& universal) const { @@ -945,7 +945,7 @@ namespace datalog { unsigned sz = get_tail_size(); for (unsigned i = 0; i < sz; ++i) { used.process(get_tail(i)); - } + } } void rule::get_vars(ast_manager& m, ptr_vector& sorts) const { @@ -994,13 +994,13 @@ namespace datalog { app * old_tail = get_tail(i); expr_ref new_tail_e(m); vs(old_tail, subst_vals.size(), subst_vals.c_ptr(), new_tail_e); - bool sign = is_neg_tail(i); + bool sign = is_neg_tail(i); m.inc_ref(new_tail_e); m.dec_ref(old_tail); m_tail[i] = TAG(app *, to_app(new_tail_e), sign); } } - + void rule::display(context & ctx, std::ostream & out) const { ast_manager & m = ctx.get_manager(); //out << mk_pp(m_head, m); @@ -1068,7 +1068,7 @@ namespace datalog { } - + }; diff --git a/src/muz/base/dl_util.h b/src/muz/base/dl_util.h index b1719577b..a4e9f192e 100644 --- a/src/muz/base/dl_util.h +++ b/src/muz/base/dl_util.h @@ -29,6 +29,7 @@ Revision History: #include"substitution.h" #include"ast_counter.h" #include"statistics.h" +#include"stopwatch.h" #include"lbool.h" namespace datalog { diff --git a/src/muz/pdr/pdr_context.cpp b/src/muz/pdr/pdr_context.cpp index 587488fc9..7484a77fa 100644 --- a/src/muz/pdr/pdr_context.cpp +++ b/src/muz/pdr/pdr_context.cpp @@ -1007,7 +1007,7 @@ namespace pdr { return m_cache[l]; } - void model_search::erase_children(model_node& n, bool backtrack) { + void model_search::erase_children(model_node& n, bool backtrack) { ptr_vector todo, nodes; todo.append(n.children()); remove_goal(n); @@ -2241,7 +2241,7 @@ namespace pdr { vars.append(aux_vars.size(), aux_vars.c_ptr()); scoped_ptr rep; - qe_lite qe(m); + qe_lite qe(m, m_params.p); expr_ref phi1 = m_pm.mk_and(Phi); qe(vars, phi1); TRACE("pdr", tout << "Eliminated\n" << mk_pp(phi1, m) << "\n";); diff --git a/src/muz/rel/dl_instruction.h b/src/muz/rel/dl_instruction.h index 160eb7b94..196f5268c 100644 --- a/src/muz/rel/dl_instruction.h +++ b/src/muz/rel/dl_instruction.h @@ -30,7 +30,6 @@ Revision History: namespace datalog { - class execution_context; class instruction_block; class rel_context; diff --git a/src/muz/tab/tab_context.cpp b/src/muz/tab/tab_context.cpp index 0a6c4c294..35eb5d936 100644 --- a/src/muz/tab/tab_context.cpp +++ b/src/muz/tab/tab_context.cpp @@ -53,10 +53,10 @@ namespace tb { app* t = to_app(_t); if (m.is_value(s) && m.is_value(t)) { - IF_VERBOSE(2, verbose_stream() << "different:" << mk_pp(s, m) << " " << mk_pp(t, m) << "\n";); + IF_VERBOSE(2, verbose_stream() << "different:" << mk_pp(s, m) << " " << mk_pp(t, m) << "\n";); return l_false; } - + if (m_dt.is_constructor(s) && m_dt.is_constructor(t)) { if (s->get_decl() == t->get_decl()) { lbool state = l_true; @@ -75,7 +75,7 @@ namespace tb { return state; } else { - IF_VERBOSE(2, verbose_stream() << "different constructors:" << mk_pp(s, m) << " " << mk_pp(t, m) << "\n";); + IF_VERBOSE(2, verbose_stream() << "different constructors:" << mk_pp(s, m) << " " << mk_pp(t, m) << "\n";); return l_false; } } @@ -109,7 +109,7 @@ namespace tb { case l_false: return false; default: - conds.push_back(m.mk_eq(p, t)); + conds.push_back(m.mk_eq(p, t)); return true; } } @@ -117,7 +117,7 @@ namespace tb { public: matcher(ast_manager& m): m(m), m_dt(m) {} - + bool operator()(app* pat, app* term, substitution& s, expr_ref_vector& conds) { // top-most term to match is a predicate. The predicates should be the same. if (pat->get_decl() != term->get_decl() || @@ -149,7 +149,7 @@ namespace tb { } } return true; - } + } }; class clause { @@ -165,22 +165,22 @@ namespace tb { unsigned m_next_rule; // next rule to expand goal on unsigned m_ref; // reference count - public: - + public: + clause(ast_manager& m): m_head(m), m_predicates(m), m_constraint(m), m_seqno(0), - m_index(0), + m_index(0), m_num_vars(0), - m_predicate_index(0), + m_predicate_index(0), m_parent_rule(0), m_parent_index(0), m_next_rule(static_cast(-1)), m_ref(0) { } - + void set_seqno(unsigned seqno) { m_seqno = seqno; } unsigned get_seqno() const { return m_seqno; } unsigned get_next_rule() const { return m_next_rule; } @@ -198,10 +198,10 @@ namespace tb { void set_head(app* h) { m_head = h; } unsigned get_parent_index() const { return m_parent_index; } unsigned get_parent_rule() const { return m_parent_rule; } - void set_parent(ref& parent) { + void set_parent(ref& parent) { m_parent_index = parent->get_index(); m_parent_rule = parent->get_next_rule(); - } + } expr_ref get_body() const { ast_manager& m = get_manager(); @@ -247,7 +247,7 @@ namespace tb { } if (!vars.empty()) { body = m.mk_forall(vars.size(), vars.c_ptr(), names.c_ptr(), body); - } + } return body; } @@ -273,18 +273,18 @@ namespace tb { reduce_equalities(); // IF_VERBOSE(1, display(verbose_stream());); } - + void inc_ref() { m_ref++; } - + void dec_ref() { --m_ref; if (m_ref == 0) { dealloc(this); } } - + void display(std::ostream& out) const { ast_manager& m = m_head.get_manager(); expr_ref_vector fmls(m); @@ -304,7 +304,7 @@ namespace tb { } out << mk_pp(fml, m) << "\n"; } - + private: ast_manager& get_manager() const { return m_head.get_manager(); } @@ -314,7 +314,7 @@ namespace tb { // - m_head - head predicate // - m_predicates - auxiliary predicates in body. // - m_constraint - side constraint - // + // void init_from_rule(datalog::rule_ref const& r) { ast_manager& m = get_manager(); expr_ref_vector fmls(m); @@ -328,7 +328,7 @@ namespace tb { m_predicates.reset(); for (unsigned i = 0; i < utsz; ++i) { m_predicates.push_back(r->get_tail(i)); - } + } bool_rewriter(m).mk_and(fmls.size(), fmls.c_ptr(), m_constraint); } @@ -348,13 +348,13 @@ namespace tb { if (get_subst(rw, subst, i, fmls)) { fmls[i] = m.mk_true(); } - } + } subst.apply(1, delta, expr_offset(m_head, 0), tmp); m_head = to_app(tmp); for (unsigned i = 0; i < m_predicates.size(); ++i) { subst.apply(1, delta, expr_offset(m_predicates[i].get(), 0), tmp); m_predicates[i] = to_app(tmp); - } + } bool_rewriter(m).mk_and(fmls.size(), fmls.c_ptr(), m_constraint); subst.apply(1, delta, expr_offset(m_constraint, 0), m_constraint); rw(m_constraint); @@ -404,7 +404,7 @@ namespace tb { throw non_constructor(); } } - void operator()(var* v) { } + void operator()(var* v) { } void operator()(quantifier* ) { throw non_constructor(); } @@ -421,7 +421,7 @@ namespace tb { return true; } - }; + }; // rules class rules { @@ -456,7 +456,7 @@ namespace tb { func_decl* f = g->get_decl(); map::obj_map_entry* e = m_index.insert_if_not_there2(f, unsigned_vector()); SASSERT(e); - e->get_data().m_value.push_back(idx); + e->get_data().m_value.push_back(idx); } unsigned get_num_rules(func_decl* p) const { @@ -475,14 +475,14 @@ namespace tb { for (; it != end; ++it) { decls.push_back(it->m_key); } - } + } ref get_rule(func_decl* p, unsigned idx) const { map::obj_map_entry* e = m_index.find_core(p); SASSERT(p); unsigned rule_id = e->get_data().get_value()[idx]; return m_rules[rule_id]; - } + } private: void reset() { m_rules.reset(); @@ -509,7 +509,7 @@ namespace tb { bool_rewriter m_rw; smt_params m_fparams; smt::kernel m_solver; - + public: index(ast_manager& m): m(m), @@ -520,7 +520,7 @@ namespace tb { m_matcher(m), m_refs(m), m_subst(m), - m_qe(m), + m_qe(m, params_ref()), m_rw(m), m_solver(m, m_fparams) {} @@ -544,7 +544,7 @@ namespace tb { } private: - + void setup(clause const& g) { m_preds.reset(); m_refs.reset(); @@ -569,8 +569,8 @@ namespace tb { } vs(g.get_constraint(), vars.size(), vars.c_ptr(), fml); fmls.push_back(fml); - m_precond = m.mk_and(fmls.size(), fmls.c_ptr()); - IF_VERBOSE(2, + m_precond = m.mk_and(fmls.size(), fmls.c_ptr()); + IF_VERBOSE(2, verbose_stream() << "setup-match: "; for (unsigned i = 0; i < m_preds.size(); ++i) { verbose_stream() << mk_pp(m_preds[i].get(), m) << " "; @@ -587,18 +587,18 @@ namespace tb { return true; } } - return false; + return false; } // // check that each predicate in r is matched by some predicate in premise. // for now: skip multiple matches within the same rule (incomplete). // bool match_rule(unsigned rule_index) { - clause const& g = *m_index[rule_index]; + clause const& g = *m_index[rule_index]; m_sideconds.reset(); m_subst.reset(); m_subst.reserve(2, g.get_num_vars()); - + IF_VERBOSE(2, g.display(verbose_stream() << "try-match\n");); return match_head(g); @@ -628,9 +628,9 @@ namespace tb { } verbose_stream() << mk_pp(q, m) << " = " << mk_pp(p, m) << "\n"; ); - - if (q->get_decl() == p->get_decl() && + + if (q->get_decl() == p->get_decl() && m_matcher(q, p, m_subst, m_sideconds) && match_predicates(predicate_index + 1, g)) { return true; @@ -646,7 +646,7 @@ namespace tb { expr_ref q(m), postcond(m); expr_ref_vector fmls(m_sideconds); m_subst.reset_cache(); - + for (unsigned i = 0; !m.canceled() && i < fmls.size(); ++i) { m_subst.apply(2, deltas, expr_offset(fmls[i].get(), 0), q); fmls[i] = q; @@ -680,7 +680,7 @@ namespace tb { verbose_stream() << "check: " << mk_pp(postcond, m, 7 + g.get_num_predicates()) << "\n";); if (!is_ground(postcond)) { - IF_VERBOSE(1, verbose_stream() << "TBD: non-ground\n" + IF_VERBOSE(1, verbose_stream() << "TBD: non-ground\n" << mk_pp(postcond, m) << "\n"; m_clause->display(verbose_stream()); verbose_stream() << "\n=>\n"; @@ -743,7 +743,7 @@ namespace tb { double m_weight_multiply; unsigned m_update_frequency; unsigned m_next_update; - + public: selection(datalog::context& ctx): @@ -766,7 +766,7 @@ namespace tb { scores.reset(); basic_score_predicate(p, scores); insert_score(p->get_decl(), scores); - } + } normalize_scores(rs); } @@ -783,7 +783,7 @@ namespace tb { default: return weight_select(g); - } + } } void reset() { @@ -867,8 +867,8 @@ namespace tb { } } IF_VERBOSE(1, verbose_stream() << "select:" << result << "\n";); - - return result; + + return result; } unsigned basic_weight_select(clause const& g) { @@ -957,7 +957,7 @@ namespace tb { } } - + double score_predicate(app* p) { double score = 1; if (find_score(p, score)) { @@ -1031,7 +1031,7 @@ namespace tb { } else { m_score_map.insert(f, scores); - } + } } }; @@ -1044,15 +1044,15 @@ namespace tb { expr_ref_vector m_sub1; expr_ref_vector m_sub2; public: - unifier(ast_manager& m): - m(m), + unifier(ast_manager& m): + m(m), m_unifier(m), m_S1(m), m_S2(m, false), m_rename(m), - m_sub1(m), + m_sub1(m), m_sub2(m) {} - + bool operator()(ref& tgt, unsigned idx, ref& src, bool compute_subst, ref& result) { return unify(*tgt, idx, *src, compute_subst, result); } @@ -1066,12 +1066,12 @@ namespace tb { } } - bool unify(clause const& tgt, unsigned idx, clause const& src, bool compute_subst, ref& result) { - qe_lite qe(m); + bool unify(clause const& tgt, unsigned idx, clause const& src, bool compute_subst, ref& result) { + qe_lite qe(m, params_ref()); reset(); SASSERT(tgt.get_predicate(idx)->get_decl() == src.get_decl()); unsigned var_cnt = std::max(tgt.get_num_vars(), src.get_num_vars()); - m_S1.reserve(2, var_cnt); + m_S1.reserve(2, var_cnt); if (!m_unifier(tgt.get_predicate(idx), src.get_head(), m_S1)) { return false; } @@ -1080,7 +1080,7 @@ namespace tb { app_ref head(m); result = alloc(clause, m); unsigned delta[2] = { 0, var_cnt }; - m_S1.apply(2, delta, expr_offset(tgt.get_head(), 0), tmp); + m_S1.apply(2, delta, expr_offset(tgt.get_head(), 0), tmp); head = to_app(tmp); for (unsigned i = 0; i < tgt.get_num_predicates(); ++i) { if (i != idx) { @@ -1096,7 +1096,7 @@ namespace tb { } m_S1.apply(2, delta, expr_offset(tgt.get_constraint(), 0), tmp); m_S1.apply(2, delta, expr_offset(src.get_constraint(), 1), tmp2); - constraint = m.mk_and(tmp, tmp2); + constraint = m.mk_and(tmp, tmp2); // perform trival quantifier-elimination: uint_set index_set; @@ -1114,7 +1114,7 @@ namespace tb { if (m.is_false(constraint)) { return false; } - + // initialize rule. result->init(head, predicates, constraint); ptr_vector vars; @@ -1147,10 +1147,10 @@ namespace tb { extract_subst(delta, src, 1); } // init result using head, predicates, constraint - return true; + return true; } - - + + private: void reset() { m_S1.reset(); @@ -1175,9 +1175,9 @@ namespace tb { else { insert_subst(offset, m.mk_true()); } - } + } } - + void insert_subst(unsigned offset, expr* e) { if (offset == 0) { m_sub1.push_back(e); @@ -1201,7 +1201,7 @@ namespace tb { // - // Given a clause + // Given a clause // P(s) :- P(t), Phi(x). // Compute the clauses: // acc: P(s) :- Delta(z,t), P(z), Phi(x). @@ -1237,7 +1237,7 @@ namespace tb { head = m.mk_app(delta, zszs.size(), zszs.c_ptr()); for (unsigned i = 0; i < zs.size(); ++i) { zszs[i+zs.size()] = q->get_arg(i); - } + } pred = m.mk_app(delta, zszs.size(), zszs.c_ptr()); preds.push_back(pred); for (unsigned i = 1; i < g.get_num_predicates(); ++i) { @@ -1247,28 +1247,28 @@ namespace tb { preds.push_back(m.mk_app(q->get_decl(), zs.size(), zs.c_ptr())); acc->init(p, preds, g.get_constraint()); - IF_VERBOSE(1, + IF_VERBOSE(1, delta1->display(verbose_stream() << "delta1:\n"); delta2->display(verbose_stream() << "delta2:\n"); acc->display(verbose_stream() << "acc:\n");); } - // + // // Given a sequence of clauses and inference rules // compute a super-predicate and auxiliary clauses. - // + // // P1(x) :- P2(y), R(z) // P2(y) :- P3(z), T(u) // P3(z) :- P1(x), U(v) // => // P1(x) :- P1(x), R(z), T(u), U(v) - // + // ref resolve_rules(unsigned num_clauses, clause*const* clauses, unsigned const* positions) { ref result = clauses[0]; ref tmp; unsigned offset = 0; - for (unsigned i = 0; i + 1 < num_clauses; ++i) { + for (unsigned i = 0; i + 1 < num_clauses; ++i) { clause const& cl = *clauses[i+1]; offset += positions[i]; VERIFY (m_unifier.unify(*result, offset, cl, false, tmp)); @@ -1276,7 +1276,7 @@ namespace tb { } return result; } - + private: @@ -1286,7 +1286,7 @@ namespace tb { unsigned num_vars = g.get_num_vars(); for (unsigned i = 0; i < p->get_num_args(); ++i) { result.push_back(m.mk_var(num_vars+i, m.get_sort(p->get_arg(i)))); - } + } return result; } }; @@ -1341,7 +1341,7 @@ namespace datalog { uint_set m_displayed_rules; public: imp(context& ctx): - m_ctx(ctx), + m_ctx(ctx), m(ctx.get_manager()), rm(ctx.get_rule_manager()), m_index(m), @@ -1358,7 +1358,7 @@ namespace datalog { m_fparams.m_timeout = 1000; } - ~imp() {} + ~imp() {} lbool query(expr* query) { m_ctx.ensure_opened(); @@ -1378,7 +1378,7 @@ namespace datalog { IF_VERBOSE(1, display_clause(*get_clause(), verbose_stream() << "g" << get_clause()->get_seqno() << " ");); return run(); } - + void cleanup() { m_clauses.reset(); } @@ -1400,7 +1400,7 @@ namespace datalog { expr_ref get_answer() const { switch(m_status) { - case l_undef: + case l_undef: UNREACHABLE(); return expr_ref(m.mk_false(), m); case l_true: { @@ -1415,7 +1415,7 @@ namespace datalog { return expr_ref(m.mk_true(), m); } private: - + void select_predicate() { tb::clause & g = *get_clause(); unsigned num_predicates = g.get_num_predicates(); @@ -1430,17 +1430,17 @@ namespace datalog { IF_VERBOSE(2, verbose_stream() << mk_pp(g.get_predicate(pi), m) << "\n";); } } - + void apply_rule(ref& r) { ref clause = get_clause(); - ref next_clause; + ref next_clause; if (m_unifier(clause, clause->get_predicate_index(), r, false, next_clause) && !query_is_tautology(*next_clause)) { init_clause(next_clause); unsigned subsumer = 0; - IF_VERBOSE(1, + IF_VERBOSE(1, display_rule(*clause, verbose_stream()); - display_premise(*clause, + display_premise(*clause, verbose_stream() << "g" << next_clause->get_seqno() << " "); display_clause(*next_clause, verbose_stream()); ); @@ -1462,8 +1462,8 @@ namespace datalog { m_instruction = tb::SELECT_RULE; } } - - void select_rule() { + + void select_rule() { tb::clause& g = *get_clause(); g.inc_next_rule(); unsigned pi = g.get_predicate_index(); @@ -1481,7 +1481,7 @@ namespace datalog { void backtrack() { SASSERT(!m_clauses.empty()); - m_clauses.pop_back(); + m_clauses.pop_back(); if (m_clauses.empty()) { m_instruction = tb::SATISFIABLE; } @@ -1500,16 +1500,16 @@ namespace datalog { return l_undef; } switch(m_instruction) { - case tb::SELECT_PREDICATE: - select_predicate(); + case tb::SELECT_PREDICATE: + select_predicate(); break; - case tb::SELECT_RULE: - select_rule(); + case tb::SELECT_RULE: + select_rule(); break; case tb::BACKTRACK: backtrack(); break; - case tb::SATISFIABLE: + case tb::SATISFIABLE: m_status = l_false; return l_false; case tb::UNSATISFIABLE: @@ -1522,18 +1522,18 @@ namespace datalog { return l_undef; } } - } + } bool query_is_tautology(tb::clause const& g) { expr_ref fml = g.to_formula(); fml = m.mk_not(fml); m_solver.push(); m_solver.assert_expr(fml); - lbool is_sat = m_solver.check(); + lbool is_sat = m_solver.check(); m_solver.pop(1); TRACE("dl", tout << is_sat << ":\n" << mk_pp(fml, m) << "\n";); - + return l_false == is_sat; } @@ -1560,7 +1560,7 @@ namespace datalog { void display_premise(tb::clause& p, std::ostream& out) { func_decl* f = p.get_predicate(p.get_predicate_index())->get_decl(); - out << "{g" << p.get_seqno() << " " << f->get_name() << " pos: " + out << "{g" << p.get_seqno() << " " << f->get_name() << " pos: " << p.get_predicate_index() << " rule: " << p.get_next_rule() << "}\n"; } @@ -1576,21 +1576,21 @@ namespace datalog { ref replayed_clause; replace_proof_converter pc(m); - // clause is a empty clause. + // clause is a empty clause. // Pretend it is asserted. // It gets replaced by premises. - SASSERT(clause->get_num_predicates() == 0); + SASSERT(clause->get_num_predicates() == 0); expr_ref root = clause->to_formula(); vector substs; - while (0 != clause->get_index()) { - SASSERT(clause->get_parent_index() < clause->get_index()); + while (0 != clause->get_index()) { + SASSERT(clause->get_parent_index() < clause->get_index()); unsigned p_index = clause->get_parent_index(); unsigned p_rule = clause->get_parent_rule(); ref parent = m_clauses[p_index]; unsigned pi = parent->get_predicate_index(); func_decl* pred = parent->get_predicate(pi)->get_decl(); - ref rl = m_rules.get_rule(pred, p_rule); + ref rl = m_rules.get_rule(pred, p_rule); VERIFY(m_unifier(parent, parent->get_predicate_index(), rl, true, replayed_clause)); expr_ref_vector s1(m_unifier.get_rule_subst(true)); expr_ref_vector s2(m_unifier.get_rule_subst(false)); @@ -1614,36 +1614,36 @@ namespace datalog { } expr_ref body = clause.get_body(); var_subst vs(m, false); - vs(body, subst.size(), subst.c_ptr(), body); + vs(body, subst.size(), subst.c_ptr(), body); out << mk_pp(body, m) << "\n"; } - void resolve_rule(replace_proof_converter& pc, tb::clause const& r1, tb::clause const& r2, + void resolve_rule(replace_proof_converter& pc, tb::clause const& r1, tb::clause const& r2, expr_ref_vector const& s1, expr_ref_vector const& s2, tb::clause const& res) const { unsigned idx = r1.get_predicate_index(); expr_ref fml = res.to_formula(); vector substs; svector > positions; substs.push_back(s1); - substs.push_back(s2); + substs.push_back(s2); scoped_proof _sc(m); proof_ref pr(m); proof_ref_vector premises(m); premises.push_back(m.mk_asserted(r1.to_formula())); premises.push_back(m.mk_asserted(r2.to_formula())); - positions.push_back(std::make_pair(idx+1, 0)); + positions.push_back(std::make_pair(idx+1, 0)); pr = m.mk_hyper_resolve(2, premises.c_ptr(), fml, positions, substs); pc.insert(pr); - } + } }; tab::tab(context& ctx): datalog::engine_base(ctx.get_manager(),"tabulation"), - m_imp(alloc(imp, ctx)) { + m_imp(alloc(imp, ctx)) { } tab::~tab() { dealloc(m_imp); - } + } lbool tab::query(expr* query) { return m_imp->query(query); } diff --git a/src/muz/transforms/dl_mk_array_blast.cpp b/src/muz/transforms/dl_mk_array_blast.cpp index 82d351113..031c5098e 100644 --- a/src/muz/transforms/dl_mk_array_blast.cpp +++ b/src/muz/transforms/dl_mk_array_blast.cpp @@ -126,6 +126,12 @@ namespace datalog { app* s; var* v; + // disable Ackerman reduction if head contains a non-variable or non-constant argument. + for (unsigned i = 0; i < to_app(head)->get_num_args(); ++i) { + expr* arg = to_app(head)->get_arg(i); + if (!is_var(arg) && !m.is_value(arg)) return false; + } + for (unsigned i = 0; i < conjs.size(); ++i) { expr* e = conjs[i].get(); if (is_select_eq_var(e, s, v)) { @@ -281,6 +287,7 @@ namespace datalog { m_rewriter(body); sub(head); m_rewriter(head); + TRACE("dl", tout << body << " => " << head << "\n";); change = ackermanize(r, body, head); if (!change) { rules.add_rule(&r); diff --git a/src/muz/transforms/dl_mk_interp_tail_simplifier.cpp b/src/muz/transforms/dl_mk_interp_tail_simplifier.cpp index ea0e6c887..455b06d3d 100644 --- a/src/muz/transforms/dl_mk_interp_tail_simplifier.cpp +++ b/src/muz/transforms/dl_mk_interp_tail_simplifier.cpp @@ -394,10 +394,6 @@ namespace datalog { m_simp(a, simp1_res); (*m_rw)(simp1_res.get(), res); - /*if (simp1_res.get()!=res.get()) { - std::cout<<"pre norm:\n"<= m_box_models.size()) { + throw default_exception("index into models is out of bounds"); + } + mdl = m_box_models[index]; + fix_model(mdl); + } + lbool context::execute_min_max(unsigned index, bool committed, bool scoped, bool is_max) { if (scoped) get_solver().push(); lbool result = m_optsmt.lex(index, is_max); @@ -1036,6 +1045,10 @@ namespace opt { TRACE("opt", tout << "Purifying " << term << "\n";); term = purify(fm, term); } + else if (m.is_ite(term)) { + TRACE("opt", tout << "Purifying " << term << "\n";); + term = purify(fm, term); + } if (fm) { m_model_converter = concat(m_model_converter.get(), fm.get()); } @@ -1226,7 +1239,7 @@ namespace opt { out << " ("; display_objective(out, obj); if (get_lower_as_num(i) != get_upper_as_num(i)) { - out << " (" << get_lower(i) << " " << get_upper(i) << ")"; + out << " (interval " << get_lower(i) << " " << get_upper(i) << ")"; } else { out << " " << get_lower(i); diff --git a/src/opt/opt_context.h b/src/opt/opt_context.h index f51f75830..53bfc19c5 100644 --- a/src/opt/opt_context.h +++ b/src/opt/opt_context.h @@ -186,6 +186,7 @@ namespace opt { virtual bool print_model() const; virtual void set_model(model_ref& _m) { m_model = _m; } virtual void get_model(model_ref& _m); + virtual void get_box_model(model_ref& _m, unsigned index); virtual void fix_model(model_ref& _m); virtual void collect_statistics(statistics& stats) const; virtual proof* get_proof() { return 0; } diff --git a/src/opt/opt_params.pyg b/src/opt/opt_params.pyg index a7c9e0011..51f58396c 100644 --- a/src/opt/opt_params.pyg +++ b/src/opt/opt_params.pyg @@ -5,6 +5,8 @@ def_module_params('opt', ('maxsat_engine', SYMBOL, 'maxres', "select engine for maxsat: 'core_maxsat', 'wmax', 'maxres', 'pd-maxres'"), ('priority', SYMBOL, 'lex', "select how to priortize objectives: 'lex' (lexicographic), 'pareto', or 'box'"), ('dump_benchmarks', BOOL, False, 'dump benchmarks for profiling'), + ('timeout', UINT, UINT_MAX, 'timeout (in milliseconds) (UINT_MAX and 0 mean no timeout)'), + ('rlimit', UINT, 0, 'resource limit (0 means no limit)'), ('print_model', BOOL, False, 'display model for satisfiable constraints'), ('enable_sls', BOOL, False, 'enable SLS tuning during weighted maxsast'), ('enable_sat', BOOL, True, 'enable the new SAT core for propositional constraints'), diff --git a/src/opt/opt_solver.cpp b/src/opt/opt_solver.cpp index 351141a3f..bc6462a18 100644 --- a/src/opt/opt_solver.cpp +++ b/src/opt/opt_solver.cpp @@ -50,6 +50,7 @@ namespace opt { if (m_params.m_case_split_strategy == CS_ACTIVITY_DELAY_NEW) { m_params.m_relevancy_lvl = 0; } + // m_params.m_auto_config = false; } unsigned opt_solver::m_dump_count = 0; @@ -357,6 +358,7 @@ namespace opt { } smt::theory_opt& opt = get_optimizer(); smt::theory_var v = m_objective_vars[var]; + TRACE("opt", tout << "v" << var << " " << val << "\n";); if (typeid(smt::theory_inf_arith) == typeid(opt)) { smt::theory_inf_arith& th = dynamic_cast(opt); @@ -386,8 +388,32 @@ namespace opt { smt::theory_rdl& th = dynamic_cast(opt); return th.mk_ge(m_fm, v, val); } + + if (typeid(smt::theory_dense_i) == typeid(opt) && + val.get_infinitesimal().is_zero()) { + smt::theory_dense_i& th = dynamic_cast(opt); + return th.mk_ge(m_fm, v, val); + } - // difference logic? + if (typeid(smt::theory_dense_mi) == typeid(opt) && + val.get_infinitesimal().is_zero()) { + smt::theory_dense_mi& th = dynamic_cast(opt); + return th.mk_ge(m_fm, v, val); + } + + if (typeid(smt::theory_dense_si) == typeid(opt) && + val.get_infinitesimal().is_zero()) { + smt::theory_dense_si& th = dynamic_cast(opt); + return th.mk_ge(m_fm, v, val); + } + + if (typeid(smt::theory_dense_smi) == typeid(opt) && + val.get_infinitesimal().is_zero()) { + smt::theory_dense_smi& th = dynamic_cast(opt); + return th.mk_ge(m_fm, v, val); + } + + IF_VERBOSE(0, verbose_stream() << "WARNING: unhandled theory " << typeid(opt).name() << "\n";); return expr_ref(m.mk_true(), m); } diff --git a/src/opt/wmax.cpp b/src/opt/wmax.cpp index 9708bdc8f..58d319a63 100644 --- a/src/opt/wmax.cpp +++ b/src/opt/wmax.cpp @@ -61,13 +61,12 @@ namespace opt { return is_sat; } m_upper = m_lower; - bool was_sat = false; expr_ref_vector asms(m); vector cores; obj_map::iterator it = soft.begin(), end = soft.end(); for (; it != end; ++it) { - expr* c = assert_weighted(wth(), it->m_key, it->m_value); + assert_weighted(wth(), it->m_key, it->m_value); if (!is_true(it->m_key)) { m_upper += it->m_value; } @@ -97,7 +96,6 @@ namespace opt { expr_ref fml = wth().mk_block(); //DEBUG_CODE(verify_cores(cores);); s().assert_expr(fml); - was_sat = true; } else { //DEBUG_CODE(verify_cores(cores);); diff --git a/src/parsers/smt2/smt2parser.cpp b/src/parsers/smt2/smt2parser.cpp index d2b55af1d..1486f6e6c 100644 --- a/src/parsers/smt2/smt2parser.cpp +++ b/src/parsers/smt2/smt2parser.cpp @@ -66,7 +66,7 @@ namespace smt2 { scoped_ptr m_bv_util; scoped_ptr m_arith_util; - scoped_ptr m_seq_util; + scoped_ptr m_seq_util; scoped_ptr m_pattern_validator; scoped_ptr m_var_shifter; @@ -2218,11 +2218,27 @@ namespace smt2 { if (m_cached_strings.empty()) throw cmd_exception("invalid get-value command, empty list of terms"); next(); + unsigned index = 0; + if (curr_is_keyword() && (curr_id() == ":model-index" || curr_id() == ":model_index")) { + next(); + check_int("integer index expected to indexed model evaluation"); + if (!curr_numeral().is_unsigned()) + throw parser_exception("expected unsigned integer index to model evaluation"); + index = curr_numeral().get_unsigned(); + next(); + } + check_rparen("invalid get-value command, ')' expected"); if (!m_ctx.is_model_available() || m_ctx.get_check_sat_result() == 0) throw cmd_exception("model is not available"); model_ref md; - m_ctx.get_check_sat_result()->get_model(md); + if (index == 0) { + m_ctx.get_check_sat_result()->get_model(md); + } + else { + m_ctx.get_opt()->get_box_model(md, index); + + } m_ctx.regular_stream() << "("; expr ** expr_it = expr_stack().c_ptr() + spos; expr ** expr_end = expr_it + m_cached_strings.size(); diff --git a/src/qe/qe_lite.cpp b/src/qe/qe_lite.cpp index 2b73381a9..eccc2d0c7 100644 --- a/src/qe/qe_lite.cpp +++ b/src/qe/qe_lite.cpp @@ -48,8 +48,8 @@ class is_variable_test : public is_variable_proc { is_var_kind m_var_kind; public: is_variable_test(uint_set const& vars, bool index_of_bound) : - m_var_set(vars), - m_num_decls(0), + m_var_set(vars), + m_num_decls(0), m_var_kind(index_of_bound?BY_VAR_SET:BY_VAR_SET_COMPLEMENT) {} is_variable_test(unsigned num_decls) : @@ -83,7 +83,7 @@ namespace eq { is_variable_proc* m_is_variable; var_subst m_subst; expr_ref_vector m_new_exprs; - + ptr_vector m_map; int_vector m_pos2var; ptr_vector m_inx2var; @@ -91,10 +91,11 @@ namespace eq { expr_ref_vector m_subst_map; expr_ref_buffer m_new_args; th_rewriter m_rewriter; - + params_ref m_params; + void der_sort_vars(ptr_vector & vars, ptr_vector & definitions, unsigned_vector & order) { order.reset(); - + // eliminate self loops, and definitions containing quantifiers. bool found = false; for (unsigned i = 0; i < definitions.size(); i++) { @@ -105,18 +106,18 @@ namespace eq { else found = true; // found at least one candidate } - + if (!found) return; - + typedef std::pair frame; svector todo; - + expr_fast_mark1 visiting; expr_fast_mark2 done; - + unsigned vidx, num; - + for (unsigned i = 0; i < definitions.size(); i++) { if (definitions[i] == 0) continue; @@ -193,11 +194,11 @@ namespace eq { } } } - + bool is_variable(expr * e) const { return (*m_is_variable)(e); } - + bool is_neg_var(ast_manager & m, expr * e, var*& v) { expr* e1; if (m.is_not(e, e1) && is_variable(e1)) { @@ -208,13 +209,13 @@ namespace eq { return false; } } - - + + /** - \brief Return true if e can be viewed as a variable disequality. + \brief Return true if e can be viewed as a variable disequality. Store the variable id in v and the definition in t. For example: - + if e is (not (= (VAR 1) T)), then v assigned to 1, and t to T. if e is (iff (VAR 2) T), then v is assigned to 2, and t to (not T). (not T) is used because this formula is equivalent to (not (iff (VAR 2) (not T))), @@ -225,7 +226,7 @@ namespace eq { if (m.is_not(e, e1)) { return is_var_eq(e, vs, ts); } - else if (is_var_eq(e, vs, ts) && vs.size() == 1 && m.is_bool(vs[0])) { + else if (is_var_eq(e, vs, ts) && vs.size() == 1 && m.is_bool(vs[0])) { expr_ref tmp(m); bool_rewriter(m).mk_not(ts[0].get(), tmp); ts[0] = tmp; @@ -305,7 +306,7 @@ namespace eq { todo.pop_back(); if (a.is_add(e)) { for (unsigned i = 0; i < to_app(e)->get_num_args(); ++i) { - todo.push_back(std::make_pair(sign, to_app(e)->get_arg(i))); + todo.push_back(std::make_pair(sign, to_app(e)->get_arg(i))); } } else if (is_invertible_mul(is_int, e, a_val)) { @@ -322,7 +323,7 @@ namespace eq { } return false; } - + bool arith_solve(expr * lhs, expr * rhs, expr * eq, ptr_vector& vs, expr_ref_vector& ts) { return solve_arith(lhs, rhs, vs, ts); } @@ -339,7 +340,7 @@ namespace eq { TRACE("qe_lite", tout << mk_pp(eq, m) << "\n";); return true; } - + bool same_vars(ptr_vector const& vs1, ptr_vector const& vs2) const { if (vs1.size() != vs2.size()) { @@ -356,12 +357,12 @@ namespace eq { /** \brief Return true if e can be viewed as a variable equality. */ - + bool is_var_eq(expr * e, ptr_vector& vs, expr_ref_vector & ts) { expr* lhs, *rhs; var* v; - - // (= VAR t), (iff VAR t), (iff (not VAR) t), (iff t (not VAR)) cases + + // (= VAR t), (iff VAR t), (iff (not VAR) t), (iff t (not VAR)) cases if (m.is_eq(e, lhs, rhs) || m.is_iff(e, lhs, rhs)) { // (iff (not VAR) t) (iff t (not VAR)) cases if (!is_variable(lhs) && !is_variable(rhs) && m.is_bool(lhs)) { @@ -384,7 +385,7 @@ namespace eq { } return false; } - + // (ite cond (= VAR t) (= VAR t2)) case expr* cond, *e2, *e3; if (m.is_ite(e, cond, e2, e3)) { @@ -400,7 +401,7 @@ namespace eq { } return false; } - + // VAR = true case if (is_variable(e)) { ts.push_back(m.mk_true()); @@ -408,7 +409,7 @@ namespace eq { TRACE("qe_lite", tout << mk_pp(e, m) << "\n";); return true; } - + // VAR = false case if (is_neg_var(m, e, v)) { ts.push_back(m.mk_false()); @@ -416,56 +417,56 @@ namespace eq { TRACE("qe_lite", tout << mk_pp(e, m) << "\n";); return true; } - + return false; } - - + + bool is_var_def(bool check_eq, expr* e, ptr_vector& vs, expr_ref_vector& ts) { if (check_eq) { return is_var_eq(e, vs, ts); } else { return is_var_diseq(e, vs, ts); - } + } } - + void get_elimination_order() { m_order.reset(); - + TRACE("top_sort", tout << "DEFINITIONS: " << std::endl; for(unsigned i = 0; i < m_map.size(); i++) if(m_map[i]) tout << "VAR " << i << " = " << mk_pp(m_map[i], m) << std::endl; ); - + der_sort_vars(m_inx2var, m_map, m_order); - - TRACE("qe_lite", + + TRACE("qe_lite", tout << "Elimination m_order:" << std::endl; for(unsigned i=0; iget_expr(); if ((q->is_forall() && m.is_or(e)) || @@ -474,15 +475,15 @@ namespace eq { args = to_app(e)->get_args(); } } - + void apply_substitution(quantifier * q, expr_ref & r) { - + expr * e = q->get_expr(); unsigned num_args = 1; expr* const* args = &e; flatten_args(q, num_args, args); bool_rewriter rw(m); - + // get a new expression m_new_args.reset(); for(unsigned i = 0; i < num_args; i++) { @@ -495,7 +496,7 @@ namespace eq { r = q; return; } - + expr_ref t(m); if (q->is_forall()) { rw.mk_or(m_new_args.size(), m_new_args.c_ptr(), t); @@ -503,9 +504,9 @@ namespace eq { else { rw.mk_and(m_new_args.size(), m_new_args.c_ptr(), t); } - expr_ref new_e(m); + expr_ref new_e(m); m_subst(t, m_subst_map.size(), m_subst_map.c_ptr(), new_e); - + // don't forget to update the quantifier patterns expr_ref_buffer new_patterns(m); expr_ref_buffer new_no_patterns(m); @@ -514,17 +515,17 @@ namespace eq { m_subst(q->get_pattern(j), m_subst_map.size(), m_subst_map.c_ptr(), new_pat); new_patterns.push_back(new_pat); } - + for (unsigned j = 0; j < q->get_num_no_patterns(); j++) { expr_ref new_nopat(m); m_subst(q->get_no_pattern(j), m_subst_map.size(), m_subst_map.c_ptr(), new_nopat); new_no_patterns.push_back(new_nopat); } - - r = m.update_quantifier(q, new_patterns.size(), new_patterns.c_ptr(), + + r = m.update_quantifier(q, new_patterns.size(), new_patterns.c_ptr(), new_no_patterns.size(), new_no_patterns.c_ptr(), new_e); } - + void reduce_quantifier1(quantifier * q, expr_ref & r, proof_ref & pr) { expr * e = q->get_expr(); is_variable_test is_v(q->get_num_decls()); @@ -532,17 +533,17 @@ namespace eq { unsigned num_args = 1; expr* const* args = &e; flatten_args(q, num_args, args); - + unsigned def_count = 0; unsigned largest_vinx = 0; - + find_definitions(num_args, args, q->is_exists(), def_count, largest_vinx); - + if (def_count > 0) { get_elimination_order(); SASSERT(m_order.size() <= def_count); // some might be missing because of cycles - - if (!m_order.empty()) { + + if (!m_order.empty()) { create_substitution(largest_vinx + 1); apply_substitution(q, r); } @@ -554,31 +555,32 @@ namespace eq { TRACE("der_bug", tout << "Did not find any diseq\n" << mk_pp(q, m) << "\n";); r = q; } - + if (m.proofs_enabled()) { pr = r == q ? 0 : m.mk_der(q, r); - } - } - + } + } + void elim_unused_vars(expr_ref& r, proof_ref &pr) { if (is_quantifier(r)) { quantifier * q = to_quantifier(r); - ::elim_unused_vars(m, q, r); + + ::elim_unused_vars(m, q, m_params, r); if (m.proofs_enabled()) { proof * p1 = m.mk_elim_unused_vars(q, r); pr = m.mk_transitivity(pr, p1); } } } - + void find_definitions(unsigned num_args, expr* const* args, bool is_exists, unsigned& def_count, unsigned& largest_vinx) { def_count = 0; largest_vinx = 0; m_map.reset(); m_pos2var.reset(); - m_inx2var.reset(); + m_inx2var.reset(); m_pos2var.reserve(num_args, -1); - + // Find all definitions for (unsigned i = 0; i < num_args; i++) { checkpoint(); @@ -591,12 +593,12 @@ namespace eq { unsigned idx = v->get_idx(); if (m_map.get(idx, 0) == 0) { m_map.reserve(idx + 1, 0); - m_inx2var.reserve(idx + 1, 0); + m_inx2var.reserve(idx + 1, 0); m_map[idx] = t; m_inx2var[idx] = v; m_pos2var[i] = idx; def_count++; - largest_vinx = std::max(idx, largest_vinx); + largest_vinx = std::max(idx, largest_vinx); m_new_exprs.push_back(t); } } @@ -646,10 +648,10 @@ namespace eq { tmp = m.mk_and(conjs.size(), conjs.c_ptr()); tout << "after flatten\n" << mk_pp(tmp, m) << "\n";); } - + void flatten_constructor(app* c, app* r, expr_ref_vector& conjs) { SASSERT(dt.is_constructor(c)); - + func_decl* d = c->get_decl(); if (dt.is_constructor(r->get_decl())) { @@ -661,7 +663,7 @@ namespace eq { } else { conjs.push_back(m.mk_false()); - } + } } else { func_decl* rec = dt.get_constructor_recognizer(d); @@ -683,7 +685,7 @@ namespace eq { bool remove_unconstrained(expr_ref_vector& conjs) { bool reduced = false, change = true; - expr* r, *l, *ne; + expr* r, *l, *ne; while (change) { change = false; for (unsigned i = 0; i < conjs.size(); ++i) { @@ -704,21 +706,21 @@ namespace eq { } return reduced; } - + bool reduce_var_set(expr_ref_vector& conjs) { unsigned def_count = 0; unsigned largest_vinx = 0; bool reduced = false; flatten_definitions(conjs); - + find_definitions(conjs.size(), conjs.c_ptr(), true, def_count, largest_vinx); - + if (def_count > 0) { get_elimination_order(); SASSERT(m_order.size() <= def_count); // some might be missing because of cycles - - if (!m_order.empty()) { + + if (!m_order.empty()) { expr_ref r(m), new_r(m); r = m.mk_and(conjs.size(), conjs.c_ptr()); create_substitution(largest_vinx + 1); @@ -739,35 +741,36 @@ namespace eq { void checkpoint() { cooperate("der"); - if (m.canceled()) + if (m.canceled()) throw tactic_exception(m.limit().get_cancel_msg()); } public: - der(ast_manager & m): - m(m), + der(ast_manager & m, params_ref const & p): + m(m), a(m), dt(m), - m_is_variable(0), - m_subst(m), - m_new_exprs(m), - m_subst_map(m), - m_new_args(m), - m_rewriter(m) {} - + m_is_variable(0), + m_subst(m), + m_new_exprs(m), + m_subst_map(m), + m_new_args(m), + m_rewriter(m), + m_params(p) {} + void set_is_variable_proc(is_variable_proc& proc) { m_is_variable = &proc;} - + void operator()(quantifier * q, expr_ref & r, proof_ref & pr) { - TRACE("qe_lite", tout << mk_pp(q, m) << "\n";); + TRACE("qe_lite", tout << mk_pp(q, m) << "\n";); pr = 0; r = q; - reduce_quantifier(q, r, pr); + reduce_quantifier(q, r, pr); if (r != q) { elim_unused_vars(r, pr); } } - - void reduce_quantifier(quantifier * q, expr_ref & r, proof_ref & pr) { + + void reduce_quantifier(quantifier * q, expr_ref & r, proof_ref & pr) { r = q; // Keep applying reduce_quantifier1 until r doesn't change anymore do { @@ -779,15 +782,15 @@ namespace eq { pr = m.mk_transitivity(pr, curr_pr); } } while (q != r && is_quantifier(r)); - + m_new_exprs.reset(); } - + void operator()(expr_ref_vector& r) { while (reduce_var_set(r)) ; m_new_exprs.reset(); } - + ast_manager& get_manager() const { return m; } @@ -804,7 +807,7 @@ namespace ar { is_variable_proc* m_is_variable; ptr_vector m_todo; expr_mark m_visited; - + bool is_variable(expr * e) const { return (*m_is_variable)(e); } @@ -827,7 +830,7 @@ namespace ar { Ex A. Phi[store(A,x,t)] Perhaps also: - Ex A. store(A,y,z)[x] = t & Phi where x \not\in A, t, y, z, A \not\in y z, t + Ex A. store(A,y,z)[x] = t & Phi where x \not\in A, t, y, z, A \not\in y z, t => Ex A, v . (x = y => z = t) & Phi[store(store(A,x,t),y,v)] @@ -873,7 +876,7 @@ namespace ar { bool solve_select(expr_ref_vector& conjs, unsigned i, expr* e) { expr* e1, *e2; - return + return m.is_eq(e, e1, e2) && (solve_select(conjs, i, e1, e2) || solve_select(conjs, i, e2, e1)); @@ -887,8 +890,8 @@ namespace ar { bool solve_neq_select(expr_ref_vector& conjs, unsigned i, expr* e) { expr* e1, *a1, *a2; if (m.is_not(e, e1) && m.is_eq(e1, a1, a2)) { - if (a.is_select(a1) && - a.is_select(a2) && + if (a.is_select(a1) && + a.is_select(a2) && to_app(a1)->get_num_args() == to_app(a2)->get_num_args()) { expr* e1 = to_app(a1)->get_arg(0); expr* e2 = to_app(a2)->get_arg(0); @@ -937,7 +940,7 @@ namespace ar { void operator()(expr* e) {} void set_is_variable_proc(is_variable_proc& proc) { m_is_variable = &proc;} - + }; }; // namespace ar @@ -976,27 +979,27 @@ namespace fm { for (; it != end; ++it) it->~rational(); } - + unsigned hash() const { return hash_u(m_id); } }; - + typedef ptr_vector constraints; - + class constraint_set { - unsigned_vector m_id2pos; + unsigned_vector m_id2pos; constraints m_set; public: typedef constraints::const_iterator iterator; - - bool contains(constraint const & c) const { - if (c.m_id >= m_id2pos.size()) - return false; - return m_id2pos[c.m_id] != UINT_MAX; + + bool contains(constraint const & c) const { + if (c.m_id >= m_id2pos.size()) + return false; + return m_id2pos[c.m_id] != UINT_MAX; } - + bool empty() const { return m_set.empty(); } unsigned size() const { return m_set.size(); } - + void insert(constraint & c) { unsigned id = c.m_id; m_id2pos.reserve(id+1, UINT_MAX); @@ -1006,7 +1009,7 @@ namespace fm { m_id2pos[id] = pos; m_set.push_back(&c); } - + void erase(constraint & c) { unsigned id = c.m_id; if (id >= m_id2pos.size()) @@ -1018,27 +1021,27 @@ namespace fm { unsigned last_pos = m_set.size() - 1; if (pos != last_pos) { constraint * last_c = m_set[last_pos]; - m_set[pos] = last_c; + m_set[pos] = last_c; m_id2pos[last_c->m_id] = pos; } m_set.pop_back(); } - + constraint & erase() { SASSERT(!empty()); - constraint & c = *m_set.back(); + constraint & c = *m_set.back(); m_id2pos[c.m_id] = UINT_MAX; m_set.pop_back(); return c; } - + void reset() { m_id2pos.reset(); m_set.reset(); } void finalize() { m_id2pos.finalize(); m_set.finalize(); } - + iterator begin() const { return m_set.begin(); } iterator end() const { return m_set.end(); } }; - + class fm { ast_manager & m; is_variable_proc* m_is_variable; @@ -1068,24 +1071,24 @@ namespace fm { bool m_inconsistent; expr_dependency_ref m_inconsistent_core; constraint_set m_sub_todo; - + // --------------------------- // // OCC clause recognizer // // --------------------------- - + bool is_literal(expr * t) const { expr * atom; return is_uninterp_const(t) || (m.is_not(t, atom) && is_uninterp_const(atom)); } - + bool is_constraint(expr * t) const { return !is_literal(t); } - + bool is_var(expr * t, expr * & x) const { - + if ((*m_is_variable)(t)) { x = t; return true; @@ -1096,24 +1099,24 @@ namespace fm { } return false; } - + bool is_var(expr * t) const { expr * x; return is_var(t, x); } - + bool is_linear_mon_core(expr * t, expr * & x) const { expr * c; if (m_util.is_mul(t, c, x) && m_util.is_numeral(c) && is_var(x, x)) return true; return is_var(t, x); } - + bool is_linear_mon(expr * t) const { expr * x; return is_linear_mon_core(t, x); } - + bool is_linear_pol(expr * t) const { unsigned num_mons; expr * const * mons; @@ -1125,7 +1128,7 @@ namespace fm { num_mons = 1; mons = &t; } - + expr_fast_mark2 visited; bool all_forbidden = true; for (unsigned i = 0; i < num_mons; i++) { @@ -1141,7 +1144,7 @@ namespace fm { } return !all_forbidden; } - + bool is_linear_ineq(expr * t) const { bool result = false; m.is_not(t, t); @@ -1153,7 +1156,7 @@ namespace fm { return result; } - + bool is_occ(expr * t) { if (m_fm_occ && m.is_or(t)) { unsigned num = to_app(t)->get_num_args(); @@ -1176,7 +1179,7 @@ namespace fm { } return is_linear_ineq(t); } - + // --------------------------- // // Memory mng @@ -1195,12 +1198,12 @@ namespace fm { for (unsigned i = 0; i < sz; i++) del_constraint(cs[i]); } - + void reset_constraints() { del_constraints(m_constraints.size(), m_constraints.c_ptr()); m_constraints.reset(); } - + constraint * mk_constraint(unsigned num_lits, literal * lits, unsigned num_vars, var * xs, rational * as, rational & c, bool strict, expr_dependency * dep) { unsigned sz = constraint::get_obj_size(num_lits, num_vars); @@ -1236,15 +1239,15 @@ namespace fm { m.inc_ref(dep); return cnstr; } - + // --------------------------- // // Util // // --------------------------- - + unsigned num_vars() const { return m_is_int.size(); } - + // multiply as and c, by the lcm of their denominators void mk_int(unsigned num, rational * as, rational & c) { rational l = denominator(c); @@ -1259,7 +1262,7 @@ namespace fm { SASSERT(as[i].is_int()); } } - + void normalize_coeffs(constraint & c) { if (c.m_num_vars == 0) return; @@ -1281,7 +1284,7 @@ namespace fm { for (unsigned i = 0; i < c.m_num_vars; i++) c.m_as[i] /= g; } - + void display(std::ostream & out, constraint const & c) const { for (unsigned i = 0; i < c.m_num_lits; i++) { literal l = c.m_lits[i]; @@ -1308,10 +1311,10 @@ namespace fm { out << c.m_c; out << ")"; } - + /** \brief Return true if c1 subsumes c2 - + c1 subsumes c2 If 1) All literals of c1 are literals of c2 2) polynomial of c1 == polynomial of c2 @@ -1329,13 +1332,13 @@ namespace fm { return false; if (!c1.m_strict && c2.m_strict && c1.m_c == c2.m_c) return false; - + m_counter += c1.m_num_lits + c2.m_num_lits; - + for (unsigned i = 0; i < c1.m_num_vars; i++) { m_var2pos[c1.m_xs[i]] = i; } - + bool failed = false; for (unsigned i = 0; i < c2.m_num_vars; i++) { unsigned pos1 = m_var2pos[c2.m_xs[i]]; @@ -1344,21 +1347,21 @@ namespace fm { break; } } - + for (unsigned i = 0; i < c1.m_num_vars; i++) { m_var2pos[c1.m_xs[i]] = UINT_MAX; } - + if (failed) return false; - + for (unsigned i = 0; i < c2.m_num_lits; i++) { literal l = c2.m_lits[i]; bvar b = lit2bvar(l); SASSERT(m_bvar2sign[b] == 0); m_bvar2sign[b] = sign(l) ? -1 : 1; } - + for (unsigned i = 0; i < c1.m_num_lits; i++) { literal l = c1.m_lits[i]; bvar b = lit2bvar(l); @@ -1368,19 +1371,19 @@ namespace fm { break; } } - + for (unsigned i = 0; i < c2.m_num_lits; i++) { literal l = c2.m_lits[i]; bvar b = lit2bvar(l); m_bvar2sign[b] = 0; } - + if (failed) return false; - + return true; } - + void backward_subsumption(constraint const & c) { if (c.m_num_vars == 0) return; @@ -1422,7 +1425,7 @@ namespace fm { } cs.set_end(it2); } - + void subsume() { while (!m_sub_todo.empty()) { constraint & c = m_sub_todo.erase(); @@ -1433,13 +1436,13 @@ namespace fm { } public: - + // --------------------------- // // Initialization // // --------------------------- - + fm(ast_manager & _m): m(_m), m_is_variable(0), @@ -1453,11 +1456,11 @@ namespace fm { m_counter = 0; m_inconsistent = false; } - + ~fm() { reset_constraints(); } - + void updt_params() { m_fm_real_only = false; m_fm_limit = 5000000; @@ -1466,9 +1469,9 @@ namespace fm { m_fm_extra = 0; m_fm_occ = true; } - + private: - + struct forbidden_proc { fm & m_owner; forbidden_proc(fm & o):m_owner(o) {} @@ -1480,7 +1483,7 @@ namespace fm { void operator()(app * n) { } void operator()(quantifier * n) {} }; - + void init_forbidden_set(expr_ref_vector const & g) { m_forbidden_set.reset(); expr_fast_mark1 visited; @@ -1494,7 +1497,7 @@ namespace fm { quick_for_each_expr(proc, visited, f); } } - + void init(expr_ref_vector const & g) { m_sub_todo.reset(); m_id_gen.reset(); @@ -1517,24 +1520,24 @@ namespace fm { m_inconsistent_core = 0; init_forbidden_set(g); } - + // --------------------------- // // Internal data-structures // // --------------------------- - + static bool sign(literal l) { return l < 0; } static bvar lit2bvar(literal l) { return l < 0 ? -l : l; } - - bool is_int(var x) const { + + bool is_int(var x) const { return m_is_int[x] != 0; } - + bool is_forbidden(var x) const { return m_forbidden[x] != 0; } - + bool all_int(constraint const & c) const { for (unsigned i = 0; i < c.m_num_vars; i++) { if (!is_int(c.m_xs[i])) @@ -1542,7 +1545,7 @@ namespace fm { } return true; } - + app * to_expr(constraint const & c) { expr * ineq; if (c.m_num_vars == 0) { @@ -1577,20 +1580,20 @@ namespace fm { ineq = m_util.mk_le(lhs, rhs); } } - + if (c.m_num_lits == 0) { if (ineq) return to_app(ineq); else return m.mk_false(); } - + ptr_buffer lits; for (unsigned i = 0; i < c.m_num_lits; i++) { literal l = c.m_lits[i]; if (sign(l)) lits.push_back(m.mk_not(m_bvar2expr.get(lit2bvar(l)))); - else + else lits.push_back(m_bvar2expr.get(lit2bvar(l))); } if (ineq) @@ -1600,7 +1603,7 @@ namespace fm { else return m.mk_or(lits.size(), lits.c_ptr()); } - + var mk_var(expr * t) { SASSERT(::is_var(t)); SASSERT(m_util.is_int(t) || m_util.is_real(t)); @@ -1617,12 +1620,12 @@ namespace fm { SASSERT(m_var2expr.size() == m_is_int.size()); SASSERT(m_lowers.size() == m_is_int.size()); SASSERT(m_uppers.size() == m_is_int.size()); - SASSERT(m_forbidden.size() == m_is_int.size()); + SASSERT(m_forbidden.size() == m_is_int.size()); SASSERT(m_var2pos.size() == m_is_int.size()); TRACE("qe_lite", tout << mk_pp(t,m) << " |-> " << x << " forbidden: " << forbidden << "\n";); return x; } - + bvar mk_bvar(expr * t) { SASSERT(is_uninterp_const(t)); SASSERT(m.is_bool(t)); @@ -1634,7 +1637,7 @@ namespace fm { SASSERT(p > 0); return p; } - + var to_var(expr * t) { var x; if (!m_expr2var.find(t, x)) @@ -1644,22 +1647,22 @@ namespace fm { TRACE("qe_lite", tout << mk_ismt2_pp(t, m) << " --> " << x << "\n";); return x; } - + bvar to_bvar(expr * t) { bvar p; if (m_expr2bvar.find(t, p)) return p; return mk_bvar(t); } - + literal to_literal(expr * t) { if (m.is_not(t, t)) - return -to_bvar(t); + return -to_bvar(t); else return to_bvar(t); } - - + + void add_constraint(expr * f, expr_dependency * dep) { TRACE("qe_lite", tout << mk_pp(f, m) << "\n";); SASSERT(!m.is_or(f) || m_fm_occ); @@ -1711,7 +1714,7 @@ namespace fm { num_mons = 1; mons = &lhs; } - + bool all_int = true; for (unsigned j = 0; j < num_mons; j++) { expr * monomial = mons[j]; @@ -1740,9 +1743,9 @@ namespace fm { } } } - + TRACE("qe_lite", tout << "before mk_constraint: "; for (unsigned i = 0; i < xs.size(); i++) tout << " " << xs[i]; tout << "\n";); - + constraint * new_c = mk_constraint(lits.size(), lits.c_ptr(), xs.size(), @@ -1751,15 +1754,15 @@ namespace fm { c, strict, dep); - + TRACE("qe_lite", tout << "add_constraint: "; display(tout, *new_c); tout << "\n";); VERIFY(register_constraint(new_c)); } - + bool is_false(constraint const & c) const { return c.m_num_lits == 0 && c.m_num_vars == 0 && (c.m_c.is_neg() || (c.m_strict && c.m_c.is_zero())); } - + bool register_constraint(constraint * c) { normalize_coeffs(*c); if (is_false(*c)) { @@ -1768,20 +1771,20 @@ namespace fm { TRACE("qe_lite", tout << "is false "; display(tout, *c); tout << "\n";); return false; } - + bool r = false; - + for (unsigned i = 0; i < c->m_num_vars; i++) { var x = c->m_xs[i]; if (!is_forbidden(x)) { r = true; - if (c->m_as[i].is_neg()) + if (c->m_as[i].is_neg()) m_lowers[x].push_back(c); else m_uppers[x].push_back(c); } } - + if (r) { m_sub_todo.insert(*c); m_constraints.push_back(c); @@ -1794,7 +1797,7 @@ namespace fm { return false; } } - + void init_use_list(expr_ref_vector const & g) { unsigned sz = g.size(); for (unsigned i = 0; !m_inconsistent && i < sz; i++) { @@ -1812,13 +1815,13 @@ namespace fm { return UINT_MAX; return static_cast(r); } - + typedef std::pair x_cost; - + struct x_cost_lt { char_vector const m_is_int; x_cost_lt(char_vector & is_int):m_is_int(is_int) {} - bool operator()(x_cost const & p1, x_cost const & p2) const { + bool operator()(x_cost const & p1, x_cost const & p2) const { // Integer variables with cost 0 can be eliminated even if they depend on real variables. // Cost 0 == no lower or no upper bound. if (p1.second == 0) { @@ -1828,7 +1831,7 @@ namespace fm { if (p2.second == 0) return false; bool int1 = m_is_int[p1.first] != 0; bool int2 = m_is_int[p2.first] != 0; - return (!int1 && int2) || (int1 == int2 && p1.second < p2.second); + return (!int1 && int2) || (int1 == int2 && p1.second < p2.second); } }; @@ -1842,7 +1845,7 @@ namespace fm { } // x_cost_lt is not a total order on variables std::stable_sort(x_cost_vector.begin(), x_cost_vector.end(), x_cost_lt(m_is_int)); - TRACE("qe_lite", + TRACE("qe_lite", svector::iterator it2 = x_cost_vector.begin(); svector::iterator end2 = x_cost_vector.end(); for (; it2 != end2; ++it2) { @@ -1855,7 +1858,7 @@ namespace fm { xs.push_back(it2->first); } } - + void cleanup_constraints(constraints & cs) { unsigned j = 0; unsigned sz = cs.size(); @@ -1868,7 +1871,7 @@ namespace fm { } cs.shrink(j); } - + // Set all_int = true if all variables in c are int. // Set unit_coeff = true if the coefficient of x in c is 1 or -1. // If all_int = false, then unit_coeff may not be set. @@ -1900,8 +1903,8 @@ namespace fm { unit_coeff = false; } } - - // An integer variable x may be eliminated, if + + // An integer variable x may be eliminated, if // 1- All variables in the contraints it occur are integer. // 2- The coefficient of x in all lower bounds (or all upper bounds) is unit. bool can_eliminate(var x) const { @@ -1915,7 +1918,7 @@ namespace fm { analyze(m_uppers[x], x, all_int, u_unit); return all_int && (l_unit || u_unit); } - + void copy_constraints(constraints const & s, clauses & t) { constraints::const_iterator it = s.begin(); constraints::const_iterator end = s.end(); @@ -1924,23 +1927,23 @@ namespace fm { t.push_back(c); } } - + clauses tmp_clauses; void save_constraints(var x) { } - + void mark_constraints_dead(constraints const & cs) { constraints::const_iterator it = cs.begin(); constraints::const_iterator end = cs.end(); for (; it != end; ++it) (*it)->m_dead = true; } - + void mark_constraints_dead(var x) { save_constraints(x); mark_constraints_dead(m_lowers[x]); mark_constraints_dead(m_uppers[x]); } - + void get_coeff(constraint const & c, var x, rational & a) { for (unsigned i = 0; i < c.m_num_vars; i++) { if (c.m_xs[i] == x) { @@ -1950,11 +1953,11 @@ namespace fm { } UNREACHABLE(); } - + var_vector new_xs; vector new_as; svector new_lits; - + constraint * resolve(constraint const & l, constraint const & u, var x) { m_counter += l.m_num_vars + u.m_num_vars + l.m_num_lits + u.m_num_lits; rational a, b; @@ -1963,14 +1966,14 @@ namespace fm { SASSERT(a.is_neg()); SASSERT(b.is_pos()); a.neg(); - + SASSERT(!is_int(x) || a.is_one() || b.is_one()); - + new_xs.reset(); new_as.reset(); rational new_c = l.m_c*b + u.m_c*a; bool new_strict = l.m_strict || u.m_strict; - + for (unsigned i = 0; i < l.m_num_vars; i++) { var xi = l.m_xs[i]; if (xi == x) @@ -1983,7 +1986,7 @@ namespace fm { SASSERT(new_xs[m_var2pos[xi]] == xi); SASSERT(new_xs.size() == new_as.size()); } - + for (unsigned i = 0; i < u.m_num_vars; i++) { var xi = u.m_xs[i]; if (xi == x) @@ -1997,7 +2000,7 @@ namespace fm { new_as[pos] += u.m_as[i] * a; } } - + // remove zeros and check whether all variables are int bool all_int = true; unsigned sz = new_xs.size(); @@ -2015,17 +2018,17 @@ namespace fm { } new_xs.shrink(j); new_as.shrink(j); - + if (all_int && new_strict) { new_strict = false; new_c --; } - + // reset m_var2pos for (unsigned i = 0; i < l.m_num_vars; i++) { m_var2pos[l.m_xs[i]] = UINT_MAX; } - + if (new_xs.empty() && (new_c.is_pos() || (!new_strict && new_c.is_zero()))) { // literal is true TRACE("qe_lite", tout << "resolution " << x << " consequent literal is always true: \n"; @@ -2034,7 +2037,7 @@ namespace fm { display(tout, u); tout << "\n";); return 0; // no constraint needs to be created. } - + new_lits.reset(); for (unsigned i = 0; i < l.m_num_lits; i++) { literal lit = l.m_lits[i]; @@ -2042,7 +2045,7 @@ namespace fm { m_bvar2sign[p] = sign(lit) ? -1 : 1; new_lits.push_back(lit); } - + bool tautology = false; for (unsigned i = 0; i < u.m_num_lits && !tautology; i++) { literal lit = u.m_lits[i]; @@ -2063,14 +2066,14 @@ namespace fm { UNREACHABLE(); } } - + // reset m_bvar2sign for (unsigned i = 0; i < l.m_num_lits; i++) { literal lit = l.m_lits[i]; bvar p = lit2bvar(lit); m_bvar2sign[p] = 0; } - + if (tautology) { TRACE("qe_lite", tout << "resolution " << x << " tautology: \n"; display(tout, l); @@ -2080,7 +2083,7 @@ namespace fm { } expr_dependency * new_dep = m.mk_join(l.m_dep, u.m_dep); - + if (new_lits.empty() && new_xs.empty() && (new_c.is_neg() || (new_strict && new_c.is_zero()))) { TRACE("qe_lite", tout << "resolution " << x << " inconsistent: \n"; display(tout, l); @@ -2090,7 +2093,7 @@ namespace fm { m_inconsistent_core = new_dep; return 0; } - + constraint * new_cnstr = mk_constraint(new_lits.size(), new_lits.c_ptr(), new_xs.size(), @@ -2105,45 +2108,45 @@ namespace fm { tout << "\n"; display(tout, u); tout << "\n---->\n"; - display(tout, *new_cnstr); + display(tout, *new_cnstr); tout << "\n"; tout << "new_dep: " << new_dep << "\n";); - + return new_cnstr; } - + ptr_vector new_constraints; - + bool try_eliminate(var x) { constraints & l = m_lowers[x]; constraints & u = m_uppers[x]; cleanup_constraints(l); cleanup_constraints(u); - + if (l.empty() || u.empty()) { // easy case mark_constraints_dead(x); TRACE("qe_lite", tout << "variable was eliminated (trivial case)\n";); return true; } - + unsigned num_lowers = l.size(); unsigned num_uppers = u.size(); - + if (num_lowers > m_fm_cutoff1 && num_uppers > m_fm_cutoff1) return false; - + if (num_lowers * num_uppers > m_fm_cutoff2) return false; - + if (!can_eliminate(x)) return false; - + m_counter += num_lowers * num_uppers; - + TRACE("qe_lite", tout << "eliminating " << mk_ismt2_pp(m_var2expr.get(x), m) << "\nlowers:\n"; display_constraints(tout, l); tout << "uppers:\n"; display_constraints(tout, u);); - + unsigned num_old_cnstrs = num_uppers + num_lowers; unsigned limit = num_old_cnstrs + m_fm_extra; unsigned num_new_cnstrs = 0; @@ -2164,13 +2167,13 @@ namespace fm { } } } - + mark_constraints_dead(x); - + unsigned sz = new_constraints.size(); - + m_counter += sz; - + for (unsigned i = 0; i < sz; i++) { constraint * c = new_constraints[i]; backward_subsumption(*c); @@ -2179,7 +2182,7 @@ namespace fm { TRACE("qe_lite", tout << "variables was eliminated old: " << num_old_cnstrs << " new_constraints: " << sz << "\n";); return true; } - + void copy_remaining(vector & v2cs) { vector::iterator it = v2cs.begin(); vector::iterator end = v2cs.end(); @@ -2199,13 +2202,13 @@ namespace fm { } v2cs.finalize(); } - + // Copy remaining clauses to m_new_fmls void copy_remaining() { copy_remaining(m_uppers); copy_remaining(m_lowers); } - + void checkpoint() { cooperate("fm"); if (m.canceled()) @@ -2224,12 +2227,12 @@ namespace fm { } else { TRACE("qe_lite", display(tout);); - + subsume(); var_vector candidates; - sort_candidates(candidates); - unsigned eliminated = 0; - + sort_candidates(candidates); + unsigned eliminated = 0; + unsigned num = candidates.size(); for (unsigned i = 0; i < num; i++) { checkpoint(); @@ -2251,8 +2254,8 @@ namespace fm { reset_constraints(); fmls.reset(); fmls.append(m_new_fmls); - } - + } + void display_constraints(std::ostream & out, constraints const & cs) const { constraints::const_iterator it = cs.begin(); constraints::const_iterator end = cs.end(); @@ -2262,7 +2265,7 @@ namespace fm { out << "\n"; } } - + void display(std::ostream & out) const { unsigned num = num_vars(); for (var x = 0; x < num; x++) { @@ -2284,10 +2287,10 @@ public: ast_manager& m; public: elim_cfg(impl& i): m_imp(i), m(i.m) {} - - bool reduce_quantifier(quantifier * q, - expr * new_body, - expr * const * new_patterns, + + bool reduce_quantifier(quantifier * q, + expr * new_body, + expr * const * new_patterns, expr * const * new_no_patterns, expr_ref & result, proof_ref & result_pr) { @@ -2299,13 +2302,13 @@ public: for (unsigned i = 0; i < q->get_num_decls(); ++i) { indices.insert(i); } - m_imp(indices, true, result); + m_imp(indices, true, result); if (is_forall(q)) { result = push_not(result); } result = m.update_quantifier( - q, - q->get_num_patterns(), new_patterns, + q, + q->get_num_patterns(), new_patterns, q->get_num_no_patterns(), new_no_patterns, result); m_imp.m_rewriter(result); return true; @@ -2315,7 +2318,7 @@ public: class elim_star : public rewriter_tpl { elim_cfg m_cfg; public: - elim_star(impl& i): + elim_star(impl& i): rewriter_tpl(i.m, false, m_cfg), m_cfg(i) {} @@ -2346,21 +2349,21 @@ private: } public: - impl(ast_manager& m): - m(m), - m_der(m), - m_fm(m), - m_array_der(m), - m_elim_star(*this), + impl(ast_manager & m, params_ref const & p): + m(m), + m_der(m, p), + m_fm(m), + m_array_der(m), + m_elim_star(*this), m_rewriter(m) {} - + void operator()(app_ref_vector& vars, expr_ref& fml) { if (vars.empty()) { return; } expr_ref tmp(fml); quantifier_ref q(m); - proof_ref pr(m); + proof_ref pr(m); symbol qe_lite("QE"); expr_abstract(m, 0, vars.size(), (expr*const*)vars.c_ptr(), fml, tmp); ptr_vector sorts; @@ -2386,12 +2389,12 @@ public: ++j; } } - vars.resize(j); - } + vars.resize(j); + } else { fml = tmp; } - } + } void operator()(expr_ref& fml, proof_ref& pr) { expr_ref tmp(m); @@ -2438,8 +2441,8 @@ public: }; -qe_lite::qe_lite(ast_manager& m) { - m_impl = alloc(impl, m); +qe_lite::qe_lite(ast_manager & m, params_ref const & p) { + m_impl = alloc(impl, m, p); } qe_lite::~qe_lite() { @@ -2464,14 +2467,14 @@ void qe_lite::operator()(uint_set const& index_set, bool index_of_bound, expr_re } class qe_lite_tactic : public tactic { - + struct imp { ast_manager& m; qe_lite m_qe; - imp(ast_manager& m, params_ref const& p): + imp(ast_manager& m, params_ref const & p): m(m), - m_qe(m) + m_qe(m, p) {} void checkpoint() { @@ -2479,7 +2482,7 @@ class qe_lite_tactic : public tactic { throw tactic_exception(m.limit().get_cancel_msg()); cooperate("qe-lite"); } - + void debug_diff(expr* a, expr* b) { ptr_vector as, bs; as.push_back(a); @@ -2515,9 +2518,9 @@ class qe_lite_tactic : public tactic { } } - void operator()(goal_ref const & g, - goal_ref_buffer & result, - model_converter_ref & mc, + void operator()(goal_ref const & g, + goal_ref_buffer & result, + model_converter_ref & mc, proof_converter_ref & pc, expr_dependency_ref & core) { SASSERT(g->is_well_sorted()); @@ -2540,7 +2543,7 @@ class qe_lite_tactic : public tactic { if (produce_proofs) { expr* fact = m.get_fact(new_pr); if (to_app(fact)->get_arg(0) != to_app(fact)->get_arg(1)) { - new_pr = m.mk_modus_ponens(g->pr(i), new_pr); + new_pr = m.mk_modus_ponens(g->pr(i), new_pr); } else { new_pr = g->pr(i); @@ -2548,7 +2551,7 @@ class qe_lite_tactic : public tactic { } if (f != new_f) { TRACE("qe", tout << mk_pp(f, m) << "\n" << new_f << "\n";); - g->update(i, new_f, new_pr, g->dep(i)); + g->update(i, new_f, new_pr, g->dep(i)); } } g->inc_depth(); @@ -2558,7 +2561,7 @@ class qe_lite_tactic : public tactic { } }; - + params_ref m_params; imp * m_imp; @@ -2567,7 +2570,7 @@ public: m_params(p) { m_imp = alloc(imp, m, p); } - + virtual ~qe_lite_tactic() { dealloc(m_imp); } @@ -2581,20 +2584,20 @@ public: // m_imp->updt_params(p); } - + virtual void collect_param_descrs(param_descrs & r) { // m_imp->collect_param_descrs(r); } - - virtual void operator()(goal_ref const & in, - goal_ref_buffer & result, - model_converter_ref & mc, + + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, proof_converter_ref & pc, expr_dependency_ref & core) { (*m_imp)(in, result, mc, pc, core); } - + virtual void collect_statistics(statistics & st) const { // m_imp->collect_statistics(st); } @@ -2603,13 +2606,13 @@ public: // m_imp->reset_statistics(); } - + virtual void cleanup() { ast_manager & m = m_imp->m; dealloc(m_imp); m_imp = alloc(imp, m, m_params); } - + }; tactic * mk_qe_lite_tactic(ast_manager & m, params_ref const & p) { diff --git a/src/qe/qe_lite.h b/src/qe/qe_lite.h index 48874f5cf..cff547f36 100644 --- a/src/qe/qe_lite.h +++ b/src/qe/qe_lite.h @@ -7,7 +7,7 @@ Module Name: Abstract: - Light weight partial quantifier-elimination procedures + Light weight partial quantifier-elimination procedures Author: @@ -31,14 +31,14 @@ class qe_lite { class impl; impl * m_impl; public: - qe_lite(ast_manager& m); + qe_lite(ast_manager & m, params_ref const & p); ~qe_lite(); /** \brief - Apply light-weight quantifier elimination - on constants provided as vector of variables. + Apply light-weight quantifier elimination + on constants provided as vector of variables. Return the updated formula and updated set of variables that were not eliminated. */ @@ -66,4 +66,4 @@ tactic * mk_qe_lite_tactic(ast_manager & m, params_ref const & p = params_ref()) ADD_TACTIC("qe-light", "apply light-weight quantifier elimination.", "mk_qe_lite_tactic(m, p)") */ -#endif +#endif diff --git a/src/sat/card_extension.cpp b/src/sat/card_extension.cpp index cb05e5d61..bd32a7c40 100644 --- a/src/sat/card_extension.cpp +++ b/src/sat/card_extension.cpp @@ -26,8 +26,7 @@ namespace sat { m_index(index), m_lit(lit), m_k(k), - m_size(lits.size()) - { + m_size(lits.size()) { for (unsigned i = 0; i < lits.size(); ++i) { m_lits[i] = lits[i]; } @@ -42,6 +41,32 @@ namespace sat { SASSERT(m_size >= m_k && m_k > 0); } + card_extension::pb::pb(unsigned index, literal lit, svector const& wlits, unsigned k): + m_index(index), + m_lit(lit), + m_k(k), + m_size(wlits.size()), + m_max_sum(0) { + for (unsigned i = 0; i < wlits.size(); ++i) { + m_wlits[i] = wlits[i]; + if (m_max_sum + wlits[i].first < m_max_sum) { + throw default_exception("addition of pb coefficients overflows"); + } + m_max_sum += wlits[i].first; + } + } + + void card_extension::pb::negate() { + m_lit.neg(); + unsigned w = 0; + for (unsigned i = 0; i < m_size; ++i) { + m_wlits[i].second.neg(); + w += m_wlits[i].first; + } + m_k = w - m_k + 1; + SASSERT(w >= m_k && m_k > 0); + } + card_extension::xor::xor(unsigned index, literal lit, literal_vector const& lits): m_index(index), m_lit(lit), @@ -191,6 +216,356 @@ namespace sat { SASSERT(s().inconsistent()); } + // pb: + + void card_extension::copy_pb(card_extension& result) { + for (unsigned i = 0; i < m_pbs.size(); ++i) { + svector wlits; + pb& p = *m_pbs[i]; + for (unsigned i = 0; i < p.size(); ++i) { + wlits.push_back(p[i]); + } + bool_var v = p.lit() == null_literal ? null_bool_var : p.lit().var(); + result.add_pb_ge(v, wlits, p.k()); + } + } + + // watch a prefix of literals, such that the slack of these is >= k + void card_extension::init_watch(pb& p, bool is_true) { + clear_watch(p); + if (p.lit() != null_literal && p.lit().sign() == is_true) { + p.negate(); + } + + TRACE("sat", display(tout << "init watch: ", p, true);); + SASSERT(p.lit() == null_literal || value(p.lit()) == l_true); + unsigned sz = p.size(), bound = p.k(); + + // put the non-false literals into the head. + unsigned slack = 0, num_watch = 0, j = 0; + for (unsigned i = 0; i < sz; ++i) { + if (value(p[i].second) != l_false) { + if (j != i) { + p.swap(i, j); + } + if (slack < bound) { + slack += p[i].first; + ++num_watch; + } + ++j; + } + } + DEBUG_CODE( + bool is_false = false; + for (unsigned k = 0; k < sz; ++k) { + SASSERT(!is_false || value(p[k].second) == l_false); + SASSERT(k < j == (value(p[k].second) != l_false)); + is_false = value(p[k].second) == l_false; + }); + + if (slack < bound) { + literal lit = p[j].second; + SASSERT(value(p[j].second) == l_false); + for (unsigned i = j + 1; j < sz; ++i) { + if (lvl(lit) < lvl(p[i].second)) { + lit = p[i].second; + } + } + set_conflict(p, lit); + } + else { + for (unsigned i = 0; i < num_watch; ++i) { + watch_literal(p, p[i]); + } + p.set_slack(slack); + p.set_num_watch(num_watch); + } + } + + /* + Chai Kuhlmann: + Lw - set of watched literals + Lu - set of unwatched literals that are not false + + Lw = Lw \ { alit } + Sw -= value + a_max = max { a | l in Lw u Lu, l = undef } + while (Sw < k + a_max & Lu != 0) { + a_s = max { a | l in Lu } + Sw += a_s + Lw = Lw u {l_s} + Lu = Lu \ {l_s} + } + if (Sw < bound) conflict + while (Sw < k + a_max) { + assign (l_max) + a_max = max { ai | li in Lw, li = undef } + } + ASSERT(Sw >= bound) + return no-conflict + + a_max index: index of non-false literal with maximal weight. + + + */ + lbool card_extension::add_assign(pb& p, literal alit) { + unsigned sz = p.size(); + unsigned bound = p.k(); + unsigned num_watch = p.num_watch(); + unsigned slack = p.slack(); + SASSERT(value(alit) == l_false); + SASSERT(p.lit() == null_literal || value(p.lit()) == l_true); + SASSERT(num_watch <= sz); + unsigned index = 0; + unsigned a_max = 0; + unsigned max_index = 0; + m_pb_undef.reset(); + for (; index < num_watch; ++index) { + literal lit = p[index].second; + if (lit == alit) { + break; + } + if (value(lit) == l_undef) { + m_pb_undef.push_back(index); + if (p[index].first > a_max) { + a_max = p[index].first; + max_index = index; + } + } + } + + for (unsigned j = index + 1; a_max == 0 && j < num_watch; ++j) { + literal lit = p[j].second; + if (value(lit) == l_undef) { + m_pb_undef.push_back(j); + a_max = p[j].first; + max_index = j; + } + } + for (unsigned j = num_watch; a_max == 0 && j < sz; ++j) { + literal lit = p[j].second; + if (value(lit) == l_undef) { + p.swap(j, num_watch); + m_pb_undef.push_back(num_watch); + a_max = p[num_watch].first; + max_index = num_watch; + } + } + + unsigned val = p[index].first; + SASSERT(num_watch > 0); + SASSERT(index < num_watch); + SASSERT(value(p[index].second) == l_false); + SASSERT(val <= slack); + slack -= val; + // find literals to swap with: + for (unsigned j = num_watch; j < sz && slack < bound + a_max; ++j) { + if (value(p[j].second) != l_false) { + slack += p[j].first; + watch_literal(p, p[j]); + p.swap(num_watch, j); + if (value(p[num_watch].second) == l_undef && a_max < p[num_watch].first) { + m_pb_undef.push_back(num_watch); + a_max = p[num_watch].first; + max_index = num_watch; + } + ++num_watch; + } + } + + if (slack < bound) { + // maintain watching the literal + slack += val; + p.set_slack(slack); + p.set_num_watch(num_watch); + SASSERT(bound <= slack); + TRACE("sat", tout << "conflict " << alit << "\n";); + set_conflict(p, alit); + return l_false; + } + + // swap out the watched literal. + p.set_slack(slack); + --num_watch; + SASSERT(num_watch > 0); + p.set_num_watch(num_watch); + p.swap(num_watch, index); + if (num_watch == max_index) { + max_index = index; + } + + SASSERT(max_index < sz); + while (slack < bound + a_max && !s().inconsistent()) { + // variable at max-index must be assigned to true. + assign(p, p[max_index].second); + + a_max = 0; + // find the next a_max among m_pb_undef + while (!m_pb_undef.empty() && l_undef != value(p[m_pb_undef.back()].second)) { + m_pb_undef.pop_back(); + } + if (m_pb_undef.empty()) { + break; + } + max_index = m_pb_undef.back(); + a_max = p[max_index].first; + m_pb_undef.pop_back(); + } + + return s().inconsistent() ? l_false : l_true; + } + + void card_extension::watch_literal(pb& p, wliteral l) { + literal lit = l.second; + init_watch(lit.var()); + ptr_vector* pbs = m_var_infos[lit.var()].m_pb_watch[lit.sign()]; + if (pbs == 0) { + pbs = alloc(ptr_vector); + m_var_infos[lit.var()].m_pb_watch[lit.sign()] = pbs; + } + else if (is_tag_empty(pbs)) { + pbs = set_tag_non_empty(pbs); + m_var_infos[lit.var()].m_pb_watch[lit.sign()] = pbs; + } + TRACE("sat_verbose", tout << "insert: " << lit.var() << " " << lit.sign() << "\n";); + pbs->push_back(&p); + } + + void card_extension::clear_watch(pb& p) { + unsigned sz = p.size(); + for (unsigned i = 0; i < sz; ++i) { + unwatch_literal(p[i].second, &p); + } + } + + void card_extension::unwatch_literal(literal lit, pb* p) { + if (m_var_infos.size() <= static_cast(lit.var())) { + return; + } + pb_watch*& pbs = m_var_infos[lit.var()].m_pb_watch[lit.sign()]; + if (!is_tag_empty(pbs)) { + if (remove(*pbs, p)) { + pbs = set_tag_empty(pbs); + } + } + } + + void card_extension::set_conflict(pb& p, literal lit) { + m_stats.m_num_pb_conflicts++; + TRACE("sat", display(tout, p, true); ); + // SASSERT(validate_conflict(p)); + SASSERT(value(lit) == l_false); + s().set_conflict(justification::mk_ext_justification(p.index()), ~lit); + SASSERT(s().inconsistent()); + } + + void card_extension::assign(pb& p, literal lit) { + switch (value(lit)) { + case l_true: + break; + case l_false: + set_conflict(p, lit); + break; + default: + m_stats.m_num_pb_propagations++; + m_num_propagations_since_pop++; + if (s().m_config.m_drat) { + svector ps; + literal_vector lits; + get_pb_antecedents(lit, p, lits); + lits.push_back(lit); + ps.push_back(drat::premise(drat::s_ext(), p.lit())); + s().m_drat.add(lits, ps); + } + s().assign(lit, justification::mk_ext_justification(p.index())); + break; + } + } + + void card_extension::display(std::ostream& out, pb& p, bool values) const { + out << p.lit() << "[" << p.size() << "]"; + if (p.lit() != null_literal && values) { + out << "@(" << value(p.lit()); + if (value(p.lit()) != l_undef) { + out << ":" << lvl(p.lit()); + } + out << "): "; + } + else { + out << ": "; + } + for (unsigned i = 0; i < p.size(); ++i) { + literal l = p[i].second; + unsigned w = p[i].first; + if (w > 1) out << w << " * "; + out << l; + if (values) { + out << "@(" << value(l); + if (value(l) != l_undef) { + out << ":" << lvl(l); + } + out << ") "; + } + else { + out << " "; + } + } + out << ">= " << p.k() << "\n"; + } + + void card_extension::asserted_pb(literal l, ptr_vector* pbs, pb* p0) { + TRACE("sat", tout << l << " " << !is_tag_empty(pbs) << " " << (p0 != 0) << "\n";); + if (!is_tag_empty(pbs)) { + ptr_vector::iterator begin = pbs->begin(); + ptr_vector::iterator it = begin, it2 = it, end = pbs->end(); + for (; it != end; ++it) { + pb& p = *(*it); + if (p.lit() != null_literal && value(p.lit()) != l_true) { + continue; + } + switch (add_assign(p, ~l)) { + case l_false: // conflict + for (; it != end; ++it, ++it2) { + *it2 = *it; + } + SASSERT(s().inconsistent()); + pbs->set_end(it2); + return; + case l_true: // unit propagation, keep watching the literal + if (it2 != it) { + *it2 = *it; + } + ++it2; + break; + case l_undef: // watch literal was swapped + break; + } + } + pbs->set_end(it2); + if (pbs->empty()) { + m_var_infos[l.var()].m_pb_watch[!l.sign()] = set_tag_empty(pbs); + } + } + + if (p0 != 0 && !s().inconsistent()) { + init_watch(*p0, !l.sign()); + } + } + + // xor: + + void card_extension::copy_xor(card_extension& result) { + for (unsigned i = 0; i < m_xors.size(); ++i) { + literal_vector lits; + xor& x = *m_xors[i]; + for (unsigned i = 0; i < x.size(); ++i) { + lits.push_back(x[i]); + } + bool_var v = x.lit() == null_literal ? null_bool_var : x.lit().var(); + result.add_xor(v, lits); + } + } + void card_extension::clear_watch(xor& x) { unwatch_literal(x[0], &x); unwatch_literal(x[1], &x); @@ -200,7 +575,7 @@ namespace sat { if (m_var_infos.size() <= static_cast(lit.var())) { return; } - xor_watch* xors = m_var_infos[lit.var()].m_xor_watch; + xor_watch*& xors = m_var_infos[lit.var()].m_xor_watch; if (!is_tag_empty(xors)) { if (remove(*xors, c)) { xors = set_tag_empty(xors); @@ -355,6 +730,45 @@ namespace sat { return s().inconsistent() ? l_false : l_true; } + void card_extension::asserted_xor(literal l, ptr_vector* xors, xor* x) { + TRACE("sat", tout << l << " " << !is_tag_empty(xors) << " " << (x != 0) << "\n";); + if (!is_tag_empty(xors)) { + ptr_vector::iterator begin = xors->begin(); + ptr_vector::iterator it = begin, it2 = it, end = xors->end(); + for (; it != end; ++it) { + xor& c = *(*it); + if (c.lit() != null_literal && value(c.lit()) != l_true) { + continue; + } + switch (add_assign(c, ~l)) { + case l_false: // conflict + for (; it != end; ++it, ++it2) { + *it2 = *it; + } + SASSERT(s().inconsistent()); + xors->set_end(it2); + return; + case l_undef: // watch literal was swapped + break; + case l_true: // unit propagation, keep watching the literal + if (it2 != it) { + *it2 = *it; + } + ++it2; + break; + } + } + xors->set_end(it2); + if (xors->empty()) { + m_var_infos[l.var()].m_xor_watch = set_tag_empty(xors); + } + } + + if (x != 0 && !s().inconsistent()) { + init_watch(*x, !l.sign()); + } + } + void card_extension::normalize_active_coeffs() { while (!m_active_var_set.empty()) m_active_var_set.erase(); @@ -510,7 +924,7 @@ namespace sat { process_card(c, offset); ++m_stats.m_num_card_resolves; } - else { + else if (is_xor_index(index)) { // jus.push_back(js); m_lemma.reset(); m_bound += offset; @@ -521,6 +935,20 @@ namespace sat { } ++m_stats.m_num_xor_resolves; } + else if (is_pb_index(index)) { + pb& p = index2pb(index); + m_lemma.reset(); + m_bound += offset; + inc_coeff(consequent, offset); + get_pb_antecedents(consequent, p, m_lemma); + for (unsigned i = 0; i < m_lemma.size(); ++i) { + process_antecedent(~m_lemma[i], offset); + } + ++m_stats.m_num_pb_resolves; + } + else { + UNREACHABLE(); + } break; } default: @@ -758,7 +1186,7 @@ namespace sat { } void card_extension::add_at_least(bool_var v, literal_vector const& lits, unsigned k) { - unsigned index = 2*m_cards.size(); + unsigned index = 4*m_cards.size(); literal lit = v == null_bool_var ? null_literal : literal(v, false); card* c = new (memory::allocate(card::get_obj_size(lits.size()))) card(index, lit, lits, k); m_cards.push_back(c); @@ -774,9 +1202,26 @@ namespace sat { } } + void card_extension::add_pb_ge(bool_var v, svector const& wlits, unsigned k) { + unsigned index = 4*m_pbs.size() + 0x11; + literal lit = v == null_bool_var ? null_literal : literal(v, false); + pb* p = new (memory::allocate(pb::get_obj_size(wlits.size()))) pb(index, lit, wlits, k); + m_pbs.push_back(p); + if (v == null_bool_var) { + init_watch(*p, true); + m_pb_axioms.push_back(p); + } + else { + init_watch(v); + m_var_infos[v].m_pb = p; + m_var_trail.push_back(v); + } + } + + void card_extension::add_xor(bool_var v, literal_vector const& lits) { m_has_xor = true; - unsigned index = 2*m_xors.size()+1; + unsigned index = 4*m_xors.size() + 0x01; xor* x = new (memory::allocate(xor::get_obj_size(lits.size()))) xor(index, literal(v, false), lits); m_xors.push_back(x); init_watch(v); @@ -819,7 +1264,7 @@ namespace sat { unsigned level = lvl(l); bool_var v = l.var(); SASSERT(js.get_kind() == justification::EXT_JUSTIFICATION); - SASSERT(!is_card_index(js.get_ext_justification_idx())); + SASSERT(is_xor_index(js.get_ext_justification_idx())); TRACE("sat", tout << l << ": " << js << "\n"; tout << s().m_trail << "\n";); unsigned num_marks = 0; @@ -828,7 +1273,7 @@ namespace sat { ++count; if (js.get_kind() == justification::EXT_JUSTIFICATION) { unsigned idx = js.get_ext_justification_idx(); - if (is_card_index(idx)) { + if (!is_xor_index(idx)) { r.push_back(l); } else { @@ -894,6 +1339,25 @@ namespace sat { TRACE("sat", tout << r << "\n";); } + void card_extension::get_pb_antecedents(literal l, pb const& p, literal_vector& r) { + if (p.lit() != null_literal) r.push_back(p.lit()); + SASSERT(p.lit() == null_literal || value(p.lit()) == l_true); + unsigned k = p.k(); + unsigned max_sum = p.max_sum(); + for (unsigned i = p.size(); i > 0 && max_sum >= k; ) { + --i; + literal lit = p[i].second; + if (lit == l) { + max_sum -= p[i].first; + } + else if (value(lit) == l_false) { + r.push_back(~p[i].second); + max_sum -= p[i].first; + } + } + SASSERT(max_sum < k); + } + void card_extension::get_antecedents(literal l, ext_justification_idx idx, literal_vector & r) { if (is_card_index(idx)) { card& c = index2card(idx); @@ -912,7 +1376,7 @@ namespace sat { r.push_back(~c[i]); } } - else { + else if (is_xor_index(idx)) { xor& x = index2xor(idx); if (x.lit() != null_literal) r.push_back(x.lit()); TRACE("sat", display(tout << l << " ", x, true);); @@ -931,6 +1395,13 @@ namespace sat { r.push_back(value(x[i]) == l_true ? x[i] : ~x[i]); } } + else if (is_pb_index(idx)) { + pb const& p = index2pb(idx); + get_pb_antecedents(l, p, r); + } + else { + UNREACHABLE(); + } } @@ -996,9 +1467,11 @@ namespace sat { if (v >= m_var_infos.size()) return; var_info& vinfo = m_var_infos[v]; ptr_vector* cards = vinfo.m_card_watch[!l.sign()]; + ptr_vector* xors = vinfo.m_xor_watch; + ptr_vector* pbs = vinfo.m_pb_watch[!l.sign()]; + pb* p = vinfo.m_pb; card* crd = vinfo.m_card; xor* x = vinfo.m_xor; - ptr_vector* xors = vinfo.m_xor_watch; if (!is_tag_empty(cards)) { ptr_vector::iterator begin = cards->begin(); @@ -1035,51 +1508,17 @@ namespace sat { if (crd != 0 && !s().inconsistent()) { init_watch(*crd, !l.sign()); } + + if ((!is_tag_empty(pbs) || p) && !s().inconsistent()) { + asserted_pb(l, pbs, p); + } + if (m_has_xor && !s().inconsistent()) { asserted_xor(l, xors, x); } } - void card_extension::asserted_xor(literal l, ptr_vector* xors, xor* x) { - TRACE("sat", tout << l << " " << !is_tag_empty(xors) << " " << (x != 0) << "\n";); - if (!is_tag_empty(xors)) { - ptr_vector::iterator begin = xors->begin(); - ptr_vector::iterator it = begin, it2 = it, end = xors->end(); - for (; it != end; ++it) { - xor& c = *(*it); - if (c.lit() != null_literal && value(c.lit()) != l_true) { - continue; - } - switch (add_assign(c, ~l)) { - case l_false: // conflict - for (; it != end; ++it, ++it2) { - *it2 = *it; - } - SASSERT(s().inconsistent()); - xors->set_end(it2); - return; - case l_undef: // watch literal was swapped - break; - case l_true: // unit propagation, keep watching the literal - if (it2 != it) { - *it2 = *it; - } - ++it2; - break; - } - } - xors->set_end(it2); - if (xors->empty()) { - m_var_infos[l.var()].m_xor_watch = set_tag_empty(xors); - } - } - - if (x != 0 && !s().inconsistent()) { - init_watch(*x, !l.sign()); - } - } - check_result card_extension::check() { return CR_DONE; } void card_extension::push() { @@ -1095,13 +1534,17 @@ namespace sat { m_var_trail.pop_back(); if (v != null_bool_var) { card* c = m_var_infos[v].m_card; - clear_watch(*c); - m_var_infos[v].m_card = 0; - dealloc(c); + if (c) { + clear_watch(*c); + m_var_infos[v].m_card = 0; + dealloc(c); + } xor* x = m_var_infos[v].m_xor; - clear_watch(*x); - m_var_infos[v].m_xor = 0; - dealloc(x); + if (x) { + clear_watch(*x); + m_var_infos[v].m_xor = 0; + dealloc(x); + } } } m_var_lim.resize(new_lim); @@ -1124,15 +1567,8 @@ namespace sat { bool_var v = c.lit() == null_literal ? null_bool_var : c.lit().var(); result->add_at_least(v, lits, c.k()); } - for (unsigned i = 0; i < m_xors.size(); ++i) { - literal_vector lits; - xor& x = *m_xors[i]; - for (unsigned i = 0; i < x.size(); ++i) { - lits.push_back(x[i]); - } - bool_var v = x.lit() == null_literal ? null_bool_var : x.lit().var(); - result->add_xor(v, lits); - } + copy_xor(*result); + copy_pb(*result); return result; } @@ -1281,13 +1717,27 @@ namespace sat { } out << ">= " << c.k(); } - else { + else if (is_xor_index(idx)) { xor& x = index2xor(idx); out << "xor " << x.lit() << ": "; for (unsigned i = 0; i < x.size(); ++i) { out << x[i] << " "; } } + else if (is_pb_index(idx)) { + pb& p = index2pb(idx); + out << "pb " << p.lit() << ": "; + for (unsigned i = 0; i < p.size(); ++i) { + if (p[i].first != 1) { + out << p[i].first << " "; + } + out << p[i].second << " "; + } + out << ">= " << p.k(); + } + else { + UNREACHABLE(); + } return out; } @@ -1298,6 +1748,9 @@ namespace sat { st.update("xor propagations", m_stats.m_num_xor_propagations); st.update("xor conflicts", m_stats.m_num_xor_conflicts); st.update("xor resolves", m_stats.m_num_xor_resolves); + st.update("pb propagations", m_stats.m_num_pb_propagations); + st.update("pb conflicts", m_stats.m_num_pb_conflicts); + st.update("pb resolves", m_stats.m_num_pb_resolves); } bool card_extension::validate_conflict(card& c) { @@ -1382,7 +1835,7 @@ namespace sat { } if (c.lit() != null_literal) p.push(~c.lit(), offset*c.k()); } - else { + else if (is_xor_index(index)) { literal_vector ls; get_antecedents(lit, index, ls); p.reset(offset); @@ -1392,6 +1845,17 @@ namespace sat { literal lxor = index2xor(index).lit(); if (lxor != null_literal) p.push(~lxor, offset); } + else if (is_pb_index(index)) { + pb& pb = index2pb(index); + p.reset(pb.k()); + for (unsigned i = 0; i < pb.size(); ++i) { + p.push(pb[i].second, pb[i].first); + } + if (pb.lit() != null_literal) p.push(~pb.lit(), pb.k()); + } + else { + UNREACHABLE(); + } break; } default: diff --git a/src/sat/card_extension.h b/src/sat/card_extension.h index 0d649a97c..0e229a056 100644 --- a/src/sat/card_extension.h +++ b/src/sat/card_extension.h @@ -36,6 +36,9 @@ namespace sat { unsigned m_num_xor_propagations; unsigned m_num_xor_conflicts; unsigned m_num_xor_resolves; + unsigned m_num_pb_propagations; + unsigned m_num_pb_conflicts; + unsigned m_num_pb_resolves; stats() { reset(); } void reset() { memset(this, 0, sizeof(*this)); } }; @@ -58,6 +61,34 @@ namespace sat { void swap(unsigned i, unsigned j) { std::swap(m_lits[i], m_lits[j]); } void negate(); }; + + typedef std::pair wliteral; + + class pb { + unsigned m_index; + literal m_lit; + unsigned m_k; + unsigned m_size; + unsigned m_slack; + unsigned m_num_watch; + unsigned m_max_sum; + wliteral m_wlits[0]; + public: + static size_t get_obj_size(unsigned num_lits) { return sizeof(card) + num_lits * sizeof(wliteral); } + pb(unsigned index, literal lit, svector const& wlits, unsigned k); + unsigned index() const { return m_index; } + literal lit() const { return m_lit; } + wliteral operator[](unsigned i) const { return m_wlits[i]; } + unsigned k() const { return m_k; } + unsigned size() const { return m_size; } + unsigned slack() const { return m_slack; } + void set_slack(unsigned s) { m_slack = s; } + unsigned num_watch() const { return m_num_watch; } + unsigned max_sum() const { return m_max_sum; } + void set_num_watch(unsigned s) { m_num_watch = s; } + void swap(unsigned i, unsigned j) { std::swap(m_wlits[i], m_wlits[j]); } + void negate(); + }; class xor { unsigned m_index; @@ -85,20 +116,28 @@ namespace sat { typedef ptr_vector card_watch; typedef ptr_vector xor_watch; + typedef ptr_vector pb_watch; struct var_info { card_watch* m_card_watch[2]; + pb_watch* m_pb_watch[2]; xor_watch* m_xor_watch; card* m_card; + pb* m_pb; xor* m_xor; - var_info(): m_xor_watch(0), m_card(0), m_xor(0) { + var_info(): m_xor_watch(0), m_card(0), m_xor(0), m_pb(0) { m_card_watch[0] = 0; m_card_watch[1] = 0; + m_pb_watch[0] = 0; + m_pb_watch[1] = 0; } void reset() { dealloc(m_card); dealloc(m_xor); + dealloc(m_pb); dealloc(card_extension::set_tag_non_empty(m_card_watch[0])); dealloc(card_extension::set_tag_non_empty(m_card_watch[1])); + dealloc(card_extension::set_tag_non_empty(m_pb_watch[0])); + dealloc(card_extension::set_tag_non_empty(m_pb_watch[1])); dealloc(card_extension::set_tag_non_empty(m_xor_watch)); } }; @@ -125,8 +164,10 @@ namespace sat { ptr_vector m_cards; ptr_vector m_xors; + ptr_vector m_pbs; scoped_ptr_vector m_card_axioms; + scoped_ptr_vector m_pb_axioms; // watch literals svector m_var_infos; @@ -145,6 +186,9 @@ namespace sat { bool m_has_xor; unsigned_vector m_parity_marks; literal_vector m_parity_trail; + + unsigned_vector m_pb_undef; + void ensure_parity_size(bool_var v); unsigned get_parity(bool_var v); void inc_parity(bool_var v); @@ -163,6 +207,7 @@ namespace sat { void unwatch_literal(literal w, card* c); // xor specific functionality + void copy_xor(card_extension& result); void clear_watch(xor& x); void watch_literal(xor& x, literal lit); void unwatch_literal(literal w, xor* x); @@ -172,11 +217,28 @@ namespace sat { bool parity(xor const& x, unsigned offset) const; lbool add_assign(xor& x, literal alit); void asserted_xor(literal l, ptr_vector* xors, xor* x); + void get_xor_antecedents(literal l, unsigned index, justification js, literal_vector& r); - bool is_card_index(unsigned idx) const { return 0 == (idx & 0x1); } - card& index2card(unsigned idx) const { SASSERT(is_card_index(idx)); return *m_cards[idx >> 1]; } - xor& index2xor(unsigned idx) const { SASSERT(!is_card_index(idx)); return *m_xors[idx >> 1]; } - void get_xor_antecedents(literal l, unsigned inddex, justification js, literal_vector& r); + + bool is_card_index(unsigned idx) const { return 0x00 == (idx & 0x11); } + bool is_xor_index(unsigned idx) const { return 0x01 == (idx & 0x11); } + bool is_pb_index(unsigned idx) const { return 0x11 == (idx & 0x11); } + card& index2card(unsigned idx) const { SASSERT(is_card_index(idx)); return *m_cards[idx >> 2]; } + xor& index2xor(unsigned idx) const { SASSERT(!is_card_index(idx)); return *m_xors[idx >> 2]; } + pb& index2pb(unsigned idx) const { SASSERT(is_pb_index(idx)); return *m_pbs[idx >> 2]; } + + + // pb functionality + void copy_pb(card_extension& result); + void asserted_pb(literal l, ptr_vector* pbs, pb* p); + void init_watch(pb& p, bool is_true); + lbool add_assign(pb& p, literal alit); + void watch_literal(pb& p, wliteral lit); + void clear_watch(pb& p); + void set_conflict(pb& p, literal lit); + void assign(pb& p, literal l); + void unwatch_literal(literal w, pb* p); + void get_pb_antecedents(literal l, pb const& p, literal_vector & r); template @@ -224,6 +286,7 @@ namespace sat { void display(std::ostream& out, ineq& p) const; void display(std::ostream& out, card& c, bool values) const; + void display(std::ostream& out, pb& p, bool values) const; void display(std::ostream& out, xor& c, bool values) const; void display_watch(std::ostream& out, bool_var v) const; void display_watch(std::ostream& out, bool_var v, bool sign) const; @@ -233,6 +296,7 @@ namespace sat { virtual ~card_extension(); virtual void set_solver(solver* s) { m_solver = s; } void add_at_least(bool_var v, literal_vector const& lits, unsigned k); + void add_pb_ge(bool_var v, svector const& wlits, unsigned k); void add_xor(bool_var v, literal_vector const& lits); virtual void propagate(literal l, ext_constraint_idx idx, bool & keep); virtual bool resolve_conflict(); diff --git a/src/sat/sat_asymm_branch.cpp b/src/sat/sat_asymm_branch.cpp index efadc5266..b779e01bf 100644 --- a/src/sat/sat_asymm_branch.cpp +++ b/src/sat/sat_asymm_branch.cpp @@ -96,7 +96,6 @@ namespace sat { if (!process(c)) continue; // clause was removed *it2 = *it; - // throw exception to test bug fix: if (it2 != it) throw solver_exception("trigger bug"); ++it2; } s.m_clauses.set_end(it2); @@ -129,14 +128,14 @@ namespace sat { // check if the clause is already satisfied for (i = 0; i < sz; i++) { if (s.value(c[i]) == l_true) { - s.dettach_clause(c); + s.detach_clause(c); s.del_clause(c); return false; } } // try asymmetric branching // clause must not be used for propagation - s.dettach_clause(c); + solver::scoped_detach scoped_d(s, c); s.push(); for (i = 0; i < sz - 1; i++) { literal l = c[i]; @@ -154,7 +153,6 @@ namespace sat { SASSERT(s.m_qhead == s.m_trail.size()); if (i == sz - 1) { // clause size can't be reduced. - s.attach_clause(c); return true; } // clause can be reduced @@ -190,14 +188,14 @@ namespace sat { case 1: TRACE("asymm_branch", tout << "produced unit clause: " << c[0] << "\n";); s.assign(c[0], justification()); - s.del_clause(c); s.propagate_core(false); + scoped_d.del_clause(); SASSERT(s.inconsistent() || s.m_qhead == s.m_trail.size()); return false; // check_missed_propagation() may fail, since m_clauses is not in a consistent state. case 2: SASSERT(s.value(c[0]) == l_undef && s.value(c[1]) == l_undef); s.mk_bin_clause(c[0], c[1], false); - s.del_clause(c); + scoped_d.del_clause(); SASSERT(s.m_qhead == s.m_trail.size()); return false; default: diff --git a/src/sat/sat_ccc.cpp b/src/sat/sat_ccc.cpp new file mode 100644 index 000000000..025bbafd4 --- /dev/null +++ b/src/sat/sat_ccc.cpp @@ -0,0 +1,561 @@ +/*++ +Copyright (c) 2017 Microsoft Corporation + +Module Name: + + sat_ccc.cpp + +Abstract: + + A variant of Concurrent Cube and Conquer + +Author: + + Nikolaj Bjorner (nbjorner) 2017-4-17 + +Notes: + + The cube process spawns conquer threads to search parts of the + state space. + The conquer threads have two modes: + - emulation mode - where they try to outpace the cuber on the same search tree + - complement mode - where they solve a sub-branch not yet explored by the cuber. + When the conquer thread returns a solved cube it is processed in the following ways: + - ignore if solved_id \not\in decisions + - mark d as closed if d \in decisions, such that d is marked by solved id + - backjump otherwise, conquer thread has solved a branch attempted by the cuber + +--*/ + +#include "sat_solver.h" +#include "sat_lookahead.h" +#include "sat_ccc.h" + +using namespace sat; + +// ------------ +// cuber + +ccc::cuber::cuber(ccc& c): m_ccc(c), lh(c.m_s), m_branch_id(0) {} + +lbool ccc::cuber::search() { + m_branch_id = 0; + m_last_closure_level = 1000; + + lh.init_search(); + lh.m_model.reset(); + + lookahead::scoped_level _sl(lh, lh.c_fixed_truth); + lh.m_search_mode = lookahead_mode::searching; + lbool r = research(); + if (r == l_true) { + m_ccc.m_model = lh.get_model(); + } + lh.collect_statistics(m_ccc.m_lh_stats); + return r; +} + +lbool ccc::cuber::research() { + m_ccc.m_s.checkpoint(); + + if (lh.inconsistent()) { + return l_false; + } + + if (get_solved()) { + return l_false; + } + + lh.inc_istamp(); + literal l = lh.choose(); + if (lh.inconsistent()) { + return l_false; + } + + if (l == null_literal) { + return l_true; + } + + if (!decisions.empty()) { + m_ccc.put_decision(decisions.back()); + } + + // update trail and decisions + + ++lh.m_stats.m_decisions; + unsigned parent_id = decisions.empty() ? 0 : decisions.back().m_id; + unsigned spawn_id = spawn_conquer(); + unsigned branch1 = m_branch_id++; + unsigned branch2 = m_branch_id++; + decision d(branch1, decisions.size() + 1, l, parent_id, spawn_id); + decisions.push_back(d); + + IF_VERBOSE(1, d.pp(verbose_stream() << "select " << m_last_closure_level << " ") << "\n";); + IF_VERBOSE(1, verbose_stream() << "select " << pp_prefix(lh.m_prefix, lh.m_trail_lim.size()) << ": " << l << " " << lh.m_trail.size() << "\n";); + IF_VERBOSE(2, pp(verbose_stream(), decisions) << "\n"; ); + + TRACE("sat", tout << "choose: " << l << "\n";); + lh.push(l, lh.c_fixed_truth); + lbool r = research(); + if (r == l_false) { + lh.pop(); + if (decisions.back().is_closed()) { + // branch was solved by a spawned conquer process + IF_VERBOSE(1, decisions.back().pp(verbose_stream() << "closed ") << "\n";); + r = l_false; + decisions.pop_back(); + } + else { + if (spawn_id > 0) { + free_conquer(spawn_id); + m_last_closure_level *= 3; + m_last_closure_level /= 4; + } + lh.inc_istamp(); + lh.flip_prefix(); + lh.push(~l, lh.c_fixed_truth); + decisions.back().negate(); + decisions.back().m_id = branch2; + decisions.back().m_spawn_id = 0; + r = research(); + if (r == l_false) { + lh.pop(); + decisions.pop_back(); + } + } + } + return r; +} + +void ccc::cuber::update_closure_level(decision const& d, int offset) { + m_last_closure_level = (d.m_depth + 3*m_last_closure_level) / 4; + if (m_last_closure_level >= static_cast(abs(offset))) { + m_last_closure_level += offset; + } +} + +unsigned ccc::cuber::spawn_conquer() { + unsigned result = 0; + // + // decisions must have been solved at a higher level by a conquer thread + // + if (!m_free_threads.empty()) { + if (m_last_closure_level <= decisions.size()) { + result = m_free_threads.back(); + ++m_ccc.m_ccc_stats.m_spawn_opened; + m_free_threads.pop_back(); + } + else { + IF_VERBOSE(1, verbose_stream() << m_last_closure_level << " " << decisions.size() << "\n";); + } + } + return result; +} + + +void ccc::cuber::free_conquer(unsigned id) { + if (id != 0) { + m_free_threads.push_back(id); + } +} + + +bool ccc::cuber::get_solved() { + // check if CDCL solver got ahead. + bool do_pop = false; + solution sol; + while (true) { + bool is_empty = true; + #pragma omp critical (ccc_solved) + { + if (do_pop) m_ccc.m_solved.pop_front(); + if (!m_ccc.m_solved.empty()) { + sol = m_ccc.m_solved.top(); + is_empty = false; + } + } + if (is_empty) { + return false; + } + do_pop = true; + unsigned branch_id = sol.m_branch_id; + unsigned thread_id = sol.m_thread_id; + bool found = false; + for (unsigned i = decisions.size(); i > 0; ) { + --i; + decision& d = decisions[i]; + if (branch_id == d.m_id) { + if (d.m_spawn_id == thread_id && thread_id != 0) { + SASSERT(d.m_spawn_id > 0); + IF_VERBOSE(1, d.pp(verbose_stream() << thread_id << ": spawn close ") << "\n";); + ++m_ccc.m_ccc_stats.m_spawn_closed; + d.close(); + free_conquer(thread_id); + update_closure_level(d, -1); + break; + } + else { + IF_VERBOSE(1, d.pp(verbose_stream() << thread_id << ": conquer ") << "\n";); + ++m_ccc.m_ccc_stats.m_cdcl_closed; + update_closure_level(d, 1); + return true; + } + } + // branch is even, d has moved to the next branch + if (branch_id == (d.m_id & ~0x1) && d.m_spawn_id == thread_id && thread_id != 0) { + IF_VERBOSE(1, d.pp(verbose_stream() << thread_id << ": spawn conquer ") << "\n";); + ++m_ccc.m_ccc_stats.m_cdcl_closed; + update_closure_level(d, 1); + return true; + } + } + } +} + +// --------------------- +// conquer state machine + +lbool ccc::conquer::search() { + try { + if (s.inconsistent()) return l_false; + s.init_search(); + s.propagate(false); + if (s.inconsistent()) return l_false; + s.cleanup(); + s.simplify_problem(); + if (s.inconsistent()) return l_false; + + while (true) { + SASSERT(!s.inconsistent()); + + lbool r = bounded_search(); + if (r != l_undef) + return r; + + s.restart(); + s.simplify_problem(); + if (s.check_inconsistent()) return l_false; + s.gc(); + } + } + catch (solver::abort_solver) { + return l_undef; + } +} + +void ccc::conquer::replay_decisions() { + s.propagate(true); + for (unsigned i = s.scope_lvl(); !s.inconsistent() && i < decisions.size(); ++i) { + decision const& d = decisions[i]; + IF_VERBOSE(2, verbose_stream() << thread_id << ": replay " << d.get_literal(thread_id) << " " << s.value(d.get_literal(thread_id)) << "\n";); + + if (!push_decision(d)) { + // negation of decision is implied. + IF_VERBOSE(1, d.pp(verbose_stream() << thread_id << ": backjump to level " << i << " ") << "\n";); + while (decisions.size() > i) { + pop_decision(decisions.back()); + decisions.pop_back(); + } + break; + } + + if (d.m_spawn_id == thread_id && d.is_left()) { + // we pushed the right branch on this thread. + IF_VERBOSE(1, d.pp(verbose_stream() << thread_id << ": skip left branch on level " << i + 1 << " ") << "\n";); + break; + } + } +} + +void ccc::conquer::pop_decision(decision const& d) { + unsigned tid = 0; + if (d.is_spawned(thread_id)) { + tid = thread_id; + m_spawned = false; + IF_VERBOSE(1, d.pp(verbose_stream() << thread_id << ": retire spawn ") << "\n";); + } + #pragma omp critical (ccc_solved) + { + m_ccc.m_solved.push(solution(tid, d.m_id)); + } +} + +bool ccc::conquer::push_decision(decision const& d) { + literal lit = d.get_literal(thread_id); + switch (s.value(lit)) { + case l_false: + //TBD: + s.m_restart_threshold = s.m_config.m_restart_initial; + //s.m_conflicts_since_last_restart = 0; + return false; + case l_true: + s.push(); + break; + case l_undef: + s.push(); + s.assign(lit, justification()); + s.propagate(true); + break; + } + m_spawned |= d.is_spawned(thread_id); + return true; +} + +bool ccc::conquer::cube_decision() { + decision d; + bool use_cube_decision = false; + SASSERT(s.m_qhead == s.m_trail.size()); + + while (true) { + if (!m_ccc.get_decision(thread_id, d)) { + return false; + } + + if (d.is_spawned(thread_id)) IF_VERBOSE(1, d.pp(verbose_stream() << thread_id << " ") << "\n";); + + if (!decisions.empty() && decisions.back().m_depth + 1 < d.m_depth) { + if (d.is_spawned(thread_id)) { + pop_decision(d); + } + } + else { + break; + } + } + SASSERT(decisions.empty() || decisions.back().m_depth + 1 >= d.m_depth); + + if (!decisions.empty() && decisions.back().is_spawned(thread_id) && decisions.back().m_depth == d.m_depth) { + SASSERT(d.m_spawn_id == 0); + SASSERT(decisions.back().is_left()); + SASSERT(!d.is_left()); + IF_VERBOSE(1, verbose_stream() << thread_id << " inherit spawn\n";); + decisions.back().m_spawn_id = 0; + m_spawned = false; + } + SASSERT(decisions.empty() || decisions.back().m_depth + 1 >= d.m_depth); + + while (!decisions.empty() && decisions.back().m_depth >= d.m_depth) { + // check_non_model("cube decision", decisions); + if (decisions.back().is_spawned(thread_id)) { + pop_decision(decisions.back()); + } + decisions.pop_back(); + } + + SASSERT(decisions.empty() || decisions.back().m_depth + 1 == d.m_depth); + SASSERT(decisions.empty() || decisions.back().m_id == d.m_parent); + if (m_spawned) { + decisions.push_back(d); + return true; + } + + s.pop_reinit(s.scope_lvl() - decisions.size()); + SASSERT(s.m_qhead == s.m_trail.size()); + SASSERT(s.scope_lvl() == decisions.size()); + literal lit = d.get_literal(thread_id); + IF_VERBOSE(1, d.pp(verbose_stream() << thread_id << ": cube ") << "\n";); + IF_VERBOSE(2, pp(verbose_stream() << thread_id << ": push ", decisions) << " @ " << s.scope_lvl() << " " << s.value(lit) << "\n"; + if (s.value(lit) == l_false) verbose_stream() << "level: " << s.lvl(lit) << "\n";); + + if (push_decision(d)) { + decisions.push_back(d); + } + else { + pop_decision(d); + } + + return true; +} + +lbool ccc::conquer::bounded_search() { + while (true) { + s.checkpoint(); + bool done = false; + while (!done) { + replay_decisions(); + lbool is_sat = s.propagate_and_backjump_step(done); + if (is_sat != l_true) return is_sat; + } + + s.gc(); + + if (!cube_decision() && !s.decide()) { + lbool is_sat = s.final_check(); + if (is_sat != l_undef) { + return is_sat; + } + } + } +} + +// -------------- +// shared state + +std::ostream& ccc::decision::pp(std::ostream& out) const { + out << "(" + << "id:" << m_id + << " l:" << m_literal + << " d:" << m_depth; + if (m_spawn_id != 0) { + out << " s:" << m_spawn_id; + } + out << ") "; + return out; +} + +std::ostream& ccc::pp(std::ostream& out, svector const& v) { + for (unsigned i = 0; i < v.size(); ++i) { + v[i].pp(out); + } + return out; +} + +bool ccc::get_decision(unsigned thread_id, decision& d) { + SASSERT(0 < thread_id && thread_id <= m_decisions.size()); + bool result = false; + #pragma omp critical (ccc_decisions) + { + if (!m_decisions[thread_id - 1].empty()) { + d = m_decisions[thread_id - 1].pop_front(); + result = true; + } + } + return result; +} + +void ccc::put_decision(decision const& d) { + for (unsigned i = 0; i < m_num_conquer; ++i) { + #pragma omp critical (ccc_decisions) + { + while (false && !m_decisions[i].empty()) { // introduces contention. + decision d = m_decisions[i].back(); + if (d.m_depth < d.m_depth || d.m_spawn_id != 0) { + break; + } + m_decisions[i].pop_back(); + } + m_decisions[i].push(d); + } + } +} + +lbool ccc::search() { + enum par_exception_kind { + DEFAULT_EX, + ERROR_EX + }; + + // set_model(); + + m_cancel = false; + + scoped_limits scoped_rlimit(m_s.rlimit()); + ptr_vector solvers; + int finished_id = -1; + std::string ex_msg; + par_exception_kind ex_kind; + unsigned error_code = 0; + lbool result = l_undef; + m_decisions.reset(); + + cuber cuber(*this); + + m_num_conquer = m_s.m_config.m_num_threads; + int num_threads = 1 + m_num_conquer; // for ccc-infinity only two threads. + + for (int i = 1; i < num_threads; ++i) { + m_s.m_params.set_uint("random_seed", m_s.m_rand()); + conquer* s1 = alloc(conquer, *this, m_s.m_params, i); + solvers.push_back(s1); + s1->s.copy(m_s); + scoped_rlimit.push_child(&(s1->m_limit)); + m_decisions.push_back(queue()); + } + for (unsigned i = 1; i < m_num_conquer; ++i) { + cuber.m_free_threads.push_back(i); + } + + #pragma omp parallel for + for (int i = 0; i < num_threads; ++i) { + try { + lbool r = l_undef; + if (i == 0) { + r = cuber.search(); + } + else { + r = solvers[i-1]->search(); + } + bool first = false; + #pragma omp critical (par_solver) + { + if (finished_id == -1) { + finished_id = i; + first = true; + result = r; + } + } + if (first) { + for (unsigned j = 0; j < solvers.size(); ++j) { + solvers[j]->m_limit.cancel(); + } + // cancel lookahead solver: + m_cancel = true; + } + } + catch (z3_error & err) { + error_code = err.error_code(); + ex_kind = ERROR_EX; + } + catch (z3_exception & ex) { + ex_msg = ex.msg(); + ex_kind = DEFAULT_EX; + } + } + + if (finished_id > 0 && result == l_true) { + // set model from auxiliary solver + m_model = solvers[finished_id - 1]->s.get_model(); + } + + for (unsigned i = 0; i < solvers.size(); ++i) { + solvers[i]->s.collect_statistics(m_lh_stats); + dealloc(solvers[i]); + } + + if (finished_id == -1) { + switch (ex_kind) { + case ERROR_EX: throw z3_error(error_code); + default: throw default_exception(ex_msg.c_str()); + } + } + +#if 0 + if (result == l_true) { + for (unsigned i = 1; i < m_model.size(); ++i) { + std::cout << "push_model(" << i << ", " << (m_model[i] > 0 ? "false" : "true") << ");\n"; + } + } +#endif + + return result; +} + + +#if 0 +void ccc::push_model(unsigned v, bool sign) { + if (m_values.size() <= v) { + m_values.resize(v + 1); + } + m_values[v] = sign; +} + +void ccc::check_non_model(char const* fn, svector const& decisions) { + for (unsigned i = 0; i < decisions.size(); ++i) { + decision d = decisions[i]; + literal lit = d.m_literal; + if (m_values[lit.var()] != lit.sign()) return; + } + + IF_VERBOSE(1, pp(verbose_stream() << "backtracking from model " << fn << " ", decisions) << "\n";); +} +#endif diff --git a/src/sat/sat_ccc.h b/src/sat/sat_ccc.h new file mode 100644 index 000000000..30c8a3229 --- /dev/null +++ b/src/sat/sat_ccc.h @@ -0,0 +1,146 @@ +/*++ +Copyright (c) 2017 Microsoft Corporation + +Module Name: + + sat_ccc.h + +Abstract: + + A variant of Concurrent Cube and Conquer + +Author: + + Nikolaj Bjorner (nbjorner) 2017-4-17 + +Notes: + +--*/ +#ifndef _SAT_CCC_H_ +#define _SAT_CCC_H_ + +#include "queue.h" + +namespace sat { + + + class ccc { + struct decision { + unsigned m_id; // unique identifier for decision + unsigned m_depth; // depth of decision + literal m_literal; // decision literal + unsigned m_parent; // id of parent + int m_spawn_id; // thread id of conquer thread processing complented branch. + // = 0 if not spawned. + // > 0 if active spawn is in progress + // < 0 if thread has closed the branch + decision(unsigned id, unsigned d, literal last, unsigned parent_id, unsigned spawn): + m_id(id), m_depth(d), m_literal(last), m_parent(parent_id), m_spawn_id(spawn) {} + decision(): + m_id(0), m_depth(0), m_literal(null_literal), m_parent(0), m_spawn_id(0) {} + + void close() { SASSERT(m_spawn_id > 0); m_spawn_id = -m_spawn_id; } + bool is_closed() const { return m_spawn_id < 0; } + void negate() { m_literal.neg(); } + bool is_left() const { return 0 == (m_id & 0x1); } + bool is_spawned(unsigned thread_id) const { return m_spawn_id == thread_id; } + + // the left branch has an even branch_id. + // the branch is spawned if it is even and the spawn_id is the same as the thread_id, and furthermore it is exploring the left branch. + // it may explore the right branch, but is then not in a spawned mode. + // we retain the spawn id so that the spawned thread can be re-spun. + literal get_literal(unsigned thread_id) const { return ((m_id & 0x1) == 0 && thread_id == m_spawn_id) ? ~m_literal : m_literal; } + std::ostream& pp(std::ostream& out) const; + }; + + struct solution { + unsigned m_thread_id; + unsigned m_branch_id; + solution(unsigned t, unsigned s): m_thread_id(t), m_branch_id(s) {} + solution(): m_thread_id(0), m_branch_id(0) {} + }; + + struct stats { + unsigned m_spawn_closed; + unsigned m_spawn_opened; + unsigned m_cdcl_closed; + stats() { reset(); } + void reset() { + memset(this, 0, sizeof(*this)); + } + }; + + struct conquer { + reslimit m_limit; + ccc& m_ccc; + solver s; + svector decisions; + unsigned thread_id; + bool m_spawned; + conquer(ccc& super, params_ref const& p, unsigned tid): m_ccc(super), s(p, m_limit), thread_id(tid), m_spawned(false) {} + + lbool search(); + bool cube_decision(); + lbool bounded_search(); + bool push_decision(decision const& d); + void pop_decision(decision const& d); + void replay_decisions(); + }; + + struct cuber { + ccc& m_ccc; + lookahead lh; + unsigned m_branch_id; + unsigned m_last_closure_level; + unsigned_vector m_free_threads; + svector decisions; + + cuber(ccc& c); + lbool search(); + lbool research(); + bool get_solved(); + void update_closure_level(decision const& d, int offset); + unsigned spawn_conquer(); + void free_conquer(unsigned thread_id); + }; + + solver& m_s; + queue m_solved; + vector > m_decisions; + unsigned m_num_conquer; + model m_model; + volatile bool m_cancel; + ::statistics m_lh_stats; + stats m_ccc_stats; + + void put_decision(decision const& d); + bool get_decision(unsigned thread_id, decision& d); + + static std::ostream& pp(std::ostream& out, svector const& v); + + void push_model(unsigned v, bool sign); + void set_model(); + bool trail_in_model(literal_vector const& trail) const; + + void check_non_model(char const* fn, svector const& decisions); + + + public: + + ccc(solver& s): m_s(s) {} + + lbool search(); + + model const& get_model() const { return m_model; } + + void collect_statistics(::statistics& st) { + st.copy(m_lh_stats); + st.update("ccc-spawn-closed", m_ccc_stats.m_spawn_closed); + st.update("ccc-cdcl-closed", m_ccc_stats.m_cdcl_closed); + st.update("ccc-spawn-opened", m_ccc_stats.m_spawn_opened); + } + }; +} + +#endif + diff --git a/src/sat/sat_config.cpp b/src/sat/sat_config.cpp index 4b019c2b7..45ce213b0 100644 --- a/src/sat/sat_config.cpp +++ b/src/sat/sat_config.cpp @@ -37,6 +37,8 @@ namespace sat { m_psm_glue("psm_glue") { m_num_threads = 1; m_local_search = 0; + m_lookahead_search = false; + m_ccc = false; updt_params(p); } @@ -81,7 +83,10 @@ namespace sat { m_max_conflicts = p.max_conflicts(); m_num_threads = p.threads(); m_local_search = p.local_search(); - + m_local_search_threads = p.local_search_threads(); + m_lookahead_search = p.lookahead_search(); + m_ccc = p.ccc(); + // These parameters are not exposed m_simplify_mult1 = _p.get_uint("simplify_mult1", 300); m_simplify_mult2 = _p.get_double("simplify_mult2", 1.5); diff --git a/src/sat/sat_config.h b/src/sat/sat_config.h index 313b4ec49..2e3d4ec86 100644 --- a/src/sat/sat_config.h +++ b/src/sat/sat_config.h @@ -58,7 +58,10 @@ namespace sat { unsigned m_burst_search; unsigned m_max_conflicts; unsigned m_num_threads; - unsigned m_local_search; + unsigned m_local_search_threads; + bool m_local_search; + bool m_lookahead_search; + bool m_ccc; unsigned m_simplify_mult1; double m_simplify_mult2; diff --git a/src/sat/sat_elim_eqs.cpp b/src/sat/sat_elim_eqs.cpp index 36dc3a8d9..0001aaf5c 100644 --- a/src/sat/sat_elim_eqs.cpp +++ b/src/sat/sat_elim_eqs.cpp @@ -94,7 +94,7 @@ namespace sat { continue; } if (!c.frozen()) - m_solver.dettach_clause(c); + m_solver.detach_clause(c); // apply substitution for (i = 0; i < sz; i++) { SASSERT(!m_solver.was_eliminated(c[i].var())); diff --git a/src/sat/sat_local_search.cpp b/src/sat/sat_local_search.cpp index 6b0205cc2..5372011ed 100644 --- a/src/sat/sat_local_search.cpp +++ b/src/sat/sat_local_search.cpp @@ -70,8 +70,8 @@ namespace sat { void local_search::init_cur_solution() { for (unsigned v = 0; v < num_vars(); ++v) { - // use bias half the time. - if (m_rand() % 100 < 10) { + // use bias with a small probability + if (m_rand() % 100 < 2) { m_vars[v].m_value = ((unsigned)(m_rand() % 100) < m_vars[v].m_bias); } } @@ -137,17 +137,24 @@ namespace sat { } void local_search::reinit() { - // the following methods does NOT converge for pseudo-boolean - // can try other way to define "worse" and "better" - // the current best noise is below 1000 - if (best_unsat_rate >= last_best_unsat_rate) { - // worse - m_noise -= m_noise * 2 * m_noise_delta; - } - else { - // better - m_noise += (10000 - m_noise) * m_noise_delta; + + if (!m_is_pb) { + // + // the following methods does NOT converge for pseudo-boolean + // can try other way to define "worse" and "better" + // the current best noise is below 1000 + // + if (m_best_unsat_rate > m_last_best_unsat_rate) { + // worse + m_noise -= m_noise * 2 * m_noise_delta; + m_best_unsat_rate *= 1000.0; + } + else { + // better + m_noise += (10000 - m_noise) * m_noise_delta; + } } + for (unsigned i = 0; i < m_constraints.size(); ++i) { constraint& c = m_constraints[i]; c.m_slack = c.m_k; @@ -178,6 +185,8 @@ namespace sat { init_slack(); init_scores(); init_goodvars(); + + m_best_unsat = m_unsat_stack.size(); } void local_search::calculate_and_update_ob() { @@ -258,7 +267,7 @@ namespace sat { unsigned id = m_constraints.size(); m_constraints.push_back(constraint(k)); for (unsigned i = 0; i < sz; ++i) { - m_vars.reserve(c[i].var() + 1); + m_vars.reserve(c[i].var() + 1); literal t(~c[i]); m_vars[t.var()].m_watch[is_pos(t)].push_back(pbcoeff(id, coeffs[i])); m_constraints.back().push(t); // add coefficient to constraint? @@ -273,6 +282,7 @@ namespace sat { } void local_search::import(solver& s, bool _init) { + m_is_pb = false; m_vars.reset(); m_constraints.reset(); @@ -343,6 +353,7 @@ namespace sat { // = ~c.lit() or (~c.lits() <= n - k) // = k*c.lit() + ~c.lits() <= n // + m_is_pb = true; lits.reset(); coeffs.reset(); for (unsigned j = 0; j < n; ++j) lits.push_back(c[j]), coeffs.push_back(1); @@ -382,13 +393,13 @@ namespace sat { IF_VERBOSE(1, verbose_stream() << "(sat-local-search" \ << " :flips " << flips \ << " :noise " << m_noise \ - << " :unsat " << /*m_unsat_stack.size()*/ best_unsat \ + << " :unsat " << /*m_unsat_stack.size()*/ m_best_unsat \ << " :time " << (timer.get_seconds() < 0.001 ? 0.0 : timer.get_seconds()) << ")\n";); \ } void local_search::walksat() { - best_unsat_rate = 1; - last_best_unsat_rate = 1; + m_best_unsat_rate = 1; + m_last_best_unsat_rate = 1; reinit(); @@ -398,10 +409,10 @@ namespace sat { PROGRESS(tries, total_flips); for (tries = 1; !m_unsat_stack.empty() && m_limit.inc(); ++tries) { - if (m_unsat_stack.size() < best_unsat) { - best_unsat = m_unsat_stack.size(); - last_best_unsat_rate = best_unsat_rate; - best_unsat_rate = (double)m_unsat_stack.size() / num_constraints(); + if (m_unsat_stack.size() < m_best_unsat) { + m_best_unsat = m_unsat_stack.size(); + m_last_best_unsat_rate = m_best_unsat_rate; + m_best_unsat_rate = (double)m_unsat_stack.size() / num_constraints(); } for (step = 0; step < m_max_steps && !m_unsat_stack.empty(); ++step) { pick_flip_walksat(); @@ -506,7 +517,7 @@ namespace sat { SASSERT(c.m_k < constraint_value(c)); // TBD: dynamic noise strategy //if (m_rand() % 100 < 98) { - if (m_rand() % 10000 >= m_noise) { + if (m_rand() % 10000 <= m_noise) { // take this branch with 98% probability. // find the first one, to fast break the rest unsigned best_bsb = 0; @@ -610,8 +621,7 @@ namespace sat { // verify_unsat_stack(); } - void local_search::flip_gsat(bool_var flipvar) - { + void local_search::flip_gsat(bool_var flipvar) { // already changed truth value!!!! m_vars[flipvar].m_value = !cur_solution(flipvar); diff --git a/src/sat/sat_local_search.h b/src/sat/sat_local_search.h index 678eee60a..edce6cc9c 100644 --- a/src/sat/sat_local_search.h +++ b/src/sat/sat_local_search.h @@ -144,6 +144,7 @@ namespace sat { literal_vector m_assumptions; unsigned m_num_non_binary_clauses; + bool m_is_pb; inline bool is_pos(literal t) const { return !t.sign(); } inline bool is_true(bool_var v) const { return cur_solution(v); } @@ -164,9 +165,9 @@ namespace sat { // information about solution - unsigned best_unsat; - double best_unsat_rate; - double last_best_unsat_rate; + unsigned m_best_unsat; + double m_best_unsat_rate; + double m_last_best_unsat_rate; int m_objective_value; // the objective function value corresponds to the current solution bool_vector m_best_solution; // !var: the best solution so far int m_best_objective_value = -1; // the objective value corresponds to the best solution so far @@ -176,7 +177,7 @@ namespace sat { unsigned m_max_steps = (1 << 30); // dynamic noise - double m_noise = 400; // normalized by 10000 + double m_noise = 9800; // normalized by 10000 double m_noise_delta = 0.05; // for tuning diff --git a/src/sat/sat_lookahead.h b/src/sat/sat_lookahead.h index c4f6a4bba..72e64ae1d 100644 --- a/src/sat/sat_lookahead.h +++ b/src/sat/sat_lookahead.h @@ -20,6 +20,8 @@ Notes: #ifndef _SAT_LOOKAHEAD_H_ #define _SAT_LOOKAHEAD_H_ +#include "sat_elim_eqs.h" + namespace sat { struct pp_prefix { @@ -34,6 +36,9 @@ namespace sat { for (unsigned i = 0; i <= d; ++i) { if (0 != (p.m_prefix & (1ull << i))) out << "1"; else out << "0"; } + if (d < p.m_depth) { + out << " d:" << p.m_depth; + } return out; } @@ -54,7 +59,11 @@ namespace sat { } class lookahead { - solver& s; + solver& m_s; + unsigned m_num_vars; + reslimit m_rlimit; + + friend class ccc; struct config { double m_dl_success; @@ -65,6 +74,7 @@ namespace sat { unsigned m_level_cand; float m_delta_rho; unsigned m_dl_max_iterations; + unsigned m_tc1_limit; config() { m_max_hlevel = 50; @@ -74,6 +84,7 @@ namespace sat { m_level_cand = 600; m_delta_rho = (float)0.9995; m_dl_max_iterations = 32; + m_tc1_limit = 10000000; } }; @@ -121,6 +132,8 @@ namespace sat { vector m_binary; // literal: binary clauses unsigned_vector m_binary_trail; // trail of added binary clauses unsigned_vector m_binary_trail_lim; + unsigned m_num_tc1; + unsigned_vector m_num_tc1_lim; unsigned m_qhead; // propagation queue head unsigned_vector m_qhead_lim; clause_vector m_clauses; // non-binary clauses @@ -150,7 +163,7 @@ namespace sat { lookahead_mode m_search_mode; // mode of search stats m_stats; model m_model; - + // --------------------------------------- // truth values @@ -232,14 +245,20 @@ namespace sat { m_binary[(~l2).index()].push_back(l1); m_binary_trail.push_back((~l1).index()); ++m_stats.m_add_binary; - if (s.m_config.m_drat) validate_binary(l1, l2); + if (m_s.m_config.m_drat) validate_binary(l1, l2); } void del_binary(unsigned idx) { // TRACE("sat", display(tout << "Delete " << to_literal(idx) << "\n");); literal_vector & lits = m_binary[idx]; - literal l = lits.back(); + if (lits.empty()) IF_VERBOSE(0, verbose_stream() << "empty literals\n";); + literal l = lits.back(); lits.pop_back(); + if (m_binary[(~l).index()].back() != ~to_literal(idx)) { + IF_VERBOSE(0, verbose_stream() << "pop bad literal: " << idx << " " << (~l).index() << "\n";); + } + if (m_binary[(~l).index()].empty()) + IF_VERBOSE(0, verbose_stream() << "empty binary\n";); m_binary[(~l).index()].pop_back(); ++m_stats.m_del_binary; } @@ -309,7 +328,11 @@ namespace sat { assign(u); return false; } - add_binary(u, w); + if (m_num_tc1 < m_config.m_tc1_limit) { + ++m_num_tc1; + IF_VERBOSE(3, verbose_stream() << "tc1: " << u << " " << w << "\n";); + add_binary(u, w); + } } } return true; @@ -321,6 +344,9 @@ namespace sat { void try_add_binary(literal u, literal v) { SASSERT(m_search_mode == lookahead_mode::searching); SASSERT(u.var() != v.var()); + if (!is_undef(u) || !is_undef(v)) { + IF_VERBOSE(0, verbose_stream() << "adding assigned binary " << v << " " << u << "\n";); + } set_bstamps(~u); if (is_stamped(~v)) { TRACE("sat", tout << "try_add_binary: " << u << "\n";); @@ -367,7 +393,8 @@ namespace sat { bool select(unsigned level) { init_pre_selection(level); - unsigned max_num_cand = level == 0 ? m_freevars.size() : m_config.m_level_cand / level; + unsigned level_cand = std::max(m_config.m_level_cand, m_freevars.size() / 50); + unsigned max_num_cand = level == 0 ? m_freevars.size() : level_cand / level; max_num_cand = std::max(m_config.m_min_cutoff, max_num_cand); float sum = 0; @@ -531,7 +558,7 @@ namespace sat { void ensure_H(unsigned level) { while (m_H.size() <= level) { m_H.push_back(svector()); - m_H.back().resize(s.num_vars() * 2, 0); + m_H.back().resize(m_num_vars * 2, 0); } } @@ -558,6 +585,9 @@ namespace sat { float sum = 0, tsum = 0; literal_vector::iterator it = m_binary[l.index()].begin(), end = m_binary[l.index()].end(); for (; it != end; ++it) { + bool_var v = it->var(); + if (it->index() >= h.size()) + IF_VERBOSE(0, verbose_stream() << l << " " << *it << " " << h.size() << "\n";); if (is_undef(*it)) sum += h[it->index()]; // if (m_freevars.contains(it->var())) sum += h[it->index()]; } @@ -577,7 +607,7 @@ namespace sat { } case watched::CLAUSE: { clause_offset cls_off = wit->get_clause_offset(); - clause & c = *(s.m_cls_allocator.get_clause(cls_off)); + clause & c = *(m_cls_allocator.get_clause(cls_off)); // approximation compared to ternary clause case: // we pick two other literals from the clause. if (c[0] == ~l) { @@ -1010,25 +1040,26 @@ namespace sat { m_lits.push_back(lit_info()); m_rating.push_back(0); m_vprefix.push_back(prefix()); - m_freevars.insert(v); + if (!m_s.was_eliminated(v)) + m_freevars.insert(v); } void init() { - m_delta_trigger = s.num_vars()/10; + m_delta_trigger = m_num_vars/10; m_config.m_dl_success = 0.8; m_inconsistent = false; m_qhead = 0; m_bstamp_id = 0; - for (unsigned i = 0; i < s.num_vars(); ++i) { + for (unsigned i = 0; i < m_num_vars; ++i) { init_var(i); } // copy binary clauses - unsigned sz = s.m_watches.size(); + unsigned sz = m_s.m_watches.size(); for (unsigned l_idx = 0; l_idx < sz; ++l_idx) { literal l = ~to_literal(l_idx); - watch_list const & wlist = s.m_watches[l_idx]; + watch_list const & wlist = m_s.m_watches[l_idx]; watch_list::const_iterator it = wlist.begin(); watch_list::const_iterator end = wlist.end(); for (; it != end; ++it) { @@ -1040,30 +1071,38 @@ namespace sat { } } + copy_clauses(m_s.m_clauses); + copy_clauses(m_s.m_learned); + + // copy units + unsigned trail_sz = m_s.init_trail_size(); + for (unsigned i = 0; i < trail_sz; ++i) { + literal l = m_s.m_trail[i]; + if (!m_s.was_eliminated(l.var())) { + if (m_s.m_config.m_drat) m_drat.add(l, false); + assign(l); + } + } + propagate(); + m_qhead = m_trail.size(); + TRACE("sat", m_s.display(tout); display(tout);); + } + + void copy_clauses(clause_vector const& clauses) { // copy clauses - clause_vector::const_iterator it = s.m_clauses.begin(); - clause_vector::const_iterator end = s.m_clauses.end(); - for (; it != end; ++it) { + clause_vector::const_iterator it = clauses.begin(); + clause_vector::const_iterator end = clauses.end(); + for (; it != end; ++it) { clause& c = *(*it); + if (c.was_removed()) continue; clause* c1 = m_cls_allocator.mk_clause(c.size(), c.begin(), false); m_clauses.push_back(c1); attach_clause(*c1); for (unsigned i = 0; i < c.size(); ++i) { m_full_watches[(~c[i]).index()].push_back(c1); } - if (s.m_config.m_drat) m_drat.add(c, false); + if (m_s.m_config.m_drat) m_drat.add(c, false); } - - // copy units - unsigned trail_sz = s.init_trail_size(); - for (unsigned i = 0; i < trail_sz; ++i) { - literal l = s.m_trail[i]; - if (s.m_config.m_drat) m_drat.add(l, false); - assign(l); - } - propagate(); - m_qhead = m_trail.size(); - TRACE("sat", s.display(tout); display(tout);); } // ------------------------------------ @@ -1073,6 +1112,7 @@ namespace sat { SASSERT(m_search_mode == lookahead_mode::searching); m_binary_trail_lim.push_back(m_binary_trail.size()); m_trail_lim.push_back(m_trail.size()); + m_num_tc1_lim.push_back(m_num_tc1); m_retired_clause_lim.push_back(m_retired_clauses.size()); m_retired_ternary_lim.push_back(m_retired_ternary.size()); m_qhead_lim.push_back(m_qhead); @@ -1083,6 +1123,7 @@ namespace sat { } void pop() { + if (m_assumptions.empty()) IF_VERBOSE(0, verbose_stream() << "empty pop\n";); m_assumptions.pop_back(); m_inconsistent = false; SASSERT(m_search_mode == lookahead_mode::searching); @@ -1099,6 +1140,9 @@ namespace sat { } m_trail.shrink(old_sz); // reset assignment. m_trail_lim.pop_back(); + + m_num_tc1 = m_num_tc1_lim.back(); + m_num_tc1_lim.pop_back(); // unretire clauses old_sz = m_retired_clause_lim.back(); @@ -1178,7 +1222,7 @@ namespace sat { clause const& get_clause(watch_list::iterator it) const { clause_offset cls_off = it->get_clause_offset(); - return *(s.m_cls_allocator.get_clause(cls_off)); + return *(m_cls_allocator.get_clause(cls_off)); } bool is_nary_propagation(clause const& c, literal l) const { @@ -1259,7 +1303,7 @@ namespace sat { break; } clause_offset cls_off = it->get_clause_offset(); - clause & c = *(s.m_cls_allocator.get_clause(cls_off)); + clause & c = *(m_cls_allocator.get_clause(cls_off)); if (c[0] == ~l) std::swap(c[0], c[1]); if (is_true(c[0])) { @@ -1393,19 +1437,24 @@ namespace sat { TRACE("sat", display_lookahead(tout); ); unsigned base = 2; bool change = true; + bool first = true; while (change && !inconsistent()) { change = false; for (unsigned i = 0; !inconsistent() && i < m_lookahead.size(); ++i) { + checkpoint(); literal lit = m_lookahead[i].m_lit; if (is_fixed_at(lit, c_fixed_truth)) continue; unsigned level = base + m_lookahead[i].m_offset; if (m_stamp[lit.var()] >= level) { continue; } + if (scope_lvl() == 1) { + IF_VERBOSE(3, verbose_stream() << scope_lvl() << " " << lit << " binary: " << m_binary_trail.size() << " trail: " << m_trail_lim.back() << "\n";); + } TRACE("sat", tout << "lookahead: " << lit << " @ " << m_lookahead[i].m_offset << "\n";); reset_wnb(lit); push_lookahead1(lit, level); - do_double(lit, base); + if (!first) do_double(lit, base); bool unsat = inconsistent(); pop_lookahead1(lit); if (unsat) { @@ -1424,7 +1473,13 @@ namespace sat { if (c_fixed_truth - 2 * m_lookahead.size() < base) { break; } - base += 2 * m_lookahead.size(); + if (first && !change) { + first = false; + change = true; + } + reset_wnb(); + init_wnb(); + // base += 2 * m_lookahead.size(); } reset_wnb(); TRACE("sat", display_lookahead(tout); ); @@ -1461,7 +1516,7 @@ namespace sat { float mixd = mix_diff(diff1, diff2); if (mixd == h) ++count; - if (mixd > h || (mixd == h && s.m_rand(count) == 0)) { + if (mixd > h || (mixd == h && m_s.m_rand(count) == 0)) { CTRACE("sat", l != null_literal, tout << lit << " mix diff: " << mixd << "\n";); if (mixd > h) count = 1; h = mixd; @@ -1487,6 +1542,7 @@ namespace sat { } bool check_autarky(literal l, unsigned level) { + return false; // no propagations are allowed to reduce clauses. clause_vector::const_iterator it = m_full_watches[l.index()].begin(); clause_vector::const_iterator end = m_full_watches[l.index()].end(); @@ -1568,7 +1624,7 @@ namespace sat { } void do_double(literal l, unsigned& base) { - if (!inconsistent() && scope_lvl() > 0 && dl_enabled(l)) { + if (!inconsistent() && scope_lvl() > 1 && dl_enabled(l)) { if (get_wnb(l) > m_delta_trigger) { if (dl_no_overflow(base)) { ++m_stats.m_double_lookahead_rounds; @@ -1588,6 +1644,7 @@ namespace sat { SASSERT(dl_no_overflow(base)); unsigned dl_truth = base + 2 * m_lookahead.size() * (m_config.m_dl_max_iterations + 1); scoped_level _sl(*this, dl_truth); + IF_VERBOSE(2, verbose_stream() << "double: " << l << "\n";); init_wnb(); assign(l); propagate(); @@ -1624,7 +1681,7 @@ namespace sat { unsigned scope_lvl() const { return m_trail_lim.size(); } void validate_assign(literal l) { - if (s.m_config.m_drat && m_search_mode == lookahead_mode::searching) { + if (m_s.m_config.m_drat && m_search_mode == lookahead_mode::searching) { m_assumptions.push_back(l); m_drat.add(m_assumptions); m_assumptions.pop_back(); @@ -1670,7 +1727,7 @@ namespace sat { if (trail.empty()) return false; pop(); flip_prefix(); - assign(~trail.back()); + assign(~trail.back()); trail.pop_back(); propagate(); } @@ -1685,7 +1742,7 @@ namespace sat { while (true) { TRACE("sat", display(tout);); inc_istamp(); - s.checkpoint(); + checkpoint(); if (inconsistent()) { if (!backtrack(trail)) return l_false; continue; @@ -1709,7 +1766,7 @@ namespace sat { void init_model() { m_model.reset(); - for (unsigned i = 0; i < s.num_vars(); ++i) { + for (unsigned i = 0; i < m_num_vars; ++i) { lbool val; literal lit(i, false); if (is_undef(lit)) { @@ -1762,26 +1819,102 @@ namespace sat { return out; } + void init_search() { + m_search_mode = lookahead_mode::searching; + scoped_level _sl(*this, c_fixed_truth); + init(); + } + + void checkpoint() { + if (!m_rlimit.inc()) { + throw solver_exception(Z3_CANCELED_MSG); + } + if (memory::get_allocation_size() > m_s.m_config.m_max_memory) { + throw solver_exception(Z3_MAX_MEMORY_MSG); + } + } + public: lookahead(solver& s) : - s(s), + m_s(s), + m_num_vars(s.num_vars()), m_drat(s), + m_num_tc1(0), m_level(2), m_prefix(0) { - m_search_mode = lookahead_mode::searching; - scoped_level _sl(*this, c_fixed_truth); - init(); + m_s.rlimit().push_child(&m_rlimit); } ~lookahead() { del_clauses(); + m_s.rlimit().pop_child(); } lbool check() { + init_search(); return search(); } + void simplify() { + SASSERT(m_prefix == 0); + SASSERT(m_watches.empty()); + m_search_mode = lookahead_mode::searching; + scoped_level _sl(*this, c_fixed_truth); + init(); + if (inconsistent()) return; + inc_istamp(); + literal l = choose(); + if (inconsistent()) return; + SASSERT(m_trail_lim.empty()); + unsigned num_units = 0; + for (unsigned i = 0; i < m_trail.size(); ++i) { + literal lit = m_trail[i]; + if (m_s.value(lit) == l_undef && !m_s.was_eliminated(lit.var())) { + m_s.m_simplifier.propagate_unit(lit); + ++num_units; + } + } + IF_VERBOSE(1, verbose_stream() << "units found: " << num_units << "\n";); + + m_s.m_simplifier.subsume(); + m_lookahead.reset(); + } + + void scc() { + SASSERT(m_prefix == 0); + SASSERT(m_watches.empty()); + m_search_mode = lookahead_mode::searching; + scoped_level _sl(*this, c_fixed_truth); + init(); + if (inconsistent()) return; + inc_istamp(); + m_lookahead.reset(); + if (select(0)) { + // extract equivalences + get_scc(); + if (inconsistent()) return; + literal_vector roots; + bool_var_vector to_elim; + for (unsigned i = 0; i < m_num_vars; ++i) { + roots.push_back(literal(i, false)); + } + for (unsigned i = 0; i < m_candidates.size(); ++i) { + bool_var v = m_candidates[i].m_var; + literal lit = literal(v, false); + literal p = get_parent(lit); + if (p != null_literal && p.var() != v && !m_s.is_external(v) && !m_s.was_eliminated(v) && !m_s.was_eliminated(p.var())) { + to_elim.push_back(v); + roots[v] = p; + } + } + IF_VERBOSE(1, verbose_stream() << "eliminate " << to_elim.size() << " variables\n";); + elim_eqs elim(m_s); + elim(roots, to_elim); + } + m_lookahead.reset(); + } + std::ostream& display(std::ostream& out) const { out << "Prefix: " << pp_prefix(m_prefix, m_trail_lim.size()) << "\n"; out << "Level: " << m_level << "\n"; diff --git a/src/sat/sat_parallel.cpp b/src/sat/sat_parallel.cpp index 67d446a29..0b9a5bcb3 100644 --- a/src/sat/sat_parallel.cpp +++ b/src/sat/sat_parallel.cpp @@ -115,12 +115,16 @@ namespace sat { m_solvers[i] = alloc(sat::solver, s.m_params, m_limits[i]); m_solvers[i]->copy(s); m_solvers[i]->set_par(this, i); - m_scoped_rlimit.push_child(&m_solvers[i]->rlimit()); + push_child(m_solvers[i]->rlimit()); } s.set_par(this, num_extra_solvers); s.m_params.set_sym("phase", saved_phase); } + void parallel::push_child(reslimit& rl) { + m_scoped_rlimit.push_child(&rl); + } + void parallel::exchange(solver& s, literal_vector const& in, unsigned& limit, literal_vector& out) { if (s.m_par_syncing_clauses) return; @@ -268,12 +272,6 @@ namespace sat { { if (m_solver_copy && s.num_non_binary_clauses() > m_solver_copy->m_clauses.size()) { s.import(*m_solver_copy.get(), true); - m_num_clauses = s.num_non_binary_clauses(); - SASSERT(s.num_non_binary_clauses() == m_solver_copy->m_clauses.size()); - m_solver_copy = 0; - } - if (m_num_clauses < s.num_non_binary_clauses()) { - m_num_clauses = s.num_non_binary_clauses(); } for (unsigned i = 0; i < m_phase.size(); ++i) { s.set_phase(i, m_phase[i]); diff --git a/src/sat/sat_parallel.h b/src/sat/sat_parallel.h index b93384bd6..ffdfddf55 100644 --- a/src/sat/sat_parallel.h +++ b/src/sat/sat_parallel.h @@ -78,6 +78,8 @@ namespace sat { void init_solvers(solver& s, unsigned num_extra_solvers); + void push_child(reslimit& rl); + // reserve space void reserve(unsigned num_owners, unsigned sz) { m_pool.reserve(num_owners, sz); } diff --git a/src/sat/sat_params.pyg b/src/sat/sat_params.pyg index 045fd803a..226c79642 100644 --- a/src/sat/sat_params.pyg +++ b/src/sat/sat_params.pyg @@ -27,6 +27,10 @@ def_module_params('sat', ('drat.file', SYMBOL, '', 'file to dump DRAT proofs'), ('drat.check', BOOL, False, 'build up internal proof and check'), ('cardinality.solver', BOOL, False, 'use cardinality solver'), + ('pb.solver', BOOL, False, 'use pb solver'), ('xor.solver', BOOL, False, 'use xor solver'), - ('local_search', UINT, 0, 'number of local search threads to find satisfiable solution'), + ('local_search_threads', UINT, 0, 'number of local search threads to find satisfiable solution'), + ('local_search', BOOL, False, 'use local search instead of CDCL'), + ('lookahead_search', BOOL, False, 'use lookahead solver'), + ('ccc', BOOL, False, 'use Concurrent Cube and Conquer solver') )) diff --git a/src/sat/sat_scc.cpp b/src/sat/sat_scc.cpp index ffbdb31c6..ec77ebfbd 100644 --- a/src/sat/sat_scc.cpp +++ b/src/sat/sat_scc.cpp @@ -98,13 +98,13 @@ namespace sat { watch_list & wlist = m_solver.get_wlist(LIDX); \ frames.push_back(frame(LIDX, wlist.begin(), wlist.end())); \ } - + NEW_NODE(l_idx); - + while (!frames.empty()) { loop: - frame & fr = frames.back(); - unsigned l_idx = fr.m_lidx; + frame & fr = frames.back(); + unsigned l_idx = fr.m_lidx; if (!fr.m_first) { // after visiting child literal l2 = fr.m_it->get_literal(); @@ -136,20 +136,19 @@ namespace sat { if (lowlink[l_idx] == index[l_idx]) { // found new SCC CTRACE("scc_cycle", s.back() != l_idx, { - tout << "cycle: "; - unsigned j = s.size() - 1; - unsigned l2_idx; - do { - l2_idx = s[j]; - j--; - tout << to_literal(l2_idx) << " "; - } - while (l2_idx != l_idx); - tout << "\n"; - }); + tout << "cycle: "; + unsigned j = s.size() - 1; + unsigned l2_idx; + do { + l2_idx = s[j]; + j--; + tout << to_literal(l2_idx) << " "; + } while (l2_idx != l_idx); + tout << "\n"; + }); SASSERT(!s.empty()); - literal l = to_literal(l_idx); + literal l = to_literal(l_idx); bool_var v = l.var(); if (roots[v] != null_literal) { // variable was already assigned... just consume stack @@ -158,10 +157,9 @@ namespace sat { do { l2_idx = s.back(); s.pop_back(); - in_s[l2_idx] = false; + in_s[l2_idx] = false; SASSERT(roots[to_literal(l2_idx).var()].var() == roots[v].var()); - } - while (l2_idx != l_idx); + } while (l2_idx != l_idx); } else { // check if the SCC has an external variable, and check for conflicts @@ -180,20 +178,19 @@ namespace sat { r = to_literal(l2_idx); break; } - } - while (l2_idx != l_idx); - + } while (l2_idx != l_idx); + if (r == null_literal) { // SCC does not contain external variable r = to_literal(l_idx); } - + TRACE("scc_detail", tout << "r: " << r << "\n";); do { l2_idx = s.back(); s.pop_back(); - in_s[l2_idx] = false; + in_s[l2_idx] = false; literal l2 = to_literal(l2_idx); bool_var v2 = l2.var(); if (roots[v2] == null_literal) { @@ -203,16 +200,20 @@ namespace sat { else { roots[v2] = r; } - if (v2 != r.var()) + if (v2 != r.var()) to_elim.push_back(v2); } - } - while (l2_idx != l_idx); + } while (l2_idx != l_idx); } } frames.pop_back(); } } + for (unsigned i = 0; i < m_solver.num_vars(); ++i) { + if (roots[i] == null_literal) { + roots[i] = literal(i, false); + } + } TRACE("scc", for (unsigned i = 0; i < roots.size(); i++) { tout << i << " -> " << roots[i] << "\n"; } tout << "to_elim: "; for (unsigned i = 0; i < to_elim.size(); i++) tout << to_elim[i] << " "; tout << "\n";); m_num_elim += to_elim.size(); diff --git a/src/sat/sat_simplifier.cpp b/src/sat/sat_simplifier.cpp index fe019427f..2687c59e5 100644 --- a/src/sat/sat_simplifier.cpp +++ b/src/sat/sat_simplifier.cpp @@ -21,6 +21,7 @@ Revision History: #include"sat_simplifier.h" #include"sat_simplifier_params.hpp" #include"sat_solver.h" +#include"sat_lookahead.h" #include"stopwatch.h" #include"trace.h" @@ -158,6 +159,8 @@ namespace sat { if (!m_subsumption && !m_elim_blocked_clauses && !m_resolution) return; + // solver::scoped_disable_checkpoint _scoped_disable_checkpoint(s); + initialize(); CASSERT("sat_solver", s.check_invariant()); @@ -171,15 +174,13 @@ namespace sat { CASSERT("sat_solver", s.check_invariant()); m_need_cleanup = false; m_use_list.init(s.num_vars()); - init_visited(); - bool learned_in_use_lists = false; + m_learned_in_use_lists = false; if (learned) { register_clauses(s.m_learned); - learned_in_use_lists = true; + m_learned_in_use_lists = true; } register_clauses(s.m_clauses); - if (!learned && (m_elim_blocked_clauses || m_elim_blocked_clauses_at == m_num_calls)) elim_blocked_clauses(); @@ -188,7 +189,9 @@ namespace sat { m_sub_counter = m_subsumption_limit; m_elim_counter = m_res_limit; - unsigned old_num_elim_vars = m_num_elim_vars; + m_old_num_elim_vars = m_num_elim_vars; + + // scoped_finalize _scoped_finalize(*this); do { if (m_subsumption) @@ -204,24 +207,31 @@ namespace sat { } while (!m_sub_todo.empty()); - bool vars_eliminated = m_num_elim_vars > old_num_elim_vars; + if (!learned) { + // perform lookahead simplification + lookahead(s).simplify(); + } + + bool vars_eliminated = m_num_elim_vars > m_old_num_elim_vars; if (m_need_cleanup) { TRACE("after_simplifier", tout << "cleanning watches...\n";); cleanup_watches(); - cleanup_clauses(s.m_learned, true, vars_eliminated, learned_in_use_lists); + cleanup_clauses(s.m_learned, true, vars_eliminated, m_learned_in_use_lists); cleanup_clauses(s.m_clauses, false, vars_eliminated, true); } else { TRACE("after_simplifier", tout << "skipping cleanup...\n";); if (vars_eliminated) { // must remove learned clauses with eliminated variables - cleanup_clauses(s.m_learned, true, true, learned_in_use_lists); + cleanup_clauses(s.m_learned, true, true, m_learned_in_use_lists); } } + CASSERT("sat_solver", s.check_invariant()); TRACE("after_simplifier", s.display(tout); tout << "model_converter:\n"; s.m_mc.display(tout);); finalize(); + } /** @@ -283,7 +293,7 @@ namespace sat { unsigned sz = c.size(); if (sz == 0) { s.set_conflict(justification()); - for (; it != end; ++it) { + for (; it != end; ++it, ++it2) { *it2 = *it; ++it2; } diff --git a/src/sat/sat_simplifier.h b/src/sat/sat_simplifier.h index 9ee239083..0f0b1d71e 100644 --- a/src/sat/sat_simplifier.h +++ b/src/sat/sat_simplifier.h @@ -91,6 +91,9 @@ namespace sat { unsigned m_num_sub_res; unsigned m_num_elim_lits; + bool m_learned_in_use_lists; + unsigned m_old_num_elim_vars; + struct size_lt { bool operator()(clause const * c1, clause const * c2) const { return c1->size() > c2->size(); } }; @@ -130,13 +133,11 @@ namespace sat { bool cleanup_clause(clause & c, bool in_use_list); bool cleanup_clause(literal_vector & c); - void propagate_unit(literal l); void elim_lit(clause & c, literal l); void elim_dup_bins(); bool subsume_with_binaries(); void mark_as_not_learned_core(watch_list & wlist, literal l2); void mark_as_not_learned(literal l1, literal l2); - void subsume(); void cleanup_watches(); void cleanup_clauses(clause_vector & cs, bool learned, bool vars_eliminated, bool in_use_lists); @@ -170,6 +171,14 @@ namespace sat { struct subsumption_report; struct elim_var_report; + class scoped_finalize { + simplifier& s; + public: + scoped_finalize(simplifier& s) : s(s) {} + ~scoped_finalize() { s.scoped_finalize_fn(); } + }; + void scoped_finalize_fn(); + public: simplifier(solver & s, params_ref const & p); ~simplifier(); @@ -191,6 +200,10 @@ namespace sat { void collect_statistics(statistics & st) const; void reset_statistics(); + + void propagate_unit(literal l); + void subsume(); + }; }; diff --git a/src/sat/sat_solver.cpp b/src/sat/sat_solver.cpp index 66ebb189f..3df718527 100644 --- a/src/sat/sat_solver.cpp +++ b/src/sat/sat_solver.cpp @@ -22,6 +22,8 @@ Revision History: #include"trace.h" #include"max_cliques.h" #include"scoped_ptr_vector.h" +#include"sat_lookahead.h" +#include"sat_ccc.h" // define to update glue during propagation #define UPDATE_GLUE @@ -34,6 +36,7 @@ namespace sat { solver::solver(params_ref const & p, reslimit& l): m_rlimit(l), + m_checkpoint_enabled(true), m_config(p), m_par(0), m_par_syncing_clauses(false), @@ -119,7 +122,7 @@ namespace sat { if (!it->is_binary_non_learned_clause()) continue; literal l2 = it->get_literal(); - if (l.index() > l2.index()) + if (l.index() > l2.index()) continue; mk_clause_core(l, l2); } @@ -299,7 +302,7 @@ namespace sat { void solver::push_reinit_stack(clause & c) { TRACE("sat_reinit", tout << "adding to reinit stack: " << c << "\n";); m_clauses_to_reinit.push_back(clause_wrapper(c)); - c.set_reinit_stack(true); + c.set_reinit_stack(true); } @@ -518,26 +521,26 @@ namespace sat { return simplify_clause_core(num_lits, lits); } - void solver::dettach_bin_clause(literal l1, literal l2, bool learned) { + void solver::detach_bin_clause(literal l1, literal l2, bool learned) { get_wlist(~l1).erase(watched(l2, learned)); get_wlist(~l2).erase(watched(l1, learned)); if (m_config.m_drat) m_drat.del(l1, l2); } - void solver::dettach_clause(clause & c) { + void solver::detach_clause(clause & c) { if (c.size() == 3) - dettach_ter_clause(c); + detach_ter_clause(c); else - dettach_nary_clause(c); + detach_nary_clause(c); } - void solver::dettach_nary_clause(clause & c) { + void solver::detach_nary_clause(clause & c) { clause_offset cls_off = get_offset(c); erase_clause_watch(get_wlist(~c[0]), cls_off); erase_clause_watch(get_wlist(~c[1]), cls_off); } - void solver::dettach_ter_clause(clause & c) { + void solver::detach_ter_clause(clause & c) { erase_ternary_watch(get_wlist(~c[0]), c[1], c[2]); erase_ternary_watch(get_wlist(~c[1]), c[0], c[2]); erase_ternary_watch(get_wlist(~c[2]), c[0], c[1]); @@ -783,7 +786,16 @@ namespace sat { pop_to_base_level(); IF_VERBOSE(2, verbose_stream() << "(sat.sat-solver)\n";); SASSERT(at_base_lvl()); - if ((m_config.m_num_threads > 1 || m_config.m_local_search > 0) && !m_par) { + if (m_config.m_lookahead_search && num_lits == 0) { + return lookahead_search(); + } + if (m_config.m_local_search) { + return do_local_search(num_lits, lits); + } + if (m_config.m_ccc && num_lits == 0) { + return do_ccc(); + } + if ((m_config.m_num_threads > 1 || m_config.m_local_search_threads > 0) && !m_par) { return check_par(num_lits, lits); } flet _searching(m_searching, true); @@ -855,12 +867,46 @@ namespace sat { ERROR_EX }; + lbool solver::do_local_search(unsigned num_lits, literal const* lits) { + scoped_limits scoped_rl(rlimit()); + local_search srch; + srch.config().set_seed(m_config.m_random_seed); + srch.import(*this, false); + scoped_rl.push_child(&srch.rlimit()); + lbool r = srch.check(num_lits, lits, 0); + m_model = srch.get_model(); + // srch.collect_statistics(m_aux_stats); + return r; + } + + lbool solver::do_ccc() { + ccc c(*this); + lbool r = c.search(); + m_model = c.get_model(); + c.collect_statistics(m_aux_stats); + return r; + } + + lbool solver::lookahead_search() { + lookahead lh(*this); + lbool r = l_undef; + try { + r = lh.check(); + m_model = lh.get_model(); + } + catch (z3_exception&) { + lh.collect_statistics(m_aux_stats); + throw; + } + lh.collect_statistics(m_aux_stats); + return r; + } lbool solver::check_par(unsigned num_lits, literal const* lits) { scoped_ptr_vector ls; - int num_threads = static_cast(m_config.m_num_threads + m_config.m_local_search); + int num_threads = static_cast(m_config.m_num_threads + m_config.m_local_search_threads); int num_extra_solvers = m_config.m_num_threads - 1; - int num_local_search = static_cast(m_config.m_local_search); + int num_local_search = static_cast(m_config.m_local_search_threads); for (int i = 0; i < num_local_search; ++i) { local_search* l = alloc(local_search); l->config().set_seed(m_config.m_random_seed + i); @@ -875,15 +921,18 @@ namespace sat { sat::parallel par(*this); par.reserve(num_threads, 1 << 12); par.init_solvers(*this, num_extra_solvers); + for (unsigned i = 0; i < ls.size(); ++i) { + par.push_child(ls[i]->rlimit()); + } int finished_id = -1; std::string ex_msg; - par_exception_kind ex_kind; + par_exception_kind ex_kind = DEFAULT_EX; unsigned error_code = 0; lbool result = l_undef; bool canceled = false; #pragma omp parallel for for (int i = 0; i < num_threads; ++i) { - try { + try { lbool r = l_undef; if (IS_AUX_SOLVER(i)) { r = par.get_solver(i).check(num_lits, lits); @@ -918,7 +967,7 @@ namespace sat { rlimit().cancel(); } } - } + } } catch (z3_error & err) { error_code = err.error_code(); @@ -1223,7 +1272,7 @@ namespace sat { for (unsigned i = 0; !inconsistent() && i < m_assumptions.size(); ++i) { assign(m_assumptions[i], justification()); } - TRACE("sat", + TRACE("sat", for (unsigned i = 0; i < m_assumptions.size(); ++i) { index_set s; if (m_antecedents.find(m_assumptions[i].var(), s)) { @@ -1292,6 +1341,8 @@ namespace sat { CASSERT("sat_simplify_bug", check_invariant()); } + lookahead(*this).scc(); + sort_watch_lits(); CASSERT("sat_simplify_bug", check_invariant()); @@ -1575,7 +1626,7 @@ namespace sat { for (unsigned i = new_sz; i < sz; i++) { clause & c = *(m_learned[i]); if (can_delete(c)) { - dettach_clause(c); + detach_clause(c); del_clause(c); } else { @@ -1633,7 +1684,7 @@ namespace sat { else { c.inc_inact_rounds(); if (c.inact_rounds() > m_config.m_gc_k) { - dettach_clause(c); + detach_clause(c); del_clause(c); m_stats.m_gc_clause++; deleted++; @@ -1644,7 +1695,7 @@ namespace sat { if (psm(c) > static_cast(c.size() * m_min_d_tk)) { // move to frozen; TRACE("sat_frozen", tout << "freezing size: " << c.size() << " psm: " << psm(c) << " " << c << "\n";); - dettach_clause(c); + detach_clause(c); c.reset_inact_rounds(); c.freeze(); m_num_frozen++; @@ -2582,7 +2633,7 @@ namespace sat { } else { clause & c = *(cw.get_clause()); - dettach_clause(c); + detach_clause(c); attach_clause(c, reinit); if (!at_base_lvl() && reinit) { // clause propagated literal, must keep it in the reinit stack. @@ -2615,7 +2666,7 @@ namespace sat { for (unsigned i = 0; i < clauses.size(); ++i) { clause & c = *(clauses[i]); if (c.contains(lit)) { - dettach_clause(c); + detach_clause(c); del_clause(c); } else { @@ -2633,7 +2684,7 @@ namespace sat { literal l1 = m_user_bin_clauses[i].first; literal l2 = m_user_bin_clauses[i].second; if (nlit == l1 || nlit == l2) { - dettach_bin_clause(l1, l2, learned); + detach_bin_clause(l1, l2, learned); } } } @@ -2759,6 +2810,7 @@ namespace sat { m_asymm_branch.collect_statistics(st); m_probing.collect_statistics(st); if (m_ext) m_ext->collect_statistics(st); + st.copy(m_aux_stats); } void solver::reset_statistics() { @@ -2767,6 +2819,7 @@ namespace sat { m_simplifier.reset_statistics(); m_asymm_branch.reset_statistics(); m_probing.reset_statistics(); + m_aux_stats.reset(); } // ----------------------- @@ -3120,7 +3173,7 @@ namespace sat { literal_pair p(l1, l2); if (!seen_bc.contains(p)) { seen_bc.insert(p); - mc.add_edge(l1.index(), l2.index()); + mc.add_edge(l1.index(), l2.index()); } } vector _mutexes; @@ -3149,6 +3202,101 @@ namespace sat { // // ----------------------- + static void prune_unfixed(sat::literal_vector& lambda, sat::model const& m) { + for (unsigned i = 0; i < lambda.size(); ++i) { + if ((m[lambda[i].var()] == l_false) != lambda[i].sign()) { + lambda[i] = lambda.back(); + lambda.pop_back(); + --i; + } + } + } + + // Algorithm 7: Corebased Algorithm with Chunking + + static void back_remove(sat::literal_vector& lits, sat::literal l) { + for (unsigned i = lits.size(); i > 0; ) { + --i; + if (lits[i] == l) { + lits[i] = lits.back(); + lits.pop_back(); + return; + } + } + UNREACHABLE(); + } + + static void brute_force_consequences(sat::solver& s, sat::literal_vector const& asms, sat::literal_vector const& gamma, vector& conseq) { + for (unsigned i = 0; i < gamma.size(); ++i) { + sat::literal nlit = ~gamma[i]; + sat::literal_vector asms1(asms); + asms1.push_back(nlit); + lbool r = s.check(asms1.size(), asms1.c_ptr()); + if (r == l_false) { + conseq.push_back(s.get_core()); + } + } + } + + static lbool core_chunking(sat::solver& s, model const& m, sat::bool_var_vector const& vars, sat::literal_vector const& asms, vector& conseq, unsigned K) { + sat::literal_vector lambda; + for (unsigned i = 0; i < vars.size(); i++) { + lambda.push_back(sat::literal(vars[i], m[vars[i]] == l_false)); + } + while (!lambda.empty()) { + IF_VERBOSE(1, verbose_stream() << "(sat-backbone-core " << lambda.size() << " " << conseq.size() << ")\n";); + unsigned k = std::min(K, lambda.size()); + sat::literal_vector gamma, omegaN; + for (unsigned i = 0; i < k; ++i) { + sat::literal l = lambda[lambda.size() - i - 1]; + gamma.push_back(l); + omegaN.push_back(~l); + } + while (true) { + sat::literal_vector asms1(asms); + asms1.append(omegaN); + lbool r = s.check(asms1.size(), asms1.c_ptr()); + if (r == l_true) { + IF_VERBOSE(1, verbose_stream() << "(sat) " << omegaN << "\n";); + prune_unfixed(lambda, s.get_model()); + break; + } + sat::literal_vector const& core = s.get_core(); + sat::literal_vector occurs; + IF_VERBOSE(1, verbose_stream() << "(core " << core.size() << ")\n";); + for (unsigned i = 0; i < omegaN.size(); ++i) { + if (core.contains(omegaN[i])) { + occurs.push_back(omegaN[i]); + } + } + if (occurs.size() == 1) { + sat::literal lit = occurs.back(); + sat::literal nlit = ~lit; + conseq.push_back(core); + back_remove(lambda, ~lit); + back_remove(gamma, ~lit); + s.mk_clause(1, &nlit); + } + for (unsigned i = 0; i < omegaN.size(); ++i) { + if (occurs.contains(omegaN[i])) { + omegaN[i] = omegaN.back(); + omegaN.pop_back(); + --i; + } + } + if (omegaN.empty() && occurs.size() > 1) { + brute_force_consequences(s, asms, gamma, conseq); + for (unsigned i = 0; i < gamma.size(); ++i) { + back_remove(lambda, gamma[i]); + } + break; + } + } + } + return l_true; + } + + lbool solver::get_consequences(literal_vector const& asms, bool_var_vector const& vars, vector& conseq) { literal_vector lits; lbool is_sat = l_true; @@ -3171,13 +3319,19 @@ namespace sat { default: break; } } - is_sat = get_consequences(asms, lits, conseq); + + if (false && asms.empty()) { + is_sat = core_chunking(*this, mdl, vars, asms, conseq, 100); + } + else { + is_sat = get_consequences(asms, lits, conseq); + } set_model(mdl); return is_sat; } void solver::fixup_consequence_core() { - index_set s; + index_set s; TRACE("sat", tout << m_core << "\n";); for (unsigned i = 0; i < m_core.size(); ++i) { TRACE("sat", tout << m_core[i] << ": "; display_index_set(tout, m_antecedents.find(m_core[i].var())) << "\n";); @@ -3227,20 +3381,20 @@ namespace sat { while (true) { ++num_iterations; SASSERT(!inconsistent()); - + lbool r = bounded_search(); if (r != l_undef) { fixup_consequence_core(); return r; } - + extract_fixed_consequences(num_units, asms, unfixed_vars, conseq); if (m_conflicts > m_config.m_max_conflicts) { IF_VERBOSE(SAT_VB_LVL, verbose_stream() << "(sat \"abort: max-conflicts = " << m_conflicts << "\")\n";); return l_undef; } - + restart(); simplify_problem(); if (check_inconsistent()) { @@ -3248,11 +3402,11 @@ namespace sat { return l_false; } gc(); - + if (m_config.m_restart_max <= num_iterations) { IF_VERBOSE(SAT_VB_LVL, verbose_stream() << "(sat \"abort: max-restarts\")\n";); return l_undef; - } + } } } @@ -3282,24 +3436,32 @@ namespace sat { if (check_inconsistent()) return l_false; SASSERT(search_lvl() == 1); - unsigned num_units = 0, num_iterations = 0; - extract_fixed_consequences(num_units, assumptions, unfixed_vars, conseq); + unsigned num_iterations = 0; + extract_fixed_consequences(unfixed_lits, assumptions, unfixed_vars, conseq); update_unfixed_literals(unfixed_lits, unfixed_vars); while (!unfixed_lits.empty()) { if (scope_lvl() > search_lvl()) { pop(scope_lvl() - search_lvl()); } + propagate(false); ++num_iterations; checkpoint(); literal_set::iterator it = unfixed_lits.begin(), end = unfixed_lits.end(); unsigned num_resolves = 0; + unsigned num_fixed = 0; + unsigned num_assigned = 0; lbool is_sat = l_true; for (; it != end; ++it) { - literal lit = *it; + literal lit = *it; if (value(lit) != l_undef) { + ++num_fixed; + if (lvl(lit) <= 1 && value(lit) == l_true) { + extract_fixed_consequences(lit, assumptions, unfixed_vars, conseq); + } continue; } push(); + ++num_assigned; assign(~lit, justification()); propagate(false); while (inconsistent()) { @@ -3312,12 +3474,17 @@ namespace sat { propagate(false); ++num_resolves; } - if (scope_lvl() == search_lvl()) { + if (false && scope_lvl() == search_lvl()) { + is_sat = l_undef; break; } } + + extract_fixed_consequences(unfixed_lits, assumptions, unfixed_vars, conseq); + if (is_sat == l_true) { if (scope_lvl() == search_lvl() && num_resolves > 0) { + IF_VERBOSE(1, verbose_stream() << "(sat.get-consequences backjump)\n";); is_sat = l_undef; } else { @@ -3325,6 +3492,7 @@ namespace sat { if (is_sat == l_undef) { restart(); } + extract_fixed_consequences(unfixed_lits, assumptions, unfixed_vars, conseq); } } if (is_sat == l_false) { @@ -3334,12 +3502,13 @@ namespace sat { if (is_sat == l_true) { delete_unfixed(unfixed_lits, unfixed_vars); } - extract_fixed_consequences(num_units, assumptions, unfixed_vars, conseq); update_unfixed_literals(unfixed_lits, unfixed_vars); IF_VERBOSE(1, verbose_stream() << "(sat.get-consequences" << " iterations: " << num_iterations << " variables: " << unfixed_lits.size() << " fixed: " << conseq.size() + << " status: " << is_sat + << " pre-assigned: " << num_fixed << " unfixed: " << lits.size() - conseq.size() - unfixed_lits.size() << ")\n";); @@ -3384,32 +3553,49 @@ namespace sat { SASSERT(!inconsistent()); unsigned sz = m_trail.size(); for (unsigned i = start; i < sz && lvl(m_trail[i]) <= 1; ++i) { - if (!extract_fixed_consequences(m_trail[i], assumptions, unfixed, conseq)) { - for (i = 0; i < sz && lvl(m_trail[i]) <= 1; ++i) { - VERIFY(extract_fixed_consequences(m_trail[i], assumptions, unfixed, conseq)); - } - break; - } + extract_fixed_consequences(m_trail[i], assumptions, unfixed, conseq); } start = sz; } + void solver::extract_fixed_consequences(literal_set const& unfixed_lits, literal_set const& assumptions, bool_var_set& unfixed_vars, vector& conseq) { + literal_set::iterator it = unfixed_lits.begin(), end = unfixed_lits.end(); + for (; it != end; ++it) { + literal lit = *it; + TRACE("sat", tout << "extract: " << lit << " " << value(lit) << " " << lvl(lit) << "\n";); + + if (lvl(lit) <= 1 && value(lit) == l_true) { + extract_fixed_consequences(lit, assumptions, unfixed_vars, conseq); + } + } + } + bool solver::check_domain(literal lit, literal lit2) { - return m_antecedents.contains(lit2.var()); + if (!m_antecedents.contains(lit2.var())) { + SASSERT(value(lit2) == l_true); + SASSERT(m_todo_antecedents.empty() || m_todo_antecedents.back() != lit2); + m_todo_antecedents.push_back(lit2); + return false; + } + else { + return true; + } } bool solver::extract_assumptions(literal lit, index_set& s) { justification js = m_justification[lit.var()]; + TRACE("sat", tout << lit << " " << js << "\n";); + bool all_found = true; switch (js.get_kind()) { case justification::NONE: break; case justification::BINARY: - if (!check_domain(lit, js.get_literal())) return false; + if (!check_domain(lit, ~js.get_literal())) return false; s |= m_antecedents.find(js.get_literal().var()); break; case justification::TERNARY: - if (!check_domain(lit, js.get_literal1())) return false; - if (!check_domain(lit, js.get_literal2())) return false; + if (!check_domain(lit, ~js.get_literal1()) || + !check_domain(lit, ~js.get_literal2())) return false; s |= m_antecedents.find(js.get_literal1().var()); s |= m_antecedents.find(js.get_literal2().var()); break; @@ -3417,8 +3603,12 @@ namespace sat { clause & c = *(m_cls_allocator.get_clause(js.get_clause_offset())); for (unsigned i = 0; i < c.size(); ++i) { if (c[i] != lit) { - if (!check_domain(lit, c[i])) return false; - s |= m_antecedents.find(c[i].var()); + if (check_domain(lit, ~c[i]) && all_found) { + s |= m_antecedents.find(c[i].var()); + } + else { + all_found = false; + } } } break; @@ -3428,8 +3618,12 @@ namespace sat { literal_vector::iterator it = m_ext_antecedents.begin(); literal_vector::iterator end = m_ext_antecedents.end(); for (; it != end; ++it) { - if (!check_domain(lit, *it)) return false; - s |= m_antecedents.find(it->var()); + if (check_domain(lit, *it) && all_found) { + s |= m_antecedents.find(it->var()); + } + else { + all_found = false; + } } break; } @@ -3438,7 +3632,7 @@ namespace sat { break; } TRACE("sat", display_index_set(tout << lit << ": " , s) << "\n";); - return true; + return all_found; } std::ostream& solver::display_index_set(std::ostream& out, index_set const& s) const { @@ -3451,7 +3645,7 @@ namespace sat { } - bool solver::extract_fixed_consequences(literal lit, literal_set const& assumptions, bool_var_set& unfixed, vector& conseq) { + bool solver::extract_fixed_consequences1(literal lit, literal_set const& assumptions, bool_var_set& unfixed, vector& conseq) { index_set s; if (m_antecedents.contains(lit.var())) { return true; @@ -3461,6 +3655,7 @@ namespace sat { } else { if (!extract_assumptions(lit, s)) { + SASSERT(!m_todo_antecedents.empty()); return false; } add_assumption(lit); @@ -3479,6 +3674,16 @@ namespace sat { return true; } + void solver::extract_fixed_consequences(literal lit, literal_set const& assumptions, bool_var_set& unfixed, vector& conseq) { + SASSERT(m_todo_antecedents.empty()); + m_todo_antecedents.push_back(lit); + while (!m_todo_antecedents.empty()) { + if (extract_fixed_consequences1(m_todo_antecedents.back(), assumptions, unfixed, conseq)) { + m_todo_antecedents.pop_back(); + } + } + } + void solver::asymmetric_branching() { if (!at_base_lvl() || inconsistent()) return; @@ -3602,6 +3807,7 @@ namespace sat { if (m_solver.m_num_frozen > 0) out << " :frozen " << m_solver.m_num_frozen; } + out << " :units " << m_solver.init_trail_size(); out << " :gc-clause " << m_solver.m_stats.m_gc_clause; out << mem_stat(); } diff --git a/src/sat/sat_solver.h b/src/sat/sat_solver.h index c35a0296c..0429fe624 100644 --- a/src/sat/sat_solver.h +++ b/src/sat/sat_solver.h @@ -74,6 +74,7 @@ namespace sat { struct abort_solver {}; protected: reslimit& m_rlimit; + bool m_checkpoint_enabled; config m_config; stats m_stats; scoped_ptr m_ext; @@ -141,6 +142,8 @@ namespace sat { unsigned m_par_num_vars; bool m_par_syncing_clauses; + statistics m_aux_stats; + void del_clauses(clause * const * begin, clause * const * end); friend class integrity_checker; @@ -158,6 +161,7 @@ namespace sat { friend class lookahead; friend class local_search; friend struct mk_stat; + friend class ccc; public: solver(params_ref const & p, reslimit& l); ~solver(); @@ -207,15 +211,44 @@ namespace sat { bool attach_nary_clause(clause & c); void attach_clause(clause & c, bool & reinit); void attach_clause(clause & c) { bool reinit; attach_clause(c, reinit); } + class scoped_detach { + solver& s; + clause& c; + bool m_deleted; + public: + scoped_detach(solver& s, clause& c): s(s), c(c), m_deleted(false) { + s.detach_clause(c); + } + ~scoped_detach() { + if (!m_deleted) s.attach_clause(c); + } + + void del_clause() { + if (!m_deleted) { + s.del_clause(c); + m_deleted = true; + } + } + }; + class scoped_disable_checkpoint { + solver& s; + public: + scoped_disable_checkpoint(solver& s): s(s) { + s.m_checkpoint_enabled = false; + } + ~scoped_disable_checkpoint() { + s.m_checkpoint_enabled = true; + } + }; unsigned select_watch_lit(clause const & cls, unsigned starting_at) const; unsigned select_learned_watch_lit(clause const & cls) const; bool simplify_clause(unsigned & num_lits, literal * lits) const; template bool simplify_clause_core(unsigned & num_lits, literal * lits) const; - void dettach_bin_clause(literal l1, literal l2, bool learned); - void dettach_clause(clause & c); - void dettach_nary_clause(clause & c); - void dettach_ter_clause(clause & c); + void detach_bin_clause(literal l1, literal l2, bool learned); + void detach_clause(clause & c); + void detach_nary_clause(clause & c); + void detach_ter_clause(clause & c); void push_reinit_stack(clause & c); // ----------------------- @@ -254,6 +287,7 @@ namespace sat { lbool status(clause const & c) const; clause_offset get_offset(clause const & c) const { return m_cls_allocator.get_offset(&c); } void checkpoint() { + if (!m_checkpoint_enabled) return; if (!m_rlimit.inc()) { m_mc.reset(); m_model_is_current = false; @@ -346,6 +380,9 @@ namespace sat { void sort_watch_lits(); void exchange_par(); lbool check_par(unsigned num_lits, literal const* lits); + lbool lookahead_search(); + lbool do_local_search(unsigned num_lits, literal const* lits); + lbool do_ccc(); // ----------------------- // @@ -462,13 +499,14 @@ namespace sat { lbool get_consequences(literal_vector const& assms, bool_var_vector const& vars, vector& conseq); // initialize and retrieve local search. - local_search& init_local_search(); + // local_search& init_local_search(); private: typedef hashtable index_set; u_map m_antecedents; + literal_vector m_todo_antecedents; vector m_binary_clause_graph; bool extract_assumptions(literal lit, index_set& s); @@ -485,7 +523,11 @@ namespace sat { void extract_fixed_consequences(unsigned& start, literal_set const& assumptions, bool_var_set& unfixed, vector& conseq); - bool extract_fixed_consequences(literal lit, literal_set const& assumptions, bool_var_set& unfixed, vector& conseq); + void extract_fixed_consequences(literal_set const& unfixed_lits, literal_set const& assumptions, bool_var_set& unfixed, vector& conseq); + + void extract_fixed_consequences(literal lit, literal_set const& assumptions, bool_var_set& unfixed, vector& conseq); + + bool extract_fixed_consequences1(literal lit, literal_set const& assumptions, bool_var_set& unfixed, vector& conseq); void update_unfixed_literals(literal_set& unfixed_lits, bool_var_set& unfixed_vars); diff --git a/src/sat/sat_solver/inc_sat_solver.cpp b/src/sat/sat_solver/inc_sat_solver.cpp index 0c0f82537..0d12c0a94 100644 --- a/src/sat/sat_solver/inc_sat_solver.cpp +++ b/src/sat/sat_solver/inc_sat_solver.cpp @@ -216,7 +216,8 @@ public: m_params.append(p); sat_params p1(p); m_params.set_bool("elim_vars", false); - m_params.set_bool("keep_cardinality_constraints", p1.cardinality_solver()); + m_params.set_bool("keep_cardinality_constraints", p1.pb_solver() || p1.cardinality_solver()); + m_params.set_bool("keep_pb_constraints", p1.pb_solver()); m_params.set_bool("xor_solver", p1.xor_solver()); m_solver.updt_params(m_params); m_optimize_model = m_params.get_bool("optimize_model", false); diff --git a/src/sat/tactic/goal2sat.cpp b/src/sat/tactic/goal2sat.cpp index 971843d55..9d07ec3e6 100644 --- a/src/sat/tactic/goal2sat.cpp +++ b/src/sat/tactic/goal2sat.cpp @@ -419,6 +419,103 @@ struct goal2sat::imp { } } + typedef std::pair wliteral; + + void check_unsigned(rational const& c) { + if (!c.is_unsigned()) { + throw default_exception("unsigned coefficient expected"); + } + } + + void convert_to_wlits(app* t, sat::literal_vector const& lits, svector& wlits) { + for (unsigned i = 0; i < lits.size(); ++i) { + rational c = pb.get_coeff(t, i); + check_unsigned(c); + wlits.push_back(std::make_pair(c.get_unsigned(), lits[i])); + } + } + + void convert_pb_args(app* t, svector& wlits) { + sat::literal_vector lits; + convert_pb_args(t->get_num_args(), lits); + convert_to_wlits(t, lits, wlits); + } + + void convert_pb_ge(app* t, bool root, bool sign) { + rational k = pb.get_k(t); + check_unsigned(k); + svector wlits; + convert_pb_args(t, wlits); + unsigned sz = m_result_stack.size(); + if (root) { + m_result_stack.reset(); + m_ext->add_pb_ge(sat::null_bool_var, wlits, k.get_unsigned()); + } + else { + sat::bool_var v = m_solver.mk_var(true); + sat::literal lit(v, sign); + m_ext->add_pb_ge(v, wlits, k.get_unsigned()); + TRACE("goal2sat", tout << "root: " << root << " lit: " << lit << "\n";); + m_result_stack.shrink(sz - t->get_num_args()); + m_result_stack.push_back(lit); + } + } + + void convert_pb_le(app* t, bool root, bool sign) { + rational k = pb.get_k(t); + k.neg(); + svector wlits; + convert_pb_args(t, wlits); + for (unsigned i = 0; i < wlits.size(); ++i) { + wlits[i].second.neg(); + k += rational(wlits[i].first); + } + check_unsigned(k); + unsigned sz = m_result_stack.size(); + if (root) { + m_result_stack.reset(); + m_ext->add_pb_ge(sat::null_bool_var, wlits, k.get_unsigned()); + } + else { + sat::bool_var v = m_solver.mk_var(true); + sat::literal lit(v, sign); + m_ext->add_pb_ge(v, wlits, k.get_unsigned()); + TRACE("goal2sat", tout << "root: " << root << " lit: " << lit << "\n";); + m_result_stack.shrink(sz - t->get_num_args()); + m_result_stack.push_back(lit); + } + } + + void convert_pb_eq(app* t, bool root, bool sign) { + rational k = pb.get_k(t); + SASSERT(k.is_unsigned()); + svector wlits; + convert_pb_args(t, wlits); + sat::bool_var v1 = root ? sat::null_bool_var : m_solver.mk_var(true); + sat::bool_var v2 = root ? sat::null_bool_var : m_solver.mk_var(true); + m_ext->add_pb_ge(v1, wlits, k.get_unsigned()); + k.neg(); + for (unsigned i = 0; i < wlits.size(); ++i) { + wlits[i].second.neg(); + k += rational(wlits[i].first); + } + check_unsigned(k); + m_ext->add_pb_ge(v2, wlits, k.get_unsigned()); + if (root) { + m_result_stack.reset(); + } + else { + sat::literal l1(v1, false), l2(v2, false); + sat::bool_var v = m_solver.mk_var(); + sat::literal l(v, false); + mk_clause(~l, l1); + mk_clause(~l, l2); + mk_clause(~l1, ~l2, l); + m_result_stack.shrink(m_result_stack.size() - t->get_num_args()); + m_result_stack.push_back(l); + } + } + void convert_at_least_k(app* t, rational k, bool root, bool sign) { SASSERT(k.is_unsigned()); sat::literal_vector lits; @@ -529,16 +626,28 @@ struct goal2sat::imp { convert_at_least_k(t, pb.get_k(t), root, sign); break; case OP_PB_LE: - SASSERT(pb.has_unit_coefficients(t)); - convert_at_most_k(t, pb.get_k(t), root, sign); + if (pb.has_unit_coefficients(t)) { + convert_at_most_k(t, pb.get_k(t), root, sign); + } + else { + convert_pb_le(t, root, sign); + } break; case OP_PB_GE: - SASSERT(pb.has_unit_coefficients(t)); - convert_at_least_k(t, pb.get_k(t), root, sign); + if (pb.has_unit_coefficients(t)) { + convert_at_least_k(t, pb.get_k(t), root, sign); + } + else { + convert_pb_ge(t, root, sign); + } break; case OP_PB_EQ: - SASSERT(pb.has_unit_coefficients(t)); - convert_eq_k(t, pb.get_k(t), root, sign); + if (pb.has_unit_coefficients(t)) { + convert_eq_k(t, pb.get_k(t), root, sign); + } + else { + convert_pb_eq(t, root, sign); + } break; default: UNREACHABLE(); diff --git a/src/shell/opt_frontend.cpp b/src/shell/opt_frontend.cpp index 9c9bc54ab..e8b152443 100644 --- a/src/shell/opt_frontend.cpp +++ b/src/shell/opt_frontend.cpp @@ -20,12 +20,12 @@ static opt::context* g_opt = 0; static double g_start_time = 0; static unsigned_vector g_handles; -class stream_buffer { +class opt_stream_buffer { std::istream & m_stream; int m_val; unsigned m_line; public: - stream_buffer(std::istream & s): + opt_stream_buffer(std::istream & s): m_stream(s), m_line(0) { m_val = m_stream.get(); @@ -111,7 +111,7 @@ public: class wcnf { opt::context& opt; ast_manager& m; - stream_buffer& in; + opt_stream_buffer& in; app_ref read_clause(unsigned& weight) { int parsed_lit; @@ -141,7 +141,7 @@ class wcnf { public: - wcnf(opt::context& opt, stream_buffer& in): opt(opt), m(opt.get_manager()), in(in) { + wcnf(opt::context& opt, opt_stream_buffer& in): opt(opt), m(opt.get_manager()), in(in) { opt.set_clausal(true); } @@ -180,7 +180,7 @@ public: class opb { opt::context& opt; ast_manager& m; - stream_buffer& in; + opt_stream_buffer& in; arith_util arith; app_ref parse_id() { @@ -264,7 +264,7 @@ class opb { opt.add_hard_constraint(t); } public: - opb(opt::context& opt, stream_buffer& in): + opb(opt::context& opt, opt_stream_buffer& in): opt(opt), m(opt.get_manager()), in(in), arith(m) {} @@ -348,7 +348,7 @@ static unsigned parse_opt(std::istream& in, bool is_wcnf) { g_opt = &opt; params_ref p = gparams::get_module("opt"); opt.updt_params(p); - stream_buffer _in(in); + opt_stream_buffer _in(in); if (is_wcnf) { wcnf wcnf(opt, _in); wcnf.parse(); diff --git a/src/smt/asserted_formulas.cpp b/src/smt/asserted_formulas.cpp index 26395f9ab..6598c3a05 100644 --- a/src/smt/asserted_formulas.cpp +++ b/src/smt/asserted_formulas.cpp @@ -288,7 +288,7 @@ void asserted_formulas::reduce() { } void asserted_formulas::eliminate_and() { - IF_IVERBOSE(10, verbose_stream() << "(smt.eliminating-and)\n";); + IF_VERBOSE(10, verbose_stream() << "(smt.eliminating-and)\n";); set_eliminate_and(true); reduce_asserted_formulas(); TRACE("after_elim_and", display(tout);); @@ -393,19 +393,19 @@ void asserted_formulas::find_macros_core() { } void asserted_formulas::find_macros() { - IF_IVERBOSE(10, verbose_stream() << "(smt.find-macros)\n";); + IF_VERBOSE(10, verbose_stream() << "(smt.find-macros)\n";); TRACE("before_find_macros", display(tout);); find_macros_core(); TRACE("after_find_macros", display(tout);); } void asserted_formulas::expand_macros() { - IF_IVERBOSE(10, verbose_stream() << "(smt.expand-macros)\n";); + IF_VERBOSE(10, verbose_stream() << "(smt.expand-macros)\n";); find_macros_core(); } void asserted_formulas::apply_quasi_macros() { - IF_IVERBOSE(10, verbose_stream() << "(smt.find-quasi-macros)\n";); + IF_VERBOSE(10, verbose_stream() << "(smt.find-quasi-macros)\n";); TRACE("before_quasi_macros", display(tout);); expr_ref_vector new_exprs(m_manager); proof_ref_vector new_prs(m_manager); @@ -423,7 +423,7 @@ void asserted_formulas::apply_quasi_macros() { } void asserted_formulas::nnf_cnf() { - IF_IVERBOSE(10, verbose_stream() << "(smt.nnf)\n";); + IF_VERBOSE(10, verbose_stream() << "(smt.nnf)\n";); nnf apply_nnf(m_manager, m_defined_names); expr_ref_vector new_exprs(m_manager); proof_ref_vector new_prs(m_manager); @@ -473,7 +473,7 @@ void asserted_formulas::nnf_cnf() { #define MK_SIMPLE_SIMPLIFIER(NAME, FUNCTOR_DEF, LABEL, MSG) \ void asserted_formulas::NAME() { \ - IF_IVERBOSE(10, verbose_stream() << "(smt." << MSG << ")\n";); \ + IF_VERBOSE(10, verbose_stream() << "(smt." << MSG << ")\n";); \ TRACE(LABEL, tout << "before:\n"; display(tout);); \ FUNCTOR_DEF; \ expr_ref_vector new_exprs(m_manager); \ @@ -508,13 +508,13 @@ void asserted_formulas::NAME() { MK_SIMPLE_SIMPLIFIER(apply_distribute_forall, distribute_forall functor(m_manager, *m_bsimp), "distribute_forall", "distribute-forall"); void asserted_formulas::reduce_and_solve() { - IF_IVERBOSE(10, verbose_stream() << "(smt.reducing)\n";); + IF_VERBOSE(10, verbose_stream() << "(smt.reducing)\n";); flush_cache(); // collect garbage reduce_asserted_formulas(); } void asserted_formulas::infer_patterns() { - IF_IVERBOSE(10, verbose_stream() << "(smt.pattern-inference)\n";); + IF_VERBOSE(10, verbose_stream() << "(smt.pattern-inference)\n";); TRACE("before_pattern_inference", display(tout);); pattern_inference infer(m_manager, m_params); expr_ref_vector new_exprs(m_manager); @@ -552,7 +552,7 @@ void asserted_formulas::commit(unsigned new_qhead) { } void asserted_formulas::eliminate_term_ite() { - IF_IVERBOSE(10, verbose_stream() << "(smt.eliminating-ite-term)\n";); + IF_VERBOSE(10, verbose_stream() << "(smt.eliminating-ite-term)\n";); TRACE("before_elim_term_ite", display(tout);); elim_term_ite elim(m_manager, m_defined_names); expr_ref_vector new_exprs(m_manager); @@ -589,7 +589,7 @@ void asserted_formulas::eliminate_term_ite() { } void asserted_formulas::propagate_values() { - IF_IVERBOSE(10, verbose_stream() << "(smt.constant-propagation)\n";); + IF_VERBOSE(10, verbose_stream() << "(smt.constant-propagation)\n";); TRACE("propagate_values", tout << "before:\n"; display(tout);); flush_cache(); bool found = false; @@ -673,7 +673,7 @@ void asserted_formulas::propagate_booleans() { flush_cache(); while (cont) { TRACE("propagate_booleans", tout << "before:\n"; display(tout);); - IF_IVERBOSE(10, verbose_stream() << "(smt.propagate-booleans)\n";); + IF_VERBOSE(10, verbose_stream() << "(smt.propagate-booleans)\n";); cont = false; unsigned i = m_asserted_qhead; unsigned sz = m_asserted_formulas.size(); @@ -716,7 +716,7 @@ void asserted_formulas::propagate_booleans() { #define MK_SIMPLIFIER(NAME, FUNCTOR, TAG, MSG, REDUCE) \ bool asserted_formulas::NAME() { \ - IF_IVERBOSE(10, verbose_stream() << "(smt." << MSG << ")\n";); \ + IF_VERBOSE(10, verbose_stream() << "(smt." << MSG << ")\n";); \ TRACE(TAG, ast_mark visited; display_ll(tout, visited);); \ FUNCTOR; \ bool changed = false; \ @@ -773,7 +773,7 @@ proof * asserted_formulas::get_inconsistency_proof() const { } void asserted_formulas::refine_inj_axiom() { - IF_IVERBOSE(10, verbose_stream() << "(smt.refine-injectivity)\n";); + IF_VERBOSE(10, verbose_stream() << "(smt.refine-injectivity)\n";); TRACE("inj_axiom", display(tout);); unsigned i = m_asserted_qhead; unsigned sz = m_asserted_formulas.size(); @@ -805,7 +805,7 @@ MK_SIMPLIFIER(elim_bvs_from_quantifiers, bv_elim_star functor(m_manager), "bv_el #define LIFT_ITE(NAME, FUNCTOR, MSG) \ void asserted_formulas::NAME() { \ - IF_IVERBOSE(10, verbose_stream() << "(smt." << MSG << ")\n";); \ + IF_VERBOSE(10, verbose_stream() << "(smt." << MSG << ")\n";); \ TRACE("lift_ite", display(tout);); \ FUNCTOR; \ unsigned i = m_asserted_qhead; \ @@ -817,7 +817,7 @@ void asserted_formulas::NAME() { proof_ref new_pr(m_manager); \ functor(n, new_n, new_pr); \ TRACE("lift_ite_step", tout << mk_pp(n, m_manager) << "\n";); \ - IF_IVERBOSE(10000, verbose_stream() << "lift before: " << get_num_exprs(n) << ", after: " << get_num_exprs(new_n) << "\n";); \ + IF_VERBOSE(10000, verbose_stream() << "lift before: " << get_num_exprs(n) << ", after: " << get_num_exprs(new_n) << "\n";); \ m_asserted_formulas.set(i, new_n); \ if (m_manager.proofs_enabled()) { \ new_pr = m_manager.mk_modus_ponens(pr, new_pr); \ @@ -841,7 +841,7 @@ unsigned asserted_formulas::get_total_size() const { } void asserted_formulas::max_bv_sharing() { - IF_IVERBOSE(10, verbose_stream() << "(smt.maximizing-bv-sharing)\n";); + IF_VERBOSE(10, verbose_stream() << "(smt.maximizing-bv-sharing)\n";); TRACE("bv_sharing", display(tout);); unsigned i = m_asserted_qhead; unsigned sz = m_asserted_formulas.size(); diff --git a/src/smt/mam.cpp b/src/smt/mam.cpp index 5d03a3563..ba9f970a2 100644 --- a/src/smt/mam.cpp +++ b/src/smt/mam.cpp @@ -3942,7 +3942,7 @@ namespace smt { #endif virtual void on_match(quantifier * qa, app * pat, unsigned num_bindings, enode * const * bindings, unsigned max_generation, ptr_vector & used_enodes) { - TRACE("trigger_bug", tout << "found match\n";); + TRACE("trigger_bug", tout << "found match " << mk_pp(qa, m_ast_manager) << "\n";); #ifdef Z3DEBUG if (m_check_missing_instances) { if (!m_context.slow_contains_instance(qa, num_bindings, bindings)) { diff --git a/src/smt/old_interval.cpp b/src/smt/old_interval.cpp index 616b74ed6..da03bc03e 100644 --- a/src/smt/old_interval.cpp +++ b/src/smt/old_interval.cpp @@ -81,7 +81,7 @@ ext_numeral & ext_numeral::operator*=(ext_numeral const & other) { m_value.reset(); return *this; } - + if (is_infinite() || other.is_infinite()) { if (sign() == other.sign()) m_kind = PLUS_INFINITY; @@ -203,7 +203,7 @@ interval::interval(v_dependency_manager & m, rational const & val, v_dependency m_lower_dep(l_dep), m_upper_dep(u_dep) { } - + /** \brief Create intervals (-oo, val], (-oo, val), [val, oo), (val, oo) */ @@ -271,8 +271,8 @@ interval & interval::operator-=(interval const & other) { return operator+=(tmp); } -v_dependency * interval::join(v_dependency * d1, v_dependency * d2, v_dependency * d3, v_dependency * d4) { - return m_manager.mk_join(m_manager.mk_join(d1, d2), m_manager.mk_join(d3,d4)); +v_dependency * interval::join(v_dependency * d1, v_dependency * d2, v_dependency * d3, v_dependency * d4) { + return m_manager.mk_join(m_manager.mk_join(d1, d2), m_manager.mk_join(d3,d4)); } /** @@ -318,7 +318,7 @@ interval & interval::operator*=(interval const & other) { v_dependency * d_d = other.m_upper_dep; TRACE("interval_bug", tout << "operator*= " << *this << " " << other << "\n";); - + if (is_N()) { if (other.is_N()) { // x <= b <= 0, y <= d <= 0 --> b*d <= x*y @@ -452,7 +452,7 @@ interval & interval::operator*=(interval const & other) { m_upper_dep = m_upper.is_infinite() ? 0 : join(b_d, d_d, a_d); } else { - // 0 <= a <= x, 0 <= c <= y --> a*c <= x*y + // 0 <= a <= x, 0 <= c <= y --> a*c <= x*y // x <= b, y <= d --> x*y <= b*d (uses the fact that x is pos (a is not negative) or y is pos (c is not negative)) TRACE("interval_bug", tout << "(P, P)\n";); SASSERT(other.is_P()); @@ -467,7 +467,7 @@ interval & interval::operator*=(interval const & other) { } } TRACE("interval_bug", tout << "operator*= result: " << *this << "\n";); - CTRACE("interval", !(!(contains_zero1 || contains_zero2) || contains_zero()), + CTRACE("interval", !(!(contains_zero1 || contains_zero2) || contains_zero()), tout << "contains_zero1: " << contains_zero1 << ", contains_zero2: " << contains_zero2 << ", contains_zero(): " << contains_zero() << "\n";); SASSERT(!(contains_zero1 || contains_zero2) || contains_zero()); return *this; @@ -482,7 +482,7 @@ bool interval::contains_zero() const { tout << "m_upper.is_zero: " << m_upper.is_zero() << "\n"; tout << "m_upper_open: " << m_upper_open << "\n"; tout << "result: " << ((m_lower.is_neg() || (m_lower.is_zero() && !m_lower_open)) && (m_upper.is_pos() || (m_upper.is_zero() && !m_upper_open))) << "\n";); - return + return (m_lower.is_neg() || (m_lower.is_zero() && !m_lower_open)) && (m_upper.is_pos() || (m_upper.is_zero() && !m_upper_open)); } @@ -510,7 +510,7 @@ interval & interval::inv() { ext_numeral new_upper; if (m_lower.is_zero()) { SASSERT(m_lower_open); - ext_numeral plus_infinity(true); + ext_numeral plus_infinity(true); new_upper = plus_infinity; } else { @@ -595,7 +595,7 @@ void interval::expt(unsigned n) { else if (m_upper.is_neg()) { // [l, u]^n = [u^n, l^n] if u < 0 // a <= x <= b < 0 --> x^n <= a^n (use lower and upper bound -- need the fact that x is negative) - // x <= b < 0 --> b^n <= x^n + // x <= b < 0 --> b^n <= x^n std::swap(m_lower, m_upper); std::swap(m_lower_open, m_upper_open); std::swap(m_lower_dep, m_upper_dep); @@ -614,7 +614,7 @@ void interval::expt(unsigned n) { m_upper = m_lower; m_upper_open = m_lower_open; } - m_upper_dep = m_upper.is_infinite() ? 0 : m_manager.mk_join(m_lower_dep, m_upper_dep); + m_upper_dep = m_upper.is_infinite() ? 0 : m_manager.mk_join(m_lower_dep, m_upper_dep); m_lower = ext_numeral(0); m_lower_open = false; m_lower_dep = 0; diff --git a/src/smt/params/smt_params.cpp b/src/smt/params/smt_params.cpp index 8a9188e2b..92ff1de90 100644 --- a/src/smt/params/smt_params.cpp +++ b/src/smt/params/smt_params.cpp @@ -31,6 +31,8 @@ void smt_params::updt_local_params(params_ref const & _p) { m_restart_strategy = static_cast(p.restart_strategy()); m_restart_factor = p.restart_factor(); m_case_split_strategy = static_cast(p.case_split()); + m_theory_case_split = p.theory_case_split(); + m_theory_aware_branching = p.theory_aware_branching(); m_delay_units = p.delay_units(); m_delay_units_threshold = p.delay_units_threshold(); m_preprocess = _p.get_bool("preprocess", true); // hidden parameter @@ -39,6 +41,7 @@ void smt_params::updt_local_params(params_ref const & _p) { m_max_conflicts = p.max_conflicts(); m_core_validate = p.core_validate(); m_logic = _p.get_sym("logic", m_logic); + m_string_solver = p.string_solver(); model_params mp(_p); m_model_compact = mp.compact(); if (_p.get_bool("arith.greatest_error_pivot", false)) @@ -155,4 +158,4 @@ void smt_params::display(std::ostream & out) const { DISPLAY_PARAM(m_check_at_labels); DISPLAY_PARAM(m_dump_goal_as_smt); DISPLAY_PARAM(m_auto_config); -} \ No newline at end of file +} diff --git a/src/smt/params/smt_params.h b/src/smt/params/smt_params.h index 366570365..4b7c924e4 100644 --- a/src/smt/params/smt_params.h +++ b/src/smt/params/smt_params.h @@ -25,6 +25,7 @@ Revision History: #include"theory_arith_params.h" #include"theory_array_params.h" #include"theory_bv_params.h" +#include"theory_str_params.h" #include"theory_pb_params.h" #include"theory_datatype_params.h" #include"preprocessor_params.h" @@ -66,7 +67,8 @@ enum case_split_strategy { CS_ACTIVITY_WITH_CACHE, // case split based on activity and cache the activity CS_RELEVANCY, // case split based on relevancy CS_RELEVANCY_ACTIVITY, // case split based on relevancy and activity - CS_RELEVANCY_GOAL // based on relevancy and the current goal + CS_RELEVANCY_GOAL, // based on relevancy and the current goal + CS_ACTIVITY_THEORY_AWARE_BRANCHING // activity-based case split, but theory solvers can manipulate activity }; struct smt_params : public preprocessor_params, @@ -75,6 +77,7 @@ struct smt_params : public preprocessor_params, public theory_arith_params, public theory_array_params, public theory_bv_params, + public theory_str_params, public theory_pb_params, public theory_datatype_params { bool m_display_proof; @@ -109,6 +112,8 @@ struct smt_params : public preprocessor_params, case_split_strategy m_case_split_strategy; unsigned m_rel_case_split_order; bool m_lookahead_diseq; + bool m_theory_case_split; + bool m_theory_aware_branching; // ----------------------------------- // @@ -212,6 +217,13 @@ struct smt_params : public preprocessor_params, bool m_dump_goal_as_smt; bool m_auto_config; + // ----------------------------------- + // + // Solver selection + // + // ----------------------------------- + symbol m_string_solver; + smt_params(params_ref const & p = params_ref()): m_display_proof(false), m_display_dot_proof(false), @@ -239,6 +251,8 @@ struct smt_params : public preprocessor_params, m_case_split_strategy(CS_ACTIVITY_DELAY_NEW), m_rel_case_split_order(0), m_lookahead_diseq(false), + m_theory_case_split(false), + m_theory_aware_branching(false), m_delay_units(false), m_delay_units_threshold(32), m_theory_resolve(false), @@ -280,7 +294,8 @@ struct smt_params : public preprocessor_params, m_at_labels_cex(false), m_check_at_labels(false), m_dump_goal_as_smt(false), - m_auto_config(true) { + m_auto_config(true), + m_string_solver(symbol("auto")){ updt_local_params(p); } diff --git a/src/smt/params/smt_params_helper.pyg b/src/smt/params/smt_params_helper.pyg index a3f163ed4..6c2ff4962 100644 --- a/src/smt/params/smt_params_helper.pyg +++ b/src/smt/params/smt_params_helper.pyg @@ -11,7 +11,7 @@ def_module_params(module_name='smt', ('phase_selection', UINT, 3, 'phase selection heuristic: 0 - always false, 1 - always true, 2 - phase caching, 3 - phase caching conservative, 4 - phase caching conservative 2, 5 - random, 6 - number of occurrences'), ('restart_strategy', UINT, 1, '0 - geometric, 1 - inner-outer-geometric, 2 - luby, 3 - fixed, 4 - arithmetic'), ('restart_factor', DOUBLE, 1.1, 'when using geometric (or inner-outer-geometric) progression of restarts, it specifies the constant used to multiply the currect restart threshold'), - ('case_split', UINT, 1, '0 - case split based on variable activity, 1 - similar to 0, but delay case splits created during the search, 2 - similar to 0, but cache the relevancy, 3 - case split based on relevancy (structural splitting), 4 - case split on relevancy and activity, 5 - case split on relevancy and current goal'), + ('case_split', UINT, 1, '0 - case split based on variable activity, 1 - similar to 0, but delay case splits created during the search, 2 - similar to 0, but cache the relevancy, 3 - case split based on relevancy (structural splitting), 4 - case split on relevancy and activity, 5 - case split on relevancy and current goal, 6 - activity-based case split with theory-aware branching activity'), ('delay_units', BOOL, False, 'if true then z3 will not restart when a unit clause is learned'), ('delay_units_threshold', UINT, 32, 'maximum number of learned unit clauses before restarting, ignored if delay_units is false'), ('pull_nested_quantifiers', BOOL, False, 'pull nested quantifiers'), @@ -61,8 +61,23 @@ def_module_params(module_name='smt', ('dack.gc', UINT, 2000, 'Dynamic ackermannization garbage collection frequency (per conflict)'), ('dack.gc_inv_decay', DOUBLE, 0.8, 'Dynamic ackermannization garbage collection decay'), ('dack.threshold', UINT, 10, ' number of times the congruence rule must be used before Leibniz\'s axiom is expanded'), + ('theory_case_split', BOOL, False, 'Allow the context to use heuristics involving theory case splits, which are a set of literals of which exactly one can be assigned True. If this option is false, the context will generate extra axioms to enforce this instead.'), + ('string_solver', SYMBOL, 'seq', 'solver for string/sequence theories. options are: \'z3str3\' (specialized string solver), \'seq\' (sequence solver), \'auto\' (use static features to choose best solver)'), ('core.validate', BOOL, False, 'validate unsat core produced by SMT context'), + ('str.strong_arrangements', BOOL, True, 'assert equivalences instead of implications when generating string arrangement axioms'), + ('str.aggressive_length_testing', BOOL, False, 'prioritize testing concrete length values over generating more options'), + ('str.aggressive_value_testing', BOOL, False, 'prioritize testing concrete string constant values over generating more options'), + ('str.aggressive_unroll_testing', BOOL, True, 'prioritize testing concrete regex unroll counts over generating more options'), + ('str.fast_length_tester_cache', BOOL, False, 'cache length tester constants instead of regenerating them'), + ('str.fast_value_tester_cache', BOOL, True, 'cache value tester constants instead of regenerating them'), + ('str.string_constant_cache', BOOL, True, 'cache all generated string constants generated from anywhere in theory_str'), + ('str.use_binary_search', BOOL, False, 'use a binary search heuristic for finding concrete length values for free variables in theory_str (set to False to use linear search)'), + ('str.binary_search_start', UINT, 64, 'initial upper bound for theory_str binary search'), + ('theory_aware_branching', BOOL, False, 'Allow the context to use extra information from theory solvers regarding literal branching prioritization.'), + ('str.finite_overlap_models', BOOL, False, 'attempt a finite model search for overlapping variables instead of completely giving up on the arrangement'), + ('str.overlap_priority', DOUBLE, -0.1, 'theory-aware priority for overlapping variable cases; use smt.theory_aware_branching=true'), ('core.minimize', BOOL, False, 'minimize unsat core produced by SMT context'), ('core.extend_patterns', BOOL, False, 'extend unsat core with literals that trigger (potential) quantifier instances'), - ('core.extend_patterns.max_distance', UINT, UINT_MAX, 'limits the distance of a pattern-extended unsat core') + ('core.extend_patterns.max_distance', UINT, UINT_MAX, 'limits the distance of a pattern-extended unsat core'), + ('core.extend_nonlocal_patterns', BOOL, False, 'extend unsat cores with literals that have quantifiers with patterns that contain symbols which are not in the quantifier\'s body') )) diff --git a/src/smt/params/theory_str_params.cpp b/src/smt/params/theory_str_params.cpp new file mode 100644 index 000000000..6090086b8 --- /dev/null +++ b/src/smt/params/theory_str_params.cpp @@ -0,0 +1,34 @@ +/*++ +Module Name: + + theory_str_params.cpp + +Abstract: + + Parameters for string theory plugin + +Author: + + Murphy Berzish (mtrberzi) 2016-12-13 + +Revision History: + +--*/ + +#include"theory_str_params.h" +#include"smt_params_helper.hpp" + +void theory_str_params::updt_params(params_ref const & _p) { + smt_params_helper p(_p); + m_StrongArrangements = p.str_strong_arrangements(); + m_AggressiveLengthTesting = p.str_aggressive_length_testing(); + m_AggressiveValueTesting = p.str_aggressive_value_testing(); + m_AggressiveUnrollTesting = p.str_aggressive_unroll_testing(); + m_UseFastLengthTesterCache = p.str_fast_length_tester_cache(); + m_UseFastValueTesterCache = p.str_fast_value_tester_cache(); + m_StringConstantCache = p.str_string_constant_cache(); + m_FiniteOverlapModels = p.str_finite_overlap_models(); + m_UseBinarySearch = p.str_use_binary_search(); + m_BinarySearchInitialUpperBound = p.str_binary_search_start(); + m_OverlapTheoryAwarePriority = p.str_overlap_priority(); +} diff --git a/src/smt/params/theory_str_params.h b/src/smt/params/theory_str_params.h new file mode 100644 index 000000000..207b635d7 --- /dev/null +++ b/src/smt/params/theory_str_params.h @@ -0,0 +1,102 @@ +/*++ +Module Name: + + theory_str_params.h + +Abstract: + + Parameters for string theory plugin + +Author: + + Murphy Berzish (mtrberzi) 2016-12-13 + +Revision History: + +--*/ + +#ifndef THEORY_STR_PARAMS_H +#define THEORY_STR_PARAMS_H + +#include"params.h" + +struct theory_str_params { + /* + * If AssertStrongerArrangements is set to true, + * the implications that would normally be asserted during arrangement generation + * will instead be asserted as equivalences. + * This is a stronger version of the standard axiom. + * The Z3str2 axioms can be simulated by setting this to false. + */ + bool m_StrongArrangements; + + /* + * If AggressiveLengthTesting is true, we manipulate the phase of length tester equalities + * to prioritize trying concrete length options over choosing the "more" option. + */ + bool m_AggressiveLengthTesting; + + /* + * Similarly, if AggressiveValueTesting is true, we manipulate the phase of value tester equalities + * to prioritize trying concrete value options over choosing the "more" option. + */ + bool m_AggressiveValueTesting; + + /* + * If AggressiveUnrollTesting is true, we manipulate the phase of regex unroll tester equalities + * to prioritize trying concrete unroll counts over choosing the "more" option. + */ + bool m_AggressiveUnrollTesting; + + /* + * If UseFastLengthTesterCache is set to true, + * length tester terms will not be generated from scratch each time they are needed, + * but will be saved in a map and looked up. + */ + bool m_UseFastLengthTesterCache; + + /* + * If UseFastValueTesterCache is set to true, + * value tester terms will not be generated from scratch each time they are needed, + * but will be saved in a map and looked up. + */ + bool m_UseFastValueTesterCache; + + /* + * If StringConstantCache is set to true, + * all string constants in theory_str generated from anywhere will be cached and saved. + */ + bool m_StringConstantCache; + + /* + * If FiniteOverlapModels is set to true, + * arrangements that result in overlapping variables will generate a small number of models + * to test instead of completely giving up on the case. + */ + bool m_FiniteOverlapModels; + + bool m_UseBinarySearch; + unsigned m_BinarySearchInitialUpperBound; + + double m_OverlapTheoryAwarePriority; + + theory_str_params(params_ref const & p = params_ref()): + m_StrongArrangements(true), + m_AggressiveLengthTesting(false), + m_AggressiveValueTesting(false), + m_AggressiveUnrollTesting(true), + m_UseFastLengthTesterCache(false), + m_UseFastValueTesterCache(true), + m_StringConstantCache(true), + m_FiniteOverlapModels(false), + m_UseBinarySearch(false), + m_BinarySearchInitialUpperBound(64), + m_OverlapTheoryAwarePriority(-0.1) + { + updt_params(p); + } + + void updt_params(params_ref const & p); +}; + +#endif /* THEORY_STR_PARAMS_H */ diff --git a/src/smt/smt_case_split_queue.cpp b/src/smt/smt_case_split_queue.cpp index 06004e3b8..129d77c85 100644 --- a/src/smt/smt_case_split_queue.cpp +++ b/src/smt/smt_case_split_queue.cpp @@ -22,9 +22,13 @@ Revision History: #include"stopwatch.h" #include"for_each_expr.h" #include"ast_pp.h" +#include"map.h" +#include"hashtable.h" namespace smt { + typedef map > theory_var_priority_map; + struct bool_var_act_lt { svector const & m_activity; bool_var_act_lt(svector const & a):m_activity(a) {} @@ -35,6 +39,27 @@ namespace smt { typedef heap bool_var_act_queue; + struct theory_aware_act_lt { + svector const & m_activity; + theory_var_priority_map const & m_theory_var_priority; + theory_aware_act_lt(svector const & act, theory_var_priority_map const & a):m_activity(act),m_theory_var_priority(a) {} + bool operator()(bool_var v1, bool_var v2) const { + double p_v1, p_v2; + if (!m_theory_var_priority.find(v1, p_v1)) { + p_v1 = 0.0; + } + if (!m_theory_var_priority.find(v2, p_v2)) { + p_v2 = 0.0; + } + // add clause activity + p_v1 += m_activity[v1]; + p_v2 += m_activity[v2]; + return p_v1 > p_v2; + } + }; + + typedef heap theory_aware_act_queue; + /** \brief Case split queue based on activity and random splits. */ @@ -1086,16 +1111,130 @@ namespace smt { m_params.m_qi_eager_threshold += start_gen; } }; - + + class theory_aware_branching_queue : public case_split_queue { + protected: + context & m_context; + smt_params & m_params; + theory_var_priority_map m_theory_var_priority; + theory_aware_act_queue m_queue; + + int_hashtable > m_theory_vars; + map > m_theory_var_phase; + public: + theory_aware_branching_queue(context & ctx, smt_params & p): + m_context(ctx), + m_params(p), + m_theory_var_priority(), + m_queue(1024, theory_aware_act_lt(ctx.get_activity_vector(), m_theory_var_priority)) { + } + + virtual void activity_increased_eh(bool_var v) { + if (m_queue.contains(v)) + m_queue.decreased(v); + } + + virtual void mk_var_eh(bool_var v) { + m_queue.reserve(v+1); + m_queue.insert(v); + } + + virtual void del_var_eh(bool_var v) { + if (m_queue.contains(v)) + m_queue.erase(v); + } + + virtual void unassign_var_eh(bool_var v) { + if (!m_queue.contains(v)) + m_queue.insert(v); + } + + virtual void relevant_eh(expr * n) {} + + virtual void init_search_eh() {} + + virtual void end_search_eh() {} + + virtual void reset() { + m_queue.reset(); + } + + virtual void push_scope() {} + + virtual void pop_scope(unsigned num_scopes) {} + + virtual void next_case_split(bool_var & next, lbool & phase) { + int threshold = static_cast(m_params.m_random_var_freq * random_gen::max_value()); + SASSERT(threshold >= 0); + if (m_context.get_random_value() < threshold) { + SASSERT(m_context.get_num_b_internalized() > 0); + next = m_context.get_random_value() % m_context.get_num_b_internalized(); + TRACE("random_split", tout << "next: " << next << " get_assignment(next): " << m_context.get_assignment(next) << "\n";); + if (m_context.get_assignment(next) == l_undef) + return; + } + + while (!m_queue.empty()) { + next = m_queue.erase_min(); + if (m_context.get_assignment(next) == l_undef) + return; + } + + next = null_bool_var; + if (m_theory_vars.contains(next)) { + if (!m_theory_var_phase.find(next, phase)) { + phase = l_undef; + } + } + } + + virtual void add_theory_aware_branching_info(bool_var v, double priority, lbool phase) { + TRACE("theory_aware_branching", tout << "Add theory-aware branching information for l#" << v << ": priority=" << priority << std::endl;); + m_theory_vars.insert(v); + m_theory_var_phase.insert(v, phase); + m_theory_var_priority.insert(v, priority); + if (m_queue.contains(v)) { + if (priority > 0.0) { + m_queue.decreased(v); + } else { + m_queue.increased(v); + } + } + // m_theory_queue.reserve(v+1); + // m_theory_queue.insert(v); + } + + virtual void display(std::ostream & out) { + bool first = true; + bool_var_act_queue::const_iterator it = m_queue.begin(); + bool_var_act_queue::const_iterator end = m_queue.end(); + for (; it != end ; ++it) { + unsigned v = *it; + if (m_context.get_assignment(v) == l_undef) { + if (first) { + out << "remaining case-splits:\n"; + first = false; + } + out << "#" << m_context.bool_var2expr(v)->get_id() << " "; + } + } + if (!first) + out << "\n"; + + } + + virtual ~theory_aware_branching_queue() {}; + }; + case_split_queue * mk_case_split_queue(context & ctx, smt_params & p) { if (p.m_relevancy_lvl < 2 && (p.m_case_split_strategy == CS_RELEVANCY || p.m_case_split_strategy == CS_RELEVANCY_ACTIVITY || - p.m_case_split_strategy == CS_RELEVANCY_GOAL)) { + p.m_case_split_strategy == CS_RELEVANCY_GOAL)) { warning_msg("relevancy must be enabled to use option CASE_SPLIT=3, 4 or 5"); p.m_case_split_strategy = CS_ACTIVITY; } if (p.m_auto_config && (p.m_case_split_strategy == CS_RELEVANCY || p.m_case_split_strategy == CS_RELEVANCY_ACTIVITY || - p.m_case_split_strategy == CS_RELEVANCY_GOAL)) { + p.m_case_split_strategy == CS_RELEVANCY_GOAL)) { warning_msg("auto configuration (option AUTO_CONFIG) must be disabled to use option CASE_SPLIT=3, 4 or 5"); p.m_case_split_strategy = CS_ACTIVITY; } @@ -1110,6 +1249,8 @@ namespace smt { return alloc(rel_act_case_split_queue, ctx, p); case CS_RELEVANCY_GOAL: return alloc(rel_goal_case_split_queue, ctx, p); + case CS_ACTIVITY_THEORY_AWARE_BRANCHING: + return alloc(theory_aware_branching_queue, ctx, p); default: return alloc(act_case_split_queue, ctx, p); } diff --git a/src/smt/smt_case_split_queue.h b/src/smt/smt_case_split_queue.h index e6b217a22..9a3a93cc6 100644 --- a/src/smt/smt_case_split_queue.h +++ b/src/smt/smt_case_split_queue.h @@ -46,6 +46,9 @@ namespace smt { virtual void next_case_split(bool_var & next, lbool & phase) = 0; virtual void display(std::ostream & out) = 0; virtual ~case_split_queue() {} + + // theory-aware branching hint + virtual void add_theory_aware_branching_info(bool_var v, double priority, lbool phase) {} }; case_split_queue * mk_case_split_queue(context & ctx, smt_params & p); diff --git a/src/smt/smt_conflict_resolution.cpp b/src/smt/smt_conflict_resolution.cpp index 7dd9144fe..8d90f9583 100644 --- a/src/smt/smt_conflict_resolution.cpp +++ b/src/smt/smt_conflict_resolution.cpp @@ -59,9 +59,9 @@ namespace smt { SASSERT(n->trans_reaches(n->get_root())); while (n) { if (Set) - n->set_mark(); + n->set_mark2(); else - n->unset_mark(); + n->unset_mark2(); n = n->m_trans.m_target; } } @@ -84,7 +84,7 @@ namespace smt { mark_enodes_in_trans(n1); while (true) { SASSERT(n2); - if (n2->is_marked()) { + if (n2->is_marked2()) { mark_enodes_in_trans(n1); return n2; } diff --git a/src/smt/smt_consequences.cpp b/src/smt/smt_consequences.cpp index 9558f6a3b..65272207e 100644 --- a/src/smt/smt_consequences.cpp +++ b/src/smt/smt_consequences.cpp @@ -30,6 +30,7 @@ namespace smt { index_set::iterator it = vars.begin(), end = vars.end(); for (; it != end; ++it) { expr* e = bool_var2expr(*it); + e = m_assumption2orig.find(e); premises.push_back(get_assignment(*it) != l_false ? e : m_manager.mk_not(e)); } return mk_and(premises); @@ -44,13 +45,14 @@ namespace smt { // - e is an equality between a variable and value that is to be fixed. // - e is a data-type recognizer of a variable that is to be fixed. // - void context::extract_fixed_consequences(literal lit, obj_map& vars, index_set const& assumptions, expr_ref_vector& conseq) { + void context::extract_fixed_consequences(literal lit, index_set const& assumptions, expr_ref_vector& conseq) { ast_manager& m = m_manager; datatype_util dt(m); expr* e1, *e2; expr_ref fml(m); if (lit == true_literal) return; expr* e = bool_var2expr(lit.var()); + TRACE("context", display(tout << mk_pp(e, m) << "\n");); index_set s; if (assumptions.contains(lit.var())) { s.insert(lit.var()); @@ -65,26 +67,32 @@ namespace smt { } tout << "\n";); bool found = false; - if (vars.contains(e)) { + if (m_var2val.contains(e)) { found = true; + m_var2val.erase(e); + e = m_var2orig.find(e); fml = lit.sign() ? m.mk_not(e) : e; - vars.erase(e); } else if (!lit.sign() && m.is_eq(e, e1, e2)) { - if (vars.contains(e2)) { - std::swap(e1, e2); - } - if (vars.contains(e1) && m.is_value(e2)) { + if (m_var2val.contains(e2) && m.is_value(e1)) { found = true; - fml = e; - vars.erase(e1); + m_var2val.erase(e2); + e2 = m_var2orig.find(e2); + std::swap(e1, e2); + fml = m.mk_eq(e1, e2); + } + else if (m_var2val.contains(e1) && m.is_value(e2)) { + found = true; + m_var2val.erase(e1); + e1 = m_var2orig.find(e1); + fml = m.mk_eq(e1, e2); } } else if (!lit.sign() && is_app(e) && dt.is_recognizer(to_app(e)->get_decl())) { - if (vars.contains(to_app(e)->get_arg(0))) { + if (m_var2val.contains(to_app(e)->get_arg(0))) { found = true; fml = m.mk_eq(to_app(e)->get_arg(0), m.mk_const(dt.get_recognizer_constructor(to_app(e)->get_decl()))); - vars.erase(to_app(e)->get_arg(0)); + m_var2val.erase(to_app(e)->get_arg(0)); } } if (found) { @@ -94,6 +102,7 @@ namespace smt { } void context::justify(literal lit, index_set& s) { + ast_manager& m = m_manager; b_justification js = get_justification(lit.var()); switch (js.get_kind()) { case b_justification::CLAUSE: { @@ -119,6 +128,9 @@ namespace smt { literal_vector literals; m_conflict_resolution->justification2literals(js.get_justification(), literals); for (unsigned j = 0; j < literals.size(); ++j) { + if (!m_antecedents.contains(literals[j].var())) { + TRACE("context", tout << literals[j] << " " << mk_pp(bool_var2expr(literals[j].var()), m) << "\n";); + } s |= m_antecedents.find(literals[j].var()); } break; @@ -126,13 +138,13 @@ namespace smt { } } - void context::extract_fixed_consequences(unsigned& start, obj_map& vars, index_set const& assumptions, expr_ref_vector& conseq) { + void context::extract_fixed_consequences(unsigned& start, index_set const& assumptions, expr_ref_vector& conseq) { pop_to_search_lvl(); SASSERT(!inconsistent()); literal_vector const& lits = assigned_literals(); unsigned sz = lits.size(); for (unsigned i = start; i < sz; ++i) { - extract_fixed_consequences(lits[i], vars, assumptions, conseq); + extract_fixed_consequences(lits[i], assumptions, conseq); } start = sz; SASSERT(!inconsistent()); @@ -150,10 +162,10 @@ namespace smt { // rules out as many non-fixed variables as possible. // - unsigned context::delete_unfixed(obj_map& var2val, expr_ref_vector& unfixed) { + unsigned context::delete_unfixed(expr_ref_vector& unfixed) { ast_manager& m = m_manager; ptr_vector to_delete; - obj_map::iterator it = var2val.begin(), end = var2val.end(); + obj_map::iterator it = m_var2val.begin(), end = m_var2val.end(); for (; it != end; ++it) { expr* k = it->m_key; expr* v = it->m_value; @@ -189,7 +201,7 @@ namespace smt { } } for (unsigned i = 0; i < to_delete.size(); ++i) { - var2val.remove(to_delete[i]); + m_var2val.remove(to_delete[i]); unfixed.push_back(to_delete[i]); } return to_delete.size(); @@ -202,12 +214,12 @@ namespace smt { // Add a clause to short-circuit the congruence justifications for // next rounds. // - unsigned context::extract_fixed_eqs(obj_map& var2val, expr_ref_vector& conseq) { + unsigned context::extract_fixed_eqs(expr_ref_vector& conseq) { TRACE("context", tout << "extract fixed consequences\n";); ast_manager& m = m_manager; ptr_vector to_delete; expr_ref fml(m), eq(m); - obj_map::iterator it = var2val.begin(), end = var2val.end(); + obj_map::iterator it = m_var2val.begin(), end = m_var2val.end(); for (; it != end; ++it) { expr* k = it->m_key; expr* v = it->m_value; @@ -220,7 +232,7 @@ namespace smt { s |= m_antecedents.find(literals[i].var()); } - fml = m.mk_eq(k, v); + fml = m.mk_eq(m_var2orig.find(k), v); fml = m.mk_implies(antecedent2fml(s), fml); conseq.push_back(fml); to_delete.push_back(k); @@ -235,16 +247,20 @@ namespace smt { } } for (unsigned i = 0; i < to_delete.size(); ++i) { - var2val.remove(to_delete[i]); + m_var2val.remove(to_delete[i]); } return to_delete.size(); } literal context::mk_diseq(expr* e, expr* val) { ast_manager& m = m_manager; - if (m.is_bool(e)) { + if (m.is_bool(e) && b_internalized(e)) { return literal(get_bool_var(e), m.is_true(val)); } + else if (m.is_bool(e)) { + internalize_formula(e, false); + return literal(get_bool_var(e), !m.is_true(val)); + } else { expr_ref eq(mk_eq_atom(e, val), m); internalize_formula(eq, false); @@ -252,43 +268,85 @@ namespace smt { } } - lbool context::get_consequences(expr_ref_vector const& assumptions, - expr_ref_vector const& vars, + lbool context::get_consequences(expr_ref_vector const& assumptions0, + expr_ref_vector const& vars0, expr_ref_vector& conseq, expr_ref_vector& unfixed) { m_antecedents.reset(); + m_antecedents.insert(true_literal.var(), index_set()); pop_to_base_lvl(); + ast_manager& m = m_manager; + expr_ref_vector vars(m), assumptions(m); + m_var2val.reset(); + m_var2orig.reset(); + m_assumption2orig.reset(); + bool pushed = false; + for (unsigned i = 0; i < vars0.size(); ++i) { + expr* v = vars0[i]; + if (is_uninterp_const(v)) { + vars.push_back(v); + m_var2orig.insert(v, v); + } + else { + if (!pushed) { + pushed = true; + push(); + } + expr_ref c(m.mk_fresh_const("v", m.get_sort(v)), m); + expr_ref eq(m.mk_eq(c, v), m); + assert_expr(eq); + vars.push_back(c); + m_var2orig.insert(c, v); + } + } + for (unsigned i = 0; i < assumptions0.size(); ++i) { + expr* a = assumptions0[i]; + if (is_uninterp_const(a)) { + assumptions.push_back(a); + m_assumption2orig.insert(a, a); + } + else { + if (!pushed) { + pushed = true; + push(); + } + expr_ref c(m.mk_fresh_const("a", m.get_sort(a)), m); + expr_ref eq(m.mk_eq(c, a), m); + assert_expr(eq); + assumptions.push_back(c); + m_assumption2orig.insert(c, a); + } + } lbool is_sat = check(assumptions.size(), assumptions.c_ptr()); if (is_sat != l_true) { TRACE("context", tout << is_sat << "\n";); + if (pushed) pop(1); return is_sat; } - obj_map var2val; index_set _assumptions; for (unsigned i = 0; i < assumptions.size(); ++i) { - _assumptions.insert(get_literal(assumptions[i]).var()); + _assumptions.insert(get_literal(assumptions[i].get()).var()); } model_ref mdl; get_model(mdl); - ast_manager& m = m_manager; expr_ref_vector trail(m); model_evaluator eval(*mdl.get()); expr_ref val(m); TRACE("context", model_pp(tout, *mdl);); for (unsigned i = 0; i < vars.size(); ++i) { - eval(vars[i], val); + eval(vars[i].get(), val); if (m.is_value(val)) { trail.push_back(val); - var2val.insert(vars[i], val); + m_var2val.insert(vars[i].get(), val); } else { - unfixed.push_back(vars[i]); + unfixed.push_back(vars[i].get()); } } unsigned num_units = 0; - extract_fixed_consequences(num_units, var2val, _assumptions, conseq); + extract_fixed_consequences(num_units, _assumptions, conseq); app_ref eq(m); TRACE("context", tout << "vars: " << vars.size() << "\n"; @@ -298,11 +356,12 @@ namespace smt { unsigned num_fixed_eqs = 0; unsigned chunk_size = 100; - while (!var2val.empty()) { - obj_map::iterator it = var2val.begin(), end = var2val.end(); + while (!m_var2val.empty()) { + obj_map::iterator it = m_var2val.begin(), end = m_var2val.end(); unsigned num_vars = 0; for (; it != end && num_vars < chunk_size; ++it) { if (get_cancel_flag()) { + if (pushed) pop(1); return l_undef; } expr* e = it->m_key; @@ -332,6 +391,7 @@ namespace smt { while (true) { is_sat = bounded_search(); if (is_sat != l_true && m_last_search_failure != OK) { + if (pushed) pop(1); return is_sat; } if (is_sat == l_undef) { @@ -347,18 +407,21 @@ namespace smt { m_not_l = null_literal; } if (is_sat == l_true) { - delete_unfixed(var2val, unfixed); + delete_unfixed(unfixed); } - extract_fixed_consequences(num_units, var2val, _assumptions, conseq); - num_fixed_eqs += extract_fixed_eqs(var2val, conseq); - IF_VERBOSE(1, display_consequence_progress(verbose_stream(), num_iterations, var2val.size(), conseq.size(), + extract_fixed_consequences(num_units, _assumptions, conseq); + num_fixed_eqs += extract_fixed_eqs(conseq); + IF_VERBOSE(1, display_consequence_progress(verbose_stream(), num_iterations, m_var2val.size(), conseq.size(), unfixed.size(), num_fixed_eqs);); - TRACE("context", display_consequence_progress(tout, num_iterations, var2val.size(), conseq.size(), + TRACE("context", display_consequence_progress(tout, num_iterations, m_var2val.size(), conseq.size(), unfixed.size(), num_fixed_eqs);); } end_search(); DEBUG_CODE(validate_consequences(assumptions, vars, conseq, unfixed);); + if (pushed) { + pop(1); + } return l_true; } diff --git a/src/smt/smt_context.cpp b/src/smt/smt_context.cpp index e413b453e..2e43f1596 100644 --- a/src/smt/smt_context.cpp +++ b/src/smt/smt_context.cpp @@ -304,7 +304,6 @@ namespace smt { TRACE("assign_core", tout << (decision?"decision: ":"propagating: ") << l << " "; display_literal_verbose(tout, l); tout << " level: " << m_scope_lvl << "\n"; display(tout, j);); - SASSERT(l.var() < static_cast(m_b_internalized_stack.size())); m_assigned_literals.push_back(l); m_assignment[l.index()] = l_true; m_assignment[(~l).index()] = l_false; @@ -319,14 +318,23 @@ namespace smt { d.m_phase_available = true; d.m_phase = !l.sign(); TRACE("phase_selection", tout << "saving phase, is_pos: " << d.m_phase << " l: " << l << "\n";); + TRACE("relevancy", - tout << "is_atom: " << d.is_atom() << " is relevant: " << is_relevant_core(bool_var2expr(l.var())) << "\n";); - if (d.is_atom() && (m_fparams.m_relevancy_lvl == 0 || (m_fparams.m_relevancy_lvl == 1 && !d.is_quantifier()) || is_relevant_core(bool_var2expr(l.var())))) + tout << "is_atom: " << d.is_atom() << " is relevant: " << is_relevant_core(l) << "\n";); + if (d.is_atom() && (m_fparams.m_relevancy_lvl == 0 || (m_fparams.m_relevancy_lvl == 1 && !d.is_quantifier()) || is_relevant_core(l))) m_atom_propagation_queue.push_back(l); if (m_manager.has_trace_stream()) trace_assign(l, j, decision); m_case_split_queue->assign_lit_eh(l); + + // a unit is asserted at search level. Mark it as relevant. + // this addresses bug... where a literal becomes fixed to true (false) + // as a conflict gets assigned misses relevancy (and quantifier instantiation). + // + if (false && !decision && relevancy() && at_search_level() && !is_relevant_core(l)) { + mark_as_relevant(l); + } } bool context::bcp() { @@ -1634,7 +1642,7 @@ namespace smt { m_atom_propagation_queue.push_back(literal(v, val == l_false)); } } - TRACE("propagate_relevancy", tout << "marking as relevant:\n" << mk_bounded_pp(n, m_manager) << "\n";); + TRACE("propagate_relevancy", tout << "marking as relevant:\n" << mk_bounded_pp(n, m_manager) << " " << m_scope_lvl << "\n";); #ifndef SMTCOMP m_case_split_queue->relevant_eh(n); #endif @@ -1760,6 +1768,8 @@ namespace smt { unsigned qhead = m_qhead; if (!bcp()) return false; + if (!propagate_th_case_split(qhead)) + return false; if (get_cancel_flag()) { m_qhead = qhead; return true; @@ -2447,8 +2457,9 @@ namespace smt { ptr_vector::iterator it = m_theory_set.begin(); ptr_vector::iterator end = m_theory_set.end(); - for (; it != end; ++it) + for (; it != end; ++it) { (*it)->pop_scope_eh(num_scopes); + } del_justifications(m_justifications, s.m_justifications_lim); @@ -2961,6 +2972,115 @@ namespace smt { assert_expr_core(e, pr); } + class case_split_insert_trail : public trail { + literal l; + public: + case_split_insert_trail(literal l): + l(l) { + } + virtual void undo(context & ctx) { + ctx.undo_th_case_split(l); + } + }; + + void context::mk_th_case_split(unsigned num_lits, literal * lits) { + TRACE("theory_case_split", display_literals_verbose(tout << "theory case split: ", num_lits, lits); tout << std::endl;); + // If we don't use the theory case split heuristic, + // for each pair of literals (l1, l2) we add the clause (~l1 OR ~l2) + // to enforce the condition that at most one literal can be assigned 'true'. + if (!m_fparams.m_theory_case_split) { + for (unsigned i = 0; i < num_lits; ++i) { + for (unsigned j = i+1; j < num_lits; ++j) { + literal l1 = lits[i]; + literal l2 = lits[j]; + mk_clause(~l1, ~l2, (justification*) 0); + } + } + } else { + literal_vector new_case_split; + for (unsigned i = 0; i < num_lits; ++i) { + literal l = lits[i]; + SASSERT(!m_all_th_case_split_literals.contains(l.index())); + m_all_th_case_split_literals.insert(l.index()); + push_trail(case_split_insert_trail(l)); + new_case_split.push_back(l); + } + m_th_case_split_sets.push_back(new_case_split); + push_trail(push_back_vector >(m_th_case_split_sets)); + for (unsigned i = 0; i < num_lits; ++i) { + literal l = lits[i]; + if (!m_literal2casesplitsets.contains(l.index())) { + m_literal2casesplitsets.insert(l.index(), vector()); + } + m_literal2casesplitsets[l.index()].push_back(new_case_split); + } + TRACE("theory_case_split", tout << "tracking case split literal set { "; + for (unsigned i = 0; i < num_lits; ++i) { + tout << lits[i].index() << " "; + } + tout << "}" << std::endl; + ); + } + } + + void context::add_theory_aware_branching_info(bool_var v, double priority, lbool phase) { + m_case_split_queue->add_theory_aware_branching_info(v, priority, phase); + } + + void context::undo_th_case_split(literal l) { + m_all_th_case_split_literals.remove(l.index()); + if (m_literal2casesplitsets.contains(l.index())) { + if (!m_literal2casesplitsets[l.index()].empty()) { + m_literal2casesplitsets[l.index()].pop_back(); + } + } + } + + bool context::propagate_th_case_split(unsigned qhead) { + if (m_all_th_case_split_literals.empty()) + return true; + + // iterate over all literals assigned since the last time this method was called, + // not counting any literals that get assigned by this method + // this relies on bcp() to give us its old m_qhead and therefore + // bcp() should always be called before this method + + unsigned assigned_literal_end = m_assigned_literals.size(); + for (; qhead < assigned_literal_end; ++qhead) { + literal l = m_assigned_literals[qhead]; + TRACE("theory_case_split", tout << "check literal " << l.index() << std::endl; display_literal_verbose(tout, l); tout << std::endl;); + // check if this literal participates in any theory case split + if (!m_all_th_case_split_literals.contains(l.index())) { + continue; + } + TRACE("theory_case_split", tout << "assigned literal " << l.index() << " is a theory case split literal" << std::endl;); + // now find the sets of literals which contain l + vector const& case_split_sets = m_literal2casesplitsets[l.index()]; + for (vector::const_iterator it = case_split_sets.begin(); it != case_split_sets.end(); ++it) { + literal_vector case_split_set = *it; + TRACE("theory_case_split", tout << "found case split set { "; + for(literal_vector::iterator set_it = case_split_set.begin(); set_it != case_split_set.end(); ++set_it) { + tout << set_it->index() << " "; + } + tout << "}" << std::endl;); + for(literal_vector::iterator set_it = case_split_set.begin(); set_it != case_split_set.end(); ++set_it) { + literal l2 = *set_it; + if (l2 != l) { + b_justification js(l); + TRACE("theory_case_split", tout << "case split literal "; l2.display(tout, m_manager, m_bool_var2expr.c_ptr());); + assign(~l2, js); + if (inconsistent()) { + TRACE("theory_case_split", tout << "conflict detected!" << std::endl;); + return false; + } + } + } + } + } + // if we get here without detecting a conflict, we're fine + return true; + } + bool context::reduce_assertions() { if (!m_asserted_formulas.inconsistent()) { SASSERT(at_base_level()); @@ -3004,11 +3124,18 @@ namespace smt { } bool is_valid_assumption(ast_manager & m, expr * assumption) { + expr* arg; if (!m.is_bool(assumption)) return false; if (is_uninterp_const(assumption)) return true; - if (m.is_not(assumption) && is_uninterp_const(to_app(assumption)->get_arg(0))) + if (m.is_not(assumption, arg) && is_uninterp_const(arg)) + return true; + if (!is_app(assumption)) + return false; + if (to_app(assumption)->get_num_args() == 0) + return true; + if (m.is_not(assumption, arg) && is_app(arg) && to_app(arg)->get_num_args() == 0) return true; return false; } @@ -3073,11 +3200,11 @@ namespace smt { m_assumptions.reset(); } - void context::mk_unsat_core() { + lbool context::mk_unsat_core() { SASSERT(inconsistent()); if (!tracking_assumptions()) { SASSERT(m_assumptions.empty()); - return; + return l_false; } uint_set already_found_assumptions; literal_vector::const_iterator it = m_conflict_resolution->begin_unsat_core(); @@ -3102,7 +3229,17 @@ namespace smt { for (unsigned i = 0; i < sz; i++) { tout << mk_pp(m_unsat_core.get(i), m_manager) << "\n"; }); - validate_unsat_core(); + validate_unsat_core(); + // theory validation of unsat core + ptr_vector::iterator th_it = m_theory_set.begin(); + ptr_vector::iterator th_end = m_theory_set.end(); + for (; th_it != th_end; ++th_it) { + lbool theory_result = (*th_it)->validate_unsat_core(m_unsat_core); + if (theory_result == l_undef) { + return l_undef; + } + } + return l_false; } /** @@ -3145,6 +3282,14 @@ namespace smt { SASSERT(m_scope_lvl == 0); SASSERT(!m_setup.already_configured()); setup_context(m_fparams.m_auto_config); + + expr_ref_vector theory_assumptions(m_manager); + add_theory_assumptions(theory_assumptions); + if (!theory_assumptions.empty()) { + TRACE("search", tout << "Adding theory assumptions to context" << std::endl;); + return check(theory_assumptions.size(), theory_assumptions.c_ptr(), reset_cancel, true); + } + internalize_assertions(); lbool r = l_undef; if (m_asserted_formulas.inconsistent()) { @@ -3206,7 +3351,15 @@ namespace smt { (*it)->setup(); } - lbool context::check(unsigned num_assumptions, expr * const * assumptions, bool reset_cancel) { + void context::add_theory_assumptions(expr_ref_vector & theory_assumptions) { + ptr_vector::iterator it = m_theory_set.begin(); + ptr_vector::iterator end = m_theory_set.end(); + for (; it != end; ++it) { + (*it)->add_theory_assumptions(theory_assumptions); + } + } + + lbool context::check(unsigned ext_num_assumptions, expr * const * ext_assumptions, bool reset_cancel, bool already_did_theory_assumptions) { m_stats.m_num_checks++; TRACE("check_bug", tout << "STARTING check(num_assumptions, assumptions)\n"; tout << "inconsistent: " << inconsistent() << ", m_unsat_core.empty(): " << m_unsat_core.empty() << "\n"; @@ -3217,6 +3370,15 @@ namespace smt { m_unsat_core.reset(); if (!check_preamble(reset_cancel)) return l_undef; + + expr_ref_vector all_assumptions(m_manager, ext_num_assumptions, ext_assumptions); + if (!already_did_theory_assumptions) { + add_theory_assumptions(all_assumptions); + } + + unsigned num_assumptions = all_assumptions.size(); + expr * const * assumptions = all_assumptions.c_ptr(); + if (!validate_assumptions(num_assumptions, assumptions)) return l_undef; TRACE("check_bug", tout << "inconsistent: " << inconsistent() << ", m_unsat_core.empty(): " << m_unsat_core.empty() << "\n";); @@ -3240,13 +3402,21 @@ namespace smt { TRACE("after_internalization", display(tout);); if (inconsistent()) { VERIFY(!resolve_conflict()); // build the proof - mk_unsat_core(); - r = l_false; + lbool result = mk_unsat_core(); + if (result == l_undef) { + r = l_undef; + } else { + r = l_false; + } } else { r = search(); - if (r == l_false) - mk_unsat_core(); + if (r == l_false) { + lbool result = mk_unsat_core(); + if (result == l_undef) { + r = l_undef; + } + } } } } @@ -3750,6 +3920,7 @@ namespace smt { // I invoke pop_scope_core instead of pop_scope because I don't want // to reset cached generations... I need them to rebuild the literals // of the new conflict clause. + if (relevancy()) record_relevancy(num_lits, lits); unsigned num_bool_vars = pop_scope_core(m_scope_lvl - new_lvl); SASSERT(m_scope_lvl == new_lvl); // the logical context may still be in conflict after @@ -3781,6 +3952,7 @@ namespace smt { } } } + if (relevancy()) restore_relevancy(num_lits, lits); // Resetting the cache manually because I did not invoke pop_scope, but pop_scope_core reset_cache_generation(); TRACE("resolve_conflict_bug", @@ -3863,6 +4035,28 @@ namespace smt { } return false; } + + /* + \brief we record and restore relevancy information for literals in conflict clauses. + A literal may have been marked relevant within the scope that gets popped during + conflict resolution. In this case, the literal is no longer marked as relevant after + the pop. This can cause quantifier instantiation to miss relevant triggers and thereby + cause incmpleteness. + */ + void context::record_relevancy(unsigned n, literal const* lits) { + m_relevant_conflict_literals.reset(); + for (unsigned i = 0; i < n; ++i) { + m_relevant_conflict_literals.push_back(is_relevant(lits[i])); + } + } + + void context::restore_relevancy(unsigned n, literal const* lits) { + for (unsigned i = 0; i < n; ++i) { + if (m_relevant_conflict_literals[i] && !is_relevant(lits[i])) { + mark_as_relevant(lits[i]); + } + } + } void context::get_relevant_labels(expr* cnstr, buffer & result) { if (m_fparams.m_check_at_labels) { @@ -4195,42 +4389,21 @@ namespace smt { for (unsigned i = 0; i < m_asserted_formulas.get_num_formulas(); ++i) { expr* e = m_asserted_formulas.get_formula(i); if (is_quantifier(e)) { + TRACE("context", tout << mk_pp(e, m) << "\n";); quantifier* q = to_quantifier(e); if (!m.is_rec_fun_def(q)) continue; - SASSERT(q->get_num_patterns() == 1); + SASSERT(q->get_num_patterns() == 2); expr* fn = to_app(q->get_pattern(0))->get_arg(0); + expr* body = to_app(q->get_pattern(1))->get_arg(0); SASSERT(is_app(fn)); func_decl* f = to_app(fn)->get_decl(); - expr* eq = q->get_expr(); - expr_ref body(m); - if (is_fun_def(fn, q->get_expr(), body)) { - func_interp* fi = alloc(func_interp, m, f->get_arity()); - fi->set_else(body); - m_model->register_decl(f, fi); - } + func_interp* fi = alloc(func_interp, m, f->get_arity()); + fi->set_else(body); + m_model->register_decl(f, fi); } } } - bool context::is_fun_def(expr* f, expr* body, expr_ref& result) { - expr* t1, *t2, *t3; - if (m_manager.is_eq(body, t1, t2) || m_manager.is_iff(body, t1, t2)) { - if (t1 == f) return result = t2, true; - if (t2 == f) return result = t1, true; - return false; - } - if (m_manager.is_ite(body, t1, t2, t3)) { - expr_ref body1(m_manager), body2(m_manager); - if (is_fun_def(f, t2, body1) && is_fun_def(f, t3, body2)) { - // f is not free in t1 - result = m_manager.mk_ite(t1, body1, body2); - return true; - } - } - return false; - } - - }; diff --git a/src/smt/smt_context.h b/src/smt/smt_context.h index d05e07c40..ce81bb1cb 100644 --- a/src/smt/smt_context.h +++ b/src/smt/smt_context.h @@ -226,6 +226,15 @@ namespace smt { literal2assumption m_literal2assumption; // maps an expression associated with a literal to the original assumption expr_ref_vector m_unsat_core; + // ----------------------------------- + // + // Theory case split + // + // ----------------------------------- + uint_set m_all_th_case_split_literals; + vector m_th_case_split_sets; + u_map< vector > m_literal2casesplitsets; // returns the case split literal sets that a literal participates in + // ----------------------------------- // // Accessors @@ -827,6 +836,29 @@ namespace smt { void mk_th_axiom(theory_id tid, literal l1, literal l2, literal l3, unsigned num_params = 0, parameter * params = 0); + /* + * Provide a hint to the core solver that the specified literals form a "theory case split". + * The core solver will enforce the condition that exactly one of these literals can be + * assigned 'true' at any time. + * We assume that the theory solver has already asserted the disjunction of these literals + * or some other axiom that means at least one of them must be assigned 'true'. + */ + void mk_th_case_split(unsigned num_lits, literal * lits); + + + /* + * Provide a hint to the branching heuristic about the priority of a "theory-aware literal". + * Literals marked in this way will always be branched on before unmarked literals, + * starting with the literal having the highest priority. + */ + void add_theory_aware_branching_info(bool_var v, double priority, lbool phase); + + public: + + // helper function for trail + void undo_th_case_split(literal l); + + bool propagate_th_case_split(unsigned qhead); bool_var mk_bool_var(expr * n); @@ -1066,7 +1098,9 @@ namespace smt { void reset_assumptions(); - void mk_unsat_core(); + void add_theory_assumptions(expr_ref_vector & theory_assumptions); + + lbool mk_unsat_core(); void validate_unsat_core(); @@ -1110,6 +1144,10 @@ namespace smt { bool is_relevant_core(expr * n) const { return m_relevancy_propagator->is_relevant(n); } + svector m_relevant_conflict_literals; + void record_relevancy(unsigned n, literal const* lits); + void restore_relevancy(unsigned n, literal const* lits); + public: // event handler for relevancy_propagator class void relevant_eh(expr * n); @@ -1131,6 +1169,10 @@ namespace smt { return is_relevant(l.var()); } + bool is_relevant_core(literal l) const { + return is_relevant_core(bool_var2expr(l.var())); + } + void mark_as_relevant(expr * n) { m_relevancy_propagator->mark_as_relevant(n); m_relevancy_propagator->propagate(); } void mark_as_relevant(enode * n) { mark_as_relevant(n->get_owner()); } @@ -1166,8 +1208,6 @@ namespace smt { void add_rec_funs_to_model(); - bool is_fun_def(expr* f, expr* q, expr_ref& body); - public: bool can_propagate() const; @@ -1378,14 +1418,17 @@ namespace smt { typedef hashtable index_set; //typedef uint_set index_set; u_map m_antecedents; - void extract_fixed_consequences(literal lit, obj_map& var2val, index_set const& assumptions, expr_ref_vector& conseq); - void extract_fixed_consequences(unsigned& idx, obj_map& var2val, index_set const& assumptions, expr_ref_vector& conseq); + obj_map m_var2orig; + obj_map m_assumption2orig; + obj_map m_var2val; + void extract_fixed_consequences(literal lit, index_set const& assumptions, expr_ref_vector& conseq); + void extract_fixed_consequences(unsigned& idx, index_set const& assumptions, expr_ref_vector& conseq); void display_consequence_progress(std::ostream& out, unsigned it, unsigned nv, unsigned fixed, unsigned unfixed, unsigned eq); - unsigned delete_unfixed(obj_map& var2val, expr_ref_vector& unfixed); + unsigned delete_unfixed(expr_ref_vector& unfixed); - unsigned extract_fixed_eqs(obj_map& var2val, expr_ref_vector& conseq); + unsigned extract_fixed_eqs(expr_ref_vector& conseq); expr_ref antecedent2fml(index_set const& ante); @@ -1442,7 +1485,7 @@ namespace smt { void pop(unsigned num_scopes); - lbool check(unsigned num_assumptions = 0, expr * const * assumptions = 0, bool reset_cancel = true); + lbool check(unsigned num_assumptions = 0, expr * const * assumptions = 0, bool reset_cancel = true, bool already_did_theory_assumptions = false); lbool get_consequences(expr_ref_vector const& assumptions, expr_ref_vector const& vars, expr_ref_vector& conseq, expr_ref_vector& unfixed); diff --git a/src/smt/smt_context_inv.cpp b/src/smt/smt_context_inv.cpp index 7d009a037..e6551b8da 100644 --- a/src/smt/smt_context_inv.cpp +++ b/src/smt/smt_context_inv.cpp @@ -405,7 +405,6 @@ namespace smt { bool context::validate_justification(bool_var v, bool_var_data const& d, b_justification const& j) { if (j.get_kind() == b_justification::CLAUSE && v != true_bool_var) { clause* cls = j.get_clause(); - unsigned num_lits = cls->get_num_literals(); literal l = cls->get_literal(0); if (l.var() != v) { l = cls->get_literal(1); @@ -433,6 +432,9 @@ namespace smt { if (!is_ground(n)) { continue; } + if (is_quantifier(n) && m.is_rec_fun_def(to_quantifier(n))) { + continue; + } switch (get_assignment(*it)) { case l_undef: break; diff --git a/src/smt/smt_justification.cpp b/src/smt/smt_justification.cpp index c091f6973..7a9938cd0 100644 --- a/src/smt/smt_justification.cpp +++ b/src/smt/smt_justification.cpp @@ -246,13 +246,15 @@ namespace smt { simple_justification::simple_justification(region & r, unsigned num_lits, literal const * lits): m_num_literals(num_lits) { - m_literals = new (r) literal[num_lits]; - memcpy(m_literals, lits, sizeof(literal) * num_lits); + if (num_lits != 0) { + m_literals = new (r) literal[num_lits]; + memcpy(m_literals, lits, sizeof(literal) * num_lits); #ifdef Z3DEBUG - for (unsigned i = 0; i < num_lits; i++) { - SASSERT(lits[i] != null_literal); - } + for (unsigned i = 0; i < num_lits; i++) { + SASSERT(lits[i] != null_literal); + } #endif + } } void simple_justification::get_antecedents(conflict_resolution & cr) { diff --git a/src/smt/smt_model_checker.cpp b/src/smt/smt_model_checker.cpp index 093d215b6..279ea20cf 100644 --- a/src/smt/smt_model_checker.cpp +++ b/src/smt/smt_model_checker.cpp @@ -316,9 +316,9 @@ namespace smt { return false; } - bool model_checker::check_rec_fun(quantifier* q) { + bool model_checker::check_rec_fun(quantifier* q, bool strict_rec_fun) { TRACE("model_checker", tout << mk_pp(q, m) << "\n";); - SASSERT(q->get_num_patterns() == 1); + SASSERT(q->get_num_patterns() == 2); // first pattern is the function, second is the body. expr* fn = to_app(q->get_pattern(0))->get_arg(0); SASSERT(is_app(fn)); func_decl* f = to_app(fn)->get_decl(); @@ -340,7 +340,7 @@ namespace smt { } sub(q->get_expr(), num_decls, args.c_ptr(), tmp); m_curr_model->eval(tmp, result, true); - if (m.is_false(result)) { + if (strict_rec_fun ? !m.is_true(result) : m.is_false(result)) { add_instance(q, args, 0); return false; } @@ -365,10 +365,10 @@ namespace smt { bool model_checker::check(proto_model * md, obj_map const & root2value) { SASSERT(md != 0); + m_root2value = &root2value; - ptr_vector::const_iterator it = m_qm->begin_quantifiers(); - ptr_vector::const_iterator end = m_qm->end_quantifiers(); - if (it == end) + + if (m_qm->num_quantifiers() == 0) return true; if (m_iteration_idx >= m_params.m_mbqi_max_iterations) { @@ -393,6 +393,36 @@ namespace smt { bool found_relevant = false; unsigned num_failures = 0; + check_quantifiers(false, found_relevant, num_failures); + + + if (found_relevant) + m_iteration_idx++; + + TRACE("model_checker", tout << "model after check:\n"; model_pp(tout, *md);); + TRACE("model_checker", tout << "model checker result: " << (num_failures == 0) << "\n";); + m_max_cexs += m_params.m_mbqi_max_cexs; + + if (num_failures == 0 && !m_context->validate_model()) { + num_failures = 1; + // this time force expanding recursive function definitions + // that are not forced true in the current model. + check_quantifiers(true, found_relevant, num_failures); + } + if (num_failures == 0) + m_curr_model->cleanup(); + if (m_params.m_mbqi_trace) { + if (num_failures == 0) + verbose_stream() << "(smt.mbqi :succeeded true)\n"; + else + verbose_stream() << "(smt.mbqi :num-failures " << num_failures << ")\n"; + } + return num_failures == 0; + } + + void model_checker::check_quantifiers(bool strict_rec_fun, bool& found_relevant, unsigned& num_failures) { + ptr_vector::const_iterator it = m_qm->begin_quantifiers(); + ptr_vector::const_iterator end = m_qm->end_quantifiers(); for (; it != end; ++it) { quantifier * q = *it; if(!m_qm->mbqi_enabled(q)) continue; @@ -406,7 +436,7 @@ namespace smt { } found_relevant = true; if (m.is_rec_fun_def(q)) { - if (!check_rec_fun(q)) { + if (!check_rec_fun(q, strict_rec_fun)) { TRACE("model_checker", tout << "checking recursive function failed\n";); num_failures++; } @@ -420,26 +450,6 @@ namespace smt { } } } - - if (found_relevant) - m_iteration_idx++; - - TRACE("model_checker", tout << "model after check:\n"; model_pp(tout, *md);); - TRACE("model_checker", tout << "model checker result: " << (num_failures == 0) << "\n";); - m_max_cexs += m_params.m_mbqi_max_cexs; - - if (num_failures == 0 && !m_context->validate_model()) { - num_failures = 1; - } - if (num_failures == 0) - m_curr_model->cleanup(); - if (m_params.m_mbqi_trace) { - if (num_failures == 0) - verbose_stream() << "(smt.mbqi :succeeded true)\n"; - else - verbose_stream() << "(smt.mbqi :num-failures " << num_failures << ")\n"; - } - return num_failures == 0; } void model_checker::init_search_eh() { diff --git a/src/smt/smt_model_checker.h b/src/smt/smt_model_checker.h index b94ddb6bb..1b7713d59 100644 --- a/src/smt/smt_model_checker.h +++ b/src/smt/smt_model_checker.h @@ -59,7 +59,8 @@ namespace smt { void assert_neg_q_m(quantifier * q, expr_ref_vector & sks); bool add_blocking_clause(model * cex, expr_ref_vector & sks); bool check(quantifier * q); - bool check_rec_fun(quantifier* q); + bool check_rec_fun(quantifier* q, bool strict_rec_fun); + void check_quantifiers(bool strict_rec_fun, bool& found_relevant, unsigned& num_failures); struct instance { quantifier * m_q; diff --git a/src/smt/smt_quantifier.cpp b/src/smt/smt_quantifier.cpp index 2a56168f2..10e2df988 100644 --- a/src/smt/smt_quantifier.cpp +++ b/src/smt/smt_quantifier.cpp @@ -52,8 +52,9 @@ namespace smt { m_qi_queue.setup(); } - bool has_trace_stream() const { return m_context.get_manager().has_trace_stream(); } - std::ostream & trace_stream() { return m_context.get_manager().trace_stream(); } + ast_manager& m() const { return m_context.get_manager(); } + bool has_trace_stream() const { return m().has_trace_stream(); } + std::ostream & trace_stream() { return m().trace_stream(); } quantifier_stat * get_stat(quantifier * q) const { return m_quantifier_stat.find(q); @@ -110,8 +111,9 @@ namespace smt { unsigned max_top_generation, ptr_vector & used_enodes) { max_generation = std::max(max_generation, get_generation(q)); - if (m_num_instances > m_params.m_qi_max_instances) + if (m_num_instances > m_params.m_qi_max_instances) { return false; + } get_stat(q)->update_max_generation(max_generation); fingerprint * f = m_context.add_fingerprint(q, q->get_id(), num_bindings, bindings); if (f) { @@ -132,9 +134,17 @@ namespace smt { } m_qi_queue.insert(f, pat, max_generation, min_top_generation, max_top_generation); // TODO m_num_instances++; - return true; } - return false; + TRACE("quantifier", + tout << mk_pp(q, m()) << " "; + for (unsigned i = 0; i < num_bindings; ++i) { + tout << mk_pp(bindings[i]->get_owner(), m()) << " "; + } + tout << "\n"; + tout << "inserted: " << (f != 0) << "\n"; + ); + + return f != 0; } void init_search_eh() { @@ -186,7 +196,7 @@ namespace smt { } bool check_quantifier(quantifier* q) { - return m_context.is_relevant(q) && m_context.get_assignment(q) == l_true; // && !m_context.get_manager().is_rec_fun_def(q); + return m_context.is_relevant(q) && m_context.get_assignment(q) == l_true; // && !m().is_rec_fun_def(q); } bool quick_check_quantifiers() { @@ -387,6 +397,10 @@ namespace smt { return m_imp->m_quantifiers.end(); } + unsigned quantifier_manager::num_quantifiers() const { + return m_imp->m_quantifiers.size(); + } + // The default plugin uses E-matching, MBQI and quick-checker class default_qm_plugin : public quantifier_manager_plugin { quantifier_manager * m_qm; @@ -501,13 +515,13 @@ namespace smt { SASSERT(m_context->get_manager().is_pattern(mp)); bool unary = (mp->get_num_args() == 1); if (!unary && j >= num_eager_multi_patterns) { - TRACE("assign_quantifier", tout << "delaying (too many multipatterns):\n" << mk_ismt2_pp(mp, m_context->get_manager()) << "\n" + TRACE("quantifier", tout << "delaying (too many multipatterns):\n" << mk_ismt2_pp(mp, m_context->get_manager()) << "\n" << "j: " << j << " unary: " << unary << " m_params.m_qi_max_eager_multipatterns: " << m_fparams->m_qi_max_eager_multipatterns << " num_eager_multi_patterns: " << num_eager_multi_patterns << "\n";); m_lazy_mam->add_pattern(q, mp); } else { - TRACE("assign_quantifier", tout << "adding:\n" << mk_ismt2_pp(mp, m_context->get_manager()) << "\n";); + TRACE("quantifier", tout << "adding:\n" << mk_ismt2_pp(mp, m_context->get_manager()) << "\n";); m_mam->add_pattern(q, mp); } if (!unary) diff --git a/src/smt/smt_quantifier.h b/src/smt/smt_quantifier.h index bc249ed1a..6dcf20583 100644 --- a/src/smt/smt_quantifier.h +++ b/src/smt/smt_quantifier.h @@ -91,6 +91,8 @@ namespace smt { ptr_vector::const_iterator begin_quantifiers() const; ptr_vector::const_iterator end_quantifiers() const; + unsigned num_quantifiers() const; + }; class quantifier_manager_plugin { diff --git a/src/smt/smt_setup.cpp b/src/smt/smt_setup.cpp index ee92c4f61..a4f2ee646 100644 --- a/src/smt/smt_setup.cpp +++ b/src/smt/smt_setup.cpp @@ -33,6 +33,7 @@ Revision History: #include"theory_seq.h" #include"theory_pb.h" #include"theory_fpa.h" +#include"theory_str.h" namespace smt { @@ -120,6 +121,8 @@ namespace smt { setup_QF_FP(); else if (m_logic == "QF_FPBV" || m_logic == "QF_BVFP") setup_QF_FPBV(); + else if (m_logic == "QF_S") + setup_QF_S(); else setup_unknown(); } @@ -161,6 +164,8 @@ namespace smt { setup_QF_BVRE(); else if (m_logic == "QF_AUFLIA") setup_QF_AUFLIA(st); + else if (m_logic == "QF_S") + setup_QF_S(); else if (m_logic == "AUFLIA") setup_AUFLIA(st); else if (m_logic == "AUFLIRA") @@ -201,7 +206,7 @@ namespace smt { void setup::setup_QF_BVRE() { setup_QF_BV(); setup_QF_LIA(); - setup_seq(); + m_context.register_plugin(alloc(theory_seq, m_manager)); } void setup::setup_QF_UF(static_features const & st) { @@ -700,6 +705,11 @@ namespace smt { m_context.register_plugin(alloc(smt::theory_fpa, m_manager)); } + void setup::setup_QF_S() { + m_context.register_plugin(alloc(smt::theory_mi_arith, m_manager, m_params)); + m_context.register_plugin(alloc(smt::theory_str, m_manager, m_params)); + } + bool is_arith(static_features const & st) { return st.m_num_arith_ineqs > 0 || st.m_num_arith_terms > 0 || st.m_num_arith_eqs > 0; } @@ -814,8 +824,25 @@ namespace smt { m_context.register_plugin(mk_theory_dl(m_manager)); } - void setup::setup_seq() { - m_context.register_plugin(alloc(theory_seq, m_manager)); + void setup::setup_seq_str(static_features const & st) { + // check params for what to do here when it's ambiguous + if (m_params.m_string_solver == "z3str3") { + setup_str(); + } + else if (m_params.m_string_solver == "seq") { + setup_seq(); + } + else if (m_params.m_string_solver == "auto") { + if (st.m_has_seq_non_str) { + setup_seq(); + } + else { + setup_str(); + } + } + else { + throw default_exception("invalid parameter for smt.string_solver, valid options are 'z3str3', 'seq', 'auto'"); + } } void setup::setup_card() { @@ -827,13 +854,25 @@ namespace smt { m_context.register_plugin(alloc(theory_fpa, m_manager)); } + void setup::setup_str() { + setup_arith(); + m_context.register_plugin(alloc(theory_str, m_manager, m_params)); + } + + void setup::setup_seq() { + m_context.register_plugin(alloc(smt::theory_seq, m_manager)); + } + void setup::setup_unknown() { + static_features st(m_manager); + st.collect(m_context.get_num_asserted_formulas(), m_context.get_asserted_formulas()); + setup_arith(); setup_arrays(); setup_bv(); setup_datatypes(); setup_dl(); - setup_seq(); + setup_seq_str(st); setup_card(); setup_fpa(); } @@ -848,7 +887,7 @@ namespace smt { setup_datatypes(); setup_bv(); setup_dl(); - setup_seq(); + setup_seq_str(st); setup_card(); setup_fpa(); return; diff --git a/src/smt/smt_setup.h b/src/smt/smt_setup.h index 68cd5703c..80d5d7d1b 100644 --- a/src/smt/smt_setup.h +++ b/src/smt/smt_setup.h @@ -77,6 +77,7 @@ namespace smt { void setup_QF_AUFLIA(static_features const & st); void setup_QF_FP(); void setup_QF_FPBV(); + void setup_QF_S(); void setup_LRA(); void setup_AUFLIA(bool simple_array = true); void setup_AUFLIA(static_features const & st); @@ -93,11 +94,13 @@ namespace smt { void setup_bv(); void setup_arith(); void setup_dl(); + void setup_seq_str(static_features const & st); void setup_seq(); void setup_card(); void setup_i_arith(); void setup_mi_arith(); void setup_fpa(); + void setup_str(); public: setup(context & c, smt_params & params); diff --git a/src/smt/smt_solver.cpp b/src/smt/smt_solver.cpp index 59a5ddf8f..cd912b72e 100644 --- a/src/smt/smt_solver.cpp +++ b/src/smt/smt_solver.cpp @@ -38,6 +38,7 @@ namespace smt { bool m_minimizing_core; bool m_core_extend_patterns; unsigned m_core_extend_patterns_max_distance; + bool m_core_extend_nonlocal_patterns; obj_map m_name2assertion; public: @@ -48,13 +49,15 @@ namespace smt { m_context(m, m_smt_params), m_minimizing_core(false), m_core_extend_patterns(false), - m_core_extend_patterns_max_distance(UINT_MAX) { + m_core_extend_patterns_max_distance(UINT_MAX), + m_core_extend_nonlocal_patterns(false) { m_logic = l; if (m_logic != symbol::null) m_context.set_logic(m_logic); smt_params_helper smth(p); m_core_extend_patterns = smth.core_extend_patterns(); m_core_extend_patterns_max_distance = smth.core_extend_patterns_max_distance(); + m_core_extend_nonlocal_patterns = smth.core_extend_nonlocal_patterns(); } virtual solver * translate(ast_manager & m, params_ref const & p) { @@ -81,6 +84,8 @@ namespace smt { m_context.updt_params(p); smt_params_helper smth(p); m_core_extend_patterns = smth.core_extend_patterns(); + m_core_extend_patterns_max_distance = smth.core_extend_patterns_max_distance(); + m_core_extend_nonlocal_patterns = smth.core_extend_nonlocal_patterns(); } virtual void collect_param_descrs(param_descrs & r) { @@ -172,6 +177,8 @@ namespace smt { if (m_core_extend_patterns) add_pattern_literals_to_core(r); + if (m_core_extend_nonlocal_patterns) + add_nonlocal_pattern_literals_to_core(r); } virtual void get_model(model_ref & m) { @@ -250,7 +257,7 @@ namespace smt { } }; - void collect_pattern_func_decls(expr_ref & e, func_decl_set & fds) { + void collect_pattern_fds(expr_ref & e, func_decl_set & fds) { collect_pattern_fds_proc p(get_manager(), fds); expr_mark visited; for_each_expr(p, visited, e); @@ -295,7 +302,7 @@ namespace smt { expr_ref name(core[i], m); SASSERT(m_name2assertion.contains(name)); expr_ref assrtn(m_name2assertion.find(name), m); - collect_pattern_func_decls(assrtn, pattern_fds); + collect_pattern_fds(assrtn, pattern_fds); } if (!pattern_fds.empty()) { @@ -317,6 +324,55 @@ namespace smt { break; } } + + struct collect_body_fds_proc { + ast_manager & m; + func_decl_set & m_fds; + collect_body_fds_proc(ast_manager & m, func_decl_set & fds) : + m(m), m_fds(fds) { + } + void operator()(var * n) {} + void operator()(app * n) {} + void operator()(quantifier * n) { + collect_fds_proc p(m, m_fds); + expr_fast_mark1 visited; + quick_for_each_expr(p, visited, n->get_expr()); + } + }; + + void collect_body_func_decls(expr_ref & e, func_decl_set & fds) { + ast_manager & m = get_manager(); + collect_body_fds_proc p(m, fds); + expr_mark visited; + for_each_expr(p, visited, e); + } + + void add_nonlocal_pattern_literals_to_core(ptr_vector & core) { + ast_manager & m = get_manager(); + + obj_map::iterator it = m_name2assertion.begin(); + obj_map::iterator end = m_name2assertion.end(); + for (unsigned i = 0; it != end; it++, i++) { + expr_ref name(it->m_key, m); + expr_ref assrtn(it->m_value, m); + + if (!core.contains(name)) { + func_decl_set pattern_fds, body_fds; + collect_pattern_fds(assrtn, pattern_fds); + collect_body_func_decls(assrtn, body_fds); + + func_decl_set::iterator pit = pattern_fds.begin(); + func_decl_set::iterator pend= pattern_fds.end(); + for (; pit != pend; pit++) { + func_decl * fd = *pit; + if (!body_fds.contains(fd)) { + core.insert(name); + break; + } + } + } + } + } }; }; diff --git a/src/smt/smt_theory.h b/src/smt/smt_theory.h index cee36535f..67091c601 100644 --- a/src/smt/smt_theory.h +++ b/src/smt/smt_theory.h @@ -177,6 +177,22 @@ namespace smt { virtual void restart_eh() { } + /** + \brief This method is called by smt_context before the search starts + to get any extra assumptions the theory wants to use. + (See theory_str for an example) + */ + virtual void add_theory_assumptions(expr_ref_vector & assumptions) { + } + + /** + \brief This method is called from the smt_context when an unsat core is generated. + The theory may change the answer to UNKNOWN by returning l_undef from this method. + */ + virtual lbool validate_unsat_core(expr_ref_vector & unsat_core) { + return l_false; + } + /** \brief This method is invoked before the search starts. */ diff --git a/src/smt/theory_arith.h b/src/smt/theory_arith.h index 77882f186..cdc1a3933 100644 --- a/src/smt/theory_arith.h +++ b/src/smt/theory_arith.h @@ -505,7 +505,7 @@ namespace smt { struct var_value_eq { theory_arith & m_th; var_value_eq(theory_arith & th):m_th(th) {} - bool operator()(theory_var v1, theory_var v2) const { return m_th.get_value(v1) == m_th.get_value(v2) && m_th.is_int(v1) == m_th.is_int(v2); } + bool operator()(theory_var v1, theory_var v2) const { return m_th.get_value(v1) == m_th.get_value(v2) && m_th.is_int_src(v1) == m_th.is_int_src(v2); } }; typedef int_hashtable var_value_table; @@ -552,6 +552,7 @@ namespace smt { bool is_int(theory_var v) const { return m_data[v].m_is_int; } bool is_int_src(theory_var v) const { return m_util.is_int(var2expr(v)); } bool is_real(theory_var v) const { return !is_int(v); } + bool is_real_src(theory_var v) const { return !is_int_src(v); } bool get_implied_old_value(theory_var v, inf_numeral & r) const; inf_numeral const & get_implied_value(theory_var v) const; inf_numeral const & get_quasi_base_value(theory_var v) const { return get_implied_value(v); } diff --git a/src/smt/theory_arith_aux.h b/src/smt/theory_arith_aux.h index de357c8d3..54b617152 100644 --- a/src/smt/theory_arith_aux.h +++ b/src/smt/theory_arith_aux.h @@ -2201,16 +2201,19 @@ namespace smt { int num = get_num_vars(); for (theory_var v = 0; v < num; v++) { enode * n = get_enode(v); - TRACE("func_interp_bug", tout << "#" << n->get_owner_id() << " -> " << m_value[v] << "\n";); - if (!is_relevant_and_shared(n)) + TRACE("func_interp_bug", tout << mk_pp(n->get_owner(), get_manager()) << " -> " << m_value[v] << " root #" << n->get_root()->get_owner_id() << " " << is_relevant_and_shared(n) << "\n";); + if (!is_relevant_and_shared(n)) { continue; + } theory_var other = null_theory_var; other = m_var_value_table.insert_if_not_there(v); - if (other == v) + if (other == v) { continue; + } enode * n2 = get_enode(other); - if (n->get_root() == n2->get_root()) + if (n->get_root() == n2->get_root()) { continue; + } TRACE("func_interp_bug", tout << "adding to assume_eq queue #" << n->get_owner_id() << " #" << n2->get_owner_id() << "\n";); m_assume_eq_candidates.push_back(std::make_pair(other, v)); result = true; diff --git a/src/smt/theory_arith_core.h b/src/smt/theory_arith_core.h index 513cf36a4..5c652414a 100644 --- a/src/smt/theory_arith_core.h +++ b/src/smt/theory_arith_core.h @@ -40,7 +40,7 @@ namespace smt { template void theory_arith::found_underspecified_op(app * n) { if (!m_found_underspecified_op) { - TRACE("arith", tout << "found non underspecificed expression:\n" << mk_pp(n, get_manager()) << "\n";); + TRACE("arith", tout << "found underspecificed expression:\n" << mk_pp(n, get_manager()) << "\n";); get_context().push_trail(value_trail(m_found_underspecified_op)); m_found_underspecified_op = true; } @@ -395,6 +395,7 @@ namespace smt { template theory_var theory_arith::internalize_div(app * n) { + if (!m_util.is_numeral(n->get_arg(1))) found_underspecified_op(n); found_underspecified_op(n); theory_var s = mk_binary_op(n); context & ctx = get_context(); @@ -418,7 +419,7 @@ namespace smt { template theory_var theory_arith::internalize_mod(app * n) { TRACE("arith_mod", tout << "internalizing...\n" << mk_pp(n, get_manager()) << "\n";); - found_underspecified_op(n); + if (!m_util.is_numeral(n->get_arg(1))) found_underspecified_op(n); theory_var s = mk_binary_op(n); context & ctx = get_context(); if (!ctx.relevancy()) @@ -428,7 +429,7 @@ namespace smt { template theory_var theory_arith::internalize_rem(app * n) { - found_underspecified_op(n); + if (!m_util.is_numeral(n->get_arg(1))) found_underspecified_op(n); theory_var s = mk_binary_op(n); context & ctx = get_context(); if (!ctx.relevancy()) { @@ -464,7 +465,7 @@ namespace smt { TRACE("arith_axiom", tout << mk_pp(ante, m) << "\n" << mk_pp(conseq, m) << "\n"; tout << s_ante << "\n" << s_conseq << "\n";); - literal lits[2] = {l_ante, l_conseq}; + // literal lits[2] = {l_ante, l_conseq}; mk_clause(l_ante, l_conseq, 0, 0); if (ctx.relevancy()) { if (l_ante == false_literal) { diff --git a/src/smt/theory_arith_int.h b/src/smt/theory_arith_int.h index c06f82c8b..e6e9e863c 100644 --- a/src/smt/theory_arith_int.h +++ b/src/smt/theory_arith_int.h @@ -206,7 +206,8 @@ namespace smt { numeral k = ceil(get_value(v)); rational _k = k.to_rational(); expr_ref bound(get_manager()); - bound = m_util.mk_ge(get_enode(v)->get_owner(), m_util.mk_numeral(_k, true)); + expr* e = get_enode(v)->get_owner(); + bound = m_util.mk_ge(e, m_util.mk_numeral(_k, m_util.is_int(e))); TRACE("arith_int", tout << mk_bounded_pp(bound, get_manager()) << "\n";); context & ctx = get_context(); ctx.internalize(bound, true); @@ -371,7 +372,7 @@ namespace smt { ctx.mk_th_axiom(get_id(), l1, l2); - TRACE("theory_arith_int", + TRACE("arith_int", tout << "cut: (or " << mk_pp(p1, get_manager()) << " " << mk_pp(p2, get_manager()) << ")\n"; ); @@ -1407,6 +1408,7 @@ namespace smt { if (m_params.m_arith_int_eq_branching && branch_infeasible_int_equality()) { return FC_CONTINUE; } + theory_var int_var = find_infeasible_int_base_var(); if (int_var != null_theory_var) { TRACE("arith_int", tout << "v" << int_var << " does not have an integer assignment: " << get_value(int_var) << "\n";); diff --git a/src/smt/theory_arith_nl.h b/src/smt/theory_arith_nl.h index 52a117cd5..8a632cf48 100644 --- a/src/smt/theory_arith_nl.h +++ b/src/smt/theory_arith_nl.h @@ -339,8 +339,13 @@ namespace smt { tout << mk_pp(var, get_manager()) << "\n"; tout << "power " << power << ": " << expt(i, power) << "\n"; display_interval(tout << "target before: ", target); tout << "\n";); + i.expt(power); target *= i; + + get_manager().limit().inc((target.is_lower_open() || target.minus_infinity()) ? 1 : target.get_lower_value().bitsize()); + get_manager().limit().inc((target.is_upper_open() || target.plus_infinity()) ? 1 : target.get_upper_value().bitsize()); + TRACE("non_linear", display_interval(tout << "target after: ", target); tout << "\n";); } @@ -439,7 +444,7 @@ namespace smt { m_asserted_bounds.push_back(new_bound); // copy justification to new bound dependency2new_bound(dep, *new_bound); - TRACE("buggy_bound", new_bound->display(*this, tout); tout << "\n";); + TRACE("non_linear", new_bound->display(*this, tout); tout << "\n";); } /** @@ -452,8 +457,19 @@ namespace smt { bool r = false; if (!i.minus_infinity()) { inf_numeral new_lower(i.get_lower_value()); - if (i.is_lower_open()) - new_lower += get_epsilon(v); + if (i.is_lower_open()) { + if (is_int(v)) { + if (new_lower.is_int()) { + new_lower += rational::one(); + } + else { + new_lower = ceil(new_lower.get_rational()); + } + } + else { + new_lower += get_epsilon(v); + } + } bound * old_lower = lower(v); if (old_lower == 0 || new_lower > old_lower->get_value()) { TRACE("non_linear", tout << "NEW lower bound for v" << v << " " << new_lower << "\n"; @@ -464,8 +480,19 @@ namespace smt { } if (!i.plus_infinity()) { inf_numeral new_upper(i.get_upper_value()); - if (i.is_upper_open()) - new_upper -= get_epsilon(v); + if (i.is_upper_open()) { + if (is_int(v)) { + if (new_upper.is_int()) { + new_upper -= rational::one(); + } + else { + new_upper = floor(new_upper.get_rational()); + } + } + else { + new_upper -= get_epsilon(v); + } + } bound * old_upper = upper(v); if (old_upper == 0 || new_upper < old_upper->get_value()) { TRACE("non_linear", tout << "NEW upper bound for v" << v << " " << new_upper << "\n"; @@ -814,6 +841,7 @@ namespace smt { if (is_fixed(_var)) r *= lower_bound(_var).get_rational(); } + TRACE("arith", tout << mk_pp(m, get_manager()) << " " << r << "\n";); return r; } @@ -891,7 +919,7 @@ namespace smt { // Assert the equality // (= (* x_1 ... x_n) k) - TRACE("non_linear", tout << "all variables are fixed.\n";); + TRACE("non_linear", tout << "all variables are fixed, and bound is: " << k << "\n";); new_lower = alloc(derived_bound, v, inf_numeral(k), B_LOWER); new_upper = alloc(derived_bound, v, inf_numeral(k), B_UPPER); } @@ -948,7 +976,8 @@ namespace smt { new_upper->m_eqs.append(new_lower->m_eqs); TRACE("non_linear", - tout << "lower: " << new_lower << " upper: " << new_upper << "\n"; + new_lower->display(*this, tout << "lower: "); tout << "\n"; + new_upper->display(*this, tout << "upper: "); tout << "\n"; for (unsigned j = 0; j < new_upper->m_lits.size(); ++j) { ctx.display_detailed_literal(tout, new_upper->m_lits[j]); tout << " "; diff --git a/src/smt/theory_bv.cpp b/src/smt/theory_bv.cpp index a886c8a1e..255e2044f 100644 --- a/src/smt/theory_bv.cpp +++ b/src/smt/theory_bv.cpp @@ -607,12 +607,13 @@ namespace smt { } expr_ref sum(m); arith_simp().mk_add(sz, args.c_ptr(), sum); + literal l(mk_eq(n, sum, false)); TRACE("bv", tout << mk_pp(n, m) << "\n"; tout << mk_pp(sum, m) << "\n"; + ctx.display_literal_verbose(tout, l); + tout << "\n"; ); - - literal l(mk_eq(n, sum, false)); ctx.mark_as_relevant(l); ctx.mk_th_axiom(get_id(), 1, &l); @@ -761,6 +762,21 @@ namespace smt { TRACE("bv", tout << mk_pp(cond, get_manager()) << "\n"; tout << l << "\n";); \ } + void theory_bv::internalize_sub(app *n) { + SASSERT(!get_context().e_internalized(n)); + SASSERT(n->get_num_args() == 2); + process_args(n); + ast_manager & m = get_manager(); + enode * e = mk_enode(n); + expr_ref_vector arg1_bits(m), arg2_bits(m), bits(m); + get_arg_bits(e, 0, arg1_bits); + get_arg_bits(e, 1, arg2_bits); + SASSERT(arg1_bits.size() == arg2_bits.size()); + expr_ref carry(m); + m_bb.mk_subtracter(arg1_bits.size(), arg1_bits.c_ptr(), arg2_bits.c_ptr(), bits, carry); + init_bits(e, bits); + } + MK_UNARY(internalize_not, mk_not); MK_UNARY(internalize_redand, mk_redand); MK_UNARY(internalize_redor, mk_redor); @@ -847,6 +863,7 @@ namespace smt { switch (term->get_decl_kind()) { case OP_BV_NUM: internalize_num(term); return true; case OP_BADD: internalize_add(term); return true; + case OP_BSUB: internalize_sub(term); return true; case OP_BMUL: internalize_mul(term); return true; case OP_BSDIV_I: internalize_sdiv(term); return true; case OP_BUDIV_I: internalize_udiv(term); return true; diff --git a/src/smt/theory_bv.h b/src/smt/theory_bv.h index 17d03b412..50e4f9c30 100644 --- a/src/smt/theory_bv.h +++ b/src/smt/theory_bv.h @@ -172,6 +172,7 @@ namespace smt { bool get_fixed_value(theory_var v, numeral & result) const; void internalize_num(app * n); void internalize_add(app * n); + void internalize_sub(app * n); void internalize_mul(app * n); void internalize_udiv(app * n); void internalize_sdiv(app * n); diff --git a/src/smt/theory_dense_diff_logic_def.h b/src/smt/theory_dense_diff_logic_def.h index 877d4f659..addb5d92b 100644 --- a/src/smt/theory_dense_diff_logic_def.h +++ b/src/smt/theory_dense_diff_logic_def.h @@ -868,7 +868,8 @@ namespace smt { e = ctx.get_enode(to_app(n)); } else { - e = ctx.mk_enode(to_app(n), false, false, true); + ctx.internalize(n, false); + e = ctx.get_enode(n); } v = e->get_th_var(get_id()); if (v == null_theory_var) { @@ -901,7 +902,7 @@ namespace smt { objective_term const& objective = m_objectives[v]; has_shared = false; - IF_VERBOSE(1, + IF_VERBOSE(4, for (unsigned i = 0; i < objective.size(); ++i) { verbose_stream() << objective[i].second << " * v" << objective[i].first << " "; @@ -991,9 +992,12 @@ namespace smt { if (num_nodes <= v && v < num_nodes + num_edges) { unsigned edge_id = v - num_nodes; literal lit = m_edges[edge_id].m_justification; - get_context().literal2expr(lit, tmp); - core.push_back(tmp); + if (lit != null_literal) { + get_context().literal2expr(lit, tmp); + core.push_back(tmp); + } } + TRACE("opt", tout << core << "\n";); } for (unsigned i = 0; i < num_nodes; ++i) { mpq_inf const& val = S.get_value(i); @@ -1005,7 +1009,8 @@ namespace smt { inf_eps result(rational(0), r); blocker = mk_gt(v, result); IF_VERBOSE(10, verbose_stream() << blocker << "\n";); - return result; + r += m_objective_consts[v]; + return inf_eps(rational(0), r); } default: TRACE("opt", tout << "unbounded\n"; ); @@ -1016,6 +1021,7 @@ namespace smt { template theory_var theory_dense_diff_logic::add_objective(app* term) { + TRACE("opt", tout << mk_pp(term, get_manager()) << "\n";); objective_term objective; theory_var result = m_objectives.size(); rational q(1), r(0); @@ -1050,6 +1056,7 @@ namespace smt { ast_manager& m = get_manager(); objective_term const& t = m_objectives[v]; expr_ref e(m), f(m), f2(m); + TRACE("opt", tout << "mk_ineq " << v << " " << val << "\n";); if (t.size() == 1 && t[0].second.is_one()) { f = get_enode(t[0].first)->get_owner(); } diff --git a/src/smt/theory_seq.cpp b/src/smt/theory_seq.cpp index d5251c56b..7a9369fd3 100644 --- a/src/smt/theory_seq.cpp +++ b/src/smt/theory_seq.cpp @@ -255,6 +255,11 @@ final_check_status theory_seq::final_check_eh() { TRACE("seq", tout << ">>solve_eqs\n";); return FC_CONTINUE; } + if (check_contains()) { + ++m_stats.m_propagate_contains; + TRACE("seq", tout << ">>propagate_contains\n";); + return FC_CONTINUE; + } if (solve_nqs(0)) { ++m_stats.m_solve_nqs; TRACE("seq", tout << ">>solve_nqs\n";); @@ -290,11 +295,6 @@ final_check_status theory_seq::final_check_eh() { TRACE("seq", tout << ">>propagate_automata\n";); return FC_CONTINUE; } - if (check_contains()) { - ++m_stats.m_propagate_contains; - TRACE("seq", tout << ">>propagate_contains\n";); - return FC_CONTINUE; - } if (is_solved()) { TRACE("seq", tout << ">>is_solved\n";); return FC_DONE; @@ -1159,7 +1159,7 @@ bool theory_seq::check_extensionality() { } /* - \brief check negated contains constriants. + \brief check negated contains constraints. */ bool theory_seq::check_contains() { context & ctx = get_context(); @@ -1199,6 +1199,11 @@ bool theory_seq::is_solved() { IF_VERBOSE(10, display_disequation(verbose_stream() << "(seq.giveup ", m_nqs[0]); verbose_stream() << " is unsolved)\n";); return false; } + if (!m_ncs.empty()) { + TRACE("seq", display_nc(tout << "(seq.giveup ", m_ncs[0]); tout << " is unsolved)\n";); + IF_VERBOSE(10, display_nc(verbose_stream() << "(seq.giveup ", m_ncs[0]); verbose_stream() << " is unsolved)\n";); + return false; + } return true; } @@ -1981,6 +1986,23 @@ bool theory_seq::solve_nc(unsigned idx) { } if (c != n.contains()) { m_ncs.push_back(nc(c, deps)); + m_new_propagation = true; + return true; + } + + expr* e1, *e2; + if (m.is_eq(c, e1, e2)) { + literal eq = mk_eq(e1, e2, false); + propagate_lit(deps, 0, 0, ~eq); + return true; + } + + if (m.is_or(c)) { + for (unsigned i = 0; i < to_app(c)->get_num_args(); ++i) { + expr_ref ci(to_app(c)->get_arg(i), m); + m_ncs.push_back(nc(ci, deps)); + } + m_new_propagation = true; return true; } return false; @@ -2344,6 +2366,17 @@ bool theory_seq::add_itos_axiom(expr* e) { return false; } add_axiom(mk_eq(e2, n, false)); + +#if 1 + expr_ref num_re(m), opt_re(m); + num_re = m_util.re.mk_range(m_util.str.mk_string(symbol("0")), m_util.str.mk_string(symbol("9"))); + num_re = m_util.re.mk_plus(num_re); + opt_re = m_util.re.mk_opt(m_util.re.mk_to_re(m_util.str.mk_string(symbol("-")))); + num_re = m_util.re.mk_concat(opt_re, num_re); + app_ref in_re(m_util.re.mk_in_re(e, num_re), m); + internalize_term(in_re); + propagate_in_re(in_re, true); +#endif m_trail_stack.push(push_replay(alloc(replay_axiom, m, e))); return true; } @@ -2403,6 +2436,18 @@ void theory_seq::display(std::ostream & out) const { } } + if (!m_ncs.empty()) { + out << "Non contains:\n"; + for (unsigned i = 0; i < m_ncs.size(); ++i) { + display_nc(out, m_ncs[i]); + } + } + +} + +void theory_seq::display_nc(std::ostream& out, nc const& nc) const { + out << "not " << mk_pp(nc.contains(), m) << "\n"; + display_deps(out << " <- ", nc.deps()); out << "\n"; } void theory_seq::display_equations(std::ostream& out) const { @@ -2719,7 +2764,9 @@ bool theory_seq::can_propagate() { expr_ref theory_seq::canonize(expr* e, dependency*& eqs) { expr_ref result = expand(e, eqs); + TRACE("seq", tout << mk_pp(e, m) << " expands to " << result << "\n";); m_rewrite(result); + TRACE("seq", tout << mk_pp(e, m) << " rewrites to " << result << "\n";); return result; } @@ -3494,6 +3541,7 @@ void theory_seq::add_extract_suffix_axiom(expr* e, expr* s, expr* i) { let e = at(s, i) 0 <= i < len(s) -> s = xey & len(x) = i & len(e) = 1 + i < 0 \/ i >= len(s) -> e = empty */ void theory_seq::add_at_axiom(expr* e) { @@ -3507,13 +3555,18 @@ void theory_seq::add_at_axiom(expr* e) { expr_ref y = mk_skolem(m_post, s, mk_sub(mk_sub(len_s, i), one)); expr_ref xey = mk_concat(x, e, y); expr_ref len_x(m_util.str.mk_length(x), m); + expr_ref emp(m_util.str.mk_empty(m.get_sort(e)), m); literal i_ge_0 = mk_literal(m_autil.mk_ge(i, zero)); literal i_ge_len_s = mk_literal(m_autil.mk_ge(mk_sub(i, m_util.str.mk_length(s)), zero)); + add_axiom(~i_ge_0, i_ge_len_s, mk_seq_eq(s, xey)); add_axiom(~i_ge_0, i_ge_len_s, mk_eq(one, len_e, false)); add_axiom(~i_ge_0, i_ge_len_s, mk_eq(i, len_x, false)); + + add_axiom(i_ge_0, mk_eq(s, emp, false)); + add_axiom(~i_ge_len_s, mk_eq(s, emp, false)); } /** @@ -3821,8 +3874,8 @@ void theory_seq::new_eq_eh(dependency* deps, enode* n1, enode* n2) { } else if (n1 != n2 && m_util.is_re(n1->get_owner())) { warning_msg("equality between regular expressions is not yet supported"); - eautomaton* a1 = get_automaton(n1->get_owner()); - eautomaton* a2 = get_automaton(n2->get_owner()); + // eautomaton* a1 = get_automaton(n1->get_owner()); + // eautomaton* a2 = get_automaton(n2->get_owner()); // eautomaton* b1 = mk_difference(*a1, *a2); // eautomaton* b2 = mk_difference(*a2, *a1); // eautomaton* c = mk_union(*b1, *b2); @@ -4469,10 +4522,11 @@ bool theory_seq::canonizes(bool sign, expr* e) { context& ctx = get_context(); dependency* deps = 0; expr_ref cont = canonize(e, deps); - TRACE("seq", tout << mk_pp(e, m) << " -> " << cont << "\n";); + TRACE("seq", tout << mk_pp(e, m) << " -> " << cont << "\n"; + if (deps) display_deps(tout, deps);); if ((m.is_true(cont) && !sign) || (m.is_false(cont) && sign)) { - TRACE("seq", display(tout);); + TRACE("seq", display(tout); tout << ctx.get_assignment(ctx.get_literal(e)) << "\n";); propagate_lit(deps, 0, 0, ctx.get_literal(e)); return true; } diff --git a/src/smt/theory_seq.h b/src/smt/theory_seq.h index aa7ddec1b..2b8fb2fd7 100644 --- a/src/smt/theory_seq.h +++ b/src/smt/theory_seq.h @@ -570,6 +570,7 @@ namespace smt { void display_disequation(std::ostream& out, ne const& e) const; void display_deps(std::ostream& out, dependency* deps) const; void display_deps(std::ostream& out, literal_vector const& lits, enode_pair_vector const& eqs) const; + void display_nc(std::ostream& out, nc const& nc) const; public: theory_seq(ast_manager& m); virtual ~theory_seq(); diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp new file mode 100644 index 000000000..818aca29a --- /dev/null +++ b/src/smt/theory_str.cpp @@ -0,0 +1,10574 @@ +/*++ + Module Name: + + theory_str.cpp + + Abstract: + + String Theory Plugin + + Author: + + Murphy Berzish and Yunhui Zheng + + Revision History: + + --*/ +#include"ast_smt2_pp.h" +#include"smt_context.h" +#include"theory_str.h" +#include"smt_model_generator.h" +#include"ast_pp.h" +#include"ast_ll_pp.h" +#include +#include +#include +#include"theory_seq_empty.h" +#include"theory_arith.h" + +namespace smt { + + theory_str::theory_str(ast_manager & m, theory_str_params const & params): + theory(m.mk_family_id("seq")), + m_params(params), + /* Options */ + opt_EagerStringConstantLengthAssertions(true), + opt_VerifyFinalCheckProgress(true), + opt_LCMUnrollStep(2), + opt_NoQuickReturn_IntegerTheory(false), + opt_DisableIntegerTheoryIntegration(false), + opt_DeferEQCConsistencyCheck(false), + opt_CheckVariableScope(true), + opt_ConcatOverlapAvoid(true), + /* Internal setup */ + search_started(false), + m_autil(m), + u(m), + sLevel(0), + finalCheckProgressIndicator(false), + m_trail(m), + m_delayed_axiom_setup_terms(m), + tmpStringVarCount(0), + tmpXorVarCount(0), + tmpLenTestVarCount(0), + tmpValTestVarCount(0), + avoidLoopCut(true), + loopDetected(false), + m_theoryStrOverlapAssumption_term(m), + contains_map(m), + string_int_conversion_terms(m), + totalCacheAccessCount(0), + cacheHitCount(0), + cacheMissCount(0), + m_find(*this), + m_trail_stack(*this) + { + initialize_charset(); + } + + theory_str::~theory_str() { + m_trail_stack.reset(); + } + + expr * theory_str::mk_string(zstring const& str) { + if (m_params.m_StringConstantCache) { + ++totalCacheAccessCount; + expr * val; + if (stringConstantCache.find(str, val)) { + return val; + } else { + val = u.str.mk_string(str); + m_trail.push_back(val); + stringConstantCache.insert(str, val); + return val; + } + } else { + return u.str.mk_string(str); + } + } + + expr * theory_str::mk_string(const char * str) { + symbol sym(str); + return u.str.mk_string(sym); + } + + void theory_str::initialize_charset() { + bool defaultCharset = true; + if (defaultCharset) { + // valid C strings can't contain the null byte ('\0') + charSetSize = 255; + char_set = alloc_svect(char, charSetSize); + int idx = 0; + // small letters + for (int i = 97; i < 123; i++) { + char_set[idx] = (char) i; + charSetLookupTable[char_set[idx]] = idx; + idx++; + } + // caps + for (int i = 65; i < 91; i++) { + char_set[idx] = (char) i; + charSetLookupTable[char_set[idx]] = idx; + idx++; + } + // numbers + for (int i = 48; i < 58; i++) { + char_set[idx] = (char) i; + charSetLookupTable[char_set[idx]] = idx; + idx++; + } + // printable marks - 1 + for (int i = 32; i < 48; i++) { + char_set[idx] = (char) i; + charSetLookupTable[char_set[idx]] = idx; + idx++; + } + // printable marks - 2 + for (int i = 58; i < 65; i++) { + char_set[idx] = (char) i; + charSetLookupTable[char_set[idx]] = idx; + idx++; + } + // printable marks - 3 + for (int i = 91; i < 97; i++) { + char_set[idx] = (char) i; + charSetLookupTable[char_set[idx]] = idx; + idx++; + } + // printable marks - 4 + for (int i = 123; i < 127; i++) { + char_set[idx] = (char) i; + charSetLookupTable[char_set[idx]] = idx; + idx++; + } + // non-printable - 1 + for (int i = 1; i < 32; i++) { + char_set[idx] = (char) i; + charSetLookupTable[char_set[idx]] = idx; + idx++; + } + // non-printable - 2 + for (int i = 127; i < 256; i++) { + char_set[idx] = (char) i; + charSetLookupTable[char_set[idx]] = idx; + idx++; + } + } else { + const char setset[] = { 'a', 'b', 'c' }; + int fSize = sizeof(setset) / sizeof(char); + + char_set = alloc_svect(char, fSize); + charSetSize = fSize; + for (int i = 0; i < charSetSize; i++) { + char_set[i] = setset[i]; + charSetLookupTable[setset[i]] = i; + } + } + } + + void theory_str::assert_axiom(expr * e) { + if (opt_VerifyFinalCheckProgress) { + finalCheckProgressIndicator = true; + } + + if (get_manager().is_true(e)) return; + TRACE("str", tout << "asserting " << mk_ismt2_pp(e, get_manager()) << std::endl;); + context & ctx = get_context(); + if (!ctx.b_internalized(e)) { + ctx.internalize(e, false); + } + literal lit(ctx.get_literal(e)); + ctx.mark_as_relevant(lit); + ctx.mk_th_axiom(get_id(), 1, &lit); + + // crash/error avoidance: add all axioms to the trail + m_trail.push_back(e); + + //TRACE("str", tout << "done asserting " << mk_ismt2_pp(e, get_manager()) << std::endl;); + } + + expr * theory_str::rewrite_implication(expr * premise, expr * conclusion) { + ast_manager & m = get_manager(); + return m.mk_or(m.mk_not(premise), conclusion); + } + + void theory_str::assert_implication(expr * premise, expr * conclusion) { + ast_manager & m = get_manager(); + TRACE("str", tout << "asserting implication " << mk_ismt2_pp(premise, m) << " -> " << mk_ismt2_pp(conclusion, m) << std::endl;); + expr_ref axiom(m.mk_or(m.mk_not(premise), conclusion), m); + assert_axiom(axiom); + } + + bool theory_str::internalize_atom(app * atom, bool gate_ctx) { + return internalize_term(atom); + } + + bool theory_str::internalize_term(app * term) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + SASSERT(term->get_family_id() == get_family_id()); + + TRACE("str", tout << "internalizing term: " << mk_ismt2_pp(term, get_manager()) << std::endl;); + + // emulation of user_smt_theory::internalize_term() + + unsigned num_args = term->get_num_args(); + for (unsigned i = 0; i < num_args; ++i) { + ctx.internalize(term->get_arg(i), false); + } + if (ctx.e_internalized(term)) { + enode * e = ctx.get_enode(term); + mk_var(e); + return true; + } + // m_parents.push_back(term); + enode * e = ctx.mk_enode(term, false, m.is_bool(term), true); + if (m.is_bool(term)) { + bool_var bv = ctx.mk_bool_var(term); + ctx.set_var_theory(bv, get_id()); + ctx.set_enode_flag(bv, true); + } + // make sure every argument is attached to a theory variable + for (unsigned i = 0; i < num_args; ++i) { + enode * arg = e->get_arg(i); + theory_var v_arg = mk_var(arg); + TRACE("str", tout << "arg has theory var #" << v_arg << std::endl;); + } + + theory_var v = mk_var(e); + TRACE("str", tout << "term has theory var #" << v << std::endl;); + + if (opt_EagerStringConstantLengthAssertions && u.str.is_string(term)) { + TRACE("str", tout << "eagerly asserting length of string term " << mk_pp(term, m) << std::endl;); + m_basicstr_axiom_todo.insert(e); + } + return true; + } + + enode* theory_str::ensure_enode(expr* e) { + context& ctx = get_context(); + if (!ctx.e_internalized(e)) { + ctx.internalize(e, false); + } + enode* n = ctx.get_enode(e); + ctx.mark_as_relevant(n); + return n; + } + + void theory_str::refresh_theory_var(expr * e) { + enode * en = ensure_enode(e); + theory_var v = mk_var(en); + TRACE("str", tout << "refresh " << mk_pp(e, get_manager()) << ": v#" << v << std::endl;); + m_basicstr_axiom_todo.push_back(en); + } + + theory_var theory_str::mk_var(enode* n) { + TRACE("str", tout << "mk_var for " << mk_pp(n->get_owner(), get_manager()) << std::endl;); + ast_manager & m = get_manager(); + if (!(m.get_sort(n->get_owner()) == u.str.mk_string_sort())) { + return null_theory_var; + } + if (is_attached_to_var(n)) { + TRACE("str", tout << "already attached to theory var" << std::endl;); + return n->get_th_var(get_id()); + } else { + theory_var v = theory::mk_var(n); + m_find.mk_var(); + TRACE("str", tout << "new theory var v#" << v << std::endl;); + get_context().attach_th_var(n, this, v); + get_context().mark_as_relevant(n); + return v; + } + } + + static void cut_vars_map_copy(std::map & dest, std::map & src) { + std::map::iterator itor = src.begin(); + for (; itor != src.end(); itor++) { + dest[itor->first] = 1; + } + } + + bool theory_str::has_self_cut(expr * n1, expr * n2) { + if (!cut_var_map.contains(n1)) { + return false; + } + if (!cut_var_map.contains(n2)) { + return false; + } + if (cut_var_map[n1].empty() || cut_var_map[n2].empty()) { + return false; + } + + std::map::iterator itor = cut_var_map[n1].top()->vars.begin(); + for (; itor != cut_var_map[n1].top()->vars.end(); ++itor) { + if (cut_var_map[n2].top()->vars.find(itor->first) != cut_var_map[n2].top()->vars.end()) { + return true; + } + } + return false; + } + + void theory_str::add_cut_info_one_node(expr * baseNode, int slevel, expr * node) { + // crash avoidance? + m_trail.push_back(baseNode); + m_trail.push_back(node); + if (!cut_var_map.contains(baseNode)) { + T_cut * varInfo = alloc(T_cut); + varInfo->level = slevel; + varInfo->vars[node] = 1; + cut_var_map.insert(baseNode, std::stack()); + cut_var_map[baseNode].push(varInfo); + TRACE("str", tout << "add var info for baseNode=" << mk_pp(baseNode, get_manager()) << ", node=" << mk_pp(node, get_manager()) << " [" << slevel << "]" << std::endl;); + } else { + if (cut_var_map[baseNode].empty()) { + T_cut * varInfo = alloc(T_cut); + varInfo->level = slevel; + varInfo->vars[node] = 1; + cut_var_map[baseNode].push(varInfo); + TRACE("str", tout << "add var info for baseNode=" << mk_pp(baseNode, get_manager()) << ", node=" << mk_pp(node, get_manager()) << " [" << slevel << "]" << std::endl;); + } else { + if (cut_var_map[baseNode].top()->level < slevel) { + T_cut * varInfo = alloc(T_cut); + varInfo->level = slevel; + cut_vars_map_copy(varInfo->vars, cut_var_map[baseNode].top()->vars); + varInfo->vars[node] = 1; + cut_var_map[baseNode].push(varInfo); + TRACE("str", tout << "add var info for baseNode=" << mk_pp(baseNode, get_manager()) << ", node=" << mk_pp(node, get_manager()) << " [" << slevel << "]" << std::endl;); + } else if (cut_var_map[baseNode].top()->level == slevel) { + cut_var_map[baseNode].top()->vars[node] = 1; + TRACE("str", tout << "add var info for baseNode=" << mk_pp(baseNode, get_manager()) << ", node=" << mk_pp(node, get_manager()) << " [" << slevel << "]" << std::endl;); + } else { + get_manager().raise_exception("entered illegal state during add_cut_info_one_node()"); + } + } + } + } + + void theory_str::add_cut_info_merge(expr * destNode, int slevel, expr * srcNode) { + // crash avoidance? + m_trail.push_back(destNode); + m_trail.push_back(srcNode); + if (!cut_var_map.contains(srcNode)) { + get_manager().raise_exception("illegal state in add_cut_info_merge(): cut_var_map doesn't contain srcNode"); + } + + if (cut_var_map[srcNode].empty()) { + get_manager().raise_exception("illegal state in add_cut_info_merge(): cut_var_map[srcNode] is empty"); + } + + if (!cut_var_map.contains(destNode)) { + T_cut * varInfo = alloc(T_cut); + varInfo->level = slevel; + cut_vars_map_copy(varInfo->vars, cut_var_map[srcNode].top()->vars); + cut_var_map.insert(destNode, std::stack()); + cut_var_map[destNode].push(varInfo); + TRACE("str", tout << "merge var info for destNode=" << mk_pp(destNode, get_manager()) << ", srcNode=" << mk_pp(srcNode, get_manager()) << " [" << slevel << "]" << std::endl;); + } else { + if (cut_var_map[destNode].empty() || cut_var_map[destNode].top()->level < slevel) { + T_cut * varInfo = alloc(T_cut); + varInfo->level = slevel; + cut_vars_map_copy(varInfo->vars, cut_var_map[destNode].top()->vars); + cut_vars_map_copy(varInfo->vars, cut_var_map[srcNode].top()->vars); + cut_var_map[destNode].push(varInfo); + TRACE("str", tout << "merge var info for destNode=" << mk_pp(destNode, get_manager()) << ", srcNode=" << mk_pp(srcNode, get_manager()) << " [" << slevel << "]" << std::endl;); + } else if (cut_var_map[destNode].top()->level == slevel) { + cut_vars_map_copy(cut_var_map[destNode].top()->vars, cut_var_map[srcNode].top()->vars); + TRACE("str", tout << "merge var info for destNode=" << mk_pp(destNode, get_manager()) << ", srcNode=" << mk_pp(srcNode, get_manager()) << " [" << slevel << "]" << std::endl;); + } else { + get_manager().raise_exception("illegal state in add_cut_info_merge(): inconsistent slevels"); + } + } + } + + void theory_str::check_and_init_cut_var(expr * node) { + if (cut_var_map.contains(node)) { + return; + } else if (!u.str.is_string(node)) { + add_cut_info_one_node(node, -1, node); + } + } + + literal theory_str::mk_literal(expr* _e) { + ast_manager & m = get_manager(); + expr_ref e(_e, m); + context& ctx = get_context(); + ensure_enode(e); + return ctx.get_literal(e); + } + + app * theory_str::mk_int(int n) { + return m_autil.mk_numeral(rational(n), true); + } + + app * theory_str::mk_int(rational & q) { + return m_autil.mk_numeral(q, true); + } + + expr * theory_str::mk_internal_lenTest_var(expr * node, int lTries) { + ast_manager & m = get_manager(); + + std::stringstream ss; + ss << "$$_len_" << mk_ismt2_pp(node, m) << "_" << lTries << "_" << tmpLenTestVarCount; + tmpLenTestVarCount += 1; + std::string name = ss.str(); + app * var = mk_str_var(name); + internal_lenTest_vars.insert(var); + m_trail.push_back(var); + return var; + } + + expr * theory_str::mk_internal_valTest_var(expr * node, int len, int vTries) { + ast_manager & m = get_manager(); + std::stringstream ss; + ss << "$$_val_" << mk_ismt2_pp(node, m) << "_" << len << "_" << vTries << "_" << tmpValTestVarCount; + tmpValTestVarCount += 1; + std::string name = ss.str(); + app * var = mk_str_var(name); + internal_valTest_vars.insert(var); + m_trail.push_back(var); + return var; + } + + void theory_str::track_variable_scope(expr * var) { + if (internal_variable_scope_levels.find(sLevel) == internal_variable_scope_levels.end()) { + internal_variable_scope_levels[sLevel] = std::set(); + } + internal_variable_scope_levels[sLevel].insert(var); + } + + app * theory_str::mk_internal_xor_var() { + return mk_int_var("$$_xor"); + } + + app * theory_str::mk_fresh_const(char const* name, sort* s) { + return u.mk_skolem(symbol(name), 0, 0, s); + } + + + app * theory_str::mk_int_var(std::string name) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + TRACE("str", tout << "creating integer variable " << name << " at scope level " << sLevel << std::endl;); + + sort * int_sort = m.mk_sort(m_autil.get_family_id(), INT_SORT); + app * a = mk_fresh_const(name.c_str(), int_sort); + + ctx.internalize(a, false); + SASSERT(ctx.get_enode(a) != NULL); + SASSERT(ctx.e_internalized(a)); + ctx.mark_as_relevant(a); + // I'm assuming that this combination will do the correct thing in the integer theory. + + //mk_var(ctx.get_enode(a)); + m_trail.push_back(a); + //variable_set.insert(a); + //internal_variable_set.insert(a); + //track_variable_scope(a); + + return a; + } + + app * theory_str::mk_unroll_bound_var() { + return mk_int_var("unroll"); + } + + app * theory_str::mk_unroll_test_var() { + app * v = mk_str_var("unrollTest"); // was uRt + internal_unrollTest_vars.insert(v); + track_variable_scope(v); + return v; + } + + app * theory_str::mk_str_var(std::string name) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + TRACE("str", tout << "creating string variable " << name << " at scope level " << sLevel << std::endl;); + + sort * string_sort = u.str.mk_string_sort(); + app * a = mk_fresh_const(name.c_str(), string_sort); + + TRACE("str", tout << "a->get_family_id() = " << a->get_family_id() << std::endl + << "this->get_family_id() = " << this->get_family_id() << std::endl;); + + // I have a hunch that this may not get internalized for free... + ctx.internalize(a, false); + SASSERT(ctx.get_enode(a) != NULL); + SASSERT(ctx.e_internalized(a)); + // this might help?? + mk_var(ctx.get_enode(a)); + m_basicstr_axiom_todo.push_back(ctx.get_enode(a)); + TRACE("str", tout << "add " << mk_pp(a, m) << " to m_basicstr_axiom_todo" << std::endl;); + + m_trail.push_back(a); + variable_set.insert(a); + internal_variable_set.insert(a); + track_variable_scope(a); + + return a; + } + + app * theory_str::mk_regex_rep_var() { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + sort * string_sort = u.str.mk_string_sort(); + app * a = mk_fresh_const("regex", string_sort); + + ctx.internalize(a, false); + SASSERT(ctx.get_enode(a) != NULL); + SASSERT(ctx.e_internalized(a)); + mk_var(ctx.get_enode(a)); + m_basicstr_axiom_todo.push_back(ctx.get_enode(a)); + TRACE("str", tout << "add " << mk_pp(a, m) << " to m_basicstr_axiom_todo" << std::endl;); + + m_trail.push_back(a); + variable_set.insert(a); + //internal_variable_set.insert(a); + regex_variable_set.insert(a); + track_variable_scope(a); + + return a; + } + + void theory_str::add_nonempty_constraint(expr * s) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + expr_ref ax1(m.mk_not(ctx.mk_eq_atom(s, mk_string(""))), m); + assert_axiom(ax1); + + { + // build LHS + expr_ref len_str(mk_strlen(s), m); + SASSERT(len_str); + // build RHS + expr_ref zero(m_autil.mk_numeral(rational(0), true), m); + SASSERT(zero); + // build LHS > RHS and assert + // we have to build !(LHS <= RHS) instead + expr_ref lhs_gt_rhs(m.mk_not(m_autil.mk_le(len_str, zero)), m); + SASSERT(lhs_gt_rhs); + assert_axiom(lhs_gt_rhs); + } + } + + app * theory_str::mk_nonempty_str_var() { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + std::stringstream ss; + ss << tmpStringVarCount; + tmpStringVarCount++; + std::string name = "$$_str" + ss.str(); + + TRACE("str", tout << "creating nonempty string variable " << name << " at scope level " << sLevel << std::endl;); + + sort * string_sort = u.str.mk_string_sort(); + app * a = mk_fresh_const(name.c_str(), string_sort); + + ctx.internalize(a, false); + SASSERT(ctx.get_enode(a) != NULL); + // this might help?? + mk_var(ctx.get_enode(a)); + + // assert a variation of the basic string axioms that ensures this string is nonempty + { + // build LHS + expr_ref len_str(mk_strlen(a), m); + SASSERT(len_str); + // build RHS + expr_ref zero(m_autil.mk_numeral(rational(0), true), m); + SASSERT(zero); + // build LHS > RHS and assert + // we have to build !(LHS <= RHS) instead + expr_ref lhs_gt_rhs(m.mk_not(m_autil.mk_le(len_str, zero)), m); + SASSERT(lhs_gt_rhs); + assert_axiom(lhs_gt_rhs); + } + + // add 'a' to variable sets, so we can keep track of it + m_trail.push_back(a); + variable_set.insert(a); + internal_variable_set.insert(a); + track_variable_scope(a); + + return a; + } + + app * theory_str::mk_unroll(expr * n, expr * bound) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + expr * args[2] = {n, bound}; + app * unrollFunc = get_manager().mk_app(get_id(), _OP_RE_UNROLL, 0, 0, 2, args); + m_trail.push_back(unrollFunc); + + expr_ref_vector items(m); + items.push_back(ctx.mk_eq_atom(ctx.mk_eq_atom(bound, mk_int(0)), ctx.mk_eq_atom(unrollFunc, mk_string("")))); + items.push_back(m_autil.mk_ge(bound, mk_int(0))); + items.push_back(m_autil.mk_ge(mk_strlen(unrollFunc), mk_int(0))); + + expr_ref finalAxiom(mk_and(items), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); + return unrollFunc; + } + + app * theory_str::mk_contains(expr * haystack, expr * needle) { + app * contains = u.str.mk_contains(haystack, needle); // TODO double-check semantics/argument order + m_trail.push_back(contains); + // immediately force internalization so that axiom setup does not fail + get_context().internalize(contains, false); + set_up_axioms(contains); + return contains; + } + + app * theory_str::mk_indexof(expr * haystack, expr * needle) { + // TODO check meaning of the third argument here + app * indexof = u.str.mk_index(haystack, needle, mk_int(0)); + m_trail.push_back(indexof); + // immediately force internalization so that axiom setup does not fail + get_context().internalize(indexof, false); + set_up_axioms(indexof); + return indexof; + } + + app * theory_str::mk_strlen(expr * e) { + /*if (m_strutil.is_string(e)) {*/ if (false) { + zstring strval; + u.str.is_string(e, strval); + unsigned int len = strval.length(); + return m_autil.mk_numeral(rational(len), true); + } else { + if (false) { + // use cache + app * lenTerm = NULL; + if (!length_ast_map.find(e, lenTerm)) { + lenTerm = u.str.mk_length(e); + length_ast_map.insert(e, lenTerm); + m_trail.push_back(lenTerm); + } + return lenTerm; + } else { + // always regen + return u.str.mk_length(e); + } + } + } + + /* + * Returns the simplified concatenation of two expressions, + * where either both expressions are constant strings + * or one expression is the empty string. + * If this precondition does not hold, the function returns NULL. + * (note: this function was strTheory::Concat()) + */ + expr * theory_str::mk_concat_const_str(expr * n1, expr * n2) { + bool n1HasEqcValue = false; + bool n2HasEqcValue = false; + expr * v1 = get_eqc_value(n1, n1HasEqcValue); + expr * v2 = get_eqc_value(n2, n2HasEqcValue); + if (n1HasEqcValue && n2HasEqcValue) { + zstring n1_str; + u.str.is_string(v1, n1_str); + zstring n2_str; + u.str.is_string(v2, n2_str); + zstring result = n1_str + n2_str; + return mk_string(result); + } else if (n1HasEqcValue && !n2HasEqcValue) { + zstring n1_str; + u.str.is_string(v1, n1_str); + if (n1_str.empty()) { + return n2; + } + } else if (!n1HasEqcValue && n2HasEqcValue) { + zstring n2_str; + u.str.is_string(v2, n2_str); + if (n2_str.empty()) { + return n1; + } + } + return NULL; + } + + expr * theory_str::mk_concat(expr * n1, expr * n2) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + ENSURE(n1 != NULL); + ENSURE(n2 != NULL); + bool n1HasEqcValue = false; + bool n2HasEqcValue = false; + n1 = get_eqc_value(n1, n1HasEqcValue); + n2 = get_eqc_value(n2, n2HasEqcValue); + if (n1HasEqcValue && n2HasEqcValue) { + return mk_concat_const_str(n1, n2); + } else if (n1HasEqcValue && !n2HasEqcValue) { + bool n2_isConcatFunc = u.str.is_concat(to_app(n2)); + zstring n1_str; + u.str.is_string(n1, n1_str); + if (n1_str.empty()) { + return n2; + } + if (n2_isConcatFunc) { + expr * n2_arg0 = to_app(n2)->get_arg(0); + expr * n2_arg1 = to_app(n2)->get_arg(1); + if (u.str.is_string(n2_arg0)) { + n1 = mk_concat_const_str(n1, n2_arg0); // n1 will be a constant + n2 = n2_arg1; + } + } + } else if (!n1HasEqcValue && n2HasEqcValue) { + zstring n2_str; + u.str.is_string(n2, n2_str); + if (n2_str.empty()) { + return n1; + } + + if (u.str.is_concat(to_app(n1))) { + expr * n1_arg0 = to_app(n1)->get_arg(0); + expr * n1_arg1 = to_app(n1)->get_arg(1); + if (u.str.is_string(n1_arg1)) { + n1 = n1_arg0; + n2 = mk_concat_const_str(n1_arg1, n2); // n2 will be a constant + } + } + } else { + if (u.str.is_concat(to_app(n1)) && u.str.is_concat(to_app(n2))) { + expr * n1_arg0 = to_app(n1)->get_arg(0); + expr * n1_arg1 = to_app(n1)->get_arg(1); + expr * n2_arg0 = to_app(n2)->get_arg(0); + expr * n2_arg1 = to_app(n2)->get_arg(1); + if (u.str.is_string(n1_arg1) && u.str.is_string(n2_arg0)) { + expr * tmpN1 = n1_arg0; + expr * tmpN2 = mk_concat_const_str(n1_arg1, n2_arg0); + n1 = mk_concat(tmpN1, tmpN2); + n2 = n2_arg1; + } + } + } + + //------------------------------------------------------ + // * expr * ast1 = mk_2_arg_app(ctx, td->Concat, n1, n2); + // * expr * ast2 = mk_2_arg_app(ctx, td->Concat, n1, n2); + // Z3 treats (ast1) and (ast2) as two different nodes. + //------------------------------------------------------- + + expr * concatAst = NULL; + + if (!concat_astNode_map.find(n1, n2, concatAst)) { + concatAst = u.str.mk_concat(n1, n2); + m_trail.push_back(concatAst); + concat_astNode_map.insert(n1, n2, concatAst); + + expr_ref concat_length(mk_strlen(concatAst), m); + + ptr_vector childrenVector; + get_nodes_in_concat(concatAst, childrenVector); + expr_ref_vector items(m); + for (unsigned int i = 0; i < childrenVector.size(); i++) { + items.push_back(mk_strlen(childrenVector.get(i))); + } + expr_ref lenAssert(ctx.mk_eq_atom(concat_length, m_autil.mk_add(items.size(), items.c_ptr())), m); + assert_axiom(lenAssert); + } + return concatAst; + } + + bool theory_str::can_propagate() { + return !m_basicstr_axiom_todo.empty() || !m_str_eq_todo.empty() + || !m_concat_axiom_todo.empty() || !m_concat_eval_todo.empty() + || !m_library_aware_axiom_todo.empty() + || !m_delayed_axiom_setup_terms.empty(); + ; + } + + void theory_str::propagate() { + context & ctx = get_context(); + while (can_propagate()) { + TRACE("str", tout << "propagating..." << std::endl;); + for (unsigned i = 0; i < m_basicstr_axiom_todo.size(); ++i) { + instantiate_basic_string_axioms(m_basicstr_axiom_todo[i]); + } + m_basicstr_axiom_todo.reset(); + TRACE("str", tout << "reset m_basicstr_axiom_todo" << std::endl;); + + for (unsigned i = 0; i < m_str_eq_todo.size(); ++i) { + std::pair pair = m_str_eq_todo[i]; + enode * lhs = pair.first; + enode * rhs = pair.second; + handle_equality(lhs->get_owner(), rhs->get_owner()); + } + m_str_eq_todo.reset(); + + for (unsigned i = 0; i < m_concat_axiom_todo.size(); ++i) { + instantiate_concat_axiom(m_concat_axiom_todo[i]); + } + m_concat_axiom_todo.reset(); + + for (unsigned i = 0; i < m_concat_eval_todo.size(); ++i) { + try_eval_concat(m_concat_eval_todo[i]); + } + m_concat_eval_todo.reset(); + + for (unsigned i = 0; i < m_library_aware_axiom_todo.size(); ++i) { + enode * e = m_library_aware_axiom_todo[i]; + app * a = e->get_owner(); + if (u.str.is_stoi(a)) { + instantiate_axiom_str_to_int(e); + } else if (u.str.is_itos(a)) { + instantiate_axiom_int_to_str(e); + } else if (u.str.is_at(a)) { + instantiate_axiom_CharAt(e); + } else if (u.str.is_prefix(a)) { + instantiate_axiom_prefixof(e); + } else if (u.str.is_suffix(a)) { + instantiate_axiom_suffixof(e); + } else if (u.str.is_contains(a)) { + instantiate_axiom_Contains(e); + } else if (u.str.is_index(a)) { + instantiate_axiom_Indexof(e); + /* TODO NEXT: Indexof2/Lastindexof rewrite? + } else if (is_Indexof2(e)) { + instantiate_axiom_Indexof2(e); + } else if (is_LastIndexof(e)) { + instantiate_axiom_LastIndexof(e); + */ + } else if (u.str.is_extract(a)) { + // TODO check semantics of substr vs. extract + instantiate_axiom_Substr(e); + } else if (u.str.is_replace(a)) { + instantiate_axiom_Replace(e); + } else if (u.str.is_in_re(a)) { + instantiate_axiom_RegexIn(e); + } else { + TRACE("str", tout << "BUG: unhandled library-aware term " << mk_pp(e->get_owner(), get_manager()) << std::endl;); + NOT_IMPLEMENTED_YET(); + } + } + m_library_aware_axiom_todo.reset(); + + for (unsigned i = 0; i < m_delayed_axiom_setup_terms.size(); ++i) { + // I think this is okay + ctx.internalize(m_delayed_axiom_setup_terms[i].get(), false); + set_up_axioms(m_delayed_axiom_setup_terms[i].get()); + } + m_delayed_axiom_setup_terms.reset(); + } + } + + /* + * Attempt to evaluate a concat over constant strings, + * and if this is possible, assert equality between the + * flattened string and the original term. + */ + + void theory_str::try_eval_concat(enode * cat) { + app * a_cat = cat->get_owner(); + SASSERT(u.str.is_concat(a_cat)); + + context & ctx = get_context(); + ast_manager & m = get_manager(); + + TRACE("str", tout << "attempting to flatten " << mk_pp(a_cat, m) << std::endl;); + + std::stack worklist; + zstring flattenedString(""); + bool constOK = true; + + { + app * arg0 = to_app(a_cat->get_arg(0)); + app * arg1 = to_app(a_cat->get_arg(1)); + + worklist.push(arg1); + worklist.push(arg0); + } + + while (constOK && !worklist.empty()) { + app * evalArg = worklist.top(); worklist.pop(); + zstring nextStr; + if (u.str.is_string(evalArg, nextStr)) { + flattenedString = flattenedString + nextStr; + } else if (u.str.is_concat(evalArg)) { + app * arg0 = to_app(evalArg->get_arg(0)); + app * arg1 = to_app(evalArg->get_arg(1)); + + worklist.push(arg1); + worklist.push(arg0); + } else { + TRACE("str", tout << "non-constant term in concat -- giving up." << std::endl;); + constOK = false; + break; + } + } + if (constOK) { + TRACE("str", tout << "flattened to \"" << flattenedString.encode().c_str() << "\"" << std::endl;); + expr_ref constStr(mk_string(flattenedString), m); + expr_ref axiom(ctx.mk_eq_atom(a_cat, constStr), m); + assert_axiom(axiom); + } + } + + /* + * Instantiate an axiom of the following form: + * Length(Concat(x, y)) = Length(x) + Length(y) + */ + void theory_str::instantiate_concat_axiom(enode * cat) { + app * a_cat = cat->get_owner(); + SASSERT(u.str.is_concat(a_cat)); + + ast_manager & m = get_manager(); + + TRACE("str", tout << "instantiating concat axiom for " << mk_ismt2_pp(a_cat, m) << std::endl;); + + // build LHS + expr_ref len_xy(m); + len_xy = mk_strlen(a_cat); + SASSERT(len_xy); + + // build RHS: start by extracting x and y from Concat(x, y) + unsigned nArgs = a_cat->get_num_args(); + SASSERT(nArgs == 2); + app * a_x = to_app(a_cat->get_arg(0)); + app * a_y = to_app(a_cat->get_arg(1)); + + expr_ref len_x(m); + len_x = mk_strlen(a_x); + SASSERT(len_x); + + expr_ref len_y(m); + len_y = mk_strlen(a_y); + SASSERT(len_y); + + // now build len_x + len_y + expr_ref len_x_plus_len_y(m); + len_x_plus_len_y = m_autil.mk_add(len_x, len_y); + SASSERT(len_x_plus_len_y); + + // finally assert equality between the two subexpressions + app * eq = m.mk_eq(len_xy, len_x_plus_len_y); + SASSERT(eq); + assert_axiom(eq); + } + + /* + * Add axioms that are true for any string variable: + * 1. Length(x) >= 0 + * 2. Length(x) == 0 <=> x == "" + * If the term is a string constant, we can assert something stronger: + * Length(x) == strlen(x) + */ + void theory_str::instantiate_basic_string_axioms(enode * str) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + TRACE("str", tout << "set up basic string axioms on " << mk_pp(str->get_owner(), m) << std::endl;); + + // TESTING: attempt to avoid a crash here when a variable goes out of scope + if (str->get_iscope_lvl() > ctx.get_scope_level()) { + TRACE("str", tout << "WARNING: skipping axiom setup on out-of-scope string term" << std::endl;); + return; + } + + // generate a stronger axiom for constant strings + app * a_str = str->get_owner(); + if (u.str.is_string(a_str)) { + expr_ref len_str(m); + len_str = mk_strlen(a_str); + SASSERT(len_str); + + zstring strconst; + u.str.is_string(str->get_owner(), strconst); + TRACE("str", tout << "instantiating constant string axioms for \"" << strconst.encode().c_str() << "\"" << std::endl;); + unsigned int l = strconst.length(); + expr_ref len(m_autil.mk_numeral(rational(l), true), m); + + literal lit(mk_eq(len_str, len, false)); + ctx.mark_as_relevant(lit); + ctx.mk_th_axiom(get_id(), 1, &lit); + } else { + // build axiom 1: Length(a_str) >= 0 + { + // build LHS + expr_ref len_str(m); + len_str = mk_strlen(a_str); + SASSERT(len_str); + // build RHS + expr_ref zero(m); + zero = m_autil.mk_numeral(rational(0), true); + SASSERT(zero); + // build LHS >= RHS and assert + app * lhs_ge_rhs = m_autil.mk_ge(len_str, zero); + SASSERT(lhs_ge_rhs); + TRACE("str", tout << "string axiom 1: " << mk_ismt2_pp(lhs_ge_rhs, m) << std::endl;); + assert_axiom(lhs_ge_rhs); + } + + // build axiom 2: Length(a_str) == 0 <=> a_str == "" + { + // build LHS of iff + expr_ref len_str(m); + len_str = mk_strlen(a_str); + SASSERT(len_str); + expr_ref zero(m); + zero = m_autil.mk_numeral(rational(0), true); + SASSERT(zero); + expr_ref lhs(m); + lhs = ctx.mk_eq_atom(len_str, zero); + SASSERT(lhs); + // build RHS of iff + expr_ref empty_str(m); + empty_str = mk_string(""); + SASSERT(empty_str); + expr_ref rhs(m); + rhs = ctx.mk_eq_atom(a_str, empty_str); + SASSERT(rhs); + // build LHS <=> RHS and assert + TRACE("str", tout << "string axiom 2: " << mk_ismt2_pp(lhs, m) << " <=> " << mk_ismt2_pp(rhs, m) << std::endl;); + literal l(mk_eq(lhs, rhs, true)); + ctx.mark_as_relevant(l); + ctx.mk_th_axiom(get_id(), 1, &l); + } + + } + } + + /* + * Add an axiom of the form: + * (lhs == rhs) -> ( Length(lhs) == Length(rhs) ) + */ + void theory_str::instantiate_str_eq_length_axiom(enode * lhs, enode * rhs) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + app * a_lhs = lhs->get_owner(); + app * a_rhs = rhs->get_owner(); + + // build premise: (lhs == rhs) + expr_ref premise(ctx.mk_eq_atom(a_lhs, a_rhs), m); + + // build conclusion: ( Length(lhs) == Length(rhs) ) + expr_ref len_lhs(mk_strlen(a_lhs), m); + SASSERT(len_lhs); + expr_ref len_rhs(mk_strlen(a_rhs), m); + SASSERT(len_rhs); + expr_ref conclusion(ctx.mk_eq_atom(len_lhs, len_rhs), m); + + TRACE("str", tout << "string-eq length-eq axiom: " + << mk_ismt2_pp(premise, m) << " -> " << mk_ismt2_pp(conclusion, m) << std::endl;); + assert_implication(premise, conclusion); + } + + void theory_str::instantiate_axiom_CharAt(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + app * expr = e->get_owner(); + if (axiomatized_terms.contains(expr)) { + TRACE("str", tout << "already set up CharAt axiom for " << mk_pp(expr, m) << std::endl;); + return; + } + axiomatized_terms.insert(expr); + + TRACE("str", tout << "instantiate CharAt axiom for " << mk_pp(expr, m) << std::endl;); + + expr_ref ts0(mk_str_var("ts0"), m); + expr_ref ts1(mk_str_var("ts1"), m); + expr_ref ts2(mk_str_var("ts2"), m); + + expr_ref cond(m.mk_and( + m_autil.mk_ge(expr->get_arg(1), mk_int(0)), + // REWRITE for arithmetic theory: + // m_autil.mk_lt(expr->get_arg(1), mk_strlen(expr->get_arg(0))) + m.mk_not(m_autil.mk_ge(m_autil.mk_add(expr->get_arg(1), m_autil.mk_mul(mk_int(-1), mk_strlen(expr->get_arg(0)))), mk_int(0))) + ), m); + + expr_ref_vector and_item(m); + and_item.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(ts0, mk_concat(ts1, ts2)))); + and_item.push_back(ctx.mk_eq_atom(expr->get_arg(1), mk_strlen(ts0))); + and_item.push_back(ctx.mk_eq_atom(mk_strlen(ts1), mk_int(1))); + + expr_ref thenBranch(m.mk_and(and_item.size(), and_item.c_ptr()), m); + expr_ref elseBranch(ctx.mk_eq_atom(ts1, mk_string("")), m); + + expr_ref axiom(m.mk_ite(cond, thenBranch, elseBranch), m); + expr_ref reductionVar(ctx.mk_eq_atom(expr, ts1), m); + + SASSERT(axiom); + SASSERT(reductionVar); + + expr_ref finalAxiom(m.mk_and(axiom, reductionVar), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); + } + + void theory_str::instantiate_axiom_prefixof(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + app * expr = e->get_owner(); + if (axiomatized_terms.contains(expr)) { + TRACE("str", tout << "already set up prefixof axiom for " << mk_pp(expr, m) << std::endl;); + return; + } + axiomatized_terms.insert(expr); + + TRACE("str", tout << "instantiate prefixof axiom for " << mk_pp(expr, m) << std::endl;); + + expr_ref ts0(mk_str_var("ts0"), m); + expr_ref ts1(mk_str_var("ts1"), m); + + expr_ref_vector innerItems(m); + innerItems.push_back(ctx.mk_eq_atom(expr->get_arg(1), mk_concat(ts0, ts1))); + innerItems.push_back(ctx.mk_eq_atom(mk_strlen(ts0), mk_strlen(expr->get_arg(0)))); + innerItems.push_back(m.mk_ite(ctx.mk_eq_atom(ts0, expr->get_arg(0)), expr, m.mk_not(expr))); + expr_ref then1(m.mk_and(innerItems.size(), innerItems.c_ptr()), m); + SASSERT(then1); + + // the top-level condition is Length(arg0) >= Length(arg1) + expr_ref topLevelCond( + m_autil.mk_ge( + m_autil.mk_add( + mk_strlen(expr->get_arg(1)), m_autil.mk_mul(mk_int(-1), mk_strlen(expr->get_arg(0)))), + mk_int(0)) + , m); + SASSERT(topLevelCond); + + expr_ref finalAxiom(m.mk_ite(topLevelCond, then1, m.mk_not(expr)), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); + } + + void theory_str::instantiate_axiom_suffixof(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + app * expr = e->get_owner(); + if (axiomatized_terms.contains(expr)) { + TRACE("str", tout << "already set up suffixof axiom for " << mk_pp(expr, m) << std::endl;); + return; + } + axiomatized_terms.insert(expr); + + TRACE("str", tout << "instantiate suffixof axiom for " << mk_pp(expr, m) << std::endl;); + + expr_ref ts0(mk_str_var("ts0"), m); + expr_ref ts1(mk_str_var("ts1"), m); + + expr_ref_vector innerItems(m); + innerItems.push_back(ctx.mk_eq_atom(expr->get_arg(1), mk_concat(ts0, ts1))); + innerItems.push_back(ctx.mk_eq_atom(mk_strlen(ts1), mk_strlen(expr->get_arg(0)))); + innerItems.push_back(m.mk_ite(ctx.mk_eq_atom(ts1, expr->get_arg(0)), expr, m.mk_not(expr))); + expr_ref then1(m.mk_and(innerItems.size(), innerItems.c_ptr()), m); + SASSERT(then1); + + // the top-level condition is Length(arg0) >= Length(arg1) + expr_ref topLevelCond( + m_autil.mk_ge( + m_autil.mk_add( + mk_strlen(expr->get_arg(1)), m_autil.mk_mul(mk_int(-1), mk_strlen(expr->get_arg(0)))), + mk_int(0)) + , m); + SASSERT(topLevelCond); + + expr_ref finalAxiom(m.mk_ite(topLevelCond, then1, m.mk_not(expr)), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); + } + + void theory_str::instantiate_axiom_Contains(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + app * ex = e->get_owner(); + if (axiomatized_terms.contains(ex)) { + TRACE("str", tout << "already set up Contains axiom for " << mk_pp(ex, m) << std::endl;); + return; + } + axiomatized_terms.insert(ex); + + // quick path, because this is necessary due to rewriter behaviour + // at minimum it should fix z3str/concat-006.smt2 + zstring haystackStr, needleStr; + if (u.str.is_string(ex->get_arg(0), haystackStr) && u.str.is_string(ex->get_arg(1), needleStr)) { + TRACE("str", tout << "eval constant Contains term " << mk_pp(ex, m) << std::endl;); + if (haystackStr.contains(needleStr)) { + assert_axiom(ex); + } else { + assert_axiom(m.mk_not(ex)); + } + return; + } + + { // register Contains() + expr * str = ex->get_arg(0); + expr * substr = ex->get_arg(1); + contains_map.push_back(ex); + std::pair key = std::pair(str, substr); + contain_pair_bool_map.insert(str, substr, ex); + contain_pair_idx_map[str].insert(key); + contain_pair_idx_map[substr].insert(key); + } + + TRACE("str", tout << "instantiate Contains axiom for " << mk_pp(ex, m) << std::endl;); + + expr_ref ts0(mk_str_var("ts0"), m); + expr_ref ts1(mk_str_var("ts1"), m); + + expr_ref breakdownAssert(ctx.mk_eq_atom(ex, ctx.mk_eq_atom(ex->get_arg(0), mk_concat(ts0, mk_concat(ex->get_arg(1), ts1)))), m); + SASSERT(breakdownAssert); + assert_axiom(breakdownAssert); + } + + void theory_str::instantiate_axiom_Indexof(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + app * expr = e->get_owner(); + if (axiomatized_terms.contains(expr)) { + TRACE("str", tout << "already set up Indexof axiom for " << mk_pp(expr, m) << std::endl;); + return; + } + axiomatized_terms.insert(expr); + + TRACE("str", tout << "instantiate Indexof axiom for " << mk_pp(expr, m) << std::endl;); + + expr_ref x1(mk_str_var("x1"), m); + expr_ref x2(mk_str_var("x2"), m); + expr_ref indexAst(mk_int_var("index"), m); + + expr_ref condAst(mk_contains(expr->get_arg(0), expr->get_arg(1)), m); + SASSERT(condAst); + + // ----------------------- + // true branch + expr_ref_vector thenItems(m); + // args[0] = x1 . args[1] . x2 + thenItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x1, mk_concat(expr->get_arg(1), x2)))); + // indexAst = |x1| + thenItems.push_back(ctx.mk_eq_atom(indexAst, mk_strlen(x1))); + // args[0] = x3 . x4 + // /\ |x3| = |x1| + |args[1]| - 1 + // /\ ! contains(x3, args[1]) + expr_ref x3(mk_str_var("x3"), m); + expr_ref x4(mk_str_var("x4"), m); + expr_ref tmpLen(m_autil.mk_add(indexAst, mk_strlen(expr->get_arg(1)), mk_int(-1)), m); + SASSERT(tmpLen); + thenItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x3, x4))); + thenItems.push_back(ctx.mk_eq_atom(mk_strlen(x3), tmpLen)); + thenItems.push_back(m.mk_not(mk_contains(x3, expr->get_arg(1)))); + expr_ref thenBranch(m.mk_and(thenItems.size(), thenItems.c_ptr()), m); + SASSERT(thenBranch); + + // ----------------------- + // false branch + expr_ref elseBranch(ctx.mk_eq_atom(indexAst, mk_int(-1)), m); + SASSERT(elseBranch); + + expr_ref breakdownAssert(m.mk_ite(condAst, thenBranch, elseBranch), m); + SASSERT(breakdownAssert); + + expr_ref reduceToIndex(ctx.mk_eq_atom(expr, indexAst), m); + SASSERT(reduceToIndex); + + expr_ref finalAxiom(m.mk_and(breakdownAssert, reduceToIndex), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); + } + + void theory_str::instantiate_axiom_Indexof2(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + app * expr = e->get_owner(); + if (axiomatized_terms.contains(expr)) { + TRACE("str", tout << "already set up Indexof2 axiom for " << mk_pp(expr, m) << std::endl;); + return; + } + axiomatized_terms.insert(expr); + + TRACE("str", tout << "instantiate Indexof2 axiom for " << mk_pp(expr, m) << std::endl;); + + // ------------------------------------------------------------------------------- + // if (arg[2] >= length(arg[0])) // ite2 + // resAst = -1 + // else + // args[0] = prefix . suffix + // /\ indexAst = indexof(suffix, arg[1]) + // /\ args[2] = len(prefix) + // /\ if (indexAst == -1) resAst = indexAst // ite3 + // else resAst = args[2] + indexAst + // ------------------------------------------------------------------------------- + + expr_ref resAst(mk_int_var("res"), m); + expr_ref indexAst(mk_int_var("index"), m); + expr_ref prefix(mk_str_var("prefix"), m); + expr_ref suffix(mk_str_var("suffix"), m); + expr_ref prefixLen(mk_strlen(prefix), m); + expr_ref zeroAst(mk_int(0), m); + expr_ref negOneAst(mk_int(-1), m); + + expr_ref ite3(m.mk_ite( + ctx.mk_eq_atom(indexAst, negOneAst), + ctx.mk_eq_atom(resAst, negOneAst), + ctx.mk_eq_atom(resAst, m_autil.mk_add(expr->get_arg(2), indexAst)) + ),m); + + expr_ref_vector ite2ElseItems(m); + ite2ElseItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(prefix, suffix))); + ite2ElseItems.push_back(ctx.mk_eq_atom(indexAst, mk_indexof(suffix, expr->get_arg(1)))); + ite2ElseItems.push_back(ctx.mk_eq_atom(expr->get_arg(2), prefixLen)); + ite2ElseItems.push_back(ite3); + expr_ref ite2Else(m.mk_and(ite2ElseItems.size(), ite2ElseItems.c_ptr()), m); + SASSERT(ite2Else); + + expr_ref ite2(m.mk_ite( + //m_autil.mk_ge(expr->get_arg(2), mk_strlen(expr->get_arg(0))), + m_autil.mk_ge(m_autil.mk_add(expr->get_arg(2), m_autil.mk_mul(mk_int(-1), mk_strlen(expr->get_arg(0)))), zeroAst), + ctx.mk_eq_atom(resAst, negOneAst), + ite2Else + ), m); + SASSERT(ite2); + + expr_ref ite1(m.mk_ite( + //m_autil.mk_lt(expr->get_arg(2), zeroAst), + m.mk_not(m_autil.mk_ge(expr->get_arg(2), zeroAst)), + ctx.mk_eq_atom(resAst, mk_indexof(expr->get_arg(0), expr->get_arg(1))), + ite2 + ), m); + SASSERT(ite1); + assert_axiom(ite1); + + expr_ref reduceTerm(ctx.mk_eq_atom(expr, resAst), m); + SASSERT(reduceTerm); + assert_axiom(reduceTerm); + } + + void theory_str::instantiate_axiom_LastIndexof(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + app * expr = e->get_owner(); + if (axiomatized_terms.contains(expr)) { + TRACE("str", tout << "already set up LastIndexof axiom for " << mk_pp(expr, m) << std::endl;); + return; + } + axiomatized_terms.insert(expr); + + TRACE("str", tout << "instantiate LastIndexof axiom for " << mk_pp(expr, m) << std::endl;); + + expr_ref x1(mk_str_var("x1"), m); + expr_ref x2(mk_str_var("x2"), m); + expr_ref indexAst(mk_int_var("index"), m); + expr_ref_vector items(m); + + // args[0] = x1 . args[1] . x2 + expr_ref eq1(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x1, mk_concat(expr->get_arg(1), x2))), m); + expr_ref arg0HasArg1(mk_contains(expr->get_arg(0), expr->get_arg(1)), m); // arg0HasArg1 = Contains(args[0], args[1]) + items.push_back(ctx.mk_eq_atom(arg0HasArg1, eq1)); + + + expr_ref condAst(arg0HasArg1, m); + //---------------------------- + // true branch + expr_ref_vector thenItems(m); + thenItems.push_back(m_autil.mk_ge(indexAst, mk_int(0))); + // args[0] = x1 . args[1] . x2 + // x1 doesn't contain args[1] + thenItems.push_back(m.mk_not(mk_contains(x2, expr->get_arg(1)))); + thenItems.push_back(ctx.mk_eq_atom(indexAst, mk_strlen(x1))); + + bool canSkip = false; + zstring arg1Str; + if (u.str.is_string(expr->get_arg(1), arg1Str)) { + if (arg1Str.length() == 1) { + canSkip = true; + } + } + + if (!canSkip) { + // args[0] = x3 . x4 /\ |x3| = |x1| + 1 /\ ! contains(x4, args[1]) + expr_ref x3(mk_str_var("x3"), m); + expr_ref x4(mk_str_var("x4"), m); + expr_ref tmpLen(m_autil.mk_add(indexAst, mk_int(1)), m); + thenItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x3, x4))); + thenItems.push_back(ctx.mk_eq_atom(mk_strlen(x3), tmpLen)); + thenItems.push_back(m.mk_not(mk_contains(x4, expr->get_arg(1)))); + } + //---------------------------- + // else branch + expr_ref_vector elseItems(m); + elseItems.push_back(ctx.mk_eq_atom(indexAst, mk_int(-1))); + + items.push_back(m.mk_ite(condAst, m.mk_and(thenItems.size(), thenItems.c_ptr()), m.mk_and(elseItems.size(), elseItems.c_ptr()))); + + expr_ref breakdownAssert(m.mk_and(items.size(), items.c_ptr()), m); + SASSERT(breakdownAssert); + + expr_ref reduceToIndex(ctx.mk_eq_atom(expr, indexAst), m); + SASSERT(reduceToIndex); + + expr_ref finalAxiom(m.mk_and(breakdownAssert, reduceToIndex), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); + } + + void theory_str::instantiate_axiom_Substr(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + app * expr = e->get_owner(); + if (axiomatized_terms.contains(expr)) { + TRACE("str", tout << "already set up Substr axiom for " << mk_pp(expr, m) << std::endl;); + return; + } + axiomatized_terms.insert(expr); + + TRACE("str", tout << "instantiate Substr axiom for " << mk_pp(expr, m) << std::endl;); + + expr_ref substrBase(expr->get_arg(0), m); + expr_ref substrPos(expr->get_arg(1), m); + expr_ref substrLen(expr->get_arg(2), m); + SASSERT(substrBase); + SASSERT(substrPos); + SASSERT(substrLen); + + expr_ref zero(m_autil.mk_numeral(rational::zero(), true), m); + expr_ref minusOne(m_autil.mk_numeral(rational::minus_one(), true), m); + SASSERT(zero); + SASSERT(minusOne); + + expr_ref_vector argumentsValid_terms(m); + // pos >= 0 + argumentsValid_terms.push_back(m_autil.mk_ge(substrPos, zero)); + // pos < strlen(base) + // --> pos + -1*strlen(base) < 0 + argumentsValid_terms.push_back(m.mk_not(m_autil.mk_ge( + m_autil.mk_add(substrPos, m_autil.mk_mul(minusOne, substrLen)), + zero))); + // len >= 0 + argumentsValid_terms.push_back(m_autil.mk_ge(substrLen, zero)); + + expr_ref argumentsValid(mk_and(argumentsValid_terms), m); + SASSERT(argumentsValid); + ctx.internalize(argumentsValid, false); + + // (pos+len) >= strlen(base) + // --> pos + len + -1*strlen(base) >= 0 + expr_ref lenOutOfBounds(m_autil.mk_ge( + m_autil.mk_add(substrPos, substrLen, m_autil.mk_mul(minusOne, mk_strlen(substrBase))), + zero), m); + SASSERT(lenOutOfBounds); + ctx.internalize(argumentsValid, false); + + // Case 1: pos < 0 or pos >= strlen(base) or len < 0 + // ==> (Substr ...) = "" + expr_ref case1_premise(m.mk_not(argumentsValid), m); + SASSERT(case1_premise); + ctx.internalize(case1_premise, false); + expr_ref case1_conclusion(ctx.mk_eq_atom(expr, mk_string("")), m); + SASSERT(case1_conclusion); + ctx.internalize(case1_conclusion, false); + expr_ref case1(rewrite_implication(case1_premise, case1_conclusion), m); + SASSERT(case1); + + // Case 2: (pos >= 0 and pos < strlen(base) and len >= 0) and (pos+len) >= strlen(base) + // ==> base = t0.t1 AND len(t0) = pos AND (Substr ...) = t1 + expr_ref t0(mk_str_var("t0"), m); + expr_ref t1(mk_str_var("t1"), m); + expr_ref case2_conclusion(m.mk_and( + ctx.mk_eq_atom(substrBase, mk_concat(t0,t1)), + ctx.mk_eq_atom(mk_strlen(t0), substrPos), + ctx.mk_eq_atom(expr, t1)), m); + expr_ref case2(rewrite_implication(m.mk_and(argumentsValid, lenOutOfBounds), case2_conclusion), m); + SASSERT(case2); + + // Case 3: (pos >= 0 and pos < strlen(base) and len >= 0) and (pos+len) < strlen(base) + // ==> base = t2.t3.t4 AND len(t2) = pos AND len(t3) = len AND (Substr ...) = t3 + expr_ref t2(mk_str_var("t2"), m); + expr_ref t3(mk_str_var("t3"), m); + expr_ref t4(mk_str_var("t4"), m); + expr_ref_vector case3_conclusion_terms(m); + case3_conclusion_terms.push_back(ctx.mk_eq_atom(substrBase, mk_concat(t2, mk_concat(t3, t4)))); + case3_conclusion_terms.push_back(ctx.mk_eq_atom(mk_strlen(t2), substrPos)); + case3_conclusion_terms.push_back(ctx.mk_eq_atom(mk_strlen(t3), substrLen)); + case3_conclusion_terms.push_back(ctx.mk_eq_atom(expr, t3)); + expr_ref case3_conclusion(mk_and(case3_conclusion_terms), m); + expr_ref case3(rewrite_implication(m.mk_and(argumentsValid, m.mk_not(lenOutOfBounds)), case3_conclusion), m); + SASSERT(case3); + + ctx.internalize(case1, false); + ctx.internalize(case2, false); + ctx.internalize(case3, false); + + expr_ref finalAxiom(m.mk_and(case1, case2, case3), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); + } + + void theory_str::instantiate_axiom_Replace(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + app * expr = e->get_owner(); + if (axiomatized_terms.contains(expr)) { + TRACE("str", tout << "already set up Replace axiom for " << mk_pp(expr, m) << std::endl;); + return; + } + axiomatized_terms.insert(expr); + + TRACE("str", tout << "instantiate Replace axiom for " << mk_pp(expr, m) << std::endl;); + + expr_ref x1(mk_str_var("x1"), m); + expr_ref x2(mk_str_var("x2"), m); + expr_ref i1(mk_int_var("i1"), m); + expr_ref result(mk_str_var("result"), m); + + // condAst = Contains(args[0], args[1]) + expr_ref condAst(mk_contains(expr->get_arg(0), expr->get_arg(1)), m); + // ----------------------- + // true branch + expr_ref_vector thenItems(m); + // args[0] = x1 . args[1] . x2 + thenItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x1, mk_concat(expr->get_arg(1), x2)))); + // i1 = |x1| + thenItems.push_back(ctx.mk_eq_atom(i1, mk_strlen(x1))); + // args[0] = x3 . x4 /\ |x3| = |x1| + |args[1]| - 1 /\ ! contains(x3, args[1]) + expr_ref x3(mk_str_var("x3"), m); + expr_ref x4(mk_str_var("x4"), m); + expr_ref tmpLen(m_autil.mk_add(i1, mk_strlen(expr->get_arg(1)), mk_int(-1)), m); + thenItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x3, x4))); + thenItems.push_back(ctx.mk_eq_atom(mk_strlen(x3), tmpLen)); + thenItems.push_back(m.mk_not(mk_contains(x3, expr->get_arg(1)))); + thenItems.push_back(ctx.mk_eq_atom(result, mk_concat(x1, mk_concat(expr->get_arg(2), x2)))); + // ----------------------- + // false branch + expr_ref elseBranch(ctx.mk_eq_atom(result, expr->get_arg(0)), m); + + expr_ref breakdownAssert(m.mk_ite(condAst, m.mk_and(thenItems.size(), thenItems.c_ptr()), elseBranch), m); + SASSERT(breakdownAssert); + + expr_ref reduceToResult(ctx.mk_eq_atom(expr, result), m); + SASSERT(reduceToResult); + + expr_ref finalAxiom(m.mk_and(breakdownAssert, reduceToResult), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); + } + + void theory_str::instantiate_axiom_str_to_int(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + app * ex = e->get_owner(); + if (axiomatized_terms.contains(ex)) { + TRACE("str", tout << "already set up str.to-int axiom for " << mk_pp(ex, m) << std::endl;); + return; + } + axiomatized_terms.insert(ex); + + TRACE("str", tout << "instantiate str.to-int axiom for " << mk_pp(ex, m) << std::endl;); + + // let expr = (str.to-int S) + // axiom 1: expr >= -1 + // axiom 2: expr = 0 <==> S = "0" + // axiom 3: expr >= 1 ==> len(S) > 0 AND S[0] != "0" + + expr * S = ex->get_arg(0); + { + expr_ref axiom1(m_autil.mk_ge(ex, m_autil.mk_numeral(rational::minus_one(), true)), m); + SASSERT(axiom1); + assert_axiom(axiom1); + } + + { + expr_ref lhs(ctx.mk_eq_atom(ex, m_autil.mk_numeral(rational::zero(), true)), m); + expr_ref rhs(ctx.mk_eq_atom(S, mk_string("0")), m); + expr_ref axiom2(ctx.mk_eq_atom(lhs, rhs), m); + SASSERT(axiom2); + assert_axiom(axiom2); + } + + { + expr_ref premise(m_autil.mk_ge(ex, m_autil.mk_numeral(rational::one(), true)), m); + expr_ref hd(mk_str_var("hd"), m); + expr_ref tl(mk_str_var("tl"), m); + expr_ref conclusion1(ctx.mk_eq_atom(S, mk_concat(hd, tl)), m); + expr_ref conclusion2(ctx.mk_eq_atom(mk_strlen(hd), m_autil.mk_numeral(rational::one(), true)), m); + expr_ref conclusion3(m.mk_not(ctx.mk_eq_atom(hd, mk_string("0"))), m); + expr_ref conclusion(m.mk_and(conclusion1, conclusion2, conclusion3), m); + SASSERT(premise); + SASSERT(conclusion); + assert_implication(premise, conclusion); + } + } + + void theory_str::instantiate_axiom_int_to_str(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + app * ex = e->get_owner(); + if (axiomatized_terms.contains(ex)) { + TRACE("str", tout << "already set up str.from-int axiom for " << mk_pp(ex, m) << std::endl;); + return; + } + axiomatized_terms.insert(ex); + + TRACE("str", tout << "instantiate str.from-int axiom for " << mk_pp(ex, m) << std::endl;); + + // axiom 1: N < 0 <==> (str.from-int N) = "" + expr * N = ex->get_arg(0); + { + expr_ref axiom1_lhs(m.mk_not(m_autil.mk_ge(N, m_autil.mk_numeral(rational::zero(), true))), m); + expr_ref axiom1_rhs(ctx.mk_eq_atom(ex, mk_string("")), m); + expr_ref axiom1(ctx.mk_eq_atom(axiom1_lhs, axiom1_rhs), m); + SASSERT(axiom1); + assert_axiom(axiom1); + } + } + + expr * theory_str::mk_RegexIn(expr * str, expr * regexp) { + app * regexIn = u.re.mk_in_re(str, regexp); + // immediately force internalization so that axiom setup does not fail + get_context().internalize(regexIn, false); + set_up_axioms(regexIn); + return regexIn; + } + + static zstring str2RegexStr(zstring str) { + zstring res(""); + int len = str.length(); + for (int i = 0; i < len; i++) { + char nc = str[i]; + // 12 special chars + if (nc == '\\' || nc == '^' || nc == '$' || nc == '.' || nc == '|' || nc == '?' + || nc == '*' || nc == '+' || nc == '(' || nc == ')' || nc == '[' || nc == '{') { + res = res + zstring("\\"); + } + char tmp[2] = {(char)str[i], '\0'}; + res = res + zstring(tmp); + } + return res; + } + + zstring theory_str::get_std_regex_str(expr * regex) { + app * a_regex = to_app(regex); + if (u.re.is_to_re(a_regex)) { + expr * regAst = a_regex->get_arg(0); + zstring regAstVal; + u.str.is_string(regAst, regAstVal); + zstring regStr = str2RegexStr(regAstVal); + return regStr; + } else if (u.re.is_concat(a_regex)) { + expr * reg1Ast = a_regex->get_arg(0); + expr * reg2Ast = a_regex->get_arg(1); + zstring reg1Str = get_std_regex_str(reg1Ast); + zstring reg2Str = get_std_regex_str(reg2Ast); + return zstring("(") + reg1Str + zstring(")(") + reg2Str + zstring(")"); + } else if (u.re.is_union(a_regex)) { + expr * reg1Ast = a_regex->get_arg(0); + expr * reg2Ast = a_regex->get_arg(1); + zstring reg1Str = get_std_regex_str(reg1Ast); + zstring reg2Str = get_std_regex_str(reg2Ast); + return zstring("(") + reg1Str + zstring(")|(") + reg2Str + zstring(")"); + } else if (u.re.is_star(a_regex)) { + expr * reg1Ast = a_regex->get_arg(0); + zstring reg1Str = get_std_regex_str(reg1Ast); + return zstring("(") + reg1Str + zstring(")*"); + } else if (u.re.is_range(a_regex)) { + expr * range1 = a_regex->get_arg(0); + expr * range2 = a_regex->get_arg(1); + zstring range1val, range2val; + u.str.is_string(range1, range1val); + u.str.is_string(range2, range2val); + return zstring("[") + range1val + zstring("-") + range2val + zstring("]"); + } else { + TRACE("str", tout << "BUG: unrecognized regex term " << mk_pp(regex, get_manager()) << std::endl;); + UNREACHABLE(); return zstring(""); + } + } + + void theory_str::instantiate_axiom_RegexIn(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + app * ex = e->get_owner(); + if (axiomatized_terms.contains(ex)) { + TRACE("str", tout << "already set up RegexIn axiom for " << mk_pp(ex, m) << std::endl;); + return; + } + axiomatized_terms.insert(ex); + + TRACE("str", tout << "instantiate RegexIn axiom for " << mk_pp(ex, m) << std::endl;); + + { + zstring regexStr = get_std_regex_str(ex->get_arg(1)); + std::pair key1(ex->get_arg(0), regexStr); + // skip Z3str's map check, because we already check if we set up axioms on this term + regex_in_bool_map[key1] = ex; + regex_in_var_reg_str_map[ex->get_arg(0)].insert(regexStr); + } + + expr_ref str(ex->get_arg(0), m); + app * regex = to_app(ex->get_arg(1)); + + if (u.re.is_to_re(regex)) { + expr_ref rxStr(regex->get_arg(0), m); + // want to assert 'expr IFF (str == rxStr)' + expr_ref rhs(ctx.mk_eq_atom(str, rxStr), m); + expr_ref finalAxiom(m.mk_iff(ex, rhs), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); + TRACE("str", tout << "set up Str2Reg: (RegexIn " << mk_pp(str, m) << " " << mk_pp(regex, m) << ")" << std::endl;); + } else if (u.re.is_concat(regex)) { + expr_ref var1(mk_regex_rep_var(), m); + expr_ref var2(mk_regex_rep_var(), m); + expr_ref rhs(mk_concat(var1, var2), m); + expr_ref rx1(regex->get_arg(0), m); + expr_ref rx2(regex->get_arg(1), m); + expr_ref var1InRegex1(mk_RegexIn(var1, rx1), m); + expr_ref var2InRegex2(mk_RegexIn(var2, rx2), m); + + expr_ref_vector items(m); + items.push_back(var1InRegex1); + items.push_back(var2InRegex2); + items.push_back(ctx.mk_eq_atom(ex, ctx.mk_eq_atom(str, rhs))); + + expr_ref finalAxiom(mk_and(items), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); + } else if (u.re.is_union(regex)) { + expr_ref var1(mk_regex_rep_var(), m); + expr_ref var2(mk_regex_rep_var(), m); + expr_ref orVar(m.mk_or(ctx.mk_eq_atom(str, var1), ctx.mk_eq_atom(str, var2)), m); + expr_ref regex1(regex->get_arg(0), m); + expr_ref regex2(regex->get_arg(1), m); + expr_ref var1InRegex1(mk_RegexIn(var1, regex1), m); + expr_ref var2InRegex2(mk_RegexIn(var2, regex2), m); + expr_ref_vector items(m); + items.push_back(var1InRegex1); + items.push_back(var2InRegex2); + items.push_back(ctx.mk_eq_atom(ex, orVar)); + assert_axiom(mk_and(items)); + } else if (u.re.is_star(regex)) { + // slightly more complex due to the unrolling step. + expr_ref regex1(regex->get_arg(0), m); + expr_ref unrollCount(mk_unroll_bound_var(), m); + expr_ref unrollFunc(mk_unroll(regex1, unrollCount), m); + expr_ref_vector items(m); + items.push_back(ctx.mk_eq_atom(ex, ctx.mk_eq_atom(str, unrollFunc))); + items.push_back(ctx.mk_eq_atom(ctx.mk_eq_atom(unrollCount, mk_int(0)), ctx.mk_eq_atom(unrollFunc, mk_string("")))); + expr_ref finalAxiom(mk_and(items), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); + } else if (u.re.is_range(regex)) { + // (re.range "A" "Z") unfolds to (re.union "A" "B" ... "Z"); + // we rewrite to expr IFF (str = "A" or str = "B" or ... or str = "Z") + expr_ref lo(regex->get_arg(0), m); + expr_ref hi(regex->get_arg(1), m); + zstring str_lo, str_hi; + SASSERT(u.str.is_string(lo)); + SASSERT(u.str.is_string(hi)); + u.str.is_string(lo, str_lo); + u.str.is_string(hi, str_hi); + SASSERT(str_lo.length() == 1); + SASSERT(str_hi.length() == 1); + unsigned int c1 = str_lo[0]; + unsigned int c2 = str_hi[0]; + if (c1 > c2) { + // exchange + unsigned int tmp = c1; + c1 = c2; + c2 = tmp; + } + expr_ref_vector range_cases(m); + for (unsigned int ch = c1; ch <= c2; ++ch) { + zstring s_ch(ch); + expr_ref rhs(ctx.mk_eq_atom(str, u.str.mk_string(s_ch)), m); + range_cases.push_back(rhs); + } + expr_ref rhs(mk_or(range_cases), m); + expr_ref finalAxiom(m.mk_iff(ex, rhs), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); + } else { + TRACE("str", tout << "ERROR: unknown regex expression " << mk_pp(regex, m) << "!" << std::endl;); + NOT_IMPLEMENTED_YET(); + } + } + + void theory_str::attach_new_th_var(enode * n) { + context & ctx = get_context(); + theory_var v = mk_var(n); + ctx.attach_th_var(n, this, v); + TRACE("str", tout << "new theory var: " << mk_ismt2_pp(n->get_owner(), get_manager()) << " := v#" << v << std::endl;); + } + + void theory_str::reset_eh() { + TRACE("str", tout << "resetting" << std::endl;); + m_trail_stack.reset(); + + m_basicstr_axiom_todo.reset(); + m_str_eq_todo.reset(); + m_concat_axiom_todo.reset(); + pop_scope_eh(get_context().get_scope_level()); + } + + /* + * Check equality among equivalence class members of LHS and RHS + * to discover an incorrect LHS == RHS. + * For example, if we have y2 == "str3" + * and the equivalence classes are + * { y2, (Concat ce m2) } + * { "str3", (Concat abc x2) } + * then y2 can't be equal to "str3". + * Then add an assertion: (y2 == (Concat ce m2)) AND ("str3" == (Concat abc x2)) -> (y2 != "str3") + */ + bool theory_str::new_eq_check(expr * lhs, expr * rhs) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + // skip this check if we defer consistency checking, as we can do it for every EQC in final check + if (!opt_DeferEQCConsistencyCheck) { + check_concat_len_in_eqc(lhs); + check_concat_len_in_eqc(rhs); + } + + // Now we iterate over all pairs of terms across both EQCs + // and check whether we can show that any pair of distinct terms + // cannot possibly be equal. + // If that's the case, we assert an axiom to that effect and stop. + + expr * eqc_nn1 = lhs; + do { + expr * eqc_nn2 = rhs; + do { + TRACE("str", tout << "checking whether " << mk_pp(eqc_nn1, m) << " and " << mk_pp(eqc_nn2, m) << " can be equal" << std::endl;); + // inconsistency check: value + if (!can_two_nodes_eq(eqc_nn1, eqc_nn2)) { + TRACE("str", tout << "inconsistency detected: " << mk_pp(eqc_nn1, m) << " cannot be equal to " << mk_pp(eqc_nn2, m) << std::endl;); + expr_ref to_assert(m.mk_not(ctx.mk_eq_atom(eqc_nn1, eqc_nn2)), m); + assert_axiom(to_assert); + // this shouldn't use the integer theory at all, so we don't allow the option of quick-return + return false; + } + if (!check_length_consistency(eqc_nn1, eqc_nn2)) { + TRACE("str", tout << "inconsistency detected: " << mk_pp(eqc_nn1, m) << " and " << mk_pp(eqc_nn2, m) << " have inconsistent lengths" << std::endl;); + if (opt_NoQuickReturn_IntegerTheory){ + TRACE("str", tout << "continuing in new_eq_check() due to opt_NoQuickReturn_IntegerTheory" << std::endl;); + } else { + return false; + } + } + eqc_nn2 = get_eqc_next(eqc_nn2); + } while (eqc_nn2 != rhs); + eqc_nn1 = get_eqc_next(eqc_nn1); + } while (eqc_nn1 != lhs); + + if (!contains_map.empty()) { + check_contain_in_new_eq(lhs, rhs); + } + + if (!regex_in_bool_map.empty()) { + TRACE("str", tout << "checking regex consistency" << std::endl;); + check_regex_in(lhs, rhs); + } + + // okay, all checks here passed + return true; + } + + // support for user_smt_theory-style EQC handling + + app * theory_str::get_ast(theory_var i) { + return get_enode(i)->get_owner(); + } + + theory_var theory_str::get_var(expr * n) const { + if (!is_app(n)) { + return null_theory_var; + } + context & ctx = get_context(); + if (ctx.e_internalized(to_app(n))) { + enode * e = ctx.get_enode(to_app(n)); + return e->get_th_var(get_id()); + } + return null_theory_var; + } + + // simulate Z3_theory_get_eqc_next() + expr * theory_str::get_eqc_next(expr * n) { + theory_var v = get_var(n); + if (v != null_theory_var) { + theory_var r = m_find.next(v); + return get_ast(r); + } + return n; + } + + void theory_str::group_terms_by_eqc(expr * n, std::set & concats, std::set & vars, std::set & consts) { + expr * eqcNode = n; + do { + app * ast = to_app(eqcNode); + if (u.str.is_concat(ast)) { + expr * simConcat = simplify_concat(ast); + if (simConcat != ast) { + if (u.str.is_concat(to_app(simConcat))) { + concats.insert(simConcat); + } else { + if (u.str.is_string(simConcat)) { + consts.insert(simConcat); + } else { + vars.insert(simConcat); + } + } + } else { + concats.insert(simConcat); + } + } else if (u.str.is_string(ast)) { + consts.insert(ast); + } else { + vars.insert(ast); + } + eqcNode = get_eqc_next(eqcNode); + } while (eqcNode != n); + } + + void theory_str::get_nodes_in_concat(expr * node, ptr_vector & nodeList) { + app * a_node = to_app(node); + if (!u.str.is_concat(a_node)) { + nodeList.push_back(node); + return; + } else { + SASSERT(a_node->get_num_args() == 2); + expr * leftArg = a_node->get_arg(0); + expr * rightArg = a_node->get_arg(1); + get_nodes_in_concat(leftArg, nodeList); + get_nodes_in_concat(rightArg, nodeList); + } + } + + // previously Concat() in strTheory.cpp + // Evaluates the concatenation (n1 . n2) with respect to + // the current equivalence classes of n1 and n2. + // Returns a constant string expression representing this concatenation + // if one can be determined, or NULL if this is not possible. + expr * theory_str::eval_concat(expr * n1, expr * n2) { + bool n1HasEqcValue = false; + bool n2HasEqcValue = false; + expr * v1 = get_eqc_value(n1, n1HasEqcValue); + expr * v2 = get_eqc_value(n2, n2HasEqcValue); + if (n1HasEqcValue && n2HasEqcValue) { + zstring n1_str, n2_str; + u.str.is_string(v1, n1_str); + u.str.is_string(v2, n2_str); + zstring result = n1_str + n2_str; + return mk_string(result); + } else if (n1HasEqcValue && !n2HasEqcValue) { + zstring v1_str; + u.str.is_string(v1, v1_str); + if (v1_str.empty()) { + return n2; + } + } else if (n2HasEqcValue && !n1HasEqcValue) { + zstring v2_str; + u.str.is_string(v2, v2_str); + if (v2_str.empty()) { + return n1; + } + } + // give up + return NULL; + } + + static inline std::string rational_to_string_if_exists(const rational & x, bool x_exists) { + if (x_exists) { + return x.to_string(); + } else { + return "?"; + } + } + + /* + * The inputs: + * ~ nn: non const node + * ~ eq_str: the equivalent constant string of nn + * Iterate the parent of all eqc nodes of nn, looking for: + * ~ concat node + * to see whether some concat nodes can be simplified. + */ + void theory_str::simplify_parent(expr * nn, expr * eq_str) { + ast_manager & m = get_manager(); + context & ctx = get_context(); + + TRACE("str", tout << "simplifying parents of " << mk_ismt2_pp(nn, m) + << " with respect to " << mk_ismt2_pp(eq_str, m) << std::endl;); + + ctx.internalize(nn, false); + + zstring eq_strValue; + u.str.is_string(eq_str, eq_strValue); + expr * n_eqNode = nn; + do { + enode * n_eq_enode = ctx.get_enode(n_eqNode); + TRACE("str", tout << "considering all parents of " << mk_ismt2_pp(n_eqNode, m) << std::endl + << "associated n_eq_enode has " << n_eq_enode->get_num_parents() << " parents" << std::endl;); + + // the goal of this next bit is to avoid dereferencing a bogus e_parent in the following loop. + // what I imagine is causing this bug is that, for example, we examine some parent, we add an axiom that involves it, + // and the parent_it iterator becomes invalidated, because we indirectly modified the container that we're iterating over. + + enode_vector current_parents; + for (enode_vector::const_iterator parent_it = n_eq_enode->begin_parents(); parent_it != n_eq_enode->end_parents(); parent_it++) { + current_parents.insert(*parent_it); + } + + for (enode_vector::iterator parent_it = current_parents.begin(); parent_it != current_parents.end(); ++parent_it) { + enode * e_parent = *parent_it; + SASSERT(e_parent != NULL); + + app * a_parent = e_parent->get_owner(); + TRACE("str", tout << "considering parent " << mk_ismt2_pp(a_parent, m) << std::endl;); + + if (u.str.is_concat(a_parent)) { + expr * arg0 = a_parent->get_arg(0); + expr * arg1 = a_parent->get_arg(1); + + rational parentLen; + bool parentLen_exists = get_len_value(a_parent, parentLen); + + if (arg0 == n_eq_enode->get_owner()) { + rational arg0Len, arg1Len; + bool arg0Len_exists = get_len_value(eq_str, arg0Len); + bool arg1Len_exists = get_len_value(arg1, arg1Len); + + TRACE("str", + tout << "simplify_parent #1:" << std::endl + << "* parent = " << mk_ismt2_pp(a_parent, m) << std::endl + << "* |parent| = " << rational_to_string_if_exists(parentLen, parentLen_exists) << std::endl + << "* |arg0| = " << rational_to_string_if_exists(arg0Len, arg0Len_exists) << std::endl + << "* |arg1| = " << rational_to_string_if_exists(arg1Len, arg1Len_exists) << std::endl; + ); + + if (parentLen_exists && !arg1Len_exists) { + TRACE("str", tout << "make up len for arg1" << std::endl;); + expr_ref implyL11(m.mk_and(ctx.mk_eq_atom(mk_strlen(a_parent), mk_int(parentLen)), + ctx.mk_eq_atom(mk_strlen(arg0), mk_int(arg0Len))), m); + rational makeUpLenArg1 = parentLen - arg0Len; + if (makeUpLenArg1.is_nonneg()) { + expr_ref implyR11(ctx.mk_eq_atom(mk_strlen(arg1), mk_int(makeUpLenArg1)), m); + assert_implication(implyL11, implyR11); + } else { + expr_ref neg(m.mk_not(implyL11), m); + assert_axiom(neg); + } + } + + // (Concat n_eqNode arg1) /\ arg1 has eq const + + expr * concatResult = eval_concat(eq_str, arg1); + if (concatResult != NULL) { + bool arg1HasEqcValue = false; + expr * arg1Value = get_eqc_value(arg1, arg1HasEqcValue); + expr_ref implyL(m); + if (arg1 != arg1Value) { + expr_ref eq_ast1(m); + eq_ast1 = ctx.mk_eq_atom(n_eqNode, eq_str); + SASSERT(eq_ast1); + + expr_ref eq_ast2(m); + eq_ast2 = ctx.mk_eq_atom(arg1, arg1Value); + SASSERT(eq_ast2); + implyL = m.mk_and(eq_ast1, eq_ast2); + } else { + implyL = ctx.mk_eq_atom(n_eqNode, eq_str); + } + + + if (!in_same_eqc(a_parent, concatResult)) { + expr_ref implyR(m); + implyR = ctx.mk_eq_atom(a_parent, concatResult); + SASSERT(implyR); + + assert_implication(implyL, implyR); + } + } else if (u.str.is_concat(to_app(n_eqNode))) { + expr_ref simpleConcat(m); + simpleConcat = mk_concat(eq_str, arg1); + if (!in_same_eqc(a_parent, simpleConcat)) { + expr_ref implyL(m); + implyL = ctx.mk_eq_atom(n_eqNode, eq_str); + SASSERT(implyL); + + expr_ref implyR(m); + implyR = ctx.mk_eq_atom(a_parent, simpleConcat); + SASSERT(implyR); + assert_implication(implyL, implyR); + } + } + } // if (arg0 == n_eq_enode->get_owner()) + + if (arg1 == n_eq_enode->get_owner()) { + rational arg0Len, arg1Len; + bool arg0Len_exists = get_len_value(arg0, arg0Len); + bool arg1Len_exists = get_len_value(eq_str, arg1Len); + + TRACE("str", + tout << "simplify_parent #2:" << std::endl + << "* parent = " << mk_ismt2_pp(a_parent, m) << std::endl + << "* |parent| = " << rational_to_string_if_exists(parentLen, parentLen_exists) << std::endl + << "* |arg0| = " << rational_to_string_if_exists(arg0Len, arg0Len_exists) << std::endl + << "* |arg1| = " << rational_to_string_if_exists(arg1Len, arg1Len_exists) << std::endl; + ); + if (parentLen_exists && !arg0Len_exists) { + TRACE("str", tout << "make up len for arg0" << std::endl;); + expr_ref implyL11(m.mk_and(ctx.mk_eq_atom(mk_strlen(a_parent), mk_int(parentLen)), + ctx.mk_eq_atom(mk_strlen(arg1), mk_int(arg1Len))), m); + rational makeUpLenArg0 = parentLen - arg1Len; + if (makeUpLenArg0.is_nonneg()) { + expr_ref implyR11(ctx.mk_eq_atom(mk_strlen(arg0), mk_int(makeUpLenArg0)), m); + assert_implication(implyL11, implyR11); + } else { + expr_ref neg(m.mk_not(implyL11), m); + assert_axiom(neg); + } + } + + // (Concat arg0 n_eqNode) /\ arg0 has eq const + + expr * concatResult = eval_concat(arg0, eq_str); + if (concatResult != NULL) { + bool arg0HasEqcValue = false; + expr * arg0Value = get_eqc_value(arg0, arg0HasEqcValue); + expr_ref implyL(m); + if (arg0 != arg0Value) { + expr_ref eq_ast1(m); + eq_ast1 = ctx.mk_eq_atom(n_eqNode, eq_str); + SASSERT(eq_ast1); + expr_ref eq_ast2(m); + eq_ast2 = ctx.mk_eq_atom(arg0, arg0Value); + SASSERT(eq_ast2); + + implyL = m.mk_and(eq_ast1, eq_ast2); + } else { + implyL = ctx.mk_eq_atom(n_eqNode, eq_str); + } + + if (!in_same_eqc(a_parent, concatResult)) { + expr_ref implyR(m); + implyR = ctx.mk_eq_atom(a_parent, concatResult); + SASSERT(implyR); + + assert_implication(implyL, implyR); + } + } else if (u.str.is_concat(to_app(n_eqNode))) { + expr_ref simpleConcat(m); + simpleConcat = mk_concat(arg0, eq_str); + if (!in_same_eqc(a_parent, simpleConcat)) { + expr_ref implyL(m); + implyL = ctx.mk_eq_atom(n_eqNode, eq_str); + SASSERT(implyL); + + expr_ref implyR(m); + implyR = ctx.mk_eq_atom(a_parent, simpleConcat); + SASSERT(implyR); + assert_implication(implyL, implyR); + } + } + } // if (arg1 == n_eq_enode->get_owner + + + //--------------------------------------------------------- + // Case (2-1) begin: (Concat n_eqNode (Concat str var)) + if (arg0 == n_eqNode && u.str.is_concat(to_app(arg1))) { + app * a_arg1 = to_app(arg1); + TRACE("str", tout << "simplify_parent #3" << std::endl;); + expr * r_concat_arg0 = a_arg1->get_arg(0); + if (u.str.is_string(r_concat_arg0)) { + expr * combined_str = eval_concat(eq_str, r_concat_arg0); + SASSERT(combined_str); + expr * r_concat_arg1 = a_arg1->get_arg(1); + expr_ref implyL(m); + implyL = ctx.mk_eq_atom(n_eqNode, eq_str); + expr * simplifiedAst = mk_concat(combined_str, r_concat_arg1); + if (!in_same_eqc(a_parent, simplifiedAst)) { + expr_ref implyR(m); + implyR = ctx.mk_eq_atom(a_parent, simplifiedAst); + assert_implication(implyL, implyR); + } + } + } + // Case (2-1) end: (Concat n_eqNode (Concat str var)) + //--------------------------------------------------------- + + + //--------------------------------------------------------- + // Case (2-2) begin: (Concat (Concat var str) n_eqNode) + if (u.str.is_concat(to_app(arg0)) && arg1 == n_eqNode) { + app * a_arg0 = to_app(arg0); + TRACE("str", tout << "simplify_parent #4" << std::endl;); + expr * l_concat_arg1 = a_arg0->get_arg(1); + if (u.str.is_string(l_concat_arg1)) { + expr * combined_str = eval_concat(l_concat_arg1, eq_str); + SASSERT(combined_str); + expr * l_concat_arg0 = a_arg0->get_arg(0); + expr_ref implyL(m); + implyL = ctx.mk_eq_atom(n_eqNode, eq_str); + expr * simplifiedAst = mk_concat(l_concat_arg0, combined_str); + if (!in_same_eqc(a_parent, simplifiedAst)) { + expr_ref implyR(m); + implyR = ctx.mk_eq_atom(a_parent, simplifiedAst); + assert_implication(implyL, implyR); + } + } + } + // Case (2-2) end: (Concat (Concat var str) n_eqNode) + //--------------------------------------------------------- + + // Have to look up one more layer: if the parent of the concat is another concat + //------------------------------------------------- + // Case (3-1) begin: (Concat (Concat var n_eqNode) str ) + if (arg1 == n_eqNode) { + for (enode_vector::iterator concat_parent_it = e_parent->begin_parents(); + concat_parent_it != e_parent->end_parents(); concat_parent_it++) { + enode * e_concat_parent = *concat_parent_it; + app * concat_parent = e_concat_parent->get_owner(); + if (u.str.is_concat(concat_parent)) { + expr * concat_parent_arg0 = concat_parent->get_arg(0); + expr * concat_parent_arg1 = concat_parent->get_arg(1); + if (concat_parent_arg0 == a_parent && u.str.is_string(concat_parent_arg1)) { + TRACE("str", tout << "simplify_parent #5" << std::endl;); + expr * combinedStr = eval_concat(eq_str, concat_parent_arg1); + SASSERT(combinedStr); + expr_ref implyL(m); + implyL = ctx.mk_eq_atom(n_eqNode, eq_str); + expr * simplifiedAst = mk_concat(arg0, combinedStr); + if (!in_same_eqc(concat_parent, simplifiedAst)) { + expr_ref implyR(m); + implyR = ctx.mk_eq_atom(concat_parent, simplifiedAst); + assert_implication(implyL, implyR); + } + } + } + } + } + // Case (3-1) end: (Concat (Concat var n_eqNode) str ) + // Case (3-2) begin: (Concat str (Concat n_eqNode var) ) + if (arg0 == n_eqNode) { + for (enode_vector::iterator concat_parent_it = e_parent->begin_parents(); + concat_parent_it != e_parent->end_parents(); concat_parent_it++) { + enode * e_concat_parent = *concat_parent_it; + app * concat_parent = e_concat_parent->get_owner(); + if (u.str.is_concat(concat_parent)) { + expr * concat_parent_arg0 = concat_parent->get_arg(0); + expr * concat_parent_arg1 = concat_parent->get_arg(1); + if (concat_parent_arg1 == a_parent && u.str.is_string(concat_parent_arg0)) { + TRACE("str", tout << "simplify_parent #6" << std::endl;); + expr * combinedStr = eval_concat(concat_parent_arg0, eq_str); + SASSERT(combinedStr); + expr_ref implyL(m); + implyL = ctx.mk_eq_atom(n_eqNode, eq_str); + expr * simplifiedAst = mk_concat(combinedStr, arg1); + if (!in_same_eqc(concat_parent, simplifiedAst)) { + expr_ref implyR(m); + implyR = ctx.mk_eq_atom(concat_parent, simplifiedAst); + assert_implication(implyL, implyR); + } + } + } + } + } + // Case (3-2) end: (Concat str (Concat n_eqNode var) ) + } // if is_concat(a_parent) + } // for parent_it : n_eq_enode->begin_parents() + + + // check next EQC member + n_eqNode = get_eqc_next(n_eqNode); + } while (n_eqNode != nn); + } + + expr * theory_str::simplify_concat(expr * node) { + ast_manager & m = get_manager(); + context & ctx = get_context(); + std::map resolvedMap; + ptr_vector argVec; + get_nodes_in_concat(node, argVec); + + for (unsigned i = 0; i < argVec.size(); ++i) { + bool vArgHasEqcValue = false; + expr * vArg = get_eqc_value(argVec[i], vArgHasEqcValue); + if (vArg != argVec[i]) { + resolvedMap[argVec[i]] = vArg; + } + } + + if (resolvedMap.size() == 0) { + // no simplification possible + return node; + } else { + expr * resultAst = mk_string(""); + for (unsigned i = 0; i < argVec.size(); ++i) { + bool vArgHasEqcValue = false; + expr * vArg = get_eqc_value(argVec[i], vArgHasEqcValue); + resultAst = mk_concat(resultAst, vArg); + } + TRACE("str", tout << mk_ismt2_pp(node, m) << " is simplified to " << mk_ismt2_pp(resultAst, m) << std::endl;); + + if (in_same_eqc(node, resultAst)) { + TRACE("str", tout << "SKIP: both concats are already in the same equivalence class" << std::endl;); + } else { + expr_ref_vector items(m); + int pos = 0; + std::map::iterator itor = resolvedMap.begin(); + for (; itor != resolvedMap.end(); ++itor) { + items.push_back(ctx.mk_eq_atom(itor->first, itor->second)); + pos += 1; + } + expr_ref premise(mk_and(items), m); + expr_ref conclusion(ctx.mk_eq_atom(node, resultAst), m); + assert_implication(premise, conclusion); + } + return resultAst; + } + + } + + // Modified signature of Z3str2's inferLenConcat(). + // Returns true iff nLen can be inferred by this method + // (i.e. the equivalent of a len_exists flag in get_len_value()). + + bool theory_str::infer_len_concat(expr * n, rational & nLen) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + expr * arg0 = to_app(n)->get_arg(0); + expr * arg1 = to_app(n)->get_arg(1); + + rational arg0_len, arg1_len; + bool arg0_len_exists = get_len_value(arg0, arg0_len); + bool arg1_len_exists = get_len_value(arg1, arg1_len); + rational tmp_len; + bool nLen_exists = get_len_value(n, tmp_len); + + if (arg0_len_exists && arg1_len_exists && !nLen_exists) { + expr_ref_vector l_items(m); + // if (mk_strlen(arg0) != mk_int(arg0_len)) { + { + l_items.push_back(ctx.mk_eq_atom(mk_strlen(arg0), mk_int(arg0_len))); + } + + // if (mk_strlen(arg1) != mk_int(arg1_len)) { + { + l_items.push_back(ctx.mk_eq_atom(mk_strlen(arg1), mk_int(arg1_len))); + } + + expr_ref axl(m.mk_and(l_items.size(), l_items.c_ptr()), m); + rational nnLen = arg0_len + arg1_len; + expr_ref axr(ctx.mk_eq_atom(mk_strlen(n), mk_int(nnLen)), m); + TRACE("str", tout << "inferred (Length " << mk_pp(n, m) << ") = " << nnLen << std::endl;); + assert_implication(axl, axr); + nLen = nnLen; + return true; + } else { + return false; + } + } + + void theory_str::infer_len_concat_arg(expr * n, rational len) { + if (len.is_neg()) { + return; + } + + context & ctx = get_context(); + ast_manager & m = get_manager(); + + expr * arg0 = to_app(n)->get_arg(0); + expr * arg1 = to_app(n)->get_arg(1); + rational arg0_len, arg1_len; + bool arg0_len_exists = get_len_value(arg0, arg0_len); + bool arg1_len_exists = get_len_value(arg1, arg1_len); + + expr_ref_vector l_items(m); + expr_ref axr(m); + axr.reset(); + + // if (mk_length(t, n) != mk_int(ctx, len)) { + { + l_items.push_back(ctx.mk_eq_atom(mk_strlen(n), mk_int(len))); + } + + if (!arg0_len_exists && arg1_len_exists) { + //if (mk_length(t, arg1) != mk_int(ctx, arg1_len)) { + { + l_items.push_back(ctx.mk_eq_atom(mk_strlen(arg1), mk_int(arg1_len))); + } + rational arg0Len = len - arg1_len; + if (arg0Len.is_nonneg()) { + axr = ctx.mk_eq_atom(mk_strlen(arg0), mk_int(arg0Len)); + } else { + // could negate + } + } else if (arg0_len_exists && !arg1_len_exists) { + //if (mk_length(t, arg0) != mk_int(ctx, arg0_len)) { + { + l_items.push_back(ctx.mk_eq_atom(mk_strlen(arg0), mk_int(arg0_len))); + } + rational arg1Len = len - arg0_len; + if (arg1Len.is_nonneg()) { + axr = ctx.mk_eq_atom(mk_strlen(arg1), mk_int(arg1Len)); + } else { + // could negate + } + } else { + + } + + if (axr) { + expr_ref axl(m.mk_and(l_items.size(), l_items.c_ptr()), m); + assert_implication(axl, axr); + } + } + + void theory_str::infer_len_concat_equality(expr * nn1, expr * nn2) { + rational nnLen; + bool nnLen_exists = get_len_value(nn1, nnLen); + if (!nnLen_exists) { + nnLen_exists = get_len_value(nn2, nnLen); + } + + // case 1: + // Known: a1_arg0 and a1_arg1 + // Unknown: nn1 + + if (u.str.is_concat(to_app(nn1))) { + rational nn1ConcatLen; + bool nn1ConcatLen_exists = infer_len_concat(nn1, nn1ConcatLen); + if (nnLen_exists && nn1ConcatLen_exists) { + nnLen = nn1ConcatLen; + } + } + + // case 2: + // Known: a1_arg0 and a1_arg1 + // Unknown: nn1 + + if (u.str.is_concat(to_app(nn2))) { + rational nn2ConcatLen; + bool nn2ConcatLen_exists = infer_len_concat(nn2, nn2ConcatLen); + if (nnLen_exists && nn2ConcatLen_exists) { + nnLen = nn2ConcatLen; + } + } + + if (nnLen_exists) { + if (u.str.is_concat(to_app(nn1))) { + infer_len_concat_arg(nn1, nnLen); + } + if (u.str.is_concat(to_app(nn2))) { + infer_len_concat_arg(nn2, nnLen); + } + } + + /* + if (isConcatFunc(t, nn2)) { + int nn2ConcatLen = inferLenConcat(t, nn2); + if (nnLen == -1 && nn2ConcatLen != -1) + nnLen = nn2ConcatLen; + } + + if (nnLen != -1) { + if (isConcatFunc(t, nn1)) { + inferLenConcatArg(t, nn1, nnLen); + } + if (isConcatFunc(t, nn2)) { + inferLenConcatArg(t, nn2, nnLen); + } + } + */ + } + + void theory_str::add_theory_aware_branching_info(expr * term, double priority, lbool phase) { + context & ctx = get_context(); + ctx.internalize(term, false); + bool_var v = ctx.get_bool_var(term); + ctx.add_theory_aware_branching_info(v, priority, phase); + } + + void theory_str::generate_mutual_exclusion(expr_ref_vector & terms) { + context & ctx = get_context(); + // pull each literal out of the arrangement disjunction + literal_vector ls; + for (unsigned i = 0; i < terms.size(); ++i) { + expr * e = terms.get(i); + literal l = ctx.get_literal(e); + ls.push_back(l); + } + ctx.mk_th_case_split(ls.size(), ls.c_ptr()); + } + + void theory_str::print_cut_var(expr * node, std::ofstream & xout) { + ast_manager & m = get_manager(); + xout << "Cut info of " << mk_pp(node, m) << std::endl; + if (cut_var_map.contains(node)) { + if (!cut_var_map[node].empty()) { + xout << "[" << cut_var_map[node].top()->level << "] "; + std::map::iterator itor = cut_var_map[node].top()->vars.begin(); + for (; itor != cut_var_map[node].top()->vars.end(); ++itor) { + xout << mk_pp(itor->first, m) << ", "; + } + xout << std::endl; + } + } + } + + /* + * Handle two equivalent Concats. + */ + void theory_str::simplify_concat_equality(expr * nn1, expr * nn2) { + ast_manager & m = get_manager(); + context & ctx = get_context(); + + app * a_nn1 = to_app(nn1); + SASSERT(a_nn1->get_num_args() == 2); + app * a_nn2 = to_app(nn2); + SASSERT(a_nn2->get_num_args() == 2); + + expr * a1_arg0 = a_nn1->get_arg(0); + expr * a1_arg1 = a_nn1->get_arg(1); + expr * a2_arg0 = a_nn2->get_arg(0); + expr * a2_arg1 = a_nn2->get_arg(1); + + rational a1_arg0_len, a1_arg1_len, a2_arg0_len, a2_arg1_len; + + bool a1_arg0_len_exists = get_len_value(a1_arg0, a1_arg0_len); + bool a1_arg1_len_exists = get_len_value(a1_arg1, a1_arg1_len); + bool a2_arg0_len_exists = get_len_value(a2_arg0, a2_arg0_len); + bool a2_arg1_len_exists = get_len_value(a2_arg1, a2_arg1_len); + + TRACE("str", tout << "nn1 = " << mk_ismt2_pp(nn1, m) << std::endl + << "nn2 = " << mk_ismt2_pp(nn2, m) << std::endl;); + + TRACE("str", tout + << "len(" << mk_pp(a1_arg0, m) << ") = " << (a1_arg0_len_exists ? a1_arg0_len.to_string() : "?") << std::endl + << "len(" << mk_pp(a1_arg1, m) << ") = " << (a1_arg1_len_exists ? a1_arg1_len.to_string() : "?") << std::endl + << "len(" << mk_pp(a2_arg0, m) << ") = " << (a2_arg0_len_exists ? a2_arg0_len.to_string() : "?") << std::endl + << "len(" << mk_pp(a2_arg1, m) << ") = " << (a2_arg1_len_exists ? a2_arg1_len.to_string() : "?") << std::endl + << std::endl;); + + infer_len_concat_equality(nn1, nn2); + + if (a1_arg0 == a2_arg0) { + if (!in_same_eqc(a1_arg1, a2_arg1)) { + expr_ref premise(ctx.mk_eq_atom(nn1, nn2), m); + expr_ref eq1(ctx.mk_eq_atom(a1_arg1, a2_arg1), m); + expr_ref eq2(ctx.mk_eq_atom(mk_strlen(a1_arg1), mk_strlen(a2_arg1)), m); + expr_ref conclusion(m.mk_and(eq1, eq2), m); + assert_implication(premise, conclusion); + } + TRACE("str", tout << "SKIP: a1_arg0 == a2_arg0" << std::endl;); + return; + } + + if (a1_arg1 == a2_arg1) { + if (!in_same_eqc(a1_arg0, a2_arg0)) { + expr_ref premise(ctx.mk_eq_atom(nn1, nn2), m); + expr_ref eq1(ctx.mk_eq_atom(a1_arg0, a2_arg0), m); + expr_ref eq2(ctx.mk_eq_atom(mk_strlen(a1_arg0), mk_strlen(a2_arg0)), m); + expr_ref conclusion(m.mk_and(eq1, eq2), m); + assert_implication(premise, conclusion); + } + TRACE("str", tout << "SKIP: a1_arg1 == a2_arg1" << std::endl;); + return; + } + + // quick path + + if (in_same_eqc(a1_arg0, a2_arg0)) { + if (in_same_eqc(a1_arg1, a2_arg1)) { + TRACE("str", tout << "SKIP: a1_arg0 =~ a2_arg0 and a1_arg1 =~ a2_arg1" << std::endl;); + return; + } else { + TRACE("str", tout << "quick path 1-1: a1_arg0 =~ a2_arg0" << std::endl;); + expr_ref premise(m.mk_and(ctx.mk_eq_atom(nn1, nn2), ctx.mk_eq_atom(a1_arg0, a2_arg0)), m); + expr_ref conclusion(m.mk_and(ctx.mk_eq_atom(a1_arg1, a2_arg1), ctx.mk_eq_atom(mk_strlen(a1_arg1), mk_strlen(a2_arg1))), m); + assert_implication(premise, conclusion); + return; + } + } else { + if (in_same_eqc(a1_arg1, a2_arg1)) { + TRACE("str", tout << "quick path 1-2: a1_arg1 =~ a2_arg1" << std::endl;); + expr_ref premise(m.mk_and(ctx.mk_eq_atom(nn1, nn2), ctx.mk_eq_atom(a1_arg1, a2_arg1)), m); + expr_ref conclusion(m.mk_and(ctx.mk_eq_atom(a1_arg0, a2_arg0), ctx.mk_eq_atom(mk_strlen(a1_arg0), mk_strlen(a2_arg0))), m); + assert_implication(premise, conclusion); + return; + } + } + + // quick path 2-1 + if (a1_arg0_len_exists && a2_arg0_len_exists && a1_arg0_len == a2_arg0_len) { + if (!in_same_eqc(a1_arg0, a2_arg0)) { + TRACE("str", tout << "quick path 2-1: len(nn1.arg0) == len(nn2.arg0)" << std::endl;); + expr_ref ax_l1(ctx.mk_eq_atom(nn1, nn2), m); + expr_ref ax_l2(ctx.mk_eq_atom(mk_strlen(a1_arg0), mk_strlen(a2_arg0)), m); + expr_ref ax_r1(ctx.mk_eq_atom(a1_arg0, a2_arg0), m); + expr_ref ax_r2(ctx.mk_eq_atom(a1_arg1, a2_arg1), m); + + expr_ref premise(m.mk_and(ax_l1, ax_l2), m); + expr_ref conclusion(m.mk_and(ax_r1, ax_r2), m); + + assert_implication(premise, conclusion); + + if (opt_NoQuickReturn_IntegerTheory) { + TRACE("str", tout << "bypassing quick return from the end of this case" << std::endl;); + } else { + return; + } + } + } + + if (a1_arg1_len_exists && a2_arg1_len_exists && a1_arg1_len == a2_arg1_len) { + if (!in_same_eqc(a1_arg1, a2_arg1)) { + TRACE("str", tout << "quick path 2-2: len(nn1.arg1) == len(nn2.arg1)" << std::endl;); + expr_ref ax_l1(ctx.mk_eq_atom(nn1, nn2), m); + expr_ref ax_l2(ctx.mk_eq_atom(mk_strlen(a1_arg1), mk_strlen(a2_arg1)), m); + expr_ref ax_r1(ctx.mk_eq_atom(a1_arg0, a2_arg0), m); + expr_ref ax_r2(ctx.mk_eq_atom(a1_arg1, a2_arg1), m); + + expr_ref premise(m.mk_and(ax_l1, ax_l2), m); + expr_ref conclusion(m.mk_and(ax_r1, ax_r2), m); + + assert_implication(premise, conclusion); + if (opt_NoQuickReturn_IntegerTheory) { + TRACE("str", tout << "bypassing quick return from the end of this case" << std::endl;); + } else { + return; + } + } + } + + expr_ref new_nn1(simplify_concat(nn1), m); + expr_ref new_nn2(simplify_concat(nn2), m); + app * a_new_nn1 = to_app(new_nn1); + app * a_new_nn2 = to_app(new_nn2); + + TRACE("str", tout << "new_nn1 = " << mk_ismt2_pp(new_nn1, m) << std::endl + << "new_nn2 = " << mk_ismt2_pp(new_nn2, m) << std::endl;); + + if (new_nn1 == new_nn2) { + TRACE("str", tout << "equal concats, return" << std::endl;); + return; + } + + if (!can_two_nodes_eq(new_nn1, new_nn2)) { + expr_ref detected(m.mk_not(ctx.mk_eq_atom(new_nn1, new_nn2)), m); + TRACE("str", tout << "inconsistency detected: " << mk_ismt2_pp(detected, m) << std::endl;); + assert_axiom(detected); + return; + } + + // check whether new_nn1 and new_nn2 are still concats + + bool n1IsConcat = u.str.is_concat(a_new_nn1); + bool n2IsConcat = u.str.is_concat(a_new_nn2); + if (!n1IsConcat && n2IsConcat) { + TRACE("str", tout << "nn1_new is not a concat" << std::endl;); + if (u.str.is_string(a_new_nn1)) { + simplify_parent(new_nn2, new_nn1); + } + return; + } else if (n1IsConcat && !n2IsConcat) { + TRACE("str", tout << "nn2_new is not a concat" << std::endl;); + if (u.str.is_string(a_new_nn2)) { + simplify_parent(new_nn1, new_nn2); + } + return; + } else if (!n1IsConcat && !n2IsConcat) { + // normally this should never happen, because group_terms_by_eqc() should have pre-simplified + // as much as possible. however, we make a defensive check here just in case + TRACE("str", tout << "WARNING: nn1_new and nn2_new both simplify to non-concat terms" << std::endl;); + return; + } + + expr * v1_arg0 = a_new_nn1->get_arg(0); + expr * v1_arg1 = a_new_nn1->get_arg(1); + expr * v2_arg0 = a_new_nn2->get_arg(0); + expr * v2_arg1 = a_new_nn2->get_arg(1); + + if (!in_same_eqc(new_nn1, new_nn2) && (nn1 != new_nn1 || nn2 != new_nn2)) { + int ii4 = 0; + expr* item[3]; + if (nn1 != new_nn1) { + item[ii4++] = ctx.mk_eq_atom(nn1, new_nn1); + } + if (nn2 != new_nn2) { + item[ii4++] = ctx.mk_eq_atom(nn2, new_nn2); + } + item[ii4++] = ctx.mk_eq_atom(nn1, nn2); + expr_ref premise(m.mk_and(ii4, item), m); + expr_ref conclusion(ctx.mk_eq_atom(new_nn1, new_nn2), m); + assert_implication(premise, conclusion); + } + + // start to split both concats + check_and_init_cut_var(v1_arg0); + check_and_init_cut_var(v1_arg1); + check_and_init_cut_var(v2_arg0); + check_and_init_cut_var(v2_arg1); + + //************************************************************* + // case 1: concat(x, y) = concat(m, n) + //************************************************************* + if (is_concat_eq_type1(new_nn1, new_nn2)) { + process_concat_eq_type1(new_nn1, new_nn2); + return; + } + + //************************************************************* + // case 2: concat(x, y) = concat(m, "str") + //************************************************************* + if (is_concat_eq_type2(new_nn1, new_nn2)) { + process_concat_eq_type2(new_nn1, new_nn2); + return; + } + + //************************************************************* + // case 3: concat(x, y) = concat("str", n) + //************************************************************* + if (is_concat_eq_type3(new_nn1, new_nn2)) { + process_concat_eq_type3(new_nn1, new_nn2); + return; + } + + //************************************************************* + // case 4: concat("str1", y) = concat("str2", n) + //************************************************************* + if (is_concat_eq_type4(new_nn1, new_nn2)) { + process_concat_eq_type4(new_nn1, new_nn2); + return; + } + + //************************************************************* + // case 5: concat(x, "str1") = concat(m, "str2") + //************************************************************* + if (is_concat_eq_type5(new_nn1, new_nn2)) { + process_concat_eq_type5(new_nn1, new_nn2); + return; + } + //************************************************************* + // case 6: concat("str1", y) = concat(m, "str2") + //************************************************************* + if (is_concat_eq_type6(new_nn1, new_nn2)) { + process_concat_eq_type6(new_nn1, new_nn2); + return; + } + + } + + /* + * Returns true if attempting to process a concat equality between lhs and rhs + * will result in overlapping variables (false otherwise). + */ + bool theory_str::will_result_in_overlap(expr * lhs, expr * rhs) { + ast_manager & m = get_manager(); + + expr_ref new_nn1(simplify_concat(lhs), m); + expr_ref new_nn2(simplify_concat(rhs), m); + app * a_new_nn1 = to_app(new_nn1); + app * a_new_nn2 = to_app(new_nn2); + + bool n1IsConcat = u.str.is_concat(a_new_nn1); + bool n2IsConcat = u.str.is_concat(a_new_nn2); + if (!n1IsConcat && !n2IsConcat) { + // we simplified both sides to non-concat expressions... + return false; + } + + expr * v1_arg0 = a_new_nn1->get_arg(0); + expr * v1_arg1 = a_new_nn1->get_arg(1); + expr * v2_arg0 = a_new_nn2->get_arg(0); + expr * v2_arg1 = a_new_nn2->get_arg(1); + + TRACE("str", tout << "checking whether " << mk_pp(new_nn1, m) << " and " << mk_pp(new_nn1, m) << " might overlap." << std::endl;); + + check_and_init_cut_var(v1_arg0); + check_and_init_cut_var(v1_arg1); + check_and_init_cut_var(v2_arg0); + check_and_init_cut_var(v2_arg1); + + //************************************************************* + // case 1: concat(x, y) = concat(m, n) + //************************************************************* + if (is_concat_eq_type1(new_nn1, new_nn2)) { + TRACE("str", tout << "Type 1 check." << std::endl;); + expr * x = to_app(new_nn1)->get_arg(0); + expr * y = to_app(new_nn1)->get_arg(1); + expr * m = to_app(new_nn2)->get_arg(0); + expr * n = to_app(new_nn2)->get_arg(1); + + if (has_self_cut(m, y)) { + TRACE("str", tout << "Possible overlap found" << std::endl; print_cut_var(m, tout); print_cut_var(y, tout);); + return true; + } else if (has_self_cut(x, n)) { + TRACE("str", tout << "Possible overlap found" << std::endl; print_cut_var(x, tout); print_cut_var(n, tout);); + return true; + } else { + return false; + } + } + + //************************************************************* + // case 2: concat(x, y) = concat(m, "str") + //************************************************************* + if (is_concat_eq_type2(new_nn1, new_nn2)) { + + expr * y = NULL; + expr * m = NULL; + expr * v1_arg0 = to_app(new_nn1)->get_arg(0); + expr * v1_arg1 = to_app(new_nn1)->get_arg(1); + expr * v2_arg0 = to_app(new_nn2)->get_arg(0); + expr * v2_arg1 = to_app(new_nn2)->get_arg(1); + + if (u.str.is_string(v1_arg1) && !u.str.is_string(v2_arg1)) { + m = v1_arg0; + y = v2_arg1; + } else { + m = v2_arg0; + y = v1_arg1; + } + + if (has_self_cut(m, y)) { + TRACE("str", tout << "Possible overlap found" << std::endl; print_cut_var(m, tout); print_cut_var(y, tout);); + return true; + } else { + return false; + } + } + + //************************************************************* + // case 3: concat(x, y) = concat("str", n) + //************************************************************* + if (is_concat_eq_type3(new_nn1, new_nn2)) { + expr * v1_arg0 = to_app(new_nn1)->get_arg(0); + expr * v1_arg1 = to_app(new_nn1)->get_arg(1); + expr * v2_arg0 = to_app(new_nn2)->get_arg(0); + expr * v2_arg1 = to_app(new_nn2)->get_arg(1); + + expr * x = NULL; + expr * n = NULL; + + if (u.str.is_string(v1_arg0) && !u.str.is_string(v2_arg0)) { + n = v1_arg1; + x = v2_arg0; + } else { + n = v2_arg1; + x = v1_arg0; + } + if (has_self_cut(x, n)) { + TRACE("str", tout << "Possible overlap found" << std::endl; print_cut_var(x, tout); print_cut_var(n, tout);); + return true; + } else { + return false; + } + } + + //************************************************************* + // case 4: concat("str1", y) = concat("str2", n) + //************************************************************* + if (is_concat_eq_type4(new_nn1, new_nn2)) { + // This case can never result in an overlap. + return false; + } + + //************************************************************* + // case 5: concat(x, "str1") = concat(m, "str2") + //************************************************************* + if (is_concat_eq_type5(new_nn1, new_nn2)) { + // This case can never result in an overlap. + return false; + } + //************************************************************* + // case 6: concat("str1", y) = concat(m, "str2") + //************************************************************* + if (is_concat_eq_type6(new_nn1, new_nn2)) { + expr * v1_arg0 = to_app(new_nn1)->get_arg(0); + expr * v1_arg1 = to_app(new_nn1)->get_arg(1); + expr * v2_arg0 = to_app(new_nn2)->get_arg(0); + expr * v2_arg1 = to_app(new_nn2)->get_arg(1); + + expr * y = NULL; + expr * m = NULL; + + if (u.str.is_string(v1_arg0)) { + y = v1_arg1; + m = v2_arg0; + } else { + y = v2_arg1; + m = v1_arg0; + } + if (has_self_cut(m, y)) { + TRACE("str", tout << "Possible overlap found" << std::endl; print_cut_var(m, tout); print_cut_var(y, tout);); + return true; + } else { + return false; + } + } + + TRACE("str", tout << "warning: unrecognized concat case" << std::endl;); + return false; + } + + /************************************************************* + * Type 1: concat(x, y) = concat(m, n) + * x, y, m and n all variables + *************************************************************/ + bool theory_str::is_concat_eq_type1(expr * concatAst1, expr * concatAst2) { + expr * x = to_app(concatAst1)->get_arg(0); + expr * y = to_app(concatAst1)->get_arg(1); + expr * m = to_app(concatAst2)->get_arg(0); + expr * n = to_app(concatAst2)->get_arg(1); + + if (!u.str.is_string(x) && !u.str.is_string(y) && !u.str.is_string(m) && !u.str.is_string(n)) { + return true; + } else { + return false; + } + } + + void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { + ast_manager & mgr = get_manager(); + context & ctx = get_context(); + + bool overlapAssumptionUsed = false; + + TRACE("str", tout << "process_concat_eq TYPE 1" << std::endl + << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl + << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; + ); + + if (!u.str.is_concat(to_app(concatAst1))) { + TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;); + return; + } + if (!u.str.is_concat(to_app(concatAst2))) { + TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;); + return; + } + expr * x = to_app(concatAst1)->get_arg(0); + expr * y = to_app(concatAst1)->get_arg(1); + expr * m = to_app(concatAst2)->get_arg(0); + expr * n = to_app(concatAst2)->get_arg(1); + + rational x_len, y_len, m_len, n_len; + bool x_len_exists = get_len_value(x, x_len); + bool y_len_exists = get_len_value(y, y_len); + bool m_len_exists = get_len_value(m, m_len); + bool n_len_exists = get_len_value(n, n_len); + + int splitType = -1; + if (x_len_exists && m_len_exists) { + TRACE("str", tout << "length values found: x/m" << std::endl;); + if (x_len < m_len) { + splitType = 0; + } else if (x_len == m_len) { + splitType = 1; + } else { + splitType = 2; + } + } + + if (splitType == -1 && y_len_exists && n_len_exists) { + TRACE("str", tout << "length values found: y/n" << std::endl;); + if (y_len > n_len) { + splitType = 0; + } else if (y_len == n_len) { + splitType = 1; + } else { + splitType = 2; + } + } + + TRACE("str", tout + << "len(x) = " << (x_len_exists ? x_len.to_string() : "?") << std::endl + << "len(y) = " << (y_len_exists ? y_len.to_string() : "?") << std::endl + << "len(m) = " << (m_len_exists ? m_len.to_string() : "?") << std::endl + << "len(n) = " << (n_len_exists ? n_len.to_string() : "?") << std::endl + << "split type " << splitType << std::endl; + ); + + expr * t1 = NULL; + expr * t2 = NULL; + expr * xorFlag = NULL; + + std::pair key1(concatAst1, concatAst2); + std::pair key2(concatAst2, concatAst1); + + // check the entries in this map to make sure they're still in scope + // before we use them. + + std::map, std::map >::iterator entry1 = varForBreakConcat.find(key1); + std::map, std::map >::iterator entry2 = varForBreakConcat.find(key2); + + bool entry1InScope; + if (entry1 == varForBreakConcat.end()) { + entry1InScope = false; + } else { + if (internal_variable_set.find((entry1->second)[0]) == internal_variable_set.end() + || internal_variable_set.find((entry1->second)[1]) == internal_variable_set.end() + /*|| internal_variable_set.find((entry1->second)[2]) == internal_variable_set.end() */) { + entry1InScope = false; + } else { + entry1InScope = true; + } + } + + bool entry2InScope; + if (entry2 == varForBreakConcat.end()) { + entry2InScope = false; + } else { + if (internal_variable_set.find((entry2->second)[0]) == internal_variable_set.end() + || internal_variable_set.find((entry2->second)[1]) == internal_variable_set.end() + /* || internal_variable_set.find((entry2->second)[2]) == internal_variable_set.end() */) { + entry2InScope = false; + } else { + entry2InScope = true; + } + } + + TRACE("str", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl + << "entry 2 " << (entry2InScope ? "in scope" : "not in scope") << std::endl;); + + if (!entry1InScope && !entry2InScope) { + t1 = mk_nonempty_str_var(); + t2 = mk_nonempty_str_var(); + xorFlag = mk_internal_xor_var(); + check_and_init_cut_var(t1); + check_and_init_cut_var(t2); + varForBreakConcat[key1][0] = t1; + varForBreakConcat[key1][1] = t2; + varForBreakConcat[key1][2] = xorFlag; + } else { + // match found + if (entry1InScope) { + t1 = varForBreakConcat[key1][0]; + t2 = varForBreakConcat[key1][1]; + xorFlag = varForBreakConcat[key1][2]; + } else { + t1 = varForBreakConcat[key2][0]; + t2 = varForBreakConcat[key2][1]; + xorFlag = varForBreakConcat[key2][2]; + } + refresh_theory_var(t1); + add_nonempty_constraint(t1); + refresh_theory_var(t2); + add_nonempty_constraint(t2); + } + + // For split types 0 through 2, we can get away with providing + // fewer split options since more length information is available. + if (splitType == 0) { + //-------------------------------------- + // Type 0: M cuts Y. + // len(x) < len(m) || len(y) > len(n) + //-------------------------------------- + expr_ref_vector ax_l_items(mgr); + expr_ref_vector ax_r_items(mgr); + + ax_l_items.push_back(ctx.mk_eq_atom(concatAst1, concatAst2)); + + expr_ref x_t1(mk_concat(x, t1), mgr); + expr_ref t1_n(mk_concat(t1, n), mgr); + + ax_r_items.push_back(ctx.mk_eq_atom(m, x_t1)); + ax_r_items.push_back(ctx.mk_eq_atom(y, t1_n)); + + if (m_len_exists && x_len_exists) { + ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len))); + ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len))); + rational m_sub_x = m_len - x_len; + ax_r_items.push_back(ctx.mk_eq_atom(mk_strlen(t1), mk_int(m_sub_x))); + } else { + ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len))); + ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(n), mk_int(n_len))); + rational y_sub_n = y_len - n_len; + ax_r_items.push_back(ctx.mk_eq_atom(mk_strlen(t1), mk_int(y_sub_n))); + } + + expr_ref ax_l(mk_and(ax_l_items), mgr); + expr_ref ax_r(mk_and(ax_r_items), mgr); + + if (!has_self_cut(m, y)) { + // Cut Info + add_cut_info_merge(t1, sLevel, m); + add_cut_info_merge(t1, sLevel, y); + + if (m_params.m_StrongArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ax_l, ax_r); + } + } else { + loopDetected = true; + if (m_params.m_FiniteOverlapModels) { + expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); + assert_implication(ax_l, tester); + add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); + } else { + TRACE("str", tout << "AVOID LOOP: SKIPPED" << std::endl;); + TRACE("str", {print_cut_var(m, tout); print_cut_var(y, tout);}); + + if (!overlapAssumptionUsed) { + overlapAssumptionUsed = true; + assert_implication(ax_l, m_theoryStrOverlapAssumption_term); + } + } + } + } else if (splitType == 1) { + // Type 1: + // len(x) = len(m) || len(y) = len(n) + expr_ref ax_l1(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); + expr_ref ax_l2(mgr.mk_or(ctx.mk_eq_atom(mk_strlen(x), mk_strlen(m)), ctx.mk_eq_atom(mk_strlen(y), mk_strlen(n))), mgr); + expr_ref ax_l(mgr.mk_and(ax_l1, ax_l2), mgr); + expr_ref ax_r(mgr.mk_and(ctx.mk_eq_atom(x,m), ctx.mk_eq_atom(y,n)), mgr); + assert_implication(ax_l, ax_r); + } else if (splitType == 2) { + // Type 2: X cuts N. + // len(x) > len(m) || len(y) < len(n) + expr_ref m_t2(mk_concat(m, t2), mgr); + expr_ref t2_y(mk_concat(t2, y), mgr); + + expr_ref_vector ax_l_items(mgr); + ax_l_items.push_back(ctx.mk_eq_atom(concatAst1, concatAst2)); + + expr_ref_vector ax_r_items(mgr); + ax_r_items.push_back(ctx.mk_eq_atom(x, m_t2)); + ax_r_items.push_back(ctx.mk_eq_atom(t2_y, n)); + + if (m_len_exists && x_len_exists) { + ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len))); + ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len))); + rational x_sub_m = x_len - m_len; + ax_r_items.push_back(ctx.mk_eq_atom(mk_strlen(t2), mk_int(x_sub_m))); + } else { + ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len))); + ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(n), mk_int(n_len))); + rational n_sub_y = n_len - y_len; + ax_r_items.push_back(ctx.mk_eq_atom(mk_strlen(t2), mk_int(n_sub_y))); + } + + expr_ref ax_l(mk_and(ax_l_items), mgr); + expr_ref ax_r(mk_and(ax_r_items), mgr); + + if (!has_self_cut(x, n)) { + // Cut Info + add_cut_info_merge(t2, sLevel, x); + add_cut_info_merge(t2, sLevel, n); + + if (m_params.m_StrongArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ax_l, ax_r); + } + } else { + loopDetected = true; + if (m_params.m_FiniteOverlapModels) { + expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); + assert_implication(ax_l, tester); + add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); + } else { + TRACE("str", tout << "AVOID LOOP: SKIPPED" << std::endl;); + TRACE("str", {print_cut_var(m, tout); print_cut_var(y, tout);}); + + if (!overlapAssumptionUsed) { + overlapAssumptionUsed = true; + assert_implication(ax_l, m_theoryStrOverlapAssumption_term); + } + } + } + } else if (splitType == -1) { + // Here we don't really have a choice. We have no length information at all... + + // This vector will eventually contain one term for each possible arrangement we explore. + expr_ref_vector arrangement_disjunction(mgr); + + // break option 1: m cuts y + // len(x) < len(m) || len(y) > len(n) + if (!avoidLoopCut || !has_self_cut(m, y)) { + expr_ref_vector and_item(mgr); + // break down option 1-1 + expr_ref x_t1(mk_concat(x, t1), mgr); + expr_ref t1_n(mk_concat(t1, n), mgr); + + and_item.push_back(ctx.mk_eq_atom(m, x_t1)); + and_item.push_back(ctx.mk_eq_atom(y, t1_n)); + + expr_ref x_plus_t1(m_autil.mk_add(mk_strlen(x), mk_strlen(t1)), mgr); + and_item.push_back(ctx.mk_eq_atom(mk_strlen(m), x_plus_t1)); + // These were crashing the solver because the integer theory + // expects a constant on the right-hand side. + // The things we want to assert here are len(m) > len(x) and len(y) > len(n). + // We rewrite A > B as A-B > 0 and then as not(A-B <= 0), + // and then, *because we aren't allowed to use subtraction*, + // as not(A + -1*B <= 0) + and_item.push_back( + mgr.mk_not(m_autil.mk_le( + m_autil.mk_add(mk_strlen(m), m_autil.mk_mul(mk_int(-1), mk_strlen(x))), + mk_int(0))) ); + and_item.push_back( + mgr.mk_not(m_autil.mk_le( + m_autil.mk_add(mk_strlen(y),m_autil.mk_mul(mk_int(-1), mk_strlen(n))), + mk_int(0))) ); + + expr_ref option1(mk_and(and_item), mgr); + arrangement_disjunction.push_back(option1); + add_theory_aware_branching_info(option1, 0.1, l_true); + + add_cut_info_merge(t1, ctx.get_scope_level(), m); + add_cut_info_merge(t1, ctx.get_scope_level(), y); + } else { + loopDetected = true; + if (m_params.m_FiniteOverlapModels) { + expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); + arrangement_disjunction.push_back(tester); + add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); + } else { + TRACE("str", tout << "AVOID LOOP: SKIPPED" << std::endl;); + TRACE("str", {print_cut_var(m, tout); print_cut_var(y, tout);}); + + if (!overlapAssumptionUsed) { + overlapAssumptionUsed = true; + arrangement_disjunction.push_back(m_theoryStrOverlapAssumption_term); + } + } + } + + // break option 2: + // x = m . t2 + // n = t2 . y + if (!avoidLoopCut || !has_self_cut(x, n)) { + expr_ref_vector and_item(mgr); + // break down option 1-2 + expr_ref m_t2(mk_concat(m, t2), mgr); + expr_ref t2_y(mk_concat(t2, y), mgr); + + and_item.push_back(ctx.mk_eq_atom(x, m_t2)); + and_item.push_back(ctx.mk_eq_atom(n, t2_y)); + + + expr_ref m_plus_t2(m_autil.mk_add(mk_strlen(m), mk_strlen(t2)), mgr); + + and_item.push_back(ctx.mk_eq_atom(mk_strlen(x), m_plus_t2)); + // want len(x) > len(m) and len(n) > len(y) + and_item.push_back( + mgr.mk_not(m_autil.mk_le( + m_autil.mk_add(mk_strlen(x), m_autil.mk_mul(mk_int(-1), mk_strlen(m))), + mk_int(0))) ); + and_item.push_back( + mgr.mk_not(m_autil.mk_le( + m_autil.mk_add(mk_strlen(n), m_autil.mk_mul(mk_int(-1), mk_strlen(y))), + mk_int(0))) ); + + expr_ref option2(mk_and(and_item), mgr); + arrangement_disjunction.push_back(option2); + add_theory_aware_branching_info(option2, 0.1, l_true); + + add_cut_info_merge(t2, ctx.get_scope_level(), x); + add_cut_info_merge(t2, ctx.get_scope_level(), n); + } else { + loopDetected = true; + if (m_params.m_FiniteOverlapModels) { + expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); + arrangement_disjunction.push_back(tester); + add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); + } else { + TRACE("str", tout << "AVOID LOOP: SKIPPED" << std::endl;); + TRACE("str", {print_cut_var(x, tout); print_cut_var(n, tout);}); + + if (!overlapAssumptionUsed) { + overlapAssumptionUsed = true; + arrangement_disjunction.push_back(m_theoryStrOverlapAssumption_term); + } + } + } + + // option 3: + // x = m, y = n + if (can_two_nodes_eq(x, m) && can_two_nodes_eq(y, n)) { + expr_ref_vector and_item(mgr); + + and_item.push_back(ctx.mk_eq_atom(x, m)); + and_item.push_back(ctx.mk_eq_atom(y, n)); + and_item.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_strlen(m))); + and_item.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_strlen(n))); + + expr_ref option3(mk_and(and_item), mgr); + arrangement_disjunction.push_back(option3); + // prioritize this case, it is easier + add_theory_aware_branching_info(option3, 0.5, l_true); + } + + if (!arrangement_disjunction.empty()) { + expr_ref premise(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); + expr_ref conclusion(mk_or(arrangement_disjunction), mgr); + if (m_params.m_StrongArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom(premise, conclusion), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(premise, conclusion); + } + // assert mutual exclusion between each branch of the arrangement + generate_mutual_exclusion(arrangement_disjunction); + } else { + TRACE("str", tout << "STOP: no split option found for two EQ concats." << std::endl;); + } + } // (splitType == -1) + } + + /************************************************************* + * Type 2: concat(x, y) = concat(m, "str") + *************************************************************/ + bool theory_str::is_concat_eq_type2(expr * concatAst1, expr * concatAst2) { + expr * v1_arg0 = to_app(concatAst1)->get_arg(0); + expr * v1_arg1 = to_app(concatAst1)->get_arg(1); + expr * v2_arg0 = to_app(concatAst2)->get_arg(0); + expr * v2_arg1 = to_app(concatAst2)->get_arg(1); + + if ((!u.str.is_string(v1_arg0)) && u.str.is_string(v1_arg1) + && (!u.str.is_string(v2_arg0)) && (!u.str.is_string(v2_arg1))) { + return true; + } else if ((!u.str.is_string(v2_arg0)) && u.str.is_string(v2_arg1) + && (!u.str.is_string(v1_arg0)) && (!u.str.is_string(v1_arg1))) { + return true; + } else { + return false; + } + } + + void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { + ast_manager & mgr = get_manager(); + context & ctx = get_context(); + + bool overlapAssumptionUsed = false; + + TRACE("str", tout << "process_concat_eq TYPE 2" << std::endl + << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl + << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; + ); + + if (!u.str.is_concat(to_app(concatAst1))) { + TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;); + return; + } + if (!u.str.is_concat(to_app(concatAst2))) { + TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;); + return; + } + + expr * x = NULL; + expr * y = NULL; + expr * strAst = NULL; + expr * m = NULL; + + expr * v1_arg0 = to_app(concatAst1)->get_arg(0); + expr * v1_arg1 = to_app(concatAst1)->get_arg(1); + expr * v2_arg0 = to_app(concatAst2)->get_arg(0); + expr * v2_arg1 = to_app(concatAst2)->get_arg(1); + + if (u.str.is_string(v1_arg1) && !u.str.is_string(v2_arg1)) { + m = v1_arg0; + strAst = v1_arg1; + x = v2_arg0; + y = v2_arg1; + } else { + m = v2_arg0; + strAst = v2_arg1; + x = v1_arg0; + y = v1_arg1; + } + + zstring strValue; + u.str.is_string(strAst, strValue); + + rational x_len, y_len, m_len, str_len; + bool x_len_exists = get_len_value(x, x_len); + bool y_len_exists = get_len_value(y, y_len); + bool m_len_exists = get_len_value(m, m_len); + bool str_len_exists = true; + str_len = rational(strValue.length()); + + // setup + + expr * xorFlag = NULL; + expr * temp1 = NULL; + std::pair key1(concatAst1, concatAst2); + std::pair key2(concatAst2, concatAst1); + + // check the entries in this map to make sure they're still in scope + // before we use them. + + std::map, std::map >::iterator entry1 = varForBreakConcat.find(key1); + std::map, std::map >::iterator entry2 = varForBreakConcat.find(key2); + + // prevent checking scope for the XOR term, as it's always in the same scope as the split var + + bool entry1InScope; + if (entry1 == varForBreakConcat.end()) { + entry1InScope = false; + } else { + if (internal_variable_set.find((entry1->second)[0]) == internal_variable_set.end() + /*|| internal_variable_set.find((entry1->second)[1]) == internal_variable_set.end()*/ + ) { + entry1InScope = false; + } else { + entry1InScope = true; + } + } + + bool entry2InScope; + if (entry2 == varForBreakConcat.end()) { + entry2InScope = false; + } else { + if (internal_variable_set.find((entry2->second)[0]) == internal_variable_set.end() + /*|| internal_variable_set.find((entry2->second)[1]) == internal_variable_set.end()*/ + ) { + entry2InScope = false; + } else { + entry2InScope = true; + } + } + + TRACE("str", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl + << "entry 2 " << (entry2InScope ? "in scope" : "not in scope") << std::endl;); + + + if (!entry1InScope && !entry2InScope) { + temp1 = mk_nonempty_str_var(); + xorFlag = mk_internal_xor_var(); + varForBreakConcat[key1][0] = temp1; + varForBreakConcat[key1][1] = xorFlag; + } else { + if (entry1InScope) { + temp1 = varForBreakConcat[key1][0]; + xorFlag = varForBreakConcat[key1][1]; + } else if (entry2InScope) { + temp1 = varForBreakConcat[key2][0]; + xorFlag = varForBreakConcat[key2][1]; + } + refresh_theory_var(temp1); + add_nonempty_constraint(temp1); + } + + int splitType = -1; + if (x_len_exists && m_len_exists) { + if (x_len < m_len) + splitType = 0; + else if (x_len == m_len) + splitType = 1; + else + splitType = 2; + } + if (splitType == -1 && y_len_exists && str_len_exists) { + if (y_len > str_len) + splitType = 0; + else if (y_len == str_len) + splitType = 1; + else + splitType = 2; + } + + TRACE("str", tout << "Split type " << splitType << std::endl;); + + // Provide fewer split options when length information is available. + + if (splitType == 0) { + // M cuts Y + // | x | y | + // | m | str | + expr_ref temp1_strAst(mk_concat(temp1, strAst), mgr); + if (can_two_nodes_eq(y, temp1_strAst)) { + expr_ref_vector l_items(mgr); + l_items.push_back(ctx.mk_eq_atom(concatAst1, concatAst2)); + + expr_ref_vector r_items(mgr); + expr_ref x_temp1(mk_concat(x, temp1), mgr); + r_items.push_back(ctx.mk_eq_atom(m, x_temp1)); + r_items.push_back(ctx.mk_eq_atom(y, temp1_strAst)); + + if (x_len_exists && m_len_exists) { + l_items.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len))); + l_items.push_back(ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len))); + rational m_sub_x = (m_len - x_len); + r_items.push_back(ctx.mk_eq_atom(mk_strlen(temp1), mk_int(m_sub_x))); + } else { + l_items.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len))); + l_items.push_back(ctx.mk_eq_atom(mk_strlen(strAst), mk_int(str_len))); + rational y_sub_str = (y_len - str_len); + r_items.push_back(ctx.mk_eq_atom(mk_strlen(temp1), mk_int(y_sub_str))); + } + + expr_ref ax_l(mk_and(l_items), mgr); + expr_ref ax_r(mk_and(r_items), mgr); + + if (!avoidLoopCut || !(has_self_cut(m, y))) { + // break down option 2-1 + add_cut_info_merge(temp1, sLevel, y); + add_cut_info_merge(temp1, sLevel, m); + + if (m_params.m_StrongArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ax_l, ax_r); + } + } else { + loopDetected = true; + + if (m_params.m_FiniteOverlapModels) { + expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); + assert_implication(ax_l, tester); + add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); + } else { + TRACE("str", tout << "AVOID LOOP: SKIP" << std::endl;); + TRACE("str", {print_cut_var(m, tout); print_cut_var(y, tout);}); + + if (!overlapAssumptionUsed) { + overlapAssumptionUsed = true; + assert_implication(ax_l, m_theoryStrOverlapAssumption_term); + } + } + } + } + } else if (splitType == 1) { + // | x | y | + // | m | str | + expr_ref ax_l1(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); + expr_ref ax_l2(mgr.mk_or( + ctx.mk_eq_atom(mk_strlen(x), mk_strlen(m)), + ctx.mk_eq_atom(mk_strlen(y), mk_strlen(strAst))), mgr); + expr_ref ax_l(mgr.mk_and(ax_l1, ax_l2), mgr); + expr_ref ax_r(mgr.mk_and(ctx.mk_eq_atom(x, m), ctx.mk_eq_atom(y, strAst)), mgr); + assert_implication(ax_l, ax_r); + } else if (splitType == 2) { + // m cut y, + // | x | y | + // | m | str | + rational lenDelta; + expr_ref_vector l_items(mgr); + l_items.push_back(ctx.mk_eq_atom(concatAst1, concatAst2)); + if (x_len_exists && m_len_exists) { + l_items.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len))); + l_items.push_back(ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len))); + lenDelta = x_len - m_len; + } else { + l_items.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len))); + lenDelta = str_len - y_len; + } + TRACE("str", + tout + << "xLen? " << (x_len_exists ? "yes" : "no") << std::endl + << "mLen? " << (m_len_exists ? "yes" : "no") << std::endl + << "yLen? " << (y_len_exists ? "yes" : "no") << std::endl + << "xLen = " << x_len.to_string() << std::endl + << "yLen = " << y_len.to_string() << std::endl + << "mLen = " << m_len.to_string() << std::endl + << "strLen = " << str_len.to_string() << std::endl + << "lenDelta = " << lenDelta.to_string() << std::endl + << "strValue = \"" << strValue << "\" (len=" << strValue.length() << ")" << "\n" + ; + ); + + zstring part1Str = strValue.extract(0, lenDelta.get_unsigned()); + zstring part2Str = strValue.extract(lenDelta.get_unsigned(), strValue.length() - lenDelta.get_unsigned()); + + expr_ref prefixStr(mk_string(part1Str), mgr); + expr_ref x_concat(mk_concat(m, prefixStr), mgr); + expr_ref cropStr(mk_string(part2Str), mgr); + + if (can_two_nodes_eq(x, x_concat) && can_two_nodes_eq(y, cropStr)) { + expr_ref_vector r_items(mgr); + r_items.push_back(ctx.mk_eq_atom(x, x_concat)); + r_items.push_back(ctx.mk_eq_atom(y, cropStr)); + expr_ref ax_l(mk_and(l_items), mgr); + expr_ref ax_r(mk_and(r_items), mgr); + + if (m_params.m_StrongArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ax_l, ax_r); + } + } else { + // negate! It's impossible to split str with these lengths + TRACE("str", tout << "CONFLICT: Impossible to split str with these lengths." << std::endl;); + expr_ref ax_l(mk_and(l_items), mgr); + assert_axiom(mgr.mk_not(ax_l)); + } + } else { + // Split type -1: no idea about the length... + expr_ref_vector arrangement_disjunction(mgr); + + expr_ref temp1_strAst(mk_concat(temp1, strAst), mgr); + + // m cuts y + if (can_two_nodes_eq(y, temp1_strAst)) { + if (!avoidLoopCut || !has_self_cut(m, y)) { + // break down option 2-1 + expr_ref_vector and_item(mgr); + + expr_ref x_temp1(mk_concat(x, temp1), mgr); + and_item.push_back(ctx.mk_eq_atom(m, x_temp1)); + and_item.push_back(ctx.mk_eq_atom(y, temp1_strAst)); + + and_item.push_back(ctx.mk_eq_atom(mk_strlen(m), + m_autil.mk_add(mk_strlen(x), mk_strlen(temp1)))); + + expr_ref option1(mk_and(and_item), mgr); + arrangement_disjunction.push_back(option1); + add_theory_aware_branching_info(option1, 0.1, l_true); + add_cut_info_merge(temp1, ctx.get_scope_level(), y); + add_cut_info_merge(temp1, ctx.get_scope_level(), m); + } else { + loopDetected = true; + if (m_params.m_FiniteOverlapModels) { + expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); + arrangement_disjunction.push_back(tester); + add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); + } else { + TRACE("str", tout << "AVOID LOOP: SKIPPED" << std::endl;); + TRACE("str", {print_cut_var(m, tout); print_cut_var(y, tout);}); + + if (!overlapAssumptionUsed) { + overlapAssumptionUsed = true; + arrangement_disjunction.push_back(m_theoryStrOverlapAssumption_term); + } + } + } + } + + for (unsigned int i = 0; i <= strValue.length(); ++i) { + zstring part1Str = strValue.extract(0, i); + zstring part2Str = strValue.extract(i, strValue.length() - i); + expr_ref prefixStr(mk_string(part1Str), mgr); + expr_ref x_concat(mk_concat(m, prefixStr), mgr); + expr_ref cropStr(mk_string(part2Str), mgr); + if (can_two_nodes_eq(x, x_concat) && can_two_nodes_eq(y, cropStr)) { + // break down option 2-2 + expr_ref_vector and_item(mgr); + and_item.push_back(ctx.mk_eq_atom(x, x_concat)); + and_item.push_back(ctx.mk_eq_atom(y, cropStr)); + and_item.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(part2Str.length()))); + expr_ref option2(mk_and(and_item), mgr); + arrangement_disjunction.push_back(option2); + double priority; + // prioritize the option where y is equal to the original string + if (i == 0) { + priority = 0.5; + } else { + priority = 0.1; + } + add_theory_aware_branching_info(option2, priority, l_true); + } + } + + if (!arrangement_disjunction.empty()) { + expr_ref implyR(mk_or(arrangement_disjunction), mgr); + + if (m_params.m_StrongArrangements) { + expr_ref implyLHS(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); + expr_ref ax_strong(ctx.mk_eq_atom(implyLHS, implyR), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + } + generate_mutual_exclusion(arrangement_disjunction); + } else { + TRACE("str", tout << "STOP: Should not split two EQ concats." << std::endl;); + } + } // (splitType == -1) + } + + /************************************************************* + * Type 3: concat(x, y) = concat("str", n) + *************************************************************/ + bool theory_str::is_concat_eq_type3(expr * concatAst1, expr * concatAst2) { + expr * v1_arg0 = to_app(concatAst1)->get_arg(0); + expr * v1_arg1 = to_app(concatAst1)->get_arg(1); + expr * v2_arg0 = to_app(concatAst2)->get_arg(0); + expr * v2_arg1 = to_app(concatAst2)->get_arg(1); + + if (u.str.is_string(v1_arg0) && (!u.str.is_string(v1_arg1)) + && (!u.str.is_string(v2_arg0)) && (!u.str.is_string(v2_arg1))) { + return true; + } else if (u.str.is_string(v2_arg0) && (!u.str.is_string(v2_arg1)) + && (!u.str.is_string(v1_arg0)) && (!u.str.is_string(v1_arg1))) { + return true; + } else { + return false; + } + } + + void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { + ast_manager & mgr = get_manager(); + context & ctx = get_context(); + + bool overlapAssumptionUsed = false; + + TRACE("str", tout << "process_concat_eq TYPE 3" << std::endl + << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl + << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; + ); + + if (!u.str.is_concat(to_app(concatAst1))) { + TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;); + return; + } + if (!u.str.is_concat(to_app(concatAst2))) { + TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;); + return; + } + + expr * v1_arg0 = to_app(concatAst1)->get_arg(0); + expr * v1_arg1 = to_app(concatAst1)->get_arg(1); + expr * v2_arg0 = to_app(concatAst2)->get_arg(0); + expr * v2_arg1 = to_app(concatAst2)->get_arg(1); + + expr * x = NULL; + expr * y = NULL; + expr * strAst = NULL; + expr * n = NULL; + + if (u.str.is_string(v1_arg0) && !u.str.is_string(v2_arg0)) { + strAst = v1_arg0; + n = v1_arg1; + x = v2_arg0; + y = v2_arg1; + } else { + strAst = v2_arg0; + n = v2_arg1; + x = v1_arg0; + y = v1_arg1; + } + + zstring strValue; + u.str.is_string(strAst, strValue); + + rational x_len, y_len, str_len, n_len; + bool x_len_exists = get_len_value(x, x_len); + bool y_len_exists = get_len_value(y, y_len); + str_len = rational((unsigned)(strValue.length())); + bool n_len_exists = get_len_value(n, n_len); + + expr_ref xorFlag(mgr); + expr_ref temp1(mgr); + std::pair key1(concatAst1, concatAst2); + std::pair key2(concatAst2, concatAst1); + + // check the entries in this map to make sure they're still in scope + // before we use them. + + std::map, std::map >::iterator entry1 = varForBreakConcat.find(key1); + std::map, std::map >::iterator entry2 = varForBreakConcat.find(key2); + + bool entry1InScope; + if (entry1 == varForBreakConcat.end()) { + entry1InScope = false; + } else { + if (internal_variable_set.find((entry1->second)[0]) == internal_variable_set.end() + /* || internal_variable_set.find((entry1->second)[1]) == internal_variable_set.end() */) { + entry1InScope = false; + } else { + entry1InScope = true; + } + } + + bool entry2InScope; + if (entry2 == varForBreakConcat.end()) { + entry2InScope = false; + } else { + if (internal_variable_set.find((entry2->second)[0]) == internal_variable_set.end() + /* || internal_variable_set.find((entry2->second)[1]) == internal_variable_set.end() */) { + entry2InScope = false; + } else { + entry2InScope = true; + } + } + + TRACE("str", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl + << "entry 2 " << (entry2InScope ? "in scope" : "not in scope") << std::endl;); + + + if (!entry1InScope && !entry2InScope) { + temp1 = mk_nonempty_str_var(); + xorFlag = mk_internal_xor_var(); + + varForBreakConcat[key1][0] = temp1; + varForBreakConcat[key1][1] = xorFlag; + } else { + if (entry1InScope) { + temp1 = varForBreakConcat[key1][0]; + xorFlag = varForBreakConcat[key1][1]; + } else if (varForBreakConcat.find(key2) != varForBreakConcat.end()) { + temp1 = varForBreakConcat[key2][0]; + xorFlag = varForBreakConcat[key2][1]; + } + refresh_theory_var(temp1); + add_nonempty_constraint(temp1); + } + + + + int splitType = -1; + if (x_len_exists) { + if (x_len < str_len) + splitType = 0; + else if (x_len == str_len) + splitType = 1; + else + splitType = 2; + } + if (splitType == -1 && y_len_exists && n_len_exists) { + if (y_len > n_len) + splitType = 0; + else if (y_len == n_len) + splitType = 1; + else + splitType = 2; + } + + TRACE("str", tout << "Split type " << splitType << std::endl;); + + // Provide fewer split options when length information is available. + if (splitType == 0) { + // | x | y | + // | str | n | + expr_ref_vector litems(mgr); + litems.push_back(ctx.mk_eq_atom(concatAst1, concatAst2)); + rational prefixLen; + if (!x_len_exists) { + prefixLen = str_len - (y_len - n_len); + litems.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len))); + litems.push_back(ctx.mk_eq_atom(mk_strlen(n), mk_int(n_len))); + } else { + prefixLen = x_len; + litems.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len))); + } + zstring prefixStr = strValue.extract(0, prefixLen.get_unsigned()); + rational str_sub_prefix = str_len - prefixLen; + zstring suffixStr = strValue.extract(prefixLen.get_unsigned(), str_sub_prefix.get_unsigned()); + expr_ref prefixAst(mk_string(prefixStr), mgr); + expr_ref suffixAst(mk_string(suffixStr), mgr); + expr_ref ax_l(mgr.mk_and(litems.size(), litems.c_ptr()), mgr); + + expr_ref suf_n_concat(mk_concat(suffixAst, n), mgr); + if (can_two_nodes_eq(x, prefixAst) && can_two_nodes_eq(y, suf_n_concat)) { + expr_ref_vector r_items(mgr); + r_items.push_back(ctx.mk_eq_atom(x, prefixAst)); + r_items.push_back(ctx.mk_eq_atom(y, suf_n_concat)); + + if (m_params.m_StrongArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom(ax_l, mk_and(r_items)), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ax_l, mk_and(r_items)); + } + } else { + // negate! It's impossible to split str with these lengths + TRACE("str", tout << "CONFLICT: Impossible to split str with these lengths." << std::endl;); + assert_axiom(mgr.mk_not(ax_l)); + } + } + else if (splitType == 1) { + expr_ref ax_l1(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); + expr_ref ax_l2(mgr.mk_or( + ctx.mk_eq_atom(mk_strlen(x), mk_strlen(strAst)), + ctx.mk_eq_atom(mk_strlen(y), mk_strlen(n))), mgr); + expr_ref ax_l(mgr.mk_and(ax_l1, ax_l2), mgr); + expr_ref ax_r(mgr.mk_and(ctx.mk_eq_atom(x, strAst), ctx.mk_eq_atom(y, n)), mgr); + + if (m_params.m_StrongArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ax_l, ax_r); + } + } + else if (splitType == 2) { + // | x | y | + // | str | n | + expr_ref_vector litems(mgr); + litems.push_back(ctx.mk_eq_atom(concatAst1, concatAst2)); + rational tmpLen; + if (!x_len_exists) { + tmpLen = n_len - y_len; + litems.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len))); + litems.push_back(ctx.mk_eq_atom(mk_strlen(n), mk_int(n_len))); + } else { + tmpLen = x_len - str_len; + litems.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len))); + } + expr_ref ax_l(mgr.mk_and(litems.size(), litems.c_ptr()), mgr); + + expr_ref str_temp1(mk_concat(strAst, temp1), mgr); + expr_ref temp1_y(mk_concat(temp1, y), mgr); + + if (can_two_nodes_eq(x, str_temp1)) { + if (!avoidLoopCut || !(has_self_cut(x, n))) { + expr_ref_vector r_items(mgr); + r_items.push_back(ctx.mk_eq_atom(x, str_temp1)); + r_items.push_back(ctx.mk_eq_atom(n, temp1_y)); + r_items.push_back(ctx.mk_eq_atom(mk_strlen(temp1), mk_int(tmpLen))); + expr_ref ax_r(mk_and(r_items), mgr); + + //Cut Info + add_cut_info_merge(temp1, sLevel, x); + add_cut_info_merge(temp1, sLevel, n); + + if (m_params.m_StrongArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ax_l, ax_r); + } + } else { + loopDetected = true; + if (m_params.m_FiniteOverlapModels) { + expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); + assert_implication(ax_l, tester); + add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); + } else { + TRACE("str", tout << "AVOID LOOP: SKIPPED" << std::endl;); + TRACE("str", {print_cut_var(x, tout); print_cut_var(n, tout);}); + + if (!overlapAssumptionUsed) { + overlapAssumptionUsed = true; + assert_implication(ax_l, m_theoryStrOverlapAssumption_term); + } + } + } + } + // else { + // // negate! It's impossible to split str with these lengths + // __debugPrint(logFile, "[Conflict] Negate! It's impossible to split str with these lengths @ %d.\n", __LINE__); + // addAxiom(t, Z3_mk_not(ctx, ax_l), __LINE__); + // } + } + else { + // Split type -1. We know nothing about the length... + + expr_ref_vector arrangement_disjunction(mgr); + + int pos = 1; + for (unsigned int i = 0; i <= strValue.length(); i++) { + zstring part1Str = strValue.extract(0, i); + zstring part2Str = strValue.extract(i, strValue.length() - i); + expr_ref cropStr(mk_string(part1Str), mgr); + expr_ref suffixStr(mk_string(part2Str), mgr); + expr_ref y_concat(mk_concat(suffixStr, n), mgr); + + if (can_two_nodes_eq(x, cropStr) && can_two_nodes_eq(y, y_concat)) { + expr_ref_vector and_item(mgr); + // break down option 3-1 + expr_ref x_eq_str(ctx.mk_eq_atom(x, cropStr), mgr); + + and_item.push_back(x_eq_str); ++pos; + and_item.push_back(ctx.mk_eq_atom(y, y_concat)); + and_item.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_strlen(cropStr))); ++pos; + + // and_item[pos++] = Z3_mk_eq(ctx, or_item[option], Z3_mk_eq(ctx, mk_length(t, y), mk_length(t, y_concat))); + // adding length constraint for _ = constStr seems slowing things down. + + expr_ref option1(mk_and(and_item), mgr); + arrangement_disjunction.push_back(option1); + double priority; + if (i == strValue.length()) { + priority = 0.5; + } else { + priority = 0.1; + } + add_theory_aware_branching_info(option1, priority, l_true); + } + } + + expr_ref strAst_temp1(mk_concat(strAst, temp1), mgr); + + + //-------------------------------------------------------- + // x cut n + //-------------------------------------------------------- + if (can_two_nodes_eq(x, strAst_temp1)) { + if (!avoidLoopCut || !(has_self_cut(x, n))) { + // break down option 3-2 + expr_ref_vector and_item(mgr); + + expr_ref temp1_y(mk_concat(temp1, y), mgr); + and_item.push_back(ctx.mk_eq_atom(x, strAst_temp1)); ++pos; + and_item.push_back(ctx.mk_eq_atom(n, temp1_y)); ++pos; + + and_item.push_back(ctx.mk_eq_atom(mk_strlen(x), + m_autil.mk_add(mk_strlen(strAst), mk_strlen(temp1)) ) ); ++pos; + + expr_ref option2(mk_and(and_item), mgr); + arrangement_disjunction.push_back(option2); + add_theory_aware_branching_info(option2, 0.1, l_true); + + add_cut_info_merge(temp1, sLevel, x); + add_cut_info_merge(temp1, sLevel, n); + } else { + loopDetected = true; + if (m_params.m_FiniteOverlapModels) { + expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); + arrangement_disjunction.push_back(tester); + add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); + } else { + TRACE("str", tout << "AVOID LOOP: SKIPPED." << std::endl;); + TRACE("str", {print_cut_var(x, tout); print_cut_var(n, tout);}); + + if (!overlapAssumptionUsed) { + overlapAssumptionUsed = true; + arrangement_disjunction.push_back(m_theoryStrOverlapAssumption_term); + } + } + } + } + + + if (!arrangement_disjunction.empty()) { + expr_ref implyR(mk_or(arrangement_disjunction), mgr); + + if (m_params.m_StrongArrangements) { + expr_ref ax_lhs(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); + expr_ref ax_strong(ctx.mk_eq_atom(ax_lhs, implyR), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + } + generate_mutual_exclusion(arrangement_disjunction); + } else { + TRACE("str", tout << "STOP: should not split two eq. concats" << std::endl;); + } + } + + } + + /************************************************************* + * Type 4: concat("str1", y) = concat("str2", n) + *************************************************************/ + bool theory_str::is_concat_eq_type4(expr * concatAst1, expr * concatAst2) { + expr * v1_arg0 = to_app(concatAst1)->get_arg(0); + expr * v1_arg1 = to_app(concatAst1)->get_arg(1); + expr * v2_arg0 = to_app(concatAst2)->get_arg(0); + expr * v2_arg1 = to_app(concatAst2)->get_arg(1); + + if (u.str.is_string(v1_arg0) && (!u.str.is_string(v1_arg1)) + && u.str.is_string(v2_arg0) && (!u.str.is_string(v2_arg1))) { + return true; + } else { + return false; + } + } + + void theory_str::process_concat_eq_type4(expr * concatAst1, expr * concatAst2) { + ast_manager & mgr = get_manager(); + context & ctx = get_context(); + TRACE("str", tout << "process_concat_eq TYPE 4" << std::endl + << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl + << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; + ); + + if (!u.str.is_concat(to_app(concatAst1))) { + TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;); + return; + } + if (!u.str.is_concat(to_app(concatAst2))) { + TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;); + return; + } + + expr * v1_arg0 = to_app(concatAst1)->get_arg(0); + expr * v1_arg1 = to_app(concatAst1)->get_arg(1); + expr * v2_arg0 = to_app(concatAst2)->get_arg(0); + expr * v2_arg1 = to_app(concatAst2)->get_arg(1); + + expr * str1Ast = v1_arg0; + expr * y = v1_arg1; + expr * str2Ast = v2_arg0; + expr * n = v2_arg1; + + zstring str1Value, str2Value; + u.str.is_string(str1Ast, str1Value); + u.str.is_string(str2Ast, str2Value); + + unsigned int str1Len = str1Value.length(); + unsigned int str2Len = str2Value.length(); + + int commonLen = (str1Len > str2Len) ? str2Len : str1Len; + if (str1Value.extract(0, commonLen) != str2Value.extract(0, commonLen)) { + TRACE("str", tout << "Conflict: " << mk_ismt2_pp(concatAst1, mgr) + << " has no common prefix with " << mk_ismt2_pp(concatAst2, mgr) << std::endl;); + expr_ref toNegate(mgr.mk_not(ctx.mk_eq_atom(concatAst1, concatAst2)), mgr); + assert_axiom(toNegate); + return; + } else { + if (str1Len > str2Len) { + zstring deltaStr = str1Value.extract(str2Len, str1Len - str2Len); + expr_ref tmpAst(mk_concat(mk_string(deltaStr), y), mgr); + if (!in_same_eqc(tmpAst, n)) { + // break down option 4-1 + expr_ref implyR(ctx.mk_eq_atom(n, tmpAst), mgr); + if (m_params.m_StrongArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + } + } + } else if (str1Len == str2Len) { + if (!in_same_eqc(n, y)) { + //break down option 4-2 + expr_ref implyR(ctx.mk_eq_atom(n, y), mgr); + + if (m_params.m_StrongArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + } + } + } else { + zstring deltaStr = str2Value.extract(str1Len, str2Len - str1Len); + expr_ref tmpAst(mk_concat(mk_string(deltaStr), n), mgr); + if (!in_same_eqc(y, tmpAst)) { + //break down option 4-3 + expr_ref implyR(ctx.mk_eq_atom(y, tmpAst), mgr); + if (m_params.m_StrongArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + } + } + } + } + } + + /************************************************************* + * case 5: concat(x, "str1") = concat(m, "str2") + *************************************************************/ + bool theory_str::is_concat_eq_type5(expr * concatAst1, expr * concatAst2) { + expr * v1_arg0 = to_app(concatAst1)->get_arg(0); + expr * v1_arg1 = to_app(concatAst1)->get_arg(1); + expr * v2_arg0 = to_app(concatAst2)->get_arg(0); + expr * v2_arg1 = to_app(concatAst2)->get_arg(1); + + if ((!u.str.is_string(v1_arg0)) && u.str.is_string(v1_arg1) + && (!u.str.is_string(v2_arg0)) && u.str.is_string(v2_arg1)) { + return true; + } else { + return false; + } + } + + void theory_str::process_concat_eq_type5(expr * concatAst1, expr * concatAst2) { + ast_manager & mgr = get_manager(); + context & ctx = get_context(); + TRACE("str", tout << "process_concat_eq TYPE 5" << std::endl + << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl + << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; + ); + + if (!u.str.is_concat(to_app(concatAst1))) { + TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;); + return; + } + if (!u.str.is_concat(to_app(concatAst2))) { + TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;); + return; + } + + expr * v1_arg0 = to_app(concatAst1)->get_arg(0); + expr * v1_arg1 = to_app(concatAst1)->get_arg(1); + expr * v2_arg0 = to_app(concatAst2)->get_arg(0); + expr * v2_arg1 = to_app(concatAst2)->get_arg(1); + + expr * x = v1_arg0; + expr * str1Ast = v1_arg1; + expr * m = v2_arg0; + expr * str2Ast = v2_arg1; + + zstring str1Value, str2Value; + u.str.is_string(str1Ast, str1Value); + u.str.is_string(str2Ast, str2Value); + + unsigned int str1Len = str1Value.length(); + unsigned int str2Len = str2Value.length(); + + int cLen = (str1Len > str2Len) ? str2Len : str1Len; + if (str1Value.extract(str1Len - cLen, cLen) != str2Value.extract(str2Len - cLen, cLen)) { + TRACE("str", tout << "Conflict: " << mk_ismt2_pp(concatAst1, mgr) + << " has no common suffix with " << mk_ismt2_pp(concatAst2, mgr) << std::endl;); + expr_ref toNegate(mgr.mk_not(ctx.mk_eq_atom(concatAst1, concatAst2)), mgr); + assert_axiom(toNegate); + return; + } else { + if (str1Len > str2Len) { + zstring deltaStr = str1Value.extract(0, str1Len - str2Len); + expr_ref x_deltaStr(mk_concat(x, mk_string(deltaStr)), mgr); + if (!in_same_eqc(m, x_deltaStr)) { + expr_ref implyR(ctx.mk_eq_atom(m, x_deltaStr), mgr); + if (m_params.m_StrongArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + } + } + } else if (str1Len == str2Len) { + // test + if (!in_same_eqc(x, m)) { + expr_ref implyR(ctx.mk_eq_atom(x, m), mgr); + if (m_params.m_StrongArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + } + } + } else { + zstring deltaStr = str2Value.extract(0, str2Len - str1Len); + expr_ref m_deltaStr(mk_concat(m, mk_string(deltaStr)), mgr); + if (!in_same_eqc(x, m_deltaStr)) { + expr_ref implyR(ctx.mk_eq_atom(x, m_deltaStr), mgr); + if (m_params.m_StrongArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + } + } + } + } + } + + /************************************************************* + * case 6: concat("str1", y) = concat(m, "str2") + *************************************************************/ + bool theory_str::is_concat_eq_type6(expr * concatAst1, expr * concatAst2) { + expr * v1_arg0 = to_app(concatAst1)->get_arg(0); + expr * v1_arg1 = to_app(concatAst1)->get_arg(1); + expr * v2_arg0 = to_app(concatAst2)->get_arg(0); + expr * v2_arg1 = to_app(concatAst2)->get_arg(1); + + if (u.str.is_string(v1_arg0) && (!u.str.is_string(v1_arg1)) + && (!u.str.is_string(v2_arg0)) && u.str.is_string(v2_arg1)) { + return true; + } else if (u.str.is_string(v2_arg0) && (!u.str.is_string(v2_arg1)) + && (!u.str.is_string(v1_arg0)) && u.str.is_string(v1_arg1)) { + return true; + } else { + return false; + } + } + + void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { + ast_manager & mgr = get_manager(); + context & ctx = get_context(); + TRACE("str", tout << "process_concat_eq TYPE 6" << std::endl + << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl + << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; + ); + + if (!u.str.is_concat(to_app(concatAst1))) { + TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;); + return; + } + if (!u.str.is_concat(to_app(concatAst2))) { + TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;); + return; + } + + expr * v1_arg0 = to_app(concatAst1)->get_arg(0); + expr * v1_arg1 = to_app(concatAst1)->get_arg(1); + expr * v2_arg0 = to_app(concatAst2)->get_arg(0); + expr * v2_arg1 = to_app(concatAst2)->get_arg(1); + + + expr * str1Ast = NULL; + expr * y = NULL; + expr * m = NULL; + expr * str2Ast = NULL; + + if (u.str.is_string(v1_arg0)) { + str1Ast = v1_arg0; + y = v1_arg1; + m = v2_arg0; + str2Ast = v2_arg1; + } else { + str1Ast = v2_arg0; + y = v2_arg1; + m = v1_arg0; + str2Ast = v1_arg1; + } + + zstring str1Value, str2Value; + u.str.is_string(str1Ast, str1Value); + u.str.is_string(str2Ast, str2Value); + + unsigned int str1Len = str1Value.length(); + unsigned int str2Len = str2Value.length(); + + //---------------------------------------- + //(a) |---str1---|----y----| + // |--m--|-----str2-----| + // + //(b) |---str1---|----y----| + // |-----m----|--str2---| + // + //(c) |---str1---|----y----| + // |------m------|-str2-| + //---------------------------------------- + + std::list overlapLen; + overlapLen.push_back(0); + + for (unsigned int i = 1; i <= str1Len && i <= str2Len; i++) { + if (str1Value.extract(str1Len - i, i) == str2Value.extract(0, i)) + overlapLen.push_back(i); + } + + //---------------------------------------------------------------- + expr * commonVar = NULL; + expr * xorFlag = NULL; + std::pair key1(concatAst1, concatAst2); + std::pair key2(concatAst2, concatAst1); + + // check the entries in this map to make sure they're still in scope + // before we use them. + + std::map, std::map >::iterator entry1 = varForBreakConcat.find(key1); + std::map, std::map >::iterator entry2 = varForBreakConcat.find(key2); + + bool entry1InScope; + if (entry1 == varForBreakConcat.end()) { + entry1InScope = false; + } else { + if (internal_variable_set.find((entry1->second)[0]) == internal_variable_set.end() + /* || internal_variable_set.find((entry1->second)[1]) == internal_variable_set.end() */) { + entry1InScope = false; + } else { + entry1InScope = true; + } + } + + bool entry2InScope; + if (entry2 == varForBreakConcat.end()) { + entry2InScope = false; + } else { + if (internal_variable_set.find((entry2->second)[0]) == internal_variable_set.end() + /* || internal_variable_set.find((entry2->second)[1]) == internal_variable_set.end() */) { + entry2InScope = false; + } else { + entry2InScope = true; + } + } + + TRACE("str", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl + << "entry 2 " << (entry2InScope ? "in scope" : "not in scope") << std::endl;); + + if (!entry1InScope && !entry2InScope) { + commonVar = mk_nonempty_str_var(); + xorFlag = mk_internal_xor_var(); + varForBreakConcat[key1][0] = commonVar; + varForBreakConcat[key1][1] = xorFlag; + } else { + if (entry1InScope) { + commonVar = (entry1->second)[0]; + xorFlag = (entry1->second)[1]; + } else { + commonVar = (entry2->second)[0]; + xorFlag = (entry2->second)[1]; + } + refresh_theory_var(commonVar); + add_nonempty_constraint(commonVar); + } + + bool overlapAssumptionUsed = false; + + expr_ref_vector arrangement_disjunction(mgr); + int pos = 1; + + if (!avoidLoopCut || !has_self_cut(m, y)) { + expr_ref_vector and_item(mgr); + + expr_ref str1_commonVar(mk_concat(str1Ast, commonVar), mgr); + and_item.push_back(ctx.mk_eq_atom(m, str1_commonVar)); + pos += 1; + + expr_ref commonVar_str2(mk_concat(commonVar, str2Ast), mgr); + and_item.push_back(ctx.mk_eq_atom(y, commonVar_str2)); + pos += 1; + + and_item.push_back(ctx.mk_eq_atom(mk_strlen(m), + m_autil.mk_add(mk_strlen(str1Ast), mk_strlen(commonVar)) )); + pos += 1; + + // addItems[0] = mk_length(t, commonVar); + // addItems[1] = mk_length(t, str2Ast); + // and_item[pos++] = Z3_mk_eq(ctx, or_item[option], Z3_mk_eq(ctx, mk_length(t, y), Z3_mk_add(ctx, 2, addItems))); + + expr_ref option1(mk_and(and_item), mgr); + arrangement_disjunction.push_back(option1); + add_theory_aware_branching_info(option1, 0.1, l_true); + } else { + loopDetected = true; + + if (m_params.m_FiniteOverlapModels) { + expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); + arrangement_disjunction.push_back(tester); + add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); + } else { + TRACE("str", tout << "AVOID LOOP: SKIPPED." << std::endl;); + TRACE("str", print_cut_var(m, tout); print_cut_var(y, tout);); + + // only add the overlap assumption one time + if (!overlapAssumptionUsed) { + arrangement_disjunction.push_back(m_theoryStrOverlapAssumption_term); + overlapAssumptionUsed = true; + } + } + } + + for (std::list::iterator itor = overlapLen.begin(); itor != overlapLen.end(); itor++) { + unsigned int overLen = *itor; + zstring prefix = str1Value.extract(0, str1Len - overLen); + zstring suffix = str2Value.extract(overLen, str2Len - overLen); + + expr_ref_vector and_item(mgr); + + expr_ref prefixAst(mk_string(prefix), mgr); + expr_ref x_eq_prefix(ctx.mk_eq_atom(m, prefixAst), mgr); + and_item.push_back(x_eq_prefix); + pos += 1; + + and_item.push_back( + ctx.mk_eq_atom(mk_strlen(m), mk_strlen(prefixAst))); + pos += 1; + + // adding length constraint for _ = constStr seems slowing things down. + + expr_ref suffixAst(mk_string(suffix), mgr); + expr_ref y_eq_suffix(ctx.mk_eq_atom(y, suffixAst), mgr); + and_item.push_back(y_eq_suffix); + pos += 1; + + and_item.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_strlen(suffixAst))); + pos += 1; + + expr_ref option2(mk_and(and_item), mgr); + arrangement_disjunction.push_back(option2); + double priority; + // prefer the option "str1" = x + if (prefix == str1Value) { + priority = 0.5; + } else { + priority = 0.1; + } + add_theory_aware_branching_info(option2, priority, l_true); + } + + // case 6: concat("str1", y) = concat(m, "str2") + + expr_ref implyR(mk_or(arrangement_disjunction), mgr); + + if (m_params.m_StrongArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + } + generate_mutual_exclusion(arrangement_disjunction); + } + + void theory_str::process_unroll_eq_const_str(expr * unrollFunc, expr * constStr) { + ast_manager & m = get_manager(); + + if (!u.re.is_unroll(to_app(unrollFunc))) { + return; + } + if (!u.str.is_string(constStr)) { + return; + } + + expr * funcInUnroll = to_app(unrollFunc)->get_arg(0); + zstring strValue; + u.str.is_string(constStr, strValue); + + TRACE("str", tout << "unrollFunc: " << mk_pp(unrollFunc, m) << std::endl + << "constStr: " << mk_pp(constStr, m) << std::endl;); + + if (strValue == "") { + return; + } + + if (u.re.is_to_re(to_app(funcInUnroll))) { + unroll_str2reg_constStr(unrollFunc, constStr); + return; + } + } + + void theory_str::process_concat_eq_unroll(expr * concat, expr * unroll) { + context & ctx = get_context(); + ast_manager & mgr = get_manager(); + + TRACE("str", tout << "concat = " << mk_pp(concat, mgr) << ", unroll = " << mk_pp(unroll, mgr) << std::endl;); + + std::pair key = std::make_pair(concat, unroll); + expr_ref toAssert(mgr); + + if (concat_eq_unroll_ast_map.find(key) == concat_eq_unroll_ast_map.end()) { + expr_ref arg1(to_app(concat)->get_arg(0), mgr); + expr_ref arg2(to_app(concat)->get_arg(1), mgr); + expr_ref r1(to_app(unroll)->get_arg(0), mgr); + expr_ref t1(to_app(unroll)->get_arg(1), mgr); + + expr_ref v1(mk_regex_rep_var(), mgr); + expr_ref v2(mk_regex_rep_var(), mgr); + expr_ref v3(mk_regex_rep_var(), mgr); + expr_ref v4(mk_regex_rep_var(), mgr); + expr_ref v5(mk_regex_rep_var(), mgr); + + expr_ref t2(mk_unroll_bound_var(), mgr); + expr_ref t3(mk_unroll_bound_var(), mgr); + expr_ref emptyStr(mk_string(""), mgr); + + expr_ref unroll1(mk_unroll(r1, t2), mgr); + expr_ref unroll2(mk_unroll(r1, t3), mgr); + + expr_ref op0(ctx.mk_eq_atom(t1, mk_int(0)), mgr); + expr_ref op1(m_autil.mk_ge(t1, mk_int(1)), mgr); + + expr_ref_vector op1Items(mgr); + expr_ref_vector op2Items(mgr); + + op1Items.push_back(ctx.mk_eq_atom(arg1, emptyStr)); + op1Items.push_back(ctx.mk_eq_atom(arg2, emptyStr)); + op1Items.push_back(ctx.mk_eq_atom(mk_strlen(arg1), mk_int(0))); + op1Items.push_back(ctx.mk_eq_atom(mk_strlen(arg2), mk_int(0))); + expr_ref opAnd1(ctx.mk_eq_atom(op0, mk_and(op1Items)), mgr); + + expr_ref v1v2(mk_concat(v1, v2), mgr); + op2Items.push_back(ctx.mk_eq_atom(arg1, v1v2)); + op2Items.push_back(ctx.mk_eq_atom(mk_strlen(arg1), m_autil.mk_add(mk_strlen(v1), mk_strlen(v2)))); + expr_ref v3v4(mk_concat(v3, v4), mgr); + op2Items.push_back(ctx.mk_eq_atom(arg2, v3v4)); + op2Items.push_back(ctx.mk_eq_atom(mk_strlen(arg2), m_autil.mk_add(mk_strlen(v3), mk_strlen(v4)))); + + op2Items.push_back(ctx.mk_eq_atom(v1, unroll1)); + op2Items.push_back(ctx.mk_eq_atom(mk_strlen(v1), mk_strlen(unroll1))); + op2Items.push_back(ctx.mk_eq_atom(v4, unroll2)); + op2Items.push_back(ctx.mk_eq_atom(mk_strlen(v4), mk_strlen(unroll2))); + expr_ref v2v3(mk_concat(v2, v3), mgr); + op2Items.push_back(ctx.mk_eq_atom(v5, v2v3)); + reduce_virtual_regex_in(v5, r1, op2Items); + op2Items.push_back(ctx.mk_eq_atom(mk_strlen(v5), m_autil.mk_add(mk_strlen(v2), mk_strlen(v3)))); + op2Items.push_back(ctx.mk_eq_atom(m_autil.mk_add(t2, t3), m_autil.mk_add(t1, mk_int(-1)))); + expr_ref opAnd2(ctx.mk_eq_atom(op1, mk_and(op2Items)), mgr); + + toAssert = mgr.mk_and(opAnd1, opAnd2); + m_trail.push_back(toAssert); + concat_eq_unroll_ast_map[key] = toAssert; + } else { + toAssert = concat_eq_unroll_ast_map[key]; + } + + assert_axiom(toAssert); + } + + void theory_str::unroll_str2reg_constStr(expr * unrollFunc, expr * eqConstStr) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + expr * str2RegFunc = to_app(unrollFunc)->get_arg(0); + expr * strInStr2RegFunc = to_app(str2RegFunc)->get_arg(0); + expr * oriCnt = to_app(unrollFunc)->get_arg(1); + + zstring strValue; + u.str.is_string(eqConstStr, strValue); + zstring regStrValue; + u.str.is_string(strInStr2RegFunc, regStrValue); + unsigned int strLen = strValue.length(); + unsigned int regStrLen = regStrValue.length(); + SASSERT(regStrLen != 0); // this should never occur -- the case for empty string is handled elsewhere + unsigned int cnt = strLen / regStrLen; + + expr_ref implyL(ctx.mk_eq_atom(unrollFunc, eqConstStr), m); + expr_ref implyR1(ctx.mk_eq_atom(oriCnt, mk_int(cnt)), m); + expr_ref implyR2(ctx.mk_eq_atom(mk_strlen(unrollFunc), mk_int(strLen)), m); + expr_ref axiomRHS(m.mk_and(implyR1, implyR2), m); + SASSERT(implyL); + SASSERT(axiomRHS); + assert_implication(implyL, axiomRHS); + } + + /* + * Look through the equivalence class of n to find a string constant. + * Return that constant if it is found, and set hasEqcValue to true. + * Otherwise, return n, and set hasEqcValue to false. + */ + + expr * theory_str::get_eqc_value(expr * n, bool & hasEqcValue) { + return z3str2_get_eqc_value(n, hasEqcValue); + } + + + // Simulate the behaviour of get_eqc_value() from Z3str2. + // We only check m_find for a string constant. + + expr * theory_str::z3str2_get_eqc_value(expr * n , bool & hasEqcValue) { + expr * curr = n; + do { + if (u.str.is_string(curr)) { + hasEqcValue = true; + return curr; + } + curr = get_eqc_next(curr); + } while (curr != n); + hasEqcValue = false; + return n; + } + + // from Z3: theory_seq.cpp + + static theory_mi_arith* get_th_arith(context& ctx, theory_id afid, expr* e) { + theory* th = ctx.get_theory(afid); + if (th && ctx.e_internalized(e)) { + return dynamic_cast(th); + } + else { + return 0; + } + } + + bool theory_str::get_value(expr* e, rational& val) const { + if (opt_DisableIntegerTheoryIntegration) { + TRACE("str", tout << "WARNING: integer theory integration disabled" << std::endl;); + return false; + } + + context& ctx = get_context(); + ast_manager & m = get_manager(); + theory_mi_arith* tha = get_th_arith(ctx, m_autil.get_family_id(), e); + if (!tha) { + return false; + } + TRACE("str", tout << "checking eqc of " << mk_pp(e, m) << " for arithmetic value" << std::endl;); + expr_ref _val(m); + enode * en_e = ctx.get_enode(e); + enode * it = en_e; + do { + if (m_autil.is_numeral(it->get_owner(), val) && val.is_int()) { + // found an arithmetic term + TRACE("str", tout << mk_pp(it->get_owner(), m) << " is an integer ( ~= " << val << " )" + << std::endl;); + return true; + } else { + TRACE("str", tout << mk_pp(it->get_owner(), m) << " not a numeral" << std::endl;); + } + it = it->get_next(); + } while (it != en_e); + TRACE("str", tout << "no arithmetic values found in eqc" << std::endl;); + return false; + } + + bool theory_str::lower_bound(expr* _e, rational& lo) { + if (opt_DisableIntegerTheoryIntegration) { + TRACE("str", tout << "WARNING: integer theory integration disabled" << std::endl;); + return false; + } + + context& ctx = get_context(); + ast_manager & m = get_manager(); + theory_mi_arith* tha = get_th_arith(ctx, m_autil.get_family_id(), _e); + expr_ref _lo(m); + if (!tha || !tha->get_lower(ctx.get_enode(_e), _lo)) return false; + return m_autil.is_numeral(_lo, lo) && lo.is_int(); + } + + bool theory_str::upper_bound(expr* _e, rational& hi) { + if (opt_DisableIntegerTheoryIntegration) { + TRACE("str", tout << "WARNING: integer theory integration disabled" << std::endl;); + return false; + } + + context& ctx = get_context(); + ast_manager & m = get_manager(); + theory_mi_arith* tha = get_th_arith(ctx, m_autil.get_family_id(), _e); + expr_ref _hi(m); + if (!tha || !tha->get_upper(ctx.get_enode(_e), _hi)) return false; + return m_autil.is_numeral(_hi, hi) && hi.is_int(); + } + + bool theory_str::get_len_value(expr* e, rational& val) { + if (opt_DisableIntegerTheoryIntegration) { + TRACE("str", tout << "WARNING: integer theory integration disabled" << std::endl;); + return false; + } + + context& ctx = get_context(); + ast_manager & m = get_manager(); + + theory* th = ctx.get_theory(m_autil.get_family_id()); + if (!th) { + TRACE("str", tout << "oops, can't get m_autil's theory" << std::endl;); + return false; + } + theory_mi_arith* tha = dynamic_cast(th); + if (!tha) { + TRACE("str", tout << "oops, can't cast to theory_mi_arith" << std::endl;); + return false; + } + + TRACE("str", tout << "checking len value of " << mk_ismt2_pp(e, m) << std::endl;); + + rational val1; + expr_ref len(m), len_val(m); + expr* e1, *e2; + ptr_vector todo; + todo.push_back(e); + val.reset(); + while (!todo.empty()) { + expr* c = todo.back(); + todo.pop_back(); + if (u.str.is_concat(to_app(c))) { + e1 = to_app(c)->get_arg(0); + e2 = to_app(c)->get_arg(1); + todo.push_back(e1); + todo.push_back(e2); + } + else if (u.str.is_string(to_app(c))) { + zstring tmp; + u.str.is_string(to_app(c), tmp); + unsigned int sl = tmp.length(); + val += rational(sl); + } + else { + len = mk_strlen(c); + + // debugging + TRACE("str", { + tout << mk_pp(len, m) << ":" << std::endl + << (ctx.is_relevant(len.get()) ? "relevant" : "not relevant") << std::endl + << (ctx.e_internalized(len) ? "internalized" : "not internalized") << std::endl + ; + if (ctx.e_internalized(len)) { + enode * e_len = ctx.get_enode(len); + tout << "has " << e_len->get_num_th_vars() << " theory vars" << std::endl; + + // eqc debugging + { + tout << "dump equivalence class of " << mk_pp(len, get_manager()) << std::endl; + enode * nNode = ctx.get_enode(len); + enode * eqcNode = nNode; + do { + app * ast = eqcNode->get_owner(); + tout << mk_pp(ast, get_manager()) << std::endl; + eqcNode = eqcNode->get_next(); + } while (eqcNode != nNode); + } + } + }); + + if (ctx.e_internalized(len) && get_value(len, val1)) { + val += val1; + TRACE("str", tout << "integer theory: subexpression " << mk_ismt2_pp(len, m) << " has length " << val1 << std::endl;); + } + else { + TRACE("str", tout << "integer theory: subexpression " << mk_ismt2_pp(len, m) << " has no length assignment; bailing out" << std::endl;); + return false; + } + } + } + + TRACE("str", tout << "length of " << mk_ismt2_pp(e, m) << " is " << val << std::endl;); + return val.is_int(); + } + + /* + * Decide whether n1 and n2 are already in the same equivalence class. + * This only checks whether the core considers them to be equal; + * they may not actually be equal. + */ + bool theory_str::in_same_eqc(expr * n1, expr * n2) { + if (n1 == n2) return true; + context & ctx = get_context(); + ast_manager & m = get_manager(); + + // similar to get_eqc_value(), make absolutely sure + // that we've set this up properly for the context + + if (!ctx.e_internalized(n1)) { + TRACE("str", tout << "WARNING: expression " << mk_ismt2_pp(n1, m) << " was not internalized" << std::endl;); + ctx.internalize(n1, false); + } + if (!ctx.e_internalized(n2)) { + TRACE("str", tout << "WARNING: expression " << mk_ismt2_pp(n2, m) << " was not internalized" << std::endl;); + ctx.internalize(n2, false); + } + + expr * curr = get_eqc_next(n1); + while (curr != n1) { + if (curr == n2) + return true; + curr = get_eqc_next(curr); + } + return false; + } + + expr * theory_str::collect_eq_nodes(expr * n, expr_ref_vector & eqcSet) { + expr * constStrNode = NULL; + + expr * ex = n; + do { + if (u.str.is_string(to_app(ex))) { + constStrNode = ex; + } + eqcSet.push_back(ex); + + ex = get_eqc_next(ex); + } while (ex != n); + return constStrNode; + } + + /* + * Collect constant strings (from left to right) in an AST node. + */ + void theory_str::get_const_str_asts_in_node(expr * node, expr_ref_vector & astList) { + if (u.str.is_string(node)) { + astList.push_back(node); + //} else if (getNodeType(t, node) == my_Z3_Func) { + } else if (is_app(node)) { + app * func_app = to_app(node); + unsigned int argCount = func_app->get_num_args(); + for (unsigned int i = 0; i < argCount; i++) { + expr * argAst = func_app->get_arg(i); + get_const_str_asts_in_node(argAst, astList); + } + } + } + + void theory_str::check_contain_by_eqc_val(expr * varNode, expr * constNode) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + TRACE("str", tout << "varNode = " << mk_pp(varNode, m) << ", constNode = " << mk_pp(constNode, m) << std::endl;); + + expr_ref_vector litems(m); + + if (contain_pair_idx_map.find(varNode) != contain_pair_idx_map.end()) { + std::set >::iterator itor1 = contain_pair_idx_map[varNode].begin(); + for (; itor1 != contain_pair_idx_map[varNode].end(); ++itor1) { + expr * strAst = itor1->first; + expr * substrAst = itor1->second; + + expr * boolVar; + if (!contain_pair_bool_map.find(strAst, substrAst, boolVar)) { + TRACE("str", tout << "warning: no entry for boolVar in contain_pair_bool_map" << std::endl;); + } + + // we only want to inspect the Contains terms where either of strAst or substrAst + // are equal to varNode. + + TRACE("t_str_detail", tout << "considering Contains with strAst = " << mk_pp(strAst, m) << ", substrAst = " << mk_pp(substrAst, m) << "..." << std::endl;); + + if (varNode != strAst && varNode != substrAst) { + TRACE("str", tout << "varNode not equal to strAst or substrAst, skip" << std::endl;); + continue; + } + TRACE("str", tout << "varNode matched one of strAst or substrAst. Continuing" << std::endl;); + + // varEqcNode is str + if (strAst == varNode) { + expr_ref implyR(m); + litems.reset(); + + if (strAst != constNode) { + litems.push_back(ctx.mk_eq_atom(strAst, constNode)); + } + zstring strConst; + u.str.is_string(constNode, strConst); + bool subStrHasEqcValue = false; + expr * substrValue = get_eqc_value(substrAst, subStrHasEqcValue); + if (substrValue != substrAst) { + litems.push_back(ctx.mk_eq_atom(substrAst, substrValue)); + } + + if (subStrHasEqcValue) { + // subStr has an eqc constant value + zstring subStrConst; + u.str.is_string(substrValue, subStrConst); + + TRACE("t_str_detail", tout << "strConst = " << strConst << ", subStrConst = " << subStrConst << "\n";); + + if (strConst.contains(subStrConst)) { + //implyR = ctx.mk_eq(ctx, boolVar, Z3_mk_true(ctx)); + implyR = boolVar; + } else { + //implyR = Z3_mk_eq(ctx, boolVar, Z3_mk_false(ctx)); + implyR = m.mk_not(boolVar); + } + } else { + // ------------------------------------------------------------------------------------------------ + // subStr doesn't have an eqc contant value + // however, subStr equals to some concat(arg_1, arg_2, ..., arg_n) + // if arg_j is a constant and is not a part of the strConst, it's sure that the contains is false + // ** This check is needed here because the "strConst" and "strAst" may not be in a same eqc yet + // ------------------------------------------------------------------------------------------------ + // collect eqc concat + std::set eqcConcats; + get_concats_in_eqc(substrAst, eqcConcats); + for (std::set::iterator concatItor = eqcConcats.begin(); + concatItor != eqcConcats.end(); concatItor++) { + expr_ref_vector constList(m); + bool counterEgFound = false; + // get constant strings in concat + expr * aConcat = *concatItor; + get_const_str_asts_in_node(aConcat, constList); + for (expr_ref_vector::iterator cstItor = constList.begin(); + cstItor != constList.end(); cstItor++) { + zstring pieceStr; + u.str.is_string(*cstItor, pieceStr); + if (!strConst.contains(pieceStr)) { + counterEgFound = true; + if (aConcat != substrAst) { + litems.push_back(ctx.mk_eq_atom(substrAst, aConcat)); + } + //implyR = Z3_mk_eq(ctx, boolVar, Z3_mk_false(ctx)); + implyR = m.mk_not(boolVar); + break; + } + } + if (counterEgFound) { + TRACE("str", tout << "Inconsistency found!" << std::endl;); + break; + } + } + } + // add assertion + if (implyR) { + expr_ref implyLHS(mk_and(litems), m); + assert_implication(implyLHS, implyR); + } + } + // varEqcNode is subStr + else if (substrAst == varNode) { + expr_ref implyR(m); + litems.reset(); + + if (substrAst != constNode) { + litems.push_back(ctx.mk_eq_atom(substrAst, constNode)); + } + bool strHasEqcValue = false; + expr * strValue = get_eqc_value(strAst, strHasEqcValue); + if (strValue != strAst) { + litems.push_back(ctx.mk_eq_atom(strAst, strValue)); + } + + if (strHasEqcValue) { + zstring strConst, subStrConst; + u.str.is_string(strValue, strConst); + u.str.is_string(constNode, subStrConst); + if (strConst.contains(subStrConst)) { + //implyR = Z3_mk_eq(ctx, boolVar, Z3_mk_true(ctx)); + implyR = boolVar; + } else { + // implyR = Z3_mk_eq(ctx, boolVar, Z3_mk_false(ctx)); + implyR = m.mk_not(boolVar); + } + } + + // add assertion + if (implyR) { + expr_ref implyLHS(mk_and(litems), m); + assert_implication(implyLHS, implyR); + } + } + } // for (itor1 : contains_map) + } // if varNode in contain_pair_idx_map + } + + void theory_str::check_contain_by_substr(expr * varNode, expr_ref_vector & willEqClass) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + expr_ref_vector litems(m); + + if (contain_pair_idx_map.find(varNode) != contain_pair_idx_map.end()) { + std::set >::iterator itor1 = contain_pair_idx_map[varNode].begin(); + for (; itor1 != contain_pair_idx_map[varNode].end(); ++itor1) { + expr * strAst = itor1->first; + expr * substrAst = itor1->second; + + expr * boolVar; + if (!contain_pair_bool_map.find(strAst, substrAst, boolVar)) { + TRACE("str", tout << "warning: no entry for boolVar in contain_pair_bool_map" << std::endl;); + } + + // we only want to inspect the Contains terms where either of strAst or substrAst + // are equal to varNode. + + TRACE("t_str_detail", tout << "considering Contains with strAst = " << mk_pp(strAst, m) << ", substrAst = " << mk_pp(substrAst, m) << "..." << std::endl;); + + if (varNode != strAst && varNode != substrAst) { + TRACE("str", tout << "varNode not equal to strAst or substrAst, skip" << std::endl;); + continue; + } + TRACE("str", tout << "varNode matched one of strAst or substrAst. Continuing" << std::endl;); + + if (substrAst == varNode) { + bool strAstHasVal = false; + expr * strValue = get_eqc_value(strAst, strAstHasVal); + if (strAstHasVal) { + TRACE("str", tout << mk_pp(strAst, m) << " has constant eqc value " << mk_pp(strValue, m) << std::endl;); + if (strValue != strAst) { + litems.push_back(ctx.mk_eq_atom(strAst, strValue)); + } + zstring strConst; + u.str.is_string(strValue, strConst); + // iterate eqc (also eqc-to-be) of substr + for (expr_ref_vector::iterator itAst = willEqClass.begin(); itAst != willEqClass.end(); itAst++) { + bool counterEgFound = false; + if (u.str.is_concat(to_app(*itAst))) { + expr_ref_vector constList(m); + // get constant strings in concat + app * aConcat = to_app(*itAst); + get_const_str_asts_in_node(aConcat, constList); + for (expr_ref_vector::iterator cstItor = constList.begin(); + cstItor != constList.end(); cstItor++) { + zstring pieceStr; + u.str.is_string(*cstItor, pieceStr); + if (!strConst.contains(pieceStr)) { + TRACE("str", tout << "Inconsistency found!" << std::endl;); + counterEgFound = true; + if (aConcat != substrAst) { + litems.push_back(ctx.mk_eq_atom(substrAst, aConcat)); + } + expr_ref implyLHS(mk_and(litems), m); + expr_ref implyR(m.mk_not(boolVar), m); + assert_implication(implyLHS, implyR); + break; + } + } + } + if (counterEgFound) { + break; + } + } + } + } + } + } // varNode in contain_pair_idx_map + } + + bool theory_str::in_contain_idx_map(expr * n) { + return contain_pair_idx_map.find(n) != contain_pair_idx_map.end(); + } + + void theory_str::check_contain_by_eq_nodes(expr * n1, expr * n2) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + if (in_contain_idx_map(n1) && in_contain_idx_map(n2)) { + std::set >::iterator keysItor1 = contain_pair_idx_map[n1].begin(); + std::set >::iterator keysItor2; + + for (; keysItor1 != contain_pair_idx_map[n1].end(); keysItor1++) { + // keysItor1 is on set {<.., n1>, ..., , ...} + std::pair key1 = *keysItor1; + if (key1.first == n1 && key1.second == n2) { + expr_ref implyL(m); + expr_ref implyR(contain_pair_bool_map[key1], m); + if (n1 != n2) { + implyL = ctx.mk_eq_atom(n1, n2); + assert_implication(implyL, implyR); + } else { + assert_axiom(implyR); + } + } + + for (keysItor2 = contain_pair_idx_map[n2].begin(); + keysItor2 != contain_pair_idx_map[n2].end(); keysItor2++) { + // keysItor2 is on set {<.., n2>, ..., , ...} + std::pair key2 = *keysItor2; + // skip if the pair is eq + if (key1 == key2) { + continue; + } + + // *************************** + // Case 1: Contains(m, ...) /\ Contains(n, ) /\ m = n + // *************************** + if (key1.first == n1 && key2.first == n2) { + expr * subAst1 = key1.second; + expr * subAst2 = key2.second; + bool subAst1HasValue = false; + bool subAst2HasValue = false; + expr * subValue1 = get_eqc_value(subAst1, subAst1HasValue); + expr * subValue2 = get_eqc_value(subAst2, subAst2HasValue); + + TRACE("str", + tout << "(Contains " << mk_pp(n1, m) << " " << mk_pp(subAst1, m) << ")" << std::endl; + tout << "(Contains " << mk_pp(n2, m) << " " << mk_pp(subAst2, m) << ")" << std::endl; + if (subAst1 != subValue1) { + tout << mk_pp(subAst1, m) << " = " << mk_pp(subValue1, m) << std::endl; + } + if (subAst2 != subValue2) { + tout << mk_pp(subAst2, m) << " = " << mk_pp(subValue2, m) << std::endl; + } + ); + + if (subAst1HasValue && subAst2HasValue) { + expr_ref_vector litems1(m); + if (n1 != n2) { + litems1.push_back(ctx.mk_eq_atom(n1, n2)); + } + if (subValue1 != subAst1) { + litems1.push_back(ctx.mk_eq_atom(subAst1, subValue1)); + } + if (subValue2 != subAst2) { + litems1.push_back(ctx.mk_eq_atom(subAst2, subValue2)); + } + + zstring subConst1, subConst2; + u.str.is_string(subValue1, subConst1); + u.str.is_string(subValue2, subConst2); + expr_ref implyR(m); + if (subConst1 == subConst2) { + // key1.first = key2.first /\ key1.second = key2.second + // ==> (containPairBoolMap[key1] = containPairBoolMap[key2]) + implyR = ctx.mk_eq_atom(contain_pair_bool_map[key1], contain_pair_bool_map[key2]); + } else if (subConst1.contains(subConst2)) { + // key1.first = key2.first /\ Contains(key1.second, key2.second) + // ==> (containPairBoolMap[key1] --> containPairBoolMap[key2]) + implyR = rewrite_implication(contain_pair_bool_map[key1], contain_pair_bool_map[key2]); + } else if (subConst2.contains(subConst1)) { + // key1.first = key2.first /\ Contains(key2.second, key1.second) + // ==> (containPairBoolMap[key2] --> containPairBoolMap[key1]) + implyR = rewrite_implication(contain_pair_bool_map[key2], contain_pair_bool_map[key1]); + } + + if (implyR) { + if (litems1.empty()) { + assert_axiom(implyR); + } else { + assert_implication(mk_and(litems1), implyR); + } + } + } else { + expr_ref_vector subAst1Eqc(m); + expr_ref_vector subAst2Eqc(m); + collect_eq_nodes(subAst1, subAst1Eqc); + collect_eq_nodes(subAst2, subAst2Eqc); + + if (subAst1Eqc.contains(subAst2)) { + // ----------------------------------------------------------- + // * key1.first = key2.first /\ key1.second = key2.second + // --> containPairBoolMap[key1] = containPairBoolMap[key2] + // ----------------------------------------------------------- + expr_ref_vector litems2(m); + if (n1 != n2) { + litems2.push_back(ctx.mk_eq_atom(n1, n2)); + } + if (subAst1 != subAst2) { + litems2.push_back(ctx.mk_eq_atom(subAst1, subAst2)); + } + expr_ref implyR(ctx.mk_eq_atom(contain_pair_bool_map[key1], contain_pair_bool_map[key2]), m); + if (litems2.empty()) { + assert_axiom(implyR); + } else { + assert_implication(mk_and(litems2), implyR); + } + } else { + // ----------------------------------------------------------- + // * key1.first = key2.first + // check eqc(key1.second) and eqc(key2.second) + // ----------------------------------------------------------- + expr_ref_vector::iterator eqItorSub1 = subAst1Eqc.begin(); + for (; eqItorSub1 != subAst1Eqc.end(); eqItorSub1++) { + expr_ref_vector::iterator eqItorSub2 = subAst2Eqc.begin(); + for (; eqItorSub2 != subAst2Eqc.end(); eqItorSub2++) { + // ------------ + // key1.first = key2.first /\ containPairBoolMap[] + // ==> (containPairBoolMap[key1] --> containPairBoolMap[key2]) + // ------------ + { + expr_ref_vector litems3(m); + if (n1 != n2) { + litems3.push_back(ctx.mk_eq_atom(n1, n2)); + } + expr * eqSubVar1 = *eqItorSub1; + if (eqSubVar1 != subAst1) { + litems3.push_back(ctx.mk_eq_atom(subAst1, eqSubVar1)); + } + expr * eqSubVar2 = *eqItorSub2; + if (eqSubVar2 != subAst2) { + litems3.push_back(ctx.mk_eq_atom(subAst2, eqSubVar2)); + } + std::pair tryKey1 = std::make_pair(eqSubVar1, eqSubVar2); + if (contain_pair_bool_map.contains(tryKey1)) { + TRACE("str", tout << "(Contains " << mk_pp(eqSubVar1, m) << " " << mk_pp(eqSubVar2, m) << ")" << std::endl;); + litems3.push_back(contain_pair_bool_map[tryKey1]); + expr_ref implR(rewrite_implication(contain_pair_bool_map[key1], contain_pair_bool_map[key2]), m); + assert_implication(mk_and(litems3), implR); + } + } + // ------------ + // key1.first = key2.first /\ containPairBoolMap[] + // ==> (containPairBoolMap[key2] --> containPairBoolMap[key1]) + // ------------ + { + expr_ref_vector litems4(m); + if (n1 != n2) { + litems4.push_back(ctx.mk_eq_atom(n1, n2)); + } + expr * eqSubVar1 = *eqItorSub1; + if (eqSubVar1 != subAst1) { + litems4.push_back(ctx.mk_eq_atom(subAst1, eqSubVar1)); + } + expr * eqSubVar2 = *eqItorSub2; + if (eqSubVar2 != subAst2) { + litems4.push_back(ctx.mk_eq_atom(subAst2, eqSubVar2)); + } + std::pair tryKey2 = std::make_pair(eqSubVar2, eqSubVar1); + if (contain_pair_bool_map.contains(tryKey2)) { + TRACE("str", tout << "(Contains " << mk_pp(eqSubVar2, m) << " " << mk_pp(eqSubVar1, m) << ")" << std::endl;); + litems4.push_back(contain_pair_bool_map[tryKey2]); + expr_ref implR(rewrite_implication(contain_pair_bool_map[key2], contain_pair_bool_map[key1]), m); + assert_implication(mk_and(litems4), implR); + } + } + } + } + } + } + } + // *************************** + // Case 2: Contains(..., m) /\ Contains(... , n) /\ m = n + // *************************** + else if (key1.second == n1 && key2.second == n2) { + expr * str1 = key1.first; + expr * str2 = key2.first; + bool str1HasValue = false; + bool str2HasValue = false; + expr * strVal1 = get_eqc_value(str1, str1HasValue); + expr * strVal2 = get_eqc_value(str2, str2HasValue); + + TRACE("str", + tout << "(Contains " << mk_pp(str1, m) << " " << mk_pp(n1, m) << ")" << std::endl; + tout << "(Contains " << mk_pp(str2, m) << " " << mk_pp(n2, m) << ")" << std::endl; + if (str1 != strVal1) { + tout << mk_pp(str1, m) << " = " << mk_pp(strVal1, m) << std::endl; + } + if (str2 != strVal2) { + tout << mk_pp(str2, m) << " = " << mk_pp(strVal2, m) << std::endl; + } + ); + + if (str1HasValue && str2HasValue) { + expr_ref_vector litems1(m); + if (n1 != n2) { + litems1.push_back(ctx.mk_eq_atom(n1, n2)); + } + if (strVal1 != str1) { + litems1.push_back(ctx.mk_eq_atom(str1, strVal1)); + } + if (strVal2 != str2) { + litems1.push_back(ctx.mk_eq_atom(str2, strVal2)); + } + + zstring const1, const2; + u.str.is_string(strVal1, const1); + u.str.is_string(strVal2, const2); + expr_ref implyR(m); + + if (const1 == const2) { + // key1.second = key2.second /\ key1.first = key2.first + // ==> (containPairBoolMap[key1] = containPairBoolMap[key2]) + implyR = ctx.mk_eq_atom(contain_pair_bool_map[key1], contain_pair_bool_map[key2]); + } else if (const1.contains(const2)) { + // key1.second = key2.second /\ Contains(key1.first, key2.first) + // ==> (containPairBoolMap[key2] --> containPairBoolMap[key1]) + implyR = rewrite_implication(contain_pair_bool_map[key2], contain_pair_bool_map[key1]); + } else if (const2.contains(const1)) { + // key1.first = key2.first /\ Contains(key2.first, key1.first) + // ==> (containPairBoolMap[key1] --> containPairBoolMap[key2]) + implyR = rewrite_implication(contain_pair_bool_map[key1], contain_pair_bool_map[key2]); + } + + if (implyR) { + if (litems1.size() == 0) { + assert_axiom(implyR); + } else { + assert_implication(mk_and(litems1), implyR); + } + } + } + + else { + expr_ref_vector str1Eqc(m); + expr_ref_vector str2Eqc(m); + collect_eq_nodes(str1, str1Eqc); + collect_eq_nodes(str2, str2Eqc); + + if (str1Eqc.contains(str2)) { + // ----------------------------------------------------------- + // * key1.first = key2.first /\ key1.second = key2.second + // --> containPairBoolMap[key1] = containPairBoolMap[key2] + // ----------------------------------------------------------- + expr_ref_vector litems2(m); + if (n1 != n2) { + litems2.push_back(ctx.mk_eq_atom(n1, n2)); + } + if (str1 != str2) { + litems2.push_back(ctx.mk_eq_atom(str1, str2)); + } + expr_ref implyR(ctx.mk_eq_atom(contain_pair_bool_map[key1], contain_pair_bool_map[key2]), m); + if (litems2.empty()) { + assert_axiom(implyR); + } else { + assert_implication(mk_and(litems2), implyR); + } + } else { + // ----------------------------------------------------------- + // * key1.second = key2.second + // check eqc(key1.first) and eqc(key2.first) + // ----------------------------------------------------------- + expr_ref_vector::iterator eqItorStr1 = str1Eqc.begin(); + for (; eqItorStr1 != str1Eqc.end(); eqItorStr1++) { + expr_ref_vector::iterator eqItorStr2 = str2Eqc.begin(); + for (; eqItorStr2 != str2Eqc.end(); eqItorStr2++) { + { + expr_ref_vector litems3(m); + if (n1 != n2) { + litems3.push_back(ctx.mk_eq_atom(n1, n2)); + } + expr * eqStrVar1 = *eqItorStr1; + if (eqStrVar1 != str1) { + litems3.push_back(ctx.mk_eq_atom(str1, eqStrVar1)); + } + expr * eqStrVar2 = *eqItorStr2; + if (eqStrVar2 != str2) { + litems3.push_back(ctx.mk_eq_atom(str2, eqStrVar2)); + } + std::pair tryKey1 = std::make_pair(eqStrVar1, eqStrVar2); + if (contain_pair_bool_map.contains(tryKey1)) { + TRACE("str", tout << "(Contains " << mk_pp(eqStrVar1, m) << " " << mk_pp(eqStrVar2, m) << ")" << std::endl;); + litems3.push_back(contain_pair_bool_map[tryKey1]); + + // ------------ + // key1.second = key2.second /\ containPairBoolMap[] + // ==> (containPairBoolMap[key2] --> containPairBoolMap[key1]) + // ------------ + expr_ref implR(rewrite_implication(contain_pair_bool_map[key2], contain_pair_bool_map[key1]), m); + assert_implication(mk_and(litems3), implR); + } + } + + { + expr_ref_vector litems4(m); + if (n1 != n2) { + litems4.push_back(ctx.mk_eq_atom(n1, n2)); + } + expr * eqStrVar1 = *eqItorStr1; + if (eqStrVar1 != str1) { + litems4.push_back(ctx.mk_eq_atom(str1, eqStrVar1)); + } + expr *eqStrVar2 = *eqItorStr2; + if (eqStrVar2 != str2) { + litems4.push_back(ctx.mk_eq_atom(str2, eqStrVar2)); + } + std::pair tryKey2 = std::make_pair(eqStrVar2, eqStrVar1); + + if (contain_pair_bool_map.contains(tryKey2)) { + TRACE("str", tout << "(Contains " << mk_pp(eqStrVar2, m) << " " << mk_pp(eqStrVar1, m) << ")" << std::endl;); + litems4.push_back(contain_pair_bool_map[tryKey2]); + // ------------ + // key1.first = key2.first /\ containPairBoolMap[] + // ==> (containPairBoolMap[key1] --> containPairBoolMap[key2]) + // ------------ + expr_ref implR(rewrite_implication(contain_pair_bool_map[key1], contain_pair_bool_map[key2]), m); + assert_implication(mk_and(litems4), implR); + } + } + } + } + } + } + + } + } + + if (n1 == n2) { + break; + } + } + } // (in_contain_idx_map(n1) && in_contain_idx_map(n2)) + } + + void theory_str::check_contain_in_new_eq(expr * n1, expr * n2) { + if (contains_map.empty()) { + return; + } + + ast_manager & m = get_manager(); + TRACE("str", tout << "consistency check for contains wrt. " << mk_pp(n1, m) << " and " << mk_pp(n2, m) << std::endl;); + + expr_ref_vector willEqClass(m); + expr * constStrAst_1 = collect_eq_nodes(n1, willEqClass); + expr * constStrAst_2 = collect_eq_nodes(n2, willEqClass); + expr * constStrAst = (constStrAst_1 != NULL) ? constStrAst_1 : constStrAst_2; + + TRACE("str", tout << "eqc of n1 is {"; + for (expr_ref_vector::iterator it = willEqClass.begin(); it != willEqClass.end(); ++it) { + expr * el = *it; + tout << " " << mk_pp(el, m); + } + tout << std::endl; + if (constStrAst == NULL) { + tout << "constStrAst = NULL" << std::endl; + } else { + tout << "constStrAst = " << mk_pp(constStrAst, m) << std::endl; + } + ); + + // step 1: we may have constant values for Contains checks now + if (constStrAst != NULL) { + expr_ref_vector::iterator itAst = willEqClass.begin(); + for (; itAst != willEqClass.end(); itAst++) { + if (*itAst == constStrAst) { + continue; + } + check_contain_by_eqc_val(*itAst, constStrAst); + } + } else { + // no concrete value to be put in eqc, solely based on context + // Check here is used to detected the facts as follows: + // * known: contains(Z, Y) /\ Z = "abcdefg" /\ Y = M + // * new fact: M = concat(..., "jio", ...) + // Note that in this branch, either M or concat(..., "jio", ...) has a constant value + // So, only need to check + // * "EQC(M) U EQC(concat(..., "jio", ...))" as substr and + // * If strAst registered has an eqc constant in the context + // ------------------------------------------------------------- + expr_ref_vector::iterator itAst = willEqClass.begin(); + for (; itAst != willEqClass.end(); ++itAst) { + check_contain_by_substr(*itAst, willEqClass); + } + } + + // ------------------------------------------ + // step 2: check for b1 = contains(x, m), b2 = contains(y, n) + // (1) x = y /\ m = n ==> b1 = b2 + // (2) x = y /\ Contains(const(m), const(n)) ==> (b1 -> b2) + // (3) x = y /\ Contains(const(n), const(m)) ==> (b2 -> b1) + // (4) x = y /\ containPairBoolMap[] ==> (b1 -> b2) + // (5) x = y /\ containPairBoolMap[] ==> (b2 -> b1) + // (6) Contains(const(x), const(y)) /\ m = n ==> (b2 -> b1) + // (7) Contains(const(y), const(x)) /\ m = n ==> (b1 -> b2) + // (8) containPairBoolMap[] /\ m = n ==> (b2 -> b1) + // (9) containPairBoolMap[] /\ m = n ==> (b1 -> b2) + // ------------------------------------------ + + expr_ref_vector::iterator varItor1 = willEqClass.begin(); + for (; varItor1 != willEqClass.end(); ++varItor1) { + expr * varAst1 = *varItor1; + expr_ref_vector::iterator varItor2 = varItor1; + for (; varItor2 != willEqClass.end(); ++varItor2) { + expr * varAst2 = *varItor2; + check_contain_by_eq_nodes(varAst1, varAst2); + } + } + } + + expr * theory_str::dealias_node(expr * node, std::map & varAliasMap, std::map & concatAliasMap) { + if (variable_set.find(node) != variable_set.end()) { + return get_alias_index_ast(varAliasMap, node); + } else if (u.str.is_concat(to_app(node))) { + return get_alias_index_ast(concatAliasMap, node); + } + return node; + } + + void theory_str::get_grounded_concats(expr* node, std::map & varAliasMap, + std::map & concatAliasMap, std::map & varConstMap, + std::map & concatConstMap, std::map > & varEqConcatMap, + std::map, std::set > > & groundedMap) { + if (u.re.is_unroll(to_app(node))) { + return; + } + // ************************************************** + // first deAlias the node if it is a var or concat + // ************************************************** + node = dealias_node(node, varAliasMap, concatAliasMap); + + if (groundedMap.find(node) != groundedMap.end()) { + return; + } + + // haven't computed grounded concats for "node" (de-aliased) + // --------------------------------------------------------- + + context & ctx = get_context(); + + // const strings: node is de-aliased + if (u.str.is_string(node)) { + std::vector concatNodes; + concatNodes.push_back(node); + groundedMap[node][concatNodes].clear(); // no condition + } + // Concat functions + else if (u.str.is_concat(to_app(node))) { + // if "node" equals to a constant string, thenjust push the constant into the concat vector + // Again "node" has been de-aliased at the very beginning + if (concatConstMap.find(node) != concatConstMap.end()) { + std::vector concatNodes; + concatNodes.push_back(concatConstMap[node]); + groundedMap[node][concatNodes].clear(); + groundedMap[node][concatNodes].insert(ctx.mk_eq_atom(node, concatConstMap[node])); + } + // node doesn't have eq constant value. Process its children. + else { + // merge arg0 and arg1 + expr * arg0 = to_app(node)->get_arg(0); + expr * arg1 = to_app(node)->get_arg(1); + expr * arg0DeAlias = dealias_node(arg0, varAliasMap, concatAliasMap); + expr * arg1DeAlias = dealias_node(arg1, varAliasMap, concatAliasMap); + get_grounded_concats(arg0DeAlias, varAliasMap, concatAliasMap, varConstMap, concatConstMap, varEqConcatMap, groundedMap); + get_grounded_concats(arg1DeAlias, varAliasMap, concatAliasMap, varConstMap, concatConstMap, varEqConcatMap, groundedMap); + + std::map, std::set >::iterator arg0_grdItor = groundedMap[arg0DeAlias].begin(); + std::map, std::set >::iterator arg1_grdItor; + for (; arg0_grdItor != groundedMap[arg0DeAlias].end(); arg0_grdItor++) { + arg1_grdItor = groundedMap[arg1DeAlias].begin(); + for (; arg1_grdItor != groundedMap[arg1DeAlias].end(); arg1_grdItor++) { + std::vector ndVec; + ndVec.insert(ndVec.end(), arg0_grdItor->first.begin(), arg0_grdItor->first.end()); + int arg0VecSize = arg0_grdItor->first.size(); + int arg1VecSize = arg1_grdItor->first.size(); + if (arg0VecSize > 0 && arg1VecSize > 0 && u.str.is_string(arg0_grdItor->first[arg0VecSize - 1]) && u.str.is_string(arg1_grdItor->first[0])) { + ndVec.pop_back(); + ndVec.push_back(mk_concat(arg0_grdItor->first[arg0VecSize - 1], arg1_grdItor->first[0])); + for (int i = 1; i < arg1VecSize; i++) { + ndVec.push_back(arg1_grdItor->first[i]); + } + } else { + ndVec.insert(ndVec.end(), arg1_grdItor->first.begin(), arg1_grdItor->first.end()); + } + // only insert if we don't know "node = concat(ndVec)" since one set of condition leads to this is enough + if (groundedMap[node].find(ndVec) == groundedMap[node].end()) { + groundedMap[node][ndVec]; + if (arg0 != arg0DeAlias) { + groundedMap[node][ndVec].insert(ctx.mk_eq_atom(arg0, arg0DeAlias)); + } + groundedMap[node][ndVec].insert(arg0_grdItor->second.begin(), arg0_grdItor->second.end()); + + if (arg1 != arg1DeAlias) { + groundedMap[node][ndVec].insert(ctx.mk_eq_atom(arg1, arg1DeAlias)); + } + groundedMap[node][ndVec].insert(arg1_grdItor->second.begin(), arg1_grdItor->second.end()); + } + } + } + } + } + // string variables + else if (variable_set.find(node) != variable_set.end()) { + // deAliasedVar = Constant + if (varConstMap.find(node) != varConstMap.end()) { + std::vector concatNodes; + concatNodes.push_back(varConstMap[node]); + groundedMap[node][concatNodes].clear(); + groundedMap[node][concatNodes].insert(ctx.mk_eq_atom(node, varConstMap[node])); + } + // deAliasedVar = someConcat + else if (varEqConcatMap.find(node) != varEqConcatMap.end()) { + expr * eqConcat = varEqConcatMap[node].begin()->first; + expr * deAliasedEqConcat = dealias_node(eqConcat, varAliasMap, concatAliasMap); + get_grounded_concats(deAliasedEqConcat, varAliasMap, concatAliasMap, varConstMap, concatConstMap, varEqConcatMap, groundedMap); + + std::map, std::set >::iterator grdItor = groundedMap[deAliasedEqConcat].begin(); + for (; grdItor != groundedMap[deAliasedEqConcat].end(); grdItor++) { + std::vector ndVec; + ndVec.insert(ndVec.end(), grdItor->first.begin(), grdItor->first.end()); + // only insert if we don't know "node = concat(ndVec)" since one set of condition leads to this is enough + if (groundedMap[node].find(ndVec) == groundedMap[node].end()) { + // condition: node = deAliasedEqConcat + groundedMap[node][ndVec].insert(ctx.mk_eq_atom(node, deAliasedEqConcat)); + // appending conditions for "deAliasedEqConcat = CONCAT(ndVec)" + groundedMap[node][ndVec].insert(grdItor->second.begin(), grdItor->second.end()); + } + } + } + // node (has been de-aliased) != constant && node (has been de-aliased) != any concat + // just push in the deAliasedVar + else { + std::vector concatNodes; + concatNodes.push_back(node); + groundedMap[node][concatNodes]; + } + } + } + + void theory_str::print_grounded_concat(expr * node, std::map, std::set > > & groundedMap) { + ast_manager & m = get_manager(); + TRACE("str", tout << mk_pp(node, m) << std::endl;); + if (groundedMap.find(node) != groundedMap.end()) { + std::map, std::set >::iterator itor = groundedMap[node].begin(); + for (; itor != groundedMap[node].end(); ++itor) { + TRACE("str", + tout << "\t[grounded] "; + std::vector::const_iterator vIt = itor->first.begin(); + for (; vIt != itor->first.end(); ++vIt) { + tout << mk_pp(*vIt, m) << ", "; + } + tout << std::endl; + tout << "\t[condition] "; + std::set::iterator sIt = itor->second.begin(); + for (; sIt != itor->second.end(); sIt++) { + tout << mk_pp(*sIt, m) << ", "; + } + tout << std::endl; + ); + } + } else { + TRACE("str", tout << "not found" << std::endl;); + } + } + + bool theory_str::is_partial_in_grounded_concat(const std::vector & strVec, const std::vector & subStrVec) { + int strCnt = strVec.size(); + int subStrCnt = subStrVec.size(); + + if (strCnt == 0 || subStrCnt == 0) { + return false; + } + + // The assumption is that all consecutive constant strings are merged into one node + if (strCnt < subStrCnt) { + return false; + } + + if (subStrCnt == 1) { + zstring subStrVal; + if (u.str.is_string(subStrVec[0], subStrVal)) { + for (int i = 0; i < strCnt; i++) { + zstring strVal; + if (u.str.is_string(strVec[i], strVal)) { + if (strVal.contains(subStrVal)) { + return true; + } + } + } + } else { + for (int i = 0; i < strCnt; i++) { + if (strVec[i] == subStrVec[0]) { + return true; + } + } + } + return false; + } else { + for (int i = 0; i <= (strCnt - subStrCnt); i++) { + // The first node in subStrVect should be + // * constant: a suffix of a note in strVec[i] + // * variable: + bool firstNodesOK = true; + zstring subStrHeadVal; + if (u.str.is_string(subStrVec[0], subStrHeadVal)) { + zstring strHeadVal; + if (u.str.is_string(strVec[i], strHeadVal)) { + if (strHeadVal.length() >= subStrHeadVal.length()) { + zstring suffix = strHeadVal.extract(strHeadVal.length() - subStrHeadVal.length(), subStrHeadVal.length()); + if (suffix != subStrHeadVal) { + firstNodesOK = false; + } + } else { + firstNodesOK = false; + } + } else { + if (subStrVec[0] != strVec[i]) { + firstNodesOK = false; + } + } + } + if (!firstNodesOK) { + continue; + } + + // middle nodes + bool midNodesOK = true; + for (int j = 1; j < subStrCnt - 1; j++) { + if (subStrVec[j] != strVec[i + j]) { + midNodesOK = false; + break; + } + } + if (!midNodesOK) { + continue; + } + + // tail nodes + int tailIdx = i + subStrCnt - 1; + zstring subStrTailVal; + if (u.str.is_string(subStrVec[subStrCnt - 1], subStrTailVal)) { + zstring strTailVal; + if (u.str.is_string(strVec[tailIdx], strTailVal)) { + if (strTailVal.length() >= subStrTailVal.length()) { + zstring prefix = strTailVal.extract(0, subStrTailVal.length()); + if (prefix == subStrTailVal) { + return true; + } else { + continue; + } + } else { + continue; + } + } + } else { + if (subStrVec[subStrCnt - 1] == strVec[tailIdx]) { + return true; + } else { + continue; + } + } + } + return false; + } + } + + void theory_str::check_subsequence(expr* str, expr* strDeAlias, expr* subStr, expr* subStrDeAlias, expr* boolVar, + std::map, std::set > > & groundedMap) { + + context & ctx = get_context(); + ast_manager & m = get_manager(); + std::map, std::set >::iterator itorStr = groundedMap[strDeAlias].begin(); + std::map, std::set >::iterator itorSubStr; + for (; itorStr != groundedMap[strDeAlias].end(); itorStr++) { + itorSubStr = groundedMap[subStrDeAlias].begin(); + for (; itorSubStr != groundedMap[subStrDeAlias].end(); itorSubStr++) { + bool contain = is_partial_in_grounded_concat(itorStr->first, itorSubStr->first); + if (contain) { + expr_ref_vector litems(m); + if (str != strDeAlias) { + litems.push_back(ctx.mk_eq_atom(str, strDeAlias)); + } + if (subStr != subStrDeAlias) { + litems.push_back(ctx.mk_eq_atom(subStr, subStrDeAlias)); + } + + //litems.insert(itorStr->second.begin(), itorStr->second.end()); + //litems.insert(itorSubStr->second.begin(), itorSubStr->second.end()); + for (std::set::const_iterator i1 = itorStr->second.begin(); + i1 != itorStr->second.end(); ++i1) { + litems.push_back(*i1); + } + for (std::set::const_iterator i1 = itorSubStr->second.begin(); + i1 != itorSubStr->second.end(); ++i1) { + litems.push_back(*i1); + } + + expr_ref implyR(boolVar, m); + + if (litems.empty()) { + assert_axiom(implyR); + } else { + expr_ref implyL(mk_and(litems), m); + assert_implication(implyL, implyR); + } + + } + } + } + } + + void theory_str::compute_contains(std::map & varAliasMap, + std::map & concatAliasMap, std::map & varConstMap, + std::map & concatConstMap, std::map > & varEqConcatMap) { + std::map, std::set > > groundedMap; + theory_str_contain_pair_bool_map_t::iterator containItor = contain_pair_bool_map.begin(); + for (; containItor != contain_pair_bool_map.end(); containItor++) { + expr* containBoolVar = containItor->get_value(); + expr* str = containItor->get_key1(); + expr* subStr = containItor->get_key2(); + + expr* strDeAlias = dealias_node(str, varAliasMap, concatAliasMap); + expr* subStrDeAlias = dealias_node(subStr, varAliasMap, concatAliasMap); + + get_grounded_concats(strDeAlias, varAliasMap, concatAliasMap, varConstMap, concatConstMap, varEqConcatMap, groundedMap); + get_grounded_concats(subStrDeAlias, varAliasMap, concatAliasMap, varConstMap, concatConstMap, varEqConcatMap, groundedMap); + + // debugging + print_grounded_concat(strDeAlias, groundedMap); + print_grounded_concat(subStrDeAlias, groundedMap); + + check_subsequence(str, strDeAlias, subStr, subStrDeAlias, containBoolVar, groundedMap); + } + } + + bool theory_str::can_concat_eq_str(expr * concat, zstring& str) { + unsigned int strLen = str.length(); + if (u.str.is_concat(to_app(concat))) { + ptr_vector args; + get_nodes_in_concat(concat, args); + expr * ml_node = args[0]; + expr * mr_node = args[args.size() - 1]; + + zstring ml_str; + if (u.str.is_string(ml_node, ml_str)) { + unsigned int ml_len = ml_str.length(); + if (ml_len > strLen) { + return false; + } + unsigned int cLen = ml_len; + if (ml_str != str.extract(0, cLen)) { + return false; + } + } + + zstring mr_str; + if (u.str.is_string(mr_node, mr_str)) { + unsigned int mr_len = mr_str.length(); + if (mr_len > strLen) { + return false; + } + unsigned int cLen = mr_len; + if (mr_str != str.extract(strLen - cLen, cLen)) { + return false; + } + } + + unsigned int sumLen = 0; + for (unsigned int i = 0 ; i < args.size() ; i++) { + expr * oneArg = args[i]; + zstring arg_str; + if (u.str.is_string(oneArg, arg_str)) { + if (!str.contains(arg_str)) { + return false; + } + sumLen += arg_str.length(); + } + } + + if (sumLen > strLen) { + return false; + } + } + return true; + } + + bool theory_str::can_concat_eq_concat(expr * concat1, expr * concat2) { + if (u.str.is_concat(to_app(concat1)) && u.str.is_concat(to_app(concat2))) { + { + // Suppose concat1 = (Concat X Y) and concat2 = (Concat M N). + expr * concat1_mostL = getMostLeftNodeInConcat(concat1); + expr * concat2_mostL = getMostLeftNodeInConcat(concat2); + // if both X and M are constant strings, check whether they have the same prefix + zstring concat1_mostL_str, concat2_mostL_str; + if (u.str.is_string(concat1_mostL, concat1_mostL_str) && u.str.is_string(concat2_mostL, concat2_mostL_str)) { + unsigned int cLen = std::min(concat1_mostL_str.length(), concat2_mostL_str.length()); + if (concat1_mostL_str.extract(0, cLen) != concat2_mostL_str.extract(0, cLen)) { + return false; + } + } + } + + { + // Similarly, if both Y and N are constant strings, check whether they have the same suffix + expr * concat1_mostR = getMostRightNodeInConcat(concat1); + expr * concat2_mostR = getMostRightNodeInConcat(concat2); + zstring concat1_mostR_str, concat2_mostR_str; + if (u.str.is_string(concat1_mostR, concat1_mostR_str) && u.str.is_string(concat2_mostR, concat2_mostR_str)) { + unsigned int cLen = std::min(concat1_mostR_str.length(), concat2_mostR_str.length()); + if (concat1_mostR_str.extract(concat1_mostR_str.length() - cLen, cLen) != + concat2_mostR_str.extract(concat2_mostR_str.length() - cLen, cLen)) { + return false; + } + } + } + } + return true; + } + + /* + * Check whether n1 and n2 could be equal. + * Returns true if n1 could equal n2 (maybe), + * and false if n1 is definitely not equal to n2 (no). + */ + bool theory_str::can_two_nodes_eq(expr * n1, expr * n2) { + app * n1_curr = to_app(n1); + app * n2_curr = to_app(n2); + + // case 0: n1_curr is const string, n2_curr is const string + if (u.str.is_string(n1_curr) && u.str.is_string(n2_curr)) { + if (n1_curr != n2_curr) { + return false; + } + } + // case 1: n1_curr is concat, n2_curr is const string + else if (u.str.is_concat(n1_curr) && u.str.is_string(n2_curr)) { + zstring n2_curr_str; + u.str.is_string(n2_curr, n2_curr_str); + if (!can_concat_eq_str(n1_curr, n2_curr_str)) { + return false; + } + } + // case 2: n2_curr is concat, n1_curr is const string + else if (u.str.is_concat(n2_curr) && u.str.is_string(n1_curr)) { + zstring n1_curr_str; + u.str.is_string(n1_curr, n1_curr_str); + if (!can_concat_eq_str(n2_curr, n1_curr_str)) { + return false; + } + } + // case 3: both are concats + else if (u.str.is_concat(n1_curr) && u.str.is_concat(n2_curr)) { + if (!can_concat_eq_concat(n1_curr, n2_curr)) { + return false; + } + } + + return true; + } + + // was checkLength2ConstStr() in Z3str2 + // returns true if everything is OK, or false if inconsistency detected + // - note that these are different from the semantics in Z3str2 + bool theory_str::check_length_const_string(expr * n1, expr * constStr) { + ast_manager & mgr = get_manager(); + context & ctx = get_context(); + + zstring tmp; + u.str.is_string(constStr, tmp); + rational strLen(tmp.length()); + + if (u.str.is_concat(to_app(n1))) { + ptr_vector args; + expr_ref_vector items(mgr); + + get_nodes_in_concat(n1, args); + + rational sumLen(0); + for (unsigned int i = 0; i < args.size(); ++i) { + rational argLen; + bool argLen_exists = get_len_value(args[i], argLen); + if (argLen_exists) { + if (!u.str.is_string(args[i])) { + items.push_back(ctx.mk_eq_atom(mk_strlen(args[i]), mk_int(argLen))); + } + TRACE("str", tout << "concat arg: " << mk_pp(args[i], mgr) << " has len = " << argLen.to_string() << std::endl;); + sumLen += argLen; + if (sumLen > strLen) { + items.push_back(ctx.mk_eq_atom(n1, constStr)); + expr_ref toAssert(mgr.mk_not(mk_and(items)), mgr); + TRACE("str", tout << "inconsistent length: concat (len = " << sumLen << ") <==> string constant (len = " << strLen << ")" << std::endl;); + assert_axiom(toAssert); + return false; + } + } + } + } else { // !is_concat(n1) + rational oLen; + bool oLen_exists = get_len_value(n1, oLen); + if (oLen_exists && oLen != strLen) { + TRACE("str", tout << "inconsistent length: var (len = " << oLen << ") <==> string constant (len = " << strLen << ")" << std::endl;); + expr_ref l(ctx.mk_eq_atom(n1, constStr), mgr); + expr_ref r(ctx.mk_eq_atom(mk_strlen(n1), mk_strlen(constStr)), mgr); + assert_implication(l, r); + return false; + } + } + rational unused; + if (get_len_value(n1, unused) == false) { + expr_ref l(ctx.mk_eq_atom(n1, constStr), mgr); + expr_ref r(ctx.mk_eq_atom(mk_strlen(n1), mk_strlen(constStr)), mgr); + assert_implication(l, r); + } + return true; + } + + bool theory_str::check_length_concat_concat(expr * n1, expr * n2) { + context & ctx = get_context(); + ast_manager & mgr = get_manager(); + + ptr_vector concat1Args; + ptr_vector concat2Args; + get_nodes_in_concat(n1, concat1Args); + get_nodes_in_concat(n2, concat2Args); + + bool concat1LenFixed = true; + bool concat2LenFixed = true; + + expr_ref_vector items(mgr); + + rational sum1(0), sum2(0); + + for (unsigned int i = 0; i < concat1Args.size(); ++i) { + expr * oneArg = concat1Args[i]; + rational argLen; + bool argLen_exists = get_len_value(oneArg, argLen); + if (argLen_exists) { + sum1 += argLen; + if (!u.str.is_string(oneArg)) { + items.push_back(ctx.mk_eq_atom(mk_strlen(oneArg), mk_int(argLen))); + } + } else { + concat1LenFixed = false; + } + } + + for (unsigned int i = 0; i < concat2Args.size(); ++i) { + expr * oneArg = concat2Args[i]; + rational argLen; + bool argLen_exists = get_len_value(oneArg, argLen); + if (argLen_exists) { + sum2 += argLen; + if (!u.str.is_string(oneArg)) { + items.push_back(ctx.mk_eq_atom(mk_strlen(oneArg), mk_int(argLen))); + } + } else { + concat2LenFixed = false; + } + } + + items.push_back(ctx.mk_eq_atom(n1, n2)); + + bool conflict = false; + + if (concat1LenFixed && concat2LenFixed) { + if (sum1 != sum2) { + conflict = true; + } + } else if (!concat1LenFixed && concat2LenFixed) { + if (sum1 > sum2) { + conflict = true; + } + } else if (concat1LenFixed && !concat2LenFixed) { + if (sum1 < sum2) { + conflict = true; + } + } + + if (conflict) { + TRACE("str", tout << "inconsistent length detected in concat <==> concat" << std::endl;); + expr_ref toAssert(mgr.mk_not(mk_and(items)), mgr); + assert_axiom(toAssert); + return false; + } + return true; + } + + bool theory_str::check_length_concat_var(expr * concat, expr * var) { + context & ctx = get_context(); + ast_manager & mgr = get_manager(); + + rational varLen; + bool varLen_exists = get_len_value(var, varLen); + if (!varLen_exists) { + return true; + } else { + rational sumLen(0); + ptr_vector args; + expr_ref_vector items(mgr); + get_nodes_in_concat(concat, args); + for (unsigned int i = 0; i < args.size(); ++i) { + expr * oneArg = args[i]; + rational argLen; + bool argLen_exists = get_len_value(oneArg, argLen); + if (argLen_exists) { + if (!u.str.is_string(oneArg) && !argLen.is_zero()) { + items.push_back(ctx.mk_eq_atom(mk_strlen(oneArg), mk_int(argLen))); + } + sumLen += argLen; + if (sumLen > varLen) { + TRACE("str", tout << "inconsistent length detected in concat <==> var" << std::endl;); + items.push_back(ctx.mk_eq_atom(mk_strlen(var), mk_int(varLen))); + items.push_back(ctx.mk_eq_atom(concat, var)); + expr_ref toAssert(mgr.mk_not(mk_and(items)), mgr); + assert_axiom(toAssert); + return false; + } + } + } + return true; + } + } + + bool theory_str::check_length_var_var(expr * var1, expr * var2) { + context & ctx = get_context(); + ast_manager & mgr = get_manager(); + + rational var1Len, var2Len; + bool var1Len_exists = get_len_value(var1, var1Len); + bool var2Len_exists = get_len_value(var2, var2Len); + + if (var1Len_exists && var2Len_exists && var1Len != var2Len) { + TRACE("str", tout << "inconsistent length detected in var <==> var" << std::endl;); + expr_ref_vector items(mgr); + items.push_back(ctx.mk_eq_atom(mk_strlen(var1), mk_int(var1Len))); + items.push_back(ctx.mk_eq_atom(mk_strlen(var2), mk_int(var2Len))); + items.push_back(ctx.mk_eq_atom(var1, var2)); + expr_ref toAssert(mgr.mk_not(mk_and(items)), mgr); + assert_axiom(toAssert); + return false; + } + return true; + } + + // returns true if everything is OK, or false if inconsistency detected + // - note that these are different from the semantics in Z3str2 + bool theory_str::check_length_eq_var_concat(expr * n1, expr * n2) { + // n1 and n2 are not const string: either variable or concat + bool n1Concat = u.str.is_concat(to_app(n1)); + bool n2Concat = u.str.is_concat(to_app(n2)); + if (n1Concat && n2Concat) { + return check_length_concat_concat(n1, n2); + } + // n1 is concat, n2 is variable + else if (n1Concat && (!n2Concat)) { + return check_length_concat_var(n1, n2); + } + // n1 is variable, n2 is concat + else if ((!n1Concat) && n2Concat) { + return check_length_concat_var(n2, n1); + } + // n1 and n2 are both variables + else { + return check_length_var_var(n1, n2); + } + return true; + } + + // returns false if an inconsistency is detected, or true if no inconsistencies were found + // - note that these are different from the semantics of checkLengConsistency() in Z3str2 + bool theory_str::check_length_consistency(expr * n1, expr * n2) { + if (u.str.is_string(n1) && u.str.is_string(n2)) { + // consistency has already been checked in can_two_nodes_eq(). + return true; + } else if (u.str.is_string(n1) && (!u.str.is_string(n2))) { + return check_length_const_string(n2, n1); + } else if (u.str.is_string(n2) && (!u.str.is_string(n1))) { + return check_length_const_string(n1, n2); + } else { + // n1 and n2 are vars or concats + return check_length_eq_var_concat(n1, n2); + } + return true; + } + + // Modified signature: returns true if nothing was learned, or false if at least one axiom was asserted. + // (This is used for deferred consistency checking) + bool theory_str::check_concat_len_in_eqc(expr * concat) { + bool no_assertions = true; + + expr * eqc_n = concat; + do { + if (u.str.is_concat(to_app(eqc_n))) { + rational unused; + bool status = infer_len_concat(eqc_n, unused); + if (status) { + no_assertions = false; + } + } + eqc_n = get_eqc_next(eqc_n); + } while (eqc_n != concat); + + return no_assertions; + } + + // Convert a regular expression to an e-NFA using Thompson's construction + void nfa::convert_re(expr * e, unsigned & start, unsigned & end, seq_util & u) { + start = next_id(); + end = next_id(); + if (u.re.is_to_re(e)) { + app * a = to_app(e); + expr * arg_str = a->get_arg(0); + zstring str; + if (u.str.is_string(arg_str, str)) { + TRACE("str", tout << "build NFA for '" << str << "'" << "\n";); + /* + * For an n-character string, we make (n-1) intermediate states, + * labelled i_(0) through i_(n-2). + * Then we construct the following transitions: + * start --str[0]--> i_(0) --str[1]--> i_(1) --...--> i_(n-2) --str[n-1]--> final + */ + unsigned last = start; + for (int i = 0; i <= ((int)str.length()) - 2; ++i) { + unsigned i_state = next_id(); + make_transition(last, str[i], i_state); + TRACE("str", tout << "string transition " << last << "--" << str[i] << "--> " << i_state << "\n";); + last = i_state; + } + make_transition(last, str[(str.length() - 1)], end); + TRACE("str", tout << "string transition " << last << "--" << str[(str.length() - 1)] << "--> " << end << "\n";); + } else { + TRACE("str", tout << "invalid string constant in Str2Reg" << std::endl;); + m_valid = false; + return; + } + } else if (u.re.is_concat(e)){ + app * a = to_app(e); + expr * re1 = a->get_arg(0); + expr * re2 = a->get_arg(1); + unsigned start1, end1; + convert_re(re1, start1, end1, u); + unsigned start2, end2; + convert_re(re2, start2, end2, u); + // start --e--> start1 --...--> end1 --e--> start2 --...--> end2 --e--> end + make_epsilon_move(start, start1); + make_epsilon_move(end1, start2); + make_epsilon_move(end2, end); + TRACE("str", tout << "concat NFA: start = " << start << ", end = " << end << std::endl;); + } else if (u.re.is_union(e)) { + app * a = to_app(e); + expr * re1 = a->get_arg(0); + expr * re2 = a->get_arg(1); + unsigned start1, end1; + convert_re(re1, start1, end1, u); + unsigned start2, end2; + convert_re(re2, start2, end2, u); + + // start --e--> start1 ; start --e--> start2 + // end1 --e--> end ; end2 --e--> end + make_epsilon_move(start, start1); + make_epsilon_move(start, start2); + make_epsilon_move(end1, end); + make_epsilon_move(end2, end); + TRACE("str", tout << "union NFA: start = " << start << ", end = " << end << std::endl;); + } else if (u.re.is_star(e)) { + app * a = to_app(e); + expr * subex = a->get_arg(0); + unsigned start_subex, end_subex; + convert_re(subex, start_subex, end_subex, u); + // start --e--> start_subex, start --e--> end + // end_subex --e--> start_subex, end_subex --e--> end + make_epsilon_move(start, start_subex); + make_epsilon_move(start, end); + make_epsilon_move(end_subex, start_subex); + make_epsilon_move(end_subex, end); + TRACE("str", tout << "star NFA: start = " << start << ", end = " << end << std::endl;); + } else if (u.re.is_range(e)) { + // range('a', 'z') + // start --'a'--> end + // start --'b'--> end + // ... + // start --'z'--> end + app * a = to_app(e); + expr * c1 = a->get_arg(0); + expr * c2 = a->get_arg(1); + zstring s_c1, s_c2; + u.str.is_string(c1, s_c1); + u.str.is_string(c2, s_c2); + + unsigned int id1 = s_c1[0]; + unsigned int id2 = s_c2[0]; + if (id1 > id2) { + unsigned int tmp = id1; + id1 = id2; + id2 = tmp; + } + + for (unsigned int i = id1; i <= id2; ++i) { + char ch = (char)i; + make_transition(start, ch, end); + } + + TRACE("str", tout << "range NFA: start = " << start << ", end = " << end << std::endl;); + } else { + TRACE("str", tout << "invalid regular expression" << std::endl;); + m_valid = false; + return; + } + } + + void nfa::epsilon_closure(unsigned start, std::set & closure) { + std::deque worklist; + closure.insert(start); + worklist.push_back(start); + + while(!worklist.empty()) { + unsigned state = worklist.front(); + worklist.pop_front(); + if (epsilon_map.find(state) != epsilon_map.end()) { + for (std::set::iterator it = epsilon_map[state].begin(); + it != epsilon_map[state].end(); ++it) { + unsigned new_state = *it; + if (closure.find(new_state) == closure.end()) { + closure.insert(new_state); + worklist.push_back(new_state); + } + } + } + } + } + + bool nfa::matches(zstring input) { + /* + * Keep a set of all states the NFA can currently be in. + * Initially this is the e-closure of m_start_state + * For each character A in the input string, + * the set of next states contains + * all states in transition_map[S][A] for each S in current_states, + * and all states in epsilon_map[S] for each S in current_states. + * After consuming the entire input string, + * the match is successful iff current_states contains m_end_state. + */ + std::set current_states; + epsilon_closure(m_start_state, current_states); + for (unsigned i = 0; i < input.length(); ++i) { + char A = (char)input[i]; + std::set next_states; + for (std::set::iterator it = current_states.begin(); + it != current_states.end(); ++it) { + unsigned S = *it; + // check transition_map + if (transition_map[S].find(A) != transition_map[S].end()) { + next_states.insert(transition_map[S][A]); + } + } + + // take e-closure over next_states to compute the actual next_states + std::set epsilon_next_states; + for (std::set::iterator it = next_states.begin(); it != next_states.end(); ++it) { + unsigned S = *it; + std::set closure; + epsilon_closure(S, closure); + epsilon_next_states.insert(closure.begin(), closure.end()); + } + current_states = epsilon_next_states; + } + if (current_states.find(m_end_state) != current_states.end()) { + return true; + } else { + return false; + } + } + + void theory_str::check_regex_in(expr * nn1, expr * nn2) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + expr_ref_vector eqNodeSet(m); + + expr * constStr_1 = collect_eq_nodes(nn1, eqNodeSet); + expr * constStr_2 = collect_eq_nodes(nn2, eqNodeSet); + expr * constStr = (constStr_1 != NULL) ? constStr_1 : constStr_2; + + if (constStr == NULL) { + return; + } else { + expr_ref_vector::iterator itor = eqNodeSet.begin(); + for (; itor != eqNodeSet.end(); itor++) { + if (regex_in_var_reg_str_map.find(*itor) != regex_in_var_reg_str_map.end()) { + std::set::iterator strItor = regex_in_var_reg_str_map[*itor].begin(); + for (; strItor != regex_in_var_reg_str_map[*itor].end(); strItor++) { + zstring regStr = *strItor; + zstring constStrValue; + u.str.is_string(constStr, constStrValue); + std::pair key1 = std::make_pair(*itor, regStr); + if (regex_in_bool_map.find(key1) != regex_in_bool_map.end()) { + expr * boolVar = regex_in_bool_map[key1]; // actually the RegexIn term + app * a_regexIn = to_app(boolVar); + expr * regexTerm = a_regexIn->get_arg(1); + + // TODO figure out regex NFA stuff + if (regex_nfa_cache.find(regexTerm) == regex_nfa_cache.end()) { + TRACE("str", tout << "regex_nfa_cache: cache miss" << std::endl;); + regex_nfa_cache[regexTerm] = nfa(u, regexTerm); + } else { + TRACE("str", tout << "regex_nfa_cache: cache hit" << std::endl;); + } + + nfa regexNFA = regex_nfa_cache[regexTerm]; + ENSURE(regexNFA.is_valid()); + bool matchRes = regexNFA.matches(constStrValue); + + TRACE("str", tout << mk_pp(*itor, m) << " in " << regStr << " : " << (matchRes ? "yes" : "no") << std::endl;); + + expr_ref implyL(ctx.mk_eq_atom(*itor, constStr), m); + if (matchRes) { + assert_implication(implyL, boolVar); + } else { + assert_implication(implyL, m.mk_not(boolVar)); + } + } + } + } + } + } + } + + /* + * strArgmt::solve_concat_eq_str() + * Solve concatenations of the form: + * const == Concat(const, X) + * const == Concat(X, const) + */ + void theory_str::solve_concat_eq_str(expr * concat, expr * str) { + ast_manager & m = get_manager(); + context & ctx = get_context(); + + TRACE("str", tout << mk_ismt2_pp(concat, m) << " == " << mk_ismt2_pp(str, m) << std::endl;); + + zstring const_str; + if (u.str.is_concat(to_app(concat)) && u.str.is_string(to_app(str), const_str)) { + app * a_concat = to_app(concat); + SASSERT(a_concat->get_num_args() == 2); + expr * a1 = a_concat->get_arg(0); + expr * a2 = a_concat->get_arg(1); + + if (const_str.empty()) { + TRACE("str", tout << "quick path: concat == \"\"" << std::endl;); + // assert the following axiom: + // ( (Concat a1 a2) == "" ) -> ( (a1 == "") AND (a2 == "") ) + + + expr_ref premise(ctx.mk_eq_atom(concat, str), m); + expr_ref c1(ctx.mk_eq_atom(a1, str), m); + expr_ref c2(ctx.mk_eq_atom(a2, str), m); + expr_ref conclusion(m.mk_and(c1, c2), m); + assert_implication(premise, conclusion); + + return; + } + bool arg1_has_eqc_value = false; + bool arg2_has_eqc_value = false; + expr * arg1 = get_eqc_value(a1, arg1_has_eqc_value); + expr * arg2 = get_eqc_value(a2, arg2_has_eqc_value); + expr_ref newConcat(m); + if (arg1 != a1 || arg2 != a2) { + TRACE("str", tout << "resolved concat argument(s) to eqc string constants" << std::endl;); + int iPos = 0; + expr_ref_vector item1(m); + if (a1 != arg1) { + item1.push_back(ctx.mk_eq_atom(a1, arg1)); + iPos += 1; + } + if (a2 != arg2) { + item1.push_back(ctx.mk_eq_atom(a2, arg2)); + iPos += 1; + } + expr_ref implyL1(mk_and(item1), m); + newConcat = mk_concat(arg1, arg2); + if (newConcat != str) { + expr_ref implyR1(ctx.mk_eq_atom(concat, newConcat), m); + assert_implication(implyL1, implyR1); + } + } else { + newConcat = concat; + } + if (newConcat == str) { + return; + } + if (!u.str.is_concat(to_app(newConcat))) { + return; + } + if (arg1_has_eqc_value && arg2_has_eqc_value) { + // Case 1: Concat(const, const) == const + TRACE("str", tout << "Case 1: Concat(const, const) == const" << std::endl;); + zstring arg1_str, arg2_str; + u.str.is_string(arg1, arg1_str); + u.str.is_string(arg2, arg2_str); + + zstring result_str = arg1_str + arg2_str; + if (result_str != const_str) { + // Inconsistency + TRACE("str", tout << "inconsistency detected: \"" + << arg1_str << "\" + \"" << arg2_str << + "\" != \"" << const_str << "\"" << "\n";); + expr_ref equality(ctx.mk_eq_atom(concat, str), m); + expr_ref diseq(m.mk_not(equality), m); + assert_axiom(diseq); + return; + } + } else if (!arg1_has_eqc_value && arg2_has_eqc_value) { + // Case 2: Concat(var, const) == const + TRACE("str", tout << "Case 2: Concat(var, const) == const" << std::endl;); + zstring arg2_str; + u.str.is_string(arg2, arg2_str); + unsigned int resultStrLen = const_str.length(); + unsigned int arg2StrLen = arg2_str.length(); + if (resultStrLen < arg2StrLen) { + // Inconsistency + TRACE("str", tout << "inconsistency detected: \"" + << arg2_str << + "\" is longer than \"" << const_str << "\"," + << " so cannot be concatenated with anything to form it" << "\n";); + expr_ref equality(ctx.mk_eq_atom(newConcat, str), m); + expr_ref diseq(m.mk_not(equality), m); + assert_axiom(diseq); + return; + } else { + int varStrLen = resultStrLen - arg2StrLen; + zstring firstPart = const_str.extract(0, varStrLen); + zstring secondPart = const_str.extract(varStrLen, arg2StrLen); + if (arg2_str != secondPart) { + // Inconsistency + TRACE("str", tout << "inconsistency detected: " + << "suffix of concatenation result expected \"" << secondPart << "\", " + << "actually \"" << arg2_str << "\"" + << "\n";); + expr_ref equality(ctx.mk_eq_atom(newConcat, str), m); + expr_ref diseq(m.mk_not(equality), m); + assert_axiom(diseq); + return; + } else { + expr_ref tmpStrConst(mk_string(firstPart), m); + expr_ref premise(ctx.mk_eq_atom(newConcat, str), m); + expr_ref conclusion(ctx.mk_eq_atom(arg1, tmpStrConst), m); + assert_implication(premise, conclusion); + return; + } + } + } else if (arg1_has_eqc_value && !arg2_has_eqc_value) { + // Case 3: Concat(const, var) == const + TRACE("str", tout << "Case 3: Concat(const, var) == const" << std::endl;); + zstring arg1_str; + u.str.is_string(arg1, arg1_str); + unsigned int resultStrLen = const_str.length(); + unsigned int arg1StrLen = arg1_str.length(); + if (resultStrLen < arg1StrLen) { + // Inconsistency + TRACE("str", tout << "inconsistency detected: \"" + << arg1_str << + "\" is longer than \"" << const_str << "\"," + << " so cannot be concatenated with anything to form it" << "\n";); + expr_ref equality(ctx.mk_eq_atom(newConcat, str), m); + expr_ref diseq(m.mk_not(equality), m); + assert_axiom(diseq); + return; + } else { + int varStrLen = resultStrLen - arg1StrLen; + zstring firstPart = const_str.extract(0, arg1StrLen); + zstring secondPart = const_str.extract(arg1StrLen, varStrLen); + if (arg1_str != firstPart) { + // Inconsistency + TRACE("str", tout << "inconsistency detected: " + << "prefix of concatenation result expected \"" << secondPart << "\", " + << "actually \"" << arg1_str << "\"" + << "\n";); + expr_ref equality(ctx.mk_eq_atom(newConcat, str), m); + expr_ref diseq(m.mk_not(equality), m); + assert_axiom(diseq); + return; + } else { + expr_ref tmpStrConst(mk_string(secondPart), m); + expr_ref premise(ctx.mk_eq_atom(newConcat, str), m); + expr_ref conclusion(ctx.mk_eq_atom(arg2, tmpStrConst), m); + assert_implication(premise, conclusion); + return; + } + } + } else { + // Case 4: Concat(var, var) == const + TRACE("str", tout << "Case 4: Concat(var, var) == const" << std::endl;); + if (eval_concat(arg1, arg2) == NULL) { + rational arg1Len, arg2Len; + bool arg1Len_exists = get_len_value(arg1, arg1Len); + bool arg2Len_exists = get_len_value(arg2, arg2Len); + rational concatStrLen((unsigned)const_str.length()); + if (arg1Len_exists || arg2Len_exists) { + expr_ref ax_l1(ctx.mk_eq_atom(concat, str), m); + expr_ref ax_l2(m); + zstring prefixStr, suffixStr; + if (arg1Len_exists) { + if (arg1Len.is_neg()) { + TRACE("str", tout << "length conflict: arg1Len = " << arg1Len << ", concatStrLen = " << concatStrLen << std::endl;); + expr_ref toAssert(m_autil.mk_ge(mk_strlen(arg1), mk_int(0)), m); + assert_axiom(toAssert); + return; + } else if (arg1Len > concatStrLen) { + TRACE("str", tout << "length conflict: arg1Len = " << arg1Len << ", concatStrLen = " << concatStrLen << std::endl;); + expr_ref ax_r1(m_autil.mk_le(mk_strlen(arg1), mk_int(concatStrLen)), m); + assert_implication(ax_l1, ax_r1); + return; + } + + prefixStr = const_str.extract(0, arg1Len.get_unsigned()); + rational concat_minus_arg1 = concatStrLen - arg1Len; + suffixStr = const_str.extract(arg1Len.get_unsigned(), concat_minus_arg1.get_unsigned()); + ax_l2 = ctx.mk_eq_atom(mk_strlen(arg1), mk_int(arg1Len)); + } else { + // arg2's length is available + if (arg2Len.is_neg()) { + TRACE("str", tout << "length conflict: arg2Len = " << arg2Len << ", concatStrLen = " << concatStrLen << std::endl;); + expr_ref toAssert(m_autil.mk_ge(mk_strlen(arg2), mk_int(0)), m); + assert_axiom(toAssert); + return; + } else if (arg2Len > concatStrLen) { + TRACE("str", tout << "length conflict: arg2Len = " << arg2Len << ", concatStrLen = " << concatStrLen << std::endl;); + expr_ref ax_r1(m_autil.mk_le(mk_strlen(arg2), mk_int(concatStrLen)), m); + assert_implication(ax_l1, ax_r1); + return; + } + + rational concat_minus_arg2 = concatStrLen - arg2Len; + prefixStr = const_str.extract(0, concat_minus_arg2.get_unsigned()); + suffixStr = const_str.extract(concat_minus_arg2.get_unsigned(), arg2Len.get_unsigned()); + ax_l2 = ctx.mk_eq_atom(mk_strlen(arg2), mk_int(arg2Len)); + } + // consistency check + if (u.str.is_concat(to_app(arg1)) && !can_concat_eq_str(arg1, prefixStr)) { + expr_ref ax_r(m.mk_not(ax_l2), m); + assert_implication(ax_l1, ax_r); + return; + } + if (u.str.is_concat(to_app(arg2)) && !can_concat_eq_str(arg2, suffixStr)) { + expr_ref ax_r(m.mk_not(ax_l2), m); + assert_implication(ax_l1, ax_r); + return; + } + expr_ref_vector r_items(m); + r_items.push_back(ctx.mk_eq_atom(arg1, mk_string(prefixStr))); + r_items.push_back(ctx.mk_eq_atom(arg2, mk_string(suffixStr))); + if (!arg1Len_exists) { + r_items.push_back(ctx.mk_eq_atom(mk_strlen(arg1), mk_int(prefixStr.length()))); + } + if (!arg2Len_exists) { + r_items.push_back(ctx.mk_eq_atom(mk_strlen(arg2), mk_int(suffixStr.length()))); + } + expr_ref lhs(m.mk_and(ax_l1, ax_l2), m); + expr_ref rhs(mk_and(r_items), m); + assert_implication(lhs, rhs); + } else { /* ! (arg1Len != 1 || arg2Len != 1) */ + expr_ref xorFlag(m); + std::pair key1(arg1, arg2); + std::pair key2(arg2, arg1); + + // check the entries in this map to make sure they're still in scope + // before we use them. + + std::map, std::map >::iterator entry1 = varForBreakConcat.find(key1); + std::map, std::map >::iterator entry2 = varForBreakConcat.find(key2); + + bool entry1InScope; + if (entry1 == varForBreakConcat.end()) { + TRACE("str", tout << "key1 no entry" << std::endl;); + entry1InScope = false; + } else { + // OVERRIDE. + entry1InScope = true; + TRACE("str", tout << "key1 entry" << std::endl;); + /* + if (internal_variable_set.find((entry1->second)[0]) == internal_variable_set.end()) { + TRACE("str", tout << "key1 entry not in scope" << std::endl;); + entry1InScope = false; + } else { + TRACE("str", tout << "key1 entry in scope" << std::endl;); + entry1InScope = true; + } + */ + } + + bool entry2InScope; + if (entry2 == varForBreakConcat.end()) { + TRACE("str", tout << "key2 no entry" << std::endl;); + entry2InScope = false; + } else { + // OVERRIDE. + entry2InScope = true; + TRACE("str", tout << "key2 entry" << std::endl;); + /* + if (internal_variable_set.find((entry2->second)[0]) == internal_variable_set.end()) { + TRACE("str", tout << "key2 entry not in scope" << std::endl;); + entry2InScope = false; + } else { + TRACE("str", tout << "key2 entry in scope" << std::endl;); + entry2InScope = true; + } + */ + } + + TRACE("str", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl + << "entry 2 " << (entry2InScope ? "in scope" : "not in scope") << std::endl;); + + if (!entry1InScope && !entry2InScope) { + xorFlag = mk_internal_xor_var(); + varForBreakConcat[key1][0] = xorFlag; + } else if (entry1InScope) { + xorFlag = varForBreakConcat[key1][0]; + } else { // entry2InScope + xorFlag = varForBreakConcat[key2][0]; + } + + int concatStrLen = const_str.length(); + int and_count = 1; + + expr_ref_vector arrangement_disjunction(m); + + for (int i = 0; i < concatStrLen + 1; ++i) { + expr_ref_vector and_items(m); + zstring prefixStr = const_str.extract(0, i); + zstring suffixStr = const_str.extract(i, concatStrLen - i); + // skip invalid options + if (u.str.is_concat(to_app(arg1)) && !can_concat_eq_str(arg1, prefixStr)) { + continue; + } + if (u.str.is_concat(to_app(arg2)) && !can_concat_eq_str(arg2, suffixStr)) { + continue; + } + + expr_ref prefixAst(mk_string(prefixStr), m); + expr_ref arg1_eq (ctx.mk_eq_atom(arg1, prefixAst), m); + and_items.push_back(arg1_eq); + and_count += 1; + + expr_ref suffixAst(mk_string(suffixStr), m); + expr_ref arg2_eq (ctx.mk_eq_atom(arg2, suffixAst), m); + and_items.push_back(arg2_eq); + and_count += 1; + + arrangement_disjunction.push_back(mk_and(and_items)); + } + + expr_ref implyL(ctx.mk_eq_atom(concat, str), m); + expr_ref implyR1(m); + if (arrangement_disjunction.empty()) { + // negate + expr_ref concat_eq_str(ctx.mk_eq_atom(concat, str), m); + expr_ref negate_ast(m.mk_not(concat_eq_str), m); + assert_axiom(negate_ast); + } else { + implyR1 = mk_or(arrangement_disjunction); + if (m_params.m_StrongArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom(implyL, implyR1), m); + assert_axiom(ax_strong); + } else { + assert_implication(implyL, implyR1); + } + generate_mutual_exclusion(arrangement_disjunction); + } + } /* (arg1Len != 1 || arg2Len != 1) */ + } /* if (Concat(arg1, arg2) == NULL) */ + } + } + } + + expr_ref theory_str::set_up_finite_model_test(expr * lhs, expr * rhs) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + TRACE("str", tout << "activating finite model testing for overlapping concats " + << mk_pp(lhs, m) << " and " << mk_pp(rhs, m) << std::endl;); + std::map concatMap; + std::map unrollMap; + std::map varMap; + classify_ast_by_type(lhs, varMap, concatMap, unrollMap); + classify_ast_by_type(rhs, varMap, concatMap, unrollMap); + TRACE("str", tout << "found vars:"; + for (std::map::iterator it = varMap.begin(); it != varMap.end(); ++it) { + tout << " " << mk_pp(it->first, m); + } + tout << std::endl; + ); + + expr_ref testvar(mk_str_var("finiteModelTest"), m); + m_trail.push_back(testvar); + ptr_vector varlist; + + for (std::map::iterator it = varMap.begin(); it != varMap.end(); ++it) { + expr * v = it->first; + varlist.push_back(v); + } + + // make things easy for the core wrt. testvar + expr_ref t1(ctx.mk_eq_atom(testvar, mk_string("")), m); + expr_ref t_yes(ctx.mk_eq_atom(testvar, mk_string("yes")), m); + expr_ref testvaraxiom(m.mk_or(t1, t_yes), m); + assert_axiom(testvaraxiom); + + finite_model_test_varlists.insert(testvar, varlist); + m_trail_stack.push(insert_obj_map >(finite_model_test_varlists, testvar) ); + return t_yes; + } + + void theory_str::finite_model_test(expr * testvar, expr * str) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + zstring s; + if (!u.str.is_string(str, s)) return; + if (s == "yes") { + TRACE("str", tout << "start finite model test for " << mk_pp(testvar, m) << std::endl;); + ptr_vector & vars = finite_model_test_varlists[testvar]; + for (ptr_vector::iterator it = vars.begin(); it != vars.end(); ++it) { + expr * v = *it; + bool v_has_eqc = false; + get_eqc_value(v, v_has_eqc); + if (v_has_eqc) { + TRACE("str", tout << "variable " << mk_pp(v,m) << " already equivalent to a string constant" << std::endl;); + continue; + } + // check for any sort of existing length tester we might interfere with + if (m_params.m_UseBinarySearch) { + if (binary_search_len_tester_stack.contains(v) && !binary_search_len_tester_stack[v].empty()) { + TRACE("str", tout << "already found existing length testers for " << mk_pp(v, m) << std::endl;); + continue; + } else { + // start binary search as normal + expr_ref implLhs(ctx.mk_eq_atom(testvar, str), m); + expr_ref implRhs(binary_search_length_test(v, NULL, ""), m); + assert_implication(implLhs, implRhs); + } + } else { + bool map_effectively_empty = false; + if (!fvar_len_count_map.contains(v)) { + map_effectively_empty = true; + } + + if (!map_effectively_empty) { + map_effectively_empty = true; + ptr_vector indicator_set = fvar_lenTester_map[v]; + for (ptr_vector::iterator it = indicator_set.begin(); it != indicator_set.end(); ++it) { + expr * indicator = *it; + if (internal_variable_set.find(indicator) != internal_variable_set.end()) { + map_effectively_empty = false; + break; + } + } + } + + if (map_effectively_empty) { + TRACE("str", tout << "no existing length testers for " << mk_pp(v, m) << std::endl;); + rational v_len; + rational v_lower_bound; + rational v_upper_bound; + expr_ref vLengthExpr(mk_strlen(v), m); + if (get_len_value(v, v_len)) { + TRACE("str", tout << "length = " << v_len.to_string() << std::endl;); + v_lower_bound = v_len; + v_upper_bound = v_len; + } else { + bool lower_bound_exists = lower_bound(vLengthExpr, v_lower_bound); + bool upper_bound_exists = upper_bound(vLengthExpr, v_upper_bound); + TRACE("str", tout << "bounds = [" << (lower_bound_exists?v_lower_bound.to_string():"?") + << ".." << (upper_bound_exists?v_upper_bound.to_string():"?") << "]" << std::endl;); + + // make sure the bounds are non-negative + if (lower_bound_exists && v_lower_bound.is_neg()) { + v_lower_bound = rational::zero(); + } + if (upper_bound_exists && v_upper_bound.is_neg()) { + v_upper_bound = rational::zero(); + } + + if (lower_bound_exists && upper_bound_exists) { + // easiest case. we will search within these bounds + } else if (upper_bound_exists && !lower_bound_exists) { + // search between 0 and the upper bound + v_lower_bound == rational::zero(); + } else if (lower_bound_exists && !upper_bound_exists) { + // check some finite portion of the search space + v_upper_bound = v_lower_bound + rational(10); + } else { + // no bounds information + v_lower_bound = rational::zero(); + v_upper_bound = v_lower_bound + rational(10); + } + } + // now create a fake length tester over this finite disjunction of lengths + + fvar_len_count_map[v] = 1; + unsigned int testNum = fvar_len_count_map[v]; + + expr_ref indicator(mk_internal_lenTest_var(v, testNum), m); + SASSERT(indicator); + m_trail.push_back(indicator); + + fvar_lenTester_map[v].shrink(0); + fvar_lenTester_map[v].push_back(indicator); + lenTester_fvar_map[indicator] = v; + + expr_ref_vector orList(m); + expr_ref_vector andList(m); + + for (rational l = v_lower_bound; l <= v_upper_bound; l += rational::one()) { + zstring lStr = zstring(l.to_string().c_str()); + expr_ref str_indicator(mk_string(lStr), m); + expr_ref or_expr(ctx.mk_eq_atom(indicator, str_indicator), m); + orList.push_back(or_expr); + expr_ref and_expr(ctx.mk_eq_atom(or_expr, ctx.mk_eq_atom(vLengthExpr, m_autil.mk_numeral(l, true))), m); + andList.push_back(and_expr); + } + andList.push_back(mk_or(orList)); + expr_ref implLhs(ctx.mk_eq_atom(testvar, str), m); + expr_ref implRhs(mk_and(andList), m); + assert_implication(implLhs, implRhs); + } else { + TRACE("str", tout << "already found existing length testers for " << mk_pp(v, m) << std::endl;); + continue; + } + } + } // foreach (v in vars) + } // (s == "yes") + } + + void theory_str::more_len_tests(expr * lenTester, zstring lenTesterValue) { + ast_manager & m = get_manager(); + if (lenTester_fvar_map.contains(lenTester)) { + expr * fVar = lenTester_fvar_map[lenTester]; + expr * toAssert = gen_len_val_options_for_free_var(fVar, lenTester, lenTesterValue); + TRACE("str", tout << "asserting more length tests for free variable " << mk_ismt2_pp(fVar, m) << std::endl;); + if (toAssert != NULL) { + assert_axiom(toAssert); + } + } + } + + void theory_str::more_value_tests(expr * valTester, zstring valTesterValue) { + ast_manager & m = get_manager(); + + expr * fVar = valueTester_fvar_map[valTester]; + if (m_params.m_UseBinarySearch) { + if (!binary_search_len_tester_stack.contains(fVar) || binary_search_len_tester_stack[fVar].empty()) { + TRACE("str", tout << "WARNING: no active length testers for " << mk_pp(fVar, m) << std::endl;); + NOT_IMPLEMENTED_YET(); + } + expr * effectiveLenInd = binary_search_len_tester_stack[fVar].back(); + bool hasEqcValue; + expr * len_indicator_value = get_eqc_value(effectiveLenInd, hasEqcValue); + if (!hasEqcValue) { + TRACE("str", tout << "WARNING: length tester " << mk_pp(effectiveLenInd, m) << " at top of stack for " << mk_pp(fVar, m) << " has no EQC value" << std::endl;); + } else { + // safety check + zstring effectiveLenIndiStr; + u.str.is_string(len_indicator_value, effectiveLenIndiStr); + if (effectiveLenIndiStr == "more" || effectiveLenIndiStr == "less") { + TRACE("str", tout << "ERROR: illegal state -- requesting 'more value tests' but a length tester is not yet concrete!" << std::endl;); + UNREACHABLE(); + } + expr * valueAssert = gen_free_var_options(fVar, effectiveLenInd, effectiveLenIndiStr, valTester, valTesterValue); + TRACE("str", tout << "asserting more value tests for free variable " << mk_ismt2_pp(fVar, m) << std::endl;); + if (valueAssert != NULL) { + assert_axiom(valueAssert); + } + } + } else { + int lenTesterCount = fvar_lenTester_map[fVar].size(); + + expr * effectiveLenInd = NULL; + zstring effectiveLenIndiStr = ""; + for (int i = 0; i < lenTesterCount; ++i) { + expr * len_indicator_pre = fvar_lenTester_map[fVar][i]; + bool indicatorHasEqcValue = false; + expr * len_indicator_value = get_eqc_value(len_indicator_pre, indicatorHasEqcValue); + if (indicatorHasEqcValue) { + zstring len_pIndiStr; + u.str.is_string(len_indicator_value, len_pIndiStr); + if (len_pIndiStr != "more") { + effectiveLenInd = len_indicator_pre; + effectiveLenIndiStr = len_pIndiStr; + break; + } + } + } + expr * valueAssert = gen_free_var_options(fVar, effectiveLenInd, effectiveLenIndiStr, valTester, valTesterValue); + TRACE("str", tout << "asserting more value tests for free variable " << mk_ismt2_pp(fVar, m) << std::endl;); + if (valueAssert != NULL) { + assert_axiom(valueAssert); + } + } + } + + bool theory_str::free_var_attempt(expr * nn1, expr * nn2) { + ast_manager & m = get_manager(); + zstring nn2_str; + if (internal_lenTest_vars.contains(nn1) && u.str.is_string(nn2, nn2_str)) { + TRACE("str", tout << "acting on equivalence between length tester var " << mk_ismt2_pp(nn1, m) + << " and constant " << mk_ismt2_pp(nn2, m) << std::endl;); + more_len_tests(nn1, nn2_str); + return true; + } else if (internal_valTest_vars.contains(nn1) && u.str.is_string(nn2, nn2_str)) { + if (nn2_str == "more") { + TRACE("str", tout << "acting on equivalence between value var " << mk_ismt2_pp(nn1, m) + << " and constant " << mk_ismt2_pp(nn2, m) << std::endl;); + more_value_tests(nn1, nn2_str); + } + return true; + } else if (internal_unrollTest_vars.contains(nn1)) { + return true; + } else { + return false; + } + } + + void theory_str::handle_equality(expr * lhs, expr * rhs) { + ast_manager & m = get_manager(); + context & ctx = get_context(); + // both terms must be of sort String + sort * lhs_sort = m.get_sort(lhs); + sort * rhs_sort = m.get_sort(rhs); + sort * str_sort = u.str.mk_string_sort(); + + if (lhs_sort != str_sort || rhs_sort != str_sort) { + TRACE("str", tout << "skip equality: not String sort" << std::endl;); + return; + } + + /* // temporarily disabled, we are borrowing these testers for something else + if (m_params.m_FiniteOverlapModels && !finite_model_test_varlists.empty()) { + if (finite_model_test_varlists.contains(lhs)) { + finite_model_test(lhs, rhs); return; + } else if (finite_model_test_varlists.contains(rhs)) { + finite_model_test(rhs, lhs); return; + } + } + */ + + if (free_var_attempt(lhs, rhs) || free_var_attempt(rhs, lhs)) { + return; + } + + if (u.str.is_concat(to_app(lhs)) && u.str.is_concat(to_app(rhs))) { + bool nn1HasEqcValue = false; + bool nn2HasEqcValue = false; + expr * nn1_value = get_eqc_value(lhs, nn1HasEqcValue); + expr * nn2_value = get_eqc_value(rhs, nn2HasEqcValue); + if (nn1HasEqcValue && !nn2HasEqcValue) { + simplify_parent(rhs, nn1_value); + } + if (!nn1HasEqcValue && nn2HasEqcValue) { + simplify_parent(lhs, nn2_value); + } + + expr * nn1_arg0 = to_app(lhs)->get_arg(0); + expr * nn1_arg1 = to_app(lhs)->get_arg(1); + expr * nn2_arg0 = to_app(rhs)->get_arg(0); + expr * nn2_arg1 = to_app(rhs)->get_arg(1); + if (nn1_arg0 == nn2_arg0 && in_same_eqc(nn1_arg1, nn2_arg1)) { + TRACE("str", tout << "skip: lhs arg0 == rhs arg0" << std::endl;); + return; + } + + if (nn1_arg1 == nn2_arg1 && in_same_eqc(nn1_arg0, nn2_arg0)) { + TRACE("str", tout << "skip: lhs arg1 == rhs arg1" << std::endl;); + return; + } + } + + if (opt_DeferEQCConsistencyCheck) { + TRACE("str", tout << "opt_DeferEQCConsistencyCheck is set; deferring new_eq_check call" << std::endl;); + } else { + // newEqCheck() -- check consistency wrt. existing equivalence classes + if (!new_eq_check(lhs, rhs)) { + return; + } + } + + // BEGIN new_eq_handler() in strTheory + + { + rational nn1Len, nn2Len; + bool nn1Len_exists = get_len_value(lhs, nn1Len); + bool nn2Len_exists = get_len_value(rhs, nn2Len); + expr * emptyStr = mk_string(""); + + if (nn1Len_exists && nn1Len.is_zero()) { + if (!in_same_eqc(lhs, emptyStr) && rhs != emptyStr) { + expr_ref eql(ctx.mk_eq_atom(mk_strlen(lhs), mk_int(0)), m); + expr_ref eqr(ctx.mk_eq_atom(lhs, emptyStr), m); + expr_ref toAssert(ctx.mk_eq_atom(eql, eqr), m); + assert_axiom(toAssert); + } + } + + if (nn2Len_exists && nn2Len.is_zero()) { + if (!in_same_eqc(rhs, emptyStr) && lhs != emptyStr) { + expr_ref eql(ctx.mk_eq_atom(mk_strlen(rhs), mk_int(0)), m); + expr_ref eqr(ctx.mk_eq_atom(rhs, emptyStr), m); + expr_ref toAssert(ctx.mk_eq_atom(eql, eqr), m); + assert_axiom(toAssert); + } + } + } + + instantiate_str_eq_length_axiom(ctx.get_enode(lhs), ctx.get_enode(rhs)); + + // group terms by equivalence class (groupNodeInEqc()) + + std::set eqc_concat_lhs; + std::set eqc_var_lhs; + std::set eqc_const_lhs; + group_terms_by_eqc(lhs, eqc_concat_lhs, eqc_var_lhs, eqc_const_lhs); + + std::set eqc_concat_rhs; + std::set eqc_var_rhs; + std::set eqc_const_rhs; + group_terms_by_eqc(rhs, eqc_concat_rhs, eqc_var_rhs, eqc_const_rhs); + + TRACE("str", + tout << "lhs eqc:" << std::endl; + tout << "Concats:" << std::endl; + for (std::set::iterator it = eqc_concat_lhs.begin(); it != eqc_concat_lhs.end(); ++it) { + expr * ex = *it; + tout << mk_ismt2_pp(ex, get_manager()) << std::endl; + } + tout << "Variables:" << std::endl; + for (std::set::iterator it = eqc_var_lhs.begin(); it != eqc_var_lhs.end(); ++it) { + expr * ex = *it; + tout << mk_ismt2_pp(ex, get_manager()) << std::endl; + } + tout << "Constants:" << std::endl; + for (std::set::iterator it = eqc_const_lhs.begin(); it != eqc_const_lhs.end(); ++it) { + expr * ex = *it; + tout << mk_ismt2_pp(ex, get_manager()) << std::endl; + } + + tout << "rhs eqc:" << std::endl; + tout << "Concats:" << std::endl; + for (std::set::iterator it = eqc_concat_rhs.begin(); it != eqc_concat_rhs.end(); ++it) { + expr * ex = *it; + tout << mk_ismt2_pp(ex, get_manager()) << std::endl; + } + tout << "Variables:" << std::endl; + for (std::set::iterator it = eqc_var_rhs.begin(); it != eqc_var_rhs.end(); ++it) { + expr * ex = *it; + tout << mk_ismt2_pp(ex, get_manager()) << std::endl; + } + tout << "Constants:" << std::endl; + for (std::set::iterator it = eqc_const_rhs.begin(); it != eqc_const_rhs.end(); ++it) { + expr * ex = *it; + tout << mk_ismt2_pp(ex, get_manager()) << std::endl; + } + ); + + // step 1: Concat == Concat + int hasCommon = 0; + if (eqc_concat_lhs.size() != 0 && eqc_concat_rhs.size() != 0) { + std::set::iterator itor1 = eqc_concat_lhs.begin(); + std::set::iterator itor2 = eqc_concat_rhs.begin(); + for (; itor1 != eqc_concat_lhs.end(); itor1++) { + if (eqc_concat_rhs.find(*itor1) != eqc_concat_rhs.end()) { + hasCommon = 1; + break; + } + } + for (; itor2 != eqc_concat_rhs.end(); itor2++) { + if (eqc_concat_lhs.find(*itor2) != eqc_concat_lhs.end()) { + hasCommon = 1; + break; + } + } + if (hasCommon == 0) { + if (opt_ConcatOverlapAvoid) { + bool found = false; + // check each pair and take the first ones that won't immediately overlap + for (itor1 = eqc_concat_lhs.begin(); itor1 != eqc_concat_lhs.end() && !found; ++itor1) { + expr * concat_lhs = *itor1; + for (itor2 = eqc_concat_rhs.begin(); itor2 != eqc_concat_rhs.end() && !found; ++itor2) { + expr * concat_rhs = *itor2; + if (will_result_in_overlap(concat_lhs, concat_rhs)) { + TRACE("str", tout << "Concats " << mk_pp(concat_lhs, m) << " and " + << mk_pp(concat_rhs, m) << " will result in overlap; skipping." << std::endl;); + } else { + TRACE("str", tout << "Concats " << mk_pp(concat_lhs, m) << " and " + << mk_pp(concat_rhs, m) << " won't overlap. Simplifying here." << std::endl;); + simplify_concat_equality(concat_lhs, concat_rhs); + found = true; + break; + } + } + } + if (!found) { + TRACE("str", tout << "All pairs of concats expected to overlap, falling back." << std::endl;); + simplify_concat_equality(*(eqc_concat_lhs.begin()), *(eqc_concat_rhs.begin())); + } + } else { + // default behaviour + simplify_concat_equality(*(eqc_concat_lhs.begin()), *(eqc_concat_rhs.begin())); + } + } + } + + // step 2: Concat == Constant + + if (eqc_const_lhs.size() != 0) { + expr * conStr = *(eqc_const_lhs.begin()); + std::set::iterator itor2 = eqc_concat_rhs.begin(); + for (; itor2 != eqc_concat_rhs.end(); itor2++) { + solve_concat_eq_str(*itor2, conStr); + } + } else if (eqc_const_rhs.size() != 0) { + expr* conStr = *(eqc_const_rhs.begin()); + std::set::iterator itor1 = eqc_concat_lhs.begin(); + for (; itor1 != eqc_concat_lhs.end(); itor1++) { + solve_concat_eq_str(*itor1, conStr); + } + } + + // simplify parents wrt. the equivalence class of both sides + bool nn1HasEqcValue = false; + bool nn2HasEqcValue = false; + // we want the Z3str2 eqc check here... + expr * nn1_value = z3str2_get_eqc_value(lhs, nn1HasEqcValue); + expr * nn2_value = z3str2_get_eqc_value(rhs, nn2HasEqcValue); + if (nn1HasEqcValue && !nn2HasEqcValue) { + simplify_parent(rhs, nn1_value); + } + + if (!nn1HasEqcValue && nn2HasEqcValue) { + simplify_parent(lhs, nn2_value); + } + + expr * nn1EqConst = NULL; + std::set nn1EqUnrollFuncs; + get_eqc_allUnroll(lhs, nn1EqConst, nn1EqUnrollFuncs); + expr * nn2EqConst = NULL; + std::set nn2EqUnrollFuncs; + get_eqc_allUnroll(rhs, nn2EqConst, nn2EqUnrollFuncs); + + if (nn2EqConst != NULL) { + for (std::set::iterator itor1 = nn1EqUnrollFuncs.begin(); itor1 != nn1EqUnrollFuncs.end(); itor1++) { + process_unroll_eq_const_str(*itor1, nn2EqConst); + } + } + + if (nn1EqConst != NULL) { + for (std::set::iterator itor2 = nn2EqUnrollFuncs.begin(); itor2 != nn2EqUnrollFuncs.end(); itor2++) { + process_unroll_eq_const_str(*itor2, nn1EqConst); + } + } + + } + + void theory_str::set_up_axioms(expr * ex) { + ast_manager & m = get_manager(); + context & ctx = get_context(); + + sort * ex_sort = m.get_sort(ex); + sort * str_sort = u.str.mk_string_sort(); + sort * bool_sort = m.mk_bool_sort(); + + family_id m_arith_fid = m.mk_family_id("arith"); + sort * int_sort = m.mk_sort(m_arith_fid, INT_SORT); + + if (ex_sort == str_sort) { + TRACE("str", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) << + ": expr is of sort String" << std::endl;); + // set up basic string axioms + enode * n = ctx.get_enode(ex); + SASSERT(n); + m_basicstr_axiom_todo.push_back(n); + TRACE("str", tout << "add " << mk_pp(ex, m) << " to m_basicstr_axiom_todo" << std::endl;); + + + if (is_app(ex)) { + app * ap = to_app(ex); + if (u.str.is_concat(ap)) { + // if ex is a concat, set up concat axioms later + m_concat_axiom_todo.push_back(n); + // we also want to check whether we can eval this concat, + // in case the rewriter did not totally finish with this term + m_concat_eval_todo.push_back(n); + } else if (u.str.is_length(ap)) { + // if the argument is a variable, + // keep track of this for later, we'll need it during model gen + expr * var = ap->get_arg(0); + app * aVar = to_app(var); + if (aVar->get_num_args() == 0 && !u.str.is_string(aVar)) { + input_var_in_len.insert(var); + } + } else if (u.str.is_at(ap) || u.str.is_extract(ap) || u.str.is_replace(ap)) { + m_library_aware_axiom_todo.push_back(n); + } else if (u.str.is_itos(ap)) { + TRACE("str", tout << "found string-integer conversion term: " << mk_pp(ex, get_manager()) << std::endl;); + string_int_conversion_terms.push_back(ap); + m_library_aware_axiom_todo.push_back(n); + } else if (ap->get_num_args() == 0 && !u.str.is_string(ap)) { + // if ex is a variable, add it to our list of variables + TRACE("str", tout << "tracking variable " << mk_ismt2_pp(ap, get_manager()) << std::endl;); + variable_set.insert(ex); + ctx.mark_as_relevant(ex); + // this might help?? + theory_var v = mk_var(n); + TRACE("str", tout << "variable " << mk_ismt2_pp(ap, get_manager()) << " is #" << v << std::endl;); + } + } + } else if (ex_sort == bool_sort) { + TRACE("str", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) << + ": expr is of sort Bool" << std::endl;); + // set up axioms for boolean terms + + ensure_enode(ex); + if (ctx.e_internalized(ex)) { + enode * n = ctx.get_enode(ex); + SASSERT(n); + + if (is_app(ex)) { + app * ap = to_app(ex); + if (u.str.is_prefix(ap) || u.str.is_suffix(ap) || u.str.is_contains(ap) || u.str.is_in_re(ap)) { + m_library_aware_axiom_todo.push_back(n); + } + } + } else { + TRACE("str", tout << "WARNING: Bool term " << mk_ismt2_pp(ex, get_manager()) << " not internalized. Delaying axiom setup to prevent a crash." << std::endl;); + ENSURE(!search_started); // infinite loop prevention + m_delayed_axiom_setup_terms.push_back(ex); + return; + } + } else if (ex_sort == int_sort) { + TRACE("str", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) << + ": expr is of sort Int" << std::endl;); + // set up axioms for integer terms + enode * n = ensure_enode(ex); + SASSERT(n); + + if (is_app(ex)) { + app * ap = to_app(ex); + // TODO indexof2/lastindexof + if (u.str.is_index(ap) /* || is_Indexof2(ap) || is_LastIndexof(ap) */) { + m_library_aware_axiom_todo.push_back(n); + } else if (u.str.is_stoi(ap)) { + TRACE("str", tout << "found string-integer conversion term: " << mk_pp(ex, get_manager()) << std::endl;); + string_int_conversion_terms.push_back(ap); + m_library_aware_axiom_todo.push_back(n); + } + } + } else { + TRACE("str", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) << + ": expr is of wrong sort, ignoring" << std::endl;); + } + + // if expr is an application, recursively inspect all arguments + if (is_app(ex)) { + app * term = (app*)ex; + unsigned num_args = term->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + set_up_axioms(term->get_arg(i)); + } + } + } + + void theory_str::add_theory_assumptions(expr_ref_vector & assumptions) { + TRACE("str", tout << "add overlap assumption for theory_str" << std::endl;); + const char* strOverlap = "!!TheoryStrOverlapAssumption!!"; + seq_util m_sequtil(get_manager()); + sort * s = get_manager().mk_bool_sort(); + m_theoryStrOverlapAssumption_term = expr_ref(mk_fresh_const(strOverlap, s), get_manager()); + assumptions.push_back(get_manager().mk_not(m_theoryStrOverlapAssumption_term)); + } + + lbool theory_str::validate_unsat_core(expr_ref_vector & unsat_core) { + app * target_term = to_app(get_manager().mk_not(m_theoryStrOverlapAssumption_term)); + get_context().internalize(target_term, false); + for (unsigned i = 0; i < unsat_core.size(); ++i) { + app * core_term = to_app(unsat_core.get(i)); + // not sure if this is the correct way to compare terms in this context + enode * e1; + enode * e2; + e1 = get_context().get_enode(target_term); + e2 = get_context().get_enode(core_term); + if (e1 == e2) { + TRACE("str", tout << "overlap detected in unsat core, changing UNSAT to UNKNOWN" << std::endl;); + return l_undef; + } + } + + return l_false; + } + + void theory_str::init_search_eh() { + ast_manager & m = get_manager(); + context & ctx = get_context(); + + TRACE("str", + tout << "dumping all asserted formulas:" << std::endl; + unsigned nFormulas = ctx.get_num_asserted_formulas(); + for (unsigned i = 0; i < nFormulas; ++i) { + expr * ex = ctx.get_asserted_formula(i); + tout << mk_ismt2_pp(ex, m) << (ctx.is_relevant(ex) ? " (rel)" : " (NOT REL)") << std::endl; + } + ); + /* + * Recursive descent through all asserted formulas to set up axioms. + * Note that this is just the input structure and not necessarily things + * that we know to be true or false. We're just doing this to see + * which terms are explicitly mentioned. + */ + unsigned nFormulas = ctx.get_num_asserted_formulas(); + for (unsigned i = 0; i < nFormulas; ++i) { + expr * ex = ctx.get_asserted_formula(i); + set_up_axioms(ex); + } + + /* + * Similar recursive descent, except over all initially assigned terms. + * This is done to find equalities between terms, etc. that we otherwise + * might not get a chance to see. + */ + + /* + expr_ref_vector assignments(m); + ctx.get_assignments(assignments); + for (expr_ref_vector::iterator i = assignments.begin(); i != assignments.end(); ++i) { + expr * ex = *i; + if (m.is_eq(ex)) { + TRACE("str", tout << "processing assignment " << mk_ismt2_pp(ex, m) << + ": expr is equality" << std::endl;); + app * eq = (app*)ex; + SASSERT(eq->get_num_args() == 2); + expr * lhs = eq->get_arg(0); + expr * rhs = eq->get_arg(1); + + enode * e_lhs = ctx.get_enode(lhs); + enode * e_rhs = ctx.get_enode(rhs); + std::pair eq_pair(e_lhs, e_rhs); + m_str_eq_todo.push_back(eq_pair); + } else { + TRACE("str", tout << "processing assignment " << mk_ismt2_pp(ex, m) + << ": expr ignored" << std::endl;); + } + } + */ + + // this might be cheating but we need to make sure that certain maps are populated + // before the first call to new_eq_eh() + propagate(); + + TRACE("str", tout << "search started" << std::endl;); + search_started = true; + } + + void theory_str::new_eq_eh(theory_var x, theory_var y) { + //TRACE("str", tout << "new eq: v#" << x << " = v#" << y << std::endl;); + TRACE("str", tout << "new eq: " << mk_ismt2_pp(get_enode(x)->get_owner(), get_manager()) << " = " << + mk_ismt2_pp(get_enode(y)->get_owner(), get_manager()) << std::endl;); + + /* + if (m_find.find(x) == m_find.find(y)) { + return; + } + */ + handle_equality(get_enode(x)->get_owner(), get_enode(y)->get_owner()); + + // replicate Z3str2 behaviour: merge eqc **AFTER** handle_equality + m_find.merge(x, y); + } + + void theory_str::new_diseq_eh(theory_var x, theory_var y) { + //TRACE("str", tout << "new diseq: v#" << x << " != v#" << y << std::endl;); + TRACE("str", tout << "new diseq: " << mk_ismt2_pp(get_enode(x)->get_owner(), get_manager()) << " != " << + mk_ismt2_pp(get_enode(y)->get_owner(), get_manager()) << std::endl;); + } + + void theory_str::relevant_eh(app * n) { + TRACE("str", tout << "relevant: " << mk_ismt2_pp(n, get_manager()) << std::endl;); + } + + void theory_str::assign_eh(bool_var v, bool is_true) { + context & ctx = get_context(); + TRACE("str", tout << "assert: v" << v << " #" << ctx.bool_var2expr(v)->get_id() << " is_true: " << is_true << std::endl;); + } + + void theory_str::push_scope_eh() { + theory::push_scope_eh(); + m_trail_stack.push_scope(); + + sLevel += 1; + TRACE("str", tout << "push to " << sLevel << std::endl;); + TRACE_CODE(if (is_trace_enabled("t_str_dump_assign_on_scope_change")) { dump_assignments(); }); + } + + void theory_str::recursive_check_variable_scope(expr * ex) { + ast_manager & m = get_manager(); + + if (is_app(ex)) { + app * a = to_app(ex); + if (a->get_num_args() == 0) { + // we only care about string variables + sort * s = m.get_sort(ex); + sort * string_sort = u.str.mk_string_sort(); + if (s != string_sort) { + return; + } + // base case: string constant / var + if (u.str.is_string(a)) { + return; + } else { + // assume var + if (variable_set.find(ex) == variable_set.end() + && internal_variable_set.find(ex) == internal_variable_set.end()) { + TRACE("str", tout << "WARNING: possible reference to out-of-scope variable " << mk_pp(ex, m) << std::endl;); + } + } + } else { + for (unsigned i = 0; i < a->get_num_args(); ++i) { + recursive_check_variable_scope(a->get_arg(i)); + } + } + } + } + + void theory_str::check_variable_scope() { + if (!opt_CheckVariableScope) { + return; + } + + if (!is_trace_enabled("t_str_detail")) { + return; + } + + TRACE("str", tout << "checking scopes of variables in the current assignment" << std::endl;); + + context & ctx = get_context(); + ast_manager & m = get_manager(); + + expr_ref_vector assignments(m); + ctx.get_assignments(assignments); + for (expr_ref_vector::iterator i = assignments.begin(); i != assignments.end(); ++i) { + expr * ex = *i; + recursive_check_variable_scope(ex); + } + } + + void theory_str::pop_scope_eh(unsigned num_scopes) { + sLevel -= num_scopes; + TRACE("str", tout << "pop " << num_scopes << " to " << sLevel << std::endl;); + ast_manager & m = get_manager(); + + TRACE_CODE(if (is_trace_enabled("t_str_dump_assign_on_scope_change")) { dump_assignments(); }); + + // list of expr* to remove from cut_var_map + ptr_vector cutvarmap_removes; + + obj_map >::iterator varItor = cut_var_map.begin(); + while (varItor != cut_var_map.end()) { + expr * e = varItor->m_key; + std::stack & val = cut_var_map[varItor->m_key]; + while ((val.size() > 0) && (val.top()->level != 0) && (val.top()->level >= sLevel)) { + TRACE("str", tout << "remove cut info for " << mk_pp(e, m) << std::endl; print_cut_var(e, tout);); + // T_cut * aCut = val.top(); + val.pop(); + // dealloc(aCut); + } + if (val.size() == 0) { + cutvarmap_removes.insert(varItor->m_key); + } + varItor++; + } + + if (!cutvarmap_removes.empty()) { + ptr_vector::iterator it = cutvarmap_removes.begin(); + for (; it != cutvarmap_removes.end(); ++it) { + expr * ex = *it; + cut_var_map.remove(ex); + } + } + + ptr_vector new_m_basicstr; + for (ptr_vector::iterator it = m_basicstr_axiom_todo.begin(); it != m_basicstr_axiom_todo.end(); ++it) { + enode * e = *it; + app * a = e->get_owner(); + TRACE("str", tout << "consider deleting " << mk_pp(a, get_manager()) + << ", enode scope level is " << e->get_iscope_lvl() + << std::endl;); + if (e->get_iscope_lvl() <= (unsigned)sLevel) { + new_m_basicstr.push_back(e); + } + } + m_basicstr_axiom_todo.reset(); + m_basicstr_axiom_todo = new_m_basicstr; + + m_trail_stack.pop_scope(num_scopes); + theory::pop_scope_eh(num_scopes); + + //check_variable_scope(); + } + + void theory_str::dump_assignments() { + TRACE_CODE( + ast_manager & m = get_manager(); + context & ctx = get_context(); + tout << "dumping all assignments:" << std::endl; + expr_ref_vector assignments(m); + ctx.get_assignments(assignments); + for (expr_ref_vector::iterator i = assignments.begin(); i != assignments.end(); ++i) { + expr * ex = *i; + tout << mk_ismt2_pp(ex, m) << (ctx.is_relevant(ex) ? "" : " (NOT REL)") << std::endl; + } + ); + } + + void theory_str::classify_ast_by_type(expr * node, std::map & varMap, + std::map & concatMap, std::map & unrollMap) { + + // check whether the node is a string variable; + // testing set membership here bypasses several expensive checks. + // note that internal variables don't count if they're only length tester / value tester vars. + if (variable_set.find(node) != variable_set.end() + && internal_lenTest_vars.find(node) == internal_lenTest_vars.end() + && internal_valTest_vars.find(node) == internal_valTest_vars.end() + && internal_unrollTest_vars.find(node) == internal_unrollTest_vars.end()) { + if (varMap[node] != 1) { + TRACE("str", tout << "new variable: " << mk_pp(node, get_manager()) << std::endl;); + } + varMap[node] = 1; + } + // check whether the node is a function that we want to inspect + else if (is_app(node)) { + app * aNode = to_app(node); + if (u.str.is_length(aNode)) { + // Length + return; + } else if (u.str.is_concat(aNode)) { + expr * arg0 = aNode->get_arg(0); + expr * arg1 = aNode->get_arg(1); + bool arg0HasEq = false; + bool arg1HasEq = false; + expr * arg0Val = get_eqc_value(arg0, arg0HasEq); + expr * arg1Val = get_eqc_value(arg1, arg1HasEq); + + int canskip = 0; + zstring tmp; + u.str.is_string(arg0Val, tmp); + if (arg0HasEq && tmp.empty()) { + canskip = 1; + } + u.str.is_string(arg1Val, tmp); + if (canskip == 0 && arg1HasEq && tmp.empty()) { + canskip = 1; + } + if (canskip == 0 && concatMap.find(node) == concatMap.end()) { + concatMap[node] = 1; + } + } else if (u.re.is_unroll(aNode)) { + // Unroll + if (unrollMap.find(node) == unrollMap.end()) { + unrollMap[node] = 1; + } + } + // recursively visit all arguments + for (unsigned i = 0; i < aNode->get_num_args(); ++i) { + expr * arg = aNode->get_arg(i); + classify_ast_by_type(arg, varMap, concatMap, unrollMap); + } + } + } + + // NOTE: this function used to take an argument `Z3_ast node`; + // it was not used and so was removed from the signature + void theory_str::classify_ast_by_type_in_positive_context(std::map & varMap, + std::map & concatMap, std::map & unrollMap) { + + context & ctx = get_context(); + ast_manager & m = get_manager(); + expr_ref_vector assignments(m); + ctx.get_assignments(assignments); + + for (expr_ref_vector::iterator it = assignments.begin(); it != assignments.end(); ++it) { + expr * argAst = *it; + // the original code jumped through some hoops to check whether the AST node + // is a function, then checked whether that function is "interesting". + // however, the only thing that's considered "interesting" is an equality predicate. + // so we bypass a huge amount of work by doing the following... + + if (m.is_eq(argAst)) { + TRACE("str", tout + << "eq ast " << mk_pp(argAst, m) << " is between args of sort " + << m.get_sort(to_app(argAst)->get_arg(0))->get_name() + << std::endl;); + classify_ast_by_type(argAst, varMap, concatMap, unrollMap); + } + } + } + + inline expr * theory_str::get_alias_index_ast(std::map & aliasIndexMap, expr * node) { + if (aliasIndexMap.find(node) != aliasIndexMap.end()) + return aliasIndexMap[node]; + else + return node; + } + + inline expr * theory_str::getMostLeftNodeInConcat(expr * node) { + app * aNode = to_app(node); + if (!u.str.is_concat(aNode)) { + return node; + } else { + expr * concatArgL = aNode->get_arg(0); + return getMostLeftNodeInConcat(concatArgL); + } + } + + inline expr * theory_str::getMostRightNodeInConcat(expr * node) { + app * aNode = to_app(node); + if (!u.str.is_concat(aNode)) { + return node; + } else { + expr * concatArgR = aNode->get_arg(1); + return getMostRightNodeInConcat(concatArgR); + } + } + + void theory_str::trace_ctx_dep(std::ofstream & tout, + std::map & aliasIndexMap, + std::map & var_eq_constStr_map, + std::map > & var_eq_concat_map, + std::map > & var_eq_unroll_map, + std::map & concat_eq_constStr_map, + std::map > & concat_eq_concat_map, + std::map > & unrollGroupMap) { +#ifdef _TRACE + context & ctx = get_context(); + ast_manager & mgr = get_manager(); + { + tout << "(0) alias: variables" << std::endl; + std::map > aliasSumMap; + std::map::iterator itor0 = aliasIndexMap.begin(); + for (; itor0 != aliasIndexMap.end(); itor0++) { + aliasSumMap[itor0->second][itor0->first] = 1; + } + std::map >::iterator keyItor = aliasSumMap.begin(); + for (; keyItor != aliasSumMap.end(); keyItor++) { + tout << " * "; + tout << mk_pp(keyItor->first, mgr); + tout << " : "; + std::map::iterator innerItor = keyItor->second.begin(); + for (; innerItor != keyItor->second.end(); innerItor++) { + tout << mk_pp(innerItor->first, mgr); + tout << ", "; + } + tout << std::endl; + } + tout << std::endl; + } + + { + tout << "(1) var = constStr:" << std::endl; + std::map::iterator itor1 = var_eq_constStr_map.begin(); + for (; itor1 != var_eq_constStr_map.end(); itor1++) { + tout << " * "; + tout << mk_pp(itor1->first, mgr); + tout << " = "; + tout << mk_pp(itor1->second, mgr); + if (!in_same_eqc(itor1->first, itor1->second)) { + tout << " (not true in ctx)"; + } + tout << std::endl; + } + tout << std::endl; + } + + { + tout << "(2) var = concat:" << std::endl; + std::map >::iterator itor2 = var_eq_concat_map.begin(); + for (; itor2 != var_eq_concat_map.end(); itor2++) { + tout << " * "; + tout << mk_pp(itor2->first, mgr); + tout << " = { "; + std::map::iterator i_itor = itor2->second.begin(); + for (; i_itor != itor2->second.end(); i_itor++) { + tout << mk_pp(i_itor->first, mgr); + tout << ", "; + } + tout << std::endl; + } + tout << std::endl; + } + + { + tout << "(3) var = unrollFunc:" << std::endl; + std::map >::iterator itor2 = var_eq_unroll_map.begin(); + for (; itor2 != var_eq_unroll_map.end(); itor2++) { + tout << " * " << mk_pp(itor2->first, mgr) << " = { "; + std::map::iterator i_itor = itor2->second.begin(); + for (; i_itor != itor2->second.end(); i_itor++) { + tout << mk_pp(i_itor->first, mgr) << ", "; + } + tout << " }" << std::endl; + } + tout << std::endl; + } + + { + tout << "(4) concat = constStr:" << std::endl; + std::map::iterator itor3 = concat_eq_constStr_map.begin(); + for (; itor3 != concat_eq_constStr_map.end(); itor3++) { + tout << " * "; + tout << mk_pp(itor3->first, mgr); + tout << " = "; + tout << mk_pp(itor3->second, mgr); + tout << std::endl; + + } + tout << std::endl; + } + + { + tout << "(5) eq concats:" << std::endl; + std::map >::iterator itor4 = concat_eq_concat_map.begin(); + for (; itor4 != concat_eq_concat_map.end(); itor4++) { + if (itor4->second.size() > 1) { + std::map::iterator i_itor = itor4->second.begin(); + tout << " * "; + for (; i_itor != itor4->second.end(); i_itor++) { + tout << mk_pp(i_itor->first, mgr); + tout << " , "; + } + tout << std::endl; + } + } + tout << std::endl; + } + + { + tout << "(6) eq unrolls:" << std::endl; + std::map >::iterator itor5 = unrollGroupMap.begin(); + for (; itor5 != unrollGroupMap.end(); itor5++) { + tout << " * "; + std::set::iterator i_itor = itor5->second.begin(); + for (; i_itor != itor5->second.end(); i_itor++) { + tout << mk_pp(*i_itor, mgr) << ", "; + } + tout << std::endl; + } + tout << std::endl; + } + + { + tout << "(7) unroll = concats:" << std::endl; + std::map >::iterator itor5 = unrollGroupMap.begin(); + for (; itor5 != unrollGroupMap.end(); itor5++) { + tout << " * "; + expr * unroll = itor5->first; + tout << mk_pp(unroll, mgr) << std::endl; + enode * e_curr = ctx.get_enode(unroll); + enode * e_curr_end = e_curr; + do { + app * curr = e_curr->get_owner(); + if (u.str.is_concat(curr)) { + tout << " >>> " << mk_pp(curr, mgr) << std::endl; + } + e_curr = e_curr->get_next(); + } while (e_curr != e_curr_end); + tout << std::endl; + } + tout << std::endl; + } +#else + return; +#endif // _TRACE + } + + + /* + * Dependence analysis from current context assignment + * - "freeVarMap" contains a set of variables that doesn't constrained by Concats. + * But it's possible that it's bounded by unrolls + * For the case of + * (1) var1 = unroll(r1, t1) + * var1 is in the freeVarMap + * > should unroll r1 for var1 + * (2) var1 = unroll(r1, t1) /\ var1 = Concat(var2, var3) + * var2, var3 are all in freeVar + * > should split the unroll function so that var2 and var3 are bounded by new unrolls + */ + int theory_str::ctx_dep_analysis(std::map & strVarMap, std::map & freeVarMap, + std::map > & unrollGroupMap, std::map > & var_eq_concat_map) { + std::map concatMap; + std::map unrollMap; + std::map aliasIndexMap; + std::map var_eq_constStr_map; + std::map concat_eq_constStr_map; + std::map > var_eq_unroll_map; + std::map > concat_eq_concat_map; + std::map > depMap; + + context & ctx = get_context(); + ast_manager & m = get_manager(); + + // note that the old API concatenated these assignments into + // a massive conjunction; we may have the opportunity to avoid that here + expr_ref_vector assignments(m); + ctx.get_assignments(assignments); + + // Step 1: get variables / concat AST appearing in the context + // the thing we iterate over should just be variable_set - internal_variable_set + // so we avoid computing the set difference (but this might be slower) + for(obj_hashtable::iterator it = variable_set.begin(); it != variable_set.end(); ++it) { + expr* var = *it; + if (internal_variable_set.find(var) == internal_variable_set.end()) { + TRACE("str", tout << "new variable: " << mk_pp(var, m) << std::endl;); + strVarMap[*it] = 1; + } + } + classify_ast_by_type_in_positive_context(strVarMap, concatMap, unrollMap); + + std::map aliasUnrollSet; + std::map::iterator unrollItor = unrollMap.begin(); + for (; unrollItor != unrollMap.end(); ++unrollItor) { + if (aliasUnrollSet.find(unrollItor->first) != aliasUnrollSet.end()) { + continue; + } + expr * aRoot = NULL; + enode * e_currEqc = ctx.get_enode(unrollItor->first); + enode * e_curr = e_currEqc; + do { + app * curr = e_currEqc->get_owner(); + if (u.re.is_unroll(curr)) { + if (aRoot == NULL) { + aRoot = curr; + } + aliasUnrollSet[curr] = aRoot; + } + e_currEqc = e_currEqc->get_next(); + } while (e_currEqc != e_curr); + } + + for (unrollItor = unrollMap.begin(); unrollItor != unrollMap.end(); unrollItor++) { + expr * unrFunc = unrollItor->first; + expr * urKey = aliasUnrollSet[unrFunc]; + unrollGroupMap[urKey].insert(unrFunc); + } + + // Step 2: collect alias relation + // e.g. suppose we have the equivalence class {x, y, z}; + // then we set aliasIndexMap[y] = x + // and aliasIndexMap[z] = x + + std::map::iterator varItor = strVarMap.begin(); + for (; varItor != strVarMap.end(); ++varItor) { + if (aliasIndexMap.find(varItor->first) != aliasIndexMap.end()) { + continue; + } + expr * aRoot = NULL; + expr * curr = varItor->first; + do { + if (variable_set.find(curr) != variable_set.end()) { + if (aRoot == NULL) { + aRoot = curr; + } else { + aliasIndexMap[curr] = aRoot; + } + } + curr = get_eqc_next(curr); + } while (curr != varItor->first); + } + + // Step 3: Collect interested cases + + varItor = strVarMap.begin(); + for (; varItor != strVarMap.end(); ++varItor) { + expr * deAliasNode = get_alias_index_ast(aliasIndexMap, varItor->first); + // Case 1: variable = string constant + // e.g. z = "str1" ::= var_eq_constStr_map[z] = "str1" + + if (var_eq_constStr_map.find(deAliasNode) == var_eq_constStr_map.end()) { + bool nodeHasEqcValue = false; + expr * nodeValue = get_eqc_value(deAliasNode, nodeHasEqcValue); + if (nodeHasEqcValue) { + var_eq_constStr_map[deAliasNode] = nodeValue; + } + } + + // Case 2: var_eq_concat + // e.g. z = concat("str1", b) ::= var_eq_concat[z][concat(c, "str2")] = 1 + // var_eq_unroll + // e.g. z = unroll(...) ::= var_eq_unroll[z][unroll(...)] = 1 + + if (var_eq_concat_map.find(deAliasNode) == var_eq_concat_map.end()) { + expr * curr = get_eqc_next(deAliasNode); + while (curr != deAliasNode) { + app * aCurr = to_app(curr); + // collect concat + if (u.str.is_concat(aCurr)) { + expr * arg0 = aCurr->get_arg(0); + expr * arg1 = aCurr->get_arg(1); + bool arg0HasEqcValue = false; + bool arg1HasEqcValue = false; + expr * arg0_value = get_eqc_value(arg0, arg0HasEqcValue); + expr * arg1_value = get_eqc_value(arg1, arg1HasEqcValue); + + bool is_arg0_emptyStr = false; + if (arg0HasEqcValue) { + zstring strval; + u.str.is_string(arg0_value, strval); + if (strval.empty()) { + is_arg0_emptyStr = true; + } + } + + bool is_arg1_emptyStr = false; + if (arg1HasEqcValue) { + zstring strval; + u.str.is_string(arg1_value, strval); + if (strval.empty()) { + is_arg1_emptyStr = true; + } + } + + if (!is_arg0_emptyStr && !is_arg1_emptyStr) { + var_eq_concat_map[deAliasNode][curr] = 1; + } + } else if (u.re.is_unroll(to_app(curr))) { + var_eq_unroll_map[deAliasNode][curr] = 1; + } + + curr = get_eqc_next(curr); + } + } + + } // for(varItor in strVarMap) + + // -------------------------------------------------- + // * collect aliasing relation among eq concats + // e.g EQC={concat1, concat2, concat3} + // concats_eq_Index_map[concat2] = concat1 + // concats_eq_Index_map[concat3] = concat1 + // -------------------------------------------------- + + std::map concats_eq_index_map; + std::map::iterator concatItor = concatMap.begin(); + for(; concatItor != concatMap.end(); ++concatItor) { + if (concats_eq_index_map.find(concatItor->first) != concats_eq_index_map.end()) { + continue; + } + expr * aRoot = NULL; + expr * curr = concatItor->first; + do { + if (u.str.is_concat(to_app(curr))) { + if (aRoot == NULL) { + aRoot = curr; + } else { + concats_eq_index_map[curr] = aRoot; + } + } + curr = get_eqc_next(curr); + } while (curr != concatItor->first); + } + + concatItor = concatMap.begin(); + for(; concatItor != concatMap.end(); ++concatItor) { + expr * deAliasConcat = NULL; + if (concats_eq_index_map.find(concatItor->first) != concats_eq_index_map.end()) { + deAliasConcat = concats_eq_index_map[concatItor->first]; + } else { + deAliasConcat = concatItor->first; + } + + // (3) concat_eq_conststr, e.g. concat(a,b) = "str1" + if (concat_eq_constStr_map.find(deAliasConcat) == concat_eq_constStr_map.end()) { + bool nodeHasEqcValue = false; + expr * nodeValue = get_eqc_value(deAliasConcat, nodeHasEqcValue); + if (nodeHasEqcValue) { + concat_eq_constStr_map[deAliasConcat] = nodeValue; + } + } + + // (4) concat_eq_concat, e.g. + // concat(a,b) = concat("str1", c) AND z = concat(a,b) AND z = concat(e,f) + if (concat_eq_concat_map.find(deAliasConcat) == concat_eq_concat_map.end()) { + expr * curr = deAliasConcat; + do { + if (u.str.is_concat(to_app(curr))) { + // curr cannot be reduced + if (concatMap.find(curr) != concatMap.end()) { + concat_eq_concat_map[deAliasConcat][curr] = 1; + } + } + curr = get_eqc_next(curr); + } while (curr != deAliasConcat); + } + } + + // print some debugging info + TRACE("str", trace_ctx_dep(tout, aliasIndexMap, var_eq_constStr_map, + var_eq_concat_map, var_eq_unroll_map, + concat_eq_constStr_map, concat_eq_concat_map, unrollGroupMap);); + + if (!contain_pair_bool_map.empty()) { + compute_contains(aliasIndexMap, concats_eq_index_map, var_eq_constStr_map, concat_eq_constStr_map, var_eq_concat_map); + } + + // step 4: dependence analysis + + // (1) var = string constant + for (std::map::iterator itor = var_eq_constStr_map.begin(); + itor != var_eq_constStr_map.end(); ++itor) { + expr * var = get_alias_index_ast(aliasIndexMap, itor->first); + expr * strAst = itor->second; + depMap[var][strAst] = 1; + } + + // (2) var = concat + for (std::map >::iterator itor = var_eq_concat_map.begin(); + itor != var_eq_concat_map.end(); ++itor) { + expr * var = get_alias_index_ast(aliasIndexMap, itor->first); + for (std::map::iterator itor1 = itor->second.begin(); itor1 != itor->second.end(); ++itor1) { + expr * concat = itor1->first; + std::map inVarMap; + std::map inConcatMap; + std::map inUnrollMap; + classify_ast_by_type(concat, inVarMap, inConcatMap, inUnrollMap); + for (std::map::iterator itor2 = inVarMap.begin(); itor2 != inVarMap.end(); ++itor2) { + expr * varInConcat = get_alias_index_ast(aliasIndexMap, itor2->first); + if (!(depMap[var].find(varInConcat) != depMap[var].end() && depMap[var][varInConcat] == 1)) { + depMap[var][varInConcat] = 2; + } + } + } + } + + for (std::map >::iterator itor = var_eq_unroll_map.begin(); + itor != var_eq_unroll_map.end(); itor++) { + expr * var = get_alias_index_ast(aliasIndexMap, itor->first); + for (std::map::iterator itor1 = itor->second.begin(); itor1 != itor->second.end(); itor1++) { + expr * unrollFunc = itor1->first; + std::map inVarMap; + std::map inConcatMap; + std::map inUnrollMap; + classify_ast_by_type(unrollFunc, inVarMap, inConcatMap, inUnrollMap); + for (std::map::iterator itor2 = inVarMap.begin(); itor2 != inVarMap.end(); itor2++) { + expr * varInFunc = get_alias_index_ast(aliasIndexMap, itor2->first); + + TRACE("str", tout << "var in unroll = " << + mk_ismt2_pp(itor2->first, m) << std::endl + << "dealiased var = " << mk_ismt2_pp(varInFunc, m) << std::endl;); + + // it's possible that we have both (Unroll $$_regVar_0 $$_unr_0) /\ (Unroll abcd $$_unr_0), + // while $$_regVar_0 = "abcd" + // have to exclude such cases + bool varHasValue = false; + get_eqc_value(varInFunc, varHasValue); + if (varHasValue) + continue; + + if (depMap[var].find(varInFunc) == depMap[var].end()) { + depMap[var][varInFunc] = 6; + } + } + } + } + + // (3) concat = string constant + for (std::map::iterator itor = concat_eq_constStr_map.begin(); + itor != concat_eq_constStr_map.end(); itor++) { + expr * concatAst = itor->first; + expr * constStr = itor->second; + std::map inVarMap; + std::map inConcatMap; + std::map inUnrollMap; + classify_ast_by_type(concatAst, inVarMap, inConcatMap, inUnrollMap); + for (std::map::iterator itor2 = inVarMap.begin(); itor2 != inVarMap.end(); itor2++) { + expr * varInConcat = get_alias_index_ast(aliasIndexMap, itor2->first); + if (!(depMap[varInConcat].find(constStr) != depMap[varInConcat].end() && depMap[varInConcat][constStr] == 1)) + depMap[varInConcat][constStr] = 3; + } + } + + // (4) equivalent concats + // - possibility 1 : concat("str", v1) = concat(concat(v2, v3), v4) = concat(v5, v6) + // ==> v2, v5 are constrained by "str" + // - possibility 2 : concat(v1, "str") = concat(v2, v3) = concat(v4, v5) + // ==> v2, v4 are constrained by "str" + //-------------------------------------------------------------- + + std::map mostLeftNodes; + std::map mostRightNodes; + + std::map mLIdxMap; + std::map > mLMap; + std::map mRIdxMap; + std::map > mRMap; + std::set nSet; + + for (std::map >::iterator itor = concat_eq_concat_map.begin(); + itor != concat_eq_concat_map.end(); itor++) { + mostLeftNodes.clear(); + mostRightNodes.clear(); + + expr * mLConst = NULL; + expr * mRConst = NULL; + + for (std::map::iterator itor1 = itor->second.begin(); itor1 != itor->second.end(); itor1++) { + expr * concatNode = itor1->first; + expr * mLNode = getMostLeftNodeInConcat(concatNode); + zstring strval; + if (u.str.is_string(to_app(mLNode), strval)) { + if (mLConst == NULL && strval.empty()) { + mLConst = mLNode; + } + } else { + mostLeftNodes[mLNode] = concatNode; + } + + expr * mRNode = getMostRightNodeInConcat(concatNode); + if (u.str.is_string(to_app(mRNode), strval)) { + if (mRConst == NULL && strval.empty()) { + mRConst = mRNode; + } + } else { + mostRightNodes[mRNode] = concatNode; + } + } + + if (mLConst != NULL) { + // ------------------------------------------------------------------------------------- + // The left most variable in a concat is constrained by a constant string in eqc concat + // ------------------------------------------------------------------------------------- + // e.g. Concat(x, ...) = Concat("abc", ...) + // ------------------------------------------------------------------------------------- + for (std::map::iterator itor1 = mostLeftNodes.begin(); + itor1 != mostLeftNodes.end(); itor1++) { + expr * deVar = get_alias_index_ast(aliasIndexMap, itor1->first); + if (depMap[deVar].find(mLConst) == depMap[deVar].end() || depMap[deVar][mLConst] != 1) { + depMap[deVar][mLConst] = 4; + } + } + } + + { + // ------------------------------------------------------------------------------------- + // The left most variables in eqc concats are constrained by each other + // ------------------------------------------------------------------------------------- + // e.g. concat(x, ...) = concat(u, ...) = ... + // x and u are constrained by each other + // ------------------------------------------------------------------------------------- + nSet.clear(); + std::map::iterator itl = mostLeftNodes.begin(); + for (; itl != mostLeftNodes.end(); itl++) { + bool lfHasEqcValue = false; + get_eqc_value(itl->first, lfHasEqcValue); + if (lfHasEqcValue) + continue; + expr * deVar = get_alias_index_ast(aliasIndexMap, itl->first); + nSet.insert(deVar); + } + + if (nSet.size() > 1) { + int lId = -1; + for (std::set::iterator itor2 = nSet.begin(); itor2 != nSet.end(); itor2++) { + if (mLIdxMap.find(*itor2) != mLIdxMap.end()) { + lId = mLIdxMap[*itor2]; + break; + } + } + if (lId == -1) + lId = mLMap.size(); + for (std::set::iterator itor2 = nSet.begin(); itor2 != nSet.end(); itor2++) { + bool itorHasEqcValue = false; + get_eqc_value(*itor2, itorHasEqcValue); + if (itorHasEqcValue) + continue; + mLIdxMap[*itor2] = lId; + mLMap[lId].insert(*itor2); + } + } + } + + if (mRConst != NULL) { + for (std::map::iterator itor1 = mostRightNodes.begin(); + itor1 != mostRightNodes.end(); itor1++) { + expr * deVar = get_alias_index_ast(aliasIndexMap, itor1->first); + if (depMap[deVar].find(mRConst) == depMap[deVar].end() || depMap[deVar][mRConst] != 1) { + depMap[deVar][mRConst] = 5; + } + } + } + + { + nSet.clear(); + std::map::iterator itr = mostRightNodes.begin(); + for (; itr != mostRightNodes.end(); itr++) { + expr * deVar = get_alias_index_ast(aliasIndexMap, itr->first); + nSet.insert(deVar); + } + if (nSet.size() > 1) { + int rId = -1; + std::set::iterator itor2 = nSet.begin(); + for (; itor2 != nSet.end(); itor2++) { + if (mRIdxMap.find(*itor2) != mRIdxMap.end()) { + rId = mRIdxMap[*itor2]; + break; + } + } + if (rId == -1) + rId = mRMap.size(); + for (itor2 = nSet.begin(); itor2 != nSet.end(); itor2++) { + bool rHasEqcValue = false; + get_eqc_value(*itor2, rHasEqcValue); + if (rHasEqcValue) + continue; + mRIdxMap[*itor2] = rId; + mRMap[rId].insert(*itor2); + } + } + } + } + + // print the dependence map + TRACE("str", + tout << "Dependence Map" << std::endl; + for(std::map >::iterator itor = depMap.begin(); itor != depMap.end(); itor++) { + tout << mk_pp(itor->first, m); + rational nnLen; + bool nnLen_exists = get_len_value(itor->first, nnLen); + tout << " [len = " << (nnLen_exists ? nnLen.to_string() : "?") << "] \t-->\t"; + for (std::map::iterator itor1 = itor->second.begin(); itor1 != itor->second.end(); itor1++) { + tout << mk_pp(itor1->first, m) << "(" << itor1->second << "), "; + } + tout << std::endl; + } + ); + + // step, errr, 5: compute free variables based on the dependence map + + // the case dependence map is empty, every var in VarMap is free + //--------------------------------------------------------------- + // remove L/R most var in eq concat since they are constrained with each other + std::map > lrConstrainedMap; + for (std::map >::iterator itor = mLMap.begin(); itor != mLMap.end(); itor++) { + for (std::set::iterator it1 = itor->second.begin(); it1 != itor->second.end(); it1++) { + std::set::iterator it2 = it1; + it2++; + for (; it2 != itor->second.end(); it2++) { + expr * n1 = *it1; + expr * n2 = *it2; + lrConstrainedMap[n1][n2] = 1; + lrConstrainedMap[n2][n1] = 1; + } + } + } + for (std::map >::iterator itor = mRMap.begin(); itor != mRMap.end(); itor++) { + for (std::set::iterator it1 = itor->second.begin(); it1 != itor->second.end(); it1++) { + std::set::iterator it2 = it1; + it2++; + for (; it2 != itor->second.end(); it2++) { + expr * n1 = *it1; + expr * n2 = *it2; + lrConstrainedMap[n1][n2] = 1; + lrConstrainedMap[n2][n1] = 1; + } + } + } + + if (depMap.size() == 0) { + std::map::iterator itor = strVarMap.begin(); + for (; itor != strVarMap.end(); itor++) { + expr * var = get_alias_index_ast(aliasIndexMap, itor->first); + if (lrConstrainedMap.find(var) == lrConstrainedMap.end()) { + freeVarMap[var] = 1; + } else { + int lrConstainted = 0; + std::map::iterator lrit = freeVarMap.begin(); + for (; lrit != freeVarMap.end(); lrit++) { + if (lrConstrainedMap[var].find(lrit->first) != lrConstrainedMap[var].end()) { + lrConstainted = 1; + break; + } + } + if (lrConstainted == 0) { + freeVarMap[var] = 1; + } + } + } + } else { + // if the keys in aliasIndexMap are not contained in keys in depMap, they are free + // e.g., x= y /\ x = z /\ t = "abc" + // aliasIndexMap[y]= x, aliasIndexMap[z] = x + // depMap t ~ "abc"(1) + // x should be free + std::map::iterator itor2 = strVarMap.begin(); + for (; itor2 != strVarMap.end(); itor2++) { + if (aliasIndexMap.find(itor2->first) != aliasIndexMap.end()) { + expr * var = aliasIndexMap[itor2->first]; + if (depMap.find(var) == depMap.end()) { + if (lrConstrainedMap.find(var) == lrConstrainedMap.end()) { + freeVarMap[var] = 1; + } else { + int lrConstainted = 0; + std::map::iterator lrit = freeVarMap.begin(); + for (; lrit != freeVarMap.end(); lrit++) { + if (lrConstrainedMap[var].find(lrit->first) != lrConstrainedMap[var].end()) { + lrConstainted = 1; + break; + } + } + if (lrConstainted == 0) { + freeVarMap[var] = 1; + } + } + } + } else if (aliasIndexMap.find(itor2->first) == aliasIndexMap.end()) { + // if a variable is not in aliasIndexMap and not in depMap, it's free + if (depMap.find(itor2->first) == depMap.end()) { + expr * var = itor2->first; + if (lrConstrainedMap.find(var) == lrConstrainedMap.end()) { + freeVarMap[var] = 1; + } else { + int lrConstainted = 0; + std::map::iterator lrit = freeVarMap.begin(); + for (; lrit != freeVarMap.end(); lrit++) { + if (lrConstrainedMap[var].find(lrit->first) != lrConstrainedMap[var].end()) { + lrConstainted = 1; + break; + } + } + if (lrConstainted == 0) { + freeVarMap[var] = 1; + } + } + } + } + } + + std::map >::iterator itor = depMap.begin(); + for (; itor != depMap.end(); itor++) { + for (std::map::iterator itor1 = itor->second.begin(); itor1 != itor->second.end(); itor1++) { + if (variable_set.find(itor1->first) != variable_set.end()) { // expr type = var + expr * var = get_alias_index_ast(aliasIndexMap, itor1->first); + // if a var is dep on itself and all dependence are type 2, it's a free variable + // e.g {y --> x(2), y(2), m --> m(2), n(2)} y,m are free + { + if (depMap.find(var) == depMap.end()) { + if (freeVarMap.find(var) == freeVarMap.end()) { + if (lrConstrainedMap.find(var) == lrConstrainedMap.end()) { + freeVarMap[var] = 1; + } else { + int lrConstainted = 0; + std::map::iterator lrit = freeVarMap.begin(); + for (; lrit != freeVarMap.end(); lrit++) { + if (lrConstrainedMap[var].find(lrit->first) != lrConstrainedMap[var].end()) { + lrConstainted = 1; + break; + } + } + if (lrConstainted == 0) { + freeVarMap[var] = 1; + } + } + + } else { + freeVarMap[var] = freeVarMap[var] + 1; + } + } + } + } + } + } + } + + return 0; + } + + // Check agreement between integer and string theories for the term a = (str.to-int S). + // Returns true if axioms were added, and false otherwise. + bool theory_str::finalcheck_str2int(app * a) { + bool axiomAdd = false; + context & ctx = get_context(); + ast_manager & m = get_manager(); + + expr * S = a->get_arg(0); + + // check integer theory + rational Ival; + bool Ival_exists = get_value(a, Ival); + if (Ival_exists) { + TRACE("str", tout << "integer theory assigns " << mk_pp(a, m) << " = " << Ival.to_string() << std::endl;); + // if that value is not -1, we can assert (str.to-int S) = Ival --> S = "Ival" + if (!Ival.is_minus_one()) { + zstring Ival_str(Ival.to_string().c_str()); + expr_ref premise(ctx.mk_eq_atom(a, m_autil.mk_numeral(Ival, true)), m); + expr_ref conclusion(ctx.mk_eq_atom(S, mk_string(Ival_str)), m); + expr_ref axiom(rewrite_implication(premise, conclusion), m); + if (!string_int_axioms.contains(axiom)) { + string_int_axioms.insert(axiom); + assert_axiom(axiom); + m_trail_stack.push(insert_obj_trail(string_int_axioms, axiom)); + axiomAdd = true; + } + } + } else { + TRACE("str", tout << "integer theory has no assignment for " << mk_pp(a, m) << std::endl;); + NOT_IMPLEMENTED_YET(); + } + + return axiomAdd; + } + + bool theory_str::finalcheck_int2str(app * a) { + bool axiomAdd = false; + context & ctx = get_context(); + ast_manager & m = get_manager(); + + expr * N = a->get_arg(0); + + // check string theory + bool Sval_expr_exists; + expr * Sval_expr = get_eqc_value(a, Sval_expr_exists); + if (Sval_expr_exists) { + zstring Sval; + u.str.is_string(Sval_expr, Sval); + TRACE("str", tout << "string theory assigns \"" << mk_pp(a, m) << " = " << Sval << "\n";); + // empty string --> integer value < 0 + if (Sval.empty()) { + // ignore this. we should already assert the axiom for what happens when the string is "" + } else { + // nonempty string --> convert to correct integer value, or disallow it + rational convertedRepresentation(0); + rational ten(10); + bool conversionOK = true; + for (unsigned i = 0; i < Sval.length(); ++i) { + char digit = (int)Sval[i]; + if (isdigit((int)digit)) { + std::string sDigit(1, digit); + int val = atoi(sDigit.c_str()); + convertedRepresentation = (ten * convertedRepresentation) + rational(val); + } else { + // not a digit, invalid + TRACE("str", tout << "str.to-int argument contains non-digit character '" << digit << "'" << std::endl;); + conversionOK = false; + break; + } + } + if (conversionOK) { + expr_ref premise(ctx.mk_eq_atom(a, mk_string(Sval)), m); + expr_ref conclusion(ctx.mk_eq_atom(N, m_autil.mk_numeral(convertedRepresentation, true)), m); + expr_ref axiom(rewrite_implication(premise, conclusion), m); + if (!string_int_axioms.contains(axiom)) { + string_int_axioms.insert(axiom); + assert_axiom(axiom); + m_trail_stack.push(insert_obj_trail(string_int_axioms, axiom)); + axiomAdd = true; + } + } else { + expr_ref axiom(m.mk_not(ctx.mk_eq_atom(a, mk_string(Sval))), m); + // always assert this axiom because this is a conflict clause + assert_axiom(axiom); + axiomAdd = true; + } + } + } else { + TRACE("str", tout << "string theory has no assignment for " << mk_pp(a, m) << std::endl;); + NOT_IMPLEMENTED_YET(); + } + return axiomAdd; + } + + void theory_str::collect_var_concat(expr * node, std::set & varSet, std::set & concatSet) { + if (variable_set.find(node) != variable_set.end()) { + if (internal_lenTest_vars.find(node) == internal_lenTest_vars.end()) { + varSet.insert(node); + } + } + else if (is_app(node)) { + app * aNode = to_app(node); + if (u.str.is_length(aNode)) { + // Length + return; + } + if (u.str.is_concat(aNode)) { + if (concatSet.find(node) == concatSet.end()) { + concatSet.insert(node); + } + } + // recursively visit all arguments + for (unsigned i = 0; i < aNode->get_num_args(); ++i) { + expr * arg = aNode->get_arg(i); + collect_var_concat(arg, varSet, concatSet); + } + } + } + + bool theory_str::propagate_length_within_eqc(expr * var) { + bool res = false; + ast_manager & m = get_manager(); + context & ctx = get_context(); + + TRACE("str", tout << "propagate_length_within_eqc: " << mk_ismt2_pp(var, m) << std::endl ;); + + rational varLen; + if (! get_len_value(var, varLen)) { + bool hasLen = false; + expr * nodeWithLen= var; + do { + if (get_len_value(nodeWithLen, varLen)) { + hasLen = true; + break; + } + nodeWithLen = get_eqc_next(nodeWithLen); + } while (nodeWithLen != var); + + if (hasLen) { + // var = nodeWithLen --> |var| = |nodeWithLen| + expr_ref_vector l_items(m); + expr_ref varEqNode(ctx.mk_eq_atom(var, nodeWithLen), m); + l_items.push_back(varEqNode); + + expr_ref nodeWithLenExpr (mk_strlen(nodeWithLen), m); + expr_ref varLenExpr (mk_int(varLen), m); + expr_ref lenEqNum(ctx.mk_eq_atom(nodeWithLenExpr, varLenExpr), m); + l_items.push_back(lenEqNum); + + expr_ref axl(m.mk_and(l_items.size(), l_items.c_ptr()), m); + expr_ref varLen(mk_strlen(var), m); + expr_ref axr(ctx.mk_eq_atom(varLen, mk_int(varLen)), m); + assert_implication(axl, axr); + TRACE("str", tout << mk_ismt2_pp(axl, m) << std::endl << " ---> " << std::endl << mk_ismt2_pp(axr, m);); + res = true; + } + } + return res; + } + + bool theory_str::propagate_length(std::set & varSet, std::set & concatSet, std::map & exprLenMap) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + expr_ref_vector assignments(m); + ctx.get_assignments(assignments); + bool axiomAdded = false; + // collect all concats in context + for (expr_ref_vector::iterator it = assignments.begin(); it != assignments.end(); ++it) { + if (! ctx.is_relevant(*it)) { + continue; + } + if (m.is_eq(*it)) { + collect_var_concat(*it, varSet, concatSet); + } + } + // iterate each concat + // if a concat doesn't have length info, check if the length of all leaf nodes can be resolved + for (std::set::iterator it = concatSet.begin(); it != concatSet.end(); it++) { + expr * concat = *it; + rational lenValue; + expr_ref concatlenExpr (mk_strlen(concat), m) ; + bool allLeafResolved = true; + if (! get_value(concatlenExpr, lenValue)) { + // the length fo concat is unresolved yet + if (get_len_value(concat, lenValue)) { + // but all leaf nodes have length information + TRACE("str", tout << "* length pop-up: " << mk_ismt2_pp(concat, m) << "| = " << lenValue << std::endl;); + std::set leafNodes; + get_unique_non_concat_nodes(concat, leafNodes); + expr_ref_vector l_items(m); + for (std::set::iterator leafIt = leafNodes.begin(); leafIt != leafNodes.end(); ++leafIt) { + rational leafLenValue; + if (get_len_value(*leafIt, leafLenValue)) { + expr_ref leafItLenExpr (mk_strlen(*leafIt), m); + expr_ref leafLenValueExpr (mk_int(leafLenValue), m); + expr_ref lcExpr (ctx.mk_eq_atom(leafItLenExpr, leafLenValueExpr), m); + l_items.push_back(lcExpr); + } else { + allLeafResolved = false; + break; + } + } + if (allLeafResolved) { + expr_ref axl(m.mk_and(l_items.size(), l_items.c_ptr()), m); + expr_ref lenValueExpr (mk_int(lenValue), m); + expr_ref axr(ctx.mk_eq_atom(concatlenExpr, lenValueExpr), m); + assert_implication(axl, axr); + TRACE("str", tout << mk_ismt2_pp(axl, m) << std::endl << " ---> " << std::endl << mk_ismt2_pp(axr, m)<< std::endl;); + axiomAdded = true; + } + } + } + } + // if no concat length is propagated, check the length of variables. + if (! axiomAdded) { + for (std::set::iterator it = varSet.begin(); it != varSet.end(); it++) { + expr * var = *it; + rational lenValue; + expr_ref varlen (mk_strlen(var), m) ; + if (! get_value(varlen, lenValue)) { + if (propagate_length_within_eqc(var)) { + axiomAdded = true; + } + } + } + + } + return axiomAdded; + } + + void theory_str::get_unique_non_concat_nodes(expr * node, std::set & argSet) { + app * a_node = to_app(node); + if (!u.str.is_concat(a_node)) { + argSet.insert(node); + return; + } else { + SASSERT(a_node->get_num_args() == 2); + expr * leftArg = a_node->get_arg(0); + expr * rightArg = a_node->get_arg(1); + get_unique_non_concat_nodes(leftArg, argSet); + get_unique_non_concat_nodes(rightArg, argSet); + } + } + + final_check_status theory_str::final_check_eh() { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + expr_ref_vector assignments(m); + ctx.get_assignments(assignments); + + if (opt_VerifyFinalCheckProgress) { + finalCheckProgressIndicator = false; + } + + TRACE("str", tout << "final check" << std::endl;); + TRACE_CODE(if (is_trace_enabled("t_str_dump_assign")) { dump_assignments(); }); + check_variable_scope(); + + if (opt_DeferEQCConsistencyCheck) { + TRACE("str", tout << "performing deferred EQC consistency check" << std::endl;); + std::set eqc_roots; + for (ptr_vector::const_iterator it = ctx.begin_enodes(); it != ctx.end_enodes(); ++it) { + enode * e = *it; + enode * root = e->get_root(); + eqc_roots.insert(root); + } + + bool found_inconsistency = false; + + for (std::set::iterator it = eqc_roots.begin(); it != eqc_roots.end(); ++it) { + enode * e = *it; + app * a = e->get_owner(); + if (!(m.get_sort(a) == u.str.mk_string_sort())) { + TRACE("str", tout << "EQC root " << mk_pp(a, m) << " not a string term; skipping" << std::endl;); + } else { + TRACE("str", tout << "EQC root " << mk_pp(a, m) << " is a string term. Checking this EQC" << std::endl;); + // first call check_concat_len_in_eqc() on each member of the eqc + enode * e_it = e; + enode * e_root = e_it; + do { + bool status = check_concat_len_in_eqc(e_it->get_owner()); + if (!status) { + TRACE("str", tout << "concat-len check asserted an axiom on " << mk_pp(e_it->get_owner(), m) << std::endl;); + found_inconsistency = true; + } + e_it = e_it->get_next(); + } while (e_it != e_root); + + // now grab any two distinct elements from the EQC and call new_eq_check() on them + enode * e1 = e; + enode * e2 = e1->get_next(); + if (e1 != e2) { + TRACE("str", tout << "deferred new_eq_check() over EQC of " << mk_pp(e1->get_owner(), m) << " and " << mk_pp(e2->get_owner(), m) << std::endl;); + bool result = new_eq_check(e1->get_owner(), e2->get_owner()); + if (!result) { + TRACE("str", tout << "new_eq_check found inconsistencies" << std::endl;); + found_inconsistency = true; + } + } + } + } + + if (found_inconsistency) { + TRACE("str", tout << "Found inconsistency in final check! Returning to search." << std::endl;); + return FC_CONTINUE; + } else { + TRACE("str", tout << "Deferred consistency check passed. Continuing in final check." << std::endl;); + } + } + + // run dependence analysis to find free string variables + std::map varAppearInAssign; + std::map freeVar_map; + std::map > unrollGroup_map; + std::map > var_eq_concat_map; + int conflictInDep = ctx_dep_analysis(varAppearInAssign, freeVar_map, unrollGroup_map, var_eq_concat_map); + if (conflictInDep == -1) { + // return Z3_TRUE; + return FC_DONE; + } + + // enhancement: improved backpropagation of string constants into var=concat terms + bool backpropagation_occurred = false; + for (std::map >::iterator veqc_map_it = var_eq_concat_map.begin(); + veqc_map_it != var_eq_concat_map.end(); ++veqc_map_it) { + expr * var = veqc_map_it->first; + for (std::map::iterator concat_map_it = veqc_map_it->second.begin(); + concat_map_it != veqc_map_it->second.end(); ++concat_map_it) { + app * concat = to_app(concat_map_it->first); + expr * concat_lhs = concat->get_arg(0); + expr * concat_rhs = concat->get_arg(1); + // If the concat LHS and RHS both have a string constant in their EQC, + // but the var does not, then we assert an axiom of the form + // (lhs = "lhs" AND rhs = "rhs") --> (Concat lhs rhs) = "lhsrhs" + bool concat_lhs_haseqc, concat_rhs_haseqc, var_haseqc; + expr * concat_lhs_str = get_eqc_value(concat_lhs, concat_lhs_haseqc); + expr * concat_rhs_str = get_eqc_value(concat_rhs, concat_rhs_haseqc); + get_eqc_value(var, var_haseqc); + if (concat_lhs_haseqc && concat_rhs_haseqc && !var_haseqc) { + TRACE("str", tout << "backpropagate into " << mk_pp(var, m) << " = " << mk_pp(concat, m) << std::endl + << "LHS ~= " << mk_pp(concat_lhs_str, m) << " RHS ~= " << mk_pp(concat_rhs_str, m) << std::endl;); + zstring lhsString, rhsString; + u.str.is_string(concat_lhs_str, lhsString); + u.str.is_string(concat_rhs_str, rhsString); + zstring concatString = lhsString + rhsString; + expr_ref lhs1(ctx.mk_eq_atom(concat_lhs, concat_lhs_str), m); + expr_ref lhs2(ctx.mk_eq_atom(concat_rhs, concat_rhs_str), m); + expr_ref lhs(m.mk_and(lhs1, lhs2), m); + expr_ref rhs(ctx.mk_eq_atom(concat, mk_string(concatString)), m); + assert_implication(lhs, rhs); + backpropagation_occurred = true; + } + } + } + + if (backpropagation_occurred) { + TRACE("str", tout << "Resuming search due to axioms added by backpropagation." << std::endl;); + return FC_CONTINUE; + } + + // enhancement: improved backpropagation of length information + { + std::set varSet; + std::set concatSet; + std::map exprLenMap; + + bool length_propagation_occurred = propagate_length(varSet, concatSet, exprLenMap); + if (length_propagation_occurred) { + TRACE("str", tout << "Resuming search due to axioms added by length propagation." << std::endl;); + return FC_CONTINUE; + } + } + + bool needToAssignFreeVars = false; + std::set free_variables; + std::set unused_internal_variables; + { // Z3str2 free variables check + std::map::iterator itor = varAppearInAssign.begin(); + for (; itor != varAppearInAssign.end(); ++itor) { + /* + std::string vName = std::string(Z3_ast_to_string(ctx, itor->first)); + if (vName.length() >= 3 && vName.substr(0, 3) == "$$_") + continue; + */ + if (internal_variable_set.find(itor->first) != internal_variable_set.end() + || regex_variable_set.find(itor->first) != regex_variable_set.end()) { + // this can be ignored, I think + TRACE("str", tout << "free internal variable " << mk_pp(itor->first, m) << " ignored" << std::endl;); + continue; + } + bool hasEqcValue = false; + expr * eqcString = get_eqc_value(itor->first, hasEqcValue); + if (!hasEqcValue) { + TRACE("str", tout << "found free variable " << mk_pp(itor->first, m) << std::endl;); + needToAssignFreeVars = true; + free_variables.insert(itor->first); + // break; + } else { + // debug + TRACE("str", tout << "variable " << mk_pp(itor->first, m) << " = " << mk_pp(eqcString, m) << std::endl;); + } + } + } + + if (!needToAssignFreeVars) { + + // check string-int terms + bool addedStrIntAxioms = false; + for (unsigned i = 0; i < string_int_conversion_terms.size(); ++i) { + app * ex = to_app(string_int_conversion_terms[i].get()); + if (u.str.is_stoi(ex)) { + bool axiomAdd = finalcheck_str2int(ex); + if (axiomAdd) { + addedStrIntAxioms = true; + } + } else if (u.str.is_itos(ex)) { + bool axiomAdd = finalcheck_int2str(ex); + if (axiomAdd) { + addedStrIntAxioms = true; + } + } else { + UNREACHABLE(); + } + } + if (addedStrIntAxioms) { + TRACE("str", tout << "Resuming search due to addition of string-integer conversion axioms." << std::endl;); + return FC_CONTINUE; + } + + if (unused_internal_variables.empty()) { + TRACE("str", tout << "All variables are assigned. Done!" << std::endl;); + return FC_DONE; + } else { + TRACE("str", tout << "Assigning decoy values to free internal variables." << std::endl;); + for (std::set::iterator it = unused_internal_variables.begin(); it != unused_internal_variables.end(); ++it) { + expr * var = *it; + expr_ref assignment(m.mk_eq(var, mk_string("**unused**")), m); + assert_axiom(assignment); + } + return FC_CONTINUE; + } + } + + CTRACE("str", needToAssignFreeVars, + tout << "Need to assign values to the following free variables:" << std::endl; + for (std::set::iterator itx = free_variables.begin(); itx != free_variables.end(); ++itx) { + tout << mk_ismt2_pp(*itx, m) << std::endl; + } + tout << "freeVar_map has the following entries:" << std::endl; + for (std::map::iterator fvIt = freeVar_map.begin(); fvIt != freeVar_map.end(); fvIt++) { + expr * var = fvIt->first; + tout << mk_ismt2_pp(var, m) << std::endl; + } + ); + + // ----------------------------------------------------------- + // variables in freeVar are those not bounded by Concats + // classify variables in freeVarMap: + // (1) freeVar = unroll(r1, t1) + // (2) vars are not bounded by either concat or unroll + // ----------------------------------------------------------- + std::map > fv_unrolls_map; + std::set tmpSet; + expr * constValue = NULL; + for (std::map::iterator fvIt2 = freeVar_map.begin(); fvIt2 != freeVar_map.end(); fvIt2++) { + expr * var = fvIt2->first; + tmpSet.clear(); + get_eqc_allUnroll(var, constValue, tmpSet); + if (tmpSet.size() > 0) { + fv_unrolls_map[var] = tmpSet; + } + } + // erase var bounded by an unroll function from freeVar_map + for (std::map >::iterator fvIt3 = fv_unrolls_map.begin(); + fvIt3 != fv_unrolls_map.end(); fvIt3++) { + expr * var = fvIt3->first; + TRACE("str", tout << "erase free variable " << mk_pp(var, m) << " from freeVar_map, it is bounded by an Unroll" << std::endl;); + freeVar_map.erase(var); + } + + // collect the case: + // * Concat(X, Y) = unroll(r1, t1) /\ Concat(X, Y) = unroll(r2, t2) + // concatEqUnrollsMap[Concat(X, Y)] = {unroll(r1, t1), unroll(r2, t2)} + + std::map > concatEqUnrollsMap; + for (std::map >::iterator urItor = unrollGroup_map.begin(); + urItor != unrollGroup_map.end(); urItor++) { + expr * unroll = urItor->first; + expr * curr = unroll; + do { + if (u.str.is_concat(to_app(curr))) { + concatEqUnrollsMap[curr].insert(unroll); + concatEqUnrollsMap[curr].insert(unrollGroup_map[unroll].begin(), unrollGroup_map[unroll].end()); + } + enode * e_curr = ctx.get_enode(curr); + curr = e_curr->get_next()->get_owner(); + // curr = get_eqc_next(curr); + } while (curr != unroll); + } + + std::map > concatFreeArgsEqUnrollsMap; + std::set fvUnrollSet; + for (std::map >::iterator concatItor = concatEqUnrollsMap.begin(); + concatItor != concatEqUnrollsMap.end(); concatItor++) { + expr * concat = concatItor->first; + expr * concatArg1 = to_app(concat)->get_arg(0); + expr * concatArg2 = to_app(concat)->get_arg(1); + bool arg1Bounded = false; + bool arg2Bounded = false; + // arg1 + if (variable_set.find(concatArg1) != variable_set.end()) { + if (freeVar_map.find(concatArg1) == freeVar_map.end()) { + arg1Bounded = true; + } else { + fvUnrollSet.insert(concatArg1); + } + } else if (u.str.is_concat(to_app(concatArg1))) { + if (concatEqUnrollsMap.find(concatArg1) == concatEqUnrollsMap.end()) { + arg1Bounded = true; + } + } + // arg2 + if (variable_set.find(concatArg2) != variable_set.end()) { + if (freeVar_map.find(concatArg2) == freeVar_map.end()) { + arg2Bounded = true; + } else { + fvUnrollSet.insert(concatArg2); + } + } else if (u.str.is_concat(to_app(concatArg2))) { + if (concatEqUnrollsMap.find(concatArg2) == concatEqUnrollsMap.end()) { + arg2Bounded = true; + } + } + if (!arg1Bounded && !arg2Bounded) { + concatFreeArgsEqUnrollsMap[concat].insert( + concatEqUnrollsMap[concat].begin(), + concatEqUnrollsMap[concat].end()); + } + } + for (std::set::iterator vItor = fvUnrollSet.begin(); vItor != fvUnrollSet.end(); vItor++) { + TRACE("str", tout << "remove " << mk_pp(*vItor, m) << " from freeVar_map" << std::endl;); + freeVar_map.erase(*vItor); + } + + // Assign free variables + std::set fSimpUnroll; + + constValue = NULL; + + { + TRACE("str", tout << "free var map (#" << freeVar_map.size() << "):" << std::endl; + for (std::map::iterator freeVarItor1 = freeVar_map.begin(); freeVarItor1 != freeVar_map.end(); freeVarItor1++) { + expr * freeVar = freeVarItor1->first; + rational lenValue; + bool lenValue_exists = get_len_value(freeVar, lenValue); + tout << mk_pp(freeVar, m) << " [depCnt = " << freeVarItor1->second << ", length = " + << (lenValue_exists ? lenValue.to_string() : "?") + << "]" << std::endl; + } + ); + } + + for (std::map >::iterator fvIt2 = concatFreeArgsEqUnrollsMap.begin(); + fvIt2 != concatFreeArgsEqUnrollsMap.end(); fvIt2++) { + expr * concat = fvIt2->first; + for (std::set::iterator urItor = fvIt2->second.begin(); urItor != fvIt2->second.end(); urItor++) { + expr * unroll = *urItor; + process_concat_eq_unroll(concat, unroll); + } + } + + // -------- + // experimental free variable assignment - begin + // * special handling for variables that are not used in concat + // -------- + bool testAssign = true; + if (!testAssign) { + for (std::map::iterator fvIt = freeVar_map.begin(); fvIt != freeVar_map.end(); fvIt++) { + expr * freeVar = fvIt->first; + /* + std::string vName = std::string(Z3_ast_to_string(ctx, freeVar)); + if (vName.length() >= 9 && vName.substr(0, 9) == "$$_regVar") { + continue; + } + */ + expr * toAssert = gen_len_val_options_for_free_var(freeVar, NULL, ""); + if (toAssert != NULL) { + assert_axiom(toAssert); + } + } + } else { + process_free_var(freeVar_map); + } + // experimental free variable assignment - end + + // now deal with removed free variables that are bounded by an unroll + TRACE("str", tout << "fv_unrolls_map (#" << fv_unrolls_map.size() << "):" << std::endl;); + for (std::map >::iterator fvIt1 = fv_unrolls_map.begin(); + fvIt1 != fv_unrolls_map.end(); fvIt1++) { + expr * var = fvIt1->first; + fSimpUnroll.clear(); + get_eqc_simpleUnroll(var, constValue, fSimpUnroll); + if (fSimpUnroll.size() == 0) { + gen_assign_unroll_reg(fv_unrolls_map[var]); + } else { + expr * toAssert = gen_assign_unroll_Str2Reg(var, fSimpUnroll); + if (toAssert != NULL) { + assert_axiom(toAssert); + } + } + } + + if (opt_VerifyFinalCheckProgress && !finalCheckProgressIndicator) { + TRACE("str", tout << "BUG: no progress in final check, giving up!!" << std::endl;); + m.raise_exception("no progress in theory_str final check"); + } + + return FC_CONTINUE; // since by this point we've added axioms + } + + inline zstring int_to_string(int i) { + std::stringstream ss; + ss << i; + std::string str = ss.str(); + return zstring(str.c_str()); + } + + inline std::string longlong_to_string(long long i) { + std::stringstream ss; + ss << i; + return ss.str(); + } + + void theory_str::print_value_tester_list(svector > & testerList) { + ast_manager & m = get_manager(); + TRACE("str", + int ss = testerList.size(); + tout << "valueTesterList = {"; + for (int i = 0; i < ss; ++i) { + if (i % 4 == 0) { + tout << std::endl; + } + tout << "(" << testerList[i].first << ", "; + tout << mk_ismt2_pp(testerList[i].second, m); + tout << "), "; + } + tout << std::endl << "}" << std::endl; + ); + } + + zstring theory_str::gen_val_string(int len, int_vector & encoding) { + SASSERT(charSetSize > 0); + SASSERT(char_set != NULL); + + std::string re(len, char_set[0]); + for (int i = 0; i < (int) encoding.size() - 1; i++) { + int idx = encoding[i]; + re[len - 1 - i] = char_set[idx]; + } + return zstring(re.c_str()); + } + + /* + * The return value indicates whether we covered the search space. + * - If the next encoding is valid, return false + * - Otherwise, return true + */ + bool theory_str::get_next_val_encode(int_vector & base, int_vector & next) { + SASSERT(charSetSize > 0); + + TRACE("str", tout << "base vector: [ "; + for (unsigned i = 0; i < base.size(); ++i) { + tout << base[i] << " "; + } + tout << "]" << std::endl; + ); + + int s = 0; + int carry = 0; + next.reset(); + + for (int i = 0; i < (int) base.size(); i++) { + if (i == 0) { + s = base[i] + 1; + carry = s / charSetSize; + s = s % charSetSize; + next.push_back(s); + } else { + s = base[i] + carry; + carry = s / charSetSize; + s = s % charSetSize; + next.push_back(s); + } + } + if (next[next.size() - 1] > 0) { + next.reset(); + return true; + } else { + return false; + } + } + + expr * theory_str::gen_val_options(expr * freeVar, expr * len_indicator, expr * val_indicator, + zstring lenStr, int tries) { + ast_manager & m = get_manager(); + context & ctx = get_context(); + + int distance = 32; + + // ---------------------------------------------------------------------------------------- + // generate value options encoding + // encoding is a vector of size (len + 1) + // e.g, len = 2, + // encoding {1, 2, 0} means the value option is "charSet[2]"."charSet[1]" + // the last item in the encoding indicates whether the whole space is covered + // for example, if the charSet = {a, b}. All valid encodings are + // {0, 0, 0}, {1, 0, 0}, {0, 1, 0}, {1, 1, 0} + // if add 1 to the last one, we get + // {0, 0, 1} + // the last item "1" shows this is not a valid encoding, and we have covered all space + // ---------------------------------------------------------------------------------------- + int len = atoi(lenStr.encode().c_str()); + bool coverAll = false; + svector options; + int_vector base; + + TRACE("str", tout + << "freeVar = " << mk_ismt2_pp(freeVar, m) << std::endl + << "len_indicator = " << mk_ismt2_pp(len_indicator, m) << std::endl + << "val_indicator = " << mk_ismt2_pp(val_indicator, m) << std::endl + << "lenstr = " << lenStr << "\n" + << "tries = " << tries << "\n"; + if (m_params.m_AggressiveValueTesting) { + tout << "note: aggressive value testing is enabled" << std::endl; + } + ); + + if (tries == 0) { + base = int_vector(len + 1, 0); + coverAll = false; + } else { + expr * lastestValIndi = fvar_valueTester_map[freeVar][len][tries - 1].second; + TRACE("str", tout << "last value tester = " << mk_ismt2_pp(lastestValIndi, m) << std::endl;); + coverAll = get_next_val_encode(val_range_map[lastestValIndi], base); + } + + long long l = (tries) * distance; + long long h = l; + for (int i = 0; i < distance; i++) { + if (coverAll) + break; + options.push_back(base); + h++; + coverAll = get_next_val_encode(options[options.size() - 1], base); + } + val_range_map[val_indicator] = options[options.size() - 1]; + + TRACE("str", + tout << "value tester encoding " << "{" << std::endl; + int_vector vec = val_range_map[val_indicator]; + + for (int_vector::iterator it = vec.begin(); it != vec.end(); ++it) { + tout << *it << std::endl; + } + tout << "}" << std::endl; + ); + + // ---------------------------------------------------------------------------------------- + + ptr_vector orList; + ptr_vector andList; + + for (long long i = l; i < h; i++) { + orList.push_back(m.mk_eq(val_indicator, mk_string(longlong_to_string(i).c_str()) )); + if (m_params.m_AggressiveValueTesting) { + literal l = mk_eq(val_indicator, mk_string(longlong_to_string(i).c_str()), false); + ctx.mark_as_relevant(l); + ctx.force_phase(l); + } + + zstring aStr = gen_val_string(len, options[i - l]); + expr * strAst; + if (m_params.m_UseFastValueTesterCache) { + if (!valueTesterCache.find(aStr, strAst)) { + strAst = mk_string(aStr); + valueTesterCache.insert(aStr, strAst); + m_trail.push_back(strAst); + } + } else { + strAst = mk_string(aStr); + } + andList.push_back(m.mk_eq(orList[orList.size() - 1], m.mk_eq(freeVar, strAst))); + } + if (!coverAll) { + orList.push_back(m.mk_eq(val_indicator, mk_string("more"))); + if (m_params.m_AggressiveValueTesting) { + literal l = mk_eq(val_indicator, mk_string("more"), false); + ctx.mark_as_relevant(l); + ctx.force_phase(~l); + } + } + + expr ** or_items = alloc_svect(expr*, orList.size()); + expr ** and_items = alloc_svect(expr*, andList.size() + 1); + + for (int i = 0; i < (int) orList.size(); i++) { + or_items[i] = orList[i]; + } + if (orList.size() > 1) + and_items[0] = m.mk_or(orList.size(), or_items); + else + and_items[0] = or_items[0]; + + for (int i = 0; i < (int) andList.size(); i++) { + and_items[i + 1] = andList[i]; + } + expr * valTestAssert = m.mk_and(andList.size() + 1, and_items); + + // --------------------------------------- + // If the new value tester is $$_val_x_16_i + // Should add ($$_len_x_j = 16) /\ ($$_val_x_16_i = "more") + // --------------------------------------- + andList.reset(); + andList.push_back(m.mk_eq(len_indicator, mk_string(lenStr))); + for (int i = 0; i < tries; i++) { + expr * vTester = fvar_valueTester_map[freeVar][len][i].second; + if (vTester != val_indicator) + andList.push_back(m.mk_eq(vTester, mk_string("more"))); + } + expr * assertL = NULL; + if (andList.size() == 1) { + assertL = andList[0]; + } else { + expr ** and_items = alloc_svect(expr*, andList.size()); + for (int i = 0; i < (int) andList.size(); i++) { + and_items[i] = andList[i]; + } + assertL = m.mk_and(andList.size(), and_items); + } + + // (assertL => valTestAssert) <=> (!assertL OR valTestAssert) + valTestAssert = m.mk_or(m.mk_not(assertL), valTestAssert); + return valTestAssert; + } + + expr * theory_str::gen_free_var_options(expr * freeVar, expr * len_indicator, + zstring len_valueStr, expr * valTesterInCbEq, zstring valTesterValueStr) { + ast_manager & m = get_manager(); + + int len = atoi(len_valueStr.encode().c_str()); + + // check whether any value tester is actually in scope + TRACE("str", tout << "checking scope of previous value testers" << std::endl;); + bool map_effectively_empty = true; + if (fvar_valueTester_map[freeVar].find(len) != fvar_valueTester_map[freeVar].end()) { + // there's *something* in the map, but check its scope + svector > entries = fvar_valueTester_map[freeVar][len]; + for (svector >::iterator it = entries.begin(); it != entries.end(); ++it) { + std::pair entry = *it; + expr * aTester = entry.second; + if (internal_variable_set.find(aTester) == internal_variable_set.end()) { + TRACE("str", tout << mk_pp(aTester, m) << " out of scope" << std::endl;); + } else { + TRACE("str", tout << mk_pp(aTester, m) << " in scope" << std::endl;); + map_effectively_empty = false; + break; + } + } + } + + if (map_effectively_empty) { + TRACE("str", tout << "no previous value testers, or none of them were in scope" << std::endl;); + int tries = 0; + expr * val_indicator = mk_internal_valTest_var(freeVar, len, tries); + valueTester_fvar_map[val_indicator] = freeVar; + fvar_valueTester_map[freeVar][len].push_back(std::make_pair(sLevel, val_indicator)); + print_value_tester_list(fvar_valueTester_map[freeVar][len]); + return gen_val_options(freeVar, len_indicator, val_indicator, len_valueStr, tries); + } else { + TRACE("str", tout << "checking previous value testers" << std::endl;); + print_value_tester_list(fvar_valueTester_map[freeVar][len]); + + // go through all previous value testers + // If some doesn't have an eqc value, add its assertion again. + int testerTotal = fvar_valueTester_map[freeVar][len].size(); + int i = 0; + for (; i < testerTotal; i++) { + expr * aTester = fvar_valueTester_map[freeVar][len][i].second; + + // it's probably worth checking scope here, actually + if (internal_variable_set.find(aTester) == internal_variable_set.end()) { + TRACE("str", tout << "value tester " << mk_pp(aTester, m) << " out of scope, skipping" << std::endl;); + continue; + } + + if (aTester == valTesterInCbEq) { + break; + } + + bool anEqcHasValue = false; + // Z3_ast anEqc = get_eqc_value(t, aTester, anEqcHasValue); + expr * aTester_eqc_value = get_eqc_value(aTester, anEqcHasValue); + if (!anEqcHasValue) { + TRACE("str", tout << "value tester " << mk_ismt2_pp(aTester, m) + << " doesn't have an equivalence class value." << std::endl;); + refresh_theory_var(aTester); + + expr * makeupAssert = gen_val_options(freeVar, len_indicator, aTester, len_valueStr, i); + + TRACE("str", tout << "var: " << mk_ismt2_pp(freeVar, m) << std::endl + << mk_ismt2_pp(makeupAssert, m) << std::endl;); + assert_axiom(makeupAssert); + } else { + TRACE("str", tout << "value tester " << mk_ismt2_pp(aTester, m) + << " == " << mk_ismt2_pp(aTester_eqc_value, m) << std::endl;); + } + } + + if (valTesterValueStr == "more") { + expr * valTester = NULL; + if (i + 1 < testerTotal) { + valTester = fvar_valueTester_map[freeVar][len][i + 1].second; + refresh_theory_var(valTester); + } else { + valTester = mk_internal_valTest_var(freeVar, len, i + 1); + valueTester_fvar_map[valTester] = freeVar; + fvar_valueTester_map[freeVar][len].push_back(std::make_pair(sLevel, valTester)); + print_value_tester_list(fvar_valueTester_map[freeVar][len]); + } + expr * nextAssert = gen_val_options(freeVar, len_indicator, valTester, len_valueStr, i + 1); + return nextAssert; + } + + return NULL; + } + } + + void theory_str::reduce_virtual_regex_in(expr * var, expr * regex, expr_ref_vector & items) { + context & ctx = get_context(); + ast_manager & mgr = get_manager(); + + TRACE("str", tout << "reduce regex " << mk_pp(regex, mgr) << " with respect to variable " << mk_pp(var, mgr) << std::endl;); + + app * regexFuncDecl = to_app(regex); + if (u.re.is_to_re(regexFuncDecl)) { + // --------------------------------------------------------- + // var \in Str2Reg(s1) + // ==> + // var = s1 /\ length(var) = length(s1) + // --------------------------------------------------------- + expr * strInside = to_app(regex)->get_arg(0); + items.push_back(ctx.mk_eq_atom(var, strInside)); + items.push_back(ctx.mk_eq_atom(mk_strlen(var), mk_strlen(strInside))); + return; + } + // RegexUnion + else if (u.re.is_union(regexFuncDecl)) { + // --------------------------------------------------------- + // var \in RegexUnion(r1, r2) + // ==> + // (var = newVar1 \/ var = newVar2) + // (var = newVar1 --> length(var) = length(newVar1)) /\ (var = newVar2 --> length(var) = length(newVar2)) + // /\ (newVar1 \in r1) /\ (newVar2 \in r2) + // --------------------------------------------------------- + expr_ref newVar1(mk_regex_rep_var(), mgr); + expr_ref newVar2(mk_regex_rep_var(), mgr); + items.push_back(mgr.mk_or(ctx.mk_eq_atom(var, newVar1), ctx.mk_eq_atom(var, newVar2))); + items.push_back(mgr.mk_or( + mgr.mk_not(ctx.mk_eq_atom(var, newVar1)), + ctx.mk_eq_atom(mk_strlen(var), mk_strlen(newVar1)))); + items.push_back(mgr.mk_or( + mgr.mk_not(ctx.mk_eq_atom(var, newVar2)), + ctx.mk_eq_atom(mk_strlen(var), mk_strlen(newVar2)))); + + expr * regArg1 = to_app(regex)->get_arg(0); + reduce_virtual_regex_in(newVar1, regArg1, items); + + expr * regArg2 = to_app(regex)->get_arg(1); + reduce_virtual_regex_in(newVar2, regArg2, items); + + return; + } + // RegexConcat + else if (u.re.is_concat(regexFuncDecl)) { + // --------------------------------------------------------- + // var \in RegexConcat(r1, r2) + // ==> + // (var = newVar1 . newVar2) /\ (length(var) = length(vewVar1 . newVar2) ) + // /\ (newVar1 \in r1) /\ (newVar2 \in r2) + // --------------------------------------------------------- + expr_ref newVar1(mk_regex_rep_var(), mgr); + expr_ref newVar2(mk_regex_rep_var(), mgr); + expr_ref concatAst(mk_concat(newVar1, newVar2), mgr); + items.push_back(ctx.mk_eq_atom(var, concatAst)); + items.push_back(ctx.mk_eq_atom(mk_strlen(var), + m_autil.mk_add(mk_strlen(newVar1), mk_strlen(newVar2)))); + + expr * regArg1 = to_app(regex)->get_arg(0); + reduce_virtual_regex_in(newVar1, regArg1, items); + expr * regArg2 = to_app(regex)->get_arg(1); + reduce_virtual_regex_in(newVar2, regArg2, items); + return; + } + // Unroll + else if (u.re.is_star(regexFuncDecl)) { + // --------------------------------------------------------- + // var \in Star(r1) + // ==> + // var = unroll(r1, t1) /\ |var| = |unroll(r1, t1)| + // --------------------------------------------------------- + expr * regArg = to_app(regex)->get_arg(0); + expr_ref unrollCnt(mk_unroll_bound_var(), mgr); + expr_ref unrollFunc(mk_unroll(regArg, unrollCnt), mgr); + items.push_back(ctx.mk_eq_atom(var, unrollFunc)); + items.push_back(ctx.mk_eq_atom(mk_strlen(var), mk_strlen(unrollFunc))); + return; + } + // re.range + else if (u.re.is_range(regexFuncDecl)) { + // var in range("a", "z") + // ==> + // (var = "a" or var = "b" or ... or var = "z") + expr_ref lo(regexFuncDecl->get_arg(0), mgr); + expr_ref hi(regexFuncDecl->get_arg(1), mgr); + zstring str_lo, str_hi; + SASSERT(u.str.is_string(lo)); + SASSERT(u.str.is_string(hi)); + u.str.is_string(lo, str_lo); + u.str.is_string(hi, str_hi); + SASSERT(str_lo.length() == 1); + SASSERT(str_hi.length() == 1); + unsigned int c1 = str_lo[0]; + unsigned int c2 = str_hi[0]; + if (c1 > c2) { + // exchange + unsigned int tmp = c1; + c1 = c2; + c2 = tmp; + } + expr_ref_vector range_cases(mgr); + for (unsigned int ch = c1; ch <= c2; ++ch) { + zstring s_ch(ch); + expr_ref rhs(ctx.mk_eq_atom(var, u.str.mk_string(s_ch)), mgr); + range_cases.push_back(rhs); + } + expr_ref rhs(mk_or(range_cases), mgr); + SASSERT(rhs); + assert_axiom(rhs); + return; + } else { + get_manager().raise_exception("unrecognized regex operator"); + UNREACHABLE(); + } + } + + void theory_str::gen_assign_unroll_reg(std::set & unrolls) { + context & ctx = get_context(); + ast_manager & mgr = get_manager(); + + expr_ref_vector items(mgr); + for (std::set::iterator itor = unrolls.begin(); itor != unrolls.end(); itor++) { + expr * unrFunc = *itor; + TRACE("str", tout << "generating assignment for unroll " << mk_pp(unrFunc, mgr) << std::endl;); + + expr * regexInUnr = to_app(unrFunc)->get_arg(0); + expr * cntInUnr = to_app(unrFunc)->get_arg(1); + items.reset(); + + rational low, high; + bool low_exists = lower_bound(cntInUnr, low); + bool high_exists = upper_bound(cntInUnr, high); + + TRACE("str", + tout << "unroll " << mk_pp(unrFunc, mgr) << std::endl; + rational unrLenValue; + bool unrLenValue_exists = get_len_value(unrFunc, unrLenValue); + tout << "unroll length: " << (unrLenValue_exists ? unrLenValue.to_string() : "?") << std::endl; + rational cntInUnrValue; + bool cntHasValue = get_value(cntInUnr, cntInUnrValue); + tout << "unroll count: " << (cntHasValue ? cntInUnrValue.to_string() : "?") + << " low = " + << (low_exists ? low.to_string() : "?") + << " high = " + << (high_exists ? high.to_string() : "?") + << std::endl; + ); + + expr_ref toAssert(mgr); + if (low.is_neg()) { + toAssert = m_autil.mk_ge(cntInUnr, mk_int(0)); + } else { + if (unroll_var_map.find(unrFunc) == unroll_var_map.end()) { + + expr_ref newVar1(mk_regex_rep_var(), mgr); + expr_ref newVar2(mk_regex_rep_var(), mgr); + expr_ref concatAst(mk_concat(newVar1, newVar2), mgr); + expr_ref newCnt(mk_unroll_bound_var(), mgr); + expr_ref newUnrollFunc(mk_unroll(regexInUnr, newCnt), mgr); + + // unroll(r1, t1) = newVar1 . newVar2 + items.push_back(ctx.mk_eq_atom(unrFunc, concatAst)); + items.push_back(ctx.mk_eq_atom(mk_strlen(unrFunc), m_autil.mk_add(mk_strlen(newVar1), mk_strlen(newVar2)))); + // mk_strlen(unrFunc) >= mk_strlen(newVar{1,2}) + items.push_back(m_autil.mk_ge(m_autil.mk_add(mk_strlen(unrFunc), m_autil.mk_mul(mk_int(-1), mk_strlen(newVar1))), mk_int(0))); + items.push_back(m_autil.mk_ge(m_autil.mk_add(mk_strlen(unrFunc), m_autil.mk_mul(mk_int(-1), mk_strlen(newVar2))), mk_int(0))); + // newVar1 \in r1 + reduce_virtual_regex_in(newVar1, regexInUnr, items); + items.push_back(ctx.mk_eq_atom(cntInUnr, m_autil.mk_add(newCnt, mk_int(1)))); + items.push_back(ctx.mk_eq_atom(newVar2, newUnrollFunc)); + items.push_back(ctx.mk_eq_atom(mk_strlen(newVar2), mk_strlen(newUnrollFunc))); + toAssert = ctx.mk_eq_atom( + m_autil.mk_ge(cntInUnr, mk_int(1)), + mk_and(items)); + + // option 0 + expr_ref op0(ctx.mk_eq_atom(cntInUnr, mk_int(0)), mgr); + expr_ref ast1(ctx.mk_eq_atom(unrFunc, mk_string("")), mgr); + expr_ref ast2(ctx.mk_eq_atom(mk_strlen(unrFunc), mk_int(0)), mgr); + expr_ref and1(mgr.mk_and(ast1, ast2), mgr); + + // put together + toAssert = mgr.mk_and(ctx.mk_eq_atom(op0, and1), toAssert); + + unroll_var_map[unrFunc] = toAssert; + } else { + toAssert = unroll_var_map[unrFunc]; + } + } + m_trail.push_back(toAssert); + assert_axiom(toAssert); + } + } + + static int computeGCD(int x, int y) { + if (x == 0) { + return y; + } + while (y != 0) { + if (x > y) { + x = x - y; + } else { + y = y - x; + } + } + return x; + } + + static int computeLCM(int a, int b) { + int temp = computeGCD(a, b); + return temp ? (a / temp * b) : 0; + } + + static zstring get_unrolled_string(zstring core, int count) { + zstring res(""); + for (int i = 0; i < count; i++) { + res = res + core; + } + return res; + } + + expr * theory_str::gen_assign_unroll_Str2Reg(expr * n, std::set & unrolls) { + context & ctx = get_context(); + ast_manager & mgr = get_manager(); + + int lcm = 1; + int coreValueCount = 0; + expr * oneUnroll = NULL; + zstring oneCoreStr(""); + for (std::set::iterator itor = unrolls.begin(); itor != unrolls.end(); itor++) { + expr * str2RegFunc = to_app(*itor)->get_arg(0); + expr * coreVal = to_app(str2RegFunc)->get_arg(0); + zstring coreStr; + u.str.is_string(coreVal, coreStr); + if (oneUnroll == NULL) { + oneUnroll = *itor; + oneCoreStr = coreStr; + } + coreValueCount++; + int core1Len = coreStr.length(); + lcm = computeLCM(lcm, core1Len); + } + // + bool canHaveNonEmptyAssign = true; + expr_ref_vector litems(mgr); + zstring lcmStr = get_unrolled_string(oneCoreStr, (lcm / oneCoreStr.length())); + for (std::set::iterator itor = unrolls.begin(); itor != unrolls.end(); itor++) { + expr * str2RegFunc = to_app(*itor)->get_arg(0); + expr * coreVal = to_app(str2RegFunc)->get_arg(0); + zstring coreStr; + u.str.is_string(coreVal, coreStr); + unsigned int core1Len = coreStr.length(); + zstring uStr = get_unrolled_string(coreStr, (lcm / core1Len)); + if (uStr != lcmStr) { + canHaveNonEmptyAssign = false; + } + litems.push_back(ctx.mk_eq_atom(n, *itor)); + } + + if (canHaveNonEmptyAssign) { + return gen_unroll_conditional_options(n, unrolls, lcmStr); + } else { + expr_ref implyL(mk_and(litems), mgr); + expr_ref implyR(ctx.mk_eq_atom(n, mk_string("")), mgr); + // want to return (implyL -> implyR) + expr * final_axiom = rewrite_implication(implyL, implyR); + return final_axiom; + } + } + + expr * theory_str::gen_unroll_conditional_options(expr * var, std::set & unrolls, zstring lcmStr) { + context & ctx = get_context(); + ast_manager & mgr = get_manager(); + + int dist = opt_LCMUnrollStep; + expr_ref_vector litems(mgr); + expr_ref moreAst(mk_string("more"), mgr); + for (std::set::iterator itor = unrolls.begin(); itor != unrolls.end(); itor++) { + expr_ref item(ctx.mk_eq_atom(var, *itor), mgr); + TRACE("str", tout << "considering unroll " << mk_pp(item, mgr) << std::endl;); + litems.push_back(item); + } + + // handle out-of-scope entries in unroll_tries_map + + ptr_vector outOfScopeTesters; + + for (ptr_vector::iterator it = unroll_tries_map[var][unrolls].begin(); + it != unroll_tries_map[var][unrolls].end(); ++it) { + expr * tester = *it; + bool inScope = (internal_unrollTest_vars.find(tester) != internal_unrollTest_vars.end()); + TRACE("str", tout << "unroll test var " << mk_pp(tester, mgr) + << (inScope ? " in scope" : " out of scope") + << std::endl;); + if (!inScope) { + outOfScopeTesters.push_back(tester); + } + } + + for (ptr_vector::iterator it = outOfScopeTesters.begin(); + it != outOfScopeTesters.end(); ++it) { + unroll_tries_map[var][unrolls].erase(*it); + } + + + if (unroll_tries_map[var][unrolls].size() == 0) { + unroll_tries_map[var][unrolls].push_back(mk_unroll_test_var()); + } + + int tries = unroll_tries_map[var][unrolls].size(); + for (int i = 0; i < tries; i++) { + expr * tester = unroll_tries_map[var][unrolls][i]; + // TESTING + refresh_theory_var(tester); + bool testerHasValue = false; + expr * testerVal = get_eqc_value(tester, testerHasValue); + if (!testerHasValue) { + // generate make-up assertion + int l = i * dist; + int h = (i + 1) * dist; + expr_ref lImp(mk_and(litems), mgr); + expr_ref rImp(gen_unroll_assign(var, lcmStr, tester, l, h), mgr); + + SASSERT(lImp); + TRACE("str", tout << "lImp = " << mk_pp(lImp, mgr) << std::endl;); + SASSERT(rImp); + TRACE("str", tout << "rImp = " << mk_pp(rImp, mgr) << std::endl;); + + expr_ref toAssert(mgr.mk_or(mgr.mk_not(lImp), rImp), mgr); + SASSERT(toAssert); + TRACE("str", tout << "Making up assignments for variable which is equal to unbounded Unroll" << std::endl;); + m_trail.push_back(toAssert); + return toAssert; + + // note: this is how the code looks in Z3str2's strRegex.cpp:genUnrollConditionalOptions. + // the return is in the same place + + // insert [tester = "more"] to litems so that the implyL for next tester is correct + litems.push_back(ctx.mk_eq_atom(tester, moreAst)); + } else { + zstring testerStr; + u.str.is_string(testerVal, testerStr); + TRACE("str", tout << "Tester [" << mk_pp(tester, mgr) << "] = " << testerStr << "\n";); + if (testerStr == "more") { + litems.push_back(ctx.mk_eq_atom(tester, moreAst)); + } + } + } + expr * tester = mk_unroll_test_var(); + unroll_tries_map[var][unrolls].push_back(tester); + int l = tries * dist; + int h = (tries + 1) * dist; + expr_ref lImp(mk_and(litems), mgr); + expr_ref rImp(gen_unroll_assign(var, lcmStr, tester, l, h), mgr); + SASSERT(lImp); + SASSERT(rImp); + expr_ref toAssert(mgr.mk_or(mgr.mk_not(lImp), rImp), mgr); + SASSERT(toAssert); + TRACE("str", tout << "Generating assignment for variable which is equal to unbounded Unroll" << std::endl;); + m_trail.push_back(toAssert); + return toAssert; + } + + expr * theory_str::gen_unroll_assign(expr * var, zstring lcmStr, expr * testerVar, int l, int h) { + context & ctx = get_context(); + ast_manager & mgr = get_manager(); + + TRACE("str", tout << "entry: var = " << mk_pp(var, mgr) << ", lcmStr = " << lcmStr + << ", l = " << l << ", h = " << h << "\n";); + + if (m_params.m_AggressiveUnrollTesting) { + TRACE("str", tout << "note: aggressive unroll testing is active" << std::endl;); + } + + expr_ref_vector orItems(mgr); + expr_ref_vector andItems(mgr); + + for (int i = l; i < h; i++) { + zstring iStr = int_to_string(i); + expr_ref testerEqAst(ctx.mk_eq_atom(testerVar, mk_string(iStr)), mgr); + TRACE("str", tout << "testerEqAst = " << mk_pp(testerEqAst, mgr) << std::endl;); + if (m_params.m_AggressiveUnrollTesting) { + literal l = mk_eq(testerVar, mk_string(iStr), false); + ctx.mark_as_relevant(l); + ctx.force_phase(l); + } + + orItems.push_back(testerEqAst); + zstring unrollStrInstance = get_unrolled_string(lcmStr, i); + + expr_ref x1(ctx.mk_eq_atom(testerEqAst, ctx.mk_eq_atom(var, mk_string(unrollStrInstance))), mgr); + TRACE("str", tout << "x1 = " << mk_pp(x1, mgr) << std::endl;); + andItems.push_back(x1); + + expr_ref x2(ctx.mk_eq_atom(testerEqAst, ctx.mk_eq_atom(mk_strlen(var), mk_int(i * lcmStr.length()))), mgr); + TRACE("str", tout << "x2 = " << mk_pp(x2, mgr) << std::endl;); + andItems.push_back(x2); + } + expr_ref testerEqMore(ctx.mk_eq_atom(testerVar, mk_string("more")), mgr); + TRACE("str", tout << "testerEqMore = " << mk_pp(testerEqMore, mgr) << std::endl;); + if (m_params.m_AggressiveUnrollTesting) { + literal l = mk_eq(testerVar, mk_string("more"), false); + ctx.mark_as_relevant(l); + ctx.force_phase(~l); + } + + orItems.push_back(testerEqMore); + int nextLowerLenBound = h * lcmStr.length(); + expr_ref more2(ctx.mk_eq_atom(testerEqMore, + //Z3_mk_ge(mk_length(t, var), mk_int(ctx, nextLowerLenBound)) + m_autil.mk_ge(m_autil.mk_add(mk_strlen(var), mk_int(-1 * nextLowerLenBound)), mk_int(0)) + ), mgr); + TRACE("str", tout << "more2 = " << mk_pp(more2, mgr) << std::endl;); + andItems.push_back(more2); + + expr_ref finalOR(mgr.mk_or(orItems.size(), orItems.c_ptr()), mgr); + TRACE("str", tout << "finalOR = " << mk_pp(finalOR, mgr) << std::endl;); + andItems.push_back(mk_or(orItems)); + + expr_ref finalAND(mgr.mk_and(andItems.size(), andItems.c_ptr()), mgr); + TRACE("str", tout << "finalAND = " << mk_pp(finalAND, mgr) << std::endl;); + + // doing the following avoids a segmentation fault + m_trail.push_back(finalAND); + return finalAND; + } + + expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tries) { + ast_manager & m = get_manager(); + context & ctx = get_context(); + + expr_ref freeVarLen(mk_strlen(freeVar), m); + SASSERT(freeVarLen); + + expr_ref_vector orList(m); + expr_ref_vector andList(m); + + int distance = 3; + int l = (tries - 1) * distance; + int h = tries * distance; + + TRACE("str", + tout << "building andList and orList" << std::endl; + if (m_params.m_AggressiveLengthTesting) { + tout << "note: aggressive length testing is active" << std::endl; + } + ); + + // experimental theory-aware case split support + literal_vector case_split_literals; + + for (int i = l; i < h; ++i) { + expr_ref str_indicator(m); + if (m_params.m_UseFastLengthTesterCache) { + rational ri(i); + expr * lookup_val; + if(lengthTesterCache.find(ri, lookup_val)) { + str_indicator = expr_ref(lookup_val, m); + } else { + // no match; create and insert + zstring i_str = int_to_string(i); + expr_ref new_val(mk_string(i_str), m); + lengthTesterCache.insert(ri, new_val); + m_trail.push_back(new_val); + str_indicator = expr_ref(new_val, m); + } + } else { + zstring i_str = int_to_string(i); + str_indicator = expr_ref(mk_string(i_str), m); + } + expr_ref or_expr(ctx.mk_eq_atom(indicator, str_indicator), m); + orList.push_back(or_expr); + + double priority; + // give high priority to small lengths if this is available + if (i <= 5) { + priority = 0.3; + } else { + // prioritize over "more" + priority = 0.2; + } + add_theory_aware_branching_info(or_expr, priority, l_true); + + if (m_params.m_AggressiveLengthTesting) { + literal l = mk_eq(indicator, str_indicator, false); + ctx.mark_as_relevant(l); + ctx.force_phase(l); + } + + case_split_literals.insert(mk_eq(freeVarLen, mk_int(i), false)); + + expr_ref and_expr(ctx.mk_eq_atom(orList.get(orList.size() - 1), m.mk_eq(freeVarLen, mk_int(i))), m); + andList.push_back(and_expr); + } + + expr_ref more_option(ctx.mk_eq_atom(indicator, mk_string("more")), m); + orList.push_back(more_option); + // decrease priority of this option + add_theory_aware_branching_info(more_option, -0.1, l_true); + if (m_params.m_AggressiveLengthTesting) { + literal l = mk_eq(indicator, mk_string("more"), false); + ctx.mark_as_relevant(l); + ctx.force_phase(~l); + } + + andList.push_back(ctx.mk_eq_atom(orList.get(orList.size() - 1), m_autil.mk_ge(freeVarLen, mk_int(h)))); + + /* + { // more experimental theory case split support + expr_ref tmp(m_autil.mk_ge(freeVarLen, mk_int(h)), m); + ctx.internalize(m_autil.mk_ge(freeVarLen, mk_int(h)), false); + case_split_literals.push_back(ctx.get_literal(tmp)); + ctx.mk_th_case_split(case_split_literals.size(), case_split_literals.c_ptr()); + } + */ + + expr_ref_vector or_items(m); + expr_ref_vector and_items(m); + + for (unsigned i = 0; i < orList.size(); ++i) { + or_items.push_back(orList.get(i)); + } + + and_items.push_back(mk_or(or_items)); + for(unsigned i = 0; i < andList.size(); ++i) { + and_items.push_back(andList.get(i)); + } + + TRACE("str", tout << "check: " << mk_pp(mk_and(and_items), m) << std::endl;); + + expr_ref lenTestAssert = mk_and(and_items); + SASSERT(lenTestAssert); + TRACE("str", tout << "crash avoidance lenTestAssert: " << mk_pp(lenTestAssert, m) << std::endl;); + + int testerCount = tries - 1; + if (testerCount > 0) { + expr_ref_vector and_items_LHS(m); + expr_ref moreAst(mk_string("more"), m); + for (int i = 0; i < testerCount; ++i) { + expr * indicator = fvar_lenTester_map[freeVar][i]; + if (internal_variable_set.find(indicator) == internal_variable_set.end()) { + TRACE("str", tout << "indicator " << mk_pp(indicator, m) << " out of scope; continuing" << std::endl;); + continue; + } else { + TRACE("str", tout << "indicator " << mk_pp(indicator, m) << " in scope" << std::endl;); + and_items_LHS.push_back(ctx.mk_eq_atom(indicator, moreAst)); + } + } + expr_ref assertL(mk_and(and_items_LHS), m); + SASSERT(assertL); + expr * finalAxiom = m.mk_or(m.mk_not(assertL), lenTestAssert.get()); + SASSERT(finalAxiom != NULL); + TRACE("str", tout << "crash avoidance finalAxiom: " << mk_pp(finalAxiom, m) << std::endl;); + return finalAxiom; + } else { + TRACE("str", tout << "crash avoidance lenTestAssert.get(): " << mk_pp(lenTestAssert.get(), m) << std::endl;); + m_trail.push_back(lenTestAssert.get()); + return lenTestAssert.get(); + } + } + + // Return an expression of the form + // (tester = "less" | tester = "N" | tester = "more") & + // (tester = "less" iff len(freeVar) < N) & (tester = "more" iff len(freeVar) > N) & (tester = "N" iff len(freeVar) = N)) + expr_ref theory_str::binary_search_case_split(expr * freeVar, expr * tester, binary_search_info & bounds, literal_vector & case_split) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + rational N = bounds.midPoint; + rational N_minus_one = N - rational::one(); + rational N_plus_one = N + rational::one(); + expr_ref lenFreeVar(mk_strlen(freeVar), m); + + TRACE("str", tout << "create case split for free var " << mk_pp(freeVar, m) + << " over " << mk_pp(tester, m) << " with midpoint " << N << std::endl;); + + expr_ref_vector combinedCaseSplit(m); + expr_ref_vector testerCases(m); + + expr_ref caseLess(ctx.mk_eq_atom(tester, mk_string("less")), m); + testerCases.push_back(caseLess); + combinedCaseSplit.push_back(ctx.mk_eq_atom(caseLess, m_autil.mk_le(lenFreeVar, m_autil.mk_numeral(N_minus_one, true) ))); + + expr_ref caseMore(ctx.mk_eq_atom(tester, mk_string("more")), m); + testerCases.push_back(caseMore); + combinedCaseSplit.push_back(ctx.mk_eq_atom(caseMore, m_autil.mk_ge(lenFreeVar, m_autil.mk_numeral(N_plus_one, true) ))); + + expr_ref caseEq(ctx.mk_eq_atom(tester, mk_string(N.to_string().c_str())), m); + testerCases.push_back(caseEq); + combinedCaseSplit.push_back(ctx.mk_eq_atom(caseEq, ctx.mk_eq_atom(lenFreeVar, m_autil.mk_numeral(N, true)))); + + combinedCaseSplit.push_back(mk_or(testerCases)); + + // force internalization on all terms in testerCases so we can extract literals + for (unsigned i = 0; i < testerCases.size(); ++i) { + expr * testerCase = testerCases.get(i); + if (!ctx.b_internalized(testerCase)) { + ctx.internalize(testerCase, false); + } + literal l = ctx.get_literal(testerCase); + case_split.push_back(l); + } + + expr_ref final_term(mk_and(combinedCaseSplit), m); + SASSERT(final_term); + TRACE("str", tout << "final term: " << mk_pp(final_term, m) << std::endl;); + return final_term; + } + + expr * theory_str::binary_search_length_test(expr * freeVar, expr * previousLenTester, zstring previousLenTesterValue) { + ast_manager & m = get_manager(); + + if (binary_search_len_tester_stack.contains(freeVar) && !binary_search_len_tester_stack[freeVar].empty()) { + TRACE("str", tout << "checking existing length testers for " << mk_pp(freeVar, m) << std::endl; + for (ptr_vector::const_iterator it = binary_search_len_tester_stack[freeVar].begin(); + it != binary_search_len_tester_stack[freeVar].end(); ++it) { + expr * tester = *it; + tout << mk_pp(tester, m) << ": "; + if (binary_search_len_tester_info.contains(tester)) { + binary_search_info & bounds = binary_search_len_tester_info[tester]; + tout << "[" << bounds.lowerBound << " | " << bounds.midPoint << " | " << bounds.upperBound << "]!" << bounds.windowSize; + } else { + tout << "[WARNING: no bounds info available]"; + } + bool hasEqcValue; + expr * testerEqcValue = get_eqc_value(tester, hasEqcValue); + if (hasEqcValue) { + tout << " = " << mk_pp(testerEqcValue, m); + } else { + tout << " [no eqc value]"; + } + tout << std::endl; + } + ); + expr * lastTester = binary_search_len_tester_stack[freeVar].back(); + bool lastTesterHasEqcValue; + expr * lastTesterValue = get_eqc_value(lastTester, lastTesterHasEqcValue); + zstring lastTesterConstant; + if (!lastTesterHasEqcValue) { + TRACE("str", tout << "length tester " << mk_pp(lastTester, m) << " at top of stack doesn't have an EQC value yet" << std::endl;); + // check previousLenTester + if (previousLenTester == lastTester) { + lastTesterConstant = previousLenTesterValue; + TRACE("str", tout << "invoked with previousLenTester info matching top of stack" << std::endl;); + } else { + TRACE("str", tout << "WARNING: unexpected reordering of length testers!" << std::endl;); + UNREACHABLE(); return NULL; + } + } else { + u.str.is_string(lastTesterValue, lastTesterConstant); + } + TRACE("str", tout << "last length tester is assigned \"" << lastTesterConstant << "\"" << "\n";); + if (lastTesterConstant == "more" || lastTesterConstant == "less") { + // use the previous bounds info to generate a new midpoint + binary_search_info lastBounds; + if (!binary_search_len_tester_info.find(lastTester, lastBounds)) { + // unexpected + TRACE("str", tout << "WARNING: no bounds information available for last tester!" << std::endl;); + UNREACHABLE(); + } + TRACE("str", tout << "last bounds are [" << lastBounds.lowerBound << " | " << lastBounds.midPoint << " | " << lastBounds.upperBound << "]!" << lastBounds.windowSize << std::endl;); + binary_search_info newBounds; + expr * newTester; + if (lastTesterConstant == "more") { + // special case: if the midpoint, upper bound, and window size are all equal, + // we double the window size and adjust the bounds + if (lastBounds.midPoint == lastBounds.upperBound && lastBounds.upperBound == lastBounds.windowSize) { + TRACE("str", tout << "search hit window size; expanding" << std::endl;); + newBounds.lowerBound = lastBounds.windowSize + rational::one(); + newBounds.windowSize = lastBounds.windowSize * rational(2); + newBounds.upperBound = newBounds.windowSize; + newBounds.calculate_midpoint(); + } else if (false) { + // handle the case where the midpoint can't be increased further + // (e.g. a window like [50 | 50 | 50]!64 and we don't answer "50") + } else { + // general case + newBounds.lowerBound = lastBounds.midPoint + rational::one(); + newBounds.windowSize = lastBounds.windowSize; + newBounds.upperBound = lastBounds.upperBound; + newBounds.calculate_midpoint(); + } + if (!binary_search_next_var_high.find(lastTester, newTester)) { + newTester = mk_internal_lenTest_var(freeVar, newBounds.midPoint.get_int32()); + binary_search_next_var_high.insert(lastTester, newTester); + } + refresh_theory_var(newTester); + } else if (lastTesterConstant == "less") { + if (false) { + // handle the case where the midpoint can't be decreased further + // (e.g. a window like [0 | 0 | 0]!64 and we don't answer "0" + } else { + // general case + newBounds.upperBound = lastBounds.midPoint - rational::one(); + newBounds.windowSize = lastBounds.windowSize; + newBounds.lowerBound = lastBounds.lowerBound; + newBounds.calculate_midpoint(); + } + if (!binary_search_next_var_low.find(lastTester, newTester)) { + newTester = mk_internal_lenTest_var(freeVar, newBounds.midPoint.get_int32()); + binary_search_next_var_low.insert(lastTester, newTester); + } + refresh_theory_var(newTester); + } + TRACE("str", tout << "new bounds are [" << newBounds.lowerBound << " | " << newBounds.midPoint << " | " << newBounds.upperBound << "]!" << newBounds.windowSize << std::endl;); + binary_search_len_tester_stack[freeVar].push_back(newTester); + m_trail_stack.push(binary_search_trail(binary_search_len_tester_stack, freeVar)); + binary_search_len_tester_info.insert(newTester, newBounds); + m_trail_stack.push(insert_obj_map(binary_search_len_tester_info, newTester)); + + literal_vector case_split_literals; + expr_ref next_case_split(binary_search_case_split(freeVar, newTester, newBounds, case_split_literals)); + m_trail.push_back(next_case_split); + // ctx.mk_th_case_split(case_split_literals.size(), case_split_literals.c_ptr()); + return next_case_split; + } else { // lastTesterConstant is a concrete value + TRACE("str", tout << "length is fixed; generating models for free var" << std::endl;); + // defensive check that this length did not converge on a negative value. + binary_search_info lastBounds; + if (!binary_search_len_tester_info.find(lastTester, lastBounds)) { + // unexpected + TRACE("str", tout << "WARNING: no bounds information available for last tester!" << std::endl;); + UNREACHABLE(); + } + if (lastBounds.midPoint.is_neg()) { + TRACE("str", tout << "WARNING: length search converged on a negative value. Negating this constraint." << std::endl;); + expr_ref axiom(m_autil.mk_ge(mk_strlen(freeVar), m_autil.mk_numeral(rational::zero(), true)), m); + return axiom; + } + // length is fixed + expr * valueAssert = gen_free_var_options(freeVar, lastTester, lastTesterConstant, NULL, zstring("")); + return valueAssert; + } + } else { + // no length testers yet + TRACE("str", tout << "no length testers for " << mk_pp(freeVar, m) << std::endl;); + binary_search_len_tester_stack.insert(freeVar, ptr_vector()); + + expr * firstTester; + rational lowerBound(0); + rational upperBound(m_params.m_BinarySearchInitialUpperBound); + rational windowSize(upperBound); + rational midPoint(floor(upperBound / rational(2))); + if (!binary_search_starting_len_tester.find(freeVar, firstTester)) { + firstTester = mk_internal_lenTest_var(freeVar, midPoint.get_int32()); + binary_search_starting_len_tester.insert(freeVar, firstTester); + } + refresh_theory_var(firstTester); + + binary_search_len_tester_stack[freeVar].push_back(firstTester); + m_trail_stack.push(binary_search_trail(binary_search_len_tester_stack, freeVar)); + binary_search_info new_info(lowerBound, midPoint, upperBound, windowSize); + binary_search_len_tester_info.insert(firstTester, new_info); + m_trail_stack.push(insert_obj_map(binary_search_len_tester_info, firstTester)); + + literal_vector case_split_literals; + expr_ref initial_case_split(binary_search_case_split(freeVar, firstTester, new_info, case_split_literals)); + m_trail.push_back(initial_case_split); + // ctx.mk_th_case_split(case_split_literals.size(), case_split_literals.c_ptr()); + return initial_case_split; + } + } + + // ----------------------------------------------------------------------------------------------------- + // True branch will be taken in final_check: + // - When we discover a variable is "free" for the first time + // lenTesterInCbEq = NULL + // lenTesterValue = "" + // False branch will be taken when invoked by new_eq_eh(). + // - After we set up length tester for a "free" var in final_check, + // when the tester is assigned to some value (e.g. "more" or "4"), + // lenTesterInCbEq != NULL, and its value will be passed by lenTesterValue + // The difference is that in new_eq_eh(), lenTesterInCbEq and its value have NOT been put into a same eqc + // ----------------------------------------------------------------------------------------------------- + expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTesterInCbEq, zstring lenTesterValue) { + + ast_manager & m = get_manager(); + + TRACE("str", tout << "gen for free var " << mk_ismt2_pp(freeVar, m) << std::endl;); + + if (m_params.m_UseBinarySearch) { + TRACE("str", tout << "using binary search heuristic" << std::endl;); + return binary_search_length_test(freeVar, lenTesterInCbEq, lenTesterValue); + } else { + bool map_effectively_empty = false; + if (!fvar_len_count_map.contains(freeVar)) { + TRACE("str", tout << "fvar_len_count_map is empty" << std::endl;); + map_effectively_empty = true; + } + + if (!map_effectively_empty) { + // check whether any entries correspond to variables that went out of scope; + // if every entry is out of scope then the map counts as being empty + + // assume empty and find a counterexample + map_effectively_empty = true; + ptr_vector indicator_set = fvar_lenTester_map[freeVar]; + for (ptr_vector::iterator it = indicator_set.begin(); it != indicator_set.end(); ++it) { + expr * indicator = *it; + if (internal_variable_set.find(indicator) != internal_variable_set.end()) { + TRACE("str", tout <<"found active internal variable " << mk_ismt2_pp(indicator, m) + << " in fvar_lenTester_map[freeVar]" << std::endl;); + map_effectively_empty = false; + break; + } + } + CTRACE("str", map_effectively_empty, tout << "all variables in fvar_lenTester_map[freeVar] out of scope" << std::endl;); + } + + if (map_effectively_empty) { + // no length assertions for this free variable have ever been added. + TRACE("str", tout << "no length assertions yet" << std::endl;); + + fvar_len_count_map.insert(freeVar, 1); + unsigned int testNum = fvar_len_count_map[freeVar]; + + expr_ref indicator(mk_internal_lenTest_var(freeVar, testNum), m); + SASSERT(indicator); + + // since the map is "effectively empty", we can remove those variables that have left scope... + fvar_lenTester_map[freeVar].shrink(0); + fvar_lenTester_map[freeVar].push_back(indicator); + lenTester_fvar_map.insert(indicator, freeVar); + + expr * lenTestAssert = gen_len_test_options(freeVar, indicator, testNum); + SASSERT(lenTestAssert != NULL); + return lenTestAssert; + } else { + TRACE("str", tout << "found previous in-scope length assertions" << std::endl;); + + expr * effectiveLenInd = NULL; + zstring effectiveLenIndiStr(""); + int lenTesterCount = (int) fvar_lenTester_map[freeVar].size(); + + TRACE("str", + tout << lenTesterCount << " length testers in fvar_lenTester_map[" << mk_pp(freeVar, m) << "]:" << std::endl; + for (int i = 0; i < lenTesterCount; ++i) { + expr * len_indicator = fvar_lenTester_map[freeVar][i]; + tout << mk_pp(len_indicator, m) << ": "; + bool effectiveInScope = (internal_variable_set.find(len_indicator) != internal_variable_set.end()); + tout << (effectiveInScope ? "in scope" : "NOT in scope"); + tout << std::endl; + } + ); + + int i = 0; + for (; i < lenTesterCount; ++i) { + expr * len_indicator_pre = fvar_lenTester_map[freeVar][i]; + // check whether this is in scope as well + if (internal_variable_set.find(len_indicator_pre) == internal_variable_set.end()) { + TRACE("str", tout << "length indicator " << mk_pp(len_indicator_pre, m) << " not in scope" << std::endl;); + continue; + } + + bool indicatorHasEqcValue = false; + expr * len_indicator_value = get_eqc_value(len_indicator_pre, indicatorHasEqcValue); + TRACE("str", tout << "length indicator " << mk_ismt2_pp(len_indicator_pre, m) << + " = " << mk_ismt2_pp(len_indicator_value, m) << std::endl;); + if (indicatorHasEqcValue) { + zstring len_pIndiStr; + u.str.is_string(len_indicator_value, len_pIndiStr); + if (len_pIndiStr != "more") { + effectiveLenInd = len_indicator_pre; + effectiveLenIndiStr = len_pIndiStr; + break; + } + } else { + if (lenTesterInCbEq != len_indicator_pre) { + TRACE("str", tout << "WARNING: length indicator " << mk_ismt2_pp(len_indicator_pre, m) + << " does not have an equivalence class value." + << " i = " << i << ", lenTesterCount = " << lenTesterCount << std::endl;); + if (i > 0) { + effectiveLenInd = fvar_lenTester_map[freeVar][i - 1]; + bool effectiveHasEqcValue; + expr * effective_eqc_value = get_eqc_value(effectiveLenInd, effectiveHasEqcValue); + bool effectiveInScope = (internal_variable_set.find(effectiveLenInd) != internal_variable_set.end()); + TRACE("str", tout << "checking effective length indicator " << mk_pp(effectiveLenInd, m) << ": " + << (effectiveInScope ? "in scope" : "NOT in scope") << ", "; + if (effectiveHasEqcValue) { + tout << "~= " << mk_pp(effective_eqc_value, m); + } else { + tout << "no eqc string constant"; + } + tout << std::endl;); + if (effectiveLenInd == lenTesterInCbEq) { + effectiveLenIndiStr = lenTesterValue; + } else { + if (effectiveHasEqcValue) { + u.str.is_string(effective_eqc_value, effectiveLenIndiStr); + } else { + NOT_IMPLEMENTED_YET(); + } + } + } + break; + } + // lenTesterInCbEq == len_indicator_pre + else { + if (lenTesterValue != "more") { + effectiveLenInd = len_indicator_pre; + effectiveLenIndiStr = lenTesterValue; + break; + } + } + } // !indicatorHasEqcValue + } // for (i : [0..lenTesterCount-1]) + if (effectiveLenIndiStr == "more" || effectiveLenIndiStr == "") { + TRACE("str", tout << "length is not fixed; generating length tester options for free var" << std::endl;); + expr_ref indicator(m); + unsigned int testNum = 0; + + TRACE("str", tout << "effectiveLenIndiStr = " << effectiveLenIndiStr + << ", i = " << i << ", lenTesterCount = " << lenTesterCount << "\n";); + + if (i == lenTesterCount) { + fvar_len_count_map[freeVar] = fvar_len_count_map[freeVar] + 1; + testNum = fvar_len_count_map[freeVar]; + indicator = mk_internal_lenTest_var(freeVar, testNum); + fvar_lenTester_map[freeVar].push_back(indicator); + lenTester_fvar_map.insert(indicator, freeVar); + } else { + indicator = fvar_lenTester_map[freeVar][i]; + refresh_theory_var(indicator); + testNum = i + 1; + } + expr * lenTestAssert = gen_len_test_options(freeVar, indicator, testNum); + SASSERT(lenTestAssert != NULL); + return lenTestAssert; + } else { + TRACE("str", tout << "length is fixed; generating models for free var" << std::endl;); + // length is fixed + expr * valueAssert = gen_free_var_options(freeVar, effectiveLenInd, effectiveLenIndiStr, NULL, zstring("")); + return valueAssert; + } + } // fVarLenCountMap.find(...) + + } // !UseBinarySearch + } + + void theory_str::get_concats_in_eqc(expr * n, std::set & concats) { + + expr * eqcNode = n; + do { + if (u.str.is_concat(to_app(eqcNode))) { + concats.insert(eqcNode); + } + eqcNode = get_eqc_next(eqcNode); + } while (eqcNode != n); + } + + void theory_str::get_var_in_eqc(expr * n, std::set & varSet) { + expr * eqcNode = n; + do { + if (variable_set.find(eqcNode) != variable_set.end()) { + varSet.insert(eqcNode); + } + eqcNode = get_eqc_next(eqcNode); + } while (eqcNode != n); + } + + bool cmpvarnames(expr * lhs, expr * rhs) { + symbol lhs_name = to_app(lhs)->get_decl()->get_name(); + symbol rhs_name = to_app(rhs)->get_decl()->get_name(); + return lhs_name.str() < rhs_name.str(); + } + + void theory_str::process_free_var(std::map & freeVar_map) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + std::set eqcRepSet; + std::set leafVarSet; + std::map > aloneVars; + + for (std::map::iterator fvIt = freeVar_map.begin(); fvIt != freeVar_map.end(); fvIt++) { + expr * freeVar = fvIt->first; + // skip all regular expression vars + if (regex_variable_set.find(freeVar) != regex_variable_set.end()) { + continue; + } + + // Iterate the EQC of freeVar, its eqc variable should not be in the eqcRepSet. + // If found, have to filter it out + std::set eqVarSet; + get_var_in_eqc(freeVar, eqVarSet); + bool duplicated = false; + expr * dupVar = NULL; + for (std::set::iterator itorEqv = eqVarSet.begin(); itorEqv != eqVarSet.end(); itorEqv++) { + if (eqcRepSet.find(*itorEqv) != eqcRepSet.end()) { + duplicated = true; + dupVar = *itorEqv; + break; + } + } + if (duplicated && dupVar != NULL) { + TRACE("str", tout << "Duplicated free variable found:" << mk_ismt2_pp(freeVar, m) + << " = " << mk_ismt2_pp(dupVar, m) << " (SKIP)" << std::endl;); + continue; + } else { + eqcRepSet.insert(freeVar); + } + } + + for (std::set::iterator fvIt = eqcRepSet.begin(); fvIt != eqcRepSet.end(); fvIt++) { + bool standAlone = true; + expr * freeVar = *fvIt; + // has length constraint initially + if (input_var_in_len.find(freeVar) != input_var_in_len.end()) { + standAlone = false; + } + // iterate parents + if (standAlone) { + // I hope this works! + enode * e_freeVar = ctx.get_enode(freeVar); + enode_vector::iterator it = e_freeVar->begin_parents(); + for (; it != e_freeVar->end_parents(); ++it) { + expr * parentAst = (*it)->get_owner(); + if (u.str.is_concat(to_app(parentAst))) { + standAlone = false; + break; + } + } + } + + if (standAlone) { + rational len_value; + bool len_value_exists = get_len_value(freeVar, len_value); + if (len_value_exists) { + leafVarSet.insert(freeVar); + } else { + aloneVars[-1].insert(freeVar); + } + } else { + leafVarSet.insert(freeVar); + } + } + + for(std::set::iterator itor1 = leafVarSet.begin(); + itor1 != leafVarSet.end(); ++itor1) { + expr * toAssert = gen_len_val_options_for_free_var(*itor1, NULL, ""); + // gen_len_val_options_for_free_var() can legally return NULL, + // as methods that it calls may assert their own axioms instead. + if (toAssert != NULL) { + assert_axiom(toAssert); + } + } + + for (std::map >::iterator mItor = aloneVars.begin(); + mItor != aloneVars.end(); ++mItor) { + std::set::iterator itor2 = mItor->second.begin(); + for(; itor2 != mItor->second.end(); ++itor2) { + expr * toAssert = gen_len_val_options_for_free_var(*itor2, NULL, ""); + // same deal with returning a NULL axiom here + if(toAssert != NULL) { + assert_axiom(toAssert); + } + } + } + } + + /* + * Collect all unroll functions + * and constant string in eqc of node n + */ + void theory_str::get_eqc_allUnroll(expr * n, expr * &constStr, std::set & unrollFuncSet) { + constStr = NULL; + unrollFuncSet.clear(); + + expr * curr = n; + do { + if (u.str.is_string(to_app(curr))) { + constStr = curr; + } else if (u.re.is_unroll(to_app(curr))) { + if (unrollFuncSet.find(curr) == unrollFuncSet.end()) { + unrollFuncSet.insert(curr); + } + } + curr = get_eqc_next(curr); + } while (curr != n); + } + + // Collect simple Unroll functions (whose core is Str2Reg) and constant strings in the EQC of n. + void theory_str::get_eqc_simpleUnroll(expr * n, expr * &constStr, std::set & unrollFuncSet) { + constStr = NULL; + unrollFuncSet.clear(); + + expr * curr = n; + do { + if (u.str.is_string(to_app(curr))) { + constStr = curr; + } else if (u.re.is_unroll(to_app(curr))) { + expr * core = to_app(curr)->get_arg(0); + if (u.re.is_to_re(to_app(core))) { + if (unrollFuncSet.find(curr) == unrollFuncSet.end()) { + unrollFuncSet.insert(curr); + } + } + } + curr = get_eqc_next(curr); + } while (curr != n); + } + + void theory_str::init_model(model_generator & mg) { + //TRACE("str", tout << "initializing model" << std::endl; display(tout);); + m_factory = alloc(str_value_factory, get_manager(), get_family_id()); + mg.register_factory(m_factory); + } + + /* + * Helper function for mk_value(). + * Attempts to resolve the expression 'n' to a string constant. + * Stronger than get_eqc_value() in that it will perform recursive descent + * through every subexpression and attempt to resolve those to concrete values as well. + * Returns the concrete value obtained from this process, + * guaranteed to satisfy m_strutil.is_string(), + * if one could be obtained, + * or else returns NULL if no concrete value was derived. + */ + app * theory_str::mk_value_helper(app * n) { + if (u.str.is_string(n)) { + return n; + } else if (u.str.is_concat(n)) { + // recursively call this function on each argument + SASSERT(n->get_num_args() == 2); + expr * a0 = n->get_arg(0); + expr * a1 = n->get_arg(1); + + app * a0_conststr = mk_value_helper(to_app(a0)); + app * a1_conststr = mk_value_helper(to_app(a1)); + + if (a0_conststr != NULL && a1_conststr != NULL) { + zstring a0_s, a1_s; + u.str.is_string(a0_conststr, a0_s); + u.str.is_string(a1_conststr, a1_s); + zstring result = a0_s + a1_s; + return to_app(mk_string(result)); + } + } + // fallback path + // try to find some constant string, anything, in the equivalence class of n + bool hasEqc = false; + expr * n_eqc = get_eqc_value(n, hasEqc); + if (hasEqc) { + return to_app(n_eqc); + } else { + return NULL; + } + } + + model_value_proc * theory_str::mk_value(enode * n, model_generator & mg) { + TRACE("str", tout << "mk_value for: " << mk_ismt2_pp(n->get_owner(), get_manager()) << + " (sort " << mk_ismt2_pp(get_manager().get_sort(n->get_owner()), get_manager()) << ")" << std::endl;); + ast_manager & m = get_manager(); + app_ref owner(m); + owner = n->get_owner(); + + // If the owner is not internalized, it doesn't have an enode associated. + SASSERT(get_context().e_internalized(owner)); + + app * val = mk_value_helper(owner); + if (val != NULL) { + return alloc(expr_wrapper_proc, val); + } else { + TRACE("str", tout << "WARNING: failed to find a concrete value, falling back" << std::endl;); + return alloc(expr_wrapper_proc, to_app(mk_string("**UNUSED**"))); + } + } + + void theory_str::finalize_model(model_generator & mg) {} + + void theory_str::display(std::ostream & out) const { + out << "TODO: theory_str display" << std::endl; + } + +}; /* namespace smt */ diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h new file mode 100644 index 000000000..0403b0623 --- /dev/null +++ b/src/smt/theory_str.h @@ -0,0 +1,653 @@ +/*++ + Module Name: + + theory_str.h + + Abstract: + + String Theory Plugin + + Author: + + Murphy Berzish and Yunhui Zheng + + Revision History: + + --*/ +#ifndef _THEORY_STR_H_ +#define _THEORY_STR_H_ + +#include"smt_theory.h" +#include"theory_str_params.h" +#include"trail.h" +#include"th_rewriter.h" +#include"value_factory.h" +#include"smt_model_generator.h" +#include"arith_decl_plugin.h" +#include +#include +#include +#include +#include"seq_decl_plugin.h" +#include"union_find.h" + +namespace smt { + +typedef hashtable symbol_set; + +class str_value_factory : public value_factory { + seq_util u; + symbol_set m_strings; + std::string delim; + unsigned m_next; +public: + str_value_factory(ast_manager & m, family_id fid) : + value_factory(m, fid), + u(m), delim("!"), m_next(0) {} + virtual ~str_value_factory() {} + virtual expr * get_some_value(sort * s) { + return u.str.mk_string(symbol("some value")); + } + virtual bool get_some_values(sort * s, expr_ref & v1, expr_ref & v2) { + v1 = u.str.mk_string(symbol("value 1")); + v2 = u.str.mk_string(symbol("value 2")); + return true; + } + virtual expr * get_fresh_value(sort * s) { + if (u.is_string(s)) { + while (true) { + std::ostringstream strm; + strm << delim << std::hex << (m_next++) << std::dec << delim; + symbol sym(strm.str().c_str()); + if (m_strings.contains(sym)) continue; + m_strings.insert(sym); + return u.str.mk_string(sym); + } + } + sort* seq = 0; + if (u.is_re(s, seq)) { + expr* v0 = get_fresh_value(seq); + return u.re.mk_to_re(v0); + } + TRACE("t_str", tout << "unexpected sort in get_fresh_value(): " << mk_pp(s, m_manager) << std::endl;); + UNREACHABLE(); return NULL; + } + virtual void register_value(expr * n) { /* Ignore */ } +}; + +// rather than modify obj_pair_map I inherit from it and add my own helper methods +class theory_str_contain_pair_bool_map_t : public obj_pair_map { +public: + expr * operator[](std::pair key) const { + expr * value; + bool found = this->find(key.first, key.second, value); + if (found) { + return value; + } else { + TRACE("t_str", tout << "WARNING: lookup miss in contain_pair_bool_map!" << std::endl;); + return NULL; + } + } + + bool contains(std::pair key) const { + expr * unused; + return this->find(key.first, key.second, unused); + } +}; + +template +class binary_search_trail : public trail { + obj_map > & target; + expr * entry; +public: + binary_search_trail(obj_map > & target, expr * entry) : + target(target), entry(entry) {} + virtual ~binary_search_trail() {} + virtual void undo(Ctx & ctx) { + TRACE("t_str_binary_search", tout << "in binary_search_trail::undo()" << std::endl;); + if (target.contains(entry)) { + if (!target[entry].empty()) { + target[entry].pop_back(); + } else { + TRACE("t_str_binary_search", tout << "WARNING: attempt to remove length tester from an empty stack" << std::endl;); + } + } else { + TRACE("t_str_binary_search", tout << "WARNING: attempt to access length tester map via invalid key" << std::endl;); + } + } +}; + + +class nfa { +protected: + bool m_valid; + unsigned m_next_id; + + unsigned next_id() { + unsigned retval = m_next_id; + ++m_next_id; + return retval; + } + + unsigned m_start_state; + unsigned m_end_state; + + std::map > transition_map; + std::map > epsilon_map; + + void make_transition(unsigned start, char symbol, unsigned end) { + transition_map[start][symbol] = end; + } + + void make_epsilon_move(unsigned start, unsigned end) { + epsilon_map[start].insert(end); + } + + // Convert a regular expression to an e-NFA using Thompson's construction + void convert_re(expr * e, unsigned & start, unsigned & end, seq_util & u); + +public: + nfa(seq_util & u, expr * e) +: m_valid(true), m_next_id(0), m_start_state(0), m_end_state(0) { + convert_re(e, m_start_state, m_end_state, u); + } + + nfa() : m_valid(false), m_next_id(0), m_start_state(0), m_end_state(0) {} + + bool is_valid() const { + return m_valid; + } + + void epsilon_closure(unsigned start, std::set & closure); + + bool matches(zstring input); +}; + +class theory_str : public theory { + struct T_cut + { + int level; + std::map vars; + + T_cut() { + level = -100; + } + }; + + typedef trail_stack th_trail_stack; + typedef union_find th_union_find; + + typedef map, default_eq > rational_map; + struct zstring_hash_proc { + unsigned operator()(zstring const & s) const { + return string_hash(s.encode().c_str(), static_cast(s.length()), 17); + } + }; + typedef map > string_map; + +protected: + theory_str_params const & m_params; + + /* + * Setting EagerStringConstantLengthAssertions to true allows some methods, + * in particular internalize_term(), to add + * length assertions about relevant string constants. + * Note that currently this should always be set to 'true', or else *no* length assertions + * will be made about string constants. + */ + bool opt_EagerStringConstantLengthAssertions; + + /* + * If VerifyFinalCheckProgress is set to true, continuing after final check is invoked + * without asserting any new axioms is considered a bug and will throw an exception. + */ + bool opt_VerifyFinalCheckProgress; + + /* + * This constant controls how eagerly we expand unrolls in unbounded regex membership tests. + */ + int opt_LCMUnrollStep; + + /* + * If NoQuickReturn_IntegerTheory is set to true, + * integer theory integration checks that assert axioms + * will not return from the function after asserting their axioms. + * The default behaviour of Z3str2 is to set this to 'false'. This may be incorrect. + */ + bool opt_NoQuickReturn_IntegerTheory; + + /* + * If DisableIntegerTheoryIntegration is set to true, + * ALL calls to the integer theory integration methods + * (get_value, get_len_value, lower_bound, upper_bound) + * will ignore what the arithmetic solver believes about length terms, + * and will return no information. + * + * This reduces performance significantly, but can be useful to enable + * if it is suspected that string-integer integration, or the arithmetic solver itself, + * might have a bug. + * + * The default behaviour of Z3str2 is to set this to 'false'. + */ + bool opt_DisableIntegerTheoryIntegration; + + /* + * If DeferEQCConsistencyCheck is set to true, + * expensive calls to new_eq_check() will be deferred until final check, + * at which time the consistency of *all* string equivalence classes will be validated. + */ + bool opt_DeferEQCConsistencyCheck; + + /* + * If CheckVariableScope is set to true, + * pop_scope_eh() and final_check_eh() will run extra checks + * to determine whether the current assignment + * contains references to any internal variables that are no longer in scope. + */ + bool opt_CheckVariableScope; + + /* + * If ConcatOverlapAvoid is set to true, + * the check to simplify Concat = Concat in handle_equality() will + * avoid simplifying wrt. pairs of Concat terms that will immediately + * result in an overlap. (false = Z3str2 behaviour) + */ + bool opt_ConcatOverlapAvoid; + + bool search_started; + arith_util m_autil; + seq_util u; + int sLevel; + + bool finalCheckProgressIndicator; + + expr_ref_vector m_trail; // trail for generated terms + + str_value_factory * m_factory; + + // terms we couldn't go through set_up_axioms() with because they weren't internalized + expr_ref_vector m_delayed_axiom_setup_terms; + + ptr_vector m_basicstr_axiom_todo; + svector > m_str_eq_todo; + ptr_vector m_concat_axiom_todo; + ptr_vector m_string_constant_length_todo; + ptr_vector m_concat_eval_todo; + + // enode lists for library-aware/high-level string terms (e.g. substr, contains) + ptr_vector m_library_aware_axiom_todo; + + // hashtable of all exprs for which we've already set up term-specific axioms -- + // this prevents infinite recursive descent with respect to axioms that + // include an occurrence of the term for which axioms are being generated + obj_hashtable axiomatized_terms; + + int tmpStringVarCount; + int tmpXorVarCount; + int tmpLenTestVarCount; + int tmpValTestVarCount; + std::map, std::map > varForBreakConcat; + + bool avoidLoopCut; + bool loopDetected; + obj_map > cut_var_map; + expr_ref m_theoryStrOverlapAssumption_term; + + obj_hashtable variable_set; + obj_hashtable internal_variable_set; + obj_hashtable regex_variable_set; + std::map > internal_variable_scope_levels; + + obj_hashtable internal_lenTest_vars; + obj_hashtable internal_valTest_vars; + obj_hashtable internal_unrollTest_vars; + + obj_hashtable input_var_in_len; + + obj_map fvar_len_count_map; + std::map > fvar_lenTester_map; + obj_map lenTester_fvar_map; + + std::map > > > fvar_valueTester_map; + std::map valueTester_fvar_map; + + std::map val_range_map; + + // This can't be an expr_ref_vector because the constructor is wrong, + // we would need to modify the allocator so we pass in ast_manager + std::map, ptr_vector > > unroll_tries_map; + std::map unroll_var_map; + std::map, expr*> concat_eq_unroll_ast_map; + + expr_ref_vector contains_map; + + theory_str_contain_pair_bool_map_t contain_pair_bool_map; + //obj_map > contain_pair_idx_map; + std::map > > contain_pair_idx_map; + + std::map, expr*> regex_in_bool_map; + std::map > regex_in_var_reg_str_map; + + std::map regex_nfa_cache; // Regex term --> NFA + + char * char_set; + std::map charSetLookupTable; + int charSetSize; + + obj_pair_map concat_astNode_map; + + // all (str.to-int) and (int.to-str) terms + expr_ref_vector string_int_conversion_terms; + obj_hashtable string_int_axioms; + + // used when opt_FastLengthTesterCache is true + rational_map lengthTesterCache; + // used when opt_FastValueTesterCache is true + string_map valueTesterCache; + + string_map stringConstantCache; + unsigned long totalCacheAccessCount; + unsigned long cacheHitCount; + unsigned long cacheMissCount; + + // cache mapping each string S to Length(S) + obj_map length_ast_map; + + th_union_find m_find; + th_trail_stack m_trail_stack; + theory_var get_var(expr * n) const; + expr * get_eqc_next(expr * n); + app * get_ast(theory_var i); + + // binary search heuristic data + struct binary_search_info { + rational lowerBound; + rational midPoint; + rational upperBound; + rational windowSize; + + binary_search_info() : lowerBound(rational::zero()), midPoint(rational::zero()), + upperBound(rational::zero()), windowSize(rational::zero()) {} + binary_search_info(rational lower, rational mid, rational upper, rational windowSize) : + lowerBound(lower), midPoint(mid), upperBound(upper), windowSize(windowSize) {} + + void calculate_midpoint() { + midPoint = floor(lowerBound + ((upperBound - lowerBound) / rational(2)) ); + } + }; + // maps a free string var to a stack of active length testers. + // can use binary_search_trail to record changes to this object + obj_map > binary_search_len_tester_stack; + // maps a length tester var to the *active* search window + obj_map binary_search_len_tester_info; + // maps a free string var to the first length tester to be (re)used + obj_map binary_search_starting_len_tester; + // maps a length tester to the next length tester to be (re)used if the split is "low" + obj_map binary_search_next_var_low; + // maps a length tester to the next length tester to be (re)used if the split is "high" + obj_map binary_search_next_var_high; + + // finite model finding data + // maps a finite model tester var to a list of variables that will be tested + obj_map > finite_model_test_varlists; +protected: + void assert_axiom(expr * e); + void assert_implication(expr * premise, expr * conclusion); + expr * rewrite_implication(expr * premise, expr * conclusion); + + expr * mk_string(zstring const& str); + expr * mk_string(const char * str); + + app * mk_strlen(expr * e); + expr * mk_concat(expr * n1, expr * n2); + expr * mk_concat_const_str(expr * n1, expr * n2); + app * mk_contains(expr * haystack, expr * needle); + app * mk_indexof(expr * haystack, expr * needle); + app * mk_fresh_const(char const* name, sort* s); + + literal mk_literal(expr* _e); + app * mk_int(int n); + app * mk_int(rational & q); + + void check_and_init_cut_var(expr * node); + void add_cut_info_one_node(expr * baseNode, int slevel, expr * node); + void add_cut_info_merge(expr * destNode, int slevel, expr * srcNode); + bool has_self_cut(expr * n1, expr * n2); + + // for ConcatOverlapAvoid + bool will_result_in_overlap(expr * lhs, expr * rhs); + + void track_variable_scope(expr * var); + app * mk_str_var(std::string name); + app * mk_int_var(std::string name); + app * mk_nonempty_str_var(); + app * mk_internal_xor_var(); + expr * mk_internal_valTest_var(expr * node, int len, int vTries); + app * mk_regex_rep_var(); + app * mk_unroll_bound_var(); + app * mk_unroll_test_var(); + void add_nonempty_constraint(expr * s); + + void instantiate_concat_axiom(enode * cat); + void try_eval_concat(enode * cat); + void instantiate_basic_string_axioms(enode * str); + void instantiate_str_eq_length_axiom(enode * lhs, enode * rhs); + + void instantiate_axiom_CharAt(enode * e); + void instantiate_axiom_prefixof(enode * e); + void instantiate_axiom_suffixof(enode * e); + void instantiate_axiom_Contains(enode * e); + void instantiate_axiom_Indexof(enode * e); + void instantiate_axiom_Indexof2(enode * e); + void instantiate_axiom_LastIndexof(enode * e); + void instantiate_axiom_Substr(enode * e); + void instantiate_axiom_Replace(enode * e); + void instantiate_axiom_str_to_int(enode * e); + void instantiate_axiom_int_to_str(enode * e); + + expr * mk_RegexIn(expr * str, expr * regexp); + void instantiate_axiom_RegexIn(enode * e); + app * mk_unroll(expr * n, expr * bound); + + void process_unroll_eq_const_str(expr * unrollFunc, expr * constStr); + void unroll_str2reg_constStr(expr * unrollFunc, expr * eqConstStr); + void process_concat_eq_unroll(expr * concat, expr * unroll); + + void set_up_axioms(expr * ex); + void handle_equality(expr * lhs, expr * rhs); + + app * mk_value_helper(app * n); + expr * get_eqc_value(expr * n, bool & hasEqcValue); + expr * z3str2_get_eqc_value(expr * n , bool & hasEqcValue); + bool in_same_eqc(expr * n1, expr * n2); + expr * collect_eq_nodes(expr * n, expr_ref_vector & eqcSet); + + bool get_value(expr* e, rational& val) const; + bool get_len_value(expr* e, rational& val); + bool lower_bound(expr* _e, rational& lo); + bool upper_bound(expr* _e, rational& hi); + + bool can_two_nodes_eq(expr * n1, expr * n2); + bool can_concat_eq_str(expr * concat, zstring& str); + bool can_concat_eq_concat(expr * concat1, expr * concat2); + bool check_concat_len_in_eqc(expr * concat); + bool check_length_consistency(expr * n1, expr * n2); + bool check_length_const_string(expr * n1, expr * constStr); + bool check_length_eq_var_concat(expr * n1, expr * n2); + bool check_length_concat_concat(expr * n1, expr * n2); + bool check_length_concat_var(expr * concat, expr * var); + bool check_length_var_var(expr * var1, expr * var2); + void check_contain_in_new_eq(expr * n1, expr * n2); + void check_contain_by_eqc_val(expr * varNode, expr * constNode); + void check_contain_by_substr(expr * varNode, expr_ref_vector & willEqClass); + void check_contain_by_eq_nodes(expr * n1, expr * n2); + bool in_contain_idx_map(expr * n); + void compute_contains(std::map & varAliasMap, + std::map & concatAliasMap, std::map & varConstMap, + std::map & concatConstMap, std::map > & varEqConcatMap); + expr * dealias_node(expr * node, std::map & varAliasMap, std::map & concatAliasMap); + void get_grounded_concats(expr* node, std::map & varAliasMap, + std::map & concatAliasMap, std::map & varConstMap, + std::map & concatConstMap, std::map > & varEqConcatMap, + std::map, std::set > > & groundedMap); + void print_grounded_concat(expr * node, std::map, std::set > > & groundedMap); + void check_subsequence(expr* str, expr* strDeAlias, expr* subStr, expr* subStrDeAlias, expr* boolVar, + std::map, std::set > > & groundedMap); + bool is_partial_in_grounded_concat(const std::vector & strVec, const std::vector & subStrVec); + + void get_nodes_in_concat(expr * node, ptr_vector & nodeList); + expr * simplify_concat(expr * node); + + void simplify_parent(expr * nn, expr * eq_str); + + void simplify_concat_equality(expr * lhs, expr * rhs); + void solve_concat_eq_str(expr * concat, expr * str); + + void infer_len_concat_equality(expr * nn1, expr * nn2); + bool infer_len_concat(expr * n, rational & nLen); + void infer_len_concat_arg(expr * n, rational len); + + bool is_concat_eq_type1(expr * concatAst1, expr * concatAst2); + bool is_concat_eq_type2(expr * concatAst1, expr * concatAst2); + bool is_concat_eq_type3(expr * concatAst1, expr * concatAst2); + bool is_concat_eq_type4(expr * concatAst1, expr * concatAst2); + bool is_concat_eq_type5(expr * concatAst1, expr * concatAst2); + bool is_concat_eq_type6(expr * concatAst1, expr * concatAst2); + + void process_concat_eq_type1(expr * concatAst1, expr * concatAst2); + void process_concat_eq_type2(expr * concatAst1, expr * concatAst2); + void process_concat_eq_type3(expr * concatAst1, expr * concatAst2); + void process_concat_eq_type4(expr * concatAst1, expr * concatAst2); + void process_concat_eq_type5(expr * concatAst1, expr * concatAst2); + void process_concat_eq_type6(expr * concatAst1, expr * concatAst2); + + void print_cut_var(expr * node, std::ofstream & xout); + + void generate_mutual_exclusion(expr_ref_vector & exprs); + void add_theory_aware_branching_info(expr * term, double priority, lbool phase); + + bool new_eq_check(expr * lhs, expr * rhs); + void group_terms_by_eqc(expr * n, std::set & concats, std::set & vars, std::set & consts); + + int ctx_dep_analysis(std::map & strVarMap, std::map & freeVarMap, + std::map > & unrollGroupMap, std::map > & var_eq_concat_map); + void trace_ctx_dep(std::ofstream & tout, + std::map & aliasIndexMap, + std::map & var_eq_constStr_map, + std::map > & var_eq_concat_map, + std::map > & var_eq_unroll_map, + std::map & concat_eq_constStr_map, + std::map > & concat_eq_concat_map, + std::map > & unrollGroupMap); + + void classify_ast_by_type(expr * node, std::map & varMap, + std::map & concatMap, std::map & unrollMap); + void classify_ast_by_type_in_positive_context(std::map & varMap, + std::map & concatMap, std::map & unrollMap); + + expr * mk_internal_lenTest_var(expr * node, int lTries); + expr * gen_len_val_options_for_free_var(expr * freeVar, expr * lenTesterInCbEq, zstring lenTesterValue); + void process_free_var(std::map & freeVar_map); + expr * gen_len_test_options(expr * freeVar, expr * indicator, int tries); + expr * gen_free_var_options(expr * freeVar, expr * len_indicator, + zstring len_valueStr, expr * valTesterInCbEq, zstring valTesterValueStr); + expr * gen_val_options(expr * freeVar, expr * len_indicator, expr * val_indicator, + zstring lenStr, int tries); + void print_value_tester_list(svector > & testerList); + bool get_next_val_encode(int_vector & base, int_vector & next); + zstring gen_val_string(int len, int_vector & encoding); + + // binary search heuristic + expr * binary_search_length_test(expr * freeVar, expr * previousLenTester, zstring previousLenTesterValue); + expr_ref binary_search_case_split(expr * freeVar, expr * tester, binary_search_info & bounds, literal_vector & case_split_lits); + + bool free_var_attempt(expr * nn1, expr * nn2); + void more_len_tests(expr * lenTester, zstring lenTesterValue); + void more_value_tests(expr * valTester, zstring valTesterValue); + + expr * get_alias_index_ast(std::map & aliasIndexMap, expr * node); + expr * getMostLeftNodeInConcat(expr * node); + expr * getMostRightNodeInConcat(expr * node); + void get_var_in_eqc(expr * n, std::set & varSet); + void get_concats_in_eqc(expr * n, std::set & concats); + void get_const_str_asts_in_node(expr * node, expr_ref_vector & constList); + expr * eval_concat(expr * n1, expr * n2); + + bool finalcheck_str2int(app * a); + bool finalcheck_int2str(app * a); + + // strRegex + + void get_eqc_allUnroll(expr * n, expr * &constStr, std::set & unrollFuncSet); + void get_eqc_simpleUnroll(expr * n, expr * &constStr, std::set & unrollFuncSet); + void gen_assign_unroll_reg(std::set & unrolls); + expr * gen_assign_unroll_Str2Reg(expr * n, std::set & unrolls); + expr * gen_unroll_conditional_options(expr * var, std::set & unrolls, zstring lcmStr); + expr * gen_unroll_assign(expr * var, zstring lcmStr, expr * testerVar, int l, int h); + void reduce_virtual_regex_in(expr * var, expr * regex, expr_ref_vector & items); + void check_regex_in(expr * nn1, expr * nn2); + zstring get_std_regex_str(expr * r); + + void dump_assignments(); + void initialize_charset(); + + void check_variable_scope(); + void recursive_check_variable_scope(expr * ex); + + void collect_var_concat(expr * node, std::set & varSet, std::set & concatSet); + bool propagate_length(std::set & varSet, std::set & concatSet, std::map & exprLenMap); + void get_unique_non_concat_nodes(expr * node, std::set & argSet); + bool propagate_length_within_eqc(expr * var); + + // TESTING + void refresh_theory_var(expr * e); + + expr_ref set_up_finite_model_test(expr * lhs, expr * rhs); + void finite_model_test(expr * v, expr * c); + +public: + theory_str(ast_manager & m, theory_str_params const & params); + virtual ~theory_str(); + + virtual char const * get_name() const { return "seq"; } + virtual void display(std::ostream & out) const; + + bool overlapping_variables_detected() const { return loopDetected; } + + th_trail_stack& get_trail_stack() { return m_trail_stack; } + 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) {} +protected: + virtual bool internalize_atom(app * atom, bool gate_ctx); + virtual bool internalize_term(app * term); + virtual enode* ensure_enode(expr* e); + virtual theory_var mk_var(enode * n); + + virtual void new_eq_eh(theory_var, theory_var); + virtual void new_diseq_eh(theory_var, theory_var); + + virtual theory* mk_fresh(context*) { return alloc(theory_str, get_manager(), m_params); } + virtual void init_search_eh(); + virtual void add_theory_assumptions(expr_ref_vector & assumptions); + virtual lbool validate_unsat_core(expr_ref_vector & unsat_core); + virtual void relevant_eh(app * n); + virtual void assign_eh(bool_var v, bool is_true); + virtual void push_scope_eh(); + virtual void pop_scope_eh(unsigned num_scopes); + virtual void reset_eh(); + + virtual bool can_propagate(); + virtual void propagate(); + + virtual final_check_status final_check_eh(); + virtual void attach_new_th_var(enode * n); + + virtual void init_model(model_generator & m); + virtual model_value_proc * mk_value(enode * n, model_generator & mg); + virtual void finalize_model(model_generator & mg); +}; + +}; + +#endif /* _THEORY_STR_H_ */ diff --git a/src/solver/smt_logics.cpp b/src/solver/smt_logics.cpp index 210a09f96..c4ead74df 100644 --- a/src/solver/smt_logics.cpp +++ b/src/solver/smt_logics.cpp @@ -24,7 +24,7 @@ Revision History: bool smt_logics::supported_logic(symbol const & s) { return logic_has_uf(s) || logic_is_all(s) || logic_has_fd(s) || logic_has_arith(s) || logic_has_bv(s) || - logic_has_array(s) || logic_has_seq(s) || + logic_has_array(s) || logic_has_seq(s) || logic_has_str(s) || logic_has_horn(s) || logic_has_fpa(s); } @@ -132,6 +132,10 @@ bool smt_logics::logic_has_seq(symbol const & s) { return s == "QF_BVRE" || s == "QF_S" || s == "ALL"; } +bool smt_logics::logic_has_str(symbol const & s) { + return s == "QF_S" || s == "ALL"; +} + bool smt_logics::logic_has_fpa(symbol const & s) { return s == "QF_FP" || s == "QF_FPBV" || s == "QF_BVFP" || s == "ALL"; } diff --git a/src/solver/smt_logics.h b/src/solver/smt_logics.h index 72c3b8764..702431cdd 100644 --- a/src/solver/smt_logics.h +++ b/src/solver/smt_logics.h @@ -30,6 +30,7 @@ public: static bool logic_has_bv(symbol const & s); static bool logic_has_array(symbol const & s); static bool logic_has_seq(symbol const & s); + static bool logic_has_str(symbol const & s); static bool logic_has_fpa(symbol const & s); static bool logic_has_horn(symbol const& s); static bool logic_has_pb(symbol const& s); diff --git a/src/tactic/bv/elim_small_bv_tactic.cpp b/src/tactic/bv/elim_small_bv_tactic.cpp index e395433f5..8cfc27950 100644 --- a/src/tactic/bv/elim_small_bv_tactic.cpp +++ b/src/tactic/bv/elim_small_bv_tactic.cpp @@ -34,6 +34,7 @@ class elim_small_bv_tactic : public tactic { struct rw_cfg : public default_rewriter_cfg { ast_manager & m; + params_ref m_params; bv_util m_util; simplifier m_simp; ref m_mc; @@ -47,6 +48,7 @@ class elim_small_bv_tactic : public tactic { rw_cfg(ast_manager & _m, params_ref const & p) : m(_m), + m_params(p), m_util(_m), m_simp(_m), m_bindings(_m), @@ -119,7 +121,7 @@ class elim_small_bv_tactic : public tactic { return res; } - br_status reduce_app(func_decl * f, unsigned num, expr * const * args, expr_ref & result, proof_ref & result_pr) { + br_status reduce_app(func_decl * f, unsigned num, expr * const * args, expr_ref & result, proof_ref & result_pr) { TRACE("elim_small_bv_app", expr_ref tmp(m.mk_app(f, num, args), m); tout << "reduce " << tmp << std::endl; ); return BR_FAILED; } @@ -178,7 +180,7 @@ class elim_small_bv_tactic : public tactic { quantifier_ref new_q(m); new_q = m.update_quantifier(q, body); - unused_vars_eliminator el(m); + unused_vars_eliminator el(m, m_params); el(new_q, result); TRACE("elim_small_bv", tout << "elimination result: " << mk_ismt2_pp(result, m) << std::endl; ); @@ -203,6 +205,7 @@ 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); @@ -305,7 +308,7 @@ public: virtual void cleanup() { ast_manager & m = m_imp->m; imp * d = alloc(imp, m, m_params); - std::swap(d, m_imp); + std::swap(d, m_imp); dealloc(d); } diff --git a/src/tactic/core/distribute_forall_tactic.cpp b/src/tactic/core/distribute_forall_tactic.cpp index 769f415f2..5d525a836 100644 --- a/src/tactic/core/distribute_forall_tactic.cpp +++ b/src/tactic/core/distribute_forall_tactic.cpp @@ -24,9 +24,9 @@ class distribute_forall_tactic : public tactic { ast_manager & m; rw_cfg(ast_manager & _m):m(_m) {} - bool reduce_quantifier(quantifier * old_q, - expr * new_body, - expr * const * new_patterns, + bool reduce_quantifier(quantifier * old_q, + expr * new_body, + expr * const * new_patterns, expr * const * new_no_patterns, expr_ref & result, proof_ref & result_pr) { @@ -34,7 +34,7 @@ class distribute_forall_tactic : public tactic { if (!old_q->is_forall()) { return false; } - + if (m.is_not(new_body) && m.is_or(to_app(new_body)->get_arg(0))) { // (forall X (not (or F1 ... Fn))) // --> @@ -50,13 +50,13 @@ class distribute_forall_tactic : public tactic { quantifier_ref tmp_q(m); tmp_q = m.update_quantifier(old_q, not_arg); expr_ref new_q(m); - elim_unused_vars(m, tmp_q, new_q); + elim_unused_vars(m, tmp_q, params_ref(), new_q); new_args.push_back(new_q); } result = m.mk_and(new_args.size(), new_args.c_ptr()); return true; } - + if (m.is_and(new_body)) { // (forall X (and F1 ... Fn)) // --> @@ -70,20 +70,20 @@ class distribute_forall_tactic : public tactic { quantifier_ref tmp_q(m); tmp_q = m.update_quantifier(old_q, arg); expr_ref new_q(m); - elim_unused_vars(m, tmp_q, new_q); + elim_unused_vars(m, tmp_q, params_ref(), new_q); new_args.push_back(new_q); } result = m.mk_and(new_args.size(), new_args.c_ptr()); return true; } - + return false; } }; struct rw : public rewriter_tpl { rw_cfg m_cfg; - + rw(ast_manager & m, bool proofs_enabled): rewriter_tpl(m, proofs_enabled, m_cfg), m_cfg(m) { @@ -99,19 +99,19 @@ public: return alloc(distribute_forall_tactic); } - virtual void operator()(goal_ref const & g, - goal_ref_buffer & result, - model_converter_ref & mc, + virtual void operator()(goal_ref const & g, + goal_ref_buffer & result, + model_converter_ref & mc, proof_converter_ref & pc, expr_dependency_ref & core) { SASSERT(g->is_well_sorted()); ast_manager & m = g->m(); bool produce_proofs = g->proofs_enabled(); rw r(m, produce_proofs); - m_rw = &r; + m_rw = &r; mc = 0; pc = 0; core = 0; result.reset(); tactic_report report("distribute-forall", *g); - + expr_ref new_curr(m); proof_ref new_pr(m); unsigned size = g->size(); @@ -126,12 +126,12 @@ public: } g->update(idx, new_curr, new_pr, g->dep(idx)); } - + g->inc_depth(); result.push_back(g.get()); TRACE("distribute-forall", g->display(tout);); SASSERT(g->is_well_sorted()); - m_rw = 0; + m_rw = 0; } virtual void cleanup() {} diff --git a/src/tactic/portfolio/bounded_int2bv_solver.cpp b/src/tactic/portfolio/bounded_int2bv_solver.cpp index 53c20b253..83693abba 100644 --- a/src/tactic/portfolio/bounded_int2bv_solver.cpp +++ b/src/tactic/portfolio/bounded_int2bv_solver.cpp @@ -34,19 +34,19 @@ Notes: class bounded_int2bv_solver : public solver_na2as { ast_manager& m; params_ref m_params; - bv_util m_bv; - arith_util m_arith; - expr_ref_vector m_assertions; + mutable bv_util m_bv; + mutable arith_util m_arith; + mutable expr_ref_vector m_assertions; ref m_solver; - ptr_vector m_bounds; - func_decl_ref_vector m_bv_fns; - func_decl_ref_vector m_int_fns; + mutable ptr_vector m_bounds; + mutable func_decl_ref_vector m_bv_fns; + mutable func_decl_ref_vector m_int_fns; unsigned_vector m_bv_fns_lim; - obj_map m_int2bv; - obj_map m_bv2int; - obj_map m_bv2offset; - bv2int_rewriter_ctx m_rewriter_ctx; - bv2int_rewriter_star m_rewriter; + mutable obj_map m_int2bv; + mutable obj_map m_bv2int; + mutable obj_map m_bv2offset; + mutable bv2int_rewriter_ctx m_rewriter_ctx; + mutable bv2int_rewriter_star m_rewriter; public: @@ -78,7 +78,19 @@ public: } virtual void assert_expr(expr * t) { + unsigned i = m_assertions.size(); m_assertions.push_back(t); + while (i < m_assertions.size()) { + t = m_assertions[i].get(); + if (m.is_and(t)) { + m_assertions.append(to_app(t)->get_num_args(), to_app(t)->get_args()); + m_assertions[i] = m_assertions.back(); + m_assertions.pop_back(); + } + else { + ++i; + } + } } virtual void push_core() { @@ -184,7 +196,7 @@ private: } filter_model_converter filter(m); for (unsigned i = 0; i < m_bv_fns.size(); ++i) { - filter.insert(m_bv_fns[i]); + filter.insert(m_bv_fns[i].get()); } filter(mdl, 0); } @@ -205,13 +217,13 @@ private: ext(mdl, 0); } - void accumulate_sub(expr_safe_replace& sub) { + void accumulate_sub(expr_safe_replace& sub) const { for (unsigned i = 0; i < m_bounds.size(); ++i) { accumulate_sub(sub, *m_bounds[i]); } } - void accumulate_sub(expr_safe_replace& sub, bound_manager& bm) { + void accumulate_sub(expr_safe_replace& sub, bound_manager& bm) const { bound_manager::iterator it = bm.begin(), end = bm.end(); for (; it != end; ++it) { expr* e = *it; @@ -252,19 +264,20 @@ private: sub.insert(e, t); } else { - IF_VERBOSE(1, - verbose_stream() << "unprocessed entry: " << mk_pp(e, m) << "\n"; - if (bm.has_lower(e, lo, s1)) { - verbose_stream() << "lower: " << lo << " " << s1 << "\n"; - } - if (bm.has_upper(e, hi, s2)) { - verbose_stream() << "upper: " << hi << " " << s2 << "\n"; - }); + TRACE("pb", + tout << "unprocessed entry: " << mk_pp(e, m) << "\n"; + if (bm.has_lower(e, lo, s1)) { + tout << "lower: " << lo << " " << s1 << "\n"; + } + if (bm.has_upper(e, hi, s2)) { + tout << "upper: " << hi << " " << s2 << "\n"; + }); } } } - unsigned get_num_bits(rational const& k) { + + unsigned get_num_bits(rational const& k) const { SASSERT(!k.is_neg()); SASSERT(k.is_int()); rational two(2); @@ -277,11 +290,13 @@ private: return num_bits; } - void flush_assertions() { + void flush_assertions() const { + if (m_assertions.empty()) return; bound_manager& bm = *m_bounds.back(); for (unsigned i = 0; i < m_assertions.size(); ++i) { bm(m_assertions[i].get()); } + TRACE("int2bv", bm.display(tout);); expr_safe_replace sub(m); accumulate_sub(sub); proof_ref proof(m); @@ -304,6 +319,17 @@ private: m_assertions.reset(); m_rewriter.reset(); } + + virtual unsigned get_num_assertions() const { + flush_assertions(); + return m_solver->get_num_assertions(); + } + + virtual expr * get_assertion(unsigned idx) const { + flush_assertions(); + return m_solver->get_assertion(idx); + } + }; solver * mk_bounded_int2bv_solver(ast_manager & m, params_ref const & p, solver* s) { diff --git a/src/tactic/portfolio/enum2bv_solver.cpp b/src/tactic/portfolio/enum2bv_solver.cpp index f3288d8d6..35601f374 100644 --- a/src/tactic/portfolio/enum2bv_solver.cpp +++ b/src/tactic/portfolio/enum2bv_solver.cpp @@ -137,8 +137,10 @@ public: SASSERT(num.is_unsigned()); expr_ref head(m); ptr_vector const& enums = *dt.get_datatype_constructors(f->get_range()); - head = m.mk_eq(m.mk_const(f), m.mk_const(enums[num.get_unsigned()])); - consequences[i] = m.mk_implies(a, head); + if (enums.size() > num.get_unsigned()) { + head = m.mk_eq(m.mk_const(f), m.mk_const(enums[num.get_unsigned()])); + consequences[i] = m.mk_implies(a, head); + } } } return r; @@ -163,6 +165,14 @@ public: ext(mdl, 0); } + virtual unsigned get_num_assertions() const { + return m_solver->get_num_assertions(); + } + + virtual expr * get_assertion(unsigned idx) const { + return m_solver->get_assertion(idx); + } + }; solver * mk_enum2bv_solver(ast_manager & m, params_ref const & p, solver* s) { diff --git a/src/tactic/portfolio/pb2bv_solver.cpp b/src/tactic/portfolio/pb2bv_solver.cpp index 106cba954..10acc0bb6 100644 --- a/src/tactic/portfolio/pb2bv_solver.cpp +++ b/src/tactic/portfolio/pb2bv_solver.cpp @@ -27,9 +27,9 @@ Notes: class pb2bv_solver : public solver_na2as { ast_manager& m; params_ref m_params; - expr_ref_vector m_assertions; - ref m_solver; - pb2bv_rewriter m_rewriter; + mutable expr_ref_vector m_assertions; + mutable ref m_solver; + mutable pb2bv_rewriter m_rewriter; public: @@ -107,8 +107,19 @@ public: filter(mdl, 0); } + virtual unsigned get_num_assertions() const { + flush_assertions(); + return m_solver->get_num_assertions(); + } + + virtual expr * get_assertion(unsigned idx) const { + flush_assertions(); + return m_solver->get_assertion(idx); + } + + private: - void flush_assertions() { + void flush_assertions() const { proof_ref proof(m); expr_ref fml(m); expr_ref_vector fmls(m); diff --git a/src/tactic/ufbv/ufbv_rewriter.cpp b/src/tactic/ufbv/ufbv_rewriter.cpp index 40fdf5e3e..f5f18d234 100644 --- a/src/tactic/ufbv/ufbv_rewriter.cpp +++ b/src/tactic/ufbv/ufbv_rewriter.cpp @@ -49,7 +49,7 @@ ufbv_rewriter::~ufbv_rewriter() { bool ufbv_rewriter::is_demodulator(expr * e, expr_ref & large, expr_ref & small) const { if (e->get_kind() == AST_QUANTIFIER) { quantifier * q = to_quantifier(e); - if (q->is_forall()) { + if (q->is_forall()) { expr * qe = q->get_expr(); if ((m_manager.is_eq(qe) || m_manager.is_iff(qe))) { app * eq = to_app(q->get_expr()); @@ -61,7 +61,7 @@ bool ufbv_rewriter::is_demodulator(expr * e, expr_ref & large, expr_ref & small) << mk_pp(lhs, m_manager) << "\n" << mk_pp(rhs, m_manager) << "\n" << "subset: " << subset << ", smaller: " << smaller << "\n";); - // We only track uninterpreted functions, everything else is likely too expensive. + // We only track uninterpreted functions, everything else is likely too expensive. if ((subset == +1 || subset == +2) && smaller == +1) { if (is_uninterp(rhs)) { large = rhs; @@ -78,7 +78,7 @@ bool ufbv_rewriter::is_demodulator(expr * e, expr_ref & large, expr_ref & small) } #endif } - + if ((subset == -1 || subset == +2) && smaller == -1) { if (is_uninterp(lhs)) { large = lhs; @@ -113,13 +113,13 @@ bool ufbv_rewriter::is_demodulator(expr * e, expr_ref & large, expr_ref & small) return false; } -class var_set_proc { +class var_set_proc { uint_set & m_set; public: var_set_proc(uint_set &s):m_set(s) {} void operator()(var * n) { m_set.insert(n->get_idx()); } void operator()(quantifier * n) {} - void operator()(app * n) {} + void operator()(app * n) {} }; int ufbv_rewriter::is_subset(expr * e1, expr * e2) const { @@ -132,10 +132,10 @@ int ufbv_rewriter::is_subset(expr * e1, expr * e2) const { for_each_expr(proc1, e1); var_set_proc proc2(ev2); for_each_expr(proc2, e2); - - return (ev1==ev2 ) ? +2 : // We return +2 if the sets are equal. - (ev1.subset_of(ev2)) ? +1 : - (ev2.subset_of(ev1)) ? -1 : + + return (ev1==ev2 ) ? +2 : // We return +2 if the sets are equal. + (ev1.subset_of(ev2)) ? +1 : + (ev2.subset_of(ev1)) ? -1 : 0 ; } @@ -154,8 +154,8 @@ int ufbv_rewriter::is_smaller(expr * e1, expr * e2) const { else if (is_uninterp(e1) && !is_uninterp(e2)) return -1; - // two uninterpreted functions are ordered first by the number of - // arguments, then by their id. + // two uninterpreted functions are ordered first by the number of + // arguments, then by their id. if (is_uninterp(e1) && is_uninterp(e2)) { if (to_app(e1)->get_num_args() < to_app(e2)->get_num_args()) return +1; @@ -163,10 +163,10 @@ int ufbv_rewriter::is_smaller(expr * e1, expr * e2) const { return -1; else { unsigned a = to_app(e1)->get_decl()->get_id(); - unsigned b = to_app(e2)->get_decl()->get_id(); - if (a < b) + unsigned b = to_app(e2)->get_decl()->get_id(); + if (a < b) return +1; - else if (a > b) + else if (a > b) return -1; } } @@ -185,8 +185,8 @@ int ufbv_rewriter::is_smaller(expr * e1, expr * e2) const { default: UNREACHABLE(); } - return (sz1 == sz2) ? 0 : - (sz1 < sz2) ? +1 : + return (sz1 == sz2) ? 0 : + (sz1 < sz2) ? +1 : -1 ; } @@ -194,9 +194,9 @@ class max_var_id_proc { unsigned m_max_var_id; public: max_var_id_proc(void):m_max_var_id(0) {} - void operator()(var * n) { - if(n->get_idx() > m_max_var_id) - m_max_var_id = n->get_idx(); + void operator()(var * n) { + if(n->get_idx() > m_max_var_id) + m_max_var_id = n->get_idx(); } void operator()(quantifier * n) {} void operator()(app * n) {} @@ -206,7 +206,7 @@ public: unsigned ufbv_rewriter::max_var_id(expr * e) { max_var_id_proc proc; - for_each_expr(proc, e); + for_each_expr(proc, e); return proc.get_max(); } @@ -219,14 +219,14 @@ void ufbv_rewriter::insert_fwd_idx(expr * large, expr * small, quantifier * demo func_decl * fd = to_app(large)->get_decl(); fwd_idx_map::iterator it = m_fwd_idx.find_iterator(fd); - if (it == m_fwd_idx.end()) { - quantifier_set * qs = alloc(quantifier_set, 1); + if (it == m_fwd_idx.end()) { + quantifier_set * qs = alloc(quantifier_set, 1); m_fwd_idx.insert(fd, qs); it = m_fwd_idx.find_iterator(fd); } SASSERT(it->m_value); - it->m_value->insert(demodulator); + it->m_value->insert(demodulator); m_manager.inc_ref(demodulator); m_manager.inc_ref(large); @@ -238,13 +238,13 @@ void ufbv_rewriter::remove_fwd_idx(func_decl * f, quantifier * demodulator) { TRACE("demodulator_fwd", tout << "REMOVE: " << std::hex << (size_t)demodulator << std::endl; ); fwd_idx_map::iterator it = m_fwd_idx.find_iterator(f); - if (it != m_fwd_idx.end()) { + if (it != m_fwd_idx.end()) { demodulator2lhs_rhs::iterator fit = m_demodulator2lhs_rhs.find_iterator(demodulator); m_manager.dec_ref(fit->m_value.first); m_manager.dec_ref(fit->m_value.second); m_manager.dec_ref(demodulator); m_demodulator2lhs_rhs.erase(demodulator); - it->m_value->erase(demodulator); + it->m_value->erase(demodulator); } else { SASSERT(m_demodulator2lhs_rhs.contains(demodulator)); } @@ -281,13 +281,13 @@ void ufbv_rewriter::show_fwd_idx(std::ostream & out) { } } -bool ufbv_rewriter::rewrite1(func_decl * f, ptr_vector & m_new_args, expr_ref & np) { +bool ufbv_rewriter::rewrite1(func_decl * f, ptr_vector & m_new_args, expr_ref & np) { fwd_idx_map::iterator it = m_fwd_idx.find_iterator(f); if (it != m_fwd_idx.end()) { TRACE("demodulator_bug", tout << "trying to rewrite: " << f->get_name() << " args:\n"; for (unsigned i = 0; i < m_new_args.size(); i++) { tout << mk_pp(m_new_args[i], m_manager) << "\n"; }); quantifier_set::iterator dit = it->m_value->begin(); - quantifier_set::iterator dend = it->m_value->end(); + quantifier_set::iterator dend = it->m_value->end(); for ( ; dit != dend ; dit++ ) { quantifier * d = *dit; @@ -302,7 +302,7 @@ bool ufbv_rewriter::rewrite1(func_decl * f, ptr_vector & m_new_args, expr_ TRACE("demodulator_bug", tout << "Matching with demodulator: " << mk_pp(d, m_manager) << std::endl; ); SASSERT(large->get_decl() == f); - + if (m_match_subst(large, l_s.second, m_new_args.c_ptr(), np)) { TRACE("demodulator_bug", tout << "succeeded...\n" << mk_pp(l_s.second, m_manager) << "\n===>\n" << mk_pp(np, m_manager) << "\n";); return true; @@ -331,22 +331,22 @@ void ufbv_rewriter::rewrite_cache(expr * e, expr * new_e, bool done) { } expr * ufbv_rewriter::rewrite(expr * n) { - if (m_fwd_idx.empty()) + if (m_fwd_idx.empty()) return n; TRACE("demodulator", tout << "rewrite: " << mk_pp(n, m_manager) << std::endl; ); app * a; - + SASSERT(m_rewrite_todo.empty()); m_rewrite_cache.reset(); - + m_rewrite_todo.push_back(n); while (!m_rewrite_todo.empty()) { TRACE("demodulator_stack", tout << "STACK: " << std::endl; - for ( unsigned i = 0; iget_decl(); m_new_args.reset(); @@ -389,12 +389,12 @@ expr * ufbv_rewriter::rewrite(expr * n) { // No pop. } else { if(all_untouched) { - rewrite_cache(e, actual, true); - } + rewrite_cache(e, actual, true); + } else { expr_ref na(m_manager); if (f->get_family_id() != m_manager.get_basic_family_id()) - na = m_manager.mk_app(f, m_new_args.size(), m_new_args.c_ptr()); + na = m_manager.mk_app(f, m_new_args.size(), m_new_args.c_ptr()); else m_bsimp.reduce(f, m_new_args.size(), m_new_args.c_ptr(), na); TRACE("demodulator_bug", tout << "e:\n" << mk_pp(e, m_manager) << "\nnew_args: \n"; @@ -405,9 +405,9 @@ expr * ufbv_rewriter::rewrite(expr * n) { } m_rewrite_todo.pop_back(); } - } + } break; - case AST_QUANTIFIER: { + case AST_QUANTIFIER: { expr * body = to_quantifier(actual)->get_expr(); if (m_rewrite_cache.contains(body)) { const expr_bool_pair ebp = m_rewrite_cache.get(body); @@ -417,13 +417,13 @@ expr * ufbv_rewriter::rewrite(expr * n) { q = m_manager.update_quantifier(to_quantifier(actual), new_body); m_new_exprs.push_back(q); expr_ref new_q(m_manager); - elim_unused_vars(m_manager, q, new_q); + elim_unused_vars(m_manager, q, params_ref(), new_q); m_new_exprs.push_back(new_q); - rewrite_cache(e, new_q, true); + rewrite_cache(e, new_q, true); m_rewrite_todo.pop_back(); } else { m_rewrite_todo.push_back(body); - } + } break; } default: @@ -437,7 +437,7 @@ expr * ufbv_rewriter::rewrite(expr * n) { expr * r = ebp.first; TRACE("demodulator", tout << "rewrite result: " << mk_pp(r, m_manager) << std::endl; ); - + return r; } @@ -448,7 +448,7 @@ public: add_back_idx_proc(back_idx_map & bi, expr * e):m_back_idx(bi),m_expr(e) {} void operator()(var * n) {} void operator()(quantifier * n) {} - void operator()(app * n) { + void operator()(app * n) { // We track only uninterpreted and constant functions. if (n->get_num_args()==0) return; SASSERT(m_expr && m_expr != (expr*) 0x00000003); @@ -464,7 +464,7 @@ public: m_back_idx.insert(d, e); } } - } + } }; class ufbv_rewriter::remove_back_idx_proc { @@ -473,15 +473,15 @@ class ufbv_rewriter::remove_back_idx_proc { public: remove_back_idx_proc(back_idx_map & bi, expr * e):m_back_idx(bi),m_expr(e) {} void operator()(var * n) {} - void operator()(quantifier * n) {} - void operator()(app * n) { + void operator()(quantifier * n) {} + void operator()(app * n) { // We track only uninterpreted and constant functions. if (n->get_num_args()==0) return; func_decl * d=n->get_decl(); - if (d->get_family_id() == null_family_id) { + if (d->get_family_id() == null_family_id) { back_idx_map::iterator it = m_back_idx.find_iterator(d); if (it != m_back_idx.end()) { - SASSERT(it->m_value); + SASSERT(it->m_value); it->m_value->remove(m_expr); } } @@ -489,12 +489,12 @@ public: }; void ufbv_rewriter::reschedule_processed(func_decl * f) { - //use m_back_idx to find all formulas p in m_processed that contains f { + //use m_back_idx to find all formulas p in m_processed that contains f { back_idx_map::iterator it = m_back_idx.find_iterator(f); if (it != m_back_idx.end()) { SASSERT(it->m_value); expr_set temp; - + expr_set::iterator sit = it->m_value->begin(); expr_set::iterator send = it->m_value->end(); for ( ; sit != send ; sit++ ) { @@ -502,7 +502,7 @@ void ufbv_rewriter::reschedule_processed(func_decl * f) { if (m_processed.contains(p)) temp.insert(p); } - + sit = temp.begin(); send = temp.end(); for ( ; sit != send; sit++) { @@ -511,7 +511,7 @@ void ufbv_rewriter::reschedule_processed(func_decl * f) { m_processed.remove(p); remove_back_idx_proc proc(m_back_idx, p); // this could change it->m_value, thus we need the `temp' set. for_each_expr(proc, p); - // insert p into m_todo + // insert p into m_todo m_todo.push_back(p); } } @@ -529,40 +529,40 @@ bool ufbv_rewriter::can_rewrite(expr * n, expr * lhs) { while (!stack.empty()) { curr = stack.back(); - + if (visited.is_marked(curr)) { stack.pop_back(); continue; } switch(curr->get_kind()) { - case AST_VAR: + case AST_VAR: visited.mark(curr, true); stack.pop_back(); break; case AST_APP: - if (for_each_expr_args(stack, visited, to_app(curr)->get_num_args(), to_app(curr)->get_args())) { + if (for_each_expr_args(stack, visited, to_app(curr)->get_num_args(), to_app(curr)->get_args())) { if (m_match_subst(lhs, curr)) return true; visited.mark(curr, true); stack.pop_back(); } break; - + case AST_QUANTIFIER: - if (!for_each_expr_args(stack, visited, to_quantifier(curr)->get_num_patterns(), + if (!for_each_expr_args(stack, visited, to_quantifier(curr)->get_num_patterns(), to_quantifier(curr)->get_patterns())) { break; } - if (!for_each_expr_args(stack, visited, to_quantifier(curr)->get_num_no_patterns(), + if (!for_each_expr_args(stack, visited, to_quantifier(curr)->get_num_no_patterns(), to_quantifier(curr)->get_no_patterns())) { break; } if (!visited.is_marked(to_quantifier(curr)->get_expr())) { stack.push_back(to_quantifier(curr)->get_expr()); break; - } + } stack.pop_back(); break; @@ -597,7 +597,7 @@ void ufbv_rewriter::reschedule_demodulators(func_decl * f, expr * lhs) { expr * occ = *esit; if (!is_quantifier(occ)) - continue; + continue; // Use the fwd idx to find out whether this is a demodulator. demodulator2lhs_rhs::iterator d2lr_it = m_demodulator2lhs_rhs.find_iterator(to_quantifier(occ)); @@ -605,22 +605,22 @@ void ufbv_rewriter::reschedule_demodulators(func_decl * f, expr * lhs) { l = d2lr_it->m_value.first; quantifier_ref d(m_manager); func_decl_ref df(m_manager); - d = to_quantifier(occ); + d = to_quantifier(occ); df = to_app(l)->get_decl(); // Now we know there is an occurrence of f in d - // if n' can rewrite d { + // if n' can rewrite d { if (can_rewrite(d, lhs)) { TRACE("demodulator", tout << "Rescheduling: " << std::endl << mk_pp(d, m_manager) << std::endl; ); // remove d from m_fwd_idx remove_fwd_idx(df, d); - // remove d from m_back_idx + // remove d from m_back_idx // just remember it here, because otherwise it and/or esit might become invalid? - // to_remove.insert(d); + // to_remove.insert(d); remove_back_idx_proc proc(m_back_idx, d); for_each_expr(proc, d); // insert d into m_todo - m_todo.push_back(d); + m_todo.push_back(d); } } } @@ -629,10 +629,10 @@ void ufbv_rewriter::reschedule_demodulators(func_decl * f, expr * lhs) { //for (ptr_vector::iterator it = to_remove.begin(); it != to_remove.end(); it++) { // expr * d = *it; // remove_back_idx_proc proc(m_manager, m_back_idx, d); - // for_each_expr(proc, d); + // for_each_expr(proc, d); //} } - + void ufbv_rewriter::operator()(unsigned n, expr * const * exprs, proof * const * prs, expr_ref_vector & new_exprs, proof_ref_vector & new_prs) { if (m_manager.proofs_enabled()) { // Let us not waste time with proof production @@ -655,7 +655,7 @@ void ufbv_rewriter::operator()(unsigned n, expr * const * exprs, proof * const * m_match_subst.reserve(max_vid); - while (!m_todo.empty()) { + while (!m_todo.empty()) { // let n be the next formula in m_todo. expr_ref cur(m_manager); cur = m_todo.back(); @@ -670,21 +670,21 @@ void ufbv_rewriter::operator()(unsigned n, expr * const * exprs, proof * const * expr_ref large(m_manager), small(m_manager); if (!is_demodulator(np, large, small)) { // insert n' into m_processed - m_processed.insert(np); - // update m_back_idx (traverse n' and for each uninterpreted function declaration f in n' add the entry f->n' to m_back_idx) + m_processed.insert(np); + // update m_back_idx (traverse n' and for each uninterpreted function declaration f in n' add the entry f->n' to m_back_idx) add_back_idx_proc proc(m_back_idx, np); for_each_expr(proc, np); - } else { + } else { // np is a demodulator that allows us to replace 'large' with 'small'. TRACE("demodulator", tout << "Found demodulator: " << std::endl; - tout << mk_pp(large.get(), m_manager) << std::endl << " ---> " << + tout << mk_pp(large.get(), m_manager) << std::endl << " ---> " << std::endl << mk_pp(small.get(), m_manager) << std::endl; ); TRACE("demodulator_s", tout << "Found demodulator: " << std::endl; - tout << to_app(large)->get_decl()->get_name() << + tout << to_app(large)->get_decl()->get_name() << "[" << to_app(large)->get_depth() << "]" << " ---> "; if (is_app(small)) - tout << to_app(small)->get_decl()->get_name() << + tout << to_app(small)->get_decl()->get_name() << "[" << to_app(small)->get_depth() << "]" << std::endl; else tout << mk_pp(small.get(), m_manager) << std::endl; ); @@ -695,14 +695,14 @@ void ufbv_rewriter::operator()(unsigned n, expr * const * exprs, proof * const * reschedule_processed(f); reschedule_demodulators(f, large); - + // insert n' into m_fwd_idx insert_fwd_idx(large, small, to_quantifier(np)); // update m_back_idx add_back_idx_proc proc(m_back_idx, np); for_each_expr(proc, np); - } + } } // the result is the contents of m_processed + all demodulators in m_fwd_idx. @@ -743,10 +743,10 @@ ufbv_rewriter::match_subst::match_subst(ast_manager & m): */ struct match_args_aux_proc { substitution & m_subst; - struct no_match {}; - + struct no_match {}; + match_args_aux_proc(substitution & s):m_subst(s) {} - + void operator()(var * n) { expr_offset r; if (m_subst.find(n, 0, r)) { @@ -766,7 +766,7 @@ struct match_args_aux_proc { bool ufbv_rewriter::match_subst::match_args(app * lhs, expr * const * args) { m_cache.reset(); m_todo.reset(); - + // fill todo-list, and perform quick success/failure tests m_all_args_eq = true; unsigned num_args = lhs->get_num_args(); @@ -777,21 +777,21 @@ bool ufbv_rewriter::match_subst::match_args(app * lhs, expr * const * args) { m_all_args_eq = false; if (is_app(t_arg) && is_app(i_arg) && to_app(t_arg)->get_decl() != to_app(i_arg)->get_decl()) { // quick failure... - return false; + return false; } m_todo.push_back(expr_pair(t_arg, i_arg)); } - - if (m_all_args_eq) { + + if (m_all_args_eq) { // quick success worked... return true; } m_subst.reset(); - + while (!m_todo.empty()) { expr_pair const & p = m_todo.back(); - + if (is_var(p.first)) { expr_offset r; if (m_subst.find(to_var(p.first), 0, r)) { @@ -814,7 +814,7 @@ bool ufbv_rewriter::match_subst::match_args(app * lhs, expr * const * args) { SASSERT(is_app(p.first) && is_app(p.second)); - if (to_app(p.first)->is_ground() && !to_app(p.second)->is_ground()) + if (to_app(p.first)->is_ground() && !to_app(p.second)->is_ground()) return false; if (p.first == p.second && to_app(p.first)->is_ground()) { @@ -827,7 +827,7 @@ bool ufbv_rewriter::match_subst::match_args(app * lhs, expr * const * args) { m_todo.pop_back(); continue; } - + if (p.first == p.second) { // p.first and p.second is not ground... @@ -855,10 +855,10 @@ bool ufbv_rewriter::match_subst::match_args(app * lhs, expr * const * args) { app * n1 = to_app(p.first); app * n2 = to_app(p.second); - + if (n1->get_decl() != n2->get_decl()) return false; - + unsigned num_args1 = n1->get_num_args(); if (num_args1 != n2->get_num_args()) return false; @@ -867,7 +867,7 @@ bool ufbv_rewriter::match_subst::match_args(app * lhs, expr * const * args) { if (num_args1 == 0) continue; - + m_cache.insert(p); unsigned j = num_args1; while (j > 0) { @@ -886,7 +886,7 @@ bool ufbv_rewriter::match_subst::operator()(app * lhs, expr * rhs, expr * const new_rhs = rhs; return true; } - unsigned deltas[2] = { 0, 0 }; + unsigned deltas[2] = { 0, 0 }; m_subst.apply(2, deltas, expr_offset(rhs, 0), new_rhs); return true; } diff --git a/src/test/cnf_backbones.cpp b/src/test/cnf_backbones.cpp new file mode 100644 index 000000000..638f9684e --- /dev/null +++ b/src/test/cnf_backbones.cpp @@ -0,0 +1,285 @@ +/*++ +Copyright (c) 2017 Microsoft Corporation + +--*/ +#include +#include +#include +#include"timeout.h" +#include"rlimit.h" +#include"dimacs.h" +#include"sat_solver.h" +#include"gparams.h" + +static sat::solver * g_solver = 0; +static clock_t g_start_time; + +static void display_statistics() { + clock_t end_time = clock(); + if (g_solver) { + std::cout.flush(); + std::cerr.flush(); + + statistics st; + g_solver->collect_statistics(st); + st.update("total time", ((static_cast(end_time) - static_cast(g_start_time)) / CLOCKS_PER_SEC)); + st.display_smt2(std::cout); + } +} + +static void on_timeout() { + display_statistics(); + exit(0); +} + +static void STD_CALL on_ctrl_c(int) { + signal (SIGINT, SIG_DFL); + display_statistics(); + raise(SIGINT); +} + +static void display_model(sat::solver const & s) { + sat::model const & m = s.get_model(); + for (unsigned i = 1; i < m.size(); i++) { + switch (m[i]) { + case l_false: std::cout << "-" << i << " "; break; + case l_undef: break; + case l_true: std::cout << i << " "; break; + } + } + std::cout << "\n"; +} + +static void display_status(lbool r) { + switch (r) { + case l_true: + std::cout << "sat\n"; + break; + case l_undef: + std::cout << "unknown\n"; + break; + case l_false: + std::cout << "unsat\n"; + break; + } +} + +static void track_clause(sat::solver& dst, + sat::literal_vector& lits, + sat::literal_vector& assumptions, + vector& tracking_clauses) { + sat::literal lit = sat::literal(dst.mk_var(true, false), false); + tracking_clauses.set(lit.var(), lits); + lits.push_back(~lit); + dst.mk_clause(lits.size(), lits.c_ptr()); + assumptions.push_back(lit); +} + + +static void track_clauses(sat::solver const& src, + sat::solver& dst, + sat::literal_vector& assumptions, + vector& tracking_clauses) { + for (sat::bool_var v = 0; v < src.num_vars(); ++v) { + dst.mk_var(false, true); + } + sat::literal_vector lits; + sat::literal lit; + sat::clause * const * it = src.begin_clauses(); + sat::clause * const * end = src.end_clauses(); + svector bin_clauses; + src.collect_bin_clauses(bin_clauses, false); + tracking_clauses.reserve(2*src.num_vars() + static_cast(end - it) + bin_clauses.size()); + + for (sat::bool_var v = 1; v < src.num_vars(); ++v) { + if (src.value(v) != l_undef) { + bool sign = src.value(v) == l_false; + lits.reset(); + lits.push_back(sat::literal(v, sign)); + track_clause(dst, lits, assumptions, tracking_clauses); + } + } + for (; it != end; ++it) { + lits.reset(); + sat::clause& cls = *(*it); + lits.append(static_cast(cls.end()-cls.begin()), cls.begin()); + track_clause(dst, lits, assumptions, tracking_clauses); + } + for (unsigned i = 0; i < bin_clauses.size(); ++i) { + lits.reset(); + lits.push_back(bin_clauses[i].first); + lits.push_back(bin_clauses[i].second); + track_clause(dst, lits, assumptions, tracking_clauses); + } +} + + +static void prune_unfixed(sat::literal_vector& lambda, sat::model const& m) { + for (unsigned i = 0; i < lambda.size(); ++i) { + if ((m[lambda[i].var()] == l_false) != lambda[i].sign()) { + lambda[i] = lambda.back(); + lambda.pop_back(); + --i; + } + } +} + +// Algorithm 7: Corebased Algorithm with Chunking + +static void back_remove(sat::literal_vector& lits, sat::literal l) { + for (unsigned i = lits.size(); i > 0; ) { + --i; + if (lits[i] == l) { + lits[i] = lits.back(); + lits.pop_back(); + return; + } + } + std::cout << "UNREACHABLE\n"; +} + +static void brute_force_consequences(sat::solver& s, sat::literal_vector const& asms, sat::literal_vector const& gamma, sat::literal_vector& backbones) { + for (unsigned i = 0; i < gamma.size(); ++i) { + sat::literal nlit = ~gamma[i]; + sat::literal_vector asms1(asms); + asms1.push_back(nlit); + lbool r = s.check(asms1.size(), asms1.c_ptr()); + if (r == l_false) { + backbones.push_back(gamma[i]); + } + } +} + +static lbool core_chunking(sat::solver& s, sat::bool_var_vector& vars, sat::literal_vector const& asms, vector& conseq, unsigned K) { + lbool r = s.check(asms.size(), asms.c_ptr()); + if (r != l_true) { + return r; + } + sat::model const & m = s.get_model(); + sat::literal_vector lambda, backbones; + for (unsigned i = 0; i < vars.size(); i++) { + lambda.push_back(sat::literal(vars[i], m[vars[i]] == l_false)); + } + while (!lambda.empty()) { + IF_VERBOSE(1, verbose_stream() << "(sat-backbone-core " << lambda.size() << " " << backbones.size() << ")\n";); + unsigned k = std::min(K, lambda.size()); + sat::literal_vector gamma, omegaN; + for (unsigned i = 0; i < k; ++i) { + sat::literal l = lambda[lambda.size() - i - 1]; + gamma.push_back(l); + omegaN.push_back(~l); + } + while (true) { + sat::literal_vector asms1(asms); + asms1.append(omegaN); + r = s.check(asms1.size(), asms1.c_ptr()); + if (r == l_true) { + IF_VERBOSE(1, verbose_stream() << "(sat) " << omegaN << "\n";); + prune_unfixed(lambda, s.get_model()); + break; + } + sat::literal_vector const& core = s.get_core(); + sat::literal_vector occurs; + IF_VERBOSE(1, verbose_stream() << "(core " << core.size() << ")\n";); + for (unsigned i = 0; i < omegaN.size(); ++i) { + if (core.contains(omegaN[i])) { + occurs.push_back(omegaN[i]); + } + } + if (occurs.size() == 1) { + sat::literal lit = occurs.back(); + sat::literal nlit = ~lit; + backbones.push_back(~lit); + back_remove(lambda, ~lit); + back_remove(gamma, ~lit); + s.mk_clause(1, &nlit); + } + for (unsigned i = 0; i < omegaN.size(); ++i) { + if (occurs.contains(omegaN[i])) { + omegaN[i] = omegaN.back(); + omegaN.pop_back(); + --i; + } + } + if (omegaN.empty() && occurs.size() > 1) { + brute_force_consequences(s, asms, gamma, backbones); + for (unsigned i = 0; i < gamma.size(); ++i) { + back_remove(lambda, gamma[i]); + } + break; + } + } + } + for (unsigned i = 0; i < backbones.size(); ++i) { + sat::literal_vector cons; + cons.push_back(backbones[i]); + conseq.push_back(cons); + } + return l_true; +} + + +static void cnf_backbones(bool use_chunk, char const* file_name) { + g_start_time = clock(); + register_on_timeout_proc(on_timeout); + signal(SIGINT, on_ctrl_c); + params_ref p = gparams::get_module("sat"); + p.set_bool("produce_models", true); + reslimit limit; + sat::solver solver(p, limit); + sat::solver solver2(p, limit); + g_solver = &solver; + + if (file_name) { + std::ifstream in(file_name); + if (in.bad() || in.fail()) { + std::cerr << "(error \"failed to open file '" << file_name << "'\")" << std::endl; + exit(ERR_OPEN_FILE); + } + parse_dimacs(in, solver); + } + else { + parse_dimacs(std::cin, solver); + } + IF_VERBOSE(20, solver.display_status(verbose_stream());); + + vector conseq; + sat::bool_var_vector vars; + sat::literal_vector assumptions; + unsigned num_vars = solver.num_vars(); + if (p.get_bool("dimacs.core", false)) { + g_solver = &solver2; + vector tracking_clauses; + track_clauses(solver, solver2, assumptions, tracking_clauses); + } + // remove this line to limit variables to exclude assumptions + num_vars = g_solver->num_vars(); + for (unsigned i = 1; i < num_vars; ++i) { + vars.push_back(i); + g_solver->set_external(i); + } + lbool r; + if (use_chunk) { + r = core_chunking(*g_solver, vars, assumptions, conseq, 100); + } + else { + r = g_solver->get_consequences(assumptions, vars, conseq); + } + std::cout << vars.size() << " " << conseq.size() << "\n"; + display_status(r); + display_statistics(); +} + +void tst_cnf_backbones(char ** argv, int argc, int& i) { + bool use_chunk = i + 1 < argc && argv[i + 1] == std::string("chunk"); + if (use_chunk) ++i; + char const* file = ""; + if (i + 1 < argc) { + file = argv[i + 1]; + } + else { + file = argv[1]; + } + cnf_backbones(use_chunk, file); + ++i; +} diff --git a/src/test/ddnf.cpp b/src/test/ddnf.cpp index d2ca92557..31503842d 100644 --- a/src/test/ddnf.cpp +++ b/src/test/ddnf.cpp @@ -1,4 +1,3 @@ - /*++ Copyright (c) 2015 Microsoft Corporation diff --git a/src/test/main.cpp b/src/test/main.cpp index 28b1537f9..a561ab8b8 100644 --- a/src/test/main.cpp +++ b/src/test/main.cpp @@ -131,7 +131,6 @@ void parse_cmd_line_args(int argc, char ** argv, bool& do_display_usage, bool& t std::cerr << ex.msg() << "\n"; } } - i++; } } @@ -245,6 +244,7 @@ int main(int argc, char ** argv) { TST(pb2bv); TST_ARGV(sat_lookahead); TST_ARGV(sat_local_search); + TST_ARGV(cnf_backbones); //TST_ARGV(hs); } diff --git a/src/test/smt2print_parse.cpp b/src/test/smt2print_parse.cpp index 982e2e3f5..c79a80ae5 100644 --- a/src/test/smt2print_parse.cpp +++ b/src/test/smt2print_parse.cpp @@ -13,8 +13,9 @@ Copyright (c) 2015 Microsoft Corporation void test_print(Z3_context ctx, Z3_ast a) { Z3_set_ast_print_mode(ctx, Z3_PRINT_SMTLIB2_COMPLIANT); char const* spec1 = Z3_benchmark_to_smtlib_string(ctx, "test", 0, 0, 0, 0, 0, a); - std::cout << spec1 << "\n"; + std::cout << "spec1: benchmark->string\n" << spec1 << "\n"; + std::cout << "attempting to parse spec1...\n"; Z3_ast b = Z3_parse_smtlib2_string(ctx, spec1, @@ -24,14 +25,14 @@ void test_print(Z3_context ctx, Z3_ast a) { 0, 0, 0); - + std::cout << "parse successful, converting ast->string\n"; char const* spec2 = Z3_ast_to_string(ctx, b); - std::cout << spec2 << "\n"; + std::cout << "spec2: string->ast->string\n" << spec2 << "\n"; } void test_parseprint(char const* spec) { Z3_context ctx = Z3_mk_context(0); - std::cout << spec << "\n"; + std::cout << "spec:\n" << spec << "\n"; Z3_ast a = Z3_parse_smtlib2_string(ctx, @@ -43,8 +44,12 @@ void test_parseprint(char const* spec) { 0, 0); + std::cout << "done parsing\n"; + test_print(ctx, a); + std::cout << "done printing\n"; + Z3_del_context(ctx); } @@ -104,6 +109,12 @@ void tst_smt2print_parse() { test_parseprint(spec5); + // Test strings + char const* spec6 = + "(assert (= \"abc\" \"abc\"))"; + + test_parseprint(spec6); + // Test ? } diff --git a/src/util/hwf.cpp b/src/util/hwf.cpp index bd8d4958d..e577e15df 100644 --- a/src/util/hwf.cpp +++ b/src/util/hwf.cpp @@ -52,6 +52,9 @@ Revision History: #ifdef USE_INTRINSICS #include +#if defined(_MSC_VER) || defined(__SSE4_1__) +#include +#endif #endif hwf_manager::hwf_manager() : @@ -303,7 +306,9 @@ void hwf_manager::round_to_integral(mpf_rounding_mode rm, hwf const & x, hwf & o // According to the Intel Architecture manual, the x87-instrunction FRNDINT is the // same in 32-bit and 64-bit mode. The _mm_round_* intrinsics are SSE4 extensions. #ifdef _WINDOWS -#ifdef USE_INTRINSICS +#if defined(USE_INTRINSICS) && \ + (defined(_WINDOWS) && (defined(__AVX__) || defined(_M_X64))) || \ + (!defined(_WINDOWS) && defined(__SSE4_1__)) switch (rm) { case 0: _mm_store_sd(&o.value, _mm_round_pd(_mm_set_sd(x.value), _MM_FROUND_TO_NEAREST_INT)); break; case 2: _mm_store_sd(&o.value, _mm_round_pd(_mm_set_sd(x.value), _MM_FROUND_TO_POS_INF)); break; diff --git a/src/util/hwf.h b/src/util/hwf.h index cf0c9b7ea..8816e5b37 100644 --- a/src/util/hwf.h +++ b/src/util/hwf.h @@ -88,9 +88,6 @@ public: bool is_pzero(hwf const & x); bool is_one(hwf const & x); - - // structural eq - bool eq_core(hwf const & x, hwf const & y); bool eq(hwf const & x, hwf const & y); bool lt(hwf const & x, hwf const & y); diff --git a/src/util/memory_manager.cpp b/src/util/memory_manager.cpp index 6bd4ec64f..c727a8af3 100644 --- a/src/util/memory_manager.cpp +++ b/src/util/memory_manager.cpp @@ -57,6 +57,7 @@ static void throw_out_of_memory() { { g_memory_out_of_memory = true; } + if (g_exit_when_out_of_memory) { std::cerr << g_out_of_memory_msg << "\n"; exit(ERR_MEMOUT); diff --git a/src/util/obj_pair_set.h b/src/util/obj_pair_set.h index 29139a51d..c4212977c 100644 --- a/src/util/obj_pair_set.h +++ b/src/util/obj_pair_set.h @@ -46,6 +46,11 @@ public: bool contains(obj_pair const & p) const { return m_set.contains(p); } void reset() { m_set.reset(); } bool empty() const { return m_set.empty(); } + + typedef typename chashtable::iterator iterator; + + iterator begin() { return m_set.begin(); } + iterator end() { return m_set.end(); } }; #endif diff --git a/src/util/queue.h b/src/util/queue.h new file mode 100644 index 000000000..a517efc24 --- /dev/null +++ b/src/util/queue.h @@ -0,0 +1,73 @@ +/*++ +Copyright (c) 2017 Microsoft Corporation + +Module Name: + + queue.h + +Abstract: + + Generic queue. + +Author: + + Nikolaj Bjorner (nbjorner) 2017-4-17 + +Notes: + +--*/ +#ifndef _QUEUE_H_ +#define _QUEUE_H_ + +#include "vector.h" + +template +class queue { + vector m_elems; + unsigned m_head; + unsigned m_capacity; + +public: + + queue(): m_head(0), m_capacity(0) {} + + void push(T const& t) { m_elems.push_back(t); } + + bool empty() const { + return m_head == m_elems.size(); + } + + T top() const { + return m_elems[m_head]; + } + + T pop_front() { + SASSERT(!empty()); + m_capacity = std::max(m_capacity, m_elems.size()); + SASSERT(m_head < m_elems.size()); + if (2 * m_head > m_capacity && m_capacity > 10) { + for (unsigned i = 0; i < m_elems.size() - m_head; ++i) { + m_elems[i] = m_elems[i + m_head]; + } + m_elems.shrink(m_elems.size() - m_head); + m_head = 0; + } + return m_elems[m_head++]; + } + + + T back() const { + return m_elems[m_elems.size() - 1]; + } + + T pop_back() { + SASSERT(!empty()); + SASSERT(m_head < m_elems.size()); + T result = back(); + m_elems.shrink(m_elems.size() - 1); + return result; + } +}; + +#endif + diff --git a/src/util/small_object_allocator.cpp b/src/util/small_object_allocator.cpp index aee84c1f0..60c85b660 100644 --- a/src/util/small_object_allocator.cpp +++ b/src/util/small_object_allocator.cpp @@ -70,6 +70,7 @@ void small_object_allocator::reset() { void small_object_allocator::deallocate(size_t size, void * p) { if (size == 0) return; + #if defined(Z3DEBUG) && !defined(_WINDOWS) // Valgrind friendly memory::deallocate(p); @@ -93,6 +94,7 @@ void small_object_allocator::deallocate(size_t size, void * p) { void * small_object_allocator::allocate(size_t size) { if (size == 0) return 0; + #if defined(Z3DEBUG) && !defined(_WINDOWS) // Valgrind friendly return memory::allocate(size); diff --git a/src/util/util.cpp b/src/util/util.cpp index bfd4923a8..50d93913a 100644 --- a/src/util/util.cpp +++ b/src/util/util.cpp @@ -17,6 +17,9 @@ Revision History: --*/ +#ifdef _WINDOWS +#include "windows.h" +#endif #include"util.h" static unsigned g_verbosity_level = 0; @@ -35,6 +38,19 @@ void set_verbose_stream(std::ostream& str) { g_verbose_stream = &str; } +static int g_thread_id = 0; +static bool g_is_threaded = false; + +bool is_threaded() { + if (g_is_threaded) return true; +#ifdef _WINDOWS + int thid = GetCurrentThreadId(); + g_is_threaded = g_thread_id != thid && g_thread_id != 0; + g_thread_id = thid; +#endif + return g_is_threaded; +} + std::ostream& verbose_stream() { return *g_verbose_stream; } diff --git a/src/util/util.h b/src/util/util.h index a040a79ae..e62f24e44 100644 --- a/src/util/util.h +++ b/src/util/util.h @@ -24,6 +24,7 @@ Revision History: #include #include #include +#include"z3_omp.h" #ifndef SIZE_MAX #define SIZE_MAX std::numeric_limits::max() @@ -182,16 +183,26 @@ void set_verbosity_level(unsigned lvl); unsigned get_verbosity_level(); std::ostream& verbose_stream(); void set_verbose_stream(std::ostream& str); +bool is_threaded(); -#define IF_VERBOSE(LVL, CODE) { if (get_verbosity_level() >= LVL) { CODE } } ((void) 0) - -#ifdef _EXTERNAL_RELEASE -#define IF_IVERBOSE(LVL, CODE) ((void) 0) -#else -#define IF_IVERBOSE(LVL, CODE) { if (get_verbosity_level() >= LVL) { CODE } } ((void) 0) -#endif - + +#define IF_VERBOSE(LVL, CODE) { \ + if (get_verbosity_level() >= LVL) { \ + if (is_threaded()) { \ + LOCK_CODE(CODE); \ + } \ + else { \ + CODE; \ + } \ + } } ((void) 0) +#define LOCK_CODE(CODE) \ + { \ + __pragma(omp critical (verbose_lock)) \ + { \ + CODE; \ + } \ + } template struct default_eq { diff --git a/src/util/version.h.cmake.in b/src/util/version.h.cmake.in new file mode 100644 index 000000000..af3a652a6 --- /dev/null +++ b/src/util/version.h.cmake.in @@ -0,0 +1,8 @@ +// automatically generated file. +#define Z3_MAJOR_VERSION @Z3_VERSION_MAJOR@ +#define Z3_MINOR_VERSION @Z3_VERSION_MINOR@ +#define Z3_BUILD_NUMBER @Z3_VERSION_PATCH@ +#define Z3_REVISION_NUMBER @Z3_VERSION_TWEAK@ + +#define Z3_FULL_VERSION @Z3_FULL_VERSION@ +#cmakedefine Z3GITHASH @Z3GITHASH@