From 5934a428980fb37cf0994a90621cc1c084e1013a Mon Sep 17 00:00:00 2001 From: dehnert <dehnert@cs.rwth-aachen.de> Date: Fri, 6 May 2016 19:23:57 +0200 Subject: [PATCH] Squashed 'resources/3rdparty/sylvan/' content from commit d91f6ac git-subtree-dir: resources/3rdparty/sylvan git-subtree-split: d91f6acb554fd7de603ab80522507f41f2faa04b --- .gitignore | 43 + .travis.yml | 100 + CMakeLists.txt | 50 + LICENSE | 202 +++ Makefile.am | 5 + README.md | 97 + cmake/FindGMP.cmake | 20 + configure.ac | 21 + examples/CMakeLists.txt | 32 + examples/getrss.c | 68 + examples/getrss.h | 26 + examples/lddmc.c | 499 +++++ examples/mc.c | 616 +++++++ examples/simple.cpp | 121 ++ m4/.gitignore | 5 + m4/m4_ax_check_compile_flag.m4 | 72 + models/at.5.8-rgs.bdd | Bin 0 -> 50448 bytes models/at.6.8-rgs.bdd | Bin 0 -> 50128 bytes models/at.7.8-rgs.bdd | Bin 0 -> 59144 bytes models/blocks.2.ldd | Bin 0 -> 14616 bytes models/blocks.4.ldd | Bin 0 -> 41000 bytes models/collision.4.9-rgs.bdd | Bin 0 -> 73696 bytes models/collision.5.9-rgs.bdd | Bin 0 -> 73760 bytes models/schedule_world.2.8-rgs.bdd | Bin 0 -> 73472 bytes models/schedule_world.3.8-rgs.bdd | Bin 0 -> 97456 bytes src/CMakeLists.txt | 81 + src/Makefile.am | 39 + src/avl.h | 398 ++++ src/lace.c | 1045 +++++++++++ src/lace.h | 2743 ++++++++++++++++++++++++++++ src/llmsset.c | 564 ++++++ src/llmsset.h | 202 +++ src/refs.c | 598 ++++++ src/refs.h | 77 + src/sha2.c | 1067 +++++++++++ src/sha2.h | 197 ++ src/stats.c | 245 +++ src/stats.h | 262 +++ src/sylvan.h | 182 ++ src/sylvan_bdd.c | 2820 +++++++++++++++++++++++++++++ src/sylvan_bdd.h | 423 +++++ src/sylvan_bdd_storm.h | 3 + src/sylvan_cache.c | 220 +++ src/sylvan_cache.h | 113 ++ src/sylvan_common.c | 304 ++++ src/sylvan_common.h | 85 + src/sylvan_config.h | 30 + src/sylvan_gmp.c | 595 ++++++ src/sylvan_gmp.h | 182 ++ src/sylvan_ldd.c | 2560 ++++++++++++++++++++++++++ src/sylvan_ldd.h | 288 +++ src/sylvan_mtbdd.c | 2542 ++++++++++++++++++++++++++ src/sylvan_mtbdd.h | 608 +++++++ src/sylvan_mtbdd_int.h | 128 ++ src/sylvan_mtbdd_storm.c | 514 ++++++ src/sylvan_mtbdd_storm.h | 111 ++ src/sylvan_obj.cpp | 1039 +++++++++++ src/sylvan_obj.hpp | 855 +++++++++ src/sylvan_obj_bdd_storm.hpp | 3 + src/sylvan_obj_mtbdd_storm.hpp | 51 + src/sylvan_obj_storm.cpp | 141 ++ src/tls.h | 35 + test/.gitignore | 5 + test/CMakeLists.txt | 15 + test/main.c | 350 ++++ test/test_assert.h | 13 + test/test_basic.c | 331 ++++ test/test_cxx.cpp | 51 + 68 files changed, 24092 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 CMakeLists.txt create mode 100644 LICENSE create mode 100644 Makefile.am create mode 100644 README.md create mode 100644 cmake/FindGMP.cmake create mode 100644 configure.ac create mode 100644 examples/CMakeLists.txt create mode 100644 examples/getrss.c create mode 100644 examples/getrss.h create mode 100644 examples/lddmc.c create mode 100644 examples/mc.c create mode 100644 examples/simple.cpp create mode 100644 m4/.gitignore create mode 100644 m4/m4_ax_check_compile_flag.m4 create mode 100644 models/at.5.8-rgs.bdd create mode 100644 models/at.6.8-rgs.bdd create mode 100644 models/at.7.8-rgs.bdd create mode 100644 models/blocks.2.ldd create mode 100644 models/blocks.4.ldd create mode 100644 models/collision.4.9-rgs.bdd create mode 100644 models/collision.5.9-rgs.bdd create mode 100644 models/schedule_world.2.8-rgs.bdd create mode 100644 models/schedule_world.3.8-rgs.bdd create mode 100644 src/CMakeLists.txt create mode 100644 src/Makefile.am create mode 100644 src/avl.h create mode 100644 src/lace.c create mode 100644 src/lace.h create mode 100644 src/llmsset.c create mode 100644 src/llmsset.h create mode 100644 src/refs.c create mode 100644 src/refs.h create mode 100644 src/sha2.c create mode 100644 src/sha2.h create mode 100644 src/stats.c create mode 100644 src/stats.h create mode 100644 src/sylvan.h create mode 100644 src/sylvan_bdd.c create mode 100644 src/sylvan_bdd.h create mode 100644 src/sylvan_bdd_storm.h create mode 100644 src/sylvan_cache.c create mode 100644 src/sylvan_cache.h create mode 100644 src/sylvan_common.c create mode 100644 src/sylvan_common.h create mode 100644 src/sylvan_config.h create mode 100644 src/sylvan_gmp.c create mode 100644 src/sylvan_gmp.h create mode 100644 src/sylvan_ldd.c create mode 100644 src/sylvan_ldd.h create mode 100644 src/sylvan_mtbdd.c create mode 100644 src/sylvan_mtbdd.h create mode 100644 src/sylvan_mtbdd_int.h create mode 100644 src/sylvan_mtbdd_storm.c create mode 100644 src/sylvan_mtbdd_storm.h create mode 100644 src/sylvan_obj.cpp create mode 100644 src/sylvan_obj.hpp create mode 100644 src/sylvan_obj_bdd_storm.hpp create mode 100644 src/sylvan_obj_mtbdd_storm.hpp create mode 100644 src/sylvan_obj_storm.cpp create mode 100644 src/tls.h create mode 100644 test/.gitignore create mode 100644 test/CMakeLists.txt create mode 100644 test/main.c create mode 100644 test/test_assert.h create mode 100644 test/test_basic.c create mode 100644 test/test_cxx.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..d45dee77e --- /dev/null +++ b/.gitignore @@ -0,0 +1,43 @@ +# autotools +**/Makefile +/autom4te.cache/ +config.* +.dirstamp +aclocal.m4 +configure +m4/* +tools +Makefile.in + +# cmake +**/CMakeCache.txt +**/CMakeFiles +**/cmake_install.cmake + +# libtool +.deps/ +.libs/ +/libtool + +# object files +*.lo +*.o +*.la + +# output files +examples/mc +examples/lddmc +test/sylvan_test +test/test_cxx +src/libsylvan.a + +# MacOS file +.DS_Store + +# eclipse files +.cproject +.project +.settings + +# coverage output +coverage diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..ac0da9fb9 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,100 @@ +sudo: false + +matrix: + include: + - os: linux + env: TOOLSET=gcc CC=gcc-4.7 CXX=g++-4.7 BUILD_TYPE="Release" HWLOC="ON" SYLVAN_STATS="ON" + addons: + apt: + packages: ["gcc-4.7", "g++-4.7", "libstd++-4.7-dev", "libgmp-dev", "cmake", "libhwloc-dev"] + sources: ["ubuntu-toolchain-r-test"] + - os: linux + env: TOOLSET=gcc CC=gcc-4.8 CXX=g++-4.8 BUILD_TYPE="Release" HWLOC="ON" SYLVAN_STATS="ON" + addons: + apt: + packages: ["gcc-4.8", "g++-4.8", "libstd++-4.8-dev", "libgmp-dev", "cmake", "libhwloc-dev"] + sources: ["ubuntu-toolchain-r-test"] + - os: linux + env: TOOLSET=gcc CC=gcc-4.9 CXX=g++-4.9 BUILD_TYPE="Release" HWLOC="ON" SYLVAN_STATS="ON" + addons: + apt: + packages: ["gcc-4.9", "g++-4.9", "libstd++-4.9-dev", "libgmp-dev", "cmake", "libhwloc-dev"] + sources: ["ubuntu-toolchain-r-test"] + - os: linux + env: TOOLSET=gcc CC=gcc-5 CXX=g++-5 BUILD_TYPE="Debug" HWLOC="OFF" SYLVAN_STATS="OFF" + addons: + apt: + packages: ["gcc-5", "g++-5", "libstd++-5-dev", "libgmp-dev", "cmake", "libhwloc-dev"] + sources: ["ubuntu-toolchain-r-test"] + - os: linux + env: TOOLSET=gcc CC=gcc-5 CXX=g++-5 BUILD_TYPE="Debug" HWLOC="ON" SYLVAN_STATS="ON" + addons: + apt: + packages: ["gcc-5", "g++-5", "libstd++-5-dev", "libgmp-dev", "cmake", "libhwloc-dev"] + sources: ["ubuntu-toolchain-r-test"] + - os: linux + env: TOOLSET=gcc CC=gcc-5 CXX=g++-5 BUILD_TYPE="Release" HWLOC="ON" SYLVAN_STATS="ON" + addons: + apt: + packages: ["gcc-5", "g++-5", "libstd++-5-dev", "libgmp-dev", "cmake", "libhwloc-dev"] + sources: ["ubuntu-toolchain-r-test"] + - os: linux + env: TOOLSET=gcc CC=gcc-5 CXX=g++-5 BUILD_TYPE="Release" HWLOC="ON" SYLVAN_STATS="OFF" + addons: + apt: + packages: ["gcc-5", "g++-5", "libstd++-5-dev", "libgmp-dev", "cmake", "libhwloc-dev"] + sources: ["ubuntu-toolchain-r-test"] + - os: linux + env: TOOLSET=gcc CC=gcc-5 CXX=g++-5 BUILD_TYPE="Release" HWLOC="ON" SYLVAN_STATS="ON" VARIANT="coverage" + addons: + apt: + packages: ["gcc-5", "g++-5", "libstd++-5-dev", "libgmp-dev", "cmake", "libhwloc-dev"] + sources: ["ubuntu-toolchain-r-test"] + - os: linux + env: TOOLSET=clang CC=/usr/local/clang-3.4/bin/clang CXX=/usr/local/clang-3.4/bin/clang++ BUILD_TYPE="Release" HWLOC="ON" SYLVAN_STATS="ON" + addons: + apt: + packages: ["clang-3.4", "libstdc++-5-dev", "libgmp-dev", "cmake", "libhwloc-dev"] + sources: ["ubuntu-toolchain-r-test", "llvm-toolchain-precise-3.4"] + - os: linux + env: TOOLSET=clang CC=clang-3.6 CXX=clang++-3.6 BUILD_TYPE="Release" HWLOC="ON" SYLVAN_STATS="ON" + addons: + apt: + packages: ["clang-3.6", "libstdc++-5-dev", "libgmp-dev", "cmake", "libhwloc-dev"] + sources: ["ubuntu-toolchain-r-test", "llvm-toolchain-precise-3.6"] + - os: linux + env: TOOLSET=clang CC=clang-3.7 CXX=clang++-3.7 BUILD_TYPE="Release" HWLOC="ON" SYLVAN_STATS="ON" + addons: + apt: + packages: ["clang-3.7", "libstdc++-5-dev", "libgmp-dev", "cmake", "libhwloc-dev"] + sources: ["ubuntu-toolchain-r-test", "llvm-toolchain-precise-3.7"] + - os: osx + env: TOOLSET=clang CC=clang CXX=clang++ BUILD_TYPE="Debug" HWLOC="ON" SYLVAN_STATS="ON" + - os: osx + env: TOOLSET=clang CC=clang CXX=clang++ BUILD_TYPE="Release" HWLOC="ON" SYLVAN_STATS="OFF" + - os: osx + env: TOOLSET=gcc CC=gcc-4.9 CXX=g++-4.9 BUILD_TYPE="Debug" HWLOC="ON" SYLVAN_STATS="OFF" + - os: osx + env: TOOLSET=gcc CC=gcc-4.9 CXX=g++-4.9 BUILD_TYPE="Release" HWLOC="ON" SYLVAN_STATS="OFF" + - os: osx + env: TOOLSET=gcc CC=gcc-5 CXX=g++-5 BUILD_TYPE="Debug" HWLOC="ON" SYLVAN_STATS="OFF" + - os: osx + env: TOOLSET=gcc CC=gcc-5 CXX=g++-5 BUILD_TYPE="Release" HWLOC="ON" SYLVAN_STATS="OFF" + +install: +- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update; brew install argp-standalone homebrew/science/hwloc; fi +- if [[ "$TRAVIS_OS_NAME" == "osx" && "$CC" == "gcc-5" ]]; then brew install homebrew/versions/gcc5; fi + +script: +- ${CC} --version +- ${CXX} --version +- cmake . -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DUSE_HWLOC=${HWLOC} -DSYLVAN_STATS=${SYLVAN_STATS} -DWITH_COVERAGE=${COVERAGE} +- make -j 2 +- make test +- examples/simple +- examples/mc models/schedule_world.2.8-rgs.bdd -w 2 | tee /dev/fd/2 | grep -q "1,570,340" +- examples/lddmc models/blocks.2.ldd -w 2 | tee /dev/fd/2 | grep -q "7057 states" + +notifications: + email: false + diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 000000000..27762655b --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,50 @@ +cmake_minimum_required(VERSION 2.6) +project(sylvan C CXX) +enable_testing() + +set(CMAKE_C_FLAGS "-O3 -Wextra -Wall -Werror -fno-strict-aliasing -std=gnu11") +set(CMAKE_CXX_FLAGS "-O3 -Wextra -Wall -Werror -fno-strict-aliasing -Wno-deprecated-register -std=gnu++11") + +option(WITH_COVERAGE "Add generation of test coverage" OFF) +if(WITH_COVERAGE) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0 -g -coverage") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -g -coverage") + + find_program(GCOV_PATH gcov) + find_program(LCOV_PATH lcov) + find_program(GENHTML_PATH genhtml) + + add_custom_target(coverage + # Cleanup lcov + ${LCOV_PATH} --directory . --zerocounters + # Run tests + COMMAND make test + # Capture counters + COMMAND ${LCOV_PATH} --gcov-tool ${GCOV_PATH} --directory . --capture --output-file coverage.info + COMMAND ${LCOV_PATH} --remove coverage.info 'test/*' '/usr/*' 'examples/*' 'src/sylvan_mtbdd*' 'src/lace*' 'src/sylvan_ldd*' 'src/avl.h' 'src/sha2.c' --output-file coverage.info.cleaned + COMMAND ${GENHTML_PATH} -o coverage coverage.info.cleaned + COMMAND ${CMAKE_COMMAND} -E remove coverage.info coverage.info.cleaned + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + ) +endif() + +set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) +find_package(GMP REQUIRED) +include_directories(${GMP_INCLUDE_DIR}) +include_directories(src) + +include_directories(src) + +add_subdirectory(src) + +option(SYLVAN_BUILD_TEST "Build test programs" ON) + +if(SYLVAN_BUILD_TEST) + add_subdirectory(test) +endif() + +option(SYLVAN_BUILD_EXAMPLES "Build example tools" OFF) + +if(SYLVAN_BUILD_EXAMPLES) + add_subdirectory(examples) +endif() diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 000000000..6e1cf8acc --- /dev/null +++ b/Makefile.am @@ -0,0 +1,5 @@ +ACLOCAL_AMFLAGS = -I m4 + +AM_CFLAGS = -g -O2 -Wall -Wextra -Werror -std=gnu11 + +SUBDIRS = src diff --git a/README.md b/README.md new file mode 100644 index 000000000..f0f88b851 --- /dev/null +++ b/README.md @@ -0,0 +1,97 @@ +Sylvan [](https://travis-ci.org/trolando/sylvan) +====== +Sylvan is a parallel (multi-core) BDD library in C. Sylvan allows both sequential and parallel BDD-based algorithms to benefit from parallelism. Sylvan uses the work-stealing framework Lace and a scalable lockless hashtable to implement scalable multi-core BDD operations. + +Sylvan is developed (© 2011-2016) by the [Formal Methods and Tools](http://fmt.ewi.utwente.nl/) group at the University of Twente as part of the MaDriD project, which is funded by NWO. Sylvan is licensed with the Apache 2.0 license. + +You can contact the main author of Sylvan at <t.vandijk@utwente.nl>. Please let us know if you use Sylvan in your projects. + +Sylvan is available at: https://github.com/utwente-fmt/sylvan +Java/JNI bindings: https://github.com/trolando/jsylvan +Haskell bindings: https://github.com/adamwalker/sylvan-haskell + +Publications +------------ +T. van Dijk and J. van de Pol (2015) [Sylvan: Multi-core Decision Diagrams](http://dx.doi.org/10.1007/978-3-662-46681-0_60). In: TACAS 2015, LNCS 9035. Springer. + +T. van Dijk and A.W. Laarman and J. van de Pol (2012) [Multi-Core BDD Operations for Symbolic Reachability](http://eprints.eemcs.utwente.nl/22166/). In: PDMC 2012, ENTCS. Elsevier. + +Usage +----- +Simple examples can be found in the `examples` subdirectory. The file `simple.cpp` contains a toy program that +uses the C++ objects to perform basic BDD manipulation. +The `mc.c` and `lddmc.c` programs are more advanced examples of symbolic model checking (with example models in the `models` subdirectory). + +Sylvan depends on the [work-stealing framework Lace](http://fmt.ewi.utwente.nl/tools/lace) for its implementation. Lace is embedded in the Sylvan distribution. +To use Sylvan, Lace must be initialized first. +For more details, see the comments in `src/sylvan.h`. + +### Basic functionality + +To create new BDDs, you can use: +- `sylvan_true`: representation of constant `true`. +- `sylvan_false`: representation of constant `false`. +- `sylvan_ithvar(var)`: representation of literal <var> (negated: `sylvan_nithvar(var)`) + +To follow the BDD edges and obtain the variable at the root of a BDD, you can use: +- `sylvan_var(bdd)`: obtain variable of the root node of <bdd> - requires that <bdd> is not constant `true` or `false`. +- `sylvan_high(bdd)`: follow high edge of <bdd>. +- `sylvan_low(bdd)`: follow low edge of <bdd>. + +You need to manually reference BDDs that you want to keep during garbage collection: +- `sylvan_ref(bdd)`: add reference to <bdd>. +- `sylvan_deref(bdd)`: remove reference to <bdd>. +- `sylvan_protect(bddptr)`: add a pointer reference to the BDD variable <bddptr> +- `sylvan_unprotect(bddptr)`: remove a pointer reference to the BDD variable <bddptr> + +It is recommended to use `sylvan_protect` and `sylvan_unprotect`. +The C++ objects handle this automatically. + +The following 'primitives' are implemented: +- `sylvan_not(bdd)`: negation of <bdd>. +- `sylvan_ite(a,b,c)`: calculate 'if <a> then <b> else <c>'. +- `sylvan_and(a, b)`: calculate a and b +- `sylvan_or(a, b)`: calculate a or b +- `sylvan_nand(a, b)`: calculate not (a and b) +- `sylvan_nor(a, b)`: calculate not (a or b) +- `sylvan_imp(a, b)`: calculate a implies b +- `sylvan_invimp(a, b)`: calculate implies a +- `sylvan_xor(a, b)`: calculate a xor b +- `sylvan_equiv(a, b)`: calculate a = b +- `sylvan_diff(a, b)`: calculate a and not b +- `sylvan_less(a, b)`: calculate b and not a +- `sylvan_exists(bdd, vars)`: existential quantification of <bdd> with respect to variables <vars>. Here, <vars> is a conjunction of literals. +- `sylvan_forall(bdd, vars)`: universal quantification of <bdd> with respect to variables <vars>. Here, <vars> is a conjunction of literals. + +### Other BDD operations + +See `src/sylvan_bdd.h`, `src/sylvan_mtbdd.h` and `src/sylvan_ldd.h` for other implemented operations. +See `src/sylvan_obj.hpp` for the C++ interface. + +### Garbage collection + +Garbage collection is triggered when trying to insert a new node and no new bucket can be found within a reasonable upper bound. +Garbage collection is stop-the-world and all workers must cooperate on garbage collection. (Beware of deadlocks if you use Sylvan operations in critical sections!) +- `sylvan_gc()`: manually trigger garbage collection. +- `sylvan_gc_enable()`: enable garbage collection. +- `sylvan_gc_disable()`: disable garbage collection. + +### Table resizing + +During garbage collection, it is possible to resize the nodes table and the cache. +Sylvan provides two default implementations: an agressive version that resizes every time garbage collection is performed, +and a less agressive version that only resizes when at least half the table is full. +This can be configured in `src/sylvan_config.h` +It is not possible to decrease the size of the nodes table and the cache. + +### Dynamic reordering + +Dynamic reordening is currently not supported. +For now, we suggest users find a good static variable ordering. + +Troubleshooting +--------------- +Sylvan may require a larger than normal program stack. You may need to increase the program stack size on your system using `ulimit -s`. Segmentation faults on large computations typically indicate a program stack overflow. + +### I am getting the error "unable to allocate memory: ...!" +Sylvan allocates virtual memory using mmap. If you specify a combined size for the cache and node table larger than your actual available memory you may need to set `vm.overcommit_memory` to `1`. E.g. `echo 1 > /proc/sys/vm/overcommit_memory`. You can make this setting permanent with `echo "vm.overcommit_memory = 1" > /etc/sysctl.d/99-sylvan.conf`. You can verify the setting with `cat /proc/sys/vm/overcommit_memory`. It should report `1`. diff --git a/cmake/FindGMP.cmake b/cmake/FindGMP.cmake new file mode 100644 index 000000000..62c75c034 --- /dev/null +++ b/cmake/FindGMP.cmake @@ -0,0 +1,20 @@ +FIND_PATH(GMP_INCLUDE_DIR + gmp.h ) + +FIND_LIBRARY(GMP_LIBRARIES + NAMES gmp + HINTS /usr/local/lib ) + +IF (GMP_INCLUDE_DIR AND GMP_LIBRARIES) + SET(GMP_FOUND TRUE) +ENDIF (GMP_INCLUDE_DIR AND GMP_LIBRARIES) + +IF (GMP_FOUND) + IF (NOT GMP_FIND_QUIETLY) + MESSAGE(STATUS "Found GMP: ${GMP_LIBRARIES}") + ENDIF (NOT GMP_FIND_QUIETLY) +ELSE (GMP_FOUND) + IF (GMP_FIND_REQUIRED) + MESSAGE(FATAL_ERROR "Could not find GMP") + ENDIF (GMP_FIND_REQUIRED) +ENDIF (GMP_FOUND) diff --git a/configure.ac b/configure.ac new file mode 100644 index 000000000..5363e6daa --- /dev/null +++ b/configure.ac @@ -0,0 +1,21 @@ +AC_PREREQ([2.60]) +AC_INIT([sylvan], [1.0]) +AC_CONFIG_MACRO_DIR([m4]) +AC_CONFIG_AUX_DIR([tools]) +AM_INIT_AUTOMAKE([foreign]) + +AC_PROG_CC +AX_CHECK_COMPILE_FLAG([-std=c11],,[AC_MSG_FAILURE([no acceptable C11 compiler found.])]) +AC_PROG_CXX +LT_INIT + +AC_CHECKING([for any suitable hwloc installation]) +AC_CHECK_LIB([hwloc], [hwloc_topology_init], [AC_CHECK_HEADER([hwloc.h], [hwloc=yes])]) +AM_CONDITIONAL([HAVE_LIBHWLOC], [test "$hwloc" = "yes"]) + +AC_CANONICAL_HOST +AM_CONDITIONAL([DARWIN], [case $host_os in darwin*) true;; *) false;; esac]) +# test x$(uname) == "xDarwin"]) + +AC_CONFIG_FILES([Makefile src/Makefile]) +AC_OUTPUT diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 000000000..bb335935d --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,32 @@ +cmake_minimum_required(VERSION 2.6) +project(sylvan C CXX) + +include_directories(.) + +add_executable(mc mc.c getrss.h getrss.c) +target_link_libraries(mc sylvan) + +add_executable(lddmc lddmc.c getrss.h getrss.c) +target_link_libraries(lddmc sylvan) + +add_executable(simple simple.cpp) +target_link_libraries(simple sylvan stdc++) + +include(CheckIncludeFiles) +check_include_files("gperftools/profiler.h" HAVE_PROFILER) + +if(HAVE_PROFILER) + set_target_properties(mc PROPERTIES COMPILE_DEFINITIONS "HAVE_PROFILER") + target_link_libraries(mc profiler) + + set_target_properties(lddmc PROPERTIES COMPILE_DEFINITIONS "HAVE_PROFILER") + target_link_libraries(lddmc profiler) +endif() + +if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + # add argp library for OSX + target_link_libraries(mc argp) + target_link_libraries(lddmc argp) +endif() + + diff --git a/examples/getrss.c b/examples/getrss.c new file mode 100644 index 000000000..f3aa5e381 --- /dev/null +++ b/examples/getrss.c @@ -0,0 +1,68 @@ +/* + * Author: David Robert Nadeau + * Site: http://NadeauSoftware.com/ + * License: Creative Commons Attribution 3.0 Unported License + * http://creativecommons.org/licenses/by/3.0/deed.en_US + */ + +/* + * Modified by Tom van Dijk to remove WIN32 and solaris code + */ + +#if defined(__APPLE__) && defined(__MACH__) +#include <unistd.h> +#include <sys/resource.h> +#include <mach/mach.h> +#elif defined(__linux__) || defined(__linux) || defined(linux) || defined(__gnu_linux__) +#include <unistd.h> +#include <sys/resource.h> +#include <stdio.h> +#else +#error "Cannot define getPeakRSS( ) or getCurrentRSS( ) for an unknown OS." +#endif + +/** + * Returns the peak (maximum so far) resident set size (physical + * memory use) measured in bytes, or zero if the value cannot be + * determined on this OS. + */ +size_t +getPeakRSS() +{ + struct rusage rusage; + getrusage(RUSAGE_SELF, &rusage); +#if defined(__APPLE__) && defined(__MACH__) + return (size_t)rusage.ru_maxrss; +#else + return (size_t)(rusage.ru_maxrss * 1024L); +#endif +} + +/** + * Returns the current resident set size (physical memory use) measured + * in bytes, or zero if the value cannot be determined on this OS. + */ +size_t +getCurrentRSS() +{ +#if defined(__APPLE__) && defined(__MACH__) + /* OSX ------------------------------------------------------ */ + struct mach_task_basic_info info; + mach_msg_type_number_t infoCount = MACH_TASK_BASIC_INFO_COUNT; + if (task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)&info, &infoCount) != KERN_SUCCESS) + return (size_t)0L; /* Can't access? */ + return (size_t)info.resident_size; +#else + /* Linux ---------------------------------------------------- */ + long rss = 0L; + FILE *fp = NULL; + if ((fp = fopen("/proc/self/statm", "r")) == NULL) + return (size_t)0L; /* Can't open? */ + if (fscanf(fp, "%*s%ld", &rss) != 1) { + fclose(fp); + return (size_t)0L; /* Can't read? */ + } + fclose(fp); + return (size_t)rss * (size_t)sysconf(_SC_PAGESIZE); +#endif +} diff --git a/examples/getrss.h b/examples/getrss.h new file mode 100644 index 000000000..653e78e76 --- /dev/null +++ b/examples/getrss.h @@ -0,0 +1,26 @@ +#ifndef GETRSS_H +#define GETRSS_H + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** + * Returns the peak (maximum so far) resident set size (physical + * memory use) measured in bytes, or zero if the value cannot be + * determined on this OS. + */ +size_t getPeakRSS(); + +/** + * Returns the current resident set size (physical memory use) measured + * in bytes, or zero if the value cannot be determined on this OS. + */ +size_t getCurrentRSS(); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + + +#endif diff --git a/examples/lddmc.c b/examples/lddmc.c new file mode 100644 index 000000000..9e9c9c008 --- /dev/null +++ b/examples/lddmc.c @@ -0,0 +1,499 @@ +#include <argp.h> +#include <assert.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/time.h> + +#ifdef HAVE_PROFILER +#include <gperftools/profiler.h> +#endif + +#include <getrss.h> +#include <sylvan.h> +#include <llmsset.h> + +/* Configuration */ +static int report_levels = 0; // report states at start of every level +static int report_table = 0; // report table size at end of every level +static int strategy = 1; // set to 1 = use PAR strategy; set to 0 = use BFS strategy +static int check_deadlocks = 0; // set to 1 to check for deadlocks +static int print_transition_matrix = 1; // print transition relation matrix +static int workers = 0; // autodetect +static char* model_filename = NULL; // filename of model +#ifdef HAVE_PROFILER +static char* profile_filename = NULL; // filename for profiling +#endif + +/* argp configuration */ +static struct argp_option options[] = +{ + {"workers", 'w', "<workers>", 0, "Number of workers (default=0: autodetect)", 0}, + {"strategy", 's', "<bfs|par|sat>", 0, "Strategy for reachability (default=par)", 0}, +#ifdef HAVE_PROFILER + {"profiler", 'p', "<filename>", 0, "Filename for profiling", 0}, +#endif + {"deadlocks", 3, 0, 0, "Check for deadlocks", 1}, + {"count-states", 1, 0, 0, "Report #states at each level", 1}, + {"count-table", 2, 0, 0, "Report table usage at each level", 1}, + {0, 0, 0, 0, 0, 0} +}; +static error_t +parse_opt(int key, char *arg, struct argp_state *state) +{ + switch (key) { + case 'w': + workers = atoi(arg); + break; + case 's': + if (strcmp(arg, "bfs")==0) strategy = 0; + else if (strcmp(arg, "par")==0) strategy = 1; + else if (strcmp(arg, "sat")==0) strategy = 2; + else argp_usage(state); + break; + case 3: + check_deadlocks = 1; + break; + case 1: + report_levels = 1; + break; + case 2: + report_table = 1; + break; +#ifdef HAVE_PROFILER + case 'p': + profile_filename = arg; + break; +#endif + case ARGP_KEY_ARG: + if (state->arg_num >= 1) argp_usage(state); + model_filename = arg; + break; + case ARGP_KEY_END: + if (state->arg_num < 1) argp_usage(state); + break; + default: + return ARGP_ERR_UNKNOWN; + } + return 0; +} +static struct argp argp = { options, parse_opt, "<model>", 0, 0, 0, 0 }; + +/* Globals */ +typedef struct set +{ + MDD mdd; + MDD proj; + int size; +} *set_t; + +typedef struct relation +{ + MDD mdd; + MDD meta; + int size; +} *rel_t; + +static size_t vector_size; // size of vector +static int next_count; // number of partitions of the transition relation +static rel_t *next; // each partition of the transition relation + +#define Abort(...) { fprintf(stderr, __VA_ARGS__); exit(-1); } + +/* Load a set from file */ +static set_t +set_load(FILE* f) +{ + lddmc_serialize_fromfile(f); + + size_t mdd; + size_t proj; + int size; + + if (fread(&mdd, sizeof(size_t), 1, f) != 1) Abort("Invalid input file!\n"); + if (fread(&proj, sizeof(size_t), 1, f) != 1) Abort("Invalid input file!\n"); + if (fread(&size, sizeof(int), 1, f) != 1) Abort("Invalid input file!\n"); + + LACE_ME; + + set_t set = (set_t)malloc(sizeof(struct set)); + set->mdd = lddmc_ref(lddmc_serialize_get_reversed(mdd)); + set->proj = lddmc_ref(lddmc_serialize_get_reversed(proj)); + set->size = size; + + return set; +} + +static int +calculate_size(MDD meta) +{ + int result = 0; + uint32_t val = lddmc_getvalue(meta); + while (val != (uint32_t)-1) { + if (val != 0) result += 1; + meta = lddmc_follow(meta, val); + assert(meta != lddmc_true && meta != lddmc_false); + val = lddmc_getvalue(meta); + } + return result; +} + +/* Load a relation from file */ +static rel_t +rel_load(FILE* f) +{ + lddmc_serialize_fromfile(f); + + size_t mdd; + size_t meta; + + if (fread(&mdd, sizeof(size_t), 1, f) != 1) Abort("Invalid input file!\n"); + if (fread(&meta, sizeof(size_t), 1, f) != 1) Abort("Invalid input file!\n"); + + LACE_ME; + + rel_t rel = (rel_t)malloc(sizeof(struct relation)); + rel->mdd = lddmc_ref(lddmc_serialize_get_reversed(mdd)); + rel->meta = lddmc_ref(lddmc_serialize_get_reversed(meta)); + rel->size = calculate_size(rel->meta); + + return rel; +} + +static void +print_example(MDD example) +{ + if (example != lddmc_false) { + LACE_ME; + uint32_t vec[vector_size]; + lddmc_sat_one(example, vec, vector_size); + + size_t i; + printf("["); + for (i=0; i<vector_size; i++) { + if (i>0) printf(","); + printf("%" PRIu32, vec[i]); + } + printf("]"); + } +} + +static void +print_matrix(size_t size, MDD meta) +{ + if (size == 0) return; + uint32_t val = lddmc_getvalue(meta); + if (val == 1) { + printf("+"); + print_matrix(size-1, lddmc_follow(lddmc_follow(meta, 1), 2)); + } else { + if (val == (uint32_t)-1) printf("-"); + else if (val == 0) printf("-"); + else if (val == 3) printf("r"); + else if (val == 4) printf("w"); + print_matrix(size-1, lddmc_follow(meta, val)); + } +} + +static char* +to_h(double size, char *buf) +{ + const char* units[] = {"B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}; + int i = 0; + for (;size>1024;size/=1024) i++; + sprintf(buf, "%.*f %s", i, size, units[i]); + return buf; +} + +static int +get_first(MDD meta) +{ + uint32_t val = lddmc_getvalue(meta); + if (val != 0) return 0; + return 1+get_first(lddmc_follow(meta, val)); +} + +/* Straight-forward implementation of parallel reduction */ +TASK_5(MDD, go_par, MDD, cur, MDD, visited, size_t, from, size_t, len, MDD*, deadlocks) +{ + if (len == 1) { + // Calculate NEW successors (not in visited) + MDD succ = lddmc_ref(lddmc_relprod(cur, next[from]->mdd, next[from]->meta)); + if (deadlocks) { + // check which MDDs in deadlocks do not have a successor in this relation + MDD anc = lddmc_ref(lddmc_relprev(succ, next[from]->mdd, next[from]->meta, cur)); + *deadlocks = lddmc_ref(lddmc_minus(*deadlocks, anc)); + lddmc_deref(anc); + } + MDD result = lddmc_ref(lddmc_minus(succ, visited)); + lddmc_deref(succ); + return result; + } else { + MDD deadlocks_left; + MDD deadlocks_right; + if (deadlocks) { + deadlocks_left = *deadlocks; + deadlocks_right = *deadlocks; + } + + // Recursively calculate left+right + SPAWN(go_par, cur, visited, from, (len+1)/2, deadlocks ? &deadlocks_left: NULL); + MDD right = CALL(go_par, cur, visited, from+(len+1)/2, len/2, deadlocks ? &deadlocks_right : NULL); + MDD left = SYNC(go_par); + + // Merge results of left+right + MDD result = lddmc_ref(lddmc_union(left, right)); + lddmc_deref(left); + lddmc_deref(right); + + if (deadlocks) { + *deadlocks = lddmc_ref(lddmc_intersect(deadlocks_left, deadlocks_right)); + lddmc_deref(deadlocks_left); + lddmc_deref(deadlocks_right); + } + + return result; + } +} + +/* PAR strategy, parallel strategy (operations called in parallel *and* parallelized by Sylvan) */ +VOID_TASK_1(par, set_t, set) +{ + MDD visited = set->mdd; + MDD new = lddmc_ref(visited); + size_t counter = 1; + do { + char buf[32]; + to_h(getCurrentRSS(), buf); + printf("Memory usage: %s\n", buf); + printf("Level %zu... ", counter++); + if (report_levels) { + printf("%zu states... ", (size_t)lddmc_satcount_cached(visited)); + } + fflush(stdout); + + // calculate successors in parallel + MDD cur = new; + MDD deadlocks = cur; + new = CALL(go_par, cur, visited, 0, next_count, check_deadlocks ? &deadlocks : NULL); + lddmc_deref(cur); + + if (check_deadlocks) { + printf("found %zu deadlock states... ", (size_t)lddmc_satcount_cached(deadlocks)); + if (deadlocks != lddmc_false) { + printf("example: "); + print_example(deadlocks); + printf("... "); + check_deadlocks = 0; + } + } + + // visited = visited + new + MDD old_visited = visited; + visited = lddmc_ref(lddmc_union(visited, new)); + lddmc_deref(old_visited); + + if (report_table) { + size_t filled, total; + sylvan_table_usage(&filled, &total); + printf("done, table: %0.1f%% full (%zu nodes).\n", 100.0*(double)filled/total, filled); + } else { + printf("done.\n"); + } + } while (new != lddmc_false); + lddmc_deref(new); + set->mdd = visited; +} + +/* Sequential version of merge-reduction */ +TASK_5(MDD, go_bfs, MDD, cur, MDD, visited, size_t, from, size_t, len, MDD*, deadlocks) +{ + if (len == 1) { + // Calculate NEW successors (not in visited) + MDD succ = lddmc_ref(lddmc_relprod(cur, next[from]->mdd, next[from]->meta)); + if (deadlocks) { + // check which MDDs in deadlocks do not have a successor in this relation + MDD anc = lddmc_ref(lddmc_relprev(succ, next[from]->mdd, next[from]->meta, cur)); + *deadlocks = lddmc_ref(lddmc_minus(*deadlocks, anc)); + lddmc_deref(anc); + } + MDD result = lddmc_ref(lddmc_minus(succ, visited)); + lddmc_deref(succ); + return result; + } else { + MDD deadlocks_left; + MDD deadlocks_right; + if (deadlocks) { + deadlocks_left = *deadlocks; + deadlocks_right = *deadlocks; + } + + // Recursively calculate left+right + MDD left = CALL(go_bfs, cur, visited, from, (len+1)/2, deadlocks ? &deadlocks_left : NULL); + MDD right = CALL(go_bfs, cur, visited, from+(len+1)/2, len/2, deadlocks ? &deadlocks_right : NULL); + + // Merge results of left+right + MDD result = lddmc_ref(lddmc_union(left, right)); + lddmc_deref(left); + lddmc_deref(right); + + if (deadlocks) { + *deadlocks = lddmc_ref(lddmc_intersect(deadlocks_left, deadlocks_right)); + lddmc_deref(deadlocks_left); + lddmc_deref(deadlocks_right); + } + + return result; + } +} + +/* BFS strategy, sequential strategy (but operations are parallelized by Sylvan) */ +VOID_TASK_1(bfs, set_t, set) +{ + MDD visited = set->mdd; + MDD new = lddmc_ref(visited); + size_t counter = 1; + do { + char buf[32]; + to_h(getCurrentRSS(), buf); + printf("Memory usage: %s\n", buf); + printf("Level %zu... ", counter++); + if (report_levels) { + printf("%zu states... ", (size_t)lddmc_satcount_cached(visited)); + } + fflush(stdout); + + MDD cur = new; + MDD deadlocks = cur; + new = CALL(go_bfs, cur, visited, 0, next_count, check_deadlocks ? &deadlocks : NULL); + lddmc_deref(cur); + + if (check_deadlocks) { + printf("found %zu deadlock states... ", (size_t)lddmc_satcount_cached(deadlocks)); + if (deadlocks != lddmc_false) { + printf("example: "); + print_example(deadlocks); + printf("... "); + check_deadlocks = 0; + } + } + + // visited = visited + new + MDD old_visited = visited; + visited = lddmc_ref(lddmc_union(visited, new)); + lddmc_deref(old_visited); + + if (report_table) { + size_t filled, total; + sylvan_table_usage(&filled, &total); + printf("done, table: %0.1f%% full (%zu nodes).\n", 100.0*(double)filled/total, filled); + } else { + printf("done.\n"); + } + } while (new != lddmc_false); + lddmc_deref(new); + set->mdd = visited; +} + +/* Obtain current wallclock time */ +static double +wctime() +{ + struct timeval tv; + gettimeofday(&tv, NULL); + return (tv.tv_sec + 1E-6 * tv.tv_usec); +} + +int +main(int argc, char **argv) +{ + argp_parse(&argp, argc, argv, 0, 0, 0); + + FILE *f = fopen(model_filename, "r"); + if (f == NULL) { + fprintf(stderr, "Cannot open file '%s'!\n", model_filename); + return -1; + } + + // Init Lace + lace_init(workers, 1000000); // auto-detect number of workers, use a 1,000,000 size task queue + lace_startup(0, NULL, NULL); // auto-detect program stack, do not use a callback for startup + + // Init Sylvan LDDmc + // Nodes table size: 24 bytes * 2**N_nodes + // Cache table size: 36 bytes * 2**N_cache + // With: N_nodes=25, N_cache=24: 1.3 GB memory + sylvan_init_package(1LL<<21, 1LL<<27, 1LL<<20, 1LL<<26); + sylvan_init_ldd(); + + // Read and report domain info (integers per vector and bits per integer) + if (fread(&vector_size, sizeof(size_t), 1, f) != 1) Abort("Invalid input file!\n"); + + printf("Vector size: %zu\n", vector_size); + + // Read initial state + printf("Loading initial state... "); + fflush(stdout); + set_t states = set_load(f); + printf("done.\n"); + + // Read transitions + if (fread(&next_count, sizeof(int), 1, f) != 1) Abort("Invalid input file!\n"); + next = (rel_t*)malloc(sizeof(rel_t) * next_count); + + printf("Loading transition relations... "); + fflush(stdout); + int i; + for (i=0; i<next_count; i++) { + next[i] = rel_load(f); + printf("%d, ", i); + fflush(stdout); + } + fclose(f); + printf("done.\n"); + + // Report statistics + printf("Read file '%s'\n", argv[1]); + printf("%zu integers per state, %d transition groups\n", vector_size, next_count); + printf("MDD nodes:\n"); + printf("Initial states: %zu MDD nodes\n", lddmc_nodecount(states->mdd)); + for (i=0; i<next_count; i++) { + printf("Transition %d: %zu MDD nodes\n", i, lddmc_nodecount(next[i]->mdd)); + } + + if (print_transition_matrix) { + for (i=0; i<next_count; i++) { + print_matrix(vector_size, next[i]->meta); + printf(" (%d)\n", get_first(next[i]->meta)); + } + } + + LACE_ME; + +#ifdef HAVE_PROFILER + if (profile_filename != NULL) ProfilerStart(profile_filename); +#endif + if (strategy == 1) { + double t1 = wctime(); + CALL(par, states); + double t2 = wctime(); + printf("PAR Time: %f\n", t2-t1); + } else { + double t1 = wctime(); + CALL(bfs, states); + double t2 = wctime(); + printf("BFS Time: %f\n", t2-t1); + } +#ifdef HAVE_PROFILER + if (profile_filename != NULL) ProfilerStop(); +#endif + + // Now we just have states + printf("Final states: %zu states\n", (size_t)lddmc_satcount_cached(states->mdd)); + printf("Final states: %zu MDD nodes\n", lddmc_nodecount(states->mdd)); + + sylvan_stats_report(stdout, 1); + + return 0; +} diff --git a/examples/mc.c b/examples/mc.c new file mode 100644 index 000000000..2e7938fd9 --- /dev/null +++ b/examples/mc.c @@ -0,0 +1,616 @@ +#include <argp.h> +#include <inttypes.h> +#include <locale.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/time.h> + +#ifdef HAVE_PROFILER +#include <gperftools/profiler.h> +#endif + +#include <sylvan.h> +#include <llmsset.h> + +/* Configuration */ +static int report_levels = 0; // report states at end of every level +static int report_table = 0; // report table size at end of every level +static int report_nodes = 0; // report number of nodes of BDDs +static int strategy = 1; // set to 1 = use PAR strategy; set to 0 = use BFS strategy +static int check_deadlocks = 0; // set to 1 to check for deadlocks +static int merge_relations = 0; // merge relations to 1 relation +static int print_transition_matrix = 0; // print transition relation matrix +static int workers = 0; // autodetect +static char* model_filename = NULL; // filename of model +#ifdef HAVE_PROFILER +static char* profile_filename = NULL; // filename for profiling +#endif + +/* argp configuration */ +static struct argp_option options[] = +{ + {"workers", 'w', "<workers>", 0, "Number of workers (default=0: autodetect)", 0}, + {"strategy", 's', "<bfs|par|sat>", 0, "Strategy for reachability (default=par)", 0}, +#ifdef HAVE_PROFILER + {"profiler", 'p', "<filename>", 0, "Filename for profiling", 0}, +#endif + {"deadlocks", 3, 0, 0, "Check for deadlocks", 1}, + {"count-nodes", 5, 0, 0, "Report #nodes for BDDs", 1}, + {"count-states", 1, 0, 0, "Report #states at each level", 1}, + {"count-table", 2, 0, 0, "Report table usage at each level", 1}, + {"merge-relations", 6, 0, 0, "Merge transition relations into one transition relation", 1}, + {"print-matrix", 4, 0, 0, "Print transition matrix", 1}, + {0, 0, 0, 0, 0, 0} +}; +static error_t +parse_opt(int key, char *arg, struct argp_state *state) +{ + switch (key) { + case 'w': + workers = atoi(arg); + break; + case 's': + if (strcmp(arg, "bfs")==0) strategy = 0; + else if (strcmp(arg, "par")==0) strategy = 1; + else if (strcmp(arg, "sat")==0) strategy = 2; + else argp_usage(state); + break; + case 4: + print_transition_matrix = 1; + break; + case 3: + check_deadlocks = 1; + break; + case 1: + report_levels = 1; + break; + case 2: + report_table = 1; + break; + case 6: + merge_relations = 1; + break; +#ifdef HAVE_PROFILER + case 'p': + profile_filename = arg; + break; +#endif + case ARGP_KEY_ARG: + if (state->arg_num >= 1) argp_usage(state); + model_filename = arg; + break; + case ARGP_KEY_END: + if (state->arg_num < 1) argp_usage(state); + break; + default: + return ARGP_ERR_UNKNOWN; + } + return 0; +} +static struct argp argp = { options, parse_opt, "<model>", 0, 0, 0, 0 }; + +/* Globals */ +typedef struct set +{ + BDD bdd; + BDD variables; // all variables in the set (used by satcount) +} *set_t; + +typedef struct relation +{ + BDD bdd; + BDD variables; // all variables in the relation (used by relprod) +} *rel_t; + +static int vector_size; // size of vector +static int statebits, actionbits; // number of bits for state, number of bits for action +static int bits_per_integer; // number of bits per integer in the vector +static int next_count; // number of partitions of the transition relation +static rel_t *next; // each partition of the transition relation + +/* Obtain current wallclock time */ +static double +wctime() +{ + struct timeval tv; + gettimeofday(&tv, NULL); + return (tv.tv_sec + 1E-6 * tv.tv_usec); +} + +static double t_start; +#define INFO(s, ...) fprintf(stdout, "[% 8.2f] " s, wctime()-t_start, ##__VA_ARGS__) +#define Abort(...) { fprintf(stderr, __VA_ARGS__); exit(-1); } + +/* Load a set from file */ +#define set_load(f) CALL(set_load, f) +TASK_1(set_t, set_load, FILE*, f) +{ + sylvan_serialize_fromfile(f); + + size_t set_bdd, set_vector_size, set_state_vars; + if ((fread(&set_bdd, sizeof(size_t), 1, f) != 1) || + (fread(&set_vector_size, sizeof(size_t), 1, f) != 1) || + (fread(&set_state_vars, sizeof(size_t), 1, f) != 1)) { + Abort("Invalid input file!\n"); + } + + set_t set = (set_t)malloc(sizeof(struct set)); + set->bdd = sylvan_serialize_get_reversed(set_bdd); + set->variables = sylvan_support(sylvan_serialize_get_reversed(set_state_vars)); + + sylvan_protect(&set->bdd); + sylvan_protect(&set->variables); + + return set; +} + +/* Load a relation from file */ +#define rel_load(f) CALL(rel_load, f) +TASK_1(rel_t, rel_load, FILE*, f) +{ + sylvan_serialize_fromfile(f); + + size_t rel_bdd, rel_vars; + if ((fread(&rel_bdd, sizeof(size_t), 1, f) != 1) || + (fread(&rel_vars, sizeof(size_t), 1, f) != 1)) { + Abort("Invalid input file!\n"); + } + + rel_t rel = (rel_t)malloc(sizeof(struct relation)); + rel->bdd = sylvan_serialize_get_reversed(rel_bdd); + rel->variables = sylvan_support(sylvan_serialize_get_reversed(rel_vars)); + + sylvan_protect(&rel->bdd); + sylvan_protect(&rel->variables); + + return rel; +} + +#define print_example(example, variables) CALL(print_example, example, variables) +VOID_TASK_2(print_example, BDD, example, BDDSET, variables) +{ + uint8_t str[vector_size * bits_per_integer]; + + if (example != sylvan_false) { + sylvan_sat_one(example, variables, str); + printf("["); + for (int i=0; i<vector_size; i++) { + uint32_t res = 0; + for (int j=0; j<bits_per_integer; j++) { + if (str[bits_per_integer*i+j] == 1) res++; + res<<=1; + } + if (i>0) printf(","); + printf("%" PRIu32, res); + } + printf("]"); + } +} + +/* Straight-forward implementation of parallel reduction */ +TASK_5(BDD, go_par, BDD, cur, BDD, visited, size_t, from, size_t, len, BDD*, deadlocks) +{ + if (len == 1) { + // Calculate NEW successors (not in visited) + BDD succ = sylvan_relnext(cur, next[from]->bdd, next[from]->variables); + bdd_refs_push(succ); + if (deadlocks) { + // check which BDDs in deadlocks do not have a successor in this relation + BDD anc = sylvan_relprev(next[from]->bdd, succ, next[from]->variables); + bdd_refs_push(anc); + *deadlocks = sylvan_diff(*deadlocks, anc); + bdd_refs_pop(1); + } + BDD result = sylvan_diff(succ, visited); + bdd_refs_pop(1); + return result; + } else { + BDD deadlocks_left; + BDD deadlocks_right; + if (deadlocks) { + deadlocks_left = *deadlocks; + deadlocks_right = *deadlocks; + sylvan_protect(&deadlocks_left); + sylvan_protect(&deadlocks_right); + } + + // Recursively calculate left+right + bdd_refs_spawn(SPAWN(go_par, cur, visited, from, (len+1)/2, deadlocks ? &deadlocks_left: NULL)); + BDD right = bdd_refs_push(CALL(go_par, cur, visited, from+(len+1)/2, len/2, deadlocks ? &deadlocks_right : NULL)); + BDD left = bdd_refs_push(bdd_refs_sync(SYNC(go_par))); + + // Merge results of left+right + BDD result = sylvan_or(left, right); + bdd_refs_pop(2); + + if (deadlocks) { + bdd_refs_push(result); + *deadlocks = sylvan_and(deadlocks_left, deadlocks_right); + sylvan_unprotect(&deadlocks_left); + sylvan_unprotect(&deadlocks_right); + bdd_refs_pop(1); + } + + return result; + } +} + +/* PAR strategy, parallel strategy (operations called in parallel *and* parallelized by Sylvan) */ +VOID_TASK_1(par, set_t, set) +{ + BDD visited = set->bdd; + BDD next_level = visited; + BDD cur_level = sylvan_false; + BDD deadlocks = sylvan_false; + + sylvan_protect(&visited); + sylvan_protect(&next_level); + sylvan_protect(&cur_level); + sylvan_protect(&deadlocks); + + int iteration = 1; + do { + // calculate successors in parallel + cur_level = next_level; + deadlocks = cur_level; + + next_level = CALL(go_par, cur_level, visited, 0, next_count, check_deadlocks ? &deadlocks : NULL); + + if (check_deadlocks && deadlocks != sylvan_false) { + INFO("Found %'0.0f deadlock states... ", sylvan_satcount(deadlocks, set->variables)); + if (deadlocks != sylvan_false) { + printf("example: "); + print_example(deadlocks, set->variables); + check_deadlocks = 0; + } + printf("\n"); + } + + // visited = visited + new + visited = sylvan_or(visited, next_level); + + if (report_table && report_levels) { + size_t filled, total; + sylvan_table_usage(&filled, &total); + INFO("Level %d done, %'0.0f states explored, table: %0.1f%% full (%'zu nodes)\n", + iteration, sylvan_satcount(visited, set->variables), + 100.0*(double)filled/total, filled); + } else if (report_table) { + size_t filled, total; + sylvan_table_usage(&filled, &total); + INFO("Level %d done, table: %0.1f%% full (%'zu nodes)\n", + iteration, + 100.0*(double)filled/total, filled); + } else if (report_levels) { + INFO("Level %d done, %'0.0f states explored\n", iteration, sylvan_satcount(visited, set->variables)); + } else { + INFO("Level %d done\n", iteration); + } + iteration++; + } while (next_level != sylvan_false); + + set->bdd = visited; + + sylvan_unprotect(&visited); + sylvan_unprotect(&next_level); + sylvan_unprotect(&cur_level); + sylvan_unprotect(&deadlocks); +} + +/* Sequential version of merge-reduction */ +TASK_5(BDD, go_bfs, BDD, cur, BDD, visited, size_t, from, size_t, len, BDD*, deadlocks) +{ + if (len == 1) { + // Calculate NEW successors (not in visited) + BDD succ = sylvan_relnext(cur, next[from]->bdd, next[from]->variables); + bdd_refs_push(succ); + if (deadlocks) { + // check which BDDs in deadlocks do not have a successor in this relation + BDD anc = sylvan_relprev(next[from]->bdd, succ, next[from]->variables); + bdd_refs_push(anc); + *deadlocks = sylvan_diff(*deadlocks, anc); + bdd_refs_pop(1); + } + BDD result = sylvan_diff(succ, visited); + bdd_refs_pop(1); + return result; + } else { + BDD deadlocks_left; + BDD deadlocks_right; + if (deadlocks) { + deadlocks_left = *deadlocks; + deadlocks_right = *deadlocks; + sylvan_protect(&deadlocks_left); + sylvan_protect(&deadlocks_right); + } + + // Recursively calculate left+right + BDD left = CALL(go_bfs, cur, visited, from, (len+1)/2, deadlocks ? &deadlocks_left : NULL); + bdd_refs_push(left); + BDD right = CALL(go_bfs, cur, visited, from+(len+1)/2, len/2, deadlocks ? &deadlocks_right : NULL); + bdd_refs_push(right); + + // Merge results of left+right + BDD result = sylvan_or(left, right); + bdd_refs_pop(2); + + if (deadlocks) { + bdd_refs_push(result); + *deadlocks = sylvan_and(deadlocks_left, deadlocks_right); + sylvan_unprotect(&deadlocks_left); + sylvan_unprotect(&deadlocks_right); + bdd_refs_pop(1); + } + + return result; + } +} + +/* BFS strategy, sequential strategy (but operations are parallelized by Sylvan) */ +VOID_TASK_1(bfs, set_t, set) +{ + BDD visited = set->bdd; + BDD next_level = visited; + BDD cur_level = sylvan_false; + BDD deadlocks = sylvan_false; + + sylvan_protect(&visited); + sylvan_protect(&next_level); + sylvan_protect(&cur_level); + sylvan_protect(&deadlocks); + + int iteration = 1; + do { + // calculate successors in parallel + cur_level = next_level; + deadlocks = cur_level; + + next_level = CALL(go_bfs, cur_level, visited, 0, next_count, check_deadlocks ? &deadlocks : NULL); + + if (check_deadlocks && deadlocks != sylvan_false) { + INFO("Found %'0.0f deadlock states... ", sylvan_satcount(deadlocks, set->variables)); + if (deadlocks != sylvan_false) { + printf("example: "); + print_example(deadlocks, set->variables); + check_deadlocks = 0; + } + printf("\n"); + } + + // visited = visited + new + visited = sylvan_or(visited, next_level); + + if (report_table && report_levels) { + size_t filled, total; + sylvan_table_usage(&filled, &total); + INFO("Level %d done, %'0.0f states explored, table: %0.1f%% full (%'zu nodes)\n", + iteration, sylvan_satcount(visited, set->variables), + 100.0*(double)filled/total, filled); + } else if (report_table) { + size_t filled, total; + sylvan_table_usage(&filled, &total); + INFO("Level %d done, table: %0.1f%% full (%'zu nodes)\n", + iteration, + 100.0*(double)filled/total, filled); + } else if (report_levels) { + INFO("Level %d done, %'0.0f states explored\n", iteration, sylvan_satcount(visited, set->variables)); + } else { + INFO("Level %d done\n", iteration); + } + iteration++; + } while (next_level != sylvan_false); + + set->bdd = visited; + + sylvan_unprotect(&visited); + sylvan_unprotect(&next_level); + sylvan_unprotect(&cur_level); + sylvan_unprotect(&deadlocks); +} + +/** + * Extend a transition relation to a larger domain (using s=s') + */ +#define extend_relation(rel, vars) CALL(extend_relation, rel, vars) +TASK_2(BDD, extend_relation, BDD, relation, BDDSET, variables) +{ + /* first determine which state BDD variables are in rel */ + int has[statebits]; + for (int i=0; i<statebits; i++) has[i] = 0; + BDDSET s = variables; + while (!sylvan_set_isempty(s)) { + BDDVAR v = sylvan_set_var(s); + if (v/2 >= (unsigned)statebits) break; // action labels + has[v/2] = 1; + s = sylvan_set_next(s); + } + + /* create "s=s'" for all variables not in rel */ + BDD eq = sylvan_true; + for (int i=statebits-1; i>=0; i--) { + if (has[i]) continue; + BDD low = sylvan_makenode(2*i+1, eq, sylvan_false); + bdd_refs_push(low); + BDD high = sylvan_makenode(2*i+1, sylvan_false, eq); + bdd_refs_pop(1); + eq = sylvan_makenode(2*i, low, high); + } + + bdd_refs_push(eq); + BDD result = sylvan_and(relation, eq); + bdd_refs_pop(1); + + return result; +} + +/** + * Compute \BigUnion ( sets[i] ) + */ +#define big_union(first, count) CALL(big_union, first, count) +TASK_2(BDD, big_union, int, first, int, count) +{ + if (count == 1) return next[first]->bdd; + + bdd_refs_spawn(SPAWN(big_union, first, count/2)); + BDD right = bdd_refs_push(CALL(big_union, first+count/2, count-count/2)); + BDD left = bdd_refs_push(bdd_refs_sync(SYNC(big_union))); + BDD result = sylvan_or(left, right); + bdd_refs_pop(2); + return result; +} + +static void +print_matrix(BDD vars) +{ + for (int i=0; i<vector_size; i++) { + if (sylvan_set_isempty(vars)) { + fprintf(stdout, "-"); + } else { + BDDVAR next_s = 2*((i+1)*bits_per_integer); + if (sylvan_set_var(vars) < next_s) { + fprintf(stdout, "+"); + for (;;) { + vars = sylvan_set_next(vars); + if (sylvan_set_isempty(vars)) break; + if (sylvan_set_var(vars) >= next_s) break; + } + } else { + fprintf(stdout, "-"); + } + } + } +} + +VOID_TASK_0(gc_start) +{ + INFO("(GC) Starting garbage collection...\n"); +} + +VOID_TASK_0(gc_end) +{ + INFO("(GC) Garbage collection done.\n"); +} + +int +main(int argc, char **argv) +{ + argp_parse(&argp, argc, argv, 0, 0, 0); + setlocale(LC_NUMERIC, "en_US.utf-8"); + t_start = wctime(); + + FILE *f = fopen(model_filename, "r"); + if (f == NULL) { + fprintf(stderr, "Cannot open file '%s'!\n", model_filename); + return -1; + } + + // Init Lace + lace_init(workers, 1000000); // auto-detect number of workers, use a 1,000,000 size task queue + lace_startup(0, NULL, NULL); // auto-detect program stack, do not use a callback for startup + + LACE_ME; + + // Init Sylvan + // Nodes table size: 24 bytes * 2**N_nodes + // Cache table size: 36 bytes * 2**N_cache + // With: N_nodes=25, N_cache=24: 1.3 GB memory + sylvan_init_package(1LL<<21, 1LL<<27, 1LL<<20, 1LL<<26); + sylvan_init_bdd(6); // granularity 6 is decent default value - 1 means "use cache for every operation" + sylvan_gc_add_mark(0, TASK(gc_start)); + sylvan_gc_add_mark(40, TASK(gc_end)); + + /* Load domain information */ + if ((fread(&vector_size, sizeof(int), 1, f) != 1) || + (fread(&statebits, sizeof(int), 1, f) != 1) || + (fread(&actionbits, sizeof(int), 1, f) != 1)) { + Abort("Invalid input file!\n"); + } + + bits_per_integer = statebits; + statebits *= vector_size; + + // Read initial state + set_t states = set_load(f); + + // Read transitions + if (fread(&next_count, sizeof(int), 1, f) != 1) Abort("Invalid input file!\n"); + next = (rel_t*)malloc(sizeof(rel_t) * next_count); + + int i; + for (i=0; i<next_count; i++) { + next[i] = rel_load(f); + } + + /* Done */ + fclose(f); + + if (print_transition_matrix) { + for (i=0; i<next_count; i++) { + INFO(""); + print_matrix(next[i]->variables); + fprintf(stdout, "\n"); + } + } + + // Report statistics + INFO("Read file '%s'\n", model_filename); + INFO("%d integers per state, %d bits per integer, %d transition groups\n", vector_size, bits_per_integer, next_count); + + if (merge_relations) { + BDD prime_variables = sylvan_set_empty(); + for (int i=statebits-1; i>=0; i--) { + bdd_refs_push(prime_variables); + prime_variables = sylvan_set_add(prime_variables, i*2+1); + bdd_refs_pop(1); + } + + bdd_refs_push(prime_variables); + + INFO("Extending transition relations to full domain.\n"); + for (int i=0; i<next_count; i++) { + next[i]->bdd = extend_relation(next[i]->bdd, next[i]->variables); + next[i]->variables = prime_variables; + } + + INFO("Taking union of all transition relations.\n"); + next[0]->bdd = big_union(0, next_count); + next_count = 1; + } + + if (report_nodes) { + INFO("BDD nodes:\n"); + INFO("Initial states: %zu BDD nodes\n", sylvan_nodecount(states->bdd)); + for (i=0; i<next_count; i++) { + INFO("Transition %d: %zu BDD nodes\n", i, sylvan_nodecount(next[i]->bdd)); + } + } + +#ifdef HAVE_PROFILER + if (profile_filename != NULL) ProfilerStart(profile_filename); +#endif + if (strategy == 1) { + double t1 = wctime(); + CALL(par, states); + double t2 = wctime(); + INFO("PAR Time: %f\n", t2-t1); + } else { + double t1 = wctime(); + CALL(bfs, states); + double t2 = wctime(); + INFO("BFS Time: %f\n", t2-t1); + } +#ifdef HAVE_PROFILER + if (profile_filename != NULL) ProfilerStop(); +#endif + + // Now we just have states + INFO("Final states: %'0.0f states\n", sylvan_satcount(states->bdd, states->variables)); + if (report_nodes) { + INFO("Final states: %'zu BDD nodes\n", sylvan_nodecount(states->bdd)); + } + + sylvan_stats_report(stdout, 1); + + return 0; +} diff --git a/examples/simple.cpp b/examples/simple.cpp new file mode 100644 index 000000000..22bd9eb8b --- /dev/null +++ b/examples/simple.cpp @@ -0,0 +1,121 @@ +#include <assert.h> +#include <stdio.h> +#include <stdint.h> + +#include <sylvan.h> +#include <sylvan_obj.hpp> + +using namespace sylvan; + +VOID_TASK_0(simple_cxx) +{ + Bdd one = Bdd::bddOne(); // the True terminal + Bdd zero = Bdd::bddZero(); // the False terminal + + // check if they really are the True/False terminal + assert(one.GetBDD() == sylvan_true); + assert(zero.GetBDD() == sylvan_false); + + Bdd a = Bdd::bddVar(0); // create a BDD variable x_0 + Bdd b = Bdd::bddVar(1); // create a BDD variable x_1 + + // check if a really is the Boolean formula "x_0" + assert(!a.isConstant()); + assert(a.TopVar() == 0); + assert(a.Then() == one); + assert(a.Else() == zero); + + // check if b really is the Boolean formula "x_1" + assert(!b.isConstant()); + assert(b.TopVar() == 1); + assert(b.Then() == one); + assert(b.Else() == zero); + + // compute !a + Bdd not_a = !a; + + // check if !!a is really a + assert((!not_a) == a); + + // compute a * b and !(!a + !b) and check if they are equivalent + Bdd a_and_b = a * b; + Bdd not_not_a_or_not_b = !(!a + !b); + assert(a_and_b == not_not_a_or_not_b); + + // perform some simple quantification and check the results + Bdd ex = a_and_b.ExistAbstract(a); // \exists a . a * b + assert(ex == b); + Bdd andabs = a.AndAbstract(b, a); // \exists a . a * b using AndAbstract + assert(ex == andabs); + Bdd univ = a_and_b.UnivAbstract(a); // \forall a . a * b + assert(univ == zero); + + // alternative method to get the cube "ab" using bddCube + BddSet variables = a * b; + std::vector<unsigned char> vec = {1, 1}; + assert(a_and_b == Bdd::bddCube(variables, vec)); + + // test the bddCube method for all combinations + assert((!a * !b) == Bdd::bddCube(variables, std::vector<uint8_t>({0, 0}))); + assert((!a * b) == Bdd::bddCube(variables, std::vector<uint8_t>({0, 1}))); + assert((!a) == Bdd::bddCube(variables, std::vector<uint8_t>({0, 2}))); + assert((a * !b) == Bdd::bddCube(variables, std::vector<uint8_t>({1, 0}))); + assert((a * b) == Bdd::bddCube(variables, std::vector<uint8_t>({1, 1}))); + assert((a) == Bdd::bddCube(variables, std::vector<uint8_t>({1, 2}))); + assert((!b) == Bdd::bddCube(variables, std::vector<uint8_t>({2, 0}))); + assert((b) == Bdd::bddCube(variables, std::vector<uint8_t>({2, 1}))); + assert(one == Bdd::bddCube(variables, std::vector<uint8_t>({2, 2}))); +} + +VOID_TASK_1(_main, void*, arg) +{ + // Initialize Sylvan + // With starting size of the nodes table 1 << 21, and maximum size 1 << 27. + // With starting size of the cache table 1 << 20, and maximum size 1 << 20. + // Memory usage: 24 bytes per node, and 36 bytes per cache bucket + // - 1<<24 nodes: 384 MB + // - 1<<25 nodes: 768 MB + // - 1<<26 nodes: 1536 MB + // - 1<<27 nodes: 3072 MB + // - 1<<24 cache: 576 MB + // - 1<<25 cache: 1152 MB + // - 1<<26 cache: 2304 MB + // - 1<<27 cache: 4608 MB + sylvan_init_package(1LL<<22, 1LL<<26, 1LL<<22, 1LL<<26); + + // Initialize the BDD module with granularity 1 (cache every operation) + // A higher granularity (e.g. 6) often results in better performance in practice + sylvan_init_bdd(1); + + // Now we can do some simple stuff using the C++ objects. + CALL(simple_cxx); + + // Report statistics (if SYLVAN_STATS is 1 in the configuration) + sylvan_stats_report(stdout, 1); + + // And quit, freeing memory + sylvan_quit(); + + // We didn't use arg + (void)arg; +} + +int +main (int argc, char *argv[]) +{ + int n_workers = 0; // automatically detect number of workers + size_t deque_size = 0; // default value for the size of task deques for the workers + size_t program_stack_size = 0; // default value for the program stack of each pthread + + // Initialize the Lace framework for <n_workers> workers. + lace_init(n_workers, deque_size); + + // Spawn and start all worker pthreads; suspends current thread until done. + lace_startup(program_stack_size, TASK(_main), NULL); + + // The lace_startup command also exits Lace after _main is completed. + + return 0; + (void)argc; // unused variable + (void)argv; // unused variable +} diff --git a/m4/.gitignore b/m4/.gitignore new file mode 100644 index 000000000..5590b8bc5 --- /dev/null +++ b/m4/.gitignore @@ -0,0 +1,5 @@ +# Ignore everything in this directory +* +# Except: +!.gitignore +!m4_ax_check_compile_flag.m4 diff --git a/m4/m4_ax_check_compile_flag.m4 b/m4/m4_ax_check_compile_flag.m4 new file mode 100644 index 000000000..c3a8d695a --- /dev/null +++ b/m4/m4_ax_check_compile_flag.m4 @@ -0,0 +1,72 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS]) +# +# DESCRIPTION +# +# Check whether the given FLAG works with the current language's compiler +# or gives an error. (Warnings, however, are ignored) +# +# ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on +# success/failure. +# +# If EXTRA-FLAGS is defined, it is added to the current language's default +# flags (e.g. CFLAGS) when the check is done. The check is thus made with +# the flags: "CFLAGS EXTRA-FLAGS FLAG". This can for example be used to +# force the compiler to issue an error when a bad flag is given. +# +# NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this +# macro in sync with AX_CHECK_{PREPROC,LINK}_FLAG. +# +# LICENSE +# +# Copyright (c) 2008 Guido U. Draheim <guidod@gmx.de> +# Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com> +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see <http://www.gnu.org/licenses/>. +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 2 + +AC_DEFUN([AX_CHECK_COMPILE_FLAG], +[AC_PREREQ(2.59)dnl for _AC_LANG_PREFIX +AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl +AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [ + ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS + _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1" + AC_COMPILE_IFELSE([AC_LANG_PROGRAM()], + [AS_VAR_SET(CACHEVAR,[yes])], + [AS_VAR_SET(CACHEVAR,[no])]) + _AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags]) +AS_IF([test x"AS_VAR_GET(CACHEVAR)" = xyes], + [m4_default([$2], :)], + [m4_default([$3], :)]) +AS_VAR_POPDEF([CACHEVAR])dnl +])dnl AX_CHECK_COMPILE_FLAGS diff --git a/models/at.5.8-rgs.bdd b/models/at.5.8-rgs.bdd new file mode 100644 index 0000000000000000000000000000000000000000..8a0c1950069e5977d02e225f5bfb06cd7902c26a GIT binary patch literal 50448 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 literal 0 HcmV?d00001 diff --git a/models/at.6.8-rgs.bdd b/models/at.6.8-rgs.bdd new file mode 100644 index 0000000000000000000000000000000000000000..71ef84a77c48033d3a1dd2db70756bf85eea2a2f GIT binary patch literal 50128 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 TL2LXq>7M%hM)~t<mF@orL*Zb~ literal 0 HcmV?d00001 diff --git a/models/at.7.8-rgs.bdd b/models/at.7.8-rgs.bdd new file mode 100644 index 0000000000000000000000000000000000000000..c8c29628e1883d36ecd6c5b9bea3ba4cae2cd11a GIT binary patch literal 59144 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 RT#fxNp~n8-;(fR8{|B5wRvG{R literal 0 HcmV?d00001 diff --git a/models/blocks.2.ldd b/models/blocks.2.ldd new file mode 100644 index 0000000000000000000000000000000000000000..1379ed1a39a5cbf1201f57a8f425df19c4985f6f GIT binary patch literal 14616 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 literal 0 HcmV?d00001 diff --git a/models/blocks.4.ldd b/models/blocks.4.ldd new file mode 100644 index 0000000000000000000000000000000000000000..7270052c42bf0ad30e626cc8569d25413767723b GIT binary patch literal 41000 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 literal 0 HcmV?d00001 diff --git a/models/collision.4.9-rgs.bdd b/models/collision.4.9-rgs.bdd new file mode 100644 index 0000000000000000000000000000000000000000..6db7602d1afa9bb19d43546a0cceb40458e095cb GIT binary patch literal 73696 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 literal 0 HcmV?d00001 diff --git a/models/collision.5.9-rgs.bdd b/models/collision.5.9-rgs.bdd new file mode 100644 index 0000000000000000000000000000000000000000..7c32c293ed278544cf3ace3a8584d05f3f2e6ff7 GIT binary patch literal 73760 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 literal 0 HcmV?d00001 diff --git a/models/schedule_world.2.8-rgs.bdd b/models/schedule_world.2.8-rgs.bdd new file mode 100644 index 0000000000000000000000000000000000000000..7c5354aef1d1c178c65ea8258707736f924766d7 GIT binary patch literal 73472 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 BL5ctX literal 0 HcmV?d00001 diff --git a/models/schedule_world.3.8-rgs.bdd b/models/schedule_world.3.8-rgs.bdd new file mode 100644 index 0000000000000000000000000000000000000000..a4e3e444d0d0d25ba351a6868a75f94e5c72dc1c GIT binary patch literal 97456 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 X{ts}||AB7$KggYpM{34{GuQtD2_-n= literal 0 HcmV?d00001 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 000000000..50a645f22 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,81 @@ +cmake_minimum_required(VERSION 2.6) +project(sylvan C CXX) + +add_library(sylvan + avl.h + lace.h + lace.c + llmsset.c + llmsset.h + refs.h + refs.c + sha2.h + sha2.c + stats.h + stats.c + sylvan.h + sylvan_bdd.h + sylvan_bdd.c + sylvan_cache.h + sylvan_cache.c + sylvan_config.h + sylvan_common.h + sylvan_common.c + sylvan_gmp.h + sylvan_gmp.c + sylvan_ldd.h + sylvan_ldd.c + sylvan_mtbdd.h + sylvan_mtbdd.c + sylvan_mtbdd_int.h + sylvan_obj.hpp + sylvan_obj.cpp + tls.h +) + +# We need to make sure that the binary is put into a folder that is independent of the +# build type. Otherwise -- for example when using Xcode -- the binary might end up in a +# sub-folder "Debug" or "Release". +set_target_properties(sylvan PROPERTIES + ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${CMAKE_CURRENT_BINARY_DIR} + ARCHIVE_OUTPUT_DIRECTORY_RELEASE ${CMAKE_CURRENT_BINARY_DIR}) + +target_link_libraries(sylvan m pthread) + +if(UNIX AND NOT APPLE) + target_link_libraries(sylvan rt) +endif() + +option(USE_HWLOC "Use HWLOC library if available" ON) + +if(USE_HWLOC) + include(CheckIncludeFiles) + check_include_files(hwloc.h HAVE_HWLOC) + if(HAVE_HWLOC) + set_target_properties(sylvan PROPERTIES COMPILE_DEFINITIONS "USE_HWLOC=1") + target_link_libraries(sylvan hwloc) + endif() +endif() + +option(SYLVAN_STATS "Collect statistics" OFF) +if(SYLVAN_STATS) + set_target_properties(sylvan PROPERTIES COMPILE_DEFINITIONS "SYLVAN_STATS") +endif() + +install(TARGETS + sylvan + DESTINATION "lib") + +install(FILES + lace.h + llmsset.h + sylvan.h + sylvan_cache.h + sylvan_common.h + sylvan_config.h + sylvan_bdd.h + sylvan_ldd.h + sylvan_mtbdd.h + sylvan_obj.hpp + tls.h + DESTINATION "include") diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 000000000..5894c000f --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,39 @@ +lib_LTLIBRARIES = libsylvan.la + +libsylvan_la_CFLAGS = $(AM_CFLAGS) -fno-strict-aliasing -std=gnu11 + +libsylvan_la_SOURCES = \ + avl.h \ + lace.c \ + lace.h \ + llmsset.c \ + llmsset.h \ + refs.h \ + refs.c \ + sha2.c \ + sha2.h \ + stats.h \ + stats.c \ + sylvan.h \ + sylvan_config.h \ + sylvan_bdd.h \ + sylvan_bdd.c \ + sylvan_ldd.h \ + sylvan_ldd.c \ + sylvan_cache.h \ + sylvan_cache.c \ + sylvan_common.c \ + sylvan_common.h \ + sylvan_mtbdd.h \ + sylvan_mtbdd.c \ + sylvan_mtbdd_int.h \ + sylvan_obj.hpp \ + sylvan_obj.cpp \ + tls.h + +libsylvan_la_LIBADD = -lm + +if HAVE_LIBHWLOC +libsylvan_la_LIBADD += -lhwloc +libsylvan_la_CFLAGS += -DUSE_HWLOC=1 +endif diff --git a/src/avl.h b/src/avl.h new file mode 100644 index 000000000..68b4ea220 --- /dev/null +++ b/src/avl.h @@ -0,0 +1,398 @@ +/* + * Copyright 2011-2014 Formal Methods and Tools, University of Twente + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Self-balancing binary tree implemented using C macros + * + * See also: http://en.wikipedia.org/wiki/AVL_tree + * Data structure originally by Adelson-Velskii, Landis, 1962. + * + * Usage of this AVL implementation: + * + * AVL(some_name, some_type) + * { + * Compare some_type *left with some_type *right + * Return <0 when left<right, >0 when left>right, 0 when left=right + * } + * + * You get: + * - some_type *some_name_put(avl_node_t **root_node, some_type *data, int *inserted); + * Either insert new or retrieve existing key, <inserted> if non-NULL receives 0 or 1. + * - int some_name_insert(avl_node_t **root_node, some_type *data); + * Try to insert, return 1 if succesful, 0 if existed. + * - int some_name_delete(avl_node_t **root_node, some_type *data); + * Try to delete, return 1 if deleted, 0 if no match. + * - some_type *some_name_search(avl_node_t *root_node, some_type *data); + * Retrieve existing data, returns NULL if unsuccesful + * - void some_name_free(avl_node_t **root_node); + * Free all memory used by the AVL tree + * - some_type *some_name_toarray(avl_node_t *root_node); + * Malloc an array and put the sorted data in it... + * - size_t avl_count(avl_node_t *root_node); + * Returns the number of items in the tree + * + * For example: + * struct my_struct { ... }; + * AVL(some_name, struct my_struct) + * { + * Compare struct my_struct *left with struct my_struct *right + * Return <0 when left<right, >0 when left>right, 0 when left=right + * } + * + * avl_node_t *the_root = NULL; + * struct mystuff; + * if (!some_name_search(the_root, &mystuff)) some_name_insert(&the_root, &mystuff); + * some_name_free(&the_root); + * + * For questions, feedback, etc: t.vandijk@utwente.nl + */ + +#include <stdlib.h> +#include <string.h> + +#ifndef __AVL_H__ +#define __AVL_H__ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef struct avl_node +{ + struct avl_node *left, *right; + unsigned int height; + char pad[8-sizeof(unsigned int)]; + char data[0]; +} avl_node_t; + +/* Retrieve the height of a tree */ +static inline int +avl_get_height(avl_node_t *node) +{ + return node == NULL ? 0 : node->height; +} + +/* Helper for rotations to update the heights of trees */ +static inline void +avl_update_height(avl_node_t *node) +{ + int h1 = avl_get_height(node->left); + int h2 = avl_get_height(node->right); + node->height = 1 + (h1 > h2 ? h1 : h2); +} + +/* Helper for avl_balance_tree */ +static inline int +avl_update_height_get_balance(avl_node_t *node) +{ + int h1 = avl_get_height(node->left); + int h2 = avl_get_height(node->right); + node->height = 1 + (h1 > h2 ? h1 : h2); + return h1 - h2; +} + +/* Helper for avl_check_consistent */ +static inline int +avl_verify_height(avl_node_t *node) +{ + int h1 = avl_get_height(node->left); + int h2 = avl_get_height(node->right); + int expected_height = 1 + (h1 > h2 ? h1 : h2); + return expected_height == avl_get_height(node); +} + +/* Optional consistency check */ +static inline int __attribute__((unused)) +avl_check_consistent(avl_node_t *root) +{ + if (root == NULL) return 1; + if (!avl_check_consistent(root->left)) return 0; + if (!avl_check_consistent(root->right)) return 0; + if (!avl_verify_height(root)) return 0; + return 1; +} + +/* Perform LL rotation, returns the new root */ +static avl_node_t* +avl_rotate_LL(avl_node_t *parent) +{ + avl_node_t *child = parent->left; + parent->left = child->right; + child->right = parent; + avl_update_height(parent); + avl_update_height(child); + return child; +} + +/* Perform RR rotation, returns the new root */ +static avl_node_t* +avl_rotate_RR(avl_node_t *parent) +{ + avl_node_t *child = parent->right; + parent->right = child->left; + child->left = parent; + avl_update_height(parent); + avl_update_height(child); + return child; +} + +/* Perform RL rotation, returns the new root */ +static avl_node_t* +avl_rotate_RL(avl_node_t *parent) +{ + avl_node_t *child = parent->right; + parent->right = avl_rotate_LL(child); + return avl_rotate_RR(parent); +} + +/* Perform LR rotation, returns the new root */ +static avl_node_t* +avl_rotate_LR(avl_node_t *parent) +{ + avl_node_t *child = parent->left; + parent->left = avl_rotate_RR(child); + return avl_rotate_LL(parent); +} + +/* Calculate balance factor */ +static inline int +avl_get_balance(avl_node_t *node) +{ + if (node == NULL) return 0; + return avl_get_height(node->left) - avl_get_height(node->right); +} + +/* Balance the tree */ +static void +avl_balance_tree(avl_node_t **node) +{ + int factor = avl_update_height_get_balance(*node); + + if (factor > 1) { + if (avl_get_balance((*node)->left) > 0) *node = avl_rotate_LL(*node); + else *node = avl_rotate_LR(*node); + } else if (factor < -1) { + if (avl_get_balance((*node)->right) < 0) *node = avl_rotate_RR(*node); + else *node = avl_rotate_RL(*node); + } +} + +/* Get number of items in the AVL */ +static size_t +avl_count(avl_node_t *node) +{ + if (node == NULL) return 0; + return 1 + avl_count(node->left) + avl_count(node->right); +} + +/* Structure for iterator */ +typedef struct avl_iter +{ + size_t height; + avl_node_t *nodes[0]; +} avl_iter_t; + +/** + * nodes[0] = root node + * nodes[1] = some node + * nodes[2] = some node + * nodes[3] = leaf node (height = 4) + * nodes[4] = NULL (max = height + 1) + */ + +/* Create a new iterator */ +static inline avl_iter_t* +avl_iter(avl_node_t *node) +{ + size_t max = node ? node->height+1 : 1; + avl_iter_t *result = (avl_iter_t*)malloc(sizeof(avl_iter_t) + sizeof(avl_node_t*) * max); + result->height = 0; + result->nodes[0] = node; + return result; +} + +/* Get the next node during iteration */ +static inline avl_node_t* +avl_iter_next(avl_iter_t *iter) +{ + /* when first node is NULL, we're done */ + if (iter->nodes[0] == NULL) return NULL; + + /* if the head is not NULL, first entry... */ + while (iter->nodes[iter->height] != NULL) { + iter->nodes[iter->height+1] = iter->nodes[iter->height]->left; + iter->height++; + } + + /* head is now NULL, take parent as result */ + avl_node_t *result = iter->nodes[iter->height-1]; + + if (result->right != NULL) { + /* if we can go right, do that */ + iter->nodes[iter->height] = result->right; + } else { + /* cannot go right, backtrack */ + do { + iter->height--; + } while (iter->height > 0 && iter->nodes[iter->height] == iter->nodes[iter->height-1]->right); + iter->nodes[iter->height] = NULL; /* set head to NULL: second entry */ + } + + return result; +} + +#define AVL(NAME, TYPE) \ +static inline int \ +NAME##_AVL_compare(TYPE *left, TYPE *right); \ +static __attribute__((unused)) TYPE* \ +NAME##_put(avl_node_t **root, TYPE *data, int *inserted) \ +{ \ + if (inserted && *inserted) *inserted = 0; /* reset inserted once */ \ + TYPE *result; \ + avl_node_t *it = *root; \ + if (it == NULL) { \ + *root = it = (avl_node_t*)malloc(sizeof(struct avl_node)+sizeof(TYPE)); \ + it->left = it->right = NULL; \ + it->height = 1; \ + memcpy(it->data, data, sizeof(TYPE)); \ + result = (TYPE *)it->data; \ + if (inserted) *inserted = 1; \ + } else { \ + int cmp = NAME##_AVL_compare(data, (TYPE*)(it->data)); \ + if (cmp == 0) return (TYPE *)it->data; \ + if (cmp < 0) result = NAME##_put(&it->left, data, inserted); \ + else result = NAME##_put(&it->right, data, inserted); \ + avl_balance_tree(root); \ + } \ + return result; \ +} \ +static __attribute__((unused)) int \ +NAME##_insert(avl_node_t **root, TYPE *data) \ +{ \ + int inserted; \ + NAME##_put(root, data, &inserted); \ + return inserted; \ +} \ +static void \ +NAME##_exchange_and_balance(avl_node_t *target, avl_node_t **node) \ +{ \ + avl_node_t *it = *node; \ + if (it->left == 0) { /* leftmost node contains lowest value */ \ + memcpy(target->data, it->data, sizeof(TYPE)); \ + *node = it->right; \ + free(it); \ + } else { \ + NAME##_exchange_and_balance(target, &it->left); \ + } \ + avl_balance_tree(node); \ +} \ +static __attribute__((unused)) int \ +NAME##_delete(avl_node_t **node, TYPE *data) \ +{ \ + avl_node_t *it = *node; \ + if (it == NULL) return 0; \ + int cmp = NAME##_AVL_compare(data, (TYPE *)((*node)->data)), res; \ + if (cmp < 0) res = NAME##_delete(&it->left, data); \ + else if (cmp > 0) res = NAME##_delete(&it->right, data); \ + else { \ + int h_left = avl_get_height(it->left); \ + int h_right = avl_get_height(it->right); \ + if (h_left == 0) { \ + if (h_right == 0) { /* Leaf */ \ + *node = NULL; \ + free(it); \ + return 1; \ + } else { /* Only right child */ \ + *node = it->right; \ + free(it); \ + return 1; \ + } \ + } else if (h_right == 0) { /* Only left child */ \ + *node = it->left; \ + free(it); \ + return 1; \ + } else { /* Exchange with successor */ \ + NAME##_exchange_and_balance(it, &it->right); \ + res = 1; \ + } \ + } \ + if (res) avl_balance_tree(node); \ + return res; \ +} \ +static __attribute__((unused)) TYPE* \ +NAME##_search(avl_node_t *node, TYPE *data) \ +{ \ + while (node != NULL) { \ + int result = NAME##_AVL_compare((TYPE *)node->data, data); \ + if (result == 0) return (TYPE *)node->data; \ + if (result > 0) node = node->left; \ + else node = node->right; \ + } \ + return NULL; \ +} \ +static __attribute__((unused)) void \ +NAME##_free(avl_node_t **node) \ +{ \ + avl_node_t *it = *node; \ + if (it) { \ + NAME##_free(&it->left); \ + NAME##_free(&it->right); \ + free(it); \ + *node = NULL; \ + } \ +} \ +static void \ +NAME##_toarray_rec(avl_node_t *node, TYPE **ptr) \ +{ \ + if (node->left != NULL) NAME##_toarray_rec(node->left, ptr); \ + memcpy(*ptr, node->data, sizeof(TYPE)); \ + (*ptr)++; \ + if (node->right != NULL) NAME##_toarray_rec(node->right, ptr); \ +} \ +static __attribute__((unused)) TYPE* \ +NAME##_toarray(avl_node_t *node) \ +{ \ + size_t count = avl_count(node); \ + TYPE *arr = (TYPE *)malloc(sizeof(TYPE) * count); \ + TYPE *ptr = arr; \ + NAME##_toarray_rec(node, &ptr); \ + return arr; \ +} \ +static __attribute__((unused)) avl_iter_t* \ +NAME##_iter(avl_node_t *node) \ +{ \ + return avl_iter(node); \ +} \ +static __attribute__((unused)) TYPE* \ +NAME##_iter_next(avl_iter_t *iter) \ +{ \ + avl_node_t *result = avl_iter_next(iter); \ + if (result == NULL) return NULL; \ + return (TYPE*)(result->data); \ +} \ +static __attribute__((unused)) void \ +NAME##_iter_free(avl_iter_t *iter) \ +{ \ + free(iter); \ +} \ +static inline int \ +NAME##_AVL_compare(TYPE *left, TYPE *right) + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif diff --git a/src/lace.c b/src/lace.c new file mode 100644 index 000000000..9a0b3cb82 --- /dev/null +++ b/src/lace.c @@ -0,0 +1,1045 @@ +/* + * Copyright 2013-2015 Formal Methods and Tools, University of Twente + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <errno.h> // for errno +#include <sched.h> // for sched_getaffinity +#include <stdio.h> // for fprintf +#include <stdlib.h> // for memalign, malloc +#include <string.h> // for memset +#include <sys/mman.h> // for mprotect +#include <sys/time.h> // for gettimeofday +#include <pthread.h> +#include <unistd.h> +#include <assert.h> + +#include <lace.h> + +#ifndef USE_HWLOC +#define USE_HWLOC 0 +#endif + +#if USE_HWLOC +#include <hwloc.h> +#endif + +// public Worker data +static Worker **workers; +static size_t default_stacksize = 0; // set by lace_init +static size_t default_dqsize = 100000; + +#if USE_HWLOC +static hwloc_topology_t topo; +static unsigned int n_nodes, n_cores, n_pus; +#endif + +static int verbosity = 0; + +static int n_workers = 0; +static int enabled_workers = 0; + +// private Worker data (just for stats at end ) +static WorkerP **workers_p; + +// set to 0 when quitting +static int lace_quits = 0; + +// for storing private Worker data +#ifdef __linux__ // use gcc thread-local storage (i.e. __thread variables) +static __thread WorkerP *current_worker; +#else +static pthread_key_t worker_key; +#endif +static pthread_attr_t worker_attr; + +static pthread_cond_t wait_until_done = PTHREAD_COND_INITIALIZER; +static pthread_mutex_t wait_until_done_mutex = PTHREAD_MUTEX_INITIALIZER; + +struct lace_worker_init +{ + void* stack; + size_t stacksize; +}; + +static struct lace_worker_init *workers_init; + +lace_newframe_t lace_newframe; + +WorkerP* +lace_get_worker() +{ +#ifdef __linux__ + return current_worker; +#else + return (WorkerP*)pthread_getspecific(worker_key); +#endif +} + +Task* +lace_get_head(WorkerP *self) +{ + Task *dq = self->dq; + if (dq[0].thief == 0) return dq; + if (dq[1].thief == 0) return dq+1; + if (dq[2].thief == 0) return dq+2; + + size_t low = 2; + size_t high = self->end - self->dq; + + for (;;) { + if (low*2 >= high) { + break; + } else if (dq[low*2].thief == 0) { + high=low*2; + break; + } else { + low*=2; + } + } + + while (low < high) { + size_t mid = low + (high-low)/2; + if (dq[mid].thief == 0) high = mid; + else low = mid + 1; + } + + return dq+low; +} + +size_t +lace_workers() +{ + return n_workers; +} + +size_t +lace_default_stacksize() +{ + return default_stacksize; +} + +#ifndef cas +#define cas(ptr, old, new) (__sync_bool_compare_and_swap((ptr),(old),(new))) +#endif + +#if LACE_PIE_TIMES +static uint64_t count_at_start, count_at_end; +static long long unsigned us_elapsed_timer; + +static void +us_elapsed_start(void) +{ + struct timeval now; + gettimeofday(&now, NULL); + us_elapsed_timer = now.tv_sec * 1000000LL + now.tv_usec; +} + +static long long unsigned +us_elapsed(void) +{ + struct timeval now; + long long unsigned t; + + gettimeofday( &now, NULL ); + + t = now.tv_sec * 1000000LL + now.tv_usec; + + return t - us_elapsed_timer; +} +#endif + +#if USE_HWLOC +// Lock used only during parallel lace_init_worker... +static volatile int __attribute__((aligned(64))) lock = 0; +static inline void +lock_acquire() +{ + while (1) { + while (lock) {} + if (cas(&lock, 0, 1)) return; + } +} +static inline void +lock_release() +{ + lock=0; +} +#endif + +/* Barrier */ +#define BARRIER_MAX_THREADS 128 + +typedef union __attribute__((__packed__)) +{ + volatile size_t val; + char pad[LINE_SIZE]; +} asize_t; + +typedef struct { + volatile int __attribute__((aligned(LINE_SIZE))) count; + volatile int __attribute__((aligned(LINE_SIZE))) wait; + /* the following is needed only for destroy: */ + asize_t entered[BARRIER_MAX_THREADS]; +} barrier_t; + +barrier_t lace_bar; + +void +lace_barrier() +{ + int id = lace_get_worker()->worker; + + lace_bar.entered[id].val = 1; // signal entry + + int wait = lace_bar.wait; + if (enabled_workers == __sync_add_and_fetch(&lace_bar.count, 1)) { + lace_bar.count = 0; // reset counter + lace_bar.wait = 1 - wait; // flip wait + lace_bar.entered[id].val = 0; // signal exit + } else { + while (wait == lace_bar.wait) {} // wait + lace_bar.entered[id].val = 0; // signal exit + } +} + +static void +lace_barrier_init() +{ + assert(n_workers <= BARRIER_MAX_THREADS); + memset(&lace_bar, 0, sizeof(barrier_t)); +} + +static void +lace_barrier_destroy() +{ + // wait for all to exit + for (int i=0; i<n_workers; i++) { + while (1 == lace_bar.entered[i].val) {} + } +} + +void +lace_init_worker(int worker, size_t dq_size) +{ + Worker *wt = NULL; + WorkerP *w = NULL; + + if (dq_size == 0) dq_size = default_dqsize; + +#if USE_HWLOC + // Get our logical processor + hwloc_obj_t pu = hwloc_get_obj_by_type(topo, HWLOC_OBJ_PU, worker % n_pus); + + // Pin our thread... + hwloc_set_cpubind(topo, pu->cpuset, HWLOC_CPUBIND_THREAD); + + // Allocate memory on our node... + lock_acquire(); + wt = (Worker *)hwloc_alloc_membind(topo, sizeof(Worker), pu->cpuset, HWLOC_MEMBIND_BIND, 0); + w = (WorkerP *)hwloc_alloc_membind(topo, sizeof(WorkerP), pu->cpuset, HWLOC_MEMBIND_BIND, 0); + if (wt == NULL || w == NULL || (w->dq = (Task*)hwloc_alloc_membind(topo, dq_size * sizeof(Task), pu->cpuset, HWLOC_MEMBIND_BIND, 0)) == NULL) { + fprintf(stderr, "Lace error: Unable to allocate memory for the Lace worker!\n"); + exit(1); + } + lock_release(); +#else + // Allocate memory... + if (posix_memalign((void**)&wt, LINE_SIZE, sizeof(Worker)) || + posix_memalign((void**)&w, LINE_SIZE, sizeof(WorkerP)) || + posix_memalign((void**)&w->dq, LINE_SIZE, dq_size * sizeof(Task))) { + fprintf(stderr, "Lace error: Unable to allocate memory for the Lace worker!\n"); + exit(1); + } +#endif + + // Initialize public worker data + wt->dq = w->dq; + wt->ts.v = 0; + wt->allstolen = 0; + wt->movesplit = 0; + + // Initialize private worker data + w->_public = wt; + w->end = w->dq + dq_size; + w->split = w->dq; + w->allstolen = 0; + w->worker = worker; +#if USE_HWLOC + w->pu = worker % n_pus; +#else + w->pu = -1; +#endif + w->enabled = 1; + if (workers_init[worker].stack != 0) { + w->stack_trigger = ((size_t)workers_init[worker].stack) + workers_init[worker].stacksize/20; + } else { + w->stack_trigger = 0; + } + +#if LACE_COUNT_EVENTS + // Reset counters + { int k; for (k=0; k<CTR_MAX; k++) w->ctr[k] = 0; } +#endif + + // Set pointers +#ifdef __linux__ + current_worker = w; +#else + pthread_setspecific(worker_key, w); +#endif + workers[worker] = wt; + workers_p[worker] = w; + + // Synchronize with others + lace_barrier(); + +#if LACE_PIE_TIMES + w->time = gethrtime(); + w->level = 0; +#endif +} + +#if defined(__APPLE__) && !defined(pthread_barrier_t) + +typedef int pthread_barrierattr_t; +typedef struct +{ + pthread_mutex_t mutex; + pthread_cond_t cond; + int count; + int tripCount; +} pthread_barrier_t; + +static int +pthread_barrier_init(pthread_barrier_t *barrier, const pthread_barrierattr_t *attr, unsigned int count) +{ + if(count == 0) + { + errno = EINVAL; + return -1; + } + if(pthread_mutex_init(&barrier->mutex, 0) < 0) + { + return -1; + } + if(pthread_cond_init(&barrier->cond, 0) < 0) + { + pthread_mutex_destroy(&barrier->mutex); + return -1; + } + barrier->tripCount = count; + barrier->count = 0; + + return 0; + (void)attr; +} + +static int +pthread_barrier_destroy(pthread_barrier_t *barrier) +{ + pthread_cond_destroy(&barrier->cond); + pthread_mutex_destroy(&barrier->mutex); + return 0; +} + +static int +pthread_barrier_wait(pthread_barrier_t *barrier) +{ + pthread_mutex_lock(&barrier->mutex); + ++(barrier->count); + if(barrier->count >= barrier->tripCount) + { + barrier->count = 0; + pthread_cond_broadcast(&barrier->cond); + pthread_mutex_unlock(&barrier->mutex); + return 1; + } + else + { + pthread_cond_wait(&barrier->cond, &(barrier->mutex)); + pthread_mutex_unlock(&barrier->mutex); + return 0; + } +} + +#endif // defined(__APPLE__) && !defined(pthread_barrier_t) + +static pthread_barrier_t suspend_barrier; +static volatile int must_suspend = 0, suspended = 0; + +void +lace_suspend() +{ + if (suspended == 0) { + suspended = 1; + must_suspend = 1; + lace_barrier(); + must_suspend = 0; + } +} + +void +lace_resume() +{ + if (suspended == 1) { + suspended = 0; + pthread_barrier_wait(&suspend_barrier); + } +} + +/** + * With set_workers, all workers 0..(N-1) are enabled and N..max are disabled. + * You can never disable the current worker or reduce the number of workers below 1. + */ +void +lace_disable_worker(int worker) +{ + int self = lace_get_worker()->worker; + if (worker == self) return; + if (workers_p[worker]->enabled == 1) { + workers_p[worker]->enabled = 0; + enabled_workers--; + } +} + +void +lace_enable_worker(int worker) +{ + int self = lace_get_worker()->worker; + if (worker == self) return; + if (workers_p[worker]->enabled == 0) { + workers_p[worker]->enabled = 1; + enabled_workers++; + } +} + +void +lace_set_workers(int workercount) +{ + if (workercount < 1) workercount = 1; + if (workercount > n_workers) workercount = n_workers; + enabled_workers = workercount; + int self = lace_get_worker()->worker; + if (self >= workercount) workercount--; + for (int i=0; i<n_workers; i++) { + workers_p[i]->enabled = (i < workercount || i == self) ? 1 : 0; + } +} + +int +lace_enabled_workers() +{ + return enabled_workers; +} + +static inline uint32_t +rng(uint32_t *seed, int max) +{ + uint32_t next = *seed; + + next *= 1103515245; + next += 12345; + + *seed = next; + + return next % max; +} + +VOID_TASK_IMPL_0(lace_steal_random) +{ + Worker *victim = workers[(__lace_worker->worker + 1 + rng(&__lace_worker->seed, n_workers-1)) % n_workers]; + + YIELD_NEWFRAME(); + + PR_COUNTSTEALS(__lace_worker, CTR_steal_tries); + Worker *res = lace_steal(__lace_worker, __lace_dq_head, victim); + if (res == LACE_STOLEN) { + PR_COUNTSTEALS(__lace_worker, CTR_steals); + } else if (res == LACE_BUSY) { + PR_COUNTSTEALS(__lace_worker, CTR_steal_busy); + } +} + +VOID_TASK_IMPL_1(lace_steal_random_loop, int*, quit) +{ + while(!(*(volatile int*)quit)) { + lace_steal_random(); + + if (must_suspend) { + lace_barrier(); + do { + pthread_barrier_wait(&suspend_barrier); + } while (__lace_worker->enabled == 0); + } + } +} + +static lace_startup_cb main_cb; + +static void* +lace_main_wrapper(void *arg) +{ + lace_init_worker(0, 0); + WorkerP *self = lace_get_worker(); + +#if LACE_PIE_TIMES + self->time = gethrtime(); +#endif + + lace_time_event(self, 1); + main_cb(self, self->dq, arg); + lace_exit(); + pthread_cond_broadcast(&wait_until_done); + + return NULL; +} + +VOID_TASK_IMPL_1(lace_steal_loop, int*, quit) +{ + // Determine who I am + const int worker_id = __lace_worker->worker; + + // Prepare self, victim + Worker ** const self = &workers[worker_id]; + Worker **victim = self; + +#if LACE_PIE_TIMES + __lace_worker->time = gethrtime(); +#endif + + uint32_t seed = worker_id; + unsigned int n = n_workers; + int i=0; + + while(*(volatile int*)quit == 0) { + // Select victim + if( i>0 ) { + i--; + victim++; + if (victim == self) victim++; + if (victim >= workers + n) victim = workers; + if (victim == self) victim++; + } else { + i = rng(&seed, 40); // compute random i 0..40 + victim = workers + (rng(&seed, n-1) + worker_id + 1) % n; + } + + PR_COUNTSTEALS(__lace_worker, CTR_steal_tries); + Worker *res = lace_steal(__lace_worker, __lace_dq_head, *victim); + if (res == LACE_STOLEN) { + PR_COUNTSTEALS(__lace_worker, CTR_steals); + } else if (res == LACE_BUSY) { + PR_COUNTSTEALS(__lace_worker, CTR_steal_busy); + } + + YIELD_NEWFRAME(); + + if (must_suspend) { + lace_barrier(); + do { + pthread_barrier_wait(&suspend_barrier); + } while (__lace_worker->enabled == 0); + } + } +} + +static void* +lace_default_worker(void* arg) +{ + lace_init_worker((size_t)arg, 0); + WorkerP *__lace_worker = lace_get_worker(); + Task *__lace_dq_head = __lace_worker->dq; + lace_steal_loop(&lace_quits); + lace_time_event(__lace_worker, 9); + lace_barrier(); + return NULL; +} + +pthread_t +lace_spawn_worker(int worker, size_t stacksize, void* (*fun)(void*), void* arg) +{ + // Determine stack size + if (stacksize == 0) stacksize = default_stacksize; + + size_t pagesize = sysconf(_SC_PAGESIZE); + stacksize = (stacksize + pagesize - 1) & ~(pagesize - 1); // ceil(stacksize, pagesize) + +#if USE_HWLOC + // Get our logical processor + hwloc_obj_t pu = hwloc_get_obj_by_type(topo, HWLOC_OBJ_PU, worker % n_pus); + + // Allocate memory for the program stack + lock_acquire(); + void *stack_location = hwloc_alloc_membind(topo, stacksize + pagesize, pu->cpuset, HWLOC_MEMBIND_BIND, 0); + lock_release(); + if (stack_location == 0) { + fprintf(stderr, "Lace error: Unable to allocate memory for the pthread stack!\n"); + exit(1); + } +#else + void *stack_location = mmap(NULL, stacksize + pagesize, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); + if (stack_location == MAP_FAILED) { + fprintf(stderr, "Lace error: Cannot allocate program stack: %s!\n", strerror(errno)); + exit(1); + } +#endif + + if (0 != mprotect(stack_location, pagesize, PROT_NONE)) { + fprintf(stderr, "Lace error: Unable to protect the allocated program stack with a guard page!\n"); + exit(1); + } + stack_location = (uint8_t *)stack_location + pagesize; // skip protected page. + if (0 != pthread_attr_setstack(&worker_attr, stack_location, stacksize)) { + fprintf(stderr, "Lace error: Unable to set the pthread stack in Lace!\n"); + exit(1); + } + + workers_init[worker].stack = stack_location; + workers_init[worker].stacksize = stacksize; + + if (fun == 0) { + fun = lace_default_worker; + arg = (void*)(size_t)worker; + } + + pthread_t res; + pthread_create(&res, &worker_attr, fun, arg); + return res; +} + +static int +get_cpu_count() +{ +#if USE_HWLOC + int count = hwloc_get_nbobjs_by_type(topo, HWLOC_OBJ_PU); +#elif defined(sched_getaffinity) + /* Best solution: find actual available cpus */ + cpu_set_t cs; + CPU_ZERO(&cs); + sched_getaffinity(0, sizeof(cs), &cs); + int count = CPU_COUNT(&cs); +#elif defined(_SC_NPROCESSORS_ONLN) + /* Fallback */ + int count = sysconf(_SC_NPROCESSORS_ONLN); +#else + /* Okay... */ + int count = 1; +#endif + return count < 1 ? 1 : count; +} + +void +lace_set_verbosity(int level) +{ + verbosity = level; +} + +void +lace_init(int n, size_t dqsize) +{ +#if USE_HWLOC + hwloc_topology_init(&topo); + hwloc_topology_load(topo); + + n_nodes = hwloc_get_nbobjs_by_type(topo, HWLOC_OBJ_NODE); + n_cores = hwloc_get_nbobjs_by_type(topo, HWLOC_OBJ_CORE); + n_pus = hwloc_get_nbobjs_by_type(topo, HWLOC_OBJ_PU); +#endif + + // Initialize globals + n_workers = n; + if (n_workers == 0) n_workers = get_cpu_count(); + enabled_workers = n_workers; + if (dqsize != 0) default_dqsize = dqsize; + lace_quits = 0; + + // Create barrier for all workers + lace_barrier_init(); + + // Create suspend barrier + pthread_barrier_init(&suspend_barrier, NULL, n_workers); + + // Allocate array with all workers + if (posix_memalign((void**)&workers, LINE_SIZE, n_workers*sizeof(Worker*)) != 0 || + posix_memalign((void**)&workers_p, LINE_SIZE, n_workers*sizeof(WorkerP*)) != 0) { + fprintf(stderr, "Lace error: unable to allocate memory!\n"); + exit(1); + } + + // Create pthread key +#ifndef __linux__ + pthread_key_create(&worker_key, NULL); +#endif + + // Prepare structures for thread creation + pthread_attr_init(&worker_attr); + + // Set contention scope to system (instead of process) + pthread_attr_setscope(&worker_attr, PTHREAD_SCOPE_SYSTEM); + + // Get default stack size + if (pthread_attr_getstacksize(&worker_attr, &default_stacksize) != 0) { + fprintf(stderr, "Lace warning: pthread_attr_getstacksize returned error!\n"); + default_stacksize = 1048576; // 1 megabyte default + } + + if (verbosity) { +#if USE_HWLOC + fprintf(stderr, "Initializing Lace, %u nodes, %u cores, %u logical processors, %d workers.\n", n_nodes, n_cores, n_pus, n_workers); +#else + fprintf(stderr, "Initializing Lace, %d workers.\n", n_workers); +#endif + } + + // Prepare lace_init structure + workers_init = (struct lace_worker_init*)calloc(1, sizeof(struct lace_worker_init) * n_workers); + + lace_newframe.t = NULL; + +#if LACE_PIE_TIMES + // Initialize counters for pie times + us_elapsed_start(); + count_at_start = gethrtime(); +#endif +} + +void +lace_startup(size_t stacksize, lace_startup_cb cb, void *arg) +{ + if (stacksize == 0) stacksize = default_stacksize; + + if (verbosity) { + if (cb != 0) { + fprintf(stderr, "Lace startup, creating %d worker threads with program stack %zu bytes.\n", n_workers, stacksize); + } else if (n_workers == 1) { + fprintf(stderr, "Lace startup, creating 0 worker threads.\n"); + } else { + fprintf(stderr, "Lace startup, creating %d worker threads with program stack %zu bytes.\n", n_workers-1, stacksize); + } + } + + /* Spawn workers */ + int i; + for (i=1; i<n_workers; i++) lace_spawn_worker(i, stacksize, 0, 0); + + if (cb != 0) { + main_cb = cb; + lace_spawn_worker(0, stacksize, lace_main_wrapper, arg); + + // Suspend this thread until cb returns + pthread_mutex_lock(&wait_until_done_mutex); + pthread_cond_wait(&wait_until_done, &wait_until_done_mutex); + pthread_mutex_unlock(&wait_until_done_mutex); + } else { + // use this thread as worker and return control + lace_init_worker(0, 0); + lace_time_event(lace_get_worker(), 1); + } +} + +#if LACE_COUNT_EVENTS +static uint64_t ctr_all[CTR_MAX]; +#endif + +void +lace_count_reset() +{ +#if LACE_COUNT_EVENTS + int i; + size_t j; + + for (i=0;i<n_workers;i++) { + for (j=0;j<CTR_MAX;j++) { + workers_p[i]->ctr[j] = 0; + } + } + +#if LACE_PIE_TIMES + for (i=0;i<n_workers;i++) { + workers_p[i]->time = gethrtime(); + if (i != 0) workers_p[i]->level = 0; + } + + us_elapsed_start(); + count_at_start = gethrtime(); +#endif +#endif +} + +void +lace_count_report_file(FILE *file) +{ +#if LACE_COUNT_EVENTS + int i; + size_t j; + + for (j=0;j<CTR_MAX;j++) ctr_all[j] = 0; + for (i=0;i<n_workers;i++) { + uint64_t *wctr = workers_p[i]->ctr; + for (j=0;j<CTR_MAX;j++) { + ctr_all[j] += wctr[j]; + } + } + +#if LACE_COUNT_TASKS + for (i=0;i<n_workers;i++) { + fprintf(file, "Tasks (%d): %zu\n", i, workers_p[i]->ctr[CTR_tasks]); + } + fprintf(file, "Tasks (sum): %zu\n", ctr_all[CTR_tasks]); + fprintf(file, "\n"); +#endif + +#if LACE_COUNT_STEALS + for (i=0;i<n_workers;i++) { + fprintf(file, "Steals (%d): %zu good/%zu busy of %zu tries; leaps: %zu good/%zu busy of %zu tries\n", i, + workers_p[i]->ctr[CTR_steals], workers_p[i]->ctr[CTR_steal_busy], + workers_p[i]->ctr[CTR_steal_tries], workers_p[i]->ctr[CTR_leaps], + workers_p[i]->ctr[CTR_leap_busy], workers_p[i]->ctr[CTR_leap_tries]); + } + fprintf(file, "Steals (sum): %zu good/%zu busy of %zu tries; leaps: %zu good/%zu busy of %zu tries\n", + ctr_all[CTR_steals], ctr_all[CTR_steal_busy], + ctr_all[CTR_steal_tries], ctr_all[CTR_leaps], + ctr_all[CTR_leap_busy], ctr_all[CTR_leap_tries]); + fprintf(file, "\n"); +#endif + +#if LACE_COUNT_STEALS && LACE_COUNT_TASKS + for (i=0;i<n_workers;i++) { + fprintf(file, "Tasks per steal (%d): %zu\n", i, + workers_p[i]->ctr[CTR_tasks]/(workers_p[i]->ctr[CTR_steals]+workers_p[i]->ctr[CTR_leaps])); + } + fprintf(file, "Tasks per steal (sum): %zu\n", ctr_all[CTR_tasks]/(ctr_all[CTR_steals]+ctr_all[CTR_leaps])); + fprintf(file, "\n"); +#endif + +#if LACE_COUNT_SPLITS + for (i=0;i<n_workers;i++) { + fprintf(file, "Splits (%d): %zu shrinks, %zu grows, %zu outgoing requests\n", i, + workers_p[i]->ctr[CTR_split_shrink], workers_p[i]->ctr[CTR_split_grow], workers_p[i]->ctr[CTR_split_req]); + } + fprintf(file, "Splits (sum): %zu shrinks, %zu grows, %zu outgoing requests\n", + ctr_all[CTR_split_shrink], ctr_all[CTR_split_grow], ctr_all[CTR_split_req]); + fprintf(file, "\n"); +#endif + +#if LACE_PIE_TIMES + count_at_end = gethrtime(); + + uint64_t count_per_ms = (count_at_end - count_at_start) / (us_elapsed() / 1000); + double dcpm = (double)count_per_ms; + + uint64_t sum_count; + sum_count = ctr_all[CTR_init] + ctr_all[CTR_wapp] + ctr_all[CTR_lapp] + ctr_all[CTR_wsteal] + ctr_all[CTR_lsteal] + + ctr_all[CTR_close] + ctr_all[CTR_wstealsucc] + ctr_all[CTR_lstealsucc] + ctr_all[CTR_wsignal] + + ctr_all[CTR_lsignal]; + + fprintf(file, "Measured clock (tick) frequency: %.2f GHz\n", count_per_ms / 1000000.0); + fprintf(file, "Aggregated time per pie slice, total time: %.2f CPU seconds\n\n", sum_count / (1000*dcpm)); + + for (i=0;i<n_workers;i++) { + fprintf(file, "Startup time (%d): %10.2f ms\n", i, workers_p[i]->ctr[CTR_init] / dcpm); + fprintf(file, "Steal work (%d): %10.2f ms\n", i, workers_p[i]->ctr[CTR_wapp] / dcpm); + fprintf(file, "Leap work (%d): %10.2f ms\n", i, workers_p[i]->ctr[CTR_lapp] / dcpm); + fprintf(file, "Steal overhead (%d): %10.2f ms\n", i, (workers_p[i]->ctr[CTR_wstealsucc]+workers_p[i]->ctr[CTR_wsignal]) / dcpm); + fprintf(file, "Leap overhead (%d): %10.2f ms\n", i, (workers_p[i]->ctr[CTR_lstealsucc]+workers_p[i]->ctr[CTR_lsignal]) / dcpm); + fprintf(file, "Steal search (%d): %10.2f ms\n", i, (workers_p[i]->ctr[CTR_wsteal]-workers_p[i]->ctr[CTR_wstealsucc]-workers_p[i]->ctr[CTR_wsignal]) / dcpm); + fprintf(file, "Leap search (%d): %10.2f ms\n", i, (workers_p[i]->ctr[CTR_lsteal]-workers_p[i]->ctr[CTR_lstealsucc]-workers_p[i]->ctr[CTR_lsignal]) / dcpm); + fprintf(file, "Exit time (%d): %10.2f ms\n", i, workers_p[i]->ctr[CTR_close] / dcpm); + fprintf(file, "\n"); + } + + fprintf(file, "Startup time (sum): %10.2f ms\n", ctr_all[CTR_init] / dcpm); + fprintf(file, "Steal work (sum): %10.2f ms\n", ctr_all[CTR_wapp] / dcpm); + fprintf(file, "Leap work (sum): %10.2f ms\n", ctr_all[CTR_lapp] / dcpm); + fprintf(file, "Steal overhead (sum): %10.2f ms\n", (ctr_all[CTR_wstealsucc]+ctr_all[CTR_wsignal]) / dcpm); + fprintf(file, "Leap overhead (sum): %10.2f ms\n", (ctr_all[CTR_lstealsucc]+ctr_all[CTR_lsignal]) / dcpm); + fprintf(file, "Steal search (sum): %10.2f ms\n", (ctr_all[CTR_wsteal]-ctr_all[CTR_wstealsucc]-ctr_all[CTR_wsignal]) / dcpm); + fprintf(file, "Leap search (sum): %10.2f ms\n", (ctr_all[CTR_lsteal]-ctr_all[CTR_lstealsucc]-ctr_all[CTR_lsignal]) / dcpm); + fprintf(file, "Exit time (sum): %10.2f ms\n", ctr_all[CTR_close] / dcpm); + fprintf(file, "\n" ); +#endif +#endif + return; + (void)file; +} + +void lace_exit() +{ + lace_time_event(lace_get_worker(), 2); + + // first suspend all other threads + lace_suspend(); + + // now enable all threads and tell them to quit + lace_set_workers(n_workers); + lace_quits = 1; + + // now resume all threads and wait until they all pass the barrier + lace_resume(); + lace_barrier(); + + // finally, destroy the barriers + lace_barrier_destroy(); + pthread_barrier_destroy(&suspend_barrier); + +#if LACE_COUNT_EVENTS + lace_count_report_file(stderr); +#endif +} + +void +lace_exec_in_new_frame(WorkerP *__lace_worker, Task *__lace_dq_head, Task *root) +{ + TailSplit old; + uint8_t old_as; + + // save old tail, split, allstolen and initiate new frame + { + Worker *wt = __lace_worker->_public; + + old_as = wt->allstolen; + wt->allstolen = 1; + old.ts.split = wt->ts.ts.split; + wt->ts.ts.split = 0; + mfence(); + old.ts.tail = wt->ts.ts.tail; + + TailSplit ts_new; + ts_new.ts.tail = __lace_dq_head - __lace_worker->dq; + ts_new.ts.split = __lace_dq_head - __lace_worker->dq; + wt->ts.v = ts_new.v; + + __lace_worker->split = __lace_dq_head; + __lace_worker->allstolen = 1; + } + + // wait until all workers are ready + lace_barrier(); + + // execute task + root->f(__lace_worker, __lace_dq_head, root); + compiler_barrier(); + + // wait until all workers are back (else they may steal from previous frame) + lace_barrier(); + + // restore tail, split, allstolen + { + Worker *wt = __lace_worker->_public; + wt->allstolen = old_as; + wt->ts.v = old.v; + __lace_worker->split = __lace_worker->dq + old.ts.split; + __lace_worker->allstolen = old_as; + } +} + +VOID_TASK_IMPL_2(lace_steal_loop_root, Task*, t, int*, done) +{ + t->f(__lace_worker, __lace_dq_head, t); + *done = 1; +} + +VOID_TASK_2(lace_together_helper, Task*, t, volatile int*, finished) +{ + t->f(__lace_worker, __lace_dq_head, t); + + for (;;) { + int f = *finished; + if (cas(finished, f, f-1)) break; + } + + while (*finished != 0) STEAL_RANDOM(); +} + +static void +lace_sync_and_exec(WorkerP *__lace_worker, Task *__lace_dq_head, Task *root) +{ + // wait until other workers have made a local copy + lace_barrier(); + + // one worker sets t to 0 again + if (LACE_WORKER_ID == 0) lace_newframe.t = 0; + // else while (*(volatile Task**)&lace_newframe.t != 0) {} + + // the above line is commented out since lace_exec_in_new_frame includes + // a lace_barrier before the task is executed + + lace_exec_in_new_frame(__lace_worker, __lace_dq_head, root); +} + +void +lace_yield(WorkerP *__lace_worker, Task *__lace_dq_head) +{ + // make a local copy of the task + Task _t; + memcpy(&_t, lace_newframe.t, sizeof(Task)); + + // wait until all workers have made a local copy + lace_barrier(); + + // one worker sets t to 0 again + if (LACE_WORKER_ID == 0) lace_newframe.t = 0; + // else while (*(volatile Task**)&lace_newframe.t != 0) {} + + // the above line is commented out since lace_exec_in_new_frame includes + // a lace_barrier before the task is executed + + lace_exec_in_new_frame(__lace_worker, __lace_dq_head, &_t); +} + +void +lace_do_together(WorkerP *__lace_worker, Task *__lace_dq_head, Task *t) +{ + /* synchronization integer */ + int done = n_workers; + + /* wrap task in lace_together_helper */ + Task _t2; + TD_lace_together_helper *t2 = (TD_lace_together_helper *)&_t2; + t2->f = lace_together_helper_WRAP; + t2->thief = THIEF_TASK; + t2->d.args.arg_1 = t; + t2->d.args.arg_2 = &done; + + while (!cas(&lace_newframe.t, 0, &_t2)) lace_yield(__lace_worker, __lace_dq_head); + lace_sync_and_exec(__lace_worker, __lace_dq_head, &_t2); +} + +void +lace_do_newframe(WorkerP *__lace_worker, Task *__lace_dq_head, Task *t) +{ + /* synchronization integer */ + int done = 0; + + /* wrap task in lace_steal_loop_root */ + Task _t2; + TD_lace_steal_loop_root *t2 = (TD_lace_steal_loop_root *)&_t2; + t2->f = lace_steal_loop_root_WRAP; + t2->thief = THIEF_TASK; + t2->d.args.arg_1 = t; + t2->d.args.arg_2 = &done; + + /* and create the lace_steal_loop task for other workers */ + Task _s; + TD_lace_steal_loop *s = (TD_lace_steal_loop *)&_s; + s->f = &lace_steal_loop_WRAP; + s->thief = THIEF_TASK; + s->d.args.arg_1 = &done; + + compiler_barrier(); + + while (!cas(&lace_newframe.t, 0, &_s)) lace_yield(__lace_worker, __lace_dq_head); + lace_sync_and_exec(__lace_worker, __lace_dq_head, &_t2); +} diff --git a/src/lace.h b/src/lace.h new file mode 100644 index 000000000..8d6d1b645 --- /dev/null +++ b/src/lace.h @@ -0,0 +1,2743 @@ +/* + * Copyright 2013-2015 Formal Methods and Tools, University of Twente + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an AS IS BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <unistd.h> +#include <stdint.h> +#include <stdio.h> +#include <pthread.h> /* for pthread_t */ + +#ifndef __LACE_H__ +#define __LACE_H__ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* Some flags */ + +#ifndef LACE_DEBUG_PROGRAMSTACK /* Write to stderr when 95% program stack reached */ +#define LACE_DEBUG_PROGRAMSTACK 0 +#endif + +#ifndef LACE_LEAP_RANDOM /* Use random leaping when leapfrogging fails */ +#define LACE_LEAP_RANDOM 1 +#endif + +#ifndef LACE_PIE_TIMES /* Record time spent stealing and leapfrogging */ +#define LACE_PIE_TIMES 0 +#endif + +#ifndef LACE_COUNT_TASKS /* Count number of tasks executed */ +#define LACE_COUNT_TASKS 0 +#endif + +#ifndef LACE_COUNT_STEALS /* Count number of steals performed */ +#define LACE_COUNT_STEALS 0 +#endif + +#ifndef LACE_COUNT_SPLITS /* Count number of times the split point is moved */ +#define LACE_COUNT_SPLITS 0 +#endif + +#ifndef LACE_COUNT_EVENTS +#define LACE_COUNT_EVENTS (LACE_PIE_TIMES || LACE_COUNT_TASKS || LACE_COUNT_STEALS || LACE_COUNT_SPLITS) +#endif + +/* Typical cacheline size of system architectures */ +#ifndef LINE_SIZE +#define LINE_SIZE 64 +#endif + +/* The size of a pointer, 8 bytes on a 64-bit architecture */ +#define P_SZ (sizeof(void *)) + +#define PAD(x,b) ( ( (b) - ((x)%(b)) ) & ((b)-1) ) /* b must be power of 2 */ +#define ROUND(x,b) ( (x) + PAD( (x), (b) ) ) + +/* The size is in bytes. Note that this is without the extra overhead from Lace. + The value must be greater than or equal to the maximum size of your tasks. + The task size is the maximum of the size of the result or of the sum of the parameter sizes. */ +#ifndef LACE_TASKSIZE +#define LACE_TASKSIZE (6)*P_SZ +#endif + +/* Some fences */ +#ifndef compiler_barrier +#define compiler_barrier() { asm volatile("" ::: "memory"); } +#endif + +#ifndef mfence +#define mfence() { asm volatile("mfence" ::: "memory"); } +#endif + +/* Compiler specific branch prediction optimization */ +#ifndef likely +#define likely(x) __builtin_expect((x),1) +#endif + +#ifndef unlikely +#define unlikely(x) __builtin_expect((x),0) +#endif + +#if LACE_PIE_TIMES +/* High resolution timer */ +static inline uint64_t gethrtime() +{ + uint32_t hi, lo; + asm volatile ("rdtsc" : "=a"(lo), "=d"(hi) :: "memory"); + return (uint64_t)hi<<32 | lo; +} +#endif + +#if LACE_COUNT_EVENTS +void lace_count_reset(); +void lace_count_report_file(FILE *file); +#endif + +#if LACE_COUNT_TASKS +#define PR_COUNTTASK(s) PR_INC(s,CTR_tasks) +#else +#define PR_COUNTTASK(s) /* Empty */ +#endif + +#if LACE_COUNT_STEALS +#define PR_COUNTSTEALS(s,i) PR_INC(s,i) +#else +#define PR_COUNTSTEALS(s,i) /* Empty */ +#endif + +#if LACE_COUNT_SPLITS +#define PR_COUNTSPLITS(s,i) PR_INC(s,i) +#else +#define PR_COUNTSPLITS(s,i) /* Empty */ +#endif + +#if LACE_COUNT_EVENTS +#define PR_ADD(s,i,k) ( ((s)->ctr[i])+=k ) +#else +#define PR_ADD(s,i,k) /* Empty */ +#endif +#define PR_INC(s,i) PR_ADD(s,i,1) + +typedef enum { +#ifdef LACE_COUNT_TASKS + CTR_tasks, /* Number of tasks spawned */ +#endif +#ifdef LACE_COUNT_STEALS + CTR_steal_tries, /* Number of steal attempts */ + CTR_leap_tries, /* Number of leap attempts */ + CTR_steals, /* Number of succesful steals */ + CTR_leaps, /* Number of succesful leaps */ + CTR_steal_busy, /* Number of steal busies */ + CTR_leap_busy, /* Number of leap busies */ +#endif +#ifdef LACE_COUNT_SPLITS + CTR_split_grow, /* Number of split right */ + CTR_split_shrink,/* Number of split left */ + CTR_split_req, /* Number of split requests */ +#endif + CTR_fast_sync, /* Number of fast syncs */ + CTR_slow_sync, /* Number of slow syncs */ +#ifdef LACE_PIE_TIMES + CTR_init, /* Timer for initialization */ + CTR_close, /* Timer for shutdown */ + CTR_wapp, /* Timer for application code (steal) */ + CTR_lapp, /* Timer for application code (leap) */ + CTR_wsteal, /* Timer for steal code (steal) */ + CTR_lsteal, /* Timer for steal code (leap) */ + CTR_wstealsucc, /* Timer for succesful steal code (steal) */ + CTR_lstealsucc, /* Timer for succesful steal code (leap) */ + CTR_wsignal, /* Timer for signal after work (steal) */ + CTR_lsignal, /* Timer for signal after work (leap) */ +#endif + CTR_MAX +} CTR_index; + +struct _WorkerP; +struct _Worker; +struct _Task; + +#define THIEF_EMPTY ((struct _Worker*)0x0) +#define THIEF_TASK ((struct _Worker*)0x1) +#define THIEF_COMPLETED ((struct _Worker*)0x2) + +#define TASK_COMMON_FIELDS(type) \ + void (*f)(struct _WorkerP *, struct _Task *, struct type *); \ + struct _Worker * volatile thief; + +struct __lace_common_fields_only { TASK_COMMON_FIELDS(_Task) }; +#define LACE_COMMON_FIELD_SIZE sizeof(struct __lace_common_fields_only) + +typedef struct _Task { + TASK_COMMON_FIELDS(_Task); + char p1[PAD(LACE_COMMON_FIELD_SIZE, P_SZ)]; + char d[LACE_TASKSIZE]; + char p2[PAD(ROUND(LACE_COMMON_FIELD_SIZE, P_SZ) + LACE_TASKSIZE, LINE_SIZE)]; +} Task; + +typedef union __attribute__((packed)) { + struct { + uint32_t tail; + uint32_t split; + } ts; + uint64_t v; +} TailSplit; + +typedef struct _Worker { + Task *dq; + TailSplit ts; + uint8_t allstolen; + + char pad1[PAD(P_SZ+sizeof(TailSplit)+1, LINE_SIZE)]; + + uint8_t movesplit; +} Worker; + +typedef struct _WorkerP { + Task *dq; // same as dq + Task *split; // same as dq+ts.ts.split + Task *end; // dq+dq_size + Worker *_public; // pointer to public Worker struct + size_t stack_trigger; // for stack overflow detection + int16_t worker; // what is my worker id? + int16_t pu; // my pu (for HWLOC) + uint8_t allstolen; // my allstolen + volatile int8_t enabled; // if this worker is enabled + +#if LACE_COUNT_EVENTS + uint64_t ctr[CTR_MAX]; // counters + volatile uint64_t time; + volatile int level; +#endif + + uint32_t seed; // my random seed (for lace_steal_random) +} WorkerP; + +#define LACE_TYPEDEF_CB(t, f, ...) typedef t (*f)(WorkerP *, Task *, ##__VA_ARGS__); +LACE_TYPEDEF_CB(void, lace_startup_cb, void*); + +/** + * Set verbosity level (0 = no startup messages, 1 = startup messages) + * Default level: 0 + */ +void lace_set_verbosity(int level); + +/** + * Initialize master structures for Lace with <n_workers> workers + * and default deque size of <dqsize>. + * Does not create new threads. + * Tries to detect number of cpus, if n_workers equals 0. + */ +void lace_init(int n_workers, size_t dqsize); + +/** + * After lace_init, start all worker threads. + * If cb,arg are set, suspend this thread, call cb(arg) in a new thread + * and exit Lace upon return + * Otherwise, the current thread is initialized as a Lace thread. + */ +void lace_startup(size_t stacksize, lace_startup_cb, void* arg); + +/** + * Initialize current thread as worker <idx> and allocate a deque with size <dqsize>. + * Use this when manually creating worker threads. + */ +void lace_init_worker(int idx, size_t dqsize); + +/** + * Manually spawn worker <idx> with (optional) program stack size <stacksize>. + * If fun,arg are set, overrides default startup method. + * Typically: for workers 1...(n_workers-1): lace_spawn_worker(i, stack_size, 0, 0); + */ +pthread_t lace_spawn_worker(int idx, size_t stacksize, void *(*fun)(void*), void* arg); + +/** + * Steal a random task. + */ +#define lace_steal_random() CALL(lace_steal_random) +void lace_steal_random_CALL(WorkerP*, Task*); + +/** + * Steal random tasks until parameter *quit is set + * Note: task declarations at end; quit is of type int* + */ +#define lace_steal_random_loop(quit) CALL(lace_steal_random_loop, quit) +#define lace_steal_loop(quit) CALL(lace_steal_loop, quit) + +/** + * Barrier (all workers must enter it before progressing) + */ +void lace_barrier(); + +/** + * Suspend and resume all other workers. + * May only be used when all other workers are idle. + */ +void lace_suspend(); +void lace_resume(); + +/** + * When all tasks are suspended, workers can be temporarily disabled. + * With set_workers, all workers 0..(N-1) are enabled and N..max are disabled. + * You can never disable the current worker or reduce the number of workers below 1. + * You cannot add workers. + */ +void lace_disable_worker(int worker); +void lace_enable_worker(int worker); +void lace_set_workers(int workercount); +int lace_enabled_workers(); + +/** + * Retrieve number of Lace workers + */ +size_t lace_workers(); + +/** + * Retrieve default program stack size + */ +size_t lace_default_stacksize(); + +/** + * Retrieve current worker. + */ +WorkerP *lace_get_worker(); + +/** + * Retrieve the current head of the deque + */ +Task *lace_get_head(WorkerP *); + +/** + * Exit Lace. Automatically called when started with cb,arg. + */ +void lace_exit(); + +#define LACE_STOLEN ((Worker*)0) +#define LACE_BUSY ((Worker*)1) +#define LACE_NOWORK ((Worker*)2) + +#define TASK(f) ( f##_CALL ) +#define WRAP(f, ...) ( f((WorkerP *)__lace_worker, (Task *)__lace_dq_head, ##__VA_ARGS__) ) +#define SYNC(f) ( __lace_dq_head--, WRAP(f##_SYNC) ) +#define DROP() ( __lace_dq_head--, WRAP(lace_drop) ) +#define SPAWN(f, ...) ( WRAP(f##_SPAWN, ##__VA_ARGS__), __lace_dq_head++ ) +#define CALL(f, ...) ( WRAP(f##_CALL, ##__VA_ARGS__) ) +#define TOGETHER(f, ...) ( WRAP(f##_TOGETHER, ##__VA_ARGS__) ) +#define NEWFRAME(f, ...) ( WRAP(f##_NEWFRAME, ##__VA_ARGS__) ) +#define STEAL_RANDOM() ( CALL(lace_steal_random) ) +#define LACE_WORKER_ID ( __lace_worker->worker ) +#define LACE_WORKER_PU ( __lace_worker->pu ) + +/* Use LACE_ME to initialize Lace variables, in case you want to call multiple Lace tasks */ +#define LACE_ME WorkerP * __attribute__((unused)) __lace_worker = lace_get_worker(); Task * __attribute__((unused)) __lace_dq_head = lace_get_head(__lace_worker); + +#define TASK_IS_STOLEN(t) ((size_t)t->thief > 1) +#define TASK_IS_COMPLETED(t) ((size_t)t->thief == 2) +#define TASK_RESULT(t) (&t->d[0]) + +#if LACE_DEBUG_PROGRAMSTACK +static inline void CHECKSTACK(WorkerP *w) +{ + if (w->stack_trigger != 0) { + register size_t rsp; + asm volatile("movq %%rsp, %0" : "+r"(rsp) : : "cc"); + if (rsp < w->stack_trigger) { + fputs("Warning: program stack 95% used!\n", stderr); + w->stack_trigger = 0; + } + } +} +#else +#define CHECKSTACK(w) {} +#endif + +typedef struct +{ + Task *t; + uint8_t all; + char pad[64-sizeof(Task *)-sizeof(uint8_t)]; +} lace_newframe_t; + +extern lace_newframe_t lace_newframe; + +/** + * Internal function to start participating on a task in a new frame + * Usually, <root> is set to NULL and the task is copied from lace_newframe.t + * It is possible to override the start task by setting <root>. + */ +void lace_do_together(WorkerP *__lace_worker, Task *__lace_dq_head, Task *task); +void lace_do_newframe(WorkerP *__lace_worker, Task *__lace_dq_head, Task *task); + +void lace_yield(WorkerP *__lace_worker, Task *__lace_dq_head); +#define YIELD_NEWFRAME() { if (unlikely((*(volatile Task**)&lace_newframe.t) != NULL)) lace_yield(__lace_worker, __lace_dq_head); } + +#if LACE_PIE_TIMES +static void lace_time_event( WorkerP *w, int event ) +{ + uint64_t now = gethrtime(), + prev = w->time; + + switch( event ) { + + // Enter application code + case 1 : + if( w->level /* level */ == 0 ) { + PR_ADD( w, CTR_init, now - prev ); + w->level = 1; + } else if( w->level /* level */ == 1 ) { + PR_ADD( w, CTR_wsteal, now - prev ); + PR_ADD( w, CTR_wstealsucc, now - prev ); + } else { + PR_ADD( w, CTR_lsteal, now - prev ); + PR_ADD( w, CTR_lstealsucc, now - prev ); + } + break; + + // Exit application code + case 2 : + if( w->level /* level */ == 1 ) { + PR_ADD( w, CTR_wapp, now - prev ); + } else { + PR_ADD( w, CTR_lapp, now - prev ); + } + break; + + // Enter sync on stolen + case 3 : + if( w->level /* level */ == 1 ) { + PR_ADD( w, CTR_wapp, now - prev ); + } else { + PR_ADD( w, CTR_lapp, now - prev ); + } + w->level++; + break; + + // Exit sync on stolen + case 4 : + if( w->level /* level */ == 1 ) { + fprintf( stderr, "This should not happen, level = %d\n", w->level ); + } else { + PR_ADD( w, CTR_lsteal, now - prev ); + } + w->level--; + break; + + // Return from failed steal + case 7 : + if( w->level /* level */ == 0 ) { + PR_ADD( w, CTR_init, now - prev ); + } else if( w->level /* level */ == 1 ) { + PR_ADD( w, CTR_wsteal, now - prev ); + } else { + PR_ADD( w, CTR_lsteal, now - prev ); + } + break; + + // Signalling time + case 8 : + if( w->level /* level */ == 1 ) { + PR_ADD( w, CTR_wsignal, now - prev ); + PR_ADD( w, CTR_wsteal, now - prev ); + } else { + PR_ADD( w, CTR_lsignal, now - prev ); + PR_ADD( w, CTR_lsteal, now - prev ); + } + break; + + // Done + case 9 : + if( w->level /* level */ == 0 ) { + PR_ADD( w, CTR_init, now - prev ); + } else { + PR_ADD( w, CTR_close, now - prev ); + } + break; + + default: return; + } + + w->time = now; +} +#else +#define lace_time_event( w, e ) /* Empty */ +#endif + +static Worker* __attribute__((noinline)) +lace_steal(WorkerP *self, Task *__dq_head, Worker *victim) +{ + if (!victim->allstolen) { + /* Must be a volatile. In GCC 4.8, if it is not declared volatile, the + compiler will optimize extra memory accesses to victim->ts instead + of comparing the local values ts.ts.tail and ts.ts.split, causing + thieves to steal non existent tasks! */ + register TailSplit ts; + ts.v = *(volatile uint64_t *)&victim->ts.v; + if (ts.ts.tail < ts.ts.split) { + register TailSplit ts_new; + ts_new.v = ts.v; + ts_new.ts.tail++; + if (__sync_bool_compare_and_swap(&victim->ts.v, ts.v, ts_new.v)) { + // Stolen + Task *t = &victim->dq[ts.ts.tail]; + t->thief = self->_public; + lace_time_event(self, 1); + t->f(self, __dq_head, t); + lace_time_event(self, 2); + t->thief = THIEF_COMPLETED; + lace_time_event(self, 8); + return LACE_STOLEN; + } + + lace_time_event(self, 7); + return LACE_BUSY; + } + + if (victim->movesplit == 0) { + victim->movesplit = 1; + PR_COUNTSPLITS(self, CTR_split_req); + } + } + + lace_time_event(self, 7); + return LACE_NOWORK; +} + +static int +lace_shrink_shared(WorkerP *w) +{ + Worker *wt = w->_public; + TailSplit ts; + ts.v = wt->ts.v; /* Force in 1 memory read */ + uint32_t tail = ts.ts.tail; + uint32_t split = ts.ts.split; + + if (tail != split) { + uint32_t newsplit = (tail + split)/2; + wt->ts.ts.split = newsplit; + mfence(); + tail = *(volatile uint32_t *)&(wt->ts.ts.tail); + if (tail != split) { + if (unlikely(tail > newsplit)) { + newsplit = (tail + split) / 2; + wt->ts.ts.split = newsplit; + } + w->split = w->dq + newsplit; + PR_COUNTSPLITS(w, CTR_split_shrink); + return 0; + } + } + + wt->allstolen = 1; + w->allstolen = 1; + return 1; +} + +static inline void +lace_leapfrog(WorkerP *__lace_worker, Task *__lace_dq_head) +{ + lace_time_event(__lace_worker, 3); + Task *t = __lace_dq_head; + Worker *thief = t->thief; + if (thief != THIEF_COMPLETED) { + while ((size_t)thief <= 1) thief = t->thief; + + /* PRE-LEAP: increase head again */ + __lace_dq_head += 1; + + /* Now leapfrog */ + int attempts = 32; + while (thief != THIEF_COMPLETED) { + PR_COUNTSTEALS(__lace_worker, CTR_leap_tries); + Worker *res = lace_steal(__lace_worker, __lace_dq_head, thief); + if (res == LACE_NOWORK) { + YIELD_NEWFRAME(); + if ((LACE_LEAP_RANDOM) && (--attempts == 0)) { lace_steal_random(); attempts = 32; } + } else if (res == LACE_STOLEN) { + PR_COUNTSTEALS(__lace_worker, CTR_leaps); + } else if (res == LACE_BUSY) { + PR_COUNTSTEALS(__lace_worker, CTR_leap_busy); + } + compiler_barrier(); + thief = t->thief; + } + + /* POST-LEAP: really pop the finished task */ + /* no need to decrease __lace_dq_head, since it is a local variable */ + compiler_barrier(); + if (__lace_worker->allstolen == 0) { + /* Assume: tail = split = head (pre-pop) */ + /* Now we do a real pop ergo either decrease tail,split,head or declare allstolen */ + Worker *wt = __lace_worker->_public; + wt->allstolen = 1; + __lace_worker->allstolen = 1; + } + } + + compiler_barrier(); + t->thief = THIEF_EMPTY; + lace_time_event(__lace_worker, 4); +} + +static __attribute__((noinline)) +void lace_drop_slow(WorkerP *w, Task *__dq_head) +{ + if ((w->allstolen) || (w->split > __dq_head && lace_shrink_shared(w))) lace_leapfrog(w, __dq_head); +} + +static inline __attribute__((unused)) +void lace_drop(WorkerP *w, Task *__dq_head) +{ + if (likely(0 == w->_public->movesplit)) { + if (likely(w->split <= __dq_head)) { + return; + } + } + lace_drop_slow(w, __dq_head); +} + + + +// Task macros for tasks of arity 0 + +#define TASK_DECL_0(RTYPE, NAME) \ + \ +typedef struct _TD_##NAME { \ + TASK_COMMON_FIELDS(_TD_##NAME) \ + union { RTYPE res; } d; \ +} TD_##NAME; \ + \ +/* If this line generates an error, please manually set the define LACE_TASKSIZE to a higher value */\ +typedef char assertion_failed_task_descriptor_out_of_bounds_##NAME[(sizeof(TD_##NAME)<=sizeof(Task)) ? 0 : -1];\ + \ +void NAME##_WRAP(WorkerP *, Task *, TD_##NAME *); \ +RTYPE NAME##_CALL(WorkerP *, Task * ); \ +static inline RTYPE NAME##_SYNC(WorkerP *, Task *); \ +static RTYPE NAME##_SYNC_SLOW(WorkerP *, Task *); \ + \ +static inline __attribute__((unused)) \ +void NAME##_SPAWN(WorkerP *w, Task *__dq_head ) \ +{ \ + PR_COUNTTASK(w); \ + \ + TD_##NAME *t; \ + TailSplit ts; \ + uint32_t head, split, newsplit; \ + \ + /* assert(__dq_head < w->end); */ /* Assuming to be true */ \ + \ + t = (TD_##NAME *)__dq_head; \ + t->f = &NAME##_WRAP; \ + t->thief = THIEF_TASK; \ + \ + compiler_barrier(); \ + \ + Worker *wt = w->_public; \ + if (unlikely(w->allstolen)) { \ + if (wt->movesplit) wt->movesplit = 0; \ + head = __dq_head - w->dq; \ + ts = (TailSplit){{head,head+1}}; \ + wt->ts.v = ts.v; \ + compiler_barrier(); \ + wt->allstolen = 0; \ + w->split = __dq_head+1; \ + w->allstolen = 0; \ + } else if (unlikely(wt->movesplit)) { \ + head = __dq_head - w->dq; \ + split = w->split - w->dq; \ + newsplit = (split + head + 2)/2; \ + wt->ts.ts.split = newsplit; \ + w->split = w->dq + newsplit; \ + compiler_barrier(); \ + wt->movesplit = 0; \ + PR_COUNTSPLITS(w, CTR_split_grow); \ + } \ +} \ + \ +static inline __attribute__((unused)) \ +RTYPE NAME##_NEWFRAME(WorkerP *w, Task *__dq_head ) \ +{ \ + Task _t; \ + TD_##NAME *t = (TD_##NAME *)&_t; \ + t->f = &NAME##_WRAP; \ + t->thief = THIEF_TASK; \ + \ + \ + lace_do_newframe(w, __dq_head, &_t); \ + return ((TD_##NAME *)t)->d.res; \ +} \ + \ +static inline __attribute__((unused)) \ +void NAME##_TOGETHER(WorkerP *w, Task *__dq_head ) \ +{ \ + Task _t; \ + TD_##NAME *t = (TD_##NAME *)&_t; \ + t->f = &NAME##_WRAP; \ + t->thief = THIEF_TASK; \ + \ + \ + lace_do_together(w, __dq_head, &_t); \ +} \ + \ +static __attribute__((noinline)) \ +RTYPE NAME##_SYNC_SLOW(WorkerP *w, Task *__dq_head) \ +{ \ + TD_##NAME *t; \ + \ + if ((w->allstolen) || (w->split > __dq_head && lace_shrink_shared(w))) { \ + lace_leapfrog(w, __dq_head); \ + t = (TD_##NAME *)__dq_head; \ + return ((TD_##NAME *)t)->d.res; \ + } \ + \ + compiler_barrier(); \ + \ + Worker *wt = w->_public; \ + if (wt->movesplit) { \ + Task *t = w->split; \ + size_t diff = __dq_head - t; \ + diff = (diff + 1) / 2; \ + w->split = t + diff; \ + wt->ts.ts.split += diff; \ + compiler_barrier(); \ + wt->movesplit = 0; \ + PR_COUNTSPLITS(w, CTR_split_grow); \ + } \ + \ + compiler_barrier(); \ + \ + t = (TD_##NAME *)__dq_head; \ + t->thief = THIEF_EMPTY; \ + return NAME##_CALL(w, __dq_head ); \ +} \ + \ +static inline __attribute__((unused)) \ +RTYPE NAME##_SYNC(WorkerP *w, Task *__dq_head) \ +{ \ + /* assert (__dq_head > 0); */ /* Commented out because we assume contract */ \ + \ + if (likely(0 == w->_public->movesplit)) { \ + if (likely(w->split <= __dq_head)) { \ + TD_##NAME *t = (TD_##NAME *)__dq_head; \ + t->thief = THIEF_EMPTY; \ + return NAME##_CALL(w, __dq_head ); \ + } \ + } \ + \ + return NAME##_SYNC_SLOW(w, __dq_head); \ +} \ + \ + \ + +#define TASK_IMPL_0(RTYPE, NAME) \ +void NAME##_WRAP(WorkerP *w, Task *__dq_head, TD_##NAME *t __attribute__((unused))) \ +{ \ + t->d.res = NAME##_CALL(w, __dq_head ); \ +} \ + \ +static inline __attribute__((always_inline)) \ +RTYPE NAME##_WORK(WorkerP *__lace_worker, Task *__lace_dq_head ); \ + \ +/* NAME##_WORK is inlined in NAME##_CALL and the parameter __lace_in_task will disappear */\ +RTYPE NAME##_CALL(WorkerP *w, Task *__dq_head ) \ +{ \ + CHECKSTACK(w); \ + return NAME##_WORK(w, __dq_head ); \ +} \ + \ +static inline __attribute__((always_inline)) \ +RTYPE NAME##_WORK(WorkerP *__lace_worker __attribute__((unused)), Task *__lace_dq_head __attribute__((unused)) )\ + +#define TASK_0(RTYPE, NAME) TASK_DECL_0(RTYPE, NAME) TASK_IMPL_0(RTYPE, NAME) + +#define VOID_TASK_DECL_0(NAME) \ + \ +typedef struct _TD_##NAME { \ + TASK_COMMON_FIELDS(_TD_##NAME) \ + \ +} TD_##NAME; \ + \ +/* If this line generates an error, please manually set the define LACE_TASKSIZE to a higher value */\ +typedef char assertion_failed_task_descriptor_out_of_bounds_##NAME[(sizeof(TD_##NAME)<=sizeof(Task)) ? 0 : -1];\ + \ +void NAME##_WRAP(WorkerP *, Task *, TD_##NAME *); \ +void NAME##_CALL(WorkerP *, Task * ); \ +static inline void NAME##_SYNC(WorkerP *, Task *); \ +static void NAME##_SYNC_SLOW(WorkerP *, Task *); \ + \ +static inline __attribute__((unused)) \ +void NAME##_SPAWN(WorkerP *w, Task *__dq_head ) \ +{ \ + PR_COUNTTASK(w); \ + \ + TD_##NAME *t; \ + TailSplit ts; \ + uint32_t head, split, newsplit; \ + \ + /* assert(__dq_head < w->end); */ /* Assuming to be true */ \ + \ + t = (TD_##NAME *)__dq_head; \ + t->f = &NAME##_WRAP; \ + t->thief = THIEF_TASK; \ + \ + compiler_barrier(); \ + \ + Worker *wt = w->_public; \ + if (unlikely(w->allstolen)) { \ + if (wt->movesplit) wt->movesplit = 0; \ + head = __dq_head - w->dq; \ + ts = (TailSplit){{head,head+1}}; \ + wt->ts.v = ts.v; \ + compiler_barrier(); \ + wt->allstolen = 0; \ + w->split = __dq_head+1; \ + w->allstolen = 0; \ + } else if (unlikely(wt->movesplit)) { \ + head = __dq_head - w->dq; \ + split = w->split - w->dq; \ + newsplit = (split + head + 2)/2; \ + wt->ts.ts.split = newsplit; \ + w->split = w->dq + newsplit; \ + compiler_barrier(); \ + wt->movesplit = 0; \ + PR_COUNTSPLITS(w, CTR_split_grow); \ + } \ +} \ + \ +static inline __attribute__((unused)) \ +void NAME##_NEWFRAME(WorkerP *w, Task *__dq_head ) \ +{ \ + Task _t; \ + TD_##NAME *t = (TD_##NAME *)&_t; \ + t->f = &NAME##_WRAP; \ + t->thief = THIEF_TASK; \ + \ + \ + lace_do_newframe(w, __dq_head, &_t); \ + return ; \ +} \ + \ +static inline __attribute__((unused)) \ +void NAME##_TOGETHER(WorkerP *w, Task *__dq_head ) \ +{ \ + Task _t; \ + TD_##NAME *t = (TD_##NAME *)&_t; \ + t->f = &NAME##_WRAP; \ + t->thief = THIEF_TASK; \ + \ + \ + lace_do_together(w, __dq_head, &_t); \ +} \ + \ +static __attribute__((noinline)) \ +void NAME##_SYNC_SLOW(WorkerP *w, Task *__dq_head) \ +{ \ + TD_##NAME *t; \ + \ + if ((w->allstolen) || (w->split > __dq_head && lace_shrink_shared(w))) { \ + lace_leapfrog(w, __dq_head); \ + t = (TD_##NAME *)__dq_head; \ + return ; \ + } \ + \ + compiler_barrier(); \ + \ + Worker *wt = w->_public; \ + if (wt->movesplit) { \ + Task *t = w->split; \ + size_t diff = __dq_head - t; \ + diff = (diff + 1) / 2; \ + w->split = t + diff; \ + wt->ts.ts.split += diff; \ + compiler_barrier(); \ + wt->movesplit = 0; \ + PR_COUNTSPLITS(w, CTR_split_grow); \ + } \ + \ + compiler_barrier(); \ + \ + t = (TD_##NAME *)__dq_head; \ + t->thief = THIEF_EMPTY; \ + return NAME##_CALL(w, __dq_head ); \ +} \ + \ +static inline __attribute__((unused)) \ +void NAME##_SYNC(WorkerP *w, Task *__dq_head) \ +{ \ + /* assert (__dq_head > 0); */ /* Commented out because we assume contract */ \ + \ + if (likely(0 == w->_public->movesplit)) { \ + if (likely(w->split <= __dq_head)) { \ + TD_##NAME *t = (TD_##NAME *)__dq_head; \ + t->thief = THIEF_EMPTY; \ + return NAME##_CALL(w, __dq_head ); \ + } \ + } \ + \ + return NAME##_SYNC_SLOW(w, __dq_head); \ +} \ + \ + \ + +#define VOID_TASK_IMPL_0(NAME) \ +void NAME##_WRAP(WorkerP *w, Task *__dq_head, TD_##NAME *t __attribute__((unused))) \ +{ \ + NAME##_CALL(w, __dq_head ); \ +} \ + \ +static inline __attribute__((always_inline)) \ +void NAME##_WORK(WorkerP *__lace_worker, Task *__lace_dq_head ); \ + \ +/* NAME##_WORK is inlined in NAME##_CALL and the parameter __lace_in_task will disappear */\ +void NAME##_CALL(WorkerP *w, Task *__dq_head ) \ +{ \ + CHECKSTACK(w); \ + return NAME##_WORK(w, __dq_head ); \ +} \ + \ +static inline __attribute__((always_inline)) \ +void NAME##_WORK(WorkerP *__lace_worker __attribute__((unused)), Task *__lace_dq_head __attribute__((unused)) )\ + +#define VOID_TASK_0(NAME) VOID_TASK_DECL_0(NAME) VOID_TASK_IMPL_0(NAME) + + +// Task macros for tasks of arity 1 + +#define TASK_DECL_1(RTYPE, NAME, ATYPE_1) \ + \ +typedef struct _TD_##NAME { \ + TASK_COMMON_FIELDS(_TD_##NAME) \ + union { struct { ATYPE_1 arg_1; } args; RTYPE res; } d; \ +} TD_##NAME; \ + \ +/* If this line generates an error, please manually set the define LACE_TASKSIZE to a higher value */\ +typedef char assertion_failed_task_descriptor_out_of_bounds_##NAME[(sizeof(TD_##NAME)<=sizeof(Task)) ? 0 : -1];\ + \ +void NAME##_WRAP(WorkerP *, Task *, TD_##NAME *); \ +RTYPE NAME##_CALL(WorkerP *, Task * , ATYPE_1 arg_1); \ +static inline RTYPE NAME##_SYNC(WorkerP *, Task *); \ +static RTYPE NAME##_SYNC_SLOW(WorkerP *, Task *); \ + \ +static inline __attribute__((unused)) \ +void NAME##_SPAWN(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1) \ +{ \ + PR_COUNTTASK(w); \ + \ + TD_##NAME *t; \ + TailSplit ts; \ + uint32_t head, split, newsplit; \ + \ + /* assert(__dq_head < w->end); */ /* Assuming to be true */ \ + \ + t = (TD_##NAME *)__dq_head; \ + t->f = &NAME##_WRAP; \ + t->thief = THIEF_TASK; \ + t->d.args.arg_1 = arg_1; \ + compiler_barrier(); \ + \ + Worker *wt = w->_public; \ + if (unlikely(w->allstolen)) { \ + if (wt->movesplit) wt->movesplit = 0; \ + head = __dq_head - w->dq; \ + ts = (TailSplit){{head,head+1}}; \ + wt->ts.v = ts.v; \ + compiler_barrier(); \ + wt->allstolen = 0; \ + w->split = __dq_head+1; \ + w->allstolen = 0; \ + } else if (unlikely(wt->movesplit)) { \ + head = __dq_head - w->dq; \ + split = w->split - w->dq; \ + newsplit = (split + head + 2)/2; \ + wt->ts.ts.split = newsplit; \ + w->split = w->dq + newsplit; \ + compiler_barrier(); \ + wt->movesplit = 0; \ + PR_COUNTSPLITS(w, CTR_split_grow); \ + } \ +} \ + \ +static inline __attribute__((unused)) \ +RTYPE NAME##_NEWFRAME(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1) \ +{ \ + Task _t; \ + TD_##NAME *t = (TD_##NAME *)&_t; \ + t->f = &NAME##_WRAP; \ + t->thief = THIEF_TASK; \ + t->d.args.arg_1 = arg_1; \ + \ + lace_do_newframe(w, __dq_head, &_t); \ + return ((TD_##NAME *)t)->d.res; \ +} \ + \ +static inline __attribute__((unused)) \ +void NAME##_TOGETHER(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1) \ +{ \ + Task _t; \ + TD_##NAME *t = (TD_##NAME *)&_t; \ + t->f = &NAME##_WRAP; \ + t->thief = THIEF_TASK; \ + t->d.args.arg_1 = arg_1; \ + \ + lace_do_together(w, __dq_head, &_t); \ +} \ + \ +static __attribute__((noinline)) \ +RTYPE NAME##_SYNC_SLOW(WorkerP *w, Task *__dq_head) \ +{ \ + TD_##NAME *t; \ + \ + if ((w->allstolen) || (w->split > __dq_head && lace_shrink_shared(w))) { \ + lace_leapfrog(w, __dq_head); \ + t = (TD_##NAME *)__dq_head; \ + return ((TD_##NAME *)t)->d.res; \ + } \ + \ + compiler_barrier(); \ + \ + Worker *wt = w->_public; \ + if (wt->movesplit) { \ + Task *t = w->split; \ + size_t diff = __dq_head - t; \ + diff = (diff + 1) / 2; \ + w->split = t + diff; \ + wt->ts.ts.split += diff; \ + compiler_barrier(); \ + wt->movesplit = 0; \ + PR_COUNTSPLITS(w, CTR_split_grow); \ + } \ + \ + compiler_barrier(); \ + \ + t = (TD_##NAME *)__dq_head; \ + t->thief = THIEF_EMPTY; \ + return NAME##_CALL(w, __dq_head , t->d.args.arg_1); \ +} \ + \ +static inline __attribute__((unused)) \ +RTYPE NAME##_SYNC(WorkerP *w, Task *__dq_head) \ +{ \ + /* assert (__dq_head > 0); */ /* Commented out because we assume contract */ \ + \ + if (likely(0 == w->_public->movesplit)) { \ + if (likely(w->split <= __dq_head)) { \ + TD_##NAME *t = (TD_##NAME *)__dq_head; \ + t->thief = THIEF_EMPTY; \ + return NAME##_CALL(w, __dq_head , t->d.args.arg_1); \ + } \ + } \ + \ + return NAME##_SYNC_SLOW(w, __dq_head); \ +} \ + \ + \ + +#define TASK_IMPL_1(RTYPE, NAME, ATYPE_1, ARG_1) \ +void NAME##_WRAP(WorkerP *w, Task *__dq_head, TD_##NAME *t __attribute__((unused))) \ +{ \ + t->d.res = NAME##_CALL(w, __dq_head , t->d.args.arg_1); \ +} \ + \ +static inline __attribute__((always_inline)) \ +RTYPE NAME##_WORK(WorkerP *__lace_worker, Task *__lace_dq_head , ATYPE_1); \ + \ +/* NAME##_WORK is inlined in NAME##_CALL and the parameter __lace_in_task will disappear */\ +RTYPE NAME##_CALL(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1) \ +{ \ + CHECKSTACK(w); \ + return NAME##_WORK(w, __dq_head , arg_1); \ +} \ + \ +static inline __attribute__((always_inline)) \ +RTYPE NAME##_WORK(WorkerP *__lace_worker __attribute__((unused)), Task *__lace_dq_head __attribute__((unused)) , ATYPE_1 ARG_1)\ + +#define TASK_1(RTYPE, NAME, ATYPE_1, ARG_1) TASK_DECL_1(RTYPE, NAME, ATYPE_1) TASK_IMPL_1(RTYPE, NAME, ATYPE_1, ARG_1) + +#define VOID_TASK_DECL_1(NAME, ATYPE_1) \ + \ +typedef struct _TD_##NAME { \ + TASK_COMMON_FIELDS(_TD_##NAME) \ + union { struct { ATYPE_1 arg_1; } args; } d; \ +} TD_##NAME; \ + \ +/* If this line generates an error, please manually set the define LACE_TASKSIZE to a higher value */\ +typedef char assertion_failed_task_descriptor_out_of_bounds_##NAME[(sizeof(TD_##NAME)<=sizeof(Task)) ? 0 : -1];\ + \ +void NAME##_WRAP(WorkerP *, Task *, TD_##NAME *); \ +void NAME##_CALL(WorkerP *, Task * , ATYPE_1 arg_1); \ +static inline void NAME##_SYNC(WorkerP *, Task *); \ +static void NAME##_SYNC_SLOW(WorkerP *, Task *); \ + \ +static inline __attribute__((unused)) \ +void NAME##_SPAWN(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1) \ +{ \ + PR_COUNTTASK(w); \ + \ + TD_##NAME *t; \ + TailSplit ts; \ + uint32_t head, split, newsplit; \ + \ + /* assert(__dq_head < w->end); */ /* Assuming to be true */ \ + \ + t = (TD_##NAME *)__dq_head; \ + t->f = &NAME##_WRAP; \ + t->thief = THIEF_TASK; \ + t->d.args.arg_1 = arg_1; \ + compiler_barrier(); \ + \ + Worker *wt = w->_public; \ + if (unlikely(w->allstolen)) { \ + if (wt->movesplit) wt->movesplit = 0; \ + head = __dq_head - w->dq; \ + ts = (TailSplit){{head,head+1}}; \ + wt->ts.v = ts.v; \ + compiler_barrier(); \ + wt->allstolen = 0; \ + w->split = __dq_head+1; \ + w->allstolen = 0; \ + } else if (unlikely(wt->movesplit)) { \ + head = __dq_head - w->dq; \ + split = w->split - w->dq; \ + newsplit = (split + head + 2)/2; \ + wt->ts.ts.split = newsplit; \ + w->split = w->dq + newsplit; \ + compiler_barrier(); \ + wt->movesplit = 0; \ + PR_COUNTSPLITS(w, CTR_split_grow); \ + } \ +} \ + \ +static inline __attribute__((unused)) \ +void NAME##_NEWFRAME(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1) \ +{ \ + Task _t; \ + TD_##NAME *t = (TD_##NAME *)&_t; \ + t->f = &NAME##_WRAP; \ + t->thief = THIEF_TASK; \ + t->d.args.arg_1 = arg_1; \ + \ + lace_do_newframe(w, __dq_head, &_t); \ + return ; \ +} \ + \ +static inline __attribute__((unused)) \ +void NAME##_TOGETHER(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1) \ +{ \ + Task _t; \ + TD_##NAME *t = (TD_##NAME *)&_t; \ + t->f = &NAME##_WRAP; \ + t->thief = THIEF_TASK; \ + t->d.args.arg_1 = arg_1; \ + \ + lace_do_together(w, __dq_head, &_t); \ +} \ + \ +static __attribute__((noinline)) \ +void NAME##_SYNC_SLOW(WorkerP *w, Task *__dq_head) \ +{ \ + TD_##NAME *t; \ + \ + if ((w->allstolen) || (w->split > __dq_head && lace_shrink_shared(w))) { \ + lace_leapfrog(w, __dq_head); \ + t = (TD_##NAME *)__dq_head; \ + return ; \ + } \ + \ + compiler_barrier(); \ + \ + Worker *wt = w->_public; \ + if (wt->movesplit) { \ + Task *t = w->split; \ + size_t diff = __dq_head - t; \ + diff = (diff + 1) / 2; \ + w->split = t + diff; \ + wt->ts.ts.split += diff; \ + compiler_barrier(); \ + wt->movesplit = 0; \ + PR_COUNTSPLITS(w, CTR_split_grow); \ + } \ + \ + compiler_barrier(); \ + \ + t = (TD_##NAME *)__dq_head; \ + t->thief = THIEF_EMPTY; \ + return NAME##_CALL(w, __dq_head , t->d.args.arg_1); \ +} \ + \ +static inline __attribute__((unused)) \ +void NAME##_SYNC(WorkerP *w, Task *__dq_head) \ +{ \ + /* assert (__dq_head > 0); */ /* Commented out because we assume contract */ \ + \ + if (likely(0 == w->_public->movesplit)) { \ + if (likely(w->split <= __dq_head)) { \ + TD_##NAME *t = (TD_##NAME *)__dq_head; \ + t->thief = THIEF_EMPTY; \ + return NAME##_CALL(w, __dq_head , t->d.args.arg_1); \ + } \ + } \ + \ + return NAME##_SYNC_SLOW(w, __dq_head); \ +} \ + \ + \ + +#define VOID_TASK_IMPL_1(NAME, ATYPE_1, ARG_1) \ +void NAME##_WRAP(WorkerP *w, Task *__dq_head, TD_##NAME *t __attribute__((unused))) \ +{ \ + NAME##_CALL(w, __dq_head , t->d.args.arg_1); \ +} \ + \ +static inline __attribute__((always_inline)) \ +void NAME##_WORK(WorkerP *__lace_worker, Task *__lace_dq_head , ATYPE_1); \ + \ +/* NAME##_WORK is inlined in NAME##_CALL and the parameter __lace_in_task will disappear */\ +void NAME##_CALL(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1) \ +{ \ + CHECKSTACK(w); \ + return NAME##_WORK(w, __dq_head , arg_1); \ +} \ + \ +static inline __attribute__((always_inline)) \ +void NAME##_WORK(WorkerP *__lace_worker __attribute__((unused)), Task *__lace_dq_head __attribute__((unused)) , ATYPE_1 ARG_1)\ + +#define VOID_TASK_1(NAME, ATYPE_1, ARG_1) VOID_TASK_DECL_1(NAME, ATYPE_1) VOID_TASK_IMPL_1(NAME, ATYPE_1, ARG_1) + + +// Task macros for tasks of arity 2 + +#define TASK_DECL_2(RTYPE, NAME, ATYPE_1, ATYPE_2) \ + \ +typedef struct _TD_##NAME { \ + TASK_COMMON_FIELDS(_TD_##NAME) \ + union { struct { ATYPE_1 arg_1; ATYPE_2 arg_2; } args; RTYPE res; } d; \ +} TD_##NAME; \ + \ +/* If this line generates an error, please manually set the define LACE_TASKSIZE to a higher value */\ +typedef char assertion_failed_task_descriptor_out_of_bounds_##NAME[(sizeof(TD_##NAME)<=sizeof(Task)) ? 0 : -1];\ + \ +void NAME##_WRAP(WorkerP *, Task *, TD_##NAME *); \ +RTYPE NAME##_CALL(WorkerP *, Task * , ATYPE_1 arg_1, ATYPE_2 arg_2); \ +static inline RTYPE NAME##_SYNC(WorkerP *, Task *); \ +static RTYPE NAME##_SYNC_SLOW(WorkerP *, Task *); \ + \ +static inline __attribute__((unused)) \ +void NAME##_SPAWN(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2) \ +{ \ + PR_COUNTTASK(w); \ + \ + TD_##NAME *t; \ + TailSplit ts; \ + uint32_t head, split, newsplit; \ + \ + /* assert(__dq_head < w->end); */ /* Assuming to be true */ \ + \ + t = (TD_##NAME *)__dq_head; \ + t->f = &NAME##_WRAP; \ + t->thief = THIEF_TASK; \ + t->d.args.arg_1 = arg_1; t->d.args.arg_2 = arg_2; \ + compiler_barrier(); \ + \ + Worker *wt = w->_public; \ + if (unlikely(w->allstolen)) { \ + if (wt->movesplit) wt->movesplit = 0; \ + head = __dq_head - w->dq; \ + ts = (TailSplit){{head,head+1}}; \ + wt->ts.v = ts.v; \ + compiler_barrier(); \ + wt->allstolen = 0; \ + w->split = __dq_head+1; \ + w->allstolen = 0; \ + } else if (unlikely(wt->movesplit)) { \ + head = __dq_head - w->dq; \ + split = w->split - w->dq; \ + newsplit = (split + head + 2)/2; \ + wt->ts.ts.split = newsplit; \ + w->split = w->dq + newsplit; \ + compiler_barrier(); \ + wt->movesplit = 0; \ + PR_COUNTSPLITS(w, CTR_split_grow); \ + } \ +} \ + \ +static inline __attribute__((unused)) \ +RTYPE NAME##_NEWFRAME(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2) \ +{ \ + Task _t; \ + TD_##NAME *t = (TD_##NAME *)&_t; \ + t->f = &NAME##_WRAP; \ + t->thief = THIEF_TASK; \ + t->d.args.arg_1 = arg_1; t->d.args.arg_2 = arg_2; \ + \ + lace_do_newframe(w, __dq_head, &_t); \ + return ((TD_##NAME *)t)->d.res; \ +} \ + \ +static inline __attribute__((unused)) \ +void NAME##_TOGETHER(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2) \ +{ \ + Task _t; \ + TD_##NAME *t = (TD_##NAME *)&_t; \ + t->f = &NAME##_WRAP; \ + t->thief = THIEF_TASK; \ + t->d.args.arg_1 = arg_1; t->d.args.arg_2 = arg_2; \ + \ + lace_do_together(w, __dq_head, &_t); \ +} \ + \ +static __attribute__((noinline)) \ +RTYPE NAME##_SYNC_SLOW(WorkerP *w, Task *__dq_head) \ +{ \ + TD_##NAME *t; \ + \ + if ((w->allstolen) || (w->split > __dq_head && lace_shrink_shared(w))) { \ + lace_leapfrog(w, __dq_head); \ + t = (TD_##NAME *)__dq_head; \ + return ((TD_##NAME *)t)->d.res; \ + } \ + \ + compiler_barrier(); \ + \ + Worker *wt = w->_public; \ + if (wt->movesplit) { \ + Task *t = w->split; \ + size_t diff = __dq_head - t; \ + diff = (diff + 1) / 2; \ + w->split = t + diff; \ + wt->ts.ts.split += diff; \ + compiler_barrier(); \ + wt->movesplit = 0; \ + PR_COUNTSPLITS(w, CTR_split_grow); \ + } \ + \ + compiler_barrier(); \ + \ + t = (TD_##NAME *)__dq_head; \ + t->thief = THIEF_EMPTY; \ + return NAME##_CALL(w, __dq_head , t->d.args.arg_1, t->d.args.arg_2); \ +} \ + \ +static inline __attribute__((unused)) \ +RTYPE NAME##_SYNC(WorkerP *w, Task *__dq_head) \ +{ \ + /* assert (__dq_head > 0); */ /* Commented out because we assume contract */ \ + \ + if (likely(0 == w->_public->movesplit)) { \ + if (likely(w->split <= __dq_head)) { \ + TD_##NAME *t = (TD_##NAME *)__dq_head; \ + t->thief = THIEF_EMPTY; \ + return NAME##_CALL(w, __dq_head , t->d.args.arg_1, t->d.args.arg_2); \ + } \ + } \ + \ + return NAME##_SYNC_SLOW(w, __dq_head); \ +} \ + \ + \ + +#define TASK_IMPL_2(RTYPE, NAME, ATYPE_1, ARG_1, ATYPE_2, ARG_2) \ +void NAME##_WRAP(WorkerP *w, Task *__dq_head, TD_##NAME *t __attribute__((unused))) \ +{ \ + t->d.res = NAME##_CALL(w, __dq_head , t->d.args.arg_1, t->d.args.arg_2); \ +} \ + \ +static inline __attribute__((always_inline)) \ +RTYPE NAME##_WORK(WorkerP *__lace_worker, Task *__lace_dq_head , ATYPE_1, ATYPE_2); \ + \ +/* NAME##_WORK is inlined in NAME##_CALL and the parameter __lace_in_task will disappear */\ +RTYPE NAME##_CALL(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2) \ +{ \ + CHECKSTACK(w); \ + return NAME##_WORK(w, __dq_head , arg_1, arg_2); \ +} \ + \ +static inline __attribute__((always_inline)) \ +RTYPE NAME##_WORK(WorkerP *__lace_worker __attribute__((unused)), Task *__lace_dq_head __attribute__((unused)) , ATYPE_1 ARG_1, ATYPE_2 ARG_2)\ + +#define TASK_2(RTYPE, NAME, ATYPE_1, ARG_1, ATYPE_2, ARG_2) TASK_DECL_2(RTYPE, NAME, ATYPE_1, ATYPE_2) TASK_IMPL_2(RTYPE, NAME, ATYPE_1, ARG_1, ATYPE_2, ARG_2) + +#define VOID_TASK_DECL_2(NAME, ATYPE_1, ATYPE_2) \ + \ +typedef struct _TD_##NAME { \ + TASK_COMMON_FIELDS(_TD_##NAME) \ + union { struct { ATYPE_1 arg_1; ATYPE_2 arg_2; } args; } d; \ +} TD_##NAME; \ + \ +/* If this line generates an error, please manually set the define LACE_TASKSIZE to a higher value */\ +typedef char assertion_failed_task_descriptor_out_of_bounds_##NAME[(sizeof(TD_##NAME)<=sizeof(Task)) ? 0 : -1];\ + \ +void NAME##_WRAP(WorkerP *, Task *, TD_##NAME *); \ +void NAME##_CALL(WorkerP *, Task * , ATYPE_1 arg_1, ATYPE_2 arg_2); \ +static inline void NAME##_SYNC(WorkerP *, Task *); \ +static void NAME##_SYNC_SLOW(WorkerP *, Task *); \ + \ +static inline __attribute__((unused)) \ +void NAME##_SPAWN(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2) \ +{ \ + PR_COUNTTASK(w); \ + \ + TD_##NAME *t; \ + TailSplit ts; \ + uint32_t head, split, newsplit; \ + \ + /* assert(__dq_head < w->end); */ /* Assuming to be true */ \ + \ + t = (TD_##NAME *)__dq_head; \ + t->f = &NAME##_WRAP; \ + t->thief = THIEF_TASK; \ + t->d.args.arg_1 = arg_1; t->d.args.arg_2 = arg_2; \ + compiler_barrier(); \ + \ + Worker *wt = w->_public; \ + if (unlikely(w->allstolen)) { \ + if (wt->movesplit) wt->movesplit = 0; \ + head = __dq_head - w->dq; \ + ts = (TailSplit){{head,head+1}}; \ + wt->ts.v = ts.v; \ + compiler_barrier(); \ + wt->allstolen = 0; \ + w->split = __dq_head+1; \ + w->allstolen = 0; \ + } else if (unlikely(wt->movesplit)) { \ + head = __dq_head - w->dq; \ + split = w->split - w->dq; \ + newsplit = (split + head + 2)/2; \ + wt->ts.ts.split = newsplit; \ + w->split = w->dq + newsplit; \ + compiler_barrier(); \ + wt->movesplit = 0; \ + PR_COUNTSPLITS(w, CTR_split_grow); \ + } \ +} \ + \ +static inline __attribute__((unused)) \ +void NAME##_NEWFRAME(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2) \ +{ \ + Task _t; \ + TD_##NAME *t = (TD_##NAME *)&_t; \ + t->f = &NAME##_WRAP; \ + t->thief = THIEF_TASK; \ + t->d.args.arg_1 = arg_1; t->d.args.arg_2 = arg_2; \ + \ + lace_do_newframe(w, __dq_head, &_t); \ + return ; \ +} \ + \ +static inline __attribute__((unused)) \ +void NAME##_TOGETHER(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2) \ +{ \ + Task _t; \ + TD_##NAME *t = (TD_##NAME *)&_t; \ + t->f = &NAME##_WRAP; \ + t->thief = THIEF_TASK; \ + t->d.args.arg_1 = arg_1; t->d.args.arg_2 = arg_2; \ + \ + lace_do_together(w, __dq_head, &_t); \ +} \ + \ +static __attribute__((noinline)) \ +void NAME##_SYNC_SLOW(WorkerP *w, Task *__dq_head) \ +{ \ + TD_##NAME *t; \ + \ + if ((w->allstolen) || (w->split > __dq_head && lace_shrink_shared(w))) { \ + lace_leapfrog(w, __dq_head); \ + t = (TD_##NAME *)__dq_head; \ + return ; \ + } \ + \ + compiler_barrier(); \ + \ + Worker *wt = w->_public; \ + if (wt->movesplit) { \ + Task *t = w->split; \ + size_t diff = __dq_head - t; \ + diff = (diff + 1) / 2; \ + w->split = t + diff; \ + wt->ts.ts.split += diff; \ + compiler_barrier(); \ + wt->movesplit = 0; \ + PR_COUNTSPLITS(w, CTR_split_grow); \ + } \ + \ + compiler_barrier(); \ + \ + t = (TD_##NAME *)__dq_head; \ + t->thief = THIEF_EMPTY; \ + return NAME##_CALL(w, __dq_head , t->d.args.arg_1, t->d.args.arg_2); \ +} \ + \ +static inline __attribute__((unused)) \ +void NAME##_SYNC(WorkerP *w, Task *__dq_head) \ +{ \ + /* assert (__dq_head > 0); */ /* Commented out because we assume contract */ \ + \ + if (likely(0 == w->_public->movesplit)) { \ + if (likely(w->split <= __dq_head)) { \ + TD_##NAME *t = (TD_##NAME *)__dq_head; \ + t->thief = THIEF_EMPTY; \ + return NAME##_CALL(w, __dq_head , t->d.args.arg_1, t->d.args.arg_2); \ + } \ + } \ + \ + return NAME##_SYNC_SLOW(w, __dq_head); \ +} \ + \ + \ + +#define VOID_TASK_IMPL_2(NAME, ATYPE_1, ARG_1, ATYPE_2, ARG_2) \ +void NAME##_WRAP(WorkerP *w, Task *__dq_head, TD_##NAME *t __attribute__((unused))) \ +{ \ + NAME##_CALL(w, __dq_head , t->d.args.arg_1, t->d.args.arg_2); \ +} \ + \ +static inline __attribute__((always_inline)) \ +void NAME##_WORK(WorkerP *__lace_worker, Task *__lace_dq_head , ATYPE_1, ATYPE_2); \ + \ +/* NAME##_WORK is inlined in NAME##_CALL and the parameter __lace_in_task will disappear */\ +void NAME##_CALL(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2) \ +{ \ + CHECKSTACK(w); \ + return NAME##_WORK(w, __dq_head , arg_1, arg_2); \ +} \ + \ +static inline __attribute__((always_inline)) \ +void NAME##_WORK(WorkerP *__lace_worker __attribute__((unused)), Task *__lace_dq_head __attribute__((unused)) , ATYPE_1 ARG_1, ATYPE_2 ARG_2)\ + +#define VOID_TASK_2(NAME, ATYPE_1, ARG_1, ATYPE_2, ARG_2) VOID_TASK_DECL_2(NAME, ATYPE_1, ATYPE_2) VOID_TASK_IMPL_2(NAME, ATYPE_1, ARG_1, ATYPE_2, ARG_2) + + +// Task macros for tasks of arity 3 + +#define TASK_DECL_3(RTYPE, NAME, ATYPE_1, ATYPE_2, ATYPE_3) \ + \ +typedef struct _TD_##NAME { \ + TASK_COMMON_FIELDS(_TD_##NAME) \ + union { struct { ATYPE_1 arg_1; ATYPE_2 arg_2; ATYPE_3 arg_3; } args; RTYPE res; } d;\ +} TD_##NAME; \ + \ +/* If this line generates an error, please manually set the define LACE_TASKSIZE to a higher value */\ +typedef char assertion_failed_task_descriptor_out_of_bounds_##NAME[(sizeof(TD_##NAME)<=sizeof(Task)) ? 0 : -1];\ + \ +void NAME##_WRAP(WorkerP *, Task *, TD_##NAME *); \ +RTYPE NAME##_CALL(WorkerP *, Task * , ATYPE_1 arg_1, ATYPE_2 arg_2, ATYPE_3 arg_3); \ +static inline RTYPE NAME##_SYNC(WorkerP *, Task *); \ +static RTYPE NAME##_SYNC_SLOW(WorkerP *, Task *); \ + \ +static inline __attribute__((unused)) \ +void NAME##_SPAWN(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2, ATYPE_3 arg_3)\ +{ \ + PR_COUNTTASK(w); \ + \ + TD_##NAME *t; \ + TailSplit ts; \ + uint32_t head, split, newsplit; \ + \ + /* assert(__dq_head < w->end); */ /* Assuming to be true */ \ + \ + t = (TD_##NAME *)__dq_head; \ + t->f = &NAME##_WRAP; \ + t->thief = THIEF_TASK; \ + t->d.args.arg_1 = arg_1; t->d.args.arg_2 = arg_2; t->d.args.arg_3 = arg_3; \ + compiler_barrier(); \ + \ + Worker *wt = w->_public; \ + if (unlikely(w->allstolen)) { \ + if (wt->movesplit) wt->movesplit = 0; \ + head = __dq_head - w->dq; \ + ts = (TailSplit){{head,head+1}}; \ + wt->ts.v = ts.v; \ + compiler_barrier(); \ + wt->allstolen = 0; \ + w->split = __dq_head+1; \ + w->allstolen = 0; \ + } else if (unlikely(wt->movesplit)) { \ + head = __dq_head - w->dq; \ + split = w->split - w->dq; \ + newsplit = (split + head + 2)/2; \ + wt->ts.ts.split = newsplit; \ + w->split = w->dq + newsplit; \ + compiler_barrier(); \ + wt->movesplit = 0; \ + PR_COUNTSPLITS(w, CTR_split_grow); \ + } \ +} \ + \ +static inline __attribute__((unused)) \ +RTYPE NAME##_NEWFRAME(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2, ATYPE_3 arg_3)\ +{ \ + Task _t; \ + TD_##NAME *t = (TD_##NAME *)&_t; \ + t->f = &NAME##_WRAP; \ + t->thief = THIEF_TASK; \ + t->d.args.arg_1 = arg_1; t->d.args.arg_2 = arg_2; t->d.args.arg_3 = arg_3; \ + \ + lace_do_newframe(w, __dq_head, &_t); \ + return ((TD_##NAME *)t)->d.res; \ +} \ + \ +static inline __attribute__((unused)) \ +void NAME##_TOGETHER(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2, ATYPE_3 arg_3)\ +{ \ + Task _t; \ + TD_##NAME *t = (TD_##NAME *)&_t; \ + t->f = &NAME##_WRAP; \ + t->thief = THIEF_TASK; \ + t->d.args.arg_1 = arg_1; t->d.args.arg_2 = arg_2; t->d.args.arg_3 = arg_3; \ + \ + lace_do_together(w, __dq_head, &_t); \ +} \ + \ +static __attribute__((noinline)) \ +RTYPE NAME##_SYNC_SLOW(WorkerP *w, Task *__dq_head) \ +{ \ + TD_##NAME *t; \ + \ + if ((w->allstolen) || (w->split > __dq_head && lace_shrink_shared(w))) { \ + lace_leapfrog(w, __dq_head); \ + t = (TD_##NAME *)__dq_head; \ + return ((TD_##NAME *)t)->d.res; \ + } \ + \ + compiler_barrier(); \ + \ + Worker *wt = w->_public; \ + if (wt->movesplit) { \ + Task *t = w->split; \ + size_t diff = __dq_head - t; \ + diff = (diff + 1) / 2; \ + w->split = t + diff; \ + wt->ts.ts.split += diff; \ + compiler_barrier(); \ + wt->movesplit = 0; \ + PR_COUNTSPLITS(w, CTR_split_grow); \ + } \ + \ + compiler_barrier(); \ + \ + t = (TD_##NAME *)__dq_head; \ + t->thief = THIEF_EMPTY; \ + return NAME##_CALL(w, __dq_head , t->d.args.arg_1, t->d.args.arg_2, t->d.args.arg_3);\ +} \ + \ +static inline __attribute__((unused)) \ +RTYPE NAME##_SYNC(WorkerP *w, Task *__dq_head) \ +{ \ + /* assert (__dq_head > 0); */ /* Commented out because we assume contract */ \ + \ + if (likely(0 == w->_public->movesplit)) { \ + if (likely(w->split <= __dq_head)) { \ + TD_##NAME *t = (TD_##NAME *)__dq_head; \ + t->thief = THIEF_EMPTY; \ + return NAME##_CALL(w, __dq_head , t->d.args.arg_1, t->d.args.arg_2, t->d.args.arg_3);\ + } \ + } \ + \ + return NAME##_SYNC_SLOW(w, __dq_head); \ +} \ + \ + \ + +#define TASK_IMPL_3(RTYPE, NAME, ATYPE_1, ARG_1, ATYPE_2, ARG_2, ATYPE_3, ARG_3) \ +void NAME##_WRAP(WorkerP *w, Task *__dq_head, TD_##NAME *t __attribute__((unused))) \ +{ \ + t->d.res = NAME##_CALL(w, __dq_head , t->d.args.arg_1, t->d.args.arg_2, t->d.args.arg_3);\ +} \ + \ +static inline __attribute__((always_inline)) \ +RTYPE NAME##_WORK(WorkerP *__lace_worker, Task *__lace_dq_head , ATYPE_1, ATYPE_2, ATYPE_3);\ + \ +/* NAME##_WORK is inlined in NAME##_CALL and the parameter __lace_in_task will disappear */\ +RTYPE NAME##_CALL(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2, ATYPE_3 arg_3)\ +{ \ + CHECKSTACK(w); \ + return NAME##_WORK(w, __dq_head , arg_1, arg_2, arg_3); \ +} \ + \ +static inline __attribute__((always_inline)) \ +RTYPE NAME##_WORK(WorkerP *__lace_worker __attribute__((unused)), Task *__lace_dq_head __attribute__((unused)) , ATYPE_1 ARG_1, ATYPE_2 ARG_2, ATYPE_3 ARG_3)\ + +#define TASK_3(RTYPE, NAME, ATYPE_1, ARG_1, ATYPE_2, ARG_2, ATYPE_3, ARG_3) TASK_DECL_3(RTYPE, NAME, ATYPE_1, ATYPE_2, ATYPE_3) TASK_IMPL_3(RTYPE, NAME, ATYPE_1, ARG_1, ATYPE_2, ARG_2, ATYPE_3, ARG_3) + +#define VOID_TASK_DECL_3(NAME, ATYPE_1, ATYPE_2, ATYPE_3) \ + \ +typedef struct _TD_##NAME { \ + TASK_COMMON_FIELDS(_TD_##NAME) \ + union { struct { ATYPE_1 arg_1; ATYPE_2 arg_2; ATYPE_3 arg_3; } args; } d; \ +} TD_##NAME; \ + \ +/* If this line generates an error, please manually set the define LACE_TASKSIZE to a higher value */\ +typedef char assertion_failed_task_descriptor_out_of_bounds_##NAME[(sizeof(TD_##NAME)<=sizeof(Task)) ? 0 : -1];\ + \ +void NAME##_WRAP(WorkerP *, Task *, TD_##NAME *); \ +void NAME##_CALL(WorkerP *, Task * , ATYPE_1 arg_1, ATYPE_2 arg_2, ATYPE_3 arg_3); \ +static inline void NAME##_SYNC(WorkerP *, Task *); \ +static void NAME##_SYNC_SLOW(WorkerP *, Task *); \ + \ +static inline __attribute__((unused)) \ +void NAME##_SPAWN(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2, ATYPE_3 arg_3)\ +{ \ + PR_COUNTTASK(w); \ + \ + TD_##NAME *t; \ + TailSplit ts; \ + uint32_t head, split, newsplit; \ + \ + /* assert(__dq_head < w->end); */ /* Assuming to be true */ \ + \ + t = (TD_##NAME *)__dq_head; \ + t->f = &NAME##_WRAP; \ + t->thief = THIEF_TASK; \ + t->d.args.arg_1 = arg_1; t->d.args.arg_2 = arg_2; t->d.args.arg_3 = arg_3; \ + compiler_barrier(); \ + \ + Worker *wt = w->_public; \ + if (unlikely(w->allstolen)) { \ + if (wt->movesplit) wt->movesplit = 0; \ + head = __dq_head - w->dq; \ + ts = (TailSplit){{head,head+1}}; \ + wt->ts.v = ts.v; \ + compiler_barrier(); \ + wt->allstolen = 0; \ + w->split = __dq_head+1; \ + w->allstolen = 0; \ + } else if (unlikely(wt->movesplit)) { \ + head = __dq_head - w->dq; \ + split = w->split - w->dq; \ + newsplit = (split + head + 2)/2; \ + wt->ts.ts.split = newsplit; \ + w->split = w->dq + newsplit; \ + compiler_barrier(); \ + wt->movesplit = 0; \ + PR_COUNTSPLITS(w, CTR_split_grow); \ + } \ +} \ + \ +static inline __attribute__((unused)) \ +void NAME##_NEWFRAME(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2, ATYPE_3 arg_3)\ +{ \ + Task _t; \ + TD_##NAME *t = (TD_##NAME *)&_t; \ + t->f = &NAME##_WRAP; \ + t->thief = THIEF_TASK; \ + t->d.args.arg_1 = arg_1; t->d.args.arg_2 = arg_2; t->d.args.arg_3 = arg_3; \ + \ + lace_do_newframe(w, __dq_head, &_t); \ + return ; \ +} \ + \ +static inline __attribute__((unused)) \ +void NAME##_TOGETHER(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2, ATYPE_3 arg_3)\ +{ \ + Task _t; \ + TD_##NAME *t = (TD_##NAME *)&_t; \ + t->f = &NAME##_WRAP; \ + t->thief = THIEF_TASK; \ + t->d.args.arg_1 = arg_1; t->d.args.arg_2 = arg_2; t->d.args.arg_3 = arg_3; \ + \ + lace_do_together(w, __dq_head, &_t); \ +} \ + \ +static __attribute__((noinline)) \ +void NAME##_SYNC_SLOW(WorkerP *w, Task *__dq_head) \ +{ \ + TD_##NAME *t; \ + \ + if ((w->allstolen) || (w->split > __dq_head && lace_shrink_shared(w))) { \ + lace_leapfrog(w, __dq_head); \ + t = (TD_##NAME *)__dq_head; \ + return ; \ + } \ + \ + compiler_barrier(); \ + \ + Worker *wt = w->_public; \ + if (wt->movesplit) { \ + Task *t = w->split; \ + size_t diff = __dq_head - t; \ + diff = (diff + 1) / 2; \ + w->split = t + diff; \ + wt->ts.ts.split += diff; \ + compiler_barrier(); \ + wt->movesplit = 0; \ + PR_COUNTSPLITS(w, CTR_split_grow); \ + } \ + \ + compiler_barrier(); \ + \ + t = (TD_##NAME *)__dq_head; \ + t->thief = THIEF_EMPTY; \ + return NAME##_CALL(w, __dq_head , t->d.args.arg_1, t->d.args.arg_2, t->d.args.arg_3);\ +} \ + \ +static inline __attribute__((unused)) \ +void NAME##_SYNC(WorkerP *w, Task *__dq_head) \ +{ \ + /* assert (__dq_head > 0); */ /* Commented out because we assume contract */ \ + \ + if (likely(0 == w->_public->movesplit)) { \ + if (likely(w->split <= __dq_head)) { \ + TD_##NAME *t = (TD_##NAME *)__dq_head; \ + t->thief = THIEF_EMPTY; \ + return NAME##_CALL(w, __dq_head , t->d.args.arg_1, t->d.args.arg_2, t->d.args.arg_3);\ + } \ + } \ + \ + return NAME##_SYNC_SLOW(w, __dq_head); \ +} \ + \ + \ + +#define VOID_TASK_IMPL_3(NAME, ATYPE_1, ARG_1, ATYPE_2, ARG_2, ATYPE_3, ARG_3) \ +void NAME##_WRAP(WorkerP *w, Task *__dq_head, TD_##NAME *t __attribute__((unused))) \ +{ \ + NAME##_CALL(w, __dq_head , t->d.args.arg_1, t->d.args.arg_2, t->d.args.arg_3); \ +} \ + \ +static inline __attribute__((always_inline)) \ +void NAME##_WORK(WorkerP *__lace_worker, Task *__lace_dq_head , ATYPE_1, ATYPE_2, ATYPE_3);\ + \ +/* NAME##_WORK is inlined in NAME##_CALL and the parameter __lace_in_task will disappear */\ +void NAME##_CALL(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2, ATYPE_3 arg_3)\ +{ \ + CHECKSTACK(w); \ + return NAME##_WORK(w, __dq_head , arg_1, arg_2, arg_3); \ +} \ + \ +static inline __attribute__((always_inline)) \ +void NAME##_WORK(WorkerP *__lace_worker __attribute__((unused)), Task *__lace_dq_head __attribute__((unused)) , ATYPE_1 ARG_1, ATYPE_2 ARG_2, ATYPE_3 ARG_3)\ + +#define VOID_TASK_3(NAME, ATYPE_1, ARG_1, ATYPE_2, ARG_2, ATYPE_3, ARG_3) VOID_TASK_DECL_3(NAME, ATYPE_1, ATYPE_2, ATYPE_3) VOID_TASK_IMPL_3(NAME, ATYPE_1, ARG_1, ATYPE_2, ARG_2, ATYPE_3, ARG_3) + + +// Task macros for tasks of arity 4 + +#define TASK_DECL_4(RTYPE, NAME, ATYPE_1, ATYPE_2, ATYPE_3, ATYPE_4) \ + \ +typedef struct _TD_##NAME { \ + TASK_COMMON_FIELDS(_TD_##NAME) \ + union { struct { ATYPE_1 arg_1; ATYPE_2 arg_2; ATYPE_3 arg_3; ATYPE_4 arg_4; } args; RTYPE res; } d;\ +} TD_##NAME; \ + \ +/* If this line generates an error, please manually set the define LACE_TASKSIZE to a higher value */\ +typedef char assertion_failed_task_descriptor_out_of_bounds_##NAME[(sizeof(TD_##NAME)<=sizeof(Task)) ? 0 : -1];\ + \ +void NAME##_WRAP(WorkerP *, Task *, TD_##NAME *); \ +RTYPE NAME##_CALL(WorkerP *, Task * , ATYPE_1 arg_1, ATYPE_2 arg_2, ATYPE_3 arg_3, ATYPE_4 arg_4);\ +static inline RTYPE NAME##_SYNC(WorkerP *, Task *); \ +static RTYPE NAME##_SYNC_SLOW(WorkerP *, Task *); \ + \ +static inline __attribute__((unused)) \ +void NAME##_SPAWN(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2, ATYPE_3 arg_3, ATYPE_4 arg_4)\ +{ \ + PR_COUNTTASK(w); \ + \ + TD_##NAME *t; \ + TailSplit ts; \ + uint32_t head, split, newsplit; \ + \ + /* assert(__dq_head < w->end); */ /* Assuming to be true */ \ + \ + t = (TD_##NAME *)__dq_head; \ + t->f = &NAME##_WRAP; \ + t->thief = THIEF_TASK; \ + t->d.args.arg_1 = arg_1; t->d.args.arg_2 = arg_2; t->d.args.arg_3 = arg_3; t->d.args.arg_4 = arg_4;\ + compiler_barrier(); \ + \ + Worker *wt = w->_public; \ + if (unlikely(w->allstolen)) { \ + if (wt->movesplit) wt->movesplit = 0; \ + head = __dq_head - w->dq; \ + ts = (TailSplit){{head,head+1}}; \ + wt->ts.v = ts.v; \ + compiler_barrier(); \ + wt->allstolen = 0; \ + w->split = __dq_head+1; \ + w->allstolen = 0; \ + } else if (unlikely(wt->movesplit)) { \ + head = __dq_head - w->dq; \ + split = w->split - w->dq; \ + newsplit = (split + head + 2)/2; \ + wt->ts.ts.split = newsplit; \ + w->split = w->dq + newsplit; \ + compiler_barrier(); \ + wt->movesplit = 0; \ + PR_COUNTSPLITS(w, CTR_split_grow); \ + } \ +} \ + \ +static inline __attribute__((unused)) \ +RTYPE NAME##_NEWFRAME(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2, ATYPE_3 arg_3, ATYPE_4 arg_4)\ +{ \ + Task _t; \ + TD_##NAME *t = (TD_##NAME *)&_t; \ + t->f = &NAME##_WRAP; \ + t->thief = THIEF_TASK; \ + t->d.args.arg_1 = arg_1; t->d.args.arg_2 = arg_2; t->d.args.arg_3 = arg_3; t->d.args.arg_4 = arg_4;\ + \ + lace_do_newframe(w, __dq_head, &_t); \ + return ((TD_##NAME *)t)->d.res; \ +} \ + \ +static inline __attribute__((unused)) \ +void NAME##_TOGETHER(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2, ATYPE_3 arg_3, ATYPE_4 arg_4)\ +{ \ + Task _t; \ + TD_##NAME *t = (TD_##NAME *)&_t; \ + t->f = &NAME##_WRAP; \ + t->thief = THIEF_TASK; \ + t->d.args.arg_1 = arg_1; t->d.args.arg_2 = arg_2; t->d.args.arg_3 = arg_3; t->d.args.arg_4 = arg_4;\ + \ + lace_do_together(w, __dq_head, &_t); \ +} \ + \ +static __attribute__((noinline)) \ +RTYPE NAME##_SYNC_SLOW(WorkerP *w, Task *__dq_head) \ +{ \ + TD_##NAME *t; \ + \ + if ((w->allstolen) || (w->split > __dq_head && lace_shrink_shared(w))) { \ + lace_leapfrog(w, __dq_head); \ + t = (TD_##NAME *)__dq_head; \ + return ((TD_##NAME *)t)->d.res; \ + } \ + \ + compiler_barrier(); \ + \ + Worker *wt = w->_public; \ + if (wt->movesplit) { \ + Task *t = w->split; \ + size_t diff = __dq_head - t; \ + diff = (diff + 1) / 2; \ + w->split = t + diff; \ + wt->ts.ts.split += diff; \ + compiler_barrier(); \ + wt->movesplit = 0; \ + PR_COUNTSPLITS(w, CTR_split_grow); \ + } \ + \ + compiler_barrier(); \ + \ + t = (TD_##NAME *)__dq_head; \ + t->thief = THIEF_EMPTY; \ + return NAME##_CALL(w, __dq_head , t->d.args.arg_1, t->d.args.arg_2, t->d.args.arg_3, t->d.args.arg_4);\ +} \ + \ +static inline __attribute__((unused)) \ +RTYPE NAME##_SYNC(WorkerP *w, Task *__dq_head) \ +{ \ + /* assert (__dq_head > 0); */ /* Commented out because we assume contract */ \ + \ + if (likely(0 == w->_public->movesplit)) { \ + if (likely(w->split <= __dq_head)) { \ + TD_##NAME *t = (TD_##NAME *)__dq_head; \ + t->thief = THIEF_EMPTY; \ + return NAME##_CALL(w, __dq_head , t->d.args.arg_1, t->d.args.arg_2, t->d.args.arg_3, t->d.args.arg_4);\ + } \ + } \ + \ + return NAME##_SYNC_SLOW(w, __dq_head); \ +} \ + \ + \ + +#define TASK_IMPL_4(RTYPE, NAME, ATYPE_1, ARG_1, ATYPE_2, ARG_2, ATYPE_3, ARG_3, ATYPE_4, ARG_4)\ +void NAME##_WRAP(WorkerP *w, Task *__dq_head, TD_##NAME *t __attribute__((unused))) \ +{ \ + t->d.res = NAME##_CALL(w, __dq_head , t->d.args.arg_1, t->d.args.arg_2, t->d.args.arg_3, t->d.args.arg_4);\ +} \ + \ +static inline __attribute__((always_inline)) \ +RTYPE NAME##_WORK(WorkerP *__lace_worker, Task *__lace_dq_head , ATYPE_1, ATYPE_2, ATYPE_3, ATYPE_4);\ + \ +/* NAME##_WORK is inlined in NAME##_CALL and the parameter __lace_in_task will disappear */\ +RTYPE NAME##_CALL(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2, ATYPE_3 arg_3, ATYPE_4 arg_4)\ +{ \ + CHECKSTACK(w); \ + return NAME##_WORK(w, __dq_head , arg_1, arg_2, arg_3, arg_4); \ +} \ + \ +static inline __attribute__((always_inline)) \ +RTYPE NAME##_WORK(WorkerP *__lace_worker __attribute__((unused)), Task *__lace_dq_head __attribute__((unused)) , ATYPE_1 ARG_1, ATYPE_2 ARG_2, ATYPE_3 ARG_3, ATYPE_4 ARG_4)\ + +#define TASK_4(RTYPE, NAME, ATYPE_1, ARG_1, ATYPE_2, ARG_2, ATYPE_3, ARG_3, ATYPE_4, ARG_4) TASK_DECL_4(RTYPE, NAME, ATYPE_1, ATYPE_2, ATYPE_3, ATYPE_4) TASK_IMPL_4(RTYPE, NAME, ATYPE_1, ARG_1, ATYPE_2, ARG_2, ATYPE_3, ARG_3, ATYPE_4, ARG_4) + +#define VOID_TASK_DECL_4(NAME, ATYPE_1, ATYPE_2, ATYPE_3, ATYPE_4) \ + \ +typedef struct _TD_##NAME { \ + TASK_COMMON_FIELDS(_TD_##NAME) \ + union { struct { ATYPE_1 arg_1; ATYPE_2 arg_2; ATYPE_3 arg_3; ATYPE_4 arg_4; } args; } d;\ +} TD_##NAME; \ + \ +/* If this line generates an error, please manually set the define LACE_TASKSIZE to a higher value */\ +typedef char assertion_failed_task_descriptor_out_of_bounds_##NAME[(sizeof(TD_##NAME)<=sizeof(Task)) ? 0 : -1];\ + \ +void NAME##_WRAP(WorkerP *, Task *, TD_##NAME *); \ +void NAME##_CALL(WorkerP *, Task * , ATYPE_1 arg_1, ATYPE_2 arg_2, ATYPE_3 arg_3, ATYPE_4 arg_4);\ +static inline void NAME##_SYNC(WorkerP *, Task *); \ +static void NAME##_SYNC_SLOW(WorkerP *, Task *); \ + \ +static inline __attribute__((unused)) \ +void NAME##_SPAWN(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2, ATYPE_3 arg_3, ATYPE_4 arg_4)\ +{ \ + PR_COUNTTASK(w); \ + \ + TD_##NAME *t; \ + TailSplit ts; \ + uint32_t head, split, newsplit; \ + \ + /* assert(__dq_head < w->end); */ /* Assuming to be true */ \ + \ + t = (TD_##NAME *)__dq_head; \ + t->f = &NAME##_WRAP; \ + t->thief = THIEF_TASK; \ + t->d.args.arg_1 = arg_1; t->d.args.arg_2 = arg_2; t->d.args.arg_3 = arg_3; t->d.args.arg_4 = arg_4;\ + compiler_barrier(); \ + \ + Worker *wt = w->_public; \ + if (unlikely(w->allstolen)) { \ + if (wt->movesplit) wt->movesplit = 0; \ + head = __dq_head - w->dq; \ + ts = (TailSplit){{head,head+1}}; \ + wt->ts.v = ts.v; \ + compiler_barrier(); \ + wt->allstolen = 0; \ + w->split = __dq_head+1; \ + w->allstolen = 0; \ + } else if (unlikely(wt->movesplit)) { \ + head = __dq_head - w->dq; \ + split = w->split - w->dq; \ + newsplit = (split + head + 2)/2; \ + wt->ts.ts.split = newsplit; \ + w->split = w->dq + newsplit; \ + compiler_barrier(); \ + wt->movesplit = 0; \ + PR_COUNTSPLITS(w, CTR_split_grow); \ + } \ +} \ + \ +static inline __attribute__((unused)) \ +void NAME##_NEWFRAME(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2, ATYPE_3 arg_3, ATYPE_4 arg_4)\ +{ \ + Task _t; \ + TD_##NAME *t = (TD_##NAME *)&_t; \ + t->f = &NAME##_WRAP; \ + t->thief = THIEF_TASK; \ + t->d.args.arg_1 = arg_1; t->d.args.arg_2 = arg_2; t->d.args.arg_3 = arg_3; t->d.args.arg_4 = arg_4;\ + \ + lace_do_newframe(w, __dq_head, &_t); \ + return ; \ +} \ + \ +static inline __attribute__((unused)) \ +void NAME##_TOGETHER(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2, ATYPE_3 arg_3, ATYPE_4 arg_4)\ +{ \ + Task _t; \ + TD_##NAME *t = (TD_##NAME *)&_t; \ + t->f = &NAME##_WRAP; \ + t->thief = THIEF_TASK; \ + t->d.args.arg_1 = arg_1; t->d.args.arg_2 = arg_2; t->d.args.arg_3 = arg_3; t->d.args.arg_4 = arg_4;\ + \ + lace_do_together(w, __dq_head, &_t); \ +} \ + \ +static __attribute__((noinline)) \ +void NAME##_SYNC_SLOW(WorkerP *w, Task *__dq_head) \ +{ \ + TD_##NAME *t; \ + \ + if ((w->allstolen) || (w->split > __dq_head && lace_shrink_shared(w))) { \ + lace_leapfrog(w, __dq_head); \ + t = (TD_##NAME *)__dq_head; \ + return ; \ + } \ + \ + compiler_barrier(); \ + \ + Worker *wt = w->_public; \ + if (wt->movesplit) { \ + Task *t = w->split; \ + size_t diff = __dq_head - t; \ + diff = (diff + 1) / 2; \ + w->split = t + diff; \ + wt->ts.ts.split += diff; \ + compiler_barrier(); \ + wt->movesplit = 0; \ + PR_COUNTSPLITS(w, CTR_split_grow); \ + } \ + \ + compiler_barrier(); \ + \ + t = (TD_##NAME *)__dq_head; \ + t->thief = THIEF_EMPTY; \ + return NAME##_CALL(w, __dq_head , t->d.args.arg_1, t->d.args.arg_2, t->d.args.arg_3, t->d.args.arg_4);\ +} \ + \ +static inline __attribute__((unused)) \ +void NAME##_SYNC(WorkerP *w, Task *__dq_head) \ +{ \ + /* assert (__dq_head > 0); */ /* Commented out because we assume contract */ \ + \ + if (likely(0 == w->_public->movesplit)) { \ + if (likely(w->split <= __dq_head)) { \ + TD_##NAME *t = (TD_##NAME *)__dq_head; \ + t->thief = THIEF_EMPTY; \ + return NAME##_CALL(w, __dq_head , t->d.args.arg_1, t->d.args.arg_2, t->d.args.arg_3, t->d.args.arg_4);\ + } \ + } \ + \ + return NAME##_SYNC_SLOW(w, __dq_head); \ +} \ + \ + \ + +#define VOID_TASK_IMPL_4(NAME, ATYPE_1, ARG_1, ATYPE_2, ARG_2, ATYPE_3, ARG_3, ATYPE_4, ARG_4)\ +void NAME##_WRAP(WorkerP *w, Task *__dq_head, TD_##NAME *t __attribute__((unused))) \ +{ \ + NAME##_CALL(w, __dq_head , t->d.args.arg_1, t->d.args.arg_2, t->d.args.arg_3, t->d.args.arg_4);\ +} \ + \ +static inline __attribute__((always_inline)) \ +void NAME##_WORK(WorkerP *__lace_worker, Task *__lace_dq_head , ATYPE_1, ATYPE_2, ATYPE_3, ATYPE_4);\ + \ +/* NAME##_WORK is inlined in NAME##_CALL and the parameter __lace_in_task will disappear */\ +void NAME##_CALL(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2, ATYPE_3 arg_3, ATYPE_4 arg_4)\ +{ \ + CHECKSTACK(w); \ + return NAME##_WORK(w, __dq_head , arg_1, arg_2, arg_3, arg_4); \ +} \ + \ +static inline __attribute__((always_inline)) \ +void NAME##_WORK(WorkerP *__lace_worker __attribute__((unused)), Task *__lace_dq_head __attribute__((unused)) , ATYPE_1 ARG_1, ATYPE_2 ARG_2, ATYPE_3 ARG_3, ATYPE_4 ARG_4)\ + +#define VOID_TASK_4(NAME, ATYPE_1, ARG_1, ATYPE_2, ARG_2, ATYPE_3, ARG_3, ATYPE_4, ARG_4) VOID_TASK_DECL_4(NAME, ATYPE_1, ATYPE_2, ATYPE_3, ATYPE_4) VOID_TASK_IMPL_4(NAME, ATYPE_1, ARG_1, ATYPE_2, ARG_2, ATYPE_3, ARG_3, ATYPE_4, ARG_4) + + +// Task macros for tasks of arity 5 + +#define TASK_DECL_5(RTYPE, NAME, ATYPE_1, ATYPE_2, ATYPE_3, ATYPE_4, ATYPE_5) \ + \ +typedef struct _TD_##NAME { \ + TASK_COMMON_FIELDS(_TD_##NAME) \ + union { struct { ATYPE_1 arg_1; ATYPE_2 arg_2; ATYPE_3 arg_3; ATYPE_4 arg_4; ATYPE_5 arg_5; } args; RTYPE res; } d;\ +} TD_##NAME; \ + \ +/* If this line generates an error, please manually set the define LACE_TASKSIZE to a higher value */\ +typedef char assertion_failed_task_descriptor_out_of_bounds_##NAME[(sizeof(TD_##NAME)<=sizeof(Task)) ? 0 : -1];\ + \ +void NAME##_WRAP(WorkerP *, Task *, TD_##NAME *); \ +RTYPE NAME##_CALL(WorkerP *, Task * , ATYPE_1 arg_1, ATYPE_2 arg_2, ATYPE_3 arg_3, ATYPE_4 arg_4, ATYPE_5 arg_5);\ +static inline RTYPE NAME##_SYNC(WorkerP *, Task *); \ +static RTYPE NAME##_SYNC_SLOW(WorkerP *, Task *); \ + \ +static inline __attribute__((unused)) \ +void NAME##_SPAWN(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2, ATYPE_3 arg_3, ATYPE_4 arg_4, ATYPE_5 arg_5)\ +{ \ + PR_COUNTTASK(w); \ + \ + TD_##NAME *t; \ + TailSplit ts; \ + uint32_t head, split, newsplit; \ + \ + /* assert(__dq_head < w->end); */ /* Assuming to be true */ \ + \ + t = (TD_##NAME *)__dq_head; \ + t->f = &NAME##_WRAP; \ + t->thief = THIEF_TASK; \ + t->d.args.arg_1 = arg_1; t->d.args.arg_2 = arg_2; t->d.args.arg_3 = arg_3; t->d.args.arg_4 = arg_4; t->d.args.arg_5 = arg_5;\ + compiler_barrier(); \ + \ + Worker *wt = w->_public; \ + if (unlikely(w->allstolen)) { \ + if (wt->movesplit) wt->movesplit = 0; \ + head = __dq_head - w->dq; \ + ts = (TailSplit){{head,head+1}}; \ + wt->ts.v = ts.v; \ + compiler_barrier(); \ + wt->allstolen = 0; \ + w->split = __dq_head+1; \ + w->allstolen = 0; \ + } else if (unlikely(wt->movesplit)) { \ + head = __dq_head - w->dq; \ + split = w->split - w->dq; \ + newsplit = (split + head + 2)/2; \ + wt->ts.ts.split = newsplit; \ + w->split = w->dq + newsplit; \ + compiler_barrier(); \ + wt->movesplit = 0; \ + PR_COUNTSPLITS(w, CTR_split_grow); \ + } \ +} \ + \ +static inline __attribute__((unused)) \ +RTYPE NAME##_NEWFRAME(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2, ATYPE_3 arg_3, ATYPE_4 arg_4, ATYPE_5 arg_5)\ +{ \ + Task _t; \ + TD_##NAME *t = (TD_##NAME *)&_t; \ + t->f = &NAME##_WRAP; \ + t->thief = THIEF_TASK; \ + t->d.args.arg_1 = arg_1; t->d.args.arg_2 = arg_2; t->d.args.arg_3 = arg_3; t->d.args.arg_4 = arg_4; t->d.args.arg_5 = arg_5;\ + \ + lace_do_newframe(w, __dq_head, &_t); \ + return ((TD_##NAME *)t)->d.res; \ +} \ + \ +static inline __attribute__((unused)) \ +void NAME##_TOGETHER(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2, ATYPE_3 arg_3, ATYPE_4 arg_4, ATYPE_5 arg_5)\ +{ \ + Task _t; \ + TD_##NAME *t = (TD_##NAME *)&_t; \ + t->f = &NAME##_WRAP; \ + t->thief = THIEF_TASK; \ + t->d.args.arg_1 = arg_1; t->d.args.arg_2 = arg_2; t->d.args.arg_3 = arg_3; t->d.args.arg_4 = arg_4; t->d.args.arg_5 = arg_5;\ + \ + lace_do_together(w, __dq_head, &_t); \ +} \ + \ +static __attribute__((noinline)) \ +RTYPE NAME##_SYNC_SLOW(WorkerP *w, Task *__dq_head) \ +{ \ + TD_##NAME *t; \ + \ + if ((w->allstolen) || (w->split > __dq_head && lace_shrink_shared(w))) { \ + lace_leapfrog(w, __dq_head); \ + t = (TD_##NAME *)__dq_head; \ + return ((TD_##NAME *)t)->d.res; \ + } \ + \ + compiler_barrier(); \ + \ + Worker *wt = w->_public; \ + if (wt->movesplit) { \ + Task *t = w->split; \ + size_t diff = __dq_head - t; \ + diff = (diff + 1) / 2; \ + w->split = t + diff; \ + wt->ts.ts.split += diff; \ + compiler_barrier(); \ + wt->movesplit = 0; \ + PR_COUNTSPLITS(w, CTR_split_grow); \ + } \ + \ + compiler_barrier(); \ + \ + t = (TD_##NAME *)__dq_head; \ + t->thief = THIEF_EMPTY; \ + return NAME##_CALL(w, __dq_head , t->d.args.arg_1, t->d.args.arg_2, t->d.args.arg_3, t->d.args.arg_4, t->d.args.arg_5);\ +} \ + \ +static inline __attribute__((unused)) \ +RTYPE NAME##_SYNC(WorkerP *w, Task *__dq_head) \ +{ \ + /* assert (__dq_head > 0); */ /* Commented out because we assume contract */ \ + \ + if (likely(0 == w->_public->movesplit)) { \ + if (likely(w->split <= __dq_head)) { \ + TD_##NAME *t = (TD_##NAME *)__dq_head; \ + t->thief = THIEF_EMPTY; \ + return NAME##_CALL(w, __dq_head , t->d.args.arg_1, t->d.args.arg_2, t->d.args.arg_3, t->d.args.arg_4, t->d.args.arg_5);\ + } \ + } \ + \ + return NAME##_SYNC_SLOW(w, __dq_head); \ +} \ + \ + \ + +#define TASK_IMPL_5(RTYPE, NAME, ATYPE_1, ARG_1, ATYPE_2, ARG_2, ATYPE_3, ARG_3, ATYPE_4, ARG_4, ATYPE_5, ARG_5)\ +void NAME##_WRAP(WorkerP *w, Task *__dq_head, TD_##NAME *t __attribute__((unused))) \ +{ \ + t->d.res = NAME##_CALL(w, __dq_head , t->d.args.arg_1, t->d.args.arg_2, t->d.args.arg_3, t->d.args.arg_4, t->d.args.arg_5);\ +} \ + \ +static inline __attribute__((always_inline)) \ +RTYPE NAME##_WORK(WorkerP *__lace_worker, Task *__lace_dq_head , ATYPE_1, ATYPE_2, ATYPE_3, ATYPE_4, ATYPE_5);\ + \ +/* NAME##_WORK is inlined in NAME##_CALL and the parameter __lace_in_task will disappear */\ +RTYPE NAME##_CALL(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2, ATYPE_3 arg_3, ATYPE_4 arg_4, ATYPE_5 arg_5)\ +{ \ + CHECKSTACK(w); \ + return NAME##_WORK(w, __dq_head , arg_1, arg_2, arg_3, arg_4, arg_5); \ +} \ + \ +static inline __attribute__((always_inline)) \ +RTYPE NAME##_WORK(WorkerP *__lace_worker __attribute__((unused)), Task *__lace_dq_head __attribute__((unused)) , ATYPE_1 ARG_1, ATYPE_2 ARG_2, ATYPE_3 ARG_3, ATYPE_4 ARG_4, ATYPE_5 ARG_5)\ + +#define TASK_5(RTYPE, NAME, ATYPE_1, ARG_1, ATYPE_2, ARG_2, ATYPE_3, ARG_3, ATYPE_4, ARG_4, ATYPE_5, ARG_5) TASK_DECL_5(RTYPE, NAME, ATYPE_1, ATYPE_2, ATYPE_3, ATYPE_4, ATYPE_5) TASK_IMPL_5(RTYPE, NAME, ATYPE_1, ARG_1, ATYPE_2, ARG_2, ATYPE_3, ARG_3, ATYPE_4, ARG_4, ATYPE_5, ARG_5) + +#define VOID_TASK_DECL_5(NAME, ATYPE_1, ATYPE_2, ATYPE_3, ATYPE_4, ATYPE_5) \ + \ +typedef struct _TD_##NAME { \ + TASK_COMMON_FIELDS(_TD_##NAME) \ + union { struct { ATYPE_1 arg_1; ATYPE_2 arg_2; ATYPE_3 arg_3; ATYPE_4 arg_4; ATYPE_5 arg_5; } args; } d;\ +} TD_##NAME; \ + \ +/* If this line generates an error, please manually set the define LACE_TASKSIZE to a higher value */\ +typedef char assertion_failed_task_descriptor_out_of_bounds_##NAME[(sizeof(TD_##NAME)<=sizeof(Task)) ? 0 : -1];\ + \ +void NAME##_WRAP(WorkerP *, Task *, TD_##NAME *); \ +void NAME##_CALL(WorkerP *, Task * , ATYPE_1 arg_1, ATYPE_2 arg_2, ATYPE_3 arg_3, ATYPE_4 arg_4, ATYPE_5 arg_5);\ +static inline void NAME##_SYNC(WorkerP *, Task *); \ +static void NAME##_SYNC_SLOW(WorkerP *, Task *); \ + \ +static inline __attribute__((unused)) \ +void NAME##_SPAWN(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2, ATYPE_3 arg_3, ATYPE_4 arg_4, ATYPE_5 arg_5)\ +{ \ + PR_COUNTTASK(w); \ + \ + TD_##NAME *t; \ + TailSplit ts; \ + uint32_t head, split, newsplit; \ + \ + /* assert(__dq_head < w->end); */ /* Assuming to be true */ \ + \ + t = (TD_##NAME *)__dq_head; \ + t->f = &NAME##_WRAP; \ + t->thief = THIEF_TASK; \ + t->d.args.arg_1 = arg_1; t->d.args.arg_2 = arg_2; t->d.args.arg_3 = arg_3; t->d.args.arg_4 = arg_4; t->d.args.arg_5 = arg_5;\ + compiler_barrier(); \ + \ + Worker *wt = w->_public; \ + if (unlikely(w->allstolen)) { \ + if (wt->movesplit) wt->movesplit = 0; \ + head = __dq_head - w->dq; \ + ts = (TailSplit){{head,head+1}}; \ + wt->ts.v = ts.v; \ + compiler_barrier(); \ + wt->allstolen = 0; \ + w->split = __dq_head+1; \ + w->allstolen = 0; \ + } else if (unlikely(wt->movesplit)) { \ + head = __dq_head - w->dq; \ + split = w->split - w->dq; \ + newsplit = (split + head + 2)/2; \ + wt->ts.ts.split = newsplit; \ + w->split = w->dq + newsplit; \ + compiler_barrier(); \ + wt->movesplit = 0; \ + PR_COUNTSPLITS(w, CTR_split_grow); \ + } \ +} \ + \ +static inline __attribute__((unused)) \ +void NAME##_NEWFRAME(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2, ATYPE_3 arg_3, ATYPE_4 arg_4, ATYPE_5 arg_5)\ +{ \ + Task _t; \ + TD_##NAME *t = (TD_##NAME *)&_t; \ + t->f = &NAME##_WRAP; \ + t->thief = THIEF_TASK; \ + t->d.args.arg_1 = arg_1; t->d.args.arg_2 = arg_2; t->d.args.arg_3 = arg_3; t->d.args.arg_4 = arg_4; t->d.args.arg_5 = arg_5;\ + \ + lace_do_newframe(w, __dq_head, &_t); \ + return ; \ +} \ + \ +static inline __attribute__((unused)) \ +void NAME##_TOGETHER(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2, ATYPE_3 arg_3, ATYPE_4 arg_4, ATYPE_5 arg_5)\ +{ \ + Task _t; \ + TD_##NAME *t = (TD_##NAME *)&_t; \ + t->f = &NAME##_WRAP; \ + t->thief = THIEF_TASK; \ + t->d.args.arg_1 = arg_1; t->d.args.arg_2 = arg_2; t->d.args.arg_3 = arg_3; t->d.args.arg_4 = arg_4; t->d.args.arg_5 = arg_5;\ + \ + lace_do_together(w, __dq_head, &_t); \ +} \ + \ +static __attribute__((noinline)) \ +void NAME##_SYNC_SLOW(WorkerP *w, Task *__dq_head) \ +{ \ + TD_##NAME *t; \ + \ + if ((w->allstolen) || (w->split > __dq_head && lace_shrink_shared(w))) { \ + lace_leapfrog(w, __dq_head); \ + t = (TD_##NAME *)__dq_head; \ + return ; \ + } \ + \ + compiler_barrier(); \ + \ + Worker *wt = w->_public; \ + if (wt->movesplit) { \ + Task *t = w->split; \ + size_t diff = __dq_head - t; \ + diff = (diff + 1) / 2; \ + w->split = t + diff; \ + wt->ts.ts.split += diff; \ + compiler_barrier(); \ + wt->movesplit = 0; \ + PR_COUNTSPLITS(w, CTR_split_grow); \ + } \ + \ + compiler_barrier(); \ + \ + t = (TD_##NAME *)__dq_head; \ + t->thief = THIEF_EMPTY; \ + return NAME##_CALL(w, __dq_head , t->d.args.arg_1, t->d.args.arg_2, t->d.args.arg_3, t->d.args.arg_4, t->d.args.arg_5);\ +} \ + \ +static inline __attribute__((unused)) \ +void NAME##_SYNC(WorkerP *w, Task *__dq_head) \ +{ \ + /* assert (__dq_head > 0); */ /* Commented out because we assume contract */ \ + \ + if (likely(0 == w->_public->movesplit)) { \ + if (likely(w->split <= __dq_head)) { \ + TD_##NAME *t = (TD_##NAME *)__dq_head; \ + t->thief = THIEF_EMPTY; \ + return NAME##_CALL(w, __dq_head , t->d.args.arg_1, t->d.args.arg_2, t->d.args.arg_3, t->d.args.arg_4, t->d.args.arg_5);\ + } \ + } \ + \ + return NAME##_SYNC_SLOW(w, __dq_head); \ +} \ + \ + \ + +#define VOID_TASK_IMPL_5(NAME, ATYPE_1, ARG_1, ATYPE_2, ARG_2, ATYPE_3, ARG_3, ATYPE_4, ARG_4, ATYPE_5, ARG_5)\ +void NAME##_WRAP(WorkerP *w, Task *__dq_head, TD_##NAME *t __attribute__((unused))) \ +{ \ + NAME##_CALL(w, __dq_head , t->d.args.arg_1, t->d.args.arg_2, t->d.args.arg_3, t->d.args.arg_4, t->d.args.arg_5);\ +} \ + \ +static inline __attribute__((always_inline)) \ +void NAME##_WORK(WorkerP *__lace_worker, Task *__lace_dq_head , ATYPE_1, ATYPE_2, ATYPE_3, ATYPE_4, ATYPE_5);\ + \ +/* NAME##_WORK is inlined in NAME##_CALL and the parameter __lace_in_task will disappear */\ +void NAME##_CALL(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2, ATYPE_3 arg_3, ATYPE_4 arg_4, ATYPE_5 arg_5)\ +{ \ + CHECKSTACK(w); \ + return NAME##_WORK(w, __dq_head , arg_1, arg_2, arg_3, arg_4, arg_5); \ +} \ + \ +static inline __attribute__((always_inline)) \ +void NAME##_WORK(WorkerP *__lace_worker __attribute__((unused)), Task *__lace_dq_head __attribute__((unused)) , ATYPE_1 ARG_1, ATYPE_2 ARG_2, ATYPE_3 ARG_3, ATYPE_4 ARG_4, ATYPE_5 ARG_5)\ + +#define VOID_TASK_5(NAME, ATYPE_1, ARG_1, ATYPE_2, ARG_2, ATYPE_3, ARG_3, ATYPE_4, ARG_4, ATYPE_5, ARG_5) VOID_TASK_DECL_5(NAME, ATYPE_1, ATYPE_2, ATYPE_3, ATYPE_4, ATYPE_5) VOID_TASK_IMPL_5(NAME, ATYPE_1, ARG_1, ATYPE_2, ARG_2, ATYPE_3, ARG_3, ATYPE_4, ARG_4, ATYPE_5, ARG_5) + + +// Task macros for tasks of arity 6 + +#define TASK_DECL_6(RTYPE, NAME, ATYPE_1, ATYPE_2, ATYPE_3, ATYPE_4, ATYPE_5, ATYPE_6)\ + \ +typedef struct _TD_##NAME { \ + TASK_COMMON_FIELDS(_TD_##NAME) \ + union { struct { ATYPE_1 arg_1; ATYPE_2 arg_2; ATYPE_3 arg_3; ATYPE_4 arg_4; ATYPE_5 arg_5; ATYPE_6 arg_6; } args; RTYPE res; } d;\ +} TD_##NAME; \ + \ +/* If this line generates an error, please manually set the define LACE_TASKSIZE to a higher value */\ +typedef char assertion_failed_task_descriptor_out_of_bounds_##NAME[(sizeof(TD_##NAME)<=sizeof(Task)) ? 0 : -1];\ + \ +void NAME##_WRAP(WorkerP *, Task *, TD_##NAME *); \ +RTYPE NAME##_CALL(WorkerP *, Task * , ATYPE_1 arg_1, ATYPE_2 arg_2, ATYPE_3 arg_3, ATYPE_4 arg_4, ATYPE_5 arg_5, ATYPE_6 arg_6);\ +static inline RTYPE NAME##_SYNC(WorkerP *, Task *); \ +static RTYPE NAME##_SYNC_SLOW(WorkerP *, Task *); \ + \ +static inline __attribute__((unused)) \ +void NAME##_SPAWN(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2, ATYPE_3 arg_3, ATYPE_4 arg_4, ATYPE_5 arg_5, ATYPE_6 arg_6)\ +{ \ + PR_COUNTTASK(w); \ + \ + TD_##NAME *t; \ + TailSplit ts; \ + uint32_t head, split, newsplit; \ + \ + /* assert(__dq_head < w->end); */ /* Assuming to be true */ \ + \ + t = (TD_##NAME *)__dq_head; \ + t->f = &NAME##_WRAP; \ + t->thief = THIEF_TASK; \ + t->d.args.arg_1 = arg_1; t->d.args.arg_2 = arg_2; t->d.args.arg_3 = arg_3; t->d.args.arg_4 = arg_4; t->d.args.arg_5 = arg_5; t->d.args.arg_6 = arg_6;\ + compiler_barrier(); \ + \ + Worker *wt = w->_public; \ + if (unlikely(w->allstolen)) { \ + if (wt->movesplit) wt->movesplit = 0; \ + head = __dq_head - w->dq; \ + ts = (TailSplit){{head,head+1}}; \ + wt->ts.v = ts.v; \ + compiler_barrier(); \ + wt->allstolen = 0; \ + w->split = __dq_head+1; \ + w->allstolen = 0; \ + } else if (unlikely(wt->movesplit)) { \ + head = __dq_head - w->dq; \ + split = w->split - w->dq; \ + newsplit = (split + head + 2)/2; \ + wt->ts.ts.split = newsplit; \ + w->split = w->dq + newsplit; \ + compiler_barrier(); \ + wt->movesplit = 0; \ + PR_COUNTSPLITS(w, CTR_split_grow); \ + } \ +} \ + \ +static inline __attribute__((unused)) \ +RTYPE NAME##_NEWFRAME(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2, ATYPE_3 arg_3, ATYPE_4 arg_4, ATYPE_5 arg_5, ATYPE_6 arg_6)\ +{ \ + Task _t; \ + TD_##NAME *t = (TD_##NAME *)&_t; \ + t->f = &NAME##_WRAP; \ + t->thief = THIEF_TASK; \ + t->d.args.arg_1 = arg_1; t->d.args.arg_2 = arg_2; t->d.args.arg_3 = arg_3; t->d.args.arg_4 = arg_4; t->d.args.arg_5 = arg_5; t->d.args.arg_6 = arg_6;\ + \ + lace_do_newframe(w, __dq_head, &_t); \ + return ((TD_##NAME *)t)->d.res; \ +} \ + \ +static inline __attribute__((unused)) \ +void NAME##_TOGETHER(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2, ATYPE_3 arg_3, ATYPE_4 arg_4, ATYPE_5 arg_5, ATYPE_6 arg_6)\ +{ \ + Task _t; \ + TD_##NAME *t = (TD_##NAME *)&_t; \ + t->f = &NAME##_WRAP; \ + t->thief = THIEF_TASK; \ + t->d.args.arg_1 = arg_1; t->d.args.arg_2 = arg_2; t->d.args.arg_3 = arg_3; t->d.args.arg_4 = arg_4; t->d.args.arg_5 = arg_5; t->d.args.arg_6 = arg_6;\ + \ + lace_do_together(w, __dq_head, &_t); \ +} \ + \ +static __attribute__((noinline)) \ +RTYPE NAME##_SYNC_SLOW(WorkerP *w, Task *__dq_head) \ +{ \ + TD_##NAME *t; \ + \ + if ((w->allstolen) || (w->split > __dq_head && lace_shrink_shared(w))) { \ + lace_leapfrog(w, __dq_head); \ + t = (TD_##NAME *)__dq_head; \ + return ((TD_##NAME *)t)->d.res; \ + } \ + \ + compiler_barrier(); \ + \ + Worker *wt = w->_public; \ + if (wt->movesplit) { \ + Task *t = w->split; \ + size_t diff = __dq_head - t; \ + diff = (diff + 1) / 2; \ + w->split = t + diff; \ + wt->ts.ts.split += diff; \ + compiler_barrier(); \ + wt->movesplit = 0; \ + PR_COUNTSPLITS(w, CTR_split_grow); \ + } \ + \ + compiler_barrier(); \ + \ + t = (TD_##NAME *)__dq_head; \ + t->thief = THIEF_EMPTY; \ + return NAME##_CALL(w, __dq_head , t->d.args.arg_1, t->d.args.arg_2, t->d.args.arg_3, t->d.args.arg_4, t->d.args.arg_5, t->d.args.arg_6);\ +} \ + \ +static inline __attribute__((unused)) \ +RTYPE NAME##_SYNC(WorkerP *w, Task *__dq_head) \ +{ \ + /* assert (__dq_head > 0); */ /* Commented out because we assume contract */ \ + \ + if (likely(0 == w->_public->movesplit)) { \ + if (likely(w->split <= __dq_head)) { \ + TD_##NAME *t = (TD_##NAME *)__dq_head; \ + t->thief = THIEF_EMPTY; \ + return NAME##_CALL(w, __dq_head , t->d.args.arg_1, t->d.args.arg_2, t->d.args.arg_3, t->d.args.arg_4, t->d.args.arg_5, t->d.args.arg_6);\ + } \ + } \ + \ + return NAME##_SYNC_SLOW(w, __dq_head); \ +} \ + \ + \ + +#define TASK_IMPL_6(RTYPE, NAME, ATYPE_1, ARG_1, ATYPE_2, ARG_2, ATYPE_3, ARG_3, ATYPE_4, ARG_4, ATYPE_5, ARG_5, ATYPE_6, ARG_6)\ +void NAME##_WRAP(WorkerP *w, Task *__dq_head, TD_##NAME *t __attribute__((unused))) \ +{ \ + t->d.res = NAME##_CALL(w, __dq_head , t->d.args.arg_1, t->d.args.arg_2, t->d.args.arg_3, t->d.args.arg_4, t->d.args.arg_5, t->d.args.arg_6);\ +} \ + \ +static inline __attribute__((always_inline)) \ +RTYPE NAME##_WORK(WorkerP *__lace_worker, Task *__lace_dq_head , ATYPE_1, ATYPE_2, ATYPE_3, ATYPE_4, ATYPE_5, ATYPE_6);\ + \ +/* NAME##_WORK is inlined in NAME##_CALL and the parameter __lace_in_task will disappear */\ +RTYPE NAME##_CALL(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2, ATYPE_3 arg_3, ATYPE_4 arg_4, ATYPE_5 arg_5, ATYPE_6 arg_6)\ +{ \ + CHECKSTACK(w); \ + return NAME##_WORK(w, __dq_head , arg_1, arg_2, arg_3, arg_4, arg_5, arg_6); \ +} \ + \ +static inline __attribute__((always_inline)) \ +RTYPE NAME##_WORK(WorkerP *__lace_worker __attribute__((unused)), Task *__lace_dq_head __attribute__((unused)) , ATYPE_1 ARG_1, ATYPE_2 ARG_2, ATYPE_3 ARG_3, ATYPE_4 ARG_4, ATYPE_5 ARG_5, ATYPE_6 ARG_6)\ + +#define TASK_6(RTYPE, NAME, ATYPE_1, ARG_1, ATYPE_2, ARG_2, ATYPE_3, ARG_3, ATYPE_4, ARG_4, ATYPE_5, ARG_5, ATYPE_6, ARG_6) TASK_DECL_6(RTYPE, NAME, ATYPE_1, ATYPE_2, ATYPE_3, ATYPE_4, ATYPE_5, ATYPE_6) TASK_IMPL_6(RTYPE, NAME, ATYPE_1, ARG_1, ATYPE_2, ARG_2, ATYPE_3, ARG_3, ATYPE_4, ARG_4, ATYPE_5, ARG_5, ATYPE_6, ARG_6) + +#define VOID_TASK_DECL_6(NAME, ATYPE_1, ATYPE_2, ATYPE_3, ATYPE_4, ATYPE_5, ATYPE_6) \ + \ +typedef struct _TD_##NAME { \ + TASK_COMMON_FIELDS(_TD_##NAME) \ + union { struct { ATYPE_1 arg_1; ATYPE_2 arg_2; ATYPE_3 arg_3; ATYPE_4 arg_4; ATYPE_5 arg_5; ATYPE_6 arg_6; } args; } d;\ +} TD_##NAME; \ + \ +/* If this line generates an error, please manually set the define LACE_TASKSIZE to a higher value */\ +typedef char assertion_failed_task_descriptor_out_of_bounds_##NAME[(sizeof(TD_##NAME)<=sizeof(Task)) ? 0 : -1];\ + \ +void NAME##_WRAP(WorkerP *, Task *, TD_##NAME *); \ +void NAME##_CALL(WorkerP *, Task * , ATYPE_1 arg_1, ATYPE_2 arg_2, ATYPE_3 arg_3, ATYPE_4 arg_4, ATYPE_5 arg_5, ATYPE_6 arg_6);\ +static inline void NAME##_SYNC(WorkerP *, Task *); \ +static void NAME##_SYNC_SLOW(WorkerP *, Task *); \ + \ +static inline __attribute__((unused)) \ +void NAME##_SPAWN(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2, ATYPE_3 arg_3, ATYPE_4 arg_4, ATYPE_5 arg_5, ATYPE_6 arg_6)\ +{ \ + PR_COUNTTASK(w); \ + \ + TD_##NAME *t; \ + TailSplit ts; \ + uint32_t head, split, newsplit; \ + \ + /* assert(__dq_head < w->end); */ /* Assuming to be true */ \ + \ + t = (TD_##NAME *)__dq_head; \ + t->f = &NAME##_WRAP; \ + t->thief = THIEF_TASK; \ + t->d.args.arg_1 = arg_1; t->d.args.arg_2 = arg_2; t->d.args.arg_3 = arg_3; t->d.args.arg_4 = arg_4; t->d.args.arg_5 = arg_5; t->d.args.arg_6 = arg_6;\ + compiler_barrier(); \ + \ + Worker *wt = w->_public; \ + if (unlikely(w->allstolen)) { \ + if (wt->movesplit) wt->movesplit = 0; \ + head = __dq_head - w->dq; \ + ts = (TailSplit){{head,head+1}}; \ + wt->ts.v = ts.v; \ + compiler_barrier(); \ + wt->allstolen = 0; \ + w->split = __dq_head+1; \ + w->allstolen = 0; \ + } else if (unlikely(wt->movesplit)) { \ + head = __dq_head - w->dq; \ + split = w->split - w->dq; \ + newsplit = (split + head + 2)/2; \ + wt->ts.ts.split = newsplit; \ + w->split = w->dq + newsplit; \ + compiler_barrier(); \ + wt->movesplit = 0; \ + PR_COUNTSPLITS(w, CTR_split_grow); \ + } \ +} \ + \ +static inline __attribute__((unused)) \ +void NAME##_NEWFRAME(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2, ATYPE_3 arg_3, ATYPE_4 arg_4, ATYPE_5 arg_5, ATYPE_6 arg_6)\ +{ \ + Task _t; \ + TD_##NAME *t = (TD_##NAME *)&_t; \ + t->f = &NAME##_WRAP; \ + t->thief = THIEF_TASK; \ + t->d.args.arg_1 = arg_1; t->d.args.arg_2 = arg_2; t->d.args.arg_3 = arg_3; t->d.args.arg_4 = arg_4; t->d.args.arg_5 = arg_5; t->d.args.arg_6 = arg_6;\ + \ + lace_do_newframe(w, __dq_head, &_t); \ + return ; \ +} \ + \ +static inline __attribute__((unused)) \ +void NAME##_TOGETHER(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2, ATYPE_3 arg_3, ATYPE_4 arg_4, ATYPE_5 arg_5, ATYPE_6 arg_6)\ +{ \ + Task _t; \ + TD_##NAME *t = (TD_##NAME *)&_t; \ + t->f = &NAME##_WRAP; \ + t->thief = THIEF_TASK; \ + t->d.args.arg_1 = arg_1; t->d.args.arg_2 = arg_2; t->d.args.arg_3 = arg_3; t->d.args.arg_4 = arg_4; t->d.args.arg_5 = arg_5; t->d.args.arg_6 = arg_6;\ + \ + lace_do_together(w, __dq_head, &_t); \ +} \ + \ +static __attribute__((noinline)) \ +void NAME##_SYNC_SLOW(WorkerP *w, Task *__dq_head) \ +{ \ + TD_##NAME *t; \ + \ + if ((w->allstolen) || (w->split > __dq_head && lace_shrink_shared(w))) { \ + lace_leapfrog(w, __dq_head); \ + t = (TD_##NAME *)__dq_head; \ + return ; \ + } \ + \ + compiler_barrier(); \ + \ + Worker *wt = w->_public; \ + if (wt->movesplit) { \ + Task *t = w->split; \ + size_t diff = __dq_head - t; \ + diff = (diff + 1) / 2; \ + w->split = t + diff; \ + wt->ts.ts.split += diff; \ + compiler_barrier(); \ + wt->movesplit = 0; \ + PR_COUNTSPLITS(w, CTR_split_grow); \ + } \ + \ + compiler_barrier(); \ + \ + t = (TD_##NAME *)__dq_head; \ + t->thief = THIEF_EMPTY; \ + return NAME##_CALL(w, __dq_head , t->d.args.arg_1, t->d.args.arg_2, t->d.args.arg_3, t->d.args.arg_4, t->d.args.arg_5, t->d.args.arg_6);\ +} \ + \ +static inline __attribute__((unused)) \ +void NAME##_SYNC(WorkerP *w, Task *__dq_head) \ +{ \ + /* assert (__dq_head > 0); */ /* Commented out because we assume contract */ \ + \ + if (likely(0 == w->_public->movesplit)) { \ + if (likely(w->split <= __dq_head)) { \ + TD_##NAME *t = (TD_##NAME *)__dq_head; \ + t->thief = THIEF_EMPTY; \ + return NAME##_CALL(w, __dq_head , t->d.args.arg_1, t->d.args.arg_2, t->d.args.arg_3, t->d.args.arg_4, t->d.args.arg_5, t->d.args.arg_6);\ + } \ + } \ + \ + return NAME##_SYNC_SLOW(w, __dq_head); \ +} \ + \ + \ + +#define VOID_TASK_IMPL_6(NAME, ATYPE_1, ARG_1, ATYPE_2, ARG_2, ATYPE_3, ARG_3, ATYPE_4, ARG_4, ATYPE_5, ARG_5, ATYPE_6, ARG_6)\ +void NAME##_WRAP(WorkerP *w, Task *__dq_head, TD_##NAME *t __attribute__((unused))) \ +{ \ + NAME##_CALL(w, __dq_head , t->d.args.arg_1, t->d.args.arg_2, t->d.args.arg_3, t->d.args.arg_4, t->d.args.arg_5, t->d.args.arg_6);\ +} \ + \ +static inline __attribute__((always_inline)) \ +void NAME##_WORK(WorkerP *__lace_worker, Task *__lace_dq_head , ATYPE_1, ATYPE_2, ATYPE_3, ATYPE_4, ATYPE_5, ATYPE_6);\ + \ +/* NAME##_WORK is inlined in NAME##_CALL and the parameter __lace_in_task will disappear */\ +void NAME##_CALL(WorkerP *w, Task *__dq_head , ATYPE_1 arg_1, ATYPE_2 arg_2, ATYPE_3 arg_3, ATYPE_4 arg_4, ATYPE_5 arg_5, ATYPE_6 arg_6)\ +{ \ + CHECKSTACK(w); \ + return NAME##_WORK(w, __dq_head , arg_1, arg_2, arg_3, arg_4, arg_5, arg_6); \ +} \ + \ +static inline __attribute__((always_inline)) \ +void NAME##_WORK(WorkerP *__lace_worker __attribute__((unused)), Task *__lace_dq_head __attribute__((unused)) , ATYPE_1 ARG_1, ATYPE_2 ARG_2, ATYPE_3 ARG_3, ATYPE_4 ARG_4, ATYPE_5 ARG_5, ATYPE_6 ARG_6)\ + +#define VOID_TASK_6(NAME, ATYPE_1, ARG_1, ATYPE_2, ARG_2, ATYPE_3, ARG_3, ATYPE_4, ARG_4, ATYPE_5, ARG_5, ATYPE_6, ARG_6) VOID_TASK_DECL_6(NAME, ATYPE_1, ATYPE_2, ATYPE_3, ATYPE_4, ATYPE_5, ATYPE_6) VOID_TASK_IMPL_6(NAME, ATYPE_1, ARG_1, ATYPE_2, ARG_2, ATYPE_3, ARG_3, ATYPE_4, ARG_4, ATYPE_5, ARG_5, ATYPE_6, ARG_6) + + +VOID_TASK_DECL_0(lace_steal_random); +VOID_TASK_DECL_1(lace_steal_random_loop, int*); +VOID_TASK_DECL_1(lace_steal_loop, int*); +VOID_TASK_DECL_2(lace_steal_loop_root, Task *, int*); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif diff --git a/src/llmsset.c b/src/llmsset.c new file mode 100644 index 000000000..9c21f2e94 --- /dev/null +++ b/src/llmsset.c @@ -0,0 +1,564 @@ +/* + * Copyright 2011-2015 Formal Methods and Tools, University of Twente + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <sylvan_config.h> + +#include <errno.h> // for errno +#include <stdint.h> // for uint64_t etc +#include <stdio.h> // for printf +#include <stdlib.h> +#include <string.h> // memset +#include <sys/mman.h> // for mmap + +#include <llmsset.h> +#include <stats.h> +#include <tls.h> + +#ifndef USE_HWLOC +#define USE_HWLOC 0 +#endif + +#if USE_HWLOC +#include <hwloc.h> + +static hwloc_topology_t topo; +#endif + +#ifndef MAP_ANONYMOUS +#define MAP_ANONYMOUS MAP_ANON +#endif + +#ifndef cas +#define cas(ptr, old, new) (__sync_bool_compare_and_swap((ptr),(old),(new))) +#endif + +DECLARE_THREAD_LOCAL(my_region, uint64_t); + +VOID_TASK_0(llmsset_reset_region) +{ + LOCALIZE_THREAD_LOCAL(my_region, uint64_t); + my_region = (uint64_t)-1; // no region + SET_THREAD_LOCAL(my_region, my_region); +} + +static uint64_t +claim_data_bucket(const llmsset_t dbs) +{ + LOCALIZE_THREAD_LOCAL(my_region, uint64_t); + + for (;;) { + if (my_region != (uint64_t)-1) { + // find empty bucket in region <my_region> + uint64_t *ptr = dbs->bitmap2 + (my_region*8); + int i=0; + for (;i<8;) { + uint64_t v = *ptr; + if (v != 0xffffffffffffffffLL) { + int j = __builtin_clzll(~v); + *ptr |= (0x8000000000000000LL>>j); + return (8 * my_region + i) * 64 + j; + } + i++; + ptr++; + } + } else { + // special case on startup or after garbage collection + my_region += (lace_get_worker()->worker*(dbs->table_size/(64*8)))/lace_workers(); + } + uint64_t count = dbs->table_size/(64*8); + for (;;) { + // check if table maybe full + if (count-- == 0) return (uint64_t)-1; + + my_region += 1; + if (my_region >= (dbs->table_size/(64*8))) my_region = 0; + + // try to claim it + uint64_t *ptr = dbs->bitmap1 + (my_region/64); + uint64_t mask = 0x8000000000000000LL >> (my_region&63); + uint64_t v; +restart: + v = *ptr; + if (v & mask) continue; // taken + if (cas(ptr, v, v|mask)) break; + else goto restart; + } + SET_THREAD_LOCAL(my_region, my_region); + } +} + +static void +release_data_bucket(const llmsset_t dbs, uint64_t index) +{ + uint64_t *ptr = dbs->bitmap2 + (index/64); + uint64_t mask = 0x8000000000000000LL >> (index&63); + *ptr &= ~mask; +} + +static void +set_custom_bucket(const llmsset_t dbs, uint64_t index, int on) +{ + uint64_t *ptr = dbs->bitmapc + (index/64); + uint64_t mask = 0x8000000000000000LL >> (index&63); + if (on) *ptr |= mask; + else *ptr &= ~mask; +} + +static int +get_custom_bucket(const llmsset_t dbs, uint64_t index) +{ + uint64_t *ptr = dbs->bitmapc + (index/64); + uint64_t mask = 0x8000000000000000LL >> (index&63); + return (*ptr & mask) ? 1 : 0; +} + +#ifndef rotl64 +static inline uint64_t +rotl64(uint64_t x, int8_t r) +{ + return ((x<<r) | (x>>(64-r))); +} +#endif + +uint64_t +llmsset_hash(const uint64_t a, const uint64_t b, const uint64_t seed) +{ + const uint64_t prime = 1099511628211; + + uint64_t hash = seed; + hash = hash ^ a; + hash = rotl64(hash, 47); + hash = hash * prime; + hash = hash ^ b; + hash = rotl64(hash, 31); + hash = hash * prime; + + return hash ^ (hash >> 32); +} + +/* + * CL_MASK and CL_MASK_R are for the probe sequence calculation. + * With 64 bytes per cacheline, there are 8 64-bit values per cacheline. + */ +// The LINE_SIZE is defined in lace.h +static const uint64_t CL_MASK = ~(((LINE_SIZE) / 8) - 1); +static const uint64_t CL_MASK_R = ((LINE_SIZE) / 8) - 1; + +/* 40 bits for the index, 24 bits for the hash */ +#define MASK_INDEX ((uint64_t)0x000000ffffffffff) +#define MASK_HASH ((uint64_t)0xffffff0000000000) + +static inline uint64_t +llmsset_lookup2(const llmsset_t dbs, uint64_t a, uint64_t b, int* created, const int custom) +{ + uint64_t hash_rehash = 14695981039346656037LLU; + if (custom) hash_rehash = dbs->hash_cb(a, b, hash_rehash); + else hash_rehash = llmsset_hash(a, b, hash_rehash); + + const uint64_t hash = hash_rehash & MASK_HASH; + uint64_t idx, last, cidx = 0; + int i=0; + +#if LLMSSET_MASK + last = idx = hash_rehash & dbs->mask; +#else + last = idx = hash_rehash % dbs->table_size; +#endif + + for (;;) { + volatile uint64_t *bucket = dbs->table + idx; + uint64_t v = *bucket; + + if (v == 0) { + if (cidx == 0) { + cidx = claim_data_bucket(dbs); + if (cidx == (uint64_t)-1) return 0; // failed to claim a data bucket + if (custom) dbs->create_cb(&a, &b); + uint64_t *d_ptr = ((uint64_t*)dbs->data) + 2*cidx; + d_ptr[0] = a; + d_ptr[1] = b; + } + if (cas(bucket, 0, hash | cidx)) { + if (custom) set_custom_bucket(dbs, cidx, custom); + *created = 1; + return cidx; + } else { + v = *bucket; + } + } + + if (hash == (v & MASK_HASH)) { + uint64_t d_idx = v & MASK_INDEX; + uint64_t *d_ptr = ((uint64_t*)dbs->data) + 2*d_idx; + if (custom) { + if (dbs->equals_cb(a, b, d_ptr[0], d_ptr[1])) { + if (cidx != 0) { + dbs->destroy_cb(a, b); + release_data_bucket(dbs, cidx); + } + *created = 0; + return d_idx; + } + } else { + if (d_ptr[0] == a && d_ptr[1] == b) { + if (cidx != 0) release_data_bucket(dbs, cidx); + *created = 0; + return d_idx; + } + } + } + + sylvan_stats_count(LLMSSET_LOOKUP); + + // find next idx on probe sequence + idx = (idx & CL_MASK) | ((idx+1) & CL_MASK_R); + if (idx == last) { + if (++i == dbs->threshold) return 0; // failed to find empty spot in probe sequence + + // go to next cache line in probe sequence + if (custom) hash_rehash = dbs->hash_cb(a, b, hash_rehash); + else hash_rehash = llmsset_hash(a, b, hash_rehash); + +#if LLMSSET_MASK + last = idx = hash_rehash & dbs->mask; +#else + last = idx = hash_rehash % dbs->table_size; +#endif + } + } +} + +uint64_t +llmsset_lookup(const llmsset_t dbs, const uint64_t a, const uint64_t b, int* created) +{ + return llmsset_lookup2(dbs, a, b, created, 0); +} + +uint64_t +llmsset_lookupc(const llmsset_t dbs, const uint64_t a, const uint64_t b, int* created) +{ + return llmsset_lookup2(dbs, a, b, created, 1); +} + +static inline int +llmsset_rehash_bucket(const llmsset_t dbs, uint64_t d_idx) +{ + const uint64_t * const d_ptr = ((uint64_t*)dbs->data) + 2*d_idx; + const uint64_t a = d_ptr[0]; + const uint64_t b = d_ptr[1]; + + uint64_t hash_rehash = 14695981039346656037LLU; + const int custom = get_custom_bucket(dbs, d_idx) ? 1 : 0; + if (custom) hash_rehash = dbs->hash_cb(a, b, hash_rehash); + else hash_rehash = llmsset_hash(a, b, hash_rehash); + const uint64_t new_v = (hash_rehash & MASK_HASH) | d_idx; + int i=0; + + uint64_t idx, last; +#if LLMSSET_MASK + last = idx = hash_rehash & dbs->mask; +#else + last = idx = hash_rehash % dbs->table_size; +#endif + + for (;;) { + volatile uint64_t *bucket = &dbs->table[idx]; + if (*bucket == 0 && cas(bucket, 0, new_v)) return 1; + + // find next idx on probe sequence + idx = (idx & CL_MASK) | ((idx+1) & CL_MASK_R); + if (idx == last) { + if (++i == dbs->threshold) return 0; // failed to find empty spot in probe sequence + + // go to next cache line in probe sequence + if (custom) hash_rehash = dbs->hash_cb(a, b, hash_rehash); + else hash_rehash = llmsset_hash(a, b, hash_rehash); + +#if LLMSSET_MASK + last = idx = hash_rehash & dbs->mask; +#else + last = idx = hash_rehash % dbs->table_size; +#endif + } + } +} + +llmsset_t +llmsset_create(size_t initial_size, size_t max_size) +{ +#if USE_HWLOC + hwloc_topology_init(&topo); + hwloc_topology_load(topo); +#endif + + llmsset_t dbs = NULL; + if (posix_memalign((void**)&dbs, LINE_SIZE, sizeof(struct llmsset)) != 0) { + fprintf(stderr, "llmsset_create: Unable to allocate memory!\n"); + exit(1); + } + +#if LLMSSET_MASK + /* Check if initial_size and max_size are powers of 2 */ + if (__builtin_popcountll(initial_size) != 1) { + fprintf(stderr, "llmsset_create: initial_size is not a power of 2!\n"); + exit(1); + } + + if (__builtin_popcountll(max_size) != 1) { + fprintf(stderr, "llmsset_create: max_size is not a power of 2!\n"); + exit(1); + } +#endif + + if (initial_size > max_size) { + fprintf(stderr, "llmsset_create: initial_size > max_size!\n"); + exit(1); + } + + // minimum size is now 512 buckets (region size, but of course, n_workers * 512 is suggested as minimum) + + if (initial_size < 512) { + fprintf(stderr, "llmsset_create: initial_size too small!\n"); + exit(1); + } + + dbs->max_size = max_size; + llmsset_set_size(dbs, initial_size); + + /* This implementation of "resizable hash table" allocates the max_size table in virtual memory, + but only uses the "actual size" part in real memory */ + + dbs->table = (uint64_t*)mmap(0, dbs->max_size * 8, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + dbs->data = (uint8_t*)mmap(0, dbs->max_size * 16, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + + /* Also allocate bitmaps. Each region is 64*8 = 512 buckets. + Overhead of bitmap1: 1 bit per 4096 bucket. + Overhead of bitmap2: 1 bit per bucket. + Overhead of bitmapc: 1 bit per bucket. */ + + dbs->bitmap1 = (uint64_t*)mmap(0, dbs->max_size / (512*8), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + dbs->bitmap2 = (uint64_t*)mmap(0, dbs->max_size / 8, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + dbs->bitmapc = (uint64_t*)mmap(0, dbs->max_size / 8, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + + if (dbs->table == (uint64_t*)-1 || dbs->data == (uint8_t*)-1 || dbs->bitmap1 == (uint64_t*)-1 || dbs->bitmap2 == (uint64_t*)-1 || dbs->bitmapc == (uint64_t*)-1) { + fprintf(stderr, "llmsset_create: Unable to allocate memory: %s!\n", strerror(errno)); + exit(1); + } + +#if defined(madvise) && defined(MADV_RANDOM) + madvise(dbs->table, dbs->max_size * 8, MADV_RANDOM); +#endif + +#if USE_HWLOC + hwloc_set_area_membind(topo, dbs->table, dbs->max_size * 8, hwloc_topology_get_allowed_cpuset(topo), HWLOC_MEMBIND_INTERLEAVE, 0); + hwloc_set_area_membind(topo, dbs->data, dbs->max_size * 16, hwloc_topology_get_allowed_cpuset(topo), HWLOC_MEMBIND_FIRSTTOUCH, 0); + hwloc_set_area_membind(topo, dbs->bitmap1, dbs->max_size / (512*8), hwloc_topology_get_allowed_cpuset(topo), HWLOC_MEMBIND_INTERLEAVE, 0); + hwloc_set_area_membind(topo, dbs->bitmap2, dbs->max_size / 8, hwloc_topology_get_allowed_cpuset(topo), HWLOC_MEMBIND_FIRSTTOUCH, 0); + hwloc_set_area_membind(topo, dbs->bitmapc, dbs->max_size / 8, hwloc_topology_get_allowed_cpuset(topo), HWLOC_MEMBIND_FIRSTTOUCH, 0); +#endif + + // forbid first two positions (index 0 and 1) + dbs->bitmap2[0] = 0xc000000000000000LL; + + dbs->hash_cb = NULL; + dbs->equals_cb = NULL; + dbs->create_cb = NULL; + dbs->destroy_cb = NULL; + + // yes, ugly. for now, we use a global thread-local value. + // that is a problem with multiple tables. + // so, for now, do NOT use multiple tables!! + + LACE_ME; + INIT_THREAD_LOCAL(my_region); + TOGETHER(llmsset_reset_region); + + return dbs; +} + +void +llmsset_free(llmsset_t dbs) +{ + munmap(dbs->table, dbs->max_size * 8); + munmap(dbs->data, dbs->max_size * 16); + munmap(dbs->bitmap1, dbs->max_size / (512*8)); + munmap(dbs->bitmap2, dbs->max_size / 8); + munmap(dbs->bitmapc, dbs->max_size / 8); + free(dbs); +} + +VOID_TASK_IMPL_1(llmsset_clear, llmsset_t, dbs) +{ + // just reallocate... + if (mmap(dbs->table, dbs->max_size * 8, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0) != (void*)-1) { +#if defined(madvise) && defined(MADV_RANDOM) + madvise(dbs->table, sizeof(uint64_t[dbs->max_size]), MADV_RANDOM); +#endif +#if USE_HWLOC + hwloc_set_area_membind(topo, dbs->table, sizeof(uint64_t[dbs->max_size]), hwloc_topology_get_allowed_cpuset(topo), HWLOC_MEMBIND_INTERLEAVE, 0); +#endif + } else { + // reallocate failed... expensive fallback + memset(dbs->table, 0, dbs->max_size * 8); + } + + if (mmap(dbs->bitmap1, dbs->max_size / (512*8), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0) != (void*)-1) { +#if USE_HWLOC + hwloc_set_area_membind(topo, dbs->bitmap1, dbs->max_size / (512*8), hwloc_topology_get_allowed_cpuset(topo), HWLOC_MEMBIND_INTERLEAVE, 0); +#endif + } else { + memset(dbs->bitmap1, 0, dbs->max_size / (512*8)); + } + + if (mmap(dbs->bitmap2, dbs->max_size / 8, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0) != (void*)-1) { +#if USE_HWLOC + hwloc_set_area_membind(topo, dbs->bitmap2, dbs->max_size / 8, hwloc_topology_get_allowed_cpuset(topo), HWLOC_MEMBIND_FIRSTTOUCH, 0); +#endif + } else { + memset(dbs->bitmap2, 0, dbs->max_size / 8); + } + + // forbid first two positions (index 0 and 1) + dbs->bitmap2[0] = 0xc000000000000000LL; + + TOGETHER(llmsset_reset_region); +} + +int +llmsset_is_marked(const llmsset_t dbs, uint64_t index) +{ + volatile uint64_t *ptr = dbs->bitmap2 + (index/64); + uint64_t mask = 0x8000000000000000LL >> (index&63); + return (*ptr & mask) ? 1 : 0; +} + +int +llmsset_mark(const llmsset_t dbs, uint64_t index) +{ + volatile uint64_t *ptr = dbs->bitmap2 + (index/64); + uint64_t mask = 0x8000000000000000LL >> (index&63); + for (;;) { + uint64_t v = *ptr; + if (v & mask) return 0; + if (cas(ptr, v, v|mask)) return 1; + } +} + +VOID_TASK_3(llmsset_rehash_par, llmsset_t, dbs, size_t, first, size_t, count) +{ + if (count > 512) { + size_t split = count/2; + SPAWN(llmsset_rehash_par, dbs, first, split); + CALL(llmsset_rehash_par, dbs, first + split, count - split); + SYNC(llmsset_rehash_par); + } else { + uint64_t *ptr = dbs->bitmap2 + (first / 64); + uint64_t mask = 0x8000000000000000LL >> (first & 63); + for (size_t k=0; k<count; k++) { + if (*ptr & mask) llmsset_rehash_bucket(dbs, first+k); + mask >>= 1; + if (mask == 0) { + ptr++; + mask = 0x8000000000000000LL; + } + } + } +} + +VOID_TASK_IMPL_1(llmsset_rehash, llmsset_t, dbs) +{ + CALL(llmsset_rehash_par, dbs, 0, dbs->table_size); +} + +TASK_3(size_t, llmsset_count_marked_par, llmsset_t, dbs, size_t, first, size_t, count) +{ + if (count > 512) { + size_t split = count/2; + SPAWN(llmsset_count_marked_par, dbs, first, split); + size_t right = CALL(llmsset_count_marked_par, dbs, first + split, count - split); + size_t left = SYNC(llmsset_count_marked_par); + return left + right; + } else { + size_t result = 0; + uint64_t *ptr = dbs->bitmap2 + (first / 64); + if (count == 512) { + result += __builtin_popcountll(ptr[0]); + result += __builtin_popcountll(ptr[1]); + result += __builtin_popcountll(ptr[2]); + result += __builtin_popcountll(ptr[3]); + result += __builtin_popcountll(ptr[4]); + result += __builtin_popcountll(ptr[5]); + result += __builtin_popcountll(ptr[6]); + result += __builtin_popcountll(ptr[7]); + } else { + uint64_t mask = 0x8000000000000000LL >> (first & 63); + for (size_t k=0; k<count; k++) { + if (*ptr & mask) result += 1; + mask >>= 1; + if (mask == 0) { + ptr++; + mask = 0x8000000000000000LL; + } + } + } + return result; + } +} + +TASK_IMPL_1(size_t, llmsset_count_marked, llmsset_t, dbs) +{ + return CALL(llmsset_count_marked_par, dbs, 0, dbs->table_size); +} + +VOID_TASK_3(llmsset_destroy_par, llmsset_t, dbs, size_t, first, size_t, count) +{ + if (count > 1024) { + size_t split = count/2; + SPAWN(llmsset_destroy_par, dbs, first, split); + CALL(llmsset_destroy_par, dbs, first + split, count - split); + SYNC(llmsset_destroy_par); + } else { + for (size_t k=first; k<first+count; k++) { + volatile uint64_t *ptr2 = dbs->bitmap2 + (k/64); + volatile uint64_t *ptrc = dbs->bitmapc + (k/64); + uint64_t mask = 0x8000000000000000LL >> (k&63); + + // if not marked but is custom + if ((*ptr2 & mask) == 0 && (*ptrc & mask)) { + uint64_t *d_ptr = ((uint64_t*)dbs->data) + 2*k; + dbs->destroy_cb(d_ptr[0], d_ptr[1]); + *ptrc &= ~mask; + } + } + } +} + +VOID_TASK_IMPL_1(llmsset_destroy_unmarked, llmsset_t, dbs) +{ + if (dbs->destroy_cb == NULL) return; // no custom function + CALL(llmsset_destroy_par, dbs, 0, dbs->table_size); +} + +/** + * Set custom functions + */ +void llmsset_set_custom(const llmsset_t dbs, llmsset_hash_cb hash_cb, llmsset_equals_cb equals_cb, llmsset_create_cb create_cb, llmsset_destroy_cb destroy_cb) +{ + dbs->hash_cb = hash_cb; + dbs->equals_cb = equals_cb; + dbs->create_cb = create_cb; + dbs->destroy_cb = destroy_cb; +} diff --git a/src/llmsset.h b/src/llmsset.h new file mode 100644 index 000000000..84c55e23b --- /dev/null +++ b/src/llmsset.h @@ -0,0 +1,202 @@ +/* + * Copyright 2011-2014 Formal Methods and Tools, University of Twente + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <sylvan_config.h> +#include <stdint.h> +#include <unistd.h> + +#include <lace.h> + +#ifndef LLMSSET_H +#define LLMSSET_H + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#ifndef LLMSSET_MASK +#define LLMSSET_MASK 0 // set to 1 to use bit mask instead of modulo +#endif + +/** + * Lockless hash table (set) to store 16-byte keys. + * Each unique key is associated with a 42-bit number. + * + * The set has support for stop-the-world garbage collection. + * Methods llmsset_clear, llmsset_mark and llmsset_rehash implement garbage collection. + * During their execution, llmsset_lookup is not allowed. + */ + +/** + * hash(a, b, seed) + * equals(lhs_a, lhs_b, rhs_a, rhs_b) + * create(a, b) -- with a,b pointers, allows changing pointers on create of node, + * but must keep hash/equals same! + * destroy(a, b) + */ +typedef uint64_t (*llmsset_hash_cb)(uint64_t, uint64_t, uint64_t); +typedef int (*llmsset_equals_cb)(uint64_t, uint64_t, uint64_t, uint64_t); +typedef void (*llmsset_create_cb)(uint64_t *, uint64_t *); +typedef void (*llmsset_destroy_cb)(uint64_t, uint64_t); + +typedef struct llmsset +{ + uint64_t *table; // table with hashes + uint8_t *data; // table with values + uint64_t *bitmap1; // ownership bitmap (per 512 buckets) + uint64_t *bitmap2; // bitmap for "contains data" + uint64_t *bitmapc; // bitmap for "use custom functions" + size_t max_size; // maximum size of the hash table (for resizing) + size_t table_size; // size of the hash table (number of slots) --> power of 2! +#if LLMSSET_MASK + size_t mask; // size-1 +#endif + size_t f_size; + llmsset_hash_cb hash_cb; // custom hash function + llmsset_equals_cb equals_cb; // custom equals function + llmsset_create_cb create_cb; // custom create function + llmsset_destroy_cb destroy_cb; // custom destroy function + int16_t threshold; // number of iterations for insertion until returning error +} *llmsset_t; + +/** + * Retrieve a pointer to the data associated with the 42-bit value. + */ +static inline void* +llmsset_index_to_ptr(const llmsset_t dbs, size_t index) +{ + return dbs->data + index * 16; +} + +/** + * Create the set. + * This will allocate a set of <max_size> buckets in virtual memory. + * The actual space used is <initial_size> buckets. + */ +llmsset_t llmsset_create(size_t initial_size, size_t max_size); + +/** + * Free the set. + */ +void llmsset_free(llmsset_t dbs); + +/** + * Retrieve the maximum size of the set. + */ +static inline size_t +llmsset_get_max_size(const llmsset_t dbs) +{ + return dbs->max_size; +} + +/** + * Retrieve the current size of the lockless MS set. + */ +static inline size_t +llmsset_get_size(const llmsset_t dbs) +{ + return dbs->table_size; +} + +/** + * Set the table size of the set. + * Typically called during garbage collection, after clear and before rehash. + * Returns 0 if dbs->table_size > dbs->max_size! + */ +static inline void +llmsset_set_size(llmsset_t dbs, size_t size) +{ + /* check bounds (don't be rediculous) */ + if (size > 128 && size <= dbs->max_size) { + dbs->table_size = size; +#if LLMSSET_MASK + /* Warning: if size is not a power of two, you will get interesting behavior */ + dbs->mask = dbs->table_size - 1; +#endif + dbs->threshold = (64 - __builtin_clzll(dbs->table_size)) + 4; // doubling table_size increases threshold by 1 + } +} + +/** + * Core function: find existing data or add new. + * Returns the unique 42-bit value associated with the data, or 0 when table is full. + * Also, this value will never equal 0 or 1. + * Note: garbage collection during lookup strictly forbidden + */ +uint64_t llmsset_lookup(const llmsset_t dbs, const uint64_t a, const uint64_t b, int *created); + +/** + * Same as lookup, but use the custom functions + */ +uint64_t llmsset_lookupc(const llmsset_t dbs, const uint64_t a, const uint64_t b, int *created); + +/** + * To perform garbage collection, the user is responsible that no lookups are performed during the process. + * + * 1) call llmsset_clear + * 2) call llmsset_mark for every bucket to rehash + * 3) call llmsset_rehash + */ +VOID_TASK_DECL_1(llmsset_clear, llmsset_t); +#define llmsset_clear(dbs) CALL(llmsset_clear, dbs) + +/** + * Check if a certain data bucket is marked (in use). + */ +int llmsset_is_marked(const llmsset_t dbs, uint64_t index); + +/** + * During garbage collection, buckets are marked (for rehashing) with this function. + * Returns 0 if the node was already marked, or non-zero if it was not marked. + * May also return non-zero if multiple workers marked at the same time. + */ +int llmsset_mark(const llmsset_t dbs, uint64_t index); + +/** + * Rehash all marked buckets. + */ +VOID_TASK_DECL_1(llmsset_rehash, llmsset_t); +#define llmsset_rehash(dbs) CALL(llmsset_rehash, dbs) + +/** + * Retrieve number of marked buckets. + */ +TASK_DECL_1(size_t, llmsset_count_marked, llmsset_t); +#define llmsset_count_marked(dbs) CALL(llmsset_count_marked, dbs) + +/** + * During garbage collection, this method calls the destroy callback + * for all 'custom' data that is not kept. + */ +VOID_TASK_DECL_1(llmsset_destroy_unmarked, llmsset_t); +#define llmsset_destroy_unmarked(dbs) CALL(llmsset_destroy_unmarked, dbs) + +/** + * Set custom functions + */ +void llmsset_set_custom(const llmsset_t dbs, llmsset_hash_cb hash_cb, llmsset_equals_cb equals_cb, llmsset_create_cb create_cb, llmsset_destroy_cb destroy_cb); + +/** + * Default hashing function + */ +uint64_t llmsset_hash(const uint64_t a, const uint64_t b, const uint64_t seed); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + + +#endif diff --git a/src/refs.c b/src/refs.c new file mode 100644 index 000000000..0d6963bfe --- /dev/null +++ b/src/refs.c @@ -0,0 +1,598 @@ +/* + * Copyright 2011-2015 Formal Methods and Tools, University of Twente + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <sylvan_config.h> + +#include <assert.h> // for assert +#include <errno.h> // for errno +#include <stdio.h> // for fprintf +#include <stdint.h> // for uint32_t etc +#include <stdlib.h> // for exit +#include <string.h> // for strerror +#include <sys/mman.h> // for mmap + +#include <refs.h> + +#ifndef compiler_barrier +#define compiler_barrier() { asm volatile("" ::: "memory"); } +#endif + +#ifndef cas +#define cas(ptr, old, new) (__sync_bool_compare_and_swap((ptr),(old),(new))) +#endif + +/** + * Implementation of external references + * Based on a hash table for 40-bit non-null values, linear probing + * Use tombstones for deleting, higher bits for reference count + */ +static const uint64_t refs_ts = 0x7fffffffffffffff; // tombstone + +/* FNV-1a 64-bit hash */ +static inline uint64_t +fnv_hash(uint64_t a) +{ + const uint64_t prime = 1099511628211; + uint64_t hash = 14695981039346656037LLU; + hash = (hash ^ a) * prime; + hash = (hash ^ ((a << 25) | (a >> 39))) * prime; + return hash ^ (hash >> 32); +} + +// Count number of unique entries (not number of references) +size_t +refs_count(refs_table_t *tbl) +{ + size_t count = 0; + uint64_t *bucket = tbl->refs_table; + uint64_t * const end = bucket + tbl->refs_size; + while (bucket != end) { + if (*bucket != 0 && *bucket != refs_ts) count++; + bucket++; + } + return count; +} + +static inline void +refs_rehash(refs_table_t *tbl, uint64_t v) +{ + if (v == 0) return; // do not rehash empty value + if (v == refs_ts) return; // do not rehash tombstone + + volatile uint64_t *bucket = tbl->refs_table + (fnv_hash(v & 0x000000ffffffffff) % tbl->refs_size); + uint64_t * const end = tbl->refs_table + tbl->refs_size; + + int i = 128; // try 128 times linear probing + while (i--) { + if (*bucket == 0) { if (cas(bucket, 0, v)) return; } + if (++bucket == end) bucket = tbl->refs_table; + } + + // assert(0); // impossible! +} + +/** + * Called internally to assist resize operations + * Returns 1 for retry, 0 for done + */ +static int +refs_resize_help(refs_table_t *tbl) +{ + if (0 == (tbl->refs_control & 0xf0000000)) return 0; // no resize in progress (anymore) + if (tbl->refs_control & 0x80000000) return 1; // still waiting for preparation + + if (tbl->refs_resize_part >= tbl->refs_resize_size / 128) return 1; // all parts claimed + size_t part = __sync_fetch_and_add(&tbl->refs_resize_part, 1); + if (part >= tbl->refs_resize_size/128) return 1; // all parts claimed + + // rehash all + int i; + volatile uint64_t *bucket = tbl->refs_resize_table + part * 128; + for (i=0; i<128; i++) refs_rehash(tbl, *bucket++); + + __sync_fetch_and_add(&tbl->refs_resize_done, 1); + return 1; +} + +static void +refs_resize(refs_table_t *tbl) +{ + while (1) { + uint32_t v = tbl->refs_control; + if (v & 0xf0000000) { + // someone else started resize + // just rehash blocks until done + while (refs_resize_help(tbl)) continue; + return; + } + if (cas(&tbl->refs_control, v, 0x80000000 | v)) { + // wait until all users gone + while (tbl->refs_control != 0x80000000) continue; + break; + } + } + + tbl->refs_resize_table = tbl->refs_table; + tbl->refs_resize_size = tbl->refs_size; + tbl->refs_resize_part = 0; + tbl->refs_resize_done = 0; + + // calculate new size + size_t new_size = tbl->refs_size; + size_t count = refs_count(tbl); + if (count*4 > tbl->refs_size) new_size *= 2; + + // allocate new table + uint64_t *new_table = (uint64_t*)mmap(0, new_size * sizeof(uint64_t), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); + if (new_table == (uint64_t*)-1) { + fprintf(stderr, "refs: Unable to allocate memory: %s!\n", strerror(errno)); + exit(1); + } + + // set new data and go + tbl->refs_table = new_table; + tbl->refs_size = new_size; + compiler_barrier(); + tbl->refs_control = 0x40000000; + + // until all parts are done, rehash blocks + while (tbl->refs_resize_done != tbl->refs_resize_size/128) refs_resize_help(tbl); + + // done! + compiler_barrier(); + tbl->refs_control = 0; + + // unmap old table + munmap(tbl->refs_resize_table, tbl->refs_resize_size * sizeof(uint64_t)); +} + +/* Enter refs_modify */ +static inline void +refs_enter(refs_table_t *tbl) +{ + for (;;) { + uint32_t v = tbl->refs_control; + if (v & 0xf0000000) { + while (refs_resize_help(tbl)) continue; + } else { + if (cas(&tbl->refs_control, v, v+1)) return; + } + } +} + +/* Leave refs_modify */ +static inline void +refs_leave(refs_table_t *tbl) +{ + for (;;) { + uint32_t v = tbl->refs_control; + if (cas(&tbl->refs_control, v, v-1)) return; + } +} + +static inline int +refs_modify(refs_table_t *tbl, const uint64_t a, const int dir) +{ + volatile uint64_t *bucket; + volatile uint64_t *ts_bucket; + uint64_t v, new_v; + int res, i; + + refs_enter(tbl); + +ref_retry: + bucket = tbl->refs_table + (fnv_hash(a) & (tbl->refs_size - 1)); + ts_bucket = NULL; // tombstone + i = 128; // try 128 times linear probing + + while (i--) { +ref_restart: + v = *bucket; + if (v == refs_ts) { + if (ts_bucket == NULL) ts_bucket = bucket; + } else if (v == 0) { + // not found + res = 0; + if (dir < 0) goto ref_exit; + if (ts_bucket != NULL) { + bucket = ts_bucket; + ts_bucket = NULL; + v = refs_ts; + } + new_v = a | (1ULL << 40); + goto ref_mod; + } else if ((v & 0x000000ffffffffff) == a) { + // found + res = 1; + uint64_t count = v >> 40; + if (count == 0x7fffff) goto ref_exit; + count += dir; + if (count == 0) new_v = refs_ts; + else new_v = a | (count << 40); + goto ref_mod; + } + + if (++bucket == tbl->refs_table + tbl->refs_size) bucket = tbl->refs_table; + } + + // not found after linear probing + if (dir < 0) { + res = 0; + goto ref_exit; + } else if (ts_bucket != NULL) { + bucket = ts_bucket; + ts_bucket = NULL; + v = refs_ts; + new_v = a | (1ULL << 40); + if (!cas(bucket, v, new_v)) goto ref_retry; + res = 1; + goto ref_exit; + } else { + // hash table full + refs_leave(tbl); + refs_resize(tbl); + return refs_modify(tbl, a, dir); + } + +ref_mod: + if (!cas(bucket, v, new_v)) goto ref_restart; + +ref_exit: + refs_leave(tbl); + return res; +} + +void +refs_up(refs_table_t *tbl, uint64_t a) +{ + refs_modify(tbl, a, 1); +} + +void +refs_down(refs_table_t *tbl, uint64_t a) +{ +#ifdef NDEBUG + refs_modify(tbl, a, -1); +#else + int res = refs_modify(tbl, a, -1); + assert(res != 0); +#endif +} + +uint64_t* +refs_iter(refs_table_t *tbl, size_t first, size_t end) +{ + // assert(first < tbl->refs_size); + // assert(end <= tbl->refs_size); + + uint64_t *bucket = tbl->refs_table + first; + while (bucket != tbl->refs_table + end) { + if (*bucket != 0 && *bucket != refs_ts) return bucket; + bucket++; + } + return NULL; +} + +uint64_t +refs_next(refs_table_t *tbl, uint64_t **_bucket, size_t end) +{ + uint64_t *bucket = *_bucket; + // assert(bucket != NULL); + // assert(end <= tbl->refs_size); + uint64_t result = *bucket & 0x000000ffffffffff; + bucket++; + while (bucket != tbl->refs_table + end) { + if (*bucket != 0 && *bucket != refs_ts) { + *_bucket = bucket; + return result; + } + bucket++; + } + *_bucket = NULL; + return result; +} + +void +refs_create(refs_table_t *tbl, size_t _refs_size) +{ + if (__builtin_popcountll(_refs_size) != 1) { + fprintf(stderr, "refs: Table size must be a power of 2!\n"); + exit(1); + } + + tbl->refs_size = _refs_size; + tbl->refs_table = (uint64_t*)mmap(0, tbl->refs_size * sizeof(uint64_t), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); + if (tbl->refs_table == (uint64_t*)-1) { + fprintf(stderr, "refs: Unable to allocate memory: %s!\n", strerror(errno)); + exit(1); + } +} + +void +refs_free(refs_table_t *tbl) +{ + munmap(tbl->refs_table, tbl->refs_size * sizeof(uint64_t)); +} + +/** + * Simple implementation of a 64-bit resizable hash-table + * No idea if this is scalable... :( but it seems thread-safe + */ + +// Count number of unique entries (not number of references) +size_t +protect_count(refs_table_t *tbl) +{ + size_t count = 0; + uint64_t *bucket = tbl->refs_table; + uint64_t * const end = bucket + tbl->refs_size; + while (bucket != end) { + if (*bucket != 0 && *bucket != refs_ts) count++; + bucket++; + } + return count; +} + +static inline void +protect_rehash(refs_table_t *tbl, uint64_t v) +{ + if (v == 0) return; // do not rehash empty value + if (v == refs_ts) return; // do not rehash tombstone + + volatile uint64_t *bucket = tbl->refs_table + (fnv_hash(v) % tbl->refs_size); + uint64_t * const end = tbl->refs_table + tbl->refs_size; + + int i = 128; // try 128 times linear probing + while (i--) { + if (*bucket == 0 && cas(bucket, 0, v)) return; + if (++bucket == end) bucket = tbl->refs_table; + } + + assert(0); // whoops! +} + +/** + * Called internally to assist resize operations + * Returns 1 for retry, 0 for done + */ +static int +protect_resize_help(refs_table_t *tbl) +{ + if (0 == (tbl->refs_control & 0xf0000000)) return 0; // no resize in progress (anymore) + if (tbl->refs_control & 0x80000000) return 1; // still waiting for preparation + if (tbl->refs_resize_part >= tbl->refs_resize_size / 128) return 1; // all parts claimed + size_t part = __sync_fetch_and_add(&tbl->refs_resize_part, 1); + if (part >= tbl->refs_resize_size/128) return 1; // all parts claimed + + // rehash all + int i; + volatile uint64_t *bucket = tbl->refs_resize_table + part * 128; + for (i=0; i<128; i++) protect_rehash(tbl, *bucket++); + + __sync_fetch_and_add(&tbl->refs_resize_done, 1); + return 1; +} + +static void +protect_resize(refs_table_t *tbl) +{ + while (1) { + uint32_t v = tbl->refs_control; + if (v & 0xf0000000) { + // someone else started resize + // just rehash blocks until done + while (protect_resize_help(tbl)) continue; + return; + } + if (cas(&tbl->refs_control, v, 0x80000000 | v)) { + // wait until all users gone + while (tbl->refs_control != 0x80000000) continue; + break; + } + } + + tbl->refs_resize_table = tbl->refs_table; + tbl->refs_resize_size = tbl->refs_size; + tbl->refs_resize_part = 0; + tbl->refs_resize_done = 0; + + // calculate new size + size_t new_size = tbl->refs_size; + size_t count = refs_count(tbl); + if (count*4 > tbl->refs_size) new_size *= 2; + + // allocate new table + uint64_t *new_table = (uint64_t*)mmap(0, new_size * sizeof(uint64_t), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); + if (new_table == (uint64_t*)-1) { + fprintf(stderr, "refs: Unable to allocate memory: %s!\n", strerror(errno)); + exit(1); + } + + // set new data and go + tbl->refs_table = new_table; + tbl->refs_size = new_size; + compiler_barrier(); + tbl->refs_control = 0x40000000; + + // until all parts are done, rehash blocks + while (tbl->refs_resize_done < tbl->refs_resize_size/128) protect_resize_help(tbl); + + // done! + compiler_barrier(); + tbl->refs_control = 0; + + // unmap old table + munmap(tbl->refs_resize_table, tbl->refs_resize_size * sizeof(uint64_t)); +} + +static inline void +protect_enter(refs_table_t *tbl) +{ + for (;;) { + uint32_t v = tbl->refs_control; + if (v & 0xf0000000) { + while (protect_resize_help(tbl)) continue; + } else { + if (cas(&tbl->refs_control, v, v+1)) return; + } + } +} + +static inline void +protect_leave(refs_table_t *tbl) +{ + for (;;) { + uint32_t v = tbl->refs_control; + if (cas(&tbl->refs_control, v, v-1)) return; + } +} + +void +protect_up(refs_table_t *tbl, uint64_t a) +{ + volatile uint64_t *bucket; + volatile uint64_t *ts_bucket; + uint64_t v; + int i; + + protect_enter(tbl); + +ref_retry: + bucket = tbl->refs_table + (fnv_hash(a) & (tbl->refs_size - 1)); + ts_bucket = NULL; // tombstone + i = 128; // try 128 times linear probing + + while (i--) { +ref_restart: + v = *bucket; + if (v == refs_ts) { + if (ts_bucket == NULL) ts_bucket = bucket; + } else if (v == 0) { + // go go go + if (ts_bucket != NULL) { + if (cas(ts_bucket, refs_ts, a)) { + protect_leave(tbl); + return; + } else { + goto ref_retry; + } + } else { + if (cas(bucket, 0, a)) { + protect_leave(tbl); + return; + } else { + goto ref_restart; + } + } + } + + if (++bucket == tbl->refs_table + tbl->refs_size) bucket = tbl->refs_table; + } + + // not found after linear probing + if (ts_bucket != NULL) { + if (cas(ts_bucket, refs_ts, a)) { + protect_leave(tbl); + return; + } else { + goto ref_retry; + } + } else { + // hash table full + protect_leave(tbl); + protect_resize(tbl); + protect_enter(tbl); + goto ref_retry; + } +} + +void +protect_down(refs_table_t *tbl, uint64_t a) +{ + volatile uint64_t *bucket; + protect_enter(tbl); + + bucket = tbl->refs_table + (fnv_hash(a) & (tbl->refs_size - 1)); + int i = 128; // try 128 times linear probing + + while (i--) { + if (*bucket == a) { + *bucket = refs_ts; + protect_leave(tbl); + return; + } + if (++bucket == tbl->refs_table + tbl->refs_size) bucket = tbl->refs_table; + } + + // not found after linear probing + assert(0); +} + +uint64_t* +protect_iter(refs_table_t *tbl, size_t first, size_t end) +{ + // assert(first < tbl->refs_size); + // assert(end <= tbl->refs_size); + + uint64_t *bucket = tbl->refs_table + first; + while (bucket != tbl->refs_table + end) { + if (*bucket != 0 && *bucket != refs_ts) return bucket; + bucket++; + } + return NULL; +} + +uint64_t +protect_next(refs_table_t *tbl, uint64_t **_bucket, size_t end) +{ + uint64_t *bucket = *_bucket; + // assert(bucket != NULL); + // assert(end <= tbl->refs_size); + uint64_t result = *bucket; + bucket++; + while (bucket != tbl->refs_table + end) { + if (*bucket != 0 && *bucket != refs_ts) { + *_bucket = bucket; + return result; + } + bucket++; + } + *_bucket = NULL; + return result; +} + +void +protect_create(refs_table_t *tbl, size_t _refs_size) +{ + if (__builtin_popcountll(_refs_size) != 1) { + fprintf(stderr, "refs: Table size must be a power of 2!\n"); + exit(1); + } + + tbl->refs_size = _refs_size; + tbl->refs_table = (uint64_t*)mmap(0, tbl->refs_size * sizeof(uint64_t), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); + if (tbl->refs_table == (uint64_t*)-1) { + fprintf(stderr, "refs: Unable to allocate memory: %s!\n", strerror(errno)); + exit(1); + } +} + +void +protect_free(refs_table_t *tbl) +{ + munmap(tbl->refs_table, tbl->refs_size * sizeof(uint64_t)); + tbl->refs_table = 0; +} diff --git a/src/refs.h b/src/refs.h new file mode 100644 index 000000000..928948c90 --- /dev/null +++ b/src/refs.h @@ -0,0 +1,77 @@ +/* + * Copyright 2011-2015 Formal Methods and Tools, University of Twente + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <sylvan_config.h> +#include <stdint.h> // for uint32_t etc + +#ifndef REFS_INLINE_H +#define REFS_INLINE_H + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** + * Implementation of external references + * Based on a hash table for 40-bit non-null values, linear probing + * Use tombstones for deleting, higher bits for reference count + */ +typedef struct +{ + uint64_t *refs_table; // table itself + size_t refs_size; // number of buckets + + /* helpers during resize operation */ + volatile uint32_t refs_control; // control field + uint64_t *refs_resize_table; // previous table + size_t refs_resize_size; // size of previous table + size_t refs_resize_part; // which part is next + size_t refs_resize_done; // how many parts are done +} refs_table_t; + +// Count number of unique entries (not number of references) +size_t refs_count(refs_table_t *tbl); + +// Increase or decrease reference to 40-bit value a +// Will fail (assertion) if more down than up are called for a +void refs_up(refs_table_t *tbl, uint64_t a); +void refs_down(refs_table_t *tbl, uint64_t a); + +// Return a bucket or NULL to start iterating +uint64_t *refs_iter(refs_table_t *tbl, size_t first, size_t end); + +// Continue iterating, set bucket to next bucket or NULL +uint64_t refs_next(refs_table_t *tbl, uint64_t **bucket, size_t end); + +// User must supply a pointer, refs_create and refs_free handle initialization/destruction +void refs_create(refs_table_t *tbl, size_t _refs_size); +void refs_free(refs_table_t *tbl); + +// The same, but now for 64-bit values ("protect pointers") +size_t protect_count(refs_table_t *tbl); +void protect_up(refs_table_t *tbl, uint64_t a); +void protect_down(refs_table_t *tbl, uint64_t a); +uint64_t *protect_iter(refs_table_t *tbl, size_t first, size_t end); +uint64_t protect_next(refs_table_t *tbl, uint64_t **bucket, size_t end); +void protect_create(refs_table_t *tbl, size_t _refs_size); +void protect_free(refs_table_t *tbl); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + + +#endif diff --git a/src/sha2.c b/src/sha2.c new file mode 100644 index 000000000..db17dbb2d --- /dev/null +++ b/src/sha2.c @@ -0,0 +1,1067 @@ +/* + * FILE: sha2.c + * AUTHOR: Aaron D. Gifford - http://www.aarongifford.com/ + * + * Copyright (c) 2000-2001, Aaron D. Gifford + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTOR(S) ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTOR(S) BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include <string.h> /* memcpy()/memset() or bcopy()/bzero() */ +#include <assert.h> /* assert() */ +#include "sha2.h" + +/* + * ASSERT NOTE: + * Some sanity checking code is included using assert(). On my FreeBSD + * system, this additional code can be removed by compiling with NDEBUG + * defined. Check your own systems manpage on assert() to see how to + * compile WITHOUT the sanity checking code on your system. + * + * UNROLLED TRANSFORM LOOP NOTE: + * You can define SHA2_UNROLL_TRANSFORM to use the unrolled transform + * loop version for the hash transform rounds (defined using macros + * later in this file). Either define on the command line, for example: + * + * cc -DSHA2_UNROLL_TRANSFORM -o sha2 sha2.c sha2prog.c + * + * or define below: + * + * #define SHA2_UNROLL_TRANSFORM + * + */ + + +/*** SHA-256/384/512 Machine Architecture Definitions *****************/ +/* + * BYTE_ORDER NOTE: + * + * Please make sure that your system defines BYTE_ORDER. If your + * architecture is little-endian, make sure it also defines + * LITTLE_ENDIAN and that the two (BYTE_ORDER and LITTLE_ENDIAN) are + * equivilent. + * + * If your system does not define the above, then you can do so by + * hand like this: + * + * #define LITTLE_ENDIAN 1234 + * #define BIG_ENDIAN 4321 + * + * And for little-endian machines, add: + * + * #define BYTE_ORDER LITTLE_ENDIAN + * + * Or for big-endian machines: + * + * #define BYTE_ORDER BIG_ENDIAN + * + * The FreeBSD machine this was written on defines BYTE_ORDER + * appropriately by including <sys/types.h> (which in turn includes + * <machine/endian.h> where the appropriate definitions are actually + * made). + */ +#if !defined(BYTE_ORDER) || (BYTE_ORDER != LITTLE_ENDIAN && BYTE_ORDER != BIG_ENDIAN) +#error Define BYTE_ORDER to be equal to either LITTLE_ENDIAN or BIG_ENDIAN +#endif + +/* + * Define the followingsha2_* types to types of the correct length on + * the native archtecture. Most BSD systems and Linux define u_intXX_t + * types. Machines with very recent ANSI C headers, can use the + * uintXX_t definintions from inttypes.h by defining SHA2_USE_INTTYPES_H + * during compile or in the sha.h header file. + * + * Machines that support neither u_intXX_t nor inttypes.h's uintXX_t + * will need to define these three typedefs below (and the appropriate + * ones in sha.h too) by hand according to their system architecture. + * + * Thank you, Jun-ichiro itojun Hagino, for suggesting using u_intXX_t + * types and pointing out recent ANSI C support for uintXX_t in inttypes.h. + */ +#ifdef SHA2_USE_INTTYPES_H + +typedef uint8_t sha2_byte; /* Exactly 1 byte */ +typedef uint32_t sha2_word32; /* Exactly 4 bytes */ +typedef uint64_t sha2_word64; /* Exactly 8 bytes */ + +#else /* SHA2_USE_INTTYPES_H */ + +typedef u_int8_t sha2_byte; /* Exactly 1 byte */ +typedef u_int32_t sha2_word32; /* Exactly 4 bytes */ +typedef u_int64_t sha2_word64; /* Exactly 8 bytes */ + +#endif /* SHA2_USE_INTTYPES_H */ + + +/*** SHA-256/384/512 Various Length Definitions ***********************/ +/* NOTE: Most of these are in sha2.h */ +#define SHA256_SHORT_BLOCK_LENGTH (SHA256_BLOCK_LENGTH - 8) +#define SHA384_SHORT_BLOCK_LENGTH (SHA384_BLOCK_LENGTH - 16) +#define SHA512_SHORT_BLOCK_LENGTH (SHA512_BLOCK_LENGTH - 16) + + +/*** ENDIAN REVERSAL MACROS *******************************************/ +#if BYTE_ORDER == LITTLE_ENDIAN +#define REVERSE32(w,x) { \ + sha2_word32 tmp = (w); \ + tmp = (tmp >> 16) | (tmp << 16); \ + (x) = ((tmp & 0xff00ff00UL) >> 8) | ((tmp & 0x00ff00ffUL) << 8); \ +} +#define REVERSE64(w,x) { \ + sha2_word64 tmp = (w); \ + tmp = (tmp >> 32) | (tmp << 32); \ + tmp = ((tmp & 0xff00ff00ff00ff00ULL) >> 8) | \ + ((tmp & 0x00ff00ff00ff00ffULL) << 8); \ + (x) = ((tmp & 0xffff0000ffff0000ULL) >> 16) | \ + ((tmp & 0x0000ffff0000ffffULL) << 16); \ +} +#endif /* BYTE_ORDER == LITTLE_ENDIAN */ + +/* + * Macro for incrementally adding the unsigned 64-bit integer n to the + * unsigned 128-bit integer (represented using a two-element array of + * 64-bit words): + */ +#define ADDINC128(w,n) { \ + (w)[0] += (sha2_word64)(n); \ + if ((w)[0] < (n)) { \ + (w)[1]++; \ + } \ +} + +/* + * Macros for copying blocks of memory and for zeroing out ranges + * of memory. Using these macros makes it easy to switch from + * using memset()/memcpy() and using bzero()/bcopy(). + * + * Please define either SHA2_USE_MEMSET_MEMCPY or define + * SHA2_USE_BZERO_BCOPY depending on which function set you + * choose to use: + */ +#if !defined(SHA2_USE_MEMSET_MEMCPY) && !defined(SHA2_USE_BZERO_BCOPY) +/* Default to memset()/memcpy() if no option is specified */ +#define SHA2_USE_MEMSET_MEMCPY 1 +#endif +#if defined(SHA2_USE_MEMSET_MEMCPY) && defined(SHA2_USE_BZERO_BCOPY) +/* Abort with an error if BOTH options are defined */ +#error Define either SHA2_USE_MEMSET_MEMCPY or SHA2_USE_BZERO_BCOPY, not both! +#endif + +#ifdef SHA2_USE_MEMSET_MEMCPY +#define MEMSET_BZERO(p,l) memset((p), 0, (l)) +#define MEMCPY_BCOPY(d,s,l) memcpy((d), (s), (l)) +#endif +#ifdef SHA2_USE_BZERO_BCOPY +#define MEMSET_BZERO(p,l) bzero((p), (l)) +#define MEMCPY_BCOPY(d,s,l) bcopy((s), (d), (l)) +#endif + + +/*** THE SIX LOGICAL FUNCTIONS ****************************************/ +/* + * Bit shifting and rotation (used by the six SHA-XYZ logical functions: + * + * NOTE: The naming of R and S appears backwards here (R is a SHIFT and + * S is a ROTATION) because the SHA-256/384/512 description document + * (see http://csrc.nist.gov/cryptval/shs/sha256-384-512.pdf) uses this + * same "backwards" definition. + */ +/* Shift-right (used in SHA-256, SHA-384, and SHA-512): */ +#define R(b,x) ((x) >> (b)) +/* 32-bit Rotate-right (used in SHA-256): */ +#define S32(b,x) (((x) >> (b)) | ((x) << (32 - (b)))) +/* 64-bit Rotate-right (used in SHA-384 and SHA-512): */ +#define S64(b,x) (((x) >> (b)) | ((x) << (64 - (b)))) + +/* Two of six logical functions used in SHA-256, SHA-384, and SHA-512: */ +#define Ch(x,y,z) (((x) & (y)) ^ ((~(x)) & (z))) +#define Maj(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z))) + +/* Four of six logical functions used in SHA-256: */ +#define Sigma0_256(x) (S32(2, (x)) ^ S32(13, (x)) ^ S32(22, (x))) +#define Sigma1_256(x) (S32(6, (x)) ^ S32(11, (x)) ^ S32(25, (x))) +#define sigma0_256(x) (S32(7, (x)) ^ S32(18, (x)) ^ R(3 , (x))) +#define sigma1_256(x) (S32(17, (x)) ^ S32(19, (x)) ^ R(10, (x))) + +/* Four of six logical functions used in SHA-384 and SHA-512: */ +#define Sigma0_512(x) (S64(28, (x)) ^ S64(34, (x)) ^ S64(39, (x))) +#define Sigma1_512(x) (S64(14, (x)) ^ S64(18, (x)) ^ S64(41, (x))) +#define sigma0_512(x) (S64( 1, (x)) ^ S64( 8, (x)) ^ R( 7, (x))) +#define sigma1_512(x) (S64(19, (x)) ^ S64(61, (x)) ^ R( 6, (x))) + +/*** INTERNAL FUNCTION PROTOTYPES *************************************/ +/* NOTE: These should not be accessed directly from outside this + * library -- they are intended for private internal visibility/use + * only. + */ +void SHA512_Last(SHA512_CTX*); +void SHA256_Transform(SHA256_CTX*, const sha2_word32*); +void SHA512_Transform(SHA512_CTX*, const sha2_word64*); + + +/*** SHA-XYZ INITIAL HASH VALUES AND CONSTANTS ************************/ +/* Hash constant words K for SHA-256: */ +static const sha2_word32 K256[64] = { + 0x428a2f98UL, 0x71374491UL, 0xb5c0fbcfUL, 0xe9b5dba5UL, + 0x3956c25bUL, 0x59f111f1UL, 0x923f82a4UL, 0xab1c5ed5UL, + 0xd807aa98UL, 0x12835b01UL, 0x243185beUL, 0x550c7dc3UL, + 0x72be5d74UL, 0x80deb1feUL, 0x9bdc06a7UL, 0xc19bf174UL, + 0xe49b69c1UL, 0xefbe4786UL, 0x0fc19dc6UL, 0x240ca1ccUL, + 0x2de92c6fUL, 0x4a7484aaUL, 0x5cb0a9dcUL, 0x76f988daUL, + 0x983e5152UL, 0xa831c66dUL, 0xb00327c8UL, 0xbf597fc7UL, + 0xc6e00bf3UL, 0xd5a79147UL, 0x06ca6351UL, 0x14292967UL, + 0x27b70a85UL, 0x2e1b2138UL, 0x4d2c6dfcUL, 0x53380d13UL, + 0x650a7354UL, 0x766a0abbUL, 0x81c2c92eUL, 0x92722c85UL, + 0xa2bfe8a1UL, 0xa81a664bUL, 0xc24b8b70UL, 0xc76c51a3UL, + 0xd192e819UL, 0xd6990624UL, 0xf40e3585UL, 0x106aa070UL, + 0x19a4c116UL, 0x1e376c08UL, 0x2748774cUL, 0x34b0bcb5UL, + 0x391c0cb3UL, 0x4ed8aa4aUL, 0x5b9cca4fUL, 0x682e6ff3UL, + 0x748f82eeUL, 0x78a5636fUL, 0x84c87814UL, 0x8cc70208UL, + 0x90befffaUL, 0xa4506cebUL, 0xbef9a3f7UL, 0xc67178f2UL +}; + +/* Initial hash value H for SHA-256: */ +static const sha2_word32 sha256_initial_hash_value[8] = { + 0x6a09e667UL, + 0xbb67ae85UL, + 0x3c6ef372UL, + 0xa54ff53aUL, + 0x510e527fUL, + 0x9b05688cUL, + 0x1f83d9abUL, + 0x5be0cd19UL +}; + +/* Hash constant words K for SHA-384 and SHA-512: */ +static const sha2_word64 K512[80] = { + 0x428a2f98d728ae22ULL, 0x7137449123ef65cdULL, + 0xb5c0fbcfec4d3b2fULL, 0xe9b5dba58189dbbcULL, + 0x3956c25bf348b538ULL, 0x59f111f1b605d019ULL, + 0x923f82a4af194f9bULL, 0xab1c5ed5da6d8118ULL, + 0xd807aa98a3030242ULL, 0x12835b0145706fbeULL, + 0x243185be4ee4b28cULL, 0x550c7dc3d5ffb4e2ULL, + 0x72be5d74f27b896fULL, 0x80deb1fe3b1696b1ULL, + 0x9bdc06a725c71235ULL, 0xc19bf174cf692694ULL, + 0xe49b69c19ef14ad2ULL, 0xefbe4786384f25e3ULL, + 0x0fc19dc68b8cd5b5ULL, 0x240ca1cc77ac9c65ULL, + 0x2de92c6f592b0275ULL, 0x4a7484aa6ea6e483ULL, + 0x5cb0a9dcbd41fbd4ULL, 0x76f988da831153b5ULL, + 0x983e5152ee66dfabULL, 0xa831c66d2db43210ULL, + 0xb00327c898fb213fULL, 0xbf597fc7beef0ee4ULL, + 0xc6e00bf33da88fc2ULL, 0xd5a79147930aa725ULL, + 0x06ca6351e003826fULL, 0x142929670a0e6e70ULL, + 0x27b70a8546d22ffcULL, 0x2e1b21385c26c926ULL, + 0x4d2c6dfc5ac42aedULL, 0x53380d139d95b3dfULL, + 0x650a73548baf63deULL, 0x766a0abb3c77b2a8ULL, + 0x81c2c92e47edaee6ULL, 0x92722c851482353bULL, + 0xa2bfe8a14cf10364ULL, 0xa81a664bbc423001ULL, + 0xc24b8b70d0f89791ULL, 0xc76c51a30654be30ULL, + 0xd192e819d6ef5218ULL, 0xd69906245565a910ULL, + 0xf40e35855771202aULL, 0x106aa07032bbd1b8ULL, + 0x19a4c116b8d2d0c8ULL, 0x1e376c085141ab53ULL, + 0x2748774cdf8eeb99ULL, 0x34b0bcb5e19b48a8ULL, + 0x391c0cb3c5c95a63ULL, 0x4ed8aa4ae3418acbULL, + 0x5b9cca4f7763e373ULL, 0x682e6ff3d6b2b8a3ULL, + 0x748f82ee5defb2fcULL, 0x78a5636f43172f60ULL, + 0x84c87814a1f0ab72ULL, 0x8cc702081a6439ecULL, + 0x90befffa23631e28ULL, 0xa4506cebde82bde9ULL, + 0xbef9a3f7b2c67915ULL, 0xc67178f2e372532bULL, + 0xca273eceea26619cULL, 0xd186b8c721c0c207ULL, + 0xeada7dd6cde0eb1eULL, 0xf57d4f7fee6ed178ULL, + 0x06f067aa72176fbaULL, 0x0a637dc5a2c898a6ULL, + 0x113f9804bef90daeULL, 0x1b710b35131c471bULL, + 0x28db77f523047d84ULL, 0x32caab7b40c72493ULL, + 0x3c9ebe0a15c9bebcULL, 0x431d67c49c100d4cULL, + 0x4cc5d4becb3e42b6ULL, 0x597f299cfc657e2aULL, + 0x5fcb6fab3ad6faecULL, 0x6c44198c4a475817ULL +}; + +/* Initial hash value H for SHA-384 */ +static const sha2_word64 sha384_initial_hash_value[8] = { + 0xcbbb9d5dc1059ed8ULL, + 0x629a292a367cd507ULL, + 0x9159015a3070dd17ULL, + 0x152fecd8f70e5939ULL, + 0x67332667ffc00b31ULL, + 0x8eb44a8768581511ULL, + 0xdb0c2e0d64f98fa7ULL, + 0x47b5481dbefa4fa4ULL +}; + +/* Initial hash value H for SHA-512 */ +static const sha2_word64 sha512_initial_hash_value[8] = { + 0x6a09e667f3bcc908ULL, + 0xbb67ae8584caa73bULL, + 0x3c6ef372fe94f82bULL, + 0xa54ff53a5f1d36f1ULL, + 0x510e527fade682d1ULL, + 0x9b05688c2b3e6c1fULL, + 0x1f83d9abfb41bd6bULL, + 0x5be0cd19137e2179ULL +}; + +/* + * Constant used by SHA256/384/512_End() functions for converting the + * digest to a readable hexadecimal character string: + */ +static const char *sha2_hex_digits = "0123456789abcdef"; + + +/*** SHA-256: *********************************************************/ +void SHA256_Init(SHA256_CTX* context) { + if (context == (SHA256_CTX*)0) { + return; + } + MEMCPY_BCOPY(context->state, sha256_initial_hash_value, SHA256_DIGEST_LENGTH); + MEMSET_BZERO(context->buffer, SHA256_BLOCK_LENGTH); + context->bitcount = 0; +} + +#ifdef SHA2_UNROLL_TRANSFORM + +/* Unrolled SHA-256 round macros: */ + +#if BYTE_ORDER == LITTLE_ENDIAN + +#define ROUND256_0_TO_15(a,b,c,d,e,f,g,h) \ + REVERSE32(*data++, W256[j]); \ + T1 = (h) + Sigma1_256(e) + Ch((e), (f), (g)) + \ + K256[j] + W256[j]; \ + (d) += T1; \ + (h) = T1 + Sigma0_256(a) + Maj((a), (b), (c)); \ + j++ + + +#else /* BYTE_ORDER == LITTLE_ENDIAN */ + +#define ROUND256_0_TO_15(a,b,c,d,e,f,g,h) \ + T1 = (h) + Sigma1_256(e) + Ch((e), (f), (g)) + \ + K256[j] + (W256[j] = *data++); \ + (d) += T1; \ + (h) = T1 + Sigma0_256(a) + Maj((a), (b), (c)); \ + j++ + +#endif /* BYTE_ORDER == LITTLE_ENDIAN */ + +#define ROUND256(a,b,c,d,e,f,g,h) \ + s0 = W256[(j+1)&0x0f]; \ + s0 = sigma0_256(s0); \ + s1 = W256[(j+14)&0x0f]; \ + s1 = sigma1_256(s1); \ + T1 = (h) + Sigma1_256(e) + Ch((e), (f), (g)) + K256[j] + \ + (W256[j&0x0f] += s1 + W256[(j+9)&0x0f] + s0); \ + (d) += T1; \ + (h) = T1 + Sigma0_256(a) + Maj((a), (b), (c)); \ + j++ + +void SHA256_Transform(SHA256_CTX* context, const sha2_word32* data) { + sha2_word32 a, b, c, d, e, f, g, h, s0, s1; + sha2_word32 T1, *W256; + int j; + + W256 = (sha2_word32*)context->buffer; + + /* Initialize registers with the prev. intermediate value */ + a = context->state[0]; + b = context->state[1]; + c = context->state[2]; + d = context->state[3]; + e = context->state[4]; + f = context->state[5]; + g = context->state[6]; + h = context->state[7]; + + j = 0; + do { + /* Rounds 0 to 15 (unrolled): */ + ROUND256_0_TO_15(a,b,c,d,e,f,g,h); + ROUND256_0_TO_15(h,a,b,c,d,e,f,g); + ROUND256_0_TO_15(g,h,a,b,c,d,e,f); + ROUND256_0_TO_15(f,g,h,a,b,c,d,e); + ROUND256_0_TO_15(e,f,g,h,a,b,c,d); + ROUND256_0_TO_15(d,e,f,g,h,a,b,c); + ROUND256_0_TO_15(c,d,e,f,g,h,a,b); + ROUND256_0_TO_15(b,c,d,e,f,g,h,a); + } while (j < 16); + + /* Now for the remaining rounds to 64: */ + do { + ROUND256(a,b,c,d,e,f,g,h); + ROUND256(h,a,b,c,d,e,f,g); + ROUND256(g,h,a,b,c,d,e,f); + ROUND256(f,g,h,a,b,c,d,e); + ROUND256(e,f,g,h,a,b,c,d); + ROUND256(d,e,f,g,h,a,b,c); + ROUND256(c,d,e,f,g,h,a,b); + ROUND256(b,c,d,e,f,g,h,a); + } while (j < 64); + + /* Compute the current intermediate hash value */ + context->state[0] += a; + context->state[1] += b; + context->state[2] += c; + context->state[3] += d; + context->state[4] += e; + context->state[5] += f; + context->state[6] += g; + context->state[7] += h; + + /* Clean up */ + a = b = c = d = e = f = g = h = T1 = 0; +} + +#else /* SHA2_UNROLL_TRANSFORM */ + +void SHA256_Transform(SHA256_CTX* context, const sha2_word32* data) { + sha2_word32 a, b, c, d, e, f, g, h, s0, s1; + sha2_word32 T1, T2, *W256; + int j; + + W256 = (sha2_word32*)context->buffer; + + /* Initialize registers with the prev. intermediate value */ + a = context->state[0]; + b = context->state[1]; + c = context->state[2]; + d = context->state[3]; + e = context->state[4]; + f = context->state[5]; + g = context->state[6]; + h = context->state[7]; + + j = 0; + do { +#if BYTE_ORDER == LITTLE_ENDIAN + /* Copy data while converting to host byte order */ + REVERSE32(*data++,W256[j]); + /* Apply the SHA-256 compression function to update a..h */ + T1 = h + Sigma1_256(e) + Ch(e, f, g) + K256[j] + W256[j]; +#else /* BYTE_ORDER == LITTLE_ENDIAN */ + /* Apply the SHA-256 compression function to update a..h with copy */ + T1 = h + Sigma1_256(e) + Ch(e, f, g) + K256[j] + (W256[j] = *data++); +#endif /* BYTE_ORDER == LITTLE_ENDIAN */ + T2 = Sigma0_256(a) + Maj(a, b, c); + h = g; + g = f; + f = e; + e = d + T1; + d = c; + c = b; + b = a; + a = T1 + T2; + + j++; + } while (j < 16); + + do { + /* Part of the message block expansion: */ + s0 = W256[(j+1)&0x0f]; + s0 = sigma0_256(s0); + s1 = W256[(j+14)&0x0f]; + s1 = sigma1_256(s1); + + /* Apply the SHA-256 compression function to update a..h */ + T1 = h + Sigma1_256(e) + Ch(e, f, g) + K256[j] + + (W256[j&0x0f] += s1 + W256[(j+9)&0x0f] + s0); + T2 = Sigma0_256(a) + Maj(a, b, c); + h = g; + g = f; + f = e; + e = d + T1; + d = c; + c = b; + b = a; + a = T1 + T2; + + j++; + } while (j < 64); + + /* Compute the current intermediate hash value */ + context->state[0] += a; + context->state[1] += b; + context->state[2] += c; + context->state[3] += d; + context->state[4] += e; + context->state[5] += f; + context->state[6] += g; + context->state[7] += h; + + /* Clean up */ + a = b = c = d = e = f = g = h = T1 = T2 = 0; +} + +#endif /* SHA2_UNROLL_TRANSFORM */ + +void SHA256_Update(SHA256_CTX* context, const sha2_byte *data, size_t len) { + unsigned int freespace, usedspace; + + if (len == 0) { + /* Calling with no data is valid - we do nothing */ + return; + } + + /* Sanity check: */ + assert(context != (SHA256_CTX*)0 && data != (sha2_byte*)0); + + usedspace = (context->bitcount >> 3) % SHA256_BLOCK_LENGTH; + if (usedspace > 0) { + /* Calculate how much free space is available in the buffer */ + freespace = SHA256_BLOCK_LENGTH - usedspace; + + if (len >= freespace) { + /* Fill the buffer completely and process it */ + MEMCPY_BCOPY(&context->buffer[usedspace], data, freespace); + context->bitcount += freespace << 3; + len -= freespace; + data += freespace; + SHA256_Transform(context, (sha2_word32*)context->buffer); + } else { + /* The buffer is not yet full */ + MEMCPY_BCOPY(&context->buffer[usedspace], data, len); + context->bitcount += len << 3; + /* Clean up: */ + usedspace = freespace = 0; + return; + } + } + while (len >= SHA256_BLOCK_LENGTH) { + /* Process as many complete blocks as we can */ + SHA256_Transform(context, (sha2_word32*)data); + context->bitcount += SHA256_BLOCK_LENGTH << 3; + len -= SHA256_BLOCK_LENGTH; + data += SHA256_BLOCK_LENGTH; + } + if (len > 0) { + /* There's left-overs, so save 'em */ + MEMCPY_BCOPY(context->buffer, data, len); + context->bitcount += len << 3; + } + /* Clean up: */ + usedspace = freespace = 0; +} + +void SHA256_Final(sha2_byte digest[], SHA256_CTX* context) { + sha2_word32 *d = (sha2_word32*)digest; + unsigned int usedspace; + + /* Sanity check: */ + assert(context != (SHA256_CTX*)0); + + /* If no digest buffer is passed, we don't bother doing this: */ + if (digest != (sha2_byte*)0) { + usedspace = (context->bitcount >> 3) % SHA256_BLOCK_LENGTH; +#if BYTE_ORDER == LITTLE_ENDIAN + /* Convert FROM host byte order */ + REVERSE64(context->bitcount,context->bitcount); +#endif + if (usedspace > 0) { + /* Begin padding with a 1 bit: */ + context->buffer[usedspace++] = 0x80; + + if (usedspace <= SHA256_SHORT_BLOCK_LENGTH) { + /* Set-up for the last transform: */ + MEMSET_BZERO(&context->buffer[usedspace], SHA256_SHORT_BLOCK_LENGTH - usedspace); + } else { + if (usedspace < SHA256_BLOCK_LENGTH) { + MEMSET_BZERO(&context->buffer[usedspace], SHA256_BLOCK_LENGTH - usedspace); + } + /* Do second-to-last transform: */ + SHA256_Transform(context, (sha2_word32*)context->buffer); + + /* And set-up for the last transform: */ + MEMSET_BZERO(context->buffer, SHA256_SHORT_BLOCK_LENGTH); + } + } else { + /* Set-up for the last transform: */ + MEMSET_BZERO(context->buffer, SHA256_SHORT_BLOCK_LENGTH); + + /* Begin padding with a 1 bit: */ + *context->buffer = 0x80; + } + /* Set the bit count: */ + sha2_word64* ptr = (sha2_word64*)(&context->buffer[SHA256_SHORT_BLOCK_LENGTH]); + *ptr = context->bitcount; + + /* Final transform: */ + SHA256_Transform(context, (sha2_word32*)context->buffer); + +#if BYTE_ORDER == LITTLE_ENDIAN + { + /* Convert TO host byte order */ + int j; + for (j = 0; j < 8; j++) { + REVERSE32(context->state[j],context->state[j]); + *d++ = context->state[j]; + } + } +#else + MEMCPY_BCOPY(d, context->state, SHA256_DIGEST_LENGTH); +#endif + } + + /* Clean up state data: */ + MEMSET_BZERO(context, sizeof(SHA256_CTX)); + usedspace = 0; +} + +char *SHA256_End(SHA256_CTX* context, char buffer[]) { + sha2_byte digest[SHA256_DIGEST_LENGTH], *d = digest; + int i; + + /* Sanity check: */ + assert(context != (SHA256_CTX*)0); + + if (buffer != (char*)0) { + SHA256_Final(digest, context); + + for (i = 0; i < SHA256_DIGEST_LENGTH; i++) { + *buffer++ = sha2_hex_digits[(*d & 0xf0) >> 4]; + *buffer++ = sha2_hex_digits[*d & 0x0f]; + d++; + } + *buffer = (char)0; + } else { + MEMSET_BZERO(context, sizeof(SHA256_CTX)); + } + MEMSET_BZERO(digest, SHA256_DIGEST_LENGTH); + return buffer; +} + +char* SHA256_Data(const sha2_byte* data, size_t len, char digest[SHA256_DIGEST_STRING_LENGTH]) { + SHA256_CTX context; + + SHA256_Init(&context); + SHA256_Update(&context, data, len); + return SHA256_End(&context, digest); +} + + +/*** SHA-512: *********************************************************/ +void SHA512_Init(SHA512_CTX* context) { + if (context == (SHA512_CTX*)0) { + return; + } + MEMCPY_BCOPY(context->state, sha512_initial_hash_value, SHA512_DIGEST_LENGTH); + MEMSET_BZERO(context->buffer, SHA512_BLOCK_LENGTH); + context->bitcount[0] = context->bitcount[1] = 0; +} + +#ifdef SHA2_UNROLL_TRANSFORM + +/* Unrolled SHA-512 round macros: */ +#if BYTE_ORDER == LITTLE_ENDIAN + +#define ROUND512_0_TO_15(a,b,c,d,e,f,g,h) \ + REVERSE64(*data++, W512[j]); \ + T1 = (h) + Sigma1_512(e) + Ch((e), (f), (g)) + \ + K512[j] + W512[j]; \ + (d) += T1, \ + (h) = T1 + Sigma0_512(a) + Maj((a), (b), (c)), \ + j++ + + +#else /* BYTE_ORDER == LITTLE_ENDIAN */ + +#define ROUND512_0_TO_15(a,b,c,d,e,f,g,h) \ + T1 = (h) + Sigma1_512(e) + Ch((e), (f), (g)) + \ + K512[j] + (W512[j] = *data++); \ + (d) += T1; \ + (h) = T1 + Sigma0_512(a) + Maj((a), (b), (c)); \ + j++ + +#endif /* BYTE_ORDER == LITTLE_ENDIAN */ + +#define ROUND512(a,b,c,d,e,f,g,h) \ + s0 = W512[(j+1)&0x0f]; \ + s0 = sigma0_512(s0); \ + s1 = W512[(j+14)&0x0f]; \ + s1 = sigma1_512(s1); \ + T1 = (h) + Sigma1_512(e) + Ch((e), (f), (g)) + K512[j] + \ + (W512[j&0x0f] += s1 + W512[(j+9)&0x0f] + s0); \ + (d) += T1; \ + (h) = T1 + Sigma0_512(a) + Maj((a), (b), (c)); \ + j++ + +void SHA512_Transform(SHA512_CTX* context, const sha2_word64* data) { + sha2_word64 a, b, c, d, e, f, g, h, s0, s1; + sha2_word64 T1, *W512 = (sha2_word64*)context->buffer; + int j; + + /* Initialize registers with the prev. intermediate value */ + a = context->state[0]; + b = context->state[1]; + c = context->state[2]; + d = context->state[3]; + e = context->state[4]; + f = context->state[5]; + g = context->state[6]; + h = context->state[7]; + + j = 0; + do { + ROUND512_0_TO_15(a,b,c,d,e,f,g,h); + ROUND512_0_TO_15(h,a,b,c,d,e,f,g); + ROUND512_0_TO_15(g,h,a,b,c,d,e,f); + ROUND512_0_TO_15(f,g,h,a,b,c,d,e); + ROUND512_0_TO_15(e,f,g,h,a,b,c,d); + ROUND512_0_TO_15(d,e,f,g,h,a,b,c); + ROUND512_0_TO_15(c,d,e,f,g,h,a,b); + ROUND512_0_TO_15(b,c,d,e,f,g,h,a); + } while (j < 16); + + /* Now for the remaining rounds up to 79: */ + do { + ROUND512(a,b,c,d,e,f,g,h); + ROUND512(h,a,b,c,d,e,f,g); + ROUND512(g,h,a,b,c,d,e,f); + ROUND512(f,g,h,a,b,c,d,e); + ROUND512(e,f,g,h,a,b,c,d); + ROUND512(d,e,f,g,h,a,b,c); + ROUND512(c,d,e,f,g,h,a,b); + ROUND512(b,c,d,e,f,g,h,a); + } while (j < 80); + + /* Compute the current intermediate hash value */ + context->state[0] += a; + context->state[1] += b; + context->state[2] += c; + context->state[3] += d; + context->state[4] += e; + context->state[5] += f; + context->state[6] += g; + context->state[7] += h; + + /* Clean up */ + a = b = c = d = e = f = g = h = T1 = 0; +} + +#else /* SHA2_UNROLL_TRANSFORM */ + +void SHA512_Transform(SHA512_CTX* context, const sha2_word64* data) { + sha2_word64 a, b, c, d, e, f, g, h, s0, s1; + sha2_word64 T1, T2, *W512 = (sha2_word64*)context->buffer; + int j; + + /* Initialize registers with the prev. intermediate value */ + a = context->state[0]; + b = context->state[1]; + c = context->state[2]; + d = context->state[3]; + e = context->state[4]; + f = context->state[5]; + g = context->state[6]; + h = context->state[7]; + + j = 0; + do { +#if BYTE_ORDER == LITTLE_ENDIAN + /* Convert TO host byte order */ + REVERSE64(*data++, W512[j]); + /* Apply the SHA-512 compression function to update a..h */ + T1 = h + Sigma1_512(e) + Ch(e, f, g) + K512[j] + W512[j]; +#else /* BYTE_ORDER == LITTLE_ENDIAN */ + /* Apply the SHA-512 compression function to update a..h with copy */ + T1 = h + Sigma1_512(e) + Ch(e, f, g) + K512[j] + (W512[j] = *data++); +#endif /* BYTE_ORDER == LITTLE_ENDIAN */ + T2 = Sigma0_512(a) + Maj(a, b, c); + h = g; + g = f; + f = e; + e = d + T1; + d = c; + c = b; + b = a; + a = T1 + T2; + + j++; + } while (j < 16); + + do { + /* Part of the message block expansion: */ + s0 = W512[(j+1)&0x0f]; + s0 = sigma0_512(s0); + s1 = W512[(j+14)&0x0f]; + s1 = sigma1_512(s1); + + /* Apply the SHA-512 compression function to update a..h */ + T1 = h + Sigma1_512(e) + Ch(e, f, g) + K512[j] + + (W512[j&0x0f] += s1 + W512[(j+9)&0x0f] + s0); + T2 = Sigma0_512(a) + Maj(a, b, c); + h = g; + g = f; + f = e; + e = d + T1; + d = c; + c = b; + b = a; + a = T1 + T2; + + j++; + } while (j < 80); + + /* Compute the current intermediate hash value */ + context->state[0] += a; + context->state[1] += b; + context->state[2] += c; + context->state[3] += d; + context->state[4] += e; + context->state[5] += f; + context->state[6] += g; + context->state[7] += h; + + /* Clean up */ + a = b = c = d = e = f = g = h = T1 = T2 = 0; +} + +#endif /* SHA2_UNROLL_TRANSFORM */ + +void SHA512_Update(SHA512_CTX* context, const sha2_byte *data, size_t len) { + unsigned int freespace, usedspace; + + if (len == 0) { + /* Calling with no data is valid - we do nothing */ + return; + } + + /* Sanity check: */ + assert(context != (SHA512_CTX*)0 && data != (sha2_byte*)0); + + usedspace = (context->bitcount[0] >> 3) % SHA512_BLOCK_LENGTH; + if (usedspace > 0) { + /* Calculate how much free space is available in the buffer */ + freespace = SHA512_BLOCK_LENGTH - usedspace; + + if (len >= freespace) { + /* Fill the buffer completely and process it */ + MEMCPY_BCOPY(&context->buffer[usedspace], data, freespace); + ADDINC128(context->bitcount, freespace << 3); + len -= freespace; + data += freespace; + SHA512_Transform(context, (sha2_word64*)context->buffer); + } else { + /* The buffer is not yet full */ + MEMCPY_BCOPY(&context->buffer[usedspace], data, len); + ADDINC128(context->bitcount, len << 3); + /* Clean up: */ + usedspace = freespace = 0; + return; + } + } + while (len >= SHA512_BLOCK_LENGTH) { + /* Process as many complete blocks as we can */ + SHA512_Transform(context, (sha2_word64*)data); + ADDINC128(context->bitcount, SHA512_BLOCK_LENGTH << 3); + len -= SHA512_BLOCK_LENGTH; + data += SHA512_BLOCK_LENGTH; + } + if (len > 0) { + /* There's left-overs, so save 'em */ + MEMCPY_BCOPY(context->buffer, data, len); + ADDINC128(context->bitcount, len << 3); + } + /* Clean up: */ + usedspace = freespace = 0; +} + +void SHA512_Last(SHA512_CTX* context) { + unsigned int usedspace; + + usedspace = (context->bitcount[0] >> 3) % SHA512_BLOCK_LENGTH; +#if BYTE_ORDER == LITTLE_ENDIAN + /* Convert FROM host byte order */ + REVERSE64(context->bitcount[0],context->bitcount[0]); + REVERSE64(context->bitcount[1],context->bitcount[1]); +#endif + if (usedspace > 0) { + /* Begin padding with a 1 bit: */ + context->buffer[usedspace++] = 0x80; + + if (usedspace <= SHA512_SHORT_BLOCK_LENGTH) { + /* Set-up for the last transform: */ + MEMSET_BZERO(&context->buffer[usedspace], SHA512_SHORT_BLOCK_LENGTH - usedspace); + } else { + if (usedspace < SHA512_BLOCK_LENGTH) { + MEMSET_BZERO(&context->buffer[usedspace], SHA512_BLOCK_LENGTH - usedspace); + } + /* Do second-to-last transform: */ + SHA512_Transform(context, (sha2_word64*)context->buffer); + + /* And set-up for the last transform: */ + MEMSET_BZERO(context->buffer, SHA512_BLOCK_LENGTH - 2); + } + } else { + /* Prepare for final transform: */ + MEMSET_BZERO(context->buffer, SHA512_SHORT_BLOCK_LENGTH); + + /* Begin padding with a 1 bit: */ + *context->buffer = 0x80; + } + /* Store the length of input data (in bits): */ + sha2_word64 *ptr = (sha2_word64*)(&context->buffer[SHA512_SHORT_BLOCK_LENGTH]); + *ptr = context->bitcount[1]; + ptr = (sha2_word64*)(&context->buffer[SHA512_SHORT_BLOCK_LENGTH+8]); + *ptr = context->bitcount[0]; + + /* Final transform: */ + SHA512_Transform(context, (sha2_word64*)context->buffer); +} + +void SHA512_Final(sha2_byte digest[], SHA512_CTX* context) { + sha2_word64 *d = (sha2_word64*)digest; + + /* Sanity check: */ + assert(context != (SHA512_CTX*)0); + + /* If no digest buffer is passed, we don't bother doing this: */ + if (digest != (sha2_byte*)0) { + SHA512_Last(context); + + /* Save the hash data for output: */ +#if BYTE_ORDER == LITTLE_ENDIAN + { + /* Convert TO host byte order */ + int j; + for (j = 0; j < 8; j++) { + REVERSE64(context->state[j],context->state[j]); + *d++ = context->state[j]; + } + } +#else + MEMCPY_BCOPY(d, context->state, SHA512_DIGEST_LENGTH); +#endif + } + + /* Zero out state data */ + MEMSET_BZERO(context, sizeof(SHA512_CTX)); +} + +char *SHA512_End(SHA512_CTX* context, char buffer[]) { + sha2_byte digest[SHA512_DIGEST_LENGTH], *d = digest; + int i; + + /* Sanity check: */ + assert(context != (SHA512_CTX*)0); + + if (buffer != (char*)0) { + SHA512_Final(digest, context); + + for (i = 0; i < SHA512_DIGEST_LENGTH; i++) { + *buffer++ = sha2_hex_digits[(*d & 0xf0) >> 4]; + *buffer++ = sha2_hex_digits[*d & 0x0f]; + d++; + } + *buffer = (char)0; + } else { + MEMSET_BZERO(context, sizeof(SHA512_CTX)); + } + MEMSET_BZERO(digest, SHA512_DIGEST_LENGTH); + return buffer; +} + +char* SHA512_Data(const sha2_byte* data, size_t len, char digest[SHA512_DIGEST_STRING_LENGTH]) { + SHA512_CTX context; + + SHA512_Init(&context); + SHA512_Update(&context, data, len); + return SHA512_End(&context, digest); +} + + +/*** SHA-384: *********************************************************/ +void SHA384_Init(SHA384_CTX* context) { + if (context == (SHA384_CTX*)0) { + return; + } + MEMCPY_BCOPY(context->state, sha384_initial_hash_value, SHA512_DIGEST_LENGTH); + MEMSET_BZERO(context->buffer, SHA384_BLOCK_LENGTH); + context->bitcount[0] = context->bitcount[1] = 0; +} + +void SHA384_Update(SHA384_CTX* context, const sha2_byte* data, size_t len) { + SHA512_Update((SHA512_CTX*)context, data, len); +} + +void SHA384_Final(sha2_byte digest[], SHA384_CTX* context) { + sha2_word64 *d = (sha2_word64*)digest; + + /* Sanity check: */ + assert(context != (SHA384_CTX*)0); + + /* If no digest buffer is passed, we don't bother doing this: */ + if (digest != (sha2_byte*)0) { + SHA512_Last((SHA512_CTX*)context); + + /* Save the hash data for output: */ +#if BYTE_ORDER == LITTLE_ENDIAN + { + /* Convert TO host byte order */ + int j; + for (j = 0; j < 6; j++) { + REVERSE64(context->state[j],context->state[j]); + *d++ = context->state[j]; + } + } +#else + MEMCPY_BCOPY(d, context->state, SHA384_DIGEST_LENGTH); +#endif + } + + /* Zero out state data */ + MEMSET_BZERO(context, sizeof(SHA384_CTX)); +} + +char *SHA384_End(SHA384_CTX* context, char buffer[]) { + sha2_byte digest[SHA384_DIGEST_LENGTH], *d = digest; + int i; + + /* Sanity check: */ + assert(context != (SHA384_CTX*)0); + + if (buffer != (char*)0) { + SHA384_Final(digest, context); + + for (i = 0; i < SHA384_DIGEST_LENGTH; i++) { + *buffer++ = sha2_hex_digits[(*d & 0xf0) >> 4]; + *buffer++ = sha2_hex_digits[*d & 0x0f]; + d++; + } + *buffer = (char)0; + } else { + MEMSET_BZERO(context, sizeof(SHA384_CTX)); + } + MEMSET_BZERO(digest, SHA384_DIGEST_LENGTH); + return buffer; +} + +char* SHA384_Data(const sha2_byte* data, size_t len, char digest[SHA384_DIGEST_STRING_LENGTH]) { + SHA384_CTX context; + + SHA384_Init(&context); + SHA384_Update(&context, data, len); + return SHA384_End(&context, digest); +} + diff --git a/src/sha2.h b/src/sha2.h new file mode 100644 index 000000000..bf759ad45 --- /dev/null +++ b/src/sha2.h @@ -0,0 +1,197 @@ +/* + * FILE: sha2.h + * AUTHOR: Aaron D. Gifford - http://www.aarongifford.com/ + * + * Copyright (c) 2000-2001, Aaron D. Gifford + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTOR(S) ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTOR(S) BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: sha2.h,v 1.1 2001/11/08 00:02:01 adg Exp adg $ + */ + +#ifndef __SHA2_H__ +#define __SHA2_H__ + +#ifdef __cplusplus +extern "C" { +#endif + + +/* + * Import u_intXX_t size_t type definitions from system headers. You + * may need to change this, or define these things yourself in this + * file. + */ +#include <sys/types.h> + +#ifdef SHA2_USE_INTTYPES_H + +#include <inttypes.h> + +#endif /* SHA2_USE_INTTYPES_H */ + + +/*** SHA-256/384/512 Various Length Definitions ***********************/ +#define SHA256_BLOCK_LENGTH 64 +#define SHA256_DIGEST_LENGTH 32 +#define SHA256_DIGEST_STRING_LENGTH (SHA256_DIGEST_LENGTH * 2 + 1) +#define SHA384_BLOCK_LENGTH 128 +#define SHA384_DIGEST_LENGTH 48 +#define SHA384_DIGEST_STRING_LENGTH (SHA384_DIGEST_LENGTH * 2 + 1) +#define SHA512_BLOCK_LENGTH 128 +#define SHA512_DIGEST_LENGTH 64 +#define SHA512_DIGEST_STRING_LENGTH (SHA512_DIGEST_LENGTH * 2 + 1) + + +/*** SHA-256/384/512 Context Structures *******************************/ +/* NOTE: If your architecture does not define either u_intXX_t types or + * uintXX_t (from inttypes.h), you may need to define things by hand + * for your system: + */ +#if 0 +typedef unsigned char u_int8_t; /* 1-byte (8-bits) */ +typedef unsigned int u_int32_t; /* 4-bytes (32-bits) */ +typedef unsigned long long u_int64_t; /* 8-bytes (64-bits) */ +#endif +/* + * Most BSD systems already define u_intXX_t types, as does Linux. + * Some systems, however, like Compaq's Tru64 Unix instead can use + * uintXX_t types defined by very recent ANSI C standards and included + * in the file: + * + * #include <inttypes.h> + * + * If you choose to use <inttypes.h> then please define: + * + * #define SHA2_USE_INTTYPES_H + * + * Or on the command line during compile: + * + * cc -DSHA2_USE_INTTYPES_H ... + */ +#ifdef SHA2_USE_INTTYPES_H + +typedef struct _SHA256_CTX { + uint32_t state[8]; + uint64_t bitcount; + uint8_t buffer[SHA256_BLOCK_LENGTH]; +} SHA256_CTX; +typedef struct _SHA512_CTX { + uint64_t state[8]; + uint64_t bitcount[2]; + uint8_t buffer[SHA512_BLOCK_LENGTH]; +} SHA512_CTX; + +#else /* SHA2_USE_INTTYPES_H */ + +typedef struct _SHA256_CTX { + u_int32_t state[8]; + u_int64_t bitcount; + u_int8_t buffer[SHA256_BLOCK_LENGTH]; +} SHA256_CTX; +typedef struct _SHA512_CTX { + u_int64_t state[8]; + u_int64_t bitcount[2]; + u_int8_t buffer[SHA512_BLOCK_LENGTH]; +} SHA512_CTX; + +#endif /* SHA2_USE_INTTYPES_H */ + +typedef SHA512_CTX SHA384_CTX; + + +/*** SHA-256/384/512 Function Prototypes ******************************/ +#ifndef NOPROTO +#ifdef SHA2_USE_INTTYPES_H + +void SHA256_Init(SHA256_CTX *); +void SHA256_Update(SHA256_CTX*, const uint8_t*, size_t); +void SHA256_Final(uint8_t[SHA256_DIGEST_LENGTH], SHA256_CTX*); +char* SHA256_End(SHA256_CTX*, char[SHA256_DIGEST_STRING_LENGTH]); +char* SHA256_Data(const uint8_t*, size_t, char[SHA256_DIGEST_STRING_LENGTH]); + +void SHA384_Init(SHA384_CTX*); +void SHA384_Update(SHA384_CTX*, const uint8_t*, size_t); +void SHA384_Final(uint8_t[SHA384_DIGEST_LENGTH], SHA384_CTX*); +char* SHA384_End(SHA384_CTX*, char[SHA384_DIGEST_STRING_LENGTH]); +char* SHA384_Data(const uint8_t*, size_t, char[SHA384_DIGEST_STRING_LENGTH]); + +void SHA512_Init(SHA512_CTX*); +void SHA512_Update(SHA512_CTX*, const uint8_t*, size_t); +void SHA512_Final(uint8_t[SHA512_DIGEST_LENGTH], SHA512_CTX*); +char* SHA512_End(SHA512_CTX*, char[SHA512_DIGEST_STRING_LENGTH]); +char* SHA512_Data(const uint8_t*, size_t, char[SHA512_DIGEST_STRING_LENGTH]); + +#else /* SHA2_USE_INTTYPES_H */ + +void SHA256_Init(SHA256_CTX *); +void SHA256_Update(SHA256_CTX*, const u_int8_t*, size_t); +void SHA256_Final(u_int8_t[SHA256_DIGEST_LENGTH], SHA256_CTX*); +char* SHA256_End(SHA256_CTX*, char[SHA256_DIGEST_STRING_LENGTH]); +char* SHA256_Data(const u_int8_t*, size_t, char[SHA256_DIGEST_STRING_LENGTH]); + +void SHA384_Init(SHA384_CTX*); +void SHA384_Update(SHA384_CTX*, const u_int8_t*, size_t); +void SHA384_Final(u_int8_t[SHA384_DIGEST_LENGTH], SHA384_CTX*); +char* SHA384_End(SHA384_CTX*, char[SHA384_DIGEST_STRING_LENGTH]); +char* SHA384_Data(const u_int8_t*, size_t, char[SHA384_DIGEST_STRING_LENGTH]); + +void SHA512_Init(SHA512_CTX*); +void SHA512_Update(SHA512_CTX*, const u_int8_t*, size_t); +void SHA512_Final(u_int8_t[SHA512_DIGEST_LENGTH], SHA512_CTX*); +char* SHA512_End(SHA512_CTX*, char[SHA512_DIGEST_STRING_LENGTH]); +char* SHA512_Data(const u_int8_t*, size_t, char[SHA512_DIGEST_STRING_LENGTH]); + +#endif /* SHA2_USE_INTTYPES_H */ + +#else /* NOPROTO */ + +void SHA256_Init(); +void SHA256_Update(); +void SHA256_Final(); +char* SHA256_End(); +char* SHA256_Data(); + +void SHA384_Init(); +void SHA384_Update(); +void SHA384_Final(); +char* SHA384_End(); +char* SHA384_Data(); + +void SHA512_Init(); +void SHA512_Update(); +void SHA512_Final(); +char* SHA512_End(); +char* SHA512_Data(); + +#endif /* NOPROTO */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __SHA2_H__ */ + diff --git a/src/stats.c b/src/stats.c new file mode 100644 index 000000000..18ad5c088 --- /dev/null +++ b/src/stats.c @@ -0,0 +1,245 @@ +/* + * Copyright 2011-2014 Formal Methods and Tools, University of Twente + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <errno.h> // for errno +#include <string.h> // memset +#include <stats.h> +#include <sys/mman.h> +#include <inttypes.h> +#include <sylvan.h> // for nodes table + +#if SYLVAN_STATS + +#ifdef __ELF__ +__thread sylvan_stats_t sylvan_stats; +#else +pthread_key_t sylvan_stats_key; +#endif + +#ifndef USE_HWLOC +#define USE_HWLOC 0 +#endif + +#if USE_HWLOC +#include <hwloc.h> +static hwloc_topology_t topo; +#endif + +VOID_TASK_0(sylvan_stats_reset_perthread) +{ +#ifdef __ELF__ + for (int i=0; i<SYLVAN_COUNTER_COUNTER; i++) { + sylvan_stats.counters[i] = 0; + } + for (int i=0; i<SYLVAN_TIMER_COUNTER; i++) { + sylvan_stats.timers[i] = 0; + } +#else + sylvan_stats_t *sylvan_stats = pthread_getspecific(sylvan_stats_key); + if (sylvan_stats == NULL) { + sylvan_stats = mmap(0, sizeof(sylvan_stats_t), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); + if (sylvan_stats == (sylvan_stats_t *)-1) { + fprintf(stderr, "sylvan_stats: Unable to allocate memory: %s!\n", strerror(errno)); + exit(1); + } +#if USE_HWLOC + // Ensure the stats object is on our pu + hwloc_obj_t pu = hwloc_get_obj_by_type(topo, HWLOC_OBJ_PU, LACE_WORKER_PU); + hwloc_set_area_membind(topo, sylvan_stats, sizeof(sylvan_stats_t), pu->cpuset, HWLOC_MEMBIND_BIND, 0); +#endif + pthread_setspecific(sylvan_stats_key, sylvan_stats); + } + for (int i=0; i<SYLVAN_COUNTER_COUNTER; i++) { + sylvan_stats->counters[i] = 0; + } + for (int i=0; i<SYLVAN_TIMER_COUNTER; i++) { + sylvan_stats->timers[i] = 0; + } +#endif +} + +VOID_TASK_IMPL_0(sylvan_stats_init) +{ +#ifndef __ELF__ + pthread_key_create(&sylvan_stats_key, NULL); +#endif +#if USE_HWLOC + hwloc_topology_init(&topo); + hwloc_topology_load(topo); +#endif + TOGETHER(sylvan_stats_reset_perthread); +} + +/** + * Reset all counters (for statistics) + */ +VOID_TASK_IMPL_0(sylvan_stats_reset) +{ + TOGETHER(sylvan_stats_reset_perthread); +} + +#define BLACK "\33[22;30m" +#define GRAY "\33[01;30m" +#define RED "\33[22;31m" +#define LRED "\33[01;31m" +#define GREEN "\33[22;32m" +#define LGREEN "\33[01;32m" +#define BLUE "\33[22;34m" +#define LBLUE "\33[01;34m" +#define BROWN "\33[22;33m" +#define YELLOW "\33[01;33m" +#define CYAN "\33[22;36m" +#define LCYAN "\33[22;36m" +#define MAGENTA "\33[22;35m" +#define LMAGENTA "\33[01;35m" +#define NC "\33[0m" +#define BOLD "\33[1m" +#define ULINE "\33[4m" //underline +#define BLINK "\33[5m" +#define INVERT "\33[7m" + +VOID_TASK_1(sylvan_stats_sum, sylvan_stats_t*, target) +{ +#ifdef __ELF__ + for (int i=0; i<SYLVAN_COUNTER_COUNTER; i++) { + __sync_fetch_and_add(&target->counters[i], sylvan_stats.counters[i]); + } + for (int i=0; i<SYLVAN_TIMER_COUNTER; i++) { + __sync_fetch_and_add(&target->timers[i], sylvan_stats.timers[i]); + } +#else + sylvan_stats_t *sylvan_stats = pthread_getspecific(sylvan_stats_key); + if (sylvan_stats != NULL) { + for (int i=0; i<SYLVAN_COUNTER_COUNTER; i++) { + __sync_fetch_and_add(&target->counters[i], sylvan_stats->counters[i]); + } + for (int i=0; i<SYLVAN_TIMER_COUNTER; i++) { + __sync_fetch_and_add(&target->timers[i], sylvan_stats->timers[i]); + } + } +#endif +} + +void +sylvan_stats_report(FILE *target, int color) +{ +#if !SYLVAN_STATS + (void)target; + (void)color; + return; +#else + (void)color; + + sylvan_stats_t totals; + memset(&totals, 0, sizeof(sylvan_stats_t)); + + LACE_ME; + TOGETHER(sylvan_stats_sum, &totals); + + // fix timers for MACH +#ifdef __MACH__ + mach_timebase_info_data_t timebase; + mach_timebase_info(&timebase); + uint64_t c = timebase.numer/timebase.denom; + for (int i=0;i<SYLVAN_TIMER_COUNTER;i++) totals.timers[i]*=c; +#endif + + if (color) fprintf(target, LRED "*** " BOLD "Sylvan stats" NC LRED " ***" NC); + else fprintf(target, "*** Sylvan stats ***"); + + if (totals.counters[BDD_NODES_CREATED]) { + if (color) fprintf(target, ULINE LBLUE); + fprintf(target, "\nBDD operations count (cache reuse, cache put)\n"); + if (color) fprintf(target, NC); + if (totals.counters[BDD_ITE]) fprintf(target, "ITE: %'"PRIu64 " (%'"PRIu64", %'"PRIu64 ")\n", totals.counters[BDD_ITE], totals.counters[BDD_ITE_CACHED], totals.counters[BDD_ITE_CACHEDPUT]); + if (totals.counters[BDD_AND]) fprintf(target, "AND: %'"PRIu64 " (%'"PRIu64", %'"PRIu64 ")\n", totals.counters[BDD_AND], totals.counters[BDD_AND_CACHED], totals.counters[BDD_AND_CACHEDPUT]); + if (totals.counters[BDD_XOR]) fprintf(target, "XOR: %'"PRIu64 " (%'"PRIu64", %'"PRIu64 ")\n", totals.counters[BDD_XOR], totals.counters[BDD_XOR_CACHED], totals.counters[BDD_XOR_CACHEDPUT]); + if (totals.counters[BDD_EXISTS]) fprintf(target, "Exists: %'"PRIu64 " (%'"PRIu64", %'"PRIu64 ")\n", totals.counters[BDD_EXISTS], totals.counters[BDD_EXISTS_CACHED], totals.counters[BDD_EXISTS_CACHEDPUT]); + if (totals.counters[BDD_AND_EXISTS]) fprintf(target, "AndExists: %'"PRIu64 " (%'"PRIu64", %'"PRIu64 ")\n", totals.counters[BDD_AND_EXISTS], totals.counters[BDD_AND_EXISTS_CACHED], totals.counters[BDD_AND_EXISTS_CACHEDPUT]); + if (totals.counters[BDD_RELNEXT]) fprintf(target, "RelNext: %'"PRIu64 " (%'"PRIu64", %'"PRIu64 ")\n", totals.counters[BDD_RELNEXT], totals.counters[BDD_RELNEXT_CACHED], totals.counters[BDD_RELNEXT_CACHEDPUT]); + if (totals.counters[BDD_RELPREV]) fprintf(target, "RelPrev: %'"PRIu64 " (%'"PRIu64", %'"PRIu64 ")\n", totals.counters[BDD_RELPREV], totals.counters[BDD_RELPREV_CACHED], totals.counters[BDD_RELPREV_CACHEDPUT]); + if (totals.counters[BDD_CLOSURE]) fprintf(target, "Closure: %'"PRIu64 " (%'"PRIu64", %'"PRIu64 ")\n", totals.counters[BDD_CLOSURE], totals.counters[BDD_CLOSURE_CACHED], totals.counters[BDD_CLOSURE_CACHEDPUT]); + if (totals.counters[BDD_COMPOSE]) fprintf(target, "Compose: %'"PRIu64 " (%'"PRIu64", %'"PRIu64 ")\n", totals.counters[BDD_COMPOSE], totals.counters[BDD_COMPOSE_CACHED], totals.counters[BDD_COMPOSE_CACHEDPUT]); + if (totals.counters[BDD_RESTRICT]) fprintf(target, "Restrict: %'"PRIu64 " (%'"PRIu64", %'"PRIu64 ")\n", totals.counters[BDD_RESTRICT], totals.counters[BDD_RESTRICT_CACHED], totals.counters[BDD_RESTRICT_CACHEDPUT]); + if (totals.counters[BDD_CONSTRAIN]) fprintf(target, "Constrain: %'"PRIu64 " (%'"PRIu64", %'"PRIu64 ")\n", totals.counters[BDD_CONSTRAIN], totals.counters[BDD_CONSTRAIN_CACHED], totals.counters[BDD_CONSTRAIN_CACHEDPUT]); + if (totals.counters[BDD_SUPPORT]) fprintf(target, "Support: %'"PRIu64 " (%'"PRIu64", %'"PRIu64 ")\n", totals.counters[BDD_SUPPORT], totals.counters[BDD_SUPPORT_CACHED], totals.counters[BDD_SUPPORT_CACHEDPUT]); + if (totals.counters[BDD_SATCOUNT]) fprintf(target, "SatCount: %'"PRIu64 " (%'"PRIu64", %'"PRIu64 ")\n", totals.counters[BDD_SATCOUNT], totals.counters[BDD_SATCOUNT_CACHED], totals.counters[BDD_SATCOUNT_CACHEDPUT]); + if (totals.counters[BDD_PATHCOUNT]) fprintf(target, "PathCount: %'"PRIu64 " (%'"PRIu64", %'"PRIu64 ")\n", totals.counters[BDD_PATHCOUNT], totals.counters[BDD_PATHCOUNT_CACHED], totals.counters[BDD_PATHCOUNT_CACHEDPUT]); + if (totals.counters[BDD_ISBDD]) fprintf(target, "IsBDD: %'"PRIu64 " (%'"PRIu64", %'"PRIu64 ")\n", totals.counters[BDD_ISBDD], totals.counters[BDD_ISBDD_CACHED], totals.counters[BDD_ISBDD_CACHEDPUT]); + fprintf(target, "BDD Nodes created: %'"PRIu64"\n", totals.counters[BDD_NODES_CREATED]); + fprintf(target, "BDD Nodes reused: %'"PRIu64"\n", totals.counters[BDD_NODES_REUSED]); + } + + if (totals.counters[LDD_NODES_CREATED]) { + if (color) fprintf(target, ULINE LBLUE); + fprintf(target, "\nLDD operations count (cache reuse, cache put)\n"); + if (color) fprintf(target, NC); + if (totals.counters[LDD_UNION]) fprintf(target, "Union: %'"PRIu64 " (%'"PRIu64", %"PRIu64")\n", totals.counters[LDD_UNION], totals.counters[LDD_UNION_CACHED], totals.counters[LDD_UNION_CACHEDPUT]); + if (totals.counters[LDD_MINUS]) fprintf(target, "Minus: %'"PRIu64 " (%'"PRIu64", %"PRIu64")\n", totals.counters[LDD_MINUS], totals.counters[LDD_MINUS_CACHED], totals.counters[LDD_MINUS_CACHEDPUT]); + if (totals.counters[LDD_INTERSECT]) fprintf(target, "Intersect: %'"PRIu64 " (%'"PRIu64", %"PRIu64")\n", totals.counters[LDD_INTERSECT], totals.counters[LDD_INTERSECT_CACHED], totals.counters[LDD_INTERSECT_CACHEDPUT]); + if (totals.counters[LDD_RELPROD]) fprintf(target, "RelProd: %'"PRIu64 " (%'"PRIu64", %"PRIu64")\n", totals.counters[LDD_RELPROD], totals.counters[LDD_RELPROD_CACHED], totals.counters[LDD_RELPROD_CACHEDPUT]); + if (totals.counters[LDD_RELPREV]) fprintf(target, "RelPrev: %'"PRIu64 " (%'"PRIu64", %"PRIu64")\n", totals.counters[LDD_RELPREV], totals.counters[LDD_RELPREV_CACHED], totals.counters[LDD_RELPREV_CACHEDPUT]); + if (totals.counters[LDD_PROJECT]) fprintf(target, "Project: %'"PRIu64 " (%'"PRIu64", %"PRIu64")\n", totals.counters[LDD_PROJECT], totals.counters[LDD_PROJECT_CACHED], totals.counters[LDD_PROJECT_CACHEDPUT]); + if (totals.counters[LDD_JOIN]) fprintf(target, "Join: %'"PRIu64 " (%'"PRIu64", %"PRIu64")\n", totals.counters[LDD_JOIN], totals.counters[LDD_JOIN_CACHED], totals.counters[LDD_JOIN_CACHEDPUT]); + if (totals.counters[LDD_MATCH]) fprintf(target, "Match: %'"PRIu64 " (%'"PRIu64", %"PRIu64")\n", totals.counters[LDD_MATCH], totals.counters[LDD_MATCH_CACHED], totals.counters[LDD_MATCH_CACHEDPUT]); + if (totals.counters[LDD_SATCOUNT]) fprintf(target, "SatCount: %'"PRIu64 " (%'"PRIu64", %"PRIu64")\n", totals.counters[LDD_SATCOUNT], totals.counters[LDD_SATCOUNT_CACHED], totals.counters[LDD_SATCOUNT_CACHEDPUT]); + if (totals.counters[LDD_SATCOUNTL]) fprintf(target, "SatCountL: %'"PRIu64 " (%'"PRIu64", %"PRIu64")\n", totals.counters[LDD_SATCOUNTL], totals.counters[LDD_SATCOUNTL_CACHED], totals.counters[LDD_SATCOUNTL_CACHEDPUT]); + if (totals.counters[LDD_ZIP]) fprintf(target, "Zip: %'"PRIu64 " (%'"PRIu64", %"PRIu64")\n", totals.counters[LDD_ZIP], totals.counters[LDD_ZIP_CACHED], totals.counters[LDD_ZIP_CACHEDPUT]); + if (totals.counters[LDD_RELPROD_UNION]) fprintf(target, "RelProdUnion: %'"PRIu64 " (%'"PRIu64", %"PRIu64")\n", totals.counters[LDD_RELPROD_UNION], totals.counters[LDD_RELPROD_UNION_CACHED], totals.counters[LDD_RELPROD_UNION_CACHEDPUT]); + if (totals.counters[LDD_PROJECT_MINUS]) fprintf(target, "ProjectMinus: %'"PRIu64 " (%'"PRIu64", %"PRIu64")\n", totals.counters[LDD_PROJECT_MINUS], totals.counters[LDD_PROJECT_MINUS_CACHED], totals.counters[LDD_PROJECT_MINUS_CACHEDPUT]); + fprintf(target, "LDD Nodes created: %'"PRIu64"\n", totals.counters[LDD_NODES_CREATED]); + fprintf(target, "LDD Nodes reused: %'"PRIu64"\n", totals.counters[LDD_NODES_REUSED]); + } + + if (color) fprintf(target, ULINE LBLUE); + fprintf(target, "\nGarbage collection\n"); + if (color) fprintf(target, NC); + fprintf(target, "Number of GC executions: %'"PRIu64"\n", totals.counters[SYLVAN_GC_COUNT]); + fprintf(target, "Total time spent: %'.6Lf sec.\n", (long double)totals.timers[SYLVAN_GC]/1000000000); + + if (color) fprintf(target, ULINE LBLUE); + fprintf(target, "\nTables\n"); + if (color) fprintf(target, NC); + fprintf(target, "Unique nodes table: %'zu of %'zu buckets filled.\n", llmsset_count_marked(nodes), llmsset_get_size(nodes)); + fprintf(target, "Operation cache: %'zu of %'zu buckets filled.\n", cache_getused(), cache_getsize()); + + if (color) fprintf(target, ULINE LBLUE); + fprintf(target, "\nUnique table\n"); + if (color) fprintf(target, NC); + fprintf(target, "Number of lookup iterations: %'"PRIu64"\n", totals.counters[LLMSSET_LOOKUP]); +#endif +} + +#else + +VOID_TASK_IMPL_0(sylvan_stats_init) +{ +} + +VOID_TASK_IMPL_0(sylvan_stats_reset) +{ +} + +void +sylvan_stats_report(FILE* target, int color) +{ + (void)target; + (void)color; +} + + + +#endif diff --git a/src/stats.h b/src/stats.h new file mode 100644 index 000000000..c7bfcaae2 --- /dev/null +++ b/src/stats.h @@ -0,0 +1,262 @@ +/* + * Copyright 2011-2015 Formal Methods and Tools, University of Twente + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <lace.h> +#include <sylvan_config.h> + +#ifndef SYLVAN_STATS_H +#define SYLVAN_STATS_H + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef enum { + BDD_ITE, + BDD_AND, + BDD_XOR, + BDD_EXISTS, + BDD_AND_EXISTS, + BDD_RELNEXT, + BDD_RELPREV, + BDD_SATCOUNT, + BDD_COMPOSE, + BDD_RESTRICT, + BDD_CONSTRAIN, + BDD_CLOSURE, + BDD_ISBDD, + BDD_SUPPORT, + BDD_PATHCOUNT, + BDD_ITE_CACHEDPUT, + BDD_AND_CACHEDPUT, + BDD_XOR_CACHEDPUT, + BDD_EXISTS_CACHEDPUT, + BDD_AND_EXISTS_CACHEDPUT, + BDD_RELNEXT_CACHEDPUT, + BDD_RELPREV_CACHEDPUT, + BDD_SATCOUNT_CACHEDPUT, + BDD_COMPOSE_CACHEDPUT, + BDD_RESTRICT_CACHEDPUT, + BDD_CONSTRAIN_CACHEDPUT, + BDD_CLOSURE_CACHEDPUT, + BDD_ISBDD_CACHEDPUT, + BDD_SUPPORT_CACHEDPUT, + BDD_PATHCOUNT_CACHEDPUT, + BDD_ITE_CACHED, + BDD_AND_CACHED, + BDD_XOR_CACHED, + BDD_EXISTS_CACHED, + BDD_AND_EXISTS_CACHED, + BDD_RELNEXT_CACHED, + BDD_RELPREV_CACHED, + BDD_SATCOUNT_CACHED, + BDD_COMPOSE_CACHED, + BDD_RESTRICT_CACHED, + BDD_CONSTRAIN_CACHED, + BDD_CLOSURE_CACHED, + BDD_ISBDD_CACHED, + BDD_SUPPORT_CACHED, + BDD_PATHCOUNT_CACHED, + BDD_NODES_CREATED, + BDD_NODES_REUSED, + + LDD_UNION, + LDD_MINUS, + LDD_INTERSECT, + LDD_RELPROD, + LDD_RELPREV, + LDD_PROJECT, + LDD_JOIN, + LDD_MATCH, + LDD_SATCOUNT, + LDD_SATCOUNTL, + LDD_ZIP, + LDD_RELPROD_UNION, + LDD_PROJECT_MINUS, + LDD_UNION_CACHEDPUT, + LDD_MINUS_CACHEDPUT, + LDD_INTERSECT_CACHEDPUT, + LDD_RELPROD_CACHEDPUT, + LDD_RELPREV_CACHEDPUT, + LDD_PROJECT_CACHEDPUT, + LDD_JOIN_CACHEDPUT, + LDD_MATCH_CACHEDPUT, + LDD_SATCOUNT_CACHEDPUT, + LDD_SATCOUNTL_CACHEDPUT, + LDD_ZIP_CACHEDPUT, + LDD_RELPROD_UNION_CACHEDPUT, + LDD_PROJECT_MINUS_CACHEDPUT, + LDD_UNION_CACHED, + LDD_MINUS_CACHED, + LDD_INTERSECT_CACHED, + LDD_RELPROD_CACHED, + LDD_RELPREV_CACHED, + LDD_PROJECT_CACHED, + LDD_JOIN_CACHED, + LDD_MATCH_CACHED, + LDD_SATCOUNT_CACHED, + LDD_SATCOUNTL_CACHED, + LDD_ZIP_CACHED, + LDD_RELPROD_UNION_CACHED, + LDD_PROJECT_MINUS_CACHED, + LDD_NODES_CREATED, + LDD_NODES_REUSED, + + LLMSSET_LOOKUP, + + SYLVAN_GC_COUNT, + SYLVAN_COUNTER_COUNTER +} Sylvan_Counters; + +typedef enum +{ + SYLVAN_GC, + SYLVAN_TIMER_COUNTER +} Sylvan_Timers; + +/** + * Initialize stats system (done by sylvan_init_package) + */ +#define sylvan_stats_init() CALL(sylvan_stats_init) +VOID_TASK_DECL_0(sylvan_stats_init) + +/** + * Reset all counters (for statistics) + */ +#define sylvan_stats_reset() CALL(sylvan_stats_reset) +VOID_TASK_DECL_0(sylvan_stats_reset) + +/** + * Write statistic report to file (stdout, stderr, etc) + */ +void sylvan_stats_report(FILE* target, int color); + +#if SYLVAN_STATS + +/* Infrastructure for internal markings */ +typedef struct +{ + uint64_t counters[SYLVAN_COUNTER_COUNTER]; + uint64_t timers[SYLVAN_TIMER_COUNTER]; + uint64_t timers_startstop[SYLVAN_TIMER_COUNTER]; +} sylvan_stats_t; + +#ifdef __MACH__ +#include <mach/mach_time.h> +#define getabstime() mach_absolute_time() +#else +#include <time.h> +static uint64_t +getabstime() +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + uint64_t t = ts.tv_sec; + t *= 1000000000UL; + t += ts.tv_nsec; + return t; +} +#endif + +#ifdef __ELF__ +extern __thread sylvan_stats_t sylvan_stats; +#else +#include <pthread.h> +extern pthread_key_t sylvan_stats_key; +#endif + +static inline void +sylvan_stats_count(size_t counter) +{ +#ifdef __ELF__ + sylvan_stats.counters[counter]++; +#else + sylvan_stats_t *sylvan_stats = (sylvan_stats_t*)pthread_getspecific(sylvan_stats_key); + sylvan_stats->counters[counter]++; +#endif +} + +static inline void +sylvan_stats_add(size_t counter, size_t amount) +{ +#ifdef __ELF__ + sylvan_stats.counters[counter]+=amount; +#else + sylvan_stats_t *sylvan_stats = (sylvan_stats_t*)pthread_getspecific(sylvan_stats_key); + sylvan_stats->counters[counter]+=amount; +#endif +} + +static inline void +sylvan_timer_start(size_t timer) +{ + uint64_t t = getabstime(); + +#ifdef __ELF__ + sylvan_stats.timers_startstop[timer] = t; +#else + sylvan_stats_t *sylvan_stats = (sylvan_stats_t*)pthread_getspecific(sylvan_stats_key); + sylvan_stats->timers_startstop[timer] = t; +#endif +} + +static inline void +sylvan_timer_stop(size_t timer) +{ + uint64_t t = getabstime(); + +#ifdef __ELF__ + sylvan_stats.timers[timer] += (t - sylvan_stats.timers_startstop[timer]); +#else + sylvan_stats_t *sylvan_stats = (sylvan_stats_t*)pthread_getspecific(sylvan_stats_key); + sylvan_stats->timers[timer] += (t - sylvan_stats->timers_startstop[timer]); +#endif +} + +#else + +static inline void +sylvan_stats_count(size_t counter) +{ + (void)counter; +} + +static inline void +sylvan_stats_add(size_t counter, size_t amount) +{ + (void)counter; + (void)amount; +} + +static inline void +sylvan_timer_start(size_t timer) +{ + (void)timer; +} + +static inline void +sylvan_timer_stop(size_t timer) +{ + (void)timer; +} + +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif diff --git a/src/sylvan.h b/src/sylvan.h new file mode 100644 index 000000000..6fe09f9d6 --- /dev/null +++ b/src/sylvan.h @@ -0,0 +1,182 @@ +/* + * Copyright 2011-2015 Formal Methods and Tools, University of Twente + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Sylvan: parallel BDD/ListDD package. + * + * This is a multi-core implementation of BDDs with complement edges. + * + * This package requires parallel the work-stealing framework Lace. + * Lace must be initialized before initializing Sylvan + * + * This package uses explicit referencing. + * Use sylvan_ref and sylvan_deref to manage external references. + * + * Garbage collection requires all workers to cooperate. Garbage collection is either initiated + * by the user (calling sylvan_gc) or when the nodes table is full. All Sylvan operations + * check whether they need to cooperate on garbage collection. Garbage collection cannot occur + * otherwise. This means that it is perfectly fine to do this: + * BDD a = sylvan_ref(sylvan_and(b, c)); + * since it is not possible that garbage collection occurs between the two calls. + * + * To temporarily disable garbage collection, use sylvan_gc_disable() and sylvan_gc_enable(). + */ + +#include <sylvan_config.h> + +#include <stdint.h> +#include <stdio.h> // for FILE +#include <stdlib.h> +#include <lace.h> // for definitions + +#include <sylvan_cache.h> +#include <llmsset.h> +#include <stats.h> + +#ifndef SYLVAN_H +#define SYLVAN_H + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#ifndef SYLVAN_SIZE_FIBONACCI +#define SYLVAN_SIZE_FIBONACCI 0 +#endif + +// For now, only support 64-bit systems +typedef char __sylvan_check_size_t_is_8_bytes[(sizeof(uint64_t) == sizeof(size_t))?1:-1]; + +/** + * Initialize the Sylvan parallel decision diagrams package. + * + * After initialization, call sylvan_init_bdd and/or sylvan_init_ldd if you want to use + * the BDD and/or LDD functionality. + * + * BDDs and LDDs share a common node table and operations cache. + * + * The node table is resizable. + * The table is resized automatically when >50% of the table is filled during garbage collection. + * This behavior can be customized by overriding the gc hook. + * + * Memory usage: + * Every node requires 24 bytes memory. (16 bytes data + 8 bytes overhead) + * Every operation cache entry requires 36 bytes memory. (32 bytes data + 4 bytes overhead) + * + * Reasonable defaults: datasize of 1L<<26 (2048 MB), cachesize of 1L<<25 (1152 MB) + */ +void sylvan_init_package(size_t initial_tablesize, size_t max_tablesize, size_t initial_cachesize, size_t max_cachesize); + +/** + * Frees all Sylvan data (also calls the quit() functions of BDD/MDD parts) + */ +void sylvan_quit(); + +/** + * Return number of occupied buckets in nodes table and total number of buckets. + */ +VOID_TASK_DECL_2(sylvan_table_usage, size_t*, size_t*); +#define sylvan_table_usage(filled, total) (CALL(sylvan_table_usage, filled, total)) + +/** + * Perform garbage collection. + * + * Garbage collection is performed in a new Lace frame, interrupting all ongoing work + * until garbage collection is completed. + * + * Garbage collection procedure: + * 1) The operation cache is cleared and the hash table is reset. + * 2) All live nodes are marked (to be rehashed). This is done by the "mark" callbacks. + * 3) The "hook" callback is called. + * By default, this doubles the hash table size when it is >50% full. + * 4) All live nodes are rehashed into the hash table. + * + * The behavior of garbage collection can be customized by adding "mark" callbacks and + * replacing the "hook" callback. + */ +VOID_TASK_DECL_0(sylvan_gc); +#define sylvan_gc() (CALL(sylvan_gc)) + +/** + * Enable or disable garbage collection. + * + * This affects both automatic and manual garbage collection, i.e., + * calling sylvan_gc() while garbage collection is disabled does not have any effect. + */ +void sylvan_gc_enable(); +void sylvan_gc_disable(); + +/** + * Add a "mark" callback to the list of callbacks. + * + * These are called during garbage collection to recursively mark nodes. + * + * Default "mark" functions that mark external references (via sylvan_ref) and internal + * references (inside operations) are added by sylvan_init_bdd/sylvan_init_bdd. + * + * Functions are called in order. + * level 10: marking functions of Sylvan (external/internal references) + * level 20: call the hook function (for resizing) + * level 30: rehashing + */ +LACE_TYPEDEF_CB(void, gc_mark_cb); +void sylvan_gc_add_mark(int order, gc_mark_cb callback); + +/** + * Set "hook" callback. There can be only one. + * + * The hook is called after the "mark" phase and before the "rehash" phase. + * This allows users to perform certain actions, such as resizing the nodes table + * and the operation cache. Also, dynamic resizing could be performed then. + */ +LACE_TYPEDEF_CB(void, gc_hook_cb); +void sylvan_gc_set_hook(gc_hook_cb new_hook); + +/** + * One of the hooks for resizing behavior. + * Default if SYLVAN_AGGRESSIVE_RESIZE is set. + * Always double size on gc() until maximum reached. + */ +VOID_TASK_DECL_0(sylvan_gc_aggressive_resize); + +/** + * One of the hooks for resizing behavior. + * Default if SYLVAN_AGGRESSIVE_RESIZE is not set. + * Double size on gc() whenever >50% is used. + */ +VOID_TASK_DECL_0(sylvan_gc_default_hook); + +/** + * Set "notify on dead" callback for the nodes table. + * See also documentation in llmsset.h + */ +#define sylvan_set_ondead(cb, ctx) llmsset_set_ondead(nodes, cb, ctx) + +/** + * Global variables (number of workers, nodes table) + */ + +extern llmsset_t nodes; + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#include <sylvan_bdd.h> +#include <sylvan_ldd.h> +#include <sylvan_mtbdd.h> + +#endif diff --git a/src/sylvan_bdd.c b/src/sylvan_bdd.c new file mode 100644 index 000000000..74936a62d --- /dev/null +++ b/src/sylvan_bdd.c @@ -0,0 +1,2820 @@ +/* + * Copyright 2011-2014 Formal Methods and Tools, University of Twente + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <sylvan_config.h> + +#include <assert.h> +#include <inttypes.h> +#include <math.h> +#include <pthread.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <avl.h> +#include <refs.h> +#include <sha2.h> +#include <sylvan.h> +#include <sylvan_common.h> + +/** + * Complement handling macros + */ +#define BDD_HASMARK(s) (s&sylvan_complement?1:0) +#define BDD_TOGGLEMARK(s) (s^sylvan_complement) +#define BDD_STRIPMARK(s) (s&~sylvan_complement) +#define BDD_TRANSFERMARK(from, to) (to ^ (from & sylvan_complement)) +// Equal under mark +#define BDD_EQUALM(a, b) ((((a)^(b))&(~sylvan_complement))==0) + +/** + * BDD node structure + */ +typedef struct __attribute__((packed)) bddnode { + uint64_t a, b; +} * bddnode_t; // 16 bytes + +#define GETNODE(bdd) ((bddnode_t)llmsset_index_to_ptr(nodes, bdd&0x000000ffffffffff)) + +static inline int __attribute__((unused)) +bddnode_getcomp(bddnode_t n) +{ + return n->a & 0x8000000000000000 ? 1 : 0; +} + +static inline uint64_t +bddnode_getlow(bddnode_t n) +{ + return n->b & 0x000000ffffffffff; // 40 bits +} + +static inline uint64_t +bddnode_gethigh(bddnode_t n) +{ + return n->a & 0x800000ffffffffff; // 40 bits plus high bit of first +} + +static inline uint32_t +bddnode_getvariable(bddnode_t n) +{ + return (uint32_t)(n->b >> 40); +} + +static inline int +bddnode_getmark(bddnode_t n) +{ + return n->a & 0x2000000000000000 ? 1 : 0; +} + +static inline void +bddnode_setmark(bddnode_t n, int mark) +{ + if (mark) n->a |= 0x2000000000000000; + else n->a &= 0xdfffffffffffffff; +} + +static inline void +bddnode_makenode(bddnode_t n, uint32_t var, uint64_t low, uint64_t high) +{ + n->a = high; + n->b = ((uint64_t)var)<<40 | low; +} + +/** + * Implementation of garbage collection. + */ + +/* Recursively mark BDD nodes as 'in use' */ +VOID_TASK_IMPL_1(sylvan_gc_mark_rec, BDD, bdd) +{ + if (bdd == sylvan_false || bdd == sylvan_true) return; + + if (llmsset_mark(nodes, bdd&0x000000ffffffffff)) { + bddnode_t n = GETNODE(bdd); + SPAWN(sylvan_gc_mark_rec, bddnode_getlow(n)); + CALL(sylvan_gc_mark_rec, bddnode_gethigh(n)); + SYNC(sylvan_gc_mark_rec); + } +} + +/** + * External references + */ + +refs_table_t bdd_refs; +refs_table_t bdd_protected; +static int bdd_protected_created = 0; + +BDD +sylvan_ref(BDD a) +{ + if (a == sylvan_false || a == sylvan_true) return a; + refs_up(&bdd_refs, BDD_STRIPMARK(a)); + return a; +} + +void +sylvan_deref(BDD a) +{ + if (a == sylvan_false || a == sylvan_true) return; + refs_down(&bdd_refs, BDD_STRIPMARK(a)); +} + +void +sylvan_protect(BDD *a) +{ + if (!bdd_protected_created) { + // In C++, sometimes sylvan_protect is called before Sylvan is initialized. Just create a table. + protect_create(&bdd_protected, 4096); + bdd_protected_created = 1; + } + protect_up(&bdd_protected, (size_t)a); +} + +void +sylvan_unprotect(BDD *a) +{ + if (bdd_protected.refs_table != NULL) protect_down(&bdd_protected, (size_t)a); +} + +size_t +sylvan_count_refs() +{ + return refs_count(&bdd_refs); +} + +size_t +sylvan_count_protected() +{ + return protect_count(&bdd_protected); +} + +/* Called during garbage collection */ +VOID_TASK_0(sylvan_gc_mark_external_refs) +{ + // iterate through refs hash table, mark all found + size_t count=0; + uint64_t *it = refs_iter(&bdd_refs, 0, bdd_refs.refs_size); + while (it != NULL) { + BDD to_mark = refs_next(&bdd_refs, &it, bdd_refs.refs_size); + SPAWN(sylvan_gc_mark_rec, to_mark); + count++; + } + while (count--) { + SYNC(sylvan_gc_mark_rec); + } +} + +VOID_TASK_0(sylvan_gc_mark_protected) +{ + // iterate through refs hash table, mark all found + size_t count=0; + uint64_t *it = protect_iter(&bdd_protected, 0, bdd_protected.refs_size); + while (it != NULL) { + BDD *to_mark = (BDD*)protect_next(&bdd_protected, &it, bdd_protected.refs_size); + SPAWN(sylvan_gc_mark_rec, *to_mark); + count++; + } + while (count--) { + SYNC(sylvan_gc_mark_rec); + } +} + +/* Infrastructure for internal markings */ +DECLARE_THREAD_LOCAL(bdd_refs_key, bdd_refs_internal_t); + +VOID_TASK_0(bdd_refs_mark_task) +{ + LOCALIZE_THREAD_LOCAL(bdd_refs_key, bdd_refs_internal_t); + size_t i, j=0; + for (i=0; i<bdd_refs_key->r_count; i++) { + if (j >= 40) { + while (j--) SYNC(sylvan_gc_mark_rec); + j=0; + } + SPAWN(sylvan_gc_mark_rec, bdd_refs_key->results[i]); + j++; + } + for (i=0; i<bdd_refs_key->s_count; i++) { + Task *t = bdd_refs_key->spawns[i]; + if (!TASK_IS_STOLEN(t)) break; + if (TASK_IS_COMPLETED(t)) { + if (j >= 40) { + while (j--) SYNC(sylvan_gc_mark_rec); + j=0; + } + SPAWN(sylvan_gc_mark_rec, *(BDD*)TASK_RESULT(t)); + j++; + } + } + while (j--) SYNC(sylvan_gc_mark_rec); +} + +VOID_TASK_0(bdd_refs_mark) +{ + TOGETHER(bdd_refs_mark_task); +} + +VOID_TASK_0(bdd_refs_init_task) +{ + bdd_refs_internal_t s = (bdd_refs_internal_t)malloc(sizeof(struct bdd_refs_internal)); + s->r_size = 128; + s->r_count = 0; + s->s_size = 128; + s->s_count = 0; + s->results = (BDD*)malloc(sizeof(BDD) * 128); + s->spawns = (Task**)malloc(sizeof(Task*) * 128); + SET_THREAD_LOCAL(bdd_refs_key, s); +} + +VOID_TASK_0(bdd_refs_init) +{ + INIT_THREAD_LOCAL(bdd_refs_key); + TOGETHER(bdd_refs_init_task); + sylvan_gc_add_mark(10, TASK(bdd_refs_mark)); +} + +/** + * Initialize and quit functions + */ + +static int granularity = 1; // default + +static void +sylvan_quit_bdd() +{ + refs_free(&bdd_refs); + if (bdd_protected_created) { + protect_free(&bdd_protected); + bdd_protected_created = 0; + } +} + +void +sylvan_init_bdd(int _granularity) +{ + sylvan_register_quit(sylvan_quit_bdd); + sylvan_gc_add_mark(10, TASK(sylvan_gc_mark_external_refs)); + sylvan_gc_add_mark(10, TASK(sylvan_gc_mark_protected)); + + granularity = _granularity; + + // Sanity check + if (sizeof(struct bddnode) != 16) { + fprintf(stderr, "Invalid size of bdd nodes: %ld\n", sizeof(struct bddnode)); + exit(1); + } + + refs_create(&bdd_refs, 1024); + if (!bdd_protected_created) { + protect_create(&bdd_protected, 4096); + bdd_protected_created = 1; + } + + LACE_ME; + CALL(bdd_refs_init); +} + +/** + * Core BDD operations + */ + +BDD +sylvan_makenode(BDDVAR level, BDD low, BDD high) +{ + if (low == high) return low; + + // Normalization to keep canonicity + // low will have no mark + + struct bddnode n; + int mark; + + if (BDD_HASMARK(low)) { + mark = 1; + low = BDD_TOGGLEMARK(low); + high = BDD_TOGGLEMARK(high); + } else { + mark = 0; + } + + bddnode_makenode(&n, level, low, high); + + BDD result; + int created; + uint64_t index = llmsset_lookup(nodes, n.a, n.b, &created); + if (index == 0) { + LACE_ME; + + bdd_refs_push(low); + bdd_refs_push(high); + sylvan_gc(); + bdd_refs_pop(2); + + index = llmsset_lookup(nodes, n.a, n.b, &created); + if (index == 0) { + fprintf(stderr, "BDD Unique table full, %zu of %zu buckets filled!\n", llmsset_count_marked(nodes), llmsset_get_size(nodes)); + exit(1); + } + } + + if (created) sylvan_stats_count(BDD_NODES_CREATED); + else sylvan_stats_count(BDD_NODES_REUSED); + + result = index; + return mark ? result | sylvan_complement : result; +} + +BDD +sylvan_ithvar(BDDVAR level) +{ + return sylvan_makenode(level, sylvan_false, sylvan_true); +} + +BDDVAR +sylvan_var(BDD bdd) +{ + return bddnode_getvariable(GETNODE(bdd)); +} + +static BDD +node_low(BDD bdd, bddnode_t node) +{ + return BDD_TRANSFERMARK(bdd, bddnode_getlow(node)); +} + +static BDD +node_high(BDD bdd, bddnode_t node) +{ + return BDD_TRANSFERMARK(bdd, bddnode_gethigh(node)); +} + +BDD +sylvan_low(BDD bdd) +{ + if (sylvan_isconst(bdd)) return bdd; + return node_low(bdd, GETNODE(bdd)); +} + +BDD +sylvan_high(BDD bdd) +{ + if (sylvan_isconst(bdd)) return bdd; + return node_high(bdd, GETNODE(bdd)); +} + +/** + * Implementation of unary, binary and if-then-else operators. + */ +TASK_IMPL_3(BDD, sylvan_and, BDD, a, BDD, b, BDDVAR, prev_level) +{ + /* Terminal cases */ + if (a == sylvan_true) return b; + if (b == sylvan_true) return a; + if (a == sylvan_false) return sylvan_false; + if (b == sylvan_false) return sylvan_false; + if (a == b) return a; + if (a == BDD_TOGGLEMARK(b)) return sylvan_false; + + sylvan_gc_test(); + + sylvan_stats_count(BDD_AND); + + /* Improve for caching */ + if (BDD_STRIPMARK(a) > BDD_STRIPMARK(b)) { + BDD t = b; + b = a; + a = t; + } + + bddnode_t na = GETNODE(a); + bddnode_t nb = GETNODE(b); + + BDDVAR va = bddnode_getvariable(na); + BDDVAR vb = bddnode_getvariable(nb); + BDDVAR level = va < vb ? va : vb; + + int cachenow = granularity < 2 || prev_level == 0 ? 1 : prev_level / granularity != level / granularity; + if (cachenow) { + BDD result; + if (cache_get3(CACHE_BDD_AND, a, b, sylvan_false, &result)) { + sylvan_stats_count(BDD_AND_CACHED); + return result; + } + } + + // Get cofactors + BDD aLow = a, aHigh = a; + BDD bLow = b, bHigh = b; + if (level == va) { + aLow = node_low(a, na); + aHigh = node_high(a, na); + } + if (level == vb) { + bLow = node_low(b, nb); + bHigh = node_high(b, nb); + } + + // Recursive computation + BDD low=sylvan_invalid, high=sylvan_invalid, result; + + int n=0; + + if (aHigh == sylvan_true) { + high = bHigh; + } else if (aHigh == sylvan_false || bHigh == sylvan_false) { + high = sylvan_false; + } else if (bHigh == sylvan_true) { + high = aHigh; + } else { + bdd_refs_spawn(SPAWN(sylvan_and, aHigh, bHigh, level)); + n=1; + } + + if (aLow == sylvan_true) { + low = bLow; + } else if (aLow == sylvan_false || bLow == sylvan_false) { + low = sylvan_false; + } else if (bLow == sylvan_true) { + low = aLow; + } else { + low = CALL(sylvan_and, aLow, bLow, level); + } + + if (n) { + bdd_refs_push(low); + high = bdd_refs_sync(SYNC(sylvan_and)); + bdd_refs_pop(1); + } + + result = sylvan_makenode(level, low, high); + + if (cachenow) { + if (cache_put3(CACHE_BDD_AND, a, b, sylvan_false, result)) sylvan_stats_count(BDD_AND_CACHEDPUT); + } + + return result; +} + +TASK_IMPL_3(BDD, sylvan_xor, BDD, a, BDD, b, BDDVAR, prev_level) +{ + /* Terminal cases */ + if (a == sylvan_false) return b; + if (b == sylvan_false) return a; + if (a == sylvan_true) return sylvan_not(b); + if (b == sylvan_true) return sylvan_not(a); + if (a == b) return sylvan_false; + if (a == sylvan_not(b)) return sylvan_true; + + sylvan_gc_test(); + + sylvan_stats_count(BDD_XOR); + + /* Improve for caching */ + if (BDD_STRIPMARK(a) > BDD_STRIPMARK(b)) { + BDD t = b; + b = a; + a = t; + } + + // XOR(~A,B) => XOR(A,~B) + if (BDD_HASMARK(a)) { + a = BDD_STRIPMARK(a); + b = sylvan_not(b); + } + + bddnode_t na = GETNODE(a); + bddnode_t nb = GETNODE(b); + + BDDVAR va = bddnode_getvariable(na); + BDDVAR vb = bddnode_getvariable(nb); + BDDVAR level = va < vb ? va : vb; + + int cachenow = granularity < 2 || prev_level == 0 ? 1 : prev_level / granularity != level / granularity; + if (cachenow) { + BDD result; + if (cache_get3(CACHE_BDD_XOR, a, b, sylvan_false, &result)) { + sylvan_stats_count(BDD_XOR_CACHED); + return result; + } + } + + // Get cofactors + BDD aLow = a, aHigh = a; + BDD bLow = b, bHigh = b; + if (level == va) { + aLow = node_low(a, na); + aHigh = node_high(a, na); + } + if (level == vb) { + bLow = node_low(b, nb); + bHigh = node_high(b, nb); + } + + // Recursive computation + BDD low, high, result; + + bdd_refs_spawn(SPAWN(sylvan_xor, aHigh, bHigh, level)); + low = CALL(sylvan_xor, aLow, bLow, level); + bdd_refs_push(low); + high = bdd_refs_sync(SYNC(sylvan_xor)); + bdd_refs_pop(1); + + result = sylvan_makenode(level, low, high); + + if (cachenow) { + if (cache_put3(CACHE_BDD_XOR, a, b, sylvan_false, result)) sylvan_stats_count(BDD_XOR_CACHEDPUT); + } + + return result; +} + + +TASK_IMPL_4(BDD, sylvan_ite, BDD, a, BDD, b, BDD, c, BDDVAR, prev_level) +{ + /* Terminal cases */ + if (a == sylvan_true) return b; + if (a == sylvan_false) return c; + if (a == b) b = sylvan_true; + if (a == sylvan_not(b)) b = sylvan_false; + if (a == c) c = sylvan_false; + if (a == sylvan_not(c)) c = sylvan_true; + if (b == c) return b; + if (b == sylvan_true && c == sylvan_false) return a; + if (b == sylvan_false && c == sylvan_true) return sylvan_not(a); + + /* Cases that reduce to AND and XOR */ + + // ITE(A,B,0) => AND(A,B) + if (c == sylvan_false) return CALL(sylvan_and, a, b, prev_level); + + // ITE(A,1,C) => ~AND(~A,~C) + if (b == sylvan_true) return sylvan_not(CALL(sylvan_and, sylvan_not(a), sylvan_not(c), prev_level)); + + // ITE(A,0,C) => AND(~A,C) + if (b == sylvan_false) return CALL(sylvan_and, sylvan_not(a), c, prev_level); + + // ITE(A,B,1) => ~AND(A,~B) + if (c == sylvan_true) return sylvan_not(CALL(sylvan_and, a, sylvan_not(b), prev_level)); + + // ITE(A,B,~B) => XOR(A,~B) + if (b == sylvan_not(c)) return CALL(sylvan_xor, a, c, 0); + + /* At this point, there are no more terminals */ + + /* Canonical for optimal cache use */ + + // ITE(~A,B,C) => ITE(A,C,B) + if (BDD_HASMARK(a)) { + a = BDD_STRIPMARK(a); + BDD t = c; + c = b; + b = t; + } + + // ITE(A,~B,C) => ~ITE(A,B,~C) + int mark = 0; + if (BDD_HASMARK(b)) { + b = sylvan_not(b); + c = sylvan_not(c); + mark = 1; + } + + bddnode_t na = GETNODE(a); + bddnode_t nb = GETNODE(b); + bddnode_t nc = GETNODE(c); + + BDDVAR va = bddnode_getvariable(na); + BDDVAR vb = bddnode_getvariable(nb); + BDDVAR vc = bddnode_getvariable(nc); + + // Get lowest level + BDDVAR level = vb < vc ? vb : vc; + + // Fast case + if (va < level && node_low(a, na) == sylvan_false && node_high(a, na) == sylvan_true) { + BDD result = sylvan_makenode(va, c, b); + return mark ? sylvan_not(result) : result; + } + + if (va < level) level = va; + + sylvan_gc_test(); + + sylvan_stats_count(BDD_ITE); + + int cachenow = granularity < 2 || prev_level == 0 ? 1 : prev_level / granularity != level / granularity; + if (cachenow) { + BDD result; + if (cache_get3(CACHE_BDD_ITE, a, b, c, &result)) { + sylvan_stats_count(BDD_ITE_CACHED); + return mark ? sylvan_not(result) : result; + } + } + + // Get cofactors + BDD aLow = a, aHigh = a; + BDD bLow = b, bHigh = b; + BDD cLow = c, cHigh = c; + if (level == va) { + aLow = node_low(a, na); + aHigh = node_high(a, na); + } + if (level == vb) { + bLow = node_low(b, nb); + bHigh = node_high(b, nb); + } + if (level == vc) { + cLow = node_low(c, nc); + cHigh = node_high(c, nc); + } + + // Recursive computation + BDD low=sylvan_invalid, high=sylvan_invalid, result; + + int n=0; + + if (aHigh == sylvan_true) { + high = bHigh; + } else if (aHigh == sylvan_false) { + high = cHigh; + } else { + bdd_refs_spawn(SPAWN(sylvan_ite, aHigh, bHigh, cHigh, level)); + n=1; + } + + if (aLow == sylvan_true) { + low = bLow; + } else if (aLow == sylvan_false) { + low = cLow; + } else { + low = CALL(sylvan_ite, aLow, bLow, cLow, level); + } + + if (n) { + bdd_refs_push(low); + high = bdd_refs_sync(SYNC(sylvan_ite)); + bdd_refs_pop(1); + } + + result = sylvan_makenode(level, low, high); + + if (cachenow) { + if (cache_put3(CACHE_BDD_ITE, a, b, c, result)) sylvan_stats_count(BDD_ITE_CACHEDPUT); + } + + return mark ? sylvan_not(result) : result; +} + +/** + * Calculate constrain a @ c + */ +TASK_IMPL_3(BDD, sylvan_constrain, BDD, a, BDD, b, BDDVAR, prev_level) +{ + /* Trivial cases */ + if (b == sylvan_true) return a; + if (b == sylvan_false) return sylvan_false; + if (sylvan_isconst(a)) return a; + if (a == b) return sylvan_true; + if (a == sylvan_not(b)) return sylvan_false; + + /* Perhaps execute garbage collection */ + sylvan_gc_test(); + + /* Count operation */ + sylvan_stats_count(BDD_CONSTRAIN); + + // a != constant and b != constant + bddnode_t na = GETNODE(a); + bddnode_t nb = GETNODE(b); + + BDDVAR va = bddnode_getvariable(na); + BDDVAR vb = bddnode_getvariable(nb); + BDDVAR level = va < vb ? va : vb; + + // CONSULT CACHE + + int cachenow = granularity < 2 || prev_level == 0 ? 1 : prev_level / granularity != level / granularity; + if (cachenow) { + BDD result; + if (cache_get3(CACHE_BDD_CONSTRAIN, a, b, 0, &result)) { + sylvan_stats_count(BDD_CONSTRAIN_CACHED); + return result; + } + } + + // DETERMINE TOP BDDVAR AND COFACTORS + + BDD aLow, aHigh, bLow, bHigh; + + if (level == va) { + aLow = node_low(a, na); + aHigh = node_high(a, na); + } else { + aLow = aHigh = a; + } + + if (level == vb) { + bLow = node_low(b, nb); + bHigh = node_high(b, nb); + } else { + bLow = bHigh = b; + } + + BDD result; + + BDD low=sylvan_invalid, high=sylvan_invalid; + if (bLow == sylvan_false) return CALL(sylvan_constrain, aHigh, bHigh, level); + if (bLow == sylvan_true) { + if (bHigh == sylvan_false) return aLow; + if (bHigh == sylvan_true) { + result = sylvan_makenode(level, aLow, bHigh); + } else { + high = CALL(sylvan_constrain, aHigh, bHigh, level); + result = sylvan_makenode(level, aLow, high); + } + } else { + if (bHigh == sylvan_false) return CALL(sylvan_constrain, aLow, bLow, level); + if (bHigh == sylvan_true) { + low = CALL(sylvan_constrain, aLow, bLow, level); + result = sylvan_makenode(level, low, bHigh); + } else { + bdd_refs_spawn(SPAWN(sylvan_constrain, aLow, bLow, level)); + high = CALL(sylvan_constrain, aHigh, bHigh, level); + bdd_refs_push(high); + low = bdd_refs_sync(SYNC(sylvan_constrain)); + bdd_refs_pop(1); + result = sylvan_makenode(level, low, high); + } + } + + if (cachenow) { + if (cache_put3(CACHE_BDD_CONSTRAIN, a, b, 0, result)) sylvan_stats_count(BDD_CONSTRAIN_CACHEDPUT); + } + + return result; +} + +/** + * Calculate restrict a @ b + */ +TASK_IMPL_3(BDD, sylvan_restrict, BDD, a, BDD, b, BDDVAR, prev_level) +{ + /* Trivial cases */ + if (b == sylvan_true) return a; + if (b == sylvan_false) return sylvan_false; + if (sylvan_isconst(a)) return a; + if (a == b) return sylvan_true; + if (a == sylvan_not(b)) return sylvan_false; + + /* Perhaps execute garbage collection */ + sylvan_gc_test(); + + /* Count operation */ + sylvan_stats_count(BDD_RESTRICT); + + // a != constant and b != constant + bddnode_t na = GETNODE(a); + bddnode_t nb = GETNODE(b); + + BDDVAR va = bddnode_getvariable(na); + BDDVAR vb = bddnode_getvariable(nb); + BDDVAR level = va < vb ? va : vb; + + /* Consult cache */ + int cachenow = granularity < 2 || prev_level == 0 ? 1 : prev_level / granularity != level / granularity; + if (cachenow) { + BDD result; + if (cache_get3(CACHE_BDD_RESTRICT, a, b, 0, &result)) { + sylvan_stats_count(BDD_RESTRICT_CACHED); + return result; + } + } + + BDD result; + + if (vb < va) { + BDD c = CALL(sylvan_ite, node_low(b,nb), sylvan_true, node_high(b,nb), 0); + bdd_refs_push(c); + result = CALL(sylvan_restrict, a, c, level); + bdd_refs_pop(1); + } else { + BDD aLow=node_low(a,na),aHigh=node_high(a,na),bLow=b,bHigh=b; + if (va == vb) { + bLow = node_low(b,nb); + bHigh = node_high(b,nb); + } + if (bLow == sylvan_false) { + result = CALL(sylvan_restrict, aHigh, bHigh, level); + } else if (bHigh == sylvan_false) { + result = CALL(sylvan_restrict, aLow, bLow, level); + } else { + bdd_refs_spawn(SPAWN(sylvan_restrict, aLow, bLow, level)); + BDD high = CALL(sylvan_restrict, aHigh, bHigh, level); + bdd_refs_push(high); + BDD low = bdd_refs_sync(SYNC(sylvan_restrict)); + bdd_refs_pop(1); + result = sylvan_makenode(level, low, high); + } + } + + if (cachenow) { + if (cache_put3(CACHE_BDD_RESTRICT, a, b, 0, result)) sylvan_stats_count(BDD_RESTRICT_CACHEDPUT); + } + + return result; +} + +/** + * Calculates \exists variables . a + */ +TASK_IMPL_3(BDD, sylvan_exists, BDD, a, BDD, variables, BDDVAR, prev_level) +{ + /* Terminal cases */ + if (a == sylvan_true) return sylvan_true; + if (a == sylvan_false) return sylvan_false; + if (sylvan_set_isempty(variables)) return a; + + // a != constant + bddnode_t na = GETNODE(a); + BDDVAR level = bddnode_getvariable(na); + + bddnode_t nv = GETNODE(variables); + BDDVAR vv = bddnode_getvariable(nv); + while (vv < level) { + variables = node_high(variables, nv); + if (sylvan_set_isempty(variables)) return a; + nv = GETNODE(variables); + vv = bddnode_getvariable(nv); + } + + sylvan_gc_test(); + + sylvan_stats_count(BDD_EXISTS); + + int cachenow = granularity < 2 || prev_level == 0 ? 1 : prev_level / granularity != level / granularity; + if (cachenow) { + BDD result; + if (cache_get3(CACHE_BDD_EXISTS, a, variables, 0, &result)) { + sylvan_stats_count(BDD_EXISTS_CACHED); + return result; + } + } + + // Get cofactors + BDD aLow = node_low(a, na); + BDD aHigh = node_high(a, na); + + BDD result; + + if (vv == level) { + // level is in variable set, perform abstraction + if (aLow == sylvan_true || aHigh == sylvan_true || aLow == sylvan_not(aHigh)) { + result = sylvan_true; + } else { + BDD _v = sylvan_set_next(variables); + BDD low = CALL(sylvan_exists, aLow, _v, level); + if (low == sylvan_true) { + result = sylvan_true; + } else { + bdd_refs_push(low); + BDD high = CALL(sylvan_exists, aHigh, _v, level); + if (high == sylvan_true) { + result = sylvan_true; + bdd_refs_pop(1); + } else if (low == sylvan_false && high == sylvan_false) { + result = sylvan_false; + bdd_refs_pop(1); + } else { + bdd_refs_push(high); + result = sylvan_or(low, high); + bdd_refs_pop(2); + } + } + } + } else { + // level is not in variable set + BDD low, high; + bdd_refs_spawn(SPAWN(sylvan_exists, aHigh, variables, level)); + low = CALL(sylvan_exists, aLow, variables, level); + bdd_refs_push(low); + high = bdd_refs_sync(SYNC(sylvan_exists)); + bdd_refs_pop(1); + result = sylvan_makenode(level, low, high); + } + + if (cachenow) { + if (cache_put3(CACHE_BDD_EXISTS, a, variables, 0, result)) sylvan_stats_count(BDD_EXISTS_CACHEDPUT); + } + + return result; +} + +/** + * Calculate exists(a AND b, v) + */ +TASK_IMPL_4(BDD, sylvan_and_exists, BDD, a, BDD, b, BDDSET, v, BDDVAR, prev_level) +{ + /* Terminal cases */ + if (a == sylvan_false) return sylvan_false; + if (b == sylvan_false) return sylvan_false; + if (a == sylvan_not(b)) return sylvan_false; + if (a == sylvan_true && b == sylvan_true) return sylvan_true; + + /* Cases that reduce to "exists" and "and" */ + if (a == sylvan_true) return CALL(sylvan_exists, b, v, 0); + if (b == sylvan_true) return CALL(sylvan_exists, a, v, 0); + if (a == b) return CALL(sylvan_exists, a, v, 0); + if (sylvan_set_isempty(v)) return sylvan_and(a, b); + + /* At this point, a and b are proper nodes, and v is non-empty */ + + /* Improve for caching */ + if (BDD_STRIPMARK(a) > BDD_STRIPMARK(b)) { + BDD t = b; + b = a; + a = t; + } + + /* Maybe perform garbage collection */ + sylvan_gc_test(); + + sylvan_stats_count(BDD_AND_EXISTS); + + // a != constant + bddnode_t na = GETNODE(a); + bddnode_t nb = GETNODE(b); + bddnode_t nv = GETNODE(v); + + BDDVAR va = bddnode_getvariable(na); + BDDVAR vb = bddnode_getvariable(nb); + BDDVAR vv = bddnode_getvariable(nv); + BDDVAR level = va < vb ? va : vb; + + /* Skip levels in v that are not in a and b */ + while (vv < level) { + v = node_high(v, nv); // get next variable in conjunction + if (sylvan_set_isempty(v)) return sylvan_and(a, b); + nv = GETNODE(v); + vv = bddnode_getvariable(nv); + } + + BDD result; + + int cachenow = granularity < 2 || prev_level == 0 ? 1 : prev_level / granularity != level / granularity; + if (cachenow) { + if (cache_get3(CACHE_BDD_AND_EXISTS, a, b, v, &result)) { + sylvan_stats_count(BDD_AND_EXISTS_CACHED); + return result; + } + } + + // Get cofactors + BDD aLow, aHigh, bLow, bHigh; + if (level == va) { + aLow = node_low(a, na); + aHigh = node_high(a, na); + } else { + aLow = a; + aHigh = a; + } + if (level == vb) { + bLow = node_low(b, nb); + bHigh = node_high(b, nb); + } else { + bLow = b; + bHigh = b; + } + + if (level == vv) { + // level is in variable set, perform abstraction + BDD _v = node_high(v, nv); + BDD low = CALL(sylvan_and_exists, aLow, bLow, _v, level); + if (low == sylvan_true || low == aHigh || low == bHigh) { + result = low; + } else { + bdd_refs_push(low); + BDD high; + if (low == sylvan_not(aHigh)) { + high = CALL(sylvan_exists, bHigh, _v, 0); + } else if (low == sylvan_not(bHigh)) { + high = CALL(sylvan_exists, aHigh, _v, 0); + } else { + high = CALL(sylvan_and_exists, aHigh, bHigh, _v, level); + } + if (high == sylvan_true) { + result = sylvan_true; + bdd_refs_pop(1); + } else if (high == sylvan_false) { + result = low; + bdd_refs_pop(1); + } else if (low == sylvan_false) { + result = high; + bdd_refs_pop(1); + } else { + bdd_refs_push(high); + result = sylvan_or(low, high); + bdd_refs_pop(2); + } + } + } else { + // level is not in variable set + bdd_refs_spawn(SPAWN(sylvan_and_exists, aHigh, bHigh, v, level)); + BDD low = CALL(sylvan_and_exists, aLow, bLow, v, level); + bdd_refs_push(low); + BDD high = bdd_refs_sync(SYNC(sylvan_and_exists)); + bdd_refs_pop(1); + result = sylvan_makenode(level, low, high); + } + + if (cachenow) { + if (cache_put3(CACHE_BDD_AND_EXISTS, a, b, v, result)) sylvan_stats_count(BDD_AND_EXISTS_CACHEDPUT); + } + + return result; +} + + +TASK_IMPL_4(BDD, sylvan_relnext, BDD, a, BDD, b, BDDSET, vars, BDDVAR, prev_level) +{ + /* Compute R(s) = \exists x: A(x) \and B(x,s) with support(result) = s, support(A) = s, support(B) = s+t + * if vars == sylvan_false, then every level is in s or t + * any other levels (outside s,t) in B are ignored / existentially quantified + */ + + /* Terminals */ + if (a == sylvan_true && b == sylvan_true) return sylvan_true; + if (a == sylvan_false) return sylvan_false; + if (b == sylvan_false) return sylvan_false; + if (sylvan_set_isempty(vars)) return a; + + /* Perhaps execute garbage collection */ + sylvan_gc_test(); + + /* Count operation */ + sylvan_stats_count(BDD_RELNEXT); + + /* Determine top level */ + bddnode_t na = sylvan_isconst(a) ? 0 : GETNODE(a); + bddnode_t nb = sylvan_isconst(b) ? 0 : GETNODE(b); + + BDDVAR va = na ? bddnode_getvariable(na) : 0xffffffff; + BDDVAR vb = nb ? bddnode_getvariable(nb) : 0xffffffff; + BDDVAR level = va < vb ? va : vb; + + /* Skip vars */ + int is_s_or_t = 0; + bddnode_t nv = 0; + if (vars == sylvan_false) { + is_s_or_t = 1; + } else { + nv = GETNODE(vars); + for (;;) { + /* check if level is s/t */ + BDDVAR vv = bddnode_getvariable(nv); + if (level == vv || (level^1) == vv) { + is_s_or_t = 1; + break; + } + /* check if level < s/t */ + if (level < vv) break; + vars = node_high(vars, nv); // get next in vars + if (sylvan_set_isempty(vars)) return a; + nv = GETNODE(vars); + } + } + + /* Consult cache */ + int cachenow = granularity < 2 || prev_level == 0 ? 1 : prev_level / granularity != level / granularity; + if (cachenow) { + BDD result; + if (cache_get3(CACHE_BDD_RELNEXT, a, b, vars, &result)) { + sylvan_stats_count(BDD_RELNEXT_CACHED); + return result; + } + } + + BDD result; + + if (is_s_or_t) { + /* Get s and t */ + BDDVAR s = level & (~1); + BDDVAR t = s+1; + + BDD a0, a1, b0, b1; + if (na && va == s) { + a0 = node_low(a, na); + a1 = node_high(a, na); + } else { + a0 = a1 = a; + } + if (nb && vb == s) { + b0 = node_low(b, nb); + b1 = node_high(b, nb); + } else { + b0 = b1 = b; + } + + BDD b00, b01, b10, b11; + if (!sylvan_isconst(b0)) { + bddnode_t nb0 = GETNODE(b0); + if (bddnode_getvariable(nb0) == t) { + b00 = node_low(b0, nb0); + b01 = node_high(b0, nb0); + } else { + b00 = b01 = b0; + } + } else { + b00 = b01 = b0; + } + if (!sylvan_isconst(b1)) { + bddnode_t nb1 = GETNODE(b1); + if (bddnode_getvariable(nb1) == t) { + b10 = node_low(b1, nb1); + b11 = node_high(b1, nb1); + } else { + b10 = b11 = b1; + } + } else { + b10 = b11 = b1; + } + + BDD _vars = vars == sylvan_false ? sylvan_false : node_high(vars, nv); + + bdd_refs_spawn(SPAWN(sylvan_relnext, a0, b00, _vars, level)); + bdd_refs_spawn(SPAWN(sylvan_relnext, a1, b10, _vars, level)); + bdd_refs_spawn(SPAWN(sylvan_relnext, a0, b01, _vars, level)); + bdd_refs_spawn(SPAWN(sylvan_relnext, a1, b11, _vars, level)); + + BDD f = bdd_refs_sync(SYNC(sylvan_relnext)); bdd_refs_push(f); + BDD e = bdd_refs_sync(SYNC(sylvan_relnext)); bdd_refs_push(e); + BDD d = bdd_refs_sync(SYNC(sylvan_relnext)); bdd_refs_push(d); + BDD c = bdd_refs_sync(SYNC(sylvan_relnext)); bdd_refs_push(c); + + bdd_refs_spawn(SPAWN(sylvan_ite, c, sylvan_true, d, 0)); /* a0 b00 \or a1 b01 */ + bdd_refs_spawn(SPAWN(sylvan_ite, e, sylvan_true, f, 0)); /* a0 b01 \or a1 b11 */ + + /* R1 */ d = bdd_refs_sync(SYNC(sylvan_ite)); bdd_refs_push(d); + /* R0 */ c = bdd_refs_sync(SYNC(sylvan_ite)); // not necessary: bdd_refs_push(c); + + bdd_refs_pop(5); + result = sylvan_makenode(s, c, d); + } else { + /* Variable not in vars! Take a, quantify b */ + BDD a0, a1, b0, b1; + if (na && va == level) { + a0 = node_low(a, na); + a1 = node_high(a, na); + } else { + a0 = a1 = a; + } + if (nb && vb == level) { + b0 = node_low(b, nb); + b1 = node_high(b, nb); + } else { + b0 = b1 = b; + } + + if (b0 != b1) { + if (a0 == a1) { + /* Quantify "b" variables */ + bdd_refs_spawn(SPAWN(sylvan_relnext, a0, b0, vars, level)); + bdd_refs_spawn(SPAWN(sylvan_relnext, a1, b1, vars, level)); + + BDD r1 = bdd_refs_sync(SYNC(sylvan_relnext)); + bdd_refs_push(r1); + BDD r0 = bdd_refs_sync(SYNC(sylvan_relnext)); + bdd_refs_push(r0); + result = sylvan_or(r0, r1); + bdd_refs_pop(2); + } else { + /* Quantify "b" variables, but keep "a" variables */ + bdd_refs_spawn(SPAWN(sylvan_relnext, a0, b0, vars, level)); + bdd_refs_spawn(SPAWN(sylvan_relnext, a0, b1, vars, level)); + bdd_refs_spawn(SPAWN(sylvan_relnext, a1, b0, vars, level)); + bdd_refs_spawn(SPAWN(sylvan_relnext, a1, b1, vars, level)); + + BDD r11 = bdd_refs_sync(SYNC(sylvan_relnext)); + bdd_refs_push(r11); + BDD r10 = bdd_refs_sync(SYNC(sylvan_relnext)); + bdd_refs_push(r10); + BDD r01 = bdd_refs_sync(SYNC(sylvan_relnext)); + bdd_refs_push(r01); + BDD r00 = bdd_refs_sync(SYNC(sylvan_relnext)); + bdd_refs_push(r00); + + bdd_refs_spawn(SPAWN(sylvan_ite, r00, sylvan_true, r01, 0)); + bdd_refs_spawn(SPAWN(sylvan_ite, r10, sylvan_true, r11, 0)); + + BDD r1 = bdd_refs_sync(SYNC(sylvan_ite)); + bdd_refs_push(r1); + BDD r0 = bdd_refs_sync(SYNC(sylvan_ite)); + bdd_refs_pop(5); + + result = sylvan_makenode(level, r0, r1); + } + } else { + /* Keep "a" variables */ + bdd_refs_spawn(SPAWN(sylvan_relnext, a0, b0, vars, level)); + bdd_refs_spawn(SPAWN(sylvan_relnext, a1, b1, vars, level)); + + BDD r1 = bdd_refs_sync(SYNC(sylvan_relnext)); + bdd_refs_push(r1); + BDD r0 = bdd_refs_sync(SYNC(sylvan_relnext)); + bdd_refs_pop(1); + result = sylvan_makenode(level, r0, r1); + } + } + + if (cachenow) { + if (cache_put3(CACHE_BDD_RELNEXT, a, b, vars, result)) sylvan_stats_count(BDD_RELNEXT_CACHEDPUT); + } + + return result; +} + +TASK_IMPL_4(BDD, sylvan_relprev, BDD, a, BDD, b, BDDSET, vars, BDDVAR, prev_level) +{ + /* Compute \exists x: A(s,x) \and B(x,t) + * if vars == sylvan_false, then every level is in s or t + * any other levels (outside s,t) in A are ignored / existentially quantified + */ + + /* Terminals */ + if (a == sylvan_true && b == sylvan_true) return sylvan_true; + if (a == sylvan_false) return sylvan_false; + if (b == sylvan_false) return sylvan_false; + if (sylvan_set_isempty(vars)) return b; + + /* Perhaps execute garbage collection */ + sylvan_gc_test(); + + /* Count operation */ + sylvan_stats_count(BDD_RELPREV); + + /* Determine top level */ + bddnode_t na = sylvan_isconst(a) ? 0 : GETNODE(a); + bddnode_t nb = sylvan_isconst(b) ? 0 : GETNODE(b); + + BDDVAR va = na ? bddnode_getvariable(na) : 0xffffffff; + BDDVAR vb = nb ? bddnode_getvariable(nb) : 0xffffffff; + BDDVAR level = va < vb ? va : vb; + + /* Skip vars */ + int is_s_or_t = 0; + bddnode_t nv = 0; + if (vars == sylvan_false) { + is_s_or_t = 1; + } else { + nv = GETNODE(vars); + for (;;) { + /* check if level is s/t */ + BDDVAR vv = bddnode_getvariable(nv); + if (level == vv || (level^1) == vv) { + is_s_or_t = 1; + break; + } + /* check if level < s/t */ + if (level < vv) break; + vars = node_high(vars, nv); // get next in vars + if (sylvan_set_isempty(vars)) return b; + nv = GETNODE(vars); + } + } + + /* Consult cache */ + int cachenow = granularity < 2 || prev_level == 0 ? 1 : prev_level / granularity != level / granularity; + if (cachenow) { + BDD result; + if (cache_get3(CACHE_BDD_RELPREV, a, b, vars, &result)) { + sylvan_stats_count(BDD_RELPREV_CACHED); + return result; + } + } + + BDD result; + + if (is_s_or_t) { + /* Get s and t */ + BDDVAR s = level & (~1); + BDDVAR t = s+1; + + BDD a0, a1, b0, b1; + if (na && va == s) { + a0 = node_low(a, na); + a1 = node_high(a, na); + } else { + a0 = a1 = a; + } + if (nb && vb == s) { + b0 = node_low(b, nb); + b1 = node_high(b, nb); + } else { + b0 = b1 = b; + } + + BDD a00, a01, a10, a11; + if (!sylvan_isconst(a0)) { + bddnode_t na0 = GETNODE(a0); + if (bddnode_getvariable(na0) == t) { + a00 = node_low(a0, na0); + a01 = node_high(a0, na0); + } else { + a00 = a01 = a0; + } + } else { + a00 = a01 = a0; + } + if (!sylvan_isconst(a1)) { + bddnode_t na1 = GETNODE(a1); + if (bddnode_getvariable(na1) == t) { + a10 = node_low(a1, na1); + a11 = node_high(a1, na1); + } else { + a10 = a11 = a1; + } + } else { + a10 = a11 = a1; + } + + BDD b00, b01, b10, b11; + if (!sylvan_isconst(b0)) { + bddnode_t nb0 = GETNODE(b0); + if (bddnode_getvariable(nb0) == t) { + b00 = node_low(b0, nb0); + b01 = node_high(b0, nb0); + } else { + b00 = b01 = b0; + } + } else { + b00 = b01 = b0; + } + if (!sylvan_isconst(b1)) { + bddnode_t nb1 = GETNODE(b1); + if (bddnode_getvariable(nb1) == t) { + b10 = node_low(b1, nb1); + b11 = node_high(b1, nb1); + } else { + b10 = b11 = b1; + } + } else { + b10 = b11 = b1; + } + + BDD _vars; + if (vars != sylvan_false) { + _vars = node_high(vars, nv); + if (sylvan_set_var(_vars) == t) _vars = sylvan_set_next(_vars); + } else { + _vars = sylvan_false; + } + + if (b00 == b01) { + bdd_refs_spawn(SPAWN(sylvan_relprev, a00, b0, _vars, level)); + bdd_refs_spawn(SPAWN(sylvan_relprev, a10, b0, _vars, level)); + } else { + bdd_refs_spawn(SPAWN(sylvan_relprev, a00, b00, _vars, level)); + bdd_refs_spawn(SPAWN(sylvan_relprev, a00, b01, _vars, level)); + bdd_refs_spawn(SPAWN(sylvan_relprev, a10, b00, _vars, level)); + bdd_refs_spawn(SPAWN(sylvan_relprev, a10, b01, _vars, level)); + } + + if (b10 == b11) { + bdd_refs_spawn(SPAWN(sylvan_relprev, a01, b1, _vars, level)); + bdd_refs_spawn(SPAWN(sylvan_relprev, a11, b1, _vars, level)); + } else { + bdd_refs_spawn(SPAWN(sylvan_relprev, a01, b10, _vars, level)); + bdd_refs_spawn(SPAWN(sylvan_relprev, a01, b11, _vars, level)); + bdd_refs_spawn(SPAWN(sylvan_relprev, a11, b10, _vars, level)); + bdd_refs_spawn(SPAWN(sylvan_relprev, a11, b11, _vars, level)); + } + + BDD r00, r01, r10, r11; + + if (b10 == b11) { + r11 = bdd_refs_push(bdd_refs_sync(SYNC(sylvan_relprev))); + r01 = bdd_refs_push(bdd_refs_sync(SYNC(sylvan_relprev))); + } else { + BDD r111 = bdd_refs_push(bdd_refs_sync(SYNC(sylvan_relprev))); + BDD r110 = bdd_refs_push(bdd_refs_sync(SYNC(sylvan_relprev))); + r11 = sylvan_makenode(t, r110, r111); + bdd_refs_pop(2); + bdd_refs_push(r11); + BDD r011 = bdd_refs_push(bdd_refs_sync(SYNC(sylvan_relprev))); + BDD r010 = bdd_refs_push(bdd_refs_sync(SYNC(sylvan_relprev))); + r01 = sylvan_makenode(t, r010, r011); + bdd_refs_pop(2); + bdd_refs_push(r01); + } + + if (b00 == b01) { + r10 = bdd_refs_push(bdd_refs_sync(SYNC(sylvan_relprev))); + r00 = bdd_refs_push(bdd_refs_sync(SYNC(sylvan_relprev))); + } else { + BDD r101 = bdd_refs_push(bdd_refs_sync(SYNC(sylvan_relprev))); + BDD r100 = bdd_refs_push(bdd_refs_sync(SYNC(sylvan_relprev))); + r10 = sylvan_makenode(t, r100, r101); + bdd_refs_pop(2); + bdd_refs_push(r10); + BDD r001 = bdd_refs_push(bdd_refs_sync(SYNC(sylvan_relprev))); + BDD r000 = bdd_refs_push(bdd_refs_sync(SYNC(sylvan_relprev))); + r00 = sylvan_makenode(t, r000, r001); + bdd_refs_pop(2); + bdd_refs_push(r00); + } + + bdd_refs_spawn(SPAWN(sylvan_and, sylvan_not(r00), sylvan_not(r01), 0)); + bdd_refs_spawn(SPAWN(sylvan_and, sylvan_not(r10), sylvan_not(r11), 0)); + + BDD r1 = sylvan_not(bdd_refs_push(bdd_refs_sync(SYNC(sylvan_and)))); + BDD r0 = sylvan_not(bdd_refs_sync(SYNC(sylvan_and))); + bdd_refs_pop(5); + result = sylvan_makenode(s, r0, r1); + } else { + BDD a0, a1, b0, b1; + if (na && va == level) { + a0 = node_low(a, na); + a1 = node_high(a, na); + } else { + a0 = a1 = a; + } + if (nb && vb == level) { + b0 = node_low(b, nb); + b1 = node_high(b, nb); + } else { + b0 = b1 = b; + } + + if (a0 != a1) { + if (b0 == b1) { + /* Quantify "a" variables */ + bdd_refs_spawn(SPAWN(sylvan_relprev, a0, b0, vars, level)); + bdd_refs_spawn(SPAWN(sylvan_relprev, a1, b1, vars, level)); + + BDD r1 = bdd_refs_sync(SYNC(sylvan_relprev)); + bdd_refs_push(r1); + BDD r0 = bdd_refs_sync(SYNC(sylvan_relprev)); + bdd_refs_push(r0); + result = CALL(sylvan_ite, r0, sylvan_true, r1, 0); + bdd_refs_pop(2); + + } else { + /* Quantify "a" variables, but keep "b" variables */ + bdd_refs_spawn(SPAWN(sylvan_relnext, a0, b0, vars, level)); + bdd_refs_spawn(SPAWN(sylvan_relnext, a1, b0, vars, level)); + bdd_refs_spawn(SPAWN(sylvan_relnext, a0, b1, vars, level)); + bdd_refs_spawn(SPAWN(sylvan_relnext, a1, b1, vars, level)); + + BDD r11 = bdd_refs_sync(SYNC(sylvan_relnext)); + bdd_refs_push(r11); + BDD r01 = bdd_refs_sync(SYNC(sylvan_relnext)); + bdd_refs_push(r01); + BDD r10 = bdd_refs_sync(SYNC(sylvan_relnext)); + bdd_refs_push(r10); + BDD r00 = bdd_refs_sync(SYNC(sylvan_relnext)); + bdd_refs_push(r00); + + bdd_refs_spawn(SPAWN(sylvan_ite, r00, sylvan_true, r10, 0)); + bdd_refs_spawn(SPAWN(sylvan_ite, r01, sylvan_true, r11, 0)); + + BDD r1 = bdd_refs_sync(SYNC(sylvan_ite)); + bdd_refs_push(r1); + BDD r0 = bdd_refs_sync(SYNC(sylvan_ite)); + bdd_refs_pop(5); + + result = sylvan_makenode(level, r0, r1); + } + } else { + bdd_refs_spawn(SPAWN(sylvan_relprev, a0, b0, vars, level)); + bdd_refs_spawn(SPAWN(sylvan_relprev, a1, b1, vars, level)); + + BDD r1 = bdd_refs_sync(SYNC(sylvan_relprev)); + bdd_refs_push(r1); + BDD r0 = bdd_refs_sync(SYNC(sylvan_relprev)); + bdd_refs_pop(1); + result = sylvan_makenode(level, r0, r1); + } + } + + if (cachenow) { + if (cache_put3(CACHE_BDD_RELPREV, a, b, vars, result)) sylvan_stats_count(BDD_RELPREV_CACHEDPUT); + } + + return result; +} + +/** + * Computes the transitive closure by traversing the BDD recursively. + * See Y. Matsunaga, P. C. McGeer, R. K. Brayton + * On Computing the Transitive Closre of a State Transition Relation + * 30th ACM Design Automation Conference, 1993. + */ +TASK_IMPL_2(BDD, sylvan_closure, BDD, a, BDDVAR, prev_level) +{ + /* Terminals */ + if (a == sylvan_true) return a; + if (a == sylvan_false) return a; + + /* Perhaps execute garbage collection */ + sylvan_gc_test(); + + /* Count operation */ + sylvan_stats_count(BDD_CLOSURE); + + /* Determine top level */ + bddnode_t n = GETNODE(a); + BDDVAR level = bddnode_getvariable(n); + + /* Consult cache */ + int cachenow = granularity < 2 || prev_level == 0 ? 1 : prev_level / granularity != level / granularity; + if (cachenow) { + BDD result; + if (cache_get3(CACHE_BDD_CLOSURE, a, 0, 0, &result)) { + sylvan_stats_count(BDD_CLOSURE_CACHED); + return result; + } + } + + BDDVAR s = level & (~1); + BDDVAR t = s+1; + + BDD a0, a1; + if (level == s) { + a0 = node_low(a, n); + a1 = node_high(a, n); + } else { + a0 = a1 = a; + } + + BDD a00, a01, a10, a11; + if (!sylvan_isconst(a0)) { + bddnode_t na0 = GETNODE(a0); + if (bddnode_getvariable(na0) == t) { + a00 = node_low(a0, na0); + a01 = node_high(a0, na0); + } else { + a00 = a01 = a0; + } + } else { + a00 = a01 = a0; + } + if (!sylvan_isconst(a1)) { + bddnode_t na1 = GETNODE(a1); + if (bddnode_getvariable(na1) == t) { + a10 = node_low(a1, na1); + a11 = node_high(a1, na1); + } else { + a10 = a11 = a1; + } + } else { + a10 = a11 = a1; + } + + BDD u1 = CALL(sylvan_closure, a11, level); + bdd_refs_push(u1); + /* u3 = */ bdd_refs_spawn(SPAWN(sylvan_relprev, a01, u1, sylvan_false, level)); + BDD u2 = CALL(sylvan_relprev, u1, a10, sylvan_false, level); + bdd_refs_push(u2); + BDD e = CALL(sylvan_relprev, a01, u2, sylvan_false, level); + bdd_refs_push(e); + e = CALL(sylvan_ite, a00, sylvan_true, e, level); + bdd_refs_pop(1); + bdd_refs_push(e); + e = CALL(sylvan_closure, e, level); + bdd_refs_pop(1); + bdd_refs_push(e); + BDD g = CALL(sylvan_relprev, u2, e, sylvan_false, level); + bdd_refs_push(g); + BDD u3 = bdd_refs_sync(SYNC(sylvan_relprev)); + bdd_refs_push(u3); + BDD f = CALL(sylvan_relprev, e, u3, sylvan_false, level); + bdd_refs_push(f); + BDD h = CALL(sylvan_relprev, u2, f, sylvan_false, level); + bdd_refs_push(h); + h = CALL(sylvan_ite, u1, sylvan_true, h, level); + bdd_refs_pop(1); + bdd_refs_push(h); + + BDD r0, r1; + /* R0 */ r0 = sylvan_makenode(t, e, f); + bdd_refs_pop(7); + bdd_refs_push(r0); + /* R1 */ r1 = sylvan_makenode(t, g, h); + bdd_refs_pop(1); + BDD result = sylvan_makenode(s, r0, r1); + + if (cachenow) { + if (cache_put3(CACHE_BDD_CLOSURE, a, 0, 0, result)) sylvan_stats_count(BDD_CLOSURE_CACHEDPUT); + } + + return result; +} + + +/** + * Function composition + */ +TASK_IMPL_3(BDD, sylvan_compose, BDD, a, BDDMAP, map, BDDVAR, prev_level) +{ + /* Trivial cases */ + if (a == sylvan_false || a == sylvan_true) return a; + if (sylvan_map_isempty(map)) return a; + + /* Perhaps execute garbage collection */ + sylvan_gc_test(); + + /* Count operation */ + sylvan_stats_count(BDD_COMPOSE); + + /* Determine top level */ + bddnode_t n = GETNODE(a); + BDDVAR level = bddnode_getvariable(n); + + /* Skip map */ + bddnode_t map_node = GETNODE(map); + BDDVAR map_var = bddnode_getvariable(map_node); + while (map_var < level) { + map = node_low(map, map_node); + if (sylvan_map_isempty(map)) return a; + map_node = GETNODE(map); + map_var = bddnode_getvariable(map_node); + } + + /* Consult cache */ + int cachenow = granularity < 2 || prev_level == 0 ? 1 : prev_level / granularity != level / granularity; + if (cachenow) { + BDD result; + if (cache_get3(CACHE_BDD_COMPOSE, a, map, 0, &result)) { + sylvan_stats_count(BDD_COMPOSE_CACHED); + return result; + } + } + + /* Recursively calculate low and high */ + bdd_refs_spawn(SPAWN(sylvan_compose, node_low(a, n), map, level)); + BDD high = CALL(sylvan_compose, node_high(a, n), map, level); + bdd_refs_push(high); + BDD low = bdd_refs_sync(SYNC(sylvan_compose)); + bdd_refs_push(low); + + /* Calculate result */ + BDD root = map_var == level ? node_high(map, map_node) : sylvan_ithvar(level); + bdd_refs_push(root); + BDD result = CALL(sylvan_ite, root, high, low, 0); + bdd_refs_pop(3); + + if (cachenow) { + if (cache_put3(CACHE_BDD_COMPOSE, a, map, 0, result)) sylvan_stats_count(BDD_COMPOSE_CACHEDPUT); + } + + return result; +} + +/** + * Count number of nodes in BDD + */ +uint64_t sylvan_nodecount_do_1(BDD a) +{ + if (sylvan_isconst(a)) return 0; + bddnode_t na = GETNODE(a); + if (bddnode_getmark(na)) return 0; + bddnode_setmark(na, 1); + uint64_t result = 1; + result += sylvan_nodecount_do_1(bddnode_getlow(na)); + result += sylvan_nodecount_do_1(bddnode_gethigh(na)); + return result; +} + +void sylvan_nodecount_do_2(BDD a) +{ + if (sylvan_isconst(a)) return; + bddnode_t na = GETNODE(a); + if (!bddnode_getmark(na)) return; + bddnode_setmark(na, 0); + sylvan_nodecount_do_2(bddnode_getlow(na)); + sylvan_nodecount_do_2(bddnode_gethigh(na)); +} + +size_t sylvan_nodecount(BDD a) +{ + uint32_t result = sylvan_nodecount_do_1(a); + sylvan_nodecount_do_2(a); + return result; +} + +/** + * Calculate the number of distinct paths to True. + */ +TASK_IMPL_2(double, sylvan_pathcount, BDD, bdd, BDDVAR, prev_level) +{ + /* Trivial cases */ + if (bdd == sylvan_false) return 0.0; + if (bdd == sylvan_true) return 1.0; + + /* Perhaps execute garbage collection */ + sylvan_gc_test(); + + sylvan_stats_count(BDD_PATHCOUNT); + + BDD level = sylvan_var(bdd); + + /* Consult cache */ + int cachenow = granularity < 2 || prev_level == 0 ? 1 : prev_level / granularity != level / granularity; + if (cachenow) { + double result; + if (cache_get3(CACHE_BDD_PATHCOUNT, bdd, 0, 0, (uint64_t*)&result)) { + sylvan_stats_count(BDD_PATHCOUNT_CACHED); + return result; + } + } + + SPAWN(sylvan_pathcount, sylvan_low(bdd), level); + SPAWN(sylvan_pathcount, sylvan_high(bdd), level); + double res1 = SYNC(sylvan_pathcount); + res1 += SYNC(sylvan_pathcount); + + if (cachenow) { + if (cache_put3(CACHE_BDD_PATHCOUNT, bdd, 0, 0, *(uint64_t*)&res1)) sylvan_stats_count(BDD_PATHCOUNT_CACHEDPUT); + } + + return res1; +} + +/** + * Calculate the number of satisfying variable assignments according to <variables>. + */ +TASK_IMPL_3(double, sylvan_satcount, BDD, bdd, BDDSET, variables, BDDVAR, prev_level) +{ + /* Trivial cases */ + if (bdd == sylvan_false) return 0.0; + if (bdd == sylvan_true) return powl(2.0L, sylvan_set_count(variables)); + + /* Perhaps execute garbage collection */ + sylvan_gc_test(); + + sylvan_stats_count(BDD_SATCOUNT); + + /* Count variables before var(bdd) */ + size_t skipped = 0; + BDDVAR var = sylvan_var(bdd); + bddnode_t set_node = GETNODE(variables); + BDDVAR set_var = bddnode_getvariable(set_node); + while (var != set_var) { + skipped++; + variables = node_high(variables, set_node); + // if this assertion fails, then variables is not the support of <bdd> + assert(!sylvan_set_isempty(variables)); + set_node = GETNODE(variables); + set_var = bddnode_getvariable(set_node); + } + + union { + double d; + uint64_t s; + } hack; + + /* Consult cache */ + int cachenow = granularity < 2 || prev_level == 0 ? 1 : prev_level / granularity != var / granularity; + if (cachenow) { + if (cache_get3(CACHE_BDD_SATCOUNT, bdd, variables, 0, &hack.s)) { + sylvan_stats_count(BDD_SATCOUNT_CACHED); + return hack.d * powl(2.0L, skipped); + } + } + + SPAWN(sylvan_satcount, sylvan_high(bdd), node_high(variables, set_node), var); + double low = CALL(sylvan_satcount, sylvan_low(bdd), node_high(variables, set_node), var); + double result = low + SYNC(sylvan_satcount); + + if (cachenow) { + hack.d = result; + if (cache_put3(CACHE_BDD_SATCOUNT, bdd, variables, 0, hack.s)) sylvan_stats_count(BDD_SATCOUNT_CACHEDPUT); + } + + return result * powl(2.0L, skipped); +} + +int +sylvan_sat_one(BDD bdd, BDDSET vars, uint8_t *str) +{ + if (bdd == sylvan_false) return 0; + if (str == NULL) return 0; + if (sylvan_set_isempty(vars)) return 1; + + for (;;) { + bddnode_t n_vars = GETNODE(vars); + if (bdd == sylvan_true) { + *str = 0; + } else { + bddnode_t n_bdd = GETNODE(bdd); + if (bddnode_getvariable(n_bdd) != bddnode_getvariable(n_vars)) { + *str = 0; + } else { + if (node_low(bdd, n_bdd) == sylvan_false) { + // take high edge + *str = 1; + bdd = node_high(bdd, n_bdd); + } else { + // take low edge + *str = 0; + bdd = node_low(bdd, n_bdd); + } + } + } + vars = node_high(vars, n_vars); + if (sylvan_set_isempty(vars)) break; + str++; + } + + return 1; +} + +BDD +sylvan_sat_one_bdd(BDD bdd) +{ + if (bdd == sylvan_false) return sylvan_false; + if (bdd == sylvan_true) return sylvan_true; + + bddnode_t node = GETNODE(bdd); + BDD low = node_low(bdd, node); + BDD high = node_high(bdd, node); + + BDD m; + + BDD result; + if (low == sylvan_false) { + m = sylvan_sat_one_bdd(high); + result = sylvan_makenode(bddnode_getvariable(node), sylvan_false, m); + } else if (high == sylvan_false) { + m = sylvan_sat_one_bdd(low); + result = sylvan_makenode(bddnode_getvariable(node), m, sylvan_false); + } else { + if (rand() & 0x2000) { + m = sylvan_sat_one_bdd(low); + result = sylvan_makenode(bddnode_getvariable(node), m, sylvan_false); + } else { + m = sylvan_sat_one_bdd(high); + result = sylvan_makenode(bddnode_getvariable(node), sylvan_false, m); + } + } + + return result; +} + +BDD +sylvan_cube(BDDSET vars, uint8_t *cube) +{ + if (sylvan_set_isempty(vars)) return sylvan_true; + + bddnode_t n = GETNODE(vars); + BDDVAR v = bddnode_getvariable(n); + vars = node_high(vars, n); + + BDD result = sylvan_cube(vars, cube+1); + if (*cube == 0) { + result = sylvan_makenode(v, result, sylvan_false); + } else if (*cube == 1) { + result = sylvan_makenode(v, sylvan_false, result); + } + + return result; +} + +TASK_IMPL_3(BDD, sylvan_union_cube, BDD, bdd, BDDSET, vars, uint8_t *, cube) +{ + /* Terminal cases */ + if (bdd == sylvan_true) return sylvan_true; + if (bdd == sylvan_false) return sylvan_cube(vars, cube); + if (sylvan_set_isempty(vars)) return sylvan_true; + + bddnode_t nv = GETNODE(vars); + + for (;;) { + if (*cube == 0 || *cube == 1) break; + // *cube should be 2 + cube++; + vars = node_high(vars, nv); + if (sylvan_set_isempty(vars)) return sylvan_true; + nv = GETNODE(vars); + } + + sylvan_gc_test(); + + // missing: SV_CNT_OP + + bddnode_t n = GETNODE(bdd); + BDD result = bdd; + BDDVAR v = bddnode_getvariable(nv); + BDDVAR n_level = bddnode_getvariable(n); + + if (v < n_level) { + vars = node_high(vars, nv); + if (*cube == 0) { + result = sylvan_union_cube(bdd, vars, cube+1); + result = sylvan_makenode(v, result, bdd); + } else /* *cube == 1 */ { + result = sylvan_union_cube(bdd, vars, cube+1); + result = sylvan_makenode(v, bdd, result); + } + } else if (v > n_level) { + BDD high = node_high(bdd, n); + BDD low = node_low(bdd, n); + SPAWN(sylvan_union_cube, high, vars, cube); + BDD new_low = sylvan_union_cube(low, vars, cube); + bdd_refs_push(new_low); + BDD new_high = SYNC(sylvan_union_cube); + bdd_refs_pop(1); + if (new_low != low || new_high != high) { + result = sylvan_makenode(n_level, new_low, new_high); + } + } else /* v == n_level */ { + vars = node_high(vars, nv); + BDD high = node_high(bdd, n); + BDD low = node_low(bdd, n); + if (*cube == 0) { + BDD new_low = sylvan_union_cube(low, vars, cube+1); + if (new_low != low) { + result = sylvan_makenode(n_level, new_low, high); + } + } else /* *cube == 1 */ { + BDD new_high = sylvan_union_cube(high, vars, cube+1); + if (new_high != high) { + result = sylvan_makenode(n_level, low, new_high); + } + } + } + + return result; +} + +struct bdd_path +{ + struct bdd_path *prev; + BDDVAR var; + int8_t val; // 0=false, 1=true, 2=both +}; + +VOID_TASK_5(sylvan_enum_do, BDD, bdd, BDDSET, vars, enum_cb, cb, void*, context, struct bdd_path*, path) +{ + if (bdd == sylvan_false) return; + + if (sylvan_set_isempty(vars)) { + /* bdd should now be true */ + assert(bdd == sylvan_true); + /* compute length of path */ + int i=0; + struct bdd_path *pp; + for (pp = path; pp != NULL; pp = pp->prev) i++; + /* if length is 0 (enum called with empty vars??), return */ + if (i == 0) return; + /* fill cube and vars with trace */ + uint8_t cube[i]; + BDDVAR vars[i]; + int j=0; + for (pp = path; pp != NULL; pp = pp->prev) { + cube[i-j-1] = pp->val; + vars[i-j-1] = pp->var; + j++; + } + /* call callback */ + WRAP(cb, context, vars, cube, i); + return; + } + + BDDVAR var = sylvan_var(vars); + vars = sylvan_set_next(vars); + BDDVAR bdd_var = sylvan_var(bdd); + + /* assert var <= bdd_var */ + if (bdd == sylvan_true || var < bdd_var) { + struct bdd_path pp0 = (struct bdd_path){path, var, 0}; + CALL(sylvan_enum_do, bdd, vars, cb, context, &pp0); + struct bdd_path pp1 = (struct bdd_path){path, var, 1}; + CALL(sylvan_enum_do, bdd, vars, cb, context, &pp1); + } else if (var == bdd_var) { + struct bdd_path pp0 = (struct bdd_path){path, var, 0}; + CALL(sylvan_enum_do, sylvan_low(bdd), vars, cb, context, &pp0); + struct bdd_path pp1 = (struct bdd_path){path, var, 1}; + CALL(sylvan_enum_do, sylvan_high(bdd), vars, cb, context, &pp1); + } else { + printf("var %u not expected (expecting %u)!\n", bdd_var, var); + assert(var <= bdd_var); + } +} + +VOID_TASK_5(sylvan_enum_par_do, BDD, bdd, BDDSET, vars, enum_cb, cb, void*, context, struct bdd_path*, path) +{ + if (bdd == sylvan_false) return; + + if (sylvan_set_isempty(vars)) { + /* bdd should now be true */ + assert(bdd == sylvan_true); + /* compute length of path */ + int i=0; + struct bdd_path *pp; + for (pp = path; pp != NULL; pp = pp->prev) i++; + /* if length is 0 (enum called with empty vars??), return */ + if (i == 0) return; + /* fill cube and vars with trace */ + uint8_t cube[i]; + BDDVAR vars[i]; + int j=0; + for (pp = path; pp != NULL; pp = pp->prev) { + cube[i-j-1] = pp->val; + vars[i-j-1] = pp->var; + j++; + } + /* call callback */ + WRAP(cb, context, vars, cube, i); + return; + } + + BDD var = sylvan_var(vars); + vars = sylvan_set_next(vars); + BDD bdd_var = sylvan_var(bdd); + + /* assert var <= bdd_var */ + if (var < bdd_var) { + struct bdd_path pp1 = (struct bdd_path){path, var, 1}; + SPAWN(sylvan_enum_par_do, bdd, vars, cb, context, &pp1); + struct bdd_path pp0 = (struct bdd_path){path, var, 0}; + CALL(sylvan_enum_par_do, bdd, vars, cb, context, &pp0); + SYNC(sylvan_enum_par_do); + } else if (var == bdd_var) { + struct bdd_path pp1 = (struct bdd_path){path, var, 1}; + SPAWN(sylvan_enum_par_do, sylvan_high(bdd), vars, cb, context, &pp1); + struct bdd_path pp0 = (struct bdd_path){path, var, 0}; + CALL(sylvan_enum_par_do, sylvan_low(bdd), vars, cb, context, &pp0); + SYNC(sylvan_enum_par_do); + } else { + assert(var <= bdd_var); + } +} + +VOID_TASK_IMPL_4(sylvan_enum, BDD, bdd, BDDSET, vars, enum_cb, cb, void*, context) +{ + CALL(sylvan_enum_do, bdd, vars, cb, context, 0); +} + +VOID_TASK_IMPL_4(sylvan_enum_par, BDD, bdd, BDDSET, vars, enum_cb, cb, void*, context) +{ + CALL(sylvan_enum_par_do, bdd, vars, cb, context, 0); +} + +TASK_5(BDD, sylvan_collect_do, BDD, bdd, BDDSET, vars, sylvan_collect_cb, cb, void*, context, struct bdd_path*, path) +{ + if (bdd == sylvan_false) return sylvan_false; + + if (sylvan_set_isempty(vars)) { + /* compute length of path */ + int i=0; + struct bdd_path *pp; + for (pp = path; pp != NULL; pp = pp->prev) i++; + /* if length is 0 (enum called with empty vars??), return */ + if (i == 0) return WRAP(cb, context, NULL); + /* fill cube and vars with trace */ + uint8_t cube[i]; + int j=0; + for (pp = path; pp != NULL; pp = pp->prev) { + cube[i-j-1] = pp->val; + j++; + } + /* call callback */ + return WRAP(cb, context, cube); + } else { + BDD var = sylvan_var(vars); + vars = sylvan_set_next(vars); + BDD bdd_var = sylvan_var(bdd); + + /* if fails, then <bdd> has variables not in <vars> */ + assert(var <= bdd_var); + + struct bdd_path pp1 = (struct bdd_path){path, var, 1}; + struct bdd_path pp0 = (struct bdd_path){path, var, 0}; + if (var < bdd_var) { + bdd_refs_spawn(SPAWN(sylvan_collect_do, bdd, vars, cb, context, &pp1)); + BDD low = bdd_refs_push(CALL(sylvan_collect_do, bdd, vars, cb, context, &pp0)); + BDD high = bdd_refs_push(bdd_refs_sync(SYNC(sylvan_collect_do))); + BDD res = sylvan_or(low, high); + bdd_refs_pop(2); + return res; + } else if (var == bdd_var) { + bdd_refs_spawn(SPAWN(sylvan_collect_do, sylvan_high(bdd), vars, cb, context, &pp1)); + BDD low = bdd_refs_push(CALL(sylvan_collect_do, sylvan_low(bdd), vars, cb, context, &pp0)); + BDD high = bdd_refs_push(bdd_refs_sync(SYNC(sylvan_collect_do))); + BDD res = sylvan_or(low, high); + bdd_refs_pop(2); + return res; + } else { + return sylvan_invalid; // unreachable + } + } +} + +TASK_IMPL_4(BDD, sylvan_collect, BDD, bdd, BDDSET, vars, sylvan_collect_cb, cb, void*, context) +{ + return CALL(sylvan_collect_do, bdd, vars, cb, context, 0); +} + +/** + * IMPLEMENTATION OF BDDSET + */ + +int +sylvan_set_in(BDDSET set, BDDVAR level) +{ + while (!sylvan_set_isempty(set)) { + bddnode_t n = GETNODE(set); + BDDVAR n_level = bddnode_getvariable(n); + if (n_level == level) return 1; + if (n_level > level) return 0; // BDDs are ordered + set = node_high(set, n); + } + + return 0; +} + +size_t +sylvan_set_count(BDDSET set) +{ + size_t result = 0; + for (;!sylvan_set_isempty(set);set = sylvan_set_next(set)) result++; + return result; +} + +void +sylvan_set_toarray(BDDSET set, BDDVAR *arr) +{ + size_t i = 0; + while (!sylvan_set_isempty(set)) { + bddnode_t n = GETNODE(set); + arr[i++] = bddnode_getvariable(n); + set = node_high(set, n); + } +} + +TASK_IMPL_2(BDDSET, sylvan_set_fromarray, BDDVAR*, arr, size_t, length) +{ + if (length == 0) return sylvan_set_empty(); + BDDSET sub = sylvan_set_fromarray(arr+1, length-1); + bdd_refs_push(sub); + BDDSET result = sylvan_set_add(sub, *arr); + bdd_refs_pop(1); + return result; +} + +void +sylvan_test_isset(BDDSET set) +{ + while (set != sylvan_false) { + assert(set != sylvan_true); + assert(llmsset_is_marked(nodes, set)); + bddnode_t n = GETNODE(set); + assert(node_low(set, n) == sylvan_true); + set = node_high(set, n); + } +} + +/** + * IMPLEMENTATION OF BDDMAP + */ + +BDDMAP +sylvan_map_add(BDDMAP map, BDDVAR key, BDD value) +{ + if (sylvan_map_isempty(map)) return sylvan_makenode(key, sylvan_map_empty(), value); + + bddnode_t n = GETNODE(map); + BDDVAR key_m = bddnode_getvariable(n); + + if (key_m < key) { + // add recursively and rebuild tree + BDDMAP low = sylvan_map_add(node_low(map, n), key, value); + BDDMAP result = sylvan_makenode(key_m, low, node_high(map, n)); + return result; + } else if (key_m > key) { + return sylvan_makenode(key, map, value); + } else { + // replace old + return sylvan_makenode(key, node_low(map, n), value); + } +} + +BDDMAP +sylvan_map_addall(BDDMAP map_1, BDDMAP map_2) +{ + // one of the maps is empty + if (sylvan_map_isempty(map_1)) return map_2; + if (sylvan_map_isempty(map_2)) return map_1; + + bddnode_t n_1 = GETNODE(map_1); + BDDVAR key_1 = bddnode_getvariable(n_1); + + bddnode_t n_2 = GETNODE(map_2); + BDDVAR key_2 = bddnode_getvariable(n_2); + + BDDMAP result; + if (key_1 < key_2) { + // key_1, recurse on n_1->low, map_2 + BDDMAP low = sylvan_map_addall(node_low(map_1, n_1), map_2); + result = sylvan_makenode(key_1, low, node_high(map_1, n_1)); + } else if (key_1 > key_2) { + // key_2, recurse on map_1, n_2->low + BDDMAP low = sylvan_map_addall(map_1, node_low(map_2, n_2)); + result = sylvan_makenode(key_2, low, node_high(map_2, n_2)); + } else { + // equal: key_2, recurse on n_1->low, n_2->low + BDDMAP low = sylvan_map_addall(node_low(map_1, n_1), node_low(map_2, n_2)); + result = sylvan_makenode(key_2, low, node_high(map_2, n_2)); + } + return result; +} + +BDDMAP +sylvan_map_remove(BDDMAP map, BDDVAR key) +{ + if (sylvan_map_isempty(map)) return map; + + bddnode_t n = GETNODE(map); + BDDVAR key_m = bddnode_getvariable(n); + + if (key_m < key) { + BDDMAP low = sylvan_map_remove(node_low(map, n), key); + BDDMAP result = sylvan_makenode(key_m, low, node_high(map, n)); + return result; + } else if (key_m > key) { + return map; + } else { + return node_low(map, n); + } +} + +BDDMAP +sylvan_map_removeall(BDDMAP map, BDDSET toremove) +{ + if (sylvan_map_isempty(map)) return map; + if (sylvan_set_isempty(toremove)) return map; + + bddnode_t n_1 = GETNODE(map); + BDDVAR key_1 = bddnode_getvariable(n_1); + + bddnode_t n_2 = GETNODE(toremove); + BDDVAR key_2 = bddnode_getvariable(n_2); + + if (key_1 < key_2) { + BDDMAP low = sylvan_map_removeall(node_low(map, n_1), toremove); + BDDMAP result = sylvan_makenode(key_1, low, node_high(map, n_1)); + return result; + } else if (key_1 > key_2) { + return sylvan_map_removeall(map, node_high(toremove, n_2)); + } else { + return sylvan_map_removeall(node_low(map, n_1), node_high(toremove, n_2)); + } +} + +int +sylvan_map_in(BDDMAP map, BDDVAR key) +{ + while (!sylvan_map_isempty(map)) { + bddnode_t n = GETNODE(map); + BDDVAR n_level = bddnode_getvariable(n); + if (n_level == key) return 1; + if (n_level > key) return 0; // BDDs are ordered + map = node_low(map, n); + } + + return 0; +} + +size_t +sylvan_map_count(BDDMAP map) +{ + size_t r=0; + while (!sylvan_map_isempty(map)) { r++; map=sylvan_map_next(map); } + return r; +} + +BDDMAP +sylvan_set_to_map(BDDSET set, BDD value) +{ + if (sylvan_set_isempty(set)) return sylvan_map_empty(); + bddnode_t set_n = GETNODE(set); + BDD sub = sylvan_set_to_map(node_high(set, set_n), value); + BDD result = sylvan_makenode(sub, bddnode_getvariable(set_n), value); + return result; +} + +/** + * Determine the support of a BDD (all variables used in the BDD) + */ +TASK_IMPL_1(BDD, sylvan_support, BDD, bdd) +{ + if (bdd == sylvan_true || bdd == sylvan_false) return sylvan_set_empty(); // return empty set + + sylvan_gc_test(); + + sylvan_stats_count(BDD_SUPPORT); + + BDD result; + if (cache_get3(CACHE_BDD_SUPPORT, bdd, 0, 0, &result)) { + sylvan_stats_count(BDD_SUPPORT_CACHED); + return result; + } + + bddnode_t n = GETNODE(bdd); + BDD high, low, set; + + /* compute recursively */ + bdd_refs_spawn(SPAWN(sylvan_support, bddnode_getlow(n))); + high = bdd_refs_push(CALL(sylvan_support, bddnode_gethigh(n))); + low = bdd_refs_push(bdd_refs_sync(SYNC(sylvan_support))); + + /* take intersection of support of low and support of high */ + set = sylvan_and(low, high); + bdd_refs_pop(2); + + /* add current level to set */ + result = sylvan_makenode(bddnode_getvariable(n), sylvan_false, set); + + if (cache_put3(CACHE_BDD_SUPPORT, bdd, 0, 0, result)) sylvan_stats_count(BDD_SUPPORT_CACHEDPUT); + return result; +} + +static void +sylvan_unmark_rec(bddnode_t node) +{ + if (bddnode_getmark(node)) { + bddnode_setmark(node, 0); + if (!sylvan_isconst(bddnode_getlow(node))) sylvan_unmark_rec(GETNODE(bddnode_getlow(node))); + if (!sylvan_isconst(bddnode_gethigh(node))) sylvan_unmark_rec(GETNODE(bddnode_gethigh(node))); + } +} + +/** + * fprint, print + */ +void +sylvan_fprint(FILE *f, BDD bdd) +{ + sylvan_serialize_reset(); + size_t v = sylvan_serialize_add(bdd); + fprintf(f, "%s%zu,", bdd&sylvan_complement?"!":"", v); + sylvan_serialize_totext(f); +} + +void +sylvan_print(BDD bdd) +{ + sylvan_fprint(stdout, bdd); +} + +/** + * Output to .DOT files + */ + +/*** + * We keep a set [level -> [node]] using AVLset + */ +struct level_to_nodeset { + BDDVAR level; + avl_node_t *set; +}; + +AVL(level_to_nodeset, struct level_to_nodeset) +{ + if (left->level > right->level) return 1; + if (right->level > left->level) return -1; + return 0; +} + +AVL(nodeset, BDD) +{ + if (*left > *right) return 1; + if (*right > *left) return -1; + return 0; +} + +/* returns 1 if inserted, 0 if already existed */ +static int __attribute__((noinline)) +sylvan_dothelper_register(avl_node_t **set, BDD bdd) +{ + struct level_to_nodeset s, *ss; + s.level = sylvan_var(bdd); + ss = level_to_nodeset_search(*set, &s); + if (ss == NULL) { + s.set = NULL; + ss = level_to_nodeset_put(set, &s, NULL); + } + assert(ss != NULL); + return nodeset_insert(&ss->set, &bdd); +} + +static void +sylvan_fprintdot_rec(FILE *out, BDD bdd, avl_node_t **levels) +{ + bdd = BDD_STRIPMARK(bdd); + if (bdd == sylvan_false) return; + if (!sylvan_dothelper_register(levels, bdd)) return; + + BDD low = sylvan_low(bdd); + BDD high = sylvan_high(bdd); + fprintf(out, "\"%" PRIx64 "\" [label=\"%d\"];\n", bdd, sylvan_var(bdd)); + fprintf(out, "\"%" PRIx64 "\" -> \"%" PRIx64 "\" [style=dashed];\n", bdd, low); + fprintf(out, "\"%" PRIx64 "\" -> \"%" PRIx64 "\" [style=solid dir=both arrowtail=%s];\n", bdd, BDD_STRIPMARK(high), BDD_HASMARK(high) ? "dot" : "none"); + sylvan_fprintdot_rec(out, low, levels); + sylvan_fprintdot_rec(out, high, levels); +} + +void +sylvan_fprintdot(FILE *out, BDD bdd) +{ + fprintf(out, "digraph \"DD\" {\n"); + fprintf(out, "graph [dpi = 300];\n"); + fprintf(out, "center = true;\n"); + fprintf(out, "edge [dir = forward];\n"); + fprintf(out, "0 [label=\"0\", style=filled, shape=box, height=0.3, width=0.3];\n"); + fprintf(out, "root [style=invis];\n"); + fprintf(out, "root -> \"%" PRIx64 "\" [style=solid dir=both arrowtail=%s];\n", BDD_STRIPMARK(bdd), BDD_HASMARK(bdd) ? "dot" : "none"); + + avl_node_t *levels = NULL; + sylvan_fprintdot_rec(out, bdd, &levels); + + if (levels != NULL) { + size_t levels_count = avl_count(levels); + struct level_to_nodeset *arr = level_to_nodeset_toarray(levels); + size_t i; + for (i=0;i<levels_count;i++) { + fprintf(out, "{ rank=same; "); + size_t node_count = avl_count(arr[i].set); + size_t j; + BDD *arr_j = nodeset_toarray(arr[i].set); + for (j=0;j<node_count;j++) { + fprintf(out, "\"%" PRIx64 "\"; ", arr_j[j]); + } + fprintf(out, "}\n"); + } + level_to_nodeset_free(&levels); + } + + fprintf(out, "}\n"); +} + +void +sylvan_printdot(BDD bdd) +{ + sylvan_fprintdot(stdout, bdd); +} + +static void +sylvan_fprintdot_nc_rec(FILE *out, BDD bdd, avl_node_t **levels) +{ + if (bdd == sylvan_true || bdd == sylvan_false) return; + if (!sylvan_dothelper_register(levels, bdd)) return; + + BDD low = sylvan_low(bdd); + BDD high = sylvan_high(bdd); + fprintf(out, "\"%" PRIx64 "\" [label=\"%d\"];\n", bdd, sylvan_var(bdd)); + fprintf(out, "\"%" PRIx64 "\" -> \"%" PRIx64 "\" [style=dashed];\n", bdd, low); + fprintf(out, "\"%" PRIx64 "\" -> \"%" PRIx64 "\" [style=solid];\n", bdd, high); + sylvan_fprintdot_nc_rec(out, low, levels); + sylvan_fprintdot_nc_rec(out, high, levels); +} + +void +sylvan_fprintdot_nc(FILE *out, BDD bdd) +{ + fprintf(out, "digraph \"DD\" {\n"); + fprintf(out, "graph [dpi = 300];\n"); + fprintf(out, "center = true;\n"); + fprintf(out, "edge [dir = forward];\n"); + fprintf(out, "\"%" PRIx64 "\" [shape=box, label=\"F\", style=filled, shape=box, height=0.3, width=0.3];\n", sylvan_false); + fprintf(out, "\"%" PRIx64 "\" [shape=box, label=\"T\", style=filled, shape=box, height=0.3, width=0.3];\n", sylvan_true); + fprintf(out, "root [style=invis];\n"); + fprintf(out, "root -> \"%" PRIx64 "\" [style=solid];\n", bdd); + + avl_node_t *levels = NULL; + sylvan_fprintdot_nc_rec(out, bdd, &levels); + + if (levels != NULL) { + size_t levels_count = avl_count(levels); + struct level_to_nodeset *arr = level_to_nodeset_toarray(levels); + size_t i; + for (i=0;i<levels_count;i++) { + fprintf(out, "{ rank=same; "); + size_t node_count = avl_count(arr[i].set); + size_t j; + BDD *arr_j = nodeset_toarray(arr[i].set); + for (j=0;j<node_count;j++) { + fprintf(out, "\"%" PRIx64 "\"; ", arr_j[j]); + } + fprintf(out, "}\n"); + } + level_to_nodeset_free(&levels); + } + + fprintf(out, "}\n"); +} + +void +sylvan_printdot_nc(BDD bdd) +{ + sylvan_fprintdot_nc(stdout, bdd); +} + +/** + * SERIALIZATION + */ + +struct sylvan_ser { + BDD bdd; + size_t assigned; +}; + +// Define a AVL tree type with prefix 'sylvan_ser' holding +// nodes of struct sylvan_ser with the following compare() function... +AVL(sylvan_ser, struct sylvan_ser) +{ + if (left->bdd > right->bdd) return 1; + if (left->bdd < right->bdd) return -1; + return 0; +} + +// Define a AVL tree type with prefix 'sylvan_ser_reversed' holding +// nodes of struct sylvan_ser with the following compare() function... +AVL(sylvan_ser_reversed, struct sylvan_ser) +{ + if (left->assigned > right->assigned) return 1; + if (left->assigned < right->assigned) return -1; + return 0; +} + +// Initially, both sets are empty +static avl_node_t *sylvan_ser_set = NULL; +static avl_node_t *sylvan_ser_reversed_set = NULL; + +// Start counting (assigning numbers to BDDs) at 1 +static size_t sylvan_ser_counter = 1; +static size_t sylvan_ser_done = 0; + +// Given a BDD, assign unique numbers to all nodes +static size_t +sylvan_serialize_assign_rec(BDD bdd) +{ + if (sylvan_isnode(bdd)) { + bddnode_t n = GETNODE(bdd); + + struct sylvan_ser s, *ss; + s.bdd = BDD_STRIPMARK(bdd); + ss = sylvan_ser_search(sylvan_ser_set, &s); + if (ss == NULL) { + // assign dummy value + s.assigned = 0; + ss = sylvan_ser_put(&sylvan_ser_set, &s, NULL); + + // first assign recursively + sylvan_serialize_assign_rec(bddnode_getlow(n)); + sylvan_serialize_assign_rec(bddnode_gethigh(n)); + + // assign real value + ss->assigned = sylvan_ser_counter++; + + // put a copy in the reversed table + sylvan_ser_reversed_insert(&sylvan_ser_reversed_set, ss); + } + + return ss->assigned; + } + + return BDD_STRIPMARK(bdd); +} + +size_t +sylvan_serialize_add(BDD bdd) +{ + return BDD_TRANSFERMARK(bdd, sylvan_serialize_assign_rec(bdd)); +} + +void +sylvan_serialize_reset() +{ + sylvan_ser_free(&sylvan_ser_set); + sylvan_ser_free(&sylvan_ser_reversed_set); + sylvan_ser_counter = 1; + sylvan_ser_done = 0; +} + +size_t +sylvan_serialize_get(BDD bdd) +{ + if (!sylvan_isnode(bdd)) return bdd; + struct sylvan_ser s, *ss; + s.bdd = BDD_STRIPMARK(bdd); + ss = sylvan_ser_search(sylvan_ser_set, &s); + assert(ss != NULL); + return BDD_TRANSFERMARK(bdd, ss->assigned); +} + +BDD +sylvan_serialize_get_reversed(size_t value) +{ + if (!sylvan_isnode(value)) return value; + struct sylvan_ser s, *ss; + s.assigned = BDD_STRIPMARK(value); + ss = sylvan_ser_reversed_search(sylvan_ser_reversed_set, &s); + assert(ss != NULL); + return BDD_TRANSFERMARK(value, ss->bdd); +} + +void +sylvan_serialize_totext(FILE *out) +{ + fprintf(out, "["); + avl_iter_t *it = sylvan_ser_reversed_iter(sylvan_ser_reversed_set); + struct sylvan_ser *s; + + while ((s=sylvan_ser_reversed_iter_next(it))) { + BDD bdd = s->bdd; + bddnode_t n = GETNODE(bdd); + fprintf(out, "(%zu,%u,%zu,%zu,%u),", s->assigned, + bddnode_getvariable(n), + (size_t)bddnode_getlow(n), + (size_t)BDD_STRIPMARK(bddnode_gethigh(n)), + BDD_HASMARK(bddnode_gethigh(n)) ? 1 : 0); + } + + sylvan_ser_reversed_iter_free(it); + fprintf(out, "]"); +} + +void +sylvan_serialize_tofile(FILE *out) +{ + size_t count = avl_count(sylvan_ser_reversed_set); + assert(count >= sylvan_ser_done); + assert(count == sylvan_ser_counter-1); + count -= sylvan_ser_done; + fwrite(&count, sizeof(size_t), 1, out); + + struct sylvan_ser *s; + avl_iter_t *it = sylvan_ser_reversed_iter(sylvan_ser_reversed_set); + + /* Skip already written entries */ + size_t index = 0; + while (index < sylvan_ser_done && (s=sylvan_ser_reversed_iter_next(it))) { + index++; + assert(s->assigned == index); + } + + while ((s=sylvan_ser_reversed_iter_next(it))) { + index++; + assert(s->assigned == index); + + bddnode_t n = GETNODE(s->bdd); + + struct bddnode node; + bddnode_makenode(&node, bddnode_getvariable(n), sylvan_serialize_get(bddnode_getlow(n)), sylvan_serialize_get(bddnode_gethigh(n))); + + fwrite(&node, sizeof(struct bddnode), 1, out); + } + + sylvan_ser_done = sylvan_ser_counter-1; + sylvan_ser_reversed_iter_free(it); +} + +void +sylvan_serialize_fromfile(FILE *in) +{ + size_t count, i; + if (fread(&count, sizeof(size_t), 1, in) != 1) { + // TODO FIXME return error + printf("sylvan_serialize_fromfile: file format error, giving up\n"); + exit(-1); + } + + for (i=1; i<=count; i++) { + struct bddnode node; + if (fread(&node, sizeof(struct bddnode), 1, in) != 1) { + // TODO FIXME return error + printf("sylvan_serialize_fromfile: file format error, giving up\n"); + exit(-1); + } + + BDD low = sylvan_serialize_get_reversed(bddnode_getlow(&node)); + BDD high = sylvan_serialize_get_reversed(bddnode_gethigh(&node)); + + struct sylvan_ser s; + s.bdd = sylvan_makenode(bddnode_getvariable(&node), low, high); + s.assigned = ++sylvan_ser_done; // starts at 0 but we want 1-based... + + sylvan_ser_insert(&sylvan_ser_set, &s); + sylvan_ser_reversed_insert(&sylvan_ser_reversed_set, &s); + } +} + +/** + * Generate SHA2 structural hashes. + * Hashes are independent of location. + * Mainly useful for debugging purposes. + */ +static void +sylvan_sha2_rec(BDD bdd, SHA256_CTX *ctx) +{ + if (bdd == sylvan_true || bdd == sylvan_false) { + SHA256_Update(ctx, (void*)&bdd, sizeof(BDD)); + return; + } + + bddnode_t node = GETNODE(bdd); + if (bddnode_getmark(node) == 0) { + bddnode_setmark(node, 1); + uint32_t level = bddnode_getvariable(node); + if (BDD_STRIPMARK(bddnode_gethigh(node))) level |= 0x80000000; + SHA256_Update(ctx, (void*)&level, sizeof(uint32_t)); + sylvan_sha2_rec(bddnode_gethigh(node), ctx); + sylvan_sha2_rec(bddnode_getlow(node), ctx); + } +} + +void +sylvan_printsha(BDD bdd) +{ + sylvan_fprintsha(stdout, bdd); +} + +void +sylvan_fprintsha(FILE *f, BDD bdd) +{ + char buf[80]; + sylvan_getsha(bdd, buf); + fprintf(f, "%s", buf); +} + +void +sylvan_getsha(BDD bdd, char *target) +{ + SHA256_CTX ctx; + SHA256_Init(&ctx); + sylvan_sha2_rec(bdd, &ctx); + if (bdd != sylvan_true && bdd != sylvan_false) sylvan_unmark_rec(GETNODE(bdd)); + SHA256_End(&ctx, target); +} + +/** + * Debug tool to check that a BDD is properly ordered. + * Also that every BDD node is marked 'in-use' in the hash table. + */ +TASK_2(int, sylvan_test_isbdd_rec, BDD, bdd, BDDVAR, parent_var) +{ + if (bdd == sylvan_true || bdd == sylvan_false) return 1; + assert(llmsset_is_marked(nodes, BDD_STRIPMARK(bdd))); + + sylvan_stats_count(BDD_ISBDD); + + uint64_t result; + if (cache_get3(CACHE_BDD_ISBDD, bdd, 0, 0, &result)) { + sylvan_stats_count(BDD_ISBDD_CACHED); + return result; + } + + bddnode_t n = GETNODE(bdd); + BDDVAR var = bddnode_getvariable(n); + if (var <= parent_var) { + result = 0; + } else { + SPAWN(sylvan_test_isbdd_rec, node_low(bdd, n), var); + result = (uint64_t)CALL(sylvan_test_isbdd_rec, node_high(bdd, n), var); + if (!SYNC(sylvan_test_isbdd_rec)) result = 0; + } + + if (cache_put3(CACHE_BDD_ISBDD, bdd, 0, 0, result)) sylvan_stats_count(BDD_ISBDD_CACHEDPUT); + return result; +} + +TASK_IMPL_1(int, sylvan_test_isbdd, BDD, bdd) +{ + if (bdd == sylvan_true) return 1; + if (bdd == sylvan_false) return 1; + + assert(llmsset_is_marked(nodes, BDD_STRIPMARK(bdd))); + + bddnode_t n = GETNODE(bdd); + BDDVAR var = bddnode_getvariable(n); + SPAWN(sylvan_test_isbdd_rec, node_low(bdd, n), var); + int result = CALL(sylvan_test_isbdd_rec, node_high(bdd, n), var); + if (!SYNC(sylvan_test_isbdd_rec)) result = 0; + return result; +} diff --git a/src/sylvan_bdd.h b/src/sylvan_bdd.h new file mode 100644 index 000000000..6cd4ef584 --- /dev/null +++ b/src/sylvan_bdd.h @@ -0,0 +1,423 @@ +/* + * Copyright 2011-2015 Formal Methods and Tools, University of Twente + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* Do not include this file directly. Instead, include sylvan.h */ + +#include <tls.h> + +#ifndef SYLVAN_BDD_H +#define SYLVAN_BDD_H + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef uint64_t BDD; // low 40 bits used for index, highest bit for complement, rest 0 +// BDDSET uses the BDD node hash table. A BDDSET is an ordered BDD. +typedef uint64_t BDDSET; // encodes a set of variables (e.g. for exists etc.) +// BDDMAP also uses the BDD node hash table. A BDDMAP is *not* an ordered BDD. +typedef uint64_t BDDMAP; // encodes a function of variable->BDD (e.g. for substitute) +typedef uint32_t BDDVAR; // low 24 bits only + +#define sylvan_complement ((uint64_t)0x8000000000000000) +#define sylvan_false ((BDD)0x0000000000000000) +#define sylvan_true (sylvan_false|sylvan_complement) +#define sylvan_invalid ((BDD)0x7fffffffffffffff) + +#define sylvan_isconst(bdd) (bdd == sylvan_true || bdd == sylvan_false) +#define sylvan_isnode(bdd) (bdd != sylvan_true && bdd != sylvan_false) + +/** + * Initialize BDD functionality. + * + * Granularity (BDD only) determines usage of operation cache. Smallest value is 1: use the operation cache always. + * Higher values mean that the cache is used less often. Variables are grouped such that + * the cache is used when going to the next group, i.e., with granularity=3, variables [0,1,2] are in the + * first group, [3,4,5] in the next, etc. Then no caching occur between 0->1, 1->2, 0->2. Caching occurs + * on 0->3, 1->4, 2->3, etc. + * + * A reasonable default is a granularity of 4-16, strongly depending on the structure of the BDDs. + */ +void sylvan_init_bdd(int granularity); + +/* Create a BDD representing just <var> or the negation of <var> */ +BDD sylvan_ithvar(BDDVAR var); +static inline BDD sylvan_nithvar(BDD var) { return sylvan_ithvar(var) ^ sylvan_complement; } + +/* Retrieve the <var> of the BDD node <bdd> */ +BDDVAR sylvan_var(BDD bdd); + +/* Follow <low> and <high> edges */ +BDD sylvan_low(BDD bdd); +BDD sylvan_high(BDD bdd); + +/* Add or remove external reference to BDD */ +BDD sylvan_ref(BDD a); +void sylvan_deref(BDD a); + +/* For use in custom mark functions */ +VOID_TASK_DECL_1(sylvan_gc_mark_rec, BDD); +#define sylvan_gc_mark_rec(mdd) CALL(sylvan_gc_mark_rec, mdd) + +/* Return the number of external references */ +size_t sylvan_count_refs(); + +/* Add or remove BDD pointers to protect (indirect external references) */ +void sylvan_protect(BDD* ptr); +void sylvan_unprotect(BDD* ptr); + +/* Return the number of protected BDD pointers */ +size_t sylvan_count_protected(); + +/* Mark BDD for "notify on dead" */ +#define sylvan_notify_ondead(bdd) llmsset_notify_ondead(nodes, bdd&~sylvan_complement) + +/* Unary, binary and if-then-else operations */ +#define sylvan_not(a) (((BDD)a)^sylvan_complement) +TASK_DECL_4(BDD, sylvan_ite, BDD, BDD, BDD, BDDVAR); +#define sylvan_ite(a,b,c) (CALL(sylvan_ite,a,b,c,0)) +TASK_DECL_3(BDD, sylvan_and, BDD, BDD, BDDVAR); +#define sylvan_and(a,b) (CALL(sylvan_and,a,b,0)) +TASK_DECL_3(BDD, sylvan_xor, BDD, BDD, BDDVAR); +#define sylvan_xor(a,b) (CALL(sylvan_xor,a,b,0)) +/* Do not use nested calls for xor/equiv parameter b! */ +#define sylvan_equiv(a,b) sylvan_not(sylvan_xor(a,b)) +#define sylvan_or(a,b) sylvan_not(sylvan_and(sylvan_not(a),sylvan_not(b))) +#define sylvan_nand(a,b) sylvan_not(sylvan_and(a,b)) +#define sylvan_nor(a,b) sylvan_not(sylvan_or(a,b)) +#define sylvan_imp(a,b) sylvan_not(sylvan_and(a,sylvan_not(b))) +#define sylvan_invimp(a,b) sylvan_not(sylvan_and(sylvan_not(a),b)) +#define sylvan_biimp sylvan_equiv +#define sylvan_diff(a,b) sylvan_and(a,sylvan_not(b)) +#define sylvan_less(a,b) sylvan_and(sylvan_not(a),b) + +/* Existential and Universal quantifiers */ +TASK_DECL_3(BDD, sylvan_exists, BDD, BDD, BDDVAR); +#define sylvan_exists(a, vars) (CALL(sylvan_exists, a, vars, 0)) +#define sylvan_forall(a, vars) (sylvan_not(CALL(sylvan_exists, sylvan_not(a), vars, 0))) + +/** + * Compute \exists v: A(...) \and B(...) + * Parameter vars is the cube (conjunction) of all v variables. + */ +TASK_DECL_4(BDD, sylvan_and_exists, BDD, BDD, BDDSET, BDDVAR); +#define sylvan_and_exists(a,b,vars) CALL(sylvan_and_exists,a,b,vars,0) + +/** + * Compute R(s,t) = \exists x: A(s,x) \and B(x,t) + * or R(s) = \exists x: A(s,x) \and B(x) + * Assumes s,t are interleaved with s even and t odd (s+1). + * Parameter vars is the cube of all s and/or t variables. + * Other variables in A are "ignored" (existential quantification) + * Other variables in B are kept + * Alternatively, vars=false means all variables are in vars + * + * Use this function to concatenate two relations --> --> + * or to take the 'previous' of a set --> S + */ +TASK_DECL_4(BDD, sylvan_relprev, BDD, BDD, BDDSET, BDDVAR); +#define sylvan_relprev(a,b,vars) CALL(sylvan_relprev,a,b,vars,0) + +/** + * Compute R(s) = \exists x: A(x) \and B(x,s) + * with support(result) = s, support(A) = s, support(B) = s+t + * Assumes s,t are interleaved with s even and t odd (s+1). + * Parameter vars is the cube of all s and/or t variables. + * Other variables in A are kept + * Other variables in B are "ignored" (existential quantification) + * Alternatively, vars=false means all variables are in vars + * + * Use this function to take the 'next' of a set S --> + */ +TASK_DECL_4(BDD, sylvan_relnext, BDD, BDD, BDDSET, BDDVAR); +#define sylvan_relnext(a,b,vars) CALL(sylvan_relnext,a,b,vars,0) + +/** + * Computes the transitive closure by traversing the BDD recursively. + * See Y. Matsunaga, P. C. McGeer, R. K. Brayton + * On Computing the Transitive Closure of a State Transition Relation + * 30th ACM Design Automation Conference, 1993. + * + * The input BDD must be a transition relation that only has levels of s,t + * with s,t interleaved with s even and t odd, i.e. + * s level 0,2,4 matches with t level 1,3,5 and so forth. + */ +TASK_DECL_2(BDD, sylvan_closure, BDD, BDDVAR); +#define sylvan_closure(a) CALL(sylvan_closure,a,0); + +/** + * Calculate a@b (a constrain b), such that (b -> a@b) = (b -> a) + * Special cases: + * - a@0 = 0 + * - a@1 = f + * - 0@b = 0 + * - 1@b = 1 + * - a@a = 1 + * - a@not(a) = 0 + */ +TASK_DECL_3(BDD, sylvan_constrain, BDD, BDD, BDDVAR); +#define sylvan_constrain(f,c) (CALL(sylvan_constrain, (f), (c), 0)) + +TASK_DECL_3(BDD, sylvan_restrict, BDD, BDD, BDDVAR); +#define sylvan_restrict(f,c) (CALL(sylvan_restrict, (f), (c), 0)) + +TASK_DECL_3(BDD, sylvan_compose, BDD, BDDMAP, BDDVAR); +#define sylvan_compose(f,m) (CALL(sylvan_compose, (f), (m), 0)) + +/** + * Calculate the support of a BDD. + * A variable v is in the support of a Boolean function f iff f[v<-0] != f[v<-1] + * It is also the set of all variables in the BDD nodes of the BDD. + */ +TASK_DECL_1(BDD, sylvan_support, BDD); +#define sylvan_support(bdd) (CALL(sylvan_support, bdd)) + +/** + * A set of BDD variables is a cube (conjunction) of variables in their positive form. + * Note 2015-06-10: This used to be a union (disjunction) of variables in their positive form. + */ +// empty bddset +#define sylvan_set_empty() sylvan_true +#define sylvan_set_isempty(set) (set == sylvan_true) +// add variables to the bddset +#define sylvan_set_add(set, var) sylvan_and(set, sylvan_ithvar(var)) +#define sylvan_set_addall(set, set_to_add) sylvan_and(set, set_to_add) +// remove variables from the bddset +#define sylvan_set_remove(set, var) sylvan_exists(set, var) +#define sylvan_set_removeall(set, set_to_remove) sylvan_exists(set, set_to_remove) +// iterate through all variables +#define sylvan_set_var(set) (sylvan_var(set)) +#define sylvan_set_next(set) (sylvan_high(set)) +int sylvan_set_in(BDDSET set, BDDVAR var); +size_t sylvan_set_count(BDDSET set); +void sylvan_set_toarray(BDDSET set, BDDVAR *arr); +// variables in arr should be ordered +TASK_DECL_2(BDDSET, sylvan_set_fromarray, BDDVAR*, size_t); +#define sylvan_set_fromarray(arr, length) ( CALL(sylvan_set_fromarray, arr, length) ) +void sylvan_test_isset(BDDSET set); + +/** + * BDDMAP maps BDDVAR-->BDD, implemented using BDD nodes. + * Based on disjunction of variables, but with high edges to BDDs instead of True terminals. + */ +// empty bddmap +static inline BDDMAP sylvan_map_empty() { return sylvan_false; } +static inline int sylvan_map_isempty(BDDMAP map) { return map == sylvan_false ? 1 : 0; } +// add key-value pairs to the bddmap +BDDMAP sylvan_map_add(BDDMAP map, BDDVAR key, BDD value); +BDDMAP sylvan_map_addall(BDDMAP map_1, BDDMAP map_2); +// remove key-value pairs from the bddmap +BDDMAP sylvan_map_remove(BDDMAP map, BDDVAR key); +BDDMAP sylvan_map_removeall(BDDMAP map, BDDSET toremove); +// iterate through all pairs +static inline BDDVAR sylvan_map_key(BDDMAP map) { return sylvan_var(map); } +static inline BDD sylvan_map_value(BDDMAP map) { return sylvan_high(map); } +static inline BDDMAP sylvan_map_next(BDDMAP map) { return sylvan_low(map); } +// is a key in the map +int sylvan_map_in(BDDMAP map, BDDVAR key); +// count number of keys +size_t sylvan_map_count(BDDMAP map); +// convert a BDDSET (cube of variables) to a map, with all variables pointing on the value +BDDMAP sylvan_set_to_map(BDDSET set, BDD value); + +/** + * Node creation primitive. + * Careful: does not check ordering! + * Preferably only use when debugging! + */ +BDD sylvan_makenode(BDDVAR level, BDD low, BDD high); + +/** + * Write a DOT representation of a BDD + */ +void sylvan_printdot(BDD bdd); +void sylvan_fprintdot(FILE *out, BDD bdd); + +/** + * Write a DOT representation of a BDD. + * This variant does not print complement edges. + */ +void sylvan_printdot_nc(BDD bdd); +void sylvan_fprintdot_nc(FILE *out, BDD bdd); + +void sylvan_print(BDD bdd); +void sylvan_fprint(FILE *f, BDD bdd); + +void sylvan_printsha(BDD bdd); +void sylvan_fprintsha(FILE *f, BDD bdd); +void sylvan_getsha(BDD bdd, char *target); // target must be at least 65 bytes... + +/** + * Calculate number of satisfying variable assignments. + * The set of variables must be >= the support of the BDD. + */ + +TASK_DECL_3(double, sylvan_satcount, BDD, BDDSET, BDDVAR); +#define sylvan_satcount(bdd, variables) CALL(sylvan_satcount, bdd, variables, 0) + +/** + * Create a BDD cube representing the conjunction of variables in their positive or negative + * form depending on whether the cube[idx] equals 0 (negative), 1 (positive) or 2 (any). + * CHANGED 2014/09/19: vars is now a BDDSET (ordered!) + */ +BDD sylvan_cube(BDDSET variables, uint8_t *cube); +TASK_DECL_3(BDD, sylvan_union_cube, BDD, BDDSET, uint8_t*); +#define sylvan_union_cube(bdd, variables, cube) CALL(sylvan_union_cube, bdd, variables, cube) + +/** + * Pick one satisfying variable assignment randomly for which <bdd> is true. + * The <variables> set must include all variables in the support of <bdd>. + * + * The function will set the values of str, such that + * str[index] where index is the index in the <variables> set is set to + * 0 when the variable is negative, 1 when positive, or 2 when it could be either. + * + * This implies that str[i] will be set in the variable ordering as in <variables>. + * + * Returns 1 when succesful, or 0 when no assignment is found (i.e. bdd==sylvan_false). + */ +int sylvan_sat_one(BDD bdd, BDDSET variables, uint8_t* str); + +/** + * Pick one satisfying variable assignment randomly from the given <bdd>. + * Functionally equivalent to performing sylvan_cube on the result of sylvan_sat_one. + * For the result: sylvan_and(res, bdd) = res. + */ +BDD sylvan_sat_one_bdd(BDD bdd); +#define sylvan_pick_cube sylvan_sat_one_bdd + +/** + * Enumerate all satisfying variable assignments from the given <bdd> using variables <vars>. + * Calls <cb> with four parameters: a user-supplied context, the array of BDD variables in <vars>, + * the cube (array of values 0 and 1 for each variables in <vars>) and the length of the two arrays. + */ +LACE_TYPEDEF_CB(void, enum_cb, void*, BDDVAR*, uint8_t*, int); +VOID_TASK_DECL_4(sylvan_enum, BDD, BDDSET, enum_cb, void*); +#define sylvan_enum(bdd, vars, cb, context) CALL(sylvan_enum, bdd, vars, cb, context) +VOID_TASK_DECL_4(sylvan_enum_par, BDD, BDDSET, enum_cb, void*); +#define sylvan_enum_par(bdd, vars, cb, context) CALL(sylvan_enum_par, bdd, vars, cb, context) + +/** + * Enumerate all satisfyable variable assignments of the given <bdd> using variables <vars>. + * Calls <cb> with two parameters: a user-supplied context and the cube (array of + * values 0 and 1 for each variable in <vars>). + * The BDD that <cb> returns is pair-wise merged (using or) and returned. + */ +LACE_TYPEDEF_CB(BDD, sylvan_collect_cb, void*, uint8_t*); +TASK_DECL_4(BDD, sylvan_collect, BDD, BDDSET, sylvan_collect_cb, void*); +#define sylvan_collect(bdd, vars, cb, context) CALL(sylvan_collect, bdd, vars, cb, context) + +/** + * Compute the number of distinct paths to sylvan_true in the BDD + */ +TASK_DECL_2(double, sylvan_pathcount, BDD, BDDVAR); +#define sylvan_pathcount(bdd) (CALL(sylvan_pathcount, bdd, 0)) + +/** + * Compute the number of BDD nodes in the BDD + */ +size_t sylvan_nodecount(BDD a); + +/** + * SAVING: + * use sylvan_serialize_add on every BDD you want to store + * use sylvan_serialize_get to retrieve the key of every stored BDD + * use sylvan_serialize_tofile + * + * LOADING: + * use sylvan_serialize_fromfile (implies sylvan_serialize_reset) + * use sylvan_serialize_get_reversed for every key + * + * MISC: + * use sylvan_serialize_reset to free all allocated structures + * use sylvan_serialize_totext to write a textual list of tuples of all BDDs. + * format: [(<key>,<level>,<key_low>,<key_high>,<complement_high>),...] + * + * for the old sylvan_print functions, use sylvan_serialize_totext + */ +size_t sylvan_serialize_add(BDD bdd); +size_t sylvan_serialize_get(BDD bdd); +BDD sylvan_serialize_get_reversed(size_t value); +void sylvan_serialize_reset(); +void sylvan_serialize_totext(FILE *out); +void sylvan_serialize_tofile(FILE *out); +void sylvan_serialize_fromfile(FILE *in); + +/** + * For debugging + * if (part of) the BDD is not 'marked' in the nodes table, assertion fails + * if the BDD is not ordered, returns 0 + * if nicely ordered, returns 1 + */ +TASK_DECL_1(int, sylvan_test_isbdd, BDD); +#define sylvan_test_isbdd(bdd) CALL(sylvan_test_isbdd, bdd) + +/* Infrastructure for internal markings */ +typedef struct bdd_refs_internal +{ + size_t r_size, r_count; + size_t s_size, s_count; + BDD *results; + Task **spawns; +} *bdd_refs_internal_t; + +extern DECLARE_THREAD_LOCAL(bdd_refs_key, bdd_refs_internal_t); + +static inline BDD +bdd_refs_push(BDD bdd) +{ + LOCALIZE_THREAD_LOCAL(bdd_refs_key, bdd_refs_internal_t); + if (bdd_refs_key->r_count >= bdd_refs_key->r_size) { + bdd_refs_key->r_size *= 2; + bdd_refs_key->results = (BDD*)realloc(bdd_refs_key->results, sizeof(BDD) * bdd_refs_key->r_size); + } + bdd_refs_key->results[bdd_refs_key->r_count++] = bdd; + return bdd; +} + +static inline void +bdd_refs_pop(int amount) +{ + LOCALIZE_THREAD_LOCAL(bdd_refs_key, bdd_refs_internal_t); + bdd_refs_key->r_count-=amount; +} + +static inline void +bdd_refs_spawn(Task *t) +{ + LOCALIZE_THREAD_LOCAL(bdd_refs_key, bdd_refs_internal_t); + if (bdd_refs_key->s_count >= bdd_refs_key->s_size) { + bdd_refs_key->s_size *= 2; + bdd_refs_key->spawns = (Task**)realloc(bdd_refs_key->spawns, sizeof(Task*) * bdd_refs_key->s_size); + } + bdd_refs_key->spawns[bdd_refs_key->s_count++] = t; +} + +static inline BDD +bdd_refs_sync(BDD result) +{ + LOCALIZE_THREAD_LOCAL(bdd_refs_key, bdd_refs_internal_t); + bdd_refs_key->s_count--; + return result; +} + +#include "sylvan_bdd_storm.h" + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif diff --git a/src/sylvan_bdd_storm.h b/src/sylvan_bdd_storm.h new file mode 100644 index 000000000..5806fba5d --- /dev/null +++ b/src/sylvan_bdd_storm.h @@ -0,0 +1,3 @@ +#define bdd_isnegated(dd) ((dd & sylvan_complement) ? 1 : 0) +#define bdd_regular(dd) (dd & ~sylvan_complement) +#define bdd_isterminal(dd) (dd == sylvan_false || dd == sylvan_true) \ No newline at end of file diff --git a/src/sylvan_cache.c b/src/sylvan_cache.c new file mode 100644 index 000000000..7bfb72afd --- /dev/null +++ b/src/sylvan_cache.c @@ -0,0 +1,220 @@ +/* + * Copyright 2011-2015 Formal Methods and Tools, University of Twente + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <errno.h> // for errno +#include <stdio.h> // for fprintf +#include <stdint.h> // for uint32_t etc +#include <stdlib.h> // for exit +#include <string.h> // for strerror +#include <sys/mman.h> // for mmap + +#include <sylvan_cache.h> + +#ifndef MAP_ANONYMOUS +#define MAP_ANONYMOUS MAP_ANON +#endif + +#ifndef compiler_barrier +#define compiler_barrier() { asm volatile("" ::: "memory"); } +#endif + +#ifndef cas +#define cas(ptr, old, new) (__sync_bool_compare_and_swap((ptr),(old),(new))) +#endif + +/** + * This cache is designed to store a,b,c->res, with a,b,c,res 64-bit integers. + * + * Each cache bucket takes 32 bytes, 2 per cache line. + * Each cache status bucket takes 4 bytes, 16 per cache line. + * Therefore, size 2^N = 36*(2^N) bytes. + */ + +struct __attribute__((packed)) cache_entry { + uint64_t a; + uint64_t b; + uint64_t c; + uint64_t res; +}; + +static size_t cache_size; // power of 2 +static size_t cache_max; // power of 2 +#if CACHE_MASK +static size_t cache_mask; // cache_size-1 +#endif +static cache_entry_t cache_table; +static uint32_t* cache_status; + +static uint64_t next_opid; + +uint64_t +cache_next_opid() +{ + return __sync_fetch_and_add(&next_opid, 1LL<<40); +} + +// status: 0x80000000 - bitlock +// 0x7fff0000 - hash (part of the 64-bit hash not used to position) +// 0x0000ffff - tag (every put increases tag field) + +/* Rotating 64-bit FNV-1a hash */ +static uint64_t +cache_hash(uint64_t a, uint64_t b, uint64_t c) +{ + const uint64_t prime = 1099511628211; + uint64_t hash = 14695981039346656037LLU; + hash = (hash ^ (a>>32)); + hash = (hash ^ a) * prime; + hash = (hash ^ b) * prime; + hash = (hash ^ c) * prime; + return hash; +} + +int +cache_get(uint64_t a, uint64_t b, uint64_t c, uint64_t *res) +{ + const uint64_t hash = cache_hash(a, b, c); +#if CACHE_MASK + volatile uint32_t *s_bucket = cache_status + (hash & cache_mask); + cache_entry_t bucket = cache_table + (hash & cache_mask); +#else + volatile uint32_t *s_bucket = cache_status + (hash % cache_size); + cache_entry_t bucket = cache_table + (hash % cache_size); +#endif + const uint32_t s = *s_bucket; + compiler_barrier(); + // abort if locked + if (s & 0x80000000) return 0; + // abort if different hash + if ((s ^ (hash>>32)) & 0x7fff0000) return 0; + // abort if key different + if (bucket->a != a || bucket->b != b || bucket->c != c) return 0; + *res = bucket->res; + compiler_barrier(); + // abort if status field changed after compiler_barrier() + return *s_bucket == s ? 1 : 0; +} + +int +cache_put(uint64_t a, uint64_t b, uint64_t c, uint64_t res) +{ + const uint64_t hash = cache_hash(a, b, c); +#if CACHE_MASK + volatile uint32_t *s_bucket = cache_status + (hash & cache_mask); + cache_entry_t bucket = cache_table + (hash & cache_mask); +#else + volatile uint32_t *s_bucket = cache_status + (hash % cache_size); + cache_entry_t bucket = cache_table + (hash % cache_size); +#endif + const uint32_t s = *s_bucket; + // abort if locked + if (s & 0x80000000) return 0; + // abort if hash identical -> no: in iscasmc this occasionally causes timeouts?! + const uint32_t hash_mask = (hash>>32) & 0x7fff0000; + // if ((s & 0x7fff0000) == hash_mask) return 0; + // use cas to claim bucket + const uint32_t new_s = ((s+1) & 0x0000ffff) | hash_mask; + if (!cas(s_bucket, s, new_s | 0x80000000)) return 0; + // cas succesful: write data + bucket->a = a; + bucket->b = b; + bucket->c = c; + bucket->res = res; + compiler_barrier(); + // after compiler_barrier(), unlock status field + *s_bucket = new_s; + return 1; +} + +void +cache_create(size_t _cache_size, size_t _max_size) +{ +#if CACHE_MASK + // Cache size must be a power of 2 + if (__builtin_popcountll(_cache_size) != 1 || __builtin_popcountll(_max_size) != 1) { + fprintf(stderr, "cache_create: Table size must be a power of 2!\n"); + exit(1); + } +#endif + + cache_size = _cache_size; + cache_max = _max_size; +#if CACHE_MASK + cache_mask = cache_size - 1; +#endif + + if (cache_size > cache_max) { + fprintf(stderr, "cache_create: Table size must be <= max size!\n"); + exit(1); + } + + cache_table = (cache_entry_t)mmap(0, cache_max * sizeof(struct cache_entry), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + cache_status = (uint32_t*)mmap(0, cache_max * sizeof(uint32_t), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + + if (cache_table == (cache_entry_t)-1 || cache_status == (uint32_t*)-1) { + fprintf(stderr, "cache_create: Unable to allocate memory: %s!\n", strerror(errno)); + exit(1); + } + + next_opid = 512LL << 40; +} + +void +cache_free() +{ + munmap(cache_table, cache_max * sizeof(struct cache_entry)); + munmap(cache_status, cache_max * sizeof(uint32_t)); +} + +void +cache_clear() +{ + // a bit silly, but this works just fine, and does not require writing 0 everywhere... + cache_free(); + cache_create(cache_size, cache_max); +} + +void +cache_setsize(size_t size) +{ + // easy solution + cache_free(); + cache_create(size, cache_max); +} + +size_t +cache_getsize() +{ + return cache_size; +} + +size_t +cache_getused() +{ + size_t result = 0; + for (size_t i=0;i<cache_size;i++) { + uint32_t s = cache_status[i]; + if (s & 0x80000000) fprintf(stderr, "cache_getuser: cache in use during cache_getused()\n"); + if (s) result++; + } + return result; +} + +size_t +cache_getmaxsize() +{ + return cache_max; +} diff --git a/src/sylvan_cache.h b/src/sylvan_cache.h new file mode 100644 index 000000000..babc74f65 --- /dev/null +++ b/src/sylvan_cache.h @@ -0,0 +1,113 @@ +#include <stdint.h> // for uint32_t etc + +#include <sylvan_config.h> + +#ifndef CACHE_H +#define CACHE_H + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#ifndef CACHE_MASK +#define CACHE_MASK 1 +#endif + +/** + * Operation cache + * + * Use cache_next_opid() at initialization time to generate unique "operation identifiers". + * You should store these operation identifiers as static globals in your implementation .C/.CPP file. + * + * For custom operations, just use the following functions: + * - cache_get3/cache_put3 for any operation with 1 BDD and 2 other values (that can be BDDs) + * int success = cache_get3(opid, dd1, value2, value3, &result); + * int success = cache_put3(opid, dd1, value2, value3, result); + * - cache_get4/cache_put4 for any operation with 4 BDDs + * int success = cache_get4(opid, dd1, dd2, dd3, dd4, &result); + * int success = cache_get4(opid, dd1, dd2, dd3, dd4, result); + * + * Notes: + * - The "result" is any 64-bit value + * - Use "0" for unused parameters + */ + +typedef struct cache_entry *cache_entry_t; + +/** + * Primitives for cache get/put + */ +int cache_get(uint64_t a, uint64_t b, uint64_t c, uint64_t *res); +int cache_put(uint64_t a, uint64_t b, uint64_t c, uint64_t res); + +/** + * Helper function to get next 'operation id' (during initialization of modules) + */ +uint64_t cache_next_opid(); + +/** + * dd must be MTBDD, d2/d3 can be anything + */ +static inline int __attribute__((unused)) +cache_get3(uint64_t opid, uint64_t dd, uint64_t d2, uint64_t d3, uint64_t *res) +{ + return cache_get(dd | opid, d2, d3, res); +} + +/** + * dd/dd2/dd3/dd4 must be MTBDDs + */ +static inline int __attribute__((unused)) +cache_get4(uint64_t opid, uint64_t dd, uint64_t dd2, uint64_t dd3, uint64_t dd4, uint64_t *res) +{ + uint64_t p2 = dd2 | ((dd4 & 0x00000000000fffff) << 40); // 20 bits and complement bit + if (dd4 & 0x8000000000000000) p2 |= 0x4000000000000000; + uint64_t p3 = dd3 | ((dd4 & 0x000000fffff00000) << 20); // 20 bits + + return cache_get3(opid, dd, p2, p3, res); +} + +/** + * dd must be MTBDD, d2/d3 can be anything + */ +static inline int __attribute__((unused)) +cache_put3(uint64_t opid, uint64_t dd, uint64_t d2, uint64_t d3, uint64_t res) +{ + return cache_put(dd | opid, d2, d3, res); +} + +/** + * dd/dd2/dd3/dd4 must be MTBDDs + */ +static inline int __attribute__((unused)) +cache_put4(uint64_t opid, uint64_t dd, uint64_t dd2, uint64_t dd3, uint64_t dd4, uint64_t res) +{ + uint64_t p2 = dd2 | ((dd4 & 0x00000000000fffff) << 40); // 20 bits and complement bit + if (dd4 & 0x8000000000000000) p2 |= 0x4000000000000000; + uint64_t p3 = dd3 | ((dd4 & 0x000000fffff00000) << 20); // 20 bits + + return cache_put3(opid, dd, p2, p3, res); +} +/** + * Functions for Sylvan for cache management + */ + +void cache_create(size_t _cache_size, size_t _max_size); + +void cache_free(); + +void cache_clear(); + +void cache_setsize(size_t size); + +size_t cache_getused(); + +size_t cache_getsize(); + +size_t cache_getmaxsize(); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif diff --git a/src/sylvan_common.c b/src/sylvan_common.c new file mode 100644 index 000000000..aa0374a7b --- /dev/null +++ b/src/sylvan_common.c @@ -0,0 +1,304 @@ +/* + * Copyright 2011-2015 Formal Methods and Tools, University of Twente + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <sylvan_config.h> + +#include <sylvan.h> +#include <sylvan_common.h> + +#ifndef cas +#define cas(ptr, old, new) (__sync_bool_compare_and_swap((ptr),(old),(new))) +#endif + +/** + * Static global variables + */ + +llmsset_t nodes; + +/** + * Retrieve nodes + */ + +llmsset_t +__sylvan_get_internal_data() +{ + return nodes; +} + +/** + * Calculate table usage (in parallel) + */ +VOID_TASK_IMPL_2(sylvan_table_usage, size_t*, filled, size_t*, total) +{ + size_t tot = llmsset_get_size(nodes); + if (filled != NULL) *filled = llmsset_count_marked(nodes); + if (total != NULL) *total = tot; +} + +/** + * Implementation of garbage collection + */ +static int gc_enabled = 1; +static volatile int gc; // variable used in cas switch to ensure only one gc at a time + +struct reg_gc_mark_entry +{ + struct reg_gc_mark_entry *next; + gc_mark_cb cb; + int order; +}; + +static struct reg_gc_mark_entry *gc_mark_register = NULL; + +void +sylvan_gc_add_mark(int order, gc_mark_cb cb) +{ + struct reg_gc_mark_entry *e = (struct reg_gc_mark_entry*)malloc(sizeof(struct reg_gc_mark_entry)); + e->cb = cb; + e->order = order; + if (gc_mark_register == NULL || gc_mark_register->order>order) { + e->next = gc_mark_register; + gc_mark_register = e; + return; + } + struct reg_gc_mark_entry *f = gc_mark_register; + for (;;) { + if (f->next == NULL) { + e->next = NULL; + f->next = e; + return; + } + if (f->next->order > order) { + e->next = f->next; + f->next = e; + return; + } + f = f->next; + } +} + +static gc_hook_cb gc_hook; + +void +sylvan_gc_set_hook(gc_hook_cb new_hook) +{ + gc_hook = new_hook; +} + +void +sylvan_gc_enable() +{ + gc_enabled = 1; +} + +void +sylvan_gc_disable() +{ + gc_enabled = 0; +} + +/* Mark hook for cache */ +VOID_TASK_0(sylvan_gc_mark_cache) +{ + /* We simply clear the cache. + * Alternatively, we could implement for example some strategy + * where part of the cache is cleared and part is marked + */ + cache_clear(); +} + +/* Default hook */ + +size_t +next_size(size_t n) +{ +#if SYLVAN_SIZE_FIBONACCI + size_t f1=1, f2=1; + for (;;) { + f2 += f1; + if (f2 > n) return f2; + f1 += f2; + if (f1 > n) return f1; + } +#else + return n*2; +#endif +} + +VOID_TASK_IMPL_0(sylvan_gc_aggressive_resize) +{ + /** + * Always resize when gc called + */ + size_t max_size = llmsset_get_max_size(nodes); + size_t size = llmsset_get_size(nodes); + if (size < max_size) { + size_t new_size = next_size(size); + if (new_size > max_size) new_size = max_size; + llmsset_set_size(nodes, new_size); + size_t cache_size = cache_getsize(); + size_t cache_max = cache_getmaxsize(); + if (cache_size < cache_max) { + new_size = next_size(cache_size); + if (new_size > cache_max) new_size = cache_max; + cache_setsize(new_size); + } + } +} + +VOID_TASK_IMPL_0(sylvan_gc_default_hook) +{ + /** + * Default behavior: + * if we can resize the nodes set, and if we use more than 50%, then increase size + */ + size_t max_size = llmsset_get_max_size(nodes); + size_t size = llmsset_get_size(nodes); + if (size < max_size) { + size_t marked = llmsset_count_marked(nodes); + if (marked*2 > size) { + size_t new_size = next_size(size); + if (new_size > max_size) new_size = max_size; + llmsset_set_size(nodes, new_size); + size_t cache_size = cache_getsize(); + size_t cache_max = cache_getmaxsize(); + if (cache_size < cache_max) { + new_size = next_size(cache_size); + if (new_size > cache_max) new_size = cache_max; + cache_setsize(new_size); + } + } + } +} + +VOID_TASK_0(sylvan_gc_call_hook) +{ + // call hook function (resizing, reordering, etc) + WRAP(gc_hook); +} + +VOID_TASK_0(sylvan_gc_rehash) +{ + // rehash marked nodes + llmsset_rehash(nodes); +} + +VOID_TASK_0(sylvan_gc_destroy_unmarked) +{ + llmsset_destroy_unmarked(nodes); +} + +VOID_TASK_0(sylvan_gc_go) +{ + sylvan_stats_count(SYLVAN_GC_COUNT); + sylvan_timer_start(SYLVAN_GC); + + // clear hash array + llmsset_clear(nodes); + + // call mark functions, hook and rehash + struct reg_gc_mark_entry *e = gc_mark_register; + while (e != NULL) { + WRAP(e->cb); + e = e->next; + } + + sylvan_timer_stop(SYLVAN_GC); +} + +/* Perform garbage collection */ +VOID_TASK_IMPL_0(sylvan_gc) +{ + if (!gc_enabled) return; + if (cas(&gc, 0, 1)) { + NEWFRAME(sylvan_gc_go); + gc = 0; + } else { + /* wait for new frame to appear */ + while (*(Task* volatile*)&(lace_newframe.t) == 0) {} + lace_yield(__lace_worker, __lace_dq_head); + } +} + +/** + * Package init and quit functions + */ +void +sylvan_init_package(size_t tablesize, size_t maxsize, size_t cachesize, size_t max_cachesize) +{ + if (tablesize > maxsize) tablesize = maxsize; + if (cachesize > max_cachesize) cachesize = max_cachesize; + + if (maxsize > 0x000003ffffffffff) { + fprintf(stderr, "sylvan_init_package error: tablesize must be <= 42 bits!\n"); + exit(1); + } + + nodes = llmsset_create(tablesize, maxsize); + cache_create(cachesize, max_cachesize); + + gc = 0; +#if SYLVAN_AGGRESSIVE_RESIZE + gc_hook = TASK(sylvan_gc_aggressive_resize); +#else + gc_hook = TASK(sylvan_gc_default_hook); +#endif + sylvan_gc_add_mark(10, TASK(sylvan_gc_mark_cache)); + sylvan_gc_add_mark(19, TASK(sylvan_gc_destroy_unmarked)); + sylvan_gc_add_mark(20, TASK(sylvan_gc_call_hook)); + sylvan_gc_add_mark(30, TASK(sylvan_gc_rehash)); + + LACE_ME; + sylvan_stats_init(); +} + +struct reg_quit_entry +{ + struct reg_quit_entry *next; + quit_cb cb; +}; + +static struct reg_quit_entry *quit_register = NULL; + +void +sylvan_register_quit(quit_cb cb) +{ + struct reg_quit_entry *e = (struct reg_quit_entry*)malloc(sizeof(struct reg_quit_entry)); + e->next = quit_register; + e->cb = cb; + quit_register = e; +} + +void +sylvan_quit() +{ + while (quit_register != NULL) { + struct reg_quit_entry *e = quit_register; + quit_register = e->next; + e->cb(); + free(e); + } + + while (gc_mark_register != NULL) { + struct reg_gc_mark_entry *e = gc_mark_register; + gc_mark_register = e->next; + free(e); + } + + cache_free(); + llmsset_free(nodes); +} diff --git a/src/sylvan_common.h b/src/sylvan_common.h new file mode 100644 index 000000000..7f512a904 --- /dev/null +++ b/src/sylvan_common.h @@ -0,0 +1,85 @@ +/* + * Copyright 2011-2015 Formal Methods and Tools, University of Twente + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SYLVAN_COMMON_H +#define SYLVAN_COMMON_H + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* Garbage collection test task - t */ +#define sylvan_gc_test() YIELD_NEWFRAME() + +// BDD operations +#define CACHE_BDD_ITE (0LL<<40) +#define CACHE_BDD_AND (1LL<<40) +#define CACHE_BDD_XOR (2LL<<40) +#define CACHE_BDD_EXISTS (3LL<<40) +#define CACHE_BDD_AND_EXISTS (4LL<<40) +#define CACHE_BDD_RELNEXT (5LL<<40) +#define CACHE_BDD_RELPREV (6LL<<40) +#define CACHE_BDD_SATCOUNT (7LL<<40) +#define CACHE_BDD_COMPOSE (8LL<<40) +#define CACHE_BDD_RESTRICT (9LL<<40) +#define CACHE_BDD_CONSTRAIN (10LL<<40) +#define CACHE_BDD_CLOSURE (11LL<<40) +#define CACHE_BDD_ISBDD (12LL<<40) +#define CACHE_BDD_SUPPORT (13LL<<40) +#define CACHE_BDD_PATHCOUNT (14LL<<40) + +// MDD operations +#define CACHE_MDD_RELPROD (20LL<<40) +#define CACHE_MDD_MINUS (21LL<<40) +#define CACHE_MDD_UNION (22LL<<40) +#define CACHE_MDD_INTERSECT (23LL<<40) +#define CACHE_MDD_PROJECT (24LL<<40) +#define CACHE_MDD_JOIN (25LL<<40) +#define CACHE_MDD_MATCH (26LL<<40) +#define CACHE_MDD_RELPREV (27LL<<40) +#define CACHE_MDD_SATCOUNT (28LL<<40) +#define CACHE_MDD_SATCOUNTL1 (29LL<<40) +#define CACHE_MDD_SATCOUNTL2 (30LL<<40) + +// MTBDD operations +#define CACHE_MTBDD_APPLY (40LL<<40) +#define CACHE_MTBDD_UAPPLY (41LL<<40) +#define CACHE_MTBDD_ABSTRACT (42LL<<40) +#define CACHE_MTBDD_ITE (43LL<<40) +#define CACHE_MTBDD_AND_EXISTS (44LL<<40) +#define CACHE_MTBDD_SUPPORT (45LL<<40) +#define CACHE_MTBDD_COMPOSE (46LL<<40) +#define CACHE_MTBDD_EQUAL_NORM (47LL<<40) +#define CACHE_MTBDD_EQUAL_NORM_REL (48LL<<40) +#define CACHE_MTBDD_MINIMUM (49LL<<40) +#define CACHE_MTBDD_MAXIMUM (50LL<<40) +#define CACHE_MTBDD_LEQ (51LL<<40) +#define CACHE_MTBDD_LESS (52LL<<40) +#define CACHE_MTBDD_GEQ (53LL<<40) +#define CACHE_MTBDD_GREATER (54LL<<40) +#define CACHE_MTBDD_NONZERO_COUNT (55LL<<40) + +/** + * Registration of quit functions + */ +typedef void (*quit_cb)(); +void sylvan_register_quit(quit_cb cb); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif diff --git a/src/sylvan_config.h b/src/sylvan_config.h new file mode 100644 index 000000000..cb234227d --- /dev/null +++ b/src/sylvan_config.h @@ -0,0 +1,30 @@ +/* Operation cache: use bitmasks for module (size must be power of 2!) */ +#ifndef CACHE_MASK +#define CACHE_MASK 1 +#endif + +/* Nodes table: use bitmasks for module (size must be power of 2!) */ +#ifndef LLMSSET_MASK +#define LLMSSET_MASK 1 +#endif + +/** + * Use Fibonacci sequence as resizing strategy. + * This MAY result in more conservative memory consumption, but is not + * great for performance. + * By default, powers of 2 should be used. + * If you set this, then set CACHE_MASK and LLMSSET_MASK to 0. + */ +#ifndef SYLVAN_SIZE_FIBONACCI +#define SYLVAN_SIZE_FIBONACCI 0 +#endif + +/* Enable/disable counters and timers */ +#ifndef SYLVAN_STATS +#define SYLVAN_STATS 0 +#endif + +/* Aggressive or conservative resizing strategy */ +#ifndef SYLVAN_AGGRESSIVE_RESIZE +#define SYLVAN_AGGRESSIVE_RESIZE 1 +#endif diff --git a/src/sylvan_gmp.c b/src/sylvan_gmp.c new file mode 100644 index 000000000..0437b1be8 --- /dev/null +++ b/src/sylvan_gmp.c @@ -0,0 +1,595 @@ +/* + * Copyright 2011-2015 Formal Methods and Tools, University of Twente + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <sylvan_config.h> + +#include <assert.h> +#include <inttypes.h> +#include <math.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <sylvan.h> +#include <sylvan_common.h> +#include <sylvan_mtbdd_int.h> +#include <sylvan_gmp.h> +#include <gmp.h> + + +/** + * helper function for hash + */ +#ifndef rotl64 +static inline uint64_t +rotl64(uint64_t x, int8_t r) +{ + return ((x<<r) | (x>>(64-r))); +} +#endif + +static uint64_t +gmp_hash(const uint64_t v, const uint64_t seed) +{ + /* Hash the mpq in pointer v + * A simpler way would be to hash the result of mpq_get_d. + * We just hash on the contents of the memory */ + + mpq_ptr x = (mpq_ptr)(size_t)v; + + const uint64_t prime = 1099511628211; + uint64_t hash = seed; + mp_limb_t *limbs; + + // hash "numerator" limbs + limbs = x[0]._mp_num._mp_d; + for (int i=0; i<x[0]._mp_num._mp_size; i++) { + hash = hash ^ limbs[i]; + hash = rotl64(hash, 47); + hash = hash * prime; + } + + // hash "denominator" limbs + limbs = x[0]._mp_den._mp_d; + for (int i=0; i<x[0]._mp_den._mp_size; i++) { + hash = hash ^ limbs[i]; + hash = rotl64(hash, 31); + hash = hash * prime; + } + + return hash ^ (hash >> 32); +} + +static int +gmp_equals(const uint64_t left, const uint64_t right) +{ + /* This function is called by the unique table when comparing a new + leaf with an existing leaf */ + mpq_ptr x = (mpq_ptr)(size_t)left; + mpq_ptr y = (mpq_ptr)(size_t)right; + + /* Just compare x and y */ + return mpq_equal(x, y) ? 1 : 0; +} + +static void +gmp_create(uint64_t *val) +{ + /* This function is called by the unique table when a leaf does not yet exist. + We make a copy, which will be stored in the hash table. */ + mpq_ptr x = (mpq_ptr)malloc(sizeof(__mpq_struct)); + mpq_init(x); + mpq_set(x, *(mpq_ptr*)val); + *(mpq_ptr*)val = x; +} + +static void +gmp_destroy(uint64_t val) +{ + /* This function is called by the unique table + when a leaf is removed during garbage collection. */ + mpq_clear((mpq_ptr)val); + free((void*)val); +} + +static uint32_t gmp_type; +static uint64_t CACHE_GMP_AND_EXISTS; + +/** + * Initialize gmp custom leaves + */ +void +gmp_init() +{ + /* Register custom leaf 3 */ + gmp_type = mtbdd_register_custom_leaf(gmp_hash, gmp_equals, gmp_create, gmp_destroy); + CACHE_GMP_AND_EXISTS = cache_next_opid(); +} + +/** + * Create GMP mpq leaf + */ +MTBDD +mtbdd_gmp(mpq_t val) +{ + mpq_canonicalize(val); + return mtbdd_makeleaf(gmp_type, (size_t)val); +} + +/** + * Operation "plus" for two mpq MTBDDs + * Interpret partial function as "0" + */ +TASK_IMPL_2(MTBDD, gmp_op_plus, MTBDD*, pa, MTBDD*, pb) +{ + MTBDD a = *pa, b = *pb; + + /* Check for partial functions */ + if (a == mtbdd_false) return b; + if (b == mtbdd_false) return a; + + /* If both leaves, compute plus */ + if (mtbdd_isleaf(a) && mtbdd_isleaf(b)) { + mpq_ptr ma = (mpq_ptr)mtbdd_getvalue(a); + mpq_ptr mb = (mpq_ptr)mtbdd_getvalue(b); + + mpq_t mres; + mpq_init(mres); + mpq_add(mres, ma, mb); + MTBDD res = mtbdd_gmp(mres); + mpq_clear(mres); + return res; + } + + /* Commutative, so swap a,b for better cache performance */ + if (a < b) { + *pa = b; + *pb = a; + } + + return mtbdd_invalid; +} + +/** + * Operation "minus" for two mpq MTBDDs + * Interpret partial function as "0" + */ +TASK_IMPL_2(MTBDD, gmp_op_minus, MTBDD*, pa, MTBDD*, pb) +{ + MTBDD a = *pa, b = *pb; + + /* Check for partial functions */ + if (a == mtbdd_false) return gmp_neg(b); + if (b == mtbdd_false) return a; + + /* If both leaves, compute plus */ + if (mtbdd_isleaf(a) && mtbdd_isleaf(b)) { + mpq_ptr ma = (mpq_ptr)mtbdd_getvalue(a); + mpq_ptr mb = (mpq_ptr)mtbdd_getvalue(b); + + mpq_t mres; + mpq_init(mres); + mpq_sub(mres, ma, mb); + MTBDD res = mtbdd_gmp(mres); + mpq_clear(mres); + return res; + } + + return mtbdd_invalid; +} + +/** + * Operation "times" for two mpq MTBDDs. + * One of the parameters can be a BDD, then it is interpreted as a filter. + * For partial functions, domain is intersection + */ +TASK_IMPL_2(MTBDD, gmp_op_times, MTBDD*, pa, MTBDD*, pb) +{ + MTBDD a = *pa, b = *pb; + + /* Check for partial functions and for Boolean (filter) */ + if (a == mtbdd_false || b == mtbdd_false) return mtbdd_false; + + /* If one of Boolean, interpret as filter */ + if (a == mtbdd_true) return b; + if (b == mtbdd_true) return a; + + /* Handle multiplication of leaves */ + if (mtbdd_isleaf(a) && mtbdd_isleaf(b)) { + mpq_ptr ma = (mpq_ptr)mtbdd_getvalue(a); + mpq_ptr mb = (mpq_ptr)mtbdd_getvalue(b); + + // compute result + mpq_t mres; + mpq_init(mres); + mpq_mul(mres, ma, mb); + MTBDD res = mtbdd_gmp(mres); + mpq_clear(mres); + return res; + } + + /* Commutative, so make "a" the lowest for better cache performance */ + if (a < b) { + *pa = b; + *pb = a; + } + + return mtbdd_invalid; +} + +/** + * Operation "divide" for two mpq MTBDDs. + * For partial functions, domain is intersection + */ +TASK_IMPL_2(MTBDD, gmp_op_divide, MTBDD*, pa, MTBDD*, pb) +{ + MTBDD a = *pa, b = *pb; + + /* Check for partial functions */ + if (a == mtbdd_false || b == mtbdd_false) return mtbdd_false; + + /* Handle division of leaves */ + if (mtbdd_isleaf(a) && mtbdd_isleaf(b)) { + mpq_ptr ma = (mpq_ptr)mtbdd_getvalue(a); + mpq_ptr mb = (mpq_ptr)mtbdd_getvalue(b); + + // compute result + mpq_t mres; + mpq_init(mres); + mpq_div(mres, ma, mb); + MTBDD res = mtbdd_gmp(mres); + mpq_clear(mres); + return res; + } + + return mtbdd_invalid; +} + +/** + * Operation "min" for two mpq MTBDDs. + */ +TASK_IMPL_2(MTBDD, gmp_op_min, MTBDD*, pa, MTBDD*, pb) +{ + MTBDD a = *pa, b = *pb; + + /* Handle partial functions */ + if (a == mtbdd_false) return b; + if (b == mtbdd_false) return a; + + /* Handle trivial case */ + if (a == b) return a; + + /* Compute result for leaves */ + if (mtbdd_isleaf(a) && mtbdd_isleaf(b)) { + mpq_ptr ma = (mpq_ptr)mtbdd_getvalue(a); + mpq_ptr mb = (mpq_ptr)mtbdd_getvalue(b); + int cmp = mpq_cmp(ma, mb); + return cmp < 0 ? a : b; + } + + /* For cache performance */ + if (a < b) { + *pa = b; + *pb = a; + } + + return mtbdd_invalid; +} + +/** + * Operation "max" for two mpq MTBDDs. + */ +TASK_IMPL_2(MTBDD, gmp_op_max, MTBDD*, pa, MTBDD*, pb) +{ + MTBDD a = *pa, b = *pb; + + /* Handle partial functions */ + if (a == mtbdd_false) return b; + if (b == mtbdd_false) return a; + + /* Handle trivial case */ + if (a == b) return a; + + /* Compute result for leaves */ + if (mtbdd_isleaf(a) && mtbdd_isleaf(b)) { + mpq_ptr ma = (mpq_ptr)mtbdd_getvalue(a); + mpq_ptr mb = (mpq_ptr)mtbdd_getvalue(b); + int cmp = mpq_cmp(ma, mb); + return cmp > 0 ? a : b; + } + + /* For cache performance */ + if (a < b) { + *pa = b; + *pb = a; + } + + return mtbdd_invalid; +} + +/** + * Operation "neg" for one mpq MTBDD + */ +TASK_IMPL_2(MTBDD, gmp_op_neg, MTBDD, dd, size_t, p) +{ + /* Handle partial functions */ + if (dd == mtbdd_false) return mtbdd_false; + + /* Compute result for leaf */ + if (mtbdd_isleaf(dd)) { + mpq_ptr m = (mpq_ptr)mtbdd_getvalue(dd); + + mpq_t mres; + mpq_init(mres); + mpq_neg(mres, m); + MTBDD res = mtbdd_gmp(mres); + mpq_clear(mres); + return res; + } + + return mtbdd_invalid; + (void)p; +} + +/** + * Operation "abs" for one mpq MTBDD + */ +TASK_IMPL_2(MTBDD, gmp_op_abs, MTBDD, dd, size_t, p) +{ + /* Handle partial functions */ + if (dd == mtbdd_false) return mtbdd_false; + + /* Compute result for leaf */ + if (mtbdd_isleaf(dd)) { + mpq_ptr m = (mpq_ptr)mtbdd_getvalue(dd); + + mpq_t mres; + mpq_init(mres); + mpq_abs(mres, m); + MTBDD res = mtbdd_gmp(mres); + mpq_clear(mres); + return res; + } + + return mtbdd_invalid; + (void)p; +} + +/** + * The abstraction operators are called in either of two ways: + * - with k=0, then just calculate "a op b" + * - with k<>0, then just calculate "a := a op a", k times + */ + +TASK_IMPL_3(MTBDD, gmp_abstract_op_plus, MTBDD, a, MTBDD, b, int, k) +{ + if (k==0) { + return mtbdd_apply(a, b, TASK(gmp_op_plus)); + } else { + MTBDD res = a; + for (int i=0; i<k; i++) { + mtbdd_refs_push(res); + res = mtbdd_apply(res, res, TASK(gmp_op_plus)); + mtbdd_refs_pop(1); + } + return res; + } +} + +TASK_IMPL_3(MTBDD, gmp_abstract_op_times, MTBDD, a, MTBDD, b, int, k) +{ + if (k==0) { + return mtbdd_apply(a, b, TASK(gmp_op_times)); + } else { + MTBDD res = a; + for (int i=0; i<k; i++) { + mtbdd_refs_push(res); + res = mtbdd_apply(res, res, TASK(gmp_op_times)); + mtbdd_refs_pop(1); + } + return res; + } +} + +TASK_IMPL_3(MTBDD, gmp_abstract_op_min, MTBDD, a, MTBDD, b, int, k) +{ + if (k == 0) { + return mtbdd_apply(a, b, TASK(gmp_op_min)); + } else { + // nothing to do: min(a, a) = a + return a; + } +} + +TASK_IMPL_3(MTBDD, gmp_abstract_op_max, MTBDD, a, MTBDD, b, int, k) +{ + if (k == 0) { + return mtbdd_apply(a, b, TASK(gmp_op_max)); + } else { + // nothing to do: max(a, a) = a + return a; + } +} + +/** + * Convert to Boolean MTBDD, terminals >= value (double) to True, or False otherwise. + */ +TASK_2(MTBDD, gmp_op_threshold_d, MTBDD, a, size_t, svalue) +{ + /* Handle partial function */ + if (a == mtbdd_false) return mtbdd_false; + + /* Compute result */ + if (mtbdd_isleaf(a)) { + double value = *(double*)&svalue; + mpq_ptr ma = (mpq_ptr)mtbdd_getvalue(a); + return mpq_get_d(ma) >= value ? mtbdd_true : mtbdd_false; + } + + return mtbdd_invalid; +} + +/** + * Convert to Boolean MTBDD, terminals > value (double) to True, or False otherwise. + */ +TASK_2(MTBDD, gmp_op_strict_threshold_d, MTBDD, a, size_t, svalue) +{ + /* Handle partial function */ + if (a == mtbdd_false) return mtbdd_false; + + /* Compute result */ + if (mtbdd_isleaf(a)) { + double value = *(double*)&svalue; + mpq_ptr ma = (mpq_ptr)mtbdd_getvalue(a); + return mpq_get_d(ma) > value ? mtbdd_true : mtbdd_false; + } + + return mtbdd_invalid; +} + +TASK_IMPL_2(MTBDD, gmp_threshold_d, MTBDD, dd, double, d) +{ + return mtbdd_uapply(dd, TASK(gmp_op_threshold_d), *(size_t*)&d); +} + +TASK_IMPL_2(MTBDD, gmp_strict_threshold_d, MTBDD, dd, double, d) +{ + return mtbdd_uapply(dd, TASK(gmp_op_strict_threshold_d), *(size_t*)&d); +} + +/** + * Operation "threshold" for mpq MTBDDs. + * The second parameter must be a mpq leaf. + */ +TASK_IMPL_2(MTBDD, gmp_op_threshold, MTBDD*, pa, MTBDD*, pb) +{ + MTBDD a = *pa, b = *pb; + + /* Check for partial functions */ + if (a == mtbdd_false) return mtbdd_false; + + /* Handle comparison of leaves */ + if (mtbdd_isleaf(a)) { + mpq_ptr ma = (mpq_ptr)mtbdd_getvalue(a); + mpq_ptr mb = (mpq_ptr)mtbdd_getvalue(b); + int cmp = mpq_cmp(ma, mb); + return cmp >= 0 ? mtbdd_true : mtbdd_false; + } + + return mtbdd_invalid; +} + +/** + * Operation "strict threshold" for mpq MTBDDs. + * The second parameter must be a mpq leaf. + */ +TASK_IMPL_2(MTBDD, gmp_op_strict_threshold, MTBDD*, pa, MTBDD*, pb) +{ + MTBDD a = *pa, b = *pb; + + /* Check for partial functions */ + if (a == mtbdd_false) return mtbdd_false; + + /* Handle comparison of leaves */ + if (mtbdd_isleaf(a)) { + mpq_ptr ma = (mpq_ptr)mtbdd_getvalue(a); + mpq_ptr mb = (mpq_ptr)mtbdd_getvalue(b); + int cmp = mpq_cmp(ma, mb); + return cmp > 0 ? mtbdd_true : mtbdd_false; + } + + return mtbdd_invalid; +} + +/** + * Multiply <a> and <b>, and abstract variables <vars> using summation. + * This is similar to the "and_exists" operation in BDDs. + */ +TASK_IMPL_3(MTBDD, gmp_and_exists, MTBDD, a, MTBDD, b, MTBDD, v) +{ + /* Check terminal cases */ + + /* If v == true, then <vars> is an empty set */ + if (v == mtbdd_true) return mtbdd_apply(a, b, TASK(gmp_op_times)); + + /* Try the times operator on a and b */ + MTBDD result = CALL(gmp_op_times, &a, &b); + if (result != mtbdd_invalid) { + /* Times operator successful, store reference (for garbage collection) */ + mtbdd_refs_push(result); + /* ... and perform abstraction */ + result = mtbdd_abstract(result, v, TASK(gmp_abstract_op_plus)); + mtbdd_refs_pop(1); + /* Note that the operation cache is used in mtbdd_abstract */ + return result; + } + + /* Maybe perform garbage collection */ + sylvan_gc_test(); + + /* Check cache. Note that we do this now, since the times operator might swap a and b (commutative) */ + if (cache_get3(CACHE_GMP_AND_EXISTS, a, b, v, &result)) return result; + + /* Now, v is not a constant, and either a or b is not a constant */ + + /* Get top variable */ + int la = mtbdd_isleaf(a); + int lb = mtbdd_isleaf(b); + mtbddnode_t na = la ? 0 : GETNODE(a); + mtbddnode_t nb = lb ? 0 : GETNODE(b); + uint32_t va = la ? 0xffffffff : mtbddnode_getvariable(na); + uint32_t vb = lb ? 0xffffffff : mtbddnode_getvariable(nb); + uint32_t var = va < vb ? va : vb; + + mtbddnode_t nv = GETNODE(v); + uint32_t vv = mtbddnode_getvariable(nv); + + if (vv < var) { + /* Recursive, then abstract result */ + result = CALL(gmp_and_exists, a, b, node_gethigh(v, nv)); + mtbdd_refs_push(result); + result = mtbdd_apply(result, result, TASK(gmp_op_plus)); + mtbdd_refs_pop(1); + } else { + /* Get cofactors */ + MTBDD alow, ahigh, blow, bhigh; + alow = (!la && va == var) ? node_getlow(a, na) : a; + ahigh = (!la && va == var) ? node_gethigh(a, na) : a; + blow = (!lb && vb == var) ? node_getlow(b, nb) : b; + bhigh = (!lb && vb == var) ? node_gethigh(b, nb) : b; + + if (vv == var) { + /* Recursive, then abstract result */ + mtbdd_refs_spawn(SPAWN(gmp_and_exists, ahigh, bhigh, node_gethigh(v, nv))); + MTBDD low = mtbdd_refs_push(CALL(gmp_and_exists, alow, blow, node_gethigh(v, nv))); + MTBDD high = mtbdd_refs_push(mtbdd_refs_sync(SYNC(gmp_and_exists))); + result = CALL(mtbdd_apply, low, high, TASK(gmp_op_plus)); + mtbdd_refs_pop(2); + } else /* vv > v */ { + /* Recursive, then create node */ + mtbdd_refs_spawn(SPAWN(gmp_and_exists, ahigh, bhigh, v)); + MTBDD low = mtbdd_refs_push(CALL(gmp_and_exists, alow, blow, v)); + MTBDD high = mtbdd_refs_sync(SYNC(gmp_and_exists)); + mtbdd_refs_pop(1); + result = mtbdd_makenode(var, low, high); + } + } + + /* Store in cache */ + cache_put3(CACHE_GMP_AND_EXISTS, a, b, v, result); + return result; +} diff --git a/src/sylvan_gmp.h b/src/sylvan_gmp.h new file mode 100644 index 000000000..fbf6bc2ad --- /dev/null +++ b/src/sylvan_gmp.h @@ -0,0 +1,182 @@ +/* + * Copyright 2011-2015 Formal Methods and Tools, University of Twente + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * This is an implementation of GMP mpq custom leaves of MTBDDs + */ + +#ifndef SYLVAN_GMP_H +#define SYLVAN_GMP_H + +#include <sylvan.h> +#include <gmp.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** + * Initialize GMP custom leaves + */ +void gmp_init(); + +/** + * Create MPQ leaf + */ +MTBDD mtbdd_gmp(mpq_t val); + +/** + * Operation "plus" for two mpq MTBDDs + */ +TASK_DECL_2(MTBDD, gmp_op_plus, MTBDD*, MTBDD*); +TASK_DECL_3(MTBDD, gmp_abstract_op_plus, MTBDD, MTBDD, int); + +/** + * Operation "minus" for two mpq MTBDDs + */ +TASK_DECL_2(MTBDD, gmp_op_minus, MTBDD*, MTBDD*); + +/** + * Operation "times" for two mpq MTBDDs + */ +TASK_DECL_2(MTBDD, gmp_op_times, MTBDD*, MTBDD*); +TASK_DECL_3(MTBDD, gmp_abstract_op_times, MTBDD, MTBDD, int); + +/** + * Operation "divide" for two mpq MTBDDs + */ +TASK_DECL_2(MTBDD, gmp_op_divide, MTBDD*, MTBDD*); + +/** + * Operation "min" for two mpq MTBDDs + */ +TASK_DECL_2(MTBDD, gmp_op_min, MTBDD*, MTBDD*); +TASK_DECL_3(MTBDD, gmp_abstract_op_min, MTBDD, MTBDD, int); + +/** + * Operation "max" for two mpq MTBDDs + */ +TASK_DECL_2(MTBDD, gmp_op_max, MTBDD*, MTBDD*); +TASK_DECL_3(MTBDD, gmp_abstract_op_max, MTBDD, MTBDD, int); + +/** + * Operation "negate" for one mpq MTBDD + */ +TASK_DECL_2(MTBDD, gmp_op_neg, MTBDD, size_t); + +/** + * Operation "abs" for one mpq MTBDD + */ +TASK_DECL_2(MTBDD, gmp_op_abs, MTBDD, size_t); + +/** + * Compute a + b + */ +#define gmp_plus(a, b) mtbdd_apply(a, b, TASK(gmp_op_plus)) + +/** + * Compute a + b + */ +#define gmp_minus(a, b) mtbdd_apply(a, b, TASK(gmp_op_minus)) + +/** + * Compute a * b + */ +#define gmp_times(a, b) mtbdd_apply(a, b, TASK(gmp_op_times)) + +/** + * Compute a * b + */ +#define gmp_divide(a, b) mtbdd_apply(a, b, TASK(gmp_op_divide)) + +/** + * Compute min(a, b) + */ +#define gmp_min(a, b) mtbdd_apply(a, b, TASK(gmp_op_min)) + +/** + * Compute max(a, b) + */ +#define gmp_max(a, b) mtbdd_apply(a, b, TASK(gmp_op_max)) + +/** + * Compute -a + */ +#define gmp_neg(a) mtbdd_uapply(a, TASK(gmp_op_neg), 0); + +/** + * Compute abs(a) + */ +#define gmp_abs(a) mtbdd_uapply(a, TASK(gmp_op_abs), 0); + +/** + * Abstract the variables in <v> from <a> by taking the sum of all values + */ +#define gmp_abstract_plus(dd, v) mtbdd_abstract(dd, v, TASK(gmp_abstract_op_plus)) + +/** + * Abstract the variables in <v> from <a> by taking the product of all values + */ +#define gmp_abstract_times(dd, v) mtbdd_abstract(dd, v, TASK(gmp_abstract_op_times)) + +/** + * Abstract the variables in <v> from <a> by taking the minimum of all values + */ +#define gmp_abstract_min(dd, v) mtbdd_abstract(dd, v, TASK(gmp_abstract_op_min)) + +/** + * Abstract the variables in <v> from <a> by taking the maximum of all values + */ +#define gmp_abstract_max(dd, v) mtbdd_abstract(dd, v, TASK(gmp_abstract_op_max)) + +/** + * Multiply <a> and <b>, and abstract variables <vars> using summation. + * This is similar to the "and_exists" operation in BDDs. + */ +TASK_DECL_3(MTBDD, gmp_and_exists, MTBDD, MTBDD, MTBDD); +#define gmp_and_exists(a, b, vars) CALL(gmp_and_exists, a, b, vars) + +/** + * Convert to a Boolean MTBDD, translate terminals >= value to 1 and to 0 otherwise; + * Parameter <dd> is the MTBDD to convert; parameter <value> is an GMP mpq leaf + */ +TASK_DECL_2(MTBDD, gmp_op_threshold, MTBDD*, MTBDD*); +#define gmp_threshold(dd, value) mtbdd_apply(dd, value, TASK(gmp_op_threshold)); + +/** + * Convert to a Boolean MTBDD, translate terminals > value to 1 and to 0 otherwise; + * Parameter <dd> is the MTBDD to convert; parameter <value> is an GMP mpq leaf + */ +TASK_DECL_2(MTBDD, gmp_op_strict_threshold, MTBDD*, MTBDD*); +#define gmp_strict_threshold(dd, value) mtbdd_apply(dd, value, TASK(gmp_op_strict_threshold)); + +/** + * Convert to a Boolean MTBDD, translate terminals >= value to 1 and to 0 otherwise; + */ +TASK_DECL_2(MTBDD, gmp_threshold_d, MTBDD, double); +#define gmp_threshold_d(dd, value) CALL(gmp_threshold_d, dd, value) + +/** + * Convert to a Boolean MTBDD, translate terminals > value to 1 and to 0 otherwise; + */ +TASK_DECL_2(MTBDD, gmp_strict_threshold_d, MTBDD, double); +#define gmp_strict_threshold_d(dd, value) CALL(gmp_strict_threshold_d, dd, value) + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif diff --git a/src/sylvan_ldd.c b/src/sylvan_ldd.c new file mode 100644 index 000000000..814b7e61c --- /dev/null +++ b/src/sylvan_ldd.c @@ -0,0 +1,2560 @@ +/* + * Copyright 2011-2014 Formal Methods and Tools, University of Twente + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <sylvan_config.h> + +#include <assert.h> +#include <inttypes.h> +#include <math.h> +#include <pthread.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <avl.h> +#include <refs.h> +#include <sha2.h> +#include <sylvan.h> +#include <sylvan_common.h> + +/** + * MDD node structure + */ +typedef struct __attribute__((packed)) mddnode { + uint64_t a, b; +} * mddnode_t; // 16 bytes + +// RmRR RRRR RRRR VVVV | VVVV DcDD DDDD DDDD (little endian - in memory) +// VVVV RRRR RRRR RRRm | DDDD DDDD DDDc VVVV (big endian) + +// Ensure our mddnode is 16 bytes +typedef char __lddmc_check_mddnode_t_is_16_bytes[(sizeof(struct mddnode)==16) ? 1 : -1]; + +static inline uint32_t __attribute__((unused)) +mddnode_getvalue(mddnode_t n) +{ + return *(uint32_t*)((uint8_t*)n+6); +} + +static inline uint8_t __attribute__((unused)) +mddnode_getmark(mddnode_t n) +{ + return n->a & 1; +} + +static inline uint8_t __attribute__((unused)) +mddnode_getcopy(mddnode_t n) +{ + return n->b & 0x10000 ? 1 : 0; +} + +static inline uint64_t __attribute__((unused)) +mddnode_getright(mddnode_t n) +{ + return (n->a & 0x0000ffffffffffff) >> 1; +} + +static inline uint64_t __attribute__((unused)) +mddnode_getdown(mddnode_t n) +{ + return n->b >> 17; +} + +static inline void __attribute__((unused)) +mddnode_setvalue(mddnode_t n, uint32_t value) +{ + *(uint32_t*)((uint8_t*)n+6) = value; +} + +static inline void __attribute__((unused)) +mddnode_setmark(mddnode_t n, uint8_t mark) +{ + n->a = (n->a & 0xfffffffffffffffe) | (mark ? 1 : 0); +} + +static inline void __attribute__((unused)) +mddnode_setright(mddnode_t n, uint64_t right) +{ + n->a = (n->a & 0xffff000000000001) | (right << 1); +} + +static inline void __attribute__((unused)) +mddnode_setdown(mddnode_t n, uint64_t down) +{ + n->b = (n->b & 0x000000000001ffff) | (down << 16); +} + +static inline void __attribute__((unused)) +mddnode_make(mddnode_t n, uint32_t value, uint64_t right, uint64_t down) +{ + n->a = right << 1; + n->b = down << 17; + *(uint32_t*)((uint8_t*)n+6) = value; +} + +static inline void __attribute__((unused)) +mddnode_makecopy(mddnode_t n, uint64_t right, uint64_t down) +{ + n->a = right << 1; + n->b = ((down << 1) | 1) << 16; +} + +#define GETNODE(mdd) ((mddnode_t)llmsset_index_to_ptr(nodes, mdd)) + +/** + * Implementation of garbage collection + */ + +/* Recursively mark MDD nodes as 'in use' */ +VOID_TASK_IMPL_1(lddmc_gc_mark_rec, MDD, mdd) +{ + if (mdd <= lddmc_true) return; + + if (llmsset_mark(nodes, mdd)) { + mddnode_t n = GETNODE(mdd); + SPAWN(lddmc_gc_mark_rec, mddnode_getright(n)); + CALL(lddmc_gc_mark_rec, mddnode_getdown(n)); + SYNC(lddmc_gc_mark_rec); + } +} + +/** + * External references + */ + +refs_table_t mdd_refs; + +MDD +lddmc_ref(MDD a) +{ + if (a == lddmc_true || a == lddmc_false) return a; + refs_up(&mdd_refs, a); + return a; +} + +void +lddmc_deref(MDD a) +{ + if (a == lddmc_true || a == lddmc_false) return; + refs_down(&mdd_refs, a); +} + +size_t +lddmc_count_refs() +{ + return refs_count(&mdd_refs); +} + +/* Called during garbage collection */ +VOID_TASK_0(lddmc_gc_mark_external_refs) +{ + // iterate through refs hash table, mark all found + size_t count=0; + uint64_t *it = refs_iter(&mdd_refs, 0, mdd_refs.refs_size); + while (it != NULL) { + SPAWN(lddmc_gc_mark_rec, refs_next(&mdd_refs, &it, mdd_refs.refs_size)); + count++; + } + while (count--) { + SYNC(lddmc_gc_mark_rec); + } +} + +/* Infrastructure for internal markings */ +DECLARE_THREAD_LOCAL(lddmc_refs_key, lddmc_refs_internal_t); + +VOID_TASK_0(lddmc_refs_mark_task) +{ + LOCALIZE_THREAD_LOCAL(lddmc_refs_key, lddmc_refs_internal_t); + size_t i, j=0; + for (i=0; i<lddmc_refs_key->r_count; i++) { + if (j >= 40) { + while (j--) SYNC(lddmc_gc_mark_rec); + j=0; + } + SPAWN(lddmc_gc_mark_rec, lddmc_refs_key->results[i]); + j++; + } + for (i=0; i<lddmc_refs_key->s_count; i++) { + Task *t = lddmc_refs_key->spawns[i]; + if (!TASK_IS_STOLEN(t)) break; + if (TASK_IS_COMPLETED(t)) { + if (j >= 40) { + while (j--) SYNC(lddmc_gc_mark_rec); + j=0; + } + SPAWN(lddmc_gc_mark_rec, *(BDD*)TASK_RESULT(t)); + j++; + } + } + while (j--) SYNC(lddmc_gc_mark_rec); +} + +VOID_TASK_0(lddmc_refs_mark) +{ + TOGETHER(lddmc_refs_mark_task); +} + +VOID_TASK_0(lddmc_refs_init_task) +{ + lddmc_refs_internal_t s = (lddmc_refs_internal_t)malloc(sizeof(struct lddmc_refs_internal)); + s->r_size = 128; + s->r_count = 0; + s->s_size = 128; + s->s_count = 0; + s->results = (BDD*)malloc(sizeof(BDD) * 128); + s->spawns = (Task**)malloc(sizeof(Task*) * 128); + SET_THREAD_LOCAL(lddmc_refs_key, s); +} + +VOID_TASK_0(lddmc_refs_init) +{ + INIT_THREAD_LOCAL(lddmc_refs_key); + TOGETHER(lddmc_refs_init_task); + sylvan_gc_add_mark(10, TASK(lddmc_refs_mark)); +} + +/** + * Initialize and quit functions + */ + +static void +lddmc_quit() +{ + refs_free(&mdd_refs); +} + +void +sylvan_init_ldd() +{ + sylvan_register_quit(lddmc_quit); + sylvan_gc_add_mark(10, TASK(lddmc_gc_mark_external_refs)); + + // Sanity check + if (sizeof(struct mddnode) != 16) { + fprintf(stderr, "Invalid size of mdd nodes: %ld\n", sizeof(struct mddnode)); + exit(1); + } + + refs_create(&mdd_refs, 1024); + + LACE_ME; + CALL(lddmc_refs_init); +} + +/** + * Primitives + */ + +MDD +lddmc_makenode(uint32_t value, MDD ifeq, MDD ifneq) +{ + if (ifeq == lddmc_false) return ifneq; + + // check if correct (should be false, or next in value) + assert(ifneq != lddmc_true); + if (ifneq != lddmc_false) assert(value < mddnode_getvalue(GETNODE(ifneq))); + + struct mddnode n; + mddnode_make(&n, value, ifneq, ifeq); + + int created; + uint64_t index = llmsset_lookup(nodes, n.a, n.b, &created); + if (index == 0) { + lddmc_refs_push(ifeq); + lddmc_refs_push(ifneq); + LACE_ME; + sylvan_gc(); + lddmc_refs_pop(1); + + index = llmsset_lookup(nodes, n.a, n.b, &created); + if (index == 0) { + fprintf(stderr, "MDD Unique table full, %zu of %zu buckets filled!\n", llmsset_count_marked(nodes), llmsset_get_size(nodes)); + exit(1); + } + } + + if (created) sylvan_stats_count(LDD_NODES_CREATED); + else sylvan_stats_count(LDD_NODES_REUSED); + + return (MDD)index; +} + +MDD +lddmc_make_copynode(MDD ifeq, MDD ifneq) +{ + struct mddnode n; + mddnode_makecopy(&n, ifneq, ifeq); + + int created; + uint64_t index = llmsset_lookup(nodes, n.a, n.b, &created); + if (index == 0) { + lddmc_refs_push(ifeq); + lddmc_refs_push(ifneq); + LACE_ME; + sylvan_gc(); + lddmc_refs_pop(1); + + index = llmsset_lookup(nodes, n.a, n.b, &created); + if (index == 0) { + fprintf(stderr, "MDD Unique table full, %zu of %zu buckets filled!\n", llmsset_count_marked(nodes), llmsset_get_size(nodes)); + exit(1); + } + } + + if (created) sylvan_stats_count(LDD_NODES_CREATED); + else sylvan_stats_count(LDD_NODES_REUSED); + + return (MDD)index; +} + +MDD +lddmc_extendnode(MDD mdd, uint32_t value, MDD ifeq) +{ + if (mdd <= lddmc_true) return lddmc_makenode(value, ifeq, mdd); + + mddnode_t n = GETNODE(mdd); + if (mddnode_getcopy(n)) return lddmc_make_copynode(mddnode_getdown(n), lddmc_extendnode(mddnode_getright(n), value, ifeq)); + uint32_t n_value = mddnode_getvalue(n); + if (n_value < value) return lddmc_makenode(n_value, mddnode_getdown(n), lddmc_extendnode(mddnode_getright(n), value, ifeq)); + if (n_value == value) return lddmc_makenode(value, ifeq, mddnode_getright(n)); + /* (n_value > value) */ return lddmc_makenode(value, ifeq, mdd); +} + +uint32_t +lddmc_getvalue(MDD mdd) +{ + return mddnode_getvalue(GETNODE(mdd)); +} + +MDD +lddmc_getdown(MDD mdd) +{ + return mddnode_getdown(GETNODE(mdd)); +} + +MDD +lddmc_getright(MDD mdd) +{ + return mddnode_getright(GETNODE(mdd)); +} + +MDD +lddmc_follow(MDD mdd, uint32_t value) +{ + for (;;) { + if (mdd <= lddmc_true) return mdd; + const mddnode_t n = GETNODE(mdd); + if (!mddnode_getcopy(n)) { + const uint32_t v = mddnode_getvalue(n); + if (v == value) return mddnode_getdown(n); + if (v > value) return lddmc_false; + } + mdd = mddnode_getright(n); + } +} + +int +lddmc_iscopy(MDD mdd) +{ + if (mdd <= lddmc_true) return 0; + + mddnode_t n = GETNODE(mdd); + return mddnode_getcopy(n) ? 1 : 0; +} + +MDD +lddmc_followcopy(MDD mdd) +{ + if (mdd <= lddmc_true) return lddmc_false; + + mddnode_t n = GETNODE(mdd); + if (mddnode_getcopy(n)) return mddnode_getdown(n); + else return lddmc_false; +} + +/** + * MDD operations + */ +static inline int +match_ldds(MDD *one, MDD *two) +{ + MDD m1 = *one, m2 = *two; + if (m1 == lddmc_false || m2 == lddmc_false) return 0; + mddnode_t n1 = GETNODE(m1), n2 = GETNODE(m2); + uint32_t v1 = mddnode_getvalue(n1), v2 = mddnode_getvalue(n2); + while (v1 != v2) { + if (v1 < v2) { + m1 = mddnode_getright(n1); + if (m1 == lddmc_false) return 0; + n1 = GETNODE(m1); + v1 = mddnode_getvalue(n1); + } else if (v1 > v2) { + m2 = mddnode_getright(n2); + if (m2 == lddmc_false) return 0; + n2 = GETNODE(m2); + v2 = mddnode_getvalue(n2); + } + } + *one = m1; + *two = m2; + return 1; +} + +TASK_IMPL_2(MDD, lddmc_union, MDD, a, MDD, b) +{ + /* Terminal cases */ + if (a == b) return a; + if (a == lddmc_false) return b; + if (b == lddmc_false) return a; + assert(a != lddmc_true && b != lddmc_true); // expecting same length + + /* Test gc */ + sylvan_gc_test(); + + sylvan_stats_count(LDD_UNION); + + /* Improve cache behavior */ + if (a < b) { MDD tmp=b; b=a; a=tmp; } + + /* Access cache */ + MDD result; + if (cache_get3(CACHE_MDD_UNION, a, b, 0, &result)) { + sylvan_stats_count(LDD_UNION_CACHED); + return result; + } + + /* Get nodes */ + mddnode_t na = GETNODE(a); + mddnode_t nb = GETNODE(b); + + const int na_copy = mddnode_getcopy(na) ? 1 : 0; + const int nb_copy = mddnode_getcopy(nb) ? 1 : 0; + const uint32_t na_value = mddnode_getvalue(na); + const uint32_t nb_value = mddnode_getvalue(nb); + + /* Perform recursive calculation */ + if (na_copy && nb_copy) { + lddmc_refs_spawn(SPAWN(lddmc_union, mddnode_getdown(na), mddnode_getdown(nb))); + MDD right = CALL(lddmc_union, mddnode_getright(na), mddnode_getright(nb)); + lddmc_refs_push(right); + MDD down = lddmc_refs_sync(SYNC(lddmc_union)); + lddmc_refs_pop(1); + result = lddmc_make_copynode(down, right); + } else if (na_copy) { + MDD right = CALL(lddmc_union, mddnode_getright(na), b); + result = lddmc_make_copynode(mddnode_getdown(na), right); + } else if (nb_copy) { + MDD right = CALL(lddmc_union, a, mddnode_getright(nb)); + result = lddmc_make_copynode(mddnode_getdown(nb), right); + } else if (na_value < nb_value) { + MDD right = CALL(lddmc_union, mddnode_getright(na), b); + result = lddmc_makenode(na_value, mddnode_getdown(na), right); + } else if (na_value == nb_value) { + lddmc_refs_spawn(SPAWN(lddmc_union, mddnode_getdown(na), mddnode_getdown(nb))); + MDD right = CALL(lddmc_union, mddnode_getright(na), mddnode_getright(nb)); + lddmc_refs_push(right); + MDD down = lddmc_refs_sync(SYNC(lddmc_union)); + lddmc_refs_pop(1); + result = lddmc_makenode(na_value, down, right); + } else /* na_value > nb_value */ { + MDD right = CALL(lddmc_union, a, mddnode_getright(nb)); + result = lddmc_makenode(nb_value, mddnode_getdown(nb), right); + } + + /* Write to cache */ + if (cache_put3(CACHE_MDD_UNION, a, b, 0, result)) sylvan_stats_count(LDD_UNION_CACHEDPUT); + + return result; +} + +TASK_IMPL_2(MDD, lddmc_minus, MDD, a, MDD, b) +{ + /* Terminal cases */ + if (a == b) return lddmc_false; + if (a == lddmc_false) return lddmc_false; + if (b == lddmc_false) return a; + assert(b != lddmc_true); + assert(a != lddmc_true); // Universe is unknown!! // Possibly depth issue? + + /* Test gc */ + sylvan_gc_test(); + + sylvan_stats_count(LDD_MINUS); + + /* Access cache */ + MDD result; + if (cache_get3(CACHE_MDD_MINUS, a, b, 0, &result)) { + sylvan_stats_count(LDD_MINUS_CACHED); + return result; + } + + /* Get nodes */ + mddnode_t na = GETNODE(a); + mddnode_t nb = GETNODE(b); + uint32_t na_value = mddnode_getvalue(na); + uint32_t nb_value = mddnode_getvalue(nb); + + /* Perform recursive calculation */ + if (na_value < nb_value) { + MDD right = CALL(lddmc_minus, mddnode_getright(na), b); + result = lddmc_makenode(na_value, mddnode_getdown(na), right); + } else if (na_value == nb_value) { + lddmc_refs_spawn(SPAWN(lddmc_minus, mddnode_getright(na), mddnode_getright(nb))); + MDD down = CALL(lddmc_minus, mddnode_getdown(na), mddnode_getdown(nb)); + lddmc_refs_push(down); + MDD right = lddmc_refs_sync(SYNC(lddmc_minus)); + lddmc_refs_pop(1); + result = lddmc_makenode(na_value, down, right); + } else /* na_value > nb_value */ { + result = CALL(lddmc_minus, a, mddnode_getright(nb)); + } + + /* Write to cache */ + if (cache_put3(CACHE_MDD_MINUS, a, b, 0, result)) sylvan_stats_count(LDD_MINUS_CACHEDPUT); + + return result; +} + +/* result: a plus b; res2: b minus a */ +TASK_IMPL_3(MDD, lddmc_zip, MDD, a, MDD, b, MDD*, res2) +{ + /* Terminal cases */ + if (a == b) { + *res2 = lddmc_false; + return a; + } + if (a == lddmc_false) { + *res2 = b; + return b; + } + if (b == lddmc_false) { + *res2 = lddmc_false; + return a; + } + + assert(a != lddmc_true && b != lddmc_true); // expecting same length + + /* Test gc */ + sylvan_gc_test(); + + /* Maybe not the ideal way */ + sylvan_stats_count(LDD_ZIP); + + /* Access cache */ + MDD result; + if (cache_get3(CACHE_MDD_UNION, a, b, 0, &result) && + cache_get3(CACHE_MDD_MINUS, b, a, 0, res2)) { + sylvan_stats_count(LDD_ZIP); + return result; + } + + /* Get nodes */ + mddnode_t na = GETNODE(a); + mddnode_t nb = GETNODE(b); + uint32_t na_value = mddnode_getvalue(na); + uint32_t nb_value = mddnode_getvalue(nb); + + /* Perform recursive calculation */ + if (na_value < nb_value) { + MDD right = CALL(lddmc_zip, mddnode_getright(na), b, res2); + result = lddmc_makenode(na_value, mddnode_getdown(na), right); + } else if (na_value == nb_value) { + MDD down2, right2; + lddmc_refs_spawn(SPAWN(lddmc_zip, mddnode_getdown(na), mddnode_getdown(nb), &down2)); + MDD right = CALL(lddmc_zip, mddnode_getright(na), mddnode_getright(nb), &right2); + lddmc_refs_push(right); + lddmc_refs_push(right2); + MDD down = lddmc_refs_sync(SYNC(lddmc_zip)); + lddmc_refs_pop(2); + result = lddmc_makenode(na_value, down, right); + *res2 = lddmc_makenode(na_value, down2, right2); + } else /* na_value > nb_value */ { + MDD right2; + MDD right = CALL(lddmc_zip, a, mddnode_getright(nb), &right2); + result = lddmc_makenode(nb_value, mddnode_getdown(nb), right); + *res2 = lddmc_makenode(nb_value, mddnode_getdown(nb), right2); + } + + /* Write to cache */ + int c1 = cache_put3(CACHE_MDD_UNION, a, b, 0, result); + int c2 = cache_put3(CACHE_MDD_MINUS, b, a, 0, *res2); + if (c1 && c2) sylvan_stats_count(LDD_ZIP_CACHEDPUT); + + return result; +} + +TASK_IMPL_2(MDD, lddmc_intersect, MDD, a, MDD, b) +{ + /* Terminal cases */ + if (a == b) return a; + if (a == lddmc_false || b == lddmc_false) return lddmc_false; + assert(a != lddmc_true && b != lddmc_true); + + /* Test gc */ + sylvan_gc_test(); + + sylvan_stats_count(LDD_INTERSECT); + + /* Get nodes */ + mddnode_t na = GETNODE(a); + mddnode_t nb = GETNODE(b); + uint32_t na_value = mddnode_getvalue(na); + uint32_t nb_value = mddnode_getvalue(nb); + + /* Skip nodes if possible */ + while (na_value != nb_value) { + if (na_value < nb_value) { + a = mddnode_getright(na); + if (a == lddmc_false) return lddmc_false; + na = GETNODE(a); + na_value = mddnode_getvalue(na); + } + if (nb_value < na_value) { + b = mddnode_getright(nb); + if (b == lddmc_false) return lddmc_false; + nb = GETNODE(b); + nb_value = mddnode_getvalue(nb); + } + } + + /* Access cache */ + MDD result; + if (cache_get3(CACHE_MDD_INTERSECT, a, b, 0, &result)) { + sylvan_stats_count(LDD_INTERSECT_CACHED); + return result; + } + + /* Perform recursive calculation */ + lddmc_refs_spawn(SPAWN(lddmc_intersect, mddnode_getright(na), mddnode_getright(nb))); + MDD down = CALL(lddmc_intersect, mddnode_getdown(na), mddnode_getdown(nb)); + lddmc_refs_push(down); + MDD right = lddmc_refs_sync(SYNC(lddmc_intersect)); + lddmc_refs_pop(1); + result = lddmc_makenode(na_value, down, right); + + /* Write to cache */ + if (cache_put3(CACHE_MDD_INTERSECT, a, b, 0, result)) sylvan_stats_count(LDD_INTERSECT_CACHEDPUT); + + return result; +} + +// proj: -1 (rest 0), 0 (no match), 1 (match) +TASK_IMPL_3(MDD, lddmc_match, MDD, a, MDD, b, MDD, proj) +{ + if (a == b) return a; + if (a == lddmc_false || b == lddmc_false) return lddmc_false; + + mddnode_t p_node = GETNODE(proj); + uint32_t p_val = mddnode_getvalue(p_node); + if (p_val == (uint32_t)-1) return a; + + assert(a != lddmc_true); + if (p_val == 1) assert(b != lddmc_true); + + /* Test gc */ + sylvan_gc_test(); + + /* Skip nodes if possible */ + if (p_val == 1) { + if (!match_ldds(&a, &b)) return lddmc_false; + } + + sylvan_stats_count(LDD_MATCH); + + /* Access cache */ + MDD result; + if (cache_get3(CACHE_MDD_MATCH, a, b, proj, &result)) { + sylvan_stats_count(LDD_MATCH_CACHED); + return result; + } + + /* Perform recursive calculation */ + mddnode_t na = GETNODE(a); + MDD down; + if (p_val == 1) { + mddnode_t nb = GETNODE(b); + /* right = */ lddmc_refs_spawn(SPAWN(lddmc_match, mddnode_getright(na), mddnode_getright(nb), proj)); + down = CALL(lddmc_match, mddnode_getdown(na), mddnode_getdown(nb), mddnode_getdown(p_node)); + } else { + /* right = */ lddmc_refs_spawn(SPAWN(lddmc_match, mddnode_getright(na), b, proj)); + down = CALL(lddmc_match, mddnode_getdown(na), b, mddnode_getdown(p_node)); + } + lddmc_refs_push(down); + MDD right = lddmc_refs_sync(SYNC(lddmc_match)); + lddmc_refs_pop(1); + result = lddmc_makenode(mddnode_getvalue(na), down, right); + + /* Write to cache */ + if (cache_put3(CACHE_MDD_MATCH, a, b, proj, result)) sylvan_stats_count(LDD_MATCH_CACHEDPUT); + + return result; +} + +TASK_4(MDD, lddmc_relprod_help, uint32_t, val, MDD, set, MDD, rel, MDD, proj) +{ + return lddmc_makenode(val, CALL(lddmc_relprod, set, rel, proj), lddmc_false); +} + +// meta: -1 (end; rest not in rel), 0 (not in rel), 1 (read), 2 (write), 3 (only-read), 4 (only-write) +TASK_IMPL_3(MDD, lddmc_relprod, MDD, set, MDD, rel, MDD, meta) +{ + if (set == lddmc_false) return lddmc_false; + if (rel == lddmc_false) return lddmc_false; + + mddnode_t n_meta = GETNODE(meta); + uint32_t m_val = mddnode_getvalue(n_meta); + if (m_val == (uint32_t)-1) return set; + if (m_val != 0) assert(set != lddmc_true && rel != lddmc_true); + + /* Skip nodes if possible */ + if (!mddnode_getcopy(GETNODE(rel))) { + if (m_val == 1 || m_val == 3) { + if (!match_ldds(&set, &rel)) return lddmc_false; + } + } + + /* Test gc */ + sylvan_gc_test(); + + sylvan_stats_count(LDD_RELPROD); + + /* Access cache */ + MDD result; + if (cache_get3(CACHE_MDD_RELPROD, set, rel, meta, &result)) { + sylvan_stats_count(LDD_RELPROD_CACHED); + return result; + } + + mddnode_t n_set = GETNODE(set); + mddnode_t n_rel = GETNODE(rel); + + /* Recursive operations */ + if (m_val == 0) { // not in rel + lddmc_refs_spawn(SPAWN(lddmc_relprod, mddnode_getright(n_set), rel, meta)); + MDD down = CALL(lddmc_relprod, mddnode_getdown(n_set), rel, mddnode_getdown(n_meta)); + lddmc_refs_push(down); + MDD right = lddmc_refs_sync(SYNC(lddmc_relprod)); + lddmc_refs_pop(1); + result = lddmc_makenode(mddnode_getvalue(n_set), down, right); + } else if (m_val == 1) { // read + // read layer: if not copy, then set&rel are already matched + lddmc_refs_spawn(SPAWN(lddmc_relprod, set, mddnode_getright(n_rel), meta)); // spawn next read in list + + // for this read, either it is copy ('for all') or it is normal match + if (mddnode_getcopy(n_rel)) { + // spawn for every value to copy (set) + int count = 0; + for (;;) { + // stay same level of set (for write) + lddmc_refs_spawn(SPAWN(lddmc_relprod, set, mddnode_getdown(n_rel), mddnode_getdown(n_meta))); + count++; + set = mddnode_getright(n_set); + if (set == lddmc_false) break; + n_set = GETNODE(set); + } + + // sync+union (one by one) + result = lddmc_false; + while (count--) { + lddmc_refs_push(result); + MDD result2 = lddmc_refs_sync(SYNC(lddmc_relprod)); + lddmc_refs_push(result2); + result = CALL(lddmc_union, result, result2); + lddmc_refs_pop(2); + } + } else { + // stay same level of set (for write) + result = CALL(lddmc_relprod, set, mddnode_getdown(n_rel), mddnode_getdown(n_meta)); + } + + lddmc_refs_push(result); + MDD result2 = lddmc_refs_sync(SYNC(lddmc_relprod)); // sync next read in list + lddmc_refs_push(result2); + result = CALL(lddmc_union, result, result2); + lddmc_refs_pop(2); + } else if (m_val == 3) { // only-read + if (mddnode_getcopy(n_rel)) { + // copy on read ('for any value') + // result = union(result_with_copy, result_without_copy) + lddmc_refs_spawn(SPAWN(lddmc_relprod, set, mddnode_getright(n_rel), meta)); // spawn without_copy + + // spawn for every value to copy (set) + int count = 0; + for (;;) { + lddmc_refs_spawn(SPAWN(lddmc_relprod_help, mddnode_getvalue(n_set), mddnode_getdown(n_set), mddnode_getdown(n_rel), mddnode_getdown(n_meta))); + count++; + set = mddnode_getright(n_set); + if (set == lddmc_false) break; + n_set = GETNODE(set); + } + + // sync+union (one by one) + result = lddmc_false; + while (count--) { + lddmc_refs_push(result); + MDD result2 = lddmc_refs_sync(SYNC(lddmc_relprod_help)); + lddmc_refs_push(result2); + result = CALL(lddmc_union, result, result2); + lddmc_refs_pop(2); + } + + // add result from without_copy + lddmc_refs_push(result); + MDD result2 = lddmc_refs_sync(SYNC(lddmc_relprod)); + lddmc_refs_push(result2); + result = CALL(lddmc_union, result, result2); + lddmc_refs_pop(2); + } else { + // only-read, without copy + lddmc_refs_spawn(SPAWN(lddmc_relprod, mddnode_getright(n_set), mddnode_getright(n_rel), meta)); + MDD down = CALL(lddmc_relprod, mddnode_getdown(n_set), mddnode_getdown(n_rel), mddnode_getdown(n_meta)); + lddmc_refs_push(down); + MDD right = lddmc_refs_sync(SYNC(lddmc_relprod)); + lddmc_refs_pop(1); + result = lddmc_makenode(mddnode_getvalue(n_set), down, right); + } + } else if (m_val == 2 || m_val == 4) { // write, only-write + if (m_val == 4) { + // only-write, so we need to include 'for all variables' + lddmc_refs_spawn(SPAWN(lddmc_relprod, mddnode_getright(n_set), rel, meta)); // next in set + } + + // spawn for every value to write (rel) + int count = 0; + for (;;) { + uint32_t value; + if (mddnode_getcopy(n_rel)) value = mddnode_getvalue(n_set); + else value = mddnode_getvalue(n_rel); + lddmc_refs_spawn(SPAWN(lddmc_relprod_help, value, mddnode_getdown(n_set), mddnode_getdown(n_rel), mddnode_getdown(n_meta))); + count++; + rel = mddnode_getright(n_rel); + if (rel == lddmc_false) break; + n_rel = GETNODE(rel); + } + + // sync+union (one by one) + result = lddmc_false; + while (count--) { + lddmc_refs_push(result); + MDD result2 = lddmc_refs_sync(SYNC(lddmc_relprod_help)); + lddmc_refs_push(result2); + result = CALL(lddmc_union, result, result2); + lddmc_refs_pop(2); + } + + if (m_val == 4) { + // sync+union with other variables + lddmc_refs_push(result); + MDD result2 = lddmc_refs_sync(SYNC(lddmc_relprod)); + lddmc_refs_push(result2); + result = CALL(lddmc_union, result, result2); + lddmc_refs_pop(2); + } + } + + /* Write to cache */ + if (cache_put3(CACHE_MDD_RELPROD, set, rel, meta, result)) sylvan_stats_count(LDD_RELPROD_CACHEDPUT); + + return result; +} + +TASK_5(MDD, lddmc_relprod_union_help, uint32_t, val, MDD, set, MDD, rel, MDD, proj, MDD, un) +{ + return lddmc_makenode(val, CALL(lddmc_relprod_union, set, rel, proj, un), lddmc_false); +} + +// meta: -1 (end; rest not in rel), 0 (not in rel), 1 (read), 2 (write), 3 (only-read), 4 (only-write) +TASK_IMPL_4(MDD, lddmc_relprod_union, MDD, set, MDD, rel, MDD, meta, MDD, un) +{ + if (set == lddmc_false) return un; + if (rel == lddmc_false) return un; + if (un == lddmc_false) return CALL(lddmc_relprod, set, rel, meta); + + mddnode_t n_meta = GETNODE(meta); + uint32_t m_val = mddnode_getvalue(n_meta); + if (m_val == (uint32_t)-1) return CALL(lddmc_union, set, un); + + // check depths (this triggers on logic error) + if (m_val != 0) assert(set != lddmc_true && rel != lddmc_true && un != lddmc_true); + + /* Skip nodes if possible */ + if (!mddnode_getcopy(GETNODE(rel))) { + if (m_val == 1 || m_val == 3) { + if (!match_ldds(&set, &rel)) return un; + } + } + + mddnode_t n_set = GETNODE(set); + mddnode_t n_rel = GETNODE(rel); + mddnode_t n_un = GETNODE(un); + + // in some cases, we know un.value < result.value + if (m_val == 0 || m_val == 3) { + // if m_val == 0, no read/write, then un.value < set.value? + // if m_val == 3, only read (write same), then un.value < set.value? + uint32_t set_value = mddnode_getvalue(n_set); + uint32_t un_value = mddnode_getvalue(n_un); + if (un_value < set_value) { + MDD right = CALL(lddmc_relprod_union, set, rel, meta, mddnode_getright(n_un)); + if (right == mddnode_getright(n_un)) return un; + else return lddmc_makenode(mddnode_getvalue(n_un), mddnode_getdown(n_un), right); + } + } else if (m_val == 2 || m_val == 4) { + // if we write, then we only know for certain that un.value < result.value if + // the root of rel is not a copy node + if (!mddnode_getcopy(n_rel)) { + uint32_t rel_value = mddnode_getvalue(n_rel); + uint32_t un_value = mddnode_getvalue(n_un); + if (un_value < rel_value) { + MDD right = CALL(lddmc_relprod_union, set, rel, meta, mddnode_getright(n_un)); + if (right == mddnode_getright(n_un)) return un; + else return lddmc_makenode(mddnode_getvalue(n_un), mddnode_getdown(n_un), right); + } + } + } + + /* Test gc */ + sylvan_gc_test(); + + sylvan_stats_count(LDD_RELPROD_UNION); + + /* Access cache */ + MDD result; + if (cache_get4(CACHE_MDD_RELPROD, set, rel, meta, un, &result)) { + sylvan_stats_count(LDD_RELPROD_UNION_CACHED); + return result; + } + + /* Recursive operations */ + if (m_val == 0) { // not in rel + uint32_t set_value = mddnode_getvalue(n_set); + uint32_t un_value = mddnode_getvalue(n_un); + // set_value > un_value already checked above + if (set_value < un_value) { + lddmc_refs_spawn(SPAWN(lddmc_relprod_union, mddnode_getright(n_set), rel, meta, un)); + // going down, we don't need _union, since un does not contain this subtree + MDD down = CALL(lddmc_relprod, mddnode_getdown(n_set), rel, mddnode_getdown(n_meta)); + lddmc_refs_push(down); + MDD right = lddmc_refs_sync(SYNC(lddmc_relprod_union)); + lddmc_refs_pop(1); + if (down == lddmc_false) result = right; + else result = lddmc_makenode(mddnode_getvalue(n_set), down, right); + } else /* set_value == un_value */ { + lddmc_refs_spawn(SPAWN(lddmc_relprod_union, mddnode_getright(n_set), rel, meta, mddnode_getright(n_un))); + MDD down = CALL(lddmc_relprod_union, mddnode_getdown(n_set), rel, mddnode_getdown(n_meta), mddnode_getdown(n_un)); + lddmc_refs_push(down); + MDD right = lddmc_refs_sync(SYNC(lddmc_relprod_union)); + lddmc_refs_pop(1); + if (right == mddnode_getright(n_un) && down == mddnode_getdown(n_un)) result = un; + else result = lddmc_makenode(mddnode_getvalue(n_set), down, right); + } + } else if (m_val == 1) { // read + // read layer: if not copy, then set&rel are already matched + lddmc_refs_spawn(SPAWN(lddmc_relprod_union, set, mddnode_getright(n_rel), meta, un)); // spawn next read in list + + // for this read, either it is copy ('for all') or it is normal match + if (mddnode_getcopy(n_rel)) { + // spawn for every value in set (copy = for all) + int count = 0; + for (;;) { + // stay same level of set and un (for write) + lddmc_refs_spawn(SPAWN(lddmc_relprod_union, set, mddnode_getdown(n_rel), mddnode_getdown(n_meta), un)); + count++; + set = mddnode_getright(n_set); + if (set == lddmc_false) break; + n_set = GETNODE(set); + } + + // sync+union (one by one) + result = lddmc_false; + while (count--) { + lddmc_refs_push(result); + MDD result2 = lddmc_refs_sync(SYNC(lddmc_relprod_union)); + lddmc_refs_push(result2); + result = CALL(lddmc_union, result, result2); + lddmc_refs_pop(2); + } + } else { + // stay same level of set and un (for write) + result = CALL(lddmc_relprod_union, set, mddnode_getdown(n_rel), mddnode_getdown(n_meta), un); + } + + lddmc_refs_push(result); + MDD result2 = lddmc_refs_sync(SYNC(lddmc_relprod_union)); // sync next read in list + lddmc_refs_push(result2); + result = CALL(lddmc_union, result, result2); + lddmc_refs_pop(2); + } else if (m_val == 3) { // only-read + // un < set already checked above + if (mddnode_getcopy(n_rel)) { + // copy on read ('for any value') + // result = union(result_with_copy, result_without_copy) + lddmc_refs_spawn(SPAWN(lddmc_relprod_union, set, mddnode_getright(n_rel), meta, un)); // spawn without_copy + + // spawn for every value to copy (set) + int count = 0; + result = lddmc_false; + for (;;) { + uint32_t set_value = mddnode_getvalue(n_set); + uint32_t un_value = mddnode_getvalue(n_un); + if (un_value < set_value) { + // this is a bit tricky + // the result of this will simply be "un_value, mddnode_getdown(n_un), false" which is intended + lddmc_refs_spawn(SPAWN(lddmc_relprod_union_help, un_value, lddmc_false, lddmc_false, mddnode_getdown(n_meta), mddnode_getdown(n_un))); + count++; + un = mddnode_getright(n_un); + if (un == lddmc_false) { + result = CALL(lddmc_relprod, set, rel, meta); + break; + } + n_un = GETNODE(un); + } else if (un_value > set_value) { + // tricky again. the result of this is a normal relprod + lddmc_refs_spawn(SPAWN(lddmc_relprod_union_help, set_value, mddnode_getdown(n_set), mddnode_getdown(n_rel), mddnode_getdown(n_meta), lddmc_false)); + count++; + set = mddnode_getright(n_set); + if (set == lddmc_false) { + result = un; + break; + } + n_set = GETNODE(set); + } else /* un_value == set_value */ { + lddmc_refs_spawn(SPAWN(lddmc_relprod_union_help, set_value, mddnode_getdown(n_set), mddnode_getdown(n_rel), mddnode_getdown(n_meta), mddnode_getdown(n_un))); + count++; + set = mddnode_getright(n_set); + un = mddnode_getright(n_un); + if (set == lddmc_false) { + result = un; + break; + } else if (un == lddmc_false) { + result = CALL(lddmc_relprod, set, rel, meta); + break; + } + n_set = GETNODE(set); + n_un = GETNODE(un); + } + } + + // sync+union (one by one) + while (count--) { + lddmc_refs_push(result); + MDD result2 = lddmc_refs_sync(SYNC(lddmc_relprod_union_help)); + lddmc_refs_push(result2); + result = CALL(lddmc_union, result, result2); + lddmc_refs_pop(2); + } + + // add result from without_copy + lddmc_refs_push(result); + MDD result2 = lddmc_refs_sync(SYNC(lddmc_relprod_union)); + lddmc_refs_push(result2); + result = CALL(lddmc_union, result, result2); + lddmc_refs_pop(2); + } else { + // only-read, not a copy node + uint32_t set_value = mddnode_getvalue(n_set); + uint32_t un_value = mddnode_getvalue(n_un); + + // already did un_value < set_value + if (un_value > set_value) { + lddmc_refs_spawn(SPAWN(lddmc_relprod_union, mddnode_getright(n_set), mddnode_getright(n_rel), meta, un)); + MDD down = CALL(lddmc_relprod, mddnode_getdown(n_set), mddnode_getdown(n_rel), mddnode_getdown(n_meta)); + lddmc_refs_push(down); + MDD right = lddmc_refs_sync(SYNC(lddmc_relprod_union)); + lddmc_refs_pop(1); + result = lddmc_makenode(mddnode_getvalue(n_set), down, right); + } else /* un_value == set_value */ { + lddmc_refs_spawn(SPAWN(lddmc_relprod_union, mddnode_getright(n_set), mddnode_getright(n_rel), meta, mddnode_getright(n_un))); + MDD down = CALL(lddmc_relprod_union, mddnode_getdown(n_set), mddnode_getdown(n_rel), mddnode_getdown(n_meta), mddnode_getdown(n_un)); + lddmc_refs_push(down); + MDD right = lddmc_refs_sync(SYNC(lddmc_relprod_union)); + lddmc_refs_pop(1); + result = lddmc_makenode(mddnode_getvalue(n_set), down, right); + } + } + } else if (m_val == 2 || m_val == 4) { // write, only-write + if (m_val == 4) { + // only-write, so we need to include 'for all variables' + lddmc_refs_spawn(SPAWN(lddmc_relprod_union, mddnode_getright(n_set), rel, meta, un)); // next in set + } + + // spawn for every value to write (rel) + int count = 0; + for (;;) { + uint32_t value; + if (mddnode_getcopy(n_rel)) value = mddnode_getvalue(n_set); + else value = mddnode_getvalue(n_rel); + uint32_t un_value = mddnode_getvalue(n_un); + if (un_value < value) { + // the result of this will simply be "un_value, mddnode_getdown(n_un), false" which is intended + lddmc_refs_spawn(SPAWN(lddmc_relprod_union_help, un_value, lddmc_false, lddmc_false, mddnode_getdown(n_meta), mddnode_getdown(n_un))); + count++; + un = mddnode_getright(n_un); + if (un == lddmc_false) { + result = CALL(lddmc_relprod, set, rel, meta); + break; + } + n_un = GETNODE(un); + } else if (un_value > value) { + lddmc_refs_spawn(SPAWN(lddmc_relprod_union_help, value, mddnode_getdown(n_set), mddnode_getdown(n_rel), mddnode_getdown(n_meta), lddmc_false)); + count++; + rel = mddnode_getright(n_rel); + if (rel == lddmc_false) { + result = un; + break; + } + n_rel = GETNODE(rel); + } else /* un_value == value */ { + lddmc_refs_spawn(SPAWN(lddmc_relprod_union_help, value, mddnode_getdown(n_set), mddnode_getdown(n_rel), mddnode_getdown(n_meta), mddnode_getdown(n_un))); + count++; + rel = mddnode_getright(n_rel); + un = mddnode_getright(n_un); + if (rel == lddmc_false) { + result = un; + break; + } else if (un == lddmc_false) { + result = CALL(lddmc_relprod, set, rel, meta); + break; + } + n_rel = GETNODE(rel); + n_un = GETNODE(un); + } + } + + // sync+union (one by one) + while (count--) { + lddmc_refs_push(result); + MDD result2 = lddmc_refs_sync(SYNC(lddmc_relprod_union_help)); + lddmc_refs_push(result2); + result = CALL(lddmc_union, result, result2); + lddmc_refs_pop(2); + } + + if (m_val == 4) { + // sync+union with other variables + lddmc_refs_push(result); + MDD result2 = lddmc_refs_sync(SYNC(lddmc_relprod_union)); + lddmc_refs_push(result2); + result = CALL(lddmc_union, result, result2); + lddmc_refs_pop(2); + } + } + + /* Write to cache */ + if (cache_put4(CACHE_MDD_RELPROD, set, rel, meta, un, result)) sylvan_stats_count(LDD_RELPROD_UNION_CACHEDPUT); + + return result; +} + +TASK_5(MDD, lddmc_relprev_help, uint32_t, val, MDD, set, MDD, rel, MDD, proj, MDD, uni) +{ + return lddmc_makenode(val, CALL(lddmc_relprev, set, rel, proj, uni), lddmc_false); +} + +/** + * Calculate all predecessors to a in uni according to rel[meta] + * <meta> follows the same semantics as relprod + * i.e. 0 (not in rel), 1 (read), 2 (write), 3 (only-read), 4 (only-write), -1 (end; rest=0) + */ +TASK_IMPL_4(MDD, lddmc_relprev, MDD, set, MDD, rel, MDD, meta, MDD, uni) +{ + if (set == lddmc_false) return lddmc_false; + if (rel == lddmc_false) return lddmc_false; + if (uni == lddmc_false) return lddmc_false; + + mddnode_t n_meta = GETNODE(meta); + uint32_t m_val = mddnode_getvalue(n_meta); + if (m_val == (uint32_t)-1) { + if (set == uni) return set; + else return lddmc_intersect(set, uni); + } + + if (m_val != 0) assert(set != lddmc_true && rel != lddmc_true && uni != lddmc_true); + + /* Skip nodes if possible */ + if (m_val == 0) { + // not in rel: match set and uni ('intersect') + if (!match_ldds(&set, &uni)) return lddmc_false; + } else if (mddnode_getcopy(GETNODE(rel))) { + // read+copy: no matching (pre is everything in uni) + // write+copy: no matching (match after split: set and uni) + // only-read+copy: match set and uni + // only-write+copy: no matching (match after split: set and uni) + if (m_val == 3) { + if (!match_ldds(&set, &uni)) return lddmc_false; + } + } else if (m_val == 1) { + // read: match uni and rel + if (!match_ldds(&uni, &rel)) return lddmc_false; + } else if (m_val == 2) { + // write: match set and rel + if (!match_ldds(&set, &rel)) return lddmc_false; + } else if (m_val == 3) { + // only-read: match uni and set and rel + mddnode_t n_set = GETNODE(set); + mddnode_t n_rel = GETNODE(rel); + mddnode_t n_uni = GETNODE(uni); + uint32_t n_set_value = mddnode_getvalue(n_set); + uint32_t n_rel_value = mddnode_getvalue(n_rel); + uint32_t n_uni_value = mddnode_getvalue(n_uni); + while (n_uni_value != n_rel_value || n_rel_value != n_set_value) { + if (n_uni_value < n_rel_value || n_uni_value < n_set_value) { + uni = mddnode_getright(n_uni); + if (uni == lddmc_false) return lddmc_false; + n_uni = GETNODE(uni); + n_uni_value = mddnode_getvalue(n_uni); + } + if (n_set_value < n_rel_value || n_set_value < n_uni_value) { + set = mddnode_getright(n_set); + if (set == lddmc_false) return lddmc_false; + n_set = GETNODE(set); + n_set_value = mddnode_getvalue(n_set); + } + if (n_rel_value < n_set_value || n_rel_value < n_uni_value) { + rel = mddnode_getright(n_rel); + if (rel == lddmc_false) return lddmc_false; + n_rel = GETNODE(rel); + n_rel_value = mddnode_getvalue(n_rel); + } + } + } else if (m_val == 4) { + // only-write: match set and rel (then use whole universe) + if (!match_ldds(&set, &rel)) return lddmc_false; + } + + /* Test gc */ + sylvan_gc_test(); + + sylvan_stats_count(LDD_RELPREV); + + /* Access cache */ + MDD result; + if (cache_get4(CACHE_MDD_RELPREV, set, rel, meta, uni, &result)) { + sylvan_stats_count(LDD_RELPREV_CACHED); + return result; + } + + mddnode_t n_set = GETNODE(set); + mddnode_t n_rel = GETNODE(rel); + mddnode_t n_uni = GETNODE(uni); + + /* Recursive operations */ + if (m_val == 0) { // not in rel + // m_val == 0 : not in rel (intersection set and universe) + lddmc_refs_spawn(SPAWN(lddmc_relprev, mddnode_getright(n_set), rel, meta, mddnode_getright(n_uni))); + MDD down = CALL(lddmc_relprev, mddnode_getdown(n_set), rel, mddnode_getdown(n_meta), mddnode_getdown(n_uni)); + lddmc_refs_push(down); + MDD right = lddmc_refs_sync(SYNC(lddmc_relprev)); + lddmc_refs_pop(1); + result = lddmc_makenode(mddnode_getvalue(n_set), down, right); + } else if (m_val == 1) { // read level + // result value is in case of copy: everything in uni! + // result value is in case of not-copy: match uni and rel! + lddmc_refs_spawn(SPAWN(lddmc_relprev, set, mddnode_getright(n_rel), meta, uni)); // next in rel + if (mddnode_getcopy(n_rel)) { + // result is everything in uni + // spawn for every value to have been read (uni) + int count = 0; + for (;;) { + lddmc_refs_spawn(SPAWN(lddmc_relprev_help, mddnode_getvalue(n_uni), set, mddnode_getdown(n_rel), mddnode_getdown(n_meta), uni)); + count++; + uni = mddnode_getright(n_uni); + if (uni == lddmc_false) break; + n_uni = GETNODE(uni); + } + + // sync+union (one by one) + result = lddmc_false; + while (count--) { + lddmc_refs_push(result); + MDD result2 = lddmc_refs_sync(SYNC(lddmc_relprev_help)); + lddmc_refs_push(result2); + result = CALL(lddmc_union, result, result2); + lddmc_refs_pop(2); + } + } else { + // already matched + MDD down = CALL(lddmc_relprev, set, mddnode_getdown(n_rel), mddnode_getdown(n_meta), uni); + result = lddmc_makenode(mddnode_getvalue(n_uni), down, lddmc_false); + } + lddmc_refs_push(result); + MDD result2 = lddmc_refs_sync(SYNC(lddmc_relprev)); + lddmc_refs_push(result2); + result = CALL(lddmc_union, result, result2); + lddmc_refs_pop(2); + } else if (m_val == 3) { // only-read level + // result value is in case of copy: match set and uni! (already done first match) + // result value is in case of not-copy: match set and uni and rel! + lddmc_refs_spawn(SPAWN(lddmc_relprev, set, mddnode_getright(n_rel), meta, uni)); // next in rel + if (mddnode_getcopy(n_rel)) { + // spawn for every matching set+uni + int count = 0; + for (;;) { + lddmc_refs_spawn(SPAWN(lddmc_relprev_help, mddnode_getvalue(n_uni), mddnode_getdown(n_set), mddnode_getdown(n_rel), mddnode_getdown(n_meta), mddnode_getdown(n_uni))); + count++; + uni = mddnode_getright(n_uni); + if (!match_ldds(&set, &uni)) break; + n_set = GETNODE(set); + n_uni = GETNODE(uni); + } + + // sync+union (one by one) + result = lddmc_false; + while (count--) { + lddmc_refs_push(result); + MDD result2 = lddmc_refs_sync(SYNC(lddmc_relprev_help)); + lddmc_refs_push(result2); + result = CALL(lddmc_union, result, result2); + lddmc_refs_pop(2); + } + } else { + // already matched + MDD down = CALL(lddmc_relprev, mddnode_getdown(n_set), mddnode_getdown(n_rel), mddnode_getdown(n_meta), mddnode_getdown(n_uni)); + result = lddmc_makenode(mddnode_getvalue(n_uni), down, lddmc_false); + } + lddmc_refs_push(result); + MDD result2 = lddmc_refs_sync(SYNC(lddmc_relprev)); + lddmc_refs_push(result2); + result = CALL(lddmc_union, result, result2); + lddmc_refs_pop(2); + } else if (m_val == 2) { // write level + // note: the read level has already matched the uni that was read. + // write+copy: only for the one set equal to uni... + // write: match set and rel (already done) + lddmc_refs_spawn(SPAWN(lddmc_relprev, set, mddnode_getright(n_rel), meta, uni)); + if (mddnode_getcopy(n_rel)) { + MDD down = lddmc_follow(set, mddnode_getvalue(n_uni)); + if (down != lddmc_false) { + result = CALL(lddmc_relprev, down, mddnode_getdown(n_rel), mddnode_getdown(n_meta), mddnode_getdown(n_uni)); + } else { + result = lddmc_false; + } + } else { + result = CALL(lddmc_relprev, mddnode_getdown(n_set), mddnode_getdown(n_rel), mddnode_getdown(n_meta), mddnode_getdown(n_uni)); + } + lddmc_refs_push(result); + MDD result2 = lddmc_refs_sync(SYNC(lddmc_relprev)); + lddmc_refs_push(result2); + result = CALL(lddmc_union, result, result2); + lddmc_refs_pop(2); + } else if (m_val == 4) { // only-write level + // only-write+copy: match set and uni after spawn + // only-write: match set and rel (already done) + lddmc_refs_spawn(SPAWN(lddmc_relprev, set, mddnode_getright(n_rel), meta, uni)); + if (mddnode_getcopy(n_rel)) { + // spawn for every matching set+uni + int count = 0; + for (;;) { + if (!match_ldds(&set, &uni)) break; + n_set = GETNODE(set); + n_uni = GETNODE(uni); + lddmc_refs_spawn(SPAWN(lddmc_relprev_help, mddnode_getvalue(n_uni), mddnode_getdown(n_set), mddnode_getdown(n_rel), mddnode_getdown(n_meta), mddnode_getdown(n_uni))); + count++; + uni = mddnode_getright(n_uni); + } + + // sync+union (one by one) + result = lddmc_false; + while (count--) { + lddmc_refs_push(result); + MDD result2 = lddmc_refs_sync(SYNC(lddmc_relprev_help)); + lddmc_refs_push(result2); + result = CALL(lddmc_union, result, result2); + lddmc_refs_pop(2); + } + } else { + // spawn for every value in universe!! + int count = 0; + for (;;) { + lddmc_refs_spawn(SPAWN(lddmc_relprev_help, mddnode_getvalue(n_uni), mddnode_getdown(n_set), mddnode_getdown(n_rel), mddnode_getdown(n_meta), mddnode_getdown(n_uni))); + count++; + uni = mddnode_getright(n_uni); + if (uni == lddmc_false) break; + n_uni = GETNODE(uni); + } + + // sync+union (one by one) + result = lddmc_false; + while (count--) { + lddmc_refs_push(result); + MDD result2 = lddmc_refs_sync(SYNC(lddmc_relprev_help)); + lddmc_refs_push(result2); + result = CALL(lddmc_union, result, result2); + lddmc_refs_pop(2); + } + } + lddmc_refs_push(result); + MDD result2 = lddmc_refs_sync(SYNC(lddmc_relprev)); + lddmc_refs_push(result2); + result = CALL(lddmc_union, result, result2); + lddmc_refs_pop(2); + } + + /* Write to cache */ + if (cache_put4(CACHE_MDD_RELPREV, set, rel, meta, uni, result)) sylvan_stats_count(LDD_RELPREV_CACHEDPUT); + + return result; +} + +// Same 'proj' as project. So: proj: -2 (end; quantify rest), -1 (end; keep rest), 0 (quantify), 1 (keep) +TASK_IMPL_4(MDD, lddmc_join, MDD, a, MDD, b, MDD, a_proj, MDD, b_proj) +{ + if (a == lddmc_false || b == lddmc_false) return lddmc_false; + + /* Test gc */ + sylvan_gc_test(); + + mddnode_t n_a_proj = GETNODE(a_proj); + mddnode_t n_b_proj = GETNODE(b_proj); + uint32_t a_proj_val = mddnode_getvalue(n_a_proj); + uint32_t b_proj_val = mddnode_getvalue(n_b_proj); + + while (a_proj_val == 0 && b_proj_val == 0) { + a_proj = mddnode_getdown(n_a_proj); + b_proj = mddnode_getdown(n_b_proj); + n_a_proj = GETNODE(a_proj); + n_b_proj = GETNODE(b_proj); + a_proj_val = mddnode_getvalue(n_a_proj); + b_proj_val = mddnode_getvalue(n_b_proj); + } + + if (a_proj_val == (uint32_t)-2) return b; // no a left + if (b_proj_val == (uint32_t)-2) return a; // no b left + if (a_proj_val == (uint32_t)-1 && b_proj_val == (uint32_t)-1) return CALL(lddmc_intersect, a, b); + + // At this point, only proj_val {-1, 0, 1}; max one with -1; max one with 0. + const int keep_a = a_proj_val != 0; + const int keep_b = b_proj_val != 0; + + if (keep_a && keep_b) { + // If both 'keep', then match values + if (!match_ldds(&a, &b)) return lddmc_false; + } + + sylvan_stats_count(LDD_JOIN); + + /* Access cache */ + MDD result; + if (cache_get4(CACHE_MDD_JOIN, a, b, a_proj, b_proj, &result)) { + sylvan_stats_count(LDD_JOIN_CACHED); + return result; + } + + /* Perform recursive calculation */ + const mddnode_t na = GETNODE(a); + const mddnode_t nb = GETNODE(b); + uint32_t val; + MDD down; + + // Make copies (for cache) + MDD _a_proj = a_proj, _b_proj = b_proj; + if (keep_a) { + if (keep_b) { + val = mddnode_getvalue(nb); + lddmc_refs_spawn(SPAWN(lddmc_join, mddnode_getright(na), mddnode_getright(nb), a_proj, b_proj)); + if (a_proj_val != (uint32_t)-1) a_proj = mddnode_getdown(n_a_proj); + if (b_proj_val != (uint32_t)-1) b_proj = mddnode_getdown(n_b_proj); + down = CALL(lddmc_join, mddnode_getdown(na), mddnode_getdown(nb), a_proj, b_proj); + } else { + val = mddnode_getvalue(na); + lddmc_refs_spawn(SPAWN(lddmc_join, mddnode_getright(na), b, a_proj, b_proj)); + if (a_proj_val != (uint32_t)-1) a_proj = mddnode_getdown(n_a_proj); + if (b_proj_val != (uint32_t)-1) b_proj = mddnode_getdown(n_b_proj); + down = CALL(lddmc_join, mddnode_getdown(na), b, a_proj, b_proj); + } + } else { + val = mddnode_getvalue(nb); + lddmc_refs_spawn(SPAWN(lddmc_join, a, mddnode_getright(nb), a_proj, b_proj)); + if (a_proj_val != (uint32_t)-1) a_proj = mddnode_getdown(n_a_proj); + if (b_proj_val != (uint32_t)-1) b_proj = mddnode_getdown(n_b_proj); + down = CALL(lddmc_join, a, mddnode_getdown(nb), a_proj, b_proj); + } + + lddmc_refs_push(down); + MDD right = lddmc_refs_sync(SYNC(lddmc_join)); + lddmc_refs_pop(1); + result = lddmc_makenode(val, down, right); + + /* Write to cache */ + if (cache_put4(CACHE_MDD_JOIN, a, b, _a_proj, _b_proj, result)) sylvan_stats_count(LDD_JOIN_CACHEDPUT); + + return result; +} + +// so: proj: -2 (end; quantify rest), -1 (end; keep rest), 0 (quantify), 1 (keep) +TASK_IMPL_2(MDD, lddmc_project, const MDD, mdd, const MDD, proj) +{ + if (mdd == lddmc_false) return lddmc_false; // projection of empty is empty + if (mdd == lddmc_true) return lddmc_true; // projection of universe is universe... + + mddnode_t p_node = GETNODE(proj); + uint32_t p_val = mddnode_getvalue(p_node); + if (p_val == (uint32_t)-1) return mdd; + if (p_val == (uint32_t)-2) return lddmc_true; // because we always end with true. + + sylvan_gc_test(); + + sylvan_stats_count(LDD_PROJECT); + + MDD result; + if (cache_get3(CACHE_MDD_PROJECT, mdd, proj, 0, &result)) { + sylvan_stats_count(LDD_PROJECT_CACHED); + return result; + } + + mddnode_t n = GETNODE(mdd); + + if (p_val == 1) { // keep + lddmc_refs_spawn(SPAWN(lddmc_project, mddnode_getright(n), proj)); + MDD down = CALL(lddmc_project, mddnode_getdown(n), mddnode_getdown(p_node)); + lddmc_refs_push(down); + MDD right = lddmc_refs_sync(SYNC(lddmc_project)); + lddmc_refs_pop(1); + result = lddmc_makenode(mddnode_getvalue(n), down, right); + } else { // quantify + if (mddnode_getdown(n) == lddmc_true) { // assume lowest level + result = lddmc_true; + } else { + int count = 0; + MDD p_down = mddnode_getdown(p_node), _mdd=mdd; + while (1) { + lddmc_refs_spawn(SPAWN(lddmc_project, mddnode_getdown(n), p_down)); + count++; + _mdd = mddnode_getright(n); + assert(_mdd != lddmc_true); + if (_mdd == lddmc_false) break; + n = GETNODE(_mdd); + } + result = lddmc_false; + while (count--) { + lddmc_refs_push(result); + MDD down = lddmc_refs_sync(SYNC(lddmc_project)); + lddmc_refs_push(down); + result = CALL(lddmc_union, result, down); + lddmc_refs_pop(2); + } + } + } + + if (cache_put3(CACHE_MDD_PROJECT, mdd, proj, 0, result)) sylvan_stats_count(LDD_PROJECT_CACHEDPUT); + + return result; +} + +// so: proj: -2 (end; quantify rest), -1 (end; keep rest), 0 (quantify), 1 (keep) +TASK_IMPL_3(MDD, lddmc_project_minus, const MDD, mdd, const MDD, proj, MDD, avoid) +{ + // This implementation assumed "avoid" has correct depth + if (avoid == lddmc_true) return lddmc_false; + if (mdd == avoid) return lddmc_false; + if (mdd == lddmc_false) return lddmc_false; // projection of empty is empty + if (mdd == lddmc_true) return lddmc_true; // avoid != lddmc_true + + mddnode_t p_node = GETNODE(proj); + uint32_t p_val = mddnode_getvalue(p_node); + if (p_val == (uint32_t)-1) return lddmc_minus(mdd, avoid); + if (p_val == (uint32_t)-2) return lddmc_true; + + sylvan_gc_test(); + + sylvan_stats_count(LDD_PROJECT_MINUS); + + MDD result; + if (cache_get3(CACHE_MDD_PROJECT, mdd, proj, avoid, &result)) { + sylvan_stats_count(LDD_PROJECT_MINUS_CACHED); + return result; + } + + mddnode_t n = GETNODE(mdd); + + if (p_val == 1) { // keep + // move 'avoid' until it matches + uint32_t val = mddnode_getvalue(n); + MDD a_down = lddmc_false; + while (avoid != lddmc_false) { + mddnode_t a_node = GETNODE(avoid); + uint32_t a_val = mddnode_getvalue(a_node); + if (a_val > val) { + break; + } else if (a_val == val) { + a_down = mddnode_getdown(a_node); + break; + } + avoid = mddnode_getright(a_node); + } + lddmc_refs_spawn(SPAWN(lddmc_project_minus, mddnode_getright(n), proj, avoid)); + MDD down = CALL(lddmc_project_minus, mddnode_getdown(n), mddnode_getdown(p_node), a_down); + lddmc_refs_push(down); + MDD right = lddmc_refs_sync(SYNC(lddmc_project_minus)); + lddmc_refs_pop(1); + result = lddmc_makenode(val, down, right); + } else { // quantify + if (mddnode_getdown(n) == lddmc_true) { // assume lowest level + result = lddmc_true; + } else { + int count = 0; + MDD p_down = mddnode_getdown(p_node), _mdd=mdd; + while (1) { + lddmc_refs_spawn(SPAWN(lddmc_project_minus, mddnode_getdown(n), p_down, avoid)); + count++; + _mdd = mddnode_getright(n); + assert(_mdd != lddmc_true); + if (_mdd == lddmc_false) break; + n = GETNODE(_mdd); + } + result = lddmc_false; + while (count--) { + lddmc_refs_push(result); + MDD down = lddmc_refs_sync(SYNC(lddmc_project_minus)); + lddmc_refs_push(down); + result = CALL(lddmc_union, result, down); + lddmc_refs_pop(2); + } + } + } + + if (cache_put3(CACHE_MDD_PROJECT, mdd, proj, avoid, result)) sylvan_stats_count(LDD_PROJECT_MINUS_CACHEDPUT); + + return result; +} + +MDD +lddmc_union_cube(MDD a, uint32_t* values, size_t count) +{ + if (a == lddmc_false) return lddmc_cube(values, count); + if (a == lddmc_true) { + assert(count == 0); + return lddmc_true; + } + assert(count != 0); + + mddnode_t na = GETNODE(a); + uint32_t na_value = mddnode_getvalue(na); + + /* Only create a new node if something actually changed */ + + if (na_value < *values) { + MDD right = lddmc_union_cube(mddnode_getright(na), values, count); + if (right == mddnode_getright(na)) return a; // no actual change + return lddmc_makenode(na_value, mddnode_getdown(na), right); + } else if (na_value == *values) { + MDD down = lddmc_union_cube(mddnode_getdown(na), values+1, count-1); + if (down == mddnode_getdown(na)) return a; // no actual change + return lddmc_makenode(na_value, down, mddnode_getright(na)); + } else /* na_value > *values */ { + return lddmc_makenode(*values, lddmc_cube(values+1, count-1), a); + } +} + +MDD +lddmc_union_cube_copy(MDD a, uint32_t* values, int* copy, size_t count) +{ + if (a == lddmc_false) return lddmc_cube_copy(values, copy, count); + if (a == lddmc_true) { + assert(count == 0); + return lddmc_true; + } + assert(count != 0); + + mddnode_t na = GETNODE(a); + + /* Only create a new node if something actually changed */ + + int na_copy = mddnode_getcopy(na); + if (na_copy && *copy) { + MDD down = lddmc_union_cube_copy(mddnode_getdown(na), values+1, copy+1, count-1); + if (down == mddnode_getdown(na)) return a; // no actual change + return lddmc_make_copynode(down, mddnode_getright(na)); + } else if (na_copy) { + MDD right = lddmc_union_cube_copy(mddnode_getright(na), values, copy, count); + if (right == mddnode_getright(na)) return a; // no actual change + return lddmc_make_copynode(mddnode_getdown(na), right); + } else if (*copy) { + return lddmc_make_copynode(lddmc_cube_copy(values+1, copy+1, count-1), a); + } + + uint32_t na_value = mddnode_getvalue(na); + if (na_value < *values) { + MDD right = lddmc_union_cube_copy(mddnode_getright(na), values, copy, count); + if (right == mddnode_getright(na)) return a; // no actual change + return lddmc_makenode(na_value, mddnode_getdown(na), right); + } else if (na_value == *values) { + MDD down = lddmc_union_cube_copy(mddnode_getdown(na), values+1, copy+1, count-1); + if (down == mddnode_getdown(na)) return a; // no actual change + return lddmc_makenode(na_value, down, mddnode_getright(na)); + } else /* na_value > *values */ { + return lddmc_makenode(*values, lddmc_cube_copy(values+1, copy+1, count-1), a); + } +} + +int +lddmc_member_cube(MDD a, uint32_t* values, size_t count) +{ + while (1) { + if (a == lddmc_false) return 0; + if (a == lddmc_true) return 1; + assert(count > 0); // size mismatch + + a = lddmc_follow(a, *values); + values++; + count--; + } +} + +int +lddmc_member_cube_copy(MDD a, uint32_t* values, int* copy, size_t count) +{ + while (1) { + if (a == lddmc_false) return 0; + if (a == lddmc_true) return 1; + assert(count > 0); // size mismatch + + if (*copy) a = lddmc_followcopy(a); + else a = lddmc_follow(a, *values); + values++; + count--; + } +} + +MDD +lddmc_cube(uint32_t* values, size_t count) +{ + if (count == 0) return lddmc_true; + return lddmc_makenode(*values, lddmc_cube(values+1, count-1), lddmc_false); +} + +MDD +lddmc_cube_copy(uint32_t* values, int* copy, size_t count) +{ + if (count == 0) return lddmc_true; + if (*copy) return lddmc_make_copynode(lddmc_cube_copy(values+1, copy+1, count-1), lddmc_false); + else return lddmc_makenode(*values, lddmc_cube_copy(values+1, copy+1, count-1), lddmc_false); +} + +/** + * Count number of nodes for each level + */ + +static void +lddmc_nodecount_levels_mark(MDD mdd, size_t *variables) +{ + if (mdd <= lddmc_true) return; + mddnode_t n = GETNODE(mdd); + if (!mddnode_getmark(n)) { + mddnode_setmark(n, 1); + (*variables) += 1; + lddmc_nodecount_levels_mark(mddnode_getright(n), variables); + lddmc_nodecount_levels_mark(mddnode_getdown(n), variables+1); + } +} + +static void +lddmc_nodecount_levels_unmark(MDD mdd) +{ + if (mdd <= lddmc_true) return; + mddnode_t n = GETNODE(mdd); + if (mddnode_getmark(n)) { + mddnode_setmark(n, 0); + lddmc_nodecount_levels_unmark(mddnode_getright(n)); + lddmc_nodecount_levels_unmark(mddnode_getdown(n)); + } +} + +void +lddmc_nodecount_levels(MDD mdd, size_t *variables) +{ + lddmc_nodecount_levels_mark(mdd, variables); + lddmc_nodecount_levels_unmark(mdd); +} + +/** + * Count number of nodes in MDD + */ + +static size_t +lddmc_nodecount_mark(MDD mdd) +{ + if (mdd <= lddmc_true) return 0; + mddnode_t n = GETNODE(mdd); + if (mddnode_getmark(n)) return 0; + mddnode_setmark(n, 1); + return 1 + lddmc_nodecount_mark(mddnode_getdown(n)) + lddmc_nodecount_mark(mddnode_getright(n)); +} + +static void +lddmc_nodecount_unmark(MDD mdd) +{ + if (mdd <= lddmc_true) return; + mddnode_t n = GETNODE(mdd); + if (mddnode_getmark(n)) { + mddnode_setmark(n, 0); + lddmc_nodecount_unmark(mddnode_getright(n)); + lddmc_nodecount_unmark(mddnode_getdown(n)); + } +} + +size_t +lddmc_nodecount(MDD mdd) +{ + size_t result = lddmc_nodecount_mark(mdd); + lddmc_nodecount_unmark(mdd); + return result; +} + +/** + * CALCULATE NUMBER OF VAR ASSIGNMENTS THAT YIELD TRUE + */ + +TASK_IMPL_1(lddmc_satcount_double_t, lddmc_satcount_cached, MDD, mdd) +{ + if (mdd == lddmc_false) return 0.0; + if (mdd == lddmc_true) return 1.0; + + /* Perhaps execute garbage collection */ + sylvan_gc_test(); + + union { + lddmc_satcount_double_t d; + uint64_t s; + } hack; + + sylvan_stats_count(LDD_SATCOUNT); + + if (cache_get3(CACHE_MDD_SATCOUNT, mdd, 0, 0, &hack.s)) { + sylvan_stats_count(LDD_SATCOUNT_CACHED); + return hack.d; + } + + mddnode_t n = GETNODE(mdd); + + SPAWN(lddmc_satcount_cached, mddnode_getdown(n)); + lddmc_satcount_double_t right = CALL(lddmc_satcount_cached, mddnode_getright(n)); + hack.d = right + SYNC(lddmc_satcount_cached); + + if (cache_put3(CACHE_MDD_SATCOUNT, mdd, 0, 0, hack.s)) sylvan_stats_count(LDD_SATCOUNT_CACHEDPUT); + + return hack.d; +} + +TASK_IMPL_1(long double, lddmc_satcount, MDD, mdd) +{ + if (mdd == lddmc_false) return 0.0; + if (mdd == lddmc_true) return 1.0; + + /* Perhaps execute garbage collection */ + sylvan_gc_test(); + + sylvan_stats_count(LDD_SATCOUNTL); + + union { + long double d; + struct { + uint64_t s1; + uint64_t s2; + } s; + } hack; + + if (cache_get3(CACHE_MDD_SATCOUNTL1, mdd, 0, 0, &hack.s.s1) && + cache_get3(CACHE_MDD_SATCOUNTL2, mdd, 0, 0, &hack.s.s2)) { + sylvan_stats_count(LDD_SATCOUNTL_CACHED); + return hack.d; + } + + mddnode_t n = GETNODE(mdd); + + SPAWN(lddmc_satcount, mddnode_getdown(n)); + long double right = CALL(lddmc_satcount, mddnode_getright(n)); + hack.d = right + SYNC(lddmc_satcount); + + int c1 = cache_put3(CACHE_MDD_SATCOUNTL1, mdd, 0, 0, hack.s.s1); + int c2 = cache_put3(CACHE_MDD_SATCOUNTL2, mdd, 0, 0, hack.s.s2); + if (c1 && c2) sylvan_stats_count(LDD_SATCOUNTL_CACHEDPUT); + + return hack.d; +} + +TASK_IMPL_5(MDD, lddmc_collect, MDD, mdd, lddmc_collect_cb, cb, void*, context, uint32_t*, values, size_t, count) +{ + if (mdd == lddmc_false) return lddmc_false; + if (mdd == lddmc_true) { + return WRAP(cb, values, count, context); + } + + mddnode_t n = GETNODE(mdd); + + lddmc_refs_spawn(SPAWN(lddmc_collect, mddnode_getright(n), cb, context, values, count)); + + uint32_t newvalues[count+1]; + if (count > 0) memcpy(newvalues, values, sizeof(uint32_t)*count); + newvalues[count] = mddnode_getvalue(n); + MDD down = CALL(lddmc_collect, mddnode_getdown(n), cb, context, newvalues, count+1); + + if (down == lddmc_false) { + MDD result = lddmc_refs_sync(SYNC(lddmc_collect)); + return result; + } + + lddmc_refs_push(down); + MDD right = lddmc_refs_sync(SYNC(lddmc_collect)); + + if (right == lddmc_false) { + lddmc_refs_pop(1); + return down; + } else { + lddmc_refs_push(right); + MDD result = CALL(lddmc_union, down, right); + lddmc_refs_pop(2); + return result; + } +} + +VOID_TASK_5(_lddmc_sat_all_nopar, MDD, mdd, lddmc_enum_cb, cb, void*, context, uint32_t*, values, size_t, count) +{ + if (mdd == lddmc_false) return; + if (mdd == lddmc_true) { + WRAP(cb, values, count, context); + return; + } + + mddnode_t n = GETNODE(mdd); + values[count] = mddnode_getvalue(n); + CALL(_lddmc_sat_all_nopar, mddnode_getdown(n), cb, context, values, count+1); + CALL(_lddmc_sat_all_nopar, mddnode_getright(n), cb, context, values, count); +} + +VOID_TASK_IMPL_3(lddmc_sat_all_nopar, MDD, mdd, lddmc_enum_cb, cb, void*, context) +{ + // determine depth + size_t count=0; + MDD _mdd = mdd; + while (_mdd > lddmc_true) { + _mdd = mddnode_getdown(GETNODE(_mdd)); + assert(_mdd != lddmc_false); + count++; + } + + uint32_t values[count]; + CALL(_lddmc_sat_all_nopar, mdd, cb, context, values, 0); +} + +VOID_TASK_IMPL_5(lddmc_sat_all_par, MDD, mdd, lddmc_enum_cb, cb, void*, context, uint32_t*, values, size_t, count) +{ + if (mdd == lddmc_false) return; + if (mdd == lddmc_true) { + WRAP(cb, values, count, context); + return; + } + + mddnode_t n = GETNODE(mdd); + + SPAWN(lddmc_sat_all_par, mddnode_getright(n), cb, context, values, count); + + uint32_t newvalues[count+1]; + if (count > 0) memcpy(newvalues, values, sizeof(uint32_t)*count); + newvalues[count] = mddnode_getvalue(n); + CALL(lddmc_sat_all_par, mddnode_getdown(n), cb, context, newvalues, count+1); + + SYNC(lddmc_sat_all_par); +} + +struct lddmc_match_sat_info +{ + MDD mdd; + MDD match; + MDD proj; + size_t count; + uint32_t values[0]; +}; + +// proj: -1 (rest 0), 0 (no match), 1 (match) +VOID_TASK_3(lddmc_match_sat, struct lddmc_match_sat_info *, info, lddmc_enum_cb, cb, void*, context) +{ + MDD a = info->mdd, b = info->match, proj = info->proj; + + if (a == lddmc_false || b == lddmc_false) return; + + if (a == lddmc_true) { + assert(b == lddmc_true); + WRAP(cb, info->values, info->count, context); + return; + } + + mddnode_t p_node = GETNODE(proj); + uint32_t p_val = mddnode_getvalue(p_node); + if (p_val == (uint32_t)-1) { + assert(b == lddmc_true); + CALL(lddmc_sat_all_par, a, cb, context, info->values, info->count); + return; + } + + /* Get nodes */ + mddnode_t na = GETNODE(a); + mddnode_t nb = GETNODE(b); + uint32_t na_value = mddnode_getvalue(na); + uint32_t nb_value = mddnode_getvalue(nb); + + /* Skip nodes if possible */ + if (p_val == 1) { + while (na_value != nb_value) { + if (na_value < nb_value) { + a = mddnode_getright(na); + if (a == lddmc_false) return; + na = GETNODE(a); + na_value = mddnode_getvalue(na); + } + if (nb_value < na_value) { + b = mddnode_getright(nb); + if (b == lddmc_false) return; + nb = GETNODE(b); + nb_value = mddnode_getvalue(nb); + } + } + } + + struct lddmc_match_sat_info *ri = (struct lddmc_match_sat_info*)alloca(sizeof(struct lddmc_match_sat_info)+sizeof(uint32_t[info->count])); + struct lddmc_match_sat_info *di = (struct lddmc_match_sat_info*)alloca(sizeof(struct lddmc_match_sat_info)+sizeof(uint32_t[info->count+1])); + + ri->mdd = mddnode_getright(na); + di->mdd = mddnode_getdown(na); + ri->match = b; + di->match = mddnode_getdown(nb); + ri->proj = proj; + di->proj = mddnode_getdown(p_node); + ri->count = info->count; + di->count = info->count+1; + if (ri->count > 0) memcpy(ri->values, info->values, sizeof(uint32_t[info->count])); + if (di->count > 0) memcpy(di->values, info->values, sizeof(uint32_t[info->count])); + di->values[info->count] = na_value; + + SPAWN(lddmc_match_sat, ri, cb, context); + CALL(lddmc_match_sat, di, cb, context); + SYNC(lddmc_match_sat); +} + +VOID_TASK_IMPL_5(lddmc_match_sat_par, MDD, mdd, MDD, match, MDD, proj, lddmc_enum_cb, cb, void*, context) +{ + struct lddmc_match_sat_info i; + i.mdd = mdd; + i.match = match; + i.proj = proj; + i.count = 0; + CALL(lddmc_match_sat, &i, cb, context); +} + +int +lddmc_sat_one(MDD mdd, uint32_t* values, size_t count) +{ + if (mdd == lddmc_false) return 0; + if (mdd == lddmc_true) return 1; + assert(count != 0); + mddnode_t n = GETNODE(mdd); + *values = mddnode_getvalue(n); + return lddmc_sat_one(mddnode_getdown(n), values+1, count-1); +} + +MDD +lddmc_sat_one_mdd(MDD mdd) +{ + if (mdd == lddmc_false) return lddmc_false; + if (mdd == lddmc_true) return lddmc_true; + mddnode_t n = GETNODE(mdd); + MDD down = lddmc_sat_one_mdd(mddnode_getdown(n)); + return lddmc_makenode(mddnode_getvalue(n), down, lddmc_false); +} + +TASK_IMPL_4(MDD, lddmc_compose, MDD, mdd, lddmc_compose_cb, cb, void*, context, int, depth) +{ + if (depth == 0 || mdd == lddmc_false || mdd == lddmc_true) { + return WRAP(cb, mdd, context); + } else { + mddnode_t n = GETNODE(mdd); + lddmc_refs_spawn(SPAWN(lddmc_compose, mddnode_getright(n), cb, context, depth)); + MDD down = lddmc_compose(mddnode_getdown(n), cb, context, depth-1); + lddmc_refs_push(down); + MDD right = lddmc_refs_sync(SYNC(lddmc_compose)); + lddmc_refs_pop(1); + return lddmc_makenode(mddnode_getvalue(n), down, right); + } +} + +VOID_TASK_IMPL_4(lddmc_visit_seq, MDD, mdd, lddmc_visit_callbacks_t*, cbs, size_t, ctx_size, void*, context) +{ + if (WRAP(cbs->lddmc_visit_pre, mdd, context) == 0) return; + + void* context_down = alloca(ctx_size); + void* context_right = alloca(ctx_size); + WRAP(cbs->lddmc_visit_init_context, context_down, context, 1); + WRAP(cbs->lddmc_visit_init_context, context_right, context, 0); + + CALL(lddmc_visit_seq, mddnode_getdown(GETNODE(mdd)), cbs, ctx_size, context_down); + CALL(lddmc_visit_seq, mddnode_getright(GETNODE(mdd)), cbs, ctx_size, context_right); + + WRAP(cbs->lddmc_visit_post, mdd, context); +} + +VOID_TASK_IMPL_4(lddmc_visit_par, MDD, mdd, lddmc_visit_callbacks_t*, cbs, size_t, ctx_size, void*, context) +{ + if (WRAP(cbs->lddmc_visit_pre, mdd, context) == 0) return; + + void* context_down = alloca(ctx_size); + void* context_right = alloca(ctx_size); + WRAP(cbs->lddmc_visit_init_context, context_down, context, 1); + WRAP(cbs->lddmc_visit_init_context, context_right, context, 0); + + SPAWN(lddmc_visit_par, mddnode_getdown(GETNODE(mdd)), cbs, ctx_size, context_down); + CALL(lddmc_visit_par, mddnode_getright(GETNODE(mdd)), cbs, ctx_size, context_right); + SYNC(lddmc_visit_par); + + WRAP(cbs->lddmc_visit_post, mdd, context); +} + +/** + * GENERIC MARK/UNMARK METHODS + */ + +static inline int +lddmc_mark(mddnode_t node) +{ + if (mddnode_getmark(node)) return 0; + mddnode_setmark(node, 1); + return 1; +} + +static inline int +lddmc_unmark(mddnode_t node) +{ + if (mddnode_getmark(node)) { + mddnode_setmark(node, 0); + return 1; + } else { + return 0; + } +} + +static void +lddmc_unmark_rec(mddnode_t node) +{ + if (lddmc_unmark(node)) { + MDD node_right = mddnode_getright(node); + if (node_right > lddmc_true) lddmc_unmark_rec(GETNODE(node_right)); + MDD node_down = mddnode_getdown(node); + if (node_down > lddmc_true) lddmc_unmark_rec(GETNODE(node_down)); + } +} + +/************* + * DOT OUTPUT +*************/ + +static void +lddmc_fprintdot_rec(FILE* out, MDD mdd) +{ + // assert(mdd > lddmc_true); + + // check mark + mddnode_t n = GETNODE(mdd); + if (mddnode_getmark(n)) return; + mddnode_setmark(n, 1); + + // print the node + uint32_t val = mddnode_getvalue(n); + fprintf(out, "%" PRIu64 " [shape=record, label=\"", mdd); + if (mddnode_getcopy(n)) fprintf(out, "<c> *"); + else fprintf(out, "<%u> %u", val, val); + MDD right = mddnode_getright(n); + while (right != lddmc_false) { + mddnode_t n2 = GETNODE(right); + uint32_t val2 = mddnode_getvalue(n2); + fprintf(out, "|<%u> %u", val2, val2); + right = mddnode_getright(n2); + // assert(right != lddmc_true); + } + fprintf(out, "\"];\n"); + + // recurse and print the edges + for (;;) { + MDD down = mddnode_getdown(n); + // assert(down != lddmc_false); + if (down > lddmc_true) { + lddmc_fprintdot_rec(out, down); + if (mddnode_getcopy(n)) { + fprintf(out, "%" PRIu64 ":c -> ", mdd); + } else { + fprintf(out, "%" PRIu64 ":%u -> ", mdd, mddnode_getvalue(n)); + } + if (mddnode_getcopy(GETNODE(down))) { + fprintf(out, "%" PRIu64 ":c [style=solid];\n", down); + } else { + fprintf(out, "%" PRIu64 ":%u [style=solid];\n", down, mddnode_getvalue(GETNODE(down))); + } + } + MDD right = mddnode_getright(n); + if (right == lddmc_false) break; + n = GETNODE(right); + } +} + +static void +lddmc_fprintdot_unmark(MDD mdd) +{ + if (mdd <= lddmc_true) return; + mddnode_t n = GETNODE(mdd); + if (mddnode_getmark(n)) { + mddnode_setmark(n, 0); + for (;;) { + lddmc_fprintdot_unmark(mddnode_getdown(n)); + mdd = mddnode_getright(n); + if (mdd == lddmc_false) return; + n = GETNODE(mdd); + } + } +} + +void +lddmc_fprintdot(FILE *out, MDD mdd) +{ + fprintf(out, "digraph \"DD\" {\n"); + fprintf(out, "graph [dpi = 300];\n"); + fprintf(out, "center = true;\n"); + fprintf(out, "edge [dir = forward];\n"); + + // Special case: false + if (mdd == lddmc_false) { + fprintf(out, "0 [shape=record, label=\"False\"];\n"); + fprintf(out, "}\n"); + return; + } + + // Special case: true + if (mdd == lddmc_true) { + fprintf(out, "1 [shape=record, label=\"True\"];\n"); + fprintf(out, "}\n"); + return; + } + + lddmc_fprintdot_rec(out, mdd); + lddmc_fprintdot_unmark(mdd); + + fprintf(out, "}\n"); +} + +void +lddmc_printdot(MDD mdd) +{ + lddmc_fprintdot(stdout, mdd); +} + +/** + * Some debug stuff + */ +void +lddmc_fprint(FILE *f, MDD mdd) +{ + lddmc_serialize_reset(); + size_t v = lddmc_serialize_add(mdd); + fprintf(f, "%zu,", v); + lddmc_serialize_totext(f); +} + +void +lddmc_print(MDD mdd) +{ + lddmc_fprint(stdout, mdd); +} + +/** + * SERIALIZATION + */ + +struct lddmc_ser { + MDD mdd; + size_t assigned; +}; + +// Define a AVL tree type with prefix 'lddmc_ser' holding +// nodes of struct lddmc_ser with the following compare() function... +AVL(lddmc_ser, struct lddmc_ser) +{ + if (left->mdd > right->mdd) return 1; + if (left->mdd < right->mdd) return -1; + return 0; +} + +// Define a AVL tree type with prefix 'lddmc_ser_reversed' holding +// nodes of struct lddmc_ser with the following compare() function... +AVL(lddmc_ser_reversed, struct lddmc_ser) +{ + if (left->assigned > right->assigned) return 1; + if (left->assigned < right->assigned) return -1; + return 0; +} + +// Initially, both sets are empty +static avl_node_t *lddmc_ser_set = NULL; +static avl_node_t *lddmc_ser_reversed_set = NULL; + +// Start counting (assigning numbers to MDDs) at 2 +static volatile size_t lddmc_ser_counter = 2; +static size_t lddmc_ser_done = 0; + +// Given a MDD, assign unique numbers to all nodes +static size_t +lddmc_serialize_assign_rec(MDD mdd) +{ + if (mdd <= lddmc_true) return mdd; + + mddnode_t n = GETNODE(mdd); + + struct lddmc_ser s, *ss; + s.mdd = mdd; + ss = lddmc_ser_search(lddmc_ser_set, &s); + if (ss == NULL) { + // assign dummy value + s.assigned = 0; + ss = lddmc_ser_put(&lddmc_ser_set, &s, NULL); + + // first assign recursively + lddmc_serialize_assign_rec(mddnode_getright(n)); + lddmc_serialize_assign_rec(mddnode_getdown(n)); + + // assign real value + ss->assigned = lddmc_ser_counter++; + + // put a copy in the reversed table + lddmc_ser_reversed_insert(&lddmc_ser_reversed_set, ss); + } + + return ss->assigned; +} + +size_t +lddmc_serialize_add(MDD mdd) +{ + return lddmc_serialize_assign_rec(mdd); +} + +void +lddmc_serialize_reset() +{ + lddmc_ser_free(&lddmc_ser_set); + lddmc_ser_free(&lddmc_ser_reversed_set); + lddmc_ser_counter = 2; + lddmc_ser_done = 0; +} + +size_t +lddmc_serialize_get(MDD mdd) +{ + if (mdd <= lddmc_true) return mdd; + struct lddmc_ser s, *ss; + s.mdd = mdd; + ss = lddmc_ser_search(lddmc_ser_set, &s); + assert(ss != NULL); + return ss->assigned; +} + +MDD +lddmc_serialize_get_reversed(size_t value) +{ + if ((MDD)value <= lddmc_true) return (MDD)value; + struct lddmc_ser s, *ss; + s.assigned = value; + ss = lddmc_ser_reversed_search(lddmc_ser_reversed_set, &s); + assert(ss != NULL); + return ss->mdd; +} + +void +lddmc_serialize_totext(FILE *out) +{ + avl_iter_t *it = lddmc_ser_reversed_iter(lddmc_ser_reversed_set); + struct lddmc_ser *s; + + fprintf(out, "["); + while ((s=lddmc_ser_reversed_iter_next(it))) { + MDD mdd = s->mdd; + mddnode_t n = GETNODE(mdd); + fprintf(out, "(%zu,v=%u,d=%zu,r=%zu),", s->assigned, + mddnode_getvalue(n), + lddmc_serialize_get(mddnode_getdown(n)), + lddmc_serialize_get(mddnode_getright(n))); + } + fprintf(out, "]"); + + lddmc_ser_reversed_iter_free(it); +} + +void +lddmc_serialize_tofile(FILE *out) +{ + size_t count = avl_count(lddmc_ser_reversed_set); + assert(count >= lddmc_ser_done); + assert(count == lddmc_ser_counter-2); + count -= lddmc_ser_done; + fwrite(&count, sizeof(size_t), 1, out); + + struct lddmc_ser *s; + avl_iter_t *it = lddmc_ser_reversed_iter(lddmc_ser_reversed_set); + + /* Skip already written entries */ + size_t index = 0; + while (index < lddmc_ser_done && (s=lddmc_ser_reversed_iter_next(it))) { + assert(s->assigned == index+2); + index++; + } + + while ((s=lddmc_ser_reversed_iter_next(it))) { + assert(s->assigned == index+2); + index++; + + mddnode_t n = GETNODE(s->mdd); + + struct mddnode node; + uint64_t right = lddmc_serialize_get(mddnode_getright(n)); + uint64_t down = lddmc_serialize_get(mddnode_getdown(n)); + if (mddnode_getcopy(n)) mddnode_makecopy(&node, right, down); + else mddnode_make(&node, mddnode_getvalue(n), right, down); + + assert(right <= index); + assert(down <= index); + + fwrite(&node, sizeof(struct mddnode), 1, out); + } + + lddmc_ser_done = lddmc_ser_counter-2; + lddmc_ser_reversed_iter_free(it); +} + +void +lddmc_serialize_fromfile(FILE *in) +{ + size_t count, i; + if (fread(&count, sizeof(size_t), 1, in) != 1) { + // TODO FIXME return error + printf("sylvan_serialize_fromfile: file format error, giving up\n"); + exit(-1); + } + + for (i=1; i<=count; i++) { + struct mddnode node; + if (fread(&node, sizeof(struct mddnode), 1, in) != 1) { + // TODO FIXME return error + printf("sylvan_serialize_fromfile: file format error, giving up\n"); + exit(-1); + } + + assert(mddnode_getright(&node) <= lddmc_ser_done+1); + assert(mddnode_getdown(&node) <= lddmc_ser_done+1); + + MDD right = lddmc_serialize_get_reversed(mddnode_getright(&node)); + MDD down = lddmc_serialize_get_reversed(mddnode_getdown(&node)); + + struct lddmc_ser s; + if (mddnode_getcopy(&node)) s.mdd = lddmc_make_copynode(down, right); + else s.mdd = lddmc_makenode(mddnode_getvalue(&node), down, right); + s.assigned = lddmc_ser_done+2; // starts at 0 but we want 2-based... + lddmc_ser_done++; + + lddmc_ser_insert(&lddmc_ser_set, &s); + lddmc_ser_reversed_insert(&lddmc_ser_reversed_set, &s); + } +} + +static void +lddmc_sha2_rec(MDD mdd, SHA256_CTX *ctx) +{ + if (mdd <= lddmc_true) { + SHA256_Update(ctx, (void*)&mdd, sizeof(uint64_t)); + return; + } + + mddnode_t node = GETNODE(mdd); + if (lddmc_mark(node)) { + uint32_t val = mddnode_getvalue(node); + SHA256_Update(ctx, (void*)&val, sizeof(uint32_t)); + lddmc_sha2_rec(mddnode_getdown(node), ctx); + lddmc_sha2_rec(mddnode_getright(node), ctx); + } +} + +void +lddmc_printsha(MDD mdd) +{ + lddmc_fprintsha(stdout, mdd); +} + +void +lddmc_fprintsha(FILE *out, MDD mdd) +{ + char buf[80]; + lddmc_getsha(mdd, buf); + fprintf(out, "%s", buf); +} + +void +lddmc_getsha(MDD mdd, char *target) +{ + SHA256_CTX ctx; + SHA256_Init(&ctx); + lddmc_sha2_rec(mdd, &ctx); + if (mdd > lddmc_true) lddmc_unmark_rec(GETNODE(mdd)); + SHA256_End(&ctx, target); +} + +#ifndef NDEBUG +size_t +lddmc_test_ismdd(MDD mdd) +{ + if (mdd == lddmc_true) return 1; + if (mdd == lddmc_false) return 0; + + int first = 1; + size_t depth = 0; + + if (mdd != lddmc_false) { + mddnode_t n = GETNODE(mdd); + if (mddnode_getcopy(n)) { + mdd = mddnode_getright(n); + depth = lddmc_test_ismdd(mddnode_getdown(n)); + assert(depth >= 1); + } + } + + uint32_t value = 0; + while (mdd != lddmc_false) { + assert(llmsset_is_marked(nodes, mdd)); + + mddnode_t n = GETNODE(mdd); + uint32_t next_value = mddnode_getvalue(n); + assert(mddnode_getcopy(n) == 0); + if (first) { + first = 0; + depth = lddmc_test_ismdd(mddnode_getdown(n)); + assert(depth >= 1); + } else { + assert(value < next_value); + assert(depth == lddmc_test_ismdd(mddnode_getdown(n))); + } + + value = next_value; + mdd = mddnode_getright(n); + } + + return 1 + depth; +} +#endif diff --git a/src/sylvan_ldd.h b/src/sylvan_ldd.h new file mode 100644 index 000000000..f104309b2 --- /dev/null +++ b/src/sylvan_ldd.h @@ -0,0 +1,288 @@ +/* + * Copyright 2011-2014 Formal Methods and Tools, University of Twente + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* Do not include this file directly. Instead, include sylvan.h */ + +#ifndef SYLVAN_LDD_H +#define SYLVAN_LDD_H + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +typedef uint64_t MDD; // Note: low 40 bits only + +#define lddmc_false ((MDD)0) +#define lddmc_true ((MDD)1) + +/* Initialize LDD functionality */ +void sylvan_init_ldd(); + +/* Primitives */ +MDD lddmc_makenode(uint32_t value, MDD ifeq, MDD ifneq); +MDD lddmc_extendnode(MDD mdd, uint32_t value, MDD ifeq); +uint32_t lddmc_getvalue(MDD mdd); +MDD lddmc_getdown(MDD mdd); +MDD lddmc_getright(MDD mdd); +MDD lddmc_follow(MDD mdd, uint32_t value); + +/** + * Copy nodes in relations. + * A copy node represents 'read x, then write x' for every x. + * In a read-write relation, use copy nodes twice, once on read level, once on write level. + * Copy nodes are only supported by relprod, relprev and union. + */ + +/* Primitive for special 'copy node' (for relprod/relprev) */ +MDD lddmc_make_copynode(MDD ifeq, MDD ifneq); +int lddmc_iscopy(MDD mdd); +MDD lddmc_followcopy(MDD mdd); + +/* Add or remove external reference to MDD */ +MDD lddmc_ref(MDD a); +void lddmc_deref(MDD a); + +/* For use in custom mark functions */ +VOID_TASK_DECL_1(lddmc_gc_mark_rec, MDD) +#define lddmc_gc_mark_rec(mdd) CALL(lddmc_gc_mark_rec, mdd) + +/* Return the number of external references */ +size_t lddmc_count_refs(); + +/* Mark MDD for "notify on dead" */ +#define lddmc_notify_ondead(mdd) llmsset_notify_ondead(nodes, mdd) + +/* Sanity check - returns depth of MDD including 'true' terminal or 0 for empty set */ +#ifndef NDEBUG +size_t lddmc_test_ismdd(MDD mdd); +#endif + +/* Operations for model checking */ +TASK_DECL_2(MDD, lddmc_union, MDD, MDD); +#define lddmc_union(a, b) CALL(lddmc_union, a, b) + +TASK_DECL_2(MDD, lddmc_minus, MDD, MDD); +#define lddmc_minus(a, b) CALL(lddmc_minus, a, b) + +TASK_DECL_3(MDD, lddmc_zip, MDD, MDD, MDD*); +#define lddmc_zip(a, b, res) CALL(lddmc_zip, a, b, res) + +TASK_DECL_2(MDD, lddmc_intersect, MDD, MDD); +#define lddmc_intersect(a, b) CALL(lddmc_intersect, a, b) + +TASK_DECL_3(MDD, lddmc_match, MDD, MDD, MDD); +#define lddmc_match(a, b, proj) CALL(lddmc_match, a, b, proj) + +MDD lddmc_union_cube(MDD a, uint32_t* values, size_t count); +int lddmc_member_cube(MDD a, uint32_t* values, size_t count); +MDD lddmc_cube(uint32_t* values, size_t count); + +MDD lddmc_union_cube_copy(MDD a, uint32_t* values, int* copy, size_t count); +int lddmc_member_cube_copy(MDD a, uint32_t* values, int* copy, size_t count); +MDD lddmc_cube_copy(uint32_t* values, int* copy, size_t count); + +TASK_DECL_3(MDD, lddmc_relprod, MDD, MDD, MDD); +#define lddmc_relprod(a, b, proj) CALL(lddmc_relprod, a, b, proj) + +TASK_DECL_4(MDD, lddmc_relprod_union, MDD, MDD, MDD, MDD); +#define lddmc_relprod_union(a, b, meta, un) CALL(lddmc_relprod_union, a, b, meta, un) + +/** + * Calculate all predecessors to a in uni according to rel[proj] + * <proj> follows the same semantics as relprod + * i.e. 0 (not in rel), 1 (read+write), 2 (read), 3 (write), -1 (end; rest=0) + */ +TASK_DECL_4(MDD, lddmc_relprev, MDD, MDD, MDD, MDD); +#define lddmc_relprev(a, rel, proj, uni) CALL(lddmc_relprev, a, rel, proj, uni) + +// so: proj: -2 (end; quantify rest), -1 (end; keep rest), 0 (quantify), 1 (keep) +TASK_DECL_2(MDD, lddmc_project, MDD, MDD); +#define lddmc_project(mdd, proj) CALL(lddmc_project, mdd, proj) + +TASK_DECL_3(MDD, lddmc_project_minus, MDD, MDD, MDD); +#define lddmc_project_minus(mdd, proj, avoid) CALL(lddmc_project_minus, mdd, proj, avoid) + +TASK_DECL_4(MDD, lddmc_join, MDD, MDD, MDD, MDD); +#define lddmc_join(a, b, a_proj, b_proj) CALL(lddmc_join, a, b, a_proj, b_proj) + +/* Write a DOT representation */ +void lddmc_printdot(MDD mdd); +void lddmc_fprintdot(FILE *out, MDD mdd); + +void lddmc_fprint(FILE *out, MDD mdd); +void lddmc_print(MDD mdd); + +void lddmc_printsha(MDD mdd); +void lddmc_fprintsha(FILE *out, MDD mdd); +void lddmc_getsha(MDD mdd, char *target); // at least 65 bytes... + +/** + * Calculate number of satisfying variable assignments. + * The set of variables must be >= the support of the MDD. + * (i.e. all variables in the MDD must be in variables) + * + * The cached version uses the operation cache, but is limited to 64-bit floating point numbers. + */ + +typedef double lddmc_satcount_double_t; +// if this line below gives an error, modify the above typedef until fixed ;) +typedef char __lddmc_check_float_is_8_bytes[(sizeof(lddmc_satcount_double_t) == sizeof(uint64_t))?1:-1]; + +TASK_DECL_1(lddmc_satcount_double_t, lddmc_satcount_cached, MDD); +#define lddmc_satcount_cached(mdd) CALL(lddmc_satcount_cached, mdd) + +TASK_DECL_1(long double, lddmc_satcount, MDD); +#define lddmc_satcount(mdd) CALL(lddmc_satcount, mdd) + +/** + * A callback for enumerating functions like sat_all_par, collect and match + * Example: + * TASK_3(void*, my_function, uint32_t*, values, size_t, count, void*, context) ... + * For collect, use: + * TASK_3(MDD, ...) + */ +LACE_TYPEDEF_CB(void, lddmc_enum_cb, uint32_t*, size_t, void*); +LACE_TYPEDEF_CB(MDD, lddmc_collect_cb, uint32_t*, size_t, void*); + +VOID_TASK_DECL_5(lddmc_sat_all_par, MDD, lddmc_enum_cb, void*, uint32_t*, size_t); +#define lddmc_sat_all_par(mdd, cb, context) CALL(lddmc_sat_all_par, mdd, cb, context, 0, 0) + +VOID_TASK_DECL_3(lddmc_sat_all_nopar, MDD, lddmc_enum_cb, void*); +#define lddmc_sat_all_nopar(mdd, cb, context) CALL(lddmc_sat_all_nopar, mdd, cb, context) + +TASK_DECL_5(MDD, lddmc_collect, MDD, lddmc_collect_cb, void*, uint32_t*, size_t); +#define lddmc_collect(mdd, cb, context) CALL(lddmc_collect, mdd, cb, context, 0, 0) + +VOID_TASK_DECL_5(lddmc_match_sat_par, MDD, MDD, MDD, lddmc_enum_cb, void*); +#define lddmc_match_sat_par(mdd, match, proj, cb, context) CALL(lddmc_match_sat_par, mdd, match, proj, cb, context) + +int lddmc_sat_one(MDD mdd, uint32_t *values, size_t count); +MDD lddmc_sat_one_mdd(MDD mdd); +#define lddmc_pick_cube lddmc_sat_one_mdd + +/** + * Callback functions for visiting nodes. + * lddmc_visit_seq sequentially visits nodes, down first, then right. + * lddmc_visit_par visits nodes in parallel (down || right) + */ +LACE_TYPEDEF_CB(int, lddmc_visit_pre_cb, MDD, void*); // int pre(MDD, context) +LACE_TYPEDEF_CB(void, lddmc_visit_post_cb, MDD, void*); // void post(MDD, context) +LACE_TYPEDEF_CB(void, lddmc_visit_init_context_cb, void*, void*, int); // void init_context(context, parent, is_down) + +typedef struct lddmc_visit_node_callbacks { + lddmc_visit_pre_cb lddmc_visit_pre; + lddmc_visit_post_cb lddmc_visit_post; + lddmc_visit_init_context_cb lddmc_visit_init_context; +} lddmc_visit_callbacks_t; + +VOID_TASK_DECL_4(lddmc_visit_par, MDD, lddmc_visit_callbacks_t*, size_t, void*); +#define lddmc_visit_par(mdd, cbs, ctx_size, context) CALL(lddmc_visit_par, mdd, cbs, ctx_size, context); + +VOID_TASK_DECL_4(lddmc_visit_seq, MDD, lddmc_visit_callbacks_t*, size_t, void*); +#define lddmc_visit_seq(mdd, cbs, ctx_size, context) CALL(lddmc_visit_seq, mdd, cbs, ctx_size, context); + +size_t lddmc_nodecount(MDD mdd); +void lddmc_nodecount_levels(MDD mdd, size_t *variables); + +/** + * Functional composition + * For every node at depth <depth>, call function cb (MDD -> MDD). + * and replace the node by the result of the function + */ +LACE_TYPEDEF_CB(MDD, lddmc_compose_cb, MDD, void*); +TASK_DECL_4(MDD, lddmc_compose, MDD, lddmc_compose_cb, void*, int); +#define lddmc_compose(mdd, cb, context, depth) CALL(lddmc_compose, mdd, cb, context, depth) + +/** + * SAVING: + * use lddmc_serialize_add on every MDD you want to store + * use lddmc_serialize_get to retrieve the key of every stored MDD + * use lddmc_serialize_tofile + * + * LOADING: + * use lddmc_serialize_fromfile (implies lddmc_serialize_reset) + * use lddmc_serialize_get_reversed for every key + * + * MISC: + * use lddmc_serialize_reset to free all allocated structures + * use lddmc_serialize_totext to write a textual list of tuples of all MDDs. + * format: [(<key>,<level>,<key_low>,<key_high>,<complement_high>),...] + * + * for the old lddmc_print functions, use lddmc_serialize_totext + */ +size_t lddmc_serialize_add(MDD mdd); +size_t lddmc_serialize_get(MDD mdd); +MDD lddmc_serialize_get_reversed(size_t value); +void lddmc_serialize_reset(); +void lddmc_serialize_totext(FILE *out); +void lddmc_serialize_tofile(FILE *out); +void lddmc_serialize_fromfile(FILE *in); + +/* Infrastructure for internal markings */ +typedef struct lddmc_refs_internal +{ + size_t r_size, r_count; + size_t s_size, s_count; + MDD *results; + Task **spawns; +} *lddmc_refs_internal_t; + +extern DECLARE_THREAD_LOCAL(lddmc_refs_key, lddmc_refs_internal_t); + +static inline MDD +lddmc_refs_push(MDD ldd) +{ + LOCALIZE_THREAD_LOCAL(lddmc_refs_key, lddmc_refs_internal_t); + if (lddmc_refs_key->r_count >= lddmc_refs_key->r_size) { + lddmc_refs_key->r_size *= 2; + lddmc_refs_key->results = (MDD*)realloc(lddmc_refs_key->results, sizeof(MDD) * lddmc_refs_key->r_size); + } + lddmc_refs_key->results[lddmc_refs_key->r_count++] = ldd; + return ldd; +} + +static inline void +lddmc_refs_pop(int amount) +{ + LOCALIZE_THREAD_LOCAL(lddmc_refs_key, lddmc_refs_internal_t); + lddmc_refs_key->r_count-=amount; +} + +static inline void +lddmc_refs_spawn(Task *t) +{ + LOCALIZE_THREAD_LOCAL(lddmc_refs_key, lddmc_refs_internal_t); + if (lddmc_refs_key->s_count >= lddmc_refs_key->s_size) { + lddmc_refs_key->s_size *= 2; + lddmc_refs_key->spawns = (Task**)realloc(lddmc_refs_key->spawns, sizeof(Task*) * lddmc_refs_key->s_size); + } + lddmc_refs_key->spawns[lddmc_refs_key->s_count++] = t; +} + +static inline MDD +lddmc_refs_sync(MDD result) +{ + LOCALIZE_THREAD_LOCAL(lddmc_refs_key, lddmc_refs_internal_t); + lddmc_refs_key->s_count--; + return result; +} + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif diff --git a/src/sylvan_mtbdd.c b/src/sylvan_mtbdd.c new file mode 100644 index 000000000..a4eb1bd62 --- /dev/null +++ b/src/sylvan_mtbdd.c @@ -0,0 +1,2542 @@ +/* + * Copyright 2011-2015 Formal Methods and Tools, University of Twente + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <sylvan_config.h> + +#include <assert.h> +#include <inttypes.h> +#include <math.h> +#include <pthread.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <refs.h> +#include <sha2.h> +#include <sylvan.h> +#include <sylvan_common.h> +#include <sylvan_mtbdd_int.h> + +/* Primitives */ +int +mtbdd_isleaf(MTBDD bdd) +{ + if (bdd == mtbdd_true || bdd == mtbdd_false) return 1; + return mtbddnode_isleaf(GETNODE(bdd)); +} + +// for nodes +uint32_t +mtbdd_getvar(MTBDD node) +{ + return mtbddnode_getvariable(GETNODE(node)); +} + +MTBDD +mtbdd_getlow(MTBDD mtbdd) +{ + return node_getlow(mtbdd, GETNODE(mtbdd)); +} + +MTBDD +mtbdd_gethigh(MTBDD mtbdd) +{ + return node_gethigh(mtbdd, GETNODE(mtbdd)); +} + +// for leaves +uint32_t +mtbdd_gettype(MTBDD leaf) +{ + return mtbddnode_gettype(GETNODE(leaf)); +} + +uint64_t +mtbdd_getvalue(MTBDD leaf) +{ + return mtbddnode_getvalue(GETNODE(leaf)); +} + +// for leaf type 0 (integer) +int64_t +mtbdd_getint64(MTBDD leaf) +{ + uint64_t value = mtbdd_getvalue(leaf); + return *(int64_t*)&value; +} + +// for leaf type 1 (double) +double +mtbdd_getdouble(MTBDD leaf) +{ + uint64_t value = mtbdd_getvalue(leaf); + return *(double*)&value; +} + +/** + * Implementation of garbage collection + */ + +/* Recursively mark MDD nodes as 'in use' */ +VOID_TASK_IMPL_1(mtbdd_gc_mark_rec, MDD, mtbdd) +{ + if (mtbdd == mtbdd_true) return; + if (mtbdd == mtbdd_false) return; + + if (llmsset_mark(nodes, mtbdd&(~mtbdd_complement))) { + mtbddnode_t n = GETNODE(mtbdd); + if (!mtbddnode_isleaf(n)) { + SPAWN(mtbdd_gc_mark_rec, mtbddnode_getlow(n)); + CALL(mtbdd_gc_mark_rec, mtbddnode_gethigh(n)); + SYNC(mtbdd_gc_mark_rec); + } + } +} + +/** + * External references + */ + +refs_table_t mtbdd_refs; +refs_table_t mtbdd_protected; +static int mtbdd_protected_created = 0; + +MDD +mtbdd_ref(MDD a) +{ + if (a == mtbdd_true || a == mtbdd_false) return a; + refs_up(&mtbdd_refs, a); + return a; +} + +void +mtbdd_deref(MDD a) +{ + if (a == mtbdd_true || a == mtbdd_false) return; + refs_down(&mtbdd_refs, a); +} + +size_t +mtbdd_count_refs() +{ + return refs_count(&mtbdd_refs); +} + +void +mtbdd_protect(MTBDD *a) +{ + if (!mtbdd_protected_created) { + // In C++, sometimes mtbdd_protect is called before Sylvan is initialized. Just create a table. + protect_create(&mtbdd_protected, 4096); + mtbdd_protected_created = 1; + } + protect_up(&mtbdd_protected, (size_t)a); +} + +void +mtbdd_unprotect(MTBDD *a) +{ + if (mtbdd_protected.refs_table != NULL) protect_down(&mtbdd_protected, (size_t)a); +} + +size_t +mtbdd_count_protected() +{ + return protect_count(&mtbdd_protected); +} + +/* Called during garbage collection */ +VOID_TASK_0(mtbdd_gc_mark_external_refs) +{ + // iterate through refs hash table, mark all found + size_t count=0; + uint64_t *it = refs_iter(&mtbdd_refs, 0, mtbdd_refs.refs_size); + while (it != NULL) { + SPAWN(mtbdd_gc_mark_rec, refs_next(&mtbdd_refs, &it, mtbdd_refs.refs_size)); + count++; + } + while (count--) { + SYNC(mtbdd_gc_mark_rec); + } +} + +VOID_TASK_0(mtbdd_gc_mark_protected) +{ + // iterate through refs hash table, mark all found + size_t count=0; + uint64_t *it = protect_iter(&mtbdd_protected, 0, mtbdd_protected.refs_size); + while (it != NULL) { + BDD *to_mark = (BDD*)protect_next(&mtbdd_protected, &it, mtbdd_protected.refs_size); + SPAWN(mtbdd_gc_mark_rec, *to_mark); + count++; + } + while (count--) { + SYNC(mtbdd_gc_mark_rec); + } +} + +/* Infrastructure for internal markings */ +DECLARE_THREAD_LOCAL(mtbdd_refs_key, mtbdd_refs_internal_t); + +VOID_TASK_0(mtbdd_refs_mark_task) +{ + LOCALIZE_THREAD_LOCAL(mtbdd_refs_key, mtbdd_refs_internal_t); + size_t i, j=0; + for (i=0; i<mtbdd_refs_key->r_count; i++) { + if (j >= 40) { + while (j--) SYNC(mtbdd_gc_mark_rec); + j=0; + } + SPAWN(mtbdd_gc_mark_rec, mtbdd_refs_key->results[i]); + j++; + } + for (i=0; i<mtbdd_refs_key->s_count; i++) { + Task *t = mtbdd_refs_key->spawns[i]; + if (!TASK_IS_STOLEN(t)) break; + if (TASK_IS_COMPLETED(t)) { + if (j >= 40) { + while (j--) SYNC(mtbdd_gc_mark_rec); + j=0; + } + SPAWN(mtbdd_gc_mark_rec, *(BDD*)TASK_RESULT(t)); + j++; + } + } + while (j--) SYNC(mtbdd_gc_mark_rec); +} + +VOID_TASK_0(mtbdd_refs_mark) +{ + TOGETHER(mtbdd_refs_mark_task); +} + +VOID_TASK_0(mtbdd_refs_init_task) +{ + mtbdd_refs_internal_t s = (mtbdd_refs_internal_t)malloc(sizeof(struct mtbdd_refs_internal)); + s->r_size = 128; + s->r_count = 0; + s->s_size = 128; + s->s_count = 0; + s->results = (BDD*)malloc(sizeof(BDD) * 128); + s->spawns = (Task**)malloc(sizeof(Task*) * 128); + SET_THREAD_LOCAL(mtbdd_refs_key, s); +} + +VOID_TASK_0(mtbdd_refs_init) +{ + INIT_THREAD_LOCAL(mtbdd_refs_key); + TOGETHER(mtbdd_refs_init_task); + sylvan_gc_add_mark(10, TASK(mtbdd_refs_mark)); +} + +/** + * Handling of custom leaves "registry" + */ + +typedef struct +{ + mtbdd_hash_cb hash_cb; + mtbdd_equals_cb equals_cb; + mtbdd_create_cb create_cb; + mtbdd_destroy_cb destroy_cb; +} customleaf_t; + +static customleaf_t *cl_registry; +static size_t cl_registry_count; + +static void +_mtbdd_create_cb(uint64_t *a, uint64_t *b) +{ + // for leaf + if ((*a & 0x4000000000000000) == 0) return; // huh? + uint32_t type = *a & 0xffffffff; + if (type >= cl_registry_count) return; // not in registry + customleaf_t *c = cl_registry + type; + if (c->create_cb == NULL) return; // not in registry + c->create_cb(b); +} + +static void +_mtbdd_destroy_cb(uint64_t a, uint64_t b) +{ + // for leaf + if ((a & 0x4000000000000000) == 0) return; // huh? + uint32_t type = a & 0xffffffff; + if (type >= cl_registry_count) return; // not in registry + customleaf_t *c = cl_registry + type; + if (c->destroy_cb == NULL) return; // not in registry + c->destroy_cb(b); +} + +static uint64_t +_mtbdd_hash_cb(uint64_t a, uint64_t b, uint64_t seed) +{ + // for leaf + if ((a & 0x4000000000000000) == 0) return llmsset_hash(a, b, seed); + uint32_t type = a & 0xffffffff; + if (type >= cl_registry_count) return llmsset_hash(a, b, seed); + customleaf_t *c = cl_registry + type; + if (c->hash_cb == NULL) return llmsset_hash(a, b, seed); + return c->hash_cb(b, seed ^ a); +} + +static int +_mtbdd_equals_cb(uint64_t a, uint64_t b, uint64_t aa, uint64_t bb) +{ + // for leaf + if (a != aa) return 0; + if ((a & 0x4000000000000000) == 0) return b == bb ? 1 : 0; + if ((aa & 0x4000000000000000) == 0) return b == bb ? 1 : 0; + uint32_t type = a & 0xffffffff; + if (type >= cl_registry_count) return b == bb ? 1 : 0; + customleaf_t *c = cl_registry + type; + if (c->equals_cb == NULL) return b == bb ? 1 : 0; + return c->equals_cb(b, bb); +} + +uint32_t +mtbdd_register_custom_leaf(mtbdd_hash_cb hash_cb, mtbdd_equals_cb equals_cb, mtbdd_create_cb create_cb, mtbdd_destroy_cb destroy_cb) +{ + uint32_t type = cl_registry_count; + if (type == 0) type = 3; + if (cl_registry == NULL) { + cl_registry = (customleaf_t *)calloc(sizeof(customleaf_t), (type+1)); + cl_registry_count = type+1; + llmsset_set_custom(nodes, _mtbdd_hash_cb, _mtbdd_equals_cb, _mtbdd_create_cb, _mtbdd_destroy_cb); + } else if (cl_registry_count <= type) { + cl_registry = (customleaf_t *)realloc(cl_registry, sizeof(customleaf_t) * (type+1)); + memset(cl_registry + cl_registry_count, 0, sizeof(customleaf_t) * (type+1-cl_registry_count)); + cl_registry_count = type+1; + } + customleaf_t *c = cl_registry + type; + c->hash_cb = hash_cb; + c->equals_cb = equals_cb; + c->create_cb = create_cb; + c->destroy_cb = destroy_cb; + return type; +} + +/** + * Initialize and quit functions + */ + +static void +mtbdd_quit() +{ + refs_free(&mtbdd_refs); + if (mtbdd_protected_created) { + protect_free(&mtbdd_protected); + mtbdd_protected_created = 0; + } + if (cl_registry != NULL) { + free(cl_registry); + cl_registry = NULL; + cl_registry_count = 0; + } +} + +void +sylvan_init_mtbdd() +{ + sylvan_register_quit(mtbdd_quit); + sylvan_gc_add_mark(10, TASK(mtbdd_gc_mark_external_refs)); + sylvan_gc_add_mark(10, TASK(mtbdd_gc_mark_protected)); + + // Sanity check + if (sizeof(struct mtbddnode) != 16) { + fprintf(stderr, "Invalid size of mtbdd nodes: %ld\n", sizeof(struct mtbddnode)); + exit(1); + } + + refs_create(&mtbdd_refs, 1024); + if (!mtbdd_protected_created) { + protect_create(&mtbdd_protected, 4096); + mtbdd_protected_created = 1; + } + + LACE_ME; + CALL(mtbdd_refs_init); + + cl_registry = NULL; + cl_registry_count = 0; +} + +/** + * Primitives + */ +MTBDD +mtbdd_makeleaf(uint32_t type, uint64_t value) +{ + struct mtbddnode n; + mtbddnode_makeleaf(&n, type, value); + + int custom = type < cl_registry_count && cl_registry[type].hash_cb != NULL ? 1 : 0; + + int created; + uint64_t index = custom ? llmsset_lookupc(nodes, n.a, n.b, &created) : llmsset_lookup(nodes, n.a, n.b, &created); + if (index == 0) { + LACE_ME; + + sylvan_gc(); + + index = custom ? llmsset_lookupc(nodes, n.a, n.b, &created) : llmsset_lookup(nodes, n.a, n.b, &created); + if (index == 0) { + fprintf(stderr, "BDD Unique table full, %zu of %zu buckets filled!\n", llmsset_count_marked(nodes), llmsset_get_size(nodes)); + exit(1); + } + } + + return (MTBDD)index; +} + +MTBDD +mtbdd_makenode(uint32_t var, MTBDD low, MTBDD high) +{ + if (low == high) return low; + + // Normalization to keep canonicity + // low will have no mark + + struct mtbddnode n; + int mark, created; + + if (MTBDD_HASMARK(low)) { + mark = 1; + low = MTBDD_TOGGLEMARK(low); + high = MTBDD_TOGGLEMARK(high); + } else { + mark = 0; + } + + mtbddnode_makenode(&n, var, low, high); + + MTBDD result; + uint64_t index = llmsset_lookup(nodes, n.a, n.b, &created); + if (index == 0) { + LACE_ME; + + mtbdd_refs_push(low); + mtbdd_refs_push(high); + sylvan_gc(); + mtbdd_refs_pop(2); + + index = llmsset_lookup(nodes, n.a, n.b, &created); + if (index == 0) { + fprintf(stderr, "BDD Unique table full, %zu of %zu buckets filled!\n", llmsset_count_marked(nodes), llmsset_get_size(nodes)); + exit(1); + } + } + + result = index; + return mark ? result | mtbdd_complement : result; +} + +/* Operations */ + +/** + * Calculate greatest common divisor + * Source: http://lemire.me/blog/archives/2013/12/26/fastest-way-to-compute-the-greatest-common-divisor/ + */ +uint32_t +gcd(uint32_t u, uint32_t v) +{ + int shift; + if (u == 0) return v; + if (v == 0) return u; + shift = __builtin_ctz(u | v); + u >>= __builtin_ctz(u); + do { + v >>= __builtin_ctz(v); + if (u > v) { + unsigned int t = v; + v = u; + u = t; + } + v = v - u; + } while (v != 0); + return u << shift; +} + +/** + * Create leaves of unsigned/signed integers and doubles + */ + +MTBDD +mtbdd_int64(int64_t value) +{ + return mtbdd_makeleaf(0, *(uint64_t*)&value); +} + +MTBDD +mtbdd_double(double value) +{ + return mtbdd_makeleaf(1, *(uint64_t*)&value); +} + +MTBDD +mtbdd_fraction(int64_t nom, uint64_t denom) +{ + if (nom == 0) return mtbdd_makeleaf(2, 1); + uint32_t c = gcd(nom < 0 ? -nom : nom, denom); + nom /= c; + denom /= c; + if (nom > 2147483647 || nom < -2147483647 || denom > 4294967295) fprintf(stderr, "mtbdd_fraction: fraction overflow\n"); + return mtbdd_makeleaf(2, (nom<<32)|denom); +} + +/** + * Create the cube of variables in arr. + */ +MTBDD +mtbdd_fromarray(uint32_t* arr, size_t length) +{ + if (length == 0) return mtbdd_true; + else if (length == 1) return mtbdd_makenode(*arr, mtbdd_false, mtbdd_true); + else return mtbdd_makenode(*arr, mtbdd_false, mtbdd_fromarray(arr+1, length-1)); +} + +/** + * Create a MTBDD cube representing the conjunction of variables in their positive or negative + * form depending on whether the cube[idx] equals 0 (negative), 1 (positive) or 2 (any). + * Use cube[idx]==3 for "s=s'" in interleaved variables (matches with next variable) + * <variables> is the cube of variables + */ +MTBDD +mtbdd_cube(MTBDD variables, uint8_t *cube, MTBDD terminal) +{ + if (variables == mtbdd_true) return terminal; + mtbddnode_t n = GETNODE(variables); + + BDD result; + switch (*cube) { + case 0: + result = mtbdd_cube(node_gethigh(variables, n), cube+1, terminal); + result = mtbdd_makenode(mtbddnode_getvariable(n), result, mtbdd_false); + return result; + case 1: + result = mtbdd_cube(node_gethigh(variables, n), cube+1, terminal); + result = mtbdd_makenode(mtbddnode_getvariable(n), mtbdd_false, result); + return result; + case 2: + return mtbdd_cube(node_gethigh(variables, n), cube+1, terminal); + case 3: + { + MTBDD variables2 = node_gethigh(variables, n); + mtbddnode_t n2 = GETNODE(variables2); + uint32_t var2 = mtbddnode_getvariable(n2); + result = mtbdd_cube(node_gethigh(variables2, n2), cube+2, terminal); + BDD low = mtbdd_makenode(var2, result, mtbdd_false); + mtbdd_refs_push(low); + BDD high = mtbdd_makenode(var2, mtbdd_false, result); + mtbdd_refs_pop(1); + result = mtbdd_makenode(mtbddnode_getvariable(n), low, high); + return result; + } + default: + return mtbdd_false; // ? + } +} + +/** + * Same as mtbdd_cube, but also performs "or" with existing MTBDD, + * effectively adding an item to the set + */ +TASK_IMPL_4(MTBDD, mtbdd_union_cube, MTBDD, mtbdd, MTBDD, vars, uint8_t*, cube, MTBDD, terminal) +{ + /* Terminal cases */ + if (mtbdd == terminal) return terminal; + if (mtbdd == mtbdd_false) return mtbdd_cube(vars, cube, terminal); + if (vars == mtbdd_true) return terminal; + + sylvan_gc_test(); + + mtbddnode_t nv = GETNODE(vars); + uint32_t v = mtbddnode_getvariable(nv); + + mtbddnode_t na = GETNODE(mtbdd); + uint32_t va = mtbddnode_getvariable(na); + + if (va < v) { + MTBDD low = node_getlow(mtbdd, na); + MTBDD high = node_gethigh(mtbdd, na); + SPAWN(mtbdd_union_cube, high, vars, cube, terminal); + BDD new_low = mtbdd_union_cube(low, vars, cube, terminal); + mtbdd_refs_push(new_low); + BDD new_high = SYNC(mtbdd_union_cube); + mtbdd_refs_pop(1); + if (new_low != low || new_high != high) return mtbdd_makenode(va, new_low, new_high); + else return mtbdd; + } else if (va == v) { + MTBDD low = node_getlow(mtbdd, na); + MTBDD high = node_gethigh(mtbdd, na); + switch (*cube) { + case 0: + { + MTBDD new_low = mtbdd_union_cube(low, node_gethigh(vars, nv), cube+1, terminal); + if (new_low != low) return mtbdd_makenode(v, new_low, high); + else return mtbdd; + } + case 1: + { + MTBDD new_high = mtbdd_union_cube(high, node_gethigh(vars, nv), cube+1, terminal); + if (new_high != high) return mtbdd_makenode(v, low, new_high); + return mtbdd; + } + case 2: + { + SPAWN(mtbdd_union_cube, high, node_gethigh(vars, nv), cube+1, terminal); + MTBDD new_low = mtbdd_union_cube(low, node_gethigh(vars, nv), cube+1, terminal); + mtbdd_refs_push(new_low); + MTBDD new_high = SYNC(mtbdd_union_cube); + mtbdd_refs_pop(1); + if (new_low != low || new_high != high) return mtbdd_makenode(v, new_low, new_high); + return mtbdd; + } + case 3: + { + return mtbdd_false; // currently not implemented + } + default: + return mtbdd_false; + } + } else /* va > v */ { + switch (*cube) { + case 0: + { + MTBDD new_low = mtbdd_union_cube(mtbdd, node_gethigh(vars, nv), cube+1, terminal); + return mtbdd_makenode(v, new_low, mtbdd_false); + } + case 1: + { + MTBDD new_high = mtbdd_union_cube(mtbdd, node_gethigh(vars, nv), cube+1, terminal); + return mtbdd_makenode(v, mtbdd_false, new_high); + } + case 2: + { + SPAWN(mtbdd_union_cube, mtbdd, node_gethigh(vars, nv), cube+1, terminal); + MTBDD new_low = mtbdd_union_cube(mtbdd, node_gethigh(vars, nv), cube+1, terminal); + mtbdd_refs_push(new_low); + MTBDD new_high = SYNC(mtbdd_union_cube); + mtbdd_refs_pop(1); + return mtbdd_makenode(v, new_low, new_high); + } + case 3: + { + return mtbdd_false; // currently not implemented + } + default: + return mtbdd_false; + } + } +} + +/** + * Apply a binary operation <op> to <a> and <b>. + */ +TASK_IMPL_3(MTBDD, mtbdd_apply, MTBDD, a, MTBDD, b, mtbdd_apply_op, op) +{ + /* Check terminal case */ + MTBDD result = WRAP(op, &a, &b); + if (result != mtbdd_invalid) return result; + + /* Maybe perform garbage collection */ + sylvan_gc_test(); + + /* Check cache */ + if (cache_get3(CACHE_MTBDD_APPLY, a, b, (size_t)op, &result)) return result; + + /* Get top variable */ + int la = mtbdd_isleaf(a); + int lb = mtbdd_isleaf(b); + mtbddnode_t na, nb; + uint32_t va, vb; + if (!la) { + na = GETNODE(a); + va = mtbddnode_getvariable(na); + } else { + na = 0; + va = 0xffffffff; + } + if (!lb) { + nb = GETNODE(b); + vb = mtbddnode_getvariable(nb); + } else { + nb = 0; + vb = 0xffffffff; + } + uint32_t v = va < vb ? va : vb; + + /* Get cofactors */ + MTBDD alow, ahigh, blow, bhigh; + if (!la && va == v) { + alow = node_getlow(a, na); + ahigh = node_gethigh(a, na); + } else { + alow = a; + ahigh = a; + } + if (!lb && vb == v) { + blow = node_getlow(b, nb); + bhigh = node_gethigh(b, nb); + } else { + blow = b; + bhigh = b; + } + + /* Recursive */ + mtbdd_refs_spawn(SPAWN(mtbdd_apply, ahigh, bhigh, op)); + MTBDD low = mtbdd_refs_push(CALL(mtbdd_apply, alow, blow, op)); + MTBDD high = mtbdd_refs_sync(SYNC(mtbdd_apply)); + mtbdd_refs_pop(1); + result = mtbdd_makenode(v, low, high); + + /* Store in cache */ + cache_put3(CACHE_MTBDD_APPLY, a, b, (size_t)op, result); + return result; +} + +/** + * Apply a binary operation <op> to <a> and <b> with parameter <p> + */ +TASK_IMPL_5(MTBDD, mtbdd_applyp, MTBDD, a, MTBDD, b, size_t, p, mtbdd_applyp_op, op, uint64_t, opid) +{ + /* Check terminal case */ + MTBDD result = WRAP(op, &a, &b, p); + if (result != mtbdd_invalid) return result; + + /* Maybe perform garbage collection */ + sylvan_gc_test(); + + /* Check cache */ + if (cache_get3(opid, a, b, p, &result)) return result; + + /* Get top variable */ + int la = mtbdd_isleaf(a); + int lb = mtbdd_isleaf(b); + mtbddnode_t na, nb; + uint32_t va, vb; + if (!la) { + na = GETNODE(a); + va = mtbddnode_getvariable(na); + } else { + na = 0; + va = 0xffffffff; + } + if (!lb) { + nb = GETNODE(b); + vb = mtbddnode_getvariable(nb); + } else { + nb = 0; + vb = 0xffffffff; + } + uint32_t v = va < vb ? va : vb; + + /* Get cofactors */ + MTBDD alow, ahigh, blow, bhigh; + if (!la && va == v) { + alow = node_getlow(a, na); + ahigh = node_gethigh(a, na); + } else { + alow = a; + ahigh = a; + } + if (!lb && vb == v) { + blow = node_getlow(b, nb); + bhigh = node_gethigh(b, nb); + } else { + blow = b; + bhigh = b; + } + + /* Recursive */ + mtbdd_refs_spawn(SPAWN(mtbdd_applyp, ahigh, bhigh, p, op, opid)); + MTBDD low = mtbdd_refs_push(CALL(mtbdd_applyp, alow, blow, p, op, opid)); + MTBDD high = mtbdd_refs_sync(SYNC(mtbdd_applyp)); + mtbdd_refs_pop(1); + result = mtbdd_makenode(v, low, high); + + /* Store in cache */ + cache_put3(opid, a, b, p, result); + return result; +} + +/** + * Apply a unary operation <op> to <dd>. + */ +TASK_IMPL_3(MTBDD, mtbdd_uapply, MTBDD, dd, mtbdd_uapply_op, op, size_t, param) +{ + /* Maybe perform garbage collection */ + sylvan_gc_test(); + + /* Check cache */ + MTBDD result; + if (cache_get3(CACHE_MTBDD_UAPPLY, dd, (size_t)op, param, &result)) return result; + + /* Check terminal case */ + result = WRAP(op, dd, param); + if (result != mtbdd_invalid) { + /* Store in cache */ + cache_put3(CACHE_MTBDD_UAPPLY, dd, (size_t)op, param, result); + return result; + } + + /* Get cofactors */ + mtbddnode_t ndd = GETNODE(dd); + MTBDD ddlow = node_getlow(dd, ndd); + MTBDD ddhigh = node_gethigh(dd, ndd); + + /* Recursive */ + mtbdd_refs_spawn(SPAWN(mtbdd_uapply, ddhigh, op, param)); + MTBDD low = mtbdd_refs_push(CALL(mtbdd_uapply, ddlow, op, param)); + MTBDD high = mtbdd_refs_sync(SYNC(mtbdd_uapply)); + mtbdd_refs_pop(1); + result = mtbdd_makenode(mtbddnode_getvariable(ndd), low, high); + + /* Store in cache */ + cache_put3(CACHE_MTBDD_UAPPLY, dd, (size_t)op, param, result); + return result; +} + +TASK_2(MTBDD, mtbdd_uop_times_uint, MTBDD, a, size_t, k) +{ + if (a == mtbdd_false) return mtbdd_false; + if (a == mtbdd_true) return mtbdd_true; + + // a != constant + mtbddnode_t na = GETNODE(a); + + if (mtbddnode_isleaf(na)) { + if (mtbddnode_gettype(na) == 0) { + int64_t v = mtbdd_getint64(a); + return mtbdd_int64(v*k); + } else if (mtbddnode_gettype(na) == 1) { + double d = mtbdd_getdouble(a); + return mtbdd_double(d*k); + } else if (mtbddnode_gettype(na) == 2) { + uint64_t v = mtbddnode_getvalue(na); + int64_t n = (int32_t)(v>>32); + uint32_t d = v; + uint32_t c = gcd(d, (uint32_t)k); + return mtbdd_fraction(n*(k/c), d/c); + } + } + + return mtbdd_invalid; +} + +TASK_2(MTBDD, mtbdd_uop_pow_uint, MTBDD, a, size_t, k) +{ + if (a == mtbdd_false) return mtbdd_false; + if (a == mtbdd_true) return mtbdd_true; + + // a != constant + mtbddnode_t na = GETNODE(a); + + if (mtbddnode_isleaf(na)) { + if (mtbddnode_gettype(na) == 0) { + int64_t v = mtbdd_getint64(a); + return mtbdd_int64(pow(v, k)); + } else if (mtbddnode_gettype(na) == 1) { + double d = mtbdd_getdouble(a); + return mtbdd_double(pow(d, k)); + } else if (mtbddnode_gettype(na) == 2) { + uint64_t v = mtbddnode_getvalue(na); + return mtbdd_fraction(pow((int32_t)(v>>32), k), (uint32_t)v); + } + } + + return mtbdd_invalid; +} + +TASK_IMPL_3(MTBDD, mtbdd_abstract_op_plus, MTBDD, a, MTBDD, b, int, k) +{ + if (k==0) { + return mtbdd_apply(a, b, TASK(mtbdd_op_plus)); + } else { + uint64_t factor = 1ULL<<k; // skip 1,2,3,4: times 2,4,8,16 + return mtbdd_uapply(a, TASK(mtbdd_uop_times_uint), factor); + } +} + +TASK_IMPL_3(MTBDD, mtbdd_abstract_op_times, MTBDD, a, MTBDD, b, int, k) +{ + if (k==0) { + return mtbdd_apply(a, b, TASK(mtbdd_op_times)); + } else { + uint64_t squares = 1ULL<<k; // square k times, ie res^(2^k): 2,4,8,16 + return mtbdd_uapply(a, TASK(mtbdd_uop_pow_uint), squares); + } +} + +TASK_IMPL_3(MTBDD, mtbdd_abstract_op_min, MTBDD, a, MTBDD, b, int, k) +{ + return k == 0 ? mtbdd_apply(a, b, TASK(mtbdd_op_min)) : a; +} + +TASK_IMPL_3(MTBDD, mtbdd_abstract_op_max, MTBDD, a, MTBDD, b, int, k) +{ + return k == 0 ? mtbdd_apply(a, b, TASK(mtbdd_op_max)) : a; +} + +/** + * Abstract the variables in <v> from <a> using the operation <op> + */ +TASK_IMPL_3(MTBDD, mtbdd_abstract, MTBDD, a, MTBDD, v, mtbdd_abstract_op, op) +{ + /* Check terminal case */ + if (a == mtbdd_false) return mtbdd_false; + if (a == mtbdd_true) return mtbdd_true; + if (v == mtbdd_true) return a; + + /* Maybe perform garbage collection */ + sylvan_gc_test(); + + /* a != constant, v != constant */ + mtbddnode_t na = GETNODE(a); + + if (mtbddnode_isleaf(na)) { + /* Count number of variables */ + uint64_t k = 0; + while (v != mtbdd_true) { + k++; + v = node_gethigh(v, GETNODE(v)); + } + + /* Check cache */ + MTBDD result; + if (cache_get3(CACHE_MTBDD_ABSTRACT, a, v | (k << 40), (size_t)op, &result)) return result; + + /* Compute result */ + result = WRAP(op, a, a, k); + + /* Store in cache */ + cache_put3(CACHE_MTBDD_ABSTRACT, a, v | (k << 40), (size_t)op, result); + return result; + } + + /* Possibly skip k variables */ + mtbddnode_t nv = GETNODE(v); + uint32_t var_a = mtbddnode_getvariable(na); + uint32_t var_v = mtbddnode_getvariable(nv); + uint64_t k = 0; + while (var_v < var_a) { + k++; + v = node_gethigh(v, nv); + if (v == mtbdd_true) break; + nv = GETNODE(v); + var_v = mtbddnode_getvariable(nv); + } + + /* Check cache */ + MTBDD result; + if (cache_get3(CACHE_MTBDD_ABSTRACT, a, v | (k << 40), (size_t)op, &result)) return result; + + /* Recursive */ + if (v == mtbdd_true) { + result = a; + } else if (var_a < var_v) { + mtbdd_refs_spawn(SPAWN(mtbdd_abstract, node_gethigh(a, na), v, op)); + MTBDD low = mtbdd_refs_push(CALL(mtbdd_abstract, node_getlow(a, na), v, op)); + MTBDD high = mtbdd_refs_sync(SYNC(mtbdd_abstract)); + mtbdd_refs_pop(1); + result = mtbdd_makenode(var_a, low, high); + } else /* var_a == var_v */ { + mtbdd_refs_spawn(SPAWN(mtbdd_abstract, node_gethigh(a, na), node_gethigh(v, nv), op)); + MTBDD low = mtbdd_refs_push(CALL(mtbdd_abstract, node_getlow(a, na), node_gethigh(v, nv), op)); + MTBDD high = mtbdd_refs_push(mtbdd_refs_sync(SYNC(mtbdd_abstract))); + result = WRAP(op, low, high, 0); + mtbdd_refs_pop(2); + } + + if (k) { + mtbdd_refs_push(result); + result = WRAP(op, result, result, k); + mtbdd_refs_pop(1); + } + + /* Store in cache */ + cache_put3(CACHE_MTBDD_ABSTRACT, a, v | (k << 40), (size_t)op, result); + return result; +} + +/** + * Binary operation Plus (for MTBDDs of same type) + * Only for MTBDDs where either all leaves are Boolean, or Integer, or Double. + * For Integer/Double MTBDDs, mtbdd_false is interpreted as "0" or "0.0". + */ +TASK_IMPL_2(MTBDD, mtbdd_op_plus, MTBDD*, pa, MTBDD*, pb) +{ + MTBDD a = *pa, b = *pb; + if (a == mtbdd_false) return b; + if (b == mtbdd_false) return a; + + // Handle Boolean MTBDDs: interpret as Or + if (a == mtbdd_true) return mtbdd_true; + if (b == mtbdd_true) return mtbdd_true; + + mtbddnode_t na = GETNODE(a); + mtbddnode_t nb = GETNODE(b); + + if (mtbddnode_isleaf(na) && mtbddnode_isleaf(nb)) { + uint64_t val_a = mtbddnode_getvalue(na); + uint64_t val_b = mtbddnode_getvalue(nb); + if (mtbddnode_gettype(na) == 0 && mtbddnode_gettype(nb) == 0) { + // both integer + return mtbdd_int64(*(int64_t*)(&val_a) + *(int64_t*)(&val_b)); + } else if (mtbddnode_gettype(na) == 1 && mtbddnode_gettype(nb) == 1) { + // both double + return mtbdd_double(*(double*)(&val_a) + *(double*)(&val_b)); + } else if (mtbddnode_gettype(na) == 2 && mtbddnode_gettype(nb) == 2) { + // both fraction + int64_t nom_a = (int32_t)(val_a>>32); + int64_t nom_b = (int32_t)(val_b>>32); + uint64_t denom_a = val_a&0xffffffff; + uint64_t denom_b = val_b&0xffffffff; + // common cases + if (nom_a == 0) return b; + if (nom_b == 0) return a; + // equalize denominators + uint32_t c = gcd(denom_a, denom_b); + nom_a *= denom_b/c; + nom_b *= denom_a/c; + denom_a *= denom_b/c; + // add + return mtbdd_fraction(nom_a + nom_b, denom_a); + } + } + + if (a < b) { + *pa = b; + *pb = a; + } + + return mtbdd_invalid; +} + +/** + * Binary operation Minus (for MTBDDs of same type) + * Only for MTBDDs where either all leaves are Boolean, or Integer, or Double. + * For Integer/Double MTBDDs, mtbdd_false is interpreted as "0" or "0.0". + */ +TASK_IMPL_2(MTBDD, mtbdd_op_minus, MTBDD*, pa, MTBDD*, pb) +{ + MTBDD a = *pa, b = *pb; + if (a == mtbdd_false) return mtbdd_negate(b); + if (b == mtbdd_false) return a; + + mtbddnode_t na = GETNODE(a); + mtbddnode_t nb = GETNODE(b); + + if (mtbddnode_isleaf(na) && mtbddnode_isleaf(nb)) { + uint64_t val_a = mtbddnode_getvalue(na); + uint64_t val_b = mtbddnode_getvalue(nb); + if (mtbddnode_gettype(na) == 0 && mtbddnode_gettype(nb) == 0) { + // both integer + return mtbdd_int64(*(int64_t*)(&val_a) - *(int64_t*)(&val_b)); + } else if (mtbddnode_gettype(na) == 1 && mtbddnode_gettype(nb) == 1) { + // both double + return mtbdd_double(*(double*)(&val_a) - *(double*)(&val_b)); + } else if (mtbddnode_gettype(na) == 2 && mtbddnode_gettype(nb) == 2) { + // both fraction + int64_t nom_a = (int32_t)(val_a>>32); + int64_t nom_b = (int32_t)(val_b>>32); + uint64_t denom_a = val_a&0xffffffff; + uint64_t denom_b = val_b&0xffffffff; + // common cases + if (nom_b == 0) return a; + // equalize denominators + uint32_t c = gcd(denom_a, denom_b); + nom_a *= denom_b/c; + nom_b *= denom_a/c; + denom_a *= denom_b/c; + // subtract + return mtbdd_fraction(nom_a - nom_b, denom_a); + } + } + + return mtbdd_invalid; +} + +/** + * Binary operation Times (for MTBDDs of same type) + * Only for MTBDDs where either all leaves are Boolean, or Integer, or Double. + * For Integer/Double MTBDD, if either operand is mtbdd_false (not defined), + * then the result is mtbdd_false (i.e. not defined). + */ +TASK_IMPL_2(MTBDD, mtbdd_op_times, MTBDD*, pa, MTBDD*, pb) +{ + MTBDD a = *pa, b = *pb; + if (a == mtbdd_false || b == mtbdd_false) return mtbdd_false; + + // Handle Boolean MTBDDs: interpret as And + if (a == mtbdd_true) return b; + if (b == mtbdd_true) return a; + + mtbddnode_t na = GETNODE(a); + mtbddnode_t nb = GETNODE(b); + + if (mtbddnode_isleaf(na) && mtbddnode_isleaf(nb)) { + uint64_t val_a = mtbddnode_getvalue(na); + uint64_t val_b = mtbddnode_getvalue(nb); + if (mtbddnode_gettype(na) == 0 && mtbddnode_gettype(nb) == 0) { + // both integer + return mtbdd_int64(*(int64_t*)(&val_a) * *(int64_t*)(&val_b)); + } else if (mtbddnode_gettype(na) == 1 && mtbddnode_gettype(nb) == 1) { + // both double + return mtbdd_double(*(double*)(&val_a) * *(double*)(&val_b)); + } else if (mtbddnode_gettype(na) == 2 && mtbddnode_gettype(nb) == 2) { + // both fraction + int64_t nom_a = (int32_t)(val_a>>32); + int64_t nom_b = (int32_t)(val_b>>32); + uint64_t denom_a = val_a&0xffffffff; + uint64_t denom_b = val_b&0xffffffff; + // multiply! + uint32_t c = gcd(nom_b < 0 ? -nom_b : nom_b, denom_a); + uint32_t d = gcd(nom_a < 0 ? -nom_a : nom_a, denom_b); + nom_a /= d; + denom_a /= c; + nom_a *= (nom_b/c); + denom_a *= (denom_b/d); + return mtbdd_fraction(nom_a, denom_a); + } + } + + if (a < b) { + *pa = b; + *pb = a; + } + + return mtbdd_invalid; +} + +/** + * Binary operation Minimum (for MTBDDs of same type) + * Only for MTBDDs where either all leaves are Boolean, or Integer, or Double. + * For Integer/Double MTBDD, if either operand is mtbdd_false (not defined), + * then the result is the other operand. + */ +TASK_IMPL_2(MTBDD, mtbdd_op_min, MTBDD*, pa, MTBDD*, pb) +{ + MTBDD a = *pa, b = *pb; + if (a == mtbdd_true) return b; + if (b == mtbdd_true) return a; + if (a == b) return a; + + // Special case where "false" indicates a partial function + if (a == mtbdd_false && b != mtbdd_false && mtbddnode_isleaf(GETNODE(b))) return b; + if (b == mtbdd_false && a != mtbdd_false && mtbddnode_isleaf(GETNODE(a))) return a; + + mtbddnode_t na = GETNODE(a); + mtbddnode_t nb = GETNODE(b); + + if (mtbddnode_isleaf(na) && mtbddnode_isleaf(nb)) { + uint64_t val_a = mtbddnode_getvalue(na); + uint64_t val_b = mtbddnode_getvalue(nb); + if (mtbddnode_gettype(na) == 0 && mtbddnode_gettype(nb) == 0) { + // both integer + int64_t va = *(int64_t*)(&val_a); + int64_t vb = *(int64_t*)(&val_b); + return va < vb ? a : b; + } else if (mtbddnode_gettype(na) == 1 && mtbddnode_gettype(nb) == 1) { + // both double + double va = *(double*)&val_a; + double vb = *(double*)&val_b; + return va < vb ? a : b; + } else if (mtbddnode_gettype(na) == 2 && mtbddnode_gettype(nb) == 2) { + // both fraction + int64_t nom_a = (int32_t)(val_a>>32); + int64_t nom_b = (int32_t)(val_b>>32); + uint64_t denom_a = val_a&0xffffffff; + uint64_t denom_b = val_b&0xffffffff; + // equalize denominators + uint32_t c = gcd(denom_a, denom_b); + nom_a *= denom_b/c; + nom_b *= denom_a/c; + // compute lowest + return nom_a < nom_b ? a : b; + } + } + + if (a < b) { + *pa = b; + *pb = a; + } + + return mtbdd_invalid; +} + +/** + * Binary operation Maximum (for MTBDDs of same type) + * Only for MTBDDs where either all leaves are Boolean, or Integer, or Double. + * For Integer/Double MTBDD, if either operand is mtbdd_false (not defined), + * then the result is the other operand. + */ +TASK_IMPL_2(MTBDD, mtbdd_op_max, MTBDD*, pa, MTBDD*, pb) +{ + MTBDD a = *pa, b = *pb; + if (a == mtbdd_true) return a; + if (b == mtbdd_true) return b; + if (a == mtbdd_false) return b; + if (b == mtbdd_false) return a; + if (a == b) return a; + + mtbddnode_t na = GETNODE(a); + mtbddnode_t nb = GETNODE(b); + + if (mtbddnode_isleaf(na) && mtbddnode_isleaf(nb)) { + uint64_t val_a = mtbddnode_getvalue(na); + uint64_t val_b = mtbddnode_getvalue(nb); + if (mtbddnode_gettype(na) == 0 && mtbddnode_gettype(nb) == 0) { + // both integer + int64_t va = *(int64_t*)(&val_a); + int64_t vb = *(int64_t*)(&val_b); + return va > vb ? a : b; + } else if (mtbddnode_gettype(na) == 1 && mtbddnode_gettype(nb) == 1) { + // both double + double vval_a = *(double*)&val_a; + double vval_b = *(double*)&val_b; + return vval_a > vval_b ? a : b; + } else if (mtbddnode_gettype(na) == 2 && mtbddnode_gettype(nb) == 2) { + // both fraction + int64_t nom_a = (int32_t)(val_a>>32); + int64_t nom_b = (int32_t)(val_b>>32); + uint64_t denom_a = val_a&0xffffffff; + uint64_t denom_b = val_b&0xffffffff; + // equalize denominators + uint32_t c = gcd(denom_a, denom_b); + nom_a *= denom_b/c; + nom_b *= denom_a/c; + // compute highest + return nom_a > nom_b ? a : b; + } + } + + if (a < b) { + *pa = b; + *pb = a; + } + + return mtbdd_invalid; +} + +TASK_IMPL_2(MTBDD, mtbdd_op_negate, MTBDD, a, size_t, k) +{ + // if a is false, then it is a partial function. Keep partial! + if (a == mtbdd_false) return mtbdd_false; + + // a != constant + mtbddnode_t na = GETNODE(a); + + if (mtbddnode_isleaf(na)) { + if (mtbddnode_gettype(na) == 0) { + int64_t v = mtbdd_getint64(a); + return mtbdd_int64(-v); + } else if (mtbddnode_gettype(na) == 1) { + double d = mtbdd_getdouble(a); + return mtbdd_double(-d); + } else if (mtbddnode_gettype(na) == 2) { + uint64_t v = mtbddnode_getvalue(na); + return mtbdd_fraction(-(int32_t)(v>>32), (uint32_t)v); + } + } + + return mtbdd_invalid; + (void)k; // unused variable +} + +/** + * Compute IF <f> THEN <g> ELSE <h>. + * <f> must be a Boolean MTBDD (or standard BDD). + */ +TASK_IMPL_3(MTBDD, mtbdd_ite, MTBDD, f, MTBDD, g, MTBDD, h) +{ + /* Terminal cases */ + if (f == mtbdd_true) return g; + if (f == mtbdd_false) return h; + if (g == h) return g; + if (g == mtbdd_true && h == mtbdd_false) return f; + if (h == mtbdd_true && g == mtbdd_false) return MTBDD_TOGGLEMARK(f); + + // If all MTBDD's are Boolean, then there could be further optimizations (see sylvan_bdd.c) + + /* Maybe perform garbage collection */ + sylvan_gc_test(); + + /* Check cache */ + MTBDD result; + if (cache_get3(CACHE_MTBDD_ITE, f, g, h, &result)) return result; + + /* Get top variable */ + int lg = mtbdd_isleaf(g); + int lh = mtbdd_isleaf(h); + mtbddnode_t nf = GETNODE(f); + mtbddnode_t ng = lg ? 0 : GETNODE(g); + mtbddnode_t nh = lh ? 0 : GETNODE(h); + uint32_t vf = mtbddnode_getvariable(nf); + uint32_t vg = lg ? 0 : mtbddnode_getvariable(ng); + uint32_t vh = lh ? 0 : mtbddnode_getvariable(nh); + uint32_t v = vf; + if (!lg && vg < v) v = vg; + if (!lh && vh < v) v = vh; + + /* Get cofactors */ + MTBDD flow, fhigh, glow, ghigh, hlow, hhigh; + flow = (vf == v) ? node_getlow(f, nf) : f; + fhigh = (vf == v) ? node_gethigh(f, nf) : f; + glow = (!lg && vg == v) ? node_getlow(g, ng) : g; + ghigh = (!lg && vg == v) ? node_gethigh(g, ng) : g; + hlow = (!lh && vh == v) ? node_getlow(h, nh) : h; + hhigh = (!lh && vh == v) ? node_gethigh(h, nh) : h; + + /* Recursive calls */ + mtbdd_refs_spawn(SPAWN(mtbdd_ite, fhigh, ghigh, hhigh)); + MTBDD low = mtbdd_refs_push(CALL(mtbdd_ite, flow, glow, hlow)); + MTBDD high = mtbdd_refs_sync(SYNC(mtbdd_ite)); + mtbdd_refs_pop(1); + result = mtbdd_makenode(v, low, high); + + /* Store in cache */ + cache_put3(CACHE_MTBDD_ITE, f, g, h, result); + return result; +} + +/** + * Monad that converts double/fraction to a Boolean MTBDD, translate terminals >= value to 1 and to 0 otherwise; + */ +TASK_IMPL_2(MTBDD, mtbdd_op_threshold_double, MTBDD, a, size_t, svalue) +{ + /* We only expect "double" terminals, or false */ + if (a == mtbdd_false) return mtbdd_false; + if (a == mtbdd_true) return mtbdd_invalid; + + // a != constant + mtbddnode_t na = GETNODE(a); + + if (mtbddnode_isleaf(na)) { + double value = *(double*)&svalue; + if (mtbddnode_gettype(na) == 1) { + return mtbdd_getdouble(a) >= value ? mtbdd_true : mtbdd_false; + } else if (mtbddnode_gettype(na) == 2) { + double d = (double)mtbdd_getnumer(a); + d /= mtbdd_getdenom(a); + return d >= value ? mtbdd_true : mtbdd_false; + } + } + + return mtbdd_invalid; +} + +/** + * Monad that converts double/fraction to a Boolean BDD, translate terminals > value to 1 and to 0 otherwise; + */ +TASK_IMPL_2(MTBDD, mtbdd_op_strict_threshold_double, MTBDD, a, size_t, svalue) +{ + /* We only expect "double" terminals, or false */ + if (a == mtbdd_false) return mtbdd_false; + if (a == mtbdd_true) return mtbdd_invalid; + + // a != constant + mtbddnode_t na = GETNODE(a); + + if (mtbddnode_isleaf(na)) { + double value = *(double*)&svalue; + if (mtbddnode_gettype(na) == 1) { + return mtbdd_getdouble(a) > value ? mtbdd_true : mtbdd_false; + } else if (mtbddnode_gettype(na) == 2) { + double d = (double)mtbdd_getnumer(a); + d /= mtbdd_getdenom(a); + return d > value ? mtbdd_true : mtbdd_false; + } + } + + return mtbdd_invalid; +} + +TASK_IMPL_2(MTBDD, mtbdd_threshold_double, MTBDD, dd, double, d) +{ + return mtbdd_uapply(dd, TASK(mtbdd_op_threshold_double), *(size_t*)&d); +} + +TASK_IMPL_2(MTBDD, mtbdd_strict_threshold_double, MTBDD, dd, double, d) +{ + return mtbdd_uapply(dd, TASK(mtbdd_op_strict_threshold_double), *(size_t*)&d); +} + +/** + * Compare two Double MTBDDs, returns Boolean True if they are equal within some value epsilon + */ +TASK_4(MTBDD, mtbdd_equal_norm_d2, MTBDD, a, MTBDD, b, size_t, svalue, int*, shortcircuit) +{ + /* Check short circuit */ + if (*shortcircuit) return mtbdd_false; + + /* Check terminal case */ + if (a == b) return mtbdd_true; + if (a == mtbdd_false) return mtbdd_false; + if (b == mtbdd_false) return mtbdd_false; + + mtbddnode_t na = GETNODE(a); + mtbddnode_t nb = GETNODE(b); + int la = mtbddnode_isleaf(na); + int lb = mtbddnode_isleaf(nb); + + if (la && lb) { + // assume Double MTBDD + double va = mtbdd_getdouble(a); + double vb = mtbdd_getdouble(b); + va -= vb; + if (va < 0) va = -va; + return (va < *(double*)&svalue) ? mtbdd_true : mtbdd_false; + } + + if (b < a) { + MTBDD t = a; + a = b; + b = t; + } + + /* Maybe perform garbage collection */ + sylvan_gc_test(); + + /* Check cache */ + MTBDD result; + if (cache_get3(CACHE_MTBDD_EQUAL_NORM, a, b, svalue, &result)) return result; + + /* Get top variable */ + uint32_t va = la ? 0xffffffff : mtbddnode_getvariable(na); + uint32_t vb = lb ? 0xffffffff : mtbddnode_getvariable(nb); + uint32_t var = va < vb ? va : vb; + + /* Get cofactors */ + MTBDD alow, ahigh, blow, bhigh; + alow = va == var ? node_getlow(a, na) : a; + ahigh = va == var ? node_gethigh(a, na) : a; + blow = vb == var ? node_getlow(b, nb) : b; + bhigh = vb == var ? node_gethigh(b, nb) : b; + + SPAWN(mtbdd_equal_norm_d2, ahigh, bhigh, svalue, shortcircuit); + result = CALL(mtbdd_equal_norm_d2, alow, blow, svalue, shortcircuit); + if (result == mtbdd_false) *shortcircuit = 1; + if (result != SYNC(mtbdd_equal_norm_d2)) result = mtbdd_false; + if (result == mtbdd_false) *shortcircuit = 1; + + /* Store in cache */ + cache_put3(CACHE_MTBDD_EQUAL_NORM, a, b, svalue, result); + return result; +} + +TASK_IMPL_3(MTBDD, mtbdd_equal_norm_d, MTBDD, a, MTBDD, b, double, d) +{ + /* the implementation checks shortcircuit in every task and if the two + MTBDDs are not equal module epsilon, then the computation tree quickly aborts */ + int shortcircuit = 0; + return CALL(mtbdd_equal_norm_d2, a, b, *(size_t*)&d, &shortcircuit); +} + +/** + * Compare two Double MTBDDs, returns Boolean True if they are equal within some value epsilon + * This version computes the relative difference vs the value in a. + */ +TASK_4(MTBDD, mtbdd_equal_norm_rel_d2, MTBDD, a, MTBDD, b, size_t, svalue, int*, shortcircuit) +{ + /* Check short circuit */ + if (*shortcircuit) return mtbdd_false; + + /* Check terminal case */ + if (a == b) return mtbdd_true; + if (a == mtbdd_false) return mtbdd_false; + if (b == mtbdd_false) return mtbdd_false; + + mtbddnode_t na = GETNODE(a); + mtbddnode_t nb = GETNODE(b); + int la = mtbddnode_isleaf(na); + int lb = mtbddnode_isleaf(nb); + + if (la && lb) { + // assume Double MTBDD + double va = mtbdd_getdouble(a); + double vb = mtbdd_getdouble(b); + if (va == 0) return mtbdd_false; + va = (va - vb) / va; + if (va < 0) va = -va; + return (va < *(double*)&svalue) ? mtbdd_true : mtbdd_false; + } + + /* Maybe perform garbage collection */ + sylvan_gc_test(); + + /* Check cache */ + MTBDD result; + if (cache_get3(CACHE_MTBDD_EQUAL_NORM_REL, a, b, svalue, &result)) return result; + + /* Get top variable */ + uint32_t va = la ? 0xffffffff : mtbddnode_getvariable(na); + uint32_t vb = lb ? 0xffffffff : mtbddnode_getvariable(nb); + uint32_t var = va < vb ? va : vb; + + /* Get cofactors */ + MTBDD alow, ahigh, blow, bhigh; + alow = va == var ? node_getlow(a, na) : a; + ahigh = va == var ? node_gethigh(a, na) : a; + blow = vb == var ? node_getlow(b, nb) : b; + bhigh = vb == var ? node_gethigh(b, nb) : b; + + SPAWN(mtbdd_equal_norm_rel_d2, ahigh, bhigh, svalue, shortcircuit); + result = CALL(mtbdd_equal_norm_rel_d2, alow, blow, svalue, shortcircuit); + if (result == mtbdd_false) *shortcircuit = 1; + if (result != SYNC(mtbdd_equal_norm_rel_d2)) result = mtbdd_false; + if (result == mtbdd_false) *shortcircuit = 1; + + /* Store in cache */ + cache_put3(CACHE_MTBDD_EQUAL_NORM_REL, a, b, svalue, result); + return result; +} + +TASK_IMPL_3(MTBDD, mtbdd_equal_norm_rel_d, MTBDD, a, MTBDD, b, double, d) +{ + /* the implementation checks shortcircuit in every task and if the two + MTBDDs are not equal module epsilon, then the computation tree quickly aborts */ + int shortcircuit = 0; + return CALL(mtbdd_equal_norm_rel_d2, a, b, *(size_t*)&d, &shortcircuit); +} + +/** + * For two MTBDDs a, b, return mtbdd_true if all common assignments a(s) <= b(s), mtbdd_false otherwise. + * For domains not in a / b, assume True. + */ +TASK_3(MTBDD, mtbdd_leq_rec, MTBDD, a, MTBDD, b, int*, shortcircuit) +{ + /* Check short circuit */ + if (*shortcircuit) return mtbdd_false; + + /* Check terminal case */ + if (a == b) return mtbdd_true; + + /* For partial functions, just return true */ + if (a == mtbdd_false) return mtbdd_true; + if (b == mtbdd_false) return mtbdd_true; + + /* Maybe perform garbage collection */ + sylvan_gc_test(); + + /* Check cache */ + MTBDD result; + if (cache_get3(CACHE_MTBDD_LEQ, a, b, 0, &result)) return result; + + mtbddnode_t na = GETNODE(a); + mtbddnode_t nb = GETNODE(b); + int la = mtbddnode_isleaf(na); + int lb = mtbddnode_isleaf(nb); + + if (la && lb) { + uint64_t va = mtbddnode_getvalue(na); + uint64_t vb = mtbddnode_getvalue(nb); + + if (mtbddnode_gettype(na) == 0 && mtbddnode_gettype(nb) == 0) { + // type 0 = integer + result = *(int64_t*)(&va) <= *(int64_t*)(&vb) ? mtbdd_true : mtbdd_false; + } else if (mtbddnode_gettype(na) == 1 && mtbddnode_gettype(nb) == 1) { + // type 1 = double + double vva = *(double*)&va; + double vvb = *(double*)&vb; + result = vva <= vvb ? mtbdd_true : mtbdd_false; + } else if (mtbddnode_gettype(na) == 2 && mtbddnode_gettype(nb) == 2) { + // type 2 = fraction + int64_t nom_a = (int32_t)(va>>32); + int64_t nom_b = (int32_t)(vb>>32); + uint64_t da = va&0xffffffff; + uint64_t db = vb&0xffffffff; + // equalize denominators + uint32_t c = gcd(da, db); + nom_a *= db/c; + nom_b *= da/c; + result = nom_a <= nom_b ? mtbdd_true : mtbdd_false; + } + } else { + /* Get top variable */ + uint32_t va = la ? 0xffffffff : mtbddnode_getvariable(na); + uint32_t vb = lb ? 0xffffffff : mtbddnode_getvariable(nb); + uint32_t var = va < vb ? va : vb; + + /* Get cofactors */ + MTBDD alow, ahigh, blow, bhigh; + alow = va == var ? node_getlow(a, na) : a; + ahigh = va == var ? node_gethigh(a, na) : a; + blow = vb == var ? node_getlow(b, nb) : b; + bhigh = vb == var ? node_gethigh(b, nb) : b; + + SPAWN(mtbdd_leq_rec, ahigh, bhigh, shortcircuit); + result = CALL(mtbdd_leq_rec, alow, blow, shortcircuit); + if (result != SYNC(mtbdd_leq_rec)) result = mtbdd_false; + } + + if (result == mtbdd_false) *shortcircuit = 1; + + /* Store in cache */ + cache_put3(CACHE_MTBDD_LEQ, a, b, 0, result); + return result; +} + +TASK_IMPL_2(MTBDD, mtbdd_leq, MTBDD, a, MTBDD, b) +{ + /* the implementation checks shortcircuit in every task and if the two + MTBDDs are not equal module epsilon, then the computation tree quickly aborts */ + int shortcircuit = 0; + return CALL(mtbdd_leq_rec, a, b, &shortcircuit); +} + +/** + * For two MTBDDs a, b, return mtbdd_true if all common assignments a(s) < b(s), mtbdd_false otherwise. + * For domains not in a / b, assume True. + */ +TASK_3(MTBDD, mtbdd_less_rec, MTBDD, a, MTBDD, b, int*, shortcircuit) +{ + /* Check short circuit */ + if (*shortcircuit) return mtbdd_false; + + /* Check terminal case */ + if (a == b) return mtbdd_false; + + /* For partial functions, just return true */ + if (a == mtbdd_false) return mtbdd_true; + if (b == mtbdd_false) return mtbdd_true; + + /* Maybe perform garbage collection */ + sylvan_gc_test(); + + /* Check cache */ + MTBDD result; + if (cache_get3(CACHE_MTBDD_LESS, a, b, 0, &result)) return result; + + mtbddnode_t na = GETNODE(a); + mtbddnode_t nb = GETNODE(b); + int la = mtbddnode_isleaf(na); + int lb = mtbddnode_isleaf(nb); + + if (la && lb) { + uint64_t va = mtbddnode_getvalue(na); + uint64_t vb = mtbddnode_getvalue(nb); + + if (mtbddnode_gettype(na) == 0 && mtbddnode_gettype(nb) == 0) { + // type 0 = integer + result = *(int64_t*)(&va) < *(int64_t*)(&vb) ? mtbdd_true : mtbdd_false; + } else if (mtbddnode_gettype(na) == 1 && mtbddnode_gettype(nb) == 1) { + // type 1 = double + double vva = *(double*)&va; + double vvb = *(double*)&vb; + result = vva < vvb ? mtbdd_true : mtbdd_false; + } else if (mtbddnode_gettype(na) == 2 && mtbddnode_gettype(nb) == 2) { + // type 2 = fraction + int64_t nom_a = (int32_t)(va>>32); + int64_t nom_b = (int32_t)(vb>>32); + uint64_t da = va&0xffffffff; + uint64_t db = vb&0xffffffff; + // equalize denominators + uint32_t c = gcd(da, db); + nom_a *= db/c; + nom_b *= da/c; + result = nom_a < nom_b ? mtbdd_true : mtbdd_false; + } + } else { + /* Get top variable */ + uint32_t va = la ? 0xffffffff : mtbddnode_getvariable(na); + uint32_t vb = lb ? 0xffffffff : mtbddnode_getvariable(nb); + uint32_t var = va < vb ? va : vb; + + /* Get cofactors */ + MTBDD alow, ahigh, blow, bhigh; + alow = va == var ? node_getlow(a, na) : a; + ahigh = va == var ? node_gethigh(a, na) : a; + blow = vb == var ? node_getlow(b, nb) : b; + bhigh = vb == var ? node_gethigh(b, nb) : b; + + SPAWN(mtbdd_less_rec, ahigh, bhigh, shortcircuit); + result = CALL(mtbdd_less_rec, alow, blow, shortcircuit); + if (result != SYNC(mtbdd_less_rec)) result = mtbdd_false; + } + + if (result == mtbdd_false) *shortcircuit = 1; + + /* Store in cache */ + cache_put3(CACHE_MTBDD_LESS, a, b, 0, result); + return result; +} + +TASK_IMPL_2(MTBDD, mtbdd_less, MTBDD, a, MTBDD, b) +{ + /* the implementation checks shortcircuit in every task and if the two + MTBDDs are not equal module epsilon, then the computation tree quickly aborts */ + int shortcircuit = 0; + return CALL(mtbdd_less_rec, a, b, &shortcircuit); +} + +/** + * For two MTBDDs a, b, return mtbdd_true if all common assignments a(s) >= b(s), mtbdd_false otherwise. + * For domains not in a / b, assume True. + */ +TASK_3(MTBDD, mtbdd_geq_rec, MTBDD, a, MTBDD, b, int*, shortcircuit) +{ + /* Check short circuit */ + if (*shortcircuit) return mtbdd_false; + + /* Check terminal case */ + if (a == b) return mtbdd_true; + + /* For partial functions, just return true */ + if (a == mtbdd_false) return mtbdd_true; + if (b == mtbdd_false) return mtbdd_true; + + /* Maybe perform garbage collection */ + sylvan_gc_test(); + + /* Check cache */ + MTBDD result; + if (cache_get3(CACHE_MTBDD_GEQ, a, b, 0, &result)) return result; + + mtbddnode_t na = GETNODE(a); + mtbddnode_t nb = GETNODE(b); + int la = mtbddnode_isleaf(na); + int lb = mtbddnode_isleaf(nb); + + if (la && lb) { + uint64_t va = mtbddnode_getvalue(na); + uint64_t vb = mtbddnode_getvalue(nb); + + if (mtbddnode_gettype(na) == 0 && mtbddnode_gettype(nb) == 0) { + // type 0 = integer + result = *(int64_t*)(&va) >= *(int64_t*)(&vb) ? mtbdd_true : mtbdd_false; + } else if (mtbddnode_gettype(na) == 1 && mtbddnode_gettype(nb) == 1) { + // type 1 = double + double vva = *(double*)&va; + double vvb = *(double*)&vb; + result = vva >= vvb ? mtbdd_true : mtbdd_false; + } else if (mtbddnode_gettype(na) == 2 && mtbddnode_gettype(nb) == 2) { + // type 2 = fraction + int64_t nom_a = (int32_t)(va>>32); + int64_t nom_b = (int32_t)(vb>>32); + uint64_t da = va&0xffffffff; + uint64_t db = vb&0xffffffff; + // equalize denominators + uint32_t c = gcd(da, db); + nom_a *= db/c; + nom_b *= da/c; + result = nom_a >= nom_b ? mtbdd_true : mtbdd_false; + } + } else { + /* Get top variable */ + uint32_t va = la ? 0xffffffff : mtbddnode_getvariable(na); + uint32_t vb = lb ? 0xffffffff : mtbddnode_getvariable(nb); + uint32_t var = va < vb ? va : vb; + + /* Get cofactors */ + MTBDD alow, ahigh, blow, bhigh; + alow = va == var ? node_getlow(a, na) : a; + ahigh = va == var ? node_gethigh(a, na) : a; + blow = vb == var ? node_getlow(b, nb) : b; + bhigh = vb == var ? node_gethigh(b, nb) : b; + + SPAWN(mtbdd_geq_rec, ahigh, bhigh, shortcircuit); + result = CALL(mtbdd_geq_rec, alow, blow, shortcircuit); + if (result != SYNC(mtbdd_geq_rec)) result = mtbdd_false; + } + + if (result == mtbdd_false) *shortcircuit = 1; + + /* Store in cache */ + cache_put3(CACHE_MTBDD_GEQ, a, b, 0, result); + return result; +} + +TASK_IMPL_2(MTBDD, mtbdd_geq, MTBDD, a, MTBDD, b) +{ + /* the implementation checks shortcircuit in every task and if the two + MTBDDs are not equal module epsilon, then the computation tree quickly aborts */ + int shortcircuit = 0; + return CALL(mtbdd_geq_rec, a, b, &shortcircuit); +} + +/** + * For two MTBDDs a, b, return mtbdd_true if all common assignments a(s) > b(s), mtbdd_false otherwise. + * For domains not in a / b, assume True. + */ +TASK_3(MTBDD, mtbdd_greater_rec, MTBDD, a, MTBDD, b, int*, shortcircuit) +{ + /* Check short circuit */ + if (*shortcircuit) return mtbdd_false; + + /* Check terminal case */ + if (a == b) return mtbdd_false; + + /* For partial functions, just return true */ + if (a == mtbdd_false) return mtbdd_true; + if (b == mtbdd_false) return mtbdd_true; + + /* Maybe perform garbage collection */ + sylvan_gc_test(); + + /* Check cache */ + MTBDD result; + if (cache_get3(CACHE_MTBDD_GREATER, a, b, 0, &result)) return result; + + mtbddnode_t na = GETNODE(a); + mtbddnode_t nb = GETNODE(b); + int la = mtbddnode_isleaf(na); + int lb = mtbddnode_isleaf(nb); + + if (la && lb) { + uint64_t va = mtbddnode_getvalue(na); + uint64_t vb = mtbddnode_getvalue(nb); + + if (mtbddnode_gettype(na) == 0 && mtbddnode_gettype(nb) == 0) { + // type 0 = integer + result = *(int64_t*)(&va) > *(int64_t*)(&vb) ? mtbdd_true : mtbdd_false; + } else if (mtbddnode_gettype(na) == 1 && mtbddnode_gettype(nb) == 1) { + // type 1 = double + double vva = *(double*)&va; + double vvb = *(double*)&vb; + result = vva > vvb ? mtbdd_true : mtbdd_false; + } else if (mtbddnode_gettype(na) == 2 && mtbddnode_gettype(nb) == 2) { + // type 2 = fraction + int64_t nom_a = (int32_t)(va>>32); + int64_t nom_b = (int32_t)(vb>>32); + uint64_t da = va&0xffffffff; + uint64_t db = vb&0xffffffff; + // equalize denominators + uint32_t c = gcd(da, db); + nom_a *= db/c; + nom_b *= da/c; + result = nom_a > nom_b ? mtbdd_true : mtbdd_false; + } + } else { + /* Get top variable */ + uint32_t va = la ? 0xffffffff : mtbddnode_getvariable(na); + uint32_t vb = lb ? 0xffffffff : mtbddnode_getvariable(nb); + uint32_t var = va < vb ? va : vb; + + /* Get cofactors */ + MTBDD alow, ahigh, blow, bhigh; + alow = va == var ? node_getlow(a, na) : a; + ahigh = va == var ? node_gethigh(a, na) : a; + blow = vb == var ? node_getlow(b, nb) : b; + bhigh = vb == var ? node_gethigh(b, nb) : b; + + SPAWN(mtbdd_greater_rec, ahigh, bhigh, shortcircuit); + result = CALL(mtbdd_greater_rec, alow, blow, shortcircuit); + if (result != SYNC(mtbdd_greater_rec)) result = mtbdd_false; + } + + if (result == mtbdd_false) *shortcircuit = 1; + + /* Store in cache */ + cache_put3(CACHE_MTBDD_GREATER, a, b, 0, result); + return result; +} + +TASK_IMPL_2(MTBDD, mtbdd_greater, MTBDD, a, MTBDD, b) +{ + /* the implementation checks shortcircuit in every task and if the two + MTBDDs are not equal module epsilon, then the computation tree quickly aborts */ + int shortcircuit = 0; + return CALL(mtbdd_greater_rec, a, b, &shortcircuit); +} + +/** + * Multiply <a> and <b>, and abstract variables <vars> using summation. + * This is similar to the "and_exists" operation in BDDs. + */ +TASK_IMPL_3(MTBDD, mtbdd_and_exists, MTBDD, a, MTBDD, b, MTBDD, v) +{ + /* Check terminal case */ + if (v == mtbdd_true) return mtbdd_apply(a, b, TASK(mtbdd_op_times)); + MTBDD result = CALL(mtbdd_op_times, &a, &b); + if (result != mtbdd_invalid) { + mtbdd_refs_push(result); + result = mtbdd_abstract(result, v, TASK(mtbdd_abstract_op_plus)); + mtbdd_refs_pop(1); + return result; + } + + /* Maybe perform garbage collection */ + sylvan_gc_test(); + + /* Check cache */ + if (cache_get3(CACHE_MTBDD_AND_EXISTS, a, b, v, &result)) return result; + + /* Now, v is not a constant, and either a or b is not a constant */ + + /* Get top variable */ + int la = mtbdd_isleaf(a); + int lb = mtbdd_isleaf(b); + mtbddnode_t na = la ? 0 : GETNODE(a); + mtbddnode_t nb = lb ? 0 : GETNODE(b); + uint32_t va = la ? 0xffffffff : mtbddnode_getvariable(na); + uint32_t vb = lb ? 0xffffffff : mtbddnode_getvariable(nb); + uint32_t var = va < vb ? va : vb; + + mtbddnode_t nv = GETNODE(v); + uint32_t vv = mtbddnode_getvariable(nv); + + if (vv < var) { + /* Recursive, then abstract result */ + result = CALL(mtbdd_and_exists, a, b, node_gethigh(v, nv)); + mtbdd_refs_push(result); + result = mtbdd_apply(result, result, TASK(mtbdd_op_plus)); + mtbdd_refs_pop(1); + } else { + /* Get cofactors */ + MTBDD alow, ahigh, blow, bhigh; + alow = (!la && va == var) ? node_getlow(a, na) : a; + ahigh = (!la && va == var) ? node_gethigh(a, na) : a; + blow = (!lb && vb == var) ? node_getlow(b, nb) : b; + bhigh = (!lb && vb == var) ? node_gethigh(b, nb) : b; + + if (vv == var) { + /* Recursive, then abstract result */ + mtbdd_refs_spawn(SPAWN(mtbdd_and_exists, ahigh, bhigh, node_gethigh(v, nv))); + MTBDD low = mtbdd_refs_push(CALL(mtbdd_and_exists, alow, blow, node_gethigh(v, nv))); + MTBDD high = mtbdd_refs_push(mtbdd_refs_sync(SYNC(mtbdd_and_exists))); + result = CALL(mtbdd_apply, low, high, TASK(mtbdd_op_plus)); + mtbdd_refs_pop(2); + } else /* vv > v */ { + /* Recursive, then create node */ + mtbdd_refs_spawn(SPAWN(mtbdd_and_exists, ahigh, bhigh, v)); + MTBDD low = mtbdd_refs_push(CALL(mtbdd_and_exists, alow, blow, v)); + MTBDD high = mtbdd_refs_sync(SYNC(mtbdd_and_exists)); + mtbdd_refs_pop(1); + result = mtbdd_makenode(var, low, high); + } + } + + /* Store in cache */ + cache_put3(CACHE_MTBDD_AND_EXISTS, a, b, v, result); + return result; +} + +/** + * Calculate the support of a MTBDD, i.e. the cube of all variables that appear in the MTBDD nodes. + */ +TASK_IMPL_1(MTBDD, mtbdd_support, MTBDD, dd) +{ + /* Terminal case */ + if (mtbdd_isleaf(dd)) return mtbdd_true; + + /* Maybe perform garbage collection */ + sylvan_gc_test(); + + /* Check cache */ + MTBDD result; + if (cache_get3(CACHE_MTBDD_SUPPORT, dd, 0, 0, &result)) return result; + + /* Recursive calls */ + mtbddnode_t n = GETNODE(dd); + mtbdd_refs_spawn(SPAWN(mtbdd_support, node_getlow(dd, n))); + MTBDD high = mtbdd_refs_push(CALL(mtbdd_support, node_gethigh(dd, n))); + MTBDD low = mtbdd_refs_push(mtbdd_refs_sync(SYNC(mtbdd_support))); + + /* Compute result */ + result = mtbdd_makenode(mtbddnode_getvariable(n), mtbdd_false, mtbdd_times(low, high)); + mtbdd_refs_pop(2); + + /* Write to cache */ + cache_put3(CACHE_MTBDD_SUPPORT, dd, 0, 0, result); + return result; +} + +/** + * Function composition, for each node with variable <key> which has a <key,value> pair in <map>, + * replace the node by the result of mtbdd_ite(<value>, <low>, <high>). + * Each <value> in <map> must be a Boolean MTBDD. + */ +TASK_IMPL_2(MTBDD, mtbdd_compose, MTBDD, a, MTBDDMAP, map) +{ + /* Terminal case */ + if (mtbdd_isleaf(a) || mtbdd_map_isempty(map)) return a; + + /* Determine top level */ + mtbddnode_t n = GETNODE(a); + uint32_t v = mtbddnode_getvariable(n); + + /* Find in map */ + while (mtbdd_map_key(map) < v) { + map = mtbdd_map_next(map); + if (mtbdd_map_isempty(map)) return a; + } + + /* Perhaps execute garbage collection */ + sylvan_gc_test(); + + /* Check cache */ + MTBDD result; + if (cache_get3(CACHE_MTBDD_COMPOSE, a, map, 0, &result)) return result; + + /* Recursive calls */ + mtbdd_refs_spawn(SPAWN(mtbdd_compose, node_getlow(a, n), map)); + MTBDD high = mtbdd_refs_push(CALL(mtbdd_compose, node_gethigh(a, n), map)); + MTBDD low = mtbdd_refs_push(mtbdd_refs_sync(SYNC(mtbdd_compose))); + + /* Calculate result */ + MTBDD r = mtbdd_map_key(map) == v ? mtbdd_map_value(map) : mtbdd_makenode(v, mtbdd_false, mtbdd_true); + mtbdd_refs_push(r); + result = CALL(mtbdd_ite, r, high, low); + mtbdd_refs_pop(3); + + /* Store in cache */ + cache_put3(CACHE_MTBDD_COMPOSE, a, map, 0, result); + return result; +} + +/** + * Compute minimum leaf in the MTBDD (for Integer, Double, Rational MTBDDs) + */ +TASK_IMPL_1(MTBDD, mtbdd_minimum, MTBDD, a) +{ + /* Check terminal case */ + if (a == mtbdd_false) return mtbdd_false; + mtbddnode_t na = GETNODE(a); + if (mtbddnode_isleaf(na)) return a; + + /* Maybe perform garbage collection */ + sylvan_gc_test(); + + /* Check cache */ + MTBDD result; + if (cache_get3(CACHE_MTBDD_MINIMUM, a, 0, 0, &result)) return result; + + /* Call recursive */ + SPAWN(mtbdd_minimum, node_getlow(a, na)); + MTBDD high = CALL(mtbdd_minimum, node_gethigh(a, na)); + MTBDD low = SYNC(mtbdd_minimum); + + /* Determine lowest */ + mtbddnode_t nl = GETNODE(low); + mtbddnode_t nh = GETNODE(high); + + if (mtbddnode_gettype(nl) == 0 && mtbddnode_gettype(nh) == 0) { + result = mtbdd_getint64(low) < mtbdd_getint64(high) ? low : high; + } else if (mtbddnode_gettype(nl) == 1 && mtbddnode_gettype(nh) == 1) { + result = mtbdd_getdouble(low) < mtbdd_getdouble(high) ? low : high; + } else if (mtbddnode_gettype(nl) == 2 && mtbddnode_gettype(nh) == 2) { + // type 2 = fraction + int64_t nom_l = mtbdd_getnumer(low); + int64_t nom_h = mtbdd_getnumer(high); + uint64_t denom_l = mtbdd_getdenom(low); + uint64_t denom_h = mtbdd_getdenom(high); + // equalize denominators + uint32_t c = gcd(denom_l, denom_h); + nom_l *= denom_h/c; + nom_h *= denom_l/c; + result = nom_l < nom_h ? low : high; + } + + /* Store in cache */ + cache_put3(CACHE_MTBDD_MINIMUM, a, 0, 0, result); + return result; +} + +/** + * Compute maximum leaf in the MTBDD (for Integer, Double, Rational MTBDDs) + */ +TASK_IMPL_1(MTBDD, mtbdd_maximum, MTBDD, a) +{ + /* Check terminal case */ + if (a == mtbdd_false) return mtbdd_false; + mtbddnode_t na = GETNODE(a); + if (mtbddnode_isleaf(na)) return a; + + /* Maybe perform garbage collection */ + sylvan_gc_test(); + + /* Check cache */ + MTBDD result; + if (cache_get3(CACHE_MTBDD_MAXIMUM, a, 0, 0, &result)) return result; + + /* Call recursive */ + SPAWN(mtbdd_maximum, node_getlow(a, na)); + MTBDD high = CALL(mtbdd_maximum, node_gethigh(a, na)); + MTBDD low = SYNC(mtbdd_maximum); + + /* Determine highest */ + mtbddnode_t nl = GETNODE(low); + mtbddnode_t nh = GETNODE(high); + + if (mtbddnode_gettype(nl) == 0 && mtbddnode_gettype(nh) == 0) { + result = mtbdd_getint64(low) > mtbdd_getint64(high) ? low : high; + } else if (mtbddnode_gettype(nl) == 1 && mtbddnode_gettype(nh) == 1) { + result = mtbdd_getdouble(low) > mtbdd_getdouble(high) ? low : high; + } else if (mtbddnode_gettype(nl) == 2 && mtbddnode_gettype(nh) == 2) { + // type 2 = fraction + int64_t nom_l = mtbdd_getnumer(low); + int64_t nom_h = mtbdd_getnumer(high); + uint64_t denom_l = mtbdd_getdenom(low); + uint64_t denom_h = mtbdd_getdenom(high); + // equalize denominators + uint32_t c = gcd(denom_l, denom_h); + nom_l *= denom_h/c; + nom_h *= denom_l/c; + result = nom_l > nom_h ? low : high; + } + + /* Store in cache */ + cache_put3(CACHE_MTBDD_MAXIMUM, a, 0, 0, result); + return result; +} + +/** + * Calculate the number of satisfying variable assignments according to <variables>. + */ +TASK_IMPL_2(double, mtbdd_satcount, MTBDD, dd, size_t, nvars) +{ + /* Trivial cases */ + if (dd == mtbdd_false) return 0.0; + if (mtbdd_isleaf(dd)) return powl(2.0L, nvars); + + /* Perhaps execute garbage collection */ + sylvan_gc_test(); + + union { + double d; + uint64_t s; + } hack; + + /* Consult cache */ + if (cache_get3(CACHE_BDD_SATCOUNT, dd, 0, nvars, &hack.s)) { + sylvan_stats_count(BDD_SATCOUNT_CACHED); + return hack.d; + } + + SPAWN(mtbdd_satcount, mtbdd_gethigh(dd), nvars-1); + double low = CALL(mtbdd_satcount, mtbdd_getlow(dd), nvars-1); + hack.d = low + SYNC(mtbdd_satcount); + + cache_put3(CACHE_BDD_SATCOUNT, dd, 0, nvars, hack.s); + return hack.d; +} + +MTBDD +mtbdd_enum_first(MTBDD dd, MTBDD variables, uint8_t *arr, mtbdd_enum_filter_cb filter_cb) +{ + if (dd == mtbdd_false) { + // the leaf dd is skipped + return mtbdd_false; + } else if (mtbdd_isleaf(dd)) { + // a leaf for which the filter returns 0 is skipped + if (filter_cb != NULL && filter_cb(dd) == 0) return mtbdd_false; + // ok, we have a leaf that is not skipped, go for it! + while (variables != mtbdd_true) { + *arr++ = 2; + variables = mtbdd_gethigh(variables); + } + return dd; + } else { + // if variables == true, then dd must be a leaf. But then this line is unreachable. + // if this assertion fails, then <variables> is not the support of <dd>. + assert(variables != mtbdd_true); + + // get next variable from <variables> + uint32_t v = mtbdd_getvar(variables); + variables = mtbdd_gethigh(variables); + + // check if MTBDD is on this variable + mtbddnode_t n = GETNODE(dd); + if (mtbddnode_getvariable(n) != v) { + *arr = 2; + return mtbdd_enum_first(dd, variables, arr+1, filter_cb); + } + + // first maybe follow low + MTBDD res = mtbdd_enum_first(node_getlow(dd, n), variables, arr+1, filter_cb); + if (res != mtbdd_false) { + *arr = 0; + return res; + } + + // if not low, try following high + res = mtbdd_enum_first(node_gethigh(dd, n), variables, arr+1, filter_cb); + if (res != mtbdd_false) { + *arr = 1; + return res; + } + + // we've tried low and high, return false + return mtbdd_false; + } +} + +MTBDD +mtbdd_enum_next(MTBDD dd, MTBDD variables, uint8_t *arr, mtbdd_enum_filter_cb filter_cb) +{ + if (mtbdd_isleaf(dd)) { + // we find the leaf in 'enum_next', then we've seen it before... + return mtbdd_false; + } else { + // if variables == true, then dd must be a leaf. But then this line is unreachable. + // if this assertion fails, then <variables> is not the support of <dd>. + assert(variables != mtbdd_true); + + variables = mtbdd_gethigh(variables); + + if (*arr == 0) { + // previous was low + mtbddnode_t n = GETNODE(dd); + MTBDD res = mtbdd_enum_next(node_getlow(dd, n), variables, arr+1, filter_cb); + if (res != mtbdd_false) { + return res; + } else { + // try to find new in high branch + res = mtbdd_enum_first(node_gethigh(dd, n), variables, arr+1, filter_cb); + if (res != mtbdd_false) { + *arr = 1; + return res; + } else { + return mtbdd_false; + } + } + } else if (*arr == 1) { + // previous was high + mtbddnode_t n = GETNODE(dd); + return mtbdd_enum_next(node_gethigh(dd, n), variables, arr+1, filter_cb); + } else { + // previous was either + return mtbdd_enum_next(dd, variables, arr+1, filter_cb); + } + } +} + +/** + * Helper function for recursive unmarking + */ +static void +mtbdd_unmark_rec(MTBDD mtbdd) +{ + mtbddnode_t n = GETNODE(mtbdd); + if (!mtbddnode_getmark(n)) return; + mtbddnode_setmark(n, 0); + if (mtbddnode_isleaf(n)) return; + mtbdd_unmark_rec(mtbddnode_getlow(n)); + mtbdd_unmark_rec(mtbddnode_gethigh(n)); +} + +/** + * Count number of leaves in MTBDD + */ + +static size_t +mtbdd_leafcount_mark(MTBDD mtbdd) +{ + if (mtbdd == mtbdd_true) return 0; // do not count true/false leaf + if (mtbdd == mtbdd_false) return 0; // do not count true/false leaf + mtbddnode_t n = GETNODE(mtbdd); + if (mtbddnode_getmark(n)) return 0; + mtbddnode_setmark(n, 1); + if (mtbddnode_isleaf(n)) return 1; // count leaf as 1 + return mtbdd_leafcount_mark(mtbddnode_getlow(n)) + mtbdd_leafcount_mark(mtbddnode_gethigh(n)); +} + +size_t +mtbdd_leafcount(MTBDD mtbdd) +{ + size_t result = mtbdd_leafcount_mark(mtbdd); + mtbdd_unmark_rec(mtbdd); + return result; +} + +/** + * Count number of nodes in MTBDD + */ + +static size_t +mtbdd_nodecount_mark(MTBDD mtbdd) +{ + if (mtbdd == mtbdd_true) return 0; // do not count true/false leaf + if (mtbdd == mtbdd_false) return 0; // do not count true/false leaf + mtbddnode_t n = GETNODE(mtbdd); + if (mtbddnode_getmark(n)) return 0; + mtbddnode_setmark(n, 1); + if (mtbddnode_isleaf(n)) return 1; // count leaf as 1 + return 1 + mtbdd_nodecount_mark(mtbddnode_getlow(n)) + mtbdd_nodecount_mark(mtbddnode_gethigh(n)); +} + +size_t +mtbdd_nodecount(MTBDD mtbdd) +{ + size_t result = mtbdd_nodecount_mark(mtbdd); + mtbdd_unmark_rec(mtbdd); + return result; +} + +TASK_2(int, mtbdd_test_isvalid_rec, MTBDD, dd, uint32_t, parent_var) +{ + // check if True/False leaf + if (dd == mtbdd_true || dd == mtbdd_false) return 1; + + // check if index is in array + uint64_t index = dd & (~mtbdd_complement); + assert(index > 1 && index < nodes->table_size); + if (index <= 1 || index >= nodes->table_size) return 0; + + // check if marked + int marked = llmsset_is_marked(nodes, index); + assert(marked); + if (marked == 0) return 0; + + // check if leaf + mtbddnode_t n = GETNODE(dd); + if (mtbddnode_isleaf(n)) return 1; // we're fine + + // check variable order + uint32_t var = mtbddnode_getvariable(n); + assert(var > parent_var); + if (var <= parent_var) return 0; + + // check cache + uint64_t result; + if (cache_get3(CACHE_BDD_ISBDD, dd, 0, 0, &result)) { + return result; + } + + // check recursively + SPAWN(mtbdd_test_isvalid_rec, node_getlow(dd, n), var); + result = (uint64_t)CALL(mtbdd_test_isvalid_rec, node_gethigh(dd, n), var); + if (!SYNC(mtbdd_test_isvalid_rec)) result = 0; + + // put in cache and return result + cache_put3(CACHE_BDD_ISBDD, dd, 0, 0, result); + return result; +} + +TASK_IMPL_1(int, mtbdd_test_isvalid, MTBDD, dd) +{ + // check if True/False leaf + if (dd == mtbdd_true || dd == mtbdd_false) return 1; + + // check if index is in array + uint64_t index = dd & (~mtbdd_complement); + assert(index > 1 && index < nodes->table_size); + if (index <= 1 || index >= nodes->table_size) return 0; + + // check if marked + int marked = llmsset_is_marked(nodes, index); + assert(marked); + if (marked == 0) return 0; + + // check if leaf + mtbddnode_t n = GETNODE(dd); + if (mtbddnode_isleaf(n)) return 1; // we're fine + + // check recursively + uint32_t var = mtbddnode_getvariable(n); + SPAWN(mtbdd_test_isvalid_rec, node_getlow(dd, n), var); + int result = CALL(mtbdd_test_isvalid_rec, node_gethigh(dd, n), var); + if (!SYNC(mtbdd_test_isvalid_rec)) result = 0; + return result; +} + +/** + * Export to .dot file + */ + +static void +mtbdd_fprintdot_rec(FILE *out, MTBDD mtbdd, print_terminal_label_cb cb) +{ + mtbddnode_t n = GETNODE(mtbdd); // also works for mtbdd_false + if (mtbddnode_getmark(n)) return; + mtbddnode_setmark(n, 1); + + if (mtbdd == mtbdd_true || mtbdd == mtbdd_false) { + fprintf(out, "0 [shape=box, style=filled, label=\"F\"];\n"); + } else if (mtbddnode_isleaf(n)) { + uint32_t type = mtbddnode_gettype(n); + uint64_t value = mtbddnode_getvalue(n); + fprintf(out, "%" PRIu64 " [shape=box, style=filled, label=\"", MTBDD_STRIPMARK(mtbdd)); + switch (type) { + case 0: + fprintf(out, "%" PRIu64, value); + break; + case 1: + fprintf(out, "%f", *(double*)&value); + break; + case 2: + fprintf(out, "%u/%u", (uint32_t)(value>>32), (uint32_t)value); + break; + default: + cb(out, type, value); + break; + } + fprintf(out, "\"];\n"); + } else { + fprintf(out, "%" PRIu64 " [label=\"%" PRIu32 "\"];\n", + MTBDD_STRIPMARK(mtbdd), mtbddnode_getvariable(n)); + + mtbdd_fprintdot_rec(out, mtbddnode_getlow(n), cb); + mtbdd_fprintdot_rec(out, mtbddnode_gethigh(n), cb); + + fprintf(out, "%" PRIu64 " -> %" PRIu64 " [style=dashed];\n", + MTBDD_STRIPMARK(mtbdd), mtbddnode_getlow(n)); + fprintf(out, "%" PRIu64 " -> %" PRIu64 " [style=solid dir=both arrowtail=%s];\n", + MTBDD_STRIPMARK(mtbdd), MTBDD_STRIPMARK(mtbddnode_gethigh(n)), + mtbddnode_getcomp(n) ? "dot" : "none"); + } +} + +void +mtbdd_fprintdot(FILE *out, MTBDD mtbdd, print_terminal_label_cb cb) +{ + fprintf(out, "digraph \"DD\" {\n"); + fprintf(out, "graph [dpi = 300];\n"); + fprintf(out, "center = true;\n"); + fprintf(out, "edge [dir = forward];\n"); + fprintf(out, "root [style=invis];\n"); + fprintf(out, "root -> %" PRIu64 " [style=solid dir=both arrowtail=%s];\n", + MTBDD_STRIPMARK(mtbdd), MTBDD_HASMARK(mtbdd) ? "dot" : "none"); + + mtbdd_fprintdot_rec(out, mtbdd, cb); + mtbdd_unmark_rec(mtbdd); + + fprintf(out, "}\n"); +} + +/** + * Return 1 if the map contains the key, 0 otherwise. + */ +int +mtbdd_map_contains(MTBDDMAP map, uint32_t key) +{ + while (!mtbdd_map_isempty(map)) { + mtbddnode_t n = GETNODE(map); + uint32_t k = mtbddnode_getvariable(n); + if (k == key) return 1; + if (k > key) return 0; + map = node_getlow(map, n); + } + + return 0; +} + +/** + * Retrieve the number of keys in the map. + */ +size_t +mtbdd_map_count(MTBDDMAP map) +{ + size_t r = 0; + + while (!mtbdd_map_isempty(map)) { + r++; + map = mtbdd_map_next(map); + } + + return r; +} + +/** + * Add the pair <key,value> to the map, overwrites if key already in map. + */ +MTBDDMAP +mtbdd_map_add(MTBDDMAP map, uint32_t key, MTBDD value) +{ + if (mtbdd_map_isempty(map)) return mtbdd_makenode(key, mtbdd_map_empty(), value); + + mtbddnode_t n = GETNODE(map); + uint32_t k = mtbddnode_getvariable(n); + + if (k < key) { + // add recursively and rebuild tree + MTBDDMAP low = mtbdd_map_add(node_getlow(map, n), key, value); + return mtbdd_makenode(k, low, node_gethigh(map, n)); + } else if (k > key) { + return mtbdd_makenode(key, map, value); + } else { + // replace old + return mtbdd_makenode(key, node_getlow(map, n), value); + } +} + +/** + * Add all values from map2 to map1, overwrites if key already in map1. + */ +MTBDDMAP +mtbdd_map_addall(MTBDDMAP map1, MTBDDMAP map2) +{ + if (mtbdd_map_isempty(map1)) return map2; + if (mtbdd_map_isempty(map2)) return map1; + + mtbddnode_t n1 = GETNODE(map1); + mtbddnode_t n2 = GETNODE(map2); + uint32_t k1 = mtbddnode_getvariable(n1); + uint32_t k2 = mtbddnode_getvariable(n2); + + MTBDDMAP result; + if (k1 < k2) { + MTBDDMAP low = mtbdd_map_addall(node_getlow(map1, n1), map2); + result = mtbdd_makenode(k1, low, node_gethigh(map1, n1)); + } else if (k1 > k2) { + MTBDDMAP low = mtbdd_map_addall(map1, node_getlow(map2, n2)); + result = mtbdd_makenode(k2, low, node_gethigh(map2, n2)); + } else { + MTBDDMAP low = mtbdd_map_addall(node_getlow(map1, n1), node_getlow(map2, n2)); + result = mtbdd_makenode(k2, low, node_gethigh(map2, n2)); + } + + return result; +} + +/** + * Remove the key <key> from the map and return the result + */ +MTBDDMAP +mtbdd_map_remove(MTBDDMAP map, uint32_t key) +{ + if (mtbdd_map_isempty(map)) return map; + + mtbddnode_t n = GETNODE(map); + uint32_t k = mtbddnode_getvariable(n); + + if (k < key) { + MTBDDMAP low = mtbdd_map_remove(node_getlow(map, n), key); + return mtbdd_makenode(k, low, node_gethigh(map, n)); + } else if (k > key) { + return map; + } else { + return node_getlow(map, n); + } +} + +/** + * Remove all keys in the cube <variables> from the map and return the result + */ +MTBDDMAP +mtbdd_map_removeall(MTBDDMAP map, MTBDD variables) +{ + if (mtbdd_map_isempty(map)) return map; + if (variables == mtbdd_true) return map; + + mtbddnode_t n1 = GETNODE(map); + mtbddnode_t n2 = GETNODE(variables); + uint32_t k1 = mtbddnode_getvariable(n1); + uint32_t k2 = mtbddnode_getvariable(n2); + + if (k1 < k2) { + MTBDDMAP low = mtbdd_map_removeall(node_getlow(map, n1), variables); + return mtbdd_makenode(k1, low, node_gethigh(map, n1)); + } else if (k1 > k2) { + return mtbdd_map_removeall(map, node_gethigh(variables, n2)); + } else { + return mtbdd_map_removeall(node_getlow(map, n1), node_gethigh(variables, n2)); + } +} + +#include "sylvan_mtbdd_storm.c" diff --git a/src/sylvan_mtbdd.h b/src/sylvan_mtbdd.h new file mode 100644 index 000000000..1a7de3f57 --- /dev/null +++ b/src/sylvan_mtbdd.h @@ -0,0 +1,608 @@ +/* + * Copyright 2011-2015 Formal Methods and Tools, University of Twente + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * This is an implementation of Multi-Terminal Binary Decision Diagrams. + * They encode functions on Boolean variables to any domain. + * + * Three domains are supported by default: Boolean, Integer and Real. + * Boolean MTBDDs are identical to BDDs (as supported by the bdd subpackage). + * Integer MTBDDs are encoded using "int64_t" terminals. + * Real MTBDDs are encoded using "double" terminals. + * + * Labels of Boolean variables of MTBDD nodes are 24-bit integers. + * + * Custom terminals are supported. + * + * Terminal type "0" is the Integer type, type "1" is the Real type. + * Type "2" is the Fraction type, consisting of two 32-bit integers (numerator and denominator) + * For non-Boolean MTBDDs, mtbdd_false is used for partial functions, i.e. mtbdd_false + * indicates that the function is not defined for a certain input. + */ + +/* Do not include this file directly. Instead, include sylvan.h */ + +#ifndef SYLVAN_MTBDD_H +#define SYLVAN_MTBDD_H + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** + * An MTBDD is a 64-bit value. The low 40 bits are an index into the unique table. + * The highest 1 bit is the complement edge, indicating negation. + * For Boolean MTBDDs, this means "not X", for Integer and Real MTBDDs, this means "-X". + */ +typedef uint64_t MTBDD; +typedef MTBDD MTBDDMAP; + +/** + * mtbdd_true is only used in Boolean MTBDDs. mtbdd_false has multiple roles (see above). + */ +#define mtbdd_complement ((MTBDD)0x8000000000000000LL) +#define mtbdd_false ((MTBDD)0) +#define mtbdd_true (mtbdd_false|mtbdd_complement) +#define mtbdd_invalid ((MTBDD)0xffffffffffffffffLL) + +/** + * Initialize MTBDD functionality. + * This initializes internal and external referencing datastructures, + * and registers them in the garbage collection framework. + */ +void sylvan_init_mtbdd(); + +/** + * Create a MTBDD terminal of type <type> and value <value>. + * For custom types, the value could be a pointer to some external struct. + */ +MTBDD mtbdd_makeleaf(uint32_t type, uint64_t value); + +/** + * Create an internal MTBDD node of Boolean variable <var>, with low edge <low> and high edge <high>. + * <var> is a 24-bit integer. + */ +MTBDD mtbdd_makenode(uint32_t var, MTBDD low, MTBDD high); + +/** + * Returns 1 is the MTBDD is a terminal, or 0 otherwise. + */ +int mtbdd_isleaf(MTBDD mtbdd); +#define mtbdd_isnode(mtbdd) (mtbdd_isleaf(mtbdd) ? 0 : 1) + +/** + * For MTBDD terminals, returns <type> and <value> + */ +uint32_t mtbdd_gettype(MTBDD terminal); +uint64_t mtbdd_getvalue(MTBDD terminal); + +/** + * For internal MTBDD nodes, returns <var>, <low> and <high> + */ +uint32_t mtbdd_getvar(MTBDD node); +MTBDD mtbdd_getlow(MTBDD node); +MTBDD mtbdd_gethigh(MTBDD node); + +/** + * Compute the complement of the MTBDD. + * For Boolean MTBDDs, this means "not X". + */ +#define mtbdd_hascomp(dd) ((dd & mtbdd_complement) ? 1 : 0) +#define mtbdd_comp(dd) (dd ^ mtbdd_complement) +#define mtbdd_not(dd) (dd ^ mtbdd_complement) + +/** + * Create terminals representing int64_t (type 0), double (type 1), or fraction (type 2) values + */ +MTBDD mtbdd_int64(int64_t value); +MTBDD mtbdd_double(double value); +MTBDD mtbdd_fraction(int64_t numer, uint64_t denom); + +/** + * Get the value of a terminal (for Integer, Real and Fraction terminals, types 0, 1 and 2) + */ +int64_t mtbdd_getint64(MTBDD terminal); +double mtbdd_getdouble(MTBDD terminal); +#define mtbdd_getnumer(terminal) ((int32_t)(mtbdd_getvalue(terminal)>>32)) +#define mtbdd_getdenom(terminal) ((uint32_t)(mtbdd_getvalue(terminal)&0xffffffff)) + +/** + * Create the conjunction of variables in arr. + * I.e. arr[0] \and arr[1] \and ... \and arr[length-1] + */ +MTBDD mtbdd_fromarray(uint32_t* arr, size_t length); + +/** + * Create a MTBDD cube representing the conjunction of variables in their positive or negative + * form depending on whether the cube[idx] equals 0 (negative), 1 (positive) or 2 (any). + * Use cube[idx]==3 for "s=s'" in interleaved variables (matches with next variable) + * <variables> is the cube of variables (var1 \and var2 \and ... \and varn) + */ +MTBDD mtbdd_cube(MTBDD variables, uint8_t *cube, MTBDD terminal); + +/** + * Same as mtbdd_cube, but extends <mtbdd> with the assignment <cube> \to <terminal>. + * If <mtbdd> already assigns a value to the cube, the new value <terminal> is taken. + * Does not support cube[idx]==3. + */ +#define mtbdd_union_cube(mtbdd, variables, cube, terminal) CALL(mtbdd_union_cube, mtbdd, variables, cube, terminal) +TASK_DECL_4(BDD, mtbdd_union_cube, MTBDD, MTBDD, uint8_t*, MTBDD); + +/** + * Count the number of satisfying assignments (minterms) leading to a non-false leaf + */ +TASK_DECL_2(double, mtbdd_satcount, MTBDD, size_t); +#define mtbdd_satcount(dd, nvars) CALL(mtbdd_satcount, dd, nvars) + +/** + * Count the number of MTBDD leaves (excluding mtbdd_false and mtbdd_true) in the MTBDD + */ +size_t mtbdd_leafcount(MTBDD mtbdd); + +/** + * Count the number of MTBDD nodes and terminals (excluding mtbdd_false and mtbdd_true) in a MTBDD + */ +size_t mtbdd_nodecount(MTBDD mtbdd); + +/** + * Callback function types for binary ("dyadic") and unary ("monadic") operations. + * The callback function returns either the MTBDD that is the result of applying op to the MTBDDs, + * or mtbdd_invalid if op cannot be applied. + * The binary function may swap the two parameters (if commutative) to improve caching. + * The unary function is allowed an extra parameter (be careful of caching) + */ +LACE_TYPEDEF_CB(MTBDD, mtbdd_apply_op, MTBDD*, MTBDD*); +LACE_TYPEDEF_CB(MTBDD, mtbdd_applyp_op, MTBDD*, MTBDD*, size_t); +LACE_TYPEDEF_CB(MTBDD, mtbdd_uapply_op, MTBDD, size_t); + +/** + * Apply a binary operation <op> to <a> and <b>. + * Callback <op> is consulted before the cache, thus the application to terminals is not cached. + */ +TASK_DECL_3(MTBDD, mtbdd_apply, MTBDD, MTBDD, mtbdd_apply_op); +#define mtbdd_apply(a, b, op) CALL(mtbdd_apply, a, b, op) + +/** + * Apply a binary operation <op> with id <opid> to <a> and <b> with parameter <p> + * Callback <op> is consulted before the cache, thus the application to terminals is not cached. + */ +TASK_DECL_5(MTBDD, mtbdd_applyp, MTBDD, MTBDD, size_t, mtbdd_applyp_op, uint64_t); +#define mtbdd_applyp(a, b, p, op, opid) CALL(mtbdd_applyp, a, b, p, op, opid) + +/** + * Apply a unary operation <op> to <dd>. + * Callback <op> is consulted after the cache, thus the application to a terminal is cached. + */ +TASK_DECL_3(MTBDD, mtbdd_uapply, MTBDD, mtbdd_uapply_op, size_t); +#define mtbdd_uapply(dd, op, param) CALL(mtbdd_uapply, dd, op, param) + +/** + * Callback function types for abstraction. + * MTBDD mtbdd_abstract_op(MTBDD a, MTBDD b, int k). + * The function is either called with k==0 (apply to two arguments) or k>0 (k skipped BDD variables) + * k == 0 => res := apply op to a and b + * k > 0 => res := apply op to op(a, a, k-1) and op(a, a, k-1) + */ +LACE_TYPEDEF_CB(MTBDD, mtbdd_abstract_op, MTBDD, MTBDD, int); + +/** + * Abstract the variables in <v> from <a> using the binary operation <op>. + */ +TASK_DECL_3(MTBDD, mtbdd_abstract, MTBDD, MTBDD, mtbdd_abstract_op); +#define mtbdd_abstract(a, v, op) CALL(mtbdd_abstract, a, v, op) + +/** + * Unary operation Negate. + * Supported domains: Integer, Real, Fraction + */ +TASK_DECL_2(MTBDD, mtbdd_op_negate, MTBDD, size_t); + +/** + * Binary operation Plus (for MTBDDs of same type) + * Only for MTBDDs where either all leaves are Boolean, or Integer, or Double. + * For Integer/Double MTBDDs, mtbdd_false is interpreted as "0" or "0.0". + */ +TASK_DECL_2(MTBDD, mtbdd_op_plus, MTBDD*, MTBDD*); +TASK_DECL_3(MTBDD, mtbdd_abstract_op_plus, MTBDD, MTBDD, int); + +/** + * Binary operation Minus (for MTBDDs of same type) + * Only for MTBDDs where either all leaves are Boolean, or Integer, or Double. + * For Integer/Double MTBDDs, mtbdd_false is interpreted as "0" or "0.0". + */ +TASK_DECL_2(MTBDD, mtbdd_op_minus, MTBDD*, MTBDD*); + +/** + * Binary operation Times (for MTBDDs of same type) + * Only for MTBDDs where either all leaves are Boolean, or Integer, or Double. + * For Integer/Double MTBDD, if either operand is mtbdd_false (not defined), + * then the result is mtbdd_false (i.e. not defined). + */ +TASK_DECL_2(MTBDD, mtbdd_op_times, MTBDD*, MTBDD*); +TASK_DECL_3(MTBDD, mtbdd_abstract_op_times, MTBDD, MTBDD, int); + +/** + * Binary operation Minimum (for MTBDDs of same type) + * Only for MTBDDs where either all leaves are Boolean, or Integer, or Double. + * For Integer/Double MTBDD, if either operand is mtbdd_false (not defined), + * then the result is the other operand. + */ +TASK_DECL_2(MTBDD, mtbdd_op_min, MTBDD*, MTBDD*); +TASK_DECL_3(MTBDD, mtbdd_abstract_op_min, MTBDD, MTBDD, int); + +/** + * Binary operation Maximum (for MTBDDs of same type) + * Only for MTBDDs where either all leaves are Boolean, or Integer, or Double. + * For Integer/Double MTBDD, if either operand is mtbdd_false (not defined), + * then the result is the other operand. + */ +TASK_DECL_2(MTBDD, mtbdd_op_max, MTBDD*, MTBDD*); +TASK_DECL_3(MTBDD, mtbdd_abstract_op_max, MTBDD, MTBDD, int); + +/** + * Compute -a + */ +#define mtbdd_negate(a) mtbdd_uapply(a, TASK(mtbdd_op_negate), 0) + +/** + * Compute a + b + */ +#define mtbdd_plus(a, b) mtbdd_apply(a, b, TASK(mtbdd_op_plus)) + +/** + * Compute a - b + */ +#define mtbdd_minus(a, b) mtbdd_apply(a, b, TASK(mtbdd_op_minus)) + +/** + * Compute a * b + */ +#define mtbdd_times(a, b) mtbdd_apply(a, b, TASK(mtbdd_op_times)) + +/** + * Compute min(a, b) + */ +#define mtbdd_min(a, b) mtbdd_apply(a, b, TASK(mtbdd_op_min)) + +/** + * Compute max(a, b) + */ +#define mtbdd_max(a, b) mtbdd_apply(a, b, TASK(mtbdd_op_max)) + +/** + * Abstract the variables in <v> from <a> by taking the sum of all values + */ +#define mtbdd_abstract_plus(dd, v) mtbdd_abstract(dd, v, TASK(mtbdd_abstract_op_plus)) + +/** + * Abstract the variables in <v> from <a> by taking the product of all values + */ +#define mtbdd_abstract_times(dd, v) mtbdd_abstract(dd, v, TASK(mtbdd_abstract_op_times)) + +/** + * Abstract the variables in <v> from <a> by taking the minimum of all values + */ +#define mtbdd_abstract_min(dd, v) mtbdd_abstract(dd, v, TASK(mtbdd_abstract_op_min)) + +/** + * Abstract the variables in <v> from <a> by taking the maximum of all values + */ +#define mtbdd_abstract_max(dd, v) mtbdd_abstract(dd, v, TASK(mtbdd_abstract_op_max)) + +/** + * Compute IF <f> THEN <g> ELSE <h>. + * <f> must be a Boolean MTBDD (or standard BDD). + */ +TASK_DECL_3(MTBDD, mtbdd_ite, MTBDD, MTBDD, MTBDD); +#define mtbdd_ite(f, g, h) CALL(mtbdd_ite, f, g, h); + +/** + * Multiply <a> and <b>, and abstract variables <vars> using summation. + * This is similar to the "and_exists" operation in BDDs. + */ +TASK_DECL_3(MTBDD, mtbdd_and_exists, MTBDD, MTBDD, MTBDD); +#define mtbdd_and_exists(a, b, vars) CALL(mtbdd_and_exists, a, b, vars) + +/** + * Monad that converts double to a Boolean MTBDD, translate terminals >= value to 1 and to 0 otherwise; + */ +TASK_DECL_2(MTBDD, mtbdd_op_threshold_double, MTBDD, size_t) + +/** + * Monad that converts double to a Boolean MTBDD, translate terminals > value to 1 and to 0 otherwise; + */ +TASK_DECL_2(MTBDD, mtbdd_op_strict_threshold_double, MTBDD, size_t) + +/** + * Convert double to a Boolean MTBDD, translate terminals >= value to 1 and to 0 otherwise; + */ +TASK_DECL_2(MTBDD, mtbdd_threshold_double, MTBDD, double); +#define mtbdd_threshold_double(dd, value) CALL(mtbdd_threshold_double, dd, value) + +/** + * Convert double to a Boolean MTBDD, translate terminals > value to 1 and to 0 otherwise; + */ +TASK_DECL_2(MTBDD, mtbdd_strict_threshold_double, MTBDD, double); +#define mtbdd_strict_threshold_double(dd, value) CALL(mtbdd_strict_threshold_double, dd, value) + +/** + * For two Double MTBDDs, calculate whether they are equal module some value epsilon + * i.e. abs(a-b) < e + */ +TASK_DECL_3(MTBDD, mtbdd_equal_norm_d, MTBDD, MTBDD, double); +#define mtbdd_equal_norm_d(a, b, epsilon) CALL(mtbdd_equal_norm_d, a, b, epsilon) + +/** + * For two Double MTBDDs, calculate whether they are equal modulo some value epsilon + * This version computes the relative difference vs the value in a. + * i.e. abs((a-b)/a) < e + */ +TASK_DECL_3(MTBDD, mtbdd_equal_norm_rel_d, MTBDD, MTBDD, double); +#define mtbdd_equal_norm_rel_d(a, b, epsilon) CALL(mtbdd_equal_norm_rel_d, a, b, epsilon) + +/** + * For two MTBDDs a, b, return mtbdd_true if all common assignments a(s) <= b(s), mtbdd_false otherwise. + * For domains not in a / b, assume True. + */ +TASK_DECL_2(MTBDD, mtbdd_leq, MTBDD, MTBDD); +#define mtbdd_leq(a, b) CALL(mtbdd_leq, a, b) + +/** + * For two MTBDDs a, b, return mtbdd_true if all common assignments a(s) < b(s), mtbdd_false otherwise. + * For domains not in a / b, assume True. + */ +TASK_DECL_2(MTBDD, mtbdd_less, MTBDD, MTBDD); +#define mtbdd_less(a, b) CALL(mtbdd_less, a, b) + +/** + * For two MTBDDs a, b, return mtbdd_true if all common assignments a(s) >= b(s), mtbdd_false otherwise. + * For domains not in a / b, assume True. + */ +TASK_DECL_2(MTBDD, mtbdd_geq, MTBDD, MTBDD); +#define mtbdd_geq(a, b) CALL(mtbdd_geq, a, b) + +/** + * For two MTBDDs a, b, return mtbdd_true if all common assignments a(s) > b(s), mtbdd_false otherwise. + * For domains not in a / b, assume True. + */ +TASK_DECL_2(MTBDD, mtbdd_greater, MTBDD, MTBDD); +#define mtbdd_greater(a, b) CALL(mtbdd_greater, a, b) + +/** + * Calculate the support of a MTBDD, i.e. the cube of all variables that appear in the MTBDD nodes. + */ +TASK_DECL_1(MTBDD, mtbdd_support, MTBDD); +#define mtbdd_support(dd) CALL(mtbdd_support, dd) + +/** + * Function composition, for each node with variable <key> which has a <key,value> pair in <map>, + * replace the node by the result of mtbdd_ite(<value>, <low>, <high>). + * Each <value> in <map> must be a Boolean MTBDD. + */ +TASK_DECL_2(MTBDD, mtbdd_compose, MTBDD, MTBDDMAP); +#define mtbdd_compose(dd, map) CALL(mtbdd_compose, dd, map) + +/** + * Compute minimal leaf in the MTBDD (for Integer, Double, Rational MTBDDs) + */ +TASK_DECL_1(MTBDD, mtbdd_minimum, MTBDD); +#define mtbdd_minimum(dd) CALL(mtbdd_minimum, dd) + +/** + * Compute maximal leaf in the MTBDD (for Integer, Double, Rational MTBDDs) + */ +TASK_DECL_1(MTBDD, mtbdd_maximum, MTBDD); +#define mtbdd_maximum(dd) CALL(mtbdd_maximum, dd) + +/** + * Given a MTBDD <dd> and a cube of variables <variables> expected in <dd>, + * mtbdd_enum_first and mtbdd_enum_next enumerates the unique paths in <dd> that lead to a non-False leaf. + * + * The function returns the leaf (or mtbdd_false if no new path is found) and encodes the path + * in the supplied array <arr>: 0 for a low edge, 1 for a high edge, and 2 if the variable is skipped. + * + * The supplied array <arr> must be large enough for all variables in <variables>. + * + * Usage: + * MTBDD leaf = mtbdd_enum_first(dd, variables, arr, NULL); + * while (leaf != mtbdd_false) { + * .... // do something with arr/leaf + * leaf = mtbdd_enum_next(dd, variables, arr, NULL); + * } + * + * The callback is an optional function that returns 0 when the given terminal node should be skipped. + */ +typedef int (*mtbdd_enum_filter_cb)(MTBDD); +MTBDD mtbdd_enum_first(MTBDD dd, MTBDD variables, uint8_t *arr, mtbdd_enum_filter_cb filter_cb); +MTBDD mtbdd_enum_next(MTBDD dd, MTBDD variables, uint8_t *arr, mtbdd_enum_filter_cb filter_cb); + +/** + * For debugging. + * Tests if all nodes in the MTBDD are correctly ``marked'' in the nodes table. + * Tests if variables in the internal nodes appear in-order. + * In Debug mode, this will cause assertion failures instead of returning 0. + * Returns 1 if all is fine, or 0 otherwise. + */ +TASK_DECL_1(int, mtbdd_test_isvalid, MTBDD); +#define mtbdd_test_isvalid(mtbdd) CALL(mtbdd_test_isvalid, mtbdd) + +/** + * Write a DOT representation of a MTBDD + * The callback function is required for custom terminals. + */ +typedef void (*print_terminal_label_cb)(FILE *out, uint32_t type, uint64_t value); +void mtbdd_fprintdot(FILE *out, MTBDD mtbdd, print_terminal_label_cb cb); +#define mtbdd_printdot(mtbdd, cb) mtbdd_fprintdot(stdout, mtbdd, cb) + +/** + * MTBDDMAP, maps uint32_t variables to MTBDDs. + * A MTBDDMAP node has variable level, low edge going to the next MTBDDMAP, high edge to the mapped MTBDD + */ +#define mtbdd_map_empty() mtbdd_false +#define mtbdd_map_isempty(map) (map == mtbdd_false ? 1 : 0) +#define mtbdd_map_key(map) mtbdd_getvar(map) +#define mtbdd_map_value(map) mtbdd_gethigh(map) +#define mtbdd_map_next(map) mtbdd_getlow(map) + +/** + * Return 1 if the map contains the key, 0 otherwise. + */ +int mtbdd_map_contains(MTBDDMAP map, uint32_t key); + +/** + * Retrieve the number of keys in the map. + */ +size_t mtbdd_map_count(MTBDDMAP map); + +/** + * Add the pair <key,value> to the map, overwrites if key already in map. + */ +MTBDDMAP mtbdd_map_add(MTBDDMAP map, uint32_t key, MTBDD value); + +/** + * Add all values from map2 to map1, overwrites if key already in map1. + */ +MTBDDMAP mtbdd_map_addall(MTBDDMAP map1, MTBDDMAP map2); + +/** + * Remove the key <key> from the map and return the result + */ +MTBDDMAP mtbdd_map_remove(MTBDDMAP map, uint32_t key); + +/** + * Remove all keys in the cube <variables> from the map and return the result + */ +MTBDDMAP mtbdd_map_removeall(MTBDDMAP map, MTBDD variables); + +/** + * Custom node types + * Overrides standard hash/equality/notify_on_dead behavior + * hash(value, seed) return hash version + * equals(value1, value2) return 1 if equal, 0 if not equal + * create(&value) replace value by new value for object allocation + * destroy(value) + * NOTE: equals(value1, value2) must imply: hash(value1, seed) == hash(value2,seed) + * NOTE: new value of create must imply: equals(old, new) + */ +typedef uint64_t (*mtbdd_hash_cb)(uint64_t, uint64_t); +typedef int (*mtbdd_equals_cb)(uint64_t, uint64_t); +typedef void (*mtbdd_create_cb)(uint64_t*); +typedef void (*mtbdd_destroy_cb)(uint64_t); + +/** + * Registry callback handlers for <type>. + */ +uint32_t mtbdd_register_custom_leaf(mtbdd_hash_cb hash_cb, mtbdd_equals_cb equals_cb, mtbdd_create_cb create_cb, mtbdd_destroy_cb destroy_cb); + +/** + * Garbage collection + * Sylvan supplies two default methods to handle references to nodes, but the user + * is encouraged to implement custom handling. Simply add a handler using sylvan_gc_add_mark + * and let the handler call mtbdd_gc_mark_rec for every MTBDD that should be saved + * during garbage collection. + */ + +/** + * Call mtbdd_gc_mark_rec for every mtbdd you want to keep in your custom mark functions. + */ +VOID_TASK_DECL_1(mtbdd_gc_mark_rec, MTBDD); +#define mtbdd_gc_mark_rec(mtbdd) CALL(mtbdd_gc_mark_rec, mtbdd) + +/** + * Default external referencing. During garbage collection, MTBDDs marked with mtbdd_ref will + * be kept in the forest. + * It is recommended to prefer mtbdd_protect and mtbdd_unprotect. + */ +MTBDD mtbdd_ref(MTBDD a); +void mtbdd_deref(MTBDD a); +size_t mtbdd_count_refs(); + +/** + * Default external pointer referencing. During garbage collection, the pointers are followed and the MTBDD + * that they refer to are kept in the forest. + */ +void mtbdd_protect(MTBDD* ptr); +void mtbdd_unprotect(MTBDD* ptr); +size_t mtbdd_count_protected(); + +/** + * If sylvan_set_ondead is set to a callback, then this function marks MTBDDs (terminals). + * When they are dead after the mark phase in garbage collection, the callback is called for marked MTBDDs. + * The ondead callback can either perform cleanup or resurrect dead terminals. + */ +#define mtbdd_notify_ondead(dd) llmsset_notify_ondead(nodes, dd&~mtbdd_complement) + +/** + * Infrastructure for internal references (per-thread, e.g. during MTBDD operations) + * Use mtbdd_refs_push and mtbdd_refs_pop to put MTBDDs on a thread-local reference stack. + * Use mtbdd_refs_spawn and mtbdd_refs_sync around SPAWN and SYNC operations when the result + * of the spawned Task is a MTBDD that must be kept during garbage collection. + */ +typedef struct mtbdd_refs_internal +{ + size_t r_size, r_count; + size_t s_size, s_count; + MTBDD *results; + Task **spawns; +} *mtbdd_refs_internal_t; + +extern DECLARE_THREAD_LOCAL(mtbdd_refs_key, mtbdd_refs_internal_t); + +static inline MTBDD +mtbdd_refs_push(MTBDD mtbdd) +{ + LOCALIZE_THREAD_LOCAL(mtbdd_refs_key, mtbdd_refs_internal_t); + if (mtbdd_refs_key->r_count >= mtbdd_refs_key->r_size) { + mtbdd_refs_key->r_size *= 2; + mtbdd_refs_key->results = (MTBDD*)realloc(mtbdd_refs_key->results, sizeof(MTBDD) * mtbdd_refs_key->r_size); + } + mtbdd_refs_key->results[mtbdd_refs_key->r_count++] = mtbdd; + return mtbdd; +} + +static inline void +mtbdd_refs_pop(int amount) +{ + LOCALIZE_THREAD_LOCAL(mtbdd_refs_key, mtbdd_refs_internal_t); + mtbdd_refs_key->r_count-=amount; +} + +static inline void +mtbdd_refs_spawn(Task *t) +{ + LOCALIZE_THREAD_LOCAL(mtbdd_refs_key, mtbdd_refs_internal_t); + if (mtbdd_refs_key->s_count >= mtbdd_refs_key->s_size) { + mtbdd_refs_key->s_size *= 2; + mtbdd_refs_key->spawns = (Task**)realloc(mtbdd_refs_key->spawns, sizeof(Task*) * mtbdd_refs_key->s_size); + } + mtbdd_refs_key->spawns[mtbdd_refs_key->s_count++] = t; +} + +static inline MTBDD +mtbdd_refs_sync(MTBDD result) +{ + LOCALIZE_THREAD_LOCAL(mtbdd_refs_key, mtbdd_refs_internal_t); + mtbdd_refs_key->s_count--; + return result; +} + +#include "sylvan_mtbdd_storm.h" + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif diff --git a/src/sylvan_mtbdd_int.h b/src/sylvan_mtbdd_int.h new file mode 100644 index 000000000..940250b9a --- /dev/null +++ b/src/sylvan_mtbdd_int.h @@ -0,0 +1,128 @@ +/* + * Copyright 2011-2015 Formal Methods and Tools, University of Twente + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Internals for MTBDDs + */ + +#ifndef SYLVAN_MTBDD_INT_H +#define SYLVAN_MTBDD_INT_H + +/** + * MTBDD node structure + */ +typedef struct __attribute__((packed)) mtbddnode { + uint64_t a, b; +} * mtbddnode_t; // 16 bytes + +#define GETNODE(mtbdd) ((mtbddnode_t)llmsset_index_to_ptr(nodes, mtbdd&0x000000ffffffffff)) + +/** + * Complement handling macros + */ +#define MTBDD_HASMARK(s) (s&mtbdd_complement?1:0) +#define MTBDD_TOGGLEMARK(s) (s^mtbdd_complement) +#define MTBDD_STRIPMARK(s) (s&~mtbdd_complement) +#define MTBDD_TRANSFERMARK(from, to) (to ^ (from & mtbdd_complement)) +// Equal under mark +#define MTBDD_EQUALM(a, b) ((((a)^(b))&(~mtbdd_complement))==0) + +// Leaf: a = L=1, M, type; b = value +// Node: a = L=0, C, M, high; b = variable, low +// Only complement edge on "high" + +static inline int +mtbddnode_isleaf(mtbddnode_t n) +{ + return n->a & 0x4000000000000000 ? 1 : 0; +} + +static inline uint32_t +mtbddnode_gettype(mtbddnode_t n) +{ + return n->a & 0x00000000ffffffff; +} + +static inline uint64_t +mtbddnode_getvalue(mtbddnode_t n) +{ + return n->b; +} + +static inline int +mtbddnode_getcomp(mtbddnode_t n) +{ + return n->a & 0x8000000000000000 ? 1 : 0; +} + +static inline uint64_t +mtbddnode_getlow(mtbddnode_t n) +{ + return n->b & 0x000000ffffffffff; // 40 bits +} + +static inline uint64_t +mtbddnode_gethigh(mtbddnode_t n) +{ + return n->a & 0x800000ffffffffff; // 40 bits plus high bit of first +} + +static inline uint32_t +mtbddnode_getvariable(mtbddnode_t n) +{ + return (uint32_t)(n->b >> 40); +} + +static inline int +mtbddnode_getmark(mtbddnode_t n) +{ + return n->a & 0x2000000000000000 ? 1 : 0; +} + +static inline void +mtbddnode_setmark(mtbddnode_t n, int mark) +{ + if (mark) n->a |= 0x2000000000000000; + else n->a &= 0xdfffffffffffffff; +} + +static inline void +mtbddnode_makeleaf(mtbddnode_t n, uint32_t type, uint64_t value) +{ + n->a = 0x4000000000000000 | (uint64_t)type; + n->b = value; +} + +static inline void +mtbddnode_makenode(mtbddnode_t n, uint32_t var, uint64_t low, uint64_t high) +{ + n->a = high; + n->b = ((uint64_t)var)<<40 | low; +} + +static MTBDD +node_getlow(MTBDD mtbdd, mtbddnode_t node) +{ + return MTBDD_TRANSFERMARK(mtbdd, mtbddnode_getlow(node)); +} + +static MTBDD +node_gethigh(MTBDD mtbdd, mtbddnode_t node) +{ + return MTBDD_TRANSFERMARK(mtbdd, mtbddnode_gethigh(node)); +} + +#endif diff --git a/src/sylvan_mtbdd_storm.c b/src/sylvan_mtbdd_storm.c new file mode 100644 index 000000000..dab4860c0 --- /dev/null +++ b/src/sylvan_mtbdd_storm.c @@ -0,0 +1,514 @@ +/** + * Generate SHA2 structural hashes. + * Hashes are independent of location. + * Mainly useful for debugging purposes. + */ +static void +mtbdd_sha2_rec(MTBDD mtbdd, SHA256_CTX *ctx) +{ + if (mtbdd == sylvan_true || mtbdd == sylvan_false) { + SHA256_Update(ctx, (void*)&mtbdd, sizeof(MTBDD)); + return; + } + + mtbddnode_t node = GETNODE(mtbdd); + if (mtbddnode_isleaf(node)) { + uint64_t val = mtbddnode_getvalue(node); + SHA256_Update(ctx, (void*)&val, sizeof(uint64_t)); + } else if (mtbddnode_getmark(node) == 0) { + mtbddnode_setmark(node, 1); + uint32_t level = mtbddnode_getvariable(node); + if (MTBDD_STRIPMARK(mtbddnode_gethigh(node))) level |= 0x80000000; + SHA256_Update(ctx, (void*)&level, sizeof(uint32_t)); + mtbdd_sha2_rec(mtbddnode_gethigh(node), ctx); + mtbdd_sha2_rec(mtbddnode_getlow(node), ctx); + } +} + +void +mtbdd_getsha(MTBDD mtbdd, char *target) +{ + SHA256_CTX ctx; + SHA256_Init(&ctx); + mtbdd_sha2_rec(mtbdd, &ctx); + if (mtbdd != sylvan_true && mtbdd != sylvan_false) mtbdd_unmark_rec(mtbdd); + SHA256_End(&ctx, target); +} + +/** + * Binary operation Times (for MTBDDs of same type) + * Only for MTBDDs where either all leaves are Integer or Double. + * If either operand is mtbdd_false (not defined), + * then the result is mtbdd_false (i.e. not defined). + */ +TASK_IMPL_2(MTBDD, mtbdd_op_divide, MTBDD*, pa, MTBDD*, pb) +{ + MTBDD a = *pa, b = *pb; + if (a == mtbdd_false || b == mtbdd_false) return mtbdd_false; + + // Do not handle Boolean MTBDDs... + + mtbddnode_t na = GETNODE(a); + mtbddnode_t nb = GETNODE(b); + + if (mtbddnode_isleaf(na) && mtbddnode_isleaf(nb)) { + uint64_t val_a = mtbddnode_getvalue(na); + uint64_t val_b = mtbddnode_getvalue(nb); + if (mtbddnode_gettype(na) == 0 && mtbddnode_gettype(nb) == 0) { + int64_t va = *(int64_t*)(&val_a); + int64_t vb = *(int64_t*)(&val_b); + + if (va == 0) return a; + else if (vb == 0) return b; + else { + MTBDD result; + if (va == 1) result = b; + else if (vb == 1) result = a; + else result = mtbdd_int64(va*vb); + return result; + } + } else if (mtbddnode_gettype(na) == 1 && mtbddnode_gettype(nb) == 1) { + // both double + double vval_a = *(double*)&val_a; + double vval_b = *(double*)&val_b; + if (vval_a == 0.0) return a; + else if (vval_b == 0.0) return b; + else { + MTBDD result; + if (vval_a == 0.0 || vval_b == 1.0) result = a; + result = mtbdd_double(vval_a / vval_b); + return result; + } + } + else if (mtbddnode_gettype(na) == 2 && mtbddnode_gettype(nb) == 2) { + // both fraction + uint64_t nom_a = val_a>>32; + uint64_t nom_b = val_b>>32; + uint64_t denom_a = val_a&0xffffffff; + uint64_t denom_b = val_b&0xffffffff; + // multiply! + uint32_t c = gcd(denom_b, denom_a); + uint32_t d = gcd(nom_a, nom_b); + nom_a /= d; + denom_a /= c; + nom_a *= (denom_b/c); + denom_a *= (nom_b/d); + // compute result + MTBDD result = mtbdd_fraction(nom_a, denom_a); + return result; + } + } + + return mtbdd_invalid; +} + +/** + * Binary operation Equals (for MTBDDs of same type) + * Only for MTBDDs where either all leaves are Boolean, or Integer, or Double. + * For Integer/Double MTBDD, if either operand is mtbdd_false (not defined), + * then the result is mtbdd_false (i.e. not defined). + */ +TASK_IMPL_2(MTBDD, mtbdd_op_equals, MTBDD*, pa, MTBDD*, pb) +{ + MTBDD a = *pa, b = *pb; + if (a == mtbdd_false && b == mtbdd_false) return mtbdd_true; + if (a == mtbdd_true && b == mtbdd_true) return mtbdd_true; + + mtbddnode_t na = GETNODE(a); + mtbddnode_t nb = GETNODE(b); + + if (mtbddnode_isleaf(na) && mtbddnode_isleaf(nb)) { + uint64_t val_a = mtbddnode_getvalue(na); + uint64_t val_b = mtbddnode_getvalue(nb); + if (mtbddnode_gettype(na) == 0 && mtbddnode_gettype(nb) == 0) { + int64_t va = *(int64_t*)(&val_a); + int64_t vb = *(int64_t*)(&val_b); + if (va == vb) return mtbdd_true; + return mtbdd_false; + } else if (mtbddnode_gettype(na) == 1 && mtbddnode_gettype(nb) == 1) { + // both double + double vval_a = *(double*)&val_a; + double vval_b = *(double*)&val_b; + if (vval_a == vval_b) return mtbdd_true; + return mtbdd_false; + } else if (mtbddnode_gettype(na) == 2 && mtbddnode_gettype(nb) == 2) { + // both fraction + uint64_t nom_a = val_a>>32; + uint64_t nom_b = val_b>>32; + uint64_t denom_a = val_a&0xffffffff; + uint64_t denom_b = val_b&0xffffffff; + if (nom_a == nom_b && denom_a == denom_b) return mtbdd_true; + return mtbdd_false; + } + } + + if (a < b) { + *pa = b; + *pb = a; + } + + return mtbdd_invalid; +} + +/** + * Binary operation Equals (for MTBDDs of same type) + * Only for MTBDDs where either all leaves are Boolean, or Integer, or Double. + * For Integer/Double MTBDD, if either operand is mtbdd_false (not defined), + * then the result is mtbdd_false (i.e. not defined). + */ +TASK_IMPL_2(MTBDD, mtbdd_op_less, MTBDD*, pa, MTBDD*, pb) +{ + MTBDD a = *pa, b = *pb; + if (a == mtbdd_false && b == mtbdd_false) return mtbdd_true; + if (a == mtbdd_true && b == mtbdd_true) return mtbdd_true; + + mtbddnode_t na = GETNODE(a); + mtbddnode_t nb = GETNODE(b); + + if (mtbddnode_isleaf(na) && mtbddnode_isleaf(nb)) { + uint64_t val_a = mtbddnode_getvalue(na); + uint64_t val_b = mtbddnode_getvalue(nb); + if (mtbddnode_gettype(na) == 0 && mtbddnode_gettype(nb) == 0) { + int64_t va = *(int64_t*)(&val_a); + int64_t vb = *(int64_t*)(&val_b); + if (va < vb) return mtbdd_true; + return mtbdd_false; + } else if (mtbddnode_gettype(na) == 1 && mtbddnode_gettype(nb) == 1) { + // both double + double vval_a = *(double*)&val_a; + double vval_b = *(double*)&val_b; + if (vval_a < vval_b) return mtbdd_true; + return mtbdd_false; + } else if (mtbddnode_gettype(na) == 2 && mtbddnode_gettype(nb) == 2) { + // both fraction + uint64_t nom_a = val_a>>32; + uint64_t nom_b = val_b>>32; + uint64_t denom_a = val_a&0xffffffff; + uint64_t denom_b = val_b&0xffffffff; + return nom_a * denom_b < nom_b * denom_a ? mtbdd_true : mtbdd_false; + } + } + + return mtbdd_invalid; +} + +/** + * Binary operation Equals (for MTBDDs of same type) + * Only for MTBDDs where either all leaves are Boolean, or Integer, or Double. + * For Integer/Double MTBDD, if either operand is mtbdd_false (not defined), + * then the result is mtbdd_false (i.e. not defined). + */ +TASK_IMPL_2(MTBDD, mtbdd_op_less_or_equal, MTBDD*, pa, MTBDD*, pb) +{ + MTBDD a = *pa, b = *pb; + if (a == mtbdd_false && b == mtbdd_false) return mtbdd_true; + if (a == mtbdd_true && b == mtbdd_true) return mtbdd_true; + + mtbddnode_t na = GETNODE(a); + mtbddnode_t nb = GETNODE(b); + + if (mtbddnode_isleaf(na) && mtbddnode_isleaf(nb)) { + uint64_t val_a = mtbddnode_getvalue(na); + uint64_t val_b = mtbddnode_getvalue(nb); + if (mtbddnode_gettype(na) == 0 && mtbddnode_gettype(nb) == 0) { + int64_t va = *(int64_t*)(&val_a); + int64_t vb = *(int64_t*)(&val_b); + return va <= vb ? mtbdd_true : mtbdd_false; + } else if (mtbddnode_gettype(na) == 1 && mtbddnode_gettype(nb) == 1) { + // both double + double vval_a = *(double*)&val_a; + double vval_b = *(double*)&val_b; + if (vval_a <= vval_b) return mtbdd_true; + return mtbdd_false; + } else if (mtbddnode_gettype(na) == 2 && mtbddnode_gettype(nb) == 2) { + // both fraction + uint64_t nom_a = val_a>>32; + uint64_t nom_b = val_b>>32; + uint64_t denom_a = val_a&0xffffffff; + uint64_t denom_b = val_b&0xffffffff; + nom_a *= denom_b; + nom_b *= denom_a; + return nom_a <= nom_b ? mtbdd_true : mtbdd_false; + } + } + + return mtbdd_invalid; +} + +/** + * Binary operation Pow (for MTBDDs of same type) + * Only for MTBDDs where either all leaves are Double. + * For Integer/Double MTBDD, if either operand is mtbdd_false (not defined), + * then the result is mtbdd_false (i.e. not defined). + */ +TASK_IMPL_2(MTBDD, mtbdd_op_pow, MTBDD*, pa, MTBDD*, pb) +{ + MTBDD a = *pa, b = *pb; + + mtbddnode_t na = GETNODE(a); + mtbddnode_t nb = GETNODE(b); + + if (mtbddnode_isleaf(na) && mtbddnode_isleaf(nb)) { + uint64_t val_a = mtbddnode_getvalue(na); + uint64_t val_b = mtbddnode_getvalue(nb); + if (mtbddnode_gettype(na) == 0 && mtbddnode_gettype(nb) == 0) { + assert(0); + } else if (mtbddnode_gettype(na) == 1 && mtbddnode_gettype(nb) == 1) { + // both double + double vval_a = *(double*)&val_a; + double vval_b = *(double*)&val_b; + return mtbdd_double(pow(vval_a, vval_b)); + } else if (mtbddnode_gettype(na) == 2 && mtbddnode_gettype(nb) == 2) { + assert(0); + } + } + + return mtbdd_invalid; +} + +/** + * Binary operation Mod (for MTBDDs of same type) + * Only for MTBDDs where either all leaves are Double. + * For Integer/Double MTBDD, if either operand is mtbdd_false (not defined), + * then the result is mtbdd_false (i.e. not defined). + */ +TASK_IMPL_2(MTBDD, mtbdd_op_mod, MTBDD*, pa, MTBDD*, pb) +{ + MTBDD a = *pa, b = *pb; + + mtbddnode_t na = GETNODE(a); + mtbddnode_t nb = GETNODE(b); + + if (mtbddnode_isleaf(na) && mtbddnode_isleaf(nb)) { + uint64_t val_a = mtbddnode_getvalue(na); + uint64_t val_b = mtbddnode_getvalue(nb); + if (mtbddnode_gettype(na) == 0 && mtbddnode_gettype(nb) == 0) { + assert(0); + } else if (mtbddnode_gettype(na) == 1 && mtbddnode_gettype(nb) == 1) { + // both double + double vval_a = *(double*)&val_a; + double vval_b = *(double*)&val_b; + return mtbdd_double(fmod(vval_a, vval_b)); + } else if (mtbddnode_gettype(na) == 2 && mtbddnode_gettype(nb) == 2) { + assert(0); + } + } + + return mtbdd_invalid; +} + +/** + * Binary operation Log (for MTBDDs of same type) + * Only for MTBDDs where either all leaves are Double. + * For Integer/Double MTBDD, if either operand is mtbdd_false (not defined), + * then the result is mtbdd_false (i.e. not defined). + */ +TASK_IMPL_2(MTBDD, mtbdd_op_logxy, MTBDD*, pa, MTBDD*, pb) +{ + MTBDD a = *pa, b = *pb; + + mtbddnode_t na = GETNODE(a); + mtbddnode_t nb = GETNODE(b); + + if (mtbddnode_isleaf(na) && mtbddnode_isleaf(nb)) { + uint64_t val_a = mtbddnode_getvalue(na); + uint64_t val_b = mtbddnode_getvalue(nb); + if (mtbddnode_gettype(na) == 0 && mtbddnode_gettype(nb) == 0) { + assert(0); + } else if (mtbddnode_gettype(na) == 1 && mtbddnode_gettype(nb) == 1) { + // both double + double vval_a = *(double*)&val_a; + double vval_b = *(double*)&val_b; + return mtbdd_double(log(vval_a) / log(vval_b)); + } else if (mtbddnode_gettype(na) == 2 && mtbddnode_gettype(nb) == 2) { + assert(0); + } + } + + return mtbdd_invalid; +} + +TASK_IMPL_2(MTBDD, mtbdd_op_not_zero, MTBDD, a, size_t, v) +{ + /* We only expect "double" terminals, or false */ + if (a == mtbdd_false) return mtbdd_false; + if (a == mtbdd_true) return mtbdd_true; + + // a != constant + mtbddnode_t na = GETNODE(a); + + if (mtbddnode_isleaf(na)) { + if (mtbddnode_gettype(na) == 0) { + return mtbdd_getint64(a) != 0 ? mtbdd_true : mtbdd_false; + } else if (mtbddnode_gettype(na) == 1) { + return mtbdd_getdouble(a) != 0.0 ? mtbdd_true : mtbdd_false; + } else if (mtbddnode_gettype(na) == 2) { + return mtbdd_getnumer(a) != 0 ? mtbdd_true : mtbdd_false; + } + } + + // Ugly hack to get rid of the error "unused variable v" (because there is no version of uapply without a parameter). + (void)v; + + return mtbdd_invalid; +} + +TASK_IMPL_1(MTBDD, mtbdd_not_zero, MTBDD, dd) +{ + return mtbdd_uapply(dd, TASK(mtbdd_op_not_zero), 0); +} + +TASK_IMPL_2(MTBDD, mtbdd_op_floor, MTBDD, a, size_t, v) +{ + /* We only expect "double" terminals, or false */ + if (a == mtbdd_false) return mtbdd_false; + if (a == mtbdd_true) return mtbdd_true; + + // a != constant + mtbddnode_t na = GETNODE(a); + + if (mtbddnode_isleaf(na)) { + if (mtbddnode_gettype(na) == 0) { + return a; + } else if (mtbddnode_gettype(na) == 1) { + MTBDD result = mtbdd_double(floor(mtbdd_getdouble(a))); + return result; + } else if (mtbddnode_gettype(na) == 2) { + MTBDD result = mtbdd_fraction(mtbdd_getnumer(a) / mtbdd_getdenom(a), 1); + return result; + } + } + + // Ugly hack to get rid of the error "unused variable v" (because there is no version of uapply without a parameter). + (void)v; + + return mtbdd_invalid; +} + +TASK_IMPL_1(MTBDD, mtbdd_floor, MTBDD, dd) +{ + return mtbdd_uapply(dd, TASK(mtbdd_op_floor), 0); +} + +TASK_IMPL_2(MTBDD, mtbdd_op_ceil, MTBDD, a, size_t, v) +{ + /* We only expect "double" terminals, or false */ + if (a == mtbdd_false) return mtbdd_false; + if (a == mtbdd_true) return mtbdd_true; + + // a != constant + mtbddnode_t na = GETNODE(a); + + if (mtbddnode_isleaf(na)) { + if (mtbddnode_gettype(na) == 0) { + return a; + } else if (mtbddnode_gettype(na) == 1) { + MTBDD result = mtbdd_double(ceil(mtbdd_getdouble(a))); + return result; + } else if (mtbddnode_gettype(na) == 2) { + MTBDD result = mtbdd_fraction(mtbdd_getnumer(a) / mtbdd_getdenom(a) + 1, 1); + return result; + } + } + + // Ugly hack to get rid of the error "unused variable v" (because there is no version of uapply without a parameter). + (void)v; + + return mtbdd_invalid; +} + +TASK_IMPL_1(MTBDD, mtbdd_ceil, MTBDD, dd) +{ + return mtbdd_uapply(dd, TASK(mtbdd_op_ceil), 0); +} + +TASK_IMPL_2(MTBDD, mtbdd_op_bool_to_double, MTBDD, a, size_t, v) +{ + /* We only expect "double" terminals, or false */ + if (a == mtbdd_false) return mtbdd_double(0); + if (a == mtbdd_true) return mtbdd_double(1.0); + + // Ugly hack to get rid of the error "unused variable v" (because there is no version of uapply without a parameter). + (void)v; + + return mtbdd_invalid; +} + +TASK_IMPL_1(MTBDD, mtbdd_bool_to_double, MTBDD, dd) +{ + return mtbdd_uapply(dd, TASK(mtbdd_op_bool_to_double), 0); +} + +TASK_IMPL_2(MTBDD, mtbdd_op_bool_to_int64, MTBDD, a, size_t, v) +{ + /* We only expect "double" terminals, or false */ + if (a == mtbdd_false) return mtbdd_int64(0); + if (a == mtbdd_true) return mtbdd_int64(1); + + // Ugly hack to get rid of the error "unused variable v" (because there is no version of uapply without a parameter). + (void)v; + + return mtbdd_invalid; +} + +TASK_IMPL_1(MTBDD, mtbdd_bool_to_int64, MTBDD, dd) +{ + return mtbdd_uapply(dd, TASK(mtbdd_op_bool_to_int64), 0); +} + +/** + * Calculate the number of satisfying variable assignments according to <variables>. + */ +TASK_IMPL_2(double, mtbdd_non_zero_count, MTBDD, dd, size_t, nvars) +{ + /* Trivial cases */ + if (dd == mtbdd_false) return 0.0; + + mtbddnode_t na = GETNODE(dd); + + if (mtbdd_isleaf(dd)) { + if (mtbddnode_gettype(na) == 0) { + return mtbdd_getint64(dd) != 0 ? powl(2.0L, nvars) : 0.0; + } else if (mtbddnode_gettype(na) == 1) { + return mtbdd_getdouble(dd) != 0 ? powl(2.0L, nvars) : 0.0; + } else if (mtbddnode_gettype(na) == 2) { + return mtbdd_getnumer(dd) != 0 ? powl(2.0L, nvars) : 0.0; + } + } + + /* Perhaps execute garbage collection */ + sylvan_gc_test(); + + union { + double d; + uint64_t s; + } hack; + + /* Consult cache */ + if (cache_get3(CACHE_MTBDD_NONZERO_COUNT, dd, 0, nvars, &hack.s)) { + sylvan_stats_count(CACHE_MTBDD_NONZERO_COUNT); + return hack.d; + } + + SPAWN(mtbdd_non_zero_count, mtbdd_gethigh(dd), nvars-1); + double low = CALL(mtbdd_non_zero_count, mtbdd_getlow(dd), nvars-1); + hack.d = low + SYNC(mtbdd_non_zero_count); + + cache_put3(CACHE_MTBDD_NONZERO_COUNT, dd, 0, nvars, hack.s); + return hack.d; +} + +int mtbdd_iszero(MTBDD dd) { + if (mtbdd_gettype(dd) == 0) { + return mtbdd_getint64(dd) == 0; + } else if (mtbdd_gettype(dd) == 1) { + return mtbdd_getdouble(dd) == 0; + } else if (mtbdd_gettype(dd) == 2) { + return mtbdd_getnumer(dd) == 0; + } + return 0; +} + +int mtbdd_isnonzero(MTBDD dd) { + return mtbdd_iszero(dd) ? 0 : 1; +} \ No newline at end of file diff --git a/src/sylvan_mtbdd_storm.h b/src/sylvan_mtbdd_storm.h new file mode 100644 index 000000000..38fa6668b --- /dev/null +++ b/src/sylvan_mtbdd_storm.h @@ -0,0 +1,111 @@ +void mtbdd_getsha(MTBDD mtbdd, char *target); // target must be at least 65 bytes... + +/** + * Binary operation Divide (for MTBDDs of same type) + * Only for MTBDDs where all leaves are Integer or Double. + * If either operand is mtbdd_false (not defined), + * then the result is mtbdd_false (i.e. not defined). + */ +TASK_DECL_2(MTBDD, mtbdd_op_divide, MTBDD*, MTBDD*); +#define mtbdd_divide(a, b) mtbdd_apply(a, b, TASK(mtbdd_op_divide)) + +/** + * Binary operation equals (for MTBDDs of same type) + * Only for MTBDDs where either all leaves are Boolean, or Integer, or Double. + * For Integer/Double MTBDD, if either operand is mtbdd_false (not defined), + * then the result is the other operand. + */ +TASK_DECL_2(MTBDD, mtbdd_op_equals, MTBDD*, MTBDD*); +#define mtbdd_equals(a, b) mtbdd_apply(a, b, TASK(mtbdd_op_equals)) + +/** + * Binary operation Less (for MTBDDs of same type) + * Only for MTBDDs where either all leaves are Boolean, or Integer, or Double. + * For Integer/Double MTBDD, if either operand is mtbdd_false (not defined), + * then the result is the other operand. + */ +TASK_DECL_2(MTBDD, mtbdd_op_less, MTBDD*, MTBDD*); +#define mtbdd_less_as_bdd(a, b) mtbdd_apply(a, b, TASK(mtbdd_op_less)) + +/** + * Binary operation Less (for MTBDDs of same type) + * Only for MTBDDs where either all leaves are Boolean, or Integer, or Double. + * For Integer/Double MTBDD, if either operand is mtbdd_false (not defined), + * then the result is the other operand. + */ +TASK_DECL_2(MTBDD, mtbdd_op_less_or_equal, MTBDD*, MTBDD*); +#define mtbdd_less_or_equal_as_bdd(a, b) mtbdd_apply(a, b, TASK(mtbdd_op_less_or_equal)) + +/** + * Binary operation Pow (for MTBDDs of same type) + * Only for MTBDDs where either all leaves are Integer, Double or a Fraction. + * For Integer/Double MTBDD, if either operand is mtbdd_false (not defined), + * then the result is the other operand. + */ +TASK_DECL_2(MTBDD, mtbdd_op_pow, MTBDD*, MTBDD*); +#define mtbdd_pow(a, b) mtbdd_apply(a, b, TASK(mtbdd_op_pow)) + +/** + * Binary operation Mod (for MTBDDs of same type) + * Only for MTBDDs where either all leaves are Integer, Double or a Fraction. + * For Integer/Double MTBDD, if either operand is mtbdd_false (not defined), + * then the result is the other operand. + */ +TASK_DECL_2(MTBDD, mtbdd_op_mod, MTBDD*, MTBDD*); +#define mtbdd_mod(a, b) mtbdd_apply(a, b, TASK(mtbdd_op_mod)) + +/** + * Binary operation Log (for MTBDDs of same type) + * Only for MTBDDs where either all leaves are Double or a Fraction. + * For Integer/Double MTBDD, if either operand is mtbdd_false (not defined), + * then the result is the other operand. + */ +TASK_DECL_2(MTBDD, mtbdd_op_logxy, MTBDD*, MTBDD*); +#define mtbdd_logxy(a, b) mtbdd_apply(a, b, TASK(mtbdd_op_logxy)) + +/** + * Monad that converts double to a Boolean MTBDD, translate terminals != 0 to 1 and to 0 otherwise; + */ +TASK_DECL_2(MTBDD, mtbdd_op_not_zero, MTBDD, size_t) +TASK_DECL_1(MTBDD, mtbdd_not_zero, MTBDD) +#define mtbdd_not_zero(dd) CALL(mtbdd_not_zero, dd) + +/** + * Monad that floors all values Double and Fraction values. + */ +TASK_DECL_2(MTBDD, mtbdd_op_floor, MTBDD, size_t) +TASK_DECL_1(MTBDD, mtbdd_floor, MTBDD) +#define mtbdd_floor(dd) CALL(mtbdd_floor, dd) + +/** + * Monad that ceils all values Double and Fraction values. + */ +TASK_DECL_2(MTBDD, mtbdd_op_ceil, MTBDD, size_t) +TASK_DECL_1(MTBDD, mtbdd_ceil, MTBDD) +#define mtbdd_ceil(dd) CALL(mtbdd_ceil, dd) + +/** + * Monad that converts Boolean to a Double MTBDD, translate terminals true to 1 and to 0 otherwise; + */ +TASK_DECL_2(MTBDD, mtbdd_op_bool_to_double, MTBDD, size_t) +TASK_DECL_1(MTBDD, mtbdd_bool_to_double, MTBDD) +#define mtbdd_bool_to_double(dd) CALL(mtbdd_bool_to_double, dd) + +/** + * Monad that converts Boolean to a uint MTBDD, translate terminals true to 1 and to 0 otherwise; + */ +TASK_DECL_2(MTBDD, mtbdd_op_bool_to_int64, MTBDD, size_t) +TASK_DECL_1(MTBDD, mtbdd_bool_to_int64, MTBDD) +#define mtbdd_bool_to_int64(dd) CALL(mtbdd_bool_to_int64, dd) + +/** + * Count the number of assignments (minterms) leading to a non-zero + */ +TASK_DECL_2(double, mtbdd_non_zero_count, MTBDD, size_t); +#define mtbdd_non_zero_count(dd, nvars) CALL(mtbdd_non_zero_count, dd, nvars) + +// Checks whether the given MTBDD (does represents a zero leaf. +int mtbdd_iszero(MTBDD); +int mtbdd_isnonzero(MTBDD); + +#define mtbdd_regular(dd) (dd & ~mtbdd_complement) diff --git a/src/sylvan_obj.cpp b/src/sylvan_obj.cpp new file mode 100644 index 000000000..a573c7a49 --- /dev/null +++ b/src/sylvan_obj.cpp @@ -0,0 +1,1039 @@ +/* + * Copyright 2011-2015 Formal Methods and Tools, University of Twente + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <sylvan_obj.hpp> + +using namespace sylvan; + +/*** + * Implementation of class Bdd + */ + +int +Bdd::operator==(const Bdd& other) const +{ + return bdd == other.bdd; +} + +int +Bdd::operator!=(const Bdd& other) const +{ + return bdd != other.bdd; +} + +Bdd +Bdd::operator=(const Bdd& right) +{ + bdd = right.bdd; + return *this; +} + +int +Bdd::operator<=(const Bdd& other) const +{ + // TODO: better implementation, since we are not interested in the BDD result + LACE_ME; + BDD r = sylvan_ite(this->bdd, sylvan_not(other.bdd), sylvan_false); + return r == sylvan_false; +} + +int +Bdd::operator>=(const Bdd& other) const +{ + // TODO: better implementation, since we are not interested in the BDD result + return other <= *this; +} + +int +Bdd::operator<(const Bdd& other) const +{ + return bdd != other.bdd && *this <= other; +} + +int +Bdd::operator>(const Bdd& other) const +{ + return bdd != other.bdd && *this >= other; +} + +Bdd +Bdd::operator!() const +{ + return Bdd(sylvan_not(bdd)); +} + +Bdd +Bdd::operator~() const +{ + return Bdd(sylvan_not(bdd)); +} + +Bdd +Bdd::operator*(const Bdd& other) const +{ + LACE_ME; + return Bdd(sylvan_and(bdd, other.bdd)); +} + +Bdd +Bdd::operator*=(const Bdd& other) +{ + LACE_ME; + bdd = sylvan_and(bdd, other.bdd); + return *this; +} + +Bdd +Bdd::operator&(const Bdd& other) const +{ + LACE_ME; + return Bdd(sylvan_and(bdd, other.bdd)); +} + +Bdd +Bdd::operator&=(const Bdd& other) +{ + LACE_ME; + bdd = sylvan_and(bdd, other.bdd); + return *this; +} + +Bdd +Bdd::operator+(const Bdd& other) const +{ + LACE_ME; + return Bdd(sylvan_or(bdd, other.bdd)); +} + +Bdd +Bdd::operator+=(const Bdd& other) +{ + LACE_ME; + bdd = sylvan_or(bdd, other.bdd); + return *this; +} + +Bdd +Bdd::operator|(const Bdd& other) const +{ + LACE_ME; + return Bdd(sylvan_or(bdd, other.bdd)); +} + +Bdd +Bdd::operator|=(const Bdd& other) +{ + LACE_ME; + bdd = sylvan_or(bdd, other.bdd); + return *this; +} + +Bdd +Bdd::operator^(const Bdd& other) const +{ + LACE_ME; + return Bdd(sylvan_xor(bdd, other.bdd)); +} + +Bdd +Bdd::operator^=(const Bdd& other) +{ + LACE_ME; + bdd = sylvan_xor(bdd, other.bdd); + return *this; +} + +Bdd +Bdd::operator-(const Bdd& other) const +{ + LACE_ME; + return Bdd(sylvan_and(bdd, sylvan_not(other.bdd))); +} + +Bdd +Bdd::operator-=(const Bdd& other) +{ + LACE_ME; + bdd = sylvan_and(bdd, sylvan_not(other.bdd)); + return *this; +} + +Bdd +Bdd::AndAbstract(const Bdd &g, const BddSet &cube) const +{ + LACE_ME; + return sylvan_and_exists(bdd, g.bdd, cube.set.bdd); +} + +Bdd +Bdd::ExistAbstract(const BddSet &cube) const +{ + LACE_ME; + return sylvan_exists(bdd, cube.set.bdd); +} + +Bdd +Bdd::UnivAbstract(const BddSet &cube) const +{ + LACE_ME; + return sylvan_forall(bdd, cube.set.bdd); +} + +Bdd +Bdd::Ite(const Bdd &g, const Bdd &h) const +{ + LACE_ME; + return sylvan_ite(bdd, g.bdd, h.bdd); +} + +Bdd +Bdd::And(const Bdd &g) const +{ + LACE_ME; + return sylvan_and(bdd, g.bdd); +} + +Bdd +Bdd::Or(const Bdd &g) const +{ + LACE_ME; + return sylvan_or(bdd, g.bdd); +} + +Bdd +Bdd::Nand(const Bdd &g) const +{ + LACE_ME; + return sylvan_nand(bdd, g.bdd); +} + +Bdd +Bdd::Nor(const Bdd &g) const +{ + LACE_ME; + return sylvan_nor(bdd, g.bdd); +} + +Bdd +Bdd::Xor(const Bdd &g) const +{ + LACE_ME; + return sylvan_xor(bdd, g.bdd); +} + +Bdd +Bdd::Xnor(const Bdd &g) const +{ + LACE_ME; + return sylvan_equiv(bdd, g.bdd); +} + +int +Bdd::Leq(const Bdd &g) const +{ + // TODO: better implementation, since we are not interested in the BDD result + LACE_ME; + BDD r = sylvan_ite(bdd, sylvan_not(g.bdd), sylvan_false); + return r == sylvan_false; +} + +Bdd +Bdd::RelPrev(const Bdd& relation, const BddSet& cube) const +{ + LACE_ME; + return sylvan_relprev(relation.bdd, bdd, cube.set.bdd); +} + +Bdd +Bdd::RelNext(const Bdd &relation, const BddSet &cube) const +{ + LACE_ME; + return sylvan_relnext(bdd, relation.bdd, cube.set.bdd); +} + +Bdd +Bdd::Closure() const +{ + LACE_ME; + return sylvan_closure(bdd); +} + +Bdd +Bdd::Constrain(const Bdd &c) const +{ + LACE_ME; + return sylvan_constrain(bdd, c.bdd); +} + +Bdd +Bdd::Restrict(const Bdd &c) const +{ + LACE_ME; + return sylvan_restrict(bdd, c.bdd); +} + +Bdd +Bdd::Compose(const BddMap &m) const +{ + LACE_ME; + return sylvan_compose(bdd, m.bdd); +} + +Bdd +Bdd::Permute(const std::vector<uint32_t>& from, const std::vector<uint32_t>& to) const +{ + LACE_ME; + + /* Create a map */ + BddMap map; + for (int i=from.size()-1; i>=0; i--) { + map.put(from[i], Bdd::bddVar(to[i])); + } + + return sylvan_compose(bdd, map.bdd); +} + +Bdd +Bdd::Support() const +{ + LACE_ME; + return sylvan_support(bdd); +} + +BDD +Bdd::GetBDD() const +{ + return bdd; +} + +void +Bdd::PrintDot(FILE *out) const +{ + sylvan_fprintdot(out, bdd); +} + +void +Bdd::GetShaHash(char *string) const +{ + sylvan_getsha(bdd, string); +} + +std::string +Bdd::GetShaHash() const +{ + char buf[65]; + sylvan_getsha(bdd, buf); + return std::string(buf); +} + +double +Bdd::SatCount(const BddSet &variables) const +{ + LACE_ME; + return sylvan_satcount(bdd, variables.set.bdd); +} + +double +Bdd::SatCount(size_t nvars) const +{ + LACE_ME; + // Note: the mtbdd_satcount can be called without initializing the MTBDD module. + return mtbdd_satcount(bdd, nvars); +} + +void +Bdd::PickOneCube(const BddSet &variables, uint8_t *values) const +{ + LACE_ME; + sylvan_sat_one(bdd, variables.set.bdd, values); +} + +std::vector<bool> +Bdd::PickOneCube(const BddSet &variables) const +{ + std::vector<bool> result = std::vector<bool>(); + + BDD bdd = this->bdd; + BDD vars = variables.set.bdd; + + if (bdd == sylvan_false) return result; + + for (; !sylvan_set_isempty(vars); vars = sylvan_set_next(vars)) { + uint32_t var = sylvan_set_var(vars); + if (bdd == sylvan_true) { + // pick 0 + result.push_back(false); + } else { + if (sylvan_var(bdd) != var) { + // pick 0 + result.push_back(false); + } else { + if (sylvan_low(bdd) == sylvan_false) { + // pick 1 + result.push_back(true); + bdd = sylvan_high(bdd); + } else { + // pick 0 + result.push_back(false); + bdd = sylvan_low(bdd); + } + } + } + } + + return result; +} + +Bdd +Bdd::PickOneCube() const +{ + LACE_ME; + return Bdd(sylvan_sat_one_bdd(bdd)); +} + +Bdd +Bdd::UnionCube(const BddSet &variables, uint8_t *values) const +{ + LACE_ME; + return sylvan_union_cube(bdd, variables.set.bdd, values); +} + +Bdd +Bdd::UnionCube(const BddSet &variables, std::vector<uint8_t> values) const +{ + LACE_ME; + uint8_t *data = values.data(); + return sylvan_union_cube(bdd, variables.set.bdd, data); +} + +/** + * @brief Generate a cube representing a set of variables + */ +Bdd +Bdd::VectorCube(const std::vector<Bdd> variables) +{ + Bdd result = Bdd::bddOne(); + for (int i=variables.size()-1; i>=0; i--) { + result *= variables[i]; + } + return result; +} + +/** + * @brief Generate a cube representing a set of variables + */ +Bdd +Bdd::VariablesCube(std::vector<uint32_t> variables) +{ + BDD result = sylvan_true; + for (int i=variables.size()-1; i>=0; i--) { + result = sylvan_makenode(variables[i], sylvan_false, result); + } + return result; +} + +size_t +Bdd::NodeCount() const +{ + return sylvan_nodecount(bdd); +} + +Bdd +Bdd::bddOne() +{ + return sylvan_true; +} + +Bdd +Bdd::bddZero() +{ + return sylvan_false; +} + +Bdd +Bdd::bddVar(uint32_t index) +{ + LACE_ME; + return sylvan_ithvar(index); +} + +Bdd +Bdd::bddCube(const BddSet &variables, uint8_t *values) +{ + LACE_ME; + return sylvan_cube(variables.set.bdd, values); +} + +Bdd +Bdd::bddCube(const BddSet &variables, std::vector<uint8_t> values) +{ + LACE_ME; + uint8_t *data = values.data(); + return sylvan_cube(variables.set.bdd, data); +} + +int +Bdd::isConstant() const +{ + return bdd == sylvan_true || bdd == sylvan_false; +} + +int +Bdd::isTerminal() const +{ + return bdd == sylvan_true || bdd == sylvan_false; +} + +int +Bdd::isOne() const +{ + return bdd == sylvan_true; +} + +int +Bdd::isZero() const +{ + return bdd == sylvan_false; +} + +uint32_t +Bdd::TopVar() const +{ + return sylvan_var(bdd); +} + +Bdd +Bdd::Then() const +{ + return Bdd(sylvan_high(bdd)); +} + +Bdd +Bdd::Else() const +{ + return Bdd(sylvan_low(bdd)); +} + +/*** + * Implementation of class BddMap + */ + +BddMap::BddMap(uint32_t key_variable, const Bdd value) +{ + bdd = sylvan_map_add(sylvan_map_empty(), key_variable, value.bdd); +} + + +BddMap +BddMap::operator+(const Bdd& other) const +{ + return BddMap(sylvan_map_addall(bdd, other.bdd)); +} + +BddMap +BddMap::operator+=(const Bdd& other) +{ + bdd = sylvan_map_addall(bdd, other.bdd); + return *this; +} + +BddMap +BddMap::operator-(const Bdd& other) const +{ + return BddMap(sylvan_map_removeall(bdd, other.bdd)); +} + +BddMap +BddMap::operator-=(const Bdd& other) +{ + bdd = sylvan_map_removeall(bdd, other.bdd); + return *this; +} + +void +BddMap::put(uint32_t key, Bdd value) +{ + bdd = sylvan_map_add(bdd, key, value.bdd); +} + +void +BddMap::removeKey(uint32_t key) +{ + bdd = sylvan_map_remove(bdd, key); +} + +size_t +BddMap::size() const +{ + return sylvan_map_count(bdd); +} + +int +BddMap::isEmpty() const +{ + return sylvan_map_isempty(bdd); +} + + +/*** + * Implementation of class Mtbdd + */ + +Mtbdd +Mtbdd::int64Terminal(int64_t value) +{ + return mtbdd_int64(value); +} + +Mtbdd +Mtbdd::doubleTerminal(double value) +{ + return mtbdd_double(value); +} + +Mtbdd +Mtbdd::fractionTerminal(int64_t nominator, uint64_t denominator) +{ + return mtbdd_fraction(nominator, denominator); +} + +Mtbdd +Mtbdd::terminal(uint32_t type, uint64_t value) +{ + return mtbdd_makeleaf(type, value); +} + +Mtbdd +Mtbdd::mtbddVar(uint32_t variable) +{ + return mtbdd_makenode(variable, mtbdd_false, mtbdd_true); +} + +Mtbdd +Mtbdd::mtbddOne() +{ + return mtbdd_true; +} + +Mtbdd +Mtbdd::mtbddZero() +{ + return mtbdd_false; +} + +Mtbdd +Mtbdd::mtbddCube(const BddSet &variables, uint8_t *values, const Mtbdd &terminal) +{ + LACE_ME; + return mtbdd_cube(variables.set.bdd, values, terminal.mtbdd); +} + +Mtbdd +Mtbdd::mtbddCube(const BddSet &variables, std::vector<uint8_t> values, const Mtbdd &terminal) +{ + LACE_ME; + uint8_t *data = values.data(); + return mtbdd_cube(variables.set.bdd, data, terminal.mtbdd); +} + +int +Mtbdd::isTerminal() const +{ + return mtbdd_isleaf(mtbdd); +} + +int +Mtbdd::isLeaf() const +{ + return mtbdd_isleaf(mtbdd); +} + +int +Mtbdd::isOne() const +{ + return mtbdd == mtbdd_true; +} + +int +Mtbdd::isZero() const +{ + return mtbdd == mtbdd_false; +} + +uint32_t +Mtbdd::TopVar() const +{ + return mtbdd_getvar(mtbdd); +} + +Mtbdd +Mtbdd::Then() const +{ + return mtbdd_isnode(mtbdd) ? mtbdd_gethigh(mtbdd) : mtbdd; +} + +Mtbdd +Mtbdd::Else() const +{ + return mtbdd_isnode(mtbdd) ? mtbdd_getlow(mtbdd) : mtbdd; +} + +Mtbdd +Mtbdd::Negate() const +{ + LACE_ME; + return mtbdd_negate(mtbdd); +} + +Mtbdd +Mtbdd::Apply(const Mtbdd &other, mtbdd_apply_op op) const +{ + LACE_ME; + return mtbdd_apply(mtbdd, other.mtbdd, op); +} + +Mtbdd +Mtbdd::UApply(mtbdd_uapply_op op, size_t param) const +{ + LACE_ME; + return mtbdd_uapply(mtbdd, op, param); +} + +Mtbdd +Mtbdd::Abstract(const BddSet &variables, mtbdd_abstract_op op) const +{ + LACE_ME; + return mtbdd_abstract(mtbdd, variables.set.bdd, op); +} + +Mtbdd +Mtbdd::Ite(const Mtbdd &g, const Mtbdd &h) const +{ + LACE_ME; + return mtbdd_ite(mtbdd, g.mtbdd, h.mtbdd); +} + +Mtbdd +Mtbdd::Plus(const Mtbdd &other) const +{ + LACE_ME; + return mtbdd_plus(mtbdd, other.mtbdd); +} + +Mtbdd +Mtbdd::Times(const Mtbdd &other) const +{ + LACE_ME; + return mtbdd_times(mtbdd, other.mtbdd); +} + +Mtbdd +Mtbdd::Min(const Mtbdd &other) const +{ + LACE_ME; + return mtbdd_min(mtbdd, other.mtbdd); +} + +Mtbdd +Mtbdd::Max(const Mtbdd &other) const +{ + LACE_ME; + return mtbdd_max(mtbdd, other.mtbdd); +} + +Mtbdd +Mtbdd::AbstractPlus(const BddSet &variables) const +{ + LACE_ME; + return mtbdd_abstract_plus(mtbdd, variables.set.bdd); +} + +Mtbdd +Mtbdd::AbstractTimes(const BddSet &variables) const +{ + LACE_ME; + return mtbdd_abstract_times(mtbdd, variables.set.bdd); +} + +Mtbdd +Mtbdd::AbstractMin(const BddSet &variables) const +{ + LACE_ME; + return mtbdd_abstract_min(mtbdd, variables.set.bdd); +} + +Mtbdd +Mtbdd::AbstractMax(const BddSet &variables) const +{ + LACE_ME; + return mtbdd_abstract_max(mtbdd, variables.set.bdd); +} + +Mtbdd +Mtbdd::AndExists(const Mtbdd &other, const BddSet &variables) const +{ + LACE_ME; + return mtbdd_and_exists(mtbdd, other.mtbdd, variables.set.bdd); +} + +int +Mtbdd::operator==(const Mtbdd& other) const +{ + return mtbdd == other.mtbdd; +} + +int +Mtbdd::operator!=(const Mtbdd& other) const +{ + return mtbdd != other.mtbdd; +} + +Mtbdd +Mtbdd::operator=(const Mtbdd& right) +{ + mtbdd = right.mtbdd; + return *this; +} + +Mtbdd +Mtbdd::operator!() const +{ + return mtbdd_not(mtbdd); +} + +Mtbdd +Mtbdd::operator~() const +{ + return mtbdd_not(mtbdd); +} + +Mtbdd +Mtbdd::operator*(const Mtbdd& other) const +{ + LACE_ME; + return mtbdd_times(mtbdd, other.mtbdd); +} + +Mtbdd +Mtbdd::operator*=(const Mtbdd& other) +{ + LACE_ME; + mtbdd = mtbdd_times(mtbdd, other.mtbdd); + return *this; +} + +Mtbdd +Mtbdd::operator+(const Mtbdd& other) const +{ + LACE_ME; + return mtbdd_plus(mtbdd, other.mtbdd); +} + +Mtbdd +Mtbdd::operator+=(const Mtbdd& other) +{ + LACE_ME; + mtbdd = mtbdd_plus(mtbdd, other.mtbdd); + return *this; +} + +Mtbdd +Mtbdd::operator-(const Mtbdd& other) const +{ + LACE_ME; + return mtbdd_minus(mtbdd, other.mtbdd); +} + +Mtbdd +Mtbdd::operator-=(const Mtbdd& other) +{ + LACE_ME; + mtbdd = mtbdd_minus(mtbdd, other.mtbdd); + return *this; +} + +Mtbdd +Mtbdd::MtbddThreshold(double value) const +{ + LACE_ME; + return mtbdd_threshold_double(mtbdd, value); +} + +Mtbdd +Mtbdd::MtbddStrictThreshold(double value) const +{ + LACE_ME; + return mtbdd_strict_threshold_double(mtbdd, value); +} + +Bdd +Mtbdd::BddThreshold(double value) const +{ + LACE_ME; + return mtbdd_threshold_double(mtbdd, value); +} + +Bdd +Mtbdd::BddStrictThreshold(double value) const +{ + LACE_ME; + return mtbdd_strict_threshold_double(mtbdd, value); +} + +Mtbdd +Mtbdd::Support() const +{ + LACE_ME; + return mtbdd_support(mtbdd); +} + +MTBDD +Mtbdd::GetMTBDD() const +{ + return mtbdd; +} + +Mtbdd +Mtbdd::Compose(MtbddMap &m) const +{ + LACE_ME; + return mtbdd_compose(mtbdd, m.mtbdd); +} + +Mtbdd +Mtbdd::Permute(const std::vector<uint32_t>& from, const std::vector<uint32_t>& to) const +{ + LACE_ME; + + /* Create a map */ + MtbddMap map; + for (int i=from.size()-1; i>=0; i--) { + map.put(from[i], Bdd::bddVar(to[i])); + } + + return mtbdd_compose(mtbdd, map.mtbdd); +} + +double +Mtbdd::SatCount(size_t nvars) const +{ + LACE_ME; + return mtbdd_satcount(mtbdd, nvars); +} + +double +Mtbdd::SatCount(const BddSet &variables) const +{ + return SatCount(sylvan_set_count(variables.set.bdd)); +} + +size_t +Mtbdd::NodeCount() const +{ + LACE_ME; + return mtbdd_nodecount(mtbdd); +} + + +/*** + * Implementation of class MtbddMap + */ + +MtbddMap::MtbddMap(uint32_t key_variable, Mtbdd value) +{ + mtbdd = mtbdd_map_add(mtbdd_map_empty(), key_variable, value.mtbdd); +} + +MtbddMap +MtbddMap::operator+(const Mtbdd& other) const +{ + return MtbddMap(mtbdd_map_addall(mtbdd, other.mtbdd)); +} + +MtbddMap +MtbddMap::operator+=(const Mtbdd& other) +{ + mtbdd = mtbdd_map_addall(mtbdd, other.mtbdd); + return *this; +} + +MtbddMap +MtbddMap::operator-(const Mtbdd& other) const +{ + return MtbddMap(mtbdd_map_removeall(mtbdd, other.mtbdd)); +} + +MtbddMap +MtbddMap::operator-=(const Mtbdd& other) +{ + mtbdd = mtbdd_map_removeall(mtbdd, other.mtbdd); + return *this; +} + +void +MtbddMap::put(uint32_t key, Mtbdd value) +{ + mtbdd = mtbdd_map_add(mtbdd, key, value.mtbdd); +} + +void +MtbddMap::removeKey(uint32_t key) +{ + mtbdd = mtbdd_map_remove(mtbdd, key); +} + +size_t +MtbddMap::size() +{ + return mtbdd_map_count(mtbdd); +} + +int +MtbddMap::isEmpty() +{ + return mtbdd_map_isempty(mtbdd); +} + + +/*** + * Implementation of class Sylvan + */ + +void +Sylvan::initPackage(size_t initialTableSize, size_t maxTableSize, size_t initialCacheSize, size_t maxCacheSize) +{ + sylvan_init_package(initialTableSize, maxTableSize, initialCacheSize, maxCacheSize); +} + +void +Sylvan::initBdd(int granularity) +{ + sylvan_init_bdd(granularity); +} + +void +Sylvan::initMtbdd() +{ + sylvan_init_mtbdd(); +} + +void +Sylvan::quitPackage() +{ + sylvan_quit(); +} + +#include "sylvan_obj_storm.cpp" diff --git a/src/sylvan_obj.hpp b/src/sylvan_obj.hpp new file mode 100644 index 000000000..e7f77f6ca --- /dev/null +++ b/src/sylvan_obj.hpp @@ -0,0 +1,855 @@ +/* + * Copyright 2011-2015 Formal Methods and Tools, University of Twente + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SYLVAN_OBJ_H +#define SYLVAN_OBJ_H + +#include <string> +#include <vector> + +#include <lace.h> +#include <sylvan.h> + +namespace sylvan { + +class BddSet; +class BddMap; +class Mtbdd; + +class Bdd { + friend class Sylvan; + friend class BddSet; + friend class BddMap; + friend class Mtbdd; + +public: + Bdd() { bdd = sylvan_false; sylvan_protect(&bdd); } + Bdd(const BDD from) : bdd(from) { sylvan_protect(&bdd); } + Bdd(const Bdd &from) : bdd(from.bdd) { sylvan_protect(&bdd); } + Bdd(const uint32_t var) { bdd = sylvan_ithvar(var); sylvan_protect(&bdd); } + ~Bdd() { sylvan_unprotect(&bdd); } + + /** + * @brief Creates a Bdd representing just the variable index in its positive form + * The variable index must be a 0<=index<=2^23 (we use 24 bits internally) + */ + static Bdd bddVar(uint32_t index); + + /** + * @brief Returns the Bdd representing "True" + */ + static Bdd bddOne(); + + /** + * @brief Returns the Bdd representing "False" + */ + static Bdd bddZero(); + + /** + * @brief Returns the Bdd representing a cube of variables, according to the given values. + * @param variables the variables that will be in the cube in their positive or negative form + * @param values a character array describing how the variables will appear in the result + * The length of string must be equal to the number of variables in the cube. + * For every ith char in string, if it is 0, the corresponding variable will appear in its negative form, + * if it is 1, it will appear in its positive form, and if it is 2, it will appear as "any", thus it will + * be skipped. + */ + static Bdd bddCube(const BddSet &variables, unsigned char *values); + + /** + * @brief Returns the Bdd representing a cube of variables, according to the given values. + * @param variables the variables that will be in the cube in their positive or negative form + * @param string a character array describing how the variables will appear in the result + * The length of string must be equal to the number of variables in the cube. + * For every ith char in string, if it is 0, the corresponding variable will appear in its negative form, + * if it is 1, it will appear in its positive form, and if it is 2, it will appear as "any", thus it will + * be skipped. + */ + static Bdd bddCube(const BddSet &variables, std::vector<uint8_t> values); + + int operator==(const Bdd& other) const; + int operator!=(const Bdd& other) const; + Bdd operator=(const Bdd& right); + int operator<=(const Bdd& other) const; + int operator>=(const Bdd& other) const; + int operator<(const Bdd& other) const; + int operator>(const Bdd& other) const; + Bdd operator!() const; + Bdd operator~() const; + Bdd operator*(const Bdd& other) const; + Bdd operator*=(const Bdd& other); + Bdd operator&(const Bdd& other) const; + Bdd operator&=(const Bdd& other); + Bdd operator+(const Bdd& other) const; + Bdd operator+=(const Bdd& other); + Bdd operator|(const Bdd& other) const; + Bdd operator|=(const Bdd& other); + Bdd operator^(const Bdd& other) const; + Bdd operator^=(const Bdd& other); + Bdd operator-(const Bdd& other) const; + Bdd operator-=(const Bdd& other); + + /** + * @brief Returns non-zero if this Bdd is bddOne() or bddZero() + */ + int isConstant() const; + + /** + * @brief Returns non-zero if this Bdd is bddOne() or bddZero() + */ + int isTerminal() const; + + /** + * @brief Returns non-zero if this Bdd is bddOne() + */ + int isOne() const; + + /** + * @brief Returns non-zero if this Bdd is bddZero() + */ + int isZero() const; + + /** + * @brief Returns the top variable index of this Bdd (the variable in the root node) + */ + uint32_t TopVar() const; + + /** + * @brief Follows the high edge ("then") of the root node of this Bdd + */ + Bdd Then() const; + + /** + * @brief Follows the low edge ("else") of the root node of this Bdd + */ + Bdd Else() const; + + /** + * @brief Computes \exists cube: f \and g + */ + Bdd AndAbstract(const Bdd& g, const BddSet& cube) const; + + /** + * @brief Computes \exists cube: f + */ + Bdd ExistAbstract(const BddSet& cube) const; + + /** + * @brief Computes \forall cube: f + */ + Bdd UnivAbstract(const BddSet& cube) const; + + /** + * @brief Computes if f then g else h + */ + Bdd Ite(const Bdd& g, const Bdd& h) const; + + /** + * @brief Computes f \and g + */ + Bdd And(const Bdd& g) const; + + /** + * @brief Computes f \or g + */ + Bdd Or(const Bdd& g) const; + + /** + * @brief Computes \not (f \and g) + */ + Bdd Nand(const Bdd& g) const; + + /** + * @brief Computes \not (f \or g) + */ + Bdd Nor(const Bdd& g) const; + + /** + * @brief Computes f \xor g + */ + Bdd Xor(const Bdd& g) const; + + /** + * @brief Computes \not (f \xor g), i.e. f \equiv g + */ + Bdd Xnor(const Bdd& g) const; + + /** + * @brief Returns whether all elements in f are also in g + */ + int Leq(const Bdd& g) const; + + /** + * @brief Computes the reverse application of a transition relation to this set. + * @param relation the transition relation to apply + * @param cube the variables that are in the transition relation + * This function assumes that s,t are interleaved with s even and t odd (s+1). + * Other variables in the relation are ignored (by existential quantification) + * Set cube to "false" (illegal cube) to assume all encountered variables are in s,t + * + * Use this function to concatenate two relations --> --> + * or to take the 'previous' of a set --> S + */ + Bdd RelPrev(const Bdd& relation, const BddSet& cube) const; + + /** + * @brief Computes the application of a transition relation to this set. + * @param relation the transition relation to apply + * @param cube the variables that are in the transition relation + * This function assumes that s,t are interleaved with s even and t odd (s+1). + * Other variables in the relation are ignored (by existential quantification) + * Set cube to "false" (illegal cube) to assume all encountered variables are in s,t + * + * Use this function to take the 'next' of a set S --> + */ + Bdd RelNext(const Bdd& relation, const BddSet& cube) const; + + /** + * @brief Computes the transitive closure by traversing the BDD recursively. + * See Y. Matsunaga, P. C. McGeer, R. K. Brayton + * On Computing the Transitive Closre of a State Transition Relation + * 30th ACM Design Automation Conference, 1993. + */ + Bdd Closure() const; + + /** + * @brief Computes the constrain f @ c + */ + Bdd Constrain(const Bdd &c) const; + + /** + * @brief Computes the BDD restrict according to Coudert and Madre's algorithm (ICCAD90). + */ + Bdd Restrict(const Bdd &c) const; + + /** + * @brief Functional composition. Whenever a variable v in the map m is found in the BDD, + * it is substituted by the associated function. + * You can also use this function to implement variable reordering. + */ + Bdd Compose(const BddMap &m) const; + + /** + * @brief Substitute all variables in the array from by the corresponding variables in to. + */ + Bdd Permute(const std::vector<uint32_t>& from, const std::vector<uint32_t>& to) const; + + /** + * @brief Computes the support of a Bdd. + */ + Bdd Support() const; + + /** + * @brief Gets the BDD of this Bdd (for C functions) + */ + BDD GetBDD() const; + + /** + * @brief Writes .dot file of this Bdd. Not thread-safe! + */ + void PrintDot(FILE *out) const; + + /** + * @brief Gets a SHA2 hash that describes the structure of this Bdd. + * @param string a character array of at least 65 characters (includes zero-termination) + * This hash is 64 characters long and is independent of the memory locations of BDD nodes. + */ + void GetShaHash(char *string) const; + + std::string GetShaHash() const; + + /** + * @brief Computes the number of satisfying variable assignments, using variables in cube. + */ + double SatCount(const BddSet &cube) const; + + /** + * @brief Compute the number of satisfying variable assignments, using the given number of variables. + */ + double SatCount(const size_t nvars) const; + + /** + * @brief Gets one satisfying assignment according to the variables. + * @param variables The set of variables to be assigned, must include the support of the Bdd. + */ + void PickOneCube(const BddSet &variables, uint8_t *string) const; + + /** + * @brief Gets one satisfying assignment according to the variables. + * @param variables The set of variables to be assigned, must include the support of the Bdd. + * Returns an empty vector when either this Bdd equals bddZero() or the cube is empty. + */ + std::vector<bool> PickOneCube(const BddSet &variables) const; + + /** + * @brief Gets a cube that satisfies this Bdd. + */ + Bdd PickOneCube() const; + + /** + * @brief Faster version of: *this + Sylvan::bddCube(variables, values); + */ + Bdd UnionCube(const BddSet &variables, uint8_t *values) const; + + /** + * @brief Faster version of: *this + Sylvan::bddCube(variables, values); + */ + Bdd UnionCube(const BddSet &variables, std::vector<uint8_t> values) const; + + /** + * @brief Generate a cube representing a set of variables + */ + static Bdd VectorCube(const std::vector<Bdd> variables); + + /** + * @brief Generate a cube representing a set of variables + * @param variables An sorted set of variable indices + */ + static Bdd VariablesCube(const std::vector<uint32_t> variables); + + /** + * @brief Gets the number of nodes in this Bdd. Not thread-safe! + */ + size_t NodeCount() const; + +#include "sylvan_obj_bdd_storm.hpp" + +private: + BDD bdd; +}; + +class BddSet +{ + friend class Bdd; + friend class Mtbdd; + Bdd set; + +public: + /** + * @brief Create a new empty set. + */ + BddSet() : set(Bdd::bddOne()) {} + + /** + * @brief Wrap the BDD cube <other> in a set. + */ + BddSet(const Bdd &other) : set(other) {} + + /** + * @brief Create a copy of the set <other>. + */ + BddSet(const BddSet &other) : set(other.set) {} + + /** + * @brief Add the variable <variable> to this set. + */ + void add(uint32_t variable) { + set *= Bdd::bddVar(variable); + } + + /** + * @brief Add all variables in the set <other> to this set. + */ + void add(BddSet &other) { + set *= other.set; + } + + /** + * @brief Remove the variable <variable> from this set. + */ + void remove(uint32_t variable) { + set = set.ExistAbstract(Bdd::bddVar(variable)); + } + + /** + * @brief Remove all variables in the set <other> from this set. + */ + void remove(BddSet &other) { + set = set.ExistAbstract(other.set); + } + + /** + * @brief Retrieve the head of the set. (The first variable.) + */ + uint32_t TopVar() const { + return set.TopVar(); + } + + /** + * @brief Retrieve the tail of the set. (The set containing all but the first variables.) + */ + BddSet Next() const { + Bdd then = set.Then(); + return BddSet(then); + } + + /** + * @brief Return true if this set is empty, or false otherwise. + */ + bool isEmpty() const { + return set.isOne(); + } + + /** + * @brief Return true if this set contains the variable <variable>, or false otherwise. + */ + bool contains(uint32_t variable) const { + if (isEmpty()) return false; + else if (TopVar() == variable) return true; + else return Next().contains(variable); + } + + /** + * @brief Return the number of variables in this set. + */ + size_t size() const { + if (isEmpty()) return 0; + else return 1 + Next().size(); + } + + /** + * @brief Create a set containing the <length> variables in <arr>. + * It is advised to have the variables in <arr> in ascending order. + */ + static BddSet fromArray(BDDVAR *arr, size_t length) { + BddSet set; + for (size_t i = 0; i < length; i++) { + set.add(arr[length-i-1]); + } + return set; + } + + /** + * @brief Create a set containing the variables in <variables>. + * It is advised to have the variables in <arr> in ascending order. + */ + static BddSet fromVector(const std::vector<Bdd> variables) { + BddSet set; + for (int i=variables.size()-1; i>=0; i--) { + set.set *= variables[i]; + } + return set; + } + + /** + * @brief Create a set containing the variables in <variables>. + * It is advised to have the variables in <arr> in ascending order. + */ + static BddSet fromVector(const std::vector<uint32_t> variables) { + BddSet set; + for (int i=variables.size()-1; i>=0; i--) { + set.add(variables[i]); + } + return set; + } + + /** + * @brief Write all variables in this set to <arr>. + * @param arr An array of at least size this.size(). + */ + void toArray(BDDVAR *arr) const { + if (!isEmpty()) { + *arr = TopVar(); + Next().toArray(arr+1); + } + } + + /** + * @brief Return the vector of all variables in this set. + */ + std::vector<uint32_t> toVector() const { + std::vector<uint32_t> result; + Bdd x = set; + while (!x.isOne()) { + result.push_back(x.TopVar()); + x = x.Then(); + } + return result; + } +}; + +class BddMap +{ + friend class Bdd; + BDD bdd; + BddMap(const BDD from) : bdd(from) { sylvan_protect(&bdd); } + BddMap(const Bdd &from) : bdd(from.bdd) { sylvan_protect(&bdd); } +public: + BddMap() : bdd(sylvan_map_empty()) { sylvan_protect(&bdd); } + ~BddMap() { sylvan_unprotect(&bdd); } + + BddMap(uint32_t key_variable, const Bdd value); + + BddMap operator+(const Bdd& other) const; + BddMap operator+=(const Bdd& other); + BddMap operator-(const Bdd& other) const; + BddMap operator-=(const Bdd& other); + + /** + * @brief Adds a key-value pair to the map + */ + void put(uint32_t key, Bdd value); + + /** + * @brief Removes a key-value pair from the map + */ + void removeKey(uint32_t key); + + /** + * @brief Returns the number of key-value pairs in this map + */ + size_t size() const; + + /** + * @brief Returns non-zero when this map is empty + */ + int isEmpty() const; +}; + +class MtbddMap; + +class Mtbdd { + friend class Sylvan; + friend class MtbddMap; + +public: + Mtbdd() { mtbdd = sylvan_false; mtbdd_protect(&mtbdd); } + Mtbdd(const MTBDD from) : mtbdd(from) { mtbdd_protect(&mtbdd); } + Mtbdd(const Mtbdd &from) : mtbdd(from.mtbdd) { mtbdd_protect(&mtbdd); } + Mtbdd(const Bdd &from) : mtbdd(from.bdd) { mtbdd_protect(&mtbdd); } + ~Mtbdd() { mtbdd_unprotect(&mtbdd); } + + /** + * @brief Creates a Mtbdd leaf representing the int64 value <value> + */ + static Mtbdd int64Terminal(int64_t value); + + /** + * @brief Creates a Mtbdd leaf representing the floating-point value <value> + */ + static Mtbdd doubleTerminal(double value); + + /** + * @brief Creates a Mtbdd leaf representing the fraction value <nominator>/<denominator> + * Internally, Sylvan uses 32-bit values and reports overflows to stderr. + */ + static Mtbdd fractionTerminal(int64_t nominator, uint64_t denominator); + + /** + * @brief Creates a Mtbdd leaf of type <type> holding value <value> + * This is useful for custom Mtbdd types. + */ + static Mtbdd terminal(uint32_t type, uint64_t value); + + /** + * @brief Creates a Boolean Mtbdd representing jsut the variable index in its positive form + * The variable index must be 0<=index<=2^23 (Sylvan uses 24 bits internally) + */ + static Mtbdd mtbddVar(uint32_t variable); + + /** + * @brief Returns the Boolean Mtbdd representing "True" + */ + static Mtbdd mtbddOne(); + + /** + * @brief Returns the Boolean Mtbdd representing "False" + */ + static Mtbdd mtbddZero(); + + /** + * @brief Returns the Mtbdd representing a cube of variables, according to the given values. + * @param variables the variables that will be in the cube in their positive or negative form + * @param values a character array describing how the variables will appear in the result + * @param terminal the leaf of the cube + * The length of string must be equal to the number of variables in the cube. + * For every ith char in string, if it is 0, the corresponding variable will appear in its negative form, + * if it is 1, it will appear in its positive form, and if it is 2, it will appear as "any", thus it will + * be skipped. + */ + static Mtbdd mtbddCube(const BddSet &variables, unsigned char *values, const Mtbdd &terminal); + + /** + * @brief Returns the Mtbdd representing a cube of variables, according to the given values. + * @param variables the variables that will be in the cube in their positive or negative form + * @param values a character array describing how the variables will appear in the result + * @param terminal the leaf of the cube + * The length of string must be equal to the number of variables in the cube. + * For every ith char in string, if it is 0, the corresponding variable will appear in its negative form, + * if it is 1, it will appear in its positive form, and if it is 2, it will appear as "any", thus it will + * be skipped. + */ + static Mtbdd mtbddCube(const BddSet &variables, std::vector<uint8_t> values, const Mtbdd &terminal); + + int operator==(const Mtbdd& other) const; + int operator!=(const Mtbdd& other) const; + Mtbdd operator=(const Mtbdd& right); + Mtbdd operator!() const; + Mtbdd operator~() const; + Mtbdd operator*(const Mtbdd& other) const; + Mtbdd operator*=(const Mtbdd& other); + Mtbdd operator+(const Mtbdd& other) const; + Mtbdd operator+=(const Mtbdd& other); + Mtbdd operator-(const Mtbdd& other) const; + Mtbdd operator-=(const Mtbdd& other); + + // not implemented (compared to Bdd): <=, >=, <, >, &, &=, |, |=, ^, ^= + + /** + * @brief Returns non-zero if this Mtbdd is a leaf + */ + int isTerminal() const; + + /** + * @brief Returns non-zero if this Mtbdd is a leaf + */ + int isLeaf() const; + + /** + * @brief Returns non-zero if this Mtbdd is mtbddOne() + */ + int isOne() const; + + /** + * @brief Returns non-zero if this Mtbdd is mtbddZero() + */ + int isZero() const; + + /** + * @brief Returns the top variable index of this Mtbdd (the variable in the root node) + */ + uint32_t TopVar() const; + + /** + * @brief Follows the high edge ("then") of the root node of this Mtbdd + */ + Mtbdd Then() const; + + /** + * @brief Follows the low edge ("else") of the root node of this Mtbdd + */ + Mtbdd Else() const; + + /** + * @brief Returns the negation of the MTBDD (every terminal negative) + * Do not use this for Boolean MTBDDs, only for Integer/Double/Fraction MTBDDs. + */ + Mtbdd Negate() const; + + /** + * @brief Applies the binary operation <op> + */ + Mtbdd Apply(const Mtbdd &other, mtbdd_apply_op op) const; + + /** + * @brief Applies the unary operation <op> with parameter <param> + */ + Mtbdd UApply(mtbdd_uapply_op op, size_t param) const; + + /** + * @brief Computers the abstraction on variables <variables> using operator <op>. + * See also: AbstractPlus, AbstractTimes, AbstractMin, AbstractMax + */ + Mtbdd Abstract(const BddSet &variables, mtbdd_abstract_op op) const; + + /** + * @brief Computes if f then g else h + * This Mtbdd must be a Boolean Mtbdd + */ + Mtbdd Ite(const Mtbdd &g, const Mtbdd &h) const; + + /** + * @brief Computes f + g + */ + Mtbdd Plus(const Mtbdd &other) const; + + /** + * @brief Computes f * g + */ + Mtbdd Times(const Mtbdd &other) const; + + /** + * @brief Computes min(f, g) + */ + Mtbdd Min(const Mtbdd &other) const; + + /** + * @brief Computes max(f, g) + */ + Mtbdd Max(const Mtbdd &other) const; + + /** + * @brief Computes abstraction by summation (existential quantification) + */ + Mtbdd AbstractPlus(const BddSet &variables) const; + + /** + * @brief Computes abstraction by multiplication (universal quantification) + */ + Mtbdd AbstractTimes(const BddSet &variables) const; + + /** + * @brief Computes abstraction by minimum + */ + Mtbdd AbstractMin(const BddSet &variables) const; + + /** + * @brief Computes abstraction by maximum + */ + Mtbdd AbstractMax(const BddSet &variables) const; + + /** + * @brief Computes abstraction by summation of f \times g + */ + Mtbdd AndExists(const Mtbdd &other, const BddSet &variables) const; + + /** + * @brief Convert floating-point/fraction Mtbdd to a Boolean Mtbdd, leaf >= value ? true : false + */ + Mtbdd MtbddThreshold(double value) const; + + /** + * @brief Convert floating-point/fraction Mtbdd to a Boolean Mtbdd, leaf > value ? true : false + */ + Mtbdd MtbddStrictThreshold(double value) const; + + /** + * @brief Convert floating-point/fraction Mtbdd to a Boolean Mtbdd, leaf >= value ? true : false + * Same as MtbddThreshold (Bdd = Boolean Mtbdd) + */ + Bdd BddThreshold(double value) const; + + /** + * @brief Convert floating-point/fraction Mtbdd to a Boolean Mtbdd, leaf > value ? true : false + * Same as MtbddStrictThreshold (Bdd = Boolean Mtbdd) + */ + Bdd BddStrictThreshold(double value) const; + + /** + * @brief Computes the support of a Mtbdd. + */ + Mtbdd Support() const; + + /** + * @brief Gets the MTBDD of this Mtbdd (for C functions) + */ + MTBDD GetMTBDD() const; + + /** + * @brief Functional composition. Whenever a variable v in the map m is found in the MTBDD, + * it is substituted by the associated function (which should be a Boolean MTBDD) + * You can also use this function to implement variable reordering. + */ + Mtbdd Compose(MtbddMap &m) const; + + /** + * @brief Substitute all variables in the array from by the corresponding variables in to. + */ + Mtbdd Permute(const std::vector<uint32_t>& from, const std::vector<uint32_t>& to) const; + + /** + * @brief Compute the number of satisfying variable assignments, using variables in cube. + */ + double SatCount(const BddSet &variables) const; + + /** + * @brief Compute the number of satisfying variable assignments, using the given number of variables. + */ + double SatCount(const size_t nvars) const; + + /** + * @brief Gets the number of nodes in this Bdd. Not thread-safe! + */ + size_t NodeCount() const; + +#include "sylvan_obj_mtbdd_storm.hpp" + +private: + MTBDD mtbdd; +}; + +class MtbddMap +{ + friend class Mtbdd; + MTBDD mtbdd; + MtbddMap(MTBDD from) : mtbdd(from) { mtbdd_protect(&mtbdd); } + MtbddMap(Mtbdd &from) : mtbdd(from.mtbdd) { mtbdd_protect(&mtbdd); } +public: + MtbddMap() : mtbdd(mtbdd_map_empty()) { mtbdd_protect(&mtbdd); } + ~MtbddMap() { mtbdd_unprotect(&mtbdd); } + + MtbddMap(uint32_t key_variable, Mtbdd value); + + MtbddMap operator+(const Mtbdd& other) const; + MtbddMap operator+=(const Mtbdd& other); + MtbddMap operator-(const Mtbdd& other) const; + MtbddMap operator-=(const Mtbdd& other); + + /** + * @brief Adds a key-value pair to the map + */ + void put(uint32_t key, Mtbdd value); + + /** + * @brief Removes a key-value pair from the map + */ + void removeKey(uint32_t key); + + /** + * @brief Returns the number of key-value pairs in this map + */ + size_t size(); + + /** + * @brief Returns non-zero when this map is empty + */ + int isEmpty(); +}; + +class Sylvan { +public: + /** + * @brief Initializes the Sylvan framework, call this only once in your program. + * @param initialTableSize The initial size of the nodes table. Must be a power of two. + * @param maxTableSize The maximum size of the nodes table. Must be a power of two. + * @param initialCacheSize The initial size of the operation cache. Must be a power of two. + * @param maxCacheSize The maximum size of the operation cache. Must be a power of two. + */ + static void initPackage(size_t initialTableSize, size_t maxTableSize, size_t initialCacheSize, size_t maxCacheSize); + + /** + * @brief Initializes the BDD module of the Sylvan framework. + * @param granularity determins operation cache behavior; for higher values (2+) it will use the operation cache less often. + * Values of 3-7 may result in better performance, since occasionally not using the operation cache is fine in practice. + * A granularity of 1 means that every BDD operation will be cached at every variable level. + */ + static void initBdd(int granularity); + + /** + * @brief Initializes the MTBDD module of the Sylvan framework. + */ + static void initMtbdd(); + + /** + * @brief Frees all memory in use by Sylvan. + * Warning: if you have any Bdd objects which are not bddZero() or bddOne() after this, your program may crash! + */ + static void quitPackage(); +}; + +} + +#endif diff --git a/src/sylvan_obj_bdd_storm.hpp b/src/sylvan_obj_bdd_storm.hpp new file mode 100644 index 000000000..393ce988c --- /dev/null +++ b/src/sylvan_obj_bdd_storm.hpp @@ -0,0 +1,3 @@ + Mtbdd toDoubleMtbdd() const; + Mtbdd toInt64Mtbdd() const; + Mtbdd Ite(Mtbdd const& thenDd, Mtbdd const& elseDd) const; diff --git a/src/sylvan_obj_mtbdd_storm.hpp b/src/sylvan_obj_mtbdd_storm.hpp new file mode 100644 index 000000000..26e5ea4be --- /dev/null +++ b/src/sylvan_obj_mtbdd_storm.hpp @@ -0,0 +1,51 @@ + /** + * @brief Computes f - g + */ + Mtbdd Minus(const Mtbdd &other) const; + + /** + * @brief Computes f / g + */ + Mtbdd Divide(const Mtbdd &other) const; + + Bdd NotZero() const; + + Bdd Equals(const Mtbdd& other) const; + + Bdd Less(const Mtbdd& other) const; + + Bdd LessOrEqual(const Mtbdd& other) const; + + Mtbdd Minimum() const; + + Mtbdd Maximum() const; + + bool EqualNorm(const Mtbdd& other, double epsilon) const; + + bool EqualNormRel(const Mtbdd& other, double epsilon) const; + + Mtbdd Floor() const; + + Mtbdd Ceil() const; + + Mtbdd Pow(const Mtbdd& other) const; + + Mtbdd Mod(const Mtbdd& other) const; + + Mtbdd Logxy(const Mtbdd& other) const; + + size_t CountLeaves() const; + + /** + * @brief Compute the number of non-zero variable assignments, using variables in cube. + */ + double NonZeroCount(size_t variableCount) const; + + bool isValid() const; + + /** + * @brief Writes .dot file of this Bdd. Not thread-safe! + */ + void PrintDot(FILE *out) const; + + std::string GetShaHash() const; diff --git a/src/sylvan_obj_storm.cpp b/src/sylvan_obj_storm.cpp new file mode 100644 index 000000000..27706dcdd --- /dev/null +++ b/src/sylvan_obj_storm.cpp @@ -0,0 +1,141 @@ +Mtbdd +Bdd::toDoubleMtbdd() const { + LACE_ME; + return mtbdd_bool_to_double(bdd); +} + +Mtbdd +Bdd::toInt64Mtbdd() const { + LACE_ME; + return mtbdd_bool_to_int64(bdd); +} + +Mtbdd +Bdd::Ite(Mtbdd const& thenDd, Mtbdd const& elseDd) const { + LACE_ME; + return mtbdd_ite(bdd, thenDd.GetMTBDD(), elseDd.GetMTBDD()); +} + +Mtbdd +Mtbdd::Minus(const Mtbdd &other) const +{ + LACE_ME; + return mtbdd_minus(mtbdd, other.mtbdd); +} + +Mtbdd +Mtbdd::Divide(const Mtbdd &other) const +{ + LACE_ME; + return mtbdd_divide(mtbdd, other.mtbdd); +} + +Bdd +Mtbdd::NotZero() const +{ + LACE_ME; + return mtbdd_not_zero(mtbdd); +} + +Bdd +Mtbdd::Equals(const Mtbdd& other) const { + LACE_ME; + return mtbdd_equals(mtbdd, other.mtbdd); +} + +Bdd +Mtbdd::Less(const Mtbdd& other) const { + LACE_ME; + return mtbdd_less_as_bdd(mtbdd, other.mtbdd); +} + +Bdd +Mtbdd::LessOrEqual(const Mtbdd& other) const { + LACE_ME; + return mtbdd_less_or_equal_as_bdd(mtbdd, other.mtbdd); +} + +bool +Mtbdd::EqualNorm(const Mtbdd& other, double epsilon) const { + LACE_ME; + return mtbdd_equal_norm_d(mtbdd, other.mtbdd, epsilon); +} + +bool +Mtbdd::EqualNormRel(const Mtbdd& other, double epsilon) const { + LACE_ME; + return mtbdd_equal_norm_rel_d(mtbdd, other.mtbdd, epsilon); +} + +Mtbdd +Mtbdd::Floor() const { + LACE_ME; + return mtbdd_floor(mtbdd); +} + +Mtbdd +Mtbdd::Ceil() const { + LACE_ME; + return mtbdd_ceil(mtbdd); +} + +Mtbdd +Mtbdd::Pow(const Mtbdd& other) const { + LACE_ME; + return mtbdd_pow(mtbdd, other.mtbdd); +} + +Mtbdd +Mtbdd::Mod(const Mtbdd& other) const { + LACE_ME; + return mtbdd_mod(mtbdd, other.mtbdd); +} + +Mtbdd +Mtbdd::Logxy(const Mtbdd& other) const { + LACE_ME; + return mtbdd_logxy(mtbdd, other.mtbdd); +} + +size_t +Mtbdd::CountLeaves() const { + LACE_ME; + return mtbdd_leafcount(mtbdd); +} + +double +Mtbdd::NonZeroCount(size_t variableCount) const { + LACE_ME; + return mtbdd_non_zero_count(mtbdd, variableCount); +} + +bool +Mtbdd::isValid() const { + LACE_ME; + return mtbdd_test_isvalid(mtbdd) == 1; +} + +Mtbdd +Mtbdd::Minimum() const { + LACE_ME; + return mtbdd_minimum(mtbdd); +} + +Mtbdd +Mtbdd::Maximum() const { + LACE_ME; + return mtbdd_maximum(mtbdd); +} + +void +Mtbdd::PrintDot(FILE *out) const { + mtbdd_fprintdot(out, mtbdd, NULL); +} + +std::string +Mtbdd::GetShaHash() const { + char buf[65]; + mtbdd_getsha(mtbdd, buf); + return std::string(buf); +} + diff --git a/src/tls.h b/src/tls.h new file mode 100644 index 000000000..80fdfe7e5 --- /dev/null +++ b/src/tls.h @@ -0,0 +1,35 @@ +/* + * Written by Josh Dybnis and released to the public domain, as explained at + * http://creativecommons.org/licenses/publicdomain + * + * A platform independant wrapper around thread-local storage. On platforms that don't support + * __thread variables (e.g. Mac OS X), we have to use the pthreads library for thread-local storage + */ +#include <assert.h> + +#ifndef TLS_H +#define TLS_H + +#ifdef __ELF__ // use gcc thread-local storage (i.e. __thread variables) +#define DECLARE_THREAD_LOCAL(name, type) __thread type name +#define INIT_THREAD_LOCAL(name) +#define SET_THREAD_LOCAL(name, value) name = value +#define LOCALIZE_THREAD_LOCAL(name, type) + +#else//!__ELF__ + +#include <pthread.h> + +#define DECLARE_THREAD_LOCAL(name, type) pthread_key_t name##_KEY + +#define INIT_THREAD_LOCAL(name) \ + do { \ + if (pthread_key_create(&name##_KEY, NULL) != 0) { assert(0); } \ + } while (0) + +#define SET_THREAD_LOCAL(name, value) pthread_setspecific(name##_KEY, (void *)(size_t)value); + +#define LOCALIZE_THREAD_LOCAL(name, type) type name = (type)(size_t)pthread_getspecific(name##_KEY) + +#endif//__ELF__ +#endif//TLS_H diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 000000000..e04430786 --- /dev/null +++ b/test/.gitignore @@ -0,0 +1,5 @@ +test +cmake_install.cmake +CMakeFiles +*.o +.libs diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 000000000..a331904e8 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 2.6) +project(sylvan C CXX) +enable_testing() + +add_executable(sylvan_test main.c) +target_link_libraries(sylvan_test sylvan) + +add_executable(test_basic test_basic.c) +target_link_libraries(test_basic sylvan) + +add_executable(test_cxx test_cxx.cpp) +target_link_libraries(test_cxx sylvan stdc++) + +add_test(test_cxx test_cxx) +add_test(test_basic test_basic) diff --git a/test/main.c b/test/main.c new file mode 100644 index 000000000..941f60d2c --- /dev/null +++ b/test/main.c @@ -0,0 +1,350 @@ +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <pthread.h> +#include <unistd.h> +#include <time.h> +#include <sys/types.h> +#include <sys/time.h> +#include <inttypes.h> + +#include <assert.h> +#include "test_assert.h" + +#include "llmsset.h" +#include "sylvan.h" + +#define BLACK "\33[22;30m" +#define GRAY "\33[01;30m" +#define RED "\33[22;31m" +#define LRED "\33[01;31m" +#define GREEN "\33[22;32m" +#define LGREEN "\33[01;32m" +#define BLUE "\33[22;34m" +#define LBLUE "\33[01;34m" +#define BROWN "\33[22;33m" +#define YELLOW "\33[01;33m" +#define CYAN "\33[22;36m" +#define LCYAN "\33[22;36m" +#define MAGENTA "\33[22;35m" +#define LMAGENTA "\33[01;35m" +#define NC "\33[0m" +#define BOLD "\33[1m" +#define ULINE "\33[4m" //underline +#define BLINK "\33[5m" +#define INVERT "\33[7m" + +__thread uint64_t seed = 1; + +uint64_t +xorshift_rand(void) +{ + uint64_t x = seed; + if (seed == 0) seed = rand(); + x ^= x >> 12; + x ^= x << 25; + x ^= x >> 27; + seed = x; + return x * 2685821657736338717LL; +} + +double +uniform_deviate(uint64_t seed) +{ + return seed * (1.0 / (0xffffffffffffffffL + 1.0)); +} + +int +rng(int low, int high) +{ + return low + uniform_deviate(xorshift_rand()) * (high-low); +} + +static inline BDD +make_random(int i, int j) +{ + if (i == j) return rng(0, 2) ? sylvan_true : sylvan_false; + + BDD yes = make_random(i+1, j); + BDD no = make_random(i+1, j); + BDD result = sylvan_invalid; + + switch(rng(0, 4)) { + case 0: + result = no; + sylvan_deref(yes); + break; + case 1: + result = yes; + sylvan_deref(no); + break; + case 2: + result = sylvan_ref(sylvan_makenode(i, yes, no)); + sylvan_deref(no); + sylvan_deref(yes); + break; + case 3: + default: + result = sylvan_ref(sylvan_makenode(i, no, yes)); + sylvan_deref(no); + sylvan_deref(yes); + break; + } + + return result; +} + +/** GC testing */ +VOID_TASK_2(gctest_fill, int, levels, int, width) +{ + if (levels > 1) { + int i; + for (i=0; i<width; i++) { SPAWN(gctest_fill, levels-1, width); } + for (i=0; i<width; i++) { SYNC(gctest_fill); } + } else { + sylvan_deref(make_random(0, 10)); + } +} + +void report_table() +{ + llmsset_t __sylvan_get_internal_data(); + llmsset_t tbl = __sylvan_get_internal_data(); + LACE_ME; + size_t filled = llmsset_count_marked(tbl); + size_t total = llmsset_get_size(tbl); + printf("done, table: %0.1f%% full (%zu nodes).\n", 100.0*(double)filled/total, filled); +} + +int test_gc(int threads) +{ + LACE_ME; + int N_canaries = 16; + BDD canaries[N_canaries]; + char* hashes[N_canaries]; + char* hashes2[N_canaries]; + int i,j; + for (i=0;i<N_canaries;i++) { + canaries[i] = make_random(0, 10); + hashes[i] = (char*)malloc(80); + hashes2[i] = (char*)malloc(80); + sylvan_getsha(canaries[i], hashes[i]); + sylvan_test_isbdd(canaries[i]); + } + test_assert(sylvan_count_refs() == (size_t)N_canaries); + for (j=0;j<10*threads;j++) { + CALL(gctest_fill, 6, 5); + for (i=0;i<N_canaries;i++) { + sylvan_test_isbdd(canaries[i]); + sylvan_getsha(canaries[i], hashes2[i]); + test_assert(strcmp(hashes[i], hashes2[i]) == 0); + } + } + test_assert(sylvan_count_refs() == (size_t)N_canaries); + return 0; +} + +TASK_2(MDD, random_ldd, int, depth, int, count) +{ + uint32_t n[depth]; + + MDD result = lddmc_false; + + int i, j; + for (i=0; i<count; i++) { + for (j=0; j<depth; j++) { + n[j] = rng(0, 10); + } + //MDD old = result; + result = lddmc_union_cube(result, n, depth); + //assert(lddmc_cube(n, depth) != lddmc_true); + //assert(result == lddmc_union(old, lddmc_cube(n, depth))); + //assert(result != lddmc_true); + } + + return result; +} + +VOID_TASK_3(enumer, uint32_t*, values, size_t, count, void*, context) +{ + return; + (void)values; + (void)count; + (void)context; +} + +int +test_lddmc() +{ + LACE_ME; + + sylvan_init_package(1LL<<24, 1LL<<24, 1LL<<24, 1LL<<24); + sylvan_init_ldd(); + sylvan_gc_disable(); + + MDD a, b, c; + + // Test union, union_cube, member_cube, satcount + + a = lddmc_cube((uint32_t[]){1,2,3,5,4,3}, 6); + a = lddmc_union(a,lddmc_cube((uint32_t[]){2,2,3,5,4,3}, 6)); + c = b = a = lddmc_union_cube(a, (uint32_t[]){2,2,3,5,4,2}, 6); + a = lddmc_union_cube(a, (uint32_t[]){2,3,3,5,4,3}, 6); + a = lddmc_union(a, lddmc_cube((uint32_t[]){2,3,4,4,4,3}, 6)); + + test_assert(lddmc_member_cube(a, (uint32_t[]){2,3,3,5,4,3}, 6)); + test_assert(lddmc_member_cube(a, (uint32_t[]){1,2,3,5,4,3}, 6)); + test_assert(lddmc_member_cube(a, (uint32_t[]){2,2,3,5,4,3}, 6)); + test_assert(lddmc_member_cube(a, (uint32_t[]){2,2,3,5,4,2}, 6)); + + test_assert(lddmc_satcount(a) == 5); + + lddmc_sat_all_par(a, TASK(enumer), NULL); + + // Test minus, member_cube, satcount + + a = lddmc_minus(a, b); + test_assert(lddmc_member_cube(a, (uint32_t[]){2,3,3,5,4,3}, 6)); + test_assert(!lddmc_member_cube(a, (uint32_t[]){1,2,3,5,4,3}, 6)); + test_assert(!lddmc_member_cube(a, (uint32_t[]){2,2,3,5,4,3}, 6)); + test_assert(!lddmc_member_cube(a, (uint32_t[]){2,2,3,5,4,2}, 6)); + test_assert(lddmc_member_cube(a, (uint32_t[]){2,3,4,4,4,3}, 6)); + + test_assert(lddmc_satcount(a) == 2); + + // Test intersect + + test_assert(lddmc_satcount(lddmc_intersect(a,b)) == 0); + test_assert(lddmc_intersect(b,c)==lddmc_intersect(c,b)); + test_assert(lddmc_intersect(b,c)==c); + + // Test project, project_minus + a = lddmc_cube((uint32_t[]){1,2,3,5,4,3}, 6); + a = lddmc_union_cube(a, (uint32_t[]){2,2,3,5,4,3}, 6); + a = lddmc_union_cube(a, (uint32_t[]){2,2,3,5,4,2}, 6); + a = lddmc_union_cube(a, (uint32_t[]){2,3,3,5,4,3}, 6); + a = lddmc_union_cube(a, (uint32_t[]){2,3,4,4,4,3}, 6); + // a = {<1,2,3,5,4,3>,<2,2,3,5,4,3>,<2,2,3,5,4,2>,<2,3,3,5,4,3>,<2,3,4,4,4,3>} + MDD proj = lddmc_cube((uint32_t[]){1,1,-2},3); + b = lddmc_cube((uint32_t[]){1,2}, 2); + b = lddmc_union_cube(b, (uint32_t[]){2,2}, 2); + b = lddmc_union_cube(b, (uint32_t[]){2,3}, 2); + test_assert(lddmc_project(a, proj)==b); + test_assert(lddmc_project_minus(a, proj, lddmc_false)==b); + test_assert(lddmc_project_minus(a, proj, b)==lddmc_false); + + // Test relprod + + a = lddmc_cube((uint32_t[]){1},1); + b = lddmc_cube((uint32_t[]){1,2},2); + proj = lddmc_cube((uint32_t[]){1,2,-1}, 3); + test_assert(lddmc_cube((uint32_t[]){2},1) == lddmc_relprod(a, b, proj)); + test_assert(lddmc_cube((uint32_t[]){3},1) == lddmc_relprod(a, lddmc_cube((uint32_t[]){1,3},2), proj)); + a = lddmc_union_cube(a, (uint32_t[]){2},1); + test_assert(lddmc_satcount(a) == 2); + test_assert(lddmc_cube((uint32_t[]){2},1) == lddmc_relprod(a, b, proj)); + b = lddmc_union_cube(b, (uint32_t[]){2,2},2); + test_assert(lddmc_cube((uint32_t[]){2},1) == lddmc_relprod(a, b, proj)); + b = lddmc_union_cube(b, (uint32_t[]){2,3},2); + test_assert(lddmc_satcount(lddmc_relprod(a, b, proj)) == 2); + test_assert(lddmc_union(lddmc_cube((uint32_t[]){2},1),lddmc_cube((uint32_t[]){3},1)) == lddmc_relprod(a, b, proj)); + + // Test relprev + MDD universe = lddmc_union(lddmc_cube((uint32_t[]){1},1), lddmc_cube((uint32_t[]){2},1)); + a = lddmc_cube((uint32_t[]){2},1); + b = lddmc_cube((uint32_t[]){1,2},2); + test_assert(lddmc_cube((uint32_t[]){1},1) == lddmc_relprev(a, b, proj, universe)); + test_assert(lddmc_cube((uint32_t[]){1},1) == lddmc_relprev(a, b, proj, lddmc_cube((uint32_t[]){1},1))); + a = lddmc_cube((uint32_t[]){1},1); + MDD next = lddmc_relprod(a, b, proj); + test_assert(lddmc_relprev(next, b, proj, a) == a); + + // Random tests + + MDD rnd1, rnd2; + + int i; + for (i=0; i<200; i++) { + int depth = rng(1, 20); + rnd1 = CALL(random_ldd, depth, rng(0, 30)); + rnd2 = CALL(random_ldd, depth, rng(0, 30)); + test_assert(rnd1 != lddmc_true); + test_assert(rnd2 != lddmc_true); + test_assert(lddmc_intersect(rnd1,rnd2) == lddmc_intersect(rnd2,rnd1)); + test_assert(lddmc_union(rnd1,rnd2) == lddmc_union(rnd2,rnd1)); + MDD tmp = lddmc_union(lddmc_minus(rnd1, rnd2), lddmc_minus(rnd2, rnd1)); + test_assert(lddmc_intersect(tmp, lddmc_intersect(rnd1, rnd2)) == lddmc_false); + test_assert(lddmc_union(tmp, lddmc_intersect(rnd1, rnd2)) == lddmc_union(rnd1, rnd2)); + test_assert(lddmc_minus(rnd1,rnd2) == lddmc_minus(rnd1, lddmc_intersect(rnd1,rnd2))); + } + + // Test file stuff + for (i=0; i<10; i++) { + FILE *f = fopen("__lddmc_test_bdd", "w+"); + int N = 20; + MDD rnd[N]; + size_t a[N]; + char sha[N][65]; + int j; + for (j=0;j<N;j++) rnd[j] = CALL(random_ldd, 5, 500); + for (j=0;j<N;j++) lddmc_getsha(rnd[j], sha[j]); + for (j=0;j<N;j++) { a[j] = lddmc_serialize_add(rnd[j]); lddmc_serialize_tofile(f); } + for (j=0;j<N;j++) test_assert(a[j] == lddmc_serialize_get(rnd[j])); + for (j=0;j<N;j++) test_assert(rnd[j] == lddmc_serialize_get_reversed(a[j])); + fseek(f, 0, SEEK_SET); + lddmc_serialize_reset(); + + sylvan_quit(); + sylvan_init_package(1LL<<24, 1LL<<24, 1LL<<24, 1LL<<24); + sylvan_init_ldd(); + sylvan_gc_disable(); + + for (j=0;j<N;j++) lddmc_serialize_fromfile(f); + fclose(f); + unlink("__lddmc_test_bdd"); + + for (j=0;j<N;j++) rnd[j] = lddmc_serialize_get_reversed(a[j]); + char sha2[N][65]; + for (j=0;j<N;j++) lddmc_getsha(rnd[j], sha2[j]); + for (j=0;j<N;j++) test_assert(memcmp(sha[j], sha2[j], 64)==0); + + lddmc_serialize_reset(); + } + + sylvan_quit(); + return 0; +} + +int runtests(int threads) +{ + lace_init(threads, 100000); + lace_startup(0, NULL, NULL); + + printf(BOLD "Testing LDDMC... "); + fflush(stdout); + if (test_lddmc()) return 1; + printf(LGREEN "success" NC "!\n"); + + printf(NC "Testing garbage collection... "); + fflush(stdout); + sylvan_init_package(1LL<<14, 1LL<<14, 1LL<<20, 1LL<<20); + sylvan_init_bdd(1); + sylvan_gc_enable(); + if (test_gc(threads)) return 1; + sylvan_quit(); + printf(LGREEN "success" NC "!\n"); + + lace_exit(); + return 0; +} + +int main(int argc, char **argv) +{ + int threads = 2; + if (argc > 1) sscanf(argv[1], "%d", &threads); + + if (runtests(threads)) exit(1); + printf(NC); + exit(0); +} diff --git a/test/test_assert.h b/test/test_assert.h new file mode 100644 index 000000000..8cd18501a --- /dev/null +++ b/test/test_assert.h @@ -0,0 +1,13 @@ +#ifndef test_assert +#define test_assert(expr) do { \ + if (!(expr)) \ + { \ + fprintf(stderr, \ + "file %s: line %d (%s): precondition `%s' failed.\n", \ + __FILE__, \ + __LINE__, \ + __PRETTY_FUNCTION__, \ + #expr); \ + return 1; \ + } } while(0) +#endif diff --git a/test/test_basic.c b/test/test_basic.c new file mode 100644 index 000000000..f6cffcc15 --- /dev/null +++ b/test/test_basic.c @@ -0,0 +1,331 @@ +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <pthread.h> +#include <unistd.h> +#include <time.h> +#include <sys/types.h> +#include <sys/time.h> +#include <inttypes.h> + +#include "llmsset.h" +#include "sylvan.h" +#include "test_assert.h" + +__thread uint64_t seed = 1; + +uint64_t +xorshift_rand(void) +{ + uint64_t x = seed; + if (seed == 0) seed = rand(); + x ^= x >> 12; + x ^= x << 25; + x ^= x >> 27; + seed = x; + return x * 2685821657736338717LL; +} + +double +uniform_deviate(uint64_t seed) +{ + return seed * (1.0 / (0xffffffffffffffffL + 1.0)); +} + +int +rng(int low, int high) +{ + return low + uniform_deviate(xorshift_rand()) * (high-low); +} + +static inline BDD +make_random(int i, int j) +{ + if (i == j) return rng(0, 2) ? sylvan_true : sylvan_false; + + BDD yes = make_random(i+1, j); + BDD no = make_random(i+1, j); + BDD result = sylvan_invalid; + + switch(rng(0, 4)) { + case 0: + result = no; + sylvan_deref(yes); + break; + case 1: + result = yes; + sylvan_deref(no); + break; + case 2: + result = sylvan_ref(sylvan_makenode(i, yes, no)); + sylvan_deref(no); + sylvan_deref(yes); + break; + case 3: + default: + result = sylvan_ref(sylvan_makenode(i, no, yes)); + sylvan_deref(no); + sylvan_deref(yes); + break; + } + + return result; +} + +int testEqual(BDD a, BDD b) +{ + if (a == b) return 1; + + if (a == sylvan_invalid) { + fprintf(stderr, "a is invalid!\n"); + return 0; + } + + if (b == sylvan_invalid) { + fprintf(stderr, "b is invalid!\n"); + return 0; + } + + fprintf(stderr, "a and b are not equal!\n"); + + sylvan_fprint(stderr, a);fprintf(stderr, "\n"); + sylvan_fprint(stderr, b);fprintf(stderr, "\n"); + + return 0; +} + +int +test_bdd() +{ + test_assert(sylvan_makenode(sylvan_ithvar(1), sylvan_true, sylvan_true) == sylvan_not(sylvan_makenode(sylvan_ithvar(1), sylvan_false, sylvan_false))); + test_assert(sylvan_makenode(sylvan_ithvar(1), sylvan_false, sylvan_true) == sylvan_not(sylvan_makenode(sylvan_ithvar(1), sylvan_true, sylvan_false))); + test_assert(sylvan_makenode(sylvan_ithvar(1), sylvan_true, sylvan_false) == sylvan_not(sylvan_makenode(sylvan_ithvar(1), sylvan_false, sylvan_true))); + test_assert(sylvan_makenode(sylvan_ithvar(1), sylvan_false, sylvan_false) == sylvan_not(sylvan_makenode(sylvan_ithvar(1), sylvan_true, sylvan_true))); + + return 0; +} + +int +test_cube() +{ + LACE_ME; + BDDSET vars = sylvan_set_fromarray(((BDDVAR[]){1,2,3,4,6,8}), 6); + + uint8_t cube[6], check[6]; + int i, j; + for (i=0;i<6;i++) cube[i] = rng(0,3); + BDD bdd = sylvan_cube(vars, cube); + + sylvan_sat_one(bdd, vars, check); + for (i=0; i<6;i++) test_assert(cube[i] == check[i] || (cube[i] == 2 && check[i] == 0)); + + BDD picked = sylvan_pick_cube(bdd); + test_assert(testEqual(sylvan_and(picked, bdd), picked)); + + BDD t1 = sylvan_cube(vars, ((uint8_t[]){1,1,2,2,0,0})); + BDD t2 = sylvan_cube(vars, ((uint8_t[]){1,1,1,0,0,2})); + test_assert(testEqual(sylvan_union_cube(t1, vars, ((uint8_t[]){1,1,1,0,0,2})), sylvan_or(t1, t2))); + t2 = sylvan_cube(vars, ((uint8_t[]){2,2,2,1,1,0})); + test_assert(testEqual(sylvan_union_cube(t1, vars, ((uint8_t[]){2,2,2,1,1,0})), sylvan_or(t1, t2))); + t2 = sylvan_cube(vars, ((uint8_t[]){1,1,1,0,0,0})); + test_assert(testEqual(sylvan_union_cube(t1, vars, ((uint8_t[]){1,1,1,0,0,0})), sylvan_or(t1, t2))); + + bdd = make_random(1, 16); + for (j=0;j<10;j++) { + for (i=0;i<6;i++) cube[i] = rng(0,3); + BDD c = sylvan_cube(vars, cube); + test_assert(sylvan_union_cube(bdd, vars, cube) == sylvan_or(bdd, c)); + } + + for (i=0;i<10;i++) { + picked = sylvan_pick_cube(bdd); + test_assert(testEqual(sylvan_and(picked, bdd), picked)); + } + return 0; +} + +static int +test_operators() +{ + // We need to test: xor, and, or, nand, nor, imp, biimp, invimp, diff, less + LACE_ME; + + //int i; + BDD a = sylvan_ithvar(1); + BDD b = sylvan_ithvar(2); + BDD one = make_random(1, 12); + BDD two = make_random(6, 24); + + // Test or + test_assert(testEqual(sylvan_or(a, b), sylvan_makenode(1, b, sylvan_true))); + test_assert(testEqual(sylvan_or(a, b), sylvan_or(b, a))); + test_assert(testEqual(sylvan_or(one, two), sylvan_or(two, one))); + + // Test and + test_assert(testEqual(sylvan_and(a, b), sylvan_makenode(1, sylvan_false, b))); + test_assert(testEqual(sylvan_and(a, b), sylvan_and(b, a))); + test_assert(testEqual(sylvan_and(one, two), sylvan_and(two, one))); + + // Test xor + test_assert(testEqual(sylvan_xor(a, b), sylvan_makenode(1, b, sylvan_not(b)))); + test_assert(testEqual(sylvan_xor(a, b), sylvan_xor(a, b))); + test_assert(testEqual(sylvan_xor(a, b), sylvan_xor(b, a))); + test_assert(testEqual(sylvan_xor(one, two), sylvan_xor(two, one))); + test_assert(testEqual(sylvan_xor(a, b), sylvan_ite(a, sylvan_not(b), b))); + + // Test diff + test_assert(testEqual(sylvan_diff(a, b), sylvan_diff(a, b))); + test_assert(testEqual(sylvan_diff(a, b), sylvan_diff(a, sylvan_and(a, b)))); + test_assert(testEqual(sylvan_diff(a, b), sylvan_and(a, sylvan_not(b)))); + test_assert(testEqual(sylvan_diff(a, b), sylvan_ite(b, sylvan_false, a))); + test_assert(testEqual(sylvan_diff(one, two), sylvan_diff(one, two))); + test_assert(testEqual(sylvan_diff(one, two), sylvan_diff(one, sylvan_and(one, two)))); + test_assert(testEqual(sylvan_diff(one, two), sylvan_and(one, sylvan_not(two)))); + test_assert(testEqual(sylvan_diff(one, two), sylvan_ite(two, sylvan_false, one))); + + // Test biimp + test_assert(testEqual(sylvan_biimp(a, b), sylvan_makenode(1, sylvan_not(b), b))); + test_assert(testEqual(sylvan_biimp(a, b), sylvan_biimp(b, a))); + test_assert(testEqual(sylvan_biimp(one, two), sylvan_biimp(two, one))); + + // Test nand / and + test_assert(testEqual(sylvan_not(sylvan_and(a, b)), sylvan_nand(b, a))); + test_assert(testEqual(sylvan_not(sylvan_and(one, two)), sylvan_nand(two, one))); + + // Test nor / or + test_assert(testEqual(sylvan_not(sylvan_or(a, b)), sylvan_nor(b, a))); + test_assert(testEqual(sylvan_not(sylvan_or(one, two)), sylvan_nor(two, one))); + + // Test xor / biimp + test_assert(testEqual(sylvan_xor(a, b), sylvan_not(sylvan_biimp(b, a)))); + test_assert(testEqual(sylvan_xor(one, two), sylvan_not(sylvan_biimp(two, one)))); + + // Test imp + test_assert(testEqual(sylvan_imp(a, b), sylvan_ite(a, b, sylvan_true))); + test_assert(testEqual(sylvan_imp(one, two), sylvan_ite(one, two, sylvan_true))); + test_assert(testEqual(sylvan_imp(one, two), sylvan_not(sylvan_diff(one, two)))); + test_assert(testEqual(sylvan_invimp(one, two), sylvan_not(sylvan_less(one, two)))); + test_assert(testEqual(sylvan_imp(a, b), sylvan_invimp(b, a))); + test_assert(testEqual(sylvan_imp(one, two), sylvan_invimp(two, one))); + + return 0; +} + +int +test_relprod() +{ + LACE_ME; + + BDDVAR vars[] = {0,2,4}; + BDDVAR all_vars[] = {0,1,2,3,4,5}; + + BDDSET vars_set = sylvan_set_fromarray(vars, 3); + BDDSET all_vars_set = sylvan_set_fromarray(all_vars, 6); + + BDD s, t, next, prev; + BDD zeroes, ones; + + // transition relation: 000 --> 111 and !000 --> 000 + t = sylvan_false; + t = sylvan_union_cube(t, all_vars_set, ((uint8_t[]){0,1,0,1,0,1})); + t = sylvan_union_cube(t, all_vars_set, ((uint8_t[]){1,0,2,0,2,0})); + t = sylvan_union_cube(t, all_vars_set, ((uint8_t[]){2,0,1,0,2,0})); + t = sylvan_union_cube(t, all_vars_set, ((uint8_t[]){2,0,2,0,1,0})); + + s = sylvan_cube(vars_set, (uint8_t[]){0,0,1}); + zeroes = sylvan_cube(vars_set, (uint8_t[]){0,0,0}); + ones = sylvan_cube(vars_set, (uint8_t[]){1,1,1}); + + next = sylvan_relnext(s, t, all_vars_set); + prev = sylvan_relprev(t, next, all_vars_set); + test_assert(next == zeroes); + test_assert(prev == sylvan_not(zeroes)); + + next = sylvan_relnext(next, t, all_vars_set); + prev = sylvan_relprev(t, next, all_vars_set); + test_assert(next == ones); + test_assert(prev == zeroes); + + t = sylvan_cube(all_vars_set, (uint8_t[]){0,0,0,0,0,1}); + test_assert(sylvan_relprev(t, s, all_vars_set) == zeroes); + test_assert(sylvan_relprev(t, sylvan_not(s), all_vars_set) == sylvan_false); + test_assert(sylvan_relnext(s, t, all_vars_set) == sylvan_false); + test_assert(sylvan_relnext(zeroes, t, all_vars_set) == s); + + t = sylvan_cube(all_vars_set, (uint8_t[]){0,0,0,0,0,2}); + test_assert(sylvan_relprev(t, s, all_vars_set) == zeroes); + test_assert(sylvan_relprev(t, zeroes, all_vars_set) == zeroes); + test_assert(sylvan_relnext(sylvan_not(zeroes), t, all_vars_set) == sylvan_false); + + return 0; +} + +int +test_compose() +{ + LACE_ME; + + BDD a = sylvan_ithvar(1); + BDD b = sylvan_ithvar(2); + + BDD a_or_b = sylvan_or(a, b); + + BDD one = make_random(3, 16); + BDD two = make_random(8, 24); + + BDDMAP map = sylvan_map_empty(); + + map = sylvan_map_add(map, 1, one); + map = sylvan_map_add(map, 2, two); + + test_assert(sylvan_map_key(map) == 1); + test_assert(sylvan_map_value(map) == one); + test_assert(sylvan_map_key(sylvan_map_next(map)) == 2); + test_assert(sylvan_map_value(sylvan_map_next(map)) == two); + + test_assert(testEqual(one, sylvan_compose(a, map))); + test_assert(testEqual(two, sylvan_compose(b, map))); + + test_assert(testEqual(sylvan_or(one, two), sylvan_compose(a_or_b, map))); + + map = sylvan_map_add(map, 2, one); + test_assert(testEqual(sylvan_compose(a_or_b, map), one)); + + map = sylvan_map_add(map, 1, two); + test_assert(testEqual(sylvan_or(one, two), sylvan_compose(a_or_b, map))); + + test_assert(testEqual(sylvan_and(one, two), sylvan_compose(sylvan_and(a, b), map))); + return 0; +} + +int runtests() +{ + // we are not testing garbage collection + sylvan_gc_disable(); + + if (test_bdd()) return 1; + for (int j=0;j<10;j++) if (test_cube()) return 1; + for (int j=0;j<10;j++) if (test_relprod()) return 1; + for (int j=0;j<10;j++) if (test_compose()) return 1; + for (int j=0;j<10;j++) if (test_operators()) return 1; + return 0; +} + +int main() +{ + // Standard Lace initialization with 1 worker + lace_init(1, 0); + lace_startup(0, NULL, NULL); + + // Simple Sylvan initialization, also initialize BDD support + sylvan_init_package(1LL<<20, 1LL<<20, 1LL<<16, 1LL<<16); + sylvan_init_bdd(1); + + int res = runtests(); + + sylvan_quit(); + lace_exit(); + + return res; +} diff --git a/test/test_cxx.cpp b/test/test_cxx.cpp new file mode 100644 index 000000000..c1d984b3f --- /dev/null +++ b/test/test_cxx.cpp @@ -0,0 +1,51 @@ +/** + * Just a small test file to ensure that Sylvan can compile in C++ + */ + +#include <assert.h> +#include <sylvan.h> +#include <sylvan_obj.hpp> + +#include "test_assert.h" + +using namespace sylvan; + +int runtest() +{ + Bdd one = Bdd::bddOne(); + Bdd zero = Bdd::bddZero(); + + test_assert(one != zero); + test_assert(one == !zero); + + Bdd v1 = Bdd::bddVar(1); + Bdd v2 = Bdd::bddVar(2); + + Bdd t = v1 + v2; + + BddMap map; + map.put(2, t); + + test_assert(v2.Compose(map) == (v1 + v2)); + test_assert((t * v2) == v2); + + return 0; +} + +int main() +{ + // Standard Lace initialization with 1 worker + lace_init(1, 0); + lace_startup(0, NULL, NULL); + + // Simple Sylvan initialization, also initialize BDD support + sylvan_init_package(1LL<<16, 1LL<<16, 1LL<<16, 1LL<<16); + sylvan_init_bdd(1); + + int res = runtest(); + + sylvan_quit(); + lace_exit(); + + return res; +}