pax_global_header00006660000000000000000000000064146163763100014521gustar00rootroot0000000000000052 comment=0257c8840e548850447050d76bd8f8fc26df5746 rumur-2024.05.07/000077500000000000000000000000001461637631000132745ustar00rootroot00000000000000rumur-2024.05.07/.cirrus.yml000066400000000000000000000130771461637631000154140ustar00rootroot00000000000000task: # only test the main branch and pull requests only_if: $CIRRUS_BRANCH == "main" || $CIRRUS_PR != "" # increase timeout to the maximum limit timeout_in: 120m matrix: - name: FreeBSD 15.0 freebsd_instance: image_family: freebsd-15-0-snap install_script: pkg upgrade -y && pkg install -y bash bison cmake gmp libxml2 z3 - name: FreeBSD 15.0, shared libraries freebsd_instance: image_family: freebsd-15-0-snap environment: CMAKE_OPTIONS: -DBUILD_SHARED_LIBS=ON install_script: pkg upgrade -y && pkg install -y bash bison cmake gmp libxml2 z3 - name: FreeBSD 14.0 freebsd_instance: image_family: freebsd-14-0-snap install_script: pkg upgrade -y && pkg install -y bash bison cmake gmp libxml2 z3 - name: FreeBSD 14.0, shared libraries freebsd_instance: image_family: freebsd-14-0-snap environment: CMAKE_OPTIONS: -DBUILD_SHARED_LIBS=ON install_script: pkg upgrade -y && pkg install -y bash bison cmake gmp libxml2 z3 - name: FreeBSD 13.2 freebsd_instance: image_family: freebsd-13-2 install_script: pkg upgrade -y && pkg install -y bash bison cmake gmp libxml2 z3 - name: FreeBSD 13.2, shared libraries freebsd_instance: image_family: freebsd-13-2 environment: CMAKE_OPTIONS: -DBUILD_SHARED_LIBS=ON install_script: pkg upgrade -y && pkg install -y bash bison cmake gmp libxml2 z3 - name: Linux, GCC 8.5 container: image: gcc:8.5 environment: DEBIAN_FRONTEND: noninteractive CXXFLAGS: -pedantic -Werror -g install_script: apt-get update -y && apt-get install --no-install-recommends -y bison cmake libfl-dev libgmp-dev - name: Linux, GCC 9.5 container: image: gcc:9.5 environment: DEBIAN_FRONTEND: noninteractive CXXFLAGS: -pedantic -Werror -g install_script: apt-get update -y && apt-get install --no-install-recommends -y bison cmake libfl-dev libgmp-dev - name: Linux, GCC 10.5 container: image: gcc:10.5 environment: DEBIAN_FRONTEND: noninteractive CXXFLAGS: -pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=address,undefined -fno-sanitize-recover=undefined -fuse-ld=gold UBSAN_OPTIONS: print_stacktrace=1 install_script: apt-get update -y && apt-get install --no-install-recommends -y bison cmake libfl-dev libgmp-dev libxml2-utils strace z3 - name: Linux, GCC 11.4 container: image: gcc:11.4 environment: DEBIAN_FRONTEND: noninteractive CXXFLAGS: -pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=address,undefined -fno-sanitize-recover=undefined -fuse-ld=gold UBSAN_OPTIONS: print_stacktrace=1 install_script: apt-get update -y && apt-get install --no-install-recommends -y bison cmake libfl-dev libgmp-dev libxml2-utils strace z3 - name: Linux, GCC 12.3 container: image: gcc:12.3 environment: DEBIAN_FRONTEND: noninteractive CXXFLAGS: -pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=address,undefined -fno-sanitize-recover=undefined -fuse-ld=gold UBSAN_OPTIONS: print_stacktrace=1 install_script: apt-get update -y && apt-get install --no-install-recommends -y bison cmake libfl-dev libgmp-dev libxml2-utils strace z3 - name: Linux, GCC 13.2 container: image: gcc:13.2 environment: DEBIAN_FRONTEND: noninteractive CXXFLAGS: -pedantic -Werror -Wno-error=overloaded-virtual -g -fno-omit-frame-pointer -fsanitize=address,undefined -fno-sanitize-recover=undefined -fuse-ld=gold UBSAN_OPTIONS: print_stacktrace=1 install_script: apt-get update -y && apt-get install --no-install-recommends -y bison cmake libfl-dev libgmp-dev libxml2-utils strace z3 - name: Linux, ARM, GCC 13.2 arm_container: image: gcc:13.2 environment: DEBIAN_FRONTEND: noninteractive CXXFLAGS: -pedantic -Werror -Wno-error=overloaded-virtual -g -fsanitize=address,undefined -fno-sanitize-recover=undefined -fuse-ld=gold UBSAN_OPTIONS: print_stacktrace=1 install_script: apt-get update -y && apt-get install --no-install-recommends -y bison cmake libfl-dev libgmp-dev libxml2-utils strace z3 - name: macOS, Homebrew macos_instance: image: ghcr.io/cirruslabs/macos-ventura-base:latest environment: # To quote Homebrew, "bison is keg-only, which means it was not # symlinked into /usr/local, because some formulae require a newer # version of bison." I do not know when this became the case as it was # not originally, but we'll force this. PATH: /usr/local/opt/bison/bin:${PATH} CXXFLAGS: -fsanitize=address -Werror SUDO: sudo install_script: brew update && brew install bison && brew link bison --force - name: macOS, Macports macos_instance: image: ghcr.io/cirruslabs/macos-ventura-base:latest environment: PATH: /opt/local/bin:${PATH} CXXFLAGS: -fsanitize=address -Werror SUDO: sudo install_script: ./misc/install-macports.sh && sudo port -v selfupdate && sudo port -N install bison # we limit the test suite to a single thread because the Cirrus CI VMs claim # to have 2 CPUs but do not seem to give two concurrent processes enough CPU # time and we end up having some of the SMT tests time out test_script: uname -rms && python3 --version && mkdir build && cd build && cmake ${CMAKE_OPTIONS:-} .. && cmake --build . && ${SUDO:-} cmake --build . -- install && cmake --build . -- check rumur-2024.05.07/.clang-format000066400000000000000000000000301461637631000156400ustar00rootroot00000000000000--- BasedOnStyle: LLVM rumur-2024.05.07/CHANGELOG.rst000066400000000000000000001735231461637631000153300ustar00rootroot00000000000000Change log ========== v2024.05.07 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: the generated verifier no longer mixes ``__atomic_*`` and ``__sync_*`` operations on the same variable. Previously this could have caused incorrect results on some hardware platforms (commit e6e8572cb5422ba2c272c49760bc16e404fe53a0). * Bug fix: the generated verifier no longer assumes that the size of a ``struct`` containing two machine words is exactly 8 or 16 bytes (commit ebbd5fdc5bd2c3c86b5786c039956b10c910793d). * On ARM platforms supporting Large System Extensions (≥ armv8.1-a), the generated verifier no longer needs to be linked against libatomic. It instead uses lock-free operations, which should result in a performance improvement (commits 28eb088ce8fdd5c039c19d39a4ef6cd85d4ea70f, 5afc797f7f8f0869e33e7c5c45846c8b70f66b59, adba81cde626901077a1c946dc57446660db47e3). Internal changes ~~~~~~~~~~~~~~~~ * The ``final`` keyword has been removed from some member functions, allowing third-party child classes to inherit and override these functions (commit ed68536883b6ba27c37af12fdc2e7adcf8b7bf6b). v2023.11.27 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Various UTF-8 equivalents of arithmetic operators (``÷``, ``−``, ``∕``, ``×``) are supported (commits d2aaea8993e469b933e2b790a4c3e077174afaee, a9de733aba646e1b0612269bbba6c8e21f485584, 6071da6b6189b8f24a07960dfccb540c6693ce0f, 5dac72950d59457aba636cb58487e54120338c7f, eba3529b10c35576c7f057be96eee86184443184, 10acb27984e0580e30e0d995cf89d2b50fba10bb, 9ebd4f24b13685ca97f05dc3e493063d3431e14e, cc345ea2b6beebe63951549b9d6616f533dd1d2e). * Man pages use more precise dashes (commit 5e7993681962346a02aac7bcb9de952da851b0bf). Internal changes ~~~~~~~~~~~~~~~~ * Nothing relevant. v2023.05.21 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: headers that use ``SIZE_MAX`` now all ``#include `` (commit 46e830d74473743598d1481aed8a845b8dc3b2f2). Internal changes ~~~~~~~~~~~~~~~~ * Nothing relevant. v2022.08.20 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * The ``getrandom`` Linux system call is now allowed in ``--sandbox on`` mode (commit 1d3a61eb5fbff15af089a7f33ee90d0c98455124). Internal changes ~~~~~~~~~~~~~~~~ * Nothing relevant. v2022.03.05 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * The ``statx`` Linux system call is now allowed in ``--sandbox on`` mode (commit 7d33181707dbeab8ec0ae2c949ac15215308f521). Internal changes ~~~~~~~~~~~~~~~~ * Nothing relevant. v2021.12.27 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * The ``newfstatat`` syscall is now allowed in ``--sandbox on`` mode (commit 53fab5e0cc431652c57439de3b15181cd07173ab). Internal changes ~~~~~~~~~~~~~~~~ * Nothing relevant. v2021.09.29 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Nothing relevant. Internal changes ~~~~~~~~~~~~~~~~ * Bug fix: build errors when compiling with shared libraries due to symbol visibility have been fixed (commit c73e8a28870d37a44bf16a1bd3701edfe82a2521) v2021.08.28 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: code generation of loop overflow checks was incorrect in a number of cases. This has now been fixed (commits c1e46b1267bd52cbb445466b2fa1aa7ccaca9a26, a2a45efb689968de5440a1b419f070077f84d306, 9c898780ea30652f4beeb1e5694c4077ea972ba8, 27a9f27f35ca024ea0de1ae2038a4e66535043b0, d69c6b51209c3e78eee4808c15501b0cbc325e5f, 2feee3e0382a561bdbdee1f0186de0db8e277e8e). * A new experimental binary, ``murphi2uclid``, is provided for translating Murphi models to the `Uclid5 prover`_. * Syntax errors now refer to operators (e.g. ``>>``) rather than their internal names (e.g. ``RSH``) (commit 7cf378f9711bbd19b4c4bdc648357b0043a57fc7). * GCC 4.7 and GCC 4.8 are no longer supported. If using GCC, the minimum baseline is now GCC 4.9. An attempt will be made to not break compilation with GCC 4.7 and 4.8, but no guarantee is provided going forwards (commits 201a7cab8d90b78696197c365d42d70998f16bd8, 517c2151e7a1923d6e625e7256e5da78d063ad6a). * The minimum required version of Python has been *decreased* from Python 3.6 to Python 3.4 (commits 25cc3303434d048066c75e1242e8ef556c09c9e4, 0b68a69a702f1c7b518f425061c6657437e0c004, b3288d12bd5d67e2ed5c23d30f0412ca0bcdda8c, a27329276aa00c5e15ba9b1831c26c950412e68b, 162355d42b9dda960624548b4c95c0b9f8008af8). * The previously deprecated ``--smt-logic`` command line option has been removed (commit bf5b076bbba95c1423e9a7c5a2b64b8ced55fb73). Internal changes ~~~~~~~~~~~~~~~~ * Librumur now includes API functions for sanitising rule names to be symbol-like. This can be useful for code generators that want to emit symbols corresponding to rule names (commit 5d57a5a13a6f0c93e31bbddc4f22a066edb40658). * Copy constructors and assignment operators are now protected on AST node classes (commit 45b27dbac7082e2b2dee85ee4ff7bb2f0c54d063). * Visibility of librumur symbols is now set via attributes. The ``RUMUR_API`` macro may be used to override this if necessary (commits 53344ec86e3a71725039de282d4307ca9af25fc0, c0cdc06024adb23f94409449266fe220f3094de9). * A previously deprecated ``Model`` constructor has been removed (commit 902e4389fca623a68c9c6fbfe298e5561a6aef04). .. _`Uclid5 prover`: https://github.com/uclid-org/uclid v2020.12.20 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: a number of issues in ``murphi2c`` translation of ``put`` statements have been fixed (commits 60c804a6cc5d04191788fc1756d1c9046cb09091, 0df3eb85f3932eb91c92c43d94d02e11a5846629, 4b7b5dfd3534aabd266fb3614b55d047526bae26, 652b50b7fc9f811ed206aa50398a5656acae0eef). * Bug fix: ``murphi2c`` no longer produces malformed code when referencing a record field within an alias statement where an alias has the same name as the field (commit 43f0dec2e112ff0adc05ab6267b8752e683d591d). * Bug fix: ``murphi2c`` no longer produces malformed code when referencing a record field within a function with a var parameter with the same name as the field (commit 074946937005c908cb7ace5ef65f3779d12420d2). * ``murphi2c`` makes a best effort to preserve comments during translation, emitting these as C comments. * Translated ``put`` statements by ``murphi2c`` no longer print a trailing newline (commit 338fa7b38a2e3224b76c095b54ef6dd869a6dd32). Internal changes ~~~~~~~~~~~~~~~~ * A new API ``parse_comments()`` has been introduced for extracting the source comments from a Murphi model. v2020.09.06 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: when using XML output (``--output-format machine-readable``) error messages no longer have their last character truncated (commit 6133e71b65c53cec050cdf5d40f735f2b9b3b525). * Bug fix: ``clock_gettim64()`` is now allowed within a sandboxed checker, fixing sandboxing on armel, armhf, and mipsel (commit 68683c4742b380421936a703c4b9262dac1e68dc). * Bug fix: ``clock_gettime()`` is now allowed within a sandboxed checker, fixing sandboxing on mips64el (commit af121b2a9bb7f7dcdd63fbc2716b314c408abf2c). * Declarations, functions/procedures, and rules no longer need to appear in this precise order within a model. E.g. a constant declaration can now appear in the middle of your model, after the definition of some functions. Expressions can still only refer to entities that have been defined prior in the source file (commits a77dc6d63e5f8bed9c39aa37209ec0b430aff67d, c99b0a604d45bedc9a1d8680912371d0a76783b6, d984fb9ad4bbedc8e99ded8aa3081bf4eba9a0ef, 40114146920f6bef44b84532f5ff6ed1f24dd454, 0a1dd7c6f476df1d7dcdd760722bff5343762609, 501e02d288532c32c236875977e65b99bdb3ebb1, aa2a9a8774af651fe46410aee2405385c23c1a28, 2d712b5838c638b6e90e0e0e34529d62b16319db, 92ca08a13ba5c40fe459733d10ae1819fc9f0796, 67a01344ad7a197887bc59ad3726847a2f2f530b). * The Murphi AST XML format emitted by ``murphi2xml`` now allows declarations, functions, procedures, and rules as direct children of ``model``. This new alternative hierarchy is what ``murphi2xml`` now emits (commits d984fb9ad4bbedc8e99ded8aa3081bf4eba9a0ef, 40114146920f6bef44b84532f5ff6ed1f24dd454). * ``rumur`` gained a new command line flag, ``--pointer-bits``, for indicating how many low bits of a pointer on the target platform are meaningful. You can use this to get extra memory optimisations through pointer compression. See the manpage for more information (commit bc90b807687ac20af9fd025a46493832977ec9aa). * On x86-64 Linux, ``rumur-run`` now auto-detects when your CPU does not support 5-level paging and enables pointer compression optimisations (commit b34bf4f2843c60c916bdafb9a95ad901f2aad5de). * In debug output during checking, the initial printing of state variables and their offsets now lists them in the order they appeared in the source file, regardless of whether they were rearranged for efficiency (commit a77dc6d63e5f8bed9c39aa37209ec0b430aff67d). * There are some minor white space changes to the C code emitted by ``murphi2c`` (commit 40114146920f6bef44b84532f5ff6ed1f24dd454). Internal changes ~~~~~~~~~~~~~~~~ * The ``Model`` constructor, ``Model::Model`` that takes four arguments has been deprecated in favour of a new constructor that takes two arguments (commits 501e02d288532c32c236875977e65b99bdb3ebb1, f375d67d929e789d22f9df882c23d774f4e60518). * The AST node members of ``Model`` have been removed in favour of a new unified collection, ``Model::children`` (commits c99b0a604d45bedc9a1d8680912371d0a76783b6, 0a1dd7c6f476df1d7dcdd760722bff5343762609). v2020.07.28 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * The permutations applied when shuffling scalarsets are now tracked and later used to reconstruct symbolic scalarset values for counterexample traces and print statements. The effect is that counterexample traces now make more intuitive sense because symmetry reduction does not interfere with interpreting scalarset values. This behaviour is controllable via the ``--scalarset-schedules`` command line option. See the manpage for more information. Internal changes ~~~~~~~~~~~~~~~~ * ``Symtab::is_global_scope()`` which was previously deprecated has now been removed (commit 7959973ce9345d16718a16b741d754c5e64bbc9e). v2020.07.11 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: using ``&`` or ``|`` within a ``return`` statement would erroneously cause the error “cannot retrieve the type of an unresolved '&' expression.” This has now been corrected (commit 54c79e090a8bd5eb3939f15742e0c45d0c09187e). * Bug fix: similar to the above, this error would also occur when using ``&`` or ``|`` within a right shift, ``>>``. This has now been corrected (commit 65f4d0d85ab1a1de530c9751a8a4af4b2da4b6b5). * Bug fix: similar to the above two items, this error would also occur when using ``&`` or ``|`` within range bounds. This has now been corrected (commit 72d2ef5b7c12803af2d1102a11321cc19a77dd55). * Bug fix: defining an alias within an ``aliasrule`` whose target was another alias previously defined in the same rule would result in generated code that would not compile. This has now been corrected (commit 30408bde597f774330748309633e547f98041e0e). * Bug fix: During verification, certain shift operations would erroneously return 0 on some platforms. These now return the correct value (commit e065dcdda6d5d263b95a101ab2e353aed9e49c9f). * Printing an array within a model (using a ``put`` statement) results in more efficient generated code (commit b2edcd1ae8408da6c647b7fa7698c2d37c2b8b73). Internal changes ~~~~~~~~~~~~~~~~ * ``Node::operator==`` which was previously deprecated has now been removed (commit df26837f4fea6a7da7fa24858ce3383367e33e82). v2020.06.20 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: State variable offsets are now updated after reordering. Previously this could cause erroneous reads or writes during checking (commit 3d8e551bf1c4873d570dc0a8adac7f52c0b9ea25). * Bug fix: fields within records are now reordered universally. Previously inconsistencies could occur resulting in a record’s fields appearing in differing orders across references to the same type (commits cf03554574c7fd2fa78d461fbae95b97624b6f78, 8b74668d28cdc73718c7e5b8234c9a138456d3ce). * In light of bugs like the above two, there is a new command line argument, ``--reorder-fields`` to control whether field reordering is enabled. This can be used to turn it off in case further bugs are encountered. See the ``rumur`` manpage for details (commit 9a33888f2303a3d1bf0e9339a2fddc4570945b02). * ``rumur-run`` now preferences the ``rumur`` binary in the same directory as itself, ahead of any ``rumur`` binary in your ``$PATH`` (commit 1f03555f89090e7de3e07dc5677380017a3762e9). Internal changes ~~~~~~~~~~~~~~~~ * Nothing relevant. v2020.05.27 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: rumur-run’s check for whether libatomic is required is now more accurate. Previously this would incorrectly detect that libatomic was not required on some platforms (e.g. Linux ARM64), resulting in a link failure (commit 620e514c1d322e05a9e67bb09cd0dc68cb810d38). Internal changes ~~~~~~~~~~~~~~~~ * Nothing relevant. v2020.05.18 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Various bitwise operators are now supported in the input syntax. See `doc/bitwise-operators.rst`_ for details. * Some more verbose messages are now printed when passing ``--debug`` to rumur (commit 7f52532280054e32b1be72f44d0f4180d1a2dc86). * Progress output lines during verification are now skipped when there is heavy contention on access to stdout. This reduces runtime bottlenecks in highly multithreaded verifications (commit 4d47d9a9abf8882935011d20950c50fe75460657). Internal changes ~~~~~~~~~~~~~~~~ * The variant of ``parse()`` that accepted a stream pointer was previously deprecated and has now been removed. Clients should call the variant that accepts a reference instead (commit dcabb240eeb7d505f673879c2ba68fbbb5d3fd96). .. _`doc/bitwise-operators.rst`: ./doc/bitwise-operators.rst v2020.04.26 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: value type calculation (which C type to use for scalar values during checking) now correctly assesses ``:= ... to ...`` for ranges. Previously the presence of one of these expressions in the input model would pessimise the calculation into selecting ``int64_t`` even if a narrower type would be acceptable (commit 371fbc37047088c7f964dfdeedea2420cae46b1c). * Record field ordering and model variable ordering is now optimised for runtime performance during checking (commit 2cb30e7c675d08837c26e0e204fa9f8457c40053). Internal changes ~~~~~~~~~~~~~~~~ * ``IsUndefined`` now inherits from ``UnaryExpr`` (commit 523a021e059382e6fa76afab7bfa011638332360). v2020.04.05 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: murphi2c should no longer confuse multiple enum types within a model when generating its C output (commit 34b66de87e17909538ff25e6c090791d1738f1f6). * Bug fix: murphi2c now reports its name correctly in its ``--version`` output instead of referring to Rumur (commit 8cf120cff76e1d58425be553b2a333c8c23482d9). * A new binary, murphi2murphi, has been added that serves as a preprocessor or source-to-source translator for Murphi models. See its man page or ``--help`` for more information. Internal changes ~~~~~~~~~~~~~~~~ * A new API function, ``Expr::is_pure()`` has been added for determining whether an expression is side-effect free (commit 499151975b8f6b25829e1bf2605943ab5e1832e0). * ``TypeExpr::equatable_with()`` that was previously deprecated, has been removed. Clients should call ``TypeExpr::coerces_to()`` instead (commit f7fc46cb7de8ead4ea840d249ae7ff0689e35abe). v2020.03.12 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: ``time()`` and ``gettimeofday()`` are now permitted when generating a sandboxed verifier on Linux. These were supposed to be allowed previously but there was a typo when initially adding this. This is only relevant for Linux platforms that do not implement these system calls in vDSO_ (commit 6cce8fe23796e459bb98021ccc172ba139745f46). * A new binary, murphi2c, has been added that translates a Murphi model into C code suitable for integration into a C/C++ simulator. See its man page or ``--help`` for more information. * A minor typo was corrected in the murphi2xml man page (commit 75dcef20a57ff939bf789bc98f6f2bd037fd1629). Internal changes ~~~~~~~~~~~~~~~~ * ``VarDecl::state_variable`` that was previously deprecated, has been removed. Clients should call ``VarDecl::is_in_state()`` as a replacement (commit 1776a4c6968e3c98861665af398bd042e435c096). * XCode < 8.3.3 is no longer supported as a development environment under macOS. Users are recommended to upgrade to a newer version of XCode/macOS. v2020.02.17 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: several latent bugs in the verifier’s state writing code have been fixed. These only affected large scalar types (> 49-bit) which were not known to be used in any existing real world models (commits 2d27f8b97aa2d24caf217a97a6df7de11e70b1b4, 7bbf8498c42ca8f19a059acc8169be2559b81427, fa87b0a361b1f7dd9fc436c063ffa5a1d4529ee6, 5b4d7154902d8474f6d0233e5af9f3bd85b0a628, 410fdbe533c3597bc2029f63e0426f56250c52bf). * The ``rumur-ast-dump`` utility has been renamed to ``murphi2xml`` to more obviously indicate its purpose (commit d5cb6a6f88498e9d8c999540f66cc838ffe1707a). * When generating a sandboxed verifier (``--sandbox on``), some further time-related system calls are now allowed. This allows the verifier to run correctly on platforms that do not have these system calls implemented in vDSO_ (commits 3ee7d3d3c2f4f35d86b59b6de7139feae8763b4c, 498853681c25272e23cf480c6c8d7269f23a974c). * The verifier’s state reading and writing functions now anticipate that the host platform may be big endian. Full big endian support will require further changes, but this is a first step (commit 8f7bb60c1bc82638dd4ed5f2248c44cd47436461). .. _vDSO: https://en.wikipedia.org/wiki/VDSO Internal changes ~~~~~~~~~~~~~~~~ * Nothing relevant. v2020.01.27 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: quantified expressions no longer result in malformed SMT problems in the SMT simplification bridge. This previously prevented some optimisation that could have otherwise occurred (commit 2a1b724d25817b1bf9f95932ed8a4f9bb65a2af9). * Bug fix: pointer compression is no longer incorrectly enabled when targeting the x32 ABI on Linux. This would cause assertion failures or invalid memory references on this platform (commit 37cfa28ad640757eb42d4e394974ad2630987089). * ``forall`` and ``exists`` expressions are now supported by the SMT bridge. The only remaining unsupported expressions are function calls and ``isundefined`` (commits 49a0d0df8d5ea67b1c26b549929f6eea361b879e, 5bb6144f684a905df44aa5955a8d04b37739e65c, 5b4e5e52e4bba0fb7ea03cb63d210701c5f3bc65, 5d4038c3933592b060203bda3e94b259a9ba9f43). * ``rumur-run`` now automatically detects whether your C compiler supports the ``-mcx16`` flag and whether the checker needs to link against libatomic (commits 6547e8b5022522732421ff337ab5113a19afb44a, f7958a3fdad6a280360903108de5f05837fa1e5f). * Some compiler warnings on Linux on ARMEL have been suppressed (commit b56cd94c6af0153dbdb983b8fd4177fc041526c8). Internal changes ~~~~~~~~~~~~~~~~ * ``Model::assumption_count()`` which was previously deprecated has been removed (commit ce2fe9d30db11dbce337355924986af48ee8878d). * ``Symtab::is_global_scope()`` has been deprecated and will be removed in a future release (commit 7943b55ab80e0ecf3563158a2ff7b8100d60ca78). v2020.01.11 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: negative literals no longer cause malformed problems to be sent to the SMT bridge (commit 47f0207dcaee6909d59ddc5577f92b3bf97571b2). Internal changes ~~~~~~~~~~~~~~~~ * Bug fix: a missing header is now shipped (commit 8cf196c3548962b15488abe293b4891740da4da0). v2020.01.07 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: compile errors in the generated verifier on Linux on ARM and RISC-V due to references to missing syscalls in the sandboxing code have been fixed (commit f1af745c54346f74ec650b192e708234de603b58). * Bug fix: the syscalls ``fstat64()`` and ``mmap2()`` are allowed within the verifier’s sandbox on Linux on i386, removing a spurious runtime error (commit 047f23b32e2510af15dd4021a3a63941a909d13f). * The state data structure in the generated verifier is now more aggressively packed, leading to reduced memory usage during checking. The runtime speed of the checker may be slightly degraded. However, see the next item (commits c17f056efcb5d3ef0cbd2160df3762a29ee90530, db0e25f04d9140242643f7c5ebf8b8e9fbc62d82, 3c8ba379e44085e772ada03c8607aac95be2ef30, ae6d776609de0462601f9beb75a8c93ce718f658, 50cff5aef32fa02f096bb7fc161a93f10b829124, 299be2fab2588b3367e8dd3406c8c9c0f591ebc6, 46d495f31c202298aef9f9dcd6638295df3f3e88, c423db32f4c34db11d671d4e9078a4211a237630, c6a040344ef4415e1983bd67dec6bb146b020d5a, f6df17322a787d268c5ba8e587070649533b82c5, a30665fb0b71040c99a19201e37ff9946b77a628, 77b97767661d17bff8b70d42b03ac63ba28c1da6, 654156b1bde6cc8d9dd613053d20de70587827cc, 77c8a12a6d6293de89670d0cbc6c4dc05c6ca9f3, 1b3383e3d2064826f67d211890011d651bfae88d, cff8c6c938cf9b491f136dcb31072d1fe8dcc00c). * ``rumur`` has a new command line option, ``--pack-state``, for controlling the trade off between memory reduction and runtime speed in the generated verifier. See the manpage for more information (commit aca06ba25db9a6a8e6311c8eaec015750371b772). * ``rumur-run`` no longer uses the compiler flags ``-march=native`` and ``-mtune=native`` if they are not supported. This is primarily relevant to non-x86 platforms whose toolchains do not all have these options (commit 1dd341e29dd7033b1d7598af8af899c322880a50). * ``rumur-run`` passes toolchain flags to link against libatomic on architectures that do not have a double-word compare-exchange instruction (MIPS, PowerPC, s390, RISC-V). This causes queue operations that are lock-free on other architectures to take a global mutex, but it seems not easily avoidable (commit 4cd3ffef193e2a87d1dd58a642ebaf93541b70ab). * ``rumur-run`` now uses `Link-Time Optimisation`_ if it is supported (commit 0adcb633ec56b476505e22fa47126437f9665671). * Various minor performance improvements were made to the generated verifier (commits 5af91bf0dfe0d8bef9f7045f5ae5692a179e9ca3, dee407613c0b1fd0c7ab851c6f84cbcb184dbea4, b517be6b83b5c17f97ab82bda448e62ecded9688, fe49bea9af67f71763227e95009441438433522a, fd04cb9c1b3f432cb35f66d6cfe0b0726ad84068). Internal changes ~~~~~~~~~~~~~~~~ * ``validate_model()`` which was previously deprecated has been removed (commit ba3a70ce8902c9baecdc94505f7c71d7dba6dca3). * ``Node::operator==`` and ``Node::operator!=`` have been deprecated and should no longer be called. There is not a consistent notion of AST node equality and these functions only implemented an approximation. They will be removed in a future release (commits 019dbe9c4b2fdf24f8cf16028e73e6105e3336fe, 489947c7e3a01ae256d467565688eded2564f34e). * New functions, ``Expr::is_literal_true()`` and ``Expr::is_literal_false()`` have been introduced for determining if an expression is the literal value ``true`` or ``false``, respectively (commit bd084b982b6f209ec2356bb56f69dc0622b9345b). * A new function, ``TypeExpr::is_boolean()`` has been introduced for determining if a type is the built-in ``Boolean`` (commit f4ad5d02161da0b6f2d5264b6a9db482c392e77e). * Some documentation on the use of C atomic APIs has been added in doc/internals-atomics.rst (commits 85602619752cb8b173a0821bb7afe2a8c301f0e1, 7fb1f0266beafd58e7bf7f859204b0ce61f35b28). * Liveness is now documented as something beyond what is supported by CMurphi in doc/vs-cmurphi.rst (commit 5c82890e2a11ccb5da5e155faba8c7b9c26544d5). .. _`Link-Time Optimisation`: https://en.wikipedia.org/wiki/Interprocedural_optimization#WPO_and_LTO v2019.12.22 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: ``rumur-run`` no longer crashes during UTF-8 decoding in generated C code (commit 7bbd50f6a7241475826e8d380b6a60bb3c6dfd18). * Support for Python 2 in ``rumur-run`` has been dropped. To use this script you will now need at least Python 3.6 (commits 0c4d5f05ebcc937921edd924465827e50d345842, ded15a4d8f23f1f1584566bd6e251679ba8f915c). * The final check of liveness properties now prints regular progress updates (commits ce162be56035e726e1077bb6b6ecc89999e8607e, 2635dae9a4f27962f4ed951a54b3d6c54b9d62c6, 44e80dc6142205904dca188d2a0277b49ed0fb7f, 048a4b54fa7a1c2a7f48fdb8a7e470d396529200, eef60ad5cf61d1a8cac2d1dbcf63581da2590e24). Internal changes ~~~~~~~~~~~~~~~~ * Support for Python 2 in all scripts has been dropped. These now require at least Python 3.6 (commits 5ad77dc6de53de9a78639faba5b65668e43c3ad8, 729a7f8a096369115bde345890bc14e03c5bd428, 6e0d248eae25a8a68b04bb5e99a3172e1e2ab453, 244b41225d36309f9e5985dbe594957782bef7fb). v2019.11.24 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * ``rumur`` has a new command line option ``--smt-bitvectors`` for controlling whether bitvectors are used in preference to integers when interacting with SMT solvers. See the man page for more information (commits 37c84bbe255d3a7aa6d234a8334379edbb24ec3c, 9821bedfa4cdadda8cf1b9f065c07813854ea7d1). * ``rumur`` has a new command line option ``--smt-prelude`` for prepending text to problems sent to SMT solvers. The ``--smt-logic`` command line option is now deprecated and ``--smt-prelude`` should be used to set the logic instead. See the man page for more information (commit ad022eb0767250734562ec1ec932ef4d99ec1f5d). * The ``rumur`` option ``--smt-simplification`` is now automatically enabled if you pass any of the other SMT related command line options (commit 39482d62009232477f18c7e5e295c633004e7b82). * A new tracing feature for memory usage in the generated checker has been added, ``--trace memory_usage``. See the man page for how to use this (commit 4f9195707ae261ed4f6f94d1411579751deff618). * ``rumur-ast-dump`` now has a ``--version`` option to print out its version (commit 76716edc76fbe608a013b0178b6e4d2d72614d08). * Some warnings when compiling generated code with recent versions of Clang have been suppressed (commit 3e9efb2855be52c20023ef3cd03e02b183e22ff5). Internal changes ~~~~~~~~~~~~~~~~ * A new ``version()`` function has been added to librumur for retrieving its version as a string (commits 77ee1c40884627e5418e3c25f902c6d7d73f5f4f, 7f95b7491859548b27ec7d9226d7c28cdec380c0). v2019.11.09 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: returning an expression of range type within a function with a return type of a differing range is now accepted. This pattern was previously rejected by ``rumur`` claiming the types were incompatible (commit 2279e30e74983c8288d097979f31ffecd25b9b4f). * Bug fix: the filename in the AST dump produced by ``rumur-ast-dump`` is now XML-escaped. Previously characters like ``<`` were incorrectly printed as-is (commit cec7f83ac781554a99e9018cef6a0285f67c8955). * ``rumur-ast-dump`` now shows source content in its output even when the input model was supplied on stdin. Previously source content was only included if the input came from an on-disk file (commits ff36e8fec7750a921d4bdc57c509ca7d12fde8cb, 6fbc34e9a6cbee0e8c9f09c9b8dc5796fd3d2aaa, 8fc052d0c3d034ed057ec69aa3ebab95b60234b7). * ``rumur-ast-dump`` now gives the filename in its output as “” when the input model is supplied on stdin instead of omitting it. The ``filename`` attribute of the head ``unit`` tag in the dump has now become mandatory (commit f20463f3e00f5ae2de9871b6b24f83f7799ff4d2). Internal changes ~~~~~~~~~~~~~~~~ * ``rumur::parse()`` now takes its argument as a reference instead of a pointer. The old implementation remains for backwards compatibility but it is deprecated (commit 947ae70c647a955ea6e24b651a6feead64bac787). v2019.10.27 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: several problems with code generation related to statements of the form ``for x := i to j by k ...`` have been fixed. Rumur now supports arbitrary expressions for any of ``i``, ``j``, and ``k``, including reverse (down-counting) loops (commits 1186e622868c124b21637f7ddb5f35f818b18f3b, 8b73384edfceb8c6f55dffdb1ae8d9952b5c8adb, 245887647ac4bfbf08685f97c99c0c84b581e8f8, b7078e9b17fb572ff7126aa42930d3dd50a4577b, df4264e5f72d7e4528211e74444512d58dd32048). * Bug fix: quantified variables are taken into account when calculating range limits for values of simple type (commits e4746dc130d3f69bf623bed503b88b0ba109b176, 3e0ac51a379a2b5612b6d72e3e286955f143e525). * Bug fix: overriding the automatically chosen value type (using ``--value-type ...``) can no longer cause an assertion failure in the generated checker. Forcing a value type that is too small previously violated an assumption in the generated code. This now causes a runtime error (commit 77729447d3cfbb523e3a4a79654eb0a1b5fbd8e8). * Bug fix: the initial pool size of the arena allocator in the generated code was being miscalculated and has now been corrected to approximate 8MB (commit 381f08975e2a0a70cd0a2210a9af12b374580075). * Bug fix: the SMT bridge now correctly detects a failure to start the child process. The check for this was previously incorrect and it would look as if the SMT solver malfunctioned (commit d1cbfd41d3051d548186acf1f17acd85df7f96d8). * Blank (``""``) and unknown logics are now supported by the SMT bridge. Solvers such as Z3 function best when given no ``set-logic`` command (commit 6c92a15f33da3804aaaba628ecc8450ac2fde13d). * The default SMT logic is now ``AUFLIA`` (commit 03ab27d04eccc18c142db7364f7000bf67c12a7f). * Some GCC warnings when compiling generated code have been suppressed (commit bae9b849a781f97e690c8e52196512150aeae4ab). Internal changes ~~~~~~~~~~~~~~~~ * Bug fix: Unresolved ``TypeExprIDs`` with differing names are now considered unequal (commit 7fe656c7db5f2578db826ea1a39a200ece93f57f). * ``TypeExpr::equatable_with`` is deprecated, and replaced by ``TypeExpr::coerces_to`` (commits aa1557bf044e62c8f3adaaca591fe272b30ca19a, e45f214cd2097bbe710a2a3eed9ed196e9feace8, befe6bb4a9b9c342ad3a7a8b96a8bff94c47319d). * ``Quantifier`` has a new member, ``decl``, that is a ``VarDecl`` for the variable it represents (commits c079a460749b1b8e7ea9dd627d369fe3395aa204, 4aba73cb86885531a56228a145ad2529cf5fe2a0). * Quantifier expressions — the bounds of the quantifier — are now validated in ``Quantifier::validate()`` (commit 1b7cd5aad63c8b3e55a266facb8100752946a59d). * The type of a ``TypeExprID`` that refers to a quantified variable is now a persistent, valid ``VarDecl``. Previously it was a synthetic declaration with an invalid ``unique_id`` (commit c567645c4778cbb33d9f696450e9c9c13f12896b). v2019.09.15 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: an alias of a constant is now correctly recognised as constant itself. This makes it possible to, for example, use such an alias as the lower or upper bound of an integer range (commit e4d139880498cfe140ae3298985c615d44f3930d). * The SMT bridge supports variable and type shadowing. For example, if your model has a rule with a local variable with the same name as something in the global state. Such models would previously cause malformed SMT problems to be passed to the solver (commits b2d5c1566530fa009c06b1c2710617b71f7c8c57, 4f5611986b12cbafa9663f1dd7b31f33d3211d25, 7b1718259185ff3e5ceabbb34fca41028da12010). * Smart quotes (“ and ”) can now be used as string delimiters in models (commit 82db1716e7b18259b00ea1941163c4808513793c). * Using an SMT logic without array support (for example, ``--smt-logic QF_LIA``) suppresses SMT simplification in models with arrays. Previously this would cause a malformed problem to be passed to the solver (commit 1100fae5b5c629b2d3e1f7dc386906ae16d7bd5a). Internal changes ~~~~~~~~~~~~~~~~ * Breaking change: ``TypeExprID::referent`` is now a ``TypeDecl`` instead of a ``TypeExpr``. The ``TypeExpr`` that would previously be stored here is available via ``referent->value`` (commit 117ae412d6aa863f54d25fa87106265cced7f680). * A new method ``Function::is_pure`` is available for determining whether a function is side effect free (commits 455acdc883a7080ad764524a7d22e8bf056c9e09, ef5eb689d81bf96c183ad6f74a754eab47229095). v2019.09.07 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * The SMT bridge now supports record types. This makes SMT simplification (``--smt-simplification on``) realistically usable on real world models. Simplification will still give up on some unsupported expressions (commits 787f074328874a470d595576ae9e8b16837582f4, 33d120df8fc7bedf1361a59f328930d311478376, 308a8239eee6dc42684c3bed21210ea95d0dd66e, b9dd7f185d6f22c31d98dfbeb2af4418fb661b79, 13092b8d8c5e62da0178b71825328cc7e75bea5b). * Recursive functions and procedures are now supported. These are supported by CMurphi, Rumur's precursor, but seemingly rarely used in real world models so their absence in Rumur went unnoticed until recently. Mutual recursion is still unsupported (commits e61b8a787ab46bde3c0ce14da885cd3005cc54c9, a9bd211028e591d90e28e2410f5988700bc5efcd). * ``rumur-ast-dump --help`` now shows its manpage instead of abbreviated help text (commits 4198edc67ed37c3dfa91031f90fdfb9e8a5190aa, 8cf86df9ef718d1e22d1ba47a63c9f1a6ba1ad78, 295b565f88660ecf4264ad1ace4e6f88423fab69, 8c612b898e9d42a17847cca3a9435fc575c58135, 577ae2862a45a1d89fe995c1a9bd7bb11fc7e34d, 38a61d670d748d7072162e506c873afa13e757ec). * Function or procedure parameters that shadow a return type are now supported. Previously Rumur would reject such models (commit ff5bbb8cd7a016fbe210757dd1c4b90093c44b4d). E.g.: .. code-block:: murphi type t: 0 .. 1; function foo(t: boolean): t; begin ... * It is now valid to name two rules identically in a model. This can lead to confusing counterexample traces, but sometimes it is natural to name multiple rules the same so supporting this seemed reasonable (commit a1d419c4d70f99d0945164e708ddd90379ddc858). Internal changes ~~~~~~~~~~~~~~~~ * A new interface, ``Function::is_recursive()``, is available for querying whether a function calls itself (commit de4cd48cc2ff64b8ba8eb41163ea45fd1676658c). v2019.08.18 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: Boolean literals (``false`` and ``true``) are now supported by the SMT bridge. These previously led to a malformed SMT problem (commit 0c9917b87523db07b604c566e2f8e3481872857b). * Array types are now supported by the SMT bridge. The bridge is still of limited use as there are many constructs it cannot handle, but it improves incrementally (commits 424467a264b923c53a1b1738604630a05457315c, 5d4f1939ddc5d5d9336f0ce35e953c51e8b5aeca, 5e07b5527a910d12be558d665110a7809838360c). * The default logic for the SMT bridge has been changed to QF_ALIA. As before, this is controllable via the ``--smt-logic`` command line option (commit dc81631881a16764d55dea834ae39d8715b13e83). * Some compiler warnings in the generated verifier have been suppressed (commits e60db38a76b2cd1ce169ad17b442b5285ee83b4c, ef5dd68576dc37d109e2370c653f1a6286042f78, a657bb19ae4ce589e64b217823b0e2c49b8b282e). Internal changes ~~~~~~~~~~~~~~~~ * Nothing relevant. v2019.07.21 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: quantified ranges that span 0 (e.g. ``-1 .. 1``) now iterate correctly. Previously such loops would become no-ops which could cause the verifier to incorrectly not explore some states. This bug was introduced in v2019.04.28 (commit 2329056db14d87301bba9c56115cdd4539bed1af). * Bug fix: models that contain assume statements but no top level assumptions no longer segfault. This bug was introduced in v2019.05.11 (commits eab626a859982d55b2ebfae8ca216ce79aec25ee, d4ae6d2c88cf0ca5a4e2a4f1f94b375d1405b2a5, ad79600751bb017ff8f85ef34e2747924c0e6eca, 0fd8636f2eca1ed6d90545ab3ee91f4ebae1da85). * Bug fix: the file descriptors used to communicate with the SMT bridge were being configured incorrectly. This caused inconsistent behaviour across different Libc implementations. This bug was introduced in v2019.06.30. Thanks to @wangqr for reporting this (commit 53f20cc00398eefd81a7a1d015517d3051b23548). * The dialect used to communicate with SMT solvers was backported from SMTLIB 2.5 to SMTLIB 2.0. This enables support for more diverse solvers (commit e0e9c5d46c8c2192d6c70987de2a1d50889dc3fd). * There is a new option for specifying the logic in which to encode SMT problems for the external solver, ``--smt-logic``. See the manpage for more information (commits e6b76b518439c0667de0b4b575ec18e5e6994705, 6ba664c341f5796a99a7b4623f424ad4f33c9852, 07ff7f7df1f4e8473f4e5f63dc0654009abb18db). * The SMT bridge learned to understand type-declared ranges/scalarsets, integer constants and enum types. It is still of limited use because it does not understand records or arrays, but support for these will arrive in future (commits c38a0f1188924622e716abbc4dcee924cb10ce52, 33ce2be1adf8c0922ea6fa7594ad9c783df35e20, 7d0146ead2cf30b15ed515beb3c56dd1da8464a8, ca07c576bb272193c1177790c359b5984f636180). * The SMT bridge has increased support for division when using CVC4 (commit e55c4c1b274dfd8797f71f49209d2e0e5eb799d7). * Some inconsistency in the XML output when using ``--output-format machine-readable`` was corrected (commit 22a0c59054563116f6210a886dd538bdfd7cd90a). * Some ``-Wsign-compare`` warnings when compiling the verifier have been suppressed (commits d2949e3516c613f6183ce3219d403e4b3e96add9, 1a7342956115a691118b315bf8ea1cb551f718f9). Internal changes ~~~~~~~~~~~~~~~~ * ``Model::assumption_count()`` has been deprecated (commit 99529844092fcbe1bbbfb3170c7b9a8364a6d055). * ``VarDecl::state_variable`` has been deprecated (commits 39bf6a2661bb6a296fbd73d9f466f052c4865477, 175193b6e0a920f016545008796a99ec3a588bfa, 6a4f9ac363b8c90beac7d5b5ddacc152f5e329d4). * A RelaxNG schema is now included for the XML output of the verifier (commit 123e2507ddf6694ddb7d2bb1baf654e467f28e23). * The validation API has been extended and now also descends into referents. The function ``validate_model()`` has been deprecated (commits 860f71d1db91e71bcab60a8fc8097ad37d3895a0, 499857ec7ab25886be5c4a76802889cb1fc034f8, 5d2449ac780c39cb72f21a03b498c766607fabb7, 45f095c97174b96df5612d0c762283f7187ba0f7). * The data members of referents (e.g. ``ExprID::value``) now have accurate values. This avoids confusion as users can now access these and rely on getting the same, e.g., ``offset`` as the target (commits 7268f636cd9187c30f6bc990abef8e4b493b0534, c3d23559c40b1504bb1a284f76303891fafae23f). v2019.06.30 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: duplicated semi-colons are now ignored. For example, an empty statement no longer causes a syntax error (commit 7e0a3eeff15707e6a67515acd499dce9e598d9ee). * Rumur gained some rudimentary ability to interact with an SMT solver. See the manpage or ``rumur --help`` for information on how to use this functionality. This simplification performed via SMT will incrementally improve in future releases. (commits 45f56b3d06759bd9a0e6343334b5fa2bf2161f2a, 1c75eefb8c9c1b3e1e543cefd992b91066929081, 0f8c1aa01f5ec517d4186ab8f65b81872dcc4374, 9aa75f12adc38efd7a107c90f659ca4d98e8d925, dce3565a8d059e480efd34ff35c5d43134eed607, 4a0b72a25318e642a4648dbcb1082068f7c20354, 4bf443d4a1eb4f069998109f8f4e9380ad35ef6c, c66061ffa216e291a325e3a33cb55fd6d911960b, c32ed61d1b51439e760558712c5c3de5e8cc2a4c). Internal changes ~~~~~~~~~~~~~~~~ * A new member of ``VarDecl`` has been added for determining whether a variable declaration is part of the global state or not (commit 80e6154c748b3cbd36c3b9fb9e1164447e85246f). * ``True`` and ``False`` constants are available to use for comparison or cloning when working with the librumur AST (commit dcb3559fbe03014bdf353649f390fc368b7e813c). v2019.06.12 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: an unlikely edge case was possible wherein the results of checking could be reported inaccurately if one thread was exiting while other threads decided to expand the seen state set. This was never seen in the wild, but has been corrected in this release anyway (commit 8cf9d785c925554e6ec4b2a8a55e619f3ecc66f2). * The generated verifier no longer requires linking against libatomic on i386 platforms. This change means FreeBSD on i386 is now supported (commit 0da98254af604a4812201b8f06dc885dcebb9787). Internal changes ~~~~~~~~~~~~~~~~ * Rumur now compiles correctly on platforms where ``size_t`` is not ``unsigned long``. Thanks to Yuri Victorovich for reporting this (commit 38489a811f0abc4aaaf6f6425dd6321325f959a0). v2019.06.05 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: when generating XML output from the verifier (``--output-format machine-readable``) some text within error messages was not correctly escaped, leading to invalid XML. This has now been corrected (commit ca97a1eb90ac667f3e5f32b41ccbb59940804516). * Bug fix: FreeBSD compatibility which had been accidentally broken was restored. Thanks to Yuri Victorovich for reporting this (commit 43054e83417e028c48b18739f6ac7916cfcbac47). Internal changes ~~~~~~~~~~~~~~~~ * Bug fix: the test suite should now run successfully in a non-UTF-8 locale. As for the above entry, thanks to Yuri Victorovich for reporting this (commits a88c8d2faf2b003e2b65af26cc42b2bcdd82e819, a9e327cd43f94ea22129244f514261ea3880eedb). v2019.06.01 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: the output message for a syntax error on a line containing a tab character previously indicated the wrong column offset with the underlining caret. This has now been corrected (commit 323fda58984e1768b659298afddc5c022160c428). * ``rumur-run`` now exits cleanly and cleans up temporary directories when you terminate it with Ctrl-C (commit 9acb49fd46d8eeddd59104d48621aa1a3c71cd34). * The default load factor of the seen state set has been changed from 65% to 75%. On most models, this decreases the runtime of the verifier. As before, it is still possible to change this value with the ``--set-expand-threshold`` command line option (commit 8ac5bf762d744fc68d8e64918fc7af120b4fc3c7). Internal changes ~~~~~~~~~~~~~~~~ * The documentation available under doc/ has been extended (commits 63e0db1b8d67529e3f042e1b1ed7ffd65ca78cab, 49e8c6a857ba8f9b46d3cf36bb702268d7e822da, f39447766ba43ccf2f218370d6a644024a3e1215, ba0521cfcd2b30d19a125b319ade63775505c73f). v2019.05.11 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: Counterexample traces using "diff" mode (the default) now correctly only show the value of a variable if it has changed compared to the previous state. Previously variables whose values did not change were sometimes repeated (commit 94ef1dec8a82d643dba459d97af3870c9e325528). * Bug fix: Running with counterexample traces disabled (``--counterexample-trace off``) is repaired. Previously this would result in generated code that did not compile (commits f78335f5d72c3fa5b4565103697c678ef62379cf, 58b7ac310caa008d57af71039080095c801956a2). * Bug fix: negative literals are taken into account when determining a type to represent scalars. Previously Rumur would fail to notice that something like ``-1`` in your model implied that values could be negative, and it might have inferred that an unsigned type like ``uint8_t`` was suitable to store this in (commit 2b27e22f00354080589815416b7796d06b37fb6c). * Bug fix: Using ``--max-errors`` with a value greater than ``1`` produces safe code. Previously this would emit a call to ``sigsetjmp`` with live non-volatile local variables. The result could lead to memory corruption or an inaccurate fired rules count, but neither of these were observed in the wild (commit 7dda120345da13f739427915fde630d71bae9ff5). * Bug fix: some spurious ``-Wtype-limits`` and ``-Wtautological-compare`` warnings when building the generated verifier have been suppressed (commit d82f251210560df694f03a6d8b6c5c2cbbe04886). * The concept of disabled properties has been removed. This feature was never documented and had no use yet, so its removal is unlikely to affect any users (commit 4e30098aee291414b5108936548218657fb47900). Internal changes ~~~~~~~~~~~~~~~~ * Some spurious ``-Wsign-compare`` warnings when using older GCC versions have been suppressed (commit 25847dca93e45a3b0616c9f2bd254eae1738f7a1). * The documentation available under doc/ has been extended (commits 5a56d259bf2b9e039ed18a4b48861b48083e730e, 7ab3e74ae2a63809ee657ea981cb2d9ae0da3fb4, b6e8ed7c4c4818aa13d7ec24cc3f7fb40f1d9842, d76467f065585a2cbc5f4f237ea20fb367140c26) v2019.04.28 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: enum types that are printed in error messages now correctly have their members separated by a comma and a space (commit 1107d95909bdd9df019f55f1208c857de5db7239). * Bug fix: one case where the size of the seen set was incorrectly read non-atomically has been fixed. This would only have affected platforms where naturally aligned reads are not already atomic (e.g. not x86). The result would have been a rare chance of a miscalculation of when to expand the seen set. (commit 02d2803ecb6a459a1a41f7d1c630d1b84d6d75ff). * Syntax error messages now provide more information about what token the lexer was expecting to see (commit 06dfee962cb3541fcedf2f319ca4504f90ee0514). * Instead of unconditionally using ``int64_t`` to represent scalar values in the generated verifier, the fastest type that can contain all scalar values in your model is used. You can override automatic selection with the new ``--value-type`` command line argument. This change has no immediate benefit but it opens the way to optimisations using Single Instruction Multiple Data (SIMD) or even SIMD Within A Register (SWAR). (commits 0a5129fb89358ea67ecc32fb07b1d768f655223e, 0933edbb4831c5fc9e483e865b202a6609090b54, f5c8cc54a8a02338a62985aaf2190d7f5fc79ca0, 2fde1dbf0fff5c3776fb77e7468a2e83693a444b, 6d20e571685f18cdb2d9bf6dd77c615ce1ab5385, e98a3d0041d64dd331a16e45897e9c3a789e0235, f9a29ea64cccbc41155b689d80ea6eb3be9189e9, c95df7007b48a89df981eec037679dd3cb87dab5, 5b33f977a55a4bd370aefea205548b1b0bf887d9, 5e5945535ff60ed01501d2b10282220b96b009bc, 5e1ee6dbe6e784516a1171996bb442e9936e426b, 79579fd5ee7cc3c120439b5d3187a09ffd5dcd6e). Internal changes ~~~~~~~~~~~~~~~~ * Nothing relevant. v2019.04.13 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: malicious models can no longer cause bad printf format strings to be generated (commits 6b30d43f6672278db0c0d7c8dfd5dbe83785fad5, a27c2391ede24c0833b045d0d4a138ecb829434b, 322d1e26b343cdc308efb50ce2d952bb26ad9ad8). * Bug fix: characters in text like rule names are no longer dropped when using XML output (commit f119f745218ed9404f6922e95aa6076bc0bdf291). * Bug fix: unnamed invariants are now correctly numbered when they are referred to (commit 450a2e7b9ced7f670eaf568e9ba484ea43a2dcb4). * Bug fix: with deadlock detection enabled (default) and multiple errors (``--max-errors ...`` with a value > 1), deadlock counterexamples are no longer duplicated in the error output (commit 17ebb307b68cb323ad0840903b96070ea1b6ca0a). * New syntax has been added for writing liveness properties. See doc/properties.rst for how to use this (commits e99fa1104ff578106075f6dc19c35b4ef2f7d986, ee1aecd172edb9fa5be775548841e38c4aa547b0, 36fae15066562eedee594fa1fd77e60af19e13bd, 4c6ee24bc922955f419c05391fa1ddc49cbc122e, 53f80d8565af4217bfe11ac2bfe549d9b2ada0af, b094269cfe516bad7bd3ab0993288ff7f3a8285a, 6ed296f61b7b942323974a7d40c2b20f7003ff26, ac54ed1cef5326260128d189a3705679a3ba02aa, 85cbc94ac9b734572874d3564d9a4240f10614f9). * Support for macOS has been extended back to XCode 7.3 (commit 35e1803b370f8a47df84812eab19bbb01dcf4e41). Internal changes ~~~~~~~~~~~~~~~~ * The test case tweak snippets (``-- ...`` Python comments at the beginning of test cases) can now refer to whether XML output is in use or not (commit af393a106773c98b79f283f02e250ec9ca9a73a5). * Using the ``-- checker_output: ...`` test case tweak no longer limits a test case to running when XML output is not in use (commit af393a106773c98b79f283f02e250ec9ca9a73a5). * There is a new API function for counting the liveness properties in a model (commit ee1aecd172edb9fa5be775548841e38c4aa547b0). * The build dependency on ``xxd`` (bundled with Vim) has been removed (commits a8575179f9a5c956be5bb50c182bbb89f1d8d057, 6b907684c4d7696acf6f9ea2a2ca566e5175da18, 43759055bf873814ec18cb692ee9a6d9d6889d1a). v2019.03.30 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: an error when compiling the generated verifier on non-x86-64 was addressed (commit 7e59f1c25a71fd6c3444fc11adc6f932b32ce926). * Bug fix: the Vim syntax extensions were missing the ``property`` keyword which has now been added (commit 9e70f6114899ca04556c3cdeb198928a65ab19fc). * Errors when generating the verifier are now printed showing the relevant source line from the model. They are also colourised Clang-style when possible (commit e7f2b615cb432bf8fab55d3a00225f3b26e8d8d9). * Support for sandboxing the generated verifier has been extended from Linux and macOS to include FreeBSD (using Capsicum) and OpenBSD (using ``pledge``) (commits b73b180dd7fedb2795f19e8a065eefe429f1177e, cb53074aaa1c898c6c0a3d6e962597b9c77c3785). * Expansion of the set of seen states has been optimised, resulting in a ~4% decrease in the runtime of the verifier. This change reduced contention, so likely leads to greater speed ups on large, multicore platforms (commits 022c3708b24b828a96f3a50c0f11c7cc1476a439, 5f4bb2cd96660a48518680f992fee041566ac722, 2e84387ec6f56c42f41ea21e17ba99eef501ab65, 5b29f2c4cb96989ba862a19acfcae0912a19f86c, 9287f5af063a430e83c8957d9f7282d1af33d6ba). Internal changes ~~~~~~~~~~~~~~~~ * Nothing relevant. v2019.03.21 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * A new bounded model checking mode is available using the command line option ``--bound``. See the manpage or ``--help`` for more detailed information (commit e60697531ab636d374946d547ae65cd380b2ce0b). * The names of quantifier variables are now included in the XML produced by ``rumur-ast-dump`` (commit 78539fa086bbdaf06c5a079e5e482637cf6f2e11). * Some optimisation has been done to state handles, resulting in a ~9% decrease in the runtime of the generated verifier (commits d783655eae837b805b69185d1d198ea142825973, 96268246ad3c9635998647fb31faf73e6721c83b). * Support for GCC on Linux has been extended from 4.8 back to GCC 4.7. It is unlikely Rumur will ever support a lesser GCC version than this (commit 76a97b5354cc10cbd5fd188c385eeb457b3fd2ab). * All major BSD flavours (DragonFly, FreeBSD, NetBSD, OpenBSD) are supported. Rumur now runs on all major desktop operating systems except Windows (commits 6524f1eaedc6724fb26462ec901c241ded7861e1, 026c9a476ba5efea5dd4fd7a5a8bcec7588381e8, 7e9addb34df01abe7449823c33772985e9f6172b). Internal changes ~~~~~~~~~~~~~~~~ * Bug fix: a memory leak on passing invalid command line options has been removed. This is under "Internal changes" because the leak occurred immediately prior to program exit, so would only have affected users debugging or embedding Rumur (commit 4f89903e244c7c188577d082c204bdb344ed1af8). * New options for scoping the range of tests that the test suite runs. This is mainly for use by the continuous integration setup (commit ba2377a3b7240774d6bfb6745bb3c424c67b9277). v2019.03.11 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: enums and booleans that were used as ruleset parameters would previously have their values printed numerically in counterexample traces. For example, ``false`` would be printed as ``0``. Both are now printed as their textual names (commit 40c281d80342e684401425769e8e91ec78e3b019). * Support for "cover" properties has been introduced. These are described in doc/properties.rst (commits 22a865897d23e2281541fe43276277b4b980a14d, 29ac671ca93a0eef79b4f2b85a43da624d10938f, f9fe9614a4beb930f54db50250e4004ad773cee5, b4c5ead18eb3d99d2434aad6732cfce305c629c2). * State allocation has been optimised, resulting in a speed up of ~46% and peak memory usage reduction of ~9% in the generated verifier (commit 7ddf00bbce10a5f0cdd994658ac4545b186826ac). * When using GCC, the minimum required version has been reduced from 4.9 to 4.8 (commits c84bad26079f49a40b4c9cbdcd50b508292a8689, 657eea8b8b84d269916207268edab85d71aba532, ff5a32521e4f937bd4d81b3ac7ae7204c8f913ec, 227f340a059ce704ac1dff9cff75d721b987e147, 7ba30edd5657c94fe5fe8c559fbde179817c795b, 554d37e47cc9f878f65161d3ae51f6fbb9345bd8, 3c827ae7b0f20d3f3f10118f61adcf73e58ee701, e929000525239eb357ad780c95aa54008633c678, a1ece0ad453ef95decd6256dac69b2af99ced2ff, b18e0430c8cd1cb5f67827e8ca2a6b0ab4117147, 4e04bb5a6333df60444710f949486ea34739acc0). * A Vim extension is included in misc/murphi.vim to add support for syntax highlighting Rumur's Murphi extensions (commit 6dbcd208025a4a07b94d818110613a69efc05e4a). Internal changes ~~~~~~~~~~~~~~~~ * Bug fix: the test suite no longer attempts to output decoded UTF-8 data on stdout/stderr (commit 551d18398189cb11ba6274d708d3ff293af034c7). v2019.03.02 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: enum types with duplicate members are now rejected. Previously, members would silently shadow earlier duplicate members (commit b476ffbdb7f5afb245c933a89d8f3cf9ecc8a884). * Bug fix: models that redeclare symbols are now rejected. Previously, definitions would silently shadow earlier same-named definitions (commit 96b8acab16310f4e80008b92827f804ba6e3ae66). * The generated verifier produces more context information in error messages (commits 45a63a9f26f531587d0c461da74467e2cc008c38, 7238dcacbf676c2649cfe82c98df25dbe96af93d, 9384c756477cbf164ea7f41227b053fca4c67fc5, 063e92bd53a5dbbb642e1d5c302a9240afff5fbc, 668c1d6ab02e9c55cfd8119e5a403c5595cd5b45, 39d35f4344633c2e1280fc0d5b28e2356140229b, 434fbf2f50d69b7824a224280bd5f7f3bcc2275d, 6822bba8a280b70d53d6dbb470f631143df0b5c4). * The implementation of the queue of pending states has been further optimised, resulting in a ~25% reduction in the runtime of the generated verifier (commits 8f0329c33343cfcf16675a110ed3211b9abc95e3, 2153f1f9e0ac7e2d015aff58cd0d8007901de808). * The warning emitted by Rumur when your model is missing a start state is now suppressed when you pass ``--quiet`` (commit 55514d39e40b2c018379e15d2f706e0a1c56ed18). Internal changes ~~~~~~~~~~~~~~~~ * Nothing relevant. v2019.02.14 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: calls of procedures (a.k.a. functions with no return type) are now rejected when appearing within an expression (commit 72d9196308a8b0d3b43929566beb571029b7e006). * Bug fix: unary negation that never worked correctly has been repaired (commit 48228f32c43423cd956f988fb0567fca080b9b28). * Between v2019.02.01 and v2019.02.04, there was an unintended performance regression in the runtime of the generated verifier (commit f5589751de2f860c3cca7d681f9710160d3c20a8). This has been addressed and the verifier runs faster than even v2019.02.01 (commit ccf410672326e04230331576a1c76003ad2ab1a3). * Returning a range-typed expression within a function that returns a *different* range type is now supported (commit e196ed43199d6d47d36eb9f225017c2123e294c3). Internal changes ~~~~~~~~~~~~~~~~ * ``Expr::type()`` returns a smart pointer that is never null (commits d89de1376abe5bbbef61d68b02c45a35c4f9a12f, beeffb42ad6514448e463e8a2d73d3a1d8b35898, e196ed43199d6d47d36eb9f225017c2123e294c3, 5dcf10f2821ffb8a2080b297fc664485884747be). v2019.02.04 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: using a non-scalar (record or array) result of a function call as an input parameter to another function or procedure would previously cause an assertion failure during code generation. This has been addressed and correct code is now generated (commit 73dcbf237f747d8958528127f6a05442bd3bf2c0). * Bug fix: the convenience wrapper ``rumur-run`` now correctly exits when one of its steps fails and also returns the correct exit status (commits 9eae5c5a22a87507713a2ebc5b57120de00e6f10, 46cc017ee8c6337453601c245e6e764254687f48, 235fbc552addefc1f34e8840a9d80845b423d30e, 80825dfb406eb6f39aaa01c9011eadd7b6ad9b05). * Bug fix: column offset information in the XML produced by ``rumur-ast-dump`` was sometimes off by one. This is now corrected (commit 7d8dc868d9e1c31243b15e3de116e4f0740a38b3). * GCC 4.9 is now supported. Previously the oldest version of GCC we supported was 5 (commits 83ce80ad8bba3f48d4316dba29b4795c13facd03, 0ed86df81586b5808be82c924ad964b25cb38447). * The error message when a model assertion fails has been made more informative (commit 608fe69abfd7aa7ab724a42b1327bb055f7fb3ac). Internal changes ~~~~~~~~~~~~~~~~ * Nothing relevant. v2019.02.01 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * The values of ruleset parameters are reported in counter-example traces (commits 37f742797d8c76523607f90e80a5d1cc0ff16226, f7a8b012bfce555f156d1682cfd1073e8ccfe462, ee2d85200708cc70c2df056409d3da1283da2218). * The name of a failing invariant is given in the failure message (commit 60e864ccd8abefd617f21af4e1a78c53d1a3a66e). * Comparison of complex types using ``=`` or ``!=`` is supported in models (commits 107f6c4ac88ce4e2c6745507aa332aa17dfd3264, bbd3beebb6ce0a51475a241eff45d7c2a223bcbb). * ``rumur-run`` passes ``-march=native -mtune=native`` to the C compiler (commit ad9e26bfafb1cdf3877f46dd31b4072e1efffb5d). * Rulesets with non-constant parameters are rejected (commit 90810e214e7fa200d683f4ee4b79ef489d9e3d34). Internal changes ~~~~~~~~~~~~~~~~ * Various new interfaces were added to types and quantifiers (commits 6ea740ec2f6518733a626805af6b0f7275fc9b86, 41e01629c30293dc91dd460d0286b74763eba387, aea30d24234777a0b0698c1ce6f28f8267b15d9f, 154885bac4950b70c80620566e37d5a2890d317e). v2019.01.12 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: an issue that could have led to the corruption of reference-counted pointers in the checker was addressed (commit 04fede03a59624f3c08ee7b80d8f928dfc1e45be). * The licence has been changed from the vague "public domain" to the Unlicense. This is just a clarification and does not indicate a change in the licensing intent (commit 592e0c62ff9b1b7bf1bada4e41fa058d2d669ab8). * All Python components now work with Python 2 in addition to Python 3 (commits f04b1442af0b30581b17fc517aeecce99bd8f1ef, de4fcd64ed20b128e7dceb44dd57b757e15096c5). * ``rumur-run`` and ``rumur-ast-dump`` now have accompanying manpages (commits fe484a28ac3f77766b7de30569c85350b499ffbd, 3c2ba659f36e6b4cbedb8fd35b7f5c0f0af3be65). * A Zsh completion script was added (commit aac9e7718f3849b66932e375d673ea6b80547ff8). * Missing documentation for the ``--output`` option was added (commit 3047fb45f4a1aee9c5064ee9bb260df25bf72c8e). Internal changes ~~~~~~~~~~~~~~~~ * A RelaxNG schema was added for the format produced by the AST dumper (commit 36d26f6c327dbbd541537ad12d07636aba55f502). * Rumur should now be compilable with ``-pedantic`` in most environments (commits b4ef8c0e8bcc1af2a1afd00204e2df735928488f, 526afa1fb9e00bb159caf8ce49f83e40c571f747). v2018.12.20 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: boolean constants are now usable in boolean expressions, rather than being considered ranges (commits 3f8e25eed1b2cd88b04aec973b84efea3737f16b, 6ee751955a0781becae7dcc0e34a7477e668e462). * Bug fix: indexing a non-array expression is now reported as an error, rather than causing an assertion failure (commits 606657b7fc656fd4c304523b98c5e2828a896271, a31c9973f63a719b676be97e7a893dd21d451511, 5222f6ddce51ea66ceda6ecb0e016a94308e835b). * Bug fix: calling a function with incorrect arguments is now reported as an error, rather than causing an assertion failure or uncaught exception (commits 705793e6b0f3646d30dcab247d27cdd3ac94430c, 2427b74c4d6fb40115943dc01bbd66cc4ada7d17, fe9344f5b723608cd8916bd16c2688f9494ca92a). * Bug fix: trying to access the field of a non-record expression is now reported as an error, rather than causing an assertion failure or uncaught exception (commits f72373b30e8031baa8c8e0e953c05e47874ae854, 76d09b6bf77414b51af2bf1da0ecd099c25ad2e1, 27b61a5f6b0be2e838a39c02e567c87b4ce80d76, b917ece31a209ba9586c7c44577ba34b19a2c0a7). * Bug fix: The boolean literals ``true`` and ``false`` are now accepted in all possible casings (commits 121d724c00e2afc1d1fa6c525dad958646936fb1, 68e9164ae8a5a17c6e6346266051b24780bbf203). * The ``isundefined`` operator is now implemented (commits d12841246e207a5691159f8ed46faf08cb596dd5, 8e3563a0309d57dc19dbd7f0d1c50a8f30878559). * Range-typed expressions can now be passed in to functions as non-var parameters of a differing range type, where previously this scenario would only accept rvalues or identical range types (commits 343e97eeeb8ccd4c59bf150c42c0b74f1b00ec6a, 09cfec88a1e648eaa240404c2b215ed4cefec926, 2324e3efc370a09a289a4998c677cf1bfb31a245, 90a95c31d5c04a6083f753bc15f566658abcdf9d). * If the generated verifier is multithreaded, it now prints a thread identifier in each progress line (commit b222b3bc5fad2ff6e8371d3b46ad28809daa2451). * Some spurious compiler warnings when building the generated verifier have been suppressed (commits 8a05ab0d209c0b8cbfa7048d5775505c1f70f283, 4f447fdcc44f694f8bc1d948bbc17d690ca3d59f, 7885b611ef9d9e6d18629b1eb696def0183eed16). Internal changes ~~~~~~~~~~~~~~~~ * The use of ``static_assert`` has been replaced with ``_Static_assert`` (commit ad26fe525f7ba99dfbf3d5c6bc248ef41602d9a5). * ``Expr`` and ``Decl`` gained a new ``is_readonly`` method (commit 47c27f217b035fa9881fe32576354c08669b0899) and the distinction between the concepts of "lvalue" and "writable" is now more accurate. * The test suite has been backported so it also runs on Python 2 in addition to Python 3 (commit 7fe028271d376188d8b5d6353e0bca720d12e6b9). v2018.12.08 ----------- * Hello world! rumur-2024.05.07/CMakeLists.txt000066400000000000000000000047701461637631000160440ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.1 FATAL_ERROR) project(rumur LANGUAGES CXX) # This seems to be some magic to get libraries to install correctly. include(GNUInstallDirs) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -W -Wall -Wextra -Wformat=2 \ -Wwrite-strings -Wmissing-declarations -Wshadow -Wundef") # enable even more warnings if the compiler supports them include(CheckCXXCompilerFlag) CHECK_CXX_COMPILER_FLAG(-Wcast-qual HAS_WARNING_CAST_QUAL) if(HAS_WARNING_CAST_QUAL) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wcast-qual") endif() CHECK_CXX_COMPILER_FLAG(-Wcast-align HAS_WARNING_CAST_ALIGN) if(HAS_WARNING_CAST_ALIGN) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wcast-align") endif() CHECK_CXX_COMPILER_FLAG(-Wlogical-op HAS_WARNING_LOGICAL_OP) if(HAS_WARNING_LOGICAL_OP) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wlogical-op") endif() CHECK_CXX_COMPILER_FLAG(-Wstrict-aliasing=1 HAS_WARNING_STRICT_ALIASING_1) if(HAS_WARNING_STRICT_ALIASING_1) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wstrict-aliasing=1") endif() CHECK_CXX_COMPILER_FLAG(-Wpointer-arith HAS_WARNING_POINTER_ARITH) if(HAS_WARNING_POINTER_ARITH) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wpointer-arith") endif() # Enable --as-needed, present on GNU ld on Linux, to minimise dependencies. if(CMAKE_SYSTEM_NAME STREQUAL "Linux") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--as-needed") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--as-needed") endif() # if we have a new enough CMake to have FindPython3, check for it if(CMAKE_VERSION VERSION_GREATER 3.11) find_package(Python3 REQUIRED COMPONENTS Interpreter) endif() add_subdirectory(librumur) add_subdirectory(murphi2c) add_subdirectory(murphi2murphi) add_subdirectory(murphi2uclid) add_subdirectory(murphi2xml) add_subdirectory(rumur) add_subdirectory(tests/murphi-comment-ls) add_custom_target(check COMMAND env PATH=${CMAKE_CURRENT_BINARY_DIR}/rumur:${CMAKE_CURRENT_BINARY_DIR}/murphi2c:${CMAKE_CURRENT_BINARY_DIR}/murphi2murphi:${CMAKE_CURRENT_BINARY_DIR}/murphi2uclid:${CMAKE_CURRENT_BINARY_DIR}/murphi2xml:${CMAKE_CURRENT_BINARY_DIR}/tests/murphi-comment-ls:$ENV{PATH} ${CMAKE_CURRENT_SOURCE_DIR}/tests/run-tests.py --verbose) add_dependencies(check murphi2c murphi2murphi murphi2uclid murphi2xml rumur) if(NOT CMAKE_CROSSCOMPILING) add_dependencies(check murphi-comment-ls) endif() add_custom_target(format COMMAND git ls-files -z '**/*.cc' '**/*.h' | xargs -0 -- clang-format -i --style=file WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) rumur-2024.05.07/CONTRIBUTING.rst000066400000000000000000000013421461637631000157350ustar00rootroot00000000000000Contributing ============ Patches are welcome in whatever form. Contributions will be distributed with Rumur under a public domain licence. Providing a change to be included implies that you agree with this and your contribution does not cause trouble with trademarks or patents. There is no CLA to sign. Issues are tracked on Github, but it is not a requirement to report issues there. Emailing the author is also fine. Feel free to report even the smallest problem, also typos in the documentation. If the above text sounds familiar, it is modelled after the excellent `Vim contribution guidelines`_. Basically, be a good open source citizen. .. _`Vim contribution guidelines`: https://github.com/vim/vim/blob/master/CONTRIBUTING.md rumur-2024.05.07/LICENSE000066400000000000000000000022721461637631000143040ustar00rootroot00000000000000This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to rumur-2024.05.07/NEWS000077700000000000000000000000001461637631000160072CHANGELOG.rstustar00rootroot00000000000000rumur-2024.05.07/README.rst000066400000000000000000000053031461637631000147640ustar00rootroot00000000000000Rumur ===== Rumur is a `model checker`_, a formal verification tool for proving safety and security properties of systems represented as state machines. It is based on a previous tool, CMurphi_, and intended to be close to a drop-in replacement. Rumur takes the same input format as CMurphi, the Murphi modelling language, with some extensions and generates a C program that implements a verifier. A more extended introduction is available in `doc/introduction.rst`_ .. _`doc/introduction.rst`: doc/introduction.rst Quickstart ---------- Installation on Ubuntu ≥ 20.04 or Debian ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: sh apt install rumur Installation on FreeBSD ~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: sh pkg install rumur Thanks to `yuri@FreeBSD`_ for packaging. .. _`yuri@FreeBSD`: https://github.com/yurivict Building from Source ~~~~~~~~~~~~~~~~~~~~ First you will need to have the following dependencies installed: * Either GCC_ or Clang_ * Bison_ * CMake_ * Flex_ * Libgmp_ * Python_ ≥ 3.4 Then: .. code-block:: sh # Download Rumur git clone https://github.com/Smattr/rumur cd rumur # Configure and compile mkdir build cd build cmake .. make make install # Generate a checker rumur my-model.m --output my-model.c # Compile the checker (also pass -mcx16 if using GCC on x86-64) cc -std=c11 -O3 my-model.c -lpthread # Run the checker ./a.out Compilation produces several artefacts including the `rumur` binary itself: * rumur: Tool for translating a Murphi model into a program that implements a checker; * murphi2c: Tool for translating a Murphi model into C code for use in a simulator; * murphi2murphi: A preprocessor for Murphi models; * murphi2xml: Tool for emitting an XML representation of a Murphi model’s Abstract Syntax Tree; * librumur.a: A library for building your own Murphi model tools; and * include/rumur/: The API for the above library. Comparison with CMurphi ----------------------- If you are migrating from CMurphi, you can read a comparison between the two model checkers at `doc/vs-cmurphi.rst`_. .. _doc/vs-cmurphi.rst: doc/vs-cmurphi.rst Legal ----- Everything in this repository is in the public domain, under the terms of `the Unlicense`_. For the full text, see LICENSE_. .. _Bison: https://www.gnu.org/software/bison/ .. _CMake: https://cmake.org/ .. _CMurphi: http://mclab.di.uniroma1.it/site/index.php/software/18-cmurphi .. _Clang: https://clang.llvm.org/ .. _Flex: https://github.com/westes/flex .. _GCC: https://gcc.gnu.org/ .. _Libgmp: https://gmplib.org/ .. _LICENSE: ./LICENSE .. _`model checker`: https://en.wikipedia.org/wiki/Model_checking .. _Python: https://www.python.org/ .. _`the Unlicense`: http://unlicense.org/ rumur-2024.05.07/common/000077500000000000000000000000001461637631000145645ustar00rootroot00000000000000rumur-2024.05.07/common/escape.cc000066400000000000000000000007061461637631000163360ustar00rootroot00000000000000#include "escape.h" #include #include #include #include static std::string octal(char c) { char buffer[sizeof("\\000")]; snprintf(buffer, sizeof(buffer), "\\%03o", c); return buffer; } std::string escape(const std::string &s) { std::string out; for (const char &c : s) { if (iscntrl(c) || c == '\\' || c == '\"') { out += "\\" + octal(c); } else { out += c; } } return out; } rumur-2024.05.07/common/escape.h000066400000000000000000000002461461637631000161770ustar00rootroot00000000000000#pragma once #include #include // escape a string for the purposes of outputting it to a C source file std::string escape(const std::string &s); rumur-2024.05.07/common/help.cc000066400000000000000000000033601461637631000160250ustar00rootroot00000000000000#include "help.h" #include #include #include #include #include #include #include /* The approach we take below is writing the manpage to a temporary location and * then asking man to display it. It would be nice to avoid the temporary file * and just pipe the manpage to man on stdin. However, man on macOS does not * seem to support reading from pipes. Since we need a work around for at least * macOS, we just do it uniformly through a temporary file for all platforms. */ int help(const unsigned char *manpage, size_t manpage_len) { int ret = 0; // Find temporary storage space std::string tmp; const char *TMPDIR = getenv("TMPDIR"); if (TMPDIR == nullptr) { tmp = "/tmp"; } else { tmp = TMPDIR; } // Create a temporary file size_t size = tmp.size() + sizeof("/temp.XXXXXX"); std::vector path(size); snprintf(path.data(), path.size(), "%s/temp.XXXXXX", tmp.c_str()); int fd = mkstemp(path.data()); if (fd == -1) { ret = errno; std::cerr << "failed to create temporary file\n"; goto done; } // Write the manpage to the temporary file { ssize_t r = write(fd, manpage, manpage_len); if (r < 0 || (size_t)r != manpage_len) { ret = errno; std::cerr << "failed to write manpage to temporary file\n"; goto done; } } // Close our file handle and mark it invalid close(fd); fd = -1; // Run man to display the help text { std::string args("man "); #ifdef __linux__ args += "--local-file "; #endif args += path.data(); ret = system(args.c_str()); } // Cleanup done: if (fd >= 0) close(fd); if (access(path.data(), F_OK) == 0) (void)unlink(path.data()); return ret; } rumur-2024.05.07/common/help.h000066400000000000000000000001721461637631000156650ustar00rootroot00000000000000#pragma once #include // Display help information int help(const unsigned char *manpage, size_t manpage_len); rumur-2024.05.07/common/isa.h000066400000000000000000000002621461637631000155110ustar00rootroot00000000000000#pragma once #include template static inline bool isa(const U ptr) { return ptr != nullptr && dynamic_cast(&*ptr) != nullptr; } rumur-2024.05.07/doc/000077500000000000000000000000001461637631000140415ustar00rootroot00000000000000rumur-2024.05.07/doc/bitwise-operators.rst000066400000000000000000000044731461637631000202650ustar00rootroot00000000000000Bitwise Operators ================= As an extension to the Murphi language, various operators for bitwise arithmetic are supported by Rumur. These can only be used on range-typed values. Using them with operands of any other type will result in an error. It is generally assumed that you will use these operators on non-negative values. There are no checks to prevent using them on negative values, but their results are likely not what you want. Bitwise AND and OR ------------------ .. code-block:: murphi x & y x | y These operators are overloaded for both logical and bitwise operations. That is, ``&`` can be used for both logical AND and bitwise AND, and ``|`` can be used for both logical OR and bitwise OR. Which interpretation is used depends on the types of the operands. If the operands are both booleans, the operator is interpreted as the logical form. If the operands are both range-typed, the operator is interpreted as the bitwise form. Any other combination of operand types is rejected as an error. These operators have the same, potentially confusing, precedence as their C equivalents. So you may have to sometimes use brackets to achieve the correct effect: .. code-block:: murphi var x: 0 .. 10 y: 0 .. 10 … -- brackets required here if (x & y) = 3 then … end; Bitwise NOT ----------- .. code-block:: murphi ~x The number of bits used to represent range values in the verifier is variable (controllable via command line option ``--value-type``). So you will probably want to mask any value being NOTed with something to cap the number of bits in the result: .. code-block:: murphi var x: 0 .. 15 … -- & to ensure the value of x does not exceed 4 bits x := ~x & 15; Bitwise XOR ----------- .. code-block:: murphi x ^ y Left and Right Shift -------------------- .. code-block:: murphi x << y x >> y Unlike the C operator equivalents, left and right shifts are well-defined for all possible shift values. A negative shift results in a shift in the opposite direction. For example, ``x >> -3`` is equivalent to ``x << 3``. Shifting by greater than the number of bits used to represent range values (controllable via command line option ``--value-type``) results in ``0``. Shifting left into the sign bit is well defined. The right shift is an arithmetic shift, not logical. rumur-2024.05.07/doc/internals-atomics.rst000066400000000000000000000136631461637631000202400ustar00rootroot00000000000000Internals — Atomics =================== When writing multithreaded C code, there are four commonly used APIs for atomic operations: 1. `inline assembly`_; 2. `__sync built-ins`_; 3. `__atomic built-ins`_; or 4. `C11 atomics`_ .. _`inline assembly`: https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html .. _`__sync built-ins`: https://gcc.gnu.org/onlinedocs/gcc/_005f_005fsync-Builtins.html .. _`__atomic built-ins`: https://gcc.gnu.org/onlinedocs/gcc/_005f_005fatomic-Builtins.html#g_t_005f_005fatomic-Builtins .. _`C11 atomics`: https://en.cppreference.com/w/c/atomic Rumur needs to use one of the above for each of its atomic operations. Inline assembly is essentially a non-starter. It is non-portable and awkward. Of the remaining three, they do not offer the equivalent functionality as seen from the table below. +----------------------------------+---------------------------------+------------------------------------+ | __sync built-ins | __atomic built-ins | C11 atomics | +==================================+=================================+====================================+ | | ``__atomic_load_n`` | ``atomic_load`` | | | ``__atomic_load`` | | +----------------------------------+---------------------------------+------------------------------------+ | | ``__atomic_store_n`` | ``atomic_store`` | | | ``__atomic_store`` | | +----------------------------------+---------------------------------+------------------------------------+ | | ``__atomic_exchange_n`` | ``atomic_exchange`` | | | ``__atomic_exchange`` | | +----------------------------------+---------------------------------+------------------------------------+ | ``__sync_bool_compare_and_swap`` | ``__atomic_compare_exchange_n`` | ``atomic_compare_exchange_strong`` | | ``__sync_val_compare_and_swap`` | ``__atomic_compare_exchange`` | ``atomic_compare_exchange_weak`` | +----------------------------------+---------------------------------+------------------------------------+ | ``__sync_lock_test_and_set`` | ``__atomic_test_and_set`` | ``atomic_flag_test_and_set`` | | ``__sync_lock_release`` | ``__atomic_clear`` | ``atomic_flag_clear`` | +----------------------------------+---------------------------------+------------------------------------+ | ``__sync_fetch_and_add`` | ``__atomic_fetch_add`` | ``atomic_fetch_add`` | | ``__sync_add_and_fetch`` | ``__atomic_add_fetch`` | | +----------------------------------+---------------------------------+------------------------------------+ | ``__sync_fetch_and_sub`` | ``__atomic_fetch_sub`` | ``atomic_fetch_sub`` | | ``__sync_sub_and_fetch`` | ``__atomic_sub_fetch`` | | +----------------------------------+---------------------------------+------------------------------------+ | ``__sync_fetch_and_or`` | ``__atomic_fetch_or`` | ``atomic_fetch_or`` | | ``__sync_or_and_fetch`` | ``__atomic_or_fetch`` | | +----------------------------------+---------------------------------+------------------------------------+ | ``__sync_fetch_and_and`` | ``__atomic_fetch_and`` | ``atomic_fetch_and`` | | ``__sync_and_and_fetch`` | ``__atomic_and_fetch`` | | +----------------------------------+---------------------------------+------------------------------------+ | ``__sync_fetch_and_xor`` | ``__atomic_fetch_xor`` | ``atomic_fetch_xor`` | | ``__sync_xor_and_fetch`` | ``__atomic_xor_fetch`` | | +----------------------------------+---------------------------------+------------------------------------+ | ``__sync_fetch_and_nand`` | ``__atomic_fetch_nand`` | | | ``__sync_nand_and_fetch`` | ``__atomic_nand_fetch`` | | +----------------------------------+---------------------------------+------------------------------------+ Some relevant points to note from this table and from other sources: * The __sync built-ins have no way of expressing that a load or store must be atomic. This is not an issue on platforms like x86 where all naturally aligned loads and stores are guaranteed atomic, but it is a problem for other platforms. * The __sync built-ins have no way of expressing an atomic exchange operation. * The __sync built-ins have no way of expressing memory ordering. This is not an issue on platforms like x86 that guarantee sequential consistency, but on platforms that allow weaker guarantees it is an issue. * Some of the C11 atomics expect to operate on ``atomic_`` types and are not directly usable on regular variables. * Double-word compare and exchange using either the __atomic built-ins or the C11 atomics causes a call to libatomic to be emitted by GCC. This is functionally correct but impairs our ability to use these in lock-free algorithms. See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80878 and https://gcc.gnu.org/pipermail/gcc-help/2017-June.txt for more information. * The C11 atomics are not available prior to GCC 4.9. With these constraints in mind, the checker that Rumur generates mostly uses __atomic built-ins, resorting to the __sync built-ins when doing something that is not possible with the __atomic built-ins. rumur-2024.05.07/doc/internals-hash-function.rst000066400000000000000000000012421461637631000213350ustar00rootroot00000000000000Internals — Hash Function ========================= The seen state set (described in `internals-seen-state-set.rst`_) hashes the contents of state data to determine an index at which to store that state. The hash function used for this is MurmurHash_ from `Austin Appleby`_. This hash function was chosen because it has reasonable statistical properties and is public domain. There might be a performance benefit to switching to a different hash function, but so far this has not proved a bottleneck. .. _`Austin Appleby`: https://github.com/aappleby .. _`internals-seen-state-set.rst`: ./internals-seen-state-set.rst .. _MurmurHash: https://github.com/aappleby/smhasher rumur-2024.05.07/doc/internals-reference-counted-pointers.rst000066400000000000000000000063101461637631000240260ustar00rootroot00000000000000Internals — Reference Counted Pointers ====================================== The generated verifier contains an implementation of reference counted pointers, ``refcounted_ptr``. This document describes their design and how they work. Definition ---------- .. code-block:: c struct refcounted_ptr { void *ptr; size_t count; }; A reference counted pointer contains two fields, the raw pointer and a count of the number of outstanding (i.e. live) references. You can think of the count as the number of threads who have "borrowed" a copy of this pointer. Interface --------- The basic operations on a reference counted pointer are get and put: .. code-block:: c void *refcounted_ptr_get(struct refcounted_ptr *p); size_t refcounted_ptr_put(struct refcounted_ptr *p, void *ptr); The get operation takes a reference to the pointer (increments its ``count`` by ``1``) and returns the raw pointer value. The caller can freely use this pointer as it would any other pointer. The put operation returns a reference to the pointer (decrements its ``count`` by ``1``) and returns the number of remaining references to this pointer. If the returned value is ``0``, the caller can free the raw pointer as it knows there are no other remaining users of it. There is nothing to prevent another thread calling ``refcounted_ptr_get`` on a pointer whose reference count has just dropped to ``0`` and thereby increasing it back to ``1``. That is, after calling ``refcounted_ptr_put`` and receiving ``0`` as the return value, it is possible for another thread to increase the reference count prior to the pointer being freed. It is up to threads to coordinate their actions to avoid this race. The ``ptr`` parameter to ``refcounted_ptr_put`` is for debugging purposes only. Two other operations are provided for setting the value of a reference counted pointer: .. code-block:: c void refcounted_ptr_set(struct refcounted_ptr *p, void *ptr); void refcounted_ptr_shift(struct refcounted_ptr *current, struct refcounted_ptr *next); The first of these, ``refcounted_ptr_set``, sets the pointer's raw value to ``ptr`` and zeroes its reference count. The second, ``refcounted_ptr_shift``, replaces fields in ``current`` with those in ``next`` and zeroes ``next``. Neither of these functions use atomic operations and threads are expected to coordinate with each other such that calls to these do not race with each other or with gets and puts. Implementation of Atomic Updates -------------------------------- Both ``refcounted_ptr_get`` and ``refcounted_ptr_put`` need to operate atomically on the pointer they are affecting. This is straightforward for put that only needs to atomically decrement the reference count, but get needs to read the pointer and the count atomically while incrementing the count. To achieve this, we use a double-word compare-exchange operation. First we read the entire structure atomically. We then extract the pointer value and increment the count in the local copy we have. Finally we use an atomic compare-exchange to write the changes back to the original structure. This relies on platform support for atomic double-word compare-exchange. For example, on x86-64 this involves a ``CMPXCHG16B`` instruction. rumur-2024.05.07/doc/internals-seen-state-set.rst000066400000000000000000000135531461637631000214400ustar00rootroot00000000000000Internals — Seen State Set ========================== When exploring the state space of your model, the generated verifier stores the states it has encountered in a global set structure. This structure's key properties are: * Insert-only: there is no support for removing elements from the set. * Thread-safe, lock-free: multiple threads can be inserting into the set at once without creating a scalability bottleneck. * Dynamically expanding: when the set fills up, its capacity is expanded to hold more states. High Level Design ----------------- The seen state set is modelled closely after the data structure described in Maier et al, "Concurrent Hash Tables: Fast and General(?)!" in arXiv 2016. This paper lays the ground work for how to build a scalable concurrent set, and the remainder of the present document will assume you have read and understood the paper. The main point of deviation from Maier et al's design is that we do not implement the optimisation that allows their set expansion to use non-atomic writes. In the Rumur design, each thread has a unique "chunk" (a 4KB block of state pointers) that it is migrating, but the destinations for these elements may collide with the writes of another thread. We use atomic exchanges to guard against this interference. The main reason we don't implement this optimisation is that I was initially anticipating a configuration where you fully fill the seen set before expansion, instead of expanding at a threshold like 60% occupancy. Now I see this as a less realistic scenario, so it might be worth looking into this optimisation in future. Definition ---------- The set itself is quite simple: .. code-block:: c struct set { slot_t *bucket; size_t size_exponent; }; The total capacity of the set can be computed by ``1 << size_exponent``, encapsulated in the function ``set_size()``. We store an exponent rather than the size itself as a micro-optimisation to make it explicit to the compiler that the set capacity is always a power of two. The occupancy of the set (how many elements are actually stored in the set) is stored in a global: .. code-block:: c static size_t seen_count; The occupancy only grows throughout the program's lifetime and storing it in a global (a) allows it to be referenced without a pointer indirection and (b) decreases the size of reallocation by ``sizeof(size_t)`` when expanding the set. Local and Global Pointers ------------------------- During execution there is a single global pointer to the currently active seen set and a local pointer to this set for each thread. The global pointer is reference counted (see `internals-reference-counted-pointers.rst`_). The motivation for this and mechanics of pointer borrowing and returning will become clearer when we discuss set expansion. .. _`internals-reference-counted-pointers.rst`: ./internals-reference-counted-pointers.rst Set Insertion ------------- The common path for inserting an element into the set is quite straightforward. A thread calls the set insertion function: .. code-block:: c bool set_insert(struct state *s, size_t *count); Within the function, a hash function (described in `internals-hash-function.rst`_) is used to compute the index at which the element should be stored. Using a thread-local pointer to access the currently active seen state set, the insertion algorithm uses `linear probing`_ to find an empty slot to insert the state pointer into. .. _`internals-hash-function.rst`: ./internals-hash-function.rst .. _`linear probing`: https://en.wikipedia.org/wiki/Linear_probing If you are following along in `../rumur/resources/header.c`_, you will have noticed two odd things: 1. The insertion algorithm checks occupancy and conditionally calls ``set_expand()``; and 2. There is a check for something called a "tombstone." Both of these are explained in the next section. .. _`../rumur/resources/header.c`: ../rumur/resources/header.c Set Expansion ------------- When the set exceeds a pre-defined occupancy threshold, it is expanded by doubling its size. A simplified summary of how this is done is: 1. ``malloc()`` a new set twice the size of the existing one. 2. Take each state stored in the old set, rehash it, and insert it into the new set. 3. ``free()`` the old set. But how is this possible if the verifier is multithreaded and set insertion is lock-free? Firstly, when a thread decides to expand the seen state set it can either "win" the race to allocate the new set or "lose" the race and fall back to joining a set migration begun by a previous thread. During migration, threads grab successive "chunks" (4KB blocks of state pointers) to exclusively migrate by atomically incrementing a shared counter. As they migrate state pointers from the old set to the new, they replace the old state pointer with a "tombstone" value. We can now understand why ``set_insert()`` is checking for tombstones. If it sees a tombstone, it knows a set expansion and migration has been initiated by another thread. It can then hold off on its insertion, join the migration effort, then return to attempting its insertion on the new set. At the end of a set migration, all threads synchronise at a rendezvous point (to be described in forthcoming documentation). The last arriving thread to the rendezvous point updates the global seen set pointer to point to the new set. When threads depart the rendezvous point, they borrow a new local copy of this pointer. A Note on Complexity -------------------- The seen state set is one of the most complex and performance sensitive components of the verifier. Given I am not a professional technical writer, it is unlikely you have fully understood how it works from the above description. If you are interested in learning more, I encourage you to read the source in `../rumur/resources/header.c`_. It is dense and the control flow can be counter intuitive, but unfortunately I did not have more time to make it simpler. rumur-2024.05.07/doc/internals-warts.rst000066400000000000000000000107461461637631000177400ustar00rootroot00000000000000Internals — Warts ================= This document covers some design flaws in Rumur that it would be nice to find a solution to at some point. Non-minimal traces when running multithreaded --------------------------------------------- * `Github issue #131 “minimal trace mode”`_ Rumur defaults to generating a multithreaded verifier (``--threads 0``) that aborts as soon as it finds an error (``--max-errors 1``). This was seen to be pragmatic and aligning with what users typically want to do when bringing up a new model. That is, run as fast as possible and stop as soon as we know an exhaustive proof is impossible. When examining counterexample traces, you generally want to be presented with the shortest possible example. This happens naturally in a single-threaded verifier as it performs a breadth-first search. The multithreaded verifier is only approximately breadth-first because all the threads are essentially racing with each other in their exploration. As a result, the first problem found may not be the shallowest and may result in a non-minimal counterexample trace. Each thread encounters states in greater-than-or-equal-depth order, so a naive attempt at forcing a minimal trace is to temporarily stall a thread when it finds an error. We can unblock this thread and allow it to report its error when all other threads have either (a) finished exploration or (b) exceeded the depth of the error location found by the first thread. If a second thread finds an error at a shallower depth than the first, it can simply kill the first thread and then itself stall as described. Alas, this is insufficient. Taking the example from the Github issue, the state machine may look as follows: .. code-block:: A -> B -> C -> ... \ ^ \______/ Thread 0 might discover ``C`` first via the path ``A -> B -> C``. Later thread 1 might rediscover ``C`` via the path ``A -> C``. Thread 1 will notice its ``C`` is a duplicate and discard it. Now suppose one of the threads finds a deeper error whose counterexample traces back to ``C``. This counterexample will trace back through the ``A -> B`` prefix rather than the shorter ``A``. So far I have not come up with a design that would result in a minimal counterexample trace in a multithreaded verifier without degrading current performance. Solving this would, I believe, be of significant value to users. .. _`Github issue #131 “minimal trace mode”`: https://github.com/Smattr/rumur/issues/131 Incomplete AST in recursive functions ------------------------------------- The librumur Abstract Syntax Tree (AST) represents child nodes using smart pointers, ``rumur::Ptr``. This has constructor and assignment operator implementations that allow the programmer to avoid thinking too much about memory management. The general pattern is to freely copy the pointed-to object into a new ``rumur::Ptr``. This isn’t very efficient, but has the advantage of being simple. Moreover, there is little point tightly optimising AST manipulation when any AST that is large enough to see benefit from this most likely results from a model that is too large to verify. A side-effect of this “deep copy” pointer design is that the AST is inherently a Directed Acyclic Graph (DAG). It is not possible for an AST node to contain a reference to something above itself in the AST because assigning this reference takes a copy of the parent node. This means that during symbol resolution, a recursive function call has no way to store a fully resolved reference to its callee. To see why this is, follow through the symbol resolution process. To begin with, the containing function has a statement that includes an unresolved function call. The symbol resolution traversal finds this unresolved function call and resolves it to the containing function by copying it. However, this copy still contains the unresolved version of the function call as a child. We can descend into the callee function (``rumur::FunctionCall::function``) but we’ll merely repeat the process and have a deeper unresolved function call. The current design merely avoids descending into the callees of function calls. This is acceptable, but it leaves an AST containing unresolved function calls that may come as a surprise to later consumers. Obtaining a fully resolved AST while supporting recursion seems inherently at odds with the current design. It seems like it would be a net loss to permit cycles in the AST (we would have to manually manage pointers and memory) but perhaps there is another compromise we could find. rumur-2024.05.07/doc/intro-to-murphi.rst000066400000000000000000000547571461637631000176720ustar00rootroot00000000000000Introduction to Murphi ====================== The input syntax accepted by Rumur is a variant of a language called “Murphi” that was originally designed for use in a different model checker. The following provides an introduction to the language by way of a simple example that we gradually build upon. Overview -------- Murphi models consist of three classes of definitions: 1. Definitions of constants, types and state variables; 2. Functions and procedures; and 3. State transition rules and invariants. Rumur’s predecessors required these to appear strictly in order. That is, all constants, types and state variables had to be defined before any functions or procedures, and all functions and procedures had to be defined before any rules or invariants. Rumur does not have this restriction, but it is a good habit to observe this if you want your model to be compatible with other Murphi tools. The Dining Philosophers ----------------------- Let’s think about a classic concurrency problem, the dining philosophers. Five philosophers go out for a spaghetti dinner. They sit around a circular table with five plates of spaghetti and five forks, placed between the plates. Each philosopher needs two forks to eat the plate of spaghetti in front of them. This is a system that can be thought of as a state machine. At each point in time, a philosopher can either be thinking, hungry and trying to eat, or eating. The moves they can each make are to pick up a fork or put down a fork. We want to ensure that all philosophers eventually get to eat. Sounds pretty simple, right? Building a Model ---------------- We can use Rumur to prove all the philosophers will eventually get to eat. To do this, we construct a Murphi model describing the dining philosophers. Our philosophy dinner has five participants, but maybe in future we will want to think about more than five philosophers. So let’s plan ahead and use a Murphi constant that we can easily update later. .. code-block:: murphi const N_PHILOSOPHERS: 5 We always have the same number of forks as philosophers, so let’s define that too. .. code-block:: murphi const N_FORKS N_PHILOSOPHERS We can identify each philosopher and each fork by an identifier from 0 to 4. .. code-block:: murphi type philosopher_id: 0 .. N_PHILOSOPHERS - 1 type fork_id: 0 .. N_FORKS - 1 The philosophers can be thinking, hungry or eating. We can represent this in Murphi with an enumerated type. .. code-block:: murphi type action: enum { THINKING, HUNGRY, EATING } We can now represent a philosopher as a record (like a C struct) capturing their mood and which forks they have. .. code-block:: murphi type philosopher: record mood: action has_left_fork: boolean has_right_fork: boolean end We’re now ready to define the state of the system. This will be the philosophers themselves and an array indicating which forks are held. .. code-block:: murphi var philosophers: array[philosopher_id] of philosopher var forks: array[fork_id] of boolean This completes the first section of our dining philosophers model. We have seen constants, all the basic types, and how to define state variables. The keywords ``const``, ``type``, and ``var`` are not actually attached to individual declarations but rather to sections. So we could write a condensed version of what we have so far as follows. .. code-block:: murphi const N_PHILOSOPHERS: 5 N_FORKS: N_PHILOSOPHERS type philosopher_id: 0 .. N_PHILOSOPHERS - 1 fork_id: 0 .. N_FORKS - 1 action: enum { THINKING, HUNGRY, EATING } philosopher: record mood: action has_left_fork: boolean has_right_fork: boolean end var philosophers: array[philosopher_id] of philosopher forks: array[fork_id] of boolean OK, time for some helper functions. We already know we will need to refer to the forks on either side of a philosopher. So let’s define functions for these to avoid having to write awkward modular arithmetic repeatedly. .. code-block:: murphi function left_fork(id: philosopher_id): fork_id; begin return id; end function right_fork(id: philosopher_id): fork_id; begin return (id + 1) % N_PHILOSOPHERS; end Note that the way parameters and return types occur might be back to front to what you expect from, for example, C. Function parameters are given as ``name: type`` and the return type appears after the function’s closing bracket. We can now start defining the state transition rules for our system. These are (optionally guarded) blocks that describe updates to the state. All the statements within a rule execute atomically. That is, the entire block as a whole represents a transition from one system state to another. The first one of these rules is the start state that describes how to initialise our system. .. code-block:: murphi startstate begin -- all philosophers start dinner hungry and empty handed for i: philosopher_id do philosophers[i].mood := HUNGRY; philosophers[i].has_left_fork := false; philosophers[i].has_right_fork := false; end; -- so all forks are unheld for i: fork_id do forks[i] := false; end; end A Murphi model can have multiple start states. However we will only use one in this model. What do the philosophers do when they are hungry? They try to grab forks to eat. To write a transition rule for this we do not want to talk about any particular philosopher but rather *any* of the five philosophers. We can do this with a rule set. .. code-block:: murphi ruleset i: philosopher_id do rule "take left fork" philosophers[i].mood = HUNGRY -- wants to eat & !philosophers[i].has_left_fork -- doesn’t have the left fork & !forks[left_fork(i)] -- left fork is available ==> begin forks[left_fork(i)] := true; philosophers[i].has_left_fork := true; end rule "take right fork" philosophers[i].mood = HUNGRY & !philosophers[i].has_right_fork & !forks[right_fork(i)] ==> begin forks[right_fork(i)] := true; philosophers[i].has_right_fork := true; end end If a philosopher has both their forks and they are hungry, they can start eating. .. code-block:: murphi ruleset i: philosopher_id do rule "eat" philosophers[i].mood = HUNGRY -- wants to eat & philosophers[i].has_left_fork & philosophers[i].has_right_fork -- has both forks ==> begin philosophers[i].mood := EATING; end end Once they have been eating, a philosopher may get full and decide to take a break and think for a while. Note that at our dinner philosophers cannot start thinking when they are hungry because they are too distracted by their stomach rumbling. .. code-block:: murphi ruleset i: philosopher_id do rule "think" philosophers[i].mood = EATING ==> begin philosophers[i].mood := THINKING; end end A thinking philosopher may always be struck by hunger again. .. code-block:: murphi ruleset i: philosopher_id do rule "get hungry" philosophers[i].mood = THINKING ==> begin philosophers[i].mood := HUNGRY; end end Finally, a philosopher who is thinking but also holding forks places them back on the table as they ponder the mysteries of the universe. .. code-block:: murphi ruleset i: philosopher_id do rule "drop left fork" philosophers[i].mood = THINKING & philosophers[i].has_left_fork ==> begin -- sanity check that the fork we are releasing was held assert forks[left_fork(i)]; forks[left_fork(i)] := false; philosophers[i].has_left_fork := false; end rule "drop right fork" philosophers[i].mood = THINKING & philosophers[i].has_right_fork ==> begin assert forks[right_fork(i)]; forks[right_fork(i)] := false; philosophers[i].has_right_fork := false; end end These are all the transition rules we need to describe the dining philosophers. Let’s add an invariant; something that should always be true. Each fork can only be held by a single philosopher, so let’s claim that a philosopher holding their right fork is always next to a philosopher missing their left fork. .. code-block:: murphi invariant "no fork sharing" -- for any philosopher... forall i: philosopher_id do -- ...either they do not have their right fork... !philosophers[i].has_right_fork -- ...or their neighbour does not have their left fork | !philosophers[(i + 1) % N_PHILOSOPHERS].has_left_fork end And that’s it! We have built a model of the dining philosophers. Let’s put is altogether and merge the rule sets into a single block. .. code-block:: murphi const N_PHILOSOPHERS: 5 N_FORKS: N_PHILOSOPHERS type philosopher_id: 0 .. N_PHILOSOPHERS - 1 fork_id: 0 .. N_FORKS - 1 action: enum { THINKING, HUNGRY, EATING } philosopher: record mood: action has_left_fork: boolean has_right_fork: boolean end var philosophers: array[philosopher_id] of philosopher forks: array[fork_id] of boolean function left_fork(id: philosopher_id): fork_id; begin return id; end function right_fork(id: philosopher_id): fork_id; begin return (id + 1) % N_PHILOSOPHERS; end startstate begin -- all philosophers start dinner hungry and empty handed for i: philosopher_id do philosophers[i].mood := HUNGRY; philosophers[i].has_left_fork := false; philosophers[i].has_right_fork := false; end; -- so all forks are unheld for i: fork_id do forks[i] := false; end; end ruleset i: philosopher_id do rule "take left fork" philosophers[i].mood = HUNGRY -- wants to eat & !philosophers[i].has_left_fork -- doesn’t have the left fork & !forks[left_fork(i)] -- left fork is available ==> begin forks[left_fork(i)] := true; philosophers[i].has_left_fork := true; end rule "take right fork" philosophers[i].mood = HUNGRY & !philosophers[i].has_right_fork & !forks[right_fork(i)] ==> begin forks[right_fork(i)] := true; philosophers[i].has_right_fork := true; end rule "eat" philosophers[i].mood = HUNGRY -- wants to eat & philosophers[i].has_left_fork & philosophers[i].has_right_fork -- has both forks ==> begin philosophers[i].mood := EATING; end rule "think" philosophers[i].mood = EATING ==> begin philosophers[i].mood := THINKING; end rule "get hungry" philosophers[i].mood = THINKING ==> begin philosophers[i].mood := HUNGRY; end rule "drop left fork" philosophers[i].mood = THINKING & philosophers[i].has_left_fork ==> begin assert forks[left_fork(i)]; forks[left_fork(i)] := false; philosophers[i].has_left_fork := false; end rule "drop right fork" philosophers[i].mood = THINKING & philosophers[i].has_right_fork ==> begin assert forks[right_fork(i)]; forks[right_fork(i)] := false; philosophers[i].has_right_fork := false; end end invariant "no fork sharing" -- for any philosopher... forall i: philosopher_id do -- ...either they do not have their right fork... !philosophers[i].has_right_fork -- ...or their neighbour does not have their left fork | !philosophers[(i + 1) % N_PHILOSOPHERS].has_left_fork end Now to see what Rumur can tell us about this system... Verifying the Model ------------------- We can take the model we have just written and ask Rumur to produce a program to verify it. That is, Rumur will generate a separate C program that captures the meaning of our model and will check its properties for us. Save the model as philosophers.m and then run the following. .. code-block:: sh rumur --output philosophers.c philosophers.m We now have a file, philosophers.c, that is our verifier. We can compile this with a C compiler. .. code-block:: sh # if you are using an x86-64 machine, also add -mcx16 cc -std=c11 -O3 -o philosophers philosophers.c -lpthread Now we can run the verifier to try and prove our model correct. .. code-block:: sh ./philosophers Memory usage: * The size of each state is 40 bits (rounded up to 5 bytes). * The size of the hash table is 32768 slots. Progress Report: The following is the error trace for the error: deadlock Startstate 1 fired. philosophers[0].mood:HUNGRY philosophers[0].has_left_fork:false philosophers[0].has_right_fork:false philosophers[1].mood:HUNGRY philosophers[1].has_left_fork:false philosophers[1].has_right_fork:false philosophers[2].mood:HUNGRY philosophers[2].has_left_fork:false philosophers[2].has_right_fork:false philosophers[3].mood:HUNGRY philosophers[3].has_left_fork:false philosophers[3].has_right_fork:false philosophers[4].mood:HUNGRY philosophers[4].has_left_fork:false philosophers[4].has_right_fork:false forks[0]:false forks[1]:false forks[2]:false forks[3]:false forks[4]:false ---------- Rule "take left fork", i: 0 fired. philosophers[0].has_left_fork:true forks[0]:true ---------- Rule "take left fork", i: 1 fired. philosophers[1].has_left_fork:true forks[1]:true ---------- Rule "take left fork", i: 2 fired. philosophers[2].has_left_fork:true forks[2]:true ---------- Rule "take left fork", i: 3 fired. philosophers[3].has_left_fork:true forks[3]:true ---------- Rule "take left fork", i: 4 fired. philosophers[4].has_left_fork:true forks[4]:true ---------- End of the error trace. ========================================================================== Status: 1 error(s) found. State Space Explored: 597 states, 1734 rules fired in 0s. Hm, it pretty clearly failed but why? And what does all of this output mean? We can see the failure cause at towards the beginning of the output, “deadlock.” The verifier found a sequence of transitions that would result in a state where none of the philosophers could make a move. The blocks of output following the error itself give a counterexample trace. This shows the exact path of rule transitions needed to reproduce the deadlocked state. By following this, you can see we have found a well known problem with this classic example, where each philosopher takes the fork to their left. After this, all philosophers are wanting the fork to their right but the fork to the right of each philosopher is already held by their neighbour. There are a couple of noteworthy points here. Rumur found a problem in our model quickly and without us having to guide it. It also found a problem that was not a violation of our invariant, but a problem it knew to check for anyway. By default, Rumur considers a deadlock of your model to be an error condition. Fixing the Model ---------------- So the design of our system is incorrect, and model checking helped us find the problem with it. How do we go about correcting it? Let’s take one of the known solutions to this problem and order the resources (forks) that are being acquired. Instead of letting a philosopher take any fork, we say they can only take the free fork with the lowest identifier of the two they need. .. code-block:: diff ruleset i: philosopher_id do rule "take left fork" philosophers[i].mood = HUNGRY -- wants to eat & !philosophers[i].has_left_fork -- doesn’t have the left fork + -- either already has the right fork or the left is lower numbered + & (philosophers[i].has_right_fork | left_fork(i) < right_fork(i)) & !forks[left_fork(i)] -- left fork is available ==> begin forks[left_fork(i)] := true; philosophers[i].has_left_fork := true; end rule "take right fork" philosophers[i].mood = HUNGRY & !philosophers[i].has_right_fork + & (philosophers[i].has_left_fork | left_fork(i) > right_fork(i)) & !forks[right_fork(i)] ==> begin forks[right_fork(i)] := true; philosophers[i].has_right_fork := true; end end We also need to apply this ordering on fork release, and only allow philosophers to release forks in descending order. .. code-block:: diff ruleset i: philosopher_id do rule "drop left fork" philosophers[i].mood = THINKING & philosophers[i].has_left_fork + -- either right is not held or the left is higher numbered + & (!philosophers[i].has_right_fork | left_fork(i) > right_fork(i)) ==> begin assert forks[left_fork(i)]; forks[left_fork(i)] := false; philosophers[i].has_left_fork := false; end rule "drop right fork" philosophers[i].mood = THINKING & philosophers[i].has_right_fork + & (!philosophers[i].has_left_fork | left_fork(i) < right_fork(i)) ==> begin assert forks[right_fork(i)]; forks[right_fork(i)] := false; philosophers[i].has_right_fork := false; end end Is this enough? Let’s ask Rumur. .. code-block:: sh rumur --output philosophers.c philosophers.m cc -std=c11 -O3 -o philosophers philosophers.c -lpthread ./philosophers Memory usage: * The size of each state is 40 bits (rounded up to 5 bytes). * The size of the hash table is 32768 slots. Progress Report: ========================================================================== Status: No error found. State Space Explored: 3216 states, 16160 rules fired in 0s. Hooray! Our model is deadlock free and our invariant is proven. From here, maybe we would like to increase the number of philosophers or make the dining arrangement more complicated. We could also introduce further invariants to check the model more thoroughly. But hopefully you have already seen enough to understand the value of model checking and have some ideas about how to write models of your own systems. Bonus - Liveness ---------------- At this point, we have proven that the philosophers do not get stuck and do not share forks, but we have not shown that each philosopher does get the chance to eat. In fact, we would like to go even further than that and prove that they are always able to eat.[1]_ To do this, we write a liveness property. .. [1] This might be phrased as that they are able to eat infinitely often, but this sounds a little peculiar. .. code-block:: murphi ruleset i: philosopher_id do liveness "can eventually eat" philosophers[i].mood = EATING end We need to rebuild and compile our model, then run it to see the result. .. code-block:: sh rumur --output philosophers.c philosophers.m cc -std=c11 -O3 -o philosophers philosophers.c -lpthread ./philosophers Memory usage: * The size of each state is 40 bits (rounded up to 5 bytes). * The size of the hash table is 32768 slots. Progress Report: ========================================================================== Status: No error found. State Space Explored: 3216 states, 16160 rules fired in 0s. And with that, we have proven that our philosophers will not starve but can all eat as much as they please. For reference, the full model we constructed is given below. .. code-block:: murphi const N_PHILOSOPHERS: 5 N_FORKS: N_PHILOSOPHERS type philosopher_id: 0 .. N_PHILOSOPHERS - 1 fork_id: 0 .. N_FORKS - 1 action: enum { THINKING, HUNGRY, EATING } philosopher: record mood: action has_left_fork: boolean has_right_fork: boolean end var philosophers: array[philosopher_id] of philosopher forks: array[fork_id] of boolean function left_fork(id: philosopher_id): fork_id; begin return id; end function right_fork(id: philosopher_id): fork_id; begin return (id + 1) % N_PHILOSOPHERS; end startstate begin -- all philosophers start dinner hungry and empty handed for i: philosopher_id do philosophers[i].mood := HUNGRY; philosophers[i].has_left_fork := false; philosophers[i].has_right_fork := false; end; -- so all forks are unheld for i: fork_id do forks[i] := false; end; end ruleset i: philosopher_id do rule "take left fork" philosophers[i].mood = HUNGRY -- wants to eat & !philosophers[i].has_left_fork -- doesn’t have the left fork -- either already has the right fork or the left is lower numbered & (philosophers[i].has_right_fork | left_fork(i) < right_fork(i)) & !forks[left_fork(i)] -- left fork is available ==> begin forks[left_fork(i)] := true; philosophers[i].has_left_fork := true; end rule "take right fork" philosophers[i].mood = HUNGRY & !philosophers[i].has_right_fork & (philosophers[i].has_left_fork | left_fork(i) > right_fork(i)) & !forks[right_fork(i)] ==> begin forks[right_fork(i)] := true; philosophers[i].has_right_fork := true; end rule "eat" philosophers[i].mood = HUNGRY -- wants to eat & philosophers[i].has_left_fork & philosophers[i].has_right_fork -- has both forks ==> begin philosophers[i].mood := EATING; end rule "think" philosophers[i].mood = EATING ==> begin philosophers[i].mood := THINKING; end rule "get hungry" philosophers[i].mood = THINKING ==> begin philosophers[i].mood := HUNGRY; end rule "drop left fork" philosophers[i].mood = THINKING & philosophers[i].has_left_fork -- either right is not held or the left is higher numbered & (!philosophers[i].has_right_fork | left_fork(i) > right_fork(i)) ==> begin assert forks[left_fork(i)]; forks[left_fork(i)] := false; philosophers[i].has_left_fork := false; end rule "drop right fork" philosophers[i].mood = THINKING & philosophers[i].has_right_fork & (!philosophers[i].has_left_fork | left_fork(i) < right_fork(i)) ==> begin assert forks[right_fork(i)]; forks[right_fork(i)] := false; philosophers[i].has_right_fork := false; end end invariant "no fork sharing" -- for any philosopher... forall i: philosopher_id do -- ...either they do not have their right fork... !philosophers[i].has_right_fork -- ...or their neighbour does not have their left fork | !philosophers[(i + 1) % N_PHILOSOPHERS].has_left_fork end ruleset i: philosopher_id do liveness "can eventually eat" philosophers[i].mood = EATING end Suggested to read next: `Murphi Idioms`_ .. _`Murphi Idioms`: murphi-idioms.rst rumur-2024.05.07/doc/introduction.rst000066400000000000000000000053111461637631000173140ustar00rootroot00000000000000An Introduction to Rumur ======================== Rumur is a tool for formal verification of complex systems. It falls into a family known as “model checkers.” This document gives a brief introduction to formal verification and model checking, aimed at helping Rumur users understand at a high level how the tool works. Formal Verification ------------------- When building a complex system, you would like to know that it is correct. “Correct” is subjective and something specific to a particular system, but a person building a system generally knows what they expect that system to do (and what they expect it *not* to do). The traditional way of gaining confidence in a piece of software is to write tests. But as Dijkstra famously said, “testing can be used to show the presence of bugs, but never to show their absence.” Formal verification approaches this problem from the other direction. Instead of writing tests to give your system specific inputs and relying on the cleverness of the test author to exercise all the system’s edge cases, formal verification covers *all possible inputs*. When first encountering this family of techniques they may seem like magic, and some of the more advanced formal verification approaches are indeed complex to understand. However, Rumur is a particular type of formal verification tool called a “model checker” whose operation is actually quite simple, as we shall see. Model Checking -------------- Suppose you built a system that took the result of the roll of a six-sided die as input. What tests would you write? One test for each of the inputs (1, 2, 3, 4, 5, 6) would cover all possible scenarios. This is what model checking does on a larger scale. It simply tries every possible path through your system. Of course, for most systems this is not possible. For example, suppose the die you were rolling had 2⁶⁴ sides. You cannot try this many values in a reasonable amount of time. Here the key is to *abstract* your system. Does this larger system behave sufficiently differently to the smaller one that takes a six-sided die? If not, maybe it is reasonable to verify the correctness of the smaller system as a stand-in for the larger one. While the type of abstraction just described needs to be done by hand, there are certain types of abstraction that can be done automatically. Some model checking tools can identify *symmetries* in your system; situations where two paths through a system are equivalent and only one need be explored. With real world systems, this can be a powerful optimisation that transforms infeasible verification problems into something achievable. Suggested to read next: `Introduction to Murphi`_ .. _`Introduction to Murphi`: intro-to-murphi.rst rumur-2024.05.07/doc/murphi-idioms.rst000066400000000000000000000424441461637631000173710ustar00rootroot00000000000000Murphi Idioms ============= This document works through modelling an example system, demonstrating some patterns commonly seen in Murphi models. Example: Multithreaded Counter ------------------------------ Lets take a simple concurrent software program and see how to prove some properties about it. Our example will use 10 threads to count to 10000: .. code-block:: c #include #include #include static int total; static void *run(void *ignored) { (void)ignored; for (size_t i = 0; i < 1000; ++i) ++total; return NULL; } int main(void) { // start 10 threads to count pthread_t threads[10]; for (size_t i = 0; i < sizeof(threads) / sizeof(threads[0]); ++i) { int r = pthread_create(&threads[i], NULL, run, NULL); assert(r == 0); } // wait for them to finish for (size_t i = 0; i < sizeof(threads) / sizeof(threads[0]); ++i) { void *ignored; int r = pthread_join(threads[i], &ignored); assert(r == 0); } // is the count the total we expect? assert(total == 10000); return 0; } What happens when we run this program? .. code-block:: bash $ gcc -Wall -Wextra -std=c99 -pthread counter.c -o counter -lpthread $ ./counter Everything looks good. But what happens if we run it a few more times? .. code-block:: bash $ ./counter $ ./counter $ ./counter counter: counter.c:33: main: Assertion `total == 10000' failed. [1] 2030135 IOT instruction (core dumped) ./counter Oh dear, we have a problem. Our program is non-deterministic and sometimes fails. If you are experienced with writing multithreaded C, you may already have spotted the problem. But you have likely also seen more complex systems where identifying bugs was not so easy. Moreover, not every program is as easy to write correctness assertions for as this one. Lets see if Rumur can find bugs for us. Global State and Thread-local State ----------------------------------- Concurrent systems typically have some state that is universally visible and other state that is per-agent. Mapping these concepts onto your implementation is usually straightforward. Programming languages generally have their own equivalent of these concepts. For our example, the global is part of global state while the threads’ loop counters are part of thread-local state: .. code-block:: murphi const INT_MIN: -2147483648; const INT_MAX: 2147483647; type int: INT_MIN..INT_MAX; var total: int; const THREADS: 10; type thread_id: 0..THREADS-1; type tls_t: record i: int; end; var tls: array[thread_id] of tls_t; Program Counters ---------------- We need some way of keeping track of where a thread’s execution is up to. Something similar to what line of ``run`` they are at, keeping in mind that it will not exactly be a line number because C statements are not atomic steps. That is, the ``run`` function is more precisely something like: .. code-block:: c for (size_t i = 0; i < 1000; ++i) { int tmp = total; total = tmp + 1; } In this transformation, we have left the thread-local variable ``i`` as written because it is not visible to other threads, but expanded out the operations on the global ``total``. (For the purposes of this example, we will ignore that reading and writing an ``int`` may itself not be atomic.) A consequence of this expansion is that we need to track the thread-local value we have upon reading ``total``: .. code-block:: diff record tls_t: record i: int; + tmp: int; end; Now lets introduce some labels so we have a way to name execution locations: .. code-block:: c for (size_t i = 0; i < 1000; ++i) { LOOP_START: int tmp = total; READ_TOTAL: total = tmp + 1; } DONE: Not all systems can be easily transformed like this within the source language; sometimes the concurrency you wish to model occurs beyond the level of expressibility of your source language. But this is still the mental elaboration to keep in mind. To replicate this in Murphi, we need to add a program counter to the thread-local state: .. code-block:: diff +type label_t: enum { LOOP_START, READ_TOTAL, DONE } + record tls_t: record + pc: label_t; i: int; tmp: int; end; Though we are discussing a software system, most concurrent hardware systems also have something analogous to the program counter for conceptually tracking thread-local progress. Statements as Steps ------------------- Translating the implementation to a series of rule steps requires thinking about the granularity of transitions. Rules execute atomically, so the points at which you wish to allow interleaving of thread steps align with the boundaries between one rule and the next. An outer ruleset captures that any thread can try to take a step: .. code-block:: murphi ruleset t: thread_id do rule "tmp = total" tls[t].pc = LOOP_START & tls[t].i < 1000 ==> begin tls[t].tmp := total; tls[t].pc := READ_TOTAL; end rule "total = tmp + 1" tls[t].pc = READ_TOTAL ==> begin total := tls[t].tmp + 1; tls[t].i := tls[t].i + 1; tls[t].pc := LOOP_START; end rule "exit loop" tls[t].pc = LOOP_START & tls[t].i >= 1000 ==> begin tls[t].pc := DONE; end end Note how each rule has, as part of its guard, a restriction on the program counter confining it to an exact location. And each rule has, as its final action, an update of the program counter that moves to another location. Initialisation/Reset -------------------- To know what state the system is in to begin with, we need to define a start state. In a software system this might be referred to as “initialisation” and in a hardware system it might be called “coming out of reset.” We can define a start state to capture static initialisation (the C variable ``total`` is set to 0 on startup) and the threads’ loop initialisation: .. code-block:: murphi startstate begin total := 0; for t: thread_id do tls[t].pc := LOOP_START; tls[t].i := 0; end; end Note that touching both global and thread-local state in the start state is typical. Specifying Correctness ---------------------- We have so far avoided the hardest part of formal modelling: what does “correct” mean for my system? This is hard because it is inherently system-specific. What it means for a system to be correct is dependent on its *purpose*. Ideally it should capture both what the system should do and what it should *not* do. For this example, we will take the property from the final assertion, ``total == 10000``: .. code-block:: murphi -- if we are done, the counter is at its expected value invariant forall t: thread_id do tls[t].pc = DONE end -> total = 10000; For more sophisticated systems, correctness properties can be arbitrarily complex and can themselves take significant work to develop. Abstraction ----------- We seem to have a full system definition. So what happens when we ask Rumur to check it? .. code-block:: bash $ rumur-run counter.m Generating the checker... Compiling the checker... Running the checker... Memory usage: * The size of each state is 713 bits (rounded up to 90 bytes). * The size of the hash table is 8192 slots. Progress Report: … It runs for a long time and eventually runs out of memory… So what is Rumur good for if it cannot find our bugs? Well, we asked it to do something unreasonable. Think about the interleaving of 10 threads across 1000 steps. This is a *lot* of possible executions to explore. This is where *abstraction* can help. Firstly, do we really need 1000 loop iterations? This is what the implementation does, but the uniformity of this program suggests we can find the bug with much fewer: .. code-block:: diff rule "tmp = total" tls[t].pc = LOOP_START - & tls[t].i < 1000 + & tls[t].i < 3 ==> begin tls[t].tmp := total; tls[t].pc := READ_TOTAL; end … rule "exit loop" tls[t].pc = LOOP_START - & tls[t].i >= 1000 + & tls[t].i >= 3 ==> begin tls[t].pc := DONE; end … -- if we are done, the counter is at its expected value invariant forall t: thread_id do tls[t].pc = DONE end -> - total = 10000; + total = 30; Next, do we really need 10 threads to find this bug? The vast majority of race conditions can be found with 3 or fewer threads (citation needed…): .. code-block:: diff -const THREADS: 10; +const THREADS: 3; type thread_id: 0..THREADS-1; … -- if we are done, the counter is at its expected value invariant forall t: thread_id do tls[t].pc = DONE end -> - total = 30; + total = 9; When we reattempt checking, Rumur can now find a bug (discussed below). This section walked through how to abstract a model into something checkable, but observe that it is somewhat of an art rather than a science. Knowing which numbers you can decrease without accidentally masking bugs is a skill that comes with experience. Interpreting a Counterexample ----------------------------- After abstraction, Rumur is able to find a bug: .. code-block:: bash $ rumur-run counter.m Generating the checker... Compiling the checker... Running the checker... Memory usage: * The size of each state is 237 bits (rounded up to 30 bytes). * The size of the hash table is 16384 slots. Progress Report: thread 5: 10000 states explored in 0s, with 2036 rules fired and 465 states in the queue. thread 6: 20000 states explored in 0s, with 4666 rules fired and 695 states in the queue. thread 2: 30000 states explored in 0s, with 7593 rules fired and 795 states in the queue. thread 7: 40000 states explored in 0s, with 10694 rules fired and 976 states in the queue. thread 2: 50000 states explored in 0s, with 13427 rules fired and 581 states in the queue. The following is the error trace for the error: invariant 1 failed Startstate 1 fired. total:0 tls[0].pc:LOOP_START tls[0].i:0 tls[0].tmp:Undefined tls[1].pc:LOOP_START tls[1].i:0 tls[1].tmp:Undefined tls[2].pc:LOOP_START tls[2].i:0 tls[2].tmp:Undefined ---------- Rule "tmp = total", t: 0 fired. tls[0].pc:READ_TOTAL tls[0].tmp:0 ---------- Rule "tmp = total", t: 2 fired. tls[2].pc:READ_TOTAL tls[2].tmp:0 ---------- Rule "total = tmp + 1", t: 0 fired. total:1 tls[0].pc:LOOP_START tls[0].i:1 ---------- Rule "tmp = total", t: 0 fired. tls[0].pc:READ_TOTAL tls[0].tmp:1 ---------- Rule "tmp = total", t: 1 fired. tls[1].pc:READ_TOTAL tls[1].tmp:1 ---------- Rule "total = tmp + 1", t: 0 fired. total:2 tls[0].pc:LOOP_START tls[0].i:2 ---------- Rule "tmp = total", t: 0 fired. tls[0].pc:READ_TOTAL tls[0].tmp:2 ---------- Rule "total = tmp + 1", t: 0 fired. total:3 tls[0].pc:LOOP_START tls[0].i:3 ---------- Rule "total = tmp + 1", t: 1 fired. total:2 tls[1].pc:LOOP_START tls[1].i:1 ---------- Rule "tmp = total", t: 1 fired. tls[1].pc:READ_TOTAL tls[1].tmp:2 ---------- Rule "total = tmp + 1", t: 1 fired. total:3 tls[1].pc:LOOP_START tls[1].i:2 ---------- Rule "tmp = total", t: 1 fired. tls[1].pc:READ_TOTAL tls[1].tmp:3 ---------- Rule "total = tmp + 1", t: 1 fired. total:4 tls[1].pc:LOOP_START tls[1].i:3 ---------- Rule "total = tmp + 1", t: 2 fired. total:1 tls[2].pc:LOOP_START tls[2].i:1 ---------- Rule "tmp = total", t: 2 fired. tls[2].pc:READ_TOTAL tls[2].tmp:1 ---------- Rule "total = tmp + 1", t: 2 fired. total:2 tls[2].pc:LOOP_START tls[2].i:2 ---------- Rule "tmp = total", t: 2 fired. tls[2].pc:READ_TOTAL tls[2].tmp:2 ---------- Rule "total = tmp + 1", t: 2 fired. total:3 tls[2].pc:LOOP_START tls[2].i:3 ---------- Rule "exit loop", t: 0 fired. tls[0].pc:DONE ---------- Rule "exit loop", t: 1 fired. tls[1].pc:DONE ---------- Rule "exit loop", t: 2 fired. tls[2].pc:DONE ---------- End of the error trace. ========================================================================== Status: 1 error(s) found. State Space Explored: 52816 states, 117240 rules fired in 0s. The trace may look long and daunting, but that is only because each step is small and Rumur is giving us precise details on how to reproduce the bug. Converting this back into implementation steps, it can more concisely be expressed as: .. code-block:: thread 0 thread 1 thread 2 total -------------------------------------------------------- tmp = total 0 tmp = total 0 total = tmp + 1 1 tmp = total 1 tmp = total 1 total = tmp + 1 2 tmp = total 2 total = tmp + 1 3 total = tmp + 1 2 tmp = total 2 total = tmp + 1 3 tmp = total 3 total = tmp + 1 4 total = tmp + 1 1 tmp = total 1 total = tmp + 1 2 tmp = total 2 total = tmp + 1 3 exit loop 3 exit loop 3 exit loop Translating a counterexample trace into an intuition of what occurred is a skill in itself and, like abstraction, comes with experience. So our program can run to completion with a final ``total`` lower than what was expected. How to fix the program to behave correctly is left as an exercise to the reader. Creating a concurrent system that avoids all possible bugs is not something Rumur solves for you: it can find your bugs or prove their absence, but actually designing the system is your job. Further Optimisations --------------------- Something you may have noticed is that all 3 threads were not necessary to reproduce this bug. We could have omitted thread 1 (while also reducing the expected ``total`` to 6) and Rumur would have still found the bug. It does not make a significant difference in this case, but in larger systems the difference between 2 and 3 threads can mean the difference between a checkable system and exhausting resources. Furthermore a counterexample trace involving fewer threads is generally easier to understand. If your system naturally has threads like this example, a good practice is to run your model with 2 until you cannot find any more bugs. Then increase to 3 and run like that until you cannot find any more bugs. Continue this until you are resource constrained or gain enough confidence in your design. To reduce the amount of memory required for state space exploration, *symmetry reduction* can be used. This is an optimisation that teaches Rumur that parts of your system are semantically equivalent (e.g. thread 0 and thread 1 are interchangeable). For further information about this, look into the ``scalarset`` Murphi keyword. Another advanced technique is to eagerly undefine variables. In our example, the value of ``tls[t].tmp`` is irrelevant unless the thread is at the ``READ_TOTAL`` label. But it will contribute unnecessary interleavings in state space exploration, increasing memory requirements. These can be minimised by explicitly discarding a value you know will not be read again: .. code-block:: diff rule "total = tmp + 1" tls[t].pc = READ_TOTAL ==> begin total := tls[t].tmp + 1; tls[t].i := tls[t].i + 1; + undefine tls[t].tmp; tls[t].pc := LOOP_START; end When combined with symmetry reduction, this can lead to significant memory savings. This can also help catch bugs in your model itself. Reading an undefined variable triggers an error, so undefining a variable allows Rumur to detect later unintended reads of it. Final Model ----------- The full model we constructed above is included here for reference: .. code-block:: murphi const INT_MIN: -2147483648; const INT_MAX: 2147483647; type int: INT_MIN..INT_MAX; var total: int; const THREADS: 3; type thread_id: 0..THREADS-1; type label_t: enum { LOOP_START, READ_TOTAL, DONE } type tls_t: record pc: label_t; i: int; tmp: int; end; var tls: array[thread_id] of tls_t; ruleset t: thread_id do rule "tmp = total" tls[t].pc = LOOP_START & tls[t].i < 3 ==> begin tls[t].tmp := total; tls[t].pc := READ_TOTAL; end rule "total = tmp + 1" tls[t].pc = READ_TOTAL ==> begin total := tls[t].tmp + 1; tls[t].i := tls[t].i + 1; tls[t].pc := LOOP_START; end rule "exit loop" tls[t].pc = LOOP_START & tls[t].i >= 3 ==> begin tls[t].pc := DONE; end; end startstate begin total := 0; for t: thread_id do tls[t].pc := LOOP_START; tls[t].i := 0; end; end -- if we are done, the counter is at its expected value invariant forall t: thread_id do tls[t].pc = DONE end -> total = 9; rumur-2024.05.07/doc/performance.rst000066400000000000000000000011541461637631000170750ustar00rootroot00000000000000Performance Summary =================== Rumur comfortably outperforms all other Murphi-based model checkers (as far as I am aware). There is no single magic bullet by which it achieves this, but rather a combination of the following techniques: 1. Multicore parallelism 2. Specialised data structures: a. Lock-free, insert-only seen state set b. Lock-free per-thread pending state queues 3. Bump-pointer, non-freeing state allocation To learn more about any of these, read the source of ../rumur/resources/header.c. The above list should give you a good intuition of what to expect to find in the source code. rumur-2024.05.07/doc/properties.rst000066400000000000000000000167001461637631000167730ustar00rootroot00000000000000Properties ========== When creating a model of a system, an important part to include is its definition of correctness. Rumur gives you four different types of global properties you can write: invariants, covers, assumptions, and liveness. Each of these except liveness can also be used locally (within a function, procedure, or rule). However, the local equivalent of an invariant is known as an assertion. Invariants ---------- An invariant is something you claim is always true following the execution of a rule. That is, a global correctness criterion for your system. You state an invariant with the ``invariant`` keyword. For example, you might model a counter that always has an even value: .. code-block:: murphi var counter: 0 .. 10; startstate begin counter := 0; end; rule "increment" counter < 10 ==> begin counter := counter + 2; end; rule "decrement" counter > 0 ==> begin counter := counter - 2; end; invariant "counter is always even" counter % 2 = 0; When you generate a verifier for this model and then run it, the verifier will check each state satisfies the condition ``counter % 2 = 0``. You can observe this by changing the property to something that will fail, like ``counter % 4 = 0``, and re-checking the model. Invariants can appear within rulesets, so you can write a parameterised invariant: .. code-block:: murphi ruleset x: 0 .. 10 do invariant "x is small" x < 100; end; This example is equivalent to: .. code-block:: murphi invariant "x is small" forall x: 0 .. 10 do x < 100 end; but sometimes it is more natural to use the ruleset style of writing. The text given to describe an invariant is optional, but it is good practice to include a description of the property to make invariant failure messages clearer. The keyword ``invariant`` and the keyword ``assert`` (see below) are treated as synonyms by Rumur, but it is good practice to use ``invariant`` for top level properties to better indicate your intentions to readers. Assertions ---------- Assertions are similar to invariants, but are only expected to hold locally at the point at which they are executed. You may be familiar with them from conventional programming languages. Re-using the counter example from above, you could add an assertion to the "increment" rule: .. code-block:: murphi rule "increment" counter < 10 ==> begin counter := counter + 2; assert "counter is non-zero" counter != 0; end; When the "increment" rule is run, this condition will be checked after incrementing ``counter``. Like invariants, the message is optional but encouraged. As mentioned above, ``invariant`` and ``assert`` are synonyms but it is recommended to use ``assert`` for local properties to aid readability. Covers ------ Similar to invariants and assertions, covers express a property you expect to hold but not necessarily every time it is checked. A cover is typically used to describe a reachability condition of your system. The generated checker counts how many times a cover property was found to be true and considers it an error if there is a cover that was never found to be true. Using our previous counter model, you could write a global cover property to ensure the counter is ``8`` at some point during execution: .. code-block:: murphi cover "counter seen as 8" counter = 8; Covers can be either global (like the example above) or local. You can use a local cover to check path coverage within a rule. For example, .. code-block:: murphi rule "increment" counter < 10 ==> begin if counter = 2 then cover "if branch covered" true; counter := counter + 4; else cover "else branch covered" true; counter := counter + 2; end; end; If you want to claim something is always true but also count the number of times you check the property, you can combine an assertion and a cover: .. code-block:: murphi rule "increment" counter < 10 ==> begin counter := counter + 2; assert "counter is even" counter % 2 = 0; cover "counter is even" counter % 2 = 0; end; Assumptions ----------- The properties discussed thus far allow you to write conditions you want to check hold in your system. Assumptions are a way to describe conditions you do not want to check but wish to assume of the environment your system operates in. For example, the "increment" rule could be written without a guard, instead using an assumption to avoid thinking about overflow: .. code-block:: murphi rule "increment" begin assume "counter within bounds" counter < 10; counter := counter + 2; end; This causes any transition that encounters a value of ``counter`` not less than ``10`` to be considered invalid. Like the other properties, assumptions can be either global or local, so we could write a global assumption that prevents the counter ever reaching ``10``: .. code-block:: murphi assume "clamp counter" counter < 8; For the reader who is curious about how this is implemented, the generated verifier discards any state that violates an assumption. That is, an assumption failure (either local within a rule or global after a rule transition) causes the invariant and cover checks to be skipped and the resulting (invalid) state to be ignored. Liveness -------- Liveness properties express something that should be reachable from every state. That is, a property that may not be true in a given state but will be true in at least one of the successors of every state. Using the counter example, we might write, .. code-block:: murphi liveness "counter can always become 4" counter = 4; to capture that we should always be able to reach the state where ``counter`` is ``4``. Unlike the other property types, liveness can only be used globally and not locally as a statement. Sometimes you might want to write a conditional liveness property. For example, that a variable ``x`` can always become ``1`` but only once you are out of some initial setup phase and into the steady state of your system. You can do this by also referencing a variable that captures the phase of your system in the liveness property. For example, .. code-block:: murphi type phase_t: enum { SETUP, RUN }; var phase: phase_t; halt: boolean; x: 0 .. 10; startstate begin phase := SETUP; halt := false; end; ruleset has_err: boolean do rule "init" !halt & phase = SETUP ==> begin if has_err then halt := true; else x := 0; phase := RUN; end; end; end; rule phase = RUN & x < 10 ==> begin x := x + 1; end; rule phase = RUN & x > 0 ==> begin x := x - 1; end; liveness "x can be 1" phase = SETUP | x = 1; For large models, note that Rumur's algorithm for checking liveness properties is not as efficient as other types of properties. You may find that checking a liveness properties on a large state space requires a long time. Relationship to Linear Temporal Logic ------------------------------------- Readers familiar with Linear Temporal Logic (LTL) might notice a similarity in the properties supported by Rumur and the expressibility of LTL. Rumur's available properties are more constrained than LTL. To be pragmatic, they are limited to some that can be checked efficiently within an explicit state model checking algorithm. Invariants roughly correspond to LTL's "always" operator, ``G`` or ``□``. Liveness roughly corresponds to "always eventually", ``G F`` or ``□ ◊``. The other types of properties do not have direct LTL equivalents. rumur-2024.05.07/doc/release-checklist.rst000066400000000000000000000105301461637631000201610ustar00rootroot00000000000000Release Checklist ================= The process of cutting a new Rumur release should follow these steps: 1. Grep the source tree for any deprecated functions or data (marked with ``__attribute__((deprecated))`` or a “deprecated” comment). Any that were deprecated more than six months ago can, and should, be removed for the upcoming release. 2. Check the `Debian buildd results`_ for the last uploaded version of Rumur. If there were any failures, consider introducing commits to try to address them prior to release. The buildd tests are only run each time there is a new Debian package uploaded, so the turn around time on seeing a failure here and having an opportunity to fix it can be long. 3. Update ../CHANGELOG.rst with information about the changes in the new release. Changes should be separated into “user-facing” and “internal,” providing commit hash(es) as a reference where possible. The ordering in which changes are listed should firstly prioritise bug fixes (which should be explicitly marked as such) and then by the impact on users/developers a change will have. Code changes that are only of relevance to people hacking on Rumur can be omitted, and this audience can read about them in the Git log. 4. Commit this to main. 5. Push this to `upstream on Github`_. 6. Wait for the `Cirrus CI regression tests`_ to pass. If one of these tests fail, you may need to look at the raw log because the summary output hides some stderr lines. 7. Tag the commit with the version number in “vYYYY.MM.DD” format. 8. Push the new version tag upstream. 9. Package Rumur for Debian (see below). Github’s automated release process should notice the version tag and show the new release as a downloadable zip/tarball on the “releases” tab of https://github.com/Smattr/rumur. Packaging for Debian -------------------- Rumur is currently `packaged in Debian`_. To update the Debian Rumur package follow these steps. 1. Switch to the branch packaging/debian. 2. Merge from main. 3. Update the Debian changelog (../debian/changelog). Debian provide guidance on the `changelog format`_. 4. Commit these changes. 5. Push this upstream to packaging/debian. Then on a `Debian Unstable installation`_: .. code-block:: sh # 6. Upgrade all packages. sudo apt update sudo apt upgrade # 7. Clone the packaging/debian branch. git clone https://github.com/Smattr/rumur -b packaging/debian cd rumur # 8. Run packaging script. ./misc/package-for-debian.sh # If any lintian errors or warnings were output, you will need to address # these and then return to step 4. # 9. Update pbuilder environment. You will need to already have a pbuilder # environment created, as described in misc/package-for-debian.sh. sudo pbuilder update # 10. Test the package under pbuilder pdebuild # 11. Sign the source package that pbuilder created. You will need GPG key(s) # configured. debsign ../rumur_*_source.changes # 12. Upload to the mentors inbox. You will need dput configured, as described # in misc/package-for-debian.sh. dput mentors ../rumur_*_source.changes In a few minutes you should get a confirmation email that the upload succeeded. Then: 13. Follow the instructions included in the confirmation email to send a `Request For Sponsorship`_ to the Debian Mentors mailing list. Hope that you get a reply from an interested party. 14. When/if the package is accepted into Debian unstable, tag the commit used to produce it with the Debian version number in “debian/vYYYY.MM.DD-1” format. Push this upstream. Note that there could still be problems with the package that a sponsor may request you fix. For example, there is currently no easy way to smoke test the autopkgtests and these could fail, preventing migration of the package to the main repositories. .. _`changelog format`: https://www.debian.org/doc/manuals/maint-guide/dreq.en.html#changelog .. _`Cirrus CI regression tests`: https://cirrus-ci.com/github/Smattr/rumur .. _`Debian buildd results`: https://buildd.debian.org/status/package.php?p=rumur .. _`Debian Unstable installation`: https://wiki.debian.org/DebianUnstable#Installation .. _`packaged in Debian`: https://packages.debian.org/bullseye/rumur .. _`Request For Sponsorship`: https://mentors.debian.net/sponsors/rfs-howto .. _`upstream on Github`: https://github.com/Smattr/rumur rumur-2024.05.07/doc/toy-model-checker.py000066400000000000000000000065401461637631000177330ustar00rootroot00000000000000#!/usr/bin/env python3 """ The following is a simplified model checker for a specific problem. The aim is to help readers understand how a model checker like Rumur works. It is written in Python for readability, however the Rumur implementation is generated C code optimised for both memory usage and execution speed. If you have read and understood the following file, you will be in a position to start reading and understanding ../rumur/resources/header.c. """ import sys from typing import Optional, Set class State(object): def __init__(self, value: int = 0, previous: Optional["State"] = None): # the state of our model; a single integer self.value = value # a pointer to the previous state, for the purpose of generating counter # example traces self.previous = previous def duplicate(self) -> "State": """create a new State that will be a successor of this one""" return State(self.value, self) # make objects of this type storable in a set def __hash__(self) -> int: return self.value # an invariant we will claim, that the state never exceeds 10 def invariant(s: State) -> bool: return s.value <= 10 # two rules and their accompanying guards def inc2_guard(s: State) -> bool: return s.value < 20 def inc2_rule(s: State) -> None: s.value += 2 def dec1_guard(s: State) -> bool: return s.value > 0 def dec1_rule(s: State) -> None: s.value -= 1 # a start state that initially sets our value to 0 def start(s: State) -> None: s.value = 0 def print_cex(s: State) -> None: """print a counter example trace ending at the given state""" if s.previous is not None: print_cex(s.previous) # Print the value of this state. In a real world model checker like Rumur, you # would typically also print the transition rule that connected this state to # the previous to aid debugging. print(f" value == {s.value}") def main() -> int: # A queue of states to be expanded and explored. Checking is done when this # queue is exhausted. pending: [State] = [] # A set of states we have already encountered. We use this to deduplicate # paths during exploration and avoid repeatedly checking the same states. seen: Set[State] = set() # we start with just our starting state in the queue s = State() start(s) pending.append(s) while len(pending) > 0: # take the next state off the queue to be expanded n = pending.pop(0) # try each rule on it to generate new states for guard, rule in ((inc2_guard, inc2_rule), (dec1_guard, dec1_rule)): if guard(n): e = n.duplicate() rule(e) # Here is where symmetry reduction steps would usually take place in a # real model checker. These are used to further deduplicate states and # reduce the state space. For the purposes of this example, we omit such # optimisations. # Have we seen this new state before? If so, we can discard it as a # duplicate. if e in seen: continue # Does the state violate our invariant? if not invariant(e): print("counter example trace:") print_cex(e) return -1 # note that we have seen this state for future checks seen.add(e) # add it to our queue and proceed pending.append(e) print("checking complete") return 0 if __name__ == "__main__": sys.exit(main()) rumur-2024.05.07/doc/vs-cmurphi.rst000066400000000000000000000125461461637631000167000ustar00rootroot00000000000000Rumur vs CMurphi ================ Because Rumur is attempting to appeal to CMurphi users, it is helpful to compare the feature set of the two. In this document we give a brief run down of the relevant differences. Performance Characteristics --------------------------- CMurphi produces a single-threaded verifier. If you have a multicore system, the verifier will only take advantage of a single core. Rumur produces a multi-threaded verifier by default, which will use as many cores as you have available. You can tune how many threads are used with the ``--threads`` command line option when generating the verifier. CMurphi seems intended to run on Linux and older Unix-style platforms. Rumur should run on any POSIX operating system. However, the verifier’s code does make use of C extensions that may only be supported by GCC and Clang. Syntax Differences ------------------ CMurphi considers the operator ``==`` an error, while Rumur treats it as an alias of ``=`` that is usable in comparisons. This is mainly to ease the life of C/C++ programmers. Rumur supports various single character operator alternatives, e.g. ``≤``. These are nicer for presentation of a model in a mathematical context. These more concise operators are generally recommended for use in models that are expected to be read more often than they are edited. The keywords ``assert`` and ``invariant`` are distinct in CMurphi, whereas they are considered synonyms by Rumur. If one of these appears within a rule or function it is considered an assertion and if it appears at the top level it is considered an invariant. Rumur also accepts the optional string message of an assertion or invariant on either side of the asserted expression. Type System ----------- CMurphi supports real arithmetic using the ``real`` data type. Rumur does not support this type and there are no plans to implement this or any floating point support. Similarly, Rumur does not support the ``union`` type and there are no plans to add it. Assumptions ----------- In addition to assertions and invariants that are supported by CMurphi, Rumur supports assumptions with the keyword ``assume``. Any state that fails an assumption is considered irrelevant and discarded. You can read more about this in properties.rst_. .. _properties.rst: properties.rst Liveness -------- Another addition to the properties supported by CMurphi, is the Rumur-specific ``liveness``. From every state that is reached during checking, there must be a state reachable from it that satisfies the liveness property. You can read more about this in properties.rst_. Comparisons ----------- CMurphi has a limitation that ``=`` and ``!=`` can only be used to compare simple values (enums, ranges or scalarsets). Rumur lets you compare any compatible values; that is, it also supports records and arrays. The semantics are equivalent to writing out a long form comparison of every one of the complex values’ members. Operators --------- The operators ``&`` and ``|`` are used for logical AND and OR, respectively. Rumur allows ``&&`` and ``||`` as synonyms for these. Rumur also supports a set of bitwise arithmetic operators, documented in bitwise-operators.rst_. .. _bitwise-operators.rst: ./bitwise-operators.rst Command-line Options -------------------- CMurphi has a set of command-line options, and its generated verifier has another set of command-line options. Rumur has some similar command-line options, but its generated verifier has none at all. The intent with this design is to expose as much information to the C compiler as possible. In some cases this can make a significant difference by allowing your C compiler to more aggressively optimise during compilation. Deadlock -------- The verifier can detect “deadlocks” in your state graph, where there are no transitions that make progress. CMurphi considers a deadlock to have occurred in any state that only has enabled transitions that lead back to itself. Rumur has two deadlock modes that can be selected with the ``--deadlock-detection`` command line option: “stuttering” and “stuck”. Stuttering, the default, matches CMurphi’s definition. Stuck uses a weaker definition that considers a deadlock to have occurred only when a state has *no* enabled transitions. Loops ----- CMurphi requires the step in a quantifier expression to be a generation-time constant. I.e. in the code: .. code-block:: murphi for i := l to u by s do ... end; ``s`` must have a known constant value when the verifier is being generated. Rumur has no such restrictions. However steps that are ``0`` at generation-time are rejected and steps that turn out to be ``0`` or incrementing in the wrong direction at runtime will be raised as a runtime error by the verifier. CMurphi considers an infinite loop to be a runtime error that the verifier should be capable of detecting and notifying the user about. It has an iteration bound (by default 1000), after which it will consider a loop to be non-terminating and will raise an error. Rumur trusts the user not to write an infinite ``while`` loop. If you write an infinite loop, your verifier will run forever. You have been warned. Colour Output ------------- Rumur’s generated verifier attempts to imitate CMurphi’s verifier’s output to smooth the transition for users, but by default Rumur colourises its output using ANSI terminal sequences. This behaviour is controllable via command-line options. rumur-2024.05.07/librumur/000077500000000000000000000000001461637631000151355ustar00rootroot00000000000000rumur-2024.05.07/librumur/CMakeLists.txt000066400000000000000000000067661461637631000177140ustar00rootroot00000000000000find_package(BISON REQUIRED) find_package(FLEX REQUIRED) find_path(GMPXX_INCLUDE NAMES gmpxx.h PATHS ENV CPLUS_INCLUDE_PATH) if(NOT GMPXX_INCLUDE) message(FATAL_ERROR "GMP headers not found") endif() find_library(GMP_LIBRARIES NAMES gmp PATHS ENV LIBRARY_PATH) if(NOT GMP_LIBRARIES) message(FATAL_ERROR "libgmp not found") endif() find_library(GMPXX_LIBRARIES NAMES gmpxx PATHS ENV LIBRARY_PATH) if(NOT GMPXX_LIBRARIES) message(FATAL_ERROR "libgmpxx not found") endif() bison_target(parser src/parser.yy ${CMAKE_CURRENT_BINARY_DIR}/parser.yy.cc COMPILE_FLAGS "--no-lines --warnings=all") flex_target(lexer src/lexer.l ${CMAKE_CURRENT_BINARY_DIR}/lexer.l.cc COMPILE_FLAGS "--noline") add_flex_bison_dependency(lexer parser) # Suppress warnings in generated code. include(CheckCXXCompilerFlag) set_source_files_properties(lexer.l.cc PROPERTIES COMPILE_FLAGS "") CHECK_CXX_COMPILER_FLAG(-Wsign-compare HAS_WARNING_SIGN_COMPARE) if(HAS_WARNING_SIGN_COMPARE) set_source_files_properties(lexer.l.cc PROPERTIES COMPILE_FLAGS "${LEXER_COMPILE_FLAGS} -Wno-sign-compare") endif() get_source_file_property(LEXER_COMPILE_FLAGS lexer.l.cc COMPILE_FLAGS) CHECK_CXX_COMPILER_FLAG(-Wregister HAS_WARNING_REGISTER) if(HAS_WARNING_REGISTER) set_source_files_properties(lexer.l.cc PROPERTIES COMPILE_FLAGS "${LEXER_COMPILE_FLAGS} -Wno-register") endif() get_source_file_property(LEXER_COMPILE_FLAGS lexer.l.cc COMPILE_FLAGS) CHECK_CXX_COMPILER_FLAG(-Wdeprecated-register HAS_WARNING_DEPRECATED_REGISTER) if(HAS_WARNING_DEPRECATED_REGISTER) set_source_files_properties(lexer.l.cc PROPERTIES COMPILE_FLAGS "${LEXER_COMPILE_FLAGS} -Wno-deprecated-register") endif() add_custom_command( OUTPUT rumur-get-version.h COMMAND src/make-version.py ${CMAKE_CURRENT_BINARY_DIR}/rumur-get-version.h MAIN_DEPENDENCY src/make-version.py DEPENDS always_run WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) # Dummy output to make sure we always re-evaluate the version step above. add_custom_command( OUTPUT always_run COMMAND /usr/bin/env true) add_library(librumur ${CMAKE_CURRENT_BINARY_DIR}/rumur-get-version.h src/Boolean.cc src/Comment.cc src/Decl.cc src/except.cc src/Expr.cc src/Function.cc src/indexer.cc src/Model.cc src/Node.cc src/Number.cc src/parse.cc src/Property.cc src/resolve-symbols.cc src/Rule.cc src/sanitise_rule_names.cc src/Stmt.cc src/traverse.cc src/TypeExpr.cc src/validate.cc ${FLEX_lexer_OUTPUTS} ${BISON_parser_OUTPUTS}) target_include_directories(librumur PUBLIC $ $ PRIVATE src ${CMAKE_CURRENT_BINARY_DIR}) target_include_directories(librumur SYSTEM PUBLIC ${GMPXX_INCLUDE} ) target_link_libraries(librumur PUBLIC ${GMPXX_LIBRARIES} ${GMP_LIBRARIES}) # Force the output to librumur.a instead of liblibrumur.a. set_target_properties(librumur PROPERTIES PREFIX "") # Clagged boiler plate to install a library. install(TARGETS librumur EXPORT LibrumurConfig ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/location.hh ${CMAKE_CURRENT_BINARY_DIR}/parser.yy.hh ${CMAKE_CURRENT_BINARY_DIR}/position.hh ${CMAKE_CURRENT_BINARY_DIR}/rumur-get-version.h ${CMAKE_CURRENT_BINARY_DIR}/stack.hh DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/rumur) export(TARGETS librumur FILE LibrumurConfig.cmake) rumur-2024.05.07/librumur/include/000077500000000000000000000000001461637631000165605ustar00rootroot00000000000000rumur-2024.05.07/librumur/include/rumur/000077500000000000000000000000001461637631000177325ustar00rootroot00000000000000rumur-2024.05.07/librumur/include/rumur/Boolean.h000066400000000000000000000014261461637631000214650ustar00rootroot00000000000000#pragma once #include #include #include #include #include #ifndef RUMUR_API #define RUMUR_API __attribute__((visibility("default"))) #endif namespace rumur { /// the built in boolean type that is implicitly declared in all Murphi models extern RUMUR_API const Ptr Boolean; /// literals for Murphi “False” and “True” /// /// These are included for convenience, so you can assign to expressions to /// these constants if relevant. Do not compare against these expressions. If /// you want to ask “is this expression the literal true/false?” use /// `Expr::is_literal_true` / `Expr::is_literal_false`. extern RUMUR_API const Ptr False; extern RUMUR_API const Ptr True; } // namespace rumur rumur-2024.05.07/librumur/include/rumur/Comment.h000066400000000000000000000015461461637631000215130ustar00rootroot00000000000000#pragma once #include "location.hh" #include #include #include #include #ifndef RUMUR_API #define RUMUR_API __attribute__((visibility("default"))) #endif namespace rumur { /// a Murphi source comment /// /// Note that this is not an AST node (does not inherit from Node) because /// comments do not fit into a strictly hierarchical AST. They can appear /// anywhere that is syntactically valid. struct RUMUR_API Comment { /// text of the comment std::string content; /// is this a /* ... */ comment, as opposed to a -- ... comment? bool multiline; /// position within source file location loc; }; /// parse source code comments from a Murphi file /// /// \param input Stream to read source from /// \return List of parsed comments std::vector parse_comments(std::istream &input); } // namespace rumur rumur-2024.05.07/librumur/include/rumur/Decl.h000066400000000000000000000077671461637631000207730ustar00rootroot00000000000000#pragma once #include "location.hh" #include #include #include #include #include #include #include #include #include #include #ifndef RUMUR_API_WITH_RTTI #define RUMUR_API_WITH_RTTI __attribute__((visibility("default"))) #endif namespace rumur { struct RUMUR_API_WITH_RTTI Decl : public Node { std::string name; Decl(const std::string &name_, const location &loc_); virtual ~Decl() = 0; Decl *clone() const override = 0; protected: Decl(const Decl &) = default; Decl &operator=(const Decl &) = default; }; struct RUMUR_API_WITH_RTTI ExprDecl : public Decl { ExprDecl(const std::string &name_, const location &loc_); virtual ~ExprDecl() = default; // Return true if this declaration is usable as an lvalue virtual bool is_lvalue() const = 0; /* Return true if this declaration is to a resource that cannot be modified. * Note that this is only relevant for declarations for which is_lvalue() * returns true. For non-lvalue declarations, this is always true. */ virtual bool is_readonly() const = 0; virtual Ptr get_type() const = 0; ExprDecl *clone() const override = 0; protected: ExprDecl(const ExprDecl &) = default; ExprDecl &operator=(const ExprDecl &) = default; }; struct RUMUR_API_WITH_RTTI AliasDecl : public ExprDecl { Ptr value; AliasDecl(const std::string &name_, const Ptr &value_, const location &loc_); AliasDecl *clone() const override; virtual ~AliasDecl() = default; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; bool is_lvalue() const override; bool is_readonly() const override; Ptr get_type() const override; }; struct RUMUR_API_WITH_RTTI ConstDecl : public ExprDecl { Ptr value; /* The type of this constant. Typically this will be NULL (untyped), but in * the case of enum members it will have the enum declaration as its type. */ Ptr type; ConstDecl(const std::string &name_, const Ptr &value_, const location &loc_); ConstDecl(const std::string &name_, const Ptr &value_, const Ptr &type_, const location &loc_); ConstDecl *clone() const override; virtual ~ConstDecl() = default; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; bool is_lvalue() const override; bool is_readonly() const override; void validate() const override; Ptr get_type() const override; }; struct RUMUR_API_WITH_RTTI TypeDecl : public Decl { Ptr value; TypeDecl(const std::string &name, const Ptr &value_, const location &loc); TypeDecl *clone() const override; virtual ~TypeDecl() = default; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; }; struct RUMUR_API_WITH_RTTI VarDecl : public ExprDecl { Ptr type; /* Offset within the model state. This is only relevant if this is a state * variable. We initially set it to an invalid value and rely on * Model::reindex setting this correctly later. */ mpz_class offset = -1; /* Whether this variable is a read-only reference. E.g. a non-var parameter to * a function or procedure. */ bool readonly = false; VarDecl(const std::string &name_, const Ptr &type_, const location &loc_); VarDecl *clone() const override; virtual ~VarDecl() = default; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; mpz_class count() const; mpz_class width() const; /* Whether this variable declaration is a global; part of the state. That is, * not a local declaration, function parameter, etc. */ bool is_in_state() const; bool is_lvalue() const override; bool is_readonly() const override; Ptr get_type() const override; }; } // namespace rumur rumur-2024.05.07/librumur/include/rumur/Expr.h000066400000000000000000000513471461637631000210330ustar00rootroot00000000000000#pragma once #include "location.hh" #include #include #include #include #include #include #include #include #include #ifndef RUMUR_API_WITH_RTTI #define RUMUR_API_WITH_RTTI __attribute__((visibility("default"))) #endif namespace rumur { // Forward declarations to avoid a circular #include struct ExprDecl; struct Function; struct TypeExpr; struct VarDecl; struct RUMUR_API_WITH_RTTI Expr : public Node { Expr(const location &loc_); virtual ~Expr() = default; virtual Expr *clone() const = 0; /// is this expression a compile-time constant? virtual bool constant() const = 0; /// The type of this expression. Never returns `nullptr`. virtual Ptr type() const = 0; /// is this expression of boolean type? bool is_boolean() const; /// Evaluate this expression. This will throw `Error` if `constant()` is not /// true. virtual mpz_class constant_fold() const = 0; /// is this value valid to use on the LHS of an assignment? virtual bool is_lvalue() const; /// Is this value a constant (cannot be modified)? It only makes sense to ask /// this of expressions for which is_lvalue() returns true. For non-lvalues, /// this is always true. virtual bool is_readonly() const; /// get a string representation of this expression virtual std::string to_string() const = 0; /// is this expression the boolean literal “true”? virtual bool is_literal_true() const; /// is this expression the boolean literal “false”? virtual bool is_literal_false() const; /// is this expression side-effect free? virtual bool is_pure() const = 0; protected: Expr(const Expr &) = default; Expr &operator=(const Expr &) = default; }; struct RUMUR_API_WITH_RTTI Ternary : public Expr { Ptr cond; Ptr lhs; Ptr rhs; Ternary(const Ptr &cond_, const Ptr &lhs_, const Ptr &rhs_, const location &loc_); virtual ~Ternary() = default; Ternary *clone() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; bool constant() const override; Ptr type() const override; mpz_class constant_fold() const override; void validate() const override; std::string to_string() const override; bool is_pure() const override; /* Note we do not override is_lvalue. Unlike in C, ternary expressions are not * considered lvalues. */ }; struct RUMUR_API_WITH_RTTI BinaryExpr : public Expr { Ptr lhs; Ptr rhs; BinaryExpr(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); virtual ~BinaryExpr() = default; BinaryExpr *clone() const override = 0; bool constant() const override; bool is_pure() const override; protected: BinaryExpr(const BinaryExpr &) = default; BinaryExpr &operator=(const BinaryExpr &) = default; }; struct RUMUR_API_WITH_RTTI BooleanBinaryExpr : public BinaryExpr { BooleanBinaryExpr(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); BooleanBinaryExpr() = delete; Ptr type() const override; void validate() const override; protected: BooleanBinaryExpr(const BooleanBinaryExpr &) = default; BooleanBinaryExpr &operator=(const BooleanBinaryExpr &) = default; }; struct RUMUR_API_WITH_RTTI Implication : public BooleanBinaryExpr { Implication(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); Implication *clone() const override; virtual ~Implication() = default; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; mpz_class constant_fold() const override; std::string to_string() const override; }; /// logical OR struct RUMUR_API_WITH_RTTI Or : public BooleanBinaryExpr { Or(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); virtual ~Or() = default; Or *clone() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; mpz_class constant_fold() const override; std::string to_string() const override; }; /// logical AND struct RUMUR_API_WITH_RTTI And : public BooleanBinaryExpr { And(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); virtual ~And() = default; And *clone() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; mpz_class constant_fold() const override; std::string to_string() const override; }; /// An 'x & y' expression where a decision has not yet been made as to whether /// the '&' is a logical AND or a bitwise AND. These nodes can only occur in the /// AST prior to symbol resolution. struct RUMUR_API_WITH_RTTI AmbiguousAmp : public BinaryExpr { AmbiguousAmp(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); virtual ~AmbiguousAmp() = default; AmbiguousAmp *clone() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; Ptr type() const override; mpz_class constant_fold() const override; std::string to_string() const override; }; /// An 'x | y' expression where a decision has not yet been made as to whether /// the '|' is a logical OR or a bitwise OR. These nodes can only occur in the /// AST prior to symbol resolution. struct RUMUR_API_WITH_RTTI AmbiguousPipe : public BinaryExpr { AmbiguousPipe(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); virtual ~AmbiguousPipe() = default; AmbiguousPipe *clone() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; Ptr type() const override; mpz_class constant_fold() const override; std::string to_string() const override; }; struct RUMUR_API_WITH_RTTI UnaryExpr : public Expr { Ptr rhs; UnaryExpr(const Ptr &rhs_, const location &loc_); UnaryExpr *clone() const override = 0; virtual ~UnaryExpr() = default; bool constant() const override; bool is_pure() const override; protected: UnaryExpr(const UnaryExpr &) = default; UnaryExpr &operator=(const UnaryExpr &) = default; }; struct RUMUR_API_WITH_RTTI Not : public UnaryExpr { Not(const Ptr &rhs_, const location &loc_); virtual ~Not() = default; Not *clone() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; Ptr type() const override; mpz_class constant_fold() const override; void validate() const override; std::string to_string() const override; }; struct RUMUR_API_WITH_RTTI ComparisonBinaryExpr : public BinaryExpr { ComparisonBinaryExpr(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); void validate() const override; protected: ComparisonBinaryExpr(const ComparisonBinaryExpr &) = default; ComparisonBinaryExpr &operator=(const ComparisonBinaryExpr &) = default; }; struct RUMUR_API_WITH_RTTI Lt : public ComparisonBinaryExpr { Lt(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); virtual ~Lt() = default; Lt *clone() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; Ptr type() const override; mpz_class constant_fold() const override; std::string to_string() const override; }; struct RUMUR_API_WITH_RTTI Leq : public ComparisonBinaryExpr { Leq(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); virtual ~Leq() = default; Leq *clone() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; Ptr type() const override; mpz_class constant_fold() const override; std::string to_string() const override; }; struct RUMUR_API_WITH_RTTI Gt : public ComparisonBinaryExpr { Gt(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); virtual ~Gt() = default; Gt *clone() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; Ptr type() const override; mpz_class constant_fold() const override; std::string to_string() const override; }; struct RUMUR_API_WITH_RTTI Geq : public ComparisonBinaryExpr { Geq(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); virtual ~Geq() = default; Geq *clone() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; Ptr type() const override; mpz_class constant_fold() const override; std::string to_string() const override; }; struct RUMUR_API_WITH_RTTI EquatableBinaryExpr : public BinaryExpr { EquatableBinaryExpr(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); void validate() const override; protected: EquatableBinaryExpr(const EquatableBinaryExpr &) = default; EquatableBinaryExpr &operator=(const EquatableBinaryExpr &) = default; }; struct RUMUR_API_WITH_RTTI Eq : public EquatableBinaryExpr { Eq(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); virtual ~Eq() = default; Eq *clone() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; Ptr type() const override; mpz_class constant_fold() const override; std::string to_string() const override; }; struct RUMUR_API_WITH_RTTI Neq : public EquatableBinaryExpr { Neq(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); virtual ~Neq() = default; Neq *clone() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; Ptr type() const override; mpz_class constant_fold() const override; std::string to_string() const override; }; struct RUMUR_API_WITH_RTTI ArithmeticBinaryExpr : public BinaryExpr { ArithmeticBinaryExpr(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); Ptr type() const override; void validate() const override; protected: ArithmeticBinaryExpr(const ArithmeticBinaryExpr &) = default; ArithmeticBinaryExpr &operator=(const ArithmeticBinaryExpr &) = default; }; struct RUMUR_API_WITH_RTTI Add : public ArithmeticBinaryExpr { Add(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); virtual ~Add() = default; Add *clone() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; mpz_class constant_fold() const override; std::string to_string() const override; }; struct RUMUR_API_WITH_RTTI Sub : public ArithmeticBinaryExpr { Sub(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); virtual ~Sub() = default; Sub *clone() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; mpz_class constant_fold() const override; std::string to_string() const override; }; struct RUMUR_API_WITH_RTTI Negative : public UnaryExpr { Negative(const Ptr &rhs_, const location &loc_); virtual ~Negative() = default; Negative *clone() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; Ptr type() const override; mpz_class constant_fold() const override; void validate() const override; std::string to_string() const override; }; struct RUMUR_API_WITH_RTTI Bnot : public UnaryExpr { Bnot(const Ptr &rhs_, const location &loc_); virtual ~Bnot() = default; Bnot *clone() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; Ptr type() const override; mpz_class constant_fold() const override; void validate() const override; std::string to_string() const override; }; struct RUMUR_API_WITH_RTTI Mul : public ArithmeticBinaryExpr { Mul(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); virtual ~Mul() = default; Mul *clone() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; mpz_class constant_fold() const override; std::string to_string() const override; }; struct RUMUR_API_WITH_RTTI Div : public ArithmeticBinaryExpr { Div(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); virtual ~Div() = default; Div *clone() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; mpz_class constant_fold() const override; std::string to_string() const override; }; struct RUMUR_API_WITH_RTTI Mod : public ArithmeticBinaryExpr { Mod(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); virtual ~Mod() = default; Mod *clone() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; mpz_class constant_fold() const override; std::string to_string() const override; }; struct RUMUR_API_WITH_RTTI Lsh : public ArithmeticBinaryExpr { Lsh(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); virtual ~Lsh() = default; Lsh *clone() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; mpz_class constant_fold() const override; std::string to_string() const override; }; struct RUMUR_API_WITH_RTTI Rsh : public ArithmeticBinaryExpr { Rsh(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); virtual ~Rsh() = default; Rsh *clone() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; mpz_class constant_fold() const override; std::string to_string() const override; }; /// bitwise AND struct RUMUR_API_WITH_RTTI Band : public ArithmeticBinaryExpr { Band(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); virtual ~Band() = default; Band *clone() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; mpz_class constant_fold() const override; std::string to_string() const override; }; /// bitwise OR struct RUMUR_API_WITH_RTTI Bor : public ArithmeticBinaryExpr { Bor(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); virtual ~Bor() = default; Bor *clone() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; mpz_class constant_fold() const override; std::string to_string() const override; }; struct RUMUR_API_WITH_RTTI Xor : public ArithmeticBinaryExpr { Xor(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); virtual ~Xor() = default; Xor *clone() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; mpz_class constant_fold() const override; std::string to_string() const override; }; struct RUMUR_API_WITH_RTTI ExprID : public Expr { std::string id; Ptr value; ExprID(const std::string &id_, const Ptr &value_, const location &loc_); virtual ~ExprID() = default; ExprID *clone() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; bool constant() const override; Ptr type() const override; mpz_class constant_fold() const override; void validate() const override; bool is_lvalue() const override; bool is_readonly() const override; std::string to_string() const override; bool is_literal_true() const override; bool is_literal_false() const override; bool is_pure() const override; }; struct RUMUR_API_WITH_RTTI Field : public Expr { Ptr record; std::string field; Field(const Ptr &record_, const std::string &field_, const location &loc_); virtual ~Field() = default; Field *clone() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; bool constant() const override; Ptr type() const override; mpz_class constant_fold() const override; void validate() const override; bool is_lvalue() const override; bool is_readonly() const override; std::string to_string() const override; bool is_pure() const override; }; struct RUMUR_API_WITH_RTTI Element : public Expr { Ptr array; Ptr index; Element(const Ptr &array_, const Ptr &index_, const location &loc_); virtual ~Element() = default; Element *clone() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; bool constant() const override; Ptr type() const override; mpz_class constant_fold() const override; void validate() const override; bool is_lvalue() const override; bool is_readonly() const override; std::string to_string() const override; bool is_pure() const override; }; struct RUMUR_API_WITH_RTTI FunctionCall : public Expr { std::string name; Ptr function; std::vector> arguments; /// whether this is a child of a `ProcedureCall` bool within_procedure_call = false; FunctionCall(const std::string &name_, const std::vector> &arguments_, const location &loc_); virtual ~FunctionCall() = default; FunctionCall *clone() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; bool constant() const override; Ptr type() const override; mpz_class constant_fold() const override; void validate() const override; std::string to_string() const override; bool is_pure() const override; }; struct RUMUR_API_WITH_RTTI Quantifier : public Node { std::string name; // if this is != nullptr, the from/to/step will be nullptr Ptr type; Ptr from; Ptr to; Ptr step; Ptr decl; Quantifier(const std::string &name_, const Ptr &type_, const location &loc); Quantifier(const std::string &name_, const Ptr &from_, const Ptr &to_, const location &loc_); Quantifier(const std::string &name_, const Ptr &from_, const Ptr &to_, const Ptr &step_, const location &loc_); virtual ~Quantifier() = default; Quantifier *clone() const override; void validate() const override; std::string to_string() const; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; /// whether the quantifier’s range can be constant folded bool constant() const; /// number of entries in this quantifier’s range (only valid when /// `constant()` returns `true`) mpz_class count() const; /// get the lower bound of this quantified expression as a C expression std::string lower_bound() const; /// is this side-effect free? bool is_pure() const; }; struct RUMUR_API_WITH_RTTI Exists : public Expr { Quantifier quantifier; Ptr expr; Exists(const Quantifier &quantifier_, const Ptr &expr_, const location &loc_); virtual ~Exists() = default; Exists *clone() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; bool constant() const override; Ptr type() const override; mpz_class constant_fold() const override; void validate() const override; std::string to_string() const override; bool is_pure() const override; }; struct RUMUR_API_WITH_RTTI Forall : public Expr { Quantifier quantifier; Ptr expr; Forall(const Quantifier &quantifier_, const Ptr &expr_, const location &loc_); virtual ~Forall() = default; Forall *clone() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; bool constant() const override; Ptr type() const override; mpz_class constant_fold() const override; void validate() const override; std::string to_string() const override; bool is_pure() const override; }; struct RUMUR_API_WITH_RTTI IsUndefined : public UnaryExpr { IsUndefined(const Ptr &expr_, const location &loc_); virtual ~IsUndefined() = default; IsUndefined *clone() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; bool constant() const override; Ptr type() const override; mpz_class constant_fold() const override; void validate() const override; std::string to_string() const override; }; } // namespace rumur rumur-2024.05.07/librumur/include/rumur/Function.h000066400000000000000000000022601461637631000216700ustar00rootroot00000000000000#pragma once #include "location.hh" #include #include #include #include #include #include #include #include #include #ifndef RUMUR_API_WITH_RTTI #define RUMUR_API_WITH_RTTI __attribute__((visibility("default"))) #endif namespace rumur { struct RUMUR_API_WITH_RTTI Function : public Node { std::string name; std::vector> parameters; Ptr return_type; std::vector> decls; std::vector> body; Function(const std::string &name_, const std::vector> ¶meters_, const Ptr &return_type_, const std::vector> &decls_, const std::vector> &body_, const location &loc_); virtual ~Function() = default; Function *clone() const override; void validate() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; // is this function side effect free? bool is_pure() const; // does this function contain calls to itself? bool is_recursive() const; }; } // namespace rumur rumur-2024.05.07/librumur/include/rumur/Model.h000066400000000000000000000025411461637631000211450ustar00rootroot00000000000000#pragma once #include "location.hh" #include #include #include #include #include #include #include #include #include #include #include #ifndef RUMUR_API_WITH_RTTI #define RUMUR_API_WITH_RTTI __attribute__((visibility("default"))) #endif namespace rumur { struct RUMUR_API_WITH_RTTI Model : public Node { // declarations, functions and rules in the order in which they appeared in // the source std::vector> children; Model(const std::vector> &children_, const location &loc_); virtual ~Model() = default; Model *clone() const override; // Get the size of the state data in bits. mpz_class size_bits() const; void validate() const override; // dispatch to the appropriate traversal method (see traverse.h) void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; /* Get the number of global liveness properties in the model. This considers * the “flat” model. That is, a ruleset-contained liveness property may count * for more than one. */ mpz_class liveness_count() const; /* Update the bit offset of each variable declaration in the model and reindex * all AST nodes. */ void reindex(); }; } // namespace rumur rumur-2024.05.07/librumur/include/rumur/Node.h000066400000000000000000000030311461637631000207650ustar00rootroot00000000000000#pragma once #include "location.hh" #include #include #include #include #ifndef RUMUR_API_WITH_RTTI #define RUMUR_API_WITH_RTTI __attribute__((visibility("default"))) #endif namespace rumur { class BaseTraversal; class ConstBaseTraversal; struct RUMUR_API_WITH_RTTI Node { /// originating line and column source position location loc; /// A numeric identifier of this node, unique within the current AST. A value /// of `SIZE_MAX` means this node (and its children) have not yet been /// indexed (see `Model::reindex`). This value can be useful when doing code /// generation and needing to invent unique symbols. size_t unique_id = SIZE_MAX; Node() = delete; Node(const location &loc_); virtual ~Node() = default; /// Create a copy of this node. This is necessary rather than simply relying /// on the copy constructor because of the inheritance hierarchy. That is, an /// agnostic class like `Ptr` can call this to copy a node without knowing /// its precise derived type and without object slicing. virtual Node *clone() const = 0; /// Confirm that data structure invariants hold. This function throws /// `rumur::Error` if invariants are violated. virtual void validate() const {} /// traverse this node and its children using the given action virtual void visit(BaseTraversal &visitor) = 0; virtual void visit(ConstBaseTraversal &visitor) const = 0; protected: Node(const Node &) = default; Node &operator=(const Node &) = default; }; } // namespace rumur rumur-2024.05.07/librumur/include/rumur/Number.h000066400000000000000000000020731461637631000213350ustar00rootroot00000000000000#pragma once #include "location.hh" #include #include #include #include #include #include #include #include #ifndef RUMUR_API_WITH_RTTI #define RUMUR_API_WITH_RTTI __attribute__((visibility("default"))) #endif namespace rumur { struct RUMUR_API_WITH_RTTI Number : public Expr { mpz_class value; Number() = delete; Number(const std::string &value_, const location &loc_); Number(const mpz_class &value_, const location &loc_); Number(const Number &) = default; Number(Number &&) = default; Number &operator=(const Number &) = default; Number &operator=(Number &&) = default; virtual ~Number() = default; Number *clone() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; bool constant() const override; Ptr type() const override; mpz_class constant_fold() const override; std::string to_string() const override; bool is_pure() const override; }; } // namespace rumur rumur-2024.05.07/librumur/include/rumur/Property.h000066400000000000000000000013431461637631000217300ustar00rootroot00000000000000#pragma once #include "location.hh" #include #include #include #include #include #include #ifndef RUMUR_API_WITH_RTTI #define RUMUR_API_WITH_RTTI __attribute__((visibility("default"))) #endif namespace rumur { struct RUMUR_API_WITH_RTTI Property : public Node { enum Category { ASSERTION, ASSUMPTION, COVER, LIVENESS, }; Category category; Ptr expr; Property(Category category_, const Ptr &expr_, const location &loc_); Property *clone() const override; virtual ~Property() = default; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; }; } // namespace rumur rumur-2024.05.07/librumur/include/rumur/Ptr.h000066400000000000000000000040341461637631000206510ustar00rootroot00000000000000#pragma once #include #include #include #include #ifndef RUMUR_API #define RUMUR_API __attribute__((visibility("default"))) #endif namespace rumur { // An implementation of a managed pointer that understands *::clone() template class RUMUR_API Ptr { private: std::unique_ptr t = std::unique_ptr(nullptr); public: Ptr() = default; Ptr(std::nullptr_t) {} explicit Ptr(TARGET *t_) : t(t_) {} Ptr(const Ptr &p) : t(p.t == nullptr ? nullptr : p.t->clone()) {} Ptr(Ptr &&p) noexcept { using std::swap; swap(t, p.t); } template Ptr(const Ptr &p) : t(p.get() == nullptr ? nullptr : p->clone()) {} Ptr &operator=(const Ptr &p) { t.reset(p.t == nullptr ? nullptr : p.t->clone()); return *this; } Ptr &operator=(Ptr &&p) noexcept { using std::swap; swap(t, p.t); return *this; } template Ptr &operator=(const Ptr &p) { t.reset(p.get() == nullptr ? nullptr : p->clone()); return *this; } const TARGET *get() const { return t.get(); } TARGET *get() { return t.get(); } TARGET &operator*() { assert(t != nullptr && "dereferencing a null pointer"); return *t; } const TARGET &operator*() const { assert(t != nullptr && "dereferencing a null pointer"); return *t; } TARGET *operator->() { assert(t != nullptr && "dereferencing a null pointer"); return t.get(); } const TARGET *operator->() const { assert(t != nullptr && "dereferencing a null pointer"); return t.get(); } bool operator==(const Ptr &other) const { return t == other.t; } bool operator==(const TARGET *other) const { return t.get() == other; } bool operator!=(const Ptr &other) const { return t != other.t; } bool operator!=(const TARGET *other) const { return t.get() != other; } template static Ptr make(Args &&...args) { return Ptr(new TARGET(std::forward(args)...)); } }; } // namespace rumur rumur-2024.05.07/librumur/include/rumur/Rule.h000066400000000000000000000061261461637631000210170ustar00rootroot00000000000000#pragma once #include "location.hh" #include #include #include #include #include #include #include #include #include #include #include #ifndef RUMUR_API_WITH_RTTI #define RUMUR_API_WITH_RTTI __attribute__((visibility("default"))) #endif namespace rumur { struct RUMUR_API_WITH_RTTI Rule : public Node { std::string name; std::vector quantifiers; std::vector> aliases; Rule(const std::string &name_, const location &loc_); Rule *clone() const override = 0; virtual std::vector> flatten() const; virtual ~Rule() = default; protected: Rule(const Rule &) = default; Rule &operator=(const Rule &) = default; }; struct RUMUR_API_WITH_RTTI AliasRule : public Rule { std::vector> rules; AliasRule(const std::vector> &aliases_, const std::vector> &rules_, const location &loc_); virtual ~AliasRule() = default; AliasRule *clone() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; std::vector> flatten() const override; }; struct RUMUR_API_WITH_RTTI SimpleRule : public Rule { Ptr guard; std::vector> decls; std::vector> body; SimpleRule(const std::string &name_, const Ptr &guard_, const std::vector> &decls_, const std::vector> &body_, const location &loc_); virtual ~SimpleRule() = default; SimpleRule *clone() const override; void validate() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; }; struct RUMUR_API_WITH_RTTI StartState : public Rule { std::vector> decls; std::vector> body; StartState(const std::string &name_, const std::vector> &decls_, const std::vector> &body_, const location &loc_); virtual ~StartState() = default; StartState *clone() const override; void validate() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; }; struct RUMUR_API_WITH_RTTI PropertyRule : public Rule { Property property; PropertyRule(const std::string &name_, const Property &property_, const location &loc_); virtual ~PropertyRule() = default; PropertyRule *clone() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; }; struct RUMUR_API_WITH_RTTI Ruleset : public Rule { std::vector> rules; Ruleset(const std::vector &quantifiers_, const std::vector> &rules_, const location &loc_); virtual ~Ruleset() = default; Ruleset *clone() const override; void validate() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; std::vector> flatten() const override; }; } // namespace rumur rumur-2024.05.07/librumur/include/rumur/Stmt.h000066400000000000000000000143371461637631000210420ustar00rootroot00000000000000#pragma once #include "location.hh" #include #include #include #include #include #include #include #include #include #include #ifndef RUMUR_API_WITH_RTTI #define RUMUR_API_WITH_RTTI __attribute__((visibility("default"))) #endif namespace rumur { struct RUMUR_API_WITH_RTTI Stmt : public Node { Stmt(const location &loc_); virtual ~Stmt() = default; virtual Stmt *clone() const = 0; protected: Stmt(const Stmt &) = default; Stmt &operator=(const Stmt &) = default; }; struct RUMUR_API_WITH_RTTI AliasStmt : public Stmt { std::vector> aliases; std::vector> body; AliasStmt(const std::vector> &aliases_, const std::vector> &body_, const location &loc_); AliasStmt *clone() const override; virtual ~AliasStmt() = default; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; }; struct RUMUR_API_WITH_RTTI PropertyStmt : public Stmt { Property property; std::string message; PropertyStmt(const Property &property_, const std::string &message_, const location &loc_); PropertyStmt *clone() const override; virtual ~PropertyStmt() = default; void validate() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; }; struct RUMUR_API_WITH_RTTI Assignment : public Stmt { Ptr lhs; Ptr rhs; Assignment(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); Assignment *clone() const override; virtual ~Assignment() = default; void validate() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; }; struct RUMUR_API_WITH_RTTI Clear : public Stmt { Ptr rhs; Clear(const Ptr &rhs_, const location &loc); virtual ~Clear() = default; Clear *clone() const override; void validate() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; }; struct RUMUR_API_WITH_RTTI ErrorStmt : public Stmt { std::string message; ErrorStmt(const std::string &message_, const location &loc_); ErrorStmt *clone() const override; virtual ~ErrorStmt() = default; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; }; struct RUMUR_API_WITH_RTTI For : public Stmt { Quantifier quantifier; std::vector> body; For(const Quantifier &quantifier_, const std::vector> &body_, const location &loc_); virtual ~For() = default; For *clone() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; }; struct RUMUR_API_WITH_RTTI IfClause : public Node { Ptr condition; std::vector> body; IfClause(const Ptr &condition_, const std::vector> &body_, const location &loc_); virtual ~IfClause() = default; IfClause *clone() const override; void validate() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; }; struct RUMUR_API_WITH_RTTI If : public Stmt { std::vector clauses; If(const std::vector &clauses_, const location &loc_); virtual ~If() = default; If *clone() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; }; struct RUMUR_API_WITH_RTTI ProcedureCall : public Stmt { FunctionCall call; ProcedureCall(const std::string &name, const std::vector> &arguments, const location &loc_); virtual ~ProcedureCall() = default; ProcedureCall *clone() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; }; struct RUMUR_API_WITH_RTTI Put : public Stmt { std::string value; Ptr expr; Put(const std::string &value_, const location &loc_); Put(const Ptr &expr_, const location &loc_); virtual ~Put() = default; Put *clone() const override; void validate() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; }; struct RUMUR_API_WITH_RTTI Return : public Stmt { Ptr expr; Return(const Ptr &expr_, const location &loc_); virtual ~Return() = default; Return *clone() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; }; struct RUMUR_API_WITH_RTTI SwitchCase : public Node { std::vector> matches; std::vector> body; SwitchCase(const std::vector> &matches_, const std::vector> &body_, const location &loc_); virtual ~SwitchCase() = default; SwitchCase *clone() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; }; struct RUMUR_API_WITH_RTTI Switch : public Stmt { Ptr expr; std::vector cases; Switch(const Ptr &expr_, const std::vector &cases_, const location &loc_); virtual ~Switch() = default; Switch *clone() const override; void validate() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; }; struct RUMUR_API_WITH_RTTI Undefine : public Stmt { Ptr rhs; Undefine(const Ptr &rhs_, const location &loc_); virtual ~Undefine() = default; Undefine *clone() const override; void validate() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; }; struct RUMUR_API_WITH_RTTI While : public Stmt { Ptr condition; std::vector> body; While(const Ptr &condition_, const std::vector> &body_, const location &loc_); virtual ~While() = default; While *clone() const override; void validate() const override; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; }; } // namespace rumur rumur-2024.05.07/librumur/include/rumur/Symtab.h000066400000000000000000000024211461637631000213410ustar00rootroot00000000000000#pragma once #include "location.hh" #include #include #include #include #include #include #include #include #include #ifndef RUMUR_API #define RUMUR_API __attribute__((visibility("default"))) #endif namespace rumur { class RUMUR_API Symtab { private: std::vector>> scope; public: void open_scope() { scope.emplace_back(); } void close_scope() { assert(!scope.empty()); scope.pop_back(); } void declare(const std::string &name, const Ptr &value) { assert(!scope.empty()); if (scope.back().count(name) > 0) throw Error("symbol \"" + name + "\" was previously declared", value->loc); scope.back()[name] = value; } template Ptr lookup(const std::string &name, const location &loc) const { for (auto it = scope.rbegin(); it != scope.rend(); it++) { auto it2 = it->find(name); if (it2 != it->end()) { if (auto ret = dynamic_cast(it2->second.get())) { return Ptr(ret->clone()); } else { break; } } } throw Error("unknown symbol: " + name, loc); } }; } // namespace rumur rumur-2024.05.07/librumur/include/rumur/TypeExpr.h000066400000000000000000000130271461637631000216660ustar00rootroot00000000000000#pragma once #include "location.hh" #include #include #include #include #include #include #include #include #include #include #include #include #ifndef RUMUR_API_WITH_RTTI #define RUMUR_API_WITH_RTTI __attribute__((visibility("default"))) #endif namespace rumur { // Forward declare to avoid circular #include struct TypeDecl; struct VarDecl; struct RUMUR_API_WITH_RTTI TypeExpr : public Node { TypeExpr(const location &loc_); virtual ~TypeExpr() = default; // Whether this type is a primitive integer-like type. virtual bool is_simple() const; TypeExpr *clone() const override = 0; virtual mpz_class width() const; virtual mpz_class count() const = 0; virtual Ptr resolve() const; /* Numeric bounds of this type as valid C code. These are only valid to use on * TypeExprs for which is_simple() returns true. */ virtual std::string lower_bound() const; virtual std::string upper_bound() const; // Get a string representation of this type virtual std::string to_string() const = 0; /* Whether this type's bounds are constant. Only valid for TypeExprs for which * is_simple() returns true. */ virtual bool constant() const; // can a value of this type can be assigned to or compared with a value of the // given type? bool coerces_to(const TypeExpr &other) const; // Is this the type Boolean? Note that this only returns true for the actual // type Boolean, and not for TypeExprIDs that point at Boolean. virtual bool is_boolean() const; protected: TypeExpr(const TypeExpr &) = default; TypeExpr &operator=(const TypeExpr &) = default; }; struct RUMUR_API_WITH_RTTI Range : public TypeExpr { Ptr min; Ptr max; Range(const Ptr &min_, const Ptr &max_, const location &loc_); Range *clone() const override; virtual ~Range() = default; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; mpz_class count() const override; bool is_simple() const override; void validate() const override; std::string lower_bound() const override; std::string upper_bound() const override; std::string to_string() const override; bool constant() const override; }; struct RUMUR_API_WITH_RTTI Scalarset : public TypeExpr { Ptr bound; Scalarset(const Ptr &bound_, const location &loc_); Scalarset *clone() const override; virtual ~Scalarset() = default; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; mpz_class count() const override; bool is_simple() const override; void validate() const override; std::string lower_bound() const override; std::string upper_bound() const override; std::string to_string() const override; bool constant() const override; }; struct RUMUR_API_WITH_RTTI Enum : public TypeExpr { std::vector> members; // The range [unique_id, unique_id_limit) is usable by this node as // identifiers. Enum types need this specialisation due to the way references // to their members are resolved (see resolve_symbols()). size_t unique_id_limit = SIZE_MAX; Enum(const std::vector> &members_, const location &loc_); Enum *clone() const override; virtual ~Enum() = default; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; mpz_class count() const override; bool is_simple() const override; void validate() const override; std::string lower_bound() const override; std::string upper_bound() const override; std::string to_string() const override; bool constant() const override; bool is_boolean() const override; }; struct RUMUR_API_WITH_RTTI Record : public TypeExpr { std::vector> fields; Record(const std::vector> &fields_, const location &loc_); Record *clone() const override; virtual ~Record() = default; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; mpz_class width() const override; mpz_class count() const override; std::string to_string() const override; }; struct RUMUR_API_WITH_RTTI Array : public TypeExpr { Ptr index_type; Ptr element_type; Array(const Ptr &index_type_, const Ptr &element_type_, const location &loc_); Array *clone() const override; virtual ~Array() = default; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; mpz_class width() const override; mpz_class count() const override; void validate() const override; std::string to_string() const override; }; struct RUMUR_API_WITH_RTTI TypeExprID : public TypeExpr { std::string name; Ptr referent; TypeExprID(const std::string &name_, const Ptr &referent_, const location &loc_); TypeExprID *clone() const override; virtual ~TypeExprID() = default; void visit(BaseTraversal &visitor) override; void visit(ConstBaseTraversal &visitor) const override; mpz_class width() const override; mpz_class count() const override; bool is_simple() const override; Ptr resolve() const override; void validate() const override; std::string lower_bound() const override; std::string upper_bound() const override; std::string to_string() const override; bool constant() const override; }; } // namespace rumur rumur-2024.05.07/librumur/include/rumur/except.h000066400000000000000000000010311461637631000213660ustar00rootroot00000000000000#pragma once #include "location.hh" #include #include #include #ifndef RUMUR_API_WITH_RTTI #define RUMUR_API_WITH_RTTI __attribute__((visibility("default"))) #endif namespace rumur { /* A basic exception to allow us to easily catch only the errors thrown by * ourselves. */ class RUMUR_API_WITH_RTTI Error : public std::runtime_error { public: location loc; Error(const std::string &message, const location &loc_); Error(const std::string &prefix, const Error &sub); }; } // namespace rumur rumur-2024.05.07/librumur/include/rumur/indexer.h000066400000000000000000000064501461637631000215460ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include #ifndef RUMUR_API_WITH_RTTI #define RUMUR_API_WITH_RTTI __attribute__((visibility("default"))) #endif namespace rumur { class RUMUR_API_WITH_RTTI Indexer : public BaseTraversal { private: size_t next = 0; public: void visit_add(Add &n) override; void visit_aliasdecl(AliasDecl &n) override; void visit_aliasrule(AliasRule &n) override; void visit_aliasstmt(AliasStmt &n) override; void visit_ambiguousamp(AmbiguousAmp &n) override; void visit_ambiguouspipe(AmbiguousPipe &n) override; void visit_and(And &n) override; void visit_array(Array &n) override; void visit_assignment(Assignment &n) override; void visit_band(Band &n) override; void visit_bnot(Bnot &n) override; void visit_bor(Bor &n) override; void visit_clear(Clear &n) override; void visit_constdecl(ConstDecl &n) override; void visit_div(Div &n) override; void visit_element(Element &n) override; void visit_enum(Enum &n) override; void visit_eq(Eq &n) override; void visit_errorstmt(ErrorStmt &n) override; void visit_exists(Exists &n) override; void visit_exprid(ExprID &n) override; void visit_field(Field &n) override; void visit_for(For &n) override; void visit_forall(Forall &n) override; void visit_function(Function &n) override; void visit_functioncall(FunctionCall &n) override; void visit_geq(Geq &n) override; void visit_gt(Gt &n) override; void visit_if(If &n) override; void visit_ifclause(IfClause &n) override; void visit_implication(Implication &n) override; void visit_isundefined(IsUndefined &n) override; void visit_leq(Leq &n) override; void visit_lsh(Lsh &n) override; void visit_lt(Lt &n) override; void visit_model(Model &n) override; void visit_mod(Mod &n) override; void visit_mul(Mul &n) override; void visit_negative(Negative &n) override; void visit_neq(Neq &n) override; void visit_not(Not &n) override; void visit_number(Number &n) override; void visit_or(Or &n) override; void visit_procedurecall(ProcedureCall &n) override; void visit_property(Property &n) override; void visit_propertyrule(PropertyRule &n) override; void visit_propertystmt(PropertyStmt &n) override; void visit_put(Put &n) override; void visit_quantifier(Quantifier &n) override; void visit_range(Range &n) override; void visit_record(Record &n) override; void visit_return(Return &n) override; void visit_rsh(Rsh &n) override; void visit_ruleset(Ruleset &n) override; void visit_scalarset(Scalarset &n) override; void visit_simplerule(SimpleRule &n) override; void visit_startstate(StartState &n) override; void visit_sub(Sub &n) override; void visit_switch(Switch &n) override; void visit_switchcase(SwitchCase &n) override; void visit_ternary(Ternary &n) override; void visit_typedecl(TypeDecl &n) override; void visit_typeexprid(TypeExprID &n) override; void visit_undefine(Undefine &n) override; void visit_vardecl(VarDecl &n) override; void visit_while(While &n) override; void visit_xor(Xor &n) override; virtual ~Indexer() = default; private: void visit_bexpr(BinaryExpr &n); void visit_uexpr(UnaryExpr &n); }; } // namespace rumur rumur-2024.05.07/librumur/include/rumur/parse.h000066400000000000000000000005361461637631000212210ustar00rootroot00000000000000#pragma once #include #include #include #include #ifndef RUMUR_API #define RUMUR_API __attribute__((visibility("default"))) #endif namespace rumur { // Parse in a model from an input stream. Throws Errors on parsing errors. RUMUR_API Ptr parse(std::istream &input); } // namespace rumur rumur-2024.05.07/librumur/include/rumur/resolve-symbols.h000066400000000000000000000006421461637631000232520ustar00rootroot00000000000000#pragma once #include #include #ifndef RUMUR_API #define RUMUR_API __attribute__((visibility("default"))) #endif namespace rumur { /** resolve symbolic references within a model * * Resolve symbolic references (`rumur::ExprID`s and `rumur::TypeExprID`s) * within a model. Throws `rumur::Error` if this process fails. */ RUMUR_API void resolve_symbols(Model &m); } // namespace rumur rumur-2024.05.07/librumur/include/rumur/rumur.h000066400000000000000000000016711461637631000212620ustar00rootroot00000000000000// Rumur exported API #pragma once #ifndef RUMUR_API #define RUMUR_API #endif namespace rumur { // forward declaration to update visibility class RUMUR_API parser; } // namespace rumur #include "location.hh" #include "parser.yy.hh" #include "position.hh" #include "rumur-get-version.h" // generated #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // stack.hh is deliberately not included; just use std::stack rumur-2024.05.07/librumur/include/rumur/sanitise_rule_names.h000066400000000000000000000007331461637631000241370ustar00rootroot00000000000000#pragma once #include #include #ifndef RUMUR_API #define RUMUR_API __attribute__((visibility("default"))) #endif namespace rumur { /** ensure all rules have [a-zA-Z_][a-zA-Z_0-9]* names * * This will rename any rules that are not safe to use as, e.g. a C symbol. This * can be useful for code generators that want to derive symbols to emit from * the names of the rules. */ RUMUR_API void sanitise_rule_names(Node &n); } // namespace rumur rumur-2024.05.07/librumur/include/rumur/scanner.h000066400000000000000000000021041461637631000215310ustar00rootroot00000000000000#pragma once #include #include #ifndef yyFlexLexerOnce #include #endif #include "parser.yy.hh" #ifndef RUMUR_API_WITH_RTTI #define RUMUR_API_WITH_RTTI __attribute__((visibility("default"))) #endif namespace rumur { class RUMUR_API_WITH_RTTI scanner : public yyFlexLexer { public: // Delegate to yyFlexLexer's constructor scanner(std::istream *arg_yyin = 0, std::ostream *arg_yyout = 0) : yyFlexLexer(arg_yyin, arg_yyout) {} /* XXX: Clang's -Woverloaded-virtual decides that the following declaration is * possibly a mistake. However, we are deliberately overloading this method with * a different type signature. The cleanest way I've found around it is to * toggle off the warning here. */ #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Woverloaded-virtual" #endif // Force a new available type signature for yylex virtual int yylex(parser::semantic_type *const lval, parser::location_type *loc); #ifdef __clang__ #pragma clang diagnostic pop #endif }; } // namespace rumur rumur-2024.05.07/librumur/include/rumur/traverse.h000066400000000000000000000522611461637631000217440ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #ifndef RUMUR_API_WITH_RTTI #define RUMUR_API_WITH_RTTI __attribute__((visibility("default"))) #endif namespace rumur { /* Generic abstract syntax tree traversal interface with no implementation. If * you want to implement a traversal that handles every Node type you should * define a class that inherits from this. If you want a more liberal base class * that provides default implementations for the 'visit' methods you don't need * to override, inherit from Traversal below. */ class RUMUR_API_WITH_RTTI BaseTraversal { public: virtual void visit_add(Add &n) = 0; virtual void visit_aliasdecl(AliasDecl &n) = 0; virtual void visit_aliasrule(AliasRule &n) = 0; virtual void visit_aliasstmt(AliasStmt &n) = 0; virtual void visit_and(And &n) = 0; virtual void visit_array(Array &n) = 0; virtual void visit_assignment(Assignment &n) = 0; virtual void visit_band(Band &n) = 0; virtual void visit_bnot(Bnot &n) = 0; virtual void visit_bor(Bor &n) = 0; virtual void visit_clear(Clear &n) = 0; virtual void visit_constdecl(ConstDecl &n) = 0; virtual void visit_div(Div &n) = 0; virtual void visit_element(Element &n) = 0; virtual void visit_enum(Enum &n) = 0; virtual void visit_eq(Eq &n) = 0; virtual void visit_errorstmt(ErrorStmt &n) = 0; virtual void visit_exists(Exists &n) = 0; virtual void visit_exprid(ExprID &n) = 0; virtual void visit_field(Field &n) = 0; virtual void visit_for(For &n) = 0; virtual void visit_forall(Forall &n) = 0; virtual void visit_function(Function &n) = 0; virtual void visit_functioncall(FunctionCall &n) = 0; virtual void visit_geq(Geq &n) = 0; virtual void visit_gt(Gt &n) = 0; virtual void visit_if(If &n) = 0; virtual void visit_ifclause(IfClause &n) = 0; virtual void visit_implication(Implication &n) = 0; virtual void visit_isundefined(IsUndefined &n) = 0; virtual void visit_leq(Leq &n) = 0; virtual void visit_lsh(Lsh &n) = 0; virtual void visit_lt(Lt &n) = 0; virtual void visit_model(Model &n) = 0; virtual void visit_mod(Mod &n) = 0; virtual void visit_mul(Mul &n) = 0; virtual void visit_negative(Negative &n) = 0; virtual void visit_neq(Neq &n) = 0; virtual void visit_not(Not &n) = 0; virtual void visit_number(Number &n) = 0; virtual void visit_or(Or &n) = 0; virtual void visit_procedurecall(ProcedureCall &n) = 0; virtual void visit_property(Property &n) = 0; virtual void visit_propertyrule(PropertyRule &n) = 0; virtual void visit_propertystmt(PropertyStmt &n) = 0; virtual void visit_put(Put &n) = 0; virtual void visit_quantifier(Quantifier &n) = 0; virtual void visit_range(Range &n) = 0; virtual void visit_record(Record &n) = 0; virtual void visit_return(Return &n) = 0; virtual void visit_rsh(Rsh &n) = 0; virtual void visit_ruleset(Ruleset &n) = 0; virtual void visit_scalarset(Scalarset &n) = 0; virtual void visit_simplerule(SimpleRule &n) = 0; virtual void visit_startstate(StartState &n) = 0; virtual void visit_sub(Sub &n) = 0; virtual void visit_switch(Switch &n) = 0; virtual void visit_switchcase(SwitchCase &n) = 0; virtual void visit_ternary(Ternary &n) = 0; virtual void visit_typedecl(TypeDecl &n) = 0; virtual void visit_typeexprid(TypeExprID &n) = 0; virtual void visit_undefine(Undefine &n) = 0; virtual void visit_vardecl(VarDecl &n) = 0; virtual void visit_while(While &n) = 0; virtual void visit_xor(Xor &n) = 0; /* Visitation dispatch. This simply determines the type of the Node argument * and calls the appropriate specialised 'visit' method. This is not virtual * because we do not anticipate use cases where this behaviour needs to * change. */ void dispatch(Node &n); // Unlike the other visitation methods, we provide an implementation for // the ambiguous nodes because they only exist in an unresolved AST. We assume // that inheritors, even if they want to handle “everything,” may not want to // handle these synthetic types. virtual void visit_ambiguousamp(AmbiguousAmp &n); virtual void visit_ambiguouspipe(AmbiguousPipe &n); virtual ~BaseTraversal() = default; }; class RUMUR_API_WITH_RTTI Traversal : public BaseTraversal { public: void visit_add(Add &n) override; void visit_aliasdecl(AliasDecl &n) override; void visit_aliasrule(AliasRule &n) override; void visit_aliasstmt(AliasStmt &n) override; void visit_and(And &n) override; void visit_array(Array &n) override; void visit_assignment(Assignment &n) override; void visit_band(Band &n) override; void visit_bnot(Bnot &n) override; void visit_bor(Bor &n) override; void visit_clear(Clear &n) override; void visit_constdecl(ConstDecl &n) override; void visit_div(Div &n) override; void visit_element(Element &n) override; void visit_enum(Enum &n) override; void visit_eq(Eq &n) override; void visit_errorstmt(ErrorStmt &n) override; void visit_exists(Exists &n) override; void visit_exprid(ExprID &n) override; void visit_field(Field &n) override; void visit_for(For &n) override; void visit_forall(Forall &n) override; void visit_function(Function &n) override; void visit_functioncall(FunctionCall &n) override; void visit_geq(Geq &n) override; void visit_gt(Gt &n) override; void visit_if(If &n) override; void visit_ifclause(IfClause &n) override; void visit_implication(Implication &n) override; void visit_isundefined(IsUndefined &n) override; void visit_leq(Leq &n) override; void visit_lsh(Lsh &n) override; void visit_lt(Lt &n) override; void visit_model(Model &n) override; void visit_mod(Mod &n) override; void visit_mul(Mul &n) override; void visit_negative(Negative &n) override; void visit_neq(Neq &n) override; void visit_not(Not &n) override; void visit_number(Number &n) override; void visit_or(Or &n) override; void visit_procedurecall(ProcedureCall &n) override; void visit_property(Property &n) override; void visit_propertyrule(PropertyRule &n) override; void visit_propertystmt(PropertyStmt &n) override; void visit_put(Put &n) override; void visit_quantifier(Quantifier &n) override; void visit_range(Range &n) override; void visit_record(Record &n) override; void visit_return(Return &n) override; void visit_rsh(Rsh &n) override; void visit_ruleset(Ruleset &n) override; void visit_scalarset(Scalarset &n) override; void visit_simplerule(SimpleRule &n) override; void visit_startstate(StartState &n) override; void visit_sub(Sub &n) override; void visit_switch(Switch &n) override; void visit_switchcase(SwitchCase &n) override; void visit_ternary(Ternary &n) override; void visit_typedecl(TypeDecl &n) override; void visit_typeexprid(TypeExprID &n) override; void visit_undefine(Undefine &n) override; void visit_vardecl(VarDecl &n) override; void visit_while(While &n) override; void visit_xor(Xor &n) override; // Force class to be abstract virtual ~Traversal() = 0; private: void visit_bexpr(BinaryExpr &n); void visit_uexpr(UnaryExpr &n); }; // Read-only equivalent of BaseTraversal. class RUMUR_API_WITH_RTTI ConstBaseTraversal { public: virtual void visit_add(const Add &n) = 0; virtual void visit_aliasdecl(const AliasDecl &n) = 0; virtual void visit_aliasrule(const AliasRule &n) = 0; virtual void visit_aliasstmt(const AliasStmt &n) = 0; virtual void visit_and(const And &n) = 0; virtual void visit_array(const Array &n) = 0; virtual void visit_assignment(const Assignment &n) = 0; virtual void visit_band(const Band &n) = 0; virtual void visit_bnot(const Bnot &n) = 0; virtual void visit_bor(const Bor &n) = 0; virtual void visit_clear(const Clear &n) = 0; virtual void visit_constdecl(const ConstDecl &n) = 0; virtual void visit_div(const Div &n) = 0; virtual void visit_element(const Element &n) = 0; virtual void visit_enum(const Enum &n) = 0; virtual void visit_eq(const Eq &n) = 0; virtual void visit_errorstmt(const ErrorStmt &n) = 0; virtual void visit_exists(const Exists &n) = 0; virtual void visit_exprid(const ExprID &n) = 0; virtual void visit_field(const Field &n) = 0; virtual void visit_for(const For &n) = 0; virtual void visit_forall(const Forall &n) = 0; virtual void visit_function(const Function &n) = 0; virtual void visit_functioncall(const FunctionCall &n) = 0; virtual void visit_geq(const Geq &n) = 0; virtual void visit_gt(const Gt &n) = 0; virtual void visit_if(const If &n) = 0; virtual void visit_ifclause(const IfClause &n) = 0; virtual void visit_implication(const Implication &n) = 0; virtual void visit_isundefined(const IsUndefined &n) = 0; virtual void visit_leq(const Leq &n) = 0; virtual void visit_lsh(const Lsh &n) = 0; virtual void visit_lt(const Lt &n) = 0; virtual void visit_model(const Model &n) = 0; virtual void visit_mod(const Mod &n) = 0; virtual void visit_mul(const Mul &n) = 0; virtual void visit_negative(const Negative &n) = 0; virtual void visit_neq(const Neq &n) = 0; virtual void visit_not(const Not &n) = 0; virtual void visit_number(const Number &n) = 0; virtual void visit_or(const Or &n) = 0; virtual void visit_procedurecall(const ProcedureCall &n) = 0; virtual void visit_property(const Property &n) = 0; virtual void visit_propertyrule(const PropertyRule &n) = 0; virtual void visit_propertystmt(const PropertyStmt &n) = 0; virtual void visit_put(const Put &n) = 0; virtual void visit_quantifier(const Quantifier &n) = 0; virtual void visit_range(const Range &n) = 0; virtual void visit_record(const Record &n) = 0; virtual void visit_return(const Return &n) = 0; virtual void visit_rsh(const Rsh &n) = 0; virtual void visit_ruleset(const Ruleset &n) = 0; virtual void visit_scalarset(const Scalarset &n) = 0; virtual void visit_simplerule(const SimpleRule &n) = 0; virtual void visit_startstate(const StartState &n) = 0; virtual void visit_sub(const Sub &n) = 0; virtual void visit_switch(const Switch &n) = 0; virtual void visit_switchcase(const SwitchCase &n) = 0; virtual void visit_ternary(const Ternary &n) = 0; virtual void visit_typedecl(const TypeDecl &n) = 0; virtual void visit_typeexprid(const TypeExprID &n) = 0; virtual void visit_undefine(const Undefine &n) = 0; virtual void visit_vardecl(const VarDecl &n) = 0; virtual void visit_while(const While &n) = 0; virtual void visit_xor(const Xor &n) = 0; void dispatch(const Node &n); virtual void visit_ambiguousamp(const AmbiguousAmp &n); virtual void visit_ambiguouspipe(const AmbiguousPipe &n); virtual ~ConstBaseTraversal() = default; }; // Read-only equivalent of Traversal. class RUMUR_API_WITH_RTTI ConstTraversal : public ConstBaseTraversal { public: void visit_add(const Add &n) override; void visit_aliasdecl(const AliasDecl &n) override; void visit_aliasrule(const AliasRule &n) override; void visit_aliasstmt(const AliasStmt &n) override; void visit_and(const And &n) override; void visit_array(const Array &n) override; void visit_assignment(const Assignment &n) override; void visit_band(const Band &n) override; void visit_bnot(const Bnot &n) override; void visit_bor(const Bor &n) override; void visit_clear(const Clear &n) override; void visit_constdecl(const ConstDecl &n) override; void visit_div(const Div &n) override; void visit_element(const Element &n) override; void visit_enum(const Enum &n) override; void visit_eq(const Eq &n) override; void visit_errorstmt(const ErrorStmt &n) override; void visit_exists(const Exists &n) override; void visit_exprid(const ExprID &n) override; void visit_field(const Field &n) override; void visit_for(const For &n) override; void visit_forall(const Forall &n) override; void visit_function(const Function &n) override; void visit_functioncall(const FunctionCall &n) override; void visit_geq(const Geq &n) override; void visit_gt(const Gt &n) override; void visit_if(const If &n) override; void visit_ifclause(const IfClause &n) override; void visit_implication(const Implication &n) override; void visit_isundefined(const IsUndefined &n) override; void visit_leq(const Leq &n) override; void visit_lsh(const Lsh &n) override; void visit_lt(const Lt &n) override; void visit_model(const Model &n) override; void visit_mod(const Mod &n) override; void visit_mul(const Mul &n) override; void visit_negative(const Negative &n) override; void visit_neq(const Neq &n) override; void visit_not(const Not &n) override; void visit_number(const Number &n) override; void visit_or(const Or &n) override; void visit_procedurecall(const ProcedureCall &n) override; void visit_property(const Property &n) override; void visit_propertyrule(const PropertyRule &n) override; void visit_propertystmt(const PropertyStmt &n) override; void visit_put(const Put &n) override; void visit_quantifier(const Quantifier &n) override; void visit_range(const Range &n) override; void visit_record(const Record &n) override; void visit_return(const Return &n) override; void visit_rsh(const Rsh &n) override; void visit_ruleset(const Ruleset &n) override; void visit_scalarset(const Scalarset &n) override; void visit_simplerule(const SimpleRule &n) override; void visit_startstate(const StartState &n) override; void visit_sub(const Sub &n) override; void visit_switch(const Switch &n) override; void visit_switchcase(const SwitchCase &n) override; void visit_ternary(const Ternary &n) override; void visit_typedecl(const TypeDecl &n) override; void visit_typeexprid(const TypeExprID &n) override; void visit_undefine(const Undefine &n) override; void visit_vardecl(const VarDecl &n) override; void visit_while(const While &n) override; void visit_xor(const Xor &n) override; // Force class to be abstract virtual ~ConstTraversal() = 0; private: void visit_bexpr(const BinaryExpr &n); void visit_uexpr(const UnaryExpr &n); }; /* Generic base for read-only traversals that only need to act on expressions. * This gives you a default implementation for visitation of any non-expression * node. */ class RUMUR_API_WITH_RTTI ConstExprTraversal : public ConstBaseTraversal { public: void visit_aliasdecl(const AliasDecl &n) final; void visit_aliasrule(const AliasRule &n) final; void visit_aliasstmt(const AliasStmt &n) final; void visit_array(const Array &n) final; void visit_assignment(const Assignment &n) final; void visit_clear(const Clear &n) final; void visit_constdecl(const ConstDecl &n) final; void visit_enum(const Enum &n) final; void visit_errorstmt(const ErrorStmt &n) final; void visit_for(const For &n) final; void visit_function(const Function &n) final; void visit_if(const If &n) final; void visit_ifclause(const IfClause &n) final; void visit_model(const Model &n) final; void visit_procedurecall(const ProcedureCall &n) final; void visit_property(const Property &n) final; void visit_propertyrule(const PropertyRule &n) final; void visit_propertystmt(const PropertyStmt &n) final; void visit_put(const Put &n) final; void visit_quantifier(const Quantifier &n) final; void visit_range(const Range &n) final; void visit_record(const Record &n) final; void visit_return(const Return &n) final; void visit_ruleset(const Ruleset &n) final; void visit_scalarset(const Scalarset &n) final; void visit_simplerule(const SimpleRule &n) final; void visit_startstate(const StartState &n) final; void visit_switch(const Switch &n) final; void visit_switchcase(const SwitchCase &n) final; void visit_typedecl(const TypeDecl &n) final; void visit_typeexprid(const TypeExprID &n) final; void visit_undefine(const Undefine &n) final; void visit_vardecl(const VarDecl &n) final; void visit_while(const While &n) final; virtual ~ConstExprTraversal() = default; }; /* Generic base for read-only traversals that only need to act on statements. * This gives you a default implementation for visitation of any non-statement * node. */ class RUMUR_API_WITH_RTTI ConstStmtTraversal : public ConstBaseTraversal { public: void visit_add(const Add &n) final; void visit_aliasdecl(const AliasDecl &n) final; void visit_aliasrule(const AliasRule &n) final; void visit_and(const And &n) final; void visit_array(const Array &n) final; void visit_band(const Band &n) final; void visit_bnot(const Bnot &n) final; void visit_bor(const Bor &n) final; void visit_constdecl(const ConstDecl &n) final; void visit_div(const Div &n) final; void visit_element(const Element &n) final; void visit_enum(const Enum &n) final; void visit_eq(const Eq &n) final; void visit_exists(const Exists &n) final; void visit_exprid(const ExprID &n) final; void visit_field(const Field &n) final; void visit_forall(const Forall &n) final; void visit_function(const Function &n) final; void visit_functioncall(const FunctionCall &n) final; void visit_geq(const Geq &n) final; void visit_gt(const Gt &n) final; void visit_ifclause(const IfClause &n) final; void visit_implication(const Implication &n) final; void visit_isundefined(const IsUndefined &n) final; void visit_leq(const Leq &n) final; void visit_lsh(const Lsh &n) final; void visit_lt(const Lt &n) final; void visit_model(const Model &n) final; void visit_mod(const Mod &n) final; void visit_mul(const Mul &n) final; void visit_negative(const Negative &n) final; void visit_neq(const Neq &n) final; void visit_not(const Not &n) final; void visit_number(const Number &n) final; void visit_or(const Or &n) final; void visit_property(const Property &n) final; void visit_propertyrule(const PropertyRule &n) final; void visit_quantifier(const Quantifier &n) final; void visit_range(const Range &n) final; void visit_record(const Record &n) final; void visit_rsh(const Rsh &n) final; void visit_ruleset(const Ruleset &n) final; void visit_scalarset(const Scalarset &n) final; void visit_simplerule(const SimpleRule &n) final; void visit_startstate(const StartState &n) final; void visit_sub(const Sub &n) final; void visit_switchcase(const SwitchCase &n) final; void visit_ternary(const Ternary &n) final; void visit_typedecl(const TypeDecl &n) final; void visit_typeexprid(const TypeExprID &n) final; void visit_vardecl(const VarDecl &n) final; void visit_xor(const Xor &n) final; virtual ~ConstStmtTraversal() = default; private: void visit_bexpr(const BinaryExpr &n); void visit_uexpr(const UnaryExpr &n); }; // Generic base for read-only traversals that only need to act on TypeExprs class RUMUR_API_WITH_RTTI ConstTypeTraversal : public ConstBaseTraversal { public: void visit_add(const Add &n) final; void visit_aliasdecl(const AliasDecl &n) final; void visit_aliasrule(const AliasRule &n) final; void visit_aliasstmt(const AliasStmt &n) final; void visit_and(const And &n) final; void visit_assignment(const Assignment &n) final; void visit_band(const Band &n) final; void visit_bnot(const Bnot &n) final; void visit_bor(const Bor &n) final; void visit_clear(const Clear &n) final; void visit_constdecl(const ConstDecl &n) final; void visit_div(const Div &n) final; void visit_element(const Element &n) final; void visit_eq(const Eq &n) final; void visit_errorstmt(const ErrorStmt &n) final; void visit_exists(const Exists &n) final; void visit_exprid(const ExprID &n) final; void visit_field(const Field &n) final; void visit_for(const For &n) final; void visit_forall(const Forall &n) final; void visit_function(const Function &n) final; void visit_functioncall(const FunctionCall &n) final; void visit_geq(const Geq &n) final; void visit_gt(const Gt &n) final; void visit_if(const If &n) final; void visit_ifclause(const IfClause &n) final; void visit_implication(const Implication &n) final; void visit_isundefined(const IsUndefined &n) final; void visit_leq(const Leq &n) final; void visit_lsh(const Lsh &n) final; void visit_lt(const Lt &n) final; void visit_model(const Model &n) final; void visit_mod(const Mod &n) final; void visit_mul(const Mul &n) final; void visit_negative(const Negative &n) final; void visit_neq(const Neq &n) final; void visit_not(const Not &n) final; void visit_number(const Number &n) final; void visit_or(const Or &n) final; void visit_procedurecall(const ProcedureCall &n) final; void visit_property(const Property &n) final; void visit_propertyrule(const PropertyRule &n) final; void visit_propertystmt(const PropertyStmt &n) final; void visit_put(const Put &n) final; void visit_quantifier(const Quantifier &n) final; void visit_return(const Return &n) final; void visit_rsh(const Rsh &n) final; void visit_ruleset(const Ruleset &n) final; void visit_simplerule(const SimpleRule &n) final; void visit_startstate(const StartState &n) final; void visit_sub(const Sub &n) final; void visit_switch(const Switch &n) final; void visit_switchcase(const SwitchCase &n) final; void visit_ternary(const Ternary &n) final; void visit_typedecl(const TypeDecl &n) final; void visit_undefine(const Undefine &n) final; void visit_vardecl(const VarDecl &n) final; void visit_while(const While &n) final; void visit_xor(const Xor &n) final; virtual ~ConstTypeTraversal() = default; private: void visit_bexpr(const BinaryExpr &n); void visit_uexpr(const UnaryExpr &n); }; } // namespace rumur rumur-2024.05.07/librumur/include/rumur/validate.h000066400000000000000000000005451461637631000217000ustar00rootroot00000000000000#pragma once #include #include #include #ifndef RUMUR_API #define RUMUR_API __attribute__((visibility("default"))) #endif namespace rumur { /* Check a node in the AST and all its children for inconsistencies and throw * `rumur::Error` if found. */ RUMUR_API void validate(const Node &n); } // namespace rumur rumur-2024.05.07/librumur/src/000077500000000000000000000000001461637631000157245ustar00rootroot00000000000000rumur-2024.05.07/librumur/src/Boolean.cc000066400000000000000000000015731461637631000176200ustar00rootroot00000000000000#include "location.hh" #include #include #include #include #include #include #include #include #include #include #include using namespace rumur; const Ptr rumur::Boolean = Ptr::make(std::vector>( {{"false", location()}, {"true", location()}}), location()); const Ptr rumur::False = Ptr::make( "false", Ptr::make("boolean", Ptr::make(0, location()), Boolean, location()), location()); const Ptr rumur::True = Ptr::make( "true", Ptr::make("boolean", Ptr::make(1, location()), Boolean, location()), location()); rumur-2024.05.07/librumur/src/Comment.cc000066400000000000000000000071531461637631000176430ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include using namespace rumur; namespace { /// a file interface that supports lookahead class File { private: position pos; std::istream ∈ std::string buffered; public: explicit File(std::istream &in_) : pos(nullptr, 1, 1), in(in_) {} /// read a new character from the file char getchar() { assert(!buffered.empty()); char c = buffered[0]; buffered = buffered.substr(1); if (c == '\n') { pos.lines(); } else { pos.columns(); } return c; } /// read the next `count` characters std::string read(size_t count) { std::string result; for (size_t i = 0; !eof() && i < count; ++i) result += getchar(); return result; } /// lookahead at the next `count` characters while retaining them std::string peek(size_t count = 1) { // buffer enough to support our lookahead while (buffered.size() < count && in) { char c; if (in.read(&c, sizeof(c))) buffered += c; } return buffered.substr(0, count); } /// are the upcoming characters the given string? bool next_is(const std::string &expectation) { return peek(expectation.size()) == expectation; } /// have we reached the end of the file? bool eof() { // do we have known remaining characters? if (!buffered.empty()) return false; // ensure buffer is populated or we have triggered EOF on the stream (void)peek(); return buffered.empty() && in.eof(); } position get_position() const { return pos; } }; } // namespace std::vector rumur::parse_comments(std::istream &input) { std::vector result; for (File in(input); !in.eof();) { // string? { bool is_quote = in.next_is("\""); bool is_smart_quote = in.next_is("“"); if (is_quote || is_smart_quote) { // discard the quote starter (void)in.read(strlen(is_quote ? "\"" : "“")); // swallow the quote itself while (!in.eof()) { if (in.next_is("\\\"")) { (void)in.read(strlen("\\\"")); } else if (in.next_is("\\”")) { (void)in.read(strlen("\\”")); } else if (in.next_is("\"")) { (void)in.read(strlen("\"")); break; } else if (in.next_is("”")) { (void)in.read(strlen("”")); break; } else { (void)in.getchar(); } } continue; } } if (in.next_is("--")) { // single line comment? position begin = in.get_position(); // discard the comment starter (void)in.read(strlen("--")); // consume the comment body std::ostringstream content; while (!in.eof() && !in.next_is("\n")) content << in.getchar(); result.push_back( Comment{content.str(), false, location(begin, in.get_position())}); continue; } else if (in.next_is("/*")) { // multiline comment? position begin = in.get_position(); // discard the comment starter (void)in.read(strlen("/*")); // consume the comment body; std::ostringstream content; while (!in.eof()) { if (in.next_is("*/")) { (void)in.read(strlen("*/")); break; } content << in.getchar(); } result.push_back( Comment{content.str(), true, location(begin, in.get_position())}); } else { // otherwise, something irrelevant (void)in.getchar(); } } return result; } rumur-2024.05.07/librumur/src/Decl.cc000066400000000000000000000066561461637631000171170ustar00rootroot00000000000000#include "location.hh" #include #include #include #include #include #include #include #include #include #include #include namespace rumur { Decl::Decl(const std::string &name_, const location &loc_) : Node(loc_), name(name_) {} Decl::~Decl() {} ExprDecl::ExprDecl(const std::string &name_, const location &loc_) : Decl(name_, loc_) {} AliasDecl::AliasDecl(const std::string &name_, const Ptr &value_, const location &loc_) : ExprDecl(name_, loc_), value(value_) {} AliasDecl *AliasDecl::clone() const { return new AliasDecl(*this); } void AliasDecl::visit(BaseTraversal &visitor) { visitor.visit_aliasdecl(*this); } void AliasDecl::visit(ConstBaseTraversal &visitor) const { visitor.visit_aliasdecl(*this); } bool AliasDecl::is_lvalue() const { return value->is_lvalue(); } bool AliasDecl::is_readonly() const { return value->is_readonly(); } Ptr AliasDecl::get_type() const { return value->type(); } ConstDecl::ConstDecl(const std::string &name_, const Ptr &value_, const location &loc_) : ExprDecl(name_, loc_), value(value_) {} ConstDecl::ConstDecl(const std::string &name_, const Ptr &value_, const Ptr &type_, const location &loc_) : ExprDecl(name_, loc_), value(value_), type(type_) {} ConstDecl *ConstDecl::clone() const { return new ConstDecl(*this); } void ConstDecl::visit(BaseTraversal &visitor) { visitor.visit_constdecl(*this); } void ConstDecl::visit(ConstBaseTraversal &visitor) const { visitor.visit_constdecl(*this); } bool ConstDecl::is_lvalue() const { return false; } bool ConstDecl::is_readonly() const { return true; } Ptr ConstDecl::get_type() const { // If this constant has an explicit type (e.g. it's an enum member), use that. if (type != nullptr) return type; /* If this doesn't have an explicit type, fall back on the type of the value * it points at. This is irrelevant for numerical constants, but important for * boolean constants. */ return value->type(); } void ConstDecl::validate() const { if (!value->constant()) throw Error("const definition is not a constant", value->loc); } TypeDecl::TypeDecl(const std::string &name_, const Ptr &value_, const location &loc_) : Decl(name_, loc_), value(value_) {} TypeDecl *TypeDecl::clone() const { return new TypeDecl(*this); } void TypeDecl::visit(BaseTraversal &visitor) { visitor.visit_typedecl(*this); } void TypeDecl::visit(ConstBaseTraversal &visitor) const { visitor.visit_typedecl(*this); } VarDecl::VarDecl(const std::string &name_, const Ptr &type_, const location &loc_) : ExprDecl(name_, loc_), type(type_) {} VarDecl *VarDecl::clone() const { return new VarDecl(*this); } void VarDecl::visit(BaseTraversal &visitor) { visitor.visit_vardecl(*this); } void VarDecl::visit(ConstBaseTraversal &visitor) const { visitor.visit_vardecl(*this); } bool VarDecl::is_lvalue() const { return true; } bool VarDecl::is_readonly() const { return readonly; } bool VarDecl::is_in_state() const { return offset >= 0; } mpz_class VarDecl::width() const { return type->width(); } mpz_class VarDecl::count() const { return type->count(); } Ptr VarDecl::get_type() const { return type; } } // namespace rumur rumur-2024.05.07/librumur/src/Expr.cc000066400000000000000000001122341461637631000171540ustar00rootroot00000000000000#include "../../common/isa.h" #include "location.hh" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace rumur { Expr::Expr(const location &loc_) : Node(loc_) {} bool Expr::is_boolean() const { return type()->resolve()->is_boolean(); } bool Expr::is_lvalue() const { return false; } bool Expr::is_readonly() const { return !is_lvalue(); } bool Expr::is_literal_true() const { return false; } bool Expr::is_literal_false() const { return false; } Ternary::Ternary(const Ptr &cond_, const Ptr &lhs_, const Ptr &rhs_, const location &loc_) : Expr(loc_), cond(cond_), lhs(lhs_), rhs(rhs_) {} Ternary *Ternary::clone() const { return new Ternary(*this); } void Ternary::visit(BaseTraversal &visitor) { return visitor.visit_ternary(*this); } void Ternary::visit(ConstBaseTraversal &visitor) const { return visitor.visit_ternary(*this); } bool Ternary::constant() const { return cond->constant() && lhs->constant() && rhs->constant(); } Ptr Ternary::type() const { // TODO: assert lhs and rhs are compatible types. return lhs->type(); } mpz_class Ternary::constant_fold() const { return cond->constant_fold() != 0 ? lhs->constant_fold() : rhs->constant_fold(); } void Ternary::validate() const { if (!cond->is_boolean()) throw Error("ternary condition is not a boolean", cond->loc); } std::string Ternary::to_string() const { return "(" + cond->to_string() + " ? " + lhs->to_string() + " : " + rhs->to_string() + ")"; } bool Ternary::is_pure() const { return cond->is_pure() && lhs->is_pure() && rhs->is_pure(); } BinaryExpr::BinaryExpr(const Ptr &lhs_, const Ptr &rhs_, const location &loc_) : Expr(loc_), lhs(lhs_), rhs(rhs_) {} bool BinaryExpr::constant() const { return lhs->constant() && rhs->constant(); } bool BinaryExpr::is_pure() const { return lhs->is_pure() && rhs->is_pure(); } BooleanBinaryExpr::BooleanBinaryExpr(const Ptr &lhs_, const Ptr &rhs_, const location &loc_) : BinaryExpr(lhs_, rhs_, loc_) {} Ptr BooleanBinaryExpr::type() const { return Boolean; } void BooleanBinaryExpr::validate() const { if (!lhs->is_boolean()) throw Error("left hand side of expression is not a boolean", lhs->loc); if (!rhs->is_boolean()) throw Error("right hand side of expression is not a boolean", rhs->loc); } Implication::Implication(const Ptr &lhs_, const Ptr &rhs_, const location &loc_) : BooleanBinaryExpr(lhs_, rhs_, loc_) {} Implication *Implication::clone() const { return new Implication(*this); } void Implication::visit(BaseTraversal &visitor) { return visitor.visit_implication(*this); } void Implication::visit(ConstBaseTraversal &visitor) const { return visitor.visit_implication(*this); } mpz_class Implication::constant_fold() const { return lhs->constant_fold() == 0 || rhs->constant_fold() != 0; } std::string Implication::to_string() const { return "(" + lhs->to_string() + " -> " + rhs->to_string() + ")"; } Or::Or(const Ptr &lhs_, const Ptr &rhs_, const location &loc_) : BooleanBinaryExpr(lhs_, rhs_, loc_) {} Or *Or::clone() const { return new Or(*this); } void Or::visit(BaseTraversal &visitor) { return visitor.visit_or(*this); } void Or::visit(ConstBaseTraversal &visitor) const { return visitor.visit_or(*this); } mpz_class Or::constant_fold() const { return lhs->constant_fold() != 0 || rhs->constant_fold() != 0; } std::string Or::to_string() const { return "(" + lhs->to_string() + " | " + rhs->to_string() + ")"; } And::And(const Ptr &lhs_, const Ptr &rhs_, const location &loc_) : BooleanBinaryExpr(lhs_, rhs_, loc_) {} And *And::clone() const { return new And(*this); } void And::visit(BaseTraversal &visitor) { return visitor.visit_and(*this); } void And::visit(ConstBaseTraversal &visitor) const { return visitor.visit_and(*this); } mpz_class And::constant_fold() const { return lhs->constant_fold() != 0 && rhs->constant_fold() != 0; } std::string And::to_string() const { return "(" + lhs->to_string() + " & " + rhs->to_string() + ")"; } AmbiguousAmp::AmbiguousAmp(const Ptr &lhs_, const Ptr &rhs_, const location &loc_) : BinaryExpr(lhs_, rhs_, loc_) {} AmbiguousAmp *AmbiguousAmp::clone() const { return new AmbiguousAmp(*this); } void AmbiguousAmp::visit(BaseTraversal &visitor) { return visitor.visit_ambiguousamp(*this); } void AmbiguousAmp::visit(ConstBaseTraversal &visitor) const { return visitor.visit_ambiguousamp(*this); } Ptr AmbiguousAmp::type() const { // we cannot retrieve the type of this expression because it has no certain // type until we decide whether it is a logical AND or bitwise AND throw Error("cannot retrieve the type of an unresolved '&' expression", loc); } mpz_class AmbiguousAmp::constant_fold() const { // we cannot constant fold this if we do not yet know whether it is a logical // AND or a bitwise AND throw Error("cannot constant fold an unresolved '&' expression", loc); } std::string AmbiguousAmp::to_string() const { return "(" + lhs->to_string() + " & " + rhs->to_string() + ")"; } AmbiguousPipe::AmbiguousPipe(const Ptr &lhs_, const Ptr &rhs_, const location &loc_) : BinaryExpr(lhs_, rhs_, loc_) {} AmbiguousPipe *AmbiguousPipe::clone() const { return new AmbiguousPipe(*this); } void AmbiguousPipe::visit(BaseTraversal &visitor) { return visitor.visit_ambiguouspipe(*this); } void AmbiguousPipe::visit(ConstBaseTraversal &visitor) const { return visitor.visit_ambiguouspipe(*this); } Ptr AmbiguousPipe::type() const { // we cannot retrieve the type of this expression because it has no certain // type until we decide whether it is a logical OR or bitwise OR throw Error("cannot retrieve the type of an unresolved '|' expression", loc); } mpz_class AmbiguousPipe::constant_fold() const { // we cannot constant fold this if we do not yet know whether it is a logical // OR or a bitwise OR throw Error("cannot constant fold an unresolved '|' expression", loc); } std::string AmbiguousPipe::to_string() const { return "(" + lhs->to_string() + " | " + rhs->to_string() + ")"; } UnaryExpr::UnaryExpr(const Ptr &rhs_, const location &loc_) : Expr(loc_), rhs(rhs_) {} bool UnaryExpr::constant() const { return rhs->constant(); } bool UnaryExpr::is_pure() const { return rhs->is_pure(); } Not::Not(const Ptr &rhs_, const location &loc_) : UnaryExpr(rhs_, loc_) {} Not *Not::clone() const { return new Not(*this); } void Not::visit(BaseTraversal &visitor) { return visitor.visit_not(*this); } void Not::visit(ConstBaseTraversal &visitor) const { return visitor.visit_not(*this); } Ptr Not::type() const { return Boolean; } mpz_class Not::constant_fold() const { return rhs->constant_fold() == 0; } void Not::validate() const { if (!rhs->is_boolean()) throw Error("argument to ! is not a boolean", rhs->loc); } std::string Not::to_string() const { return "(!" + rhs->to_string() + ")"; } static bool comparable(const Expr &lhs, const Expr &rhs) { const Ptr t1 = lhs.type()->resolve(); const Ptr t2 = rhs.type()->resolve(); if (isa(t1)) { if (isa(t2)) return true; } return false; } ComparisonBinaryExpr::ComparisonBinaryExpr(const Ptr &lhs_, const Ptr &rhs_, const location &loc_) : BinaryExpr(lhs_, rhs_, loc_) {} void ComparisonBinaryExpr::validate() const { if (!comparable(*lhs, *rhs)) throw Error("expressions are not comparable", loc); } Lt::Lt(const Ptr &lhs_, const Ptr &rhs_, const location &loc_) : ComparisonBinaryExpr(lhs_, rhs_, loc_) {} Lt *Lt::clone() const { return new Lt(*this); } void Lt::visit(BaseTraversal &visitor) { return visitor.visit_lt(*this); } void Lt::visit(ConstBaseTraversal &visitor) const { return visitor.visit_lt(*this); } Ptr Lt::type() const { return Boolean; } mpz_class Lt::constant_fold() const { return lhs->constant_fold() < rhs->constant_fold(); } std::string Lt::to_string() const { return "(" + lhs->to_string() + " < " + rhs->to_string() + ")"; } Leq::Leq(const Ptr &lhs_, const Ptr &rhs_, const location &loc_) : ComparisonBinaryExpr(lhs_, rhs_, loc_) {} Leq *Leq::clone() const { return new Leq(*this); } void Leq::visit(BaseTraversal &visitor) { return visitor.visit_leq(*this); } void Leq::visit(ConstBaseTraversal &visitor) const { return visitor.visit_leq(*this); } Ptr Leq::type() const { return Boolean; } mpz_class Leq::constant_fold() const { return lhs->constant_fold() <= rhs->constant_fold(); } std::string Leq::to_string() const { return "(" + lhs->to_string() + " <= " + rhs->to_string() + ")"; } Gt::Gt(const Ptr &lhs_, const Ptr &rhs_, const location &loc_) : ComparisonBinaryExpr(lhs_, rhs_, loc_) {} Gt *Gt::clone() const { return new Gt(*this); } void Gt::visit(BaseTraversal &visitor) { return visitor.visit_gt(*this); } void Gt::visit(ConstBaseTraversal &visitor) const { return visitor.visit_gt(*this); } Ptr Gt::type() const { return Boolean; } mpz_class Gt::constant_fold() const { return lhs->constant_fold() > rhs->constant_fold(); } std::string Gt::to_string() const { return "(" + lhs->to_string() + " > " + rhs->to_string() + ")"; } Geq::Geq(const Ptr &lhs_, const Ptr &rhs_, const location &loc_) : ComparisonBinaryExpr(lhs_, rhs_, loc_) {} Geq *Geq::clone() const { return new Geq(*this); } void Geq::visit(BaseTraversal &visitor) { return visitor.visit_geq(*this); } void Geq::visit(ConstBaseTraversal &visitor) const { return visitor.visit_geq(*this); } Ptr Geq::type() const { return Boolean; } mpz_class Geq::constant_fold() const { return lhs->constant_fold() >= rhs->constant_fold(); } std::string Geq::to_string() const { return "(" + lhs->to_string() + " >= " + rhs->to_string() + ")"; } EquatableBinaryExpr::EquatableBinaryExpr(const Ptr &lhs_, const Ptr &rhs_, const location &loc_) : BinaryExpr(lhs_, rhs_, loc_) {} void EquatableBinaryExpr::validate() const { if (!lhs->type()->coerces_to(*rhs->type())) throw Error("expressions are not comparable", loc); } Eq::Eq(const Ptr &lhs_, const Ptr &rhs_, const location &loc_) : EquatableBinaryExpr(lhs_, rhs_, loc_) {} Eq *Eq::clone() const { return new Eq(*this); } void Eq::visit(BaseTraversal &visitor) { return visitor.visit_eq(*this); } void Eq::visit(ConstBaseTraversal &visitor) const { return visitor.visit_eq(*this); } Ptr Eq::type() const { return Boolean; } mpz_class Eq::constant_fold() const { return lhs->constant_fold() == rhs->constant_fold(); } std::string Eq::to_string() const { return "(" + lhs->to_string() + " = " + rhs->to_string() + ")"; } Neq::Neq(const Ptr &lhs_, const Ptr &rhs_, const location &loc_) : EquatableBinaryExpr(lhs_, rhs_, loc_) {} Neq *Neq::clone() const { return new Neq(*this); } void Neq::visit(BaseTraversal &visitor) { return visitor.visit_neq(*this); } void Neq::visit(ConstBaseTraversal &visitor) const { return visitor.visit_neq(*this); } Ptr Neq::type() const { return Boolean; } mpz_class Neq::constant_fold() const { return lhs->constant_fold() != rhs->constant_fold(); } std::string Neq::to_string() const { return "(" + lhs->to_string() + " != " + rhs->to_string() + ")"; } static bool arithmetic(const Expr &lhs, const Expr &rhs) { const Ptr t1 = lhs.type()->resolve(); const Ptr t2 = rhs.type()->resolve(); if (isa(t1) && isa(t2)) return true; return false; } ArithmeticBinaryExpr::ArithmeticBinaryExpr(const Ptr &lhs_, const Ptr &rhs_, const location &loc_) : BinaryExpr(lhs_, rhs_, loc_) {} void ArithmeticBinaryExpr::validate() const { if (!arithmetic(*lhs, *rhs)) throw Error("expressions are incompatible in arithmetic expression", loc); } Ptr ArithmeticBinaryExpr::type() const { return Ptr::make(nullptr, nullptr, location()); } Add::Add(const Ptr &lhs_, const Ptr &rhs_, const location &loc_) : ArithmeticBinaryExpr(lhs_, rhs_, loc_) {} Add *Add::clone() const { return new Add(*this); } void Add::visit(BaseTraversal &visitor) { return visitor.visit_add(*this); } void Add::visit(ConstBaseTraversal &visitor) const { return visitor.visit_add(*this); } mpz_class Add::constant_fold() const { return lhs->constant_fold() + rhs->constant_fold(); } std::string Add::to_string() const { return "(" + lhs->to_string() + " + " + rhs->to_string() + ")"; } Sub::Sub(const Ptr &lhs_, const Ptr &rhs_, const location &loc_) : ArithmeticBinaryExpr(lhs_, rhs_, loc_) {} Sub *Sub::clone() const { return new Sub(*this); } void Sub::visit(BaseTraversal &visitor) { return visitor.visit_sub(*this); } void Sub::visit(ConstBaseTraversal &visitor) const { return visitor.visit_sub(*this); } mpz_class Sub::constant_fold() const { return lhs->constant_fold() - rhs->constant_fold(); } std::string Sub::to_string() const { return "(" + lhs->to_string() + " - " + rhs->to_string() + ")"; } Negative::Negative(const Ptr &rhs_, const location &loc_) : UnaryExpr(rhs_, loc_) {} void Negative::validate() const { if (!isa(rhs->type()->resolve())) throw Error("expression cannot be negated", rhs->loc); } Negative *Negative::clone() const { return new Negative(*this); } void Negative::visit(BaseTraversal &visitor) { return visitor.visit_negative(*this); } void Negative::visit(ConstBaseTraversal &visitor) const { return visitor.visit_negative(*this); } Ptr Negative::type() const { return Ptr::make(nullptr, nullptr, location()); } mpz_class Negative::constant_fold() const { return -rhs->constant_fold(); } std::string Negative::to_string() const { return "(-" + rhs->to_string() + ")"; } Bnot::Bnot(const Ptr &rhs_, const location &loc_) : UnaryExpr(rhs_, loc_) {} void Bnot::validate() const { if (!isa(rhs->type()->resolve())) throw Error("expression cannot be bitwise NOTed", rhs->loc); } Bnot *Bnot::clone() const { return new Bnot(*this); } void Bnot::visit(BaseTraversal &visitor) { return visitor.visit_bnot(*this); } void Bnot::visit(ConstBaseTraversal &visitor) const { return visitor.visit_bnot(*this); } Ptr Bnot::type() const { return Ptr::make(nullptr, nullptr, location()); } mpz_class Bnot::constant_fold() const { return ~rhs->constant_fold(); } std::string Bnot::to_string() const { return "(~" + rhs->to_string() + ")"; } Mul::Mul(const Ptr &lhs_, const Ptr &rhs_, const location &loc_) : ArithmeticBinaryExpr(lhs_, rhs_, loc_) {} Mul *Mul::clone() const { return new Mul(*this); } void Mul::visit(BaseTraversal &visitor) { return visitor.visit_mul(*this); } void Mul::visit(ConstBaseTraversal &visitor) const { return visitor.visit_mul(*this); } mpz_class Mul::constant_fold() const { return lhs->constant_fold() * rhs->constant_fold(); } std::string Mul::to_string() const { return "(" + lhs->to_string() + " * " + rhs->to_string() + ")"; } Div::Div(const Ptr &lhs_, const Ptr &rhs_, const location &loc_) : ArithmeticBinaryExpr(lhs_, rhs_, loc_) {} Div *Div::clone() const { return new Div(*this); } void Div::visit(BaseTraversal &visitor) { return visitor.visit_div(*this); } void Div::visit(ConstBaseTraversal &visitor) const { return visitor.visit_div(*this); } mpz_class Div::constant_fold() const { mpz_class a = lhs->constant_fold(); mpz_class b = rhs->constant_fold(); if (b == 0) throw Error("division by 0 in " + a.get_str() + " / " + b.get_str(), loc); return a / b; } std::string Div::to_string() const { return "(" + lhs->to_string() + " / " + rhs->to_string() + ")"; } Mod::Mod(const Ptr &lhs_, const Ptr &rhs_, const location &loc_) : ArithmeticBinaryExpr(lhs_, rhs_, loc_) {} Mod *Mod::clone() const { return new Mod(*this); } void Mod::visit(BaseTraversal &visitor) { return visitor.visit_mod(*this); } void Mod::visit(ConstBaseTraversal &visitor) const { return visitor.visit_mod(*this); } mpz_class Mod::constant_fold() const { mpz_class a = lhs->constant_fold(); mpz_class b = rhs->constant_fold(); if (b == 0) throw Error("mod by 0 in " + a.get_str() + " % " + b.get_str(), loc); return a % b; } std::string Mod::to_string() const { return "(" + lhs->to_string() + " % " + rhs->to_string() + ")"; } Lsh::Lsh(const Ptr &lhs_, const Ptr &rhs_, const location &loc_) : ArithmeticBinaryExpr(lhs_, rhs_, loc_) {} Lsh *Lsh::clone() const { return new Lsh(*this); } void Lsh::visit(BaseTraversal &visitor) { return visitor.visit_lsh(*this); } void Lsh::visit(ConstBaseTraversal &visitor) const { return visitor.visit_lsh(*this); } // right shift an mpz value static mpz_class rshift(mpz_class a, mpz_class b); // left shift an mpz value static mpz_class lshift(mpz_class a, mpz_class b) { // is this actually a right shift? if (b < 0) return rshift(a, -b); // if the shift is beyond what we can do in one shot, recurse while (!b.fits_ulong_p()) { a = lshift(a, mpz_class(ULONG_MAX)); b -= ULONG_MAX; } // extract the shift value into a bit count mp_bitcnt_t l = static_cast(b.get_ui()); // do a left shift using the GMP C API mpz_t rop; mpz_init(rop); mpz_mul_2exp(rop, a.get_mpz_t(), l); return mpz_class(rop); } static mpz_class rshift(mpz_class a, mpz_class b) { // is this actually a left shift? if (b < 0) return lshift(a, -b); // if the shift is beyond what we can do in one shot, recurse while (!b.fits_ulong_p()) { a = rshift(a, mpz_class(ULONG_MAX)); b -= ULONG_MAX; } // extract the shift value into a bit count mp_bitcnt_t r = static_cast(b.get_ui()); // do a right shift using the GMP C API mpz_t rop; mpz_init(rop); mpz_fdiv_q_2exp(rop, a.get_mpz_t(), r); return mpz_class(rop); } mpz_class Lsh::constant_fold() const { mpz_class a = lhs->constant_fold(); mpz_class b = rhs->constant_fold(); return lshift(a, b); } std::string Lsh::to_string() const { return "(" + lhs->to_string() + " << " + rhs->to_string() + ")"; } Rsh::Rsh(const Ptr &lhs_, const Ptr &rhs_, const location &loc_) : ArithmeticBinaryExpr(lhs_, rhs_, loc_) {} Rsh *Rsh::clone() const { return new Rsh(*this); } void Rsh::visit(BaseTraversal &visitor) { return visitor.visit_rsh(*this); } void Rsh::visit(ConstBaseTraversal &visitor) const { return visitor.visit_rsh(*this); } mpz_class Rsh::constant_fold() const { mpz_class a = lhs->constant_fold(); mpz_class b = rhs->constant_fold(); return rshift(a, b); } std::string Rsh::to_string() const { return "(" + lhs->to_string() + " >> " + rhs->to_string() + ")"; } Band::Band(const Ptr &lhs_, const Ptr &rhs_, const location &loc_) : ArithmeticBinaryExpr(lhs_, rhs_, loc_) {} Band *Band::clone() const { return new Band(*this); } void Band::visit(BaseTraversal &visitor) { return visitor.visit_band(*this); } void Band::visit(ConstBaseTraversal &visitor) const { return visitor.visit_band(*this); } mpz_class Band::constant_fold() const { mpz_class a = lhs->constant_fold(); mpz_class b = rhs->constant_fold(); return a & b; } std::string Band::to_string() const { return "(" + lhs->to_string() + " & " + rhs->to_string() + ")"; } Bor::Bor(const Ptr &lhs_, const Ptr &rhs_, const location &loc_) : ArithmeticBinaryExpr(lhs_, rhs_, loc_) {} Bor *Bor::clone() const { return new Bor(*this); } void Bor::visit(BaseTraversal &visitor) { return visitor.visit_bor(*this); } void Bor::visit(ConstBaseTraversal &visitor) const { return visitor.visit_bor(*this); } mpz_class Bor::constant_fold() const { mpz_class a = lhs->constant_fold(); mpz_class b = rhs->constant_fold(); return a | b; } std::string Bor::to_string() const { return "(" + lhs->to_string() + " | " + rhs->to_string() + ")"; } Xor::Xor(const Ptr &lhs_, const Ptr &rhs_, const location &loc_) : ArithmeticBinaryExpr(lhs_, rhs_, loc_) {} Xor *Xor::clone() const { return new Xor(*this); } void Xor::visit(BaseTraversal &visitor) { return visitor.visit_xor(*this); } void Xor::visit(ConstBaseTraversal &visitor) const { return visitor.visit_xor(*this); } mpz_class Xor::constant_fold() const { mpz_class a = lhs->constant_fold(); mpz_class b = rhs->constant_fold(); return a ^ b; } std::string Xor::to_string() const { return "(" + lhs->to_string() + " ^ " + rhs->to_string() + ")"; } ExprID::ExprID(const std::string &id_, const Ptr &value_, const location &loc_) : Expr(loc_), id(id_), value(value_) {} ExprID *ExprID::clone() const { return new ExprID(*this); } void ExprID::visit(BaseTraversal &visitor) { return visitor.visit_exprid(*this); } void ExprID::visit(ConstBaseTraversal &visitor) const { return visitor.visit_exprid(*this); } bool ExprID::constant() const { if (isa(value)) return true; if (auto a = dynamic_cast(value.get())) return a->value->constant(); return false; } Ptr ExprID::type() const { if (value == nullptr) throw Error("symbol \"" + id + "\" in expression is unresolved", loc); return value->get_type(); } mpz_class ExprID::constant_fold() const { if (auto c = dynamic_cast(value.get())) return c->value->constant_fold(); if (auto a = dynamic_cast(value.get())) return a->value->constant_fold(); throw Error("symbol \"" + id + "\" is not a constant", loc); } void ExprID::validate() const { if (value == nullptr) throw Error("unresolved expression \"" + id + "\"", loc); } bool ExprID::is_lvalue() const { if (value == nullptr) throw Error("unresolved expression \"" + id + "\"", loc); return value->is_lvalue(); } bool ExprID::is_readonly() const { return value->is_readonly(); } std::string ExprID::to_string() const { return id; } bool ExprID::is_literal_true() const { // It is not possible to shadow “true,” so we simply need to check the text of // this expression. Boolean literals are normalised to lower case during // lexing (see lexer.l) so we do not need to worry about case. return id == "true"; } bool ExprID::is_literal_false() const { return id == "false"; } bool ExprID::is_pure() const { return true; } Field::Field(const Ptr &record_, const std::string &field_, const location &loc_) : Expr(loc_), record(record_), field(field_) {} Field *Field::clone() const { return new Field(*this); } void Field::visit(BaseTraversal &visitor) { return visitor.visit_field(*this); } void Field::visit(ConstBaseTraversal &visitor) const { return visitor.visit_field(*this); } bool Field::constant() const { return false; } Ptr Field::type() const { const Ptr root = record->type(); const Ptr resolved = root->resolve(); auto r = dynamic_cast(resolved.get()); // if we are called before types have been resolved (or during type // resolution on a malformed model), it is possible that the root is not a // record if (r == nullptr) throw Error("invalid left hand side of field expression", loc); for (const Ptr &f : r->fields) { if (f->name == field) return f->type; } throw Error("no field named \"" + field + "\" in record", loc); } mpz_class Field::constant_fold() const { throw Error("field expression used in constant", loc); } void Field::validate() const { const Ptr root = record->type()->resolve(); if (!isa(root)) throw Error("left hand side of field expression is not a record", loc); auto r = dynamic_cast(*root); for (const Ptr &f : r.fields) { if (f->name == field) return; } throw Error("no field named \"" + field + "\" in record", loc); } bool Field::is_lvalue() const { return record->is_lvalue(); } bool Field::is_readonly() const { return record->is_readonly(); } std::string Field::to_string() const { return record->to_string() + "." + field; } bool Field::is_pure() const { return true; } Element::Element(const Ptr &array_, const Ptr &index_, const location &loc_) : Expr(loc_), array(array_), index(index_) {} Element *Element::clone() const { return new Element(*this); } void Element::visit(BaseTraversal &visitor) { return visitor.visit_element(*this); } void Element::visit(ConstBaseTraversal &visitor) const { return visitor.visit_element(*this); } bool Element::constant() const { return false; } Ptr Element::type() const { const Ptr t = array->type()->resolve(); const Array *a = dynamic_cast(t.get()); // if we are called during symbol resolution on a malformed expression, our // left hand side may not be an array if (a == nullptr) throw Error("array reference based on something that is not an array", loc); return a->element_type; } mpz_class Element::constant_fold() const { throw Error("array element used in constant", loc); } void Element::validate() const { const Ptr t = array->type()->resolve(); ; if (!isa(t)) throw Error("array index on an expression that is not an array", loc); auto a = dynamic_cast(*t); if (!index->type()->coerces_to(*a.index_type)) throw Error("array indexed using an expression of incorrect type", loc); } bool Element::is_lvalue() const { return array->is_lvalue(); } bool Element::is_readonly() const { return array->is_readonly(); } std::string Element::to_string() const { return array->to_string() + "[" + index->to_string() + "]"; } bool Element::is_pure() const { return true; } FunctionCall::FunctionCall(const std::string &name_, const std::vector> &arguments_, const location &loc_) : Expr(loc_), name(name_), arguments(arguments_) {} FunctionCall *FunctionCall::clone() const { return new FunctionCall(*this); } void FunctionCall::visit(BaseTraversal &visitor) { return visitor.visit_functioncall(*this); } void FunctionCall::visit(ConstBaseTraversal &visitor) const { return visitor.visit_functioncall(*this); } bool FunctionCall::constant() const { /* TODO: For now, we conservatively treat function calls as non-constant. In * future, it would be nice to lift this restriction to support more advanced * parameterised models. */ return false; } Ptr FunctionCall::type() const { if (function == nullptr) throw Error("unresolved function call \"" + name + "\"", loc); if (function->return_type == nullptr) throw Error("procedure calls have no type", loc); return function->return_type; } mpz_class FunctionCall::constant_fold() const { // See FunctionCall::constant() regarding conservatism here. throw Error("function call used in a constant", loc); } void FunctionCall::validate() const { if (function == nullptr) throw Error("unknown function call \"" + name + "\"", loc); if (arguments.size() != function->parameters.size()) throw Error("incorrect number of parameters passed to function", loc); if (!within_procedure_call && function->return_type == nullptr) throw Error("procedure (function with no return value) called in " "expression", loc); auto it = arguments.begin(); for (const Ptr &v : function->parameters) { assert(it != arguments.end() && "mismatch in size of parameter list and " "function arguments list"); if ((*it)->is_readonly() && !v->is_readonly()) throw Error("function call passes a read-only value as a var parameter", (*it)->loc); if (!(*it)->type()->coerces_to(*v->get_type())) throw Error("function call contains parameter of incorrect type", (*it)->loc); const Ptr param_type = v->get_type()->resolve(); // if this is a writable range-typed parameter, we additionally require it // to be of exactly the same type in order to guarantee the caller’s and // callee’s handles are compatible if (!v->is_readonly() && isa(param_type)) { const Ptr arg_type = (*it)->type()->resolve(); assert(isa(arg_type) && "non-range considered type-compatible with range"); auto p = dynamic_cast(*param_type); auto a = dynamic_cast(*arg_type); if (p.min->constant_fold() != a.min->constant_fold() || p.max->constant_fold() != a.max->constant_fold()) throw Error("range types of function call argument and var parameter " "differ", (*it)->loc); } it++; } } std::string FunctionCall::to_string() const { std::string s = name + "("; bool first = true; for (const Ptr &arg : arguments) { if (!first) s += ", "; s += arg->to_string(); } s += ")"; return s; } bool FunctionCall::is_pure() const { // If this is a recursive call within a function, it may have no referent. In // this case, conservatively assume it is impure. if (function == nullptr) return false; // if the function itself has side effects, the function call is impure if (!function->is_pure()) return false; // if any of the parameters have side effects, the function call is impure for (const Ptr &a : arguments) { if (!a->is_pure()) return false; } return true; } Quantifier::Quantifier(const std::string &name_, const Ptr &type_, const location &loc_) : Node(loc_), name(name_), type(type_), decl(Ptr::make(name_, type_, loc_)) {} Quantifier::Quantifier(const std::string &name_, const Ptr &from_, const Ptr &to_, const location &loc_) : Quantifier(name_, from_, to_, nullptr, loc_) {} Quantifier::Quantifier(const std::string &name_, const Ptr &from_, const Ptr &to_, const Ptr &step_, const location &loc_) : Node(loc_), name(name_), from(from_), to(to_), step(step_), // we construct an artificial unbounded range here because we do not know // whether the bounds of this iteration are constant prior to symbol // resolution decl(Ptr::make(name_, Ptr::make(nullptr, nullptr, loc_), loc_)) {} Quantifier *Quantifier::clone() const { return new Quantifier(*this); } void Quantifier::visit(BaseTraversal &visitor) { return visitor.visit_quantifier(*this); } void Quantifier::visit(ConstBaseTraversal &visitor) const { return visitor.visit_quantifier(*this); } void Quantifier::validate() const { bool from_const = from != nullptr && from->constant(); bool to_const = to != nullptr && to->constant(); bool step_const = step != nullptr && step->constant(); if (step_const && step->constant_fold() == 0) throw Error("infinite loop due to 0 step", loc); bool step_positive = step == nullptr || (step_const && step->constant_fold() > 0); bool step_negative = step != nullptr && step_const && step->constant_fold() < 0; if (from_const && to_const) { bool up_count = from->constant_fold() < to->constant_fold(); bool down_count = from->constant_fold() > to->constant_fold(); if (up_count && step_negative) throw Error("infinite loop due to inverted step", loc); if (down_count && step_positive) throw Error("infinite loop due to inverted step", loc); } } std::string Quantifier::to_string() const { if (type == nullptr) { std::string s = name + " from " + from->to_string() + " to " + to->to_string(); if (step != nullptr) s += " by " + step->to_string(); return s; } return name + " : " + type->to_string(); } bool Quantifier::constant() const { if (type != nullptr) { assert(type->is_simple() && "complex type used in quantifier"); if (!type->constant()) return false; } if (from != nullptr && !from->constant()) return false; if (to != nullptr && !to->constant()) return false; if (step != nullptr && !step->constant()) return false; return true; } mpz_class Quantifier::count() const { if (!constant()) throw Error("non-constant quantifier is uncountable", loc); if (type != nullptr) { // subtract 1 because quantified variable can never be 'undefined' return type->count() - 1; } assert(from != nullptr && to != nullptr && "quantifier with null type and bounds"); mpz_class lb = from->constant_fold(); mpz_class ub = to->constant_fold(); mpz_class inc = step == nullptr ? 1 : step->constant_fold(); mpz_class c = 0; for (mpz_class i = lb; i <= ub; i += inc) c++; return c; } std::string Quantifier::lower_bound() const { if (!constant()) throw Error("non-constant quantifier has a lower bound that cannot be " "calculated ahead of time", loc); if (type != nullptr) return type->lower_bound(); assert(from != nullptr && "quantifier with null type and null lower bound"); return "VALUE_C(" + from->constant_fold().get_str() + ")"; } bool Quantifier::is_pure() const { if (type != nullptr) { const Ptr t = type->resolve(); if (auto r = dynamic_cast(t.get())) return r->min->is_pure() && r->max->is_pure(); if (auto s = dynamic_cast(t.get())) return s->bound->is_pure(); assert(dynamic_cast(t.get()) != nullptr && "complex type encountered in quantifier"); return true; } if (from != nullptr && !from->is_pure()) return false; if (to != nullptr && !to->is_pure()) return false; if (step != nullptr && !step->is_pure()) return false; return true; } Exists::Exists(const Quantifier &quantifier_, const Ptr &expr_, const location &loc_) : Expr(loc_), quantifier(quantifier_), expr(expr_) {} Exists *Exists::clone() const { return new Exists(*this); } void Exists::visit(BaseTraversal &visitor) { return visitor.visit_exists(*this); } void Exists::visit(ConstBaseTraversal &visitor) const { return visitor.visit_exists(*this); } bool Exists::constant() const { return expr->constant(); } Ptr Exists::type() const { return Boolean; } mpz_class Exists::constant_fold() const { throw Error("exists expression used in constant", loc); } void Exists::validate() const { if (!expr->is_boolean()) throw Error("expression in exists is not boolean", expr->loc); } std::string Exists::to_string() const { return "exists " + quantifier.to_string() + " do " + expr->to_string() + " endexists"; } bool Exists::is_pure() const { return quantifier.is_pure() && expr->is_pure(); } Forall::Forall(const Quantifier &quantifier_, const Ptr &expr_, const location &loc_) : Expr(loc_), quantifier(quantifier_), expr(expr_) {} Forall *Forall::clone() const { return new Forall(*this); } void Forall::visit(BaseTraversal &visitor) { return visitor.visit_forall(*this); } void Forall::visit(ConstBaseTraversal &visitor) const { return visitor.visit_forall(*this); } bool Forall::constant() const { return expr->constant(); } Ptr Forall::type() const { return Boolean; } mpz_class Forall::constant_fold() const { throw Error("forall expression used in constant", loc); } void Forall::validate() const { if (!expr->is_boolean()) throw Error("expression in forall is not boolean", expr->loc); } std::string Forall::to_string() const { return "forall " + quantifier.to_string() + " do " + expr->to_string() + " endforall"; } bool Forall::is_pure() const { return quantifier.is_pure() && expr->is_pure(); } IsUndefined::IsUndefined(const Ptr &expr_, const location &loc_) : UnaryExpr(expr_, loc_) {} IsUndefined *IsUndefined::clone() const { return new IsUndefined(*this); } void IsUndefined::visit(BaseTraversal &visitor) { return visitor.visit_isundefined(*this); } void IsUndefined::visit(ConstBaseTraversal &visitor) const { return visitor.visit_isundefined(*this); } bool IsUndefined::constant() const { return false; } Ptr IsUndefined::type() const { return Boolean; } mpz_class IsUndefined::constant_fold() const { throw Error("isundefined used in constant", loc); } void IsUndefined::validate() const { if (!rhs->is_lvalue()) throw Error("non-lvalue expression cannot be used in isundefined", rhs->loc); const Ptr t = rhs->type(); if (!t->is_simple()) throw Error("complex type used in isundefined", rhs->loc); } std::string IsUndefined::to_string() const { return "isundefined(" + rhs->to_string() + ")"; } } // namespace rumur rumur-2024.05.07/librumur/src/Function.cc000066400000000000000000000114251461637631000200230ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include namespace rumur { Function::Function(const std::string &name_, const std::vector> ¶meters_, const Ptr &return_type_, const std::vector> &decls_, const std::vector> &body_, const location &loc_) : Node(loc_), name(name_), parameters(parameters_), return_type(return_type_), decls(decls_), body(body_) {} Function *Function::clone() const { return new Function(*this); } void Function::validate() const { /*Define a traversal that checks our contained return statements for * correctness. */ class ReturnChecker : public ConstTraversal { private: const TypeExpr *return_type; public: ReturnChecker(const TypeExpr *rt) : return_type(rt) {} void visit_return(const Return &n) final { if (return_type == nullptr) { if (n.expr != nullptr) throw Error("statement returns a value from a procedure", n.loc); } else { if (n.expr == nullptr) throw Error("empty return statement in a function", n.loc); if (!n.expr->type()->coerces_to(*return_type)) throw Error("returning incompatible typed value from a function", n.loc); } } virtual ~ReturnChecker() = default; }; // Run the checker ReturnChecker rt(return_type.get()); for (auto &s : body) rt.dispatch(*s); } void Function::visit(BaseTraversal &visitor) { visitor.visit_function(*this); } void Function::visit(ConstBaseTraversal &visitor) const { visitor.visit_function(*this); } bool Function::is_pure() const { // if the function takes a var parameter, consider it impure for (const Ptr &p : parameters) { if (!p->is_readonly()) return false; } // a traversal that looks for impure features class PurityDetector : public ConstTraversal { private: const Function *root; // is this a reference to a state variable? bool is_global_ref(const ExprDecl &expr) const { if (auto e = dynamic_cast(&expr)) return is_global_ref(*e->value); if (auto e = dynamic_cast(&expr)) return e->is_in_state(); return false; } bool is_global_ref(const Expr &expr) const { if (auto e = dynamic_cast(&expr)) return is_global_ref(*e->value); if (auto e = dynamic_cast(&expr)) return is_global_ref(*e->record); if (auto e = dynamic_cast(&expr)) return is_global_ref(*e->array); return false; } public: bool pure = true; PurityDetector(const Function &root_) : root(&root_) {} void visit_assignment(const Assignment &n) final { pure &= !is_global_ref(*n.lhs); dispatch(*n.lhs); dispatch(*n.rhs); } void visit_clear(const Clear &n) final { pure &= !is_global_ref(*n.rhs); dispatch(*n.rhs); } void visit_errorstmt(const ErrorStmt &) final { // treat any error as a side effect pure = false; } void visit_functioncall(const FunctionCall &n) final { assert(n.function != nullptr && "unresolved function call"); for (const Ptr &a : n.arguments) dispatch(*a); // check if this is a recursive call, to avoid looping if (n.function->unique_id != root->unique_id) pure &= n.function->is_pure(); } void visit_propertystmt(const PropertyStmt &) final { // treat any property statement as a side effect pure = false; // no need to further descend } void visit_put(const Put &) final { pure = false; // no need to descend into children } void visit_undefine(const Undefine &n) final { pure &= !is_global_ref(*n.rhs); dispatch(*n.rhs); } }; // run the traversal on ourselves PurityDetector pd(*this); pd.dispatch(*this); return pd.pure; } bool Function::is_recursive() const { // a traversal that finds calls to a given function class CallFinder : public ConstTraversal { private: const Function *needle; public: bool found = false; explicit CallFinder(const Function &needle_) : needle(&needle_) {} void visit_functioncall(const FunctionCall &n) final { if (n.function != nullptr && n.function->unique_id == needle->unique_id) found = true; } virtual ~CallFinder() = default; }; // run the traversal on ourselves CallFinder cf(*this); cf.dispatch(*this); return cf.found; } } // namespace rumur rumur-2024.05.07/librumur/src/Model.cc000066400000000000000000000057401461637631000173010ustar00rootroot00000000000000#include "location.hh" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace rumur { Model::Model(const std::vector> &children_, const location &loc_) : Node(loc_), children(children_) {} Model *Model::clone() const { return new Model(*this); } mpz_class Model::size_bits() const { mpz_class s = 0; for (const Ptr &n : children) { if (auto v = dynamic_cast(n.get())) s += v->type->width(); } return s; } void Model::validate() const { // Check all state variable names are distinct. { std::unordered_set names; for (const Ptr &c : children) { if (auto v = dynamic_cast(c.get())) { if (!names.insert(v->name).second) throw Error("duplicate state variable name \"" + v->name + "\"", v->loc); } } } } void Model::visit(BaseTraversal &visitor) { visitor.visit_model(*this); } void Model::visit(ConstBaseTraversal &visitor) const { visitor.visit_model(*this); } mpz_class Model::liveness_count() const { // Define a traversal for counting liveness properties. class LivenessCounter : public ConstTraversal { public: mpz_class count = 0; mpz_class multiplier = 1; void visit_ruleset(const Ruleset &n) final { /* Adjust the multiplier for the number of copies of the contained rules * we will eventually generate. */ for (const Quantifier &q : n.quantifiers) { assert(q.constant() && "non-constant quantifier in ruleset"); multiplier *= q.count(); } // Descend into our children; we can ignore the quantifiers themselves. for (const Ptr &r : n.rules) dispatch(*r); /* Reduce the multiplier, removing our effect. This juggling is necessary * because we ourselves may be within a ruleset or contain further * rulesets. */ for (const Quantifier &q : n.quantifiers) { assert(multiplier % q.count() == 0 && "logic error in handling LivenessCounter::multiplier"); multiplier /= q.count(); } } void visit_propertyrule(const PropertyRule &n) final { if (n.property.category == Property::LIVENESS) count += multiplier; // No need to descend into child nodes. } virtual ~LivenessCounter() = default; }; // Use the traversal to count liveness rules. LivenessCounter lc; lc.dispatch(*this); return lc.count; } void Model::reindex() { // Re-number our and our children's 'unique_id' members Indexer i; i.dispatch(*this); } } // namespace rumur rumur-2024.05.07/librumur/src/Node.cc000066400000000000000000000002051461637631000171150ustar00rootroot00000000000000#include #include namespace rumur { Node::Node(const location &loc_) : loc(loc_) {} } // namespace rumur rumur-2024.05.07/librumur/src/Number.cc000066400000000000000000000022461461637631000174670ustar00rootroot00000000000000#include "location.hh" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace rumur { Number::Number(const std::string &value_, const location &loc_) try : Expr(loc_), value(value_) { } catch (std::invalid_argument &e) { throw Error("invalid number: " + value_, loc_); } Number::Number(const mpz_class &value_, const location &loc_) : Expr(loc_), value(value_) {} Number *Number::clone() const { return new Number(*this); } void Number::visit(BaseTraversal &visitor) { visitor.visit_number(*this); } void Number::visit(ConstBaseTraversal &visitor) const { visitor.visit_number(*this); } bool Number::constant() const { return true; } Ptr Number::type() const { return Ptr::make(nullptr, nullptr, location()); } mpz_class Number::constant_fold() const { return value; } std::string Number::to_string() const { return value.get_str(); } bool Number::is_pure() const { return true; } } // namespace rumur rumur-2024.05.07/librumur/src/Property.cc000066400000000000000000000010341461637631000200550ustar00rootroot00000000000000#include #include #include #include namespace rumur { Property::Property(Category category_, const Ptr &expr_, const location &loc_) : Node(loc_), category(category_), expr(expr_) {} Property *Property::clone() const { return new Property(*this); } void Property::visit(BaseTraversal &visitor) { visitor.visit_property(*this); } void Property::visit(ConstBaseTraversal &visitor) const { visitor.visit_property(*this); } } // namespace rumur rumur-2024.05.07/librumur/src/Rule.cc000066400000000000000000000112111461637631000171360ustar00rootroot00000000000000#include "location.hh" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace rumur { namespace { /* A traversal pass that checks any return statements within a rule do not * have a trailing expression. */ class ReturnChecker : public ConstTraversal { public: /* Avoid recursing into functions, that may have return statements with an * expression. */ void visit_function(const Function &) final {} void visit_functioncall(const FunctionCall &) final {} void visit_procedurecall(const ProcedureCall &) final {} void visit_return(const Return &n) final { if (n.expr != nullptr) throw Error("return statement in rule or startstate returns a value", n.loc); // No need to recurse into the return statement's child. } static void check(const Node &n) { ReturnChecker c; c.dispatch(n); } virtual ~ReturnChecker() = default; }; } // namespace Rule::Rule(const std::string &name_, const location &loc_) : Node(loc_), name(name_) {} std::vector> Rule::flatten() const { return {Ptr(clone())}; } AliasRule::AliasRule(const std::vector> &aliases_, const std::vector> &rules_, const location &loc_) : Rule("", loc_), rules(rules_) { aliases = aliases_; } AliasRule *AliasRule::clone() const { return new AliasRule(*this); } void AliasRule::visit(BaseTraversal &visitor) { visitor.visit_aliasrule(*this); } void AliasRule::visit(ConstBaseTraversal &visitor) const { visitor.visit_aliasrule(*this); } std::vector> AliasRule::flatten() const { std::vector> rs; for (const Ptr &r : rules) { for (Ptr &f : r->flatten()) { f->aliases.insert(f->aliases.begin(), aliases.begin(), aliases.end()); rs.push_back(f); } } return rs; } SimpleRule::SimpleRule(const std::string &name_, const Ptr &guard_, const std::vector> &decls_, const std::vector> &body_, const location &loc_) : Rule(name_, loc_), guard(guard_), decls(decls_), body(body_) {} SimpleRule *SimpleRule::clone() const { return new SimpleRule(*this); } void SimpleRule::validate() const { ReturnChecker::check(*this); } void SimpleRule::visit(BaseTraversal &visitor) { visitor.visit_simplerule(*this); } void SimpleRule::visit(ConstBaseTraversal &visitor) const { visitor.visit_simplerule(*this); } StartState::StartState(const std::string &name_, const std::vector> &decls_, const std::vector> &body_, const location &loc_) : Rule(name_, loc_), decls(decls_), body(body_) {} StartState *StartState::clone() const { return new StartState(*this); } void StartState::validate() const { ReturnChecker::check(*this); } void StartState::visit(BaseTraversal &visitor) { visitor.visit_startstate(*this); } void StartState::visit(ConstBaseTraversal &visitor) const { visitor.visit_startstate(*this); } PropertyRule::PropertyRule(const std::string &name_, const Property &property_, const location &loc_) : Rule(name_, loc_), property(property_) {} PropertyRule *PropertyRule::clone() const { return new PropertyRule(*this); } void PropertyRule::visit(BaseTraversal &visitor) { visitor.visit_propertyrule(*this); } void PropertyRule::visit(ConstBaseTraversal &visitor) const { visitor.visit_propertyrule(*this); } Ruleset::Ruleset(const std::vector &quantifiers_, const std::vector> &rules_, const location &loc_) : Rule("", loc_), rules(rules_) { quantifiers = quantifiers_; } Ruleset *Ruleset::clone() const { return new Ruleset(*this); } void Ruleset::validate() const { for (const Quantifier &q : quantifiers) { if (!q.constant()) throw Error("non-constant quantifier expression as ruleset parameter", q.loc); } } void Ruleset::visit(BaseTraversal &visitor) { visitor.visit_ruleset(*this); } void Ruleset::visit(ConstBaseTraversal &visitor) const { visitor.visit_ruleset(*this); } std::vector> Ruleset::flatten() const { std::vector> rs; for (const Ptr &r : rules) { for (Ptr &f : r->flatten()) { for (const Quantifier &q : quantifiers) f->quantifiers.push_back(q); rs.push_back(f); } } return rs; } } // namespace rumur rumur-2024.05.07/librumur/src/Stmt.cc000066400000000000000000000204211461637631000171610ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace rumur { Stmt::Stmt(const location &loc_) : Node(loc_) {} AliasStmt::AliasStmt(const std::vector> &aliases_, const std::vector> &body_, const location &loc_) : Stmt(loc_), aliases(aliases_), body(body_) {} AliasStmt *AliasStmt::clone() const { return new AliasStmt(*this); } void AliasStmt::visit(BaseTraversal &visitor) { visitor.visit_aliasstmt(*this); } void AliasStmt::visit(ConstBaseTraversal &visitor) const { visitor.visit_aliasstmt(*this); } PropertyStmt::PropertyStmt(const Property &property_, const std::string &message_, const location &loc_) : Stmt(loc_), property(property_), message(message_) {} PropertyStmt *PropertyStmt::clone() const { return new PropertyStmt(*this); } void PropertyStmt::validate() const { if (property.category == Property::LIVENESS) throw Error("liveness property appearing as a statement instead of a top " "level property", loc); } void PropertyStmt::visit(BaseTraversal &visitor) { visitor.visit_propertystmt(*this); } void PropertyStmt::visit(ConstBaseTraversal &visitor) const { visitor.visit_propertystmt(*this); } Assignment::Assignment(const Ptr &lhs_, const Ptr &rhs_, const location &loc_) : Stmt(loc_), lhs(lhs_), rhs(rhs_) {} Assignment *Assignment::clone() const { return new Assignment(*this); } void Assignment::validate() const { if (!lhs->is_lvalue()) throw Error("non-lvalue expression cannot be assigned to", loc); if (lhs->is_readonly()) throw Error("read-only expression cannot be assigned to", loc); if (!lhs->type()->coerces_to(*rhs->type())) throw Error("invalid assignment from incompatible type", loc); } void Assignment::visit(BaseTraversal &visitor) { visitor.visit_assignment(*this); } void Assignment::visit(ConstBaseTraversal &visitor) const { visitor.visit_assignment(*this); } Clear::Clear(const Ptr &rhs_, const location &loc_) : Stmt(loc_), rhs(rhs_) {} Clear *Clear::clone() const { return new Clear(*this); } void Clear::validate() const { if (!rhs->is_lvalue()) throw Error("invalid clear of non-lvalue expression", loc); if (rhs->is_readonly()) throw Error("invalid clear of read-only expression", loc); } void Clear::visit(BaseTraversal &visitor) { visitor.visit_clear(*this); } void Clear::visit(ConstBaseTraversal &visitor) const { visitor.visit_clear(*this); } ErrorStmt::ErrorStmt(const std::string &message_, const location &loc_) : Stmt(loc_), message(message_) {} ErrorStmt *ErrorStmt::clone() const { return new ErrorStmt(*this); } void ErrorStmt::visit(BaseTraversal &visitor) { visitor.visit_errorstmt(*this); } void ErrorStmt::visit(ConstBaseTraversal &visitor) const { visitor.visit_errorstmt(*this); } For::For(const Quantifier &quantifier_, const std::vector> &body_, const location &loc_) : Stmt(loc_), quantifier(quantifier_), body(body_) {} For *For::clone() const { return new For(*this); } void For::visit(BaseTraversal &visitor) { visitor.visit_for(*this); } void For::visit(ConstBaseTraversal &visitor) const { visitor.visit_for(*this); } IfClause::IfClause(const Ptr &condition_, const std::vector> &body_, const location &loc_) : Node(loc_), condition(condition_), body(body_) {} IfClause *IfClause::clone() const { return new IfClause(*this); } void IfClause::validate() const { if (condition == nullptr) { // This is an 'else' block. return; } // Only boolean conditions are supported. if (!condition->is_boolean()) throw Error("condition of if clause is not a boolean expression", loc); } void IfClause::visit(BaseTraversal &visitor) { visitor.visit_ifclause(*this); } void IfClause::visit(ConstBaseTraversal &visitor) const { visitor.visit_ifclause(*this); } If::If(const std::vector &clauses_, const location &loc_) : Stmt(loc_), clauses(clauses_) {} If *If::clone() const { return new If(*this); } void If::visit(BaseTraversal &visitor) { visitor.visit_if(*this); } void If::visit(ConstBaseTraversal &visitor) const { visitor.visit_if(*this); } ProcedureCall::ProcedureCall(const std::string &name, const std::vector> &arguments, const location &loc_) : Stmt(loc_), call(name, arguments, loc_) { call.within_procedure_call = true; } ProcedureCall *ProcedureCall::clone() const { return new ProcedureCall(*this); } void ProcedureCall::visit(BaseTraversal &visitor) { visitor.visit_procedurecall(*this); } void ProcedureCall::visit(ConstBaseTraversal &visitor) const { visitor.visit_procedurecall(*this); } Put::Put(const std::string &value_, const location &loc_) : Stmt(loc_), value(value_) {} Put::Put(const Ptr &expr_, const location &loc_) : Stmt(loc_), expr(expr_) {} Put *Put::clone() const { return new Put(*this); } void Put::validate() const { if (expr != nullptr && !expr->is_lvalue() && !expr->type()->is_simple()) throw Error("printing a complex non-lvalue is not supported", loc); } void Put::visit(BaseTraversal &visitor) { visitor.visit_put(*this); } void Put::visit(ConstBaseTraversal &visitor) const { visitor.visit_put(*this); } Return::Return(const Ptr &expr_, const location &loc_) : Stmt(loc_), expr(expr_) {} Return *Return::clone() const { return new Return(*this); } void Return::visit(BaseTraversal &visitor) { visitor.visit_return(*this); } void Return::visit(ConstBaseTraversal &visitor) const { visitor.visit_return(*this); } SwitchCase::SwitchCase(const std::vector> &matches_, const std::vector> &body_, const location &loc_) : Node(loc_), matches(matches_), body(body_) {} SwitchCase *SwitchCase::clone() const { return new SwitchCase(*this); } void SwitchCase::visit(BaseTraversal &visitor) { visitor.visit_switchcase(*this); } void SwitchCase::visit(ConstBaseTraversal &visitor) const { visitor.visit_switchcase(*this); } Switch::Switch(const Ptr &expr_, const std::vector &cases_, const location &loc_) : Stmt(loc_), expr(expr_), cases(cases_) {} Switch *Switch::clone() const { return new Switch(*this); } void Switch::validate() const { const Ptr t = expr->type(); if (!t->is_simple()) throw Error("switch expression has complex type", expr->loc); for (const SwitchCase &c : cases) { for (const Ptr &e : c.matches) { if (!t->coerces_to(*e->type())) throw Error("expression in case cannot be compared to switch " "expression", e->loc); } } } void Switch::visit(BaseTraversal &visitor) { visitor.visit_switch(*this); } void Switch::visit(ConstBaseTraversal &visitor) const { visitor.visit_switch(*this); } Undefine::Undefine(const Ptr &rhs_, const location &loc_) : Stmt(loc_), rhs(rhs_) {} Undefine *Undefine::clone() const { return new Undefine(*this); } void Undefine::validate() const { if (!rhs->is_lvalue()) throw Error("invalid undefine of non-lvalue expression", loc); if (rhs->is_readonly()) throw Error("invalid undefine of read-only expression", loc); } void Undefine::visit(BaseTraversal &visitor) { visitor.visit_undefine(*this); } void Undefine::visit(ConstBaseTraversal &visitor) const { visitor.visit_undefine(*this); } While::While(const Ptr &condition_, const std::vector> &body_, const location &loc_) : Stmt(loc_), condition(condition_), body(body_) {} While *While::clone() const { return new While(*this); } void While::validate() const { if (!condition->is_boolean()) throw Error("condition in while loop is not a boolean expression", condition->loc); } void While::visit(BaseTraversal &visitor) { visitor.visit_while(*this); } void While::visit(ConstBaseTraversal &visitor) const { visitor.visit_while(*this); } } // namespace rumur rumur-2024.05.07/librumur/src/TypeExpr.cc000066400000000000000000000307061461637631000200210ustar00rootroot00000000000000#include "../../common/isa.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace rumur { TypeExpr::TypeExpr(const location &loc_) : Node(loc_) {} bool TypeExpr::is_simple() const { return false; } Ptr TypeExpr::resolve() const { return Ptr(clone()); } std::string TypeExpr::lower_bound() const { throw Error("complex types do not have valid lower bounds", loc); } std::string TypeExpr::upper_bound() const { throw Error("complex types do not have valid upper bounds", loc); } mpz_class TypeExpr::width() const { mpz_class c = count(); // If there are 0 or 1 values of this type, its width is trivial. if (c <= 1) return 0; /* Otherwise, we need the number of bits required to represent the largest * value. */ mpz_class largest(c - 1); mpz_class bits(0); while (largest != 0) { bits++; largest >>= 1; } return bits; } bool TypeExpr::constant() const { assert(!is_simple() && "TypeExpr::constant invoked for simple type; missing " "TypeExpr::constant override?"); throw Error("complex types do not have bounds to query", loc); } // Compare two types for equality. This is implemented here rather than // operator== because its semantics are not exactly what you would expect from // operator== and we do not want to expose it to other files. static bool equal(const TypeExpr &t1, const TypeExpr &t2) { class Equater : public ConstTypeTraversal { private: const Ptr t; public: bool result = true; explicit Equater(const TypeExpr &type) : t(type.resolve()) {} void visit_array(const Array &n) final { if (auto a = dynamic_cast(t.get())) { result &= equal(*a->index_type, *n.index_type); result &= equal(*a->element_type, *n.element_type); } else { result = false; } } void visit_enum(const Enum &n) final { if (auto e = dynamic_cast(t.get())) { for (auto it = e->members.begin(), it2 = n.members.begin();; it++, it2++) { if (it == e->members.end()) { result &= it2 == n.members.end(); break; } if (it2 == n.members.end()) { result = false; break; } result &= it->first == it2->first; } } else { result = false; } } void visit_range(const Range &n) final { if (auto r = dynamic_cast(t.get())) { result = r->min->constant_fold() == n.min->constant_fold() && r->max->constant_fold() == n.max->constant_fold(); } else { result = false; } } void visit_record(const Record &n) final { if (auto r = dynamic_cast(t.get())) { for (auto it = r->fields.begin(), it2 = n.fields.begin();; it++, it2++) { if (it == r->fields.end()) { result &= it2 == n.fields.end(); break; } if (it2 == n.fields.end()) { result = false; break; } result &= (*it)->name == (*it2)->name && equal(*(*it)->get_type(), *(*it2)->get_type()); } } else { result = false; } } void visit_scalarset(const Scalarset &n) final { if (auto s = dynamic_cast(t.get())) { result = s->bound->constant_fold() == n.bound->constant_fold(); } else { result = false; } } void visit_typeexprid(const TypeExprID &n) final { dispatch(*n.referent); } }; Equater eq(t1); eq.dispatch(t2); return eq.result; } bool TypeExpr::coerces_to(const TypeExpr &other) const { const Ptr t1 = resolve(); const Ptr t2 = other.resolve(); if (isa(t1) && isa(t2)) return true; return equal(*t1, *t2); } bool TypeExpr::is_boolean() const { return false; } Range::Range(const Ptr &min_, const Ptr &max_, const location &loc_) : TypeExpr(loc_), min(min_), max(max_) { if (min == nullptr) { // FIXME: avoid hard coding INT64 limits here // FIXME: this isn't even the right limit because of - overflowing grr... min = Ptr::make(mpz_class("-9223372036854775807"), location()); } if (max == nullptr) { max = Ptr::make(mpz_class("9223372036854775807"), location()); } } Range *Range::clone() const { return new Range(*this); } void Range::visit(BaseTraversal &visitor) { visitor.visit_range(*this); } void Range::visit(ConstBaseTraversal &visitor) const { visitor.visit_range(*this); } mpz_class Range::count() const { mpz_class lb = min->constant_fold(); mpz_class ub = max->constant_fold(); return ub - lb + 2; } bool Range::is_simple() const { return true; } void Range::validate() const { if (!min->constant()) throw Error("lower bound of range is not a constant", min->loc); if (!max->constant()) throw Error("upper bound of range is not a constant", max->loc); if (max->constant_fold() < min->constant_fold()) throw Error("upper bound of range is less than lower bound", loc); } std::string Range::lower_bound() const { return "VALUE_C(" + min->constant_fold().get_str() + ")"; } std::string Range::upper_bound() const { return "VALUE_C(" + max->constant_fold().get_str() + ")"; } std::string Range::to_string() const { return min->to_string() + ".." + max->to_string(); } bool Range::constant() const { return min->constant() && max->constant(); } Scalarset::Scalarset(const Ptr &bound_, const location &loc_) : TypeExpr(loc_), bound(bound_) {} Scalarset *Scalarset::clone() const { return new Scalarset(*this); } void Scalarset::visit(BaseTraversal &visitor) { visitor.visit_scalarset(*this); } void Scalarset::visit(ConstBaseTraversal &visitor) const { visitor.visit_scalarset(*this); } mpz_class Scalarset::count() const { mpz_class b = bound->constant_fold(); assert(b > 0 && "non-positive bound for scalarset"); return b + 1; } bool Scalarset::is_simple() const { return true; } void Scalarset::validate() const { if (!bound->constant()) throw Error("bound of scalarset is not a constant", bound->loc); if (bound->constant_fold() <= 0) throw Error("bound of scalarset is not positive", bound->loc); } std::string Scalarset::lower_bound() const { return "VALUE_C(0)"; } std::string Scalarset::upper_bound() const { mpz_class b = bound->constant_fold() - 1; return "VALUE_C(" + b.get_str() + ")"; } std::string Scalarset::to_string() const { return "scalarset(" + bound->to_string() + ")"; } bool Scalarset::constant() const { return bound->constant(); } Enum::Enum(const std::vector> &members_, const location &loc_) : TypeExpr(loc_), members(members_) {} Enum *Enum::clone() const { return new Enum(*this); } void Enum::visit(BaseTraversal &visitor) { visitor.visit_enum(*this); } void Enum::visit(ConstBaseTraversal &visitor) const { visitor.visit_enum(*this); } mpz_class Enum::count() const { mpz_class members_size = members.size(); return members_size + 1; } bool Enum::is_simple() const { return true; } void Enum::validate() const { std::unordered_set ms; for (const std::pair &member : members) { auto it = ms.insert(member.first); if (!it.second) throw Error("duplicate enum member \"" + member.first + "\"", member.second); } } std::string Enum::lower_bound() const { return "VALUE_C(0)"; } std::string Enum::upper_bound() const { mpz_class size = members.size(); if (size > 0) size--; return "VALUE_C(" + size.get_str() + ")"; } std::string Enum::to_string() const { std::string s = "enum { "; bool first = true; for (const std::pair &m : members) { if (!first) s += ", "; s += m.first; first = false; } return s + " }"; } bool Enum::constant() const { // enums always have a known constant bound return true; } bool Enum::is_boolean() const { // the boolean literals cannot be shadowed, so we simply need to check if our // members are “false” and “true” return members.size() == 2 && members[0].first == "false" && members[1].first == "true"; } Record::Record(const std::vector> &fields_, const location &loc_) : TypeExpr(loc_), fields(fields_) { std::unordered_set names; for (const Ptr &f : fields) { if (!names.insert(f->name).second) throw Error("duplicate field name \"" + f->name + "\"", f->loc); } } Record *Record::clone() const { return new Record(*this); } void Record::visit(BaseTraversal &visitor) { visitor.visit_record(*this); } void Record::visit(ConstBaseTraversal &visitor) const { visitor.visit_record(*this); } mpz_class Record::width() const { mpz_class s = 0; for (const Ptr &v : fields) s += v->type->width(); return s; } mpz_class Record::count() const { mpz_class s = 1; for (const Ptr &v : fields) s *= v->type->count(); return s; } std::string Record::to_string() const { std::string s = "record "; for (const Ptr &v : fields) s += v->name + " : " + v->type->to_string() + "; "; return s + "endrecord"; } Array::Array(const Ptr &index_type_, const Ptr &element_type_, const location &loc_) : TypeExpr(loc_), index_type(index_type_), element_type(element_type_) {} Array *Array::clone() const { return new Array(*this); } void Array::visit(BaseTraversal &visitor) { visitor.visit_array(*this); } void Array::visit(ConstBaseTraversal &visitor) const { visitor.visit_array(*this); } mpz_class Array::width() const { mpz_class i = index_type->count(); mpz_class e = element_type->width(); assert(i >= 1 && "index count apparently does not include undefined"); i--; return i * e; } mpz_class Array::count() const { mpz_class i = index_type->count(); mpz_class e = element_type->count(); assert(i >= 1 && "index count apparently does not include undefined"); i--; if (i == 0) return 0; mpz_class s = 1; for (size_t j = 0; j < i; j++) s *= e; return s; } void Array::validate() const { if (!index_type->is_simple()) throw Error("array indices must be simple types", loc); } std::string Array::to_string() const { return "array [" + index_type->to_string() + "] of " + element_type->to_string(); } TypeExprID::TypeExprID(const std::string &name_, const Ptr &referent_, const location &loc_) : TypeExpr(loc_), name(name_), referent(referent_) {} TypeExprID *TypeExprID::clone() const { return new TypeExprID(*this); } void TypeExprID::visit(BaseTraversal &visitor) { visitor.visit_typeexprid(*this); } void TypeExprID::visit(ConstBaseTraversal &visitor) const { visitor.visit_typeexprid(*this); } mpz_class TypeExprID::width() const { if (referent == nullptr) throw Error("unresolved type symbol \"" + name + "\"", loc); return referent->value->width(); } mpz_class TypeExprID::count() const { if (referent == nullptr) throw Error("unresolved type symbol \"" + name + "\"", loc); return referent->value->count(); } bool TypeExprID::is_simple() const { if (referent == nullptr) throw Error("unresolved type symbol \"" + name + "\"", loc); return referent->value->is_simple(); } Ptr TypeExprID::resolve() const { if (referent == nullptr) throw Error("unresolved type symbol \"" + name + "\"", loc); return referent->value->resolve(); } void TypeExprID::validate() const { if (referent == nullptr) throw Error("unresolved type symbol \"" + name + "\"", loc); } std::string TypeExprID::lower_bound() const { if (referent == nullptr) throw Error("unresolved type symbol \"" + name + "\"", loc); return referent->value->lower_bound(); } std::string TypeExprID::upper_bound() const { if (referent == nullptr) throw Error("unresolved type symbol \"" + name + "\"", loc); return referent->value->upper_bound(); } std::string TypeExprID::to_string() const { return name; } bool TypeExprID::constant() const { if (referent == nullptr) throw Error("unresolved type symbol \"" + name + "\"", loc); return referent->value->constant(); } } // namespace rumur rumur-2024.05.07/librumur/src/except.cc000066400000000000000000000005761461637631000175330ustar00rootroot00000000000000#include "location.hh" #include #include #include #include namespace rumur { Error::Error(const std::string &message, const location &loc_) : std::runtime_error(message), loc(loc_) {} Error::Error(const std::string &prefix, const Error &sub) : std::runtime_error(prefix + sub.what()), loc(sub.loc) {} } // namespace rumur rumur-2024.05.07/librumur/src/indexer.cc000066400000000000000000000164111461637631000176740ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include namespace rumur { void Indexer::visit_add(Add &n) { visit_bexpr(n); } void Indexer::visit_aliasdecl(AliasDecl &n) { n.unique_id = next++; dispatch(*n.value); } void Indexer::visit_aliasrule(AliasRule &n) { n.unique_id = next++; for (auto &a : n.aliases) dispatch(*a); for (auto &r : n.rules) dispatch(*r); } void Indexer::visit_aliasstmt(AliasStmt &n) { n.unique_id = next++; for (auto &a : n.aliases) dispatch(*a); for (auto &s : n.body) dispatch(*s); } void Indexer::visit_ambiguousamp(AmbiguousAmp &n) { visit_bexpr(n); } void Indexer::visit_ambiguouspipe(AmbiguousPipe &n) { visit_bexpr(n); } void Indexer::visit_and(And &n) { visit_bexpr(n); } void Indexer::visit_array(Array &n) { n.unique_id = next++; dispatch(*n.index_type); dispatch(*n.element_type); } void Indexer::visit_assignment(Assignment &n) { n.unique_id = next++; dispatch(*n.lhs); dispatch(*n.rhs); } void Indexer::visit_band(Band &n) { visit_bexpr(n); } void Indexer::visit_bnot(Bnot &n) { visit_uexpr(n); } void Indexer::visit_bor(Bor &n) { visit_bexpr(n); } void Indexer::visit_bexpr(BinaryExpr &n) { n.unique_id = next++; dispatch(*n.lhs); dispatch(*n.rhs); } void Indexer::visit_clear(Clear &n) { n.unique_id = next++; dispatch(*n.rhs); } void Indexer::visit_constdecl(ConstDecl &n) { n.unique_id = next++; dispatch(*n.value); } void Indexer::visit_div(Div &n) { visit_bexpr(n); } void Indexer::visit_element(Element &n) { n.unique_id = next++; dispatch(*n.array); dispatch(*n.index); } void Indexer::visit_enum(Enum &n) { n.unique_id = next++; // allocate a block of IDs that this Enum can use for its members during type // checking (see resolve_symbols()) n.unique_id_limit = next + n.members.size(); next = n.unique_id_limit; } void Indexer::visit_eq(Eq &n) { visit_bexpr(n); } void Indexer::visit_errorstmt(ErrorStmt &n) { n.unique_id = next++; } void Indexer::visit_exists(Exists &n) { n.unique_id = next++; dispatch(n.quantifier); dispatch(*n.expr); } void Indexer::visit_exprid(ExprID &n) { n.unique_id = next++; } void Indexer::visit_field(Field &n) { n.unique_id = next++; dispatch(*n.record); } void Indexer::visit_for(For &n) { n.unique_id = next++; dispatch(n.quantifier); for (auto &s : n.body) dispatch(*s); } void Indexer::visit_forall(Forall &n) { n.unique_id = next++; dispatch(n.quantifier); dispatch(*n.expr); } void Indexer::visit_function(Function &n) { n.unique_id = next++; for (auto &p : n.parameters) dispatch(*p); if (n.return_type != nullptr) dispatch(*n.return_type); for (auto &d : n.decls) dispatch(*d); for (auto &s : n.body) dispatch(*s); } void Indexer::visit_functioncall(FunctionCall &n) { n.unique_id = next++; for (auto &a : n.arguments) dispatch(*a); } void Indexer::visit_geq(Geq &n) { visit_bexpr(n); } void Indexer::visit_gt(Gt &n) { visit_bexpr(n); } void Indexer::visit_if(If &n) { n.unique_id = next++; for (IfClause &c : n.clauses) dispatch(c); } void Indexer::visit_ifclause(IfClause &n) { n.unique_id = next++; if (n.condition != nullptr) dispatch(*n.condition); for (auto &s : n.body) dispatch(*s); } void Indexer::visit_implication(Implication &n) { visit_bexpr(n); } void Indexer::visit_isundefined(IsUndefined &n) { visit_uexpr(n); } void Indexer::visit_leq(Leq &n) { visit_bexpr(n); } void Indexer::visit_lsh(Lsh &n) { visit_bexpr(n); } void Indexer::visit_lt(Lt &n) { visit_bexpr(n); } void Indexer::visit_mod(Mod &n) { visit_bexpr(n); } void Indexer::visit_model(Model &n) { n.unique_id = next++; for (Ptr &c : n.children) dispatch(*c); } void Indexer::visit_mul(Mul &n) { visit_bexpr(n); } void Indexer::visit_negative(Negative &n) { visit_uexpr(n); } void Indexer::visit_neq(Neq &n) { visit_bexpr(n); } void Indexer::visit_not(Not &n) { visit_uexpr(n); } void Indexer::visit_number(Number &n) { n.unique_id = next++; } void Indexer::visit_or(Or &n) { visit_bexpr(n); } void Indexer::visit_procedurecall(ProcedureCall &n) { n.unique_id = next++; dispatch(n.call); } void Indexer::visit_property(Property &n) { n.unique_id = next++; dispatch(*n.expr); } void Indexer::visit_propertyrule(PropertyRule &n) { n.unique_id = next++; for (Quantifier &q : n.quantifiers) dispatch(q); dispatch(n.property); } void Indexer::visit_propertystmt(PropertyStmt &n) { n.unique_id = next++; dispatch(n.property); } void Indexer::visit_put(Put &n) { n.unique_id = next++; if (n.expr != nullptr) dispatch(*n.expr); } void Indexer::visit_quantifier(Quantifier &n) { n.unique_id = next++; if (n.type != nullptr) dispatch(*n.type); if (n.from != nullptr) dispatch(*n.from); if (n.to != nullptr) dispatch(*n.to); if (n.step != nullptr) dispatch(*n.step); dispatch(*n.decl); } void Indexer::visit_range(Range &n) { n.unique_id = next++; dispatch(*n.min); dispatch(*n.max); } void Indexer::visit_record(Record &n) { n.unique_id = next++; for (auto &f : n.fields) dispatch(*f); } void Indexer::visit_return(Return &n) { n.unique_id = next++; if (n.expr != nullptr) dispatch(*n.expr); } void Indexer::visit_rsh(Rsh &n) { visit_bexpr(n); } void Indexer::visit_ruleset(Ruleset &n) { n.unique_id = next++; for (Quantifier &q : n.quantifiers) dispatch(q); for (auto &r : n.rules) dispatch(*r); } void Indexer::visit_scalarset(Scalarset &n) { n.unique_id = next++; dispatch(*n.bound); } void Indexer::visit_simplerule(SimpleRule &n) { n.unique_id = next++; for (Quantifier &q : n.quantifiers) dispatch(q); if (n.guard != nullptr) dispatch(*n.guard); for (auto &d : n.decls) dispatch(*d); for (auto &s : n.body) dispatch(*s); } void Indexer::visit_startstate(StartState &n) { n.unique_id = next++; for (Quantifier &q : n.quantifiers) dispatch(q); for (auto &d : n.decls) dispatch(*d); for (auto &s : n.body) dispatch(*s); } void Indexer::visit_sub(Sub &n) { visit_bexpr(n); } void Indexer::visit_switch(Switch &n) { n.unique_id = next++; dispatch(*n.expr); for (SwitchCase &c : n.cases) dispatch(c); } void Indexer::visit_switchcase(SwitchCase &n) { n.unique_id = next++; for (auto &m : n.matches) dispatch(*m); for (auto &s : n.body) dispatch(*s); } void Indexer::visit_ternary(Ternary &n) { n.unique_id = next++; dispatch(*n.cond); dispatch(*n.lhs); dispatch(*n.rhs); } void Indexer::visit_typedecl(TypeDecl &n) { n.unique_id = next++; dispatch(*n.value); } void Indexer::visit_typeexprid(TypeExprID &n) { n.unique_id = next++; } void Indexer::visit_uexpr(UnaryExpr &n) { n.unique_id = next++; dispatch(*n.rhs); } void Indexer::visit_undefine(Undefine &n) { n.unique_id = next++; dispatch(*n.rhs); } void Indexer::visit_vardecl(VarDecl &n) { n.unique_id = next++; dispatch(*n.type); } void Indexer::visit_while(While &n) { n.unique_id = next++; dispatch(*n.condition); for (auto &s : n.body) dispatch(*s); } void Indexer::visit_xor(Xor &n) { visit_bexpr(n); } } // namespace rumur rumur-2024.05.07/librumur/src/lexer.l000066400000000000000000000201431461637631000172200ustar00rootroot00000000000000%{ #include #include "parser.yy.hh" #include #include /* Override the declaration of yylex with the one from our derived scanner. */ #ifdef YY_DECL #undef YY_DECL #endif #define YY_DECL int rumur::scanner::yylex(\ rumur::parser::semantic_type *const lval, rumur::parser::location_type *loc) /* Each time yylex() is called, update the begin position with the last end * position. This ensures we get correct starting column numbers. */ #define YY_USER_ACTION loc->step(); loc->columns(yyleng); %} /* Track line numbers while scanning. These are retrievable via * FlexLexer::lineno(). */ %option yylineno /* When the end of the input is encountered, do not expect another file. */ %option noyywrap /* Generate a C++ scanner, not C. */ %option c++ /* Use our own scanner class, rather than the default yyFlexLexer. */ %option yyclass="rumur::scanner" /* Switch to case-insensitive. */ %option caseless /* States for handling comments. This is not the most efficient way of * dealing with these, but meh. */ %x SC_COMMENT SC_MULTILINE_COMMENT %% (0x[[:xdigit:]]+|[[:digit:]]+) { lval->build(YYText()); return rumur::parser::token::NUMBER; } ([[:digit:]]?\.[[:digit:]]+) { throw rumur::Error("real types are not supported", *loc); } alias { return rumur::parser::token::ALIAS; } array { return rumur::parser::token::ARRAY; } assert { return rumur::parser::token::ASSERT; } assume { return rumur::parser::token::ASSUME; } begin { return rumur::parser::token::BEGIN_TOK; } boolean { return rumur::parser::token::BOOLEAN; } by { return rumur::parser::token::BY; } case { return rumur::parser::token::CASE; } clear { return rumur::parser::token::CLEAR; } const { return rumur::parser::token::CONST; } cover { return rumur::parser::token::COVER; } do { return rumur::parser::token::DO; } else { return rumur::parser::token::ELSE; } elsif { return rumur::parser::token::ELSIF; } end { return rumur::parser::token::END; } endalias { return rumur::parser::token::ENDALIAS; } endexists { return rumur::parser::token::ENDEXISTS; } endfor { return rumur::parser::token::ENDFOR; } endforall { return rumur::parser::token::ENDFORALL; } endfunction { return rumur::parser::token::ENDFUNCTION; } endif { return rumur::parser::token::ENDIF; } endprocedure { return rumur::parser::token::ENDPROCEDURE; } endrecord { return rumur::parser::token::ENDRECORD; } endrule { return rumur::parser::token::ENDRULE; } endruleset { return rumur::parser::token::ENDRULESET; } endstartstate { return rumur::parser::token::ENDSTARTSTATE; } endswitch { return rumur::parser::token::ENDSWITCH; } endwhile { return rumur::parser::token::ENDWHILE; } enum { return rumur::parser::token::ENUM; } error { return rumur::parser::token::ERROR; } exists { return rumur::parser::token::EXISTS; } for { return rumur::parser::token::FOR; } forall { return rumur::parser::token::FORALL; } function { return rumur::parser::token::FUNCTION; } if { return rumur::parser::token::IF; } invariant { return rumur::parser::token::INVARIANT; } isundefined { return rumur::parser::token::ISUNDEFINED; } liveness { return rumur::parser::token::LIVENESS; } of { return rumur::parser::token::OF; } procedure { return rumur::parser::token::PROCEDURE; } put { return rumur::parser::token::PUT; } real { throw rumur::Error("real types are not supported", *loc); } record { return rumur::parser::token::RECORD; } return { return rumur::parser::token::RETURN; } rule { return rumur::parser::token::RULE; } ruleset { return rumur::parser::token::RULESET; } scalarset { return rumur::parser::token::SCALARSET; } startstate { return rumur::parser::token::STARTSTATE; } switch { return rumur::parser::token::SWITCH; } then { return rumur::parser::token::THEN; } to { return rumur::parser::token::TO; } type { return rumur::parser::token::TYPE; } undefine { return rumur::parser::token::UNDEFINE; } union { throw rumur::Error("union types are not supported", *loc); } var { return rumur::parser::token::VAR; } while { return rumur::parser::token::WHILE; } "∀" { return rumur::parser::token::FORALL; } "∃" { return rumur::parser::token::EXISTS; } /* Recognise true and false explicitly rather than as generic IDs (below). The * purpose of this is so that we match them case-insensitively. */ false { lval->build("false"); return rumur::parser::token::ID; } true { lval->build("true"); return rumur::parser::token::ID; } [[:alpha:]][_[:alnum:]]* { lval->build(YYText()); return rumur::parser::token::ID; } "--" { BEGIN(SC_COMMENT); } "/*" { BEGIN(SC_MULTILINE_COMMENT); } ":=" { return rumur::parser::token::COLON_EQ; } "≔" { return rumur::parser::token::COLON_EQ; } ".." { return rumur::parser::token::DOTDOT; } ">=" { return rumur::parser::token::GEQ; } "≥" { return rumur::parser::token::GEQ; } ">>" { return rumur::parser::token::RSH; } "->" { return rumur::parser::token::IMPLIES; } "→" { return rumur::parser::token::IMPLIES; } "<=" { return rumur::parser::token::LEQ; } "≤" { return rumur::parser::token::LEQ; } "<<" { return rumur::parser::token::LSH; } "!=" { return rumur::parser::token::NEQ; } "≠" { return rumur::parser::token::NEQ; } "==>" { return rumur::parser::token::ARROW; } "⇒" { return rumur::parser::token::ARROW; } "==" { return rumur::parser::token::DEQ; } "¬" { return '!'; } "∧" { return rumur::parser::token::LAND; } "∨" { return rumur::parser::token::LOR; } "&&" { return rumur::parser::token::AMPAMP; } "||" { return rumur::parser::token::PIPEPIPE; } "÷" { return '/'; } "−" { return '-'; } "∕" { return '/'; } "×" { return '*'; } [\^~&!|:=><\-\%\+;{},\[\]\.()/\*\?] { return YYText()[0]; } (\"|“)(\\.|[^\\\"]|\\”)*(\"|”) { std::string s(YYText()); /* figure out which quote character this string starts with */ const std::string opener = s.find("“") == 0 ? "“" : "\""; assert(s.find(opener) == 0 && "logic bug in string lexing rule"); /* figure out which quote character this string ends with */ static const std::string squote = "”"; size_t offset = s.size() - squote.size(); const std::string closer = s.size() > squote.size() && s.find(squote, offset) == offset ? squote : "\""; /* build a string, stripping the quote delimiters */ lval->build(s.substr(opener.size(), s.size() - opener.size() - closer.size()).c_str()); return rumur::parser::token::STRING; } /* Ensure we keep a correct line count for error reporting. */ "\n" { loc->lines(yyleng); loc->step(); /* maintain correct column count */ } /* Ignore white space. */ [[:space:]] { loc->step(); /* maintain correct column count */ } . { throw rumur::Error(std::string("unexpected character: '") + YYText() + "'", *loc); } /* Comment handling. */ . { /* do nothing */ } \n { loc->lines(yyleng); loc->step(); /* maintain correct column count */ BEGIN(INITIAL); } [^*\n]* { /* do nothing */ } "*"+[^*/\n]* { /* do nothing */ } \n { loc->lines(yyleng); } "*"+"/" { loc->step(); /* maintain correct column count */ BEGIN(INITIAL); } rumur-2024.05.07/librumur/src/make-version.py000077500000000000000000000065541461637631000207130ustar00rootroot00000000000000#!/usr/bin/env python3 """ Generate contents of a version.cc. """ import os import re import shutil import subprocess as sp import sys import textwrap def last_release(): """ The version of the last tagged release of Rumur. This will be used as the version number if no Git information is available. """ with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../CHANGELOG.rst"), "rt") as f: for line in f: m = re.match(r"(v\d{4}\.\d{2}\.\d{2})$", line) if m is not None: return m.group(1) raise Exception("version heading not found in changelog") def has_git(): """ Return True if we are in a Git repository and have Git. """ # Return False if we don't have Git. if shutil.which("git") is None: return False # Return False if we have no Git repository information. if not os.path.exists(os.path.join(os.path.dirname(__file__), "..", "..", ".git")): return False return True def get_tag(): """ Find the version tag of the current Git commit, e.g. v2020.05.03, if it exists. """ try: tag = sp.check_output(["git", "describe", "--tags"], stderr=sp.DEVNULL) except sp.CalledProcessError: tag = None if tag is not None: tag = tag.decode("utf-8", "replace").strip() if re.match(r"v[\d\.]+$", tag) is None: # Not a version tag. tag = None return tag def get_sha(): """ Find the hash of the current Git commit. """ rev = sp.check_output(["git", "rev-parse", "--verify", "HEAD"]) rev = rev.decode("utf-8", "replace").strip() return rev def is_dirty(): """ Determine whether the current working directory has uncommitted changes. """ dirty = False ret = sp.call(["git", "diff", "--exit-code"], stdout=sp.DEVNULL, stderr=sp.DEVNULL) dirty |= ret != 0 ret = sp.call(["git", "diff", "--cached", "--exit-code"], stdout=sp.DEVNULL, stderr=sp.DEVNULL) dirty |= ret != 0 return dirty def main(args): if len(args) != 2 or args[1] == "--help": sys.stderr.write("usage: {} file\n".format(args[0])) sys.stderr.write(" write version information as a C++ source file\n") return -1 # Get the contents of the old version file if it exists. old = None if os.path.exists(args[1]): with open(args[1], "rt") as f: old = f.read() version = None # first, look for an environment variable that overrides other version sources version = os.environ.get("RUMUR_VERSION") # second, look for a version tag on the current commit if version is None and has_git(): tag = get_tag() if tag is not None: version = "{}{}".format(tag, " (dirty)" if is_dirty() else "") # third, look for the commit hash as the version if version is None and has_git(): rev = get_sha() assert rev is not None version = "Git commit {}{}".format(rev, " (dirty)" if is_dirty() else "") # Finally, fall back to our known release version. if version is None: version = last_release() new = textwrap.dedent("""\ #pragma once namespace rumur {{ static constexpr const char *get_version() {{ return "{}"; }} }} """.format(version)) # If the version has changed, update the output. Otherwise we leave the old # contents -- and more importantly, the timestamp -- intact. if old != new: with open(args[1], "wt") as f: f.write(new) return 0 if __name__ == "__main__": sys.exit(main(sys.argv)) rumur-2024.05.07/librumur/src/parse.cc000066400000000000000000000010261461637631000173440ustar00rootroot00000000000000#include "location.hh" #include "parser.yy.hh" #include #include #include #include #include #include #include #include #include using namespace rumur; Ptr rumur::parse(std::istream &input) { // Setup the parser scanner s(&input); Ptr m; parser p(s, m); // Parse the input model int err = p.parse(); if (err != 0) throw Error("parsing failed", location()); return m; } rumur-2024.05.07/librumur/src/parser.yy000066400000000000000000000454621461637631000176160ustar00rootroot00000000000000%skeleton "lalr1.cc" %require "3.0" /* Ordinarily, Bison emits the parser in a namespace "yy". By changing the * namespace our parser can coexist with other Bison-generated parsers if * necessary. */ %define api.namespace {rumur} /* Make yylval, the semantic type of the current token, a variant. The * purpose of this is to allow us to return richer information about the * contents of a token or expression. */ %define api.value.type variant /* Enable more explanatory error messages. Without this, Bison just says * "syntax error" for any problem. The extra information can sometimes be * inaccurate unless you define "parse.lac full" which is not available for * C++, but even a slightly inaccurate message is typically more useful to the * user than "syntax error". */ %define parse.error verbose /* Turn on some safety checks for construction and destruction of variant * types. This is only relevant if we enable them (api.value.type variant). */ %define parse.assert /* Receive source location from the scanner. This allows us to give proper * feedback on user errors, pointing them at the line and column where they * went wrong. */ %locations /* Code that we need before anything in both the implementation * (parser.yy.cc) and the header (parser.yy.hh). */ %code requires { #include #include #include #include #include #include #include #include #include #include #include /* Forward declare the scanner class that Flex will produce for us. */ namespace rumur { class scanner; } } /* Code that we need in the implementation, but in no particular location. */ %code { #include #include #include #include #include #include #include #include #include /* Redirect yylex to call our derived scanner. */ #ifdef yylex #undef yylex #endif #define yylex sc.yylex } /* Code that we need to include at the top of the implementation * (parser.yy.cc) but not in the header. Including this code in the header * (parser.yy.hh) would induce a circular include. */ %code top { #include /* squash warnings that Bison-generated code triggers under Clang ≥14 */ #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-but-set-variable" #endif } /* Tell Bison that the parser receives a reference to an instance of our * scanner class, in order to use our redirected yylex defined above. */ %parse-param { scanner &sc } /* Tell Bison we'll receive another parameter that will allow us to pass * back the result of parsing. */ %parse-param { rumur::Ptr &output } %token ALIAS %token AMPAMP "&&" %token ARRAY %token ARROW %token ASSERT %token ASSUME %token BEGIN_TOK %token BOOLEAN %token BY %token CASE %token CLEAR %token COLON_EQ ":=" %token CONST %token COVER %token DEQ "==" %token DO %token DOTDOT ".." %token ELSE %token ELSIF %token END %token ENDALIAS %token ENDEXISTS %token ENDFOR %token ENDFORALL %token ENDFUNCTION %token ENDIF %token ENDPROCEDURE %token ENDRECORD %token ENDRULE %token ENDRULESET %token ENDSTARTSTATE %token ENDSWITCH %token ENDWHILE %token ENUM %token ERROR %token EXISTS %token FOR %token FORALL %token FUNCTION %token GEQ ">=" %token ID %token IF %token IMPLIES "->" %token INVARIANT %token ISUNDEFINED %token LAND "∧" %token LEQ "<=" %token LIVENESS %token LOR "∨" %token LSH "<<" %token NEQ "!=" %token NUMBER %token OF %token PIPEPIPE "||" %token PROCEDURE %token PUT %token RECORD %token RETURN %token RSH ">>" %token RULE %token RULESET %token SCALARSET %token STARTSTATE %token STRING %token SWITCH %token THEN %token TO %token TYPE %token UNDEFINE %token VAR %token WHILE %nonassoc '?' ':' %nonassoc IMPLIES %left PIPEPIPE LOR %left AMPAMP LAND %left '|' %left '^' %left '&' %precedence '!' %nonassoc '<' LEQ DEQ '=' NEQ GEQ '>' %left LSH RSH %left '+' '-' %left '*' '/' '%' %precedence '~' %type > aliasrule %type > category %type >> decl %type >> decls %type >> decls_header %type > designator %type > else_opt %type > elsifs %type > expr %type , rumur::location>>> exprdecl %type , rumur::location>>> exprdecls %type >> exprlist %type >> exprlist_cont %type > guard_opt %type >> id_list %type >> id_list_opt %type >> nodes %type >> parameter %type >> parameters %type > procdecl %type > property %type > quantifier %type > quantifiers %type > return_type %type > rule %type > ruleset %type >> rules %type > simplerule %type > startstate %type > stmt %type >> stmts %type >> stmts_cont %type string_opt %type > switchcases %type > switchcases_cont %type >> typedecl %type >> typedecls %type > typeexpr %type >> vardecl %type >> vardecls %type > var_opt %% model: nodes { output = rumur::Ptr::make($1, @$); }; nodes: nodes decl { $$ = $1; std::move($2.begin(), $2.end(), std::back_inserter($$)); } | nodes procdecl { $$ = $1; $$.push_back($2); } | nodes rule semi_opt { $$ = $1; $$.push_back($2); } | %empty { }; aliasrule: ALIAS exprdecls DO rules endalias { std::vector> decls; for (const std::tuple, rumur::location> &d : $2) { decls.push_back(rumur::Ptr::make(std::get<0>(d), std::get<1>(d), std::get<2>(d))); } $$ = rumur::Ptr::make(decls, $4, @$); }; begin_opt: BEGIN_TOK | %empty; category: ASSERT { $$ = std::make_shared(rumur::Property::ASSERTION); } | ASSUME { $$ = std::make_shared(rumur::Property::ASSUMPTION); } | COVER { $$ = std::make_shared(rumur::Property::COVER); } | INVARIANT { $$ = std::make_shared(rumur::Property::ASSERTION); } | LIVENESS { $$ = std::make_shared(rumur::Property::LIVENESS); }; comma_opt: ',' | %empty; decl: CONST exprdecls { for (const std::tuple, rumur::location> &d : $2) { $$.push_back(rumur::Ptr::make(std::get<0>(d), std::get<1>(d), std::get<2>(d))); } } | TYPE typedecls { $$ = $2; } | VAR vardecls { std::move($2.begin(), $2.end(), std::back_inserter($$)); }; decls: decls decl { $$ = $1; std::move($2.begin(), $2.end(), std::back_inserter($$)); } | %empty { /* nothing required */ }; decls_header: decls BEGIN_TOK { $$ = $1; } | %empty { }; designator: designator '.' ID { $$ = rumur::Ptr::make($1, $3, @$); } | designator '[' expr ']' { $$ = rumur::Ptr::make($1, $3, @$); } | ID { $$ = rumur::Ptr::make($1, nullptr, @$); }; else_opt: ELSE stmts { $$.push_back(rumur::IfClause(nullptr, $2, @$)); } | %empty { }; elsifs: elsifs ELSIF expr THEN stmts { $$ = $1; $$.push_back(rumur::IfClause($3, $5, rumur::location(@2.begin, @5.end))); } | %empty { }; endalias: END | ENDALIAS; endexists: END | ENDEXISTS; endfor: END | ENDFOR; endforall: END | ENDFORALL; endfunction: END | ENDFUNCTION | ENDPROCEDURE; endif: END | ENDIF; endrecord: END | ENDRECORD; endrule: END | ENDRULE; endruleset: END | ENDRULESET; endstartstate: END | ENDSTARTSTATE; endswitch: END | ENDSWITCH; endwhile: END | ENDWHILE; expr: expr '?' expr ':' expr { $$ = rumur::Ptr::make($1, $3, $5, @$); } | expr IMPLIES expr { $$ = rumur::Ptr::make($1, $3, @$); } | expr PIPEPIPE expr { $$ = rumur::Ptr::make($1, $3, @$); } | expr LOR expr { $$ = rumur::Ptr::make($1, $3, @$); } | expr AMPAMP expr { $$ = rumur::Ptr::make($1, $3, @$); } | expr LAND expr { $$ = rumur::Ptr::make($1, $3, @$); } | expr '|' expr { /* construct this as an ambiguous expression, that will later be resolved into * an Or or a Bor */ $$ = rumur::Ptr::make($1, $3, @$); } | expr '^' expr { $$ = rumur::Ptr::make($1, $3, @$); } | expr '&' expr { /* construct this as an ambiguous expression, that will later be resolved into * an And or a Band */ $$ = rumur::Ptr::make($1, $3, @$); } | '!' expr { $$ = rumur::Ptr::make($2, @$); } | '~' expr { $$ = rumur::Ptr::make($2, @$); } | expr '<' expr { $$ = rumur::Ptr::make($1, $3, @$); } | expr LEQ expr { $$ = rumur::Ptr::make($1, $3, @$); } | expr '>' expr { $$ = rumur::Ptr::make($1, $3, @$); } | expr GEQ expr { $$ = rumur::Ptr::make($1, $3, @$); } | expr DEQ expr { $$ = rumur::Ptr::make($1, $3, @$); } | expr '=' expr { $$ = rumur::Ptr::make($1, $3, @$); } | expr NEQ expr { $$ = rumur::Ptr::make($1, $3, @$); } | expr LSH expr { $$ = rumur::Ptr::make($1, $3, @$); } | expr RSH expr { $$ = rumur::Ptr::make($1, $3, @$); } | expr '+' expr { $$ = rumur::Ptr::make($1, $3, @$); } | expr '-' expr { $$ = rumur::Ptr::make($1, $3, @$); } | '+' expr %prec '*' { $$ = $2; $$->loc = @$; } | '-' expr %prec '*' { $$ = rumur::Ptr::make($2, @$); } | expr '*' expr { $$ = rumur::Ptr::make($1, $3, @$); } | expr '/' expr { $$ = rumur::Ptr::make($1, $3, @$); } | expr '%' expr { $$ = rumur::Ptr::make($1, $3, @$); } | FORALL quantifier DO expr endforall { $$ = rumur::Ptr::make(*$2, $4, @$); } | EXISTS quantifier DO expr endexists { $$ = rumur::Ptr::make(*$2, $4, @$); } | designator { $$ = $1; } | NUMBER { $$ = rumur::Ptr::make($1, @$); } | '(' expr ')' { $$ = $2; $$->loc = @$; } | ID '(' exprlist ')' { $$ = rumur::Ptr::make($1, $3, @$); } | ISUNDEFINED '(' designator ')' { $$ = rumur::Ptr::make($3, @$); }; exprdecl: id_list_opt ':' expr { for (const std::pair &m : $1) { $$.push_back(std::make_tuple(m.first, $3, @$)); } }; exprdecls: exprdecls exprdecl semi_opt { $$ = $1; std::move($2.begin(), $2.end(), std::back_inserter($$)); } | %empty { /* nothing required */ }; exprlist: exprlist_cont expr comma_opt { $$ = $1; $$.push_back($2); } | %empty { }; exprlist_cont: exprlist_cont expr ',' { $$ = $1; $$.push_back($2); } | %empty { }; function: FUNCTION | PROCEDURE; guard_opt: expr ARROW { $$ = $1; } | %empty { $$ = nullptr; }; id_list: id_list ',' ID { $$ = $1; $$.emplace_back(std::make_pair($3, @3)); } | ID { $$.emplace_back(std::make_pair($1, @$)); }; /* Support optional trailing comma to make it easier for tools that generate * an input mdoels. */ id_list_opt: id_list comma_opt { $$ = $1; } | %empty { }; parameter: var_opt id_list ':' typeexpr { for (const std::pair &i : $2) { auto v = rumur::Ptr::make(i.first, $4, @$); v->readonly = !*$1; $$.push_back(v); } }; parameters: parameters parameter semi_opt { $$ = $1; std::move($2.begin(), $2.end(), std::back_inserter($$)); } | %empty { }; procdecl: function ID '(' parameters ')' return_type decls begin_opt stmts endfunction semi_opt { $$ = rumur::Ptr::make($2, $4, $6, $7, $9, @$); }; property: category STRING expr { rumur::Property p(*$1, $3, @3); $$ = rumur::Ptr::make($2, p, @$); } | category expr string_opt { rumur::Property p(*$1, $2, @2); $$ = rumur::Ptr::make($3, p, @$); }; quantifier: ID ':' typeexpr { $$ = std::make_shared($1, $3, @$); } | ID COLON_EQ expr TO expr BY expr { $$ = std::make_shared($1, $3, $5, $7, @$); } | ID COLON_EQ expr TO expr { $$ = std::make_shared($1, $3, $5, @$); }; quantifiers: quantifiers semis quantifier { $$ = $1; $$.push_back(*$3); } | quantifier { $$.push_back(*$1); }; return_type: ':' typeexpr semi_opt { $$ = $2; } | semi_opt { $$ = nullptr; }; rule: startstate { $$ = $1; } | simplerule { $$ = $1; } | property { $$ = $1; } | ruleset { $$ = $1; } | aliasrule { $$ = $1; }; rules: rules rule semi_opt { $$ = $1; $$.push_back($2); } | %empty { }; ruleset: RULESET quantifiers DO rules endruleset { $$ = rumur::Ptr::make($2, $4, @$); }; semi_opt: semi_opt ';' | %empty; semis: semi_opt ';'; simplerule: RULE string_opt guard_opt decls_header stmts endrule { $$ = rumur::Ptr::make($2, $3, $4, $5, @$); }; startstate: STARTSTATE string_opt decls_header stmts endstartstate { $$ = rumur::Ptr::make($2, $3, $4, @$); }; stmt: category STRING expr { rumur::Property p(*$1, $3, @3); $$ = rumur::Ptr::make(p, $2, @$); } | category expr string_opt { rumur::Property p(*$1, $2, @2); $$ = rumur::Ptr::make(p, $3, @$); } | designator COLON_EQ expr { $$ = rumur::Ptr::make($1, $3, @$); } | ALIAS exprdecls DO stmts endalias { std::vector> decls; for (const std::tuple, rumur::location> &d : $2) { decls.push_back(rumur::Ptr::make(std::get<0>(d), std::get<1>(d), std::get<2>(d))); } $$ = rumur::Ptr::make(decls, $4, @$); } | ERROR STRING { $$ = rumur::Ptr::make($2, @$); } | CLEAR designator { $$ = rumur::Ptr::make($2, @$); } | FOR quantifier DO stmts endfor { $$ = rumur::Ptr::make(*$2, $4, @$); } | IF expr THEN stmts elsifs else_opt endif { std::vector cs = { rumur::IfClause($2, $4, rumur::location(@1.begin, @4.end)) }; cs.insert(cs.end(), $5.begin(), $5.end()); cs.insert(cs.end(), $6.begin(), $6.end()); $$ = rumur::Ptr::make(cs, @$); } | PUT STRING { $$ = rumur::Ptr::make($2, @$); } | PUT expr { $$ = rumur::Ptr::make($2, @$); } | RETURN { $$ = rumur::Ptr::make(nullptr, @$); } | RETURN expr { $$ = rumur::Ptr::make($2, @$); } | UNDEFINE designator { $$ = rumur::Ptr::make($2, @$); } | ID '(' exprlist ')' { $$ = rumur::Ptr::make($1, $3, @$); } | WHILE expr DO stmts endwhile { $$ = rumur::Ptr::make($2, $4, @$); } | SWITCH expr switchcases endswitch { $$ = rumur::Ptr::make($2, $3, @$); }; stmts: stmts_cont stmt semi_opt { $$ = $1; $$.push_back($2); } | stmt semi_opt { $$.push_back($1); } | %empty { }; stmts_cont: stmts_cont stmt semis { $$ = $1; $$.push_back($2); } | stmt semis { $$.push_back($1); }; string_opt: STRING { $$ = $1; } | %empty { /* nothing required */ }; switchcases: switchcases_cont ELSE stmts { $$ = $1; $$.push_back(rumur::SwitchCase(std::vector>(), $3, @$)); } | switchcases_cont { $$ = $1; }; switchcases_cont: switchcases_cont CASE exprlist ':' stmts { $$ = $1; $$.push_back(rumur::SwitchCase($3, $5, @$)); } | %empty { /* nothing required */ }; typedecl: id_list_opt ':' typeexpr { for (const std::pair &m : $1) { $$.push_back(rumur::Ptr::make(m.first, $3, @$)); } }; typedecls: typedecls typedecl semi_opt { $$ = $1; std::move($2.begin(), $2.end(), std::back_inserter($$)); } | %empty { /* nothing required */ }; typeexpr: BOOLEAN { /* We need to special case this instead of just using the ID rule because IDs * are treated as case-sensitive while "boolean" is not. To avoid awkwardness * in later symbol resolution, we force it to lower case here. */ $$ = rumur::Ptr::make("boolean", nullptr, @$); } | ID { $$ = rumur::Ptr::make($1, nullptr, @$); } | expr DOTDOT expr { $$ = rumur::Ptr::make($1, $3, @$); } | ENUM '{' id_list_opt '}' { $$ = rumur::Ptr::make($3, @$); } | RECORD vardecls endrecord { $$ = rumur::Ptr::make($2, @$); } | ARRAY '[' typeexpr ']' OF typeexpr { $$ = rumur::Ptr::make($3, $6, @$); } | SCALARSET '(' expr ')' { $$ = rumur::Ptr::make($3, @$); }; vardecl: id_list_opt ':' typeexpr { for (const std::pair &m : $1) { $$.push_back(rumur::Ptr::make(m.first, $3, @$)); } }; vardecls: vardecls vardecl semi_opt { $$ = $1; std::move($2.begin(), $2.end(), std::back_inserter($$)); } | %empty { /* nothing required */ }; var_opt: VAR { $$ = std::make_shared(true); } | %empty { $$ = std::make_shared(false); }; %% void rumur::parser::error(const location_type &loc, const std::string &message) { throw Error(message, loc); } rumur-2024.05.07/librumur/src/resolve-symbols.cc000066400000000000000000000343321461637631000214050ustar00rootroot00000000000000#include "../../common/isa.h" #include "location.hh" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace rumur; namespace { class Resolver : public Traversal { private: Symtab symtab; public: Resolver() { // Open a global scope symtab.open_scope(); // Teach the symbol table the built ins auto td = Ptr::make("boolean", Boolean, location()); symtab.declare("boolean", td); mpz_class index = 0; for (const std::pair &m : Boolean->members) { symtab.declare(m.first, Ptr::make("boolean", Ptr::make(index, location()), Boolean, location())); index++; } } void visit_add(Add &n) final { visit_bexpr(n); } void visit_aliasdecl(AliasDecl &n) final { dispatch(*n.value); disambiguate(n.value); } void visit_aliasrule(AliasRule &n) final { symtab.open_scope(); for (auto &a : n.aliases) { dispatch(*a); symtab.declare(a->name, a); } for (auto &r : n.rules) dispatch(*r); symtab.close_scope(); } void visit_aliasstmt(AliasStmt &n) final { symtab.open_scope(); for (auto &a : n.aliases) { dispatch(*a); symtab.declare(a->name, a); } for (auto &s : n.body) dispatch(*s); symtab.close_scope(); } void visit_ambiguousamp(AmbiguousAmp &n) final { visit_bexpr(n); } void visit_ambiguouspipe(AmbiguousPipe &n) final { visit_bexpr(n); } void visit_and(And &n) final { visit_bexpr(n); } void visit_assignment(Assignment &n) final { dispatch(*n.lhs); dispatch(*n.rhs); disambiguate(n.lhs); disambiguate(n.rhs); } void visit_band(Band &n) final { visit_bexpr(n); } void visit_bnot(Bnot &n) final { visit_uexpr(n); } void visit_bor(Bor &n) final { visit_bexpr(n); } void visit_clear(Clear &n) final { dispatch(*n.rhs); disambiguate(n.rhs); } void visit_constdecl(ConstDecl &n) final { dispatch(*n.value); disambiguate(n.value); } void visit_div(Div &n) final { visit_bexpr(n); } void visit_element(Element &n) final { dispatch(*n.array); dispatch(*n.index); disambiguate(n.array); disambiguate(n.index); } void visit_enum(Enum &n) final { auto e = Ptr::make(n); // register all the enum members so they can be referenced later mpz_class index = 0; size_t id = e->unique_id + 1; for (const std::pair &m : n.members) { auto cd = Ptr::make( m.first, Ptr::make(index, m.second), e, m.second); // assign this member a unique id so that referrers can use it if need be assert(id < e->unique_id_limit && "number of enum members exceeds what was expected"); cd->unique_id = id; symtab.declare(m.first, cd); index++; id++; } } void visit_eq(Eq &n) final { visit_bexpr(n); } void visit_exists(Exists &n) final { symtab.open_scope(); dispatch(n.quantifier); dispatch(*n.expr); symtab.close_scope(); disambiguate(n.expr); } void visit_exprid(ExprID &n) final { if (n.value == nullptr) { // This reference is unresolved Ptr d = symtab.lookup(n.id, n.loc); if (d == nullptr) throw Error("unknown symbol \"" + n.id + "\"", n.loc); n.value = d; } } void visit_field(Field &n) final { dispatch(*n.record); disambiguate(n.record); } void visit_for(For &n) final { symtab.open_scope(); dispatch(n.quantifier); for (auto &s : n.body) dispatch(*s); symtab.close_scope(); } void visit_forall(Forall &n) final { symtab.open_scope(); dispatch(n.quantifier); dispatch(*n.expr); symtab.close_scope(); disambiguate(n.expr); } void visit_function(Function &n) final { symtab.open_scope(); for (auto &p : n.parameters) dispatch(*p); if (n.return_type != nullptr) dispatch(*n.return_type); // register the function itself, even though its body has not yet been // resolved, in order to allow contained function calls to resolve to the // containing function, supporting recursion symtab.declare(n.name, Ptr::make(n)); // only register the function parameters now, to avoid their names shadowing // anything that needs to be resolved during symbol resolution of another // parameter or the return type for (auto &p : n.parameters) symtab.declare(p->name, p); for (auto &d : n.decls) { dispatch(*d); symtab.declare(d->name, d); } for (auto &s : n.body) dispatch(*s); symtab.close_scope(); } void visit_functioncall(FunctionCall &n) final { if (n.function == nullptr) { // This reference is unresolved Ptr f = symtab.lookup(n.name, n.loc); if (f == nullptr) throw Error("unknown function call \"" + n.name + "\"", n.loc); n.function = f; } for (auto &a : n.arguments) dispatch(*a); for (Ptr &a : n.arguments) disambiguate(a); } void visit_geq(Geq &n) final { visit_bexpr(n); } void visit_gt(Gt &n) final { visit_bexpr(n); } void visit_ifclause(IfClause &n) final { if (n.condition != nullptr) dispatch(*n.condition); for (auto &s : n.body) dispatch(*s); if (n.condition != nullptr) disambiguate(n.condition); } void visit_implication(Implication &n) final { visit_bexpr(n); } void visit_isundefined(IsUndefined &n) final { visit_uexpr(n); } void visit_leq(Leq &n) final { visit_bexpr(n); } void visit_lsh(Lsh &n) final { visit_bexpr(n); } void visit_lt(Lt &n) final { visit_bexpr(n); } void visit_model(Model &n) final { // running marker of offset in the global state data mpz_class offset = 0; /* whether we have not yet hit any problems that make offset calculation * impossible */ bool ok = true; for (Ptr &c : n.children) { dispatch(*c); /* if this was a variable declaration, we now know enough to determine its * offset in the global state data */ if (ok) { if (auto v = dynamic_cast(c.get())) { /* If the declaration or one of its children does not validate, it is * unsafe to call width(). */ try { validate(*v); } catch (Error &) { /* Skip this and future offset calculations and assume our caller * will eventually discover the underlying reason when they call * n.validate(). */ ok = false; } if (ok) { v->offset = offset; offset += v->type->width(); } } } if (auto d = dynamic_cast(c.get())) symtab.declare(d->name, c); if (auto f = dynamic_cast(c.get())) symtab.declare(f->name, c); } } void visit_mod(Mod &n) final { visit_bexpr(n); } void visit_mul(Mul &n) final { visit_bexpr(n); } void visit_negative(Negative &n) final { visit_uexpr(n); } void visit_neq(Neq &n) final { visit_bexpr(n); } void visit_not(Not &n) final { visit_uexpr(n); } void visit_or(Or &n) final { visit_bexpr(n); } void visit_property(Property &n) final { dispatch(*n.expr); disambiguate(n.expr); } void visit_put(Put &n) final { if (n.expr != nullptr) { dispatch(*n.expr); disambiguate(n.expr); } } void visit_quantifier(Quantifier &n) final { if (n.type != nullptr) { // wrap symbol resolution within the type in a dummy scope to suppress any // declarations (primarily enum members) as these will be duplicated in // when we descend into decl below symtab.open_scope(); dispatch(*n.type); symtab.close_scope(); } if (n.from != nullptr) dispatch(*n.from); if (n.to != nullptr) dispatch(*n.to); if (n.step != nullptr) dispatch(*n.step); if (n.from != nullptr) disambiguate(n.from); if (n.to != nullptr) disambiguate(n.to); if (n.step != nullptr) disambiguate(n.step); // if the bounds for this iteration are now known to be constant, we can // narrow its VarDecl if (n.from != nullptr && n.from->constant() && n.to != nullptr && n.to->constant()) { auto r = dynamic_cast(n.decl->type.get()); assert(r != nullptr && "non-range type used for inferred loop decl"); // the range may have been given as either an up count or down count if (n.from->constant_fold() <= n.to->constant_fold()) { r->min = n.from; r->max = n.to; } else { r->min = n.to; r->max = n.from; } } dispatch(*n.decl); symtab.declare(n.name, n.decl); } void visit_range(Range &n) final { dispatch(*n.min); disambiguate(n.min); dispatch(*n.max); disambiguate(n.max); } void visit_return(Return &n) final { if (n.expr != nullptr) { dispatch(*n.expr); disambiguate(n.expr); } } void visit_rsh(Rsh &n) final { visit_bexpr(n); } void visit_ruleset(Ruleset &n) final { symtab.open_scope(); for (Quantifier &q : n.quantifiers) dispatch(q); for (auto &r : n.rules) dispatch(*r); symtab.close_scope(); } void visit_scalarset(Scalarset &n) final { dispatch(*n.bound); disambiguate(n.bound); } void visit_simplerule(SimpleRule &n) final { symtab.open_scope(); for (Quantifier &q : n.quantifiers) dispatch(q); if (n.guard != nullptr) dispatch(*n.guard); for (auto &d : n.decls) { dispatch(*d); symtab.declare(d->name, d); } for (auto &s : n.body) dispatch(*s); symtab.close_scope(); if (n.guard != nullptr) disambiguate(n.guard); } void visit_startstate(StartState &n) final { symtab.open_scope(); for (Quantifier &q : n.quantifiers) dispatch(q); for (auto &d : n.decls) { dispatch(*d); symtab.declare(d->name, d); } for (auto &s : n.body) dispatch(*s); symtab.close_scope(); } void visit_sub(Sub &n) final { visit_bexpr(n); } void visit_switch(Switch &n) final { dispatch(*n.expr); for (SwitchCase &c : n.cases) dispatch(c); disambiguate(n.expr); } void visit_switchcase(SwitchCase &n) final { for (auto &m : n.matches) dispatch(*m); for (auto &s : n.body) dispatch(*s); for (Ptr &m : n.matches) disambiguate(m); } void visit_ternary(Ternary &n) { dispatch(*n.cond); dispatch(*n.lhs); dispatch(*n.rhs); disambiguate(n.cond); disambiguate(n.lhs); disambiguate(n.rhs); } void visit_typeexprid(TypeExprID &n) final { if (n.referent == nullptr) { // This reference is unresolved Ptr t = symtab.lookup(n.name, n.loc); if (t == nullptr) throw Error("unknown type symbol \"" + n.name + "\"", n.loc); n.referent = t; } } void visit_undefine(Undefine &n) final { dispatch(*n.rhs); disambiguate(n.rhs); } void visit_while(While &n) final { dispatch(*n.condition); for (auto &s : n.body) dispatch(*s); disambiguate(n.condition); } void visit_xor(Xor &n) final { visit_bexpr(n); } virtual ~Resolver() = default; private: void visit_bexpr(BinaryExpr &n) { dispatch(*n.lhs); dispatch(*n.rhs); disambiguate(n.lhs); disambiguate(n.rhs); } void visit_uexpr(UnaryExpr &n) { dispatch(*n.rhs); disambiguate(n.rhs); } // detect whether this is an ambiguous node and, if so, resolve it into its // more precise AST node type void disambiguate(Ptr &e) { if (auto a = dynamic_cast(e.get())) { // try to get the type of the left hand side Ptr t; try { t = a->lhs->type(); } catch (Error &) { // We failed because the left operand is somehow invalid. Silently // ignore this, assuming it will be rediscovered during AST validation. return; } // Form an unambiguous replacement node based on the type of the left // operand. Note that the types of the left and right operands may be // incompatible. However, this will cause an error during AST validation // so we do not need to worry about that here. Ptr replacement; if (isa(t)) { replacement = Ptr::make(a->lhs, a->rhs, a->loc); } else { replacement = Ptr::make(a->lhs, a->rhs, a->loc); } // also preserve the identifier which has already been set replacement->unique_id = a->unique_id; // replace the ambiguous node e = replacement; return; } if (auto o = dynamic_cast(e.get())) { // try to get the type of the left hand side Ptr t; try { t = o->lhs->type(); } catch (Error &) { // We failed because the left operand is somehow invalid. Silently // ignore this, assuming it will be rediscovered during AST validation. return; } // Form an unambiguous replacement node based on the type of the left // operand. Note that the types of the left and right operands may be // incompatible. However, this will cause an error during AST validation // so we do not need to worry about that here. Ptr replacement; if (isa(t)) { replacement = Ptr::make(o->lhs, o->rhs, o->loc); } else { replacement = Ptr::make(o->lhs, o->rhs, o->loc); } // also preserve the identifier which has already been set replacement->unique_id = o->unique_id; // replace the ambiguous node e = replacement; return; } } }; } // namespace void rumur::resolve_symbols(Model &m) { Resolver r; r.dispatch(m); } rumur-2024.05.07/librumur/src/sanitise_rule_names.cc000066400000000000000000000030551461637631000222670ustar00rootroot00000000000000#include #include #include #include #include #include #include #include using namespace rumur; namespace { class RuleNamer : public Traversal { private: size_t index = 0; std::unordered_set used; // names already taken void name(Rule &n, const std::string &prefix) { std::string replacement = n.name; // name by index if there is no name if (replacement == "") { replacement = prefix + std::to_string(index); index++; } // remove non-symbol characters for (char &c : replacement) { if (!(c >= 'a' && c <= 'z') && !(c >= 'A' && c <= 'Z') && !(c >= '0' && c <= '9')) { c = '_'; } } std::string candidate = replacement; while (!used.insert(candidate).second) { candidate = replacement + std::to_string(index); index++; } n.name = candidate; } public: void visit_aliasrule(AliasRule &n) final { name(n, "alias"); for (Ptr &r : n.rules) { dispatch(*r); } } void visit_propertyrule(PropertyRule &n) final { name(n, "property"); } void visit_ruleset(Ruleset &n) final { name(n, "ruleset"); for (Ptr &r : n.rules) { dispatch(*r); } } void visit_simplerule(SimpleRule &n) final { name(n, "rule"); } void visit_startstate(StartState &n) final { name(n, "startstate"); } }; } // namespace void rumur::sanitise_rule_names(Node &n) { RuleNamer r; r.dispatch(n); } rumur-2024.05.07/librumur/src/traverse.cc000066400000000000000000000714251461637631000200770ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include namespace rumur { void BaseTraversal::dispatch(Node &n) { n.visit(*this); } void BaseTraversal::visit_ambiguousamp(AmbiguousAmp &n) { dispatch(*n.lhs); dispatch(*n.rhs); } void BaseTraversal::visit_ambiguouspipe(AmbiguousPipe &n) { dispatch(*n.lhs); dispatch(*n.rhs); } void Traversal::visit_add(Add &n) { visit_bexpr(n); } void Traversal::visit_aliasdecl(AliasDecl &n) { dispatch(*n.value); } void Traversal::visit_aliasrule(AliasRule &n) { for (auto &a : n.aliases) dispatch(*a); for (auto &r : n.rules) dispatch(*r); } void Traversal::visit_aliasstmt(AliasStmt &n) { for (auto &a : n.aliases) dispatch(*a); for (auto &s : n.body) dispatch(*s); } void Traversal::visit_and(And &n) { visit_bexpr(n); } void Traversal::visit_array(Array &n) { dispatch(*n.index_type); dispatch(*n.element_type); } void Traversal::visit_assignment(Assignment &n) { dispatch(*n.lhs); dispatch(*n.rhs); } void Traversal::visit_band(Band &n) { visit_bexpr(n); } void Traversal::visit_bnot(Bnot &n) { visit_uexpr(n); } void Traversal::visit_bor(Bor &n) { visit_bexpr(n); } void Traversal::visit_bexpr(BinaryExpr &n) { dispatch(*n.lhs); dispatch(*n.rhs); } void Traversal::visit_clear(Clear &n) { dispatch(*n.rhs); } void Traversal::visit_constdecl(ConstDecl &n) { dispatch(*n.value); } void Traversal::visit_div(Div &n) { visit_bexpr(n); } void Traversal::visit_element(Element &n) { dispatch(*n.array); dispatch(*n.index); } void Traversal::visit_enum(Enum &) {} void Traversal::visit_eq(Eq &n) { visit_bexpr(n); } void Traversal::visit_errorstmt(ErrorStmt &) {} void Traversal::visit_exists(Exists &n) { dispatch(n.quantifier); dispatch(*n.expr); } void Traversal::visit_exprid(ExprID &) {} void Traversal::visit_field(Field &n) { dispatch(*n.record); } void Traversal::visit_for(For &n) { dispatch(n.quantifier); for (auto &s : n.body) dispatch(*s); } void Traversal::visit_forall(Forall &n) { dispatch(n.quantifier); dispatch(*n.expr); } void Traversal::visit_function(Function &n) { for (auto &p : n.parameters) dispatch(*p); if (n.return_type != nullptr) dispatch(*n.return_type); for (auto &d : n.decls) dispatch(*d); for (auto &s : n.body) dispatch(*s); } void Traversal::visit_functioncall(FunctionCall &n) { for (auto &a : n.arguments) dispatch(*a); } void Traversal::visit_geq(Geq &n) { visit_bexpr(n); } void Traversal::visit_gt(Gt &n) { visit_bexpr(n); } void Traversal::visit_if(If &n) { for (IfClause &c : n.clauses) dispatch(c); } void Traversal::visit_ifclause(IfClause &n) { if (n.condition != nullptr) dispatch(*n.condition); for (auto &s : n.body) dispatch(*s); } void Traversal::visit_implication(Implication &n) { visit_bexpr(n); } void Traversal::visit_isundefined(IsUndefined &n) { visit_uexpr(n); } void Traversal::visit_leq(Leq &n) { visit_bexpr(n); } void Traversal::visit_lsh(Lsh &n) { visit_bexpr(n); } void Traversal::visit_lt(Lt &n) { visit_bexpr(n); } void Traversal::visit_mod(Mod &n) { visit_bexpr(n); } void Traversal::visit_model(Model &n) { for (Ptr &c : n.children) dispatch(*c); } void Traversal::visit_mul(Mul &n) { visit_bexpr(n); } void Traversal::visit_negative(Negative &n) { visit_uexpr(n); } void Traversal::visit_neq(Neq &n) { visit_bexpr(n); } void Traversal::visit_not(Not &n) { visit_uexpr(n); } void Traversal::visit_number(Number &) {} void Traversal::visit_or(Or &n) { visit_bexpr(n); } void Traversal::visit_procedurecall(ProcedureCall &n) { dispatch(n.call); } void Traversal::visit_property(Property &n) { dispatch(*n.expr); } void Traversal::visit_propertyrule(PropertyRule &n) { for (Quantifier &q : n.quantifiers) dispatch(q); dispatch(n.property); } void Traversal::visit_propertystmt(PropertyStmt &n) { dispatch(n.property); } void Traversal::visit_put(Put &n) { if (n.expr != nullptr) dispatch(*n.expr); } void Traversal::visit_quantifier(Quantifier &n) { if (n.type != nullptr) dispatch(*n.type); if (n.from != nullptr) dispatch(*n.from); if (n.to != nullptr) dispatch(*n.to); if (n.step != nullptr) dispatch(*n.step); } void Traversal::visit_range(Range &n) { dispatch(*n.min); dispatch(*n.max); } void Traversal::visit_record(Record &n) { for (auto &f : n.fields) dispatch(*f); } void Traversal::visit_return(Return &n) { if (n.expr != nullptr) dispatch(*n.expr); } void Traversal::visit_rsh(Rsh &n) { visit_bexpr(n); } void Traversal::visit_ruleset(Ruleset &n) { for (Quantifier &q : n.quantifiers) dispatch(q); for (auto &r : n.rules) dispatch(*r); } void Traversal::visit_scalarset(Scalarset &n) { dispatch(*n.bound); } void Traversal::visit_simplerule(SimpleRule &n) { for (Quantifier &q : n.quantifiers) dispatch(q); if (n.guard != nullptr) dispatch(*n.guard); for (auto &d : n.decls) dispatch(*d); for (auto &s : n.body) dispatch(*s); } void Traversal::visit_startstate(StartState &n) { for (Quantifier &q : n.quantifiers) dispatch(q); for (auto &d : n.decls) dispatch(*d); for (auto &s : n.body) dispatch(*s); } void Traversal::visit_sub(Sub &n) { visit_bexpr(n); } void Traversal::visit_switch(Switch &n) { dispatch(*n.expr); for (SwitchCase &c : n.cases) dispatch(c); } void Traversal::visit_switchcase(SwitchCase &n) { for (auto &m : n.matches) dispatch(*m); for (auto &s : n.body) dispatch(*s); } void Traversal::visit_ternary(Ternary &n) { dispatch(*n.cond); dispatch(*n.lhs); dispatch(*n.rhs); } void Traversal::visit_typedecl(TypeDecl &n) { dispatch(*n.value); } void Traversal::visit_typeexprid(TypeExprID &) {} void Traversal::visit_uexpr(UnaryExpr &n) { dispatch(*n.rhs); } void Traversal::visit_undefine(Undefine &n) { dispatch(*n.rhs); } void Traversal::visit_vardecl(VarDecl &n) { if (n.type != nullptr) dispatch(*n.type); } void Traversal::visit_while(While &n) { dispatch(*n.condition); for (auto &s : n.body) dispatch(*s); } void Traversal::visit_xor(Xor &n) { visit_bexpr(n); } Traversal::~Traversal() {} void ConstBaseTraversal::dispatch(const Node &n) { n.visit(*this); } void ConstBaseTraversal::visit_ambiguousamp(const AmbiguousAmp &n) { dispatch(*n.lhs); dispatch(*n.rhs); } void ConstBaseTraversal::visit_ambiguouspipe(const AmbiguousPipe &n) { dispatch(*n.lhs); dispatch(*n.rhs); } void ConstTraversal::visit_add(const Add &n) { visit_bexpr(n); } void ConstTraversal::visit_aliasdecl(const AliasDecl &n) { dispatch(*n.value); } void ConstTraversal::visit_aliasrule(const AliasRule &n) { for (auto &a : n.aliases) dispatch(*a); for (auto &r : n.rules) dispatch(*r); } void ConstTraversal::visit_aliasstmt(const AliasStmt &n) { for (auto &a : n.aliases) dispatch(*a); for (auto &s : n.body) dispatch(*s); } void ConstTraversal::visit_and(const And &n) { visit_bexpr(n); } void ConstTraversal::visit_array(const Array &n) { dispatch(*n.index_type); dispatch(*n.element_type); } void ConstTraversal::visit_assignment(const Assignment &n) { dispatch(*n.lhs); dispatch(*n.rhs); } void ConstTraversal::visit_band(const Band &n) { visit_bexpr(n); } void ConstTraversal::visit_bnot(const Bnot &n) { visit_uexpr(n); } void ConstTraversal::visit_bor(const Bor &n) { visit_bexpr(n); } void ConstTraversal::visit_bexpr(const BinaryExpr &n) { dispatch(*n.lhs); dispatch(*n.rhs); } void ConstTraversal::visit_clear(const Clear &n) { dispatch(*n.rhs); } void ConstTraversal::visit_constdecl(const ConstDecl &n) { dispatch(*n.value); } void ConstTraversal::visit_div(const Div &n) { visit_bexpr(n); } void ConstTraversal::visit_element(const Element &n) { dispatch(*n.array); dispatch(*n.index); } void ConstTraversal::visit_enum(const Enum &) {} void ConstTraversal::visit_eq(const Eq &n) { visit_bexpr(n); } void ConstTraversal::visit_errorstmt(const ErrorStmt &) {} void ConstTraversal::visit_exists(const Exists &n) { dispatch(n.quantifier); dispatch(*n.expr); } void ConstTraversal::visit_exprid(const ExprID &) {} void ConstTraversal::visit_field(const Field &n) { dispatch(*n.record); } void ConstTraversal::visit_for(const For &n) { dispatch(n.quantifier); for (auto &s : n.body) dispatch(*s); } void ConstTraversal::visit_forall(const Forall &n) { dispatch(n.quantifier); dispatch(*n.expr); } void ConstTraversal::visit_function(const Function &n) { for (auto &p : n.parameters) dispatch(*p); if (n.return_type != nullptr) dispatch(*n.return_type); for (auto &d : n.decls) dispatch(*d); for (auto &s : n.body) dispatch(*s); } void ConstTraversal::visit_functioncall(const FunctionCall &n) { for (auto &a : n.arguments) dispatch(*a); } void ConstTraversal::visit_geq(const Geq &n) { visit_bexpr(n); } void ConstTraversal::visit_gt(const Gt &n) { visit_bexpr(n); } void ConstTraversal::visit_if(const If &n) { for (const IfClause &c : n.clauses) dispatch(c); } void ConstTraversal::visit_ifclause(const IfClause &n) { if (n.condition != nullptr) dispatch(*n.condition); for (auto &s : n.body) dispatch(*s); } void ConstTraversal::visit_implication(const Implication &n) { visit_bexpr(n); } void ConstTraversal::visit_isundefined(const IsUndefined &n) { visit_uexpr(n); } void ConstTraversal::visit_leq(const Leq &n) { visit_bexpr(n); } void ConstTraversal::visit_lsh(const Lsh &n) { visit_bexpr(n); } void ConstTraversal::visit_lt(const Lt &n) { visit_bexpr(n); } void ConstTraversal::visit_mod(const Mod &n) { visit_bexpr(n); } void ConstTraversal::visit_model(const Model &n) { for (const Ptr &c : n.children) dispatch(*c); } void ConstTraversal::visit_mul(const Mul &n) { visit_bexpr(n); } void ConstTraversal::visit_negative(const Negative &n) { visit_uexpr(n); } void ConstTraversal::visit_neq(const Neq &n) { visit_bexpr(n); } void ConstTraversal::visit_not(const Not &n) { visit_uexpr(n); } void ConstTraversal::visit_number(const Number &) {} void ConstTraversal::visit_or(const Or &n) { visit_bexpr(n); } void ConstTraversal::visit_procedurecall(const ProcedureCall &n) { dispatch(n.call); } void ConstTraversal::visit_property(const Property &n) { dispatch(*n.expr); } void ConstTraversal::visit_propertyrule(const PropertyRule &n) { for (const Quantifier &q : n.quantifiers) dispatch(q); dispatch(n.property); } void ConstTraversal::visit_propertystmt(const PropertyStmt &n) { dispatch(n.property); } void ConstTraversal::visit_put(const Put &n) { if (n.expr != nullptr) dispatch(*n.expr); } void ConstTraversal::visit_quantifier(const Quantifier &n) { if (n.type != nullptr) dispatch(*n.type); if (n.from != nullptr) dispatch(*n.from); if (n.to != nullptr) dispatch(*n.to); if (n.step != nullptr) dispatch(*n.step); } void ConstTraversal::visit_range(const Range &n) { dispatch(*n.min); dispatch(*n.max); } void ConstTraversal::visit_record(const Record &n) { for (auto &f : n.fields) dispatch(*f); } void ConstTraversal::visit_return(const Return &n) { if (n.expr != nullptr) dispatch(*n.expr); } void ConstTraversal::visit_rsh(const Rsh &n) { visit_bexpr(n); } void ConstTraversal::visit_ruleset(const Ruleset &n) { for (const Quantifier &q : n.quantifiers) dispatch(q); for (auto &r : n.rules) dispatch(*r); } void ConstTraversal::visit_scalarset(const Scalarset &n) { dispatch(*n.bound); } void ConstTraversal::visit_simplerule(const SimpleRule &n) { for (const Quantifier &q : n.quantifiers) dispatch(q); if (n.guard != nullptr) dispatch(*n.guard); for (auto &d : n.decls) dispatch(*d); for (auto &s : n.body) dispatch(*s); } void ConstTraversal::visit_startstate(const StartState &n) { for (const Quantifier &q : n.quantifiers) dispatch(q); for (auto &d : n.decls) dispatch(*d); for (auto &s : n.body) dispatch(*s); } void ConstTraversal::visit_sub(const Sub &n) { visit_bexpr(n); } void ConstTraversal::visit_switch(const Switch &n) { dispatch(*n.expr); for (const SwitchCase &c : n.cases) dispatch(c); } void ConstTraversal::visit_switchcase(const SwitchCase &n) { for (auto &m : n.matches) dispatch(*m); for (auto &s : n.body) dispatch(*s); } void ConstTraversal::visit_ternary(const Ternary &n) { dispatch(*n.cond); dispatch(*n.lhs); dispatch(*n.rhs); } void ConstTraversal::visit_typedecl(const TypeDecl &n) { dispatch(*n.value); } void ConstTraversal::visit_typeexprid(const TypeExprID &) {} void ConstTraversal::visit_uexpr(const UnaryExpr &n) { dispatch(*n.rhs); } void ConstTraversal::visit_undefine(const Undefine &n) { dispatch(*n.rhs); } void ConstTraversal::visit_vardecl(const VarDecl &n) { if (n.type != nullptr) dispatch(*n.type); } void ConstTraversal::visit_while(const While &n) { dispatch(*n.condition); for (auto &s : n.body) dispatch(*s); } void ConstTraversal::visit_xor(const Xor &n) { visit_bexpr(n); } ConstTraversal::~ConstTraversal() {} void ConstExprTraversal::visit_aliasdecl(const AliasDecl &n) { dispatch(*n.value); } void ConstExprTraversal::visit_aliasrule(const AliasRule &n) { for (auto &a : n.aliases) dispatch(*a); for (auto &r : n.rules) dispatch(*r); } void ConstExprTraversal::visit_aliasstmt(const AliasStmt &n) { for (auto &a : n.aliases) dispatch(*a); for (auto &s : n.body) dispatch(*s); } void ConstExprTraversal::visit_array(const Array &n) { dispatch(*n.index_type); dispatch(*n.element_type); } void ConstExprTraversal::visit_assignment(const Assignment &n) { dispatch(*n.lhs); dispatch(*n.rhs); } void ConstExprTraversal::visit_clear(const Clear &n) { dispatch(*n.rhs); } void ConstExprTraversal::visit_constdecl(const ConstDecl &n) { dispatch(*n.value); } void ConstExprTraversal::visit_enum(const Enum &) {} void ConstExprTraversal::visit_errorstmt(const ErrorStmt &) {} void ConstExprTraversal::visit_for(const For &n) { dispatch(n.quantifier); for (auto &s : n.body) dispatch(*s); } void ConstExprTraversal::visit_function(const Function &n) { for (auto &p : n.parameters) dispatch(*p); if (n.return_type != nullptr) dispatch(*n.return_type); for (auto &d : n.decls) dispatch(*d); for (auto &s : n.body) dispatch(*s); } void ConstExprTraversal::visit_if(const If &n) { for (const IfClause &c : n.clauses) dispatch(c); } void ConstExprTraversal::visit_ifclause(const IfClause &n) { if (n.condition != nullptr) dispatch(*n.condition); for (auto &s : n.body) dispatch(*s); } void ConstExprTraversal::visit_model(const Model &n) { for (const Ptr &c : n.children) dispatch(*c); } void ConstExprTraversal::visit_procedurecall(const ProcedureCall &n) { dispatch(n.call); } void ConstExprTraversal::visit_property(const Property &n) { dispatch(*n.expr); } void ConstExprTraversal::visit_propertyrule(const PropertyRule &n) { for (const Quantifier &q : n.quantifiers) dispatch(q); dispatch(n.property); } void ConstExprTraversal::visit_propertystmt(const PropertyStmt &n) { dispatch(n.property); } void ConstExprTraversal::visit_put(const Put &n) { if (n.expr != nullptr) dispatch(*n.expr); } void ConstExprTraversal::visit_quantifier(const Quantifier &n) { if (n.type != nullptr) dispatch(*n.type); if (n.from != nullptr) dispatch(*n.from); if (n.to != nullptr) dispatch(*n.to); if (n.step != nullptr) dispatch(*n.step); } void ConstExprTraversal::visit_range(const Range &n) { dispatch(*n.min); dispatch(*n.max); } void ConstExprTraversal::visit_record(const Record &n) { for (auto &f : n.fields) dispatch(*f); } void ConstExprTraversal::visit_return(const Return &n) { if (n.expr != nullptr) dispatch(*n.expr); } void ConstExprTraversal::visit_ruleset(const Ruleset &n) { for (const Quantifier &q : n.quantifiers) dispatch(q); for (auto &r : n.rules) dispatch(*r); } void ConstExprTraversal::visit_scalarset(const Scalarset &n) { dispatch(*n.bound); } void ConstExprTraversal::visit_simplerule(const SimpleRule &n) { for (const Quantifier &q : n.quantifiers) dispatch(q); if (n.guard != nullptr) dispatch(*n.guard); for (auto &d : n.decls) dispatch(*d); for (auto &s : n.body) dispatch(*s); } void ConstExprTraversal::visit_startstate(const StartState &n) { for (const Quantifier &q : n.quantifiers) dispatch(q); for (auto &d : n.decls) dispatch(*d); for (auto &s : n.body) dispatch(*s); } void ConstExprTraversal::visit_switch(const Switch &n) { dispatch(*n.expr); for (const SwitchCase &c : n.cases) dispatch(c); } void ConstExprTraversal::visit_switchcase(const SwitchCase &n) { for (auto &m : n.matches) dispatch(*m); for (auto &s : n.body) dispatch(*s); } void ConstExprTraversal::visit_typedecl(const TypeDecl &n) { dispatch(*n.value); } void ConstExprTraversal::visit_typeexprid(const TypeExprID &) {} void ConstExprTraversal::visit_undefine(const Undefine &n) { dispatch(*n.rhs); } void ConstExprTraversal::visit_vardecl(const VarDecl &n) { if (n.type != nullptr) dispatch(*n.type); } void ConstExprTraversal::visit_while(const While &n) { dispatch(*n.condition); for (auto &s : n.body) dispatch(*s); } void ConstStmtTraversal::visit_aliasdecl(const AliasDecl &n) { dispatch(*n.value); } void ConstStmtTraversal::visit_aliasrule(const AliasRule &n) { for (auto &a : n.aliases) dispatch(*a); for (auto &r : n.rules) dispatch(*r); } void ConstStmtTraversal::visit_add(const Add &n) { visit_bexpr(n); } void ConstStmtTraversal::visit_and(const And &n) { visit_bexpr(n); } void ConstStmtTraversal::visit_array(const Array &n) { dispatch(*n.index_type); dispatch(*n.element_type); } void ConstStmtTraversal::visit_band(const Band &n) { visit_bexpr(n); } void ConstStmtTraversal::visit_bnot(const Bnot &n) { visit_uexpr(n); } void ConstStmtTraversal::visit_bor(const Bor &n) { visit_bexpr(n); } void ConstStmtTraversal::visit_bexpr(const BinaryExpr &n) { dispatch(*n.lhs); dispatch(*n.rhs); } void ConstStmtTraversal::visit_constdecl(const ConstDecl &n) { dispatch(*n.value); } void ConstStmtTraversal::visit_div(const Div &n) { visit_bexpr(n); } void ConstStmtTraversal::visit_element(const Element &n) { dispatch(*n.array); dispatch(*n.index); } void ConstStmtTraversal::visit_enum(const Enum &) {} void ConstStmtTraversal::visit_eq(const Eq &n) { visit_bexpr(n); } void ConstStmtTraversal::visit_exists(const Exists &n) { dispatch(n.quantifier); dispatch(*n.expr); } void ConstStmtTraversal::visit_exprid(const ExprID &) {} void ConstStmtTraversal::visit_field(const Field &n) { dispatch(*n.record); } void ConstStmtTraversal::visit_forall(const Forall &n) { dispatch(n.quantifier); dispatch(*n.expr); } void ConstStmtTraversal::visit_function(const Function &n) { for (auto &p : n.parameters) dispatch(*p); if (n.return_type != nullptr) dispatch(*n.return_type); for (auto &d : n.decls) dispatch(*d); for (auto &s : n.body) dispatch(*s); } void ConstStmtTraversal::visit_functioncall(const FunctionCall &n) { for (auto &a : n.arguments) dispatch(*a); } void ConstStmtTraversal::visit_geq(const Geq &n) { visit_bexpr(n); } void ConstStmtTraversal::visit_gt(const Gt &n) { visit_bexpr(n); } void ConstStmtTraversal::visit_ifclause(const IfClause &n) { if (n.condition != nullptr) dispatch(*n.condition); for (auto &s : n.body) dispatch(*s); } void ConstStmtTraversal::visit_implication(const Implication &n) { visit_bexpr(n); } void ConstStmtTraversal::visit_isundefined(const IsUndefined &n) { visit_uexpr(n); } void ConstStmtTraversal::visit_leq(const Leq &n) { visit_bexpr(n); } void ConstStmtTraversal::visit_lsh(const Lsh &n) { visit_bexpr(n); } void ConstStmtTraversal::visit_lt(const Lt &n) { visit_bexpr(n); } void ConstStmtTraversal::visit_mod(const Mod &n) { visit_bexpr(n); } void ConstStmtTraversal::visit_model(const Model &n) { for (const Ptr &c : n.children) dispatch(*c); } void ConstStmtTraversal::visit_mul(const Mul &n) { visit_bexpr(n); } void ConstStmtTraversal::visit_negative(const Negative &n) { visit_uexpr(n); } void ConstStmtTraversal::visit_neq(const Neq &n) { visit_bexpr(n); } void ConstStmtTraversal::visit_not(const Not &n) { visit_uexpr(n); } void ConstStmtTraversal::visit_number(const Number &) {} void ConstStmtTraversal::visit_or(const Or &n) { visit_bexpr(n); } void ConstStmtTraversal::visit_property(const Property &n) { dispatch(*n.expr); } void ConstStmtTraversal::visit_propertyrule(const PropertyRule &n) { for (const Quantifier &q : n.quantifiers) dispatch(q); dispatch(n.property); } void ConstStmtTraversal::visit_quantifier(const Quantifier &n) { if (n.type != nullptr) dispatch(*n.type); if (n.from != nullptr) dispatch(*n.from); if (n.to != nullptr) dispatch(*n.to); if (n.step != nullptr) dispatch(*n.step); } void ConstStmtTraversal::visit_range(const Range &n) { dispatch(*n.min); dispatch(*n.max); } void ConstStmtTraversal::visit_record(const Record &n) { for (auto &f : n.fields) dispatch(*f); } void ConstStmtTraversal::visit_rsh(const Rsh &n) { visit_bexpr(n); } void ConstStmtTraversal::visit_ruleset(const Ruleset &n) { for (const Quantifier &q : n.quantifiers) dispatch(q); for (auto &r : n.rules) dispatch(*r); } void ConstStmtTraversal::visit_scalarset(const Scalarset &n) { dispatch(*n.bound); } void ConstStmtTraversal::visit_simplerule(const SimpleRule &n) { for (const Quantifier &q : n.quantifiers) dispatch(q); if (n.guard != nullptr) dispatch(*n.guard); for (auto &d : n.decls) dispatch(*d); for (auto &s : n.body) dispatch(*s); } void ConstStmtTraversal::visit_startstate(const StartState &n) { for (const Quantifier &q : n.quantifiers) dispatch(q); for (auto &d : n.decls) dispatch(*d); for (auto &s : n.body) dispatch(*s); } void ConstStmtTraversal::visit_switchcase(const SwitchCase &n) { for (auto &m : n.matches) dispatch(*m); for (auto &s : n.body) dispatch(*s); } void ConstStmtTraversal::visit_sub(const Sub &n) { visit_bexpr(n); } void ConstStmtTraversal::visit_ternary(const Ternary &n) { dispatch(*n.cond); dispatch(*n.lhs); dispatch(*n.rhs); } void ConstStmtTraversal::visit_typedecl(const TypeDecl &n) { dispatch(*n.value); } void ConstStmtTraversal::visit_typeexprid(const TypeExprID &) {} void ConstStmtTraversal::visit_uexpr(const UnaryExpr &n) { dispatch(*n.rhs); } void ConstStmtTraversal::visit_vardecl(const VarDecl &n) { if (n.type != nullptr) dispatch(*n.type); } void ConstStmtTraversal::visit_xor(const Xor &n) { visit_bexpr(n); } void ConstTypeTraversal::visit_add(const Add &n) { visit_bexpr(n); } void ConstTypeTraversal::visit_aliasdecl(const AliasDecl &n) { dispatch(*n.value); } void ConstTypeTraversal::visit_aliasrule(const AliasRule &n) { for (auto &a : n.aliases) dispatch(*a); for (auto &r : n.rules) dispatch(*r); } void ConstTypeTraversal::visit_aliasstmt(const AliasStmt &n) { for (auto &a : n.aliases) dispatch(*a); for (auto &s : n.body) dispatch(*s); } void ConstTypeTraversal::visit_and(const And &n) { visit_bexpr(n); } void ConstTypeTraversal::visit_assignment(const Assignment &n) { dispatch(*n.lhs); dispatch(*n.rhs); } void ConstTypeTraversal::visit_band(const Band &n) { visit_bexpr(n); } void ConstTypeTraversal::visit_bnot(const Bnot &n) { visit_uexpr(n); } void ConstTypeTraversal::visit_bor(const Bor &n) { visit_bexpr(n); } void ConstTypeTraversal::visit_bexpr(const BinaryExpr &n) { dispatch(*n.lhs); dispatch(*n.rhs); } void ConstTypeTraversal::visit_clear(const Clear &n) { dispatch(*n.rhs); } void ConstTypeTraversal::visit_constdecl(const ConstDecl &n) { dispatch(*n.value); } void ConstTypeTraversal::visit_div(const Div &n) { visit_bexpr(n); } void ConstTypeTraversal::visit_element(const Element &n) { dispatch(*n.array); dispatch(*n.index); } void ConstTypeTraversal::visit_eq(const Eq &n) { visit_bexpr(n); } void ConstTypeTraversal::visit_errorstmt(const ErrorStmt &) {} void ConstTypeTraversal::visit_exists(const Exists &n) { dispatch(n.quantifier); dispatch(*n.expr); } void ConstTypeTraversal::visit_exprid(const ExprID &) {} void ConstTypeTraversal::visit_field(const Field &n) { dispatch(*n.record); } void ConstTypeTraversal::visit_for(const For &n) { dispatch(n.quantifier); for (auto &s : n.body) dispatch(*s); } void ConstTypeTraversal::visit_forall(const Forall &n) { dispatch(n.quantifier); dispatch(*n.expr); } void ConstTypeTraversal::visit_function(const Function &n) { for (auto &p : n.parameters) dispatch(*p); if (n.return_type != nullptr) dispatch(*n.return_type); for (auto &d : n.decls) dispatch(*d); for (auto &s : n.body) dispatch(*s); } void ConstTypeTraversal::visit_functioncall(const FunctionCall &n) { for (auto &a : n.arguments) dispatch(*a); } void ConstTypeTraversal::visit_geq(const Geq &n) { visit_bexpr(n); } void ConstTypeTraversal::visit_gt(const Gt &n) { visit_bexpr(n); } void ConstTypeTraversal::visit_if(const If &n) { for (const IfClause &c : n.clauses) dispatch(c); } void ConstTypeTraversal::visit_ifclause(const IfClause &n) { if (n.condition != nullptr) dispatch(*n.condition); for (auto &s : n.body) dispatch(*s); } void ConstTypeTraversal::visit_implication(const Implication &n) { visit_bexpr(n); } void ConstTypeTraversal::visit_isundefined(const IsUndefined &n) { visit_uexpr(n); } void ConstTypeTraversal::visit_leq(const Leq &n) { visit_bexpr(n); } void ConstTypeTraversal::visit_lsh(const Lsh &n) { visit_bexpr(n); } void ConstTypeTraversal::visit_lt(const Lt &n) { visit_bexpr(n); } void ConstTypeTraversal::visit_mod(const Mod &n) { visit_bexpr(n); } void ConstTypeTraversal::visit_model(const Model &n) { for (const Ptr &c : n.children) dispatch(*c); } void ConstTypeTraversal::visit_mul(const Mul &n) { visit_bexpr(n); } void ConstTypeTraversal::visit_negative(const Negative &n) { visit_uexpr(n); } void ConstTypeTraversal::visit_neq(const Neq &n) { visit_bexpr(n); } void ConstTypeTraversal::visit_not(const Not &n) { visit_uexpr(n); } void ConstTypeTraversal::visit_number(const Number &) {} void ConstTypeTraversal::visit_or(const Or &n) { visit_bexpr(n); } void ConstTypeTraversal::visit_procedurecall(const ProcedureCall &n) { dispatch(n.call); } void ConstTypeTraversal::visit_property(const Property &n) { dispatch(*n.expr); } void ConstTypeTraversal::visit_propertyrule(const PropertyRule &n) { for (const Quantifier &q : n.quantifiers) dispatch(q); dispatch(n.property); } void ConstTypeTraversal::visit_propertystmt(const PropertyStmt &n) { dispatch(n.property); } void ConstTypeTraversal::visit_put(const Put &n) { if (n.expr != nullptr) dispatch(*n.expr); } void ConstTypeTraversal::visit_quantifier(const Quantifier &n) { if (n.type != nullptr) dispatch(*n.type); if (n.from != nullptr) dispatch(*n.from); if (n.to != nullptr) dispatch(*n.to); if (n.step != nullptr) dispatch(*n.step); } void ConstTypeTraversal::visit_return(const Return &n) { if (n.expr != nullptr) dispatch(*n.expr); } void ConstTypeTraversal::visit_rsh(const Rsh &n) { visit_bexpr(n); } void ConstTypeTraversal::visit_ruleset(const Ruleset &n) { for (const Quantifier &q : n.quantifiers) dispatch(q); for (auto &r : n.rules) dispatch(*r); } void ConstTypeTraversal::visit_simplerule(const SimpleRule &n) { for (const Quantifier &q : n.quantifiers) dispatch(q); if (n.guard != nullptr) dispatch(*n.guard); for (auto &d : n.decls) dispatch(*d); for (auto &s : n.body) dispatch(*s); } void ConstTypeTraversal::visit_startstate(const StartState &n) { for (const Quantifier &q : n.quantifiers) dispatch(q); for (auto &d : n.decls) dispatch(*d); for (auto &s : n.body) dispatch(*s); } void ConstTypeTraversal::visit_sub(const Sub &n) { visit_bexpr(n); } void ConstTypeTraversal::visit_switch(const Switch &n) { dispatch(*n.expr); for (const SwitchCase &c : n.cases) dispatch(c); } void ConstTypeTraversal::visit_switchcase(const SwitchCase &n) { for (auto &m : n.matches) dispatch(*m); for (auto &s : n.body) dispatch(*s); } void ConstTypeTraversal::visit_ternary(const Ternary &n) { dispatch(*n.cond); dispatch(*n.lhs); dispatch(*n.rhs); } void ConstTypeTraversal::visit_typedecl(const TypeDecl &n) { dispatch(*n.value); } void ConstTypeTraversal::visit_uexpr(const UnaryExpr &n) { dispatch(*n.rhs); } void ConstTypeTraversal::visit_undefine(const Undefine &n) { dispatch(*n.rhs); } void ConstTypeTraversal::visit_vardecl(const VarDecl &n) { if (n.type != nullptr) dispatch(*n.type); } void ConstTypeTraversal::visit_while(const While &n) { dispatch(*n.condition); for (auto &s : n.body) dispatch(*s); } void ConstTypeTraversal::visit_xor(const Xor &n) { visit_bexpr(n); } } // namespace rumur rumur-2024.05.07/librumur/src/validate.cc000066400000000000000000000213221461637631000200240ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace rumur; namespace { class Validator : public ConstBaseTraversal { public: void visit_add(const Add &n) final { dispatch(*n.lhs); dispatch(*n.rhs); n.validate(); } void visit_aliasdecl(const AliasDecl &n) final { dispatch(*n.value); n.validate(); } void visit_aliasrule(const AliasRule &n) final { for (auto &a : n.aliases) dispatch(*a); for (auto &r : n.rules) dispatch(*r); n.validate(); } void visit_aliasstmt(const AliasStmt &n) final { for (auto &a : n.aliases) dispatch(*a); for (auto &s : n.body) dispatch(*s); n.validate(); } void visit_ambiguousamp(const AmbiguousAmp &n) final { dispatch(*n.lhs); dispatch(*n.rhs); n.validate(); } void visit_ambiguouspipe(const AmbiguousPipe &n) final { dispatch(*n.lhs); dispatch(*n.rhs); n.validate(); } void visit_and(const And &n) final { dispatch(*n.lhs); dispatch(*n.rhs); n.validate(); } void visit_array(const Array &n) final { dispatch(*n.index_type); dispatch(*n.element_type); n.validate(); } void visit_assignment(const Assignment &n) final { dispatch(*n.lhs); dispatch(*n.rhs); n.validate(); } void visit_band(const Band &n) final { dispatch(*n.lhs); dispatch(*n.rhs); n.validate(); } void visit_bnot(const Bnot &n) final { dispatch(*n.rhs); n.validate(); } void visit_bor(const Bor &n) final { dispatch(*n.lhs); dispatch(*n.rhs); n.validate(); } void visit_clear(const Clear &n) final { dispatch(*n.rhs); n.validate(); } void visit_constdecl(const ConstDecl &n) final { dispatch(*n.value); n.validate(); } void visit_div(const Div &n) final { dispatch(*n.lhs); dispatch(*n.rhs); n.validate(); } void visit_element(const Element &n) final { dispatch(*n.array); dispatch(*n.index); n.validate(); } void visit_enum(const Enum &n) final { n.validate(); } void visit_eq(const Eq &n) final { dispatch(*n.lhs); dispatch(*n.rhs); n.validate(); } void visit_errorstmt(const ErrorStmt &n) final { n.validate(); } void visit_exists(const Exists &n) final { dispatch(n.quantifier); dispatch(*n.expr); n.validate(); } void visit_exprid(const ExprID &n) final { if (n.value != nullptr) dispatch(*n.value); n.validate(); } void visit_field(const Field &n) final { dispatch(*n.record); n.validate(); } void visit_for(const For &n) final { dispatch(n.quantifier); for (auto &s : n.body) dispatch(*s); n.validate(); } void visit_forall(const Forall &n) final { dispatch(n.quantifier); dispatch(*n.expr); n.validate(); } void visit_function(const Function &n) final { for (auto &p : n.parameters) dispatch(*p); if (n.return_type != nullptr) dispatch(*n.return_type); for (auto &d : n.decls) dispatch(*d); for (auto &s : n.body) dispatch(*s); n.validate(); } void visit_functioncall(const FunctionCall &n) final { for (auto &a : n.arguments) dispatch(*a); n.validate(); } void visit_geq(const Geq &n) final { dispatch(*n.lhs); dispatch(*n.rhs); n.validate(); } void visit_gt(const Gt &n) final { dispatch(*n.lhs); dispatch(*n.rhs); n.validate(); } void visit_if(const If &n) final { for (const IfClause &c : n.clauses) dispatch(c); n.validate(); } void visit_ifclause(const IfClause &n) final { if (n.condition != nullptr) dispatch(*n.condition); for (auto &s : n.body) dispatch(*s); n.validate(); } void visit_implication(const Implication &n) final { dispatch(*n.lhs); dispatch(*n.rhs); n.validate(); } void visit_isundefined(const IsUndefined &n) final { dispatch(*n.rhs); n.validate(); } void visit_leq(const Leq &n) final { dispatch(*n.lhs); dispatch(*n.rhs); n.validate(); } void visit_lsh(const Lsh &n) final { dispatch(*n.lhs); dispatch(*n.rhs); n.validate(); } void visit_lt(const Lt &n) final { dispatch(*n.lhs); dispatch(*n.rhs); n.validate(); } void visit_mod(const Mod &n) final { dispatch(*n.lhs); dispatch(*n.rhs); n.validate(); } void visit_model(const Model &n) final { for (const Ptr &c : n.children) dispatch(*c); n.validate(); } void visit_mul(const Mul &n) final { dispatch(*n.lhs); dispatch(*n.rhs); n.validate(); } void visit_negative(const Negative &n) final { dispatch(*n.rhs); n.validate(); } void visit_neq(const Neq &n) final { dispatch(*n.lhs); dispatch(*n.rhs); n.validate(); } void visit_not(const Not &n) final { dispatch(*n.rhs); n.validate(); } void visit_number(const Number &n) final { n.validate(); } void visit_or(const Or &n) final { dispatch(*n.lhs); dispatch(*n.rhs); n.validate(); } void visit_procedurecall(const ProcedureCall &n) final { dispatch(n.call); n.validate(); } void visit_property(const Property &n) final { dispatch(*n.expr); n.validate(); } void visit_propertyrule(const PropertyRule &n) final { for (const Quantifier &q : n.quantifiers) dispatch(q); dispatch(n.property); n.validate(); } void visit_propertystmt(const PropertyStmt &n) final { dispatch(n.property); n.validate(); } void visit_put(const Put &n) final { if (n.expr != nullptr) dispatch(*n.expr); n.validate(); } void visit_quantifier(const Quantifier &n) final { if (n.type != nullptr) dispatch(*n.type); if (n.from != nullptr) dispatch(*n.from); if (n.to != nullptr) dispatch(*n.to); if (n.step != nullptr) dispatch(*n.step); n.validate(); } void visit_range(const Range &n) final { dispatch(*n.min); dispatch(*n.max); n.validate(); } void visit_record(const Record &n) final { for (auto &f : n.fields) dispatch(*f); n.validate(); } void visit_return(const Return &n) final { if (n.expr != nullptr) dispatch(*n.expr); n.validate(); } void visit_rsh(const Rsh &n) final { dispatch(*n.lhs); dispatch(*n.rhs); n.validate(); } void visit_ruleset(const Ruleset &n) final { for (const Quantifier &q : n.quantifiers) dispatch(q); for (auto &r : n.rules) dispatch(*r); n.validate(); } void visit_scalarset(const Scalarset &n) final { dispatch(*n.bound); n.validate(); } void visit_simplerule(const SimpleRule &n) final { for (const Quantifier &q : n.quantifiers) dispatch(q); if (n.guard != nullptr) dispatch(*n.guard); for (auto &d : n.decls) dispatch(*d); for (auto &s : n.body) dispatch(*s); n.validate(); } void visit_startstate(const StartState &n) final { for (const Quantifier &q : n.quantifiers) dispatch(q); for (auto &d : n.decls) dispatch(*d); for (auto &s : n.body) dispatch(*s); n.validate(); } void visit_sub(const Sub &n) final { dispatch(*n.lhs); dispatch(*n.rhs); n.validate(); } void visit_switch(const Switch &n) final { dispatch(*n.expr); for (const SwitchCase &c : n.cases) dispatch(c); n.validate(); } void visit_switchcase(const SwitchCase &n) final { for (auto &m : n.matches) dispatch(*m); for (auto &s : n.body) dispatch(*s); n.validate(); } void visit_ternary(const Ternary &n) final { dispatch(*n.cond); dispatch(*n.lhs); dispatch(*n.rhs); n.validate(); } void visit_typedecl(const TypeDecl &n) final { dispatch(*n.value); n.validate(); } void visit_typeexprid(const TypeExprID &n) final { if (n.referent != nullptr) dispatch(*n.referent); n.validate(); } void visit_undefine(const Undefine &n) final { dispatch(*n.rhs); n.validate(); } void visit_vardecl(const VarDecl &n) final { if (n.type != nullptr) dispatch(*n.type); n.validate(); } void visit_while(const While &n) final { dispatch(*n.condition); for (auto &s : n.body) dispatch(*s); n.validate(); } void visit_xor(const Xor &n) final { dispatch(*n.lhs); dispatch(*n.rhs); n.validate(); } virtual ~Validator() = default; }; } // namespace void rumur::validate(const Node &n) { Validator v; v.dispatch(n); } rumur-2024.05.07/misc/000077500000000000000000000000001461637631000142275ustar00rootroot00000000000000rumur-2024.05.07/misc/_murphi2c000066400000000000000000000006131461637631000160420ustar00rootroot00000000000000#compdef murphi2c # Zsh completion script for Murphi2C _arguments \ '--header[generate a C header]' \ '--help[display help information]' \ {--output,-o}'[path to write source/header to]:filename:_files' \ '--source[generate a C source file]' \ '--value-type[change C type user to represent scalars]:TYPE' \ '--version[output version information]' \ '*::filename:_files -g "*.m"' rumur-2024.05.07/misc/_murphi2murphi000066400000000000000000000016131461637631000171250ustar00rootroot00000000000000#compdef murphi2murphi # Zsh completion script for Murphi2Murphi _arguments \ '--decompose-complex-comparisons[expand array and record equality tests]' \ '--explicit-semicolons[add omitted semicolons]' \ '--help[display help information]' \ '--no-decompose-complex-comparisons[do not expand array and record equality tests]' \ '--no-explicit-semicolons[do not add omitted semicolons]' \ '--no-remove-liveness[do not delete liveness properties]' \ '--no-switch-to-if[do not turn switch statements into if statements]' \ '--no-to-ascii[do not remove use of unicode operators]' \ {--output,-o}'[path to write resulting model to]:filename:_files' \ '--remove-liveness[delete liveness properties]' \ '--switch-to-if[turn switch statements into if statements]' \ '--to-ascii[remove use of unicode operators]' \ '--version[output version information]' \ '*::filename:_files -g "*.m"' rumur-2024.05.07/misc/_murphi2xml000066400000000000000000000003671461637631000164260ustar00rootroot00000000000000#compdef murphi2xml # Zsh completion script for Murphi2XML _arguments \ '--help[display help information]' \ {--output,-o}'[path to write XML to]:filename:_files' \ '--version[output version information]' \ '*::filename:_files -g "*.m"' rumur-2024.05.07/misc/_rumur000066400000000000000000000044051461637631000154660ustar00rootroot00000000000000#compdef rumur # Zsh completion script for Rumur _arguments \ '--bound[limit of the state space exploration depth]:steps' \ '--colour[enable or disable ANSI colour codes]: :(auto off on)' \ '--counterexample-trace[how to print counterexample traces]: :(diff full off)' \ '--deadlock-detection[deadlock semantics to use]: :(off stuck stuttering)' \ {--debug,-d}'[enabled debugging mode]' \ '--help[display help information]' \ '--max-errors[number of errors to report before exiting]:count' \ '--monopolise[use all machine resources]' \ {--output,-o}'[path to write C verifier to]:filename:_files' \ '--output-format[how verifier should print output]: :(machine-readable human-readable)' \ '--pack-state[compress verifier auxiliary state]: :(on off)' \ '--pointer-bits[number of relevant bits in a pointer]bits' \ {--quiet,-q}'[suppress output while generating verifier]' \ '--reorder-fields[optimise state variable and record field order]: :(on off)' \ '--sandbox[verifier privilege restriction]: :(on off)' \ '--scalarset-schedules[track scalarset permutations]: :(on off)' \ {--set-capacity,-s}'[initial memory (in bytes) to allocate for the seen set]:SIZE' \ {--set-expand-threshold,-e}'[limit at which to expand the seen set]:occupancy percentage' \ '--smt-arg[argument to pass to SMT solver]:ARG' \ '--smt-bitvectors[disable or enable using bitvectors instead of unbounded integers in SMT translation]: :(off on)' \ '--smt-budget[time allotment for SMT solver]:MILLISECONDS' \ '--smt-path[path to SMT solver]:path:_cmdstring' \ '--smt-prelude[text to pass to SMT solver preceding problems]:TEXT' \ '--smt-simplification[disable or enable using SMT solver for simplification]: :(off on)' \ '--symmetry-reduction[symmetry reduction optimisation]: :(off heuristic exhaustive)' \ {--threads,-t}'[number of threads to use in the verifier]:count' \ '--trace[tracing messages to print in the verifier]: :(handle_reads handle_writes queue set symmetry_reduction all)' \ '--value-type[C type to use for scalar values in the verifier]: :(auto int8_t uint8_t int16_t uint16_t int32_t uint32_t int64_t uint64_t)' \ {--verbose,-v}'[output more detail while generating verifier]' \ '--version[output version information]' \ '*::filename:_files -g "*.m"' rumur-2024.05.07/misc/install-macports.sh000077500000000000000000000011301461637631000200550ustar00rootroot00000000000000#!/usr/bin/env bash # Download and install Macports. For use in CI. # Version of Macports to install VERSION=2.8.1 if [ "$(uname)" != "Darwin" ]; then printf 'this script is only intended to run on macOS\n' >&2 exit 1 fi # Exit on error set -e # Echo all commands set -x # Create a temporary space to work in TMP=$(mktemp -d) cd "${TMP}" # Download curl --retry 3 --location --no-progress-meter -O \ https://github.com/macports/macports-base/releases/download/v${VERSION}/MacPorts-${VERSION}-13-Ventura.pkg # Install sudo installer -package MacPorts-${VERSION}-13-Ventura.pkg -target / rumur-2024.05.07/misc/murphi-mode.el000066400000000000000000000026051461637631000170020ustar00rootroot00000000000000;;; murphi-mode.el ;; major mode for editing Murphi source files ;; Author: Matthew Fernandez ;; Homepage: https://github.com/smattr/rumur (setq murphi-highlights '( ("--.*\n" . font-lock-comment-delimiter-face) ; FIXME: how do we match multiline comments? ("\\_<\\(alias\\|assert\\|begin\\|by\\|case\\|clear\\|const\\|do\\|else\\|elsif\\|end\\|endalias\\|endexists\\|endfor\\|endforall\\|endfunction\\|endif\\|endprocedure\\|endrecord\\|endrule\\|endruleset\\|endstartstate\\|endswitch\\|endwhile\\|error\\|exists\\|for\\|forall\\|if\\|in\\|isundefined\\|of\\|put\\|return\\|switch\\|then\\|to\\|type\\|undefine\\|var\\|while\\)\\_>" . font-lock-keyword-face) ("\\_<\\(false\\|true\\)\\_>" . font-lock-constant-face) ("\\_<-*[0-9]+\\_>" . font-lock-constant-face) ("\\_<\\(function\\|procedure\\|invariant\\|rule\\|ruleset\\|startstate\\)\\_>" . font-lock-function-name-face) ; FIXME: why does the following take precedence over a -- comment? ; TODO: support for \" as an escape within strings ("\"[^\"]*\"" . font-lock-string-face) ("\\_<\\(array\\|boolean\\|enum\\|record\\|scalarset\\|union\\)\\_>" . font-lock-type-face) )) (define-derived-mode murphi-mode fundamental-mode "murphi" "major mode for editing Murphi source code" (setq font-lock-defaults '(murphi-highlights)) (setq font-lock-keywords-case-fold-search t) ) rumur-2024.05.07/misc/murphi.vim000066400000000000000000000024521461637631000162530ustar00rootroot00000000000000" Vim support for Rumur extensions to the Murphi syntax " Language: murphi " Maintainer: Matthew Fernandez " License: The Unlicense " Vim already knows how to highlight Murphi files, but doesn't know about the " extra things that Rumur supports. To use this file, copy it into " ~/.vim/after/syntax/. The Rumur extensions to Murphi should now be correctly " highlighted when editing models. " extra keywords that Rumur supports syntax case ignore syn keyword murphiKeyword assume syn keyword murphiKeyword cover syn keyword murphiKeyword liveness syntax case match " support for hex and octal numbers in addition to decimal syn match murphiNumber "\<\(0[xX]\x\+\|0\o+\|[1-9]\d*\)\>" " error-highlight bad octals syn match murphiError "\<0\o*[89]" " override the base syntax's highlighting of `==` as an error syn match murphiOperator "==[^>]"he=e-1 " UTF-8 operators that Rumur recognises syn match murphiOperator "[∀∃≔≥→≤≠⇒¬∧∨÷−∕×]" " extra logical operators that Rumur supports syn match murphiOperator "&&" syn match murphiOperator "||" " bitwise operators that Rumur supports syn match murphiOperator "\^" syn match murphiOperator "\~" " recognise escape sequences in strings syn region murphiString start=+"\|“+ skip=+\\\\\|\\"\|\\”+ end=+"\|”+ rumur-2024.05.07/misc/murphi2xml.rng000066400000000000000000000647641461637631000170670ustar00rootroot00000000000000 [a-zA-Z_][a-zA-Z0-9_]* (assertion|assumption|cover|liveness) [01] rumur-2024.05.07/misc/package-for-debian.sh000077500000000000000000000041521461637631000201670ustar00rootroot00000000000000#!/usr/bin/env bash # Package Rumur for Debian. To use this, you must be on the 'packaging/debian' # branch. # Some relevant notes for Debian packaging: # * Pass --date=rfc2822 to git-log to get the commit dates in the right format # for Debian packaging. # * After successful packaging, sign the release with # `debsign ../rumur__source.changes`. # * Upload the package to mentors.debian.net with # `dput mentors ../rumur__source.changes`. # * Check consistency of ../debian/watch with # `cd .. && uscan --report --verbose`. # * For building with pbuilder, first # `sudo pbuilder create --debootstrapopts --variant=buildd` to setup a base # chroot image, then `pdebuild` to test. # * To update a pre-existing pbuilder environment, `sudo pbuilder update`. # # For uploading to mentors.debian.net, you will need ~/.dput.cf configured: # # [mentors] # fqdn = mentors.debian.net # incoming = /upload # method = https # allow_unsigned_uploads = 0 # progress_indicator = 2 # allowed_distributions = .* if [ "$(uname -s)" != "Linux" ]; then printf 'This script is only intended to run on Linux (Debian unstable)\n' >&2 exit 1 fi if [ "$(lsb_release --id | cut -d " " -f2)" != "Debian" ]; then printf 'This script is only intended to run on Debian unstable\n' >&2 exit 1 fi # Move to our parent directory cd "$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)/.." # check for artefacts from a prior build ls ../rumur_* &>/dev/null if [ $? -eq 0 ]; then printf 'There are files leftover from the previous build (../rumur_*)\n' >&2 exit 1 fi # check git tree if [[ ! -z "$(git status --short)" ]]; then printf 'Working tree is not clean\n' >&2 exit 1 fi # Exit on error set -e # Echo all commands set -x # Build the package from scratch gbp buildpackage --git-ignore-new --git-upstream-tag='v%(version)s' --git-debian-branch=packaging/debian # Check for consistency errors in the built package lintian --display-experimental --display-info --info --pedantic --show-overrides --verbose printf 'Run pdebuild to test the package under pbuilder\n' rumur-2024.05.07/misc/pending-queue-4k.m000066400000000000000000000626601461637631000175010ustar00rootroot00000000000000/* Model of the pending queue algorithm in the generated verifier. This was * originally adapted from pending-queue.m. * * The generated verifier uses a moderately complex lock-free algorithm for * enqueueing and dequeueing to the per-thread pending states queue. The * argument for the correctness of this algorithm is non-trivial and quite * subtle in places. The model below attempts to capture the abstract logic of * this algorithm, with the intent that we can gain some more evidence for its * correctness. * * Where we abstract the implementation: * * In the implementation, each thread maintains the ID of the queue it last * dequeued from (`queue_id`), which is then the first queue it tries to * dequeue from next time. This is an optimisation to reduce scanning * likely-empty queues again. This ID is reset to the thread's own queue * when it enqueues there to terminate work-stealing and switch back to * normal operation. In the model below, we remove this constraint and let * any thread try to dequeue from any queue. This is a safe * over-approximation. * * In the implementation, threads only ever enqueue to their own queue. In * the model we let threads enqueue to arbitrary queues. This is a safe * over-approximation and represents something we might want to consider in * future. * * The queue nodes of the implementation store * `4096 / sizeof(struct state*) - 1` states. In the model, we store fewer * (`STATES_PER_NODE`) to make the model manageable. */ const -- number of threads THREADS: 2 /* Number of queues. We use per-thread queues, so this is always the same as * the number of threads. */ QUEUES: THREADS /* Number of queue nodes available for allocation. Indirectly, this would be * something like the available heap memory. */ QUEUE_NODES: 2 /* Number of states stored per queue node. In the implementation, this is * `4096 / sizeof(struct state*) - 1`, but we abstract this here to a value * that should still be reasonable enough to expose any concurrency problems. */ STATES_PER_NODE: 2 -- a value indicating an invalid queue node NULL: -1 type -- thread identifier thread_id_t: 0 .. THREADS - 1 -- Queue identifier. Same as thread identifier as we have per-thread queues. queue_id_t: 0 .. QUEUES - 1 /* Queue node identifier. This is an abstraction of what would be virtual * address in the real system. */ queue_node_id: 0 .. QUEUE_NODES - 1 /* As above, but with `-1` to indicate an invalid queue node. Similar to the * way we use `NULL` in the real system. */ queue_node_id_opt: -1 .. QUEUE_NODES - 1 -- handle to either a state within a queue node or the node's next pointer queue_handle_t: record node: queue_node_id_opt offset: 0 .. STATES_PER_NODE end -- the contents of a queue node itself queue_node: record /* we only model whether a state slot is occupied or not, not what actual * state pointer it contains */ s: array[0 .. STATES_PER_NODE - 1] of boolean next: queue_node_id_opt end -- a per-thread queue queue: record head: queue_handle_t -- handle to the start of the queue tail: queue_handle_t -- handle to the end of the queue end /* A two-pointer structure. This is more general in the implementation, but in * the context of this model we only ever need to talk about a struct of two * queue handles, so we make this definition specific. */ double_ptr_t: record head: queue_handle_t tail: queue_handle_t end -- local state used during enqueue() enqueue_state: record ends: double_ptr_t ends_check: double_ptr_t n: queue_node_id_opt new: double_ptr_t queue_id: queue_id_t new_node: queue_node_id_opt next_tail: queue_handle_t end -- local state used during dequeue() dequeue_state: record attempts: 0 .. THREADS queue_id: queue_id_t ends: double_ptr_t ends_check: double_ptr_t new: double_ptr_t new_head: queue_node_id_opt reclaim_call: enum { L1911, L1940 } old: double_ptr_t s: boolean end -- local state used during reclaim() reclaim_state: record i: 0 .. THREADS j: 0 .. THREADS conflict: boolean end /* Program counter values. Note that some of these suffixed with 'L*' refer to * source code lines in ../rumur/resources/header.c at the time of writing. * These may have drifted out of sync with changes since then, so if you want * to cross reference these you may have to consult the Git commit in which * this model was added. */ label_t: enum { -- not running any operation IDLE, -- running enqueue() ENQUEUE_L1708, ENQUEUE_L1723, ENQUEUE_L1726, ENQUEUE_L1737, ENQUEUE_L1740, ENQUEUE_L1751, ENQUEUE_L1764, ENQUEUE_L1784, ENQUEUE_L1787, ENQUEUE_L1788, ENQUEUE_L1797, ENQUEUE_L1813, ENQUEUE_L1829, ENQUEUE_L1832, ENQUEUE_L1840, -- running dequeue() DEQUEUE_L1859, DEQUEUE_L1861, DEQUEUE_L1870, DEQUEUE_L1873, DEQUEUE_L1882, DEQUEUE_L1904, DEQUEUE_L1908, DEQUEUE_L1909, DEQUEUE_L1913, DEQUEUE_L1919, DEQUEUE_L1922, DEQUEUE_L1932, DEQUEUE_L1939, DEQUEUE_L1943, -- running reclaim() RECLAIM_L1503, RECLAIM_BLOCK_2, RECLAIM_BLOCK_3, RECLAIM_BLOCK_4, RECLAIM_BLOCK_5 } -- thread-local state thread_local: record pc: label_t -- program counter head: queue_node_id_opt -- queue head being currently examined tail: queue_node_id_opt -- queue tail being currently examined enqueue_locals: enqueue_state -- state used during enqueue() dequeue_locals: dequeue_state -- state used during dequeue() reclaim_locals: reclaim_state -- state used during reclaim() deferred: array [0 .. THREADS - 1] of queue_node_id_opt -- deferred pointers to free used in reclaim() end var /* Globally addressable queue nodes. You can think of this as memory indexed * by virtual address. */ queue_nodes: array [queue_node_id] of queue_node -- IDs of free queue nodes from which malloc allocates freelist: array [queue_node_id] of boolean -- the pending queues for each thread q: array [queue_id_t] of queue -- thread-local states thread_locals: array [thread_id_t] of thread_local -- hazarded pointers hazarded: array [thread_id_t] of queue_node_id_opt function queue_node_new(): queue_node_id_opt; begin -- try to find a free queue node /* XXX: awkward hack here where we need to loop over queue_node_id_opt instead * of queue_node_id because Murphi doesn't like us returning a value of a * different type than the function's return type. */ for i: queue_node_id_opt do if i != NULL then if freelist[i] then freelist[i] := false; -- memset 0 for j: 0 .. STATES_PER_NODE - 1 do queue_nodes[i].s[j] := false; end; queue_nodes[i].next := NULL; return i; end; end; end; -- all queue nodes are in-use (allocated) return NULL; end procedure queue_node_free(p: queue_node_id_opt); begin -- allow callers to free NULL as a no-op if p = NULL then return; end; assert !freelist[p] "freeing a queue node that was not in use"; freelist[p] := true; end function queue_handle_from_node_ptr(n: queue_node_id_opt): queue_handle_t; var tmp: queue_handle_t begin tmp.node := n; tmp.offset := 0; return tmp; end function queue_handle_base(h: queue_handle_t): queue_node_id_opt; return h.node; end function queue_handle_is_state_pptr(h: queue_handle_t): boolean; begin return h.offset < STATES_PER_NODE; end function queue_handle_next(h: queue_handle_t): queue_handle_t; var tmp: queue_handle_t begin assert h.offset < STATES_PER_NODE "queue_handle_next() called on exhausted queue handle"; tmp.node := h.node; tmp.offset := h.offset + 1; return tmp; end -- is the given queue node in the given queue? function in_queue(qid: queue_id_t; qnid: queue_node_id): boolean; var i: queue_node_id_opt begin -- follow the linked-list of queue nodes, looking for our target i := q[qid].head.node; while i != NULL do if i = qnid then return true; end; i := queue_nodes[i].next; end; -- we didn't find it return false; end function double_ptr_make(head: queue_handle_t; tail: queue_handle_t): double_ptr_t; var tmp: double_ptr_t begin tmp.head := head; tmp.tail := tail; return tmp; end procedure hazard(thread_id: thread_id_t; h: queue_handle_t); var p: queue_node_id_opt begin p := queue_handle_base(h); assert hazarded[thread_id] = NULL "hazarding multiple pointers at once"; hazarded[thread_id] := p; end procedure unhazard(thread_id: thread_id_t; h: queue_handle_t); var p: queue_node_id_opt begin p := queue_handle_base(h); assert hazarded[thread_id] != NULL "unhazarding a pointer when none are hazarded"; assert hazarded[thread_id] = p "unhazarding a pointer that differs from the one hazarded"; hazarded[thread_id] := NULL; end procedure return_from_reclaim(thread_id: thread_id_t); begin alias reclaim_call: thread_locals[thread_id].dequeue_locals.reclaim_call pc: thread_locals[thread_id].pc do assert !isundefined(reclaim_call) "unknown reclaim() call label"; if reclaim_call = L1911 then pc := DEQUEUE_L1913; else pc := DEQUEUE_L1943 end; undefine reclaim_call; end; end procedure goto_idle(thread_id: thread_id_t); begin -- blank all function-local state undefine thread_locals[thread_id].enqueue_locals; undefine thread_locals[thread_id].dequeue_locals; undefine thread_locals[thread_id].reclaim_locals; thread_locals[thread_id].pc := IDLE; end startstate begin -- mark all queue nodes as unallocated for qnid: queue_node_id do freelist[qnid] := true; end; -- set all queues as empty for qid: queue_id_t do q[qid].head.node := NULL; q[qid].head.offset := 0; q[qid].tail.node := NULL; q[qid].tail.offset := 0; end; -- reset all threads for thread_id: thread_id_t do goto_idle(thread_id); end; -- we start with no deferred deallocations for thread_id: thread_id_t do for i: 0 .. THREADS - 1 do thread_locals[thread_id].deferred[i] := NULL; end; end; -- we start with no hazarded pointers for i: thread_id_t do hazarded[i] := NULL; end; end ruleset thread_id: thread_id_t do alias pc: thread_locals[thread_id].pc do ruleset queue_id: queue_id_t do rule "enqueue start" pc = IDLE ==> begin thread_locals[thread_id].enqueue_locals.queue_id := queue_id; thread_locals[thread_id].enqueue_locals.ends := q[queue_id]; pc := ENQUEUE_L1708; end end alias ends: thread_locals[thread_id].enqueue_locals.ends ends_check: thread_locals[thread_id].enqueue_locals.ends_check head: thread_locals[thread_id].enqueue_locals.ends.head n: thread_locals[thread_id].enqueue_locals.n new: thread_locals[thread_id].enqueue_locals.new queue_id: thread_locals[thread_id].enqueue_locals.queue_id tail: thread_locals[thread_id].enqueue_locals.ends.tail new_node: thread_locals[thread_id].enqueue_locals.new_node next_tail: thread_locals[thread_id].enqueue_locals.next_tail do rule "header.c:1708" pc = ENQUEUE_L1708 ==> begin if tail.node = NULL then assert tail.offset = 0 "queue_node_offset has non-zero offset with null node"; assert head.node = NULL "tail of queue 0 while head is non-0"; assert head.offset = 0 "queue_node_offset has non-zero offset with null node"; n := queue_node_new(); assume n != NULL; -- avoid dealing with OOM queue_nodes[n].s[0] := true; new := double_ptr_make(queue_handle_from_node_ptr(n), queue_handle_from_node_ptr(n)); pc := ENQUEUE_L1723; else hazard(thread_id, tail); pc := ENQUEUE_L1737; end; end rule "header.c:1723" pc = ENQUEUE_L1723 ==> begin if q[queue_id] = ends then q[queue_id] := new; goto_idle(thread_id); else ends := q[queue_id]; pc := ENQUEUE_L1726; end; end rule "header.c:1726" pc = ENQUEUE_L1726 ==> begin queue_node_free(n); undefine n; pc := ENQUEUE_L1708; -- "goto retry" end rule "header.c:1737" pc = ENQUEUE_L1737 ==> begin ends_check := q[queue_id]; if ends != ends_check then pc := ENQUEUE_L1740; else pc := ENQUEUE_L1751; end; end rule "header.c:1740" pc = ENQUEUE_L1740 ==> begin unhazard(thread_id, tail); ends := ends_check; undefine ends_check; pc := ENQUEUE_L1708; -- "goto retry" end rule "header.c:1751" pc = ENQUEUE_L1751 ==> begin next_tail := queue_handle_next(tail); if queue_handle_is_state_pptr(next_tail) then alias target: queue_nodes[next_tail.node].s[next_tail.offset] do if !target then target := true; pc := ENQUEUE_L1797; else undefine next_tail; pc := ENQUEUE_L1764; end; end; else new_node := queue_node_new(); assume new_node != NULL; -- avoid dealing with OOM queue_nodes[new_node].s[0] := true; pc := ENQUEUE_L1784; end; end rule "header.c:1764" pc = ENQUEUE_L1764 ==> begin unhazard(thread_id, tail); pc := ENQUEUE_L1708; -- "goto retry" end rule "header.c:1784" pc = ENQUEUE_L1784 ==> begin alias target: queue_nodes[next_tail.node].next do if target = NULL then target := new_node; next_tail := queue_handle_from_node_ptr(new_node); pc := ENQUEUE_L1797; else undefine next_tail; pc := ENQUEUE_L1787; end; end; end rule "header.c:1787" pc = ENQUEUE_L1787 ==> begin queue_node_free(new_node); undefine new_node; pc := ENQUEUE_L1788; end rule "header.c:1788" pc = ENQUEUE_L1788 ==> begin unhazard(thread_id, tail); pc := ENQUEUE_L1708; -- "goto retry" end rule "header.c:1797" pc = ENQUEUE_L1797 ==> begin new := double_ptr_make(head, next_tail); if q[queue_id] = ends then q[queue_id] := new; pc := ENQUEUE_L1840; else -- use `ends_check` instead of `old` to save a state variable ends_check := q[queue_id]; pc := ENQUEUE_L1813; end; end rule "header.c:1813" pc = ENQUEUE_L1813 ==> begin next_tail := queue_handle_next(tail); if queue_handle_is_state_pptr(next_tail) then alias target: queue_nodes[next_tail.node].s[next_tail.offset] do if target then target := false; pc := ENQUEUE_L1832; else assert false "undo of write to next_tail failed"; end; end; else alias target: queue_nodes[next_tail.node].next do if target = new_node then target := NULL; pc := ENQUEUE_L1829; else assert false "undo of write to next_tail failed"; end; end; end; end rule "header.c:1829" pc = ENQUEUE_L1829 ==> begin queue_node_free(new_node); undefine new_node; pc := ENQUEUE_L1832; end rule "header.c:1832" pc = ENQUEUE_L1832 ==> begin unhazard(thread_id, tail); ends := ends_check; undefine ends_check; pc := ENQUEUE_L1708; -- "goto retry" end rule "header.c:1840" pc = ENQUEUE_L1840 ==> begin unhazard(thread_id, tail); goto_idle(thread_id); end end ruleset queue_id: queue_id_t do rule "dequeue start" thread_locals[thread_id].pc = IDLE ==> begin thread_locals[thread_id].dequeue_locals.attempts := 0; thread_locals[thread_id].dequeue_locals.queue_id := queue_id; thread_locals[thread_id].dequeue_locals.ends := q[queue_id]; thread_locals[thread_id].pc := DEQUEUE_L1861; end end alias ends: thread_locals[thread_id].dequeue_locals.ends head: thread_locals[thread_id].dequeue_locals.ends.head tail: thread_locals[thread_id].dequeue_locals.ends.tail queue_id: thread_locals[thread_id].dequeue_locals.queue_id attempts: thread_locals[thread_id].dequeue_locals.attempts ends_check: thread_locals[thread_id].dequeue_locals.ends_check new: thread_locals[thread_id].dequeue_locals.new new_head: thread_locals[thread_id].dequeue_locals.new_head reclaim_call: thread_locals[thread_id].dequeue_locals.reclaim_call old: thread_locals[thread_id].dequeue_locals.old s: thread_locals[thread_id].dequeue_locals.s do rule "header.c:1859" pc = DEQUEUE_L1859 ==> begin ends := q[queue_id]; pc := DEQUEUE_L1861; end rule "header.c:1861" pc = DEQUEUE_L1861 ==> begin if head.node != NULL then hazard(thread_id, head); pc := DEQUEUE_L1870; else queue_id := (queue_id + 1) % QUEUES; attempts := attempts + 1; if attempts < QUEUES then pc := DEQUEUE_L1859; else goto_idle(thread_id); end; end; end rule "header.c:1870" pc = DEQUEUE_L1870 ==> begin ends_check := q[queue_id]; if ends != ends_check then pc := DEQUEUE_L1873; else undefine ends_check; pc := DEQUEUE_L1882; end; end rule "header.c:1873" pc = DEQUEUE_L1873 ==> begin unhazard(thread_id, head); ends := ends_check; undefine ends_check; pc := DEQUEUE_L1861; -- "goto retry" end rule "header.c:1882" pc = DEQUEUE_L1882 ==> var zero: queue_handle_t begin if head = tail then zero.node := NULL; zero.offset := 0; new := double_ptr_make(zero, zero); pc := DEQUEUE_L1919; elsif queue_handle_is_state_pptr(head) then new := double_ptr_make(queue_handle_next(head), tail); pc := DEQUEUE_L1919; else new_head := queue_nodes[head.node].next; new := double_ptr_make(queue_handle_from_node_ptr(new_head), tail); pc := DEQUEUE_L1904; end; end rule "header.c:1904" pc = DEQUEUE_L1904 ==> begin old := q[queue_id]; if q[queue_id] = ends then q[queue_id] := new; end; pc := DEQUEUE_L1908; end rule "header.c:1908" pc = DEQUEUE_L1908 ==> begin unhazard(thread_id, head); pc := DEQUEUE_L1909; end rule "header.c:1909" pc = DEQUEUE_L1909 ==> begin if old = ends then reclaim_call := L1911; pc := RECLAIM_L1503; else pc := DEQUEUE_L1913; end; end rule "header.c:1913" pc = DEQUEUE_L1913 ==> begin ends := old; undefine old; pc := DEQUEUE_L1861; -- "goto retry" end rule "header.c:1919" pc = DEQUEUE_L1919 ==> begin if q[queue_id] = ends then q[queue_id] := new; undefine new; pc := DEQUEUE_L1932; else old := q[queue_id]; undefine new; pc := DEQUEUE_L1922; end; end rule "header.c:1922" pc = DEQUEUE_L1922 ==> begin unhazard(thread_id, head); ends := old; undefine old; pc := DEQUEUE_L1861; -- "goto retry" end rule "header.c:1932" pc = DEQUEUE_L1932 ==> begin if queue_handle_is_state_pptr(head) then s := true; else s := false; end; unhazard(thread_id, head); pc := DEQUEUE_L1939; end; rule "header.c:1939" pc = DEQUEUE_L1939 ==> begin if head = tail | !queue_handle_is_state_pptr(head) then reclaim_call := L1940; pc := RECLAIM_L1503; else pc := DEQUEUE_L1943; end; end rule "header.c:1943" pc = DEQUEUE_L1943 ==> begin if !s then queue_id := (queue_id + 1) % QUEUES; attempts := attempts + 1; if attempts < QUEUES then pc := DEQUEUE_L1859; else goto_idle(thread_id); end; else goto_idle(thread_id); end; end end alias head: thread_locals[thread_id].dequeue_locals.ends.head reclaim_call: thread_locals[thread_id].dequeue_locals.reclaim_call i: thread_locals[thread_id].reclaim_locals.i j: thread_locals[thread_id].reclaim_locals.j conflict: thread_locals[thread_id].reclaim_locals.conflict deferred: thread_locals[thread_id].deferred do rule "header.c:1503" pc = RECLAIM_L1503 ==> begin assert !isundefined(reclaim_call) "no return address when entering reclaim()"; assert head.node != NULL "reclaiming a null pointer"; assert hazarded[thread_id] = NULL "reclaiming a pointer while holding a hazarded pointer"; -- compressed loop iterations until we find something interesting i := 0; while i < THREADS & deferred[i] = NULL do i := i + 1; end; if i < THREADS then conflict := false; j := 0; pc := RECLAIM_BLOCK_2; else conflict := false; i := 0; pc := RECLAIM_BLOCK_4; end; end rule "reclaim block 2" pc = RECLAIM_BLOCK_2 ==> begin assert i < THREADS; assert deferred[i] != NULL; if j = thread_id then assert hazarded[j] = NULL; elsif deferred[i] = hazarded[j] then conflict := true; j := THREADS - 1; -- "break" end; j := j + 1; if j = THREADS then undefine j; pc := RECLAIM_BLOCK_3; end; end rule "reclaim block 3" pc = RECLAIM_BLOCK_3 ==> begin if !conflict then queue_node_free(deferred[i]); deferred[i] := NULL; end; undefine conflict; while i < THREADS & deferred[i] = NULL do i := i + 1; end; if i < THREADS then conflict := false; j := 0; pc := RECLAIM_BLOCK_2; else conflict := false; i := 0; pc := RECLAIM_BLOCK_4; end; end rule "reclaim block 4" pc = RECLAIM_BLOCK_4 ==> begin assert i < THREADS; if i = thread_id then assert hazarded[i] = NULL; elsif head.node = hazarded[i] then conflict := true; i := THREADS - 1; -- "break" end; i := i + 1; if i = THREADS then undefine i; pc := RECLAIM_BLOCK_5; end; end rule "reclaim block 5" pc = RECLAIM_BLOCK_5 ==> begin if !conflict then queue_node_free(head.node); return_from_reclaim(thread_id); return; else for ii: 0 .. THREADS - 1 do if deferred[ii] = NULL then deferred[ii] := head.node; return_from_reclaim(thread_id); return; end; end; end; assert false "deferred more than `THREADS` reclamations"; end end end end invariant "queue only empty when head and tail agree" forall qid: queue_id_t do (q[qid].head.node = NULL -> q[qid].head.offset = 0) & (q[qid].head.node = NULL -> q[qid].tail.node = NULL) & (q[qid].tail.node = NULL -> q[qid].tail.offset = 0) & (q[qid].tail.node = NULL -> q[qid].head.node = NULL) end invariant "no memory leaks" /* at quiescence, every queue node is either in a queue, in the free list, or * in a thread's deferred deallocations list */ exists thread_id: thread_id_t do thread_locals[thread_id].pc != IDLE end | forall qnid: queue_node_id do exists qid: queue_id_t do in_queue(qid, qnid) end | freelist[qnid] | exists thread_id: thread_id_t do exists i: 0 .. THREADS - 1 do thread_locals[thread_id].deferred[i] = qnid end end end invariant "no double use of nodes" -- every queue node in a queue is nowhere else forall qnid: queue_node_id do forall qid: queue_id_t do in_queue(qid, qnid) -> (forall qid2: queue_id_t do qid2 = qid | !in_queue(qid2, qnid) end & !freelist[qnid] & forall thread_id: thread_id_t do forall i: 0 .. THREADS - 1 do thread_locals[thread_id].deferred[i] != qnid end end) end end rumur-2024.05.07/misc/pending-queue.m000066400000000000000000000440201461637631000171530ustar00rootroot00000000000000/* Update: the following is based on a prior pending queue algorithm which used * a single-state-per-node linked-list. It has been retained as an interesting * large model to run through Rumur. * * ---- * * Model of the pending queue algorithm in the generated verifier. * * The generated verifier uses a moderately complex lock-free algorithm for * enqueueing and dequeueing to the per-thread pending states queue. The * argument for the correctness of this algorithm is non-trivial and quite * subtle in places. The model below attempts to capture the abstract logic of * this algorithm, with the intent that we can gain some more evidence for its * correctness. * * Where we abstract the implementation: * * In the implementation, each thread maintains the ID of the queue it last * dequeued from (`queue_id`), which is then the first queue it tries to * dequeue from next time. This is an optimisation to reduce scanning * likely-empty queues again. This ID is reset to the thread's own queue * when it enqueues there to terminate work-stealing and switch back to * normal operation. In the model below, we remove this constraint and let * any thread try to dequeue from any queue. This is a safe * over-approximation. * * In the implementation, threads only ever enqueue to their own queue. In * the model we let threads enqueue to arbitrary queues. This is a safe * over-approximation and represents something we might want to consider in * future. */ const -- number of threads THREADS: 2 /* Number of queues. We use per-thread queues, so this is always the same as * the number of threads. */ QUEUES: THREADS /* Number of queue nodes available for allocation. Indirectly, this would be * something like the available heap memory. */ QUEUE_NODES: 4 -- a value indicating an invalid queue node NULL: -1 type -- thread identifier thread_id_t: 0 .. THREADS - 1 -- Queue identifier. Same as thread identifier as we have per-thread queues. queue_id_t: 0 .. QUEUES - 1 /* Queue node identifier. This is an abstraction of what would be virtual * address in the real system. */ queue_node_id: 0 .. QUEUE_NODES - 1 /* As above, but with `-1` to indicate an invalid queue node. Similar to the * way we use `NULL` in the real system. */ queue_node_id_opt: -1 .. QUEUE_NODES - 1 -- the contents of a queue node itself queue_node: record -- we ignored the state member that is not relevant for verification next: queue_node_id_opt end -- a per-thread queue queue: record head: queue_node_id_opt -- pointer to the start of the queue tail: queue_node_id_opt -- pointer to the end of the queue end /* A two-pointer structure. This is more general in the implementation, but in * the context of this model we only ever need to talk about a struct of two * queue pointers, so we make this definition specific. */ double_ptr_t: record head: queue_node_id_opt tail: queue_node_id_opt end -- local state used during enqueue() enqueue_state: record ends: double_ptr_t ends_check: double_ptr_t n: queue_node_id_opt new: double_ptr_t queue_id: queue_id_t end -- local state used during dequeue() dequeue_state: record attempts: 0 .. THREADS queue_id: queue_id_t ends: double_ptr_t ends_check: double_ptr_t new: double_ptr_t end -- local state used during reclaim() reclaim_state: record i: 0 .. THREADS j: 0 .. THREADS conflict: boolean end -- program counter values label_t: enum { -- not running any operation IDLE, -- running enqueue() ENQUEUE_BLOCK_2, ENQUEUE_BLOCK_3, ENQUEUE_BLOCK_4, ENQUEUE_BLOCK_5, ENQUEUE_BLOCK_6, ENQUEUE_BLOCK_7, ENQUEUE_BLOCK_8, ENQUEUE_BLOCK_9, ENQUEUE_BLOCK_10, ENQUEUE_BLOCK_11, -- running dequeue() DEQUEUE_BLOCK_2, DEQUEUE_BLOCK_3, DEQUEUE_BLOCK_4, DEQUEUE_BLOCK_5, DEQUEUE_BLOCK_6, DEQUEUE_BLOCK_7, DEQUEUE_BLOCK_8, -- running reclaim() RECLAIM_BLOCK_1, RECLAIM_BLOCK_2, RECLAIM_BLOCK_3, RECLAIM_BLOCK_4, RECLAIM_BLOCK_5 } -- thread-local state thread_local: record pc: label_t -- program counter head: queue_node_id_opt -- queue head being currently examined tail: queue_node_id_opt -- queue tail being currently examined enqueue_locals: enqueue_state -- state used during enqueue() dequeue_locals: dequeue_state -- state used during dequeue() reclaim_locals: reclaim_state -- state used during reclaim() deferred: array [0 .. THREADS - 1] of queue_node_id_opt -- deferred pointers to free used in reclaim() end var /* Globally addressable queue nodes. You can think of this as memory indexed * by virtual address. */ queue_nodes: array [queue_node_id] of queue_node -- IDs of free queue nodes from which malloc allocates freelist: array [queue_node_id] of boolean -- the pending queues for each thread q: array [queue_id_t] of queue -- thread-local states thread_locals: array [thread_id_t] of thread_local -- hazarded pointers hazarded: array [thread_id_t] of queue_node_id_opt function malloc(): queue_node_id_opt; begin -- try to find a free queue node /* XXX: awkward hack here where we need to loop over queue_node_id_opt instead * of queue_node_id because Murphi doesn't like us indexing freelist with a * value not of its index type. */ for i: queue_node_id_opt do if i != NULL then if freelist[i] then freelist[i] := false; return i; end; end; end; -- all queue nodes are in-use (allocated) return NULL; end procedure free(p: queue_node_id_opt); begin -- allow callers to free NULL as a no-op if p = NULL then return; end; assert !freelist[p] "freeing a queue node that was not in use"; freelist[p] := true; end -- is the given queue node in the given queue? function in_queue(qid: queue_id_t; qnid: queue_node_id): boolean; var i: queue_node_id_opt begin -- follow the linked-list of queue nodes, looking for our target i := q[qid].head; while i != NULL do if i = qnid then return true; end; i := queue_nodes[i].next; end; -- we didn't find it return false; end function double_ptr_make(head: queue_node_id_opt; tail: queue_node_id_opt): double_ptr_t; var tmp: double_ptr_t begin tmp.head := head; tmp.tail := tail; return tmp; end procedure hazard(thread_id: thread_id_t; p: queue_node_id); begin assert hazarded[thread_id] = NULL "hazarding multiple pointers at once"; hazarded[thread_id] := p; end procedure unhazard(thread_id: thread_id_t; p: queue_node_id); begin assert hazarded[thread_id] != NULL "unhazarding a pointer when none are hazarded"; assert hazarded[thread_id] = p "unhazarding a pointer that differs from the one hazarded"; hazarded[thread_id] := NULL; end procedure goto_idle(thread_id: thread_id_t); begin -- blank all function-local state undefine thread_locals[thread_id].enqueue_locals; undefine thread_locals[thread_id].dequeue_locals; undefine thread_locals[thread_id].reclaim_locals; thread_locals[thread_id].pc := IDLE; end startstate begin -- mark all queue nodes as unallocated for qnid: queue_node_id do freelist[qnid] := true; end; -- set all queues as empty for qid: queue_id_t do q[qid].head := NULL; q[qid].tail := NULL; end; -- reset all threads for thread_id: thread_id_t do goto_idle(thread_id); end; -- we start with no deferred deallocations for thread_id: thread_id_t do for i: 0 .. THREADS - 1 do thread_locals[thread_id].deferred[i] := NULL; end; end; -- we start with no hazarded pointers for i: thread_id_t do hazarded[i] := NULL; end; end ruleset thread_id: thread_id_t do alias pc: thread_locals[thread_id].pc do ruleset queue_id: queue_id_t do alias n: thread_locals[thread_id].enqueue_locals.n do rule "enqueue start" pc = IDLE ==> begin thread_locals[thread_id].enqueue_locals.queue_id := queue_id; n := malloc(); if n = NULL then goto_idle(thread_id); return; end; queue_nodes[n].next := NULL; pc := ENQUEUE_BLOCK_2; end end end alias ends: thread_locals[thread_id].enqueue_locals.ends ends_check: thread_locals[thread_id].enqueue_locals.ends_check head: thread_locals[thread_id].enqueue_locals.ends.head n: thread_locals[thread_id].enqueue_locals.n new: thread_locals[thread_id].enqueue_locals.new queue_id: thread_locals[thread_id].enqueue_locals.queue_id tail: thread_locals[thread_id].enqueue_locals.ends.tail do rule "enqueue block 2" pc = ENQUEUE_BLOCK_2 ==> begin ends := q[queue_id]; pc := ENQUEUE_BLOCK_3; end rule "enqueue block 3" pc = ENQUEUE_BLOCK_3 ==> begin if tail = NULL then assert head = NULL "tail of queue null while head is non-null"; new := double_ptr_make(n, n); if q[queue_id] = ends then q[queue_id] := new; goto_idle(thread_id); else ends := q[queue_id]; -- intentional lack of pc change ("goto retry") end; else hazard(thread_id, tail); pc := ENQUEUE_BLOCK_4; end; end rule "enqueue block 4" pc = ENQUEUE_BLOCK_4 ==> begin ends_check := q[queue_id]; if ends != ends_check then pc := ENQUEUE_BLOCK_5; else pc := ENQUEUE_BLOCK_6; end; end rule "enqueue block 5" pc = ENQUEUE_BLOCK_5 ==> begin unhazard(thread_id, tail); ends := ends_check; undefine ends_check; pc := ENQUEUE_BLOCK_3; end rule "enqueue block 6" pc = ENQUEUE_BLOCK_6 ==> begin if queue_nodes[tail].next = NULL then queue_nodes[tail].next := n; pc := ENQUEUE_BLOCK_8; else pc := ENQUEUE_BLOCK_7; end; end rule "enqueue block 7" pc = ENQUEUE_BLOCK_7 ==> begin unhazard(thread_id, tail); pc := ENQUEUE_BLOCK_3; end rule "enqueue block 8" pc = ENQUEUE_BLOCK_8 ==> begin new := double_ptr_make(head, n); if q[queue_id] = ends then q[queue_id] := new; pc := ENQUEUE_BLOCK_11; else -- use `ends_check` instead of `old` to save a state variable ends_check := q[queue_id]; pc := ENQUEUE_BLOCK_9; end; end rule "enqueue block 9" pc = ENQUEUE_BLOCK_9 ==> begin assert queue_nodes[tail].next = n "undo of write to tail->next failed"; queue_nodes[tail].next := NULL; pc := ENQUEUE_BLOCK_10; end rule "enqueue block 10" pc = ENQUEUE_BLOCK_10 ==> begin -- remember, using `ends_check` here where the implementation uses `old` unhazard(thread_id, tail); ends := ends_check; undefine ends_check; pc := ENQUEUE_BLOCK_3; end rule "enqueue block 11" pc = ENQUEUE_BLOCK_11 ==> begin unhazard(thread_id, tail); goto_idle(thread_id); end end ruleset queue_id: queue_id_t do rule "dequeue start" thread_locals[thread_id].pc = IDLE ==> begin thread_locals[thread_id].dequeue_locals.attempts := 0; thread_locals[thread_id].dequeue_locals.queue_id := queue_id; thread_locals[thread_id].dequeue_locals.ends := q[queue_id]; thread_locals[thread_id].pc := DEQUEUE_BLOCK_3; end end alias ends: thread_locals[thread_id].dequeue_locals.ends head: thread_locals[thread_id].dequeue_locals.ends.head tail: thread_locals[thread_id].dequeue_locals.ends.tail queue_id: thread_locals[thread_id].dequeue_locals.queue_id attempts: thread_locals[thread_id].dequeue_locals.attempts ends_check: thread_locals[thread_id].dequeue_locals.ends_check new: thread_locals[thread_id].dequeue_locals.new do rule "dequeue block 2" pc = DEQUEUE_BLOCK_2 ==> begin ends := q[queue_id]; pc := DEQUEUE_BLOCK_3; end rule "dequeue block 3" pc = DEQUEUE_BLOCK_3 ==> begin if head != NULL then hazard(thread_id, head); pc := DEQUEUE_BLOCK_4; else queue_id := (queue_id + 1) % QUEUES; attempts := attempts + 1; if attempts < QUEUES then pc := DEQUEUE_BLOCK_2; else goto_idle(thread_id); end; end; end rule "dequeue block 4" pc = DEQUEUE_BLOCK_4 ==> begin ends_check := q[queue_id]; if ends != ends_check then pc := DEQUEUE_BLOCK_5; else undefine ends_check; pc := DEQUEUE_BLOCK_6; end; end rule "dequeue block 5" pc = DEQUEUE_BLOCK_5 ==> begin unhazard(thread_id, head); ends := ends_check; undefine ends_check; pc := DEQUEUE_BLOCK_2; end rule "dequeue block 6" pc = DEQUEUE_BLOCK_6 ==> begin if head = tail then new := double_ptr_make(NULL, NULL); else new := double_ptr_make(queue_nodes[head].next, tail); end; pc := DEQUEUE_BLOCK_7; end rule "dequeue block 7" pc = DEQUEUE_BLOCK_7 ==> begin if q[queue_id] = ends then q[queue_id] := new; undefine tail; undefine new; pc := DEQUEUE_BLOCK_8; else undefine new; ends_check := q[queue_id]; pc := DEQUEUE_BLOCK_5; end; end rule "dequeue block 8" pc = DEQUEUE_BLOCK_8 ==> begin unhazard(thread_id, head); pc := RECLAIM_BLOCK_1; end end alias head: thread_locals[thread_id].dequeue_locals.ends.head i: thread_locals[thread_id].reclaim_locals.i j: thread_locals[thread_id].reclaim_locals.j conflict: thread_locals[thread_id].reclaim_locals.conflict deferred: thread_locals[thread_id].deferred do rule "reclaim block 1" pc = RECLAIM_BLOCK_1 ==> begin assert head != NULL "reclaiming a null pointer"; assert hazarded[thread_id] = NULL "reclaiming a pointer while holding a hazarded pointer"; -- compressed loop iterations until we find something interesting i := 0; while i < THREADS & deferred[i] = NULL do i := i + 1; end; if i < THREADS then conflict := false; j := 0; pc := RECLAIM_BLOCK_2; else conflict := false; i := 0; pc := RECLAIM_BLOCK_4; end; end rule "reclaim block 2" pc = RECLAIM_BLOCK_2 ==> begin assert i < THREADS; assert deferred[i] != NULL; if j = thread_id then assert hazarded[j] = NULL; elsif deferred[i] = hazarded[j] then conflict := true; j := THREADS - 1; -- "break" end; j := j + 1; if j = THREADS then undefine j; pc := RECLAIM_BLOCK_3; end; end rule "reclaim block 3" pc = RECLAIM_BLOCK_3 ==> begin if !conflict then free(deferred[i]); deferred[i] := NULL; end; undefine conflict; while i < THREADS & deferred[i] = NULL do i := i + 1; end; if i < THREADS then conflict := false; j := 0; pc := RECLAIM_BLOCK_2; else conflict := false; i := 0; pc := RECLAIM_BLOCK_4; end; end rule "reclaim block 4" pc = RECLAIM_BLOCK_4 ==> begin assert i < THREADS; if i = thread_id then assert hazarded[i] = NULL; elsif head = hazarded[i] then conflict := true; i := THREADS - 1; -- "break" end; i := i + 1; if i = THREADS then undefine i; pc := RECLAIM_BLOCK_5; end; end rule "reclaim block 5" pc = RECLAIM_BLOCK_5 ==> begin if !conflict then free(head); goto_idle(thread_id); return; else for ii: 0 .. THREADS - 1 do if deferred[ii] = NULL then deferred[ii] := head; goto_idle(thread_id); return; end; end; end; assert false "deferred more than `THREADS` reclamations"; end end end end invariant "queue only empty when head and tail agree" forall qid: queue_id_t do (q[qid].head = NULL -> q[qid].tail = NULL) & (q[qid].tail = NULL -> q[qid].head = NULL) end invariant "no memory leaks" /* at quiescence, every queue node is either in a queue, in the free list, or * in a thread's deferred deallocations list */ exists thread_id: thread_id_t do thread_locals[thread_id].pc != IDLE end | forall qnid: queue_node_id do exists qid: queue_id_t do in_queue(qid, qnid) end | freelist[qnid] | exists thread_id: thread_id_t do exists i: 0 .. THREADS - 1 do thread_locals[thread_id].deferred[i] = qnid end end end invariant "no double use of nodes" -- every queue node in a queue is nowhere else forall qnid: queue_node_id do forall qid: queue_id_t do in_queue(qid, qnid) -> (forall qid2: queue_id_t do qid2 = qid | !in_queue(qid2, qnid) end & !freelist[qnid] & forall thread_id: thread_id_t do forall i: 0 .. THREADS - 1 do thread_locals[thread_id].deferred[i] != qnid end end) end end rumur-2024.05.07/misc/read-raw.smt2000066400000000000000000000405531461637631000165470ustar00rootroot00000000000000; proof of correctness of read_raw() ; To prove read_raw() correct, we wish to prove that its implementation ; corresponds to a specification: ; ; spec(h.base, h.offset, h.width) == impl(h) ; ; Its specification is straightforward to define: ; ; spec(h_base, h_offset, h_width) ; = ((*(integer*)h_base) >> h_offset) & ((((integer)1) << h_width) - 1) ; ; where “integer” is an infinite precision integer type. Note however that we ; can relax this constraint. Offsets are always within the first byte (i.e. they ; are bit 0 - bit 7) and the maximum supported width is 64 bits. Therefore we ; can treat the base as pointing to a 71-bit vector, instead of to an infinite ; precision number. However, instead of using these bounds, we assume that in ; future we may support widths up to 128 bits if we have a uint128_t type ; available. So we need to treat the base as a 128 + 7 = 135-bit vector. ; ; The implementation of read_raw() can be viewed in ../rumur/resources/header.c. ; To put it into a more suitable form for SMT reasoning, we transform it into a ; branchless Static Single Assignment (SSA) form: ; ; // inline handle_align() ; size_t width = h_width + h_offset; ; size_t aligned_width = width % 8 != 0 ? width + 8 - width % 8 : width; ; ; bool u128_branch = aligned_width > (sizeof(uint64_t) - 1) * 8; ; ; size_t low_size = u128_branch ? aligned_width / 8 : 0; ; size_t low_size2 = u128_branch ? low_size > sizeof(uint128_t) ? sizeof(uint128_t) : low_size : 0; ; ; // inline copy_out128() ; uint128_t low = 0; ; u128_branch ? memcpy(&low, h_base, low_size2) : (void); ; ; uint128_t low2 = u128_branch ? low >> h_offset : 0; ; ; size_t high_size = u128_branch ? aligned_width / 8 - low_size2 : 0; ; ; // inline copy_out128() ; uint128_t high = 0; ; u128_branch ? high_size != 0 ? memcpy(&high, h_base + sizeof(low2), high_size) : (void) : (void); ; ; uint128_t high2 = u128_branch ? high_size != 0 ? high << (sizeof(low2) * 8 - h_offset) : 0 : 0; ; ; uint128_t low3 = u128_branch ? high_size != 0 ? low2 | high2 : low2 : 0; ; ; uint128_t mask = u128_branch ? h_width < sizeof(low3) * 8 ? (((uint128_t)1) << h_width) - 1 : 0 : 0; ; uint128_t low4 = u128_branch ? h_width < sizeof(low3) * 8 ? low3 & mask : low3 : 0; ; ; uint64_t ret = low4; ; ; // future possibility where we can read out >64-bit values ; uint128_t ret2 = low4; ; ; size_t low_size3 = aligned_width / 8; ; size_t low_size4 = low_size3 > sizeof(uint64_t) ? sizeof(uint64_t) : low_size3; ; ; // inline copy_out64() ; uint64_t low5 = 0; ; memcpy(&low5, h_base, low_size4); ; ; uint64_t low6 = low5 >> h_offset; ; ; size_t high_size2 = aligned_width / 8 - low_size4; ; ; // inline copy_out64() ; uint64_t high3 = 0; ; high_size2 != 0 ? memcpy(&high3, h_base + sizeof(low6), high_size2) : (void); ; uint64_t high4 = high_size2 != 0 ? high3 << (sizeof(low6) * 8 - h_offset : 0; ; ; uint64_t low7 = high_size2 != 0 ? low6 | high4 : low6; ; ; uint64_t mask2 = h_width < sizeof(low7) * 8 ? (UINT64_C(1) << h_width) - 1 : 0; ; uint64_t low8 = h_width < sizeof(low7) * 8 ? low7 & mask2 : low7; ; ; uint64_t ret3 = h_width == 0 ? 0 : low8; ; ; Now we can construct a correctness proof by transliterating both the ; specification and implementation into SMT, and then proving that they are ; equal for all valid inputs. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (set-logic QF_AUFBV) (set-option :produce-models true) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; input base, that we treat as a bitvector (declare-fun h_base () (_ BitVec 135)) ; input offset, that can be 0-7 (declare-fun h_offset () (_ BitVec 64)) (assert (bvule h_offset (_ bv7 64))) ; input width, that can be up to 64, but we future proof this to allow up to 128 (declare-fun h_width () (_ BitVec 64)) (assert (bvule h_width (_ bv128 64))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; the spec, which we can state quite simply (declare-fun spec () (_ BitVec 128)) (assert (= spec (bvand ((_ extract 127 0) (bvlshr h_base ((_ zero_extend 71) h_offset))) (bvsub (bvshl (_ bv1 128) ((_ zero_extend 64) h_width)) (_ bv1 128))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; transliterate the implementation ; size_t width = h_width + h_offset; (declare-fun width () (_ BitVec 64)) (assert (= width (bvadd h_width h_offset))) ; size_t aligned_width = width % 8 != 0 ? width + 8 - width % 8 : width; (declare-fun aligned_width () (_ BitVec 64)) (assert (= aligned_width (ite (not (= (bvsmod width (_ bv8 64)) (_ bv0 64))) (bvadd width (bvsub (_ bv8 64) (bvsmod width (_ bv8 64)))) width))) ; bool u128_branch = aligned_width > (sizeof(uint64_t) - 1) * 8; (declare-fun u128_branch () Bool) (assert (= u128_branch (bvugt aligned_width (bvmul (bvsub (_ bv8 64) (_ bv1 64)) (_ bv8 64))))) ; size_t low_size = u128_branch ? aligned_width / 8 : 0; (declare-fun low_size () (_ BitVec 64)) (assert (= low_size (ite u128_branch (bvudiv aligned_width (_ bv8 64)) (_ bv0 64)))) ; size_t low_size2 = u128_branch ? low_size > sizeof(uint128_t) ? sizeof(uint128_t) : low_size : 0; (declare-fun low_size2 () (_ BitVec 64)) (assert (= low_size2 (ite u128_branch (ite (bvugt low_size (_ bv16 64)) (_ bv16 64) low_size) (_ bv0 64)))) ; u128_branch ? memcpy(&low, h_base, low_size2) : (void); (declare-fun low () (_ BitVec 128)) (assert (= low (ite u128_branch (bvor (ite (bvugt low_size2 (_ bv0 64)) ((_ zero_extend 120) ((_ extract 7 0) h_base)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv1 64)) (bvshl ((_ zero_extend 120) ((_ extract 15 8) h_base)) (_ bv8 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv2 64)) (bvshl ((_ zero_extend 120) ((_ extract 23 16) h_base)) (_ bv16 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv3 64)) (bvshl ((_ zero_extend 120) ((_ extract 31 24) h_base)) (_ bv24 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv4 64)) (bvshl ((_ zero_extend 120) ((_ extract 39 32) h_base)) (_ bv32 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv5 64)) (bvshl ((_ zero_extend 120) ((_ extract 47 40) h_base)) (_ bv40 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv6 64)) (bvshl ((_ zero_extend 120) ((_ extract 55 48) h_base)) (_ bv48 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv7 64)) (bvshl ((_ zero_extend 120) ((_ extract 63 56) h_base)) (_ bv56 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv8 64)) (bvshl ((_ zero_extend 120) ((_ extract 71 64) h_base)) (_ bv64 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv9 64)) (bvshl ((_ zero_extend 120) ((_ extract 79 72) h_base)) (_ bv72 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv10 64)) (bvshl ((_ zero_extend 120) ((_ extract 87 80) h_base)) (_ bv80 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv11 64)) (bvshl ((_ zero_extend 120) ((_ extract 95 88) h_base)) (_ bv88 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv12 64)) (bvshl ((_ zero_extend 120) ((_ extract 103 96) h_base)) (_ bv96 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv13 64)) (bvshl ((_ zero_extend 120) ((_ extract 111 104) h_base)) (_ bv104 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv14 64)) (bvshl ((_ zero_extend 120) ((_ extract 119 112) h_base)) (_ bv112 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv15 64)) (bvshl ((_ zero_extend 120) ((_ extract 127 120) h_base)) (_ bv120 128)) (_ bv0 128))) (_ bv0 128)))) ; uint128_t low2 = u128_branch ? low >> h_offset : 0; (declare-fun low2 () (_ BitVec 128)) (assert (= low2 (ite u128_branch (bvlshr low ((_ zero_extend 64) h_offset)) (_ bv0 128)))) ; size_t high_size = u128_branch ? aligned_width / 8 - low_size2 : 0; (declare-fun high_size () (_ BitVec 64)) (assert (= high_size (ite u128_branch (bvsub (bvudiv aligned_width (_ bv8 64)) low_size2) (_ bv0 64)))) ; u128_branch ? high_size != 0 ? memcpy(&high, h_base + sizeof(low2), high_size) : (void) : (void); (declare-fun high () (_ BitVec 128)) (assert (= high (ite u128_branch (ite (not (= high_size (_ bv0 64))) (bvor (ite (bvugt high_size (_ bv0 64)) (bvshl ((_ zero_extend 120) ((_ extract 7 0) (bvlshr h_base (_ bv128 135)))) (_ bv0 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv1 64)) (bvshl ((_ zero_extend 120) ((_ extract 15 8) (bvlshr h_base (_ bv128 135)))) (_ bv8 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv2 64)) (bvshl ((_ zero_extend 120) ((_ extract 23 16) (bvlshr h_base (_ bv128 135)))) (_ bv16 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv3 64)) (bvshl ((_ zero_extend 120) ((_ extract 31 24) (bvlshr h_base (_ bv128 135)))) (_ bv24 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv4 64)) (bvshl ((_ zero_extend 120) ((_ extract 39 32) (bvlshr h_base (_ bv128 135)))) (_ bv32 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv5 64)) (bvshl ((_ zero_extend 120) ((_ extract 47 40) (bvlshr h_base (_ bv128 135)))) (_ bv40 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv6 64)) (bvshl ((_ zero_extend 120) ((_ extract 55 48) (bvlshr h_base (_ bv128 135)))) (_ bv48 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv7 64)) (bvshl ((_ zero_extend 120) ((_ extract 63 56) (bvlshr h_base (_ bv128 135)))) (_ bv56 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv8 64)) (bvshl ((_ zero_extend 120) ((_ extract 71 64) (bvlshr h_base (_ bv128 135)))) (_ bv64 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv9 64)) (bvshl ((_ zero_extend 120) ((_ extract 79 72) (bvlshr h_base (_ bv128 135)))) (_ bv72 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv10 64)) (bvshl ((_ zero_extend 120) ((_ extract 87 80) (bvlshr h_base (_ bv128 135)))) (_ bv80 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv11 64)) (bvshl ((_ zero_extend 120) ((_ extract 95 88) (bvlshr h_base (_ bv128 135)))) (_ bv88 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv12 64)) (bvshl ((_ zero_extend 120) ((_ extract 103 96) (bvlshr h_base (_ bv128 135)))) (_ bv96 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv13 64)) (bvshl ((_ zero_extend 120) ((_ extract 111 104) (bvlshr h_base (_ bv128 135)))) (_ bv104 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv14 64)) (bvshl ((_ zero_extend 120) ((_ extract 119 112) (bvlshr h_base (_ bv128 135)))) (_ bv112 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv15 64)) (bvshl ((_ zero_extend 120) ((_ extract 127 120) (bvlshr h_base (_ bv128 135)))) (_ bv120 128)) (_ bv0 128))) (_ bv0 128)) (_ bv0 128)))) ; uint128_t high2 = u128_branch ? high_size != 0 ? high << (sizeof(low2) * 8 - h_offset) : 0 : 0; (declare-fun high2 () (_ BitVec 128)) (assert (= high2 (ite u128_branch (ite (not (= high_size (_ bv0 64))) (bvshl high (bvsub (bvmul (_ bv16 128) (_ bv8 128)) ((_ zero_extend 64) h_offset))) (_ bv0 128)) (_ bv0 128)))) ; uint128_t low3 = u128_branch ? high_size != 0 ? low2 | high2 : low2 : 0; (declare-fun low3 () (_ BitVec 128)) (assert (= low3 (ite u128_branch (ite (not (= high_size (_ bv0 64))) (bvor low2 high2) low2) (_ bv0 128)))) ; uint128_t mask = u128_branch ? h_width < sizeof(low3) * 8 ? (((uint128_t)1) << h_width) - 1 : 0 : 0; (declare-fun mask () (_ BitVec 128)) (assert (= mask (ite u128_branch (ite (bvult h_width (bvmul (_ bv16 64) (_ bv8 64))) (bvsub (bvshl (_ bv1 128) ((_ zero_extend 64) h_width)) (_ bv1 128)) (_ bv0 128)) (_ bv0 128)))) ; uint128_t low4 = u128_branch ? h_width < sizeof(low3) * 8 ? low3 & mask : low3 : 0; (declare-fun low4 () (_ BitVec 128)) (assert (= low4 (ite u128_branch (ite (bvult h_width (bvmul (_ bv16 64) (_ bv8 64))) (bvand low3 mask) low3) (_ bv0 128)))) ; uint64_t ret = low4; (declare-fun ret () (_ BitVec 64)) (assert (= ret ((_ extract 63 0) low4))) ; uint128_t ret2 = low4; (declare-fun ret2 () (_ BitVec 128)) (assert (= ret2 low4)) ; low_size3 = aligned.width / 8 (declare-fun low_size3 () (_ BitVec 64)) (assert (= low_size3 (bvudiv aligned_width (_ bv8 64)))) ; size_t low_size4 = low_size3 > sizeof(uint64_t) ? sizeof(uint64_t) : low_size3; (declare-fun low_size4 () (_ BitVec 64)) (assert (= low_size4 (ite (bvugt low_size3 (_ bv8 64)) (_ bv8 64) low_size3))) ; memcpy(&low5, h_base, low_size4); (declare-fun low5 () (_ BitVec 64)) (assert (= low5 (bvor (ite (bvugt low_size4 (_ bv0 64)) ((_ zero_extend 56) ((_ extract 7 0) h_base)) (_ bv0 64)) (ite (bvugt low_size4 (_ bv1 64)) (bvshl ((_ zero_extend 56) ((_ extract 15 8) h_base)) (_ bv8 64)) (_ bv0 64)) (ite (bvugt low_size4 (_ bv2 64)) (bvshl ((_ zero_extend 56) ((_ extract 23 16) h_base)) (_ bv16 64)) (_ bv0 64)) (ite (bvugt low_size4 (_ bv3 64)) (bvshl ((_ zero_extend 56) ((_ extract 31 24) h_base)) (_ bv24 64)) (_ bv0 64)) (ite (bvugt low_size4 (_ bv4 64)) (bvshl ((_ zero_extend 56) ((_ extract 39 32) h_base)) (_ bv32 64)) (_ bv0 64)) (ite (bvugt low_size4 (_ bv5 64)) (bvshl ((_ zero_extend 56) ((_ extract 47 40) h_base)) (_ bv40 64)) (_ bv0 64)) (ite (bvugt low_size4 (_ bv6 64)) (bvshl ((_ zero_extend 56) ((_ extract 55 48) h_base)) (_ bv48 64)) (_ bv0 64)) (ite (bvugt low_size4 (_ bv7 64)) (bvshl ((_ zero_extend 56) ((_ extract 63 56) h_base)) (_ bv56 64)) (_ bv0 64))))) ; uint64_t low6 = low5 >> h_offset; (declare-fun low6 () (_ BitVec 64)) (assert (= low6 (bvlshr low5 h_offset))) ; size_t high_size2 = aligned_width / 8 - low_size4; (declare-fun high_size2 () (_ BitVec 64)) (assert (= high_size2 (bvsub (bvudiv aligned_width (_ bv8 64)) low_size4))) ; high_size2 != 0 ? memcpy(&high3, h_base + sizeof(low6), high_size2) : (void); (declare-fun high3 () (_ BitVec 64)) (assert (= high3 (ite (not (= high_size2 (_ bv0 64))) (bvor (ite (bvugt high_size2 (_ bv0 64)) ((_ zero_extend 56) ((_ extract 7 0) (bvlshr h_base (_ bv64 135)))) (_ bv0 64)) (ite (bvugt high_size2 (_ bv1 64)) (bvshl ((_ zero_extend 56) ((_ extract 15 8) (bvlshr h_base (_ bv64 135)))) (_ bv8 64)) (_ bv0 64)) (ite (bvugt high_size2 (_ bv2 64)) (bvshl ((_ zero_extend 56) ((_ extract 23 16) (bvlshr h_base (_ bv64 135)))) (_ bv16 64)) (_ bv0 64)) (ite (bvugt high_size2 (_ bv3 64)) (bvshl ((_ zero_extend 56) ((_ extract 31 24) (bvlshr h_base (_ bv64 135)))) (_ bv24 64)) (_ bv0 64)) (ite (bvugt high_size2 (_ bv4 64)) (bvshl ((_ zero_extend 56) ((_ extract 39 32) (bvlshr h_base (_ bv64 135)))) (_ bv32 64)) (_ bv0 64)) (ite (bvugt high_size2 (_ bv5 64)) (bvshl ((_ zero_extend 56) ((_ extract 47 40) (bvlshr h_base (_ bv64 135)))) (_ bv40 64)) (_ bv0 64)) (ite (bvugt high_size2 (_ bv6 64)) (bvshl ((_ zero_extend 56) ((_ extract 55 48) (bvlshr h_base (_ bv64 135)))) (_ bv48 64)) (_ bv0 64)) (ite (bvugt high_size2 (_ bv7 64)) (bvshl ((_ zero_extend 56) ((_ extract 63 56) (bvlshr h_base (_ bv64 135)))) (_ bv56 64)) (_ bv0 64))) (_ bv0 64)))) ; uint64_t high4 = high_size2 != 0 ? high3 << (sizeof(low6) * 8 - h_offset : 0; (declare-fun high4 () (_ BitVec 64)) (assert (= high4 (ite (not (= high_size2 (_ bv0 64))) (bvshl high3 (bvsub (bvmul (_ bv8 64) (_ bv8 64)) h_offset)) (_ bv0 64)))) ; uint64_t low7 = high_size2 != 0 ? low6 | high4 : low6; (declare-fun low7 () (_ BitVec 64)) (assert (= low7 (ite (not (= high_size2 (_ bv0 64))) (bvor low6 high4) low6))) ; uint64_t mask2 = h_width < sizeof(low7) * 8 ? (UINT64_C(1) << h_width) - 1 : 0; (declare-fun mask2 () (_ BitVec 64)) (assert (= mask2 (ite (bvult h_width (bvmul (_ bv8 64) (_ bv8 64))) (bvsub (bvshl (_ bv1 64) h_width) (_ bv1 64)) (bvnot (_ bv0 64))))) ; uint64_t low8 = h_width < sizeof(low7) * 8 ? low7 & mask2 : low7; (declare-fun low8 () (_ BitVec 64)) (assert (= low8 (ite (bvult h_width (bvmul (_ bv8 64) (_ bv8 64))) (bvand low7 mask2) low7))) ; uint64_t ret3 = h_width == 0 ? 0 : low8; (declare-fun ret3 () (_ BitVec 64)) (assert (= ret3 (ite (= h_width (_ bv0 64)) (_ bv0 64) low8))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; attempt to find a case where the specification and the value returned by the ; implementation differ (assert (not (and ; the uint128_t with uint64_t return path only returns the correct answer for ; 57-64-bit unpacks (or (not u128_branch) (bvugt h_width (_ bv64 64)) (= ((_ extract 63 0) spec) ret)) ; the uint128_t with uint128_t return path is correct for all widths when ; enabled (or (not u128_branch) (= spec ret2)) ; the uint64_t path returns the correct answer for widths ≤64 (or (bvugt h_width (_ bv64 64)) (= ((_ extract 63 0) spec) ret3)) ))) (check-sat) ;(get-model) rumur-2024.05.07/misc/sudoku.m000066400000000000000000000060411461637631000157200ustar00rootroot00000000000000-- This is an example of brute force Sudoku solving via model checking. It is -- not particularly efficient, but simply demonstrates that such a thing is -- possible. type square: 1 .. 9 var board: array[square] of array[square] of square -- have we finished? function won(): boolean; begin return forall i: square do forall j: square do !isundefined(board[i][j]) end end; end -- is the value at these two positions the same function same(i: square; j: square; k: square; l: square): boolean; begin return !isundefined(board[i][j]) & !isundefined(board[k][l]) & board[i][j] = board[k][l]; end -- have we made a mistake? function lost(): boolean; begin return -- a row contains duplicate values exists i: square do exists j: square do exists k: square do j != k & same(i, j, i, k) end end end | -- a column contains duplicate values exists i: square do exists j: square do exists k: square do j != k & same(j, i, k, i) end end end | -- a square contains duplicate values exists a: 0 .. 2 do exists b: 0 .. 2 do exists i: 1 .. 3 do exists j: 1 .. 3 do exists k: 1 .. 3 do exists l: 1 .. 3 do (i != k | j != l) & same(a * 3 + i, b * 3 + j, a * 3 + k, b * 3 + l) end end end end end end; end startstate begin -- setup the following board: -- -- ┌─┬─┬─┬─┬─┬─┬─┬─┬─┐ -- │ │ │ │ │ │ │ │ │ │ -- ├─┼─┼─┼─┼─┼─┼─┼─┼─┤ -- │ │ │ │ │ │ │ │ │ │ -- ├─┼─┼─┼─┼─┼─┼─┼─┼─┤ -- │ │ │ │ │ │ │ │ │ │ -- ├─┼─┼─┼─┼─┼─┼─┼─┼─┤ -- │3│8│4│ │ │ │ │ │ │ -- ├─┼─┼─┼─┼─┼─┼─┼─┼─┤ -- │ │ │ │ │ │ │ │ │ │ -- ├─┼─┼─┼─┼─┼─┼─┼─┼─┤ -- │ │ │ │ │ │ │ │ │ │ -- ├─┼─┼─┼─┼─┼─┼─┼─┼─┤ -- │ │ │ │ │ │ │ │ │ │ -- ├─┼─┼─┼─┼─┼─┼─┼─┼─┤ -- │ │ │ │ │ │ │ │ │ │ -- ├─┼─┼─┼─┼─┼─┼─┼─┼─┤ -- │ │ │ │ │ │ │ │ │2│ -- └─┴─┴─┴─┴─┴─┴─┴─┴─┘ -- -- This was given as an example of a hard Sudoku. I suspect it has multiple -- solutions, though I have not validated this. board[4][1] := 3; board[4][2] := 8; board[4][3] := 4; board[9][9] := 2; end -- a rule to assign an arbitrary value to a blank square ruleset i: square; j: square do ruleset v: square do rule isundefined(board[i][j]) ==> begin board[i][j] := v; end end end -- we can stop when complete invariant !won() -- backtrack when we make a mistake assume !lost() rumur-2024.05.07/misc/verifier.rng000066400000000000000000000062261461637631000165600ustar00rootroot00000000000000 rumur-2024.05.07/misc/write-raw.smt2000066400000000000000000000745311461637631000167710ustar00rootroot00000000000000; proof of correctness of write_raw() ; Similar to read-raw.smt2, the following proves that write_raw() does what it ; is intended to do. Unlike the read_raw() proof, we cannot write a simple ; equivalence to describe write_raw()’s correctness because we want to prove ; multiple things. Namely, (1) it writes the given value to the target byte ; buffer and (2) it does not overwrite any unrelated bits. ; ; The implementation of write_raw() can be viewed in ; ../rumur/resources/header.c. To begin with, we transform it into a branchless ; Static Single Assignment (SSA) form: ; ; // inline handle_align() ; size_t width = h_width + h_offset; ; size_t aligned_width = width % 8 != 0 ? width + 8 - width % 8 : width; ; ; bool u128_branch = defined(__SIZEOF_INT128__) && aligned_width > (sizeof(uint64_t) - 1) * 8; ; ; size_t low_size = u128_branch ? aligned_width / 8 : 0; ; size_t low_size2 = u128_branch ? low_size > sizeof(uint128_t) ? sizeof(uint128_t) : low_size : 0; ; ; // inline copy_out128() ; uint128_t low = 0; ; u128_branch ? memcpy(&low, h_base, low_size2) : (void); ; ; uint128_t or_mask = u128_branch ? ((uint128_t)v) << h_offset : 0; ; uint128_t or_mask2 = u128_branch ? low_size2 < sizeof(low) ? or_mask & ((((uint128_t)1) << (low_size2 * 8)) - 1) : or_mask : 0; ; ; uint128_t and_mask = u128_branch ? (((uint128_t)1) << h_offset) - 1 : 0; ; size_t high_bits = u128_branch ? h_width + h_offset < sizeof(low) * 8 ? aligned_width - h_offset - h_width : 0 : 0; ; uint128_t and_mask2 = u128_branch ? h_width + h_offset < sizeof(low) * 8 ? and_mask | (((((uint128_t)1) << high_bits) - 1) << (low_size2 * 8 - high_bits)) : and_mask : 0; ; ; uint128_t low2 = u128_branch ? (low & and_mask2) | or_mask2 : 0; ; ; // inline copy_in128() ; void *h_base2 = h_base; ; u128_branch ? memcpy(h_base2, &low2, low_size2) : (void); ; ; size_t high_size = u128_branch ? aligned_width / 8 - low_size2 : 0; ; ; // inline copy_out128() ; uint128_t high = 0; ; u128_branch ? high_size != 0 ? memcpy(&high, h_base + sizeof(low2), high_size) : (void) : (void); ; ; uint128_t or_mask3 = u128_branch ? high_size != 0 ? ((uint128_t)v) >> (sizeof(low2) * 8 - h_offset) : 0 : 0; ; uint128_t and_mask3 = u128_branch ? high_size != 0 ? ~((((uint128_t)1) << (h_width + h_offset - sizeof(low2) * 8)) - 1) : 0 : 0; ; ; uint128_t high2 = u128_branch ? high_size != 0 ? (high & and_mask3) | or_mask3 : 0 : 0; ; ; // inline copy_in128() ; void *h_base3 = h_base2; ; u128_branch ? high_size != 0 ? memcpy(h_base3 + sizeof(low2), &high2, high_size) : (void) : (void); ; ; // return from u128_branch ; ; // uint64_t logic follows ; ; size_t low_size3 = u128_branch ? 0 : aligned_width / 8; ; size_t low_size4 = u128_branch ? 0 : low_size3 > sizeof(uint64_t) ? sizeof(uint64_t) : low_size3; ; ; // inline copy_out64() ; uint64_t low3 = 0; ; u128_branch ? (void) : memcpy(&low3, h_base, low_size4); ; ; uint64_t or_mask4 = u128_branch ? 0 : ((uint64_t)v) << h_offset; ; uint64_t or_mask5 = u128_branch ? 0 : low_size4 < sizeof(low3) ? or_mask4 & ((UINT64_C(1) << (low_size4 * 8)) - 1) : or_mask4; ; ; uint64_t and_mask4 = u128_branch ? 0 : (UINT64_C(1) << h_offset) - 1; ; size_t high_bits2 = u128_branch ? 0 : h_width + h_offset < sizeof(low3) * 8 ? aligned_width - h_offset - h_width : 0; ; uint64_t and_mask5 = u128_branch ? 0 : h_wdith + h_offset < sizeof(low3) * 8 ? and_mask4 | (((UINT64_C(1) << high_bits2) - 1) << (low_size4 * 8 - high_bits2)) : and_mask4; ; ; uint64_t low4 = u128_branch ? 0 : (low3 & and_mask5) | or_mask5; ; ; // inline copy_in64() ; void *h_base4 = h_base; ; u128_branch ? (void) : memcpy(h_base4, &low4, low_size4); ; ; size_t high_size2 = u128_branch ? 0 : aligned_width / 8 - low_size4; ; ; // inline copy_out64() ; uint64_t high3 = 0; ; u128_branch ? (void) : high_size2 != 0 ? memcpy(&high3, h_base4 + sizeof(low4), high_size2) : (void); ; ; uint64_t or_mask6 = u128_branch ? 0 : high_size2 != 0 ? ((uint64_t)v) >> (sizeof(low4) * 8 - h_offset) : 0; ; uint64_t and_mask6 = u128_branch ? 0 : high_size2 != 0 ? ~((UINT64_C(1) << (h_width + h_offset - sizeof(low4) * 8)) - 1) : 0; ; ; uint64_t high4 = u128_branch ? 0 : high_size2 != 0 ? (high3 & and_mask6) | or_mask6 : 0; ; ; // inline copy_in64() ; void *h_base5 = h_base4; ; u128_branch ? (void) : high_size2 != 0 ? memcpy(h_base5 + sizeof(low4), &high4, high_size2) : (void); ; ; Now we can start translating to SMT. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (set-logic QF_AUFBV) (set-option :produce-models true) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; input value, that we treat as 128-bit to be future proof (declare-fun v () (_ BitVec 128)) ; input base, that we treat as a bitvector (declare-fun h_base () (_ BitVec 136)) ; input offset, that can be 0-7 (declare-fun h_offset () (_ BitVec 64)) (assert (bvule h_offset (_ bv7 64))) ; input width, that can be up to 64, but we future proof this to allow up to 128 (declare-fun h_width () (_ BitVec 64)) (assert (bvule h_width (_ bv128 64))) ; treat whether we have a 128-bit type as an input (declare-fun defined__SIZEOF_INT128__ () Bool) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; We cannot yet describe the spec, as we need to refer to the final value of ; h_base. Defer this to after we have expressed the logic of write_raw(). ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; translate this ; ; /* sanitise input value */ ; if (h.width < sizeof(v) * 8) { ; v &= (UINT64_C(1) << h.width) - 1; ; } ; ; into an assumption on the input (assert (bvult v (bvshl (_ bv1 128) ((_ zero_extend 64) h_width)))) ; now transliterate the rest of the implementation ; size_t width = h_width + h_offset; (declare-fun width () (_ BitVec 64)) (assert (= width (bvadd h_width h_offset))) ; size_t aligned_width = width % 8 != 0 ? width + 8 - width % 8 : width; (declare-fun aligned_width () (_ BitVec 64)) (assert (= aligned_width (ite (not (= (bvsmod width (_ bv8 64)) (_ bv0 64))) (bvadd width (bvsub (_ bv8 64) (bvsmod width (_ bv8 64)))) width))) ; bool u128_branch = defined(__SIZEOF_INT128__) && aligned_width > (sizeof(uint64_t) - 1) * 8; (declare-fun u128_branch () Bool) (assert (= u128_branch (and defined__SIZEOF_INT128__ (bvugt aligned_width (bvmul (bvsub (_ bv8 64) (_ bv1 64)) (_ bv8 64)))))) ; size_t low_size = u128_branch ? aligned_width / 8 : 0; (declare-fun low_size () (_ BitVec 64)) (assert (= low_size (ite u128_branch (bvudiv aligned_width (_ bv8 64)) (_ bv0 64)))) ; size_t low_size2 = u128_branch ? low_size > sizeof(uint128_t) ? sizeof(uint128_t) : low_size : 0; (declare-fun low_size2 () (_ BitVec 64)) (assert (= low_size2 (ite u128_branch (ite (bvugt low_size (_ bv16 64)) (_ bv16 64) low_size) (_ bv0 64)))) ; uint128_t low = 0; (declare-fun low () (_ BitVec 128)) (assert (= low (ite u128_branch (bvor (ite (bvugt low_size2 (_ bv0 64)) (bvshl ((_ zero_extend 120) ((_ extract 7 0) h_base)) (_ bv0 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv1 64)) (bvshl ((_ zero_extend 120) ((_ extract 15 8) h_base)) (_ bv8 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv2 64)) (bvshl ((_ zero_extend 120) ((_ extract 23 16) h_base)) (_ bv16 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv3 64)) (bvshl ((_ zero_extend 120) ((_ extract 31 24) h_base)) (_ bv24 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv4 64)) (bvshl ((_ zero_extend 120) ((_ extract 39 32) h_base)) (_ bv32 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv5 64)) (bvshl ((_ zero_extend 120) ((_ extract 47 40) h_base)) (_ bv40 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv6 64)) (bvshl ((_ zero_extend 120) ((_ extract 55 48) h_base)) (_ bv48 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv7 64)) (bvshl ((_ zero_extend 120) ((_ extract 63 56) h_base)) (_ bv56 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv8 64)) (bvshl ((_ zero_extend 120) ((_ extract 71 64) h_base)) (_ bv64 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv9 64)) (bvshl ((_ zero_extend 120) ((_ extract 79 72) h_base)) (_ bv72 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv10 64)) (bvshl ((_ zero_extend 120) ((_ extract 87 80) h_base)) (_ bv80 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv11 64)) (bvshl ((_ zero_extend 120) ((_ extract 95 88) h_base)) (_ bv88 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv12 64)) (bvshl ((_ zero_extend 120) ((_ extract 103 96) h_base)) (_ bv96 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv13 64)) (bvshl ((_ zero_extend 120) ((_ extract 111 104) h_base)) (_ bv104 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv14 64)) (bvshl ((_ zero_extend 120) ((_ extract 119 112) h_base)) (_ bv112 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv15 64)) (bvshl ((_ zero_extend 120) ((_ extract 127 120) h_base)) (_ bv120 128)) (_ bv0 128))) (_ bv0 128)))) ; uint128_t or_mask = u128_branch ? ((uint128_t)v) << h_offset : 0; (declare-fun or_mask () (_ BitVec 128)) (assert (= or_mask (ite u128_branch (bvshl v ((_ zero_extend 64) h_offset)) (_ bv0 128)))) ; uint128_t or_mask2 = u128_branch ? low_size2 < sizeof(low) ? or_mask & ((((uint128_t)1) << (low_size2 * 8)) - 1) : or_mask : 0; (declare-fun or_mask2 () (_ BitVec 128)) (assert (= or_mask2 (ite u128_branch (ite (bvult low_size2 (_ bv16 64)) (bvand or_mask (bvsub (bvshl (_ bv1 128) (bvmul ((_ zero_extend 64) low_size2) (_ bv8 128))) (_ bv1 128))) or_mask) (_ bv0 128)))) ; uint128_t and_mask = u128_branch ? (((uint128_t)1) << h_offset) - 1 : 0; (declare-fun and_mask () (_ BitVec 128)) (assert (= and_mask (ite u128_branch (bvsub (bvshl (_ bv1 128) ((_ zero_extend 64) h_offset)) (_ bv1 128)) (_ bv0 128)))) ; size_t high_bits = u128_branch ? h_width + h_offset < sizeof(low) * 8 ? aligned_width - h_offset - h_width : 0 : 0; (declare-fun high_bits () (_ BitVec 64)) (assert (= high_bits (ite u128_branch (ite (bvult (bvadd h_width h_offset) (bvmul (_ bv16 64) (_ bv8 64))) (bvsub (bvsub aligned_width h_offset) h_width) (_ bv0 64)) (_ bv0 64)))) ; uint128_t and_mask2 = u128_branch ? h_width + h_offset < sizeof(low) * 8 ? and_mask | (((((uint128_t)1) << high_bits) - 1) << (low_size2 * 8 - high_bits)) : and_mask : 0; (declare-fun and_mask2 () (_ BitVec 128)) (assert (= and_mask2 (ite u128_branch (ite (bvult (bvadd h_width h_offset) (bvmul (_ bv16 64) (_ bv8 64))) (bvor and_mask (bvshl (bvsub (bvshl (_ bv1 128) ((_ zero_extend 64) high_bits)) (_ bv1 128)) (bvsub (bvmul ((_ zero_extend 64) low_size2) (_ bv8 128)) ((_ zero_extend 64) high_bits)))) and_mask) (_ bv0 128)))) ; uint128_t low2 = u128_branch ? (low & and_mask2) | or_mask2 : 0; (declare-fun low2 () (_ BitVec 128)) (assert (= low2 (ite u128_branch (bvor (bvand low and_mask2) or_mask2) (_ bv0 128)))) ; u128_branch ? memcpy(h_base2, &low2, low_size2) : (void); (declare-fun h_base2 () (_ BitVec 136)) (assert (= h_base2 (ite u128_branch (bvor (ite (bvugt low_size2 (_ bv0 64)) (bvshl ((_ zero_extend 128) ((_ extract 7 0) low2)) (_ bv0 136)) (bvshl ((_ zero_extend 128) ((_ extract 7 0) h_base)) (_ bv0 136))) (ite (bvugt low_size2 (_ bv1 64)) (bvshl ((_ zero_extend 128) ((_ extract 15 8) low2)) (_ bv8 136)) (bvshl ((_ zero_extend 128) ((_ extract 15 8) h_base)) (_ bv8 136))) (ite (bvugt low_size2 (_ bv2 64)) (bvshl ((_ zero_extend 128) ((_ extract 23 16) low2)) (_ bv16 136)) (bvshl ((_ zero_extend 128) ((_ extract 23 16) h_base)) (_ bv16 136))) (ite (bvugt low_size2 (_ bv3 64)) (bvshl ((_ zero_extend 128) ((_ extract 31 24) low2)) (_ bv24 136)) (bvshl ((_ zero_extend 128) ((_ extract 31 24) h_base)) (_ bv24 136))) (ite (bvugt low_size2 (_ bv4 64)) (bvshl ((_ zero_extend 128) ((_ extract 39 32) low2)) (_ bv32 136)) (bvshl ((_ zero_extend 128) ((_ extract 39 32) h_base)) (_ bv32 136))) (ite (bvugt low_size2 (_ bv5 64)) (bvshl ((_ zero_extend 128) ((_ extract 47 40) low2)) (_ bv40 136)) (bvshl ((_ zero_extend 128) ((_ extract 47 40) h_base)) (_ bv40 136))) (ite (bvugt low_size2 (_ bv6 64)) (bvshl ((_ zero_extend 128) ((_ extract 55 48) low2)) (_ bv48 136)) (bvshl ((_ zero_extend 128) ((_ extract 55 48) h_base)) (_ bv48 136))) (ite (bvugt low_size2 (_ bv7 64)) (bvshl ((_ zero_extend 128) ((_ extract 63 56) low2)) (_ bv56 136)) (bvshl ((_ zero_extend 128) ((_ extract 63 56) h_base)) (_ bv56 136))) (ite (bvugt low_size2 (_ bv8 64)) (bvshl ((_ zero_extend 128) ((_ extract 71 64) low2)) (_ bv64 136)) (bvshl ((_ zero_extend 128) ((_ extract 71 64) h_base)) (_ bv64 136))) (ite (bvugt low_size2 (_ bv9 64)) (bvshl ((_ zero_extend 128) ((_ extract 79 72) low2)) (_ bv72 136)) (bvshl ((_ zero_extend 128) ((_ extract 79 72) h_base)) (_ bv72 136))) (ite (bvugt low_size2 (_ bv10 64)) (bvshl ((_ zero_extend 128) ((_ extract 87 80) low2)) (_ bv80 136)) (bvshl ((_ zero_extend 128) ((_ extract 87 80) h_base)) (_ bv80 136))) (ite (bvugt low_size2 (_ bv11 64)) (bvshl ((_ zero_extend 128) ((_ extract 95 88) low2)) (_ bv88 136)) (bvshl ((_ zero_extend 128) ((_ extract 95 88) h_base)) (_ bv88 136))) (ite (bvugt low_size2 (_ bv12 64)) (bvshl ((_ zero_extend 128) ((_ extract 103 96) low2)) (_ bv96 136)) (bvshl ((_ zero_extend 128) ((_ extract 103 96) h_base)) (_ bv96 136))) (ite (bvugt low_size2 (_ bv13 64)) (bvshl ((_ zero_extend 128) ((_ extract 111 104) low2)) (_ bv104 136)) (bvshl ((_ zero_extend 128) ((_ extract 111 104) h_base)) (_ bv104 136))) (ite (bvugt low_size2 (_ bv14 64)) (bvshl ((_ zero_extend 128) ((_ extract 119 112) low2)) (_ bv112 136)) (bvshl ((_ zero_extend 128) ((_ extract 119 112) h_base)) (_ bv112 136))) (ite (bvugt low_size2 (_ bv15 64)) (bvshl ((_ zero_extend 128) ((_ extract 127 120) low2)) (_ bv120 136)) (bvshl ((_ zero_extend 128) ((_ extract 127 120) h_base)) (_ bv120 136))) (bvshl ((_ zero_extend 128) ((_ extract 135 128) h_base)) (_ bv128 136))) (_ bv0 136)))) ; size_t high_size = u128_branch ? aligned_width / 8 - low_size2 : 0; (declare-fun high_size () (_ BitVec 64)) (assert (= high_size (ite u128_branch (bvsub (bvudiv aligned_width (_ bv8 64)) low_size2) (_ bv0 64)))) ; u128_branch ? high_size != 0 ? memcpy(&high, h_base + sizeof(low2), high_size) : (void) : (void); (declare-fun high () (_ BitVec 128)) (assert (= high (ite u128_branch (ite (not (= high_size (_ bv0 64))) (bvor (ite (bvugt high_size (_ bv0 64)) (bvshl ((_ zero_extend 120) ((_ extract 7 0) (bvlshr h_base2 (_ bv128 136)))) (_ bv0 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv1 64)) (bvshl ((_ zero_extend 120) ((_ extract 15 8) (bvlshr h_base2 (_ bv128 136)))) (_ bv8 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv2 64)) (bvshl ((_ zero_extend 120) ((_ extract 23 16) (bvlshr h_base2 (_ bv128 136)))) (_ bv16 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv3 64)) (bvshl ((_ zero_extend 120) ((_ extract 31 24) (bvlshr h_base2 (_ bv128 136)))) (_ bv24 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv4 64)) (bvshl ((_ zero_extend 120) ((_ extract 39 32) (bvlshr h_base2 (_ bv128 136)))) (_ bv32 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv5 64)) (bvshl ((_ zero_extend 120) ((_ extract 47 40) (bvlshr h_base2 (_ bv128 136)))) (_ bv40 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv6 64)) (bvshl ((_ zero_extend 120) ((_ extract 55 48) (bvlshr h_base2 (_ bv128 136)))) (_ bv48 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv7 64)) (bvshl ((_ zero_extend 120) ((_ extract 63 56) (bvlshr h_base2 (_ bv128 136)))) (_ bv56 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv8 64)) (bvshl ((_ zero_extend 120) ((_ extract 71 64) (bvlshr h_base2 (_ bv128 136)))) (_ bv64 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv9 64)) (bvshl ((_ zero_extend 120) ((_ extract 79 72) (bvlshr h_base2 (_ bv128 136)))) (_ bv72 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv10 64)) (bvshl ((_ zero_extend 120) ((_ extract 87 80) (bvlshr h_base2 (_ bv128 136)))) (_ bv80 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv11 64)) (bvshl ((_ zero_extend 120) ((_ extract 95 88) (bvlshr h_base2 (_ bv128 136)))) (_ bv88 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv12 64)) (bvshl ((_ zero_extend 120) ((_ extract 103 96) (bvlshr h_base2 (_ bv128 136)))) (_ bv96 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv13 64)) (bvshl ((_ zero_extend 120) ((_ extract 111 104) (bvlshr h_base2 (_ bv128 136)))) (_ bv104 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv14 64)) (bvshl ((_ zero_extend 120) ((_ extract 119 112) (bvlshr h_base2 (_ bv128 136)))) (_ bv112 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv15 64)) (bvshl ((_ zero_extend 120) ((_ extract 127 120) (bvlshr h_base2 (_ bv128 136)))) (_ bv120 128)) (_ bv0 128))) (_ bv0 128)) (_ bv0 128)))) ; uint128_t or_mask3 = u128_branch ? high_size != 0 ? ((uint128_t)v) >> (sizeof(low2) * 8 - h_offset) : 0 : 0; (declare-fun or_mask3 () (_ BitVec 128)) (assert (= or_mask3 (ite u128_branch (ite (not (= high_size (_ bv0 64))) (bvlshr v (bvsub (bvmul (_ bv16 128) (_ bv8 128)) ((_ zero_extend 64) h_offset))) (_ bv0 128)) (_ bv0 128)))) ; uint128_t and_mask3 = u128_branch ? high_size != 0 ? ~((((uint128_t)1) << (h_width + h_offset - sizeof(low2) * 8)) - 1) : 0 : 0; (declare-fun and_mask3 () (_ BitVec 128)) (assert (= and_mask3 (ite u128_branch (ite (not (= high_size (_ bv0 64))) (bvnot (bvsub (bvshl (_ bv1 128) ((_ zero_extend 64) (bvsub (bvadd h_width h_offset) (_ bv128 64)))) (_ bv1 128))) (_ bv0 128)) (_ bv0 128)))) ; uint128_t high2 = u128_branch ? high_size != 0 ? (high & and_mask3) | or_mask3 : 0 : 0; (declare-fun high2 () (_ BitVec 128)) (assert (= high2 (ite u128_branch (ite (not (= high_size (_ bv0 64))) (bvor (bvand high and_mask3) or_mask3) (_ bv0 128)) (_ bv0 128)))) ; u128_branch ? high_size != 0 ? memcpy(h_base + sizeof(low2), &high2, high_size) : (void) : (void); (declare-fun h_base3 () (_ BitVec 136)) (assert (= h_base3 (ite u128_branch (ite (not (= high_size (_ bv0 64))) (bvor ((_ zero_extend 8) ((_ extract 127 0) h_base2)) (ite (bvugt high_size (_ bv0 64)) (bvshl ((_ zero_extend 128) ((_ extract 7 0) high2)) (_ bv128 136)) (bvshl ((_ zero_extend 128) ((_ extract 135 128) h_base2)) (_ bv128 136)))) h_base2) (_ bv0 136)))) ; size_t low_size3 = u128_branch ? 0 : aligned_width / 8; (declare-fun low_size3 () (_ BitVec 64)) (assert (= low_size3 (ite u128_branch (_ bv0 64) (bvudiv aligned_width (_ bv8 64))))) ; size_t low_size4 = u128_branch ? 0 : low_size3 > sizeof(uint64_t) ? sizeof(uint64_t) : low_size3; (declare-fun low_size4 () (_ BitVec 64)) (assert (= low_size4 (ite u128_branch (_ bv0 64) (ite (bvugt low_size3 (_ bv8 64)) (_ bv8 64) low_size3)))) ; u128_branch ? (void) : memcpy(&low3, h_base, low_size4); (declare-fun low3 () (_ BitVec 64)) (assert (= low3 (ite u128_branch (_ bv0 64) (bvor (ite (bvugt low_size4 (_ bv0 64)) (bvshl ((_ zero_extend 56) ((_ extract 7 0) h_base)) (_ bv0 64)) (_ bv0 64)) (ite (bvugt low_size4 (_ bv1 64)) (bvshl ((_ zero_extend 56) ((_ extract 15 8) h_base)) (_ bv8 64)) (_ bv0 64)) (ite (bvugt low_size4 (_ bv2 64)) (bvshl ((_ zero_extend 56) ((_ extract 23 16) h_base)) (_ bv16 64)) (_ bv0 64)) (ite (bvugt low_size4 (_ bv3 64)) (bvshl ((_ zero_extend 56) ((_ extract 31 24) h_base)) (_ bv24 64)) (_ bv0 64)) (ite (bvugt low_size4 (_ bv4 64)) (bvshl ((_ zero_extend 56) ((_ extract 39 32) h_base)) (_ bv32 64)) (_ bv0 64)) (ite (bvugt low_size4 (_ bv5 64)) (bvshl ((_ zero_extend 56) ((_ extract 47 40) h_base)) (_ bv40 64)) (_ bv0 64)) (ite (bvugt low_size4 (_ bv6 64)) (bvshl ((_ zero_extend 56) ((_ extract 55 48) h_base)) (_ bv48 64)) (_ bv0 64)) (ite (bvugt low_size4 (_ bv7 64)) (bvshl ((_ zero_extend 56) ((_ extract 63 56) h_base)) (_ bv56 64)) (_ bv0 64)))))) ; uint64_t or_mask4 = u128_branch ? 0 : ((uint64_t)v) << h_offset; (declare-fun or_mask4 () (_ BitVec 64)) (assert (= or_mask4 (ite u128_branch (_ bv0 64) (bvshl ((_ extract 63 0) v) h_offset)))) ; uint64_t or_mask5 = u128_branch ? 0 : low_size4 < sizeof(low3) ? or_mask4 & ((UINT64_C(1) << (low_size4 * 8)) - 1) : or_mask4; (declare-fun or_mask5 () (_ BitVec 64)) (assert (= or_mask5 (ite u128_branch (_ bv0 64) (ite (bvult low_size4 (_ bv8 64)) (bvand or_mask4 (bvsub (bvshl (_ bv1 64) (bvmul low_size4 (_ bv8 64))) (_ bv1 64))) or_mask4)))) ; uint64_t and_mask4 = u128_branch ? 0 : (UINT64_C(1) << h_offset) - 1; (declare-fun and_mask4 () (_ BitVec 64)) (assert (= and_mask4 (ite u128_branch (_ bv0 64) (bvsub (bvshl (_ bv1 64) h_offset) (_ bv1 64))))) ; size_t high_bits2 = u128_branch ? 0 : h_wdith + h_offset < sizeof(low3) * 8 ? aligned_width - h_offset - h_width : 0; (declare-fun high_bits2 () (_ BitVec 64)) (assert (= high_bits2 (ite u128_branch (_ bv0 64) (ite (bvult (bvadd h_width h_offset) (bvmul (_ bv8 64) (_ bv8 64))) (bvsub (bvsub aligned_width h_offset) h_width) (_ bv0 64))))) ; uint64_t and_mask5 = u128_branch ? 0 : h_width + h_offset < sizeof(low3) * 8 ? and_mask4 | (((UINT64_C(1) << high_bits2) - 1) << (low_size4 * 8 - high_bits2)) : and_mask4; (declare-fun and_mask5 () (_ BitVec 64)) (assert (= and_mask5 (ite u128_branch (_ bv0 64) (ite (bvult (bvadd h_width h_offset) (bvmul (_ bv8 64) (_ bv8 64))) (bvor and_mask4 (bvshl (bvsub (bvshl (_ bv1 64) high_bits2) (_ bv1 64)) (bvsub (bvmul low_size4 (_ bv8 64)) high_bits2))) and_mask4)))) ; uint64_t low4 = u128_branch ? 0 : (low3 & and_mask5) | or_mask5; (declare-fun low4 () (_ BitVec 64)) (assert (= low4 (ite u128_branch (_ bv0 64) (bvor (bvand low3 and_mask5) or_mask5)))) ; u128_branch ? (void) : memcpy(h_base4, &low4, low_size4); (declare-fun h_base4 () (_ BitVec 136)) (assert (= h_base4 (ite u128_branch (_ bv0 136) (bvor (ite (bvugt low_size4 (_ bv0 64)) (bvshl ((_ zero_extend 128) ((_ extract 7 0) low4)) (_ bv0 136)) (bvshl ((_ zero_extend 128) ((_ extract 7 0) h_base)) (_ bv0 136))) (ite (bvugt low_size4 (_ bv1 64)) (bvshl ((_ zero_extend 128) ((_ extract 15 8) low4)) (_ bv8 136)) (bvshl ((_ zero_extend 128) ((_ extract 15 8) h_base)) (_ bv8 136))) (ite (bvugt low_size4 (_ bv2 64)) (bvshl ((_ zero_extend 128) ((_ extract 23 16) low4)) (_ bv16 136)) (bvshl ((_ zero_extend 128) ((_ extract 23 16) h_base)) (_ bv16 136))) (ite (bvugt low_size4 (_ bv3 64)) (bvshl ((_ zero_extend 128) ((_ extract 31 24) low4)) (_ bv24 136)) (bvshl ((_ zero_extend 128) ((_ extract 31 24) h_base)) (_ bv24 136))) (ite (bvugt low_size4 (_ bv4 64)) (bvshl ((_ zero_extend 128) ((_ extract 39 32) low4)) (_ bv32 136)) (bvshl ((_ zero_extend 128) ((_ extract 39 32) h_base)) (_ bv32 136))) (ite (bvugt low_size4 (_ bv5 64)) (bvshl ((_ zero_extend 128) ((_ extract 47 40) low4)) (_ bv40 136)) (bvshl ((_ zero_extend 128) ((_ extract 47 40) h_base)) (_ bv40 136))) (ite (bvugt low_size4 (_ bv6 64)) (bvshl ((_ zero_extend 128) ((_ extract 55 48) low4)) (_ bv48 136)) (bvshl ((_ zero_extend 128) ((_ extract 55 48) h_base)) (_ bv48 136))) (ite (bvugt low_size4 (_ bv7 64)) (bvshl ((_ zero_extend 128) ((_ extract 63 56) low4)) (_ bv56 136)) (bvshl ((_ zero_extend 128) ((_ extract 63 56) h_base)) (_ bv56 136))) (bvshl ((_ zero_extend 64) ((_ extract 135 64) h_base)) (_ bv64 136)))))) ; size_t high_size2 = u128_branch ? 0 : aligned_width / 8 - low_size4; (declare-fun high_size2 () (_ BitVec 64)) (assert (= high_size2 (ite u128_branch (_ bv0 64) (bvsub (bvudiv aligned_width (_ bv8 64)) low_size4)))) ; u128_branch ? (void) : high_size2 != 0 ? memcpy(&high3, h_base4 + sizeof(low4), high_size2) : (void); (declare-fun high3 () (_ BitVec 64)) (assert (= high3 (ite u128_branch (_ bv0 64) (ite (not (= high_size2 (_ bv0 64))) (bvor (ite (bvugt high_size2 (_ bv0 64)) (bvshl ((_ zero_extend 56) ((_ extract 7 0) (bvlshr h_base4 (_ bv64 136)))) (_ bv0 64)) (_ bv0 64)) (ite (bvugt high_size2 (_ bv1 64)) (bvshl ((_ zero_extend 56) ((_ extract 15 8) (bvlshr h_base4 (_ bv64 136)))) (_ bv8 64)) (_ bv0 64)) (ite (bvugt high_size2 (_ bv2 64)) (bvshl ((_ zero_extend 56) ((_ extract 23 16) (bvlshr h_base4 (_ bv64 136)))) (_ bv16 64)) (_ bv0 64)) (ite (bvugt high_size2 (_ bv3 64)) (bvshl ((_ zero_extend 56) ((_ extract 31 24) (bvlshr h_base4 (_ bv64 136)))) (_ bv24 64)) (_ bv0 64)) (ite (bvugt high_size2 (_ bv4 64)) (bvshl ((_ zero_extend 56) ((_ extract 39 32) (bvlshr h_base4 (_ bv64 136)))) (_ bv32 64)) (_ bv0 64)) (ite (bvugt high_size2 (_ bv5 64)) (bvshl ((_ zero_extend 56) ((_ extract 47 40) (bvlshr h_base4 (_ bv64 136)))) (_ bv40 64)) (_ bv0 64)) (ite (bvugt high_size2 (_ bv6 64)) (bvshl ((_ zero_extend 56) ((_ extract 55 48) (bvlshr h_base4 (_ bv64 136)))) (_ bv48 64)) (_ bv0 64)) (ite (bvugt high_size2 (_ bv7 64)) (bvshl ((_ zero_extend 56) ((_ extract 63 56) (bvlshr h_base4 (_ bv64 136)))) (_ bv56 64)) (_ bv0 64))) (_ bv0 64))))) ; uint64_t or_mask6 = u128_branch ? 0 : high_size2 != 0 ? ((uint64_t)v) >> (sizeof(low4) * 8 - h_offset) : 0; (declare-fun or_mask6 () (_ BitVec 64)) (assert (= or_mask6 (ite u128_branch (_ bv0 64) (ite (not (= high_size2 (_ bv0 64))) (bvlshr ((_ extract 63 0) v) (bvsub (bvmul (_ bv8 64) (_ bv8 64)) h_offset)) (_ bv0 64))))) ; uint64_t and_mask6 = u128_branch ? 0 : high_size2 != 0 ? ~((UINT64_C(1) << (h_width + h_offset - sizeof(low4) * 8)) - 1) : 0; (declare-fun and_mask6 () (_ BitVec 64)) (assert (= and_mask6 (ite u128_branch (_ bv0 64) (ite (not (= high_size2 (_ bv0 64))) (bvnot (bvsub (bvshl (_ bv1 64) (bvsub (bvadd h_width h_offset) (_ bv64 64))) (_ bv1 64))) (_ bv0 64))))) ; uint64_t high4 = u128_branch ? 0 : high_size2 != 0 ? (high3 & and_mask6) | or_mask6 : 0; (declare-fun high4 () (_ BitVec 64)) (assert (= high4 (ite u128_branch (_ bv0 64) (ite (not (= high_size2 (_ bv0 64))) (bvor (bvand high3 and_mask6) or_mask6) (_ bv0 64))))) ; u128_branch ? (void) : high_size2 != 0 ? memcpy(h_base5 + sizeof(low4), &high4, high_size2) : (void); (declare-fun h_base5 () (_ BitVec 136)) (assert (= h_base5 (ite u128_branch (_ bv0 136) (ite (not (= high_size2 (_ bv0 64))) (bvor ((_ zero_extend 72) ((_ extract 63 0) h_base4)) (ite (bvugt high_size2 (_ bv0 64)) (bvshl ((_ zero_extend 128) ((_ extract 7 0) high4)) (_ bv64 136)) (bvshl ((_ zero_extend 128) ((_ extract 71 64) h_base4)) (_ bv64 136))) (ite (bvugt high_size2 (_ bv1 64)) (bvshl ((_ zero_extend 128) ((_ extract 15 8) high4)) (_ bv72 136)) (bvshl ((_ zero_extend 128) ((_ extract 79 72) h_base4)) (_ bv72 136))) (ite (bvugt high_size2 (_ bv2 64)) (bvshl ((_ zero_extend 128) ((_ extract 23 16) high4)) (_ bv80 136)) (bvshl ((_ zero_extend 128) ((_ extract 87 80) h_base4)) (_ bv80 136))) (ite (bvugt high_size2 (_ bv3 64)) (bvshl ((_ zero_extend 128) ((_ extract 31 24) high4)) (_ bv88 136)) (bvshl ((_ zero_extend 128) ((_ extract 95 88) h_base4)) (_ bv88 136))) (ite (bvugt high_size2 (_ bv4 64)) (bvshl ((_ zero_extend 128) ((_ extract 39 32) high4)) (_ bv96 136)) (bvshl ((_ zero_extend 128) ((_ extract 103 96) h_base4)) (_ bv96 136))) (ite (bvugt high_size2 (_ bv5 64)) (bvshl ((_ zero_extend 128) ((_ extract 47 40) high4)) (_ bv104 136)) (bvshl ((_ zero_extend 128) ((_ extract 111 104) h_base4)) (_ bv104 136))) (ite (bvugt high_size2 (_ bv6 64)) (bvshl ((_ zero_extend 128) ((_ extract 55 48) high4)) (_ bv112 136)) (bvshl ((_ zero_extend 128) ((_ extract 119 112) h_base4)) (_ bv112 136))) (ite (bvugt high_size2 (_ bv7 64)) (bvshl ((_ zero_extend 128) ((_ extract 63 56) high4)) (_ bv120 136)) (bvshl ((_ zero_extend 128) ((_ extract 127 120) h_base4)) (_ bv120 136))) (bvshl ((_ zero_extend 128) ((_ extract 135 128) h_base4)) (_ bv128 136))) h_base4)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; We can now prove the specification we described at the top of this file. (assert (not (and ; on the 128-bit branch, the input value is correctly written to the store (or (not u128_branch) (= (bvlshr (bvand h_base3 (bvsub (bvshl (_ bv1 136) ((_ zero_extend 72) (bvadd h_width h_offset))) (_ bv1 136))) ((_ zero_extend 72) h_offset)) (bvand ((_ zero_extend 8) v) (bvsub (bvshl (_ bv1 136) ((_ zero_extend 72) h_width)) (_ bv1 136))))) ; on the 128-bit branch, all bits below the store range are unaffected (or (not u128_branch) (= h_offset (_ bv0 64)) (= (bvand h_base (bvsub (bvshl (_ bv1 136) ((_ zero_extend 72) h_offset)) (_ bv1 136))) (bvand h_base3 (bvsub (bvshl (_ bv1 136) ((_ zero_extend 72) h_offset)) (_ bv1 136))))) ; on the 128-bit branch, all bits above the store range are unaffected (or (not u128_branch) (= (bvlshr h_base ((_ zero_extend 72) (bvadd h_width h_offset))) (bvlshr h_base3 ((_ zero_extend 72) (bvadd h_width h_offset))))) ; on the 64-bit branch with a width ≤ 64, the input value is correctly ; written to the store (or u128_branch (bvugt h_width (_ bv64 64)) (= (bvlshr (bvand h_base5 (bvsub (bvshl (_ bv1 136) ((_ zero_extend 72) (bvadd h_width h_offset))) (_ bv1 136))) ((_ zero_extend 72) h_offset)) (bvand ((_ zero_extend 8) v) (bvsub (bvshl (_ bv1 136) ((_ zero_extend 72) h_width)) (_ bv1 136))))) ; on the 64-bit branch with a width ≤ 64, all bits below the store range are ; unaffected (or u128_branch (= h_offset (_ bv0 64)) (bvugt h_width (_ bv64 64)) (= (bvand h_base (bvsub (bvshl (_ bv1 136) ((_ zero_extend 72) h_offset)) (_ bv1 136))) (bvand h_base5 (bvsub (bvshl (_ bv1 136) ((_ zero_extend 72) h_offset)) (_ bv1 136))))) ; on the 64-bit branch with a width ≤ 64, all bits above the store range are ; unaffected (or u128_branch (bvugt h_width (_ bv64 64)) (= (bvlshr h_base ((_ zero_extend 72) (bvadd h_width h_offset))) (bvlshr h_base5 ((_ zero_extend 72) (bvadd h_width h_offset))))) ))) (check-sat) rumur-2024.05.07/misc/xxd.py000077500000000000000000000021171461637631000154100ustar00rootroot00000000000000#!/usr/bin/env python3 import argparse import re import sys import textwrap def main(args): # parse command line arguments parser = argparse.ArgumentParser( description="convert file contents to a C++ string") parser.add_argument("input", type=argparse.FileType("rb"), help="input file") parser.add_argument("output", type=argparse.FileType("wt"), help="output file") options = parser.parse_args(args[1:]) array = re.sub(r"[^\w\d]", "_", options.input.name) size = "{}_len".format(array) options.output.write(textwrap.dedent("""\ #include extern const unsigned char {}[] = {{""".format(array))) index = 0 while True: c = options.input.read(1) if c == b"": break if index % 12 == 0: options.output.write("\n ") options.output.write( " 0x{:02x},".format(int.from_bytes(c, byteorder="little"))) index += 1 options.output.write(textwrap.dedent("""\ }}; extern const size_t {} = sizeof({}) / sizeof({}[0]); """.format(size, array, array))) return 0 if __name__ == "__main__": sys.exit(main(sys.argv)) rumur-2024.05.07/misc/zebra-puzzle.m000066400000000000000000000110701461637631000170360ustar00rootroot00000000000000-- rumur_flags: ['--deadlock-detection', 'off'] /* This model is an experiment in using Rumur to solve the zebra puzzle * (https://en.wikipedia.org/wiki/Zebra_Puzzle). The model should eventually * fail the invariant with a counterexample that ends in the solution to the * puzzle. */ type nationality: enum { ENGLISHMAN, SPANIARD, UKRAINIAN, JAPANESE, NORWEGIAN } colour: enum { RED, GREEN, IVORY, YELLOW, BLUE } animal: enum { DOG, SNAIL, FOX, HORSE, ZEBRA } cigarette: enum { OLDGOLD, CHESTERFIELDS, KOOLS, LUCKYSTRIKE, PARLIAMENTS } drink: enum { COFFEE, TEA, MILK, ORANGEJUICE, WATER } house: record citizenship: nationality paint: colour pet: animal brand: cigarette beverage: drink end var houses: array [0 .. 4] of house startstate begin end ruleset i: 0 .. 4 do -- define rules to make arbitrary assignments for each house ruleset n: nationality do rule isundefined(houses[i].citizenship) ==> begin houses[i].citizenship := n; end end ruleset c: colour do rule isundefined(houses[i].paint) ==> begin houses[i].paint := c; end end ruleset a: animal do rule isundefined(houses[i].pet) ==> begin houses[i].pet := a; end end ruleset c: cigarette do rule isundefined(houses[i].brand) ==> begin houses[i].brand := c; end end ruleset d: drink do rule isundefined(houses[i].beverage) ==> begin houses[i].beverage := d; end end -- define assumptions to capture the rules of the puzzle assume "the Englishman lives in the red house" !isundefined(houses[i].citizenship) & !isundefined(houses[i].paint) & houses[i].citizenship = ENGLISHMAN -> houses[i].paint = RED assume "the Spaniard owns the dog" !isundefined(houses[i].citizenship) & !isundefined(houses[i].pet) & houses[i].citizenship = SPANIARD -> houses[i].pet = DOG assume "coffee is drunk in the green house" !isundefined(houses[i].paint) & !isundefined(houses[i].beverage) & houses[i].beverage = COFFEE -> houses[i].paint = GREEN assume "the Ukrainian drinks tea" !isundefined(houses[i].citizenship) & !isundefined(houses[i].beverage) & houses[i].citizenship = UKRAINIAN -> houses[i].beverage = TEA assume "the green house is immediately to the right of the ivory house" forall j: 0 .. 4 do !isundefined(houses[i].paint) & houses[i].paint = GREEN & !isundefined(houses[j].paint) & houses[j].paint = IVORY -> i = j + 1 end assume "the Old Gold smoker owns snails" !isundefined(houses[i].brand) & !isundefined(houses[i].pet) & houses[i].brand = OLDGOLD -> houses[i].pet = SNAIL assume "Kools are smoked in the yellow house" !isundefined(houses[i].brand) & !isundefined(houses[i].paint) & houses[i].brand = KOOLS -> houses[i].paint = YELLOW assume "milk is drunk in the middle house" !isundefined(houses[i].beverage) & houses[i].beverage = MILK -> i = 2 assume "the Norwegian lives in the first house" !isundefined(houses[i].citizenship) & houses[i].citizenship = NORWEGIAN -> i = 0 assume "the man who smnokes Chesterfields lives in the house next to the man with the fox" forall j: 0 .. 4 do !isundefined(houses[i].brand) & !isundefined(houses[j].pet) & houses[i].brand = CHESTERFIELDS & houses[j].pet = FOX -> i = j + 1 | j = i + 1 end assume "Kools are smoked in the house next to the house where the horse is kept" forall j: 0 .. 4 do !isundefined(houses[i].brand) & !isundefined(houses[j].pet) & houses[i].brand = KOOLS & houses[j].pet = HORSE -> i = j + 1 | j = i + 1 end assume "the Lucky Strike smoker drinks orange juice" !isundefined(houses[i].brand) & !isundefined(houses[i].beverage) & houses[i].brand = LUCKYSTRIKE -> houses[i].beverage = ORANGEJUICE assume "the Japanese smokes Parliaments" !isundefined(houses[i].citizenship) & !isundefined(houses[i].brand) & houses[i].citizenship = JAPANESE -> houses[i].brand = PARLIAMENTS assume "the Norwegian lives next to the blue house" forall j: 0 .. 4 do !isundefined(houses[i].citizenship) & !isundefined(houses[j].paint) & houses[i].citizenship = NORWEGIAN & houses[j].paint = BLUE -> i = j + 1 | j = i + 1 end end -- terminate as soon as everything in the model has been chosen invariant exists i: 0 .. 4 do isundefined(houses[i].citizenship) | isundefined(houses[i].paint) | isundefined(houses[i].pet) | isundefined(houses[i].brand) | isundefined(houses[i].beverage) end rumur-2024.05.07/murphi2c/000077500000000000000000000000001461637631000150255ustar00rootroot00000000000000rumur-2024.05.07/murphi2c/CMakeLists.txt000066400000000000000000000034171461637631000175720ustar00rootroot00000000000000# Define resources. add_custom_command( OUTPUT resources_manpage.cc COMMAND ../misc/xxd.py doc/murphi2c.1 ${CMAKE_CURRENT_BINARY_DIR}/resources_manpage.cc MAIN_DEPENDENCY doc/murphi2c.1 DEPENDS ../misc/xxd.py WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) add_custom_command( OUTPUT resources_c_prefix.cc COMMAND ../misc/xxd.py resources/c_prefix.c ${CMAKE_CURRENT_BINARY_DIR}/resources_c_prefix.cc MAIN_DEPENDENCY resources/c_prefix.c DEPENDS ../misc/xxd.py WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) add_custom_command( OUTPUT resources_h_prefix.cc COMMAND ../misc/xxd.py resources/h_prefix.h ${CMAKE_CURRENT_BINARY_DIR}/resources_h_prefix.cc MAIN_DEPENDENCY resources/h_prefix.h DEPENDS ../misc/xxd.py WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) add_executable(murphi2c ${CMAKE_CURRENT_BINARY_DIR}/resources_c_prefix.cc ${CMAKE_CURRENT_BINARY_DIR}/resources_h_prefix.cc ${CMAKE_CURRENT_BINARY_DIR}/resources_manpage.cc ../common/escape.cc ../common/help.cc src/check.cc src/CLikeGenerator.cc src/CodeGenerator.cc src/compares_complex_values.cc src/generate_c.cc src/generate_h.cc src/main.cc src/options.cc) target_include_directories(murphi2c PRIVATE src ${CMAKE_CURRENT_BINARY_DIR}/../librumur) target_link_libraries(murphi2c PRIVATE librumur) # Compress manpage add_custom_target(man-murphi2c ALL DEPENDS murphi2c.1.gz) add_custom_command( OUTPUT murphi2c.1.gz COMMAND gzip -9 --no-name --to-stdout doc/murphi2c.1 >"${CMAKE_CURRENT_BINARY_DIR}/murphi2c.1.gz" MAIN_DEPENDENCY doc/murphi2c.1 WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) install(TARGETS murphi2c RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/murphi2c.1.gz DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) rumur-2024.05.07/murphi2c/doc/000077500000000000000000000000001461637631000155725ustar00rootroot00000000000000rumur-2024.05.07/murphi2c/doc/murphi2c.1000066400000000000000000000111171461637631000174060ustar00rootroot00000000000000.TH MURPHI2C 1 .SH NAME murphi2c \- translate a Murphi model to C for simulation .SH SYNOPSIS .B \fBmurphi2c\fR \fBoptions\fR [\fB\-\-output\fR \fIFILE\fR] [\fIFILE\fR] .SH DESCRIPTION Murphi2C is a utility bundled with the Rumur model checker. It can be used to translate a Murphi model into C source code for integration into a simulator. The translation is intended to match the user's intuition of the C equivalent of their Murphi model. That is, the produced code is more readable and less micro-optimised than the model checking code produced by Rumur itself. .PP The C translation produced by Murphi2C is only an approximation of the original Murphi model. For example, there is no equivalent of the "undefined" value in C. If the input model relies on such details, the translation will not precisely match the model. Similarly the type compatibility rules for Murphi and C differ, so models that use aliases or rely on type equivalence may cause Murphi2C to produce C code that does not compile. For such models, Murphi2C is only intended to generate an initial skeleton for a C translation. You should always inspect the output C code to confirm it matches your expectations. .PP See .BR rumur(1) for more information about Rumur or Murphi. .SH OPTIONS \fB\-\-header\fR .RS Generate a C header, as opposed to a source file. .RE .PP \fB\-\-output\fR \fIFILE\fR or \fB\-o\fR \fIFILE\fR .RS Set path to write the generated C code to. Without this option, code is written to stdout. .RE .PP \fB\-\-source\fR .RS Generate a C source file, as opposed to a header. This is the default. .RE .PP \fB\-\-value\-type\fR \fITYPE\fR .RS Change the C type used to represent scalar values in the emitted code. By default, \fIint\fR is used. Murphi2C does not validate that the type you specify is a valid C type, but simply trusts that you have given something that will be available when you compile the generated code. .RE .PP \fB\-\-version\fR .RS Display version information and exit. .RE .SH NOTES The generated C code exposes a set of function pointers that can be overwritten by other code to control the behaviour of certain events: .PP .RS // Called when a model assertion is violated. The default implementation prints .br // the message and then calls exit(). .br void (*failed_assertion)(const char *message); .PP // Called when a model assumption is violated. The default implementation prints .br // the message and then calls exit(). .br void (*failed_assumption)(const char *message); .PP // Called when an error statement is reached. The default implementation prints .br // the message and then calls exit(). .br void (*error)(const char *message); .PP // Called when a cover condition is hit. The default implementation does .br // nothing. void (*cover)(const char *message); .PP // Called when a liveness condition is hit. The default implementation does .br // nothing. .br void (*liveness)(const char *message); .PP .RE Murphi records are translated into C structs that use native, platform-dependent member layout. An exception to this is if the input model performs aggregate comparisons of record or array expressions (using \fB==\fR or \fB!=\fR). If this is the case, the produced structs will be packed (using \fB__attribute__((packed))\fR) to ensure they can be compared with \fBmemcmp\fR. .SH SEE ALSO rumur(1) .SH AUTHOR All comments, questions and complaints should be directed to Matthew Fernandez . .SH LICENSE This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED \[lq]AS IS\[rq], WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to rumur-2024.05.07/murphi2c/resources/000077500000000000000000000000001461637631000170375ustar00rootroot00000000000000rumur-2024.05.07/murphi2c/resources/README.rst000066400000000000000000000003441461637631000205270ustar00rootroot00000000000000This directory contains static code snippets that are included in generated code. That is, the code in this directory is not compiled into any Rumur functionality. Rather it is emitted verbatim as part of generated source code. rumur-2024.05.07/murphi2c/resources/c_prefix.c000066400000000000000000000052271461637631000210100ustar00rootroot00000000000000/* this code was generated by Murphi2C */ #include #include #include #include #include #include #include #include /* built-in boolean type */ typedef bool boolean; static void failed_assertion_(const char *message) { fprintf(stderr, "failed assertion: %s\n", message); exit(EXIT_FAILURE); } void (*failed_assertion)(const char *) = failed_assertion_; static void failed_assumption_(const char *message) { fprintf(stderr, "failed assumption: %s\n", message); exit(EXIT_FAILURE); } void (*failed_assumption)(const char *) = failed_assumption_; static void error_(const char *message) { fprintf(stderr, "error: %s\n", message); exit(EXIT_FAILURE); } void (*error)(const char *) = error_; static void cover_(const char *message __attribute__((unused))) {} void (*cover)(const char *) = cover_; static void liveness_(const char *message __attribute__((unused))) {} void (*liveness)(const char *) = liveness_; // various printf wrappers to deal with the user having passed --value-type static __attribute__((unused)) void print_int (int v) { printf("%d", v); } static __attribute__((unused)) void print_unsigned(unsigned v) { printf("%u", v); } static __attribute__((unused)) void print_short (short v) { printf("%hd", v); } static __attribute__((unused)) void print_long (long v) { printf("%ld", v); } static __attribute__((unused)) void print_int8_t (int8_t v) { printf("%" PRId8 , v); } static __attribute__((unused)) void print_uint8_t (uint8_t v) { printf("%" PRIu8 , v); } static __attribute__((unused)) void print_int16_t (int16_t v) { printf("%" PRId16, v); } static __attribute__((unused)) void print_uint16_t(uint16_t v) { printf("%" PRIu16, v); } static __attribute__((unused)) void print_int32_t (int32_t v) { printf("%" PRId32, v); } static __attribute__((unused)) void print_uint32_t(uint32_t v) { printf("%" PRIu32, v); } static __attribute__((unused)) void print_int64_t (int64_t v) { printf("%" PRId64, v); } static __attribute__((unused)) void print_uint64_t(uint64_t v) { printf("%" PRIu64, v); } // wrappers for producing literal expressions of value type #define int_VALUE_C(v) (v) #define unsigned_VALUE_C(v) (v ## u) #define short_VALUE_C(v) ((short)(v)) #define long_VALUE_C(v) (v ## l) #define int8_t_VALUE_C(v) INT8_C(v) #define uint8_t_VALUE_C(v) UINT8_C(v) #define int16_t_VALUE_C(v) INT16_C(v) #define uint16_t_VALUE_C(v) UINT16_C(v) #define int32_t_VALUE_C(v) INT32_C(v) #define uint32_t_VALUE_C(v) UINT32_C(v) #define int64_t_VALUE_C(v) INT64_C(v) #define uint64_t_VALUE_C(v) UINT64_C(v) rumur-2024.05.07/murphi2c/resources/h_prefix.h000066400000000000000000000024141461637631000210150ustar00rootroot00000000000000#pragma once /* this code was generated by Murphi2C */ #include #include #include #include #ifdef __cplusplus extern "C" { #endif /* built-in boolean type */ typedef bool boolean; /* The following function pointers are invoked when model conditions are hit. * They have default implementations in generated code, but the default * implementation may not be the behaviour you desire in simulation. If this is * the case, reassign these to functions implementing your desired behaviour * prior to beginning simulation. You can reassign any or all of these as you * wish. */ /* Called when a model assertion is violated. The default implementation prints * the failure message to stderr and then exits. */ extern void (*failed_assertion)(const char *message); /* Called when a model assumption is violated. The default implementation prints * the failure message to stderr and then exits. */ extern void (*failed_assumption)(const char *message); /* Called when a model cover property is hit. The default implementation does * nothing. */ extern void (*cover)(const char *message); /* Called when a model liveness property is hit. The default implementation does * nothing. */ extern void (*liveness)(const char *message); rumur-2024.05.07/murphi2c/src/000077500000000000000000000000001461637631000156145ustar00rootroot00000000000000rumur-2024.05.07/murphi2c/src/CLikeGenerator.cc000066400000000000000000000602741461637631000207720ustar00rootroot00000000000000#include "CLikeGenerator.h" #include "../../common/escape.h" #include "../../common/isa.h" #include "options.h" #include #include #include #include #include #include #include #include using namespace rumur; void CLikeGenerator::visit_add(const Add &n) { *this << "(" << *n.lhs << " + " << *n.rhs << ")"; } void CLikeGenerator::visit_aliasdecl(const AliasDecl &n) { *this << "#define " << n.name << "() " << *n.value; emit_trailing_comments(n); *this << "\n"; } void CLikeGenerator::visit_aliasrule(const AliasRule &) { // this is unreachable because generate_c is only ever called with a Model // and visit_model flattens all rules assert(!"unreachable"); __builtin_unreachable(); } void CLikeGenerator::visit_aliasstmt(const AliasStmt &n) { for (const Ptr &a : n.aliases) { emit_leading_comments(*a); *this << *a; } for (const Ptr &s : n.body) { emit_leading_comments(*s); *this << *s; } for (const Ptr &a : n.aliases) { *this << "#undef " << a->name << "\n"; } if (emit_trailing_comments(n) > 0) *this << "\n"; } void CLikeGenerator::visit_and(const And &n) { *this << "(" << *n.lhs << " && " << *n.rhs << ")"; } void CLikeGenerator::visit_array(const Array &n) { mpz_class count = n.index_type->count(); assert(count > 0 && "index type of array does not include undefined"); count--; // wrap the array in a struct so that we do not have the awkwardness of // having to emit its type and size on either side of another node *this << "struct " << (pack ? "__attribute__((packed)) " : "") << "{ " << *n.element_type << " data[" << count.get_str() << "];"; // The index for this array may be an enum declared inline: // // array [enum {A, B}] of foo // // If so, we need to emit it somehow so that the enum’s members can be // referenced later. We define it within this struct to avoid any awkward // lexical issues. if (auto e = dynamic_cast(n.index_type.get())) { *this << " " << *e << ";"; } *this << " }"; } void CLikeGenerator::visit_assignment(const Assignment &n) { *this << indentation() << *n.lhs << " = " << *n.rhs << ";"; emit_trailing_comments(n); *this << "\n"; } void CLikeGenerator::visit_band(const Band &n) { *this << "(" << *n.lhs << " & " << *n.rhs << ")"; } void CLikeGenerator::visit_bnot(const Bnot &n) { *this << "(~" << *n.rhs << ")"; } void CLikeGenerator::visit_bor(const Bor &n) { *this << "(" << *n.lhs << " | " << *n.rhs << ")"; } void CLikeGenerator::visit_clear(const Clear &n) { *this << indentation() << "memset(&" << *n.rhs << ", 0, sizeof(" << *n.rhs << "));"; emit_trailing_comments(n); *this << "\n"; } void CLikeGenerator::visit_div(const Div &n) { *this << "(" << *n.lhs << " / " << *n.rhs << ")"; } void CLikeGenerator::visit_element(const Element &n) { // rather than simply indexing into the array based on the value of the index // expression, we need to account for the fact that the generated C array will // start from 0 while the Murphi array will start from a custom lower bound // find the type of the array expression const Ptr t = n.array->type()->resolve(); auto a = dynamic_cast(t.get()); assert(a != nullptr && "non-array on LHS of array indexing expression"); // find the lower bound of its index type, using some hacky mangling to align // with one of the macros from ../resources/c_prefix.c const std::string lb = value_type + "_" + a->index_type->lower_bound(); // emit an indexing operation, now account for this *this << "(" << *n.array << ".data[(" << *n.index << ") - " << lb << "])"; } void CLikeGenerator::visit_enum(const Enum &n) { *this << "enum { "; for (const std::pair &m : n.members) { *this << m.first << ", "; } *this << "}"; } void CLikeGenerator::visit_eq(const Eq &n) { if (!n.lhs->type()->is_simple()) { // This is a comparison of an array or struct. We cannot use the built-in // == operator, so we use memcmp. This only works if all members are // packed, hence why `__attribute__((pack))` is emitted in other places. assert(pack && "comparison of complex types is present but structures " "are not packed"); *this << "(memcmp(&" << *n.lhs << ", &" << *n.rhs << ", sizeof" << *n.lhs << ") == 0)"; return; } *this << "(" << *n.lhs << " == " << *n.rhs << ")"; } void CLikeGenerator::visit_errorstmt(const ErrorStmt &n) { *this << indentation() << "error(\"" << escape(n.message) << "\");"; emit_trailing_comments(n); *this << "\n"; } void CLikeGenerator::visit_exists(const Exists &n) { *this << "({ bool res_ = false; " << n.quantifier << " { res_ |= " << *n.expr << "; } res_; })"; } void CLikeGenerator::visit_exprid(const ExprID &n) { *this << "("; if (is_pointer.count(n.value->unique_id) > 0) { *this << "*"; } *this << n.id; // if this refers to an alias, it will have been emitted as a macro if (isa(n.value)) { *this << "()"; } *this << ")"; } void CLikeGenerator::visit_field(const Field &n) { *this << "(" << *n.record << "." << n.field << ")"; } void CLikeGenerator::visit_for(const For &n) { // open a scope to make all of this appear as a single statement to any // enclosing code *this << indentation() << "do {\n"; indent(); // if the type of the quantifier is an enum defined inline, we need to // define this in advance because C does not permit this to be defined // within the for loop initialiser if (auto e = dynamic_cast(n.quantifier.type.get())) { *this << indentation() << *e << ";\n"; } *this << indentation() << n.quantifier << " {\n"; indent(); for (const Ptr &s : n.body) { emit_leading_comments(*s); *this << *s; } dedent(); *this << indentation() << "}\n"; dedent(); *this << indentation() << "} while (0);"; emit_trailing_comments(n); *this << "\n"; } void CLikeGenerator::visit_forall(const Forall &n) { // open a GNU statement expression *this << "({ "; // see corresponding logic in visit_for() for an explanation if (auto e = dynamic_cast(n.quantifier.type.get())) { *this << *e << "; "; } *this << "bool res_ = true; " << n.quantifier << " { res_ &= " << *n.expr << "; } res_; })"; } void CLikeGenerator::visit_functioncall(const FunctionCall &n) { *this << n.name << "("; assert(n.function != nullptr && "unresolved function call in AST"); auto it = n.function->parameters.begin(); bool first = true; for (const Ptr &a : n.arguments) { if (!first) { *this << ", "; } if (!(*it)->readonly) { *this << "&"; } *this << *a; first = false; it++; } *this << ")"; } void CLikeGenerator::visit_geq(const Geq &n) { *this << "(" << *n.lhs << " >= " << *n.rhs << ")"; } void CLikeGenerator::visit_gt(const Gt &n) { *this << "(" << *n.lhs << " > " << *n.rhs << ")"; } void CLikeGenerator::visit_if(const If &n) { bool first = true; for (const IfClause &c : n.clauses) { if (first) { *this << indentation(); } else { *this << " else "; } dispatch(c); first = false; } emit_trailing_comments(n); *this << "\n"; } void CLikeGenerator::visit_ifclause(const IfClause &n) { if (n.condition != nullptr) { // we do not need to emit surrounding brackets for binary expressions // because they are already emitted with brackets bool needs_bracketing = !isa(n.condition); *this << "if "; if (needs_bracketing) *this << "("; *this << *n.condition; if (needs_bracketing) *this << ")"; *this << " "; } *this << "{\n"; indent(); for (const Ptr &s : n.body) { emit_leading_comments(*s); *this << *s; } dedent(); *this << indentation() << "}"; } void CLikeGenerator::visit_implication(const Implication &n) { *this << "(!" << *n.lhs << " || " << *n.rhs << ")"; } void CLikeGenerator::visit_isundefined(const IsUndefined &) { // check() prevents a model with isundefined expressions from making it // through to here assert(!"unreachable"); __builtin_unreachable(); } void CLikeGenerator::visit_leq(const Leq &n) { *this << "(" << *n.lhs << " <= " << *n.rhs << ")"; } void CLikeGenerator::visit_lsh(const Lsh &n) { *this << "(" << *n.lhs << " << " << *n.rhs << ")"; } void CLikeGenerator::visit_lt(const Lt &n) { *this << "(" << *n.lhs << " < " << *n.rhs << ")"; } void CLikeGenerator::visit_mod(const Mod &n) { *this << "(" << *n.lhs << " % " << *n.rhs << ")"; } void CLikeGenerator::visit_model(const Model &n) { emit_leading_comments(n); for (const Ptr &c : n.children) { emit_leading_comments(*c); // if this is a rule, first flatten it so we do not have to deal with the // hierarchy of rulesets, aliasrules, etc. if (auto r = dynamic_cast(c.get())) { std::vector> rs = r->flatten(); for (const Ptr &r2 : rs) *this << *r2 << "\n"; } else { *this << *c << "\n"; } } } void CLikeGenerator::visit_mul(const Mul &n) { *this << "(" << *n.lhs << " * " << *n.rhs << ")"; } void CLikeGenerator::visit_negative(const Negative &n) { *this << "(-" << *n.rhs << ")"; } void CLikeGenerator::visit_neq(const Neq &n) { if (!n.lhs->type()->is_simple()) { // see explanation in visit_eq() assert(pack && "comparison of complex types is present but structures " "are not packed"); *this << "(memcmp(&" << *n.lhs << ", &" << *n.rhs << ", sizeof" << *n.lhs << ") != 0)"; return; } *this << "(" << *n.lhs << " != " << *n.rhs << ")"; } void CLikeGenerator::visit_not(const Not &n) { *this << "(!" << *n.rhs << ")"; } void CLikeGenerator::visit_number(const Number &n) { *this << "((" << value_type << ")(" << n.value.get_str() << "))"; } void CLikeGenerator::visit_or(const Or &n) { *this << "(" << *n.lhs << " || " << *n.rhs << ")"; } void CLikeGenerator::visit_procedurecall(const ProcedureCall &n) { *this << indentation() << n.call << ";"; emit_trailing_comments(n); *this << "\n"; } void CLikeGenerator::visit_property(const Property &) { // this is unreachable because generate_c is only ever called with a Model // and nothing that contains a Property descends into it assert(!"unreachable"); __builtin_unreachable(); } void CLikeGenerator::visit_propertystmt(const PropertyStmt &n) { switch (n.property.category) { case Property::ASSERTION: *this << indentation() << "if (!" << *n.property.expr << ") {\n"; indent(); *this << indentation() << "failed_assertion(\"" << escape(n.message == "" ? n.property.expr->to_string() : n.message) << "\");\n"; dedent(); *this << indentation() << "}"; break; case Property::ASSUMPTION: *this << indentation() << "if (!" << *n.property.expr << ") {\n"; indent(); *this << indentation() << "failed_assumption(\"" << escape(n.message == "" ? n.property.expr->to_string() : n.message) << "\");\n"; dedent(); *this << indentation() << "}"; break; case Property::COVER: *this << indentation() << "if " << *n.property.expr << " {\n"; indent(); *this << indentation() << "cover(\"" << escape(n.message == "" ? n.property.expr->to_string() : n.message) << "\");\n"; dedent(); *this << indentation() << "}"; break; case Property::LIVENESS: *this << indentation() << "if " << *n.property.expr << " {\n"; indent(); *this << indentation() << "liveness(\"" << escape(n.message == "" ? n.property.expr->to_string() : n.message) << "\");\n"; dedent(); *this << indentation() << "}"; break; } emit_trailing_comments(n); *this << "\n"; } void CLikeGenerator::print(const std::string &suffix, const TypeExpr &t, const Expr &e, size_t counter) { const Ptr type = t.resolve(); // if this is boolean, handle it separately to other Enums to avoid // -Wswitch-bool warnings and cope with badly behaved users setting non-0/1 // values if (type->is_boolean()) { *this << indentation() << "printf(\"%s\", ((" << e << suffix << ") ? \"true\" : \"false\"))"; return; } // if this is an enum, print the member corresponding to its value if (auto en = dynamic_cast(type.get())) { *this << indentation() << "do {\n"; indent(); *this << indentation() << "switch (" << e << suffix << ") {\n"; indent(); size_t i = 0; for (const std::pair &m : en->members) { *this << indentation() << "case " << std::to_string(i) << ":\n"; indent(); *this << indentation() << "printf(\"%s\", \"" << m.first << "\");\n" << indentation() << "break;\n"; dedent(); ++i; } *this << indentation() << "default:\n"; indent(); *this << indentation() << "assert(!\"invalid enum value\");\n" << indentation() << "__builtin_unreachable();\n"; dedent(); dedent(); *this << indentation() << "}\n"; dedent(); *this << indentation() << "} while (0)"; return; } if (auto a = dynamic_cast(type.get())) { // printing opening “[” *this << indentation() << "do {\n"; indent(); *this << indentation() << "printf(\"[\");\n"; // invent a unique symbol using our counter const std::string i = "array_index" + std::to_string(counter); // get the bounds of the index and hackily prepend the value type to produce // something corresponding to one of the macros in ../resources/c_prefix.c const std::string lb = value_type + "_" + a->index_type->lower_bound(); const std::string ub = value_type + "_" + a->index_type->upper_bound(); *this << indentation() << "for (size_t " << i << " = 0; ; ++" << i << ") {\n"; indent(); // construct a print statement of the element at this index print(suffix + ".data[" + i + "]", *a->element_type, e, counter + 1); *this << ";\n"; *this << indentation() << "if (" << i << " == (size_t)" << ub << " - (size_t)" << lb << ") {\n"; indent(); *this << indentation() << "break;\n"; dedent(); *this << indentation() << "} else {\n"; indent(); *this << indentation() << "printf(\", \");\n"; dedent(); *this << indentation() << "}\n"; dedent(); *this << indentation() << "}\n"; // print closing “]” *this << indentation() << "printf(\"]\");\n"; dedent(); *this << indentation() << "} while (0)"; return; } if (auto r = dynamic_cast(type.get())) { // print opening “{” *this << indentation() << "do {\n"; indent(); *this << indentation() << "printf(\"{\");\n"; // print contained fields as a comma-separated list std::string sep; for (const Ptr &f : r->fields) { *this << indentation() << "printf(\"%s\", \"" << sep << "\");\n"; const Ptr ft = f->get_type(); print(suffix + "." + f->name, *ft, e, counter); *this << ";\n"; sep = ", "; } // print closing “}” *this << indentation() << "printf(\"}\");\n"; dedent(); *this << indentation() << "} while (0)"; return; } // fall back case, for Ranges and Scalarsets *this << indentation() << "print_" << value_type << "(" << e << suffix << ")"; } void CLikeGenerator::visit_put(const Put &n) { // is this a put of a literal string? if (n.expr == nullptr) { *this << indentation() << "printf(\"%s\\n\", \"" << n.value << "\");"; } else { const Ptr type = n.expr->type(); print("", *type, *n.expr, 0); *this << ";"; } emit_trailing_comments(n); *this << "\n"; } void CLikeGenerator::visit_quantifier(const Quantifier &n) { if (n.type == nullptr) { bool down_count = n.from->constant() && n.to->constant() && n.to->constant_fold() < n.from->constant_fold(); *this << "for (" << value_type << " " << n.name << " = " << *n.from << "; " << n.name << " " << (down_count ? ">=" : "<=") << " " << *n.to << "; " << n.name << " += "; if (n.step == nullptr) { *this << "1"; } else { *this << *n.step; } *this << ")"; return; } const Ptr resolved = n.type->resolve(); if (auto e = dynamic_cast(resolved.get())) { if (e->members.empty()) { // degenerate loop *this << "for (int " << n.name << " = 0; " << n.name << " < 0; " << n.name << "++)"; } else { // common case *this << "for (__typeof__(" << e->members[0].first << ") " << n.name << " = " << e->members[0].first << "; " << n.name << " <= " << e->members[e->members.size() - 1].first << "; " << n.name << "++)"; } return; } if (auto r = dynamic_cast(resolved.get())) { *this << "for (" << value_type << " " << n.name << " = " << *r->min << "; " << n.name << " <= " << *r->max << "; " << n.name << "++)"; return; } if (auto s = dynamic_cast(resolved.get())) { *this << "for (" << value_type << " " << n.name << " = 0; " << n.name << " <= " << *s->bound << "; " << n.name << "++)"; return; } assert(!"missing case in visit_quantifier()"); } void CLikeGenerator::visit_range(const Range &) { *this << value_type; } void CLikeGenerator::visit_record(const Record &n) { *this << "struct " << (pack ? "__attribute__((packed)) " : "") << "{\n"; indent(); for (const Ptr &f : n.fields) { *this << *f; } dedent(); *this << indentation() << "}"; } void CLikeGenerator::visit_return(const Return &n) { *this << indentation() << "return"; if (n.expr != nullptr) { *this << " " << *n.expr; } *this << ";"; emit_trailing_comments(n); *this << "\n"; } void CLikeGenerator::visit_rsh(const Rsh &n) { *this << "(" << *n.lhs << " >> " << *n.rhs << ")"; } void CLikeGenerator::visit_ruleset(const Ruleset &) { // this is unreachable because generate_c is only ever called with a Model // and all rule are flattened during visit_model assert(!"unreachable"); __builtin_unreachable(); } void CLikeGenerator::visit_scalarset(const Scalarset &) { *this << value_type; } void CLikeGenerator::visit_sub(const Sub &n) { *this << "(" << *n.lhs << " - " << *n.rhs << ")"; } void CLikeGenerator::visit_switch(const Switch &n) { // Rumur permits switch statements with non-constant case expressions, while // C’s switch statements do not support this. To deal with this discrepancy, // we emit switch statements as more flexible if-then-else blocks instead. // make the variable declaration and if-then-else block appear as a single // statement to any enclosing code *this << indentation() << "do {\n"; indent(); // we need to declare a temporary for the expression here because it may not // be pure, so we cannot necessarily safely emit it repeatedly in the // if-then-else conditions *this << indentation() << "__typeof__(" << *n.expr << ") res_ = " << *n.expr << ";\n"; bool first = true; for (const SwitchCase &c : n.cases) { if (first) { *this << indentation(); } else { *this << " else "; } if (!c.matches.empty()) { std::string sep = ""; *this << "if ("; for (const Ptr &m : c.matches) { *this << sep << "res_ == " << *m; sep = " || "; } *this << ") "; } *this << "{\n"; indent(); for (const Ptr &s : c.body) { emit_leading_comments(*s); *this << *s; } dedent(); *this << indentation() << "}"; first = false; } *this << "\n"; dedent(); *this << indentation() << "} while (0);"; emit_trailing_comments(n); *this << "\n"; } void CLikeGenerator::visit_switchcase(const SwitchCase &n) { if (n.matches.empty()) { *this << indentation() << "default:\n"; } else { for (const Ptr &m : n.matches) { *this << indentation() << "case " << *m << ":\n"; } } indent(); for (const Ptr &s : n.body) { emit_leading_comments(*s); *this << *s; } *this << indentation() << "break;\n"; dedent(); } void CLikeGenerator::visit_ternary(const Ternary &n) { *this << "(" << *n.cond << " ? " << *n.lhs << " : " << *n.rhs << ")"; } void CLikeGenerator::visit_typedecl(const TypeDecl &n) { // If we are typedefing something that is an enum, save this for later lookup. // See CGenerator/HGenerator::visit_constdecl for the purpose of this. if (auto e = dynamic_cast(n.value.get())) enum_typedefs[e->unique_id] = n.name; *this << indentation() << "typedef " << *n.value << " " << n.name << ";"; emit_trailing_comments(n); *this << "\n"; } void CLikeGenerator::visit_typeexprid(const TypeExprID &n) { *this << n.name; } void CLikeGenerator::visit_undefine(const Undefine &n) { *this << indentation() << "memset(&" << *n.rhs << ", 0, sizeof(" << *n.rhs << "));"; emit_trailing_comments(n); *this << "\n"; } void CLikeGenerator::visit_while(const While &n) { *this << indentation() << "while " << *n.condition << " {\n"; indent(); for (const Ptr &s : n.body) { emit_leading_comments(*s); *this << *s; } dedent(); *this << indentation() << "}"; emit_trailing_comments(n); *this << "\n"; } void CLikeGenerator::visit_xor(const Xor &n) { *this << "(" << *n.lhs << " ^ " << *n.rhs << ")"; } CLikeGenerator &CLikeGenerator::operator<<(const std::string &s) { out << s; return *this; } CLikeGenerator &CLikeGenerator::operator<<(const Node &n) { dispatch(n); return *this; } // write out a single line comment, accounting for the fact that '\' is an // escape leader in C that we should suppress if it is escaping the trailing new // line static void write_content(const Comment &c, std::ostream &out) { assert(!c.multiline); // detect if we have a trailing backslash size_t i = 0; for (; i < c.content.size(); ++i) { if (c.content[i] == '\\') { bool remainder_is_space = true; for (size_t j = i + 1; j < c.content.size(); ++j) { if (!isspace(c.content[j])) { remainder_is_space = false; break; } } if (remainder_is_space) { break; } ++i; // skip escape } } out << c.content.substr(0, i); } size_t CLikeGenerator::emit_leading_comments(const Node &n) { size_t count = 0; size_t i = 0; for (const Comment &c : comments) { // has this not yet been printed? if (!emitted[i]) { // does this precede the given node? if (c.loc.end.line < n.loc.begin.line || (c.loc.end.line == n.loc.begin.line && c.loc.end.column <= n.loc.begin.column)) { // do some white space adjustment for multiline comments if (c.multiline) { *this << indentation() << "/*"; bool dropping = false; for (const char &b : c.content) { if (b == '\n') { *this << "\n" << indentation() << " "; dropping = true; } else if (dropping) { if (!isspace(b)) { out << b; dropping = false; } } else { out << b; } } *this << "*/\n"; } else { // single line comments can be emitted simpler *this << indentation() << "//"; write_content(c, out); *this << "\n"; } emitted[i] = true; } } ++i; } return count; } size_t CLikeGenerator::drop_comments(const position &pos) { size_t count = 0; size_t i = 0; for (const Comment &c : comments) { // does this precede the given node? if (c.loc.end.line < pos.line || (c.loc.end.line == pos.line && c.loc.end.column <= pos.column)) { // mark it as emitted so it will be skipped in future emitted[i] = true; } ++i; } return count; } size_t CLikeGenerator::emit_trailing_comments(const Node &n) { size_t count = 0; size_t i = 0; for (const Comment &c : comments) { if (!emitted[i] && !c.multiline && c.loc.begin.line == n.loc.end.line) { *this << " //"; write_content(c, out); emitted[i] = true; ++count; } ++i; } return count; } CLikeGenerator::~CLikeGenerator() {} rumur-2024.05.07/murphi2c/src/CLikeGenerator.h000066400000000000000000000113271461637631000206270ustar00rootroot00000000000000#pragma once #include "CodeGenerator.h" #include #include #include #include #include #include #include // generator for C-like code class __attribute__((visibility("hidden"))) CLikeGenerator : public CodeGenerator, public rumur::ConstBaseTraversal { protected: std::ostream &out; bool pack; // mapping of Enum unique_ids to the name of a TypeDecl to them std::unordered_map enum_typedefs; // collection of unique_ids that were emitted as pointers instead of standard // variables std::unordered_set is_pointer; // list of comments from the original source std::vector comments; // whether each comment has been written to the output yet std::vector emitted; public: CLikeGenerator(const std::vector &comments_, std::ostream &out_, bool pack_) : out(out_), pack(pack_), comments(comments_), emitted(comments_.size(), false) {} void visit_add(const rumur::Add &n) final; void visit_aliasdecl(const rumur::AliasDecl &n) final; void visit_aliasrule(const rumur::AliasRule &) final; void visit_aliasstmt(const rumur::AliasStmt &n) final; void visit_and(const rumur::And &n) final; void visit_array(const rumur::Array &n) final; void visit_assignment(const rumur::Assignment &n) final; void visit_band(const rumur::Band &n) final; void visit_bnot(const rumur::Bnot &n) final; void visit_bor(const rumur::Bor &n) final; void visit_clear(const rumur::Clear &n) final; void visit_div(const rumur::Div &n) final; void visit_element(const rumur::Element &n) final; void visit_enum(const rumur::Enum &n) final; void visit_eq(const rumur::Eq &n) final; void visit_errorstmt(const rumur::ErrorStmt &n) final; void visit_exists(const rumur::Exists &n) final; void visit_exprid(const rumur::ExprID &n) final; void visit_field(const rumur::Field &n) final; void visit_implication(const rumur::Implication &n) final; void visit_isundefined(const rumur::IsUndefined &) final; void visit_for(const rumur::For &n) final; void visit_forall(const rumur::Forall &n) final; void visit_functioncall(const rumur::FunctionCall &n) final; void visit_geq(const rumur::Geq &n) final; void visit_gt(const rumur::Gt &n) final; void visit_if(const rumur::If &n) final; void visit_ifclause(const rumur::IfClause &n) final; void visit_leq(const rumur::Leq &n) final; void visit_lsh(const rumur::Lsh &n) final; void visit_lt(const rumur::Lt &n) final; void visit_mod(const rumur::Mod &n) final; void visit_model(const rumur::Model &n) final; void visit_mul(const rumur::Mul &n) final; void visit_negative(const rumur::Negative &n) final; void visit_neq(const rumur::Neq &n) final; void visit_not(const rumur::Not &n) final; void visit_number(const rumur::Number &n) final; void visit_or(const rumur::Or &n) final; void visit_procedurecall(const rumur::ProcedureCall &n) final; void visit_property(const rumur::Property &) final; void visit_propertystmt(const rumur::PropertyStmt &n) final; void visit_put(const rumur::Put &n) final; void visit_quantifier(const rumur::Quantifier &n) final; void visit_range(const rumur::Range &) final; void visit_record(const rumur::Record &n) final; void visit_return(const rumur::Return &n) final; void visit_rsh(const rumur::Rsh &n) final; void visit_ruleset(const rumur::Ruleset &) final; void visit_scalarset(const rumur::Scalarset &) final; void visit_sub(const rumur::Sub &n) final; void visit_switch(const rumur::Switch &n) final; void visit_switchcase(const rumur::SwitchCase &n) final; void visit_ternary(const rumur::Ternary &n) final; void visit_typedecl(const rumur::TypeDecl &n) final; void visit_typeexprid(const rumur::TypeExprID &n) final; void visit_undefine(const rumur::Undefine &n) final; void visit_while(const rumur::While &n) final; void visit_xor(const rumur::Xor &n) final; // helpers to make output more natural CLikeGenerator &operator<<(const std::string &s); CLikeGenerator &operator<<(const rumur::Node &n); // make this class abstract virtual ~CLikeGenerator() = 0; private: // generate a print statement of the given expression and (possibly // not-terminal) type void print(const std::string &suffix, const rumur::TypeExpr &t, const rumur::Expr &e, size_t counter); protected: // output comments preceding the given node size_t emit_leading_comments(const rumur::Node &n); // discard any un-emitted comments preceding the given position size_t drop_comments(const rumur::position &pos); // output single line comments following the given node size_t emit_trailing_comments(const rumur::Node &n); }; rumur-2024.05.07/murphi2c/src/CodeGenerator.cc000066400000000000000000000005761461637631000206540ustar00rootroot00000000000000#include "CodeGenerator.h" #include #include #include std::string CodeGenerator::indentation() const { return std::string(indent_level * 2, ' '); } void CodeGenerator::indent() { indent_level++; } void CodeGenerator::dedent() { assert(indent_level > 0 && "attempted negative indentation"); indent_level--; } CodeGenerator::~CodeGenerator() {} rumur-2024.05.07/murphi2c/src/CodeGenerator.h000066400000000000000000000010021461637631000204770ustar00rootroot00000000000000#pragma once #include #include // some common supporting code used by code generation functions class CodeGenerator { private: size_t indent_level = 0; // current indentation level protected: // get a white space string for the current indentation level std::string indentation() const; // increase the current indentation level void indent(); // decrease the current indentation level void dedent(); public: // make this class abstract virtual ~CodeGenerator() = 0; }; rumur-2024.05.07/murphi2c/src/check.cc000066400000000000000000000007151461637631000172030ustar00rootroot00000000000000#include "check.h" #include #include #include #include using namespace rumur; namespace { class Check : public ConstTraversal { public: bool ok = true; void visit_isundefined(const IsUndefined &) final { if (ok) { std::cerr << "isundefined expressions are not supported\n"; ok = false; } } }; } // namespace bool check(const Node &n) { Check c; c.dispatch(n); return c.ok; } rumur-2024.05.07/murphi2c/src/check.h000066400000000000000000000003001461637631000170330ustar00rootroot00000000000000#pragma once #include // validate the given AST contains no idioms that cannot be handled by murphi2c, // and return false if any are found bool check(const rumur::Node &n); rumur-2024.05.07/murphi2c/src/compares_complex_values.cc000066400000000000000000000015301461637631000230410ustar00rootroot00000000000000#include "compares_complex_values.h" #include #include using namespace rumur; namespace { class ComplexComparisonFinder : public ConstTraversal { private: bool seen = false; public: void visit_eq(const Eq &n) final { dispatch(*n.lhs); dispatch(*n.rhs); seen |= !n.lhs->type()->is_simple(); // we do not need to look at the type of the RHS as we assume the model has // already been validated, thus all comparisons are type checked, and // therefore the type of the RHS is identical } void visit_neq(const Neq &n) final { dispatch(*n.lhs); dispatch(*n.rhs); seen |= !n.lhs->type()->is_simple(); } bool found() const { return seen; } }; } // namespace bool compares_complex_values(const Node &n) { ComplexComparisonFinder ccf; ccf.dispatch(n); return ccf.found(); } rumur-2024.05.07/murphi2c/src/compares_complex_values.h000066400000000000000000000003661461637631000227110ustar00rootroot00000000000000#pragma once #include #include // Determine whether the given AST contains any comparisons of records or // arrays. See main.cc for why this is interesting/relevant. bool compares_complex_values(const rumur::Node &n); rumur-2024.05.07/murphi2c/src/generate_c.cc000066400000000000000000000157421461637631000202300ustar00rootroot00000000000000#include "generate_c.h" #include "CLikeGenerator.h" #include "options.h" #include "resources.h" #include #include #include #include #include using namespace rumur; namespace { class CGenerator : public CLikeGenerator { public: CGenerator(const std::vector &comments_, std::ostream &out_, bool pack_) : CLikeGenerator(comments_, out_, pack_) {} void visit_constdecl(const ConstDecl &n) final { *this << indentation() << "const "; // if this constant has an explicit type, use that if (n.type != nullptr) { *this << *n.type; } else { // otherwise, if it was a previously typedefed enum, use its typedefed // name (to avoid later -Wsign-compare warnings on GCC) const Ptr type = n.value->type(); auto it = enum_typedefs.find(type->unique_id); if (it != enum_typedefs.end()) { *this << it->second; } else { // fallback on the type of the right hand side *this << "__typeof__(" << *n.value << ")"; } } *this << " " << n.name << " = " << *n.value << ";"; emit_trailing_comments(n); *this << "\n"; } void visit_function(const Function &n) final { *this << indentation(); if (n.return_type == nullptr) { *this << "void"; } else { *this << *n.return_type; } *this << " " << n.name << "("; if (n.parameters.empty()) { *this << "void"; } else { std::string sep; for (const Ptr &p : n.parameters) { *this << sep << *p->type << " "; // if this is a var parameter, it needs to be a pointer if (!p->readonly) { (void)is_pointer.insert(p->unique_id); *this << "*"; } *this << p->name; sep = ", "; } } *this << ") {\n"; indent(); for (const Ptr &d : n.decls) { emit_leading_comments(*d); *this << *d; } for (const Ptr &s : n.body) { emit_leading_comments(*s); *this << *s; } dedent(); *this << "}\n"; } void visit_propertyrule(const PropertyRule &n) final { // function prototype *this << indentation() << "bool " << n.name << "("; // parameters if (n.quantifiers.empty()) { *this << "void"; } else { std::string sep; for (const Quantifier &q : n.quantifiers) { *this << sep; if (auto t = dynamic_cast(q.type.get())) { *this << t->name; } else { *this << value_type; } *this << " " << q.name; sep = ", "; } } *this << ") {\n"; indent(); // any aliases this property uses for (const Ptr &a : n.aliases) { *this << *a; } *this << indentation() << "return " << *n.property.expr << ";\n"; // clean up any aliases we defined for (const Ptr &a : n.aliases) { *this << "#undef " << a->name << "\n"; } dedent(); *this << "}\n"; } void visit_simplerule(const SimpleRule &n) final { *this << indentation() << "bool guard_" << n.name << "("; // parameters if (n.quantifiers.empty()) { *this << "void"; } else { std::string sep; for (const Quantifier &q : n.quantifiers) { *this << sep; if (auto t = dynamic_cast(q.type.get())) { *this << t->name; } else { *this << value_type; } *this << " " << q.name; sep = ", "; } } *this << ") {\n"; indent(); // any aliases that are defined in an outer scope for (const Ptr &a : n.aliases) { *this << *a; } *this << indentation() << "return "; if (n.guard == nullptr) { *this << "true"; } else { *this << *n.guard; } *this << ";\n"; // clean up aliases for (const Ptr &a : n.aliases) { *this << "#undef " << a->name << "\n"; } dedent(); *this << indentation() << "}\n\n"; *this << indentation() << "void rule_" << n.name << "("; // parameters if (n.quantifiers.empty()) { *this << "void"; } else { std::string sep; for (const Quantifier &q : n.quantifiers) { *this << sep; if (auto t = dynamic_cast(q.type.get())) { *this << t->name; } else { *this << value_type; } *this << " " << q.name; sep = ", "; } } *this << ") {\n"; indent(); // aliases, variables, local types, etc. for (const Ptr &a : n.aliases) { emit_leading_comments(*a); *this << *a; } for (const Ptr &d : n.decls) { emit_leading_comments(*d); *this << *d; } for (const Ptr &s : n.body) { emit_leading_comments(*s); *this << *s; } // clean up any aliases we defined for (const Ptr &d : n.decls) { if (auto a = dynamic_cast(d.get())) { *this << "#undef " << a->name << "\n"; } } for (const Ptr &a : n.aliases) { *this << "#undef " << a->name << "\n"; } dedent(); *this << indentation() << "}\n"; } void visit_startstate(const StartState &n) final { *this << indentation() << "void startstate_" << n.name << "("; // parameters if (n.quantifiers.empty()) { *this << "void"; } else { std::string sep; for (const Quantifier &q : n.quantifiers) { *this << sep; if (auto t = dynamic_cast(q.type.get())) { *this << t->name; } else { *this << value_type; } *this << " " << q.name; sep = ", "; } } *this << ") {\n"; indent(); // aliases, variables, local types, etc. for (const Ptr &a : n.aliases) { emit_leading_comments(*a); *this << *a; } for (const Ptr &d : n.decls) { emit_leading_comments(*d); *this << *d; } for (const Ptr &s : n.body) { emit_leading_comments(*s); *this << *s; } // clean up any aliases we defined for (const Ptr &d : n.decls) { if (auto a = dynamic_cast(d.get())) { *this << "#undef " << a->name << "\n"; } } for (const Ptr &a : n.aliases) { *this << "#undef " << a->name << "\n"; } dedent(); *this << indentation() << "}\n\n"; } void visit_vardecl(const VarDecl &n) final { *this << indentation() << *n.type << " " << n.name << ";"; emit_trailing_comments(n); *this << "\n"; } virtual ~CGenerator() = default; }; } // namespace void generate_c(const Node &n, const std::vector &comments, bool pack, std::ostream &out) { // write the static prefix to the beginning of the source file for (size_t i = 0; i < resources_c_prefix_c_len; i++) out << (char)resources_c_prefix_c[i]; CGenerator gen(comments, out, pack); gen.dispatch(n); } rumur-2024.05.07/murphi2c/src/generate_c.h000066400000000000000000000007231461637631000200630ustar00rootroot00000000000000#pragma once #include #include #include #include /// output C code equivalent of the given node /// /// \param n Node to translate /// \param comments List of source code comments /// \param pack Whether all structs are packed /// \param out Stream to write translation to void generate_c(const rumur::Node &n, const std::vector &comments, bool pack, std::ostream &out); rumur-2024.05.07/murphi2c/src/generate_h.cc000066400000000000000000000114371461637631000202320ustar00rootroot00000000000000#include "generate_h.h" #include "CLikeGenerator.h" #include "options.h" #include "resources.h" #include #include #include #include #include using namespace rumur; namespace { class HGenerator : public CLikeGenerator { public: HGenerator(const std::vector &comments_, std::ostream &out_, bool pack_) : CLikeGenerator(comments_, out_, pack_) {} void visit_constdecl(const ConstDecl &n) final { *this << indentation() << "extern const "; // replicate the logic from CGenerator::visit_constdecl if (n.type != nullptr) { *this << *n.type; } else { const Ptr type = n.value->type(); auto it = enum_typedefs.find(type->unique_id); if (it != enum_typedefs.end()) { *this << it->second; } else { *this << "__typeof__(" << *n.value << ")"; } } *this << " " << n.name << ";"; emit_trailing_comments(n); *this << "\n"; } void visit_function(const Function &n) final { *this << indentation(); if (n.return_type == nullptr) { *this << "void"; } else { *this << *n.return_type; } *this << " " << n.name << "("; if (n.parameters.empty()) { *this << "void"; } else { std::string sep; for (const Ptr &p : n.parameters) { *this << sep << *p->type << " "; // if this is a var parameter, it needs to be a pointer if (!p->readonly) { (void)is_pointer.insert(p->unique_id); *this << "*"; } *this << p->name; sep = ", "; } } *this << ");\n"; // discard any comments related to declarations and statements within this // function drop_comments(n.loc.end); } void visit_propertyrule(const PropertyRule &n) final { // function prototype *this << indentation() << "bool " << n.name << "("; // parameters if (n.quantifiers.empty()) { *this << "void"; } else { std::string sep; for (const Quantifier &q : n.quantifiers) { *this << sep; if (auto t = dynamic_cast(q.type.get())) { *this << t->name; } else { *this << value_type; } *this << " " << q.name; sep = ", "; } } *this << ");\n"; } void visit_simplerule(const SimpleRule &n) final { *this << indentation() << "bool guard_" << n.name << "("; // parameters if (n.quantifiers.empty()) { *this << "void"; } else { std::string sep; for (const Quantifier &q : n.quantifiers) { *this << sep; if (auto t = dynamic_cast(q.type.get())) { *this << t->name; } else { *this << value_type; } *this << " " << q.name; sep = ", "; } } *this << ");\n\n"; *this << indentation() << "void rule_" << n.name << "("; // parameters if (n.quantifiers.empty()) { *this << "void"; } else { std::string sep; for (const Quantifier &q : n.quantifiers) { *this << sep; if (auto t = dynamic_cast(q.type.get())) { *this << t->name; } else { *this << value_type; } *this << " " << q.name; sep = ", "; } } *this << ");\n"; // discard any comments associated with things within this rule drop_comments(n.loc.end); } void visit_startstate(const StartState &n) final { *this << indentation() << "void startstate_" << n.name << "("; // parameters if (n.quantifiers.empty()) { *this << "void"; } else { std::string sep; for (const Quantifier &q : n.quantifiers) { *this << sep; if (auto t = dynamic_cast(q.type.get())) { *this << t->name; } else { *this << value_type; } *this << " " << q.name; sep = ", "; } } *this << ");\n"; // discard any comments associated with things within this rule drop_comments(n.loc.end); } void visit_vardecl(const VarDecl &n) final { *this << indentation(); if (n.is_in_state()) *this << "extern "; *this << *n.type << " " << n.name << ";"; emit_trailing_comments(n); *this << "\n"; } }; } // namespace void generate_h(const Node &n, const std::vector &comments, bool pack, std::ostream &out) { // write the static prefix to the beginning of the source file for (size_t i = 0; i < resources_h_prefix_h_len; i++) out << (char)resources_h_prefix_h[i]; HGenerator gen(comments, out, pack); gen.dispatch(n); // close the `extern "C"` block opened in ../resources/h_prefix.h out << "\n" << "#ifdef __cplusplus\n" << "}\n" << "#endif\n"; } rumur-2024.05.07/murphi2c/src/generate_h.h000066400000000000000000000007201461637631000200650ustar00rootroot00000000000000#pragma once #include #include #include #include // output a C header API for the given node /// /// \param n Node to translate /// \param comments List of source code comments /// \param pack Whether all structs are packed /// \param out Stream to write translation to void generate_h(const rumur::Node &n, const std::vector &comments, bool pack, std::ostream &out); rumur-2024.05.07/murphi2c/src/main.cc000066400000000000000000000112401461637631000170450ustar00rootroot00000000000000#include "../../common/help.h" #include "check.h" #include "compares_complex_values.h" #include "generate_c.h" #include "generate_h.h" #include "options.h" #include "resources.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include // a pair of input streams using dup_t = std::pair, std::shared_ptr>; static std::string in_filename = ""; static dup_t in; static std::shared_ptr out; // output C source? (as opposed to C header) static bool source = true; static void parse_args(int argc, char **argv) { for (;;) { static struct option options[] = { // clang-format off { "header", no_argument, 0, 128 }, { "help", no_argument, 0, 'h' }, { "output", required_argument, 0, 'o' }, { "source", no_argument, 0, 129 }, { "value-type", required_argument, 0, 130 }, { "version", no_argument, 0, 131 }, { 0, 0, 0, 0 }, // clang-format on }; int option_index = 0; int c = getopt_long(argc, argv, "ho:", options, &option_index); if (c == -1) break; switch (c) { case '?': std::cerr << "run `" << argv[0] << " --help` to see available options\n"; exit(EXIT_SUCCESS); case 128: // --header source = false; break; case 'h': // --help help(doc_murphi2c_1, doc_murphi2c_1_len); exit(EXIT_SUCCESS); case 'o': { auto o = std::make_shared(optarg); if (!o->is_open()) { std::cerr << "failed to open " << optarg << "\n"; exit(EXIT_FAILURE); } out = o; break; } case 129: // --source source = true; break; case 130: // --value-type // note that we just assume the type the user gave us exists value_type = optarg; break; case 131: // --version std::cout << "Murphi2C version " << rumur::get_version() << "\n"; exit(EXIT_SUCCESS); default: std::cerr << "unexpected error\n"; exit(EXIT_FAILURE); } } if (optind == argc - 1) { struct stat buf; if (stat(argv[optind], &buf) < 0) { std::cerr << "failed to open " << argv[optind] << ": " << strerror(errno) << "\n"; exit(EXIT_FAILURE); } if (S_ISDIR(buf.st_mode)) { std::cerr << "failed to open " << argv[optind] << ": this is a directory\n"; exit(EXIT_FAILURE); } in_filename = argv[optind]; auto i = std::make_shared(in_filename); auto j = std::make_shared(in_filename); if (!i->is_open() || !j->is_open()) { std::cerr << "failed to open " << in_filename << "\n"; exit(EXIT_FAILURE); } in = dup_t(i, j); } } static dup_t make_stdin_dup() { // read stdin into memory auto buffer = std::make_shared(); *buffer << std::cin.rdbuf(); // duplicate the buffer auto copy = std::make_shared(buffer->str()); return dup_t(buffer, copy); } int main(int argc, char **argv) { // parse command line options parse_args(argc, argv); // if we are reading from stdin, duplicate it so that we can parse it both as // Murphi and for comments if (in.first == nullptr) in = make_stdin_dup(); // parse input model rumur::Ptr m; try { m = rumur::parse(*in.first); } catch (rumur::Error &e) { std::cerr << e.loc << ":" << e.what() << "\n"; return EXIT_FAILURE; } assert(m != nullptr); // update unique identifiers within the model m->reindex(); // check the model is valid try { resolve_symbols(*m); validate(*m); } catch (rumur::Error &e) { std::cerr << e.loc << ":" << e.what() << "\n"; return EXIT_FAILURE; } // validate that this model is OK to translate if (!check(*m)) return EXIT_FAILURE; // name any rules that are unnamed, so they get valid C symbols rumur::sanitise_rule_names(*m); // Determine if we have any == or != involving records or arrays, in which // case we will need to pack structs. See generate_c() for why. bool pack = compares_complex_values(*m); // parse comments from the source code std::vector comments = rumur::parse_comments(*in.second); // output code if (source) { generate_c(*m, comments, pack, out == nullptr ? std::cout : *out); } else { generate_h(*m, comments, pack, out == nullptr ? std::cout : *out); } return EXIT_SUCCESS; } rumur-2024.05.07/murphi2c/src/options.cc000066400000000000000000000001331461637631000176130ustar00rootroot00000000000000#include "options.h" #include #include std::string value_type = "int"; rumur-2024.05.07/murphi2c/src/options.h000066400000000000000000000002031461637631000174530ustar00rootroot00000000000000#pragma once #include #include // what C type should we use for scalar values? extern std::string value_type; rumur-2024.05.07/murphi2c/src/resources.h000066400000000000000000000006631461637631000200040ustar00rootroot00000000000000#pragma once #include // these are generated by ../../misc/xxd.py // ../doc/murphi2c.1 extern const unsigned char doc_murphi2c_1[]; extern const size_t doc_murphi2c_1_len; // ../resources/c_prefix.c extern const unsigned char resources_c_prefix_c[]; extern const size_t resources_c_prefix_c_len; // ../resources/h_prefix.h extern const unsigned char resources_h_prefix_h[]; extern const size_t resources_h_prefix_h_len; rumur-2024.05.07/murphi2murphi/000077500000000000000000000000001461637631000161075ustar00rootroot00000000000000rumur-2024.05.07/murphi2murphi/CMakeLists.txt000066400000000000000000000023631461637631000206530ustar00rootroot00000000000000# Define resources. add_custom_command( OUTPUT resources_manpage.cc COMMAND ../misc/xxd.py doc/murphi2murphi.1 ${CMAKE_CURRENT_BINARY_DIR}/resources_manpage.cc MAIN_DEPENDENCY doc/murphi2murphi.1 DEPENDS ../misc/xxd.py WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) add_executable(murphi2murphi ${CMAKE_CURRENT_BINARY_DIR}/resources_manpage.cc ../common/help.cc src/DecomposeComplexComparisons.cc src/ExplicitSemicolons.cc src/main.cc src/options.cc src/Pipeline.cc src/Printer.cc src/RemoveLiveness.cc src/Stage.cc src/SwitchToIf.cc src/ToAscii.cc) target_include_directories(murphi2murphi PRIVATE src ${CMAKE_CURRENT_BINARY_DIR}/../librumur) target_link_libraries(murphi2murphi PRIVATE librumur) # Compress manpage add_custom_target(man-murphi2murphi ALL DEPENDS murphi2murphi.1.gz) add_custom_command( OUTPUT murphi2murphi.1.gz COMMAND gzip -9 --no-name --to-stdout doc/murphi2murphi.1 >"${CMAKE_CURRENT_BINARY_DIR}/murphi2murphi.1.gz" MAIN_DEPENDENCY doc/murphi2murphi.1 WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) install(TARGETS murphi2murphi RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/murphi2murphi.1.gz DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) rumur-2024.05.07/murphi2murphi/doc/000077500000000000000000000000001461637631000166545ustar00rootroot00000000000000rumur-2024.05.07/murphi2murphi/doc/murphi2murphi.1000066400000000000000000000077261461637631000215650ustar00rootroot00000000000000.TH MURPHI2MURPHI 1 .SH NAME murphi2murphi \- preprocessor for Murphi models .SH SYNOPSIS .B \fBmurphi2murphi\fR \fBoptions\fR [\fB\-\-output\fR \fIFILE\fR] [\fIFILE\fR] .SH DESCRIPTION Murphi2Murphi is a utility bundled with the Rumur model checker. It can be used for various source-to-source transformations of Murphi models. It supports options for "desugaring" Rumur-specific constructs into equivalent, more widely supported Murphi constructs. It is also useful for reducing the number of different constructs that appear in a Murphi model, to simplify the job of downstream model-consuming tools. .PP All transformations are off by default. I.e. Murphi2Murphi will simply reproduce the source model unmodified. Transformations can be selectively enabled using the options described below. Each transformation option below has an inverse \fBno\-\fRprefixed option (e.g. \fB\-\-no-explicit\-semicolons\fR for \fB\-\-explicit\-semicolons\fR) for disabling that transformation. Whichever occurs last, the enabling option for a transformation or the disabling option for a transformation, will take precedence. .PP See .BR rumur(1) for more information about Rumur or Murphi. .SH OPTIONS \fB\-\-decompose\-complex\-comparisons\fR .RS Rumur supports comparing values of complex type (records and arrays) with each other using the \fB=\fR and \fB!=\fR operators. Other Murphi tools typically only support the comparison of values of simple type (booleans, ranges, enums, scalarsets). This option breaks comparisons of complex typed values into element-wise comparisons of their members, for compatibility with other tools. .RE .PP \fB\-\-explicit\-semicolons\fR .RS Rumur allows semi-colons to be omitted in some places within a Murphi model. E.g. the semi-colon following a rule definition is optional. This option adds semi-colons where they have been omitted, to aid other Murphi tools that may require them. .RE .PP \fB\-\-output\fR \fIFILE\fR or \fB\-o\fR \fIFILE\fR .RS Set path to write the resulting model to. Without this option, the model is written to stdout. .RE .PP \fB\-\-remove\-liveness\fR .RS Remove any liveness properties from the model. These are not supported by other Murphi tools. .RE .PP \fB\-\-switch\-to\-if\fR .RS Transform switch statements into if-then-else statements. This can be useful for downstream tools that then only need to handle if-then-else statements, and not also switch statements. .RE .PP \fB\-\-to\-ascii\fR .RS Turn extended unicode operators into their ASCII equivalents. This makes models that use these extended characters compatible with other Murphi tools. .RE .PP \fB\-\-version\fR .RS Display version information and exit. .RE .SH SEE ALSO rumur(1) .SH AUTHOR All comments, questions and complaints should be directed to Matthew Fernandez . .SH LICENSE This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED \[lq]AS IS\[rq], WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to rumur-2024.05.07/murphi2murphi/src/000077500000000000000000000000001461637631000166765ustar00rootroot00000000000000rumur-2024.05.07/murphi2murphi/src/DecomposeComplexComparisons.cc000066400000000000000000000104121461637631000246670ustar00rootroot00000000000000#include "DecomposeComplexComparisons.h" #include "Stage.h" #include #include #include #include #include #include #include using namespace rumur; DecomposeComplexComparisons::DecomposeComplexComparisons(Stage &next_) : IntermediateStage(next_) {} void DecomposeComplexComparisons::visit_eq(const Eq &n) { rewrite(n, true); } void DecomposeComplexComparisons::visit_neq(const Neq &n) { rewrite(n, false); } // find all identifiers used within a given expression static std::unordered_set find_ids(const Expr &e) { // a traversal that collects ExprIDs class ExprIDFinder : public ConstTraversal { public: std::unordered_set ids; void visit_exprid(const ExprID &n) final { (void)ids.insert(n.id); } }; // use this to find all contained ExprIDs ExprIDFinder finder; finder.dispatch(e); return finder.ids; } // create a new identifier, unique with respect to the given identifiers static std::string make_id(std::unordered_set &ids) { // arbitrary prefix std::string v = "i"; for (size_t i = 0;; i++) { std::string candidate = v + std::to_string(i); auto res = ids.insert(candidate); if (res.second) return candidate; assert(i != SIZE_MAX && "exhausted variable space"); } __builtin_unreachable(); } static std::string explode(std::unordered_set &ids, const std::string &prefix_a, const std::string &prefix_b, const std::string &stem, const TypeExpr &type, bool is_eq) { std::ostringstream buf; // if this is a simple type, we have bottomed out in something we can directly // compare if (type.is_simple()) { buf << prefix_a << stem << (is_eq ? " = " : " != ") << prefix_b << stem; return buf.str(); } const Ptr t = type.resolve(); // if this is a record, join together a comparison of each of its fields if (auto r = dynamic_cast(t.get())) { std::string sep; for (const Ptr &f : r->fields) { const std::string new_stem = stem + "." + f->name; buf << sep << "(" << explode(ids, prefix_a, prefix_b, new_stem, *f->type, is_eq) << ")"; sep = is_eq ? " & " : " | "; } return buf.str(); } auto a = dynamic_cast(t.get()); assert(a != nullptr && "non-record, non-array encountered when decomposing " "complex expression"); const std::string binder = is_eq ? "forall" : "exists"; // invent an id for the index in the binder const std::string i = make_id(ids); const std::string new_stem = stem + "[" + i + "]"; buf << binder << " " << i << ": " << a->index_type->to_string() << " do " << explode(ids, prefix_a, prefix_b, new_stem, *a->element_type, is_eq) << " end" << binder; return buf.str(); } void DecomposeComplexComparisons::rewrite(const EquatableBinaryExpr &n, bool is_eq) { // if this is a comparison of simple types, we can let it pass through const Ptr t = n.lhs->type(); if (t->is_simple()) { assert(n.rhs->type()->is_simple() && "comparison of simple type to complex type"); next.dispatch(n); return; } // if either side of the comparison has side effects, we cannot decompose it // because emitting the stem multiple times will cause the side effect(s) to // repeat if (!n.lhs->is_pure()) throw Error("cannot decompose complex comparison because the left hand " "side has side effects", n.lhs->loc); if (!n.rhs->is_pure()) throw Error("cannot decompose complex comparison because the right hand " "side has side effects", n.rhs->loc); // synchronise output to the start of this comparison top->sync_to(n); // skip this comparison itself top->skip_to(n.loc.end); // find any identifiers used in either LHS or RHS std::unordered_set ids = find_ids(*n.lhs); std::unordered_set rhs_ids = find_ids(*n.rhs); ids.insert(rhs_ids.begin(), rhs_ids.end()); // write a decomposed version of the comparison *top << explode(ids, n.lhs->to_string(), n.rhs->to_string(), "", *t, is_eq); } rumur-2024.05.07/murphi2murphi/src/DecomposeComplexComparisons.h000066400000000000000000000010201461637631000245240ustar00rootroot00000000000000// support for turning ==/!= between complex types into a string of comparisons // on their components #pragma once #include "Stage.h" #include #include class DecomposeComplexComparisons : public IntermediateStage { public: explicit DecomposeComplexComparisons(Stage &next_); void visit_eq(const rumur::Eq &n) final; void visit_neq(const rumur::Neq &n) final; virtual ~DecomposeComplexComparisons() = default; private: void rewrite(const rumur::EquatableBinaryExpr &n, bool is_eq); }; rumur-2024.05.07/murphi2murphi/src/ExplicitSemicolons.cc000066400000000000000000000063271461637631000230320ustar00rootroot00000000000000#include "ExplicitSemicolons.h" #include #include #include #include using namespace rumur; ExplicitSemicolons::ExplicitSemicolons(Stage &next_) : IntermediateStage(next_) {} void ExplicitSemicolons::process(const Token &t) { // if this is a message to ourselves, update our state if (t.type == Token::SUBJ && t.subject == this) { assert(!state.empty() && "message to shift state when we have no pending next state"); pending_semi = state.front(); state.pop(); return; } // if we are not waiting on a semi-colons, we can simply output this character if (!pending_semi) { assert(pending.empty()); next.process(t); return; } switch (t.type) { case Token::CHAR: // if this is white space, keep accruing pending characters if (t.character.size() == 1 && isspace(t.character.c_str()[0])) { pending.push_back(t); return; } break; // if this was a shift message to another Stage, accrue it case Token::SUBJ: pending.push_back(t); return; } // if we reached here, we know one way or another we are done accruing pending_semi = false; // the semi-colon was either explicit already if this character itself is a // semi-colon, or it was omitted otherwise if (t.type == Token::CHAR && t.character != ";") next << ";"; // flush the accrued white space and shift messages flush(); next.process(t); } // each of these need to simply passthrough to the next stage in the pipeline // and note that we then have a pending semi-colon void ExplicitSemicolons::visit_aliasrule(const AliasRule &n) { next.visit_aliasrule(n); set_pending_semi(); } void ExplicitSemicolons::visit_constdecl(const ConstDecl &n) { next.visit_constdecl(n); set_pending_semi(); } void ExplicitSemicolons::visit_function(const Function &n) { next.visit_function(n); set_pending_semi(); } void ExplicitSemicolons::visit_propertyrule(const PropertyRule &n) { next.visit_propertyrule(n); set_pending_semi(); } void ExplicitSemicolons::visit_ruleset(const Ruleset &n) { next.visit_ruleset(n); set_pending_semi(); } void ExplicitSemicolons::visit_simplerule(const SimpleRule &n) { next.dispatch(n); set_pending_semi(); } void ExplicitSemicolons::visit_startstate(const StartState &n) { next.visit_startstate(n); set_pending_semi(); } void ExplicitSemicolons::visit_typedecl(const TypeDecl &n) { next.visit_typedecl(n); set_pending_semi(); } void ExplicitSemicolons::visit_vardecl(const VarDecl &n) { next.visit_vardecl(n); set_pending_semi(); } void ExplicitSemicolons::finalise() { // apply any unconsumed updates while (!state.empty()) { pending_semi = state.front(); state.pop(); } // if we have a pending semi-colon, we know we are never going to see one now if (pending_semi) next << ";"; pending_semi = false; flush(); } void ExplicitSemicolons::flush() { for (const Token &t : pending) next.process(t); pending.clear(); } void ExplicitSemicolons::set_pending_semi() { // queue the update to take effect in the future state.push(true); // put a message in the pipeline to tell ourselves to later shift this state // into .pending_semi top->process(Token(this)); } rumur-2024.05.07/murphi2murphi/src/ExplicitSemicolons.h000066400000000000000000000023671461637631000226740ustar00rootroot00000000000000#pragma once #include "Stage.h" #include #include #include #include class ExplicitSemicolons : public IntermediateStage { private: // does the next written character need to be a semicolon? bool pending_semi = false; // buffered tokens we have not yet sent to the next stage std::vector pending; // queued updates to .pending_semi std::queue state; public: explicit ExplicitSemicolons(Stage &next_); void process(const Token &t) final; // override visitors for all nodes that can have an omitted semicolon void visit_aliasrule(const rumur::AliasRule &n) final; void visit_constdecl(const rumur::ConstDecl &n) final; void visit_function(const rumur::Function &n) final; void visit_propertyrule(const rumur::PropertyRule &n) final; void visit_ruleset(const rumur::Ruleset &n) final; void visit_simplerule(const rumur::SimpleRule &n) final; void visit_startstate(const rumur::StartState &n) final; void visit_typedecl(const rumur::TypeDecl &n) final; void visit_vardecl(const rumur::VarDecl &n) final; void finalise() final; virtual ~ExplicitSemicolons() = default; private: void flush(); // queue an update of .pending_semi = true void set_pending_semi(); }; rumur-2024.05.07/murphi2murphi/src/Pipeline.cc000066400000000000000000000015221461637631000207520ustar00rootroot00000000000000#include "Pipeline.h" #include "Stage.h" #include #include #include #include using namespace rumur; void Pipeline::add_stage(Stage &s) { // add this stage as the new head of the pipeline stages.insert(stages.begin(), &s); // inform all stages of the new head for (Stage *st : stages) st->attach(s); } void Pipeline::process(const Node &n) { assert(!stages.empty() && "starting processing with an empty pipeline"); // pass this node to the head of the pipeline stages[0]->dispatch(n); } void Pipeline::finalise() { assert(!stages.empty() && "finalising an empty pipeline"); // write the remaining contents of the input file stages[0]->sync_to(position(nullptr, UINT_MAX, UINT_MAX)); // notify all stages that we are done for (Stage *s : stages) s->finalise(); } rumur-2024.05.07/murphi2murphi/src/Pipeline.h000066400000000000000000000010441461637631000206130ustar00rootroot00000000000000#pragma once #include "Stage.h" #include #include #include #include #include class Pipeline { private: std::vector stages; std::vector> managed; public: void add_stage(Stage &s); template void make_stage() { assert(!stages.empty() && "make_stage() on an empty pipeline"); auto s = std::make_shared(*stages[0]); managed.push_back(s); add_stage(*s); } void process(const rumur::Node &n); void finalise(); }; rumur-2024.05.07/murphi2murphi/src/Printer.cc000066400000000000000000000334771461637631000206460ustar00rootroot00000000000000#include "Printer.h" #include #include #include #include #include #include using namespace rumur; Printer::Printer(std::istream &in_, std::ostream &out_) : in(in_), out(out_) {} void Printer::visit_add(const Add &n) { visit_bexpr(n); } void Printer::visit_aliasdecl(const AliasDecl &n) { top->sync_to(n); top->dispatch(*n.value); top->sync_to(n.loc.end); } void Printer::visit_aliasrule(const AliasRule &n) { top->sync_to(n); if (!n.aliases.empty()) { top->sync_to(*n.aliases[0]); for (auto &a : n.aliases) { top->sync_to(*a); top->dispatch(*a); } } if (!n.rules.empty()) { top->sync_to(*n.rules[0]); for (auto &r : n.rules) { top->sync_to(*r); top->dispatch(*r); } } top->sync_to(n.loc.end); } void Printer::visit_aliasstmt(const AliasStmt &n) { top->sync_to(n); if (!n.aliases.empty()) { top->sync_to(*n.aliases[0]); for (auto &a : n.aliases) { top->sync_to(*a); top->dispatch(*a); } } if (!n.body.empty()) { top->sync_to(*n.body[0]); for (auto &s : n.body) { top->sync_to(*s); top->dispatch(*s); } } top->sync_to(n.loc.end); } void Printer::visit_and(const And &n) { visit_bexpr(n); } void Printer::visit_array(const Array &n) { top->sync_to(n); top->sync_to(*n.index_type); top->dispatch(*n.index_type); top->sync_to(*n.element_type); top->dispatch(*n.element_type); top->sync_to(n.loc.end); } void Printer::visit_band(const Band &n) { visit_bexpr(n); } void Printer::visit_bnot(const Bnot &n) { visit_uexpr(n); } void Printer::visit_bor(const Bor &n) { visit_bexpr(n); } void Printer::visit_assignment(const Assignment &n) { top->sync_to(n); top->dispatch(*n.lhs); top->sync_to(*n.rhs); top->dispatch(*n.rhs); top->sync_to(n.loc.end); } void Printer::visit_clear(const Clear &n) { top->sync_to(n); top->sync_to(*n.rhs); top->dispatch(*n.rhs); top->sync_to(n.loc.end); } void Printer::visit_constdecl(const ConstDecl &n) { top->sync_to(n); top->sync_to(*n.value); top->dispatch(*n.value); top->sync_to(n.loc.end); } void Printer::visit_div(const Div &n) { visit_bexpr(n); } void Printer::visit_element(const Element &n) { top->sync_to(n); top->sync_to(*n.array); top->dispatch(*n.array); top->sync_to(*n.index); top->dispatch(*n.index); top->sync_to(n.loc.end); } void Printer::visit_enum(const Enum &n) { top->sync_to(n); for (const std::pair &m : n.members) { top->sync_to(m.second.begin); top->sync_to(m.second.end); } top->sync_to(n.loc.end); } void Printer::visit_eq(const Eq &n) { visit_bexpr(n); } void Printer::visit_errorstmt(const ErrorStmt &n) { top->sync_to(n); top->sync_to(n.loc.end); } void Printer::visit_exists(const Exists &n) { top->sync_to(n); top->sync_to(n.quantifier); top->dispatch(n.quantifier); top->sync_to(*n.expr); top->dispatch(*n.expr); top->sync_to(n.loc.end); } void Printer::visit_exprid(const ExprID &n) { top->sync_to(n); top->sync_to(n.loc.end); } void Printer::visit_field(const Field &n) { top->sync_to(n); top->sync_to(*n.record); top->dispatch(*n.record); top->sync_to(n.loc.end); } void Printer::visit_for(const For &n) { top->sync_to(n); top->sync_to(n.quantifier); top->dispatch(n.quantifier); if (!n.body.empty()) { top->sync_to(*n.body[0]); for (auto &s : n.body) { top->sync_to(*s); top->dispatch(*s); } } top->sync_to(n.loc.end); } void Printer::visit_forall(const Forall &n) { top->sync_to(n); top->sync_to(n.quantifier); top->dispatch(n.quantifier); top->sync_to(*n.expr); top->dispatch(*n.expr); top->sync_to(n.loc.end); } void Printer::visit_function(const Function &n) { top->sync_to(n); if (!n.parameters.empty()) { top->sync_to(*n.parameters[0]); for (auto &p : n.parameters) { top->sync_to(*p); top->dispatch(*p); } } if (n.return_type != nullptr) { top->sync_to(*n.return_type); top->dispatch(*n.return_type); } if (!n.decls.empty()) { top->sync_to(*n.decls[0]); for (auto &d : n.decls) { top->sync_to(*d); top->dispatch(*d); } } if (!n.body.empty()) { top->sync_to(*n.body[0]); for (auto &s : n.body) { top->sync_to(*s); top->dispatch(*s); } } top->sync_to(n.loc.end); } void Printer::visit_functioncall(const FunctionCall &n) { top->sync_to(n); for (auto &a : n.arguments) { top->sync_to(*a); top->dispatch(*a); } top->sync_to(n.loc.end); } void Printer::visit_geq(const Geq &n) { visit_bexpr(n); } void Printer::visit_gt(const Gt &n) { visit_bexpr(n); } void Printer::visit_if(const If &n) { top->sync_to(n); for (const IfClause &c : n.clauses) { top->sync_to(c); top->dispatch(c); } top->sync_to(n.loc.end); } void Printer::visit_ifclause(const IfClause &n) { top->sync_to(n); if (n.condition != nullptr) { top->sync_to(*n.condition); top->dispatch(*n.condition); } if (!n.body.empty()) { top->sync_to(*n.body[0]); for (auto &s : n.body) { top->sync_to(*s); top->dispatch(*s); } } top->sync_to(n.loc.end); } void Printer::visit_implication(const Implication &n) { visit_bexpr(n); } void Printer::visit_isundefined(const IsUndefined &n) { visit_uexpr(n); } void Printer::visit_leq(const Leq &n) { visit_bexpr(n); } void Printer::visit_lsh(const Lsh &n) { visit_bexpr(n); } void Printer::visit_lt(const Lt &n) { visit_bexpr(n); } void Printer::visit_mod(const Mod &n) { visit_bexpr(n); } void Printer::visit_model(const Model &n) { if (!n.children.empty()) { top->sync_to(*n.children[0]); for (const Ptr &c : n.children) { top->sync_to(*c); top->dispatch(*c); } } top->sync_to(n.loc.end); } void Printer::visit_mul(const Mul &n) { visit_bexpr(n); } void Printer::visit_negative(const Negative &n) { visit_uexpr(n); } void Printer::visit_neq(const Neq &n) { visit_bexpr(n); } void Printer::visit_not(const Not &n) { visit_uexpr(n); } void Printer::visit_number(const Number &n) { top->sync_to(n); top->sync_to(n.loc.end); } void Printer::visit_or(const Or &n) { visit_bexpr(n); } void Printer::visit_procedurecall(const ProcedureCall &n) { top->sync_to(n); top->sync_to(n.call); top->dispatch(n.call); top->sync_to(n.loc.end); } void Printer::visit_property(const Property &n) { top->sync_to(n); top->sync_to(*n.expr); top->dispatch(*n.expr); top->sync_to(n.loc.end); } void Printer::visit_propertyrule(const PropertyRule &n) { top->sync_to(n); if (!n.quantifiers.empty()) { top->sync_to(n.quantifiers[0]); for (const Quantifier &q : n.quantifiers) { top->sync_to(q); top->dispatch(q); } } top->sync_to(n.property); top->dispatch(n.property); top->sync_to(n.loc.end); } void Printer::visit_propertystmt(const PropertyStmt &n) { top->sync_to(n); top->sync_to(n.property); top->dispatch(n.property); top->sync_to(n.loc.end); } void Printer::visit_put(const Put &n) { top->sync_to(n); if (n.expr != nullptr) { top->sync_to(*n.expr); top->dispatch(*n.expr); } top->sync_to(n.loc.end); } void Printer::visit_quantifier(const Quantifier &n) { top->sync_to(n); if (n.type != nullptr) { top->sync_to(*n.type); top->dispatch(*n.type); } if (n.from != nullptr) { top->sync_to(*n.from); top->dispatch(*n.from); } if (n.to != nullptr) { top->sync_to(*n.to); top->dispatch(*n.to); } if (n.step != nullptr) { top->sync_to(*n.step); top->dispatch(*n.step); } top->sync_to(n.loc.end); } void Printer::visit_range(const Range &n) { top->sync_to(n); top->sync_to(*n.min); top->dispatch(*n.min); top->sync_to(*n.max); top->dispatch(*n.max); top->sync_to(n.loc.end); } void Printer::visit_record(const Record &n) { top->sync_to(n); for (auto &f : n.fields) { top->sync_to(*f); top->dispatch(*f); } top->sync_to(n.loc.end); } void Printer::visit_return(const Return &n) { top->sync_to(n); if (n.expr != nullptr) { top->sync_to(*n.expr); top->dispatch(*n.expr); } top->sync_to(n.loc.end); } void Printer::visit_rsh(const Rsh &n) { visit_bexpr(n); } void Printer::visit_ruleset(const Ruleset &n) { top->sync_to(n); if (!n.quantifiers.empty()) { top->sync_to(n.quantifiers[0]); for (const Quantifier &q : n.quantifiers) { top->sync_to(q); top->dispatch(q); } } if (!n.rules.empty()) { top->sync_to(*n.rules[0]); for (auto &r : n.rules) { top->sync_to(*r); top->dispatch(*r); } } top->sync_to(n.loc.end); } void Printer::visit_scalarset(const Scalarset &n) { top->sync_to(n); top->sync_to(*n.bound); top->dispatch(*n.bound); top->sync_to(n.loc.end); } void Printer::visit_simplerule(const SimpleRule &n) { top->sync_to(n); if (!n.quantifiers.empty()) { top->sync_to(n.quantifiers[0]); for (const Quantifier &q : n.quantifiers) { top->sync_to(q); top->dispatch(q); } } if (n.guard != nullptr) { top->sync_to(*n.guard); top->dispatch(*n.guard); } if (!n.decls.empty()) { top->sync_to(*n.decls[0]); for (auto &d : n.decls) { top->sync_to(*d); top->dispatch(*d); } } if (!n.body.empty()) { top->sync_to(*n.body[0]); for (auto &s : n.body) { top->sync_to(*s); top->dispatch(*s); } } top->sync_to(n.loc.end); } void Printer::visit_startstate(const StartState &n) { top->sync_to(n); if (!n.quantifiers.empty()) { top->sync_to(n.quantifiers[0]); for (const Quantifier &q : n.quantifiers) { top->sync_to(q); top->dispatch(q); } } if (!n.decls.empty()) { top->sync_to(*n.decls[0]); for (auto &d : n.decls) { top->sync_to(*d); top->dispatch(*d); } } if (!n.body.empty()) { top->sync_to(*n.body[0]); for (auto &s : n.body) { top->sync_to(*s); top->dispatch(*s); } } top->sync_to(n.loc.end); } void Printer::visit_sub(const Sub &n) { visit_bexpr(n); } void Printer::visit_switch(const Switch &n) { top->sync_to(n); top->sync_to(*n.expr); top->dispatch(*n.expr); if (!n.cases.empty()) { top->sync_to(n.cases[0]); for (const SwitchCase &c : n.cases) { top->sync_to(c); top->dispatch(c); } } top->sync_to(n.loc.end); } void Printer::visit_switchcase(const SwitchCase &n) { top->sync_to(n); if (!n.matches.empty()) { top->sync_to(*n.matches[0]); for (auto &m : n.matches) { top->sync_to(*m); top->dispatch(*m); } } if (!n.body.empty()) { top->sync_to(*n.body[0]); for (auto &s : n.body) { top->sync_to(*s); top->dispatch(*s); } } top->sync_to(n.loc.end); } void Printer::visit_ternary(const Ternary &n) { top->sync_to(n); top->sync_to(*n.cond); top->dispatch(*n.cond); top->sync_to(*n.lhs); top->dispatch(*n.lhs); top->sync_to(*n.rhs); top->dispatch(*n.rhs); top->sync_to(n.loc.end); } void Printer::visit_typedecl(const TypeDecl &n) { top->sync_to(n); top->sync_to(*n.value); top->dispatch(*n.value); top->sync_to(n.loc.end); } void Printer::visit_typeexprid(const TypeExprID &n) { top->sync_to(n); top->sync_to(n.loc.end); } void Printer::visit_undefine(const Undefine &n) { top->sync_to(n); top->sync_to(*n.rhs); top->dispatch(*n.rhs); top->sync_to(n.loc.end); } void Printer::visit_vardecl(const VarDecl &n) { top->sync_to(n); top->sync_to(*n.type); top->dispatch(*n.type); top->sync_to(n.loc.end); } void Printer::visit_while(const While &n) { top->sync_to(n); top->sync_to(*n.condition); top->dispatch(*n.condition); if (!n.body.empty()) { top->sync_to(*n.body[0]); for (auto &s : n.body) { top->sync_to(*s); top->dispatch(*s); } } top->sync_to(n.loc.end); } void Printer::visit_xor(const Xor &n) { visit_bexpr(n); } void Printer::process(const Token &t) { assert(t.type != Token::SUBJ && "an IntermediateStage did not put a shift " "message in the processing pipe that they did not consume"); out << t.character; } void Printer::sync_to(const Node &n) { sync_to(n.loc.begin); } void Printer::sync_to(const position &pos) { // the type of position.line and position.column changes across Bison // releases, so avoid some -Wsign-compare warnings by casting them in advance auto pos_line = static_cast(pos.line); auto pos_col = static_cast(pos.column); // buffer up to the given position std::ostringstream buffer; while (in.good() && (line < pos_line || (line == pos_line && column < pos_col))) { int c = in.get(); if (c == EOF) break; buffer << static_cast(c); if (c == '\n') { line++; column = 1; } else { column++; } } // if we actually advanced in the input, output the data if (buffer.str() != "") *top << buffer.str(); } void Printer::skip_to(const Node &n) { skip_to(n.loc.begin); } void Printer::skip_to(const position &pos) { // the type of position.line and position.column changes across Bison // releases, so avoid some -Wsign-compare warnings by casting them in advance auto pos_line = static_cast(pos.line); auto pos_col = static_cast(pos.column); // if this is ahead of our current position, seek forwards while dropping // characters while (in.good() && (line < pos_line || (line == pos_line && column < pos_col))) { int c = in.get(); if (c == EOF) break; if (c == '\n') { line++; column = 1; } else { column++; } } } void Printer::finalise() { out.flush(); } void Printer::visit_bexpr(const BinaryExpr &n) { top->sync_to(n); top->sync_to(*n.lhs); top->dispatch(*n.lhs); top->sync_to(*n.rhs); top->dispatch(*n.rhs); top->sync_to(n.loc.end); } void Printer::visit_uexpr(const UnaryExpr &n) { top->sync_to(n); top->sync_to(*n.rhs); top->dispatch(*n.rhs); top->sync_to(n.loc.end); } rumur-2024.05.07/murphi2murphi/src/Printer.h000066400000000000000000000077541461637631000205070ustar00rootroot00000000000000#pragma once #include "Stage.h" #include #include #include class Printer : public Stage { private: std::istream ∈ std::ostream &out; // current position within the input file unsigned long line = 1; unsigned long column = 1; public: Printer(std::istream &in_, std::ostream &out_); void visit_add(const rumur::Add &n) final; void visit_aliasdecl(const rumur::AliasDecl &n) final; void visit_aliasrule(const rumur::AliasRule &n) final; void visit_aliasstmt(const rumur::AliasStmt &n) final; void visit_and(const rumur::And &n) final; void visit_array(const rumur::Array &n) final; void visit_assignment(const rumur::Assignment &n) final; void visit_band(const rumur::Band &n) final; void visit_bnot(const rumur::Bnot &n) final; void visit_bor(const rumur::Bor &n) final; void visit_clear(const rumur::Clear &n) final; void visit_constdecl(const rumur::ConstDecl &n) final; void visit_div(const rumur::Div &n) final; void visit_element(const rumur::Element &n) final; void visit_enum(const rumur::Enum &n) final; void visit_eq(const rumur::Eq &n) final; void visit_errorstmt(const rumur::ErrorStmt &n) final; void visit_exists(const rumur::Exists &n) final; void visit_exprid(const rumur::ExprID &n) final; void visit_field(const rumur::Field &n) final; void visit_for(const rumur::For &n) final; void visit_forall(const rumur::Forall &n) final; void visit_function(const rumur::Function &n) final; void visit_functioncall(const rumur::FunctionCall &n) final; void visit_geq(const rumur::Geq &n) final; void visit_gt(const rumur::Gt &n) final; void visit_if(const rumur::If &n) final; void visit_ifclause(const rumur::IfClause &n) final; void visit_implication(const rumur::Implication &n) final; void visit_isundefined(const rumur::IsUndefined &n) final; void visit_leq(const rumur::Leq &n) final; void visit_lsh(const rumur::Lsh &n) final; void visit_lt(const rumur::Lt &n) final; void visit_mod(const rumur::Mod &n) final; void visit_model(const rumur::Model &n) final; void visit_mul(const rumur::Mul &n) final; void visit_negative(const rumur::Negative &n) final; void visit_neq(const rumur::Neq &n) final; void visit_not(const rumur::Not &n) final; void visit_number(const rumur::Number &n) final; void visit_or(const rumur::Or &n) final; void visit_procedurecall(const rumur::ProcedureCall &n) final; void visit_property(const rumur::Property &n) final; void visit_propertyrule(const rumur::PropertyRule &n) final; void visit_propertystmt(const rumur::PropertyStmt &n) final; void visit_put(const rumur::Put &n) final; void visit_quantifier(const rumur::Quantifier &n) final; void visit_range(const rumur::Range &n) final; void visit_record(const rumur::Record &n) final; void visit_return(const rumur::Return &n) final; void visit_rsh(const rumur::Rsh &n) final; void visit_ruleset(const rumur::Ruleset &n) final; void visit_scalarset(const rumur::Scalarset &n) final; void visit_simplerule(const rumur::SimpleRule &n) final; void visit_startstate(const rumur::StartState &n) final; void visit_sub(const rumur::Sub &n) final; void visit_switch(const rumur::Switch &n) final; void visit_switchcase(const rumur::SwitchCase &n) final; void visit_ternary(const rumur::Ternary &n) final; void visit_typedecl(const rumur::TypeDecl &n) final; void visit_typeexprid(const rumur::TypeExprID &n) final; void visit_undefine(const rumur::Undefine &n) final; void visit_vardecl(const rumur::VarDecl &n) final; void visit_while(const rumur::While &n) final; void visit_xor(const rumur::Xor &n) final; void process(const Token &t) final; void sync_to(const rumur::Node &n) final; void sync_to(const rumur::position &pos) final; void skip_to(const rumur::Node &n) final; void skip_to(const rumur::position &pos) final; void finalise() final; virtual ~Printer() = default; private: void visit_bexpr(const rumur::BinaryExpr &n); void visit_uexpr(const rumur::UnaryExpr &n); }; rumur-2024.05.07/murphi2murphi/src/RemoveLiveness.cc000066400000000000000000000031741461637631000221600ustar00rootroot00000000000000#include "RemoveLiveness.h" #include "Stage.h" #include #include #include #include using namespace rumur; RemoveLiveness::RemoveLiveness(Stage &next_) : IntermediateStage(next_) {} void RemoveLiveness::process(const Token &t) { if (t.type == Token::SUBJ) { // if this is for us, update our state if (t.subject == this) { swallow_semi = state.front(); state.pop(); } else { // ignore any other shift messages next.process(t); } return; } // if we are not hunting a to-be-deleted semi-colon, we are done if (!swallow_semi) { next.process(t); return; } // if this is white space, remain on the hunt for a semi-colon if (t.character.size() == 1 && isspace(t.character.c_str()[0])) { next.process(t); return; } // if we reached here, we know one way or another we are done watching for // semi-colons swallow_semi = false; // if this is a semi-colon, suppress it if (t.character == ";") return; // otherwise, let it go through next.process(t); } void RemoveLiveness::visit_propertyrule(const PropertyRule &n) { // if this is not a liveness property, we can let it pass through if (n.property.category != Property::LIVENESS) { next.visit_propertyrule(n); return; } // seek to the start of this property top->sync_to(n); // omit the property itself top->skip_to(n.loc.end); // note that we may now encounter a semi-colon that needs to be deleted state.push(true); // push a shift state message into the pipe to tell ourselves to later consume // this update top->process(Token(this)); } rumur-2024.05.07/murphi2murphi/src/RemoveLiveness.h000066400000000000000000000020111461637631000220070ustar00rootroot00000000000000// a stage to delete liveness properties from the model #pragma once #include "Stage.h" #include #include #include class RemoveLiveness : public IntermediateStage { private: // does the next seen semi-colon need to be deleted? bool swallow_semi = false; // queued updates to .swallow_semi std::queue state; public: explicit RemoveLiveness(Stage &next_); // interpose on the output, so we have a chance to suppress any spurious // semi-colons following a deleted liveness property void process(const Token &t) final; // Structurally a liveness property can be contained within a PropertyRule or // a PropertyStmt. However, liveness properties are only legally allowed to // exist as top-level claims. Hence we know that a validated model will never // have any liveness properties within PropertyStmts and so we only need to // override visit_propertyrule() and not also visit_propertystmt(). void visit_propertyrule(const rumur::PropertyRule &n) final; }; rumur-2024.05.07/murphi2murphi/src/Stage.cc000066400000000000000000000171541461637631000202600ustar00rootroot00000000000000#include "Stage.h" #include #include #include #include #include using namespace rumur; Stage &Stage::operator<<(const std::string &s) { // We expect the input argument to contain a sequence of UTF-8 characters that // we want to pass to process() character-by-character. However iterating // through a std::string yields 8-bit chars, not UTF-8 characters. So we // commit a minor sin and roll our own UTF-8 character-by-character iteration. for (size_t i = 0; i < s.size();) { // clamp the size of the current character to at most this to avoid // overrunning the string even in the case of malformed UTF-8 size_t length = s.size() - i; // From the UTF-8 RFC (3629): // // Char. number range | UTF-8 octet sequence // (hexadecimal) | (binary) // --------------------+--------------------------------------------- // 0000 0000-0000 007F | 0xxxxxxx // 0000 0080-0000 07FF | 110xxxxx 10xxxxxx // 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx // 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx // // So we can determine the length of the current character by the bits set // in the first byte. uint8_t leader = static_cast(s[i]); if ((leader >> 7) == 0) { length = length < 1 ? length : 1; } else if ((leader >> 5) == 6) { length = length < 2 ? length : 2; } else if ((leader >> 4) == 14) { length = length < 3 ? length : 3; } else if ((leader >> 3) == 30) { length = length < 4 ? length : 4; } else { // Malformed. Just treat it as a 1-byte character. length = 1; } // write out this character Token t(s.substr(i, length)); process(t); // move to the next character assert(i + length <= s.size()); i += length; } return *this; } void Stage::attach(Stage &top_) { top = &top_; } IntermediateStage::IntermediateStage(Stage &next_) : next(next_) {} // by default, pass through to the next stage void IntermediateStage::visit_add(const Add &n) { next.visit_add(n); } void IntermediateStage::visit_aliasdecl(const AliasDecl &n) { next.visit_aliasdecl(n); } void IntermediateStage::visit_aliasrule(const AliasRule &n) { next.visit_aliasrule(n); } void IntermediateStage::visit_aliasstmt(const AliasStmt &n) { next.visit_aliasstmt(n); } void IntermediateStage::visit_and(const And &n) { next.visit_and(n); } void IntermediateStage::visit_array(const Array &n) { next.visit_array(n); } void IntermediateStage::visit_assignment(const Assignment &n) { next.visit_assignment(n); } void IntermediateStage::visit_band(const Band &n) { next.visit_band(n); } void IntermediateStage::visit_bnot(const Bnot &n) { next.visit_bnot(n); } void IntermediateStage::visit_bor(const Bor &n) { next.visit_bor(n); } void IntermediateStage::visit_clear(const Clear &n) { next.visit_clear(n); } void IntermediateStage::visit_constdecl(const ConstDecl &n) { next.visit_constdecl(n); } void IntermediateStage::visit_div(const Div &n) { next.visit_div(n); } void IntermediateStage::visit_element(const Element &n) { next.visit_element(n); } void IntermediateStage::visit_enum(const Enum &n) { next.visit_enum(n); } void IntermediateStage::visit_eq(const Eq &n) { next.visit_eq(n); } void IntermediateStage::visit_errorstmt(const ErrorStmt &n) { next.visit_errorstmt(n); } void IntermediateStage::visit_exists(const Exists &n) { next.visit_exists(n); } void IntermediateStage::visit_exprid(const ExprID &n) { next.visit_exprid(n); } void IntermediateStage::visit_field(const Field &n) { next.visit_field(n); } void IntermediateStage::visit_for(const For &n) { next.visit_for(n); } void IntermediateStage::visit_forall(const Forall &n) { next.visit_forall(n); } void IntermediateStage::visit_function(const Function &n) { next.visit_function(n); } void IntermediateStage::visit_functioncall(const FunctionCall &n) { next.visit_functioncall(n); } void IntermediateStage::visit_geq(const Geq &n) { next.visit_geq(n); } void IntermediateStage::visit_gt(const Gt &n) { next.visit_gt(n); } void IntermediateStage::visit_if(const If &n) { next.visit_if(n); } void IntermediateStage::visit_ifclause(const IfClause &n) { next.visit_ifclause(n); } void IntermediateStage::visit_implication(const Implication &n) { next.visit_implication(n); } void IntermediateStage::visit_isundefined(const IsUndefined &n) { next.visit_isundefined(n); } void IntermediateStage::visit_leq(const Leq &n) { next.visit_leq(n); } void IntermediateStage::visit_lsh(const Lsh &n) { next.visit_lsh(n); } void IntermediateStage::visit_lt(const Lt &n) { next.visit_lt(n); } void IntermediateStage::visit_mod(const Mod &n) { next.visit_mod(n); } void IntermediateStage::visit_model(const Model &n) { next.visit_model(n); } void IntermediateStage::visit_mul(const Mul &n) { next.visit_mul(n); } void IntermediateStage::visit_negative(const Negative &n) { next.visit_negative(n); } void IntermediateStage::visit_neq(const Neq &n) { next.visit_neq(n); } void IntermediateStage::visit_not(const Not &n) { next.visit_not(n); } void IntermediateStage::visit_number(const Number &n) { next.visit_number(n); } void IntermediateStage::visit_or(const Or &n) { next.visit_or(n); } void IntermediateStage::visit_procedurecall(const ProcedureCall &n) { next.visit_procedurecall(n); } void IntermediateStage::visit_property(const Property &n) { next.visit_property(n); } void IntermediateStage::visit_propertyrule(const PropertyRule &n) { next.visit_propertyrule(n); } void IntermediateStage::visit_propertystmt(const PropertyStmt &n) { next.visit_propertystmt(n); } void IntermediateStage::visit_put(const Put &n) { next.visit_put(n); } void IntermediateStage::visit_quantifier(const Quantifier &n) { next.visit_quantifier(n); } void IntermediateStage::visit_range(const Range &n) { next.visit_range(n); } void IntermediateStage::visit_record(const Record &n) { next.visit_record(n); } void IntermediateStage::visit_return(const Return &n) { next.visit_return(n); } void IntermediateStage::visit_rsh(const Rsh &n) { next.visit_rsh(n); } void IntermediateStage::visit_ruleset(const Ruleset &n) { next.visit_ruleset(n); } void IntermediateStage::visit_scalarset(const Scalarset &n) { next.visit_scalarset(n); } void IntermediateStage::visit_simplerule(const SimpleRule &n) { next.visit_simplerule(n); } void IntermediateStage::visit_startstate(const StartState &n) { next.visit_startstate(n); } void IntermediateStage::visit_sub(const Sub &n) { next.visit_sub(n); } void IntermediateStage::visit_switch(const Switch &n) { next.visit_switch(n); } void IntermediateStage::visit_switchcase(const SwitchCase &n) { next.visit_switchcase(n); } void IntermediateStage::visit_ternary(const Ternary &n) { next.visit_ternary(n); } void IntermediateStage::visit_typedecl(const TypeDecl &n) { next.visit_typedecl(n); } void IntermediateStage::visit_typeexprid(const TypeExprID &n) { next.visit_typeexprid(n); } void IntermediateStage::visit_undefine(const Undefine &n) { next.visit_undefine(n); } void IntermediateStage::visit_vardecl(const VarDecl &n) { next.visit_vardecl(n); } void IntermediateStage::visit_while(const While &n) { next.visit_while(n); } void IntermediateStage::visit_xor(const Xor &n) { next.visit_xor(n); } void IntermediateStage::process(const Token &t) { next.process(t); } void IntermediateStage::sync_to(const Node &n) { next.sync_to(n); } void IntermediateStage::sync_to(const position &pos) { next.sync_to(pos); } void IntermediateStage::skip_to(const Node &n) { next.skip_to(n); } void IntermediateStage::skip_to(const position &pos) { next.skip_to(pos); } IntermediateStage::~IntermediateStage() {} rumur-2024.05.07/murphi2murphi/src/Stage.h000066400000000000000000000125371461637631000201220ustar00rootroot00000000000000#pragma once #include #include #include class Stage; // a message sent to a stage struct Token { enum { CHAR, // write the given character to the output SUBJ, // update to your next internal pending state } type; // only one of these is ever active, but we avoid using a union so we still // get default ctors etc std::string character; const Stage *subject = nullptr; explicit Token(const std::string &c) : type(CHAR), character(c) {} explicit Token(const Stage *s) : type(SUBJ), subject(s) {} }; class Stage : public rumur::ConstBaseTraversal { protected: Stage *top = nullptr; public: // pass one or more characters to process() Stage &operator<<(const std::string &s); // make this stage aware it is part of the given pipeline void attach(Stage &top_); // process a token, either a character to write to the output stream or a // notification to shift state virtual void process(const Token &t) = 0; // write characters from the input file into the output file, up to the given // position in the input virtual void sync_to(const rumur::Node &n) = 0; virtual void sync_to(const rumur::position &pos) = 0; // seek over characters in the input file up to the given position in the // input virtual void skip_to(const rumur::Node &n) = 0; virtual void skip_to(const rumur::position &pos) = 0; // perform any pending actions, assuming that processing is done virtual void finalise(){}; virtual ~Stage() = default; }; class IntermediateStage : public Stage { protected: Stage &next; public: explicit IntermediateStage(Stage &next_); void visit_add(const rumur::Add &n) override; void visit_aliasdecl(const rumur::AliasDecl &n) override; void visit_aliasrule(const rumur::AliasRule &n) override; void visit_aliasstmt(const rumur::AliasStmt &n) override; void visit_and(const rumur::And &n) override; void visit_array(const rumur::Array &n) override; void visit_assignment(const rumur::Assignment &n) override; void visit_band(const rumur::Band &n) override; void visit_bnot(const rumur::Bnot &n) override; void visit_bor(const rumur::Bor &n) override; void visit_clear(const rumur::Clear &n) override; void visit_constdecl(const rumur::ConstDecl &n) override; void visit_div(const rumur::Div &n) override; void visit_element(const rumur::Element &n) override; void visit_enum(const rumur::Enum &n) override; void visit_eq(const rumur::Eq &n) override; void visit_errorstmt(const rumur::ErrorStmt &n) override; void visit_exists(const rumur::Exists &n) override; void visit_exprid(const rumur::ExprID &n) override; void visit_field(const rumur::Field &n) override; void visit_for(const rumur::For &n) override; void visit_forall(const rumur::Forall &n) override; void visit_function(const rumur::Function &n) override; void visit_functioncall(const rumur::FunctionCall &n) override; void visit_geq(const rumur::Geq &n) override; void visit_gt(const rumur::Gt &n) override; void visit_if(const rumur::If &n) override; void visit_ifclause(const rumur::IfClause &n) override; void visit_implication(const rumur::Implication &n) override; void visit_isundefined(const rumur::IsUndefined &n) override; void visit_leq(const rumur::Leq &n) override; void visit_lsh(const rumur::Lsh &n) override; void visit_lt(const rumur::Lt &n) override; void visit_mod(const rumur::Mod &n) override; void visit_model(const rumur::Model &n) override; void visit_mul(const rumur::Mul &n) override; void visit_negative(const rumur::Negative &n) override; void visit_neq(const rumur::Neq &n) override; void visit_not(const rumur::Not &n) override; void visit_number(const rumur::Number &n) override; void visit_or(const rumur::Or &n) override; void visit_procedurecall(const rumur::ProcedureCall &n) override; void visit_property(const rumur::Property &n) override; void visit_propertyrule(const rumur::PropertyRule &n) override; void visit_propertystmt(const rumur::PropertyStmt &n) override; void visit_put(const rumur::Put &n) override; void visit_quantifier(const rumur::Quantifier &n) override; void visit_range(const rumur::Range &n) override; void visit_record(const rumur::Record &n) override; void visit_return(const rumur::Return &n) override; void visit_rsh(const rumur::Rsh &n) override; void visit_ruleset(const rumur::Ruleset &n) override; void visit_scalarset(const rumur::Scalarset &n) override; void visit_simplerule(const rumur::SimpleRule &n) override; void visit_startstate(const rumur::StartState &n) override; void visit_sub(const rumur::Sub &n) override; void visit_switch(const rumur::Switch &n) override; void visit_switchcase(const rumur::SwitchCase &n) override; void visit_ternary(const rumur::Ternary &n) override; void visit_typedecl(const rumur::TypeDecl &n) override; void visit_typeexprid(const rumur::TypeExprID &n) override; void visit_undefine(const rumur::Undefine &n) override; void visit_vardecl(const rumur::VarDecl &n) override; void visit_while(const rumur::While &n) override; void visit_xor(const rumur::Xor &n) override; void process(const Token &t) override; void sync_to(const rumur::Node &n) override; void sync_to(const rumur::position &pos) override; void skip_to(const rumur::Node &n) override; void skip_to(const rumur::position &pos) override; virtual ~IntermediateStage() = 0; }; rumur-2024.05.07/murphi2murphi/src/SwitchToIf.cc000066400000000000000000000061131461637631000212310ustar00rootroot00000000000000#include "SwitchToIf.h" #include "Stage.h" #include #include #include #include #include using namespace rumur; SwitchToIf::SwitchToIf(Stage &next_) : IntermediateStage(next_) {} void SwitchToIf::process(const Token &t) { // ignore any shift messages if (t.type == Token::SUBJ) { next.process(t); return; } // if we already understand indentation, we can just pass through if (learned_indentation) { next.process(t); return; } // if this character is a newline, assume we have an opportunity to learn // indentation coming up if (t.character == "\n") { last_newline = true; indentation = ""; next.process(t); return; } // if we have not just seen a newline, assume we are not currently writing // indentation if (!last_newline) { next.process(t); return; } // if this is white space, continue accruing if (t.character.size() == 1 && isspace(t.character[0])) { indentation += t.character; // otherwise, if we have found a non-zero offset, we know the indentation } else if (indentation != "") { learned_indentation = true; // otherwise, we are looking at a non-indented line } else { last_newline = false; } next.process(t); } void SwitchToIf::visit_switch(const Switch &n) { // we need to emit the switched-on expression multiple times, so we require it // to be side-effect free if (!n.expr->is_pure()) throw Error("impure expression in switch statement expression prevents " "transforming this into an if-then-else", n.loc); // write everything up to the start of this expression top->sync_to(n); // handle an edge case where the switch has no cases and should be omitted // entirely if (n.cases.empty()) { top->skip_to(n.loc.end); return; } // see if we can figure out what level of indentation this switch statement is // at assert(n.loc.begin.column > 0); size_t spaces = static_cast(n.loc.begin.column) - 1; std::string indent; if (learned_indentation && spaces % indentation.size() == 0) { for (size_t i = 0; i * indentation.size() < spaces; i++) indent += indentation; } std::string sep; for (const SwitchCase &c : n.cases) { // seek up to this case top->skip_to(c); if (c.matches.empty()) { *top << sep << "e\n"; } else { *top << sep << "if "; std::string sep2; bool brackets = c.matches.size() > 1; for (const Ptr &m : c.matches) { *top << sep2 << (brackets ? "(" : "") << n.expr->to_string() << " = "; top->skip_to(*m); top->dispatch(*m); *top << (brackets ? ")" : ""); sep2 = " | "; } *top << " then\n"; } if (!c.body.empty()) { top->skip_to(*c.body[0]); if (learned_indentation) *top << indent << indentation; } for (const Ptr &s : c.body) top->dispatch(*s); if (!c.body.empty()) *top << ";"; sep = "\n" + indent + "els"; } *top << "\n" << indent << "endif"; top->skip_to(n.loc.end); } rumur-2024.05.07/murphi2murphi/src/SwitchToIf.h000066400000000000000000000012451461637631000210740ustar00rootroot00000000000000#pragma once #include "Stage.h" #include #include #include // a stage for turning switch statements into if statements class SwitchToIf : public IntermediateStage { private: // have we figured out what character(s) are used for indentation? bool learned_indentation = false; // was the last relevant character we saw a newline? bool last_newline = false; // the character(s) we believe to represent one level of indentation std::string indentation; public: explicit SwitchToIf(Stage &next_); void process(const Token &t) final; void visit_switch(const rumur::Switch &n) final; virtual ~SwitchToIf() = default; }; rumur-2024.05.07/murphi2murphi/src/ToAscii.cc000066400000000000000000000050151461637631000205410ustar00rootroot00000000000000#include "ToAscii.h" #include "Stage.h" #include #include #include ToAscii::ToAscii(Stage &next_) : IntermediateStage(next_) {} void ToAscii::process(const Token &t) { // ignore any shift messages if (t.type == Token::SUBJ) { next.process(t); return; } std::string s = t.character; if (pending_space) { if (s.size() != 1 || !isspace(s[0])) next << " "; pending_space = false; } bool in_idle = state == IDLE || state == IDLE_DASH || state == IDLE_SLASH; if (in_idle) { if (s == "“") s = "\""; else if (s == "≔") s = ":="; else if (s == "≥") s = ">="; else if (s == "→") s = "->"; else if (s == "≤") s = "<="; else if (s == "≠") s = "!="; else if (s == "⇒") s = "==>"; else if (s == "¬") s = "!"; else if (s == "∧") s = "&"; else if (s == "∨") s = "|"; else if (s == "∀") { s = "forall"; pending_space = true; } else if (s == "∃") { s = "exists"; pending_space = true; } else if (s == "÷") s = "/"; else if (s == "−") s = "-"; else if (s == "∕") s = "/"; else if (s == "×") s = "*"; } switch (state) { case IDLE: next << s; if (s == "-") { state = IDLE_DASH; } else if (s == "/") { state = IDLE_SLASH; } else if (s == "\"") { state = IN_STRING; } break; case IDLE_DASH: next << s; if (s == "-") { state = IN_LINE_COMMENT; } else if (s == "/") { state = IDLE_SLASH; } else if (s == "\"") { state = IN_STRING; } else { state = IDLE; } break; case IDLE_SLASH: next << s; if (s == "-") { state = IDLE_DASH; } else if (s == "/") { // stay in IDLE_SLASH } else if (s == "*") { state = IN_MULTILINE_COMMENT; } else if (s == "\"") { state = IN_STRING; } else { state = IDLE; } break; case IN_STRING: if (s == "\"" || s == "”") { next << "\""; state = IDLE; } else { next << s; if (s == "\\") state = IN_STRING_SLASH; } break; case IN_STRING_SLASH: next << s; state = IN_STRING; break; case IN_LINE_COMMENT: next << s; if (s == "\n") state = IDLE; break; case IN_MULTILINE_COMMENT: next << s; if (s == "*") state = IN_MULTILINE_COMMENT_STAR; break; case IN_MULTILINE_COMMENT_STAR: next << s; if (s == "*") { // stay in IN_MULTILINE_COMMENT_STAR } else if (s == "/") { state = IDLE; } break; } } rumur-2024.05.07/murphi2murphi/src/ToAscii.h000066400000000000000000000015171461637631000204060ustar00rootroot00000000000000// a stage for removing extended unicode characters like ≔ #pragma once #include "Stage.h" #include class ToAscii : public IntermediateStage { private: // states for process() state machine enum State { IDLE, IDLE_DASH, // saw '-' IDLE_SLASH, // saw '/' IN_STRING, // within a string IN_STRING_SLASH, // within string and saw a '\' IN_LINE_COMMENT, // within a -- comment IN_MULTILINE_COMMENT, // within a /* */ comment IN_MULTILINE_COMMENT_STAR, // within a /* */ comment and saw '*' }; State state = IDLE; // do we need to insert a space if we do not see one next? bool pending_space = false; public: explicit ToAscii(Stage &next_); void process(const Token &t) final; virtual ~ToAscii() = default; }; rumur-2024.05.07/murphi2murphi/src/main.cc000066400000000000000000000144511461637631000201360ustar00rootroot00000000000000#include "../../common/help.h" #include "DecomposeComplexComparisons.h" #include "ExplicitSemicolons.h" #include "Pipeline.h" #include "Printer.h" #include "RemoveLiveness.h" #include "Stage.h" #include "SwitchToIf.h" #include "ToAscii.h" #include "options.h" #include "resources.h" #include #include #include #include #include #include #include #include #include #include #include using namespace rumur; static std::shared_ptr in; static std::shared_ptr in_replay; static std::shared_ptr out; // buffer the contents of stdin so we can read it twice static void buffer_stdin() { // read in all of stdin std::ostringstream buf; buf << std::cin.rdbuf(); buf.flush(); // put this into two buffers we can read from in = std::make_shared(buf.str()); in_replay = std::make_shared(buf.str()); } static void parse_args(int argc, char **argv) { for (;;) { static struct option opts[] = { // clang-format off { "decompose-complex-comparisons", no_argument, 0, 128 }, { "explicit-semicolons", no_argument, 0, 129 }, { "help", no_argument, 0, 'h' }, { "no-decompose-complex-comparisons", no_argument, 0, 130 }, { "no-explicit-semicolons", no_argument, 0, 131 }, { "no-remove-liveness", no_argument, 0, 132 }, { "no-switch-to-if", no_argument, 0, 133 }, { "no-to-ascii", no_argument, 0, 134 }, { "output", required_argument, 0, 'o' }, { "remove-liveness", no_argument, 0, 135 }, { "switch-to-if", no_argument, 0, 136 }, { "to-ascii", no_argument, 0, 137 }, { "version", no_argument, 0, 138 }, { 0, 0, 0, 0 }, // clang-format on }; int option_index = 0; int c = getopt_long(argc, argv, "ho:", opts, &option_index); if (c == -1) { break; } switch (c) { case 128: // --decompose-complex-comparisons options.decompose_complex_comparisons = true; break; case 129: // --explicit-semicolons options.explicit_semicolons = true; break; case '?': std::cerr << "run `" << argv[0] << " --help` to see available options\n"; exit(EXIT_SUCCESS); case 'h': // --help help(doc_murphi2murphi_1, doc_murphi2murphi_1_len); exit(EXIT_SUCCESS); case 130: // --no-decompose-complex-comparisons options.decompose_complex_comparisons = false; break; case 131: // --no-explicit-semicolons options.explicit_semicolons = false; break; case 132: // --no-remove-liveness options.remove_liveness = false; break; case 133: // --no-switch-to-if options.switch_to_if = false; break; case 134: // --no-to-ascii options.to_ascii = false; break; case 'o': { // --output auto o = std::make_shared(optarg); if (!o->is_open()) { std::cerr << "failed to open " << optarg << "\n"; exit(EXIT_FAILURE); } out = o; break; } case 135: // --remove-liveness options.remove_liveness = true; break; case 136: // --switch-to-if options.switch_to_if = true; break; case 137: // --to-ascii options.to_ascii = true; break; case 138: // --version std::cout << "Murphi2Murphi version " << get_version() << "\n"; exit(EXIT_SUCCESS); default: std::cerr << "unexpected error\n"; exit(EXIT_FAILURE); } } if (optind == argc - 1) { struct stat buf; if (stat(argv[optind], &buf) < 0) { std::cerr << "failed to open " << argv[optind] << ": " << strerror(errno) << "\n"; exit(EXIT_FAILURE); } if (S_ISDIR(buf.st_mode)) { std::cerr << "failed to open " << argv[optind] << ": this is a directory\n"; exit(EXIT_FAILURE); } auto i = std::make_shared(argv[optind]); if (!i->is_open()) { std::cerr << "failed to open " << argv[optind] << "\n"; exit(EXIT_FAILURE); } in = i; // open the input again that we need for replay during XML output auto i2 = std::make_shared(argv[optind]); if (!i2->is_open()) { std::cerr << "failed to open " << argv[optind] << "\n"; exit(EXIT_FAILURE); } in_replay = i2; } else { // we are going to read data from stdin buffer_stdin(); } } int main(int argc, char **argv) { // parse command line options parse_args(argc, argv); assert(in != nullptr); // parse input model Ptr m; try { m = parse(*in); } catch (Error &e) { std::cerr << e.loc << ":" << e.what() << "\n"; return EXIT_FAILURE; } assert(m != nullptr); // assign unique identifiers to AST nodes m->reindex(); // resolve symbolic references and validate the model try { resolve_symbols(*m); validate(*m); } catch (Error &e) { std::cerr << e.loc << ":" << e.what() << "\n"; return EXIT_FAILURE; } // create a pipeline that we will incrementally populate Pipeline pipe; // add output generator Printer p(*in_replay, out == nullptr ? std::cout : *out); pipe.add_stage(p); // are we adding semi-colons? if (options.explicit_semicolons) pipe.make_stage(); // are we removing liveness? if (options.remove_liveness) pipe.make_stage(); // are we transforming switches to ifs? if (options.switch_to_if) pipe.make_stage(); // are we removing unicode operators? if (options.to_ascii) pipe.make_stage(); // are we decomposing complex comparisons? if (options.decompose_complex_comparisons) pipe.make_stage(); try { // now we can run the pipeline pipe.process(*m); // note that we are done pipe.finalise(); } catch (Error &e) { std::cerr << e.loc << ":" << e.what() << "\n"; return EXIT_FAILURE; } return EXIT_SUCCESS; } rumur-2024.05.07/murphi2murphi/src/options.cc000066400000000000000000000000721461637631000206770ustar00rootroot00000000000000#include "options.h" #include Options options; rumur-2024.05.07/murphi2murphi/src/options.h000066400000000000000000000010611461637631000205400ustar00rootroot00000000000000#pragma once #include // global options set by command line flags struct Options { // add Rumur-optional semicolons for compatibility with CMurphi? bool explicit_semicolons = false; // remove liveness invariants and statements? bool remove_liveness = false; // turn switch statements into if statements? bool switch_to_if = false; // turn unicode operators into their ASCII equivalents? bool to_ascii = false; // turn complex ==/!= into simple ==/!=s? bool decompose_complex_comparisons = false; }; extern Options options; rumur-2024.05.07/murphi2murphi/src/resources.h000066400000000000000000000003101461637631000210530ustar00rootroot00000000000000#pragma once #include // these are generated by ../../misc/xxd.py // ../doc/murphi2murphi.1 extern const unsigned char doc_murphi2murphi_1[]; extern const size_t doc_murphi2murphi_1_len; rumur-2024.05.07/murphi2uclid/000077500000000000000000000000001461637631000157035ustar00rootroot00000000000000rumur-2024.05.07/murphi2uclid/CMakeLists.txt000066400000000000000000000020331461637631000204410ustar00rootroot00000000000000add_custom_command( OUTPUT manpage.cc COMMAND ../misc/xxd.py doc/murphi2uclid.1 ${CMAKE_CURRENT_BINARY_DIR}/manpage.cc MAIN_DEPENDENCY doc/murphi2uclid.1 DEPENDS ../misc/xxd.py WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) add_executable(murphi2uclid ${CMAKE_CURRENT_BINARY_DIR}/manpage.cc ../common/help.cc src/check.cc src/codegen.cc src/main.cc src/pick_numeric_type.cc) target_include_directories(murphi2uclid PRIVATE src ${CMAKE_CURRENT_BINARY_DIR}/../librumur) target_link_libraries(murphi2uclid PRIVATE librumur) add_custom_target(man-murphi2uclid ALL DEPENDS murphi2uclid.1.gz) add_custom_command( OUTPUT murphi2uclid.1.gz COMMAND gzip -9 --no-name --to-stdout doc/murphi2uclid.1 >"${CMAKE_CURRENT_BINARY_DIR}/murphi2uclid.1.gz" MAIN_DEPENDENCY doc/murphi2uclid.1 WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) install(TARGETS murphi2uclid RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/murphi2uclid.1.gz DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) rumur-2024.05.07/murphi2uclid/doc/000077500000000000000000000000001461637631000164505ustar00rootroot00000000000000rumur-2024.05.07/murphi2uclid/doc/murphi2uclid.1000066400000000000000000000061001461637631000211360ustar00rootroot00000000000000.TH MURPHI2UCLID 1 .SH NAME murphi2uclid \- translate a Murphi model to Uclid5 .SH SYNOPSIS .B \fBmurphi2uclid\fR [\fB\-\-output\fR \fIFILE\fR | \fB\-o\fR \fIFILE\fR] \fIFILE\fR .SH DESCRIPTION The utility \fBmurphi2uclid\fR is bundled with the model checker Rumur and can be used to translate a Murphi model into a Uclid5 model. See .BR rumur(1) for more information about Rumur or Murphi. .SH OPTIONS \fB\-\-help\fR or \fB\-?\fR .RS Display usage information. .RE .PP \fB\-\-module\fR \fINAME\fR or \fB\-m\fR \fINAME\fR .RS Set the name to use for the generated Uclid5 module. If you do not supply this, it defaults to \fBmain\fR. .RE .PP \fB\-\-numeric\-type\fR \fITYPE\fRR or \fB\-n\fR \fITYPE\fR .RS Set the Uclid5 type used for ranges, scalarsets and numeric literals. This can be either \fIinteger\fR or a bit-vector type, for example, \fIbv8\fR. If this option is omitted, either \fIinteger\fR or \fIbv64\fR is chosen automatically based on the content of the input model. .RE .PP \fB\-\-output\fR \fIFILE\fR or \fB\-o\fR \fIFILE\fR .RS Set the path to write the output Uclid5 model to. If this argument is omitted, output is written to standard out. .RE .PP \fB\-\-quiet\fR or \fB\-q\fR .RS Silence warning diagnostics. .RE .PP \fB\-\-verbose or \fB\-v\fR .RS Report extra debugging information. .RE .PP \fB\-\-version\fR .RS Display version information and exit. .RE .SH NOTES Translation to Uclid5 is imprecise in the case where there is no direct Uclid5 equivalent for a Murphi concept. For example, an \fBundefine\fR statement in Murphi has no equivalent in Uclid5. This is translated to a \fBhavoc\fR statement. Obviously this is not exactly equivalent. However the hope is that in real world models this achieves a similar effect. Namely, any read of an undefined variable results in the verifier exploring unintended behavior. To get reasonable utility out of translation of a model using \fBundefine\fR, you will likely need an assertion-heavy model. Or, put another way, defensive programming is advised. .SH LIMITATIONS The following Murphi concepts have no translation to Uclid5 and are rejected by murphi2uclid: .RS .IP \[bu] 2 Aliases, in the form of declarations statements or rules .IP \[bu] The \fBisundefined\fR operator .IP \[bu] The modulo operator, \fB%\fR .IP \[bu] The left and shift shift operators, \fB<<\fR and \fB>>\fR .IP \[bu] Early return statements, with or without an expression .IP \[bu] Cover properties .IP \[bu] Liveness properties inside rulesets .IP \[bu] Cover and liveness statements .IP \[bu] A \fBclear\fR statement with a value of complex type as its argument .IP \[bu] Step sizes other than 1 in \fBexists\fR or \fBforall\fR expressions .RE .PP Function calls within expressions are translated as if they were calls to uninterpreted functions. Uclid5 does not support calling interpreted functions (procedures, in Uclid5 terminology) this way. The desirable mapping (uninterpreted function vs rephrasing the call site) cannot be determined automatically. So it is left to the user to tweak or post-process the output, as it will not be accepted by Uclid5 as-is. .SH SEE ALSO rumur(1) rumur-2024.05.07/murphi2uclid/src/000077500000000000000000000000001461637631000164725ustar00rootroot00000000000000rumur-2024.05.07/murphi2uclid/src/check.cc000066400000000000000000000211611461637631000200570ustar00rootroot00000000000000#include "check.h" #include "is_one_step.h" #include #include #include using namespace rumur; namespace { class Checker : public ConstTraversal { private: bool is_tail = false; std::vector params; ///< parameters in the ruleset context we are currently within public: void visit_aliasdecl(const AliasDecl &n) final { // I think Uclid5 has nothing that could reasonably implement AliasDecl…? throw Error("Uclid5 has no equivalent of alias declarations", n.loc); } void visit_aliasrule(const AliasRule &n) final { // I think Uclid5 has nothing that could reasonably implement AliasRule…? throw Error("Uclid5 has no equivalent of alias rules", n.loc); } void visit_aliasstmt(const AliasStmt &n) final { // I think Uclid5 has nothing that could reasonably implement AliasStmt…? throw Error("Uclid5 has no equivalent of alias statements", n.loc); } void visit_clear(const Clear &n) final { const Ptr type = n.rhs->type(); if (!type->is_simple()) throw Error("Clear of complex types is not supported", n.loc); } void visit_div(const Div &n) final { throw Error("Uclid5 has no equivalent of the division operator", n.loc); } void visit_exists(const Exists &n) final { // we could support this, but it seems rarely used so reject it for now if (n.quantifier.type == nullptr && !is_one_step(n.quantifier.step)) throw Error("exists with a non-1 stride are not supported", n.loc); } void visit_forall(const Forall &n) final { // we could support this, but it seems rarely used so reject it for now if (n.quantifier.type == nullptr && !is_one_step(n.quantifier.step)) throw Error("forall with a non-1 stride are not supported", n.loc); } void visit_isundefined(const IsUndefined &n) final { throw Error("Uclid5 has no equivalent of the isundefined operator", n.loc); } void visit_function(const Function &n) final { assert(!is_tail && "incorrect tracking of tail statements"); for (const Ptr &p : n.parameters) p->visit(*this); if (n.return_type != nullptr) n.return_type->visit(*this); for (const Ptr &d : n.decls) d->visit(*this); // descend while only considering the last statement a tail for (size_t i = 0; i + 1 < n.body.size(); ++i) n.body[i]->visit(*this); is_tail = true; if (!n.body.empty()) n.body.back()->visit(*this); // restore default is_tail state is_tail = false; } void visit_for(const For &n) final { n.quantifier.visit(*this); // save current is_tail state bool old_is_tail = is_tail; is_tail = false; for (size_t i = 0; i + 1 < n.body.size(); ++i) n.body[i]->visit(*this); // restore is_tail for the last statement in the block is_tail = old_is_tail; if (!n.body.empty()) n.body.back()->visit(*this); } void visit_ifclause(const IfClause &n) final { if (n.condition != nullptr) n.condition->visit(*this); // save current is_tail state bool old_is_tail = is_tail; is_tail = false; for (size_t i = 0; i + 1 < n.body.size(); ++i) n.body[i]->visit(*this); // restore is_tail for the last statement in the block is_tail = old_is_tail; if (!n.body.empty()) n.body.back()->visit(*this); } void visit_lsh(const Lsh &n) final { // TODO: technically we could implement this as a Uclid5 function. However, // it is a little awkward because Uclid5 does not support generic functions // so we would have to detect which types << is used with and emit a // function for each of these. throw Error("Uclid5 has no equivalent of the left shift operator", n.loc); } void visit_mod(const Mod &n) final { throw Error("Uclid5 has no equivalent of the modulo operator", n.loc); } void visit_propertyrule(const PropertyRule &n) final { if (n.property.category == Property::COVER) throw Error("cover properties have no LTL equivalent in Uclid5", n.loc); // forall quantifiers outside `G(F(…))` does not work in Uclid5 if (!params.empty() && n.property.category == Property::LIVENESS) throw Error("liveness properties within rulesets cannot be translated to " "Uclid5", n.loc); for (const Quantifier *q : params) { if (q->type == nullptr && !is_one_step(q->step)) throw Error("properties within rulesets using quantifiers with non-1 " "steps are not supported", q->loc); } n.property.visit(*this); } void visit_propertystmt(const PropertyStmt &n) final { if (n.property.category == Property::COVER) throw Error("Uclid5 has no equivalent of cover statements", n.loc); if (n.property.category == Property::LIVENESS) throw Error("Ucild5 has no equivalent of liveness statements", n.loc); n.property.visit(*this); } void visit_put(const Put &n) final { throw Error("Uclid5 has no equivalent of put statements except print, " "which is only permitted in control blocks", n.loc); } void visit_return(const Return &n) final { // there seems to be no way to return early from a Uclid5 procedure if (!is_tail) throw Error("early return statements are not supported", n.loc); if (n.expr != nullptr) n.expr->visit(*this); } void visit_rsh(const Rsh &n) final { // TODO: technically we could implement this as a Uclid5 function. However, // it is a little awkward because Uclid5 does not support generic functions // so we would have to detect which types >> is used with and emit a // function for each of these. throw Error("Uclid5 has no equivalent of the right shift operator", n.loc); } void visit_ruleset(const Ruleset &n) final { for (const Quantifier &q : n.quantifiers) { q.visit(*this); // make each parameter visible to following children as we descend params.push_back(&q); } for (const Ptr &r : n.rules) r->visit(*this); assert(params.size() >= n.quantifiers.size() && "ruleset parameter management out of sync"); for (size_t i = 0; i < n.quantifiers.size(); ++i) { size_t j __attribute__((unused)) = params.size() - n.quantifiers.size() + i; assert(params[j] == &n.quantifiers[i] && "ruleset parameter management out of sync"); } // remove the parameters as we ascend params.resize(params.size() - n.quantifiers.size()); } void visit_simplerule(const SimpleRule &n) final { assert(!is_tail && "incorrect trackin of tail statements"); for (const Quantifier &q : n.quantifiers) q.visit(*this); for (const Ptr &a : n.aliases) a->visit(*this); if (n.guard != nullptr) n.guard->visit(*this); for (const Ptr &d : n.decls) d->visit(*this); // descend while only considering the last statement a tail for (size_t i = 0; i + 1 < n.body.size(); ++i) n.body[i]->visit(*this); is_tail = true; if (!n.body.empty()) n.body.back()->visit(*this); // restore default is_tail state is_tail = false; } void visit_startstate(const StartState &n) final { assert(!is_tail && "incorrect trackin of tail statements"); for (const Quantifier &q : n.quantifiers) q.visit(*this); for (const Ptr &a : n.aliases) a->visit(*this); for (const Ptr &d : n.decls) d->visit(*this); // descend while only considering the last statement a tail for (size_t i = 0; i + 1 < n.body.size(); ++i) n.body[i]->visit(*this); is_tail = true; if (!n.body.empty()) n.body.back()->visit(*this); // restore default is_tail state is_tail = false; } void visit_switchcase(const SwitchCase &n) final { for (const Ptr &m : n.matches) m->visit(*this); // save current is_tail state bool old_is_tail = is_tail; is_tail = false; for (size_t i = 0; i + 1 < n.body.size(); ++i) n.body[i]->visit(*this); // restore is_tail for the last statement in the block is_tail = old_is_tail; if (!n.body.empty()) n.body.back()->visit(*this); } void visit_while(const While &n) final { n.condition->visit(*this); // save current is_tail state bool old_is_tail = is_tail; is_tail = false; for (size_t i = 0; i + 1 < n.body.size(); ++i) n.body[i]->visit(*this); // restore is_tail for the last statement in the block is_tail = old_is_tail; if (!n.body.empty()) n.body.back()->visit(*this); } }; } // namespace void check(const Node &n) { Checker c; n.visit(c); } rumur-2024.05.07/murphi2uclid/src/check.h000066400000000000000000000005101461637631000177140ustar00rootroot00000000000000#pragma once #include /** validate that the given node is translatable to Uclid5 * * This function recursively scans a Murphi AST node and throws a rumur::Error * if it encounters anything for which we know we have no Uclid5 equivalent. * * \param n The node to scan */ void check(const rumur::Node &n); rumur-2024.05.07/murphi2uclid/src/codegen.cc000066400000000000000000000720161461637631000204130ustar00rootroot00000000000000#include "codegen.h" #include "../../common/isa.h" #include "is_one_step.h" #include "options.h" #include #include #include #include #include #include #include #include using namespace rumur; namespace { /** a visitor that prints Uclid5 code * * While murphi2uclid only attempts to translate Models to Uclid5, this visitor * is actually capable of starting translation at a Node of any type. */ class Printer : public ConstBaseTraversal { private: std::ostream &o; ///< output stream to emit code to size_t indentation = 0; ///< current indentation level std::vector vars; ///< state variables we have seen size_t id = 0; ///< counter used for constructing unique symbols const std::vector &comments; ///< comments from the original source file std::vector emitted; ///< whether each comment has been written to the output yet std::vector params; ///< parameters in the ruleset context we are currently within std::vector to_return; ///< function parameters that are 'var' (not read-only) and hence we need to ///< track and then deal with in return statements public: Printer(std::ostream &o_, const std::vector &comments_) : o(o_), comments(comments_), emitted(comments_.size(), false) {} void visit_add(const Add &n) final { *this << "(" << *n.lhs << " + " << *n.rhs << ")"; } void visit_aliasdecl(const AliasDecl &) final { throw std::logic_error("alias declaration should have been rejected during " "check()"); } void visit_aliasrule(const AliasRule &) final { throw std::logic_error("alias rule should have been rejected during " "check()"); } void visit_aliasstmt(const AliasStmt &) final { throw std::logic_error("alias statement should have been rejected during " "check()"); } void visit_and(const And &n) final { *this << "(" << *n.lhs << " && " << *n.rhs << ")"; } void visit_array(const Array &n) final { // TODO: multi-dimensional arrays *this << "[" << *n.index_type << "]" << *n.element_type; } void visit_assignment(const Assignment &n) final { // assume we are within a procedure or init and so can use synchronous // assignment // special case function call assignment that has its own syntax if (auto c = dynamic_cast(n.rhs.get())) { *this << tab() << "call (" << *n.lhs; // We need to also assign to any 'var' parameters. The function itself // will have been translate to something that yields these as extra return // values. assert( c->arguments.size() == c->function->parameters.size() && "function call with a different number of arguments than its target"); for (size_t i = 0; i < c->arguments.size(); ++i) { if (!c->function->parameters[i]->is_readonly()) { // FIXME: not strictly safe to emit this and then emit it again in the // call itself; this is not guaranteed to be a pure expression *this << ", " << *c->arguments[i]; } } *this << ") = " << *c << ";\n"; return; } *this << tab() << *n.lhs << " = " << *n.rhs << ";\n"; } void visit_band(const Band &n) final { *this << "(" << *n.lhs << " & " << *n.rhs << ")"; } void visit_bnot(const Bnot &n) final { *this << "(~" << *n.rhs << ")"; } void visit_bor(const Bor &n) final { *this << "(" << *n.lhs << " | " << *n.rhs << ")"; } void visit_clear(const Clear &n) final { const Ptr type = n.rhs->type()->resolve(); if (auto r = dynamic_cast(type.get())) { *this << tab() << *n.rhs << " = " << r->min->constant_fold().get_str() << ";\n"; return; } if (isa(type)) { *this << tab() << *n.rhs << " = 0;\n"; return; } if (auto e = dynamic_cast(type.get())) { assert(!e->members.empty() && "enum with no members"); *this << tab() << *n.rhs << " = " << e->members[0].first << "\n;"; return; } throw std::logic_error("clear of complex type should have been rejected " "during check()"); } void visit_constdecl(const ConstDecl &n) final { // emit as a symbolic constant *this << tab() << "const " << n.name << " : "; const Ptr type = n.get_type(); if (type->is_boolean()) { *this << "boolean"; } else { *this << *type; } *this << ";\n"; // constrain it to have exactly its known value *this << tab() << "assume " << n.name << "_value: " << n.name << " == " << *n.value << ";\n"; } void visit_div(const Div &) final { throw std::logic_error("/ should have been rejected during check()"); } void visit_element(const Element &n) final { *this << *n.array << "[" << *n.index << "]"; } void visit_enum(const Enum &n) final { *this << "enum {"; indent(); std::string sep; for (const std::pair &m : n.members) { *this << sep << "\n" << tab() << m.first; sep = ","; } *this << "\n"; dedent(); *this << tab() << "}"; } void visit_eq(const Eq &n) final { *this << "(" << *n.lhs << " == " << *n.rhs << ")"; } void visit_errorstmt(const ErrorStmt &n) final { // no direct equivalent of this, so just emit the message as a comment and // then an always-failing assertion *this << tab() << "// " << n.message << "\n" << tab() << "assert (false);\n"; } void visit_exists(const Exists &n) final { if (n.quantifier.type != nullptr) { *this << "(exists (" << n.quantifier.name << " : " << *n.quantifier.type << ") :: " << *n.expr << ")"; return; } if (is_one_step(n.quantifier.step)) { *this << "(exists (" << n.quantifier.name << " : " << numeric_type << ") :: (" << n.quantifier.name << " >= " << *n.quantifier.from << " && " << n.quantifier.name << " <= " << *n.quantifier.to << " && " << *n.expr << "))"; return; } throw std::logic_error("exists should have been rejected during check()"); } void visit_exprid(const ExprID &n) final { *this << n.id; } void visit_field(const Field &n) final { *this << *n.record << "." << n.field; } void visit_for(const For &n) final { // do we need to generate a while loop instead of a for loop bool needs_while = !is_one_step(n.quantifier.step); // non-1 steps need to be handled as a while loop if (needs_while) { *this << tab() << "{\n"; indent(); const std::string lb = make_symbol("lower"); *this << tab() << "var " << lb << " : " << numeric_type << ";\n"; const std::string ub = make_symbol("upper"); *this << tab() << "var " << ub << " : " << numeric_type << ";\n"; const std::string &i = n.quantifier.name; assert(n.quantifier.step != nullptr); const Expr &step = *n.quantifier.step; *this << tab() << "var " << i << " : " << numeric_type << ";\n"; *this << tab() << lb << " = " << *n.quantifier.from << ";\n" << tab() << ub << " = " << *n.quantifier.to << ";\n" << tab() << i << " = " << lb << ";\n" << tab() << "while ((" << lb << " <= " << ub << " && " << i << " <= " << ub << ") ||\n" << tab() << " (" << lb << " > " << ub << " && " << i << " >= " << ub << ")) {\n"; indent(); for (const Ptr &s : n.body) { emit_leading_comments(*s); *this << *s; } *this << tab() << i << " = " << i << " + " << step << ";\n"; dedent(); *this << tab() << "}\n"; dedent(); *this << tab() << "}\n"; return; } *this << tab() << "for " << n.quantifier << " {\n"; indent(); for (const Ptr &s : n.body) { emit_leading_comments(*s); *this << *s; } dedent(); *this << tab() << "}\n"; } void visit_forall(const Forall &n) final { if (n.quantifier.type != nullptr) { *this << "(forall (" << n.quantifier.name << " : " << *n.quantifier.type << ") :: " << *n.expr << ")"; return; } if (is_one_step(n.quantifier.step)) { *this << "(forall (" << n.quantifier.name << " : " << numeric_type << ") :: (" << n.quantifier.name << " < " << *n.quantifier.from << " && " << n.quantifier.name << " > " << *n.quantifier.to << " && " << *n.expr << "))"; return; } throw std::logic_error("forall should have been rejected during check()"); } void visit_function(const Function &n) final { *this << "\n" << tab() << "procedure " << n.name << "("; { std::string sep; for (const Ptr &p : n.parameters) { *this << sep << p->name << ": " << *p->get_type(); sep = ", "; } } *this << ")\n"; indent(); { std::string sep = tab() + "returns ("; std::string ender; if (n.return_type != nullptr) { *this << sep << "__return: " << *n.return_type; sep = ", "; ender = ")\n"; } for (const Ptr &p : n.parameters) { if (!p->is_readonly()) { *this << sep << "__" << p->name << ": " << *p->get_type(); sep = ", "; ender = ")\n"; to_return.push_back(p->name); } } *this << ender; } // conservatively allow the function to modify anything, to avoid having to // inspect its body if (!vars.empty()) { *this << tab() << "modifies "; std::string sep = ""; for (const std::string &v : vars) { *this << sep << v; sep = ", "; } *this << ";\n"; } dedent(); *this << tab() << "{\n"; indent(); for (const Ptr &d : n.decls) { emit_leading_comments(*d); *this << *d; } for (const Ptr &s : n.body) { emit_leading_comments(*s); *this << *s; } // if we reached the end of the function, we may have seen no return // statement but need to propagate parameters passed in by value to return // values for (const std::string &s : to_return) *this << tab() << "__" << s << " = " << s << ";\n"; dedent(); *this << tab() << "}\n"; to_return.clear(); } void visit_functioncall(const FunctionCall &n) final { *this << n.name << "("; std::string sep; for (const Ptr &a : n.arguments) { *this << sep << *a; sep = ", "; } *this << ")"; } void visit_geq(const Geq &n) final { *this << "(" << *n.lhs << " >= " << *n.rhs << ")"; } void visit_gt(const Gt &n) final { *this << "(" << *n.lhs << " > " << *n.rhs << ")"; } void visit_if(const If &n) final { assert(!n.clauses.empty() && "if statement with no content"); bool first = true; for (const IfClause &c : n.clauses) { if (first) { *this << c; first = false; } else { *this << " else {\n"; indent(); *this << c; } } first = true; for (const IfClause &c : n.clauses) { if (first) { first = false; } else if (c.condition != nullptr) { // did we indent in visit_ifclause? dedent(); *this << "\n" << tab() << "}"; } } *this << "\n"; } void visit_ifclause(const IfClause &n) final { if (n.condition != nullptr) { bool needs_brackets = !isa(n.condition); *this << tab() << "if "; if (needs_brackets) *this << "("; *this << *n.condition; if (needs_brackets) *this << ")"; *this << " {\n"; indent(); } for (const Ptr &s : n.body) { emit_leading_comments(*s); *this << *s; } dedent(); *this << tab() << "}"; } void visit_implication(const Implication &n) final { *this << "(" << *n.lhs << " ==> " << *n.rhs << ")"; } void visit_isundefined(const IsUndefined &) final { throw std::logic_error("isundefined should have been rejected during " "check()"); } void visit_leq(const Leq &n) final { *this << "(" << *n.lhs << " <= " << *n.rhs << ")"; } void visit_lsh(const Lsh &) final { throw std::logic_error("<< should have been rejected during check()"); } void visit_lt(const Lt &n) final { *this << "(" << *n.lhs << " < " << *n.rhs << ")"; } void visit_mod(const Mod &) final { throw std::logic_error("% should have been rejected during check()"); } void visit_model(const Model &n) final { emit_leading_comments(n); // force more natural placement for file-leading comments if (!n.children.empty()) emit_leading_comments(*n.children[0]); // output module header *this << "module " << module_name << " {\n"; indent(); for (const Ptr &c : n.children) { emit_leading_comments(*c); *this << *c; } // close module dedent(); *this << "}\n"; } void visit_mul(const Mul &n) final { *this << "(" << *n.lhs << " * " << *n.rhs << ")"; } void visit_negative(const Negative &n) final { *this << "-" << *n.rhs; } void visit_neq(const Neq &n) final { *this << "(" << *n.lhs << " != " << *n.rhs << ")"; } void visit_not(const Not &n) final { *this << "!" << *n.rhs; } void visit_number(const Number &n) final { *this << n.value.get_str(); // if we are using a bit-vector type, we need to suffix numeric literals // with it if (numeric_type != "integer") *this << numeric_type; } void visit_or(const Or &n) final { *this << "(" << *n.lhs << " || " << *n.rhs << ")"; } void visit_procedurecall(const ProcedureCall &n) final { // Murphi permits calling a function that return a value and then // discarding the result. However, this is an error in Uclid5. So if we have // such a situation, work around this with an ignored local variable. const Ptr &ret = n.call.function->return_type; if (ret != nullptr) { // open a scope so we can declare a new local *this << tab() << "{\n"; indent(); const std::string s = make_symbol("ignored"); *this << tab() << "var " << s << " : " << *ret << ";\n"; *this << tab() << "call (" << s; // also deal with any 'var' parameters assert( n.call.arguments.size() == n.call.function->parameters.size() && "function call with a different number of arguments than its target"); for (size_t i = 0; i < n.call.arguments.size(); ++i) { if (!n.call.function->parameters[i]->is_readonly()) { // FIXME: not strictly safe to emit this and then emit it again in the // call itself; this is not guaranteed to be a pure expression *this << ", " << *n.call.arguments[i]; } } *this << ") = " << n.call << ";\n"; dedent(); *this << tab() << "}\n"; return; } *this << tab() << "call "; // handle any 'var' parameters this function may have bool has_var = false; assert( n.call.arguments.size() == n.call.function->parameters.size() && "function call with a different number of arguments than its target"); std::string sep = "("; for (size_t i = 0; i < n.call.arguments.size(); ++i) { if (!n.call.function->parameters[i]->is_readonly()) { // FIXME: not strictly safe to emit this and then emit it again in the // call itself; this is not guaranteed to be a pure expression *this << sep << *n.call.arguments[i]; sep = ", "; has_var = true; } } if (has_var) *this << ") = "; *this << n.call << ";\n"; } void visit_property(const Property &) final { throw std::logic_error("property should have been handled in its parent (" "either PropertyRule or PropertyStmt)"); } void visit_propertyrule(const PropertyRule &n) final { *this << "\n" << tab(); switch (n.property.category) { case Property::ASSERTION: *this << "invariant"; break; case Property::ASSUMPTION: *this << "assume"; break; case Property::COVER: throw std::logic_error("cover property should have been rejected during " "check()"); case Property::LIVENESS: *this << "property[LTL] "; break; } *this << " " << n.name << ": "; for (const Quantifier *q : params) { *this << "(forall (" << q->name << " : "; if (q->type == nullptr) { *this << numeric_type; } else { *this << *q->type; } *this << ") :: ("; if (q->type == nullptr) { if (!is_one_step(q->step)) // TODO throw std::logic_error("property should have been rejected during " "check()"); *this << q->name << " < " << *q->from << " || " << q->name << " > " << *q->to << " || "; } } if (n.property.category == Property::LIVENESS) *this << "G(F("; *this << *n.property.expr; if (n.property.category == Property::LIVENESS) *this << "))"; for (size_t i = 0; i < params.size(); ++i) *this << "))"; *this << ";\n"; } void visit_propertystmt(const PropertyStmt &n) final { // there is no equivalent of property messages, so just emit this as a // comment if (n.message != "") { *this << tab() << "// "; for (const char &c : n.message) { *this << c; if (c == '\n') *this << tab() << "// "; } *this << "\n"; } switch (n.property.category) { case Property::ASSERTION: *this << tab() << "assert " << *n.property.expr << ";\n"; break; case Property::ASSUMPTION: *this << tab() << "assume " << *n.property.expr << ";\n"; break; case Property::COVER: throw std::logic_error("cover statement should have been rejected during " "check()"); case Property::LIVENESS: throw std::logic_error("liveness statement should have been rejected " "during check()"); } } void visit_put(const Put &) final { throw std::logic_error("put statement should have been rejected during " "check()"); } void visit_quantifier(const Quantifier &n) final { assert(is_one_step(n.step) && "non-trivial step in quantifier visitation"); if (n.type == nullptr) { *this << n.name << " in range(" << *n.from << ", " << *n.to << ")"; } else { *this << "(" << n.name << " : " << *n.type << ") in range("; const Ptr resolved = n.type->resolve(); if (auto r = dynamic_cast(resolved.get())) { *this << *r->min << ", " << *r->max; } else if (auto s = dynamic_cast(resolved.get())) { *this << "0, " << *s->bound << " - 1"; } else if (auto e = dynamic_cast(resolved.get())) { if (e->members.empty()) throw Error("you cannot iterate over an enum with no members", n.loc); *this << e->members[0].first << ", " << e->members.back().first; } else { assert(!"unhandled simple type"); } *this << ")"; } } void visit_range(const Range &) final { *this << numeric_type; // TODO: range limits } void visit_record(const Record &n) final { *this << "record {\n"; indent(); std::string sep; for (const Ptr &f : n.fields) { *this << sep << tab() << f->name << " : " << *f->get_type(); sep = ",\n"; } *this << "\n"; dedent(); *this << tab() << "}"; } void visit_return(const Return &n) final { // do we need to propagate parameters passed in by value to return values? if (!to_return.empty()) { // use a block so anything containing sees us as a single statement *this << tab() << "{\n"; indent(); for (const std::string &s : to_return) *this << tab() << "__" << s << " = " << s << ";\n"; } // only relevant if an actual value is being returned if (n.expr != nullptr) *this << tab() << "__return = " << *n.expr << ";\n"; if (!to_return.empty()) { dedent(); *this << tab() << "}\n"; } } void visit_rsh(const Rsh &) final { throw std::logic_error(">> should have been rejected during check()"); } void visit_ruleset(const Ruleset &n) final { // make our parameters visible to children as we descend for (const Quantifier &q : n.quantifiers) params.push_back(&q); for (const Ptr &r : n.rules) *this << *r; assert(params.size() >= n.quantifiers.size() && "ruleset parameter management out of sync"); for (size_t i = 0; i < n.quantifiers.size(); ++i) { size_t j __attribute__((unused)) = params.size() - n.quantifiers.size() + i; assert(params[j] == &n.quantifiers[i] && "ruleset parameter management out of sync"); } // remove the parameters as we ascend params.resize(params.size() - n.quantifiers.size()); } void visit_scalarset(const Scalarset &) final { *this << numeric_type; // TODO: range limits } void visit_simplerule(const SimpleRule &n) final { if (n.guard != nullptr) { *this << "\n" << tab() << "define guard_" << n.name << "("; emit_params(); *this << ") : boolean = " << *n.guard << ";\n"; } // emit rules as procedures, so we can use synchronous assignment *this << "\n" << tab() << "procedure rule_" << n.name << "("; emit_params(); *this << ")\n"; // conservatively allow the rule to modify anything, to avoid having to // inspect its body if (!vars.empty()) { indent(); *this << tab() << "modifies "; std::string sep = ""; for (const std::string &v : vars) { *this << sep << v; sep = ", "; } *this << ";\n"; dedent(); } *this << tab() << "{\n"; indent(); for (const Ptr &d : n.decls) { emit_leading_comments(*d); *this << *d; } for (const Ptr &s : n.body) { emit_leading_comments(*s); *this << *s; } dedent(); *this << tab() << "}\n"; } void visit_startstate(const StartState &n) final { *this << "\n" << tab() << "procedure startstate_" << n.name << "("; emit_params(); *this << ")\n"; // conservatively allow the startstate to modify anything, to avoid having // to inspect its body if (!vars.empty()) { indent(); *this << tab() << "modifies "; std::string sep = ""; for (const std::string &v : vars) { *this << sep << v; sep = ", "; } *this << ";\n"; dedent(); } *this << tab() << "{\n"; indent(); for (const Ptr &d : n.decls) { emit_leading_comments(*d); *this << *d; } for (const Ptr &s : n.body) { emit_leading_comments(*s); *this << *s; } dedent(); *this << tab() << "}\n"; } void visit_sub(const Sub &n) final { *this << "(" << *n.lhs << " - " << *n.rhs << ")"; } void visit_switch(const Switch &n) final { if (n.expr->is_pure()) { *this << tab() << "case\n"; indent(); for (const SwitchCase &c : n.cases) { emit_leading_comments(c); if (c.matches.empty()) { *this << tab() << "default"; } else { *this << tab(); std::string sep; for (const Ptr &m : c.matches) { *this << sep << *n.expr << " == " << *m; sep = " || "; } } *this << " : " << c; } dedent(); *this << tab() << "esac\n"; } else { // this switch is on an expression with side effects, so we need to first // store it in a temporary which we will then emit multiple times std::string expr = make_symbol(""); *this << tab() << "{\n"; indent(); *this << tab() << "var " << expr << " : " << *n.expr->type() << ";\n" << tab() << expr << " = " << *n.expr; *this << tab() << "case\n"; indent(); for (const SwitchCase &c : n.cases) { emit_leading_comments(c); if (c.matches.empty()) { *this << tab() << "default"; } else { *this << tab(); std::string sep; for (const Ptr &m : c.matches) { *this << sep << expr << " == " << *m; sep = " || "; } } *this << " : " << c; } dedent(); *this << tab() << "esac\n"; dedent(); *this << tab() << "}\n"; } } void visit_switchcase(const SwitchCase &n) final { // the match itself will have been handled by our parent (Switch) *this << "{\n"; indent(); for (const Ptr &s : n.body) { emit_leading_comments(*s); *this << *s; } dedent(); *this << tab() << "}\n"; } void visit_ternary(const Ternary &n) final { bool needs_brackets = !isa(n.cond); *this << "(if "; if (needs_brackets) *this << "("; *this << *n.cond; if (needs_brackets) *this << ")"; *this << " then " << *n.lhs << " else " << *n.rhs << ")"; } void visit_typedecl(const TypeDecl &n) final { *this << tab() << "type " << n.name << " = " << *n.value << ";\n"; } void visit_typeexprid(const TypeExprID &n) final { // no need for special “boolean” handling because it has the same // spelling in Murphi and Uclid5 *this << n.name; } void visit_undefine(const Undefine &n) final { // Uclid5 has no direct equivalent of the Murphi `undefine` statement. // However, it is hoped that havocking the variable has a similar effect. // Namely, encouraging the model checker to treat any following read without // an intervening write as an error. Obviously this is not exactly what // happens. See the murphi2uclid man page for some further discussion. *this << tab() << "havoc " << *n.rhs << ";\n"; } void visit_vardecl(const VarDecl &n) final { *this << tab() << "var " << n.name << " : " << *n.get_type() << ";\n"; vars.push_back(n.name); } void visit_while(const While &n) final { bool needs_brackets = !isa(n.condition); *this << tab() << "while "; if (needs_brackets) *this << "("; *this << *n.condition; if (needs_brackets) *this << ")"; *this << " {\n"; indent(); for (const Ptr &s : n.body) { emit_leading_comments(*s); *this << *s; } dedent(); *this << tab() << "}\n"; } void visit_xor(const Xor &n) final { *this << "(" << *n.lhs << " ^ " << *n.rhs << ")"; } private: // wrappers to allow more readable code above Printer &operator<<(const std::string &s) { o << s; return *this; } Printer &operator<<(char c) { o << c; return *this; } Printer &operator<<(const Node &n) { n.visit(*this); return *this; } void indent() { ++indentation; } void dedent() { assert(indentation > 0); --indentation; } std::string tab() { return std::string(indentation * 2, ' '); } /// create a unique symbol for use in generated code std::string make_symbol(const std::string &name) { // FIXME: better strategy for avoiding collisions with user symbols return "__sym_" + name + std::to_string(id++); } /// print the current list of enclosing parameters, assuming we are within /// something like a procedure’s parameter list void emit_params(void) { std::string sep; for (const Quantifier *q : params) { *this << sep << q->name << " : "; if (q->type == nullptr) { *this << numeric_type; } else { *this << *q->type; } sep = ", "; } } /// print any source comments to prior to the given node that have not yet /// been printed size_t emit_leading_comments(const Node &n) { size_t count = 0; size_t i = 0; for (const Comment &c : comments) { // has this not yet been printed? if (!emitted[i]) { // does this precede the given node? if (c.loc.end.line < n.loc.begin.line || (c.loc.end.line == n.loc.begin.line && c.loc.end.column <= n.loc.begin.column)) { // do some white space adjustment for multiline comments if (c.multiline) { o << tab() << "/*"; bool dropping = false; for (const char &b : c.content) { if (b == '\n') { o << "\n" << tab() << " "; dropping = true; } else if (dropping) { if (!isspace(b)) { o << b; dropping = false; } } else { o << b; } } o << "*/\n"; } else { // single line comments can be emitted simpler o << tab() << "//" << c.content << "\n"; } emitted[i] = true; } } ++i; } return count; } }; } // namespace void codegen(const Node &n, const std::vector &comments, std::ostream &out) { Printer p(out, comments); n.visit(p); } rumur-2024.05.07/murphi2uclid/src/codegen.h000066400000000000000000000010771461637631000202540ustar00rootroot00000000000000#pragma once #include #include #include #include /** translate a Murphi AST node to Uclid5 code * * This function may throw rumur::Errors when encountering unsupported nodes. * Desired indentation is assumed to be two spaces with a starting indentation * level 0. * * \param n The node to translate * \param comments Comments parsed from the source code * \param out Stream to write the translation to */ void codegen(const rumur::Node &n, const std::vector &comments, std::ostream &out); rumur-2024.05.07/murphi2uclid/src/is_one_step.h000066400000000000000000000004761461637631000211610ustar00rootroot00000000000000#pragma once #include #include // is the given parameter, representing a for step, known to be 1? static inline bool is_one_step(const rumur::Ptr &step) { if (step == nullptr) return true; if (!step->constant()) return false; return step->constant_fold() == 1; } rumur-2024.05.07/murphi2uclid/src/main.cc000066400000000000000000000126131461637631000177300ustar00rootroot00000000000000#include "../../common/help.h" #include "check.h" #include "codegen.h" #include "options.h" #include "pick_numeric_type.h" #include "resources.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // a pair of input streams using dup_t = std::pair, std::shared_ptr>; static std::string in_filename = ""; static dup_t in; static std::string out_filename = "-"; static std::shared_ptr out; std::string module_name = "main"; std::string numeric_type; static bool is_valid_numeric_type(const char *s) { assert(s != NULL); if (strcmp(s, "integer") == 0) return true; if (strncmp(s, "bv", strlen("bv")) != 0) return false; for (const char *p = s + strlen("bv"); *p != '\0'; ++p) { if (!isdigit(*p)) return false; } return true; } verbosity_t verbosity = WARNINGS; static void parse_args(int argc, char **argv) { for (;;) { static struct option options[] = { // clang-format off { "help", no_argument, 0, 'h' }, { "module", required_argument, 0, 'm' }, { "numeric-type", required_argument, 0, 'n' }, { "output", required_argument, 0, 'o' }, { "quiet", no_argument, 0, 'q' }, { "verbose", no_argument, 0, 'v' }, { "version", no_argument, 0, 128 }, { 0, 0, 0, 0 }, // clange-format on }; int option_index = 0; int c = getopt_long(argc, argv, "ho:", options, &option_index); if (c == -1) break; switch (c) { case '?': std::cerr << "run `" << argv[0] << " --help` to see available options\n"; exit(EXIT_SUCCESS); case 'h': // --help help(doc_murphi2uclid_1, doc_murphi2uclid_1_len); exit(EXIT_SUCCESS); case 'm': module_name = optarg; break; case 'n': // --numeric-type if (!is_valid_numeric_type(optarg)) { std::cerr << "invalid argument to --numeric-type " << optarg << "\n"; exit(EXIT_FAILURE); } numeric_type = optarg; break; case 'o': out_filename = optarg; break; case 'q': // --quiet verbosity = QUIET; break; case 'v': // --verbose verbosity = VERBOSE; break; case 128: // --version std::cout << "Murphi2Uclid version " << rumur::get_version() << "\n"; exit(EXIT_SUCCESS); default: std::cerr << "unexpected error\n"; exit(EXIT_FAILURE); } } if (optind == argc - 1) { struct stat buf; if (stat(argv[optind], &buf) < 0) { std::cerr << "failed to open " << argv[optind] << ": " << strerror(errno) << "\n"; exit(EXIT_FAILURE); } if (S_ISDIR(buf.st_mode)) { std::cerr << "failed to open " << argv[optind] << ": this is a directory\n"; exit(EXIT_FAILURE); } in_filename = argv[optind]; auto i = std::make_shared(in_filename); auto j = std::make_shared(in_filename); if (!i->is_open() || !j->is_open()) { std::cerr << "failed to open " << in_filename << "\n"; exit(EXIT_FAILURE); } in = dup_t(i, j); } } static dup_t make_stdin_dup() { // read stdin into memory auto buffer = std::make_shared(); *buffer << std::cin.rdbuf(); // duplicate the buffer auto copy = std::make_shared(buffer->str()); return dup_t(buffer, copy); } static std::ostream &output() { return out == nullptr ? std::cout : *out; } int main(int argc, char **argv) { // parse command line options parse_args(argc, argv); // if we are reading from stdin, duplicate it so that we can parse it both as // Murphi and for comments if (in.first == nullptr) in = make_stdin_dup(); // parse input model rumur::Ptr m; try { m = rumur::parse(*in.first); } catch (rumur::Error &e) { std::cerr << e.loc << ":" << e.what() << "\n"; return EXIT_FAILURE; } assert(m != nullptr); // update unique identifiers within the model m->reindex(); // check the model is valid try { resolve_symbols(*m); validate(*m); } catch (rumur::Error &e) { std::cerr << e.loc << ":" << e.what() << "\n"; return EXIT_FAILURE; } // name any rules that are unnamed, so they get valid Uclid5 symbols rumur::sanitise_rule_names(*m); // check this can be translated to Uclid5 try { check(*m); } catch (rumur::Error &e) { std::cerr << e.loc << ":" << e.what() << "\n"; return EXIT_FAILURE; } // if the user did not select a numeric type, select one for them numeric_type = pick_numeric_type(*m); // parse comments from the source code std::vector comments = rumur::parse_comments(*in.second); // only *now* open the output file, to avoid creating an empty file if any of // the preceding steps fail if (out_filename != "-") { auto o = std::make_shared(out_filename); if (!o->is_open()) { std::cerr << "failed to open " << out_filename << "\n"; exit(EXIT_FAILURE); } out = o; } // generate Uclid5 source code codegen(*m, comments, output()); return EXIT_SUCCESS; } rumur-2024.05.07/murphi2uclid/src/options.h000066400000000000000000000006571461637631000203460ustar00rootroot00000000000000#pragma once #include // name to give output Uclid5 module extern std::string module_name; // Type to use for ranges and scalarsets. Either “integer” or a bit-vector type // “bvX”. An empty string chooses automatically between “integer” and “bv64”. extern std::string numeric_type; enum verbosity_t { QUIET, WARNINGS, VERBOSE }; // level of diagnostic messages to report extern verbosity_t verbosity; rumur-2024.05.07/murphi2uclid/src/pick_numeric_type.cc000066400000000000000000000033231461637631000225130ustar00rootroot00000000000000#include "pick_numeric_type.h" #include #include #include #include #include using namespace rumur; namespace { /// a visitor that infers any numeric type requirements class Picker : public ConstTraversal { public: bool needs_bv = false; ///< does this need a bit-vector type? bool needs_integer = false; ///< does this need integer as the type? // bitwise operations are only valid on bit-vector types void visit_band(const Band &n) final { needs_bv = true; n.lhs->visit(*this); n.rhs->visit(*this); } void visit_bnot(const Bnot &n) final { needs_bv = true; n.rhs->visit(*this); } void visit_bor(const Bor &n) final { needs_bv = true; n.lhs->visit(*this); n.rhs->visit(*this); } // negative numbers require integers void visit_negative(const Negative &n) final { if (n.rhs->constant()) { if (n.rhs->constant_fold() > 0) needs_integer = true; } n.rhs->visit(*this); } void visit_number(const Number &n) final { if (n.value < 0) needs_integer = true; } void visit_xor(const Xor &n) final { needs_bv = true; n.lhs->visit(*this); n.rhs->visit(*this); } }; } // namespace std::string pick_numeric_type(const Node &n) { Picker p; n.visit(p); if (p.needs_bv && p.needs_integer) { std::cerr << "Model has conflicting requirements for a numeric type. You " "will need to select one explicitly with --numeric-type\n"; exit(EXIT_FAILURE); } if (p.needs_bv) return "bv64"; // use 64-bit precision as a default if (p.needs_integer) return "integer"; // if there are no constraints, default to integer return "integer"; } rumur-2024.05.07/murphi2uclid/src/pick_numeric_type.h000066400000000000000000000004151461637631000223540ustar00rootroot00000000000000#pragma once #include #include #include /** pick a numeric type based on the content of a model * * @param n Model to examine * @return Automatically selected numeric type */ std::string pick_numeric_type(const rumur::Node &n); rumur-2024.05.07/murphi2uclid/src/resources.h000066400000000000000000000001771461637631000206620ustar00rootroot00000000000000#pragma once #include extern const unsigned char doc_murphi2uclid_1[]; extern const size_t doc_murphi2uclid_1_len; rumur-2024.05.07/murphi2xml/000077500000000000000000000000001461637631000154035ustar00rootroot00000000000000rumur-2024.05.07/murphi2xml/CMakeLists.txt000066400000000000000000000017561461637631000201540ustar00rootroot00000000000000add_custom_command( OUTPUT manpage.cc COMMAND ../misc/xxd.py doc/murphi2xml.1 ${CMAKE_CURRENT_BINARY_DIR}/manpage.cc MAIN_DEPENDENCY doc/murphi2xml.1 DEPENDS ../misc/xxd.py WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) add_executable(murphi2xml ${CMAKE_CURRENT_BINARY_DIR}/manpage.cc ../common/help.cc src/main.cc src/XMLPrinter.cc) target_include_directories(murphi2xml PRIVATE src ${CMAKE_CURRENT_BINARY_DIR}/../librumur) target_link_libraries(murphi2xml PRIVATE librumur) # Compress manpages add_custom_target(man-murphi2xml ALL DEPENDS murphi2xml.1.gz) add_custom_command( OUTPUT murphi2xml.1.gz COMMAND gzip -9 --no-name --to-stdout doc/murphi2xml.1 >"${CMAKE_CURRENT_BINARY_DIR}/murphi2xml.1.gz" MAIN_DEPENDENCY doc/murphi2xml.1 WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) install(TARGETS murphi2xml RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/murphi2xml.1.gz DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) rumur-2024.05.07/murphi2xml/doc/000077500000000000000000000000001461637631000161505ustar00rootroot00000000000000rumur-2024.05.07/murphi2xml/doc/murphi2xml.1000066400000000000000000000013751461637631000203470ustar00rootroot00000000000000.TH MURPHI2XML 1 .SH NAME murphi2xml \- Print the abstract syntax tree of a parsed Murphi model .SH SYNOPSIS .B \fBmurphi2xml\fR [\fB\-\-output\fR \fIFILE\fR | \fB\-o\fR \fIFILE\fR] \fIFILE\fR .SH DESCRIPTION The utility \fBmurphi2xml\fR is bundled with the model checker Rumur and can be used to translate a Murphi model into its abstract syntax tree in an XML format. See .BR rumur(1) for more information about Rumur or Murphi. .SH OPTIONS \fB\-\-help\fR or \fB\-?\fR .RS Display usage information. .RE .PP \fB\-\-output\fR \fIFILE\fR or \fB\-o\fR \fIFILE\fR .RS Set the path to write the output XML to. If this argument is omitted, output is written to standard out. .RE .PP \fB\-\-version\fR .RS Display version information and exit. .RE .SH SEE ALSO rumur(1) rumur-2024.05.07/murphi2xml/src/000077500000000000000000000000001461637631000161725ustar00rootroot00000000000000rumur-2024.05.07/murphi2xml/src/XMLPrinter.cc000066400000000000000000000507001461637631000205070ustar00rootroot00000000000000#include "XMLPrinter.h" #include #include #include #include #include #include using namespace rumur; static std::string xml_escape(char c) { switch (c) { case '"' : return """; case '\'': return "'"; case '<' : return "<"; case '>' : return ">"; case '&' : return "&"; /* XXX: Form feed is apparently not a valid character to use in XML 1.0, * encoded or otherwise. However, some legacy models use this. To cope with * it, we just translate it to a single space. */ case 12 : return " "; default : return std::string(1, c); } } static std::string xml_escape(const std::string &s) { std::string escaped; for (char c : s) escaped += xml_escape(c); return escaped; } XMLPrinter::XMLPrinter(const std::string &in_filename, std::istream &in_, std::ostream &o_) : in(in_), o(o_) { // Write out XML version header o << "\n"; o << ""; } void XMLPrinter::visit_add(const Add &n) { visit_bexpr("add", n); } void XMLPrinter::visit_aliasdecl(const AliasDecl &n) { sync_to(n); o << ""; sync_to(*n.value); o << ""; dispatch(*n.value); o << ""; sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_aliasrule(const AliasRule &n) { sync_to(n); o << ""; if (!n.aliases.empty()) { sync_to(*n.aliases[0]); o << ""; for (auto &a : n.aliases) { sync_to(*a); dispatch(*a); } o << ""; } if (!n.rules.empty()) { sync_to(*n.rules[0]); o << ""; for (auto &r : n.rules) { sync_to(*r); dispatch(*r); } o << ""; } sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_aliasstmt(const AliasStmt &n) { sync_to(n); o << ""; if (!n.aliases.empty()) { sync_to(*n.aliases[0]); o << ""; for (auto &a : n.aliases) { sync_to(*a); dispatch(*a); } o << ""; } if (!n.body.empty()) { sync_to(*n.body[0]); o << ""; for (auto &s : n.body) { sync_to(*s); dispatch(*s); } o << ""; } sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_and(const And &n) { visit_bexpr("and", n); } void XMLPrinter::visit_array(const Array &n) { sync_to(n); o << ""; sync_to(*n.index_type); o << ""; dispatch(*n.index_type); o << ""; sync_to(*n.element_type); o << ""; dispatch(*n.element_type); o << ""; sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_assignment(const Assignment &n) { sync_to(n); o << ""; dispatch(*n.lhs); o << ""; sync_to(*n.rhs); o << ""; dispatch(*n.rhs); o << ""; sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_band(const Band &n) { visit_bexpr("band", n); } void XMLPrinter::visit_bnot(const Bnot &n) { visit_uexpr("bnot", n); } void XMLPrinter::visit_bor(const Bor &n) { visit_bexpr("bor", n); } void XMLPrinter::visit_clear(const Clear &n) { sync_to(n); o << ""; sync_to(*n.rhs); dispatch(*n.rhs); sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_constdecl(const ConstDecl &n) { sync_to(n); o << ""; sync_to(*n.value); o << ""; dispatch(*n.value); o << ""; sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_div(const Div &n) { visit_bexpr("div", n); } void XMLPrinter::visit_element(const Element &n) { sync_to(n); o << ""; sync_to(*n.array); o << ""; dispatch(*n.array); o << ""; sync_to(*n.index); o << ""; dispatch(*n.index); o << ""; sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_enum(const Enum &n) { sync_to(n); o << ""; for (const std::pair &m : n.members) { sync_to(m.second.begin); o << ""; sync_to(m.second.end); o << ""; } sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_eq(const Eq &n) { visit_bexpr("eq", n); } void XMLPrinter::visit_errorstmt(const ErrorStmt &n) { sync_to(n); o << ""; sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_exists(const Exists &n) { sync_to(n); o << ""; sync_to(n.quantifier); o << ""; dispatch(n.quantifier); o << ""; sync_to(*n.expr); o << ""; dispatch(*n.expr); o << ""; sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_exprid(const ExprID &n) { sync_to(n); o << ""; sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_field(const Field &n) { sync_to(n); o << ""; sync_to(*n.record); o << ""; dispatch(*n.record); o << ""; /* FIXME: We don't have location information for the field itself, so we just * dump the entire remaining text of this node here. Does this produce * inaccurate output? */ sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_for(const For &n) { sync_to(n); o << ""; sync_to(n.quantifier); dispatch(n.quantifier); if (!n.body.empty()) { sync_to(*n.body[0]); o << ""; for (auto &s : n.body) { sync_to(*s); dispatch(*s); } o << ""; } sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_forall(const Forall &n) { sync_to(n); o << ""; sync_to(n.quantifier); o << ""; dispatch(n.quantifier); o << ""; sync_to(*n.expr); o << ""; dispatch(*n.expr); o << ""; sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_function(const Function &n) { sync_to(n); o << ""; if (!n.parameters.empty()) { sync_to(*n.parameters[0]); o << ""; for (auto &p : n.parameters) { sync_to(*p); dispatch(*p); } o << ""; } if (n.return_type != nullptr) { sync_to(*n.return_type); o << ""; dispatch(*n.return_type); o << ""; } if (!n.decls.empty()) { sync_to(*n.decls[0]); o << ""; for (auto &d : n.decls) { sync_to(*d); dispatch(*d); } o << ""; } if (!n.body.empty()) { sync_to(*n.body[0]); o << ""; for (auto &s : n.body) { sync_to(*s); dispatch(*s); } o << ""; } sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_functioncall(const FunctionCall &n) { sync_to(n); /* We deliberately list the called function by name as an attribute, rather * than emitting the function itself as a child of this node because morally * this is just a reference to a previously defined function. */ o << ""; for (auto &a : n.arguments) { sync_to(*a); o << ""; dispatch(*a); o << ""; } sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_geq(const Geq &n) { visit_bexpr("geq", n); } void XMLPrinter::visit_gt(const Gt &n) { visit_bexpr("gt", n); } void XMLPrinter::visit_if(const If &n) { sync_to(n); o << ""; for (const IfClause &c : n.clauses) { sync_to(c); dispatch(c); } sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_ifclause(const IfClause &n) { sync_to(n); o << ""; if (n.condition != nullptr) { sync_to(*n.condition); o << ""; dispatch(*n.condition); o << ""; } if (!n.body.empty()) { sync_to(*n.body[0]); o << ""; for (auto &s : n.body) { sync_to(*s); dispatch(*s); } o << ""; } sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_implication(const Implication &n) { visit_bexpr("implication", n); } void XMLPrinter::visit_isundefined(const IsUndefined &n) { visit_uexpr("isundefined", n); } void XMLPrinter::visit_leq(const Leq &n) { visit_bexpr("leq", n); } void XMLPrinter::visit_lsh(const Lsh &n) { visit_bexpr("lsh", n); } void XMLPrinter::visit_lt(const Lt &n) { visit_bexpr("lt", n); } void XMLPrinter::visit_mod(const Mod &n) { visit_bexpr("mod", n); } void XMLPrinter::visit_model(const Model &n) { o << ""; if (!n.children.empty()) { sync_to(*n.children[0]); for (auto &c : n.children) { sync_to(*c); dispatch(*c); } } sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_mul(const Mul &n) { visit_bexpr("mul", n); } void XMLPrinter::visit_negative(const Negative &n) { visit_uexpr("negative", n); } void XMLPrinter::visit_neq(const Neq &n) { visit_bexpr("neq", n); } void XMLPrinter::visit_not(const Not &n) { visit_uexpr("not", n); } void XMLPrinter::visit_number(const Number &n) { sync_to(n); o << ""; sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_or(const Or &n) { visit_bexpr("or", n); } void XMLPrinter::visit_procedurecall(const ProcedureCall &n) { sync_to(n); o << ""; sync_to(n.call); dispatch(n.call); sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_property(const Property &n) { sync_to(n); o << ""; sync_to(*n.expr); o << ""; dispatch(*n.expr); o << ""; sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_propertyrule(const PropertyRule &n) { sync_to(n); o << ""; if (!n.quantifiers.empty()) { sync_to(n.quantifiers[0]); o << ""; for (const Quantifier &q : n.quantifiers) { sync_to(q); dispatch(q); } o << ""; } sync_to(n.property); dispatch(n.property); sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_propertystmt(const PropertyStmt &n) { sync_to(n); o << ""; sync_to(n.property); dispatch(n.property); sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_put(const Put &n) { sync_to(n); o << ""; if (n.expr != nullptr) { sync_to(*n.expr); dispatch(*n.expr); } sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_quantifier(const Quantifier &n) { sync_to(n); o << ""; if (n.type != nullptr) { sync_to(*n.type); o << ""; dispatch(*n.type); o << ""; } if (n.from != nullptr) { sync_to(*n.from); o << ""; dispatch(*n.from); o << ""; } if (n.to != nullptr) { sync_to(*n.to); o << ""; dispatch(*n.to); o << ""; } if (n.step != nullptr) { sync_to(*n.step); o << ""; dispatch(*n.step); o << ""; } sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_range(const Range &n) { sync_to(n); o << ""; sync_to(*n.min); o << ""; dispatch(*n.min); o << ""; sync_to(*n.max); o << ""; dispatch(*n.max); o << ""; sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_record(const Record &n) { sync_to(n); o << ""; for (auto &f : n.fields) { sync_to(*f); dispatch(*f); } sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_return(const Return &n) { sync_to(n); o << ""; if (n.expr != nullptr) { sync_to(*n.expr); dispatch(*n.expr); } sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_rsh(const Rsh &n) { visit_bexpr("rsh", n); } void XMLPrinter::visit_ruleset(const Ruleset &n) { sync_to(n); o << ""; if (!n.quantifiers.empty()) { sync_to(n.quantifiers[0]); o << ""; for (const Quantifier &q : n.quantifiers) { sync_to(q); dispatch(q); } o << ""; } if (!n.rules.empty()) { sync_to(*n.rules[0]); o << ""; for (auto &r : n.rules) { sync_to(*r); dispatch(*r); } o << ""; } sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_scalarset(const Scalarset &n) { sync_to(n); o << ""; sync_to(*n.bound); o << ""; dispatch(*n.bound); o << ""; sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_simplerule(const SimpleRule &n) { sync_to(n); o << ""; if (!n.quantifiers.empty()) { sync_to(n.quantifiers[0]); o << ""; for (const Quantifier &q : n.quantifiers) { sync_to(q); dispatch(q); } o << ""; } if (n.guard != nullptr) { sync_to(*n.guard); o << ""; dispatch(*n.guard); o << ""; } if (!n.decls.empty()) { sync_to(*n.decls[0]); o << ""; for (auto &d : n.decls) { sync_to(*d); dispatch(*d); } o << ""; } if (!n.body.empty()) { sync_to(*n.body[0]); o << ""; for (auto &s : n.body) { sync_to(*s); dispatch(*s); } o << ""; } sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_startstate(const StartState &n) { sync_to(n); o << ""; if (!n.quantifiers.empty()) { sync_to(n.quantifiers[0]); o << ""; for (const Quantifier &q : n.quantifiers) { sync_to(q); dispatch(q); } o << ""; } if (!n.decls.empty()) { sync_to(*n.decls[0]); o << ""; for (auto &d : n.decls) { sync_to(*d); dispatch(*d); } o << ""; } if (!n.body.empty()) { sync_to(*n.body[0]); o << ""; for (auto &s : n.body) { sync_to(*s); dispatch(*s); } o << ""; } sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_sub(const Sub &n) { visit_bexpr("sub", n); } void XMLPrinter::visit_switch(const Switch &n) { sync_to(n); o << ""; sync_to(*n.expr); o << ""; dispatch(*n.expr); o << ""; if (!n.cases.empty()) { sync_to(n.cases[0]); o << ""; for (const SwitchCase &c : n.cases) { sync_to(c); dispatch(c); } o << ""; } sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_switchcase(const SwitchCase &n) { sync_to(n); o << ""; if (!n.matches.empty()) { sync_to(*n.matches[0]); o << ""; for (auto &m : n.matches) { sync_to(*m); dispatch(*m); } o << ""; } if (!n.body.empty()) { sync_to(*n.body[0]); o << ""; for (auto &s : n.body) { sync_to(*s); dispatch(*s); } o << ""; } sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_ternary(const Ternary &n) { sync_to(n); o << ""; sync_to(*n.cond); o << ""; dispatch(*n.cond); o << ""; sync_to(*n.lhs); o << ""; dispatch(*n.lhs); o << ""; sync_to(*n.rhs); o << ""; dispatch(*n.rhs); o << ""; sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_typedecl(const TypeDecl &n) { sync_to(n); o << ""; sync_to(*n.value); o << ""; dispatch(*n.value); o << ""; sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_typeexprid(const TypeExprID &n) { sync_to(n); o << ""; /* We deliberately omit n.referent because this is a result of symbol * resolution and not morally a child of this node. */ sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_undefine(const Undefine &n) { sync_to(n); o << ""; sync_to(*n.rhs); dispatch(*n.rhs); sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_vardecl(const VarDecl &n) { sync_to(n); o << ""; sync_to(*n.type); o << ""; dispatch(*n.type); o << ""; sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_while(const While &n) { sync_to(n); o << ""; sync_to(*n.condition); o << ""; dispatch(*n.condition); o << ""; if (!n.body.empty()) { sync_to(*n.body[0]); o << ""; for (auto &s : n.body) { sync_to(*s); dispatch(*s); } o << ""; } sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_xor(const Xor &n) { visit_bexpr("xor", n); } XMLPrinter::~XMLPrinter() { sync_to(); o << "\n"; o.flush(); } void XMLPrinter::sync_to(const Node &n) { sync_to(n.loc.begin); } void XMLPrinter::sync_to(const position &pos) { // the type of position.line and position.column changes across Bison // releases, so avoid some -Wsign-compare warnings by casting them in advance auto pos_line = static_cast(pos.line); auto pos_col = static_cast(pos.column); while (in.good() && (line < pos_line || (line == pos_line && column < pos_col))) { int c = in.get(); if (c == EOF) { break; } o << xml_escape(static_cast(c)); if (c == '\n') { line++; column = 1; } else { column++; } } } void XMLPrinter::add_location(const Node &n) { o << "first_line=\"" << n.loc.begin.line << "\" first_column=\"" << n.loc.begin.column << "\" last_line=\"" << n.loc.end.line << "\" last_column=\"" << n.loc.end.column << "\""; } void XMLPrinter::visit_bexpr(const std::string &tag, const BinaryExpr &n) { sync_to(n); o << "<" << tag << " "; add_location(n); o << ">"; sync_to(*n.lhs); o << ""; dispatch(*n.lhs); o << ""; sync_to(*n.rhs); o << ""; dispatch(*n.rhs); o << ""; sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_uexpr(const std::string &tag, const UnaryExpr &n) { sync_to(n); o << "<" << tag << " "; add_location(n); o << ">"; sync_to(*n.rhs); o << ""; dispatch(*n.rhs); o << ""; sync_to(n.loc.end); o << ""; } rumur-2024.05.07/murphi2xml/src/XMLPrinter.h000066400000000000000000000100231461637631000203430ustar00rootroot00000000000000#pragma once #include #include #include #include class XMLPrinter : public rumur::ConstBaseTraversal { private: std::istream ∈ std::ostream &o; unsigned long line = 1; unsigned long column = 1; public: XMLPrinter(const std::string &in_filename, std::istream &in_, std::ostream &o_); void visit_add(const rumur::Add &n) final; void visit_aliasdecl(const rumur::AliasDecl &n) final; void visit_aliasrule(const rumur::AliasRule &n) final; void visit_aliasstmt(const rumur::AliasStmt &n) final; void visit_and(const rumur::And &n) final; void visit_array(const rumur::Array &n) final; void visit_assignment(const rumur::Assignment &n) final; void visit_band(const rumur::Band &n) final; void visit_bnot(const rumur::Bnot &n) final; void visit_bor(const rumur::Bor &n) final; void visit_clear(const rumur::Clear &n) final; void visit_constdecl(const rumur::ConstDecl &n) final; void visit_div(const rumur::Div &n) final; void visit_element(const rumur::Element &n) final; void visit_enum(const rumur::Enum &n) final; void visit_eq(const rumur::Eq &n) final; void visit_errorstmt(const rumur::ErrorStmt &n) final; void visit_exists(const rumur::Exists &n) final; void visit_exprid(const rumur::ExprID &n) final; void visit_field(const rumur::Field &n) final; void visit_for(const rumur::For &n) final; void visit_forall(const rumur::Forall &n) final; void visit_function(const rumur::Function &n) final; void visit_functioncall(const rumur::FunctionCall &n) final; void visit_geq(const rumur::Geq &n) final; void visit_gt(const rumur::Gt &n) final; void visit_if(const rumur::If &n) final; void visit_ifclause(const rumur::IfClause &n) final; void visit_implication(const rumur::Implication &n) final; void visit_isundefined(const rumur::IsUndefined &n) final; void visit_leq(const rumur::Leq &n) final; void visit_lsh(const rumur::Lsh &n) final; void visit_lt(const rumur::Lt &n) final; void visit_mod(const rumur::Mod &n) final; void visit_model(const rumur::Model &n) final; void visit_mul(const rumur::Mul &n) final; void visit_negative(const rumur::Negative &n) final; void visit_neq(const rumur::Neq &n) final; void visit_not(const rumur::Not &n) final; void visit_number(const rumur::Number &n) final; void visit_or(const rumur::Or &n) final; void visit_procedurecall(const rumur::ProcedureCall &n) final; void visit_property(const rumur::Property &n) final; void visit_propertyrule(const rumur::PropertyRule &n) final; void visit_propertystmt(const rumur::PropertyStmt &n) final; void visit_put(const rumur::Put &n) final; void visit_quantifier(const rumur::Quantifier &n) final; void visit_range(const rumur::Range &n) final; void visit_record(const rumur::Record &n) final; void visit_return(const rumur::Return &n) final; void visit_rsh(const rumur::Rsh &n) final; void visit_ruleset(const rumur::Ruleset &n) final; void visit_scalarset(const rumur::Scalarset &n) final; void visit_simplerule(const rumur::SimpleRule &n) final; void visit_startstate(const rumur::StartState &n) final; void visit_sub(const rumur::Sub &n) final; void visit_switch(const rumur::Switch &n) final; void visit_switchcase(const rumur::SwitchCase &n) final; void visit_ternary(const rumur::Ternary &n) final; void visit_typedecl(const rumur::TypeDecl &n) final; void visit_typeexprid(const rumur::TypeExprID &n) final; void visit_undefine(const rumur::Undefine &n) final; void visit_vardecl(const rumur::VarDecl &n) final; void visit_while(const rumur::While &n) final; void visit_xor(const rumur::Xor &n) final; virtual ~XMLPrinter(); private: void add_location(const rumur::Node &n); void visit_bexpr(const std::string &tag, const rumur::BinaryExpr &n); void visit_uexpr(const std::string &tag, const rumur::UnaryExpr &n); void sync_to(const rumur::Node &n); void sync_to(const rumur::position &pos = rumur::position(nullptr, UINT_MAX, UINT_MAX)); }; rumur-2024.05.07/murphi2xml/src/main.cc000066400000000000000000000067231461637631000174350ustar00rootroot00000000000000#include "../../common/help.h" #include "XMLPrinter.h" #include "resources.h" #include #include #include #include #include #include #include #include #include #include #include static std::string in_filename = ""; static std::shared_ptr in; static std::shared_ptr in_replay; static std::shared_ptr out; // buffer the contents of stdin so we can read it twice static void buffer_stdin() { // read in all of stdin std::ostringstream buf; buf << std::cin.rdbuf(); buf.flush(); // put this into two buffers we can read from in = std::make_shared(buf.str()); in_replay = std::make_shared(buf.str()); } static void parse_args(int argc, char **argv) { for (;;) { static struct option options[] = { {"help", no_argument, 0, '?'}, {"output", required_argument, 0, 'o'}, {"version", no_argument, 0, 128}, {0, 0, 0, 0}, }; int option_index = 0; int c = getopt_long(argc, argv, "o:", options, &option_index); if (c == -1) break; switch (c) { case '?': help(doc_murphi2xml_1, doc_murphi2xml_1_len); exit(EXIT_SUCCESS); case 'o': { auto o = std::make_shared(optarg); if (!o->is_open()) { std::cerr << "failed to open " << optarg << "\n"; exit(EXIT_FAILURE); } out = o; break; } case 128: // --version std::cout << "Rumur version " << rumur::get_version() << "\n"; exit(EXIT_SUCCESS); default: std::cerr << "unexpected error\n"; exit(EXIT_FAILURE); } } if (optind == argc - 1) { struct stat buf; if (stat(argv[optind], &buf) < 0) { std::cerr << "failed to open " << argv[optind] << ": " << strerror(errno) << "\n"; exit(EXIT_FAILURE); } if (S_ISDIR(buf.st_mode)) { std::cerr << "failed to open " << argv[optind] << ": this is a directory\n"; exit(EXIT_FAILURE); } in_filename = argv[optind]; auto i = std::make_shared(in_filename); if (!i->is_open()) { std::cerr << "failed to open " << in_filename << "\n"; exit(EXIT_FAILURE); } in = i; // open the input again that we need for replay during XML output auto i2 = std::make_shared(in_filename); if (!i2->is_open()) { std::cerr << "failed to open " << in_filename << "\n"; exit(EXIT_FAILURE); } in_replay = i2; } else { // we are going to read data from stdin buffer_stdin(); } } int main(int argc, char **argv) { // Parse command line options parse_args(argc, argv); assert(in != nullptr); // parse input model rumur::Ptr m; try { m = rumur::parse(*in); } catch (rumur::Error &e) { std::cerr << e.loc << ":" << e.what() << "\n"; return EXIT_FAILURE; } // re-index the model to make sure AST node identifiers are ready for symbol // resolution below m->reindex(); // resolve symbolic references and validate the model try { resolve_symbols(*m); validate(*m); } catch (rumur::Error &e) { std::cerr << e.loc << ":" << e.what() << "\n"; return EXIT_FAILURE; } assert(m != nullptr); { XMLPrinter p(in_filename, *in_replay, out == nullptr ? std::cout : *out); p.dispatch(*m); } return EXIT_SUCCESS; } rumur-2024.05.07/murphi2xml/src/resources.h000066400000000000000000000001731461637631000203560ustar00rootroot00000000000000#pragma once #include extern const unsigned char doc_murphi2xml_1[]; extern const size_t doc_murphi2xml_1_len; rumur-2024.05.07/rumur/000077500000000000000000000000001461637631000144465ustar00rootroot00000000000000rumur-2024.05.07/rumur/CMakeLists.txt000066400000000000000000000055611461637631000172150ustar00rootroot00000000000000# Define resources. add_custom_command( OUTPUT resources_manpage.cc COMMAND ../misc/xxd.py doc/rumur.1 ${CMAKE_CURRENT_BINARY_DIR}/resources_manpage.cc MAIN_DEPENDENCY doc/rumur.1 DEPENDS ../misc/xxd.py WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) add_custom_command( OUTPUT resources_includes.cc COMMAND ../misc/xxd.py resources/includes.c ${CMAKE_CURRENT_BINARY_DIR}/resources_includes.cc MAIN_DEPENDENCY resources/includes.c DEPENDS ../misc/xxd.py WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) add_custom_command( OUTPUT resources_header.cc COMMAND ../misc/xxd.py resources/header.c ${CMAKE_CURRENT_BINARY_DIR}/resources_header.cc MAIN_DEPENDENCY resources/header.c DEPENDS ../misc/xxd.py WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) add_executable(rumur ${CMAKE_CURRENT_BINARY_DIR}/resources_includes.cc ${CMAKE_CURRENT_BINARY_DIR}/resources_header.cc ${CMAKE_CURRENT_BINARY_DIR}/resources_manpage.cc ../common/escape.cc ../common/help.cc src/assume-statements-count.cc src/environ.cc src/generate-allocations.cc src/generate-cover-array.cc src/generate-decl.cc src/generate-expr.cc src/generate-function.cc src/generate-model.cc src/generate-print.cc src/generate-property.cc src/generate-quantifier.cc src/generate-stmt.cc src/has-start-state.cc src/log.cc src/main.cc src/max-simple-width.cc src/optimise-field-ordering.cc src/options.cc src/output.cc src/prints-scalarsets.cc src/process.cc src/smt/define-enum-members.cc src/smt/define-records.cc src/smt/logic.cc src/smt/simplify.cc src/smt/solver.cc src/smt/translate.cc src/smt/typeexpr-to-smt.cc src/symmetry-reduction.cc src/utils.cc src/ValueType.cc) target_include_directories(rumur PRIVATE src # FIXME: This is a hack to include generated headers from the library. We # really want to be able to stage these somewhere and talk about them here # as if they were just a regular, static exported header. ${CMAKE_CURRENT_BINARY_DIR}/../librumur) target_link_libraries(rumur PRIVATE librumur) # Compress manpages add_custom_target(man-rumur ALL DEPENDS rumur.1.gz rumur-run.1.gz) add_custom_command( OUTPUT rumur.1.gz COMMAND gzip -9 --no-name --to-stdout doc/rumur.1 >"${CMAKE_CURRENT_BINARY_DIR}/rumur.1.gz" MAIN_DEPENDENCY doc/rumur.1 WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) add_custom_command( OUTPUT rumur-run.1.gz COMMAND gzip -9 --no-name --to-stdout doc/rumur-run.1 >"${CMAKE_CURRENT_BINARY_DIR}/rumur-run.1.gz" MAIN_DEPENDENCY doc/rumur-run.1 WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) install(TARGETS rumur RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) install(PROGRAMS ${CMAKE_CURRENT_SOURCE_DIR}/src/rumur-run DESTINATION ${CMAKE_INSTALL_BINDIR}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/rumur.1.gz ${CMAKE_CURRENT_BINARY_DIR}/rumur-run.1.gz DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) rumur-2024.05.07/rumur/doc/000077500000000000000000000000001461637631000152135ustar00rootroot00000000000000rumur-2024.05.07/rumur/doc/rumur-run.1000066400000000000000000000005731461637631000172560ustar00rootroot00000000000000.TH RUMUR-RUN 1 .SH NAME rumur \- Yet another explicit state model checker .SH SYNOPSIS .B \fBrumur\-run\fR \fBoptions...\fR .SH DESCRIPTION As a convenient way to run \fBrumur\fR, the wrapper \fBrumur\-run\fR is provided. It runs \fBrumur\fR with the given options, compiles the generated verifier and then runs it. See .BR rumur(1) for available options. .SH SEE ALSO rumur(1) rumur-2024.05.07/rumur/doc/rumur.1000066400000000000000000000372051461637631000164560ustar00rootroot00000000000000.TH RUMUR 1 .SH NAME rumur \- Yet another explicit state model checker .SH SYNOPSIS .B \fBrumur\fR \fBoptions\fR \fB\-\-output\fR \fIFILE\fR [\fIFILE\fR] .SH DESCRIPTION Rumur is a reimplementation of the model checker CMurphi with improved performance and a slightly different feature set. .SH OPTIONS \fB\-\-bound\fR \fISTEPS\fR .RS Set a limit for state space exploration. The verifier will stop checking beyond this depth. A bound of \fB0\fR, the default, indicates unlimited exploration. That is, the verifier will not stop checking until it has expanded all seen states. .RE .PP \fB\-\-colour\fR [\fBauto\fR | \fBoff\fR | \fBon\fR] .RS Enable or disable the use of ANSI colour codes in the verifier's output. The default is \fBauto\fR, to auto-detect based on whether the verifier's stdout is a TTY. .RE .PP \fB\-\-counterexample\-trace\fR [\fBdiff\fR | \fBfull\fR | \fBoff\fR] .RS Set how counterexample traces are printed when an error is found during checking. \fBdiff\fR, the default, prints each state showing only the differences from the previous state. \fBfull\fR shows the entire contents of each state. \fBoff\fR disables counterexample trace printing altogether. .RE .PP \fB\-\-deadlock\-detection\fR [\fBoff\fR | \fBstuck\fR | \fBstuttering\fR] .RS Enable or disable deadlock detection. Rumur has the ability to generate a verifier that notices when there is no valid transition out of a state and raise an error in this scenario. The possible modes for this check are: .RS .IP \[bu] 2 \fBoff\fR No deadlock checks are performed. .IP \[bu] \fBstuck\fR A deadlock is reached when arriving at a state from which there are no enabled transitions, and an error is signaled in this case. .IP \[bu] \fBstuttering\fR A deadlock is reached in the same circumstances as for the \fBstuck\fR option or additionally if there are enabled transitions but these all result in an identical state. For CMurphi users, this is the scenario that CMurphi considers to be a deadlock. .RE .PP This defaults to \fBstuttering\fR. However, whether such a deadlock actually represents a problem depends on the properties of the system you are modelling. Hence you may want to change deadlock detection. .RE .PP \fB\-\-debug\fR or \fB\-d\fR .RS Enable debugging options in the generated verifier. This includes enabling runtime assertions. This will also output debugging messages while generating the verifier. .RE .PP \fB\-\-help\fR .RS Display this information. .RE .PP \fB\-\-max\-errors\fR \fICOUNT\fR .RS Number of errors the verifier should report before considering them fatal. By default this is \fI1\fR, that is exit as soon as an error is encountered. However, you may wish to set a higher value to get multiple error traces from a single run. .RE .PP \fB\-\-monopolise\fR .RS Assume that the machine the generated verifier will run on is the current host and that it will be the only process running. This flag causes the verifier to upfront allocate a seen set that will eventually occupy all of memory. That is, it is the same as passing \fB\-\-set\-expand\-threshold 100\fR and \fB\-\-set\-capacity\fR with the total amount of physical memory available on the current machine. .RE .PP \fB\-\-output\fR \fIFILE\fR or \fB\-o\fR \fIFILE\fR .RS Set path to write the generated C verifier's code to. .RE .PP \fB\-\-output\-format\fR [\fBmachine\-readable\fR | \fBhuman\-readable\fR] .RS Change the format in which the verifier displays its output. By default, it uses \fBhuman\-readable\fR which results in progress output and then a final summary of the result. Using \fBmachine\-readable\fR generates output in an XML format suitable for consumption by a following tool in an I/O pipeline. .RE .PP \fB\-\-pack\-state\fR [\fBon\fR | \fBoff\fR] .RS Set whether auxiliary state data is compressed in the generated verifier. Compression (\fBon\fR, the default) saves memory at the expense of runtime. That is, by default the verifier will try to minimise memory usage. If your model is small enough to comfortably fit in available memory, you may want to set this to \fBoff\fR to accelerate the checking process. .RE .PP \fB\-\-pointer\-bits [\fBauto\fR | \fIBITS\fR] .RS Number of relevant (non-zero) bits in a pointer on the target platform on which the verifier will be compiled. This option can be used to tune pointer compression, which can save memory when checking larger models. With the default, \fBauto\fR, 5\-level paging is assumed for x86\-64, meaning pointers can be compressed and stored in 56 bits. Other platforms currently have no auto-detection, and will store pointers uncompressed at their full width. If you know a certain number of high bits of pointers on your target platform are always zero, you can teach Rumur this information with this option. For example, if you are compiling on an x86\-64 platform that you know is using 4\-level paging you can pass \fB\-\-pointer\-bits\fR \fB48\fR to tell Rumur that the upper 16 bits of a pointer will always be zero. .RE .PP \fB\-\-quiet\fR or \fB\-q\fR .RS Don't output any messages while generating the verifier. .RE .PP \fB\-\-reorder\-fields\fR [\fBon\fR | \fBoff\fR] .RS Control whether access to state variables and record fields is optimised by reordering them. By default this is \fBon\fR, causing the order of a model's state variables and fields within record types to be optimised to more likely result in naturally aligned memory accesses, which are assumed to be faster. You should never normally have cause to turn this \fBoff\fR, but this feature was buggy when first implemented so this option is provided for debugging purposes. .RE .PP \fB\-\-sandbox\fR [\fBon\fR | \fBoff\fR] .RS Control whether the generated verifier uses your operating system's sandboxing facilities to limit its own operations. The verifier does not intentionally perform any malicious or dangerous operations, but at the end of the day it is a generated program that you are going to execute. To safeguard against a bug in the code generator, it is recommended to constrain the operations the verifier is allowed to perform if you are using a model you did not write yourself. By default this is \fBoff\fR. .RE .PP \fB\-\-scalarset\-schedules\fR [\fBon\fR | \fBoff\fR] .RS Enable or disable tracking of the permutation of scalarset values for more comprehensible counterexample traces. The permuting of scalarset values that is performed during symmetry reduction leads to paths in the state space where a single scalarset identity does not have the same value throughout the trace. When this option is \fBon\fR (the default), Rumur tracks these permutations and takes them into account when printing scalarset values or reconstructing counterexample traces. The result is more intuitive and easily understandable traces. Turning this \fBoff\fR may gain runtime performance on models that use scalarsets. However counterexample traces will likely be confusing in this configuration, as scalarset variables will appear to have their values change arbitrarily. .RE .PP \fB\-\-set\-capacity\fR \fISIZE\fR or \fB\-s\fR \fISIZE\fR .RS The size of the initial set to allocate for storing seen states. This is given in bytes and is interpreted to mean the desired size of the set when it is completely full. That is, the initial allocation performed will be for a number of "state slots" that, when all occupied, will consume this much memory. Default value for this 8MB. .RE .PP \fB\-\-set\-expand\-threshold\fR \fIPERCENT\fR or \fB\-e\fR \fIPERCENT\fR .RS Expand the state set when its occupancy exceeds this percentage. Default is \fI75\fR, valid values are \fI1\fR - \fI100\fR. Setting a value of 100 will result in the set only expanding when completely full. This may sound ideal, but will actually result in a much longer runtime. .RE .PP \fB\-\-symmetry\-reduction\fR [\fBoff\fR | \fBheuristic\fR | \fBexhaustive\fR] .RS Enable or disable symmetry reduction. Symmetry reduction is an optimisation that decreases the state space that must be searched by deriving a canonical representation of each state. While two states may not be directly equal, if their canonical representations are the same only one of them need be expanded. To take advantage of this optimisation you must be using named \fBscalarset\fR types. The available options are: .RS .IP \[bu] 2 \fBoff\fR Do not use symmetry reduction. All scalarsets will be treated as if they were range types. .IP \[bu] \fBheuristic\fR Use a symmetry reduction algorithm based on sorting the state data. This is not guaranteed to find a single, canonical representation for each equivalent state, but it is fast and performs reasonably well empirically. Using this option, you may explore more states than you need to, with the advantage that you will process each individual state much faster than with \fBexhaustive\fR. This is the default. .IP \[bu] \fBexhaustive\fR Use a symmetry reduction algorithm based on exhaustive permutation of the state data. This is guaranteed to find a single, canonical representation for each equivalent state, but is typically very slow. Use this if you want to minimise memory usage at the expense of runtime. .RE .RE .PP \fB\-\-threads\fR \fICOUNT\fR or \fB\-t\fR \fICOUNT\fR .RS Specify the number of threads the verifier should use. If you do not specify this parameter or pass \fI0\fR, the number of threads will be chosen based on the available hardware threads on the platform on which you generate the model. .RE .PP \fB\-\-trace\fR \fICATEGORY\fR .RS Enable tracing of specific events while checking. This option is for debugging Rumur itself, and lets you generate a verifier that writes events to stderr. Available event categories are: .RS .IP \[bu] 2 \fIhandle_reads\fR Reads from variable handles .IP \[bu] \fIhandle_writes\fR Writes to variable handles .IP \[bu] \fImemory_usage\fR Summary of memory allocation during checking .IP \[bu] \fIqueue\fR Events relating to the pending state queue .IP \[bu] \fIset\fR Events relating to the seen state set .IP \[bu] \fIsymmetry_reduction\fR Events related to the symmetry reduction optimisation .IP \[bu] \fIall\fR Enable all of the above .RE .PP More than one of these can be enabled at once by passing the \fB\-\-trace\fR argument multiple times. Note that enabling tracing will significantly slow the verifier and is only intended for debugging purposes. .RE .PP \fB\-\-value\-type\fR \fITYPE\fR .RS Change the C type used to represent scalar values in the generated verifier. Valid values are \fIauto\fR and the C fixed-width types, \fIint8_t\fR, \fIuint8_t\fR, \fIint16_t\fR, \fIuint16_t\fR, \fIint32_t\fR, \fIuint32_t\fR, \fIint64_t\fR, and \fIuint64_t\fR. The type you select is mapped to its fast equivalent (e.g. \fIint_fast8_t\fR) and then used in the verifier. The default is \fIauto\fR that selects the narrowest type that can contain all the scalar types in use in your model. It is possible that your model does some arithmetic that temporarily exceeds the bound of any declared type in your model, in which case you will need to use this option to select a wider type. However, this is not a common case. .RE .PP \fB\-\-verbose\fR or \fB\-v\fR .RS Output informational messages while generating the verifier. .RE .PP \fB\-\-version\fR .RS Display version information and exit. .RE .SH SMT OPTIONS If you have a Satisfiability Modulo Theories (SMT) solver installed, Rumur can use it to optimise your model while generating a verifier. This functionality is not enabled by default, but you can use the following options to configure Rumur to find and use your SMT solver. Some examples of solver configuration: .PP .RS # for Z3 with a 5 second timeout .br \fBrumur \-\-smt\-path z3 \-\-smt\-arg=\-smt2 \-\-smt\-arg=\-in \-\-smt\-arg=\-t:5000 ...\fR .PP # for CVC4 with a 5 second timeout .br \fBrumur \-\-smt\-path cvc4 \-\-smt\-prelude "(set\-logic AUFLIA)" \-\-smt\-arg=\-\-lang=smt2 \-\-smt\-arg=\-\-rewrite\-divk \-\-smt\-arg=\-\-tlimit=5000 ...\fR .RE .PP For other solvers, consult their manpages or documentation to determine what command line parameters they accept. Then use the options described below to instruct Rumur how to use them. Note that Rumur can only use a single SMT solver and specifying the \fB\-\-smt\-path\fR option multiple times will only retain the last path given. .PP \fB\-\-smt\-arg\fR \fIARG\fR .RS A command line argument to pass to the SMT solver. This option can be given multiple times and arguments are passed in the order listed. E.g. if you specify \fB\-\-smt\-arg=\-\-tlimit\fR \fB\-\-smt\-arg=5000\fR the solver will be called with the command line arguments \fB\-\-tlimit\fR \fB5000\fR. .RE .PP \fB\-\-smt\-bitvectors\fR [\fBoff\fR | \fBon\fR] .RS Select whether simple types (enums, ranges, and scalarsets) are translated to bitvectors or unbounded integers when passed to the solver. By default, unbounded integers are used (\fB\-\-smt\-bitvectors off\fR). If you turn this option on, 64\-bit vectors are used instead. Whether integers, bitvectors, or both are supported will depend on your solver as well as the SMT logic you are using. .RE .PP \fB\-\-smt\-budget\fR \fIMILLISECONDS\fR .RS Total time allotted for running the SMT solver. That is, the time the solver will be allowed to run for over multiple executions. This defaults to \fI30000\fR, 30 seconds. So if the solver runs for 10 seconds the first time it is called, then 5 seconds the second time it is called, then 20 seconds the third time it is called, it will not be called again. Note that Rumur trusts the SMT solver to limit itself to a reasonable timeout per run, so its final run can exceed the budget. You may want to use the \fB\-\-smt\-arg\fR option to pass the SMT solver a timeout limit if it supports one. .RE .PP \fB\-\-smt\-path\fR \fIPATH\fR .RS Command or path to the SMT solver. This will use your environment's \fBPATH\fR variable, so if the solver is in one of your system directories you can simply provide the name of its binary. Note that this option has no effect unless you also pass \fB\-\-smt\-simplification\fR \fBon\fR. .RE .PP \fB\-\-smt\-prelude\fR \fITEXT\fR .RS Text to emit when communicating with the solver prior to sending the actual problem itself. You can use this to set a solver logic or other options. This option can be given multiple times and each argument will be passed to the solver on a separate line. .RE .PP \fB\-\-smt\-simplification\fR [\fBoff\fR | \fBon\fR] .RS Disable or enable using the SMT solver to simplify the input model. By default, this is automatic, in that it is turned \fBon\fR if you use any of the other SMT options or \fBoff\fR if you do not use them. .RE .SH AUTHOR All comments, questions and complaints should be directed to Matthew Fernandez . .SH LICENSE This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED \[lq]AS IS\[rq], WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to rumur-2024.05.07/rumur/resources/000077500000000000000000000000001461637631000164605ustar00rootroot00000000000000rumur-2024.05.07/rumur/resources/README.rst000066400000000000000000000003701461637631000201470ustar00rootroot00000000000000This directory contains static code snippets that are included in the generated checker. That is, the code in this directory is not compiled into librumur or the rumur binary. Rather it is emitted verbatim into the generated checker's source code. rumur-2024.05.07/rumur/resources/header.c000066400000000000000000003640531461637631000200670ustar00rootroot00000000000000#ifndef __OPTIMIZE__ #ifdef __clang__ #ifdef __x86_64__ #warning you are compiling without optimizations enabled. I would suggest -march=native -O3 -mcx16. #else #warning you are compiling without optimizations enabled. I would suggest -march=native -O3. #endif #else #ifdef __x86_64__ #warning you are compiling without optimizations enabled. I would suggest -march=native -O3 -fwhole-program -mcx16. #else #warning you are compiling without optimizations enabled. I would suggest -march=native -O3 -fwhole-program. #endif #endif #endif #define value_to_string(v) ((value_t)(v)) #define raw_value_to_string(v) ((raw_value_t)(v)) /* A more powerful assert that treats the assertion as an assumption when * assertions are disabled. */ #ifndef NDEBUG #define ASSERT(expr) assert(expr) #elif defined(__clang__) #define ASSERT(expr) __builtin_assume(expr) #else /* GCC doesn't have __builtin_assume, so we need something else. */ #define ASSERT(expr) \ do { \ /* The following is an idiom for teaching the compiler an assumption. */ \ if (!(expr)) { \ __builtin_unreachable(); \ } \ } while (0) #endif #define BITS_TO_BYTES(size) ((size) / 8 + ((size) % 8 == 0 ? 0 : 1)) #define BITS_FOR(value) \ ((value) == 0 ? 0 : (sizeof(unsigned long long) * 8 - __builtin_clzll(value))) /* The size of the compressed state data in bytes. */ enum { STATE_SIZE_BYTES = BITS_TO_BYTES(STATE_SIZE_BITS) }; /* the size of auxliary members of the state struct */ enum { BOUND_BITS = BITS_FOR(BOUND) }; #if COUNTEREXAMPLE_TRACE != CEX_OFF || LIVENESS_COUNT > 0 #if POINTER_BITS != 0 enum { PREVIOUS_BITS = POINTER_BITS }; #elif defined(__linux__) && defined(__x86_64__) && !defined(__ILP32__) /* assume 5-level paging, and hence the top 1 byte of any user pointer are * always 0 and not required. * https://www.kernel.org/doc/Documentation/x86/x86_64/mm.txt */ enum { PREVIOUS_BITS = 56 }; #else enum { PREVIOUS_BITS = sizeof(void *) * 8 }; #endif #else enum { PREVIOUS_BITS = 0 }; #endif #if COUNTEREXAMPLE_TRACE != CEX_OFF enum { RULE_TAKEN_BITS = BITS_FOR(RULE_TAKEN_LIMIT) }; #else enum { RULE_TAKEN_BITS = 0 }; #endif enum { STATE_OTHER_BYTES = BITS_TO_BYTES(BOUND_BITS + PREVIOUS_BITS + RULE_TAKEN_BITS + (USE_SCALARSET_SCHEDULES ? SCHEDULE_BITS : 0)) }; /* Implement _Thread_local for GCC <4.9, which is missing this. */ #if defined(__GNUC__) && defined(__GNUC_MINOR__) #if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 9) #define _Thread_local __thread #endif #endif #ifdef __clang__ #define NONNULL _Nonnull #else #define NONNULL /* nothing; other compilers don't have _Nonnull */ #endif /* A word about atomics... There are two different atomic operation mechanisms * used in this code and it may not immediately be obvious why one was not * sufficient. The two are: * * 1. GCC __atomic built-ins: Used for variables that are sometimes accessed * with atomic semantics and sometimes as regular memory operations. The * C11 atomics cannot give us this and the __atomic built-ins are * implemented by the major compilers. * 2. GCC __sync built-ins: used for 128-bit atomic accesses on x86-64 and * ARM64. * 2a. On x86-64, it seems the __atomic built-ins do not result in a * CMPXCHG instruction, but rather in a less efficient library call. * See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80878. * 2b. On ARM64, it seems the __atomic built-ins do not result in a CASP * instruction (available in ≥armv8.1-a, “Large System Extensions”), * but rather in a less efficient library call. See * https://gcc.gnu.org/pipermail/gcc-help/2017-June.txt. It seems * undocumented, but the __sync built-ins emit a CASP with * ≥-march=armv8.1a. This only affects GCC, not Clang which will emit * a CASP for both __atomic and __sync built-ins. * * Though this is intended to be a C11 program, we avoid the C11 atomics to be * compatible with GCC <4.9. */ /* Identifier of the current thread. This counts up from 0 and thus is suitable * to use for, e.g., indexing into arrays. The initial thread has ID 0. */ static _Thread_local size_t thread_id; /* The threads themselves. Note that we have no element for the initial thread, * so *your* thread is 'threads[thread_id - 1]'. */ static pthread_t threads[THREADS - 1]; /* What we are currently doing. Either "warming up" (running single threaded * building up queue occupancy) or "free running" (running multithreaded). */ static enum { WARMUP, RUN } phase = WARMUP; /* Number of errors we've noted so far. If a thread sees this hit or exceed * MAX_ERRORS, they should attempt to exit gracefully as soon as possible. */ static unsigned long error_count; /* Number of rules that have been processed. There are two representations of * this: a thread-local count of how many rules we have fired thus far and a * global array of *final* counts of fired rules per-thread that is updated and * used as threads are exiting. The purpose of this duplication is to let the * compiler layout the thread-local variable in a cache-friendly way and use * this during checking, rather than having all threads contending on the global * array whose entries are likely all within the same cache line. */ static _Thread_local uintmax_t rules_fired_local; static uintmax_t rules_fired[THREADS]; /* Checkpoint to restore to after reporting an error. This is only used if we * are tolerating more than one error before exiting. */ static _Thread_local sigjmp_buf checkpoint; _Static_assert(MAX_ERRORS > 0, "illegal MAX_ERRORS value"); /* Whether we need to save and restore checkpoints. This is determined by * whether we ever need to perform the action "discard the current state and * skip to checking the next." This scenario can occur for two reasons: * 1. We are running multithreaded, have just found an error and have not yet * hit MAX_ERRORS. In this case we want to longjmp back to resume checking. * 2. We failed an assume statement. In this case we want to mark the current * state as invalid and resume checking with the next state. * In either scenario the actual longjmp performed is the same, but by knowing * statically whether either can occur we can avoid calling setjmp if both are * impossible. */ enum { JMP_BUF_NEEDED = MAX_ERRORS > 1 || ASSUME_STATEMENTS_COUNT > 0 }; /******************************************************************************* * Sandbox support. * * * * Because we're running generated code, it seems wise to use OS mechanisms to * * reduce our privileges, where possible. * ******************************************************************************/ static void sandbox(void) { if (!SANDBOX_ENABLED) { return; } #ifdef __APPLE__ { char *err; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" int r = sandbox_init(kSBXProfilePureComputation, SANDBOX_NAMED, &err); #pragma clang diagnostic pop if (__builtin_expect(r != 0, 0)) { fprintf(stderr, "sandbox_init failed: %s\n", err); free(err); exit(EXIT_FAILURE); } return; } #endif #ifdef __FreeBSD__ { if (__builtin_expect(cap_enter() != 0, 0)) { perror("cap_enter"); exit(EXIT_FAILURE); } return; } #endif #if defined(__linux__) #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0) { /* Disable the addition of new privileges via execve and friends. */ int r = prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); if (__builtin_expect(r != 0, 0)) { perror("prctl(PR_SET_NO_NEW_PRIVS) failed"); exit(EXIT_FAILURE); } /* A BPF program that traps on any syscall we want to disallow. */ static struct sock_filter filter[] = { /* Load syscall number. */ BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)), /* Enable exiting. */ #ifdef __NR_exit_group BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_exit_group, 0, 1), BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), #endif /* Enable syscalls used by printf. */ #ifdef __NR_fstat BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_fstat, 0, 1), BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), #endif #ifdef __NR_fstat64 BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_fstat64, 0, 1), BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), #endif #ifdef __NR_newfstatat BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_newfstatat, 0, 1), BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), #endif #ifdef __NR_statx BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_statx, 0, 1), BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), #endif #ifdef __NR_write BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_write, 0, 1), BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), #endif /* Enable syscalls used by malloc. */ #ifdef __NR_brk BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_brk, 0, 1), BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), #endif #ifdef __NR_mmap BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_mmap, 0, 1), BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), #endif #ifdef __NR_mmap2 BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_mmap2, 0, 1), BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), #endif #ifdef __NR_munmap BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_munmap, 0, 1), BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), #endif /* If we're running multithreaded, enable syscalls used by pthreads. */ #ifdef __NR_clone BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_clone, 0, 1), BPF_STMT(BPF_RET | BPF_K, THREADS > 1 ? SECCOMP_RET_ALLOW : SECCOMP_RET_TRAP), #endif #ifdef __NR_close BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_close, 0, 1), BPF_STMT(BPF_RET | BPF_K, THREADS > 1 ? SECCOMP_RET_ALLOW : SECCOMP_RET_TRAP), #endif #ifdef __NR_exit BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_exit, 0, 1), BPF_STMT(BPF_RET | BPF_K, THREADS > 1 ? SECCOMP_RET_ALLOW : SECCOMP_RET_TRAP), #endif #ifdef __NR_futex BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_futex, 0, 1), BPF_STMT(BPF_RET | BPF_K, THREADS > 1 ? SECCOMP_RET_ALLOW : SECCOMP_RET_TRAP), #endif #ifdef __NR_get_robust_list BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_get_robust_list, 0, 1), BPF_STMT(BPF_RET | BPF_K, THREADS > 1 ? SECCOMP_RET_ALLOW : SECCOMP_RET_TRAP), #endif #ifdef __NR_madvise BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_madvise, 0, 1), BPF_STMT(BPF_RET | BPF_K, THREADS > 1 ? SECCOMP_RET_ALLOW : SECCOMP_RET_TRAP), #endif #ifdef __NR_mprotect BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_mprotect, 0, 1), BPF_STMT(BPF_RET | BPF_K, THREADS > 1 ? SECCOMP_RET_ALLOW : SECCOMP_RET_TRAP), #endif #ifdef __NR_open /* XXX: it would be nice to avoid open() but pthreads seems to open * libgcc. */ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_open, 0, 1), BPF_STMT(BPF_RET | BPF_K, THREADS > 1 ? SECCOMP_RET_ALLOW : SECCOMP_RET_TRAP), #endif #ifdef __NR_read BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_read, 0, 1), BPF_STMT(BPF_RET | BPF_K, THREADS > 1 ? SECCOMP_RET_ALLOW : SECCOMP_RET_TRAP), #endif #ifdef __NR_set_robust_list BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_set_robust_list, 0, 1), BPF_STMT(BPF_RET | BPF_K, THREADS > 1 ? SECCOMP_RET_ALLOW : SECCOMP_RET_TRAP), #endif /* on platforms without vDSO support, time() makes an actual syscall, so * we need to allow them */ #ifdef __NR_clock_gettime BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_clock_gettime, 0, 1), BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), #endif #ifdef __NR_clock_gettime64 BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_clock_gettime64, 0, 1), BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), #endif #ifdef __NR_gettimeofday BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_gettimeofday, 0, 1), BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), #endif #ifdef __NR_time BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_time, 0, 1), BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), #endif /* When `getrandom` is available, it is used during initialisation. */ #ifdef __NR_getrandom BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_getrandom, 0, 1), BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), #endif /* Deny everything else. On a disallowed syscall, we trap instead of * killing to allow the user to debug the failure. If you are debugging * seccomp denials, strace the checker and find the number of the denied * syscall in the first si_value parameter reported in the terminating * SIG_SYS. */ BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_TRAP), }; static const struct sock_fprog filter_program = { .len = sizeof(filter) / sizeof(filter[0]), .filter = filter, }; /* Apply the above filter to ourselves. */ r = prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &filter_program, 0, 0); if (__builtin_expect(r != 0, 0)) { perror("prctl(PR_SET_SECCOMP) failed"); exit(EXIT_FAILURE); } return; } #endif #endif #ifdef __OpenBSD__ { if (__builtin_expect(pledge("stdio", "") != 0, 0)) { perror("pledge"); exit(EXIT_FAILURE); } return; } #endif /* No sandbox available. */ fprintf(stderr, "no sandboxing facilities available\n"); exit(EXIT_FAILURE); } /******************************************************************************/ /* ANSI colour code support */ static bool istty; static const char *green() { if (COLOR == ON || (COLOR == AUTO && istty)) return "\033[32m"; return ""; } static const char *red() { if (COLOR == ON || (COLOR == AUTO && istty)) return "\033[31m"; return ""; } static const char *yellow() { if (COLOR == ON || (COLOR == AUTO && istty)) return "\033[33m"; return ""; } static const char *bold() { if (COLOR == ON || (COLOR == AUTO && istty)) return "\033[1m"; return ""; } static const char *reset() { if (COLOR == ON || (COLOR == AUTO && istty)) return "\033[0m"; return ""; } #ifdef __SIZEOF_INT128__ /* if we have the type `__int128` */ #define UINT128_MAX \ ((((unsigned __int128)UINT64_MAX) << 64) | ((unsigned __int128)UINT64_MAX)) #define INT128_MAX ((((__int128)INT64_MAX) << 64) | ((__int128)UINT64_MAX)) #define INT128_MIN (-INT128_MAX - 1) struct string_buffer { char data[41]; }; static struct string_buffer value_u128_to_string(unsigned __int128 v) { struct string_buffer buffer; if (v == 0) { buffer.data[0] = '0'; buffer.data[1] = '\0'; return buffer; } size_t i = sizeof(buffer.data); while (v != 0) { i--; buffer.data[i] = '0' + v % 10; v /= 10; } memmove(buffer.data, &buffer.data[i], sizeof(buffer) - i); buffer.data[sizeof(buffer) - i] = '\0'; return buffer; } static __attribute__((unused)) struct string_buffer value_128_to_string(__int128 v) { if (v == INT128_MIN) { struct string_buffer buffer; strcpy(buffer.data, "-170141183460469231731687303715884105728"); return buffer; } bool negative = v < 0; if (negative) { v = -v; } struct string_buffer buffer = value_u128_to_string(v); if (negative) { memmove(&buffer.data[1], buffer.data, strlen(buffer.data) + 1); buffer.data[0] = '-'; } return buffer; } #endif /* Is value_t a signed type? We need this as a function because value_t is a * typedef that can be tweaked by the user. */ static __attribute__((const)) bool value_is_signed(void) { #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wtautological-compare" #pragma clang diagnostic ignored "-Wtautological-unsigned-zero-compare" #elif defined(__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wtype-limits" #endif return (value_t)-1 < 0; #ifdef __clang__ #pragma clang diagnostic pop #elif defined(__GNUC__) #pragma GCC diagnostic pop #endif } /******************************************************************************* * MurmurHash by Austin Appleby * * * * More information on this at https://github.com/aappleby/smhasher/ * ******************************************************************************/ static uint64_t MurmurHash64A(const void *NONNULL key, size_t len) { static const uint64_t seed = 0; static const uint64_t m = UINT64_C(0xc6a4a7935bd1e995); static const unsigned r = 47; uint64_t h = seed ^ (len * m); const unsigned char *data = key; const unsigned char *end = data + len / sizeof(uint64_t) * sizeof(uint64_t); while (data != end) { uint64_t k; memcpy(&k, data, sizeof(k)); data += sizeof(k); k *= m; k ^= k >> r; k *= m; h ^= k; h *= m; } const unsigned char *data2 = data; switch (len & 7) { case 7: h ^= (uint64_t)data2[6] << 48; /* fall through */ case 6: h ^= (uint64_t)data2[5] << 40; /* fall through */ case 5: h ^= (uint64_t)data2[4] << 32; /* fall through */ case 4: h ^= (uint64_t)data2[3] << 24; /* fall through */ case 3: h ^= (uint64_t)data2[2] << 16; /* fall through */ case 2: h ^= (uint64_t)data2[1] << 8; /* fall through */ case 1: h ^= (uint64_t)data2[0]; h *= m; } h ^= h >> r; h *= m; h ^= h >> r; return h; } /******************************************************************************/ /* Signal an out-of-memory condition and terminate abruptly. */ static _Noreturn void oom(void) { fputs("out of memory", stderr); exit(EXIT_FAILURE); } static void *xmalloc(size_t size) { void *p = malloc(size); if (__builtin_expect(p == NULL, 0)) { oom(); } return p; } static void *xcalloc(size_t count, size_t size) { void *p = calloc(count, size); if (__builtin_expect(p == NULL, 0)) { oom(); } return p; } static void put(const char *NONNULL s) { for (; *s != '\0'; ++s) { putchar_unlocked(*s); } } static void put_int(intmax_t u) { char buffer[128] = {0}; snprintf(buffer, sizeof(buffer), "%" PRIdMAX, u); put(buffer); } static void put_uint(uintmax_t u) { char buffer[128] = {0}; snprintf(buffer, sizeof(buffer), "%" PRIuMAX, u); put(buffer); } static __attribute__((unused)) void put_val(value_t v) { if (value_is_signed()) { put_int((intmax_t)v); } else { put_uint((uintmax_t)v); } } static void xml_printf(const char *NONNULL s) { while (*s != '\0') { switch (*s) { case '"': put("""); break; case '<': put("<"); break; case '>': put(">"); break; case '&': put("&"); break; default: putchar_unlocked(*s); break; } s++; } } /* Support for tracing specific operations. This can be enabled during checker * generation with '--trace ...' and is useful for debugging Rumur itself. */ static __attribute__((format(printf, 1, 2))) void trace(const char *NONNULL fmt, ...) { va_list ap; va_start(ap, fmt); flockfile(stdout); (void)fprintf(stderr, "%sTRACE%s:", yellow(), reset()); (void)vfprintf(stderr, fmt, ap); (void)fprintf(stderr, "\n"); funlockfile(stdout); va_end(ap); } /* Wrap up trace() as a macro. It looks as if the following could just be * incorporated into trace(). However, present compilers seem unwilling to * inline varargs functions or do interprocedural analysis across a call to one. * As a result, the compiler does not notice when tracing is disabled and a call * to trace() would be a no-op that can be elided. By making the call a macro we * make the category comparison visible to the compiler's optimising passes. */ #define TRACE(category, args...) \ do { \ if ((category) & TRACES_ENABLED) { \ trace(args); \ } \ } while (0) /******************************************************************************* * Arithmetic wrappers * * * * For compilers that support them, we call the overflow built-ins to check * * undefined operations during arithmetic. For others, we just emit the bare * * operation. * ******************************************************************************/ #if defined(__clang__) || (defined(__GNUC__) && __GNUC__ >= 5) #define ADD(a, b, c) __builtin_add_overflow((a), (b), (c)) #define MUL(a, b, c) __builtin_mul_overflow((a), (b), (c)) #define SUB(a, b, c) __builtin_sub_overflow((a), (b), (c)) #else #define ADD(a, b, c) ({ *(c) = (a) + (b); false; }) #define MUL(a, b, c) ({ *(c) = (a) * (b); false; }) #define SUB(a, b, c) ({ *(c) = (a) - (b); false; }) #endif /******************************************************************************/ /* The state of the current model. */ struct state { #if LIVENESS_COUNT > 0 uintptr_t liveness[LIVENESS_COUNT / sizeof(uintptr_t) / CHAR_BIT + (LIVENESS_COUNT % sizeof(uintptr_t) == 0 && LIVENESS_COUNT / sizeof(uintptr_t) % CHAR_BIT == 0 ? 0 : 1)]; #endif uint8_t data[STATE_SIZE_BYTES]; #if PACK_STATE /* the following effective fields are packed into here: * * * uint64_t bound; * * const struct state *previous; * * uint64_t rule_taken; * * uint8_t schedules[BITS_TO_BYTES(SCHEDULE_BITS)]; * * They are bit-packed, so may take up less space. E.g. if the maximum value * of `bound` is known to be 5, it will be stored in 3 bits instead of 64. */ uint8_t other[STATE_OTHER_BYTES]; #else uint64_t bound; const struct state *previous; uint64_t rule_taken; uint8_t schedules[USE_SCALARSET_SCHEDULES ? BITS_TO_BYTES(SCHEDULE_BITS) : 0]; #endif }; struct handle { uint8_t *base; size_t offset; size_t width; }; static struct handle handle_align(struct handle h) { ASSERT(h.offset < CHAR_BIT && "handle has an offset outside the base byte"); size_t width = h.width + h.offset; if (width % 8 != 0) { width += 8 - width % 8; } return (struct handle){ .base = h.base, .offset = 0, .width = width, }; } static bool is_big_endian(void) { union { uint32_t x; uint8_t y[sizeof(uint32_t)]; } z; z.y[0] = 1; z.y[1] = 2; z.y[2] = 3; z.y[3] = 4; return z.x == 0x01020304; } static uint64_t copy_out64(const uint8_t *p, size_t extent) { ASSERT(extent <= sizeof(uint64_t)); uint64_t x = 0; memcpy(&x, p, extent); if (is_big_endian()) { x = __builtin_bswap64(x); } return x; } #ifdef __SIZEOF_INT128__ /* if we have the type `__int128` */ static unsigned __int128 byte_swap128(unsigned __int128 x) { union { unsigned __int128 a; uint64_t b[2]; } in, out; in.a = x; out.b[0] = __builtin_bswap64(in.b[1]); out.b[1] = __builtin_bswap64(in.b[0]); return out.a; } static unsigned __int128 copy_out128(const uint8_t *p, size_t extent) { ASSERT(extent <= sizeof(unsigned __int128)); unsigned __int128 x = 0; memcpy(&x, p, extent); if (is_big_endian()) { x = byte_swap128(x); } return x; } #endif /* If you are in the Rumur repository modifying the following function, remember * to also update ../../misc/read-raw.smt2. */ static __attribute__((pure)) uint64_t read_raw(struct handle h) { /* a uint64_t is the maximum value we support reading */ ASSERT(h.width <= 64 && "read of too wide value"); if (h.width == 0) { return 0; } /* Generate a handle that is offset- and width-aligned on byte boundaries. * Essentially, we widen the handle to align it. The motivation for this is * that we can only do byte-granularity reads, so we need to "over-read" if we * have an unaligned handle. */ struct handle aligned = handle_align(h); ASSERT(aligned.offset == 0); /* The code below attempts to provide four alternatives for reading out the * bits corresponding to a value of simple type referenced by a handle, and to * give the compiler enough hints to steer it towards picking one of these and * removing the other three as dead code: * * 1. Read into a single 64-bit variable. Enabled when the maximum width for * an unaligned handle spans 0 - 8 bytes. * 2. Read into two 64-bit variables and then combine the result using * shifts and ORs. Enabled when the maximum width for an unaligned handle * spans 9 - 16 bytes and the compiler does not provide the `__int128` * type. * 3. Read into a single 128-bit variable. Enabled when the compiler does * provide the `__int128` type and the maximum width for an unaligned * handle spans 9 - 16 bytes. * 4. Read into two 128-bit chunks, and then combine the result using shifts * and ORs. Enabled when the compiler provides the `__int128` type and * the maximum width for an unaligned handle spans 17 - 32 bytes. */ #ifdef __SIZEOF_INT128__ /* if we have the type `__int128` */ /* If a byte-unaligned value_t cannot be fully read into a single uint64_t * using byte-aligned reads... */ if (aligned.width > (sizeof(uint64_t) - 1) * 8) { /* Read the low double-word of this (possibly quad-word-sized) value. */ unsigned __int128 low = 0; size_t low_size = aligned.width / 8; /* optimisation hint: */ ASSERT(low_size <= sizeof(low) || aligned.width > (sizeof(low) - 1) * 8); if (low_size > sizeof(low)) { low_size = sizeof(low); } { const uint8_t *src = aligned.base; low = copy_out128(src, low_size); } low >>= h.offset; size_t high_size = aligned.width / 8 - low_size; /* If the value could not be read into a single double-word... */ ASSERT(high_size == 0 || aligned.width > (sizeof(low) - 1) * 8); if (high_size != 0) { unsigned __int128 high = 0; const uint8_t *src = aligned.base + sizeof(low); high = copy_out128(src, high_size); high <<= sizeof(low) * 8 - h.offset; /* Combine the two halves into a single double-word. */ low |= high; } if (h.width < sizeof(low) * 8) { unsigned __int128 mask = (((unsigned __int128)1) << h.width) - 1; low &= mask; } ASSERT(low <= UINT64_MAX && "read of value larger than a uint64_t"); return (uint64_t)low; } #endif /* Read the low word of this (possibly two-word-sized) value. */ uint64_t low = 0; size_t low_size = aligned.width / 8; /* optimisation hint: */ ASSERT(low_size <= sizeof(low) || aligned.width > (sizeof(low) - 1) * 8); if (low_size > sizeof(low)) { low_size = sizeof(low); } { const uint8_t *src = aligned.base; low = copy_out64(src, low_size); } low >>= h.offset; size_t high_size = aligned.width / 8 - low_size; /* If the value could not be read into a single word... */ ASSERT(high_size == 0 || aligned.width > (sizeof(low) - 1) * 8); if (high_size != 0) { const uint8_t *src = aligned.base + sizeof(low); uint64_t high = copy_out64(src, high_size); high <<= sizeof(low) * 8 - h.offset; /* Combine the high and low words. Note that we know we can store the final * result in a single word because the width is guaranteed to be <= 64. */ low |= high; } if (h.width < sizeof(low) * 8) { uint64_t mask = (UINT64_C(1) << h.width) - 1; low &= mask; } return low; } static void copy_in64(uint8_t *p, uint64_t v, size_t extent) { ASSERT(extent <= sizeof(v)); if (is_big_endian()) { v = __builtin_bswap64(v); } memcpy(p, &v, extent); } #ifdef __SIZEOF_INT128__ /* if we have the type `__int128` */ static void copy_in128(uint8_t *p, unsigned __int128 v, size_t extent) { ASSERT(extent <= sizeof(v)); if (is_big_endian()) { v = byte_swap128(v); } memcpy(p, &v, extent); } #endif /* If you are in the Rumur repository modifying the following function, remember * to also update ../../misc/write-raw.smt2. */ static void write_raw(struct handle h, uint64_t v) { if (h.width == 0) { return; } /* sanitise input value */ if (h.width < sizeof(v) * 8) { v &= (UINT64_C(1) << h.width) - 1; } /* Generate a offset- and width-aligned handle on byte boundaries. */ struct handle aligned = handle_align(h); ASSERT(aligned.offset == 0); #ifdef __SIZEOF_INT128__ /* if we have the type `__int128` */ /* If a byte-unaligned value_t cannot be fully written within a single * byte-aligned uint64_t... */ if (aligned.width > (sizeof(uint64_t) - 1) * 8) { /* Read the low double-word of this region. */ unsigned __int128 low = 0; size_t low_size = aligned.width / 8; ASSERT(low_size <= sizeof(low) || aligned.width > (sizeof(low) - 1) * 8); if (low_size > sizeof(low)) { low_size = sizeof(low); } { const uint8_t *src = aligned.base; low = copy_out128(src, low_size); } { unsigned __int128 or_mask = ((unsigned __int128)v) << h.offset; if (low_size < sizeof(low)) { or_mask &= (((unsigned __int128)1) << (low_size * 8)) - 1; } unsigned __int128 and_mask = (((unsigned __int128)1) << h.offset) - 1; if (h.width + h.offset < sizeof(low) * 8) { size_t high_bits = aligned.width - h.offset - h.width; and_mask |= ((((unsigned __int128)1) << high_bits) - 1) << (low_size * 8 - high_bits); } low = (low & and_mask) | or_mask; } { uint8_t *dest = aligned.base; copy_in128(dest, low, low_size); } /* Now do the second double-word if necessary. */ size_t high_size = aligned.width / 8 - low_size; ASSERT(high_size == 0 || aligned.width > (sizeof(low) - 1) * 8); if (high_size != 0) { unsigned __int128 high = 0; { const uint8_t *src = aligned.base + sizeof(low); high = copy_out128(src, high_size); } { unsigned __int128 or_mask = ((unsigned __int128)v) >> (sizeof(low) * 8 - h.offset); unsigned __int128 and_mask = ~( (((unsigned __int128)1) << (h.width + h.offset - sizeof(low) * 8)) - 1); high = (high & and_mask) | or_mask; } { uint8_t *dest = aligned.base + sizeof(low); copy_in128(dest, high, high_size); } } return; } #endif /* Replicate the above logic for uint64_t. */ uint64_t low = 0; size_t low_size = aligned.width / 8; ASSERT(low_size <= sizeof(low) || aligned.width > (sizeof(low) - 1) * 8); if (low_size > sizeof(low)) { low_size = sizeof(low); } { const uint8_t *src = aligned.base; low = copy_out64(src, low_size); } { uint64_t or_mask = ((uint64_t)v) << h.offset; if (low_size < sizeof(low)) { or_mask &= (UINT64_C(1) << (low_size * 8)) - 1; } uint64_t and_mask = (UINT64_C(1) << h.offset) - 1; if (h.width + h.offset < sizeof(low) * 8) { size_t high_bits = aligned.width - h.offset - h.width; and_mask |= ((UINT64_C(1) << high_bits) - 1) << (low_size * 8 - high_bits); } low = (low & and_mask) | or_mask; } { uint8_t *dest = aligned.base; copy_in64(dest, low, low_size); } size_t high_size = aligned.width / 8 - low_size; ASSERT(high_size == 0 || aligned.width > (sizeof(low) - 1) * 8); if (high_size != 0) { uint64_t high = 0; { const uint8_t *src = aligned.base + sizeof(low); high = copy_out64(src, high_size); } { uint64_t or_mask = ((uint64_t)v) >> (sizeof(low) * 8 - h.offset); uint64_t and_mask = ~((UINT64_C(1) << (h.width + h.offset - sizeof(low) * 8)) - 1); high = (high & and_mask) | or_mask; } { uint8_t *dest = aligned.base + sizeof(low); copy_in64(dest, high, high_size); } } } #if BOUND > 0 #if PACK_STATE static struct handle state_bound_handle(const struct state *NONNULL s) { struct handle h = (struct handle){ .base = (uint8_t *)s->other, .offset = 0, .width = BITS_FOR(BOUND), }; return h; } #endif _Static_assert((uintmax_t)BOUND <= UINT64_MAX, "bound limit does not fit in a uint64_t"); static __attribute__((pure)) uint64_t state_bound_get(const struct state *NONNULL s) { assert(s != NULL); #if PACK_STATE struct handle h = state_bound_handle(s); return read_raw(h); #else return s->bound; #endif } static void state_bound_set(struct state *NONNULL s, uint64_t bound) { assert(s != NULL); #if PACK_STATE struct handle h = state_bound_handle(s); write_raw(h, bound); #else s->bound = bound; #endif } #endif #if COUNTEREXAMPLE_TRACE != CEX_OFF || LIVENESS_COUNT > 0 #if PACK_STATE static struct handle state_previous_handle(const struct state *NONNULL s) { size_t offset = BOUND_BITS; struct handle h = (struct handle){ .base = (uint8_t *)s->other + offset / 8, .offset = offset % 8, .width = PREVIOUS_BITS, }; return h; } #endif static __attribute__((pure)) const struct state * state_previous_get(const struct state *NONNULL s) { #if PACK_STATE struct handle h = state_previous_handle(s); return (const struct state *)(uintptr_t)read_raw(h); #else return s->previous; #endif } static void state_previous_set(struct state *NONNULL s, const struct state *previous) { #if PACK_STATE ASSERT( (PREVIOUS_BITS == sizeof(void *) * 8 || ((uintptr_t)previous >> PREVIOUS_BITS) == 0) && "upper bits of pointer are non-zero (incorrect --pointer-bits setting?)"); struct handle h = state_previous_handle(s); write_raw(h, (uint64_t)(uintptr_t)previous); #else s->previous = previous; #endif } #endif #if COUNTEREXAMPLE_TRACE != CEX_OFF #if PACK_STATE static struct handle state_rule_taken_handle(const struct state *NONNULL s) { size_t offset = BOUND_BITS + PREVIOUS_BITS; struct handle h = (struct handle){ .base = (uint8_t *)s->other + offset / 8, .offset = offset % 8, .width = RULE_TAKEN_BITS, }; return h; } #endif static __attribute__((pure)) uint64_t state_rule_taken_get(const struct state *NONNULL s) { assert(s != NULL); #if PACK_STATE struct handle h = state_rule_taken_handle(s); return read_raw(h); #else return s->rule_taken; #endif } static void state_rule_taken_set(struct state *NONNULL s, uint64_t rule_taken) { assert(s != NULL); #if PACK_STATE struct handle h = state_rule_taken_handle(s); write_raw(h, rule_taken); #else s->rule_taken = rule_taken; #endif } #endif static struct handle state_schedule_handle(const struct state *NONNULL s, size_t offset, size_t width) { /* the maximum schedule width handle ever derived should be SCHEDULE_BITS, and * should only ever occur when writing the entire schedule */ #if !defined(__clang__) && defined(__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wtype-limits" #endif ASSERT(width <= SCHEDULE_BITS && (width < SCHEDULE_BITS || offset == 0) && "out-of-bounds handle derived to access schedule data"); #if !defined(__clang__) && defined(__GNUC__) #pragma GCC diagnostic pop #endif uint8_t *b; size_t o; #if PACK_STATE b = (uint8_t *)s->other; o = BOUND_BITS + PREVIOUS_BITS + RULE_TAKEN_BITS + offset; #else b = (uint8_t *)s->schedules; o = offset; #endif struct handle h = (struct handle){ .base = (uint8_t *)b + o / 8, .offset = o % 8, .width = width, }; return h; } static __attribute__((pure, unused)) size_t state_schedule_get(const struct state *NONNULL s, size_t offset, size_t width) { assert(s != NULL); struct handle h = state_schedule_handle(s, offset, width); return (size_t)read_raw(h); } static __attribute__((unused)) void state_schedule_set(struct state *NONNULL s, size_t offset, size_t width, size_t v) { assert(s != NULL); /* we should never attempt to write an invalid schedule index that will be * truncated */ if (width < 64) { ASSERT((((UINT64_C(1) << width) - 1) & (uint64_t)v) == (uint64_t)v && "truncation in writing schedule data to state"); } struct handle h = state_schedule_handle(s, offset, width); write_raw(h, (uint64_t)v); } /******************************************************************************* * State allocator. * * * * The following implements a simple bump allocator for states. The purpose of * * this (rather than simply mallocing individual states) is to speed up * * allocation by taking global locks less frequently and decrease allocator * * metadata overhead. * ******************************************************************************/ /* An initial size of thread-local allocator pools ~8MB. */ static _Thread_local size_t arena_count = (sizeof(struct state) > 8 * 1024 * 1024) ? 1 : (8 * 1024 * 1024 / sizeof(struct state)); static _Thread_local struct state *arena_base; static _Thread_local struct state *arena_limit; static struct state *state_new(void) { if (arena_base == arena_limit) { /* Allocation pool is empty. We need to set up a new pool. */ for (;;) { if (arena_count == 1) { arena_base = xmalloc(sizeof(*arena_base)); } else { arena_base = calloc(arena_count, sizeof(*arena_base)); if (__builtin_expect(arena_base == NULL, 0)) { /* Memory pressure high. Decrease our attempted allocation and try * again. */ arena_count /= 2; continue; } } arena_limit = arena_base + arena_count; break; } } assert(arena_base != NULL); assert(arena_base != arena_limit); struct state *s = arena_base; arena_base++; return s; } static void state_free(struct state *s) { if (s == NULL) { return; } assert(s + 1 == arena_base); arena_base--; } /******************************************************************************* * statistics for memory usage * * * * This functionality is only used when `--trace memory_usage` is given on the * * command line. * ******************************************************************************/ /* number of allocated state structs per depth of expansion */ static size_t allocated[BOUND == 0 ? 1 : (BOUND + 1)]; /* note a new allocation of a state struct at the given depth */ static void register_allocation(size_t depth) { /* if we are not tracing memory usage, make this a no-op */ if (!(TC_MEMORY_USAGE & TRACES_ENABLED)) { return; } ASSERT(depth < sizeof(allocated) / sizeof(allocated[0]) && "out of range access to allocated array"); /* increment the number of known allocated states, avoiding an expensive * atomic if we are single-threaded */ if (THREADS == 1) { allocated[depth]++; } else { (void)__sync_add_and_fetch(&allocated[depth], 1); } } /* print a summary of the allocation results we have accrued */ static void print_allocation_summary(void) { /* it is assumed we are running single-threaded here and do not need atomic * accesses */ if (BOUND == 0) { TRACE(TC_MEMORY_USAGE, "allocated %zu state structure(s), totaling %zu bytes", allocated[0], allocated[0] * sizeof(struct state)); } else { for (size_t i = 0; i < sizeof(allocated) / sizeof(allocated[0]); i++) { if (allocated[i] == 0) { /* no state at this depth was reached, therefore no states at deeper * depths were reached either and we are done */ for (size_t j = i + 1; j < sizeof(allocated) / sizeof(allocated[0]); j++) { assert(allocated[j] == 0 && "state allocated at a deeper depth than an empty level"); } break; } TRACE(TC_MEMORY_USAGE, "depth %zu: allocated %zu state structure(s), totaling %zu bytes", i, allocated[i], allocated[i] * sizeof(struct state)); } } } /******************************************************************************/ /* Print a counterexample trace terminating at the given state. This function * assumes that the caller already holds a lock on stdout. */ static void print_counterexample(const struct state *NONNULL s __attribute__((unused))); /* "Exit" the current thread. This takes into account which thread we are. I.e. * the correct way to exit the checker is for every thread to eventually call * this function. */ static _Noreturn int exit_with(int status); static __attribute__((format(printf, 2, 3))) _Noreturn void error(const struct state *NONNULL s, const char *NONNULL fmt, ...) { unsigned long prior_errors = __atomic_fetch_add(&error_count, 1, __ATOMIC_SEQ_CST); if (__builtin_expect(prior_errors < MAX_ERRORS, 1)) { flockfile(stdout); va_list ap; va_start(ap, fmt); if (MACHINE_READABLE_OUTPUT) { put("\n"); put(""); { va_list ap2; va_copy(ap2, ap); int size = vsnprintf(NULL, 0, fmt, ap2); va_end(ap2); if (__builtin_expect(size < 0, 0)) { fputs("vsnprintf failed", stderr); exit(EXIT_FAILURE); } char *buffer = xmalloc(size + 1); if (__builtin_expect(vsnprintf(buffer, size + 1, fmt, ap) != size, 0)) { fputs("vsnprintf failed", stderr); exit(EXIT_FAILURE); } xml_printf(buffer); free(buffer); } put("\n"); if (s != NULL && COUNTEREXAMPLE_TRACE != CEX_OFF) { print_counterexample(s); } put("\n"); } else { if (s != NULL) { put("The following is the error trace for the error:\n\n"); } else { put("Result:\n\n"); } put("\t"); put(red()); put(bold()); { va_list ap2; va_copy(ap2, ap); int size = vsnprintf(NULL, 0, fmt, ap2); va_end(ap2); if (__builtin_expect(size < 0, 0)) { fputs("vsnprintf failed", stderr); exit(EXIT_FAILURE); } char *buffer = xmalloc(size + 1); if (__builtin_expect(vsnprintf(buffer, size + 1, fmt, ap) != size, 0)) { fputs("vsnprintf failed", stderr); exit(EXIT_FAILURE); } put(buffer); free(buffer); } put(reset()); put("\n\n"); if (s != NULL && COUNTEREXAMPLE_TRACE != CEX_OFF) { print_counterexample(s); put("End of the error trace.\n\n"); } } va_end(ap); funlockfile(stdout); } #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wtautological-compare" #pragma clang diagnostic ignored "-Wtautological-unsigned-zero-compare" #elif defined(__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wtype-limits" #endif if (prior_errors < MAX_ERRORS - 1) { #ifdef __clang__ #pragma clang diagnostic pop #elif defined(__GNUC__) #pragma GCC diagnostic pop #endif assert(JMP_BUF_NEEDED && "longjmping without a setup jmp_buf"); siglongjmp(checkpoint, 1); } exit_with(EXIT_FAILURE); } static void deadlock(const struct state *NONNULL s) { if (JMP_BUF_NEEDED) { if (sigsetjmp(checkpoint, 0)) { /* error() longjmped back to us. */ return; } } error(s, "deadlock"); } static int state_cmp(const struct state *NONNULL a, const struct state *NONNULL b) { return memcmp(a->data, b->data, sizeof(a->data)); } static bool state_eq(const struct state *NONNULL a, const struct state *NONNULL b) { return state_cmp(a, b) == 0; } static void handle_copy(struct handle a, struct handle b); static struct state *state_dup(const struct state *NONNULL s) { struct state *n = state_new(); memcpy(n->data, s->data, sizeof(n->data)); #if COUNTEREXAMPLE_TRACE != CEX_OFF || LIVENESS_COUNT > 0 state_previous_set(n, s); #endif #if BOUND > 0 assert(state_bound_get(s) < BOUND && "exceeding bounded exploration depth"); state_bound_set(n, state_bound_get(s) + 1); #endif #if LIVENESS_COUNT > 0 memset(n->liveness, 0, sizeof(n->liveness)); #endif if (USE_SCALARSET_SCHEDULES) { /* copy schedule data related to past scalarset permutations */ struct handle sch_src = state_schedule_handle(s, 0, SCHEDULE_BITS); struct handle sch_dst = state_schedule_handle(n, 0, SCHEDULE_BITS); handle_copy(sch_dst, sch_src); } return n; } static size_t state_hash(const struct state *NONNULL s) { return (size_t)MurmurHash64A(s->data, sizeof(s->data)); } #if COUNTEREXAMPLE_TRACE != CEX_OFF static __attribute__((unused)) size_t state_depth(const struct state *NONNULL s) { #if BOUND > 0 uint64_t bound = state_bound_get(s); ASSERT(bound <= BOUND && "claimed state bound exceeds limit"); return (size_t)bound + 1; #else size_t d = 0; while (s != NULL) { d++; s = state_previous_get(s); } return d; #endif } #endif /* A type-safe const cast. */ static __attribute__((unused)) struct state * state_drop_const(const struct state *s) { return (struct state *)s; } /* These functions are generated. */ static void state_canonicalise_heuristic(struct state *NONNULL s); static void state_canonicalise_exhaustive(struct state *NONNULL s); static void state_canonicalise(struct state *NONNULL s) { assert(s != NULL && "attempt to canonicalise NULL state"); switch (SYMMETRY_REDUCTION) { case SYMMETRY_REDUCTION_OFF: break; case SYMMETRY_REDUCTION_HEURISTIC: state_canonicalise_heuristic(s); break; case SYMMETRY_REDUCTION_EXHAUSTIVE: state_canonicalise_exhaustive(s); break; } } /* This function is generated. */ static __attribute__((unused)) void state_print_field_offsets(void); /* Print a state to stderr. This function is generated. This function assumes * that the caller already holds a lock on stdout. */ static __attribute__((unused)) void state_print(const struct state *previous, const struct state *NONNULL s); /* Print the first rule that resulted in s. This function is generated. This * function assumes that the caller holds a lock on stdout. */ static __attribute__((unused)) void print_transition(const struct state *NONNULL s); static void print_counterexample(const struct state *NONNULL s __attribute__((unused))) { assert(s != NULL && "missing state in request for counterexample trace"); #if COUNTEREXAMPLE_TRACE != CEX_OFF /* Construct an array of the states we need to print by walking backwards to * the initial starting state. */ size_t trace_length = state_depth(s); const struct state **cex = xcalloc(trace_length, sizeof(cex[0])); { size_t i = trace_length - 1; for (const struct state *p = s; p != NULL; p = state_previous_get(p)) { assert(i < trace_length && "error in counterexample trace traversal logic"); cex[i] = p; i--; } } for (size_t i = 0; i < trace_length; i++) { const struct state *current = cex[i]; const struct state *previous = i == 0 ? NULL : cex[i - 1]; print_transition(current); if (MACHINE_READABLE_OUTPUT) { put("\n"); } state_print(COUNTEREXAMPLE_TRACE == FULL ? NULL : previous, current); if (MACHINE_READABLE_OUTPUT) { put("\n"); } else { put("----------\n\n"); } } free(cex); #endif } static __attribute__((unused)) struct handle state_handle(const struct state *NONNULL s, size_t offset, size_t width) { assert(sizeof(s->data) * CHAR_BIT - width >= offset && "generating an out of bounds handle in state_handle()"); return (struct handle){ .base = (uint8_t *)s->data + offset / CHAR_BIT, .offset = offset % CHAR_BIT, .width = width, }; } static raw_value_t handle_read_raw(const struct state *NONNULL s, struct handle h) { /* Check if this read is larger than the variable we will store it in. This * can only occur if the user has manually overridden value_t with the * --value-type command line argument. */ if (__builtin_expect(h.width > sizeof(raw_value_t) * 8, 0)) { error(s, "read of a handle that is wider than the value type"); } ASSERT(h.width <= MAX_SIMPLE_WIDTH && "read of a handle that is larger than " "the maximum width of a simple type in this model"); uint64_t raw = (raw_value_t)read_raw(h); TRACE(TC_HANDLE_READS, "read value %" PRIRAWVAL " from handle { %p, %zu, %zu }", raw_value_to_string(raw), h.base, h.offset, h.width); return raw; } static value_t decode_value(value_t lb, value_t ub, raw_value_t v) { value_t dest = 0; bool r __attribute__((unused)) = SUB(v, 1, &v) || ADD(v, lb, &dest) || dest < lb || dest > ub; ASSERT(!r && "read of out-of-range value"); return dest; } static __attribute__((unused)) value_t handle_read(const char *NONNULL context, const char *rule_name, const char *NONNULL name, const struct state *NONNULL s, value_t lb, value_t ub, struct handle h) { assert(context != NULL); assert(name != NULL); /* If we happen to be reading from the current state, do a sanity check that * we're only reading within bounds. */ assert((h.base != (uint8_t *)s->data /* not a read from the current state */ || sizeof(s->data) * CHAR_BIT - h.width >= h.offset) /* in bounds */ && "out of bounds read in handle_read()"); raw_value_t dest = handle_read_raw(s, h); if (__builtin_expect(dest == 0, 0)) { error(s, "%sread of undefined value in %s%s%s", context, name, rule_name == NULL ? "" : " within ", rule_name == NULL ? "" : rule_name); } return decode_value(lb, ub, dest); } static void handle_write_raw(const struct state *NONNULL s, struct handle h, raw_value_t value) { /* Check if this write is larger than the variable we will are reading from. * This can only occur if the user has manually overridden value_t with the * --value-type command line argument. */ if (__builtin_expect(h.width > sizeof(raw_value_t) * 8, 0)) { error(s, "write of a handle that is wider than the value type"); } ASSERT(h.width <= MAX_SIMPLE_WIDTH && "write to a handle that is larger than " "the maximum width of a simple type in this model"); TRACE(TC_HANDLE_WRITES, "writing value %" PRIRAWVAL " to handle { %p, %zu, %zu }", raw_value_to_string(value), h.base, h.offset, h.width); #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wtautological-compare" #elif defined(__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wtype-limits" #endif ASSERT((uintmax_t)value <= UINT64_MAX && "truncating value during handle_write_raw"); #ifdef __clang__ #pragma clang diagnostic pop #elif defined(__GNUC__) #pragma GCC diagnostic pop #endif write_raw(h, (uint64_t)value); } static __attribute__((unused)) void handle_write(const char *NONNULL context, const char *rule_name, const char *NONNULL name, const struct state *NONNULL s, value_t lb, value_t ub, struct handle h, value_t value) { assert(context != NULL); assert(name != NULL); /* If we happen to be writing to the current state, do a sanity check that * we're only writing within bounds. */ assert((h.base != (uint8_t *)s->data /* not a write to the current state */ || sizeof(s->data) * CHAR_BIT - h.width >= h.offset) /* in bounds */ && "out of bounds write in handle_write()"); raw_value_t r; if (__builtin_expect( value < lb || value > ub || SUB(value, lb, &r) || ADD(r, 1, &r), 0)) { error(s, "%swrite of out-of-range value into %s%s%s", context, name, rule_name == NULL ? "" : " within ", rule_name == NULL ? "" : rule_name); } handle_write_raw(s, h, r); } static __attribute__((unused)) void handle_zero(struct handle h) { uint8_t *p = h.base + h.offset / 8; /* Zero out up to a byte-aligned offset. */ if (h.offset % 8 != 0) { uint8_t mask = (UINT8_C(1) << (h.offset % 8)) - 1; if (h.width < 8 - h.offset % 8) { mask |= UINT8_MAX & ~((UINT8_C(1) << (h.offset % 8 + h.width)) - 1); } *p &= mask; p++; if (h.width < 8 - h.offset % 8) { return; } h.width -= 8 - h.offset % 8; } /* Zero out as many bytes as we can. */ memset(p, 0, h.width / 8); p += h.width / 8; h.width -= h.width / 8 * 8; /* Zero out the trailing bits in the final byte. */ if (h.width > 0) { uint8_t mask = ~((UINT8_C(1) << h.width) - 1); *p &= mask; } } static void handle_copy(struct handle a, struct handle b) { ASSERT(a.width == b.width && "copying between handles of different sizes"); /* FIXME: This does a bit-by-bit copy which almost certainly could be * accelerated by detecting byte-boundaries and complementary alignment and * then calling memcpy when possible. */ for (size_t i = 0; i < a.width; i++) { uint8_t *dst = a.base + (a.offset + i) / 8; size_t dst_off = (a.offset + i) % 8; const uint8_t *src = b.base + (b.offset + i) / 8; size_t src_off = (b.offset + i) % 8; uint8_t or_mask = ((*src >> src_off) & UINT8_C(1)) << dst_off; uint8_t and_mask = ~(UINT8_C(1) << dst_off); *dst = (*dst & and_mask) | or_mask; } } static __attribute__((unused)) bool handle_eq(struct handle a, struct handle b) { ASSERT(a.width == b.width && "comparing handles of different sizes"); /* FIXME: as with handle_copy, we do a bit-by-bit comparison which could be * made more efficient. */ for (size_t i = 0; i < a.width; i++) { uint8_t *x = a.base + (a.offset + i) / 8; size_t x_off = (a.offset + i) % 8; bool x_bit = (*x >> x_off) & 0x1; const uint8_t *y = b.base + (b.offset + i) / 8; size_t y_off = (b.offset + i) % 8; bool y_bit = (*y >> y_off) & 0x1; if (x_bit != y_bit) { return false; } } return true; } static __attribute__((unused)) struct handle handle_narrow(struct handle h, size_t offset, size_t width) { ASSERT(h.offset + offset + width <= h.offset + h.width && "narrowing a handle with values that actually expand it"); size_t r __attribute__((unused)); assert(!ADD(h.offset, offset, &r) && "narrowing handle overflows a size_t"); return (struct handle){ .base = h.base + (h.offset + offset) / CHAR_BIT, .offset = (h.offset + offset) % CHAR_BIT, .width = width, }; } static __attribute__((unused)) struct handle handle_index(const char *NONNULL context, const char *rule_name, const char *NONNULL expr, const struct state *NONNULL s, size_t element_width, value_t index_min, value_t index_max, struct handle root, value_t index) { assert(expr != NULL); if (__builtin_expect(index < index_min || index > index_max, 0)) { error(s, "%sindex out of range in expression %s%s%s", context, expr, rule_name == NULL ? "" : " within ", rule_name == NULL ? "" : rule_name); } size_t r1, r2; if (__builtin_expect( SUB(index, index_min, &r1) || MUL(r1, element_width, &r2), 0)) { error(s, "%soverflow when indexing array in expression %s%s%s", context, expr, rule_name == NULL ? "" : " within ", rule_name == NULL ? "" : rule_name); } size_t r __attribute__((unused)); assert(!ADD(root.offset, r2, &r) && "indexing handle overflows a size_t"); return (struct handle){ .base = root.base + (root.offset + r2) / CHAR_BIT, .offset = (root.offset + r2) % CHAR_BIT, .width = element_width, }; } static __attribute__((unused)) value_t handle_isundefined(const struct state *NONNULL s, struct handle h) { raw_value_t v = handle_read_raw(s, h); return v == 0; } /* Overflow-safe helpers for doing bounded arithmetic. */ static __attribute__((unused)) value_t add(const char *NONNULL context, const char *rule_name, const char *NONNULL expr, const struct state *NONNULL s, value_t a, value_t b) { assert(context != NULL); assert(expr != NULL); value_t r; if (__builtin_expect(ADD(a, b, &r), 0)) { error(s, "%sinteger overflow in addition in expression %s%s%s", context, expr, rule_name == NULL ? "" : " within ", rule_name == NULL ? "" : rule_name); } return r; } static __attribute__((unused)) value_t sub(const char *NONNULL context, const char *rule_name, const char *NONNULL expr, const struct state *NONNULL s, value_t a, value_t b) { assert(context != NULL); assert(expr != NULL); value_t r; if (__builtin_expect(SUB(a, b, &r), 0)) { error(s, "%sinteger overflow in subtraction in expression %s%s%s", context, expr, rule_name == NULL ? "" : " within ", rule_name == NULL ? "" : rule_name); } return r; } static __attribute__((unused)) value_t mul(const char *NONNULL context, const char *rule_name, const char *NONNULL expr, const struct state *NONNULL s, value_t a, value_t b) { assert(context != NULL); assert(expr != NULL); value_t r; if (__builtin_expect(MUL(a, b, &r), 0)) { error(s, "%sinteger overflow in multiplication in expression %s%s%s", context, expr, rule_name == NULL ? "" : " within ", rule_name == NULL ? "" : rule_name); } return r; } static __attribute__((unused)) value_t divide(const char *NONNULL context, const char *rule_name, const char *NONNULL expr, const struct state *NONNULL s, value_t a, value_t b) { assert(context != NULL); assert(expr != NULL); if (__builtin_expect(b == 0, 0)) { error(s, "%sdivision by zero in expression %s%s%s", context, expr, rule_name == NULL ? "" : " within ", rule_name == NULL ? "" : rule_name); } if (__builtin_expect(VALUE_MIN != 0 && a == VALUE_MIN && b == (value_t)-1, 0)) { error(s, "%sinteger overflow in division in expression %s%s%s", context, expr, rule_name == NULL ? "" : " within ", rule_name == NULL ? "" : rule_name); } return a / b; } static __attribute__((unused)) value_t mod(const char *NONNULL context, const char *rule_name, const char *NONNULL expr, const struct state *NONNULL s, value_t a, value_t b) { assert(context != NULL); assert(expr != NULL); if (__builtin_expect(b == 0, 0)) { error(s, "%smodulus by zero in expression %s%s%s", context, expr, rule_name == NULL ? "" : " within ", rule_name == NULL ? "" : rule_name); } /* Is INT_MIN % -1 UD? Reading the C spec I am not sure. */ if (__builtin_expect(VALUE_MIN != 0 && a == VALUE_MIN && b == (value_t)-1, 0)) { error(s, "%sinteger overflow in modulo in expression %s%s%s", context, expr, rule_name == NULL ? "" : " within ", rule_name == NULL ? "" : rule_name); } return a % b; } static __attribute__((unused)) value_t negate(const char *NONNULL context, const char *rule_name, const char *NONNULL expr, const struct state *NONNULL s, value_t a) { assert(context != NULL); assert(expr != NULL); if (__builtin_expect(VALUE_MIN != 0 && a == VALUE_MIN, 0)) { error(s, "%sinteger overflow in negation in expression %s%s%s", context, expr, rule_name == NULL ? "" : " within ", rule_name == NULL ? "" : rule_name); } return -a; } /* wrappers for << and >> to avoid C undefined behaviour */ static __attribute__((unused)) value_t rsh(value_t a, value_t b); static __attribute__((unused)) value_t lsh(value_t a, value_t b) { #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wtautological-compare" #pragma clang diagnostic ignored "-Wtautological-unsigned-zero-compare" #elif defined(__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wtype-limits" #endif if (value_is_signed() && b <= -(value_t)(sizeof(a) * 8)) { return 0; } if (b >= (value_t)(sizeof(a) * 8)) { return 0; } if (b < 0) { return rsh(a, -b); } #ifdef __clang__ #pragma clang diagnostic pop #elif defined(__GNUC__) #pragma GCC diagnostic pop #endif return (value_t)((raw_value_t)a << b); } static value_t rsh(value_t a, value_t b) { #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wtautological-compare" #pragma clang diagnostic ignored "-Wtautological-unsigned-zero-compare" #elif defined(__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wtype-limits" #endif if (value_is_signed() && b <= -(value_t)(sizeof(a) * 8)) { return 0; } if (b >= (value_t)(sizeof(a) * 8)) { return 0; } if (b < 0) { return lsh(a, -b); } #ifdef __clang__ #pragma clang diagnostic pop #elif defined(__GNUC__) #pragma GCC diagnostic pop #endif return a >> b; } /* Bitwise NOT wrapper. We only need this to suppress some spurious GCC * warnings: * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=38341 * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59098 */ static __attribute__((unused)) value_t bnot(value_t v) { return (value_t)~(raw_value_t)v; } /* use Heap's algorithm for generating permutations to implement a * permutation-to-number mapping */ static __attribute__((unused)) size_t permutation_to_index(const size_t *NONNULL permutation, size_t *NONNULL stack, size_t *NONNULL working, size_t count) { /* byte extent of the permutation arrays */ size_t bytes = sizeof(permutation[0]) * count; size_t index = 0; /* clear our stack */ memset(stack, 0, bytes); /* initialise the first (identity) permutation */ for (size_t i = 0; i < count; ++i) { working[i] = i; } if (memcmp(permutation, working, bytes) == 0) { return index; } size_t i = 0; while (i < count) { if (stack[i] < i) { ++index; if (i % 2 == 0) { /* swap working[0] and working[i] */ size_t tmp = working[0]; working[0] = working[i]; working[i] = tmp; } else { /* swap working[stack[i]] and working[i] */ size_t tmp = working[stack[i]]; working[stack[i]] = working[i]; working[i] = tmp; } if (memcmp(permutation, working, bytes) == 0) { return index; } ++stack[i]; i = 0; } else { stack[i] = 0; ++i; } } /* the permutation should have been one of the possible ones */ ASSERT(!"invalid permutation passed to permutation_to_index"); } static __attribute__((unused)) void index_to_permutation(size_t index, size_t *NONNULL permutation, size_t *NONNULL stack, size_t count) { /* byte extent of the permutation arrays */ size_t bytes = sizeof(permutation[0]) * count; size_t ind = 0; /* clear our stack */ memset(stack, 0, bytes); /* initialise the first (identity) permutation */ for (size_t i = 0; i < count; ++i) { permutation[i] = i; } if (ind == index) { return; } size_t i = 0; while (i < count) { if (stack[i] < i) { ++ind; if (i % 2 == 0) { /* swap permutation[0] and permutation[i] */ size_t tmp = permutation[0]; permutation[0] = permutation[i]; permutation[i] = tmp; } else { /* swap permutation[stack[i]] and permutation[i] */ size_t tmp = permutation[stack[i]]; permutation[stack[i]] = permutation[i]; permutation[i] = tmp; } if (ind == index) { return; } ++stack[i]; i = 0; } else { stack[i] = 0; ++i; } } /* the target permutation should have been one of the possible ones */ ASSERT(!"invalid index passed to index_to_permutation"); } /******************************************************************************* * State queue node * * * * Queue nodes are 4K-sized, 4K-aligned linked-list nodes. They contain * * pending states and then a pointer to the next node in the queue. * ******************************************************************************/ struct queue_node { struct state *s[(4096 - sizeof(struct queue_node *)) / sizeof(struct state *)]; struct queue_node *next; }; _Static_assert(sizeof(struct queue_node) == 4096, "incorrect queue_node size calculation"); static struct queue_node *queue_node_new(void) { struct queue_node *p = NULL; int r = posix_memalign((void **)&p, sizeof(*p), sizeof(*p)); assert((r == 0 || r == ENOMEM) && "invalid alignment to posix_memalign"); if (__builtin_expect(r != 0, 0)) { oom(); } memset(p, 0, sizeof(*p)); return p; } static void queue_node_free(struct queue_node *p) { free(p); } /******************************************************************************/ /******************************************************************************* * Queue node handles * * * * These are pointers to a member-aligned address within a queue_node. The * * idea is that you can have a queue_handle_t pointing at either one of the * * elements of the `s` member or at the chained `next` pointer. Since * * queue_node pointers are always 4K-aligned, you can examine the low 12 bits * * of the queue node handle to determine which member you are pointing at. * ******************************************************************************/ typedef uintptr_t queue_handle_t; static queue_handle_t queue_handle_from_node_ptr(const struct queue_node *n) { return (queue_handle_t)n; } static struct queue_node *queue_handle_base(queue_handle_t h) { return (struct queue_node *)(h - h % sizeof(struct queue_node)); } static bool queue_handle_is_state_pptr(queue_handle_t h) { return h % sizeof(struct queue_node) < __builtin_offsetof(struct queue_node, next); } static struct state **queue_handle_to_state_pptr(queue_handle_t h) { assert(queue_handle_is_state_pptr(h) && "invalid use of queue_handle_to_state_pptr"); return (struct state **)h; } static struct queue_node **queue_handle_to_node_pptr(queue_handle_t h) { assert(!queue_handle_is_state_pptr(h) && "invalid use of queue_handle_to_node_pptr"); return (struct queue_node **)h; } static queue_handle_t queue_handle_next(queue_handle_t h) { return h + sizeof(struct state *); } /******************************************************************************/ /******************************************************************************* * Hazard pointers * * * * The idea of "hazard pointers" comes from Maged Michael, "Hazard Pointers: * * Safe Memory Reclamation for Lock-Free Objects" in TPDS 15(8) 2004. The * * basic concept is to maintain a collection of safe-to-dereference pointers. * * Before freeing a pointer, you look in this collection to see if it is in * * use and you must always add a pointer to this collection before * * dereferencing it. The finer details of how we keep this consistent and why * * the size of this collection can be statically known ahead of time are a * * little complicated, but the paper explains this in further detail. * ******************************************************************************/ /* Queue node pointers currently safe to dereference. */ static const struct queue_node *hazarded[THREADS]; /* Protect a pointer that we wish to dereference. */ static void hazard(queue_handle_t h) { /* Find the queue node this handle lies within. */ const struct queue_node *p = queue_handle_base(h); /* You can't protect the null pointer because it is invalid to dereference it. */ assert(p != NULL && "attempt to hazard an invalid pointer"); /* Each thread is only allowed a single hazarded pointer at a time. */ assert(__atomic_load_n(&hazarded[thread_id], __ATOMIC_SEQ_CST) == NULL && "hazarding multiple pointers at once"); __atomic_store_n(&hazarded[thread_id], p, __ATOMIC_SEQ_CST); } /* Drop protection on a pointer whose target we are done accessing. */ static void unhazard(queue_handle_t h) { /* Find the queue node this handle lies within. */ const struct queue_node *p __attribute__((unused)) = queue_handle_base(h); assert(p != NULL && "attempt to unhazard an invalid pointer"); assert(__atomic_load_n(&hazarded[thread_id], __ATOMIC_SEQ_CST) != NULL && "unhazarding a pointer when none are hazarded"); assert(__atomic_load_n(&hazarded[thread_id], __ATOMIC_SEQ_CST) == p && "unhazarding a pointer that differs from the one hazarded"); __atomic_store_n(&hazarded[thread_id], NULL, __ATOMIC_SEQ_CST); } /* Free a pointer or, if not possible, defer this to later. */ static void reclaim(queue_handle_t h) { /* Find the queue node this handle lies within. */ struct queue_node *p = queue_handle_base(h); assert(p != NULL && "reclaiming a null pointer"); /* The reclaimer is not allowed to be freeing something while also holding a * hazarded pointer. */ assert(__atomic_load_n(&hazarded[thread_id], __ATOMIC_SEQ_CST) == NULL && "reclaiming a pointer while holding a hazarded pointer"); /* Pointers that we failed to free initially because they were in use * (hazarded) at the time they were passed to reclaim(). * * Why are we sure we will only ever have a maximum of `THREADS - 1` pointers * outstanding? Anything passed to reclaim() is expected to be * now-unreachable, so the only outstanding references to such are threads * racing with us. Because each thread can only have one hazarded pointer at a * time, the maximum number of in use pointers right now is `THREADS - 1` * (because the current thread does not have one). * * There is an edge case where another thread (1) held a hazarded pointer we * previously tried to reclaim and thus ended up on our deferred list, then * (2) in-between that time and now dropped this reference and acquired a * hazarded pointer to `p`. This still will not exceed our count of * `THREADS - 1` as the order in which we scan the deferred list and then add * `p` to it below ensures that we will discover the originally hazarded * pointer (now no longer conflicted) and clear its slot, leaving this * available for `p`. */ static _Thread_local struct queue_node *deferred[THREADS - 1]; static const size_t DEFERRED_SIZE = sizeof(deferred) / sizeof(deferred[0]); /* First try to free any previously deferred pointers. */ for (size_t i = 0; i < DEFERRED_SIZE; i++) { if (deferred[i] != NULL) { bool conflict = false; for (size_t j = 0; j < sizeof(hazarded) / sizeof(hazarded[0]); j++) { if (j == thread_id) { /* No need to check for conflicts with ourself. */ assert(__atomic_load_n(&hazarded[j], __ATOMIC_SEQ_CST) == NULL); continue; } if (deferred[i] == __atomic_load_n(&hazarded[j], __ATOMIC_SEQ_CST)) { /* This pointer is in use by thread j. */ conflict = true; break; } } if (!conflict) { queue_node_free(deferred[i]); deferred[i] = NULL; } } } /* Now deal with the pointer we were passed. The most likely case is that no * one else is using this pointer, so try this first. */ bool conflict = false; for (size_t i = 0; i < sizeof(hazarded) / sizeof(hazarded[i]); i++) { if (i == thread_id) { /* No need to check for conflicts with ourself. */ assert(__atomic_load_n(&hazarded[i], __ATOMIC_SEQ_CST) == NULL); continue; } if (p == __atomic_load_n(&hazarded[i], __ATOMIC_SEQ_CST)) { /* Bad luck :( */ conflict = true; break; } } if (!conflict) { /* We're done! */ queue_node_free(p); return; } /* If we reached here, we need to defer this reclamation to later. */ for (size_t i = 0; i < DEFERRED_SIZE; i++) { if (deferred[i] == NULL) { deferred[i] = p; return; } } ASSERT(!"deferred more than `THREADS` reclamations"); } /******************************************************************************/ /******************************************************************************* * Atomic operations on double word values * ******************************************************************************/ #if __SIZEOF_POINTER__ <= 4 typedef uint64_t dword_t; #elif __SIZEOF_POINTER__ <= 8 typedef unsigned __int128 dword_t; #else #error "unexpected pointer size; what scalar type to use for dword_t?" #endif static dword_t atomic_read(dword_t *p) { if (THREADS == 1) { return *p; } #if defined(__x86_64__) || defined(__i386__) || \ (defined(__aarch64__) && defined(__GNUC__) && !defined(__clang__)) /* x86-64: MOV is not guaranteed to be atomic on 128-bit naturally aligned * memory. The way to work around this is apparently the following * degenerate CMPXCHG16B. * i386: __atomic_load_n emits code calling a libatomic function that takes a * lock, making this no longer lock free. Force a CMPXCHG8B by using the * __sync built-in instead. * ARM64: __atomic_load_n emits code calling a libatomic function that takes a * lock, making this no longer lock free. Force a CASP by using the __sync * built-in instead. * * XXX: the obvious (irrelevant) literal to use here is “0” but this triggers * a GCC bug on ARM, https://gcc.gnu.org/bugzilla/show_bug.cgi?id=114310. */ return __sync_val_compare_and_swap(p, 1, 1); #endif return __atomic_load_n(p, __ATOMIC_SEQ_CST); } static void atomic_write(dword_t *p, dword_t v) { if (THREADS == 1) { *p = v; return; } #if defined(__x86_64__) || defined(__i386__) || \ (defined(__aarch64__) && defined(__GNUC__) && !defined(__clang__)) /* As explained above, we need some extra gymnastics to avoid a call to * libatomic on x86-64, i386, and ARM64. */ dword_t expected; dword_t old = 0; do { expected = old; old = __sync_val_compare_and_swap(p, expected, v); } while (expected != old); return; #endif __atomic_store_n(p, v, __ATOMIC_SEQ_CST); } static bool atomic_cas(dword_t *p, dword_t expected, dword_t new) { if (THREADS == 1) { if (*p == expected) { *p = new; return true; } return false; } #if defined(__x86_64__) || defined(__i386__) || \ (defined(__aarch64__) && defined(__GNUC__) && !defined(__clang__)) /* Make GCC >= 7.1 emit cmpxchg on x86-64 and i386. See * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80878. * Make GCC emit a CASP on ARM64 (undocumented?). */ return __sync_bool_compare_and_swap(p, expected, new); #endif return __atomic_compare_exchange_n(p, &expected, new, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); } static dword_t atomic_cas_val(dword_t *p, dword_t expected, dword_t new) { if (THREADS == 1) { dword_t old = *p; if (old == expected) { *p = new; } return old; } #if defined(__x86_64__) || defined(__i386__) || \ (defined(__aarch64__) && defined(__GNUC__) && !defined(__clang__)) /* Make GCC >= 7.1 emit cmpxchg on x86-64 and i386. See * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80878. * Make GCC emit a CASP on ARM64 (undocumented?). */ return __sync_val_compare_and_swap(p, expected, new); #endif (void)__atomic_compare_exchange_n(p, &expected, new, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); return expected; } /******************************************************************************/ /******************************************************************************* * Double pointers * * * * A scalar type that can store the value of two pointers. * ******************************************************************************/ #if __SIZEOF_POINTER__ <= 4 typedef uint64_t double_ptr_t; #elif __SIZEOF_POINTER__ <= 8 typedef unsigned __int128 double_ptr_t; #else #error "unexpected pointer size; what scalar type to use for double_ptr_t?" #endif static uintptr_t double_ptr_extract1(double_ptr_t p) { _Static_assert(sizeof(p) > sizeof(uintptr_t), "double_ptr_t is not big enough to fit a pointer"); uintptr_t q; memcpy(&q, &p, sizeof(q)); return q; } static uintptr_t double_ptr_extract2(double_ptr_t p) { _Static_assert(sizeof(p) >= 2 * sizeof(uintptr_t), "double_ptr_t is not big enough to fit two pointers"); uintptr_t q; memcpy(&q, (unsigned char *)&p + sizeof(void *), sizeof(q)); return q; } static double_ptr_t double_ptr_make(uintptr_t q1, uintptr_t q2) { double_ptr_t p = 0; _Static_assert(sizeof(p) >= 2 * sizeof(uintptr_t), "double_ptr_t is not big enough to fit two pointers"); memcpy(&p, &q1, sizeof(q1)); memcpy((unsigned char *)&p + sizeof(q1), &q2, sizeof(q2)); return p; } /******************************************************************************/ /******************************************************************************* * State queue * * * * The following implements a per-thread queue for pending states. The only * * supported operations are enqueueing and dequeueing states. A property we * * maintain is that all states within all queues pass the current model's * * invariants. * ******************************************************************************/ static struct { double_ptr_t ends; size_t count; } q[THREADS]; static size_t queue_enqueue(struct state *NONNULL s, size_t queue_id) { assert(queue_id < sizeof(q) / sizeof(q[0]) && "out of bounds queue access"); /* Look up the tail of the queue. */ double_ptr_t ends = atomic_read(&q[queue_id].ends); retry:; queue_handle_t tail = double_ptr_extract2(ends); if (tail == 0) { /* There's nothing currently in the queue. */ assert(double_ptr_extract1(ends) == 0 && "tail of queue 0 while head is non-0"); struct queue_node *n = queue_node_new(); n->s[0] = s; double_ptr_t new = double_ptr_make(queue_handle_from_node_ptr(n), queue_handle_from_node_ptr(n)); double_ptr_t old = atomic_cas_val(&q[queue_id].ends, ends, new); if (old != ends) { /* Failed. */ queue_node_free(n); ends = old; goto retry; } } else { /* The queue is non-empty, so we'll need to access the last element. */ /* Try to protect our upcoming access to the tail. */ hazard(tail); { double_ptr_t ends_check = atomic_read(&q[queue_id].ends); if (ends != ends_check) { /* Failed. Someone else modified the queue in the meantime. */ unhazard(tail); ends = ends_check; goto retry; } } /* We've now notified other threads that we're going to be accessing the * tail, so we can safely dereference its pointer. */ struct queue_node *new_node = NULL; queue_handle_t next_tail = queue_handle_next(tail); if (queue_handle_is_state_pptr(next_tail)) { /* There's an available slot in this queue node; no need to create a new * one. */ { struct state **target = queue_handle_to_state_pptr(next_tail); struct state *null = NULL; if (!__atomic_compare_exchange_n(target, &null, s, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) { /* Failed. Someone else enqueued before we could. */ unhazard(tail); goto retry; } } } else { /* There's no remaining slot in this queue node. We'll need to create a * new (empty) queue node, add our state to this one and then append this * node to the queue. */ /* Create the new node. */ new_node = queue_node_new(); new_node->s[0] = s; /* Try to update the chained pointer of the current tail to point to this * new node. */ struct queue_node **target = queue_handle_to_node_pptr(next_tail); struct queue_node *null = NULL; if (!__atomic_compare_exchange_n(target, &null, new_node, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) { /* Failed. Someone else enqueued before we could. */ queue_node_free(new_node); unhazard(tail); goto retry; } /* We now need the tail to point at our new node. */ next_tail = queue_handle_from_node_ptr(new_node); } queue_handle_t head = double_ptr_extract1(ends); double_ptr_t new = double_ptr_make(head, next_tail); /* Try to update the queue. */ { double_ptr_t old = atomic_cas_val(&q[queue_id].ends, ends, new); if (old != ends) { /* Failed. Someone else dequeued before we could finish. We know the * operation that beat us was a dequeue and not an enqueue, because by * writing to next_tail we have prevented any other enqueue from * succeeding. */ /* Undo the update of next_tail. We know this is safe (non-racy) because * no other enqueue can proceed and a dequeue will never look into * next_tail due to the way it handles the case when head == tail. */ next_tail = queue_handle_next(tail); if (queue_handle_is_state_pptr(next_tail)) { /* We previously wrote into an existing queue node. */ struct state **target = queue_handle_to_state_pptr(next_tail); struct state *temp = s; bool r __attribute__((unused)) = __atomic_compare_exchange_n( target, &temp, NULL, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); assert(r && "undo of write to next_tail failed"); } else { /* We previously wrote into a new queue node. */ struct queue_node **target = queue_handle_to_node_pptr(next_tail); struct queue_node *temp = new_node; bool r __attribute__((unused)) = __atomic_compare_exchange_n( target, &temp, NULL, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); assert(r && "undo of write to next_tail failed"); queue_node_free(new_node); } unhazard(tail); ends = old; goto retry; } } /* Success! */ unhazard(tail); } size_t count = __atomic_add_fetch(&q[queue_id].count, 1, __ATOMIC_SEQ_CST); TRACE(TC_QUEUE, "enqueued state %p into queue %zu, queue length is now %zu", s, queue_id, count); return count; } static const struct state *queue_dequeue(size_t *NONNULL queue_id) { assert(queue_id != NULL && *queue_id < sizeof(q) / sizeof(q[0]) && "out of bounds queue access"); const struct state *s = NULL; for (size_t attempts = 0; attempts < sizeof(q) / sizeof(q[0]); attempts++) { double_ptr_t ends = atomic_read(&q[*queue_id].ends); retry:; queue_handle_t head = double_ptr_extract1(ends); if (head != 0) { /* This queue is non-empty. */ /* Try to protect our upcoming accesses to the head. */ hazard(head); { double_ptr_t ends_check = atomic_read(&q[*queue_id].ends); if (ends != ends_check) { /* Failed. Someone else updated the queue. */ unhazard(head); ends = ends_check; goto retry; } } queue_handle_t tail = double_ptr_extract2(ends); double_ptr_t new; if (head == tail) { /* There is only a single element in the queue. We will need to update * both head and tail. */ new = double_ptr_make(0, 0); } else if (queue_handle_is_state_pptr(head)) { /* There are multiple elements in the queue; we can deal only with the * head. */ new = double_ptr_make(queue_handle_next(head), tail); } else { /* The head of the queue is the end of a queue node. I.e. the only thing * remaining in this queue node is the chained pointer to the next queue * node. */ /* Load the next queue node. */ struct queue_node **n = queue_handle_to_node_pptr(head); struct queue_node *new_head = __atomic_load_n(n, __ATOMIC_SEQ_CST); new = double_ptr_make(queue_handle_from_node_ptr(new_head), tail); /* Try to replace the current head with the next node. */ double_ptr_t old = atomic_cas_val(&q[*queue_id].ends, ends, new); /* Either way, now we'll need to retry, but if we succeeded we also need * to free the queue node we just removed. */ unhazard(head); if (old == ends) { /* Succeeded. */ reclaim(head); } ends = old; goto retry; } /* Try to remove the head. */ { double_ptr_t old = atomic_cas_val(&q[*queue_id].ends, ends, new); if (old != ends) { /* Failed. Someone else either enqueued or dequeued. */ unhazard(head); ends = old; goto retry; } } /* We now have either a pointer to a state or we've just removed an empty * queue node that was the last in the queue. */ if (queue_handle_is_state_pptr(head)) { struct state **st = queue_handle_to_state_pptr(head); s = *st; } unhazard(head); if (head == tail || !queue_handle_is_state_pptr(head)) { reclaim(head); } if (s == NULL) { /* Move to the next queue to try. */ *queue_id = (*queue_id + 1) % (sizeof(q) / sizeof(q[0])); continue; } size_t count = __atomic_sub_fetch(&q[*queue_id].count, 1, __ATOMIC_SEQ_CST); TRACE(TC_QUEUE, "dequeued state %p from queue %zu, queue length is now %zu", s, *queue_id, count); return s; } /* Move to the next queue to try. */ *queue_id = (*queue_id + 1) % (sizeof(q) / sizeof(q[0])); } return s; } /******************************************************************************/ /******************************************************************************* * Reference counted pointers * * * * These are capable of encapsulating any generic pointer (void*). Note that * * we rely on the existence of double-word atomics. On x86-64, you need to * * use compiler flag '-mcx16' to get an efficient 128-bit cmpxchg. * * * * Of these functions, only the following are thread safe: * * * * * refcounted_ptr_get * * * refcounted_ptr_peek * * * refcounted_ptr_put * * * refcounted_ptr_set * * * * The caller is expected to coordinate with other threads to exclude them * * operating on the relevant refcounted_ptr_t when using one of the other * * functions: * * * * * refcounted_ptr_shift * * * ******************************************************************************/ struct refcounted_ptr { void *ptr; size_t count; }; #if __SIZEOF_POINTER__ <= 4 typedef uint64_t refcounted_ptr_t; #elif __SIZEOF_POINTER__ <= 8 typedef unsigned __int128 refcounted_ptr_t; #else #error "unexpected pointer size; what scalar type to use for refcounted_ptr_t?" #endif _Static_assert(sizeof(struct refcounted_ptr) <= sizeof(refcounted_ptr_t), "refcounted_ptr does not fit in a refcounted_ptr_t, which we " "need to operate on it atomically"); static void refcounted_ptr_set(refcounted_ptr_t *NONNULL p, void *ptr) { #ifndef NDEBUG /* Read the current state of the pointer. */ refcounted_ptr_t old = atomic_read(p); struct refcounted_ptr p2; memcpy(&p2, &old, sizeof(p2)); assert(p2.count == 0 && "overwriting a pointer source while someone still " "has a reference to this pointer"); #endif /* Set the current source pointer with no outstanding references. */ struct refcounted_ptr p3; p3.ptr = ptr; p3.count = 0; /* Commit the result. */ refcounted_ptr_t new; memcpy(&new, &p3, sizeof(p3)); atomic_write(p, new); } static void *refcounted_ptr_get(refcounted_ptr_t *NONNULL p) { refcounted_ptr_t old, new = 0; void *ret; bool r; do { /* Read the current state of the pointer. */ old = atomic_read(p); struct refcounted_ptr p2; memcpy(&p2, &old, sizeof(p2)); /* Take a reference to it. */ p2.count++; ret = p2.ptr; /* Try to commit our results. */ memcpy(&new, &p2, sizeof(p2)); r = atomic_cas(p, old, new); } while (!r); return ret; } static void refcounted_ptr_put(refcounted_ptr_t *NONNULL p, void *ptr __attribute__((unused))) { refcounted_ptr_t old, new = 0; bool r; do { /* Read the current state of the pointer. */ old = atomic_read(p); struct refcounted_ptr p2; memcpy(&p2, &old, sizeof(p2)); /* Release our reference to it. */ ASSERT(p2.ptr == ptr && "releasing a reference to a pointer after someone " "has changed the pointer source"); ASSERT(p2.count > 0 && "releasing a reference to a pointer when it had no " "outstanding references"); p2.count--; /* Try to commit our results. */ memcpy(&new, &p2, sizeof(p2)); r = atomic_cas(p, old, new); } while (!r); } static void *refcounted_ptr_peek(refcounted_ptr_t *NONNULL p) { /* Read the current state of the pointer. */ refcounted_ptr_t value = atomic_read(p); struct refcounted_ptr p2; memcpy(&p2, &value, sizeof(p2)); return p2.ptr; } static void refcounted_ptr_shift(refcounted_ptr_t *NONNULL current, refcounted_ptr_t *NONNULL next) { /* None of the operations in this function are performed atomically because we * assume the caller has synchronised with other threads via other means. */ /* The pointer we're about to overwrite should not be referenced. */ struct refcounted_ptr p __attribute__((unused)); memcpy(&p, current, sizeof(*current)); ASSERT(p.count == 0 && "overwriting a pointer that still has outstanding " "references"); /* Shift the next value into the current pointer. */ *current = *next; /* Blank the value we just shifted over. */ *next = 0; } /******************************************************************************/ /******************************************************************************* * Thread rendezvous support * ******************************************************************************/ static pthread_mutex_t rendezvous_lock; /* mutual exclusion mechanism for below. */ static pthread_cond_t rendezvous_cond; /* sleep mechanism for below. */ static size_t running_count = 1; /* how many threads are opted in to rendezvous? */ static size_t rendezvous_pending = 1; /* how many threads are opted in and not sleeping? */ static void rendezvous_init(void) { int r = pthread_mutex_init(&rendezvous_lock, NULL); if (__builtin_expect(r != 0, 0)) { fprintf(stderr, "pthread_mutex_init failed: %s\n", strerror(r)); exit(EXIT_FAILURE); } r = pthread_cond_init(&rendezvous_cond, NULL); if (__builtin_expect(r != 0, 0)) { fprintf(stderr, "pthread_cond_init failed: %s\n", strerror(r)); exit(EXIT_FAILURE); } } /** call this at the start of a rendezvous point * * This is a low level function, not expected to be directly used outside of the * context of the rendezvous implementation. * * \return True if the caller was the last to arrive and henceforth dubbed the * 'leader'. */ static bool rendezvous_arrive(void) { int r __attribute__((unused)) = pthread_mutex_lock(&rendezvous_lock); assert(r == 0); /* Take a token from the rendezvous down-counter. */ assert(rendezvous_pending > 0); rendezvous_pending--; /* If we were the last to arrive then it was our arrival that dropped the * counter to zero. */ return rendezvous_pending == 0; } /** call this at the end of a rendezvous point * * This is a low level function, not expected to be directly used outside of the * context of the rendezvous implementation. * * \param leader Whether the caller is the 'leader'. If you call this when you * are the 'leader' it will unblock all 'followers' at the rendezvous point. * \param action Optional code for the leader to run. */ static void rendezvous_depart(bool leader, void (*action)(void)) { int r __attribute((unused)); if (leader) { if (action != NULL) { action(); } /* Reset the counter for the next rendezvous. */ assert(rendezvous_pending == 0 && "a rendezvous point is being exited " "while some participating threads have yet to arrive"); rendezvous_pending = running_count; /* Wake up the 'followers'. */ r = pthread_cond_broadcast(&rendezvous_cond); assert(r == 0); } else { /* Wait on the 'leader' to wake us up. */ r = pthread_cond_wait(&rendezvous_cond, &rendezvous_lock); assert(r == 0); } r = pthread_mutex_unlock(&rendezvous_lock); assert(r == 0); } /* Exposed friendly function for performing a rendezvous. */ static void rendezvous(void (*action)(void)) { bool leader = rendezvous_arrive(); if (leader) { TRACE(TC_SET, "arrived at rendezvous point as leader"); } rendezvous_depart(leader, action); } /* Remove the caller from the pool of threads who participate in this * rendezvous. */ static void rendezvous_opt_out(void (*action)(void)) { retry:; /* "Arrive" at the rendezvous to decrement the count of outstanding threads. */ bool leader = rendezvous_arrive(); if (leader && running_count > 1) { /* We unfortunately opted out of this rendezvous while the remaining threads * were arriving at one and we were the last to arrive. Let's pretend we are * participating in the rendezvous and unblock them. */ rendezvous_depart(true, action); /* Re-attempt opting-out. */ goto retry; } /* Remove ourselves from the known threads. */ assert(running_count > 0); running_count--; int r __attribute__((unused)) = pthread_mutex_unlock(&rendezvous_lock); assert(r == 0); } /******************************************************************************/ /******************************************************************************* * 'Slots', an opaque wrapper around a state pointer * * * * See usage of this in the state set below for its purpose. * ******************************************************************************/ typedef uintptr_t slot_t; static __attribute__((const)) slot_t slot_empty(void) { return 0; } static __attribute__((const)) bool slot_is_empty(slot_t s) { return s == slot_empty(); } static __attribute__((const)) slot_t slot_tombstone(void) { static const slot_t TOMBSTONE = ~(slot_t)0; return TOMBSTONE; } static __attribute__((const)) bool slot_is_tombstone(slot_t s) { return s == slot_tombstone(); } static struct state *slot_to_state(slot_t s) { ASSERT(!slot_is_empty(s)); ASSERT(!slot_is_tombstone(s)); return (struct state *)s; } static slot_t state_to_slot(const struct state *s) { return (slot_t)s; } /******************************************************************************/ /******************************************************************************* * State set * * * * The following implementation provides a set for storing the seen states. * * There is no support for removing elements, only thread-safe insertion of * * elements. * ******************************************************************************/ enum { INITIAL_SET_SIZE_EXPONENT = sizeof(unsigned long long) * 8 - 1 - __builtin_clzll(SET_CAPACITY / sizeof(struct state *) / sizeof(struct state)) }; struct set { slot_t *bucket; size_t size_exponent; }; /* Some utility functions for dealing with exponents. */ static size_t set_size(const struct set *NONNULL set) { return ((size_t)1) << set->size_exponent; } static size_t set_index(const struct set *NONNULL set, size_t index) { return index & (set_size(set) - 1); } /* The states we have encountered. This collection will only ever grow while * checking the model. Note that we have a global reference-counted pointer and * a local bare pointer. See below for an explanation. */ static refcounted_ptr_t global_seen; static _Thread_local struct set *local_seen; /* Number of elements in the global set (i.e. occupancy). */ static size_t seen_count; /* The "next" 'global_seen' value. See below for an explanation. */ static refcounted_ptr_t next_global_seen; /* Now the explanation I teased... When the set capacity exceeds a threshold * (see 'set_expand' related logic below) it is expanded and the reference * tracking within the 'refcounted_ptr_t's comes in to play. We need to allocate * a new seen set ('next_global_seen'), copy over all the elements (done in * 'set_migrate'), and then "shift" the new set to become the current set. The * role of the reference counts of both 'global_seen' and 'next_global_seen' in * all of this is to detect when the last thread releases its reference to the * old seen set and hence can deallocate it. */ /* The next chunk to migrate from the old set to the new set. What exactly a * "chunk" is is covered in 'set_migrate'. */ static size_t next_migration; /* A mechanism for synchronisation in 'set_expand'. */ static pthread_mutex_t set_expand_mutex; static void set_expand_lock(void) { if (THREADS > 1) { int r __attribute__((unused)) = pthread_mutex_lock(&set_expand_mutex); ASSERT(r == 0); } } static void set_expand_unlock(void) { if (THREADS > 1) { int r __attribute__((unused)) = pthread_mutex_unlock(&set_expand_mutex); ASSERT(r == 0); } } static void set_init(void) { if (THREADS > 1) { int r = pthread_mutex_init(&set_expand_mutex, NULL); if (__builtin_expect(r < 0, 0)) { fprintf(stderr, "pthread_mutex_init failed: %s\n", strerror(r)); exit(EXIT_FAILURE); } } /* Allocate the set we'll store seen states in at some conservative initial * size. */ struct set *set = xmalloc(sizeof(*set)); set->size_exponent = INITIAL_SET_SIZE_EXPONENT; set->bucket = xcalloc(set_size(set), sizeof(set->bucket[0])); /* Stash this somewhere for threads to later retrieve it from. Note that we * initialize its reference count to zero as we (the setup logic) are not * using it beyond this function. */ refcounted_ptr_set(&global_seen, set); } static void set_thread_init(void) { /* Take a local reference to the global seen set. */ local_seen = refcounted_ptr_get(&global_seen); } static void set_update(void) { /* Guard against the case where we've been called from exit_with() and we're * not finishing a migration, but just opting out of the rendezvous protocol. */ if (refcounted_ptr_peek(&next_global_seen) != NULL) { /* Clean up the old set. Note that we are using a pointer we have already * given up our reference count to here, but we rely on the caller to ensure * this access is safe. */ free(local_seen->bucket); free(local_seen); /* Reset migration state for the next time we expand the set. */ next_migration = 0; /* Update the global pointer to the new set. We know all the above * migrations have completed and no one needs the old set. */ refcounted_ptr_shift(&global_seen, &next_global_seen); } } static void set_migrate(void) { TRACE(TC_SET, "assisting in set migration..."); /* Size of a migration chunk. Threads in this function grab a chunk at a time * to migrate. */ enum { CHUNK_SIZE = 4096 / sizeof(local_seen->bucket[0]) /* slots */ }; /* Take a pointer to the target set for the migration. */ struct set *next = refcounted_ptr_get(&next_global_seen); for (;;) { size_t chunk = __atomic_fetch_add(&next_migration, 1, __ATOMIC_SEQ_CST); size_t start = chunk * CHUNK_SIZE; size_t end = start + CHUNK_SIZE; /* Bail out if we've finished migrating all of the set. */ if (start >= set_size(local_seen)) { break; } /* TODO: The following algorithm assumes insertions can collide. That is, it * operates atomically on slots because another thread could be migrating * and also targeting the same slot. If we were to more closely wick to the * Maier design this would not be required. */ for (size_t i = start; i < end; i++) { /* retrieve the slot element and mark it as migrated */ slot_t s = __atomic_exchange_n(&local_seen->bucket[i], slot_tombstone(), __ATOMIC_SEQ_CST); ASSERT(!slot_is_tombstone(s) && "attempted double slot migration"); /* If the current slot contained a state, rehash it and insert it into the * new set. Note we don't need to do any state comparisons because we know * everything in the old set is unique. */ if (!slot_is_empty(s)) { size_t index = set_index(next, state_hash(slot_to_state(s))); /* insert and shuffle any colliding entries one along */ for (size_t j = index; !slot_is_empty(s); j = set_index(next, j + 1)) { s = __atomic_exchange_n(&next->bucket[j], s, __ATOMIC_SEQ_CST); } } } } /* Release our reference to the old set now we're done with it. */ refcounted_ptr_put(&global_seen, local_seen); /* Now we need to make sure all the threads get to this point before any one * thread leaves. The purpose of this is to guarantee we only ever have at * most two seen sets "in flight". Without this rendezvous, one thread could * race ahead, fill the new set, and then decide to expand again while some * are still working on the old set. It's possible to make such a scheme work * but the synchronisation requirements just seem too complicated. */ rendezvous(set_update); /* We're now ready to resume model checking. Note that we already have a * (reference counted) pointer to the now-current global seen set, so we don't * need to take a fresh reference to it. */ local_seen = next; } static void set_expand(void) { /* Using double-checked locking, we look to see if someone else has already * started expanding the set. We do this by first checking before acquiring * the set mutex, and then later again checking after we've acquired the * mutex. The idea here is that, with multiple threads, you'll frequently find * someone else beat you to expansion and you can jump straight to helping * them with migration without having the expense of acquiring the set mutex. */ if (THREADS > 1 && refcounted_ptr_peek(&next_global_seen) != NULL) { /* Someone else already expanded it. Join them in the migration effort. */ TRACE(TC_SET, "attempted expansion failed because another thread got there " "first"); set_migrate(); return; } set_expand_lock(); /* Check again, as described above. */ if (THREADS > 1 && refcounted_ptr_peek(&next_global_seen) != NULL) { set_expand_unlock(); TRACE(TC_SET, "attempted expansion failed because another thread got there " "first"); set_migrate(); return; } TRACE(TC_SET, "expanding set from %zu slots to %zu slots...", (((size_t)1) << local_seen->size_exponent) / sizeof(slot_t), (((size_t)1) << (local_seen->size_exponent + 1)) / sizeof(slot_t)); /* Create a set of double the size. */ struct set *set = xmalloc(sizeof(*set)); set->size_exponent = local_seen->size_exponent + 1; set->bucket = xcalloc(set_size(set), sizeof(set->bucket[0])); /* Advertise this as the newly expanded global set. */ refcounted_ptr_set(&next_global_seen, set); /* We now need to migrate all slots from the old set to the new one, but we * can do this multithreaded. */ set_expand_unlock(); set_migrate(); } static bool set_insert(struct state *NONNULL s, size_t *NONNULL count) { restart:; if (__atomic_load_n(&seen_count, __ATOMIC_SEQ_CST) * 100 / set_size(local_seen) >= SET_EXPAND_THRESHOLD) set_expand(); size_t index = set_index(local_seen, state_hash(s)); size_t attempts = 0; for (size_t i = index; attempts < set_size(local_seen); i = set_index(local_seen, i + 1)) { /* Guess that the current slot is empty and try to insert here. */ slot_t c = slot_empty(); if (__atomic_compare_exchange_n(&local_seen->bucket[i], &c, state_to_slot(s), false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) { /* Success */ *count = __atomic_add_fetch(&seen_count, 1, __ATOMIC_SEQ_CST); TRACE(TC_SET, "added state %p, set size is now %zu", s, *count); /* The maximum possible size of the seen state set should be constrained * by the number of possible states based on how many bits we are using to * represent the state data. */ if (STATE_SIZE_BITS < sizeof(size_t) * CHAR_BIT) { assert(*count <= ((size_t)1) << STATE_SIZE_BITS && "seen set size " "exceeds total possible number of states"); } /* Update statistics if `--trace memory_usage` is in effect. Note that we * do this here (when a state is being added to the seen set) rather than * when the state was originally allocated to ensure that the final * allocation figures do not include transient states that we allocated * and then discarded as duplicates. */ size_t depth = 0; #if BOUND > 0 depth = (size_t)state_bound_get(s); #endif register_allocation(depth); return true; } if (slot_is_tombstone(c)) { /* This slot has been migrated. We need to rendezvous with other migrating * threads and restart our insertion attempt on the newly expanded set. */ set_migrate(); goto restart; } /* If we find this already in the set, we're done. */ if (state_eq(s, slot_to_state(c))) { TRACE(TC_SET, "skipped adding state %p that was already in set", s); return false; } attempts++; } /* If we reach here, the set is full. Expand it and retry the insertion. */ set_expand(); return set_insert(s, count); } /* Find an existing element in the set. * * Why would you ever want to do this? If you already have the state, why do you * want to find a copy of it? The answer is for liveness information. When * checking liveness properties, a duplicate of your current state that is * already contained in the state set might know some of the liveness properties * are satisfied that your current state considers unknown. */ static __attribute__((unused)) const struct state * set_find(const struct state *NONNULL s) { assert(s != NULL); size_t index = set_index(local_seen, state_hash(s)); size_t attempts = 0; for (size_t i = index; attempts < set_size(local_seen); i = set_index(local_seen, i + 1)) { slot_t slot = __atomic_load_n(&local_seen->bucket[i], __ATOMIC_SEQ_CST); /* This function is only expected to be called during the final liveness * scan, in which all threads participate. So we should never encounter a * set undergoing migration. */ ASSERT(!slot_is_tombstone(slot) && "tombstone encountered during final phase"); if (slot_is_empty(slot)) { /* reached the end of the linear block in which this state could lie */ break; } const struct state *n = slot_to_state(slot); ASSERT(n != NULL && "null pointer stored in state set"); if (state_eq(s, n)) { /* found */ return n; } attempts++; } /* not found */ return NULL; } /******************************************************************************/ static time_t START_TIME; static unsigned long long gettime() { return (unsigned long long)(time(NULL) - START_TIME); } #if LIVENESS_COUNT > 0 /* Set one of the liveness bits (i.e. mark the matching property as 'hit') in a * state and all its predecessors. */ static __attribute__((unused)) void mark_liveness(struct state *NONNULL s, size_t index, bool shared) { assert(s != NULL); ASSERT(index < sizeof(s->liveness) * CHAR_BIT && "out of range liveness write"); size_t word_index = index / (sizeof(s->liveness[0]) * CHAR_BIT); size_t bit_index = index % (sizeof(s->liveness[0]) * CHAR_BIT); uintptr_t previous_value; uintptr_t *target = &s->liveness[word_index]; uintptr_t mask = ((uintptr_t)1) << bit_index; if (shared) { /* If this state is shared (accessible by other threads) we need to operate * on its liveness data atomically. */ previous_value = __atomic_fetch_or(target, mask, __ATOMIC_SEQ_CST); } else { /* Otherwise we can use a cheaper ordinary OR. */ previous_value = *target; *target |= mask; } /* Cheat a little and cast away the constness of the previous state for which * we may need to update liveness data. */ struct state *previous = state_drop_const(state_previous_get(s)); /* If the given bit was already set, we know all the predecessors of this * state have already had their corresponding bit marked. However, if it was * not we now need to recurse to mark them. Note that we assume any * predecessors of this state are globally visible and hence shared. The * recursion depth here can be indeterminately deep, but we assume the * compiler can tail-optimise this call. */ if ((previous_value & mask) != mask && previous != NULL) { mark_liveness(previous, index, true); } } /* number of unknown liveness properties for a given state */ static unsigned long unknown_liveness(const struct state *NONNULL s) { assert(s != NULL); unsigned long unknown = 0; for (size_t i = 0; i < sizeof(s->liveness) / sizeof(s->liveness[0]); i++) { uintptr_t word = __atomic_load_n(&s->liveness[i], __ATOMIC_SEQ_CST); for (size_t j = 0; j < sizeof(s->liveness[0]) * CHAR_BIT; j++) { if (i * sizeof(s->liveness[0]) * CHAR_BIT + j >= LIVENESS_COUNT) { break; } if (!((word >> j) & 0x1)) { unknown++; } } } return unknown; } /** learn new liveness information about the state `s` from its successor * * Note that typically `state_previous_get(successor) != s` because `successor` * is actually one of the de-duped aliases of the original successor to `s`. * * \param s State to learn information about * \param successor Successor to s * \return Number of new liveness information facts learnt */ static unsigned long learn_liveness(struct state *NONNULL s, const struct state *NONNULL successor) { assert(s != NULL); assert(successor != NULL); unsigned long new_info = 0; for (size_t i = 0; i < sizeof(s->liveness) / sizeof(s->liveness[0]); i++) { uintptr_t word_src = __atomic_load_n(&successor->liveness[i], __ATOMIC_SEQ_CST); uintptr_t word_dst = __atomic_load_n(&s->liveness[i], __ATOMIC_SEQ_CST); for (size_t j = 0; j < sizeof(s->liveness[0]) * CHAR_BIT; j++) { if (i * sizeof(s->liveness[0]) * CHAR_BIT + j >= LIVENESS_COUNT) { break; } bool live_src = !!((word_src >> j) & 0x1); bool live_dst = !!((word_dst >> j) & 0x1); if (!live_dst && live_src) { mark_liveness(s, i * sizeof(s->liveness[0]) * CHAR_BIT + j, true); new_info++; } } } return new_info; } #endif /* Prototypes for generated functions. */ static void init(void); static _Noreturn void explore(void); #if LIVENESS_COUNT > 0 static void check_liveness_final(void); static unsigned long check_liveness_summarise(void); #endif static int exit_with(int status) { /* Opt out of the thread-wide rendezvous protocol. */ refcounted_ptr_put(&global_seen, local_seen); rendezvous_opt_out(set_update); local_seen = NULL; /* Make fired rule count visible globally. */ rules_fired[thread_id] = rules_fired_local; if (thread_id == 0) { /* We are the initial thread. Wait on the others before exiting. */ #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wtautological-compare" #pragma clang diagnostic ignored "-Wtautological-unsigned-zero-compare" #elif defined(__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wtype-limits" #endif for (size_t i = 0; phase == RUN && i < sizeof(threads) / sizeof(threads[0]); i++) { #ifdef __clang__ #pragma clang diagnostic pop #elif defined(__GNUC__) #pragma GCC diagnostic pop #endif void *ret; int r = pthread_join(threads[i], &ret); if (__builtin_expect(r != 0, 0)) { flockfile(stdout); fprintf(stderr, "failed to join thread: %s\n", strerror(r)); funlockfile(stdout); continue; } status |= (int)(intptr_t)ret; } /* We're now single-threaded again. */ /* Reacquire a pointer to the seen set. Note that this may not be the same * value as what we previously had in local_seen because the other threads * may have expanded and migrated the seen set in the meantime. */ local_seen = refcounted_ptr_get(&global_seen); if (error_count == 0) { /* If we didn't see any other errors, print cover information. */ #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wtautological-compare" #pragma clang diagnostic ignored "-Wtautological-unsigned-zero-compare" #elif defined(__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wtype-limits" #endif for (size_t i = 0; i < sizeof(covers) / sizeof(covers[0]); i++) { #ifdef __clang__ #pragma clang diagnostic pop #elif defined(__GNUC__) #pragma GCC diagnostic pop #endif if (MACHINE_READABLE_OUTPUT) { put("\n"); } if (covers[i] == 0) { if (!MACHINE_READABLE_OUTPUT) { put("\t"); put(red()); put(bold()); put("cover \""); put(COVER_MESSAGES[i]); put("\" not hit"); put(reset()); put("\n"); } error_count++; status = EXIT_FAILURE; } else if (!MACHINE_READABLE_OUTPUT) { put("\t"); put(green()); put(bold()); put("cover \""); put(COVER_MESSAGES[i]); put("\" hit "); put_uint(covers[i]); put(" times"); put(reset()); put("\n"); } } } #if LIVENESS_COUNT > 0 /* If we have liveness properties to assess and have seen no previous * errors, do a final check of them now. */ if (error_count == 0) { check_liveness_final(); unsigned long failed = check_liveness_summarise(); if (failed > 0) { error_count += failed; status = EXIT_FAILURE; } } #endif if (!MACHINE_READABLE_OUTPUT) { put("\n" "====================================================================" "======\n" "\n" "Status:\n" "\n"); if (error_count == 0) { put("\t"); put(green()); put(bold()); put("No error found."); put(reset()); put("\n"); } else { put("\t"); put(red()); put(bold()); put_uint(error_count); put(" error(s) found."); put(reset()); put("\n"); } put("\n"); } /* Calculate the total number of rules fired. */ uintmax_t fire_count = 0; for (size_t i = 0; i < sizeof(rules_fired) / sizeof(rules_fired[0]); i++) { fire_count += rules_fired[i]; } /* Paranoid check that we didn't miscount during set insertions/expansions. */ #ifndef NDEBUG size_t count = 0; for (size_t i = 0; i < set_size(local_seen); i++) { if (!slot_is_empty(local_seen->bucket[i])) { count++; } } #endif assert(count == seen_count && "seen set count is inconsistent at exit"); if (MACHINE_READABLE_OUTPUT) { put("\n"); put("\n"); } else { put("State Space Explored:\n" "\n" "\t"); put_uint(seen_count); put(" states, "); put_uint(fire_count); put(" rules fired in "); put_uint(gettime()); put("s.\n"); } /* print memory usage statistics if `--trace memory_usage` is in effect */ print_allocation_summary(); exit(status); } else { pthread_exit((void *)(intptr_t)status); } } static void *thread_main(void *arg) { /* Initialize (thread-local) thread identifier. */ thread_id = (size_t)(uintptr_t)arg; set_thread_init(); explore(); } static void start_secondary_threads(void) { /* XXX: Kind of hacky. We've left the rendezvous down-counter at 1 until now * in case we triggered a rendezvous before starting the other threads (it * *can* happen). We bump it immediately -- i.e. before starting *any* of the * secondary threads -- because any of them could race us and trigger a * rendezvous before we exit the below loop and they need to know about the * thundering herd bearing down on them. It is safe to do this without holding * rendezvous_lock because we are still single threaded at this point. */ assert(running_count == 1); running_count = THREADS; assert(rendezvous_pending == 1); rendezvous_pending = THREADS; #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wtautological-compare" #pragma clang diagnostic ignored "-Wtautological-unsigned-zero-compare" #elif defined(__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wtype-limits" #endif for (size_t i = 0; i < sizeof(threads) / sizeof(threads[0]); i++) { #ifdef __clang__ #pragma clang diagnostic pop #elif defined(__GNUC__) #pragma GCC diagnostic pop #endif int r = pthread_create(&threads[i], NULL, thread_main, (void *)(uintptr_t)(i + 1)); if (__builtin_expect(r != 0, 0)) { fprintf(stderr, "pthread_create failed: %s\n", strerror(r)); exit(EXIT_FAILURE); } } } int main(void) { if (COLOR == AUTO) istty = isatty(STDOUT_FILENO) != 0; /* We don't need to read anything from stdin, so discard it. */ (void)fclose(stdin); sandbox(); if (MACHINE_READABLE_OUTPUT) { put("\n" "\n" "\n"); } else { put("Memory usage:\n" "\n" "\t* The size of each state is "); put_uint(STATE_SIZE_BITS); put(" bits (rounded up to "); put_uint(STATE_SIZE_BYTES); put(" bytes).\n" "\t* The size of the hash table is "); put_uint(((size_t)1) << INITIAL_SET_SIZE_EXPONENT); put(" slots.\n" "\n"); } #ifndef NDEBUG state_print_field_offsets(); #endif START_TIME = time(NULL); rendezvous_init(); set_init(); set_thread_init(); init(); if (!MACHINE_READABLE_OUTPUT) { put("Progress Report:\n\n"); } explore(); } rumur-2024.05.07/rumur/resources/includes.c000066400000000000000000000014501461637631000204320ustar00rootroot00000000000000/* Setting a POSIX version on FreeBSD causes other functions to become hidden. */ #ifndef __FreeBSD__ #define _POSIX_C_SOURCE 200809L #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __linux__ #include #endif #ifdef __APPLE__ #include #elif defined(__FreeBSD__) #include #elif defined(__linux__) #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0) #include #include #include #include #include #include #endif #endif rumur-2024.05.07/rumur/src/000077500000000000000000000000001461637631000152355ustar00rootroot00000000000000rumur-2024.05.07/rumur/src/ValueType.cc000066400000000000000000000146661461637631000174770ustar00rootroot00000000000000#include "ValueType.h" #include "log.h" #include #include #include #include #include #include #include #include #include #include #include using namespace rumur; namespace { class BoundsFinder : public ConstTraversal { public: mpz_class min = 0; mpz_class max = 0; private: void increase_max(const mpz_class &new_value, const std::string &cause) { *debug << "increasing maximum numerical bound to " << new_value << " due to \"" << cause << "\"\n"; max = new_value; } void decrease_min(const mpz_class &new_value, const std::string &cause) { *debug << "decreasing minimum numerical bound to " << new_value << " due to \"" << cause << "\"\n"; min = new_value; } public: void visit_enum(const Enum &n) final { if (n.members.size() > max) increase_max(n.members.size(), n.to_string()); } /* We explicitly handle negative expressions because the Rumur AST sees them * as something compound, but users tend to think of them as an atom. For * example, if a user writes "-1" they expect the automatically derived type * will be able to contain -1. If we did not handle Negate specifically, this * analysis would only look at the inner "1" and conclude a narrower range * than was intuitive to the user. */ void visit_negative(const Negative &n) final { if (auto l = dynamic_cast(&*n.rhs)) { mpz_class v = -l->value; if (v < min) decrease_min(v, n.to_string()); if (v > max) increase_max(v, n.to_string()); } dispatch(*n.rhs); } void visit_number(const Number &n) final { if (n.value < min) decrease_min(n.value, n.to_string()); if (n.value > max) increase_max(n.value, n.to_string()); } // we override visit_quantifier in order to also descend into the quantifier’s // decl that the generic traversal logic assumes you do not want to do void visit_quantifier(const Quantifier &n) final { if (n.type != nullptr) dispatch(*n.type); if (n.from != nullptr) dispatch(*n.from); if (n.to != nullptr) dispatch(*n.to); if (n.step != nullptr) dispatch(*n.step); dispatch(*n.decl); } void visit_range(const Range &n) final { if (n.min->constant()) { mpz_class m = n.min->constant_fold(); if (m < min) decrease_min(m, n.min->to_string()); if (m > max) increase_max(m, n.min->to_string()); } if (n.max->constant()) { mpz_class m = n.max->constant_fold(); if (m < min) decrease_min(m, n.max->to_string()); if (m > max) increase_max(m, n.max->to_string()); } } void visit_scalarset(const Scalarset &n) final { if (n.bound->constant()) { mpz_class m = n.bound->constant_fold(); if (m < min) decrease_min(m, n.bound->to_string()); if (m > max) increase_max(m, n.bound->to_string()); } } }; } // namespace static const std::unordered_map types = { // clang-format off { "int8_t", { "int_fast8_t", "INT_FAST8_MIN", "INT_FAST8_MAX", "INT8_C", "PRIdFAST8", mpz_class(std::to_string(INT8_MIN)), mpz_class(std::to_string(INT8_MAX)) } }, { "uint8_t", { "uint_fast8_t", "((uint_fast8_t)0)", "UINT_FAST8_MAX", "UINT8_C", "PRIuFAST8", 0, mpz_class(std::to_string(UINT8_MAX)) } }, { "int16_t", { "int_fast16_t", "INT_FAST16_MIN", "INT_FAST16_MAX", "INT16_C", "PRIdFAST16", mpz_class(std::to_string(INT16_MIN)), mpz_class(std::to_string(INT16_MAX)) } }, { "uint16_t", { "uint_fast16_t", "((uint_fast16_t)0)", "UINT_FAST16_MAX", "UINT16_C", "PRIuFAST16", 0, mpz_class(std::to_string(UINT16_MAX)) } }, { "int32_t", { "int_fast32_t", "INT_FAST32_MIN", "INT_FAST32_MAX", "INT32_C", "PRIdFAST32", mpz_class(std::to_string(INT32_MIN)), mpz_class(std::to_string(INT32_MAX)) } }, { "uint32_t", { "uint_fast32_t", "((uint_fast32_t)0)", "UINT_FAST32_MAX", "UINT32_C", "PRIuFAST32", 0, mpz_class(std::to_string(UINT32_MAX)) } }, { "int64_t", { "int_fast64_t", "INT_FAST64_MIN", "INT_FAST64_MAX", "INT64_C", "PRIdFAST64", mpz_class(std::to_string(INT64_MIN)), mpz_class(std::to_string(INT64_MAX)) } }, { "uint64_t", { "uint_fast64_t", "((uint_fast64_t)0)", "UINT_FAST64_MAX", "UINT64_C", "PRIuFAST64", 0, mpz_class(std::to_string(UINT64_MAX)) } }, // clang-format on }; // a list of the types sorted by estimated expense static const std::vector TYPES = { "uint8_t", "int8_t", "uint16_t", "int16_t", "uint32_t", "int32_t", "uint64_t", "int64_t"}; // find an unsigned type that can contain the given range shifted to be 1-based static const ValueType raw_type(const mpz_class &min, const mpz_class &max) { assert(min <= max && "reversed bounds in raw_type()"); mpz_class limit = max - min + 1; for (const std::string &t : TYPES) { const ValueType &vt = types.at(t); // skip signed types if (vt.min < 0) continue; if (vt.max >= limit) return vt; } throw std::runtime_error("no supported unsigned type is wide enough to " "contain enough values to cover the range [" + min.get_str() + ", " + max.get_str() + "]"); } std::pair get_value_type(const std::string &name, const Model &m) { if (name == "auto") { // find least and greatest numerical values needed for this model BoundsFinder bf; bf.dispatch(m); // find the first type that satisfies these for (const std::string &t : TYPES) { *debug << "considering type " << t << "...\n"; const ValueType &vt = types.at(t); if (vt.min <= bf.min && vt.max >= bf.max) { *info << "using numerical type " << t << " as value type\n"; return {vt, raw_type(bf.min, bf.max)}; } } throw std::runtime_error("model's numerical bounds are [" + bf.min.get_str() + ", " + bf.max.get_str() + "] which no supported type contains"); } auto it = types.find(name); if (it == types.end()) throw std::runtime_error("unknown type " + name); const ValueType &vt = it->second; return {vt, raw_type(vt.min, vt.max)}; } rumur-2024.05.07/rumur/src/ValueType.h000066400000000000000000000013641461637631000173300ustar00rootroot00000000000000#pragma once #include #include #include #include #include // abstraction over the type used to represent scalar values during checking struct ValueType { std::string c_type; // C symbol that names the type std::string int_min; // equivalent of INT_MIN std::string int_max; // equivalent of INT_MAX std::string int_c; // equivalent of INT_C std::string pri; // equivalent of PRId64 mpz_class min; // minimum value that can be represented in this type mpz_class max; // maximum value that can be represented in this type }; std::pair get_value_type(const std::string &name, const rumur::Model &m); rumur-2024.05.07/rumur/src/assume-statements-count.cc000066400000000000000000000012311461637631000223510ustar00rootroot00000000000000#include "assume-statements-count.h" #include #include using namespace rumur; unsigned long assume_statements_count(const Model &model) { // define a traversal for counting assume statements class AssumeCounter : public ConstTraversal { public: unsigned long count = 0; void visit_propertystmt(const PropertyStmt &n) final { if (n.property.category == Property::ASSUMPTION) count++; // no need to descend into children } virtual ~AssumeCounter() = default; }; // use the counter to find how many assume statements we have AssumeCounter ac; ac.dispatch(model); return ac.count; } rumur-2024.05.07/rumur/src/assume-statements-count.h000066400000000000000000000002621461637631000222160ustar00rootroot00000000000000#pragma once #include #include // find the number of assume statements in the model unsigned long assume_statements_count(const rumur::Model &model); rumur-2024.05.07/rumur/src/environ.cc000066400000000000000000000006421461637631000172260ustar00rootroot00000000000000#include "environ.h" #include #ifdef __APPLE__ #include #endif char **get_environ() { #ifdef __APPLE__ // on macOS, environ is not directly accessible return *_NSGetEnviron(); #else /* some platforms fail to expose environ in a header (e.g. FreeBSD), so * declare it ourselves and assume it will be available when linking */ extern char **environ; return environ; #endif } rumur-2024.05.07/rumur/src/environ.h000066400000000000000000000001151461637631000170630ustar00rootroot00000000000000#pragma once // get a pointer to the environ variable char **get_environ(); rumur-2024.05.07/rumur/src/generate-allocations.cc000066400000000000000000000021331461637631000216430ustar00rootroot00000000000000#include "generate.h" #include #include #include #include #include using namespace rumur; namespace { class Generator : public ConstTraversal { private: std::ostream *out; public: Generator(std::ostream &o) : out(&o) {} void visit_functioncall(const FunctionCall &n) final { if (n.function == nullptr) throw Error("function call to unresolved target " + n.name, n.loc); define_backing_mem(n.unique_id, n.function->return_type.get()); for (auto &a : n.arguments) dispatch(*a); } virtual ~Generator() = default; private: void define_backing_mem(size_t id, const TypeExpr *t) { if (t != nullptr && !t->is_simple()) *out << " uint8_t ret" << id << "[BITS_TO_BYTES(" << t->width() << ")];\n"; } }; } // namespace void generate_allocations(std::ostream &out, const Stmt &stmt) { Generator g(out); g.dispatch(stmt); } void generate_allocations(std::ostream &out, const std::vector> &stmts) { for (auto &s : stmts) generate_allocations(out, *s); } rumur-2024.05.07/rumur/src/generate-cover-array.cc000066400000000000000000000036231461637631000215720ustar00rootroot00000000000000#include "../../common/escape.h" #include "generate.h" #include "utils.h" #include #include #include using namespace rumur; namespace { class CoverAccumulator : public ConstTraversal { private: std::ostream *out; size_t count = 0; public: CoverAccumulator(std::ostream &o) : out(&o) {} void visit_property(const Property &n) final { if (n.category == Property::COVER) { *out << " COVER_" << n.unique_id << " = " << count << ",\n"; count++; } } size_t get_count() const { return count; } }; class MessageGenerator : public ConstTraversal { private: std::ostream *out; public: MessageGenerator(std::ostream &o) : out(&o) {} void visit_propertyrule(const PropertyRule &n) final { if (n.property.category == Property::COVER) { if (n.name == "") { // No associated message. Just use the expression itself. *out << " \"" << to_C_string(*n.property.expr) << "\",\n"; } else { *out << " \"" << escape(n.name) << "\",\n"; } } } void visit_propertystmt(const PropertyStmt &n) final { if (n.property.category == Property::COVER) { if (n.message == "") { // No associated message. Just use the expression itself. *out << " \"" << to_C_string(*n.property.expr) << "\",\n"; } else { *out << " \"" << escape(n.message) << "\",\n"; } } } }; } // namespace void generate_cover_array(std::ostream &out, const Model &model) { out << "enum {\n"; CoverAccumulator ca(out); ca.dispatch(model); out << " /* Dummy entry in case the above generated list is empty to avoid " "an empty enum. */\n" << " COVER_INVALID = -1,\n" << "};\n\n"; out << "static const char *COVER_MESSAGES[] = {\n"; MessageGenerator mg(out); mg.dispatch(model); out << "};\n\n"; out << "static uintmax_t covers[" << ca.get_count() << "];\n\n"; } rumur-2024.05.07/rumur/src/generate-decl.cc000066400000000000000000000032331461637631000202440ustar00rootroot00000000000000#include "generate.h" #include #include #include #include using namespace rumur; void generate_decl(std::ostream &out, const Decl &d) { if (auto a = dynamic_cast(&d)) { const Ptr t = a->value->type(); if (t->is_simple() && !a->value->is_lvalue()) { out << "value_t"; } else { out << "struct handle"; } out << " ru_" << a->name << " __attribute__((unused)) = "; if (a->value->is_lvalue()) { generate_lvalue(out, *a->value); } else { generate_rvalue(out, *a->value); } return; } if (auto c = dynamic_cast(&d)) { assert(c->get_type()->is_simple() && "complex const decl"); out << "static const value_t ru_" << c->name << " __attribute__((unused)) " << "= VALUE_C(" << c->value->constant_fold() << ")"; return; } if (auto v = dynamic_cast(&d)) { // If this has a valid offset, it's a state variable. if (v->offset >= 0) { out << "const struct handle ru_" << v->name << " __attribute__((unused)) " << "= state_handle(s, " << v->offset << "ull, " << v->type->width() << "ull)"; // Otherwise we need to allocate backing memory for it. } else { out << "uint8_t _ru_" << v->name << "[BITS_TO_BYTES(" << v->type->width() << ")] = { 0 };\n" << " const struct handle ru_" << v->name << " __attribute__((unused)) " << "= { .base = _ru_" << v->name << ", .offset = 0ul, .width = " << v->type->width() << "ull }"; } return; } assert(!"unexpected TypeDecl in generate_decl"); } rumur-2024.05.07/rumur/src/generate-expr.cc000066400000000000000000000446261461637631000203260ustar00rootroot00000000000000#include "../../common/isa.h" #include "generate.h" #include "utils.h" #include #include #include #include #include #include #include using namespace rumur; namespace { class Generator : public ConstExprTraversal { private: std::ostream *out; bool lvalue; public: Generator(std::ostream &o, bool lvalue_) : out(&o), lvalue(lvalue_) {} // Make emitting an rvalue more concise below Generator &operator<<(const Expr &e) { Generator g(*out, false); g.dispatch(e); return *this; } Generator &operator<<(const std::string &s) { *out << s; return *this; } void visit_add(const Add &n) final { if (lvalue) invalid(n); *this << "add(" << to_C_string(n.loc) << ", rule_name, " << to_C_string(n) << ", s, " << *n.lhs << ", " << *n.rhs << ")"; } void visit_and(const And &n) final { if (lvalue) invalid(n); *this << "(" << *n.lhs << " && " << *n.rhs << ")"; } void visit_band(const Band &n) final { if (lvalue) invalid(n); *this << "((value_t)(" << *n.lhs << " & " << *n.rhs << "))"; } void visit_bnot(const Bnot &n) final { if (lvalue) invalid(n); *this << "bnot(" << *n.rhs << ")"; } void visit_bor(const Bor &n) final { if (lvalue) invalid(n); *this << "((value_t)(" << *n.lhs << " | " << *n.rhs << "))"; } void visit_div(const Div &n) final { if (lvalue) invalid(n); *this << "divide(" << to_C_string(n.loc) << ", rule_name, " << to_C_string(n) << ", s, " << *n.lhs << ", " << *n.rhs << ")"; } void visit_element(const Element &n) final { if (lvalue && !n.is_lvalue()) invalid(n); // First, determine the width of the array's elements const Ptr t1 = n.array->type(); assert(t1 != nullptr && "array with invalid type"); const Ptr t2 = t1->resolve(); assert(t2 != nullptr && "array with invalid type"); auto a = dynamic_cast(*t2); mpz_class element_width = a.element_type->width(); // Second, determine the minimum and maximum values of the array's index // type const Ptr t3 = a.index_type->resolve(); assert(t3 != nullptr && "array with invalid index type"); mpz_class min, max; if (auto r = dynamic_cast(t3.get())) { min = r->min->constant_fold(); max = r->max->constant_fold(); } else if (auto e = dynamic_cast(t3.get())) { min = 0; max = e->count() - 1; } else if (auto s = dynamic_cast(t3.get())) { min = 0; max = s->bound->constant_fold() - 1; } else { assert(false && "array with invalid index type"); } if (!lvalue && a.element_type->is_simple()) { const std::string lb = a.element_type->lower_bound(); const std::string ub = a.element_type->upper_bound(); *out << "handle_read(" << to_C_string(n.loc) << ", rule_name, " << to_C_string(n) << ", s, " << lb << ", " << ub << ", "; } *out << "handle_index(" << to_C_string(n.loc) << ", rule_name, " << to_C_string(n) << ", s, " << element_width << "ull, VALUE_C(" << min << "), VALUE_C(" << max << "), "; if (lvalue) { generate_lvalue(*out, *n.array); } else { generate_rvalue(*out, *n.array); } *this << ", " << *n.index << ")"; if (!lvalue && a.element_type->is_simple()) *out << ")"; } void visit_eq(const Eq &n) final { if (lvalue) invalid(n); if (!n.lhs->type()->is_simple()) { assert(!n.rhs->type()->is_simple() && "comparison between simple and complex type"); *this << "handle_eq(" << *n.lhs << ", " << *n.rhs << ")"; } else { *this << "(" << *n.lhs << " == " << *n.rhs << ")"; } } void visit_exists(const Exists &n) final { if (lvalue) invalid(n); *out << "({ bool result = false; "; generate_quantifier_header(*out, n.quantifier); *this << "if (" << *n.expr << ") { result = true; break; }"; generate_quantifier_footer(*out, n.quantifier); *out << " result; })"; } void visit_exprid(const ExprID &n) final { if (n.value == nullptr) throw Error("symbol \"" + n.id + "\" in expression is unresolved", n.loc); if (lvalue && !n.is_lvalue()) invalid(n); /* This is a reference to a const. Note, this also covers enum * members. */ if (auto c = dynamic_cast(n.value.get())) { assert(!lvalue && "const appearing as an lvalue"); *out << "VALUE_C(" << c->value->constant_fold() << ")"; return; } // This is either a state variable, a local variable or an alias. if (isa(n.value) || isa(n.value)) { const Ptr t = n.type(); assert((!n.is_lvalue() || t != nullptr) && "lvalue without a type"); if (!lvalue && n.is_lvalue() && t->is_simple()) { const std::string lb = t->lower_bound(); const std::string ub = t->upper_bound(); *out << "handle_read(" << to_C_string(n.loc) << ", rule_name, " << to_C_string(n) << ", s, " << lb << ", " << ub << ", "; } *out << "ru_" << n.id; if (!lvalue && n.is_lvalue() && t->is_simple()) *out << ")"; return; } // FIXME: there's another case here where it's a reference to a quanitified // variable. I suspect we should just handle that the same way as a local. } void visit_field(const Field &n) final { if (lvalue && !n.is_lvalue()) invalid(n); const Ptr root = n.record->type(); assert(root != nullptr); const Ptr resolved = root->resolve(); assert(resolved != nullptr); if (auto r = dynamic_cast(resolved.get())) { mpz_class offset = 0; for (const Ptr &f : r->fields) { if (f->name == n.field) { if (!lvalue && f->type->is_simple()) { const std::string lb = f->type->lower_bound(); const std::string ub = f->type->upper_bound(); *out << "handle_read(" << to_C_string(n.loc) << ", rule_name, " << to_C_string(n) << ", s, " << lb << ", " << ub << ", "; } *out << "handle_narrow("; if (lvalue) { generate_lvalue(*out, *n.record); } else { generate_rvalue(*out, *n.record); } *out << ", " << offset << ", " << f->type->width() << ")"; if (!lvalue && f->type->is_simple()) *out << ")"; return; } offset += f->type->width(); } throw Error("no field named \"" + n.field + "\" in record", n.loc); } throw Error("left hand side of field expression is not a record", n.loc); } void visit_forall(const Forall &n) final { if (lvalue) invalid(n); *out << "({ bool result = true; "; generate_quantifier_header(*out, n.quantifier); *this << "if (!" << *n.expr << ") { result = false; break; }"; generate_quantifier_footer(*out, n.quantifier); *out << " result; })"; } void visit_functioncall(const FunctionCall &n) final { if (lvalue) invalid(n); if (n.function == nullptr) throw Error("unresolved function reference " + n.name, n.loc); const Ptr &return_type = n.function->return_type; // Open a statement-expression so we can declare temporaries. *out << "({ "; /* Secondly, a read-only value is never passed to a var parameter. This * should have been validated by FunctionCall::validate(). */ { auto it = n.function->parameters.begin(); for (const Ptr &a __attribute__((unused)) : n.arguments) { assert(((*it)->is_readonly() || !a->is_readonly()) && "read-only value passed to var parameter"); it++; } } /* Now for each parameter we need to consider four distinct methods, based * on the parameter's circumstance as described in the following table: * * ┌──────┬────────────────┬─────────┬────────────╥────────┐ * │ var? │ simple/complex │ lvalue? │ read-only? ║ method │ * ├──────┼────────────────┼─────────┼────────────╫────────┤ * │ no │ simple │ no │ - ║ 1 │ * │ no │ simple │ yes │ no ║ 2 │ * │ no │ simple │ yes │ yes ║ 2 │ * │ no │ complex │ no │ - ║ 5 │ * │ no │ complex │ yes │ no ║ 3 │ * │ no │ complex │ yes │ yes ║ 3 │ * │ yes │ simple │ no │ no ║ 1 │ * │ yes │ simple │ yes │ no ║ 4 │ * │ yes │ complex │ yes │ no ║ 4 │ * └──────┴────────────────┴─────────┴────────────╨────────┘ * * 1. We can create a temporary handle and backing storage, then extract * the value of the argument as an rvalue and write it to this * temporary. The temporary can then be passed into the function, * ensuring we don't modify the original argument. * * 2. We can do the same as (1), but extract the value of the argument * with handle_read_raw. We need to do this because the argument might * be undefined, in which case we want to extract its value without * error. Another wrinkle we need to handle here is that the argument * might be of a different range type than the function parameter type * (differing lower and upper bounds). * * 3. We can create a temporary handle and backing store and then use * handle_copy to transfer the value of the original argument. This is * correct as we know the argument and the parameter it will be passed * as have identical width. * * 4. We just pass the original handle, the lvalue of the argument. * * 5. We pass the original (rvalue) handle. */ auto get_method = [](const Ptr ¶meter, const Ptr &argument) { bool var = !parameter->is_readonly(); bool simple = parameter->type->is_simple(); bool is_lvalue = argument->is_lvalue(); bool readonly = argument->is_readonly(); if (!var && simple && !is_lvalue ) return 1; if (!var && simple && is_lvalue && !readonly) return 2; if (!var && simple && is_lvalue && readonly) return 2; if (!var && !simple && !is_lvalue ) return 5; if (!var && !simple && is_lvalue && !readonly) return 3; if (!var && !simple && is_lvalue && readonly) return 3; if ( var && simple && !is_lvalue ) return 1; if ( var && simple && is_lvalue && !readonly) return 4; if ( var && !simple && !readonly) return 4; assert(!"unreachable"); __builtin_unreachable(); }; // Create the temporaries for each argument. { size_t index = 0; auto it = n.function->parameters.begin(); for (const Ptr &a : n.arguments) { const Ptr &p = *it; const std::string storage = "v" + std::to_string(n.unique_id) + "_" + std::to_string(index) + "_"; const std::string handle = "v" + std::to_string(n.unique_id) + "_" + std::to_string(index); auto method = get_method(p, a); assert(method >= 1 && method <= 5); if (method == 1 || method == 2 || method == 3) *out << "uint8_t " << storage << "[BITS_TO_BYTES(" << p->width() << ")] = { 0 }; " << "struct handle " << handle << " = { .base = " << storage << ", .offset = 0, .width = " << p->width() << "ull }; "; if (method == 1) { const std::string lb = p->get_type()->lower_bound(); const std::string ub = p->get_type()->upper_bound(); *out << "handle_write(" << to_C_string(n.loc) << ", rule_name, " << "\"\", s, " << lb << ", " << ub << ", " << handle << ", "; generate_rvalue(*out, *a); *out << "); "; } else if (method == 2) { const std::string lb = p->get_type()->lower_bound(); const std::string ub = p->get_type()->upper_bound(); const std::string lba = a->type()->lower_bound(); *out << "{ " << "raw_value_t v = handle_read_raw(s, "; generate_lvalue(*out, *a); *out << "); " << "raw_value_t v2; " << "value_t v3; " << "static const value_t lb = " << lb << "; " << "static const value_t ub = " << ub << "; " << "if (v != 0 && (SUB(v, 1, &v2) || ADD(v2, " << lba << ", &v3) " << "|| v3 < lb || v3 > ub)) { " << "error(s, \"call to function %s passed an out-of-range value " << "%\" PRIRAWVAL \" to parameter " << (index + 1) << "\", \"" << n.name << "\", raw_value_to_string(v + " << lba << " - 1)); " << "} " << "handle_write_raw(s, " << handle << ", v == 0 ? v : " << "((raw_value_t)(v3 - " << lb << ") + 1)); " << "} "; } else if (method == 3) { assert(a->type()->width() == p->width() && "complex function " "parameter receiving an argument of a differing width"); *out << "handle_copy(" << handle << ", "; generate_lvalue(*out, *a); *out << "); "; } it++; index++; } } *out << "ru_" << n.name << "(rule_name, state_drop_const(s)"; // Pass the return type output parameter if required. if (return_type != nullptr && !return_type->is_simple()) *out << ", (struct handle){ .base = ret" << n.unique_id << ", .offset = 0ul, .width = " << return_type->width() << "ull }"; // Now emit the arguments to the function. { size_t index = 0; auto it = n.function->parameters.begin(); for (const Ptr &a : n.arguments) { *out << ", "; assert(it != n.function->parameters.end() && "function call has more arguments than its target function"); const Ptr &p = *it; const std::string handle = "v" + std::to_string(n.unique_id) + "_" + std::to_string(index); switch (get_method(p, a)) { case 4: generate_lvalue(*out, *a); break; case 5: generate_rvalue(*out, *a); break; default: *out << handle; break; } index++; it++; } } *out << ");"; // Close the statement-expression. *out << " })"; } void visit_geq(const Geq &n) final { if (lvalue) invalid(n); *this << "(" << *n.lhs << " >= " << *n.rhs << ")"; } void visit_gt(const Gt &n) final { if (lvalue) invalid(n); *this << "(" << *n.lhs << " > " << *n.rhs << ")"; } void visit_implication(const Implication &n) final { if (lvalue) invalid(n); *this << "(!" << *n.lhs << " || " << *n.rhs << ")"; } void visit_isundefined(const IsUndefined &n) final { *this << "handle_isundefined(s, "; generate_lvalue(*out, *n.rhs); *this << ")"; } void visit_leq(const Leq &n) final { if (lvalue) invalid(n); *this << "(" << *n.lhs << " <= " << *n.rhs << ")"; } void visit_lsh(const Lsh &n) final { if (lvalue) invalid(n); *this << "lsh(" << *n.lhs << ", " << *n.rhs << ")"; } void visit_lt(const Lt &n) final { if (lvalue) invalid(n); *this << "(" << *n.lhs << " < " << *n.rhs << ")"; } void visit_mod(const Mod &n) final { if (lvalue) invalid(n); *this << "mod(" << to_C_string(n.loc) << ", rule_name, " << to_C_string(n) << ", s, " << *n.lhs << ", " << *n.rhs << ")"; } void visit_mul(const Mul &n) final { if (lvalue) invalid(n); *this << "mul(" << to_C_string(n.loc) << ", rule_name, " << to_C_string(n) << ", s, " << *n.lhs << ", " << *n.rhs << ")"; } void visit_negative(const Negative &n) final { if (lvalue) invalid(n); *this << "negate(" << to_C_string(n.loc) << ", rule_name, " << to_C_string(n) << ", s, " << *n.rhs << ")"; } void visit_neq(const Neq &n) final { if (lvalue) invalid(n); if (!n.lhs->type()->is_simple()) { assert(!n.rhs->type()->is_simple() && "comparison between simple and complex type"); *this << "(!handle_eq(" << *n.lhs << ", " << *n.rhs << "))"; } else { *this << "(" << *n.lhs << " != " << *n.rhs << ")"; } } void visit_not(const Not &n) final { if (lvalue) invalid(n); *this << "(!" << *n.rhs << ")"; } void visit_number(const Number &n) final { *out << "VALUE_C(" << n.value << ")"; } void visit_or(const Or &n) final { if (lvalue) invalid(n); *this << "(" << *n.lhs << " || " << *n.rhs << ")"; } void visit_rsh(const Rsh &n) final { if (lvalue) invalid(n); *this << "rsh(" << *n.lhs << ", " << *n.rhs << ")"; } void visit_sub(const Sub &n) final { if (lvalue) invalid(n); *this << "sub(" << to_C_string(n.loc) << ", rule_name, " << to_C_string(n) << ", s, " << *n.lhs << ", " << *n.rhs << ")"; } void visit_ternary(const Ternary &n) final { if (lvalue) invalid(n); *this << "(" << *n.cond << " ? " << *n.lhs << " : " << *n.rhs << ")"; } void visit_xor(const Xor &n) final { if (lvalue) invalid(n); *this << "((value_t)(" << *n.lhs << " ^ " << *n.rhs << "))"; } virtual ~Generator() = default; private: void invalid(const Expr &n) const { throw Error("invalid expression used as lvalue", n.loc); } }; } // namespace void generate_lvalue(std::ostream &out, const Expr &e) { Generator g(out, true); g.dispatch(e); } void generate_rvalue(std::ostream &out, const Expr &e) { Generator g(out, false); g.dispatch(e); } rumur-2024.05.07/rumur/src/generate-function.cc000066400000000000000000000067171461637631000211740ustar00rootroot00000000000000#include "../../common/isa.h" #include "generate.h" #include #include #include #include #include using namespace rumur; void generate_function(std::ostream &out, const Function &f, const std::vector &decls) { out << "static "; bool needs_return_handle = f.return_type != nullptr && !f.return_type->is_simple(); // if this function has no side effects and doesn’t have output arguments, // pass the compiler a hint for this if (!needs_return_handle && f.is_pure()) out << "__attribute__((pure)) "; /* Functions returning a simple type return a value, as expected. Functions * returning a complex type return a handle that is actually the same as their * second parameter (see below). */ if (f.return_type == nullptr) { /* We need to give void-returning functions a dummy boolean return type to * align with the type signature for rules. More specifically a return * can appear either within a rule or within a function/procedure. Within a * rule, it needs to return true to indicate to the caller no errors were * encountered during the rule. To allow a return statement to be emitted * uniformly without having to first check whether its within a rule or a * function/procedure we make the latter return a (ignored) boolean as well. */ out << "bool"; } else if (f.return_type->is_simple()) { out << "value_t"; } else { out << "struct handle"; } out << " ru_" << f.name << "(const char *rule_name __attribute__((unused)), " << "struct state *NONNULL s __attribute__((unused))"; // If required, generate the return (out) parameter. if (needs_return_handle) out << ", struct handle ret"; for (const Ptr &p : f.parameters) out << ", struct handle ru_" << p->name; out << ") {\n"; /* Output the state variable handles so we can reference them within * this start state. */ for (const Decl *d : decls) { if (isa(d)) { /* Exciting kludge: we need to suppress the definition of state variables * that are shadowed by function parameters. Yes, real world models seem * to do this. */ bool shadowed = false; for (const Ptr &p : f.parameters) { if (p->name == d->name) { shadowed = true; break; } } if (shadowed) continue; out << " "; generate_decl(out, *d); out << ";\n"; } } /* Open a scope to support local declarations can shadow the state * variables. */ out << " {\n"; // Output this function's local decls for (const Ptr &d : f.decls) { if (isa(d) || isa(d)) { out << " "; generate_decl(out, *d); out << ";\n"; } } // Allocate memory for any complex-returning functions we call generate_allocations(out, f.body); // Generate the body of the function for (auto &s : f.body) { out << " "; generate_stmt(out, *s); out << ";\n"; } /* Emit a guard against the user leaving a control flow path through their * function that doesn't return. */ if (f.return_type != nullptr) out << " error(s, \"The end of function %s reached without returning " << "values.\", \"" << f.name << "\");\n"; if (f.return_type == nullptr) out << " return true; /* ignored by caller */\n"; // Close the scope we created. out << " }\n"; out << "}"; } rumur-2024.05.07/rumur/src/generate-model.cc000066400000000000000000001622671461637631000204520ustar00rootroot00000000000000#include "../../common/escape.h" #include "../../common/isa.h" #include "generate.h" #include "symmetry-reduction.h" #include #include #include #include #include #include #include #include using namespace rumur; static std::string rule_name_string(const Rule &r, size_t index) { if (r.name == "") return std::to_string(index + 1); return "\\\"" + escape(r.name) + "\\\""; } void generate_model(std::ostream &out, const Model &m) { // Write out the symmetry reduction canonicalisation function generate_canonicalise(m, out); out << "\n\n"; // index counters for various things size_t start_index = 0; // for start states size_t property_index = 0; // for property rules size_t rule_index = 0; // for simple rules for (const Ptr &child : m.children) { // if this is a constant, emit it if (auto d = dynamic_cast(child.get())) { generate_decl(out, *d); out << ";\n\n"; continue; } if (auto f = dynamic_cast(child.get())) { // create a list of the global declarations that are in scope (seen // previously) for this function std::vector decls; for (const Ptr &c : m.children) { if (child.get() == c.get()) break; if (auto d = dynamic_cast(c.get())) decls.push_back(d); } generate_function(out, *f, decls); out << "\n\n"; continue; } if (auto rule = dynamic_cast(child.get())) { // Generate a set of flattened (non-hierarchical) rules. The purpose of // this is to essentially remove rulesets from the cases we need to deal // with below. const std::vector> rs = rule->flatten(); for (const Ptr &r : rs) { if (auto s = dynamic_cast(r.get())) { out << "static bool startstate" << start_index << "(struct state *NONNULL s"; for (const Quantifier &q : s->quantifiers) out << ", struct handle ru_" << q.name; out << ") {\n"; out << " static const char *rule_name __attribute__((unused)) = " << "\"startstate " << rule_name_string(*s, start_index) << "\";\n"; out << " if (JMP_BUF_NEEDED) {\n" << " if (sigsetjmp(checkpoint, 0)) {\n" << " /* error triggered during this startstate */\n" << " return false;\n" << " }\n" << " }\n"; // output the state variable handles that are in scope so we can // reference them within this start state for (const Ptr &c : m.children) { if (child.get() == c.get()) break; if (auto d = dynamic_cast(c.get())) { out << " "; generate_decl(out, *d); out << ";\n"; } } // output alias definitions, opening a scope in advance to support // aliases that shadow state variables, parameters, or other aliases for (const Ptr &a : s->aliases) { out << " {\n "; generate_decl(out, *a); out << ";\n"; } // open a scope to support local declarations can shadow the state // variables out << " {\n"; for (const Ptr &d : s->decls) { if (auto v = dynamic_cast(d.get())) { out << " "; generate_decl(out, *v); out << ";\n"; } } // allocate memory for any complex-returning functions we call generate_allocations(out, s->body); for (auto &st : s->body) { out << " "; generate_stmt(out, *st); out << ";\n"; } // close the scopes we created out << " }\n" << std::string(s->aliases.size(), '}') << "\n" << " return true;\n" << "}\n\n"; ++start_index; } if (auto p = dynamic_cast(r.get())) { out << "static __attribute__((unused)) bool property" << property_index << "(const struct state *NONNULL s"; for (const Quantifier &q : p->quantifiers) out << ", struct handle ru_" << q.name; out << ") {\n"; out << " static const char *rule_name __attribute__((unused)) = " "\"property " << rule_name_string(*p, property_index) << "\";\n"; // output the state variable handles that are in scope so we can // reference them within this property for (const Ptr &c : m.children) { if (child.get() == c.get()) break; if (auto d = dynamic_cast(c.get())) { out << " "; generate_decl(out, *d); out << ";\n"; } } // output alias definitions, opening a scope in advance to support // aliases that shadow state variables, parameters, or other aliases for (const Ptr &a : p->aliases) { out << " {\n "; generate_decl(out, *a); out << ";\n"; } out << " return "; generate_property(out, p->property); out << ";\n" << std::string(p->aliases.size(), '}') << "\n" << "}\n\n"; ++property_index; } if (auto s = dynamic_cast(r.get())) { // write the guard out << "static int guard" << rule_index << "(const struct state *NONNULL s __attribute__((unused))"; for (const Quantifier &q : s->quantifiers) out << ", struct handle ru_" << q.name << " __attribute__((unused))"; out << ") {\n"; out << " static const char *rule_name __attribute__((unused)) = \"" << "guard of rule " << rule_name_string(*s, rule_index) << "\";\n"; out << " if (JMP_BUF_NEEDED) {\n" << " if (sigsetjmp(checkpoint, 0)) {\n" << " /* this guard triggered an error */\n" << " return -1;\n" << " }\n" << " }\n"; // output the state variable handles that are in scope so we can // reference them within this guard for (const Ptr &c : m.children) { if (child.get() == c.get()) break; if (auto d = dynamic_cast(c.get())) { out << " "; generate_decl(out, *d); out << ";\n"; } } // output alias definitions, opening a scope in advance to support // aliases that shadow state variables, parameters, or other aliases for (const Ptr &a : s->aliases) { out << " {\n "; generate_decl(out, *a); out << ";\n"; } out << " return "; if (s->guard == nullptr) { out << "true"; } else { generate_rvalue(out, *s->guard); } out << " ? 1 : 0;\n" << std::string(s->aliases.size(), '}') << "\n" << "}\n\n"; // write the body out << "static bool rule" << rule_index << "(struct state *NONNULL s"; for (const Quantifier &q : s->quantifiers) out << ", struct handle ru_" << q.name; out << ") {\n"; out << " static const char *rule_name __attribute__((unused)) = " << "\"rule " << rule_name_string(*s, rule_index) << "\";\n"; out << " if (JMP_BUF_NEEDED) {\n" << " if (sigsetjmp(checkpoint, 0)) {\n" << " /* an error was triggered during this rule */\n" << " return false;\n" << " }\n" << " }\n"; // output the state variable handles that are in scope so we can // reference them within this rule for (const Ptr &c : m.children) { if (child.get() == c.get()) break; if (auto d = dynamic_cast(c.get())) { out << " "; generate_decl(out, *d); out << ";\n"; } } // output alias definitions, opening a scope in advance to support // aliases that shadow state variables, parameters, or other aliases for (const Ptr &a : s->aliases) { out << " {\n "; generate_decl(out, *a); out << ";\n"; } // open a scope to support local declarations can shadow the state // variables out << " {\n"; for (const Ptr &d : s->decls) { if (isa(d)) { out << " "; generate_decl(out, *d); out << ";\n"; } } // allocate memory for any complex-returning functions we call generate_allocations(out, s->body); for (auto &st : s->body) { out << " "; generate_stmt(out, *st); out << ";\n"; } // Close the scopes we created. out << " }\n" << std::string(s->aliases.size(), '}') << "\n" << " return true;\n" << "}\n\n"; rule_index++; } } } } // Write invariant checker { out << "static bool check_invariants(const struct state *NONNULL s " << "__attribute__((unused))) {\n" << " static const char *rule_name __attribute__((unused)) = NULL;\n" << " if (JMP_BUF_NEEDED) {\n" << " if (sigsetjmp(checkpoint, 0)) {\n" << " /* invariant violated */\n" << " return false;\n" << " }\n" << " }\n"; size_t index = 0; size_t invariant_index = 0; for (const Ptr &c : m.children) { if (auto rule = dynamic_cast(c.get())) { // as above, we flatten the rule to avoid dealing with rulesets const std::vector> rs = rule->flatten(); for (const Ptr &r : rs) { if (auto p = dynamic_cast(r.get())) { if (p->property.category == Property::ASSERTION) { // open a scope so we do not have to think about name collisions out << " {\n"; // set up quantifiers for (const Quantifier &q : r->quantifiers) generate_quantifier_header(out, q); assert(index < property_index && "miscounted property rules during model generation"); out << " if (!property" << index << "(s"; for (const Quantifier &q : r->quantifiers) out << ", ru_" << q.name; out << ")) {\n" << " error(s, \"invariant %s failed\", \"" << rule_name_string(*p, invariant_index) << "\");\n" << " }\n"; // close the quantifier loops for (auto it = r->quantifiers.rbegin(); it != r->quantifiers.rend(); it++) generate_quantifier_footer(out, *it); // close this invariant's scope out << " }\n"; assert(invariant_index <= index && "incorrect invariant checker generation logic"); ++invariant_index; } ++index; } } } } out << " return true;\n" << "}\n\n"; } // Write assumption checker { out << "static bool check_assumptions(const struct state *NONNULL s " << "__attribute__((unused))) {\n" << " static const char *rule_name __attribute__((unused)) = NULL;\n" << " if (JMP_BUF_NEEDED) {\n" << " if (sigsetjmp(checkpoint, 0)) {\n" << " /* one of the properties triggered an error */\n" << " return false;\n" << " }\n" << " }\n"; size_t index = 0; for (const Ptr &c : m.children) { if (auto rule = dynamic_cast(c.get())) { // as above, we flatten the rule to avoid dealing with rulesets const std::vector> rs = rule->flatten(); for (const Ptr &r : rs) { if (auto p = dynamic_cast(r.get())) { if (p->property.category == Property::ASSUMPTION) { // open a scope so we do not have to think about name collisions out << " {\n"; // set up quantifiers for (const Quantifier &q : r->quantifiers) generate_quantifier_header(out, q); assert(index < property_index && "miscounted property rules during model generation"); out << " if (!property" << index << "(s"; for (const Quantifier &q : r->quantifiers) out << ", ru_" << q.name; out << ")) {\n" << " /* Assumption violated. */\n" << " return false;\n" << " }\n"; // close the quantifier loops for (auto it = r->quantifiers.rbegin(); it != r->quantifiers.rend(); it++) generate_quantifier_footer(out, *it); // close this assumptions's scope. out << " }\n"; } ++index; } } } } out << " return true;\n" << "}\n\n"; } // Write cover checker { out << "static bool check_covers(const struct state *NONNULL s " << "__attribute__((unused))) {\n" << " static const char *rule_name __attribute__((unused)) = NULL;\n" << " if (JMP_BUF_NEEDED) {\n" << " if (sigsetjmp(checkpoint, 0)) {\n" << " /* one of the properties triggered an error */\n" << " return false;\n" << " }\n" << " }\n"; size_t index = 0; for (const Ptr &c : m.children) { if (auto rule = dynamic_cast(c.get())) { // as above, we flatten the rule to avoid dealing with rulesets const std::vector> rs = rule->flatten(); for (const Ptr &r : rs) { if (auto p = dynamic_cast(r.get())) { if (p->property.category == Property::COVER) { // open a scope so we do not have to think about name collisions out << " {\n"; // set up quantifiers for (const Quantifier &q : r->quantifiers) generate_quantifier_header(out, q); assert(index < property_index && "miscounted property rules during model generation"); out << " if (property" << index << "(s"; for (const Quantifier &q : r->quantifiers) out << ", ru_" << q.name; out << ")) {\n" << " /* Covered. */\n" << " (void)__atomic_fetch_add(&covers[COVER_" << p->property.unique_id << "], 1, __ATOMIC_SEQ_CST);\n" << " }\n"; // close the quantifier loops for (auto it = r->quantifiers.rbegin(); it != r->quantifiers.rend(); it++) generate_quantifier_footer(out, *it); // close this cover's scope out << " }\n"; } ++index; } } } } out << " return true;\n" << "}\n\n"; } // Write liveness checker { out << "#if LIVENESS_COUNT > 0\n" << "static bool check_liveness(struct state *NONNULL s " << "__attribute__((unused))) {\n" << " static const char *rule_name __attribute__((unused)) = NULL;\n" << " if (JMP_BUF_NEEDED) {\n" << " if (sigsetjmp(checkpoint, 0)) {\n" << " /* one of the liveness properties triggered an error */\n" << " return false;\n" << " }\n" << " }\n" << " size_t liveness_index __attribute__((unused)) = 0;\n"; size_t index = 0; for (const Ptr &c : m.children) { if (auto rule = dynamic_cast(c.get())) { // as above, we flatten the rule to avoid dealing with rulesets const std::vector> rs = rule->flatten(); for (const Ptr &r : rs) { if (auto p = dynamic_cast(r.get())) { if (p->property.category == Property::LIVENESS) { // open a scope so we do not have to think about name collisions out << " {\n"; // set up quantifiers for (const Quantifier &q : r->quantifiers) generate_quantifier_header(out, q); assert(index < property_index && "miscounted property rules during model generation"); out << " if (property" << index << "(s"; for (const Quantifier &q : r->quantifiers) out << ", ru_" << q.name; out << ")) {\n" << " /* Hit. */\n" << " mark_liveness(s, liveness_index, false);\n" << " }\n" << " liveness_index++;\n"; // close the quantifier loops for (auto it = r->quantifiers.rbegin(); it != r->quantifiers.rend(); it++) generate_quantifier_footer(out, *it); // close this liveness property's scope out << " }\n"; } ++index; } } } } out << " return true;\n" << "}\n\n"; } // Write final liveness checker, the one that runs just prior to termination { out << "static void check_liveness_final(void) {\n" << "\n" << " static const char *rule_name __attribute__((unused)) = NULL;\n" << "\n" << " if (!MACHINE_READABLE_OUTPUT) {\n" << " put(\"trying to prove remaining liveness " "constraints...\\n\");\n" << " }\n" << "\n" << " /* find how many liveness bits are unknown */\n" << " unsigned long remaining = 0;\n" << " unsigned long long last_update = 0;\n" << " unsigned long learned_since_last = 0;\n" << " if (!MACHINE_READABLE_OUTPUT) {\n" << " for (size_t i = 0; i < set_size(local_seen); i++) {\n" << "\n" << " slot_t slot = __atomic_load_n(&local_seen->bucket[i], " "__ATOMIC_SEQ_CST);\n" << "\n" << " ASSERT(!slot_is_tombstone(slot)\n" << " && \"seen set being migrated during final liveness " "check\");\n" << "\n" << " if (slot_is_empty(slot)) {\n" << " /* skip empty entries in the hash table */\n" << " continue;\n" << " }\n" << "\n" << " struct state *s = slot_to_state(slot);\n" << " ASSERT(s != NULL && \"null pointer stored in state set\");\n" << "\n" << " remaining += unknown_liveness(s);\n" << " }\n" << " put(\"\\t \");\n" << " put_uint(remaining);\n" << " put(\" constraints remaining\\n\");\n" << " last_update = gettime();\n" << " }\n" << "\n" << " bool progress = true;\n" << " while (progress) {\n" << " progress = false;\n" << "\n" << " /* Run through all seen states trying to learn new liveness " "information. */\n" << " for (size_t i = 0; i < set_size(local_seen); i++) {\n" << "\n" << " slot_t slot = __atomic_load_n(&local_seen->bucket[i], " "__ATOMIC_SEQ_CST);\n" << "\n" << " ASSERT(!slot_is_tombstone(slot)\n" << " && \"seen set being migrated during final liveness " "check\");\n" << "\n" << " if (slot_is_empty(slot)) {\n" << " /* skip empty entries in the hash table */\n" << " continue;\n" << " }\n" << "\n" << " struct state *s = slot_to_state(slot);\n" << " ASSERT(s != NULL && \"null pointer stored in state set\");\n" << "\n" << " if (unknown_liveness(s) == 0) {\n" << " /* skip entries where liveness is fully satisfied already " "*/\n" << " continue;\n" << " }\n" << "\n" << "#if BOUND > 0\n" << " /* If we're doing bounded checking and this state is at the " "bound limit,\n" << " * it's not valid to expand beyond this.\n" << " */\n" << " ASSERT(state_bound_get(s) <= BOUND && \"a state that " "exceeded the bound depth was explored\");\n" << " if (state_bound_get(s) == BOUND) {\n" << " continue;\n" << " }\n" << "#endif\n" << "\n"; size_t index = 0; for (const Ptr &c : m.children) { if (auto rule = dynamic_cast(c.get())) { const std::vector> rs = rule->flatten(); for (const Ptr &r : rs) { if (isa(r)) { assert(index < rule_index && "miscounted simple rules during model generation"); // open a scope so we do not have to think about name collisions out << " {\n"; for (const Quantifier &q : r->quantifiers) generate_quantifier_header(out, q); out // use a dummy do-while to give us 'break' as a local goto << " do {\n" << " struct state *n = state_dup(s);\n" << "\n" << " int g = guard" << index << "(n"; for (const Quantifier &q : r->quantifiers) out << ", ru_" << q.name; out << ");\n" << " if (g == -1) {\n" << " /* guard triggered an error */\n" << " state_free(n);\n" << " break;\n" << " } else if (g == 1) {\n" << " if (!rule" << index << "(n"; for (const Quantifier &q : r->quantifiers) out << ", ru_" << q.name; out << ")) {\n" << " /* this rule triggered an error */\n" << " state_free(n);\n" << " break;\n" << " }\n" << " state_canonicalise(n);\n" << " if (!check_assumptions(n)) {\n" << " /* assumption violated */\n" << " state_free(n);\n" << " break;\n" << " }\n" << "\n" << " /* note that we can skip an invariant check " "because we already know it\n" << " * passed from prior expansion of this state.\n" << " */\n" << "\n" << " /* We should be able to find this state in the " "seen set. */\n" << " const struct state *t = set_find(n);\n" << " ASSERT(t != NULL && \"state encountered during " "final liveness wrap up \"\n" << " \"that was not previously seen\");\n" << "\n" << " /* See if this successor state learned a " "liveness property it never\n" << " * passed back to us. This can occur if the " "state our exploration\n" << " * encountered (`n`) was not the first of its " "kind seen and thus was\n" << " * de-duped and never made it into the seen " "set with a back pointer\n" << " * to `s`.\n" << " */\n" << " unsigned long learned = learn_liveness(s, t);\n" << " if (learned > 0) {\n" << " if (!MACHINE_READABLE_OUTPUT) {\n" << " learned_since_last += learned;\n" << " remaining -= learned;\n" << " unsigned long long t = gettime();\n" << " if (t > last_update) {\n" << " put(\"\\t \");\n" << " put_uint(learned_since_last);\n" << " put(\" further liveness constraints " "proved in \");\n" << " put_uint(t - last_update);\n" << " put(\"s, with \");\n" << " put(green()); put_uint(remaining); " "put(reset());\n" << " put(\" remaining\\n\");\n" << " learned_since_last = 0;\n" << " last_update = t;\n" << " }\n" << " }\n" << " progress = true;\n" << " }\n" << " }\n" << " /* we don't need this state anymore. */\n" << " state_free(n);\n" << " } while (0);\n"; // close the quantifier loops for (auto it = r->quantifiers.rbegin(); it != r->quantifiers.rend(); it++) generate_quantifier_footer(out, *it); // close this rule's scope out << "}\n"; ++index; } } } } out << " }\n" << " }\n" << "}\n" << "\n" << "\n" << "static unsigned long check_liveness_summarise(void) {\n" << "\n" << " /* We can now finally check whether all liveness properties were " "hit. */\n" << " bool missed[LIVENESS_COUNT];\n" << " memset(missed, 0, sizeof(missed));\n" << " for (size_t i = 0; i < set_size(local_seen); i++) {\n" << "\n" << " slot_t slot = __atomic_load_n(&local_seen->bucket[i], " "__ATOMIC_SEQ_CST);\n" << "\n" << " ASSERT(!slot_is_tombstone(slot)\n" << " && \"seen set being migrated during final liveness " "check\");\n" << "\n" << " if (slot_is_empty(slot)) {\n" << " /* skip empty entries in the hash table */\n" << " continue;\n" << " }\n" << "\n" << " const struct state *s = slot_to_state(slot);\n" << " ASSERT(s != NULL && \"null pointer stored in state set\");\n" << "\n" << " size_t index __attribute__((unused)) = 0;\n"; index = 0; for (const Ptr &c : m.children) { if (auto rule = dynamic_cast(c.get())) { const std::vector> rs = rule->flatten(); for (const Ptr &r : rs) { if (auto p = dynamic_cast(r.get())) { if (p->property.category == Property::LIVENESS) { assert(index < property_index && "miscounted liveness properties during model generation"); // open a scope so we don't have to think about name collisions out << " {\n"; // Set up quantifiers. Note, in this case they are not used. for (const Quantifier &q : r->quantifiers) generate_quantifier_header(out, q); out << " size_t word_index = index / " "(sizeof(s->liveness[0]) * CHAR_BIT);\n" << " size_t bit_index = index % (sizeof(s->liveness[0]) " "* CHAR_BIT);\n" << " if (!missed[index] && !((s->liveness[word_index] " ">> bit_index) & 0x1)) {\n" << " /* missed */\n" << " missed[index] = true;\n" << " if (MACHINE_READABLE_OUTPUT) {\n" << " put(\"\\nliveness property \");\n" << " xml_printf(\"" << rule_name_string(*p, index) << "\");\n" << " put(\" violated\\n\");\n" << " } else {\n" << " put(\"\\t\");\n" << " put(red()); put(bold());\n" << " put(\"liveness property " << rule_name_string(*p, index) << " violated:\");\n" << " put(reset()); put(\"\\n\");\n" << " }\n" << " print_counterexample(s);\n" << " if (MACHINE_READABLE_OUTPUT) {\n" << " put(\"\\n\");\n" << " }\n" << " }\n" << " index++;\n"; // close the quantifier loops for (auto it = r->quantifiers.rbegin(); it != r->quantifiers.rend(); it++) generate_quantifier_footer(out, *it); // close this liveness property's scope out << " }\n"; ++index; } } } } } out << " }\n" << "\n" << " /* total up how many misses we saw */\n" << " unsigned long total = 0;\n" << " for (size_t i = 0; i < sizeof(missed) / sizeof(missed[0]); i++) " "{\n" << " if (missed[i]) {\n" << " total++;\n" << " }\n" << " }\n" << "\n" << " return total;\n" << "}\n" << "#endif\n" << "\n"; } // Write initialisation { out << "static void init(void) {\n" << " static const char *rule_name __attribute__((unused)) = NULL;\n" << " size_t queue_id = 0;\n" << " uint64_t rule_taken = 1;\n"; size_t index = 0; for (const Ptr &c : m.children) { if (auto rule = dynamic_cast(c.get())) { const std::vector> rs = rule->flatten(); for (const Ptr &r : rs) { if (isa(r)) { assert(index < start_index && "miscounted start states during model generation"); // open a scope so we do not have to think about name collisions out << " {\n"; // Define the state variable because the code emitted for // quantifiers expects it. They do not need a non-NULL value. out << " struct state *s = NULL;\n"; // set up quantifiers for (const Quantifier &q : r->quantifiers) generate_quantifier_header(out, q); out // use a dummy do-while to give us 'break' as a local goto << " do {\n" << " s = state_new();\n" << " memset(s, 0, sizeof(*s));\n" << "#if COUNTEREXAMPLE_TRACE != CEX_OFF\n" << " state_rule_taken_set(s, rule_taken);\n" << "#endif\n" << " if (!startstate" << index << "(s"; for (const Quantifier &q : r->quantifiers) out << ", ru_" << q.name; out << ")) {\n" << " /* startstate triggered an error */\n" << " state_free(s);\n" << " break;\n" << " }\n" << " state_canonicalise(s);\n" << " if (!check_assumptions(s)) {\n" << " /* assumption violated */\n" << " state_free(s);\n" << " break;\n" << " }\n" << " if (!check_invariants(s)) {\n" << " /* invariant violated */\n" << " state_free(s);\n" << " break;\n" << " }\n" << " size_t size;\n" << " if (set_insert(s, &size)) {\n" << " if (!check_covers(s)) {\n" << " /* one of the cover properties triggered an " "error */\n" << " break;\n" << " }\n" << "#if LIVENESS_COUNT > 0\n" << " if (!check_liveness(s)) {\n" << " /* one of the liveness properties triggered an " "error */\n" << " break;\n" << " }\n" << "#endif\n" << " (void)queue_enqueue(s, queue_id);\n" << " queue_id = (queue_id + 1) % (sizeof(q) / " "sizeof(q[0]));\n" << " } else {\n" << " state_free(s);\n" << " }\n" << " } while (0);\n" << " rule_taken++;\n"; // close the quantifier loops for (auto it = r->quantifiers.rbegin(); it != r->quantifiers.rend(); it++) generate_quantifier_footer(out, *it); // close this startstate's scope out << " }\n"; ++index; } } } } out << "}\n\n"; } // Write exploration logic { out << "static void explore(void) {\n" << "\n" << " /* Used when writing to quantifier variables. */\n" << " static const char *rule_name __attribute__((unused)) = NULL;\n" << "\n" << " size_t last_queue_size = 0;\n" << "\n" << " /* Identifier of the last queue we interacted with. */\n" << " size_t queue_id = thread_id;\n" << "\n" << " for (;;) {\n" << "\n" << " if (THREADS > 1 && __atomic_load_n(&error_count,\n" << " __ATOMIC_SEQ_CST) >= MAX_ERRORS) {\n" << " /* Another thread found an error. */\n" << " break;\n" << " }\n" << "\n" << " const struct state *s = queue_dequeue(&queue_id);\n" << " if (s == NULL) {\n" << " break;\n" << " }\n" << "\n" << " bool possible_deadlock = true;\n" << " uint64_t rule_taken = 1;\n"; size_t index = 0; for (const Ptr &c : m.children) { if (auto rule = dynamic_cast(c.get())) { const std::vector> rs = rule->flatten(); for (const Ptr &r : rs) { if (isa(r)) { assert(index < rule_index && "miscounted simple rules during model generation"); // open a scope so we do not have to think about name collisions out << " {\n"; for (const Quantifier &q : r->quantifiers) generate_quantifier_header(out, q); out // use a dummy do-while to give us 'break' as a local goto << " do {\n" << " struct state *n = state_dup(s);\n" << "#if COUNTEREXAMPLE_TRACE != CEX_OFF\n" << " state_rule_taken_set(n, rule_taken);\n" << "#endif\n" << " int g = guard" << index << "(n"; for (const Quantifier &q : r->quantifiers) out << ", ru_" << q.name; out << ");\n" << " if (g == -1) {\n" << " /* error() was called */\n" << " state_free(n);\n" << " break;\n" << " } else if (g == 1) {\n" << " if (!rule" << index << "(n"; for (const Quantifier &q : r->quantifiers) out << ", ru_" << q.name; out << ")) {\n" << " /* this rule triggered an error */\n" << " state_free(n);\n" << " break;\n" << " }\n" << " rules_fired_local++;\n" << " if (DEADLOCK_DETECTION != " "DEADLOCK_DETECTION_STUTTERING || !state_eq(s, n)) {\n" << " possible_deadlock = false;\n" << " }\n" << " state_canonicalise(n);\n" << " if (!check_assumptions(n)) {\n" << " /* assumption violated */\n" << " state_free(n);\n" << " break;\n" << " }\n" << " if (!check_invariants(n)) {\n" << " /* invariant violated */\n" << " state_free(n);\n" << " break;\n" << " }\n" << " size_t size;\n" << " if (set_insert(n, &size)) {\n" << "\n" << " if (!check_covers(n)) {\n" << " /* one of the cover properties triggered an " "error */\n" << " break;\n" << " }\n" << "#if LIVENESS_COUNT > 0\n" << " if (!check_liveness(n)) {\n" << " /* one of the liveness properties triggered " "an error */\n" << " break;\n" << " }\n" << "#endif\n" << "\n" << "#if BOUND > 0\n" << " if (state_bound_get(n) < BOUND) {\n" << "#endif\n" << " size_t queue_size = queue_enqueue(n, " "thread_id);\n" << " queue_id = thread_id;\n" << "\n" << " if (size % 10000 == 0 && ftrylockfile(stdout) " "== 0) {\n" << " if (MACHINE_READABLE_OUTPUT) {\n" << " put(\"\\n\");\n" << " } else {\n" << " put(\"\\t \");\n" << " if (THREADS > 1) {\n" << " put(\"thread \");\n" << " put_uint(thread_id);\n" << " put(\": \");\n" << " }\n" << " put_uint(size);\n" << " put(\" states explored in \");\n" << " put_uint(gettime());\n" << " put(\"s, with \");\n" << " put_uint(rules_fired_local);\n" << " put(\" rules fired and \");\n" << " put(queue_size > last_queue_size ? " "yellow() : green());\n" << " put_uint(queue_size);\n" << " put(reset());\n" << " put(\" states in the queue.\\n\");\n" << " }\n" << " funlockfile(stdout);\n" << " last_queue_size = queue_size;\n" << " }\n" << "\n" << " if (THREADS > 1 && thread_id == 0 && phase == " "WARMUP && queue_size > 20) {\n" << " start_secondary_threads();\n" << " phase = RUN;\n" << " }\n" << "\n" << "#if BOUND > 0\n" << " }\n" << "#endif\n" << " } else {\n" << " state_free(n);\n" << " }\n" << " } else {\n" << " state_free(n);\n" << " }\n" << " } while (0);\n" << " rule_taken++;\n"; // close the quantifier loops for (auto it = r->quantifiers.rbegin(); it != r->quantifiers.rend(); it++) generate_quantifier_footer(out, *it); // close this rule's scope out << "}\n"; ++index; } } } } out << " /* If we did not toggle 'possible_deadlock' off by this point, " "we\n" << " * have a deadlock.\n" << " */\n" << " if (DEADLOCK_DETECTION != DEADLOCK_DETECTION_OFF && " "possible_deadlock) {\n" << " deadlock(s);\n" << " }\n" << "\n" << " }\n" << " exit_with(EXIT_SUCCESS);\n" << "}\n\n"; } // Write a function to print the state. out << "static void state_print(const struct state *previous, const struct " << "state *NONNULL s) {\n"; /* Output the state variable handles so we can reference them within this * function. */ for (const Ptr &c : m.children) { if (auto d = dynamic_cast(c.get())) { out << " "; generate_decl(out, *d); out << ";\n"; } } for (const Ptr &c : m.children) { if (auto v = dynamic_cast(c.get())) generate_print(out, *v->type, v->name, "ru_" + v->name, true, true); } out << "}\n\n"; // Write a function to print state transitions. out << "static void print_transition(const struct state *NONNULL s " << "__attribute__((unused))) {\n" << " ASSERT(s != NULL);\n" << " static const char *rule_name __attribute__((unused)) = NULL;\n" << "#if COUNTEREXAMPLE_TRACE != CEX_OFF\n" << "\n" << " ASSERT(state_rule_taken_get(s) != 0 && \"unknown state " "transition\");\n" << "\n"; { out << " if (state_previous_get(s) == NULL) {\n" << " uint64_t rule_taken = 1;\n"; mpz_class base = 1; size_t index = 0; for (const Ptr &c : m.children) { if (auto rule = dynamic_cast(c.get())) { const std::vector> rs = rule->flatten(); for (const Ptr &r : rs) { if (isa(r)) { assert(index < start_index && "miscounted start states during model generation"); // set up quantifiers out << " {\n"; for (const Quantifier &q : r->quantifiers) generate_quantifier_header(out, q); out << " if (state_rule_taken_get(s) == rule_taken) {\n" << " if (MACHINE_READABLE_OUTPUT) {\n" << " put(\"\");\n" << " xml_printf(\"Startstate " << rule_name_string(*r, index) << "\");\n" << " } else {\n" << " put(\"Startstate " << rule_name_string(*r, index) << "\");\n" << " }\n"; { size_t i = 0; for (const Quantifier &q : r->quantifiers) { out << " {\n" << " value_t v = (value_t)((rule_taken - " << base << ") / (1"; size_t j = r->quantifiers.size() - 1; for (auto it = r->quantifiers.rbegin(); it != r->quantifiers.rend(); it++) { if (i == j) break; out << " * " << it->count(); j--; } out << ") % " << q.count() << ") + " << q.lower_bound() << ";\n" << " if (MACHINE_READABLE_OUTPUT) {\n" << " put(\"\");\n" << " } else {\n" << " put(\", " << q.name << ": \");\n" << " }\n"; const Ptr t = q.type->resolve(); if (auto e = dynamic_cast(t.get())) { size_t member_index = 0; for (const std::pair &member : e->members) { out << " "; if (member_index > 0) out << "else "; out << "if (v == VALUE_C(" << member_index << ")) {\n" << " if (MACHINE_READABLE_OUTPUT) {\n" << " xml_printf(\"" << member.first << "\");\n" << " } else {\n" << " put(\"" << member.first << "\");\n" << " }\n" << " }\n"; member_index++; } out << " else {\n" << " ASSERT(!\"illegal value for " << q.name << "\");\n" << " }\n"; } else if (isa(t)) { // figure out if this is a named scalarset (i.e. ony eligible // for symmetry reduction) auto id = dynamic_cast(q.type.get()); if (id != nullptr) { // remove any levels of indirection (TypeExprIDs of // TypeExprIDs) while (auto inner = dynamic_cast( id->referent->value.get())) id = inner; // We do not need to do any schedule reversal because this // is a start state. I.e. the implicit schedule under which // this parameter was chosen is the identity permutation. // dump the symbolic value of this parameter out << " if (USE_SCALARSET_SCHEDULES) {\n" << " put(\"" << escape(id->name) << "_\");\n" << " put_val(v);\n" << " } else {\n" << " put_val(v);\n" << " }\n"; } else { // this scalarset seems not eligible for symmetry reduction // (declared inline rather than as a TypeDecl), so fall back // on just printing its value out << " put_val(v);\n"; } } else { out << " put_val(v);\n"; } out << " if (MACHINE_READABLE_OUTPUT) {\n" << " put(\"\");\n" << " }\n" << " }\n"; i++; } } out << " if (MACHINE_READABLE_OUTPUT) {\n" << " put(\"\\n\");\n" << " } else {\n" << " put(\" fired.\\n\");\n" << " }\n" << " return;\n" << " }\n"; out << " rule_taken++;\n"; // close the quantifier loops for (auto it = r->quantifiers.rbegin(); it != r->quantifiers.rend(); it++) generate_quantifier_footer(out, *it); out << " }\n"; // update base for future comparison against rule_taken mpz_class inc = 1; for (const Quantifier &q : r->quantifiers) { inc *= q.count(); } base += inc; ++index; } } } } } { out << " } else {\n" << " uint64_t rule_taken = 1;\n"; mpz_class base = 1; size_t index = 0; for (const Ptr &c : m.children) { if (auto rule = dynamic_cast(c.get())) { const std::vector> rs = rule->flatten(); for (const Ptr &r : rs) { if (isa(r)) { assert(index < rule_index && "miscounted simple rules during model generation"); // set up quantifiers out << " {\n"; for (const Quantifier &q : r->quantifiers) generate_quantifier_header(out, q); out << " if (state_rule_taken_get(s) == rule_taken) {\n" << " if (MACHINE_READABLE_OUTPUT) {\n" << " put(\"\");\n" << " xml_printf(\"Rule " << rule_name_string(*r, index) << "\");\n" << " } else {\n" << " put(\"Rule " << rule_name_string(*r, index) << "\");\n" << " }\n"; { size_t i = 0; for (const Quantifier &q : r->quantifiers) { out << " {\n" << " value_t v = (value_t)((rule_taken - " << base << ") / (1"; size_t j = r->quantifiers.size() - 1; for (auto it = r->quantifiers.rbegin(); it != r->quantifiers.rend(); it++) { if (i == j) break; out << " * " << it->count(); j--; } out << ") % " << q.count() << ") + " << q.lower_bound() << ";\n" << " if (MACHINE_READABLE_OUTPUT) {\n" << " put(\"\");\n" << " } else {\n" << " put(\", " << q.name << ": \");\n" << " }\n"; const Ptr t = q.type->resolve(); if (auto e = dynamic_cast(t.get())) { size_t member_index = 0; for (const std::pair &member : e->members) { out << " "; if (member_index > 0) out << "else "; out << "if (v == VALUE_C(" << member_index << ")) {\n" << " if (MACHINE_READABLE_OUTPUT) {\n" << " xml_printf(\"" << member.first << "\");\n" << " } else {\n" << " put(\"" << member.first << "\");\n" << " }\n" << " }\n"; member_index++; } out << " else {\n" << " ASSERT(!\"illegal value for " << q.name << "\");\n" << " }\n"; } else if (auto s = dynamic_cast(t.get())) { // open a scope to contain the schedule computation variables out << " {\n"; const std::string b = "((size_t)" + s->bound->constant_fold().get_str() + "ull)"; // figure out if this is a named scalarset (i.e. ony eligible // for symmetry reduction) auto id = dynamic_cast(q.type.get()); if (id != nullptr) { // remove any levels of indirection (TypeExprIDs of // TypeExprIDs) while (auto inner = dynamic_cast( id->referent->value.get())) id = inner; // generate schedule retrieval out << " size_t schedule[" << b << "];\n" << " /* setup a default identity mapping for " "when\n" << " * symmetry reduction is off\n" << " */\n" << " for (size_t i = 0; i < " << b << "; ++i) {\n" << " schedule[i] = i;\n" << " }\n" << " if (USE_SCALARSET_SCHEDULES) {\n" // note that we read from the *previous* state’s // schedule here because that is what this value is // relative to << " size_t index = schedule_read_" << id->name << "(state_previous_get(s));\n" << " size_t stack[" << b << "];\n" << " index_to_permutation(index, schedule, " "stack, " << b << ");\n" << " }\n"; // map the parameter value through the retrieved permutation out << " assert((size_t)v < " << b << " && \"illegal scalarset " << " parameter recorded\");\n" << " v = (value_t)schedule[(size_t)v];\n"; // dump the resulting value out << " if (USE_SCALARSET_SCHEDULES) {\n" << " put(\"" << escape(id->name) << "_\");\n" << " put_val(v);\n" << " } else {\n" << " put_val(v);\n" << " }\n"; } else { // this scalarset seems not eligible for symmetry reduction // (declared inline rather than as a TypeDecl), so fall back // on just printing its value out << " put_val(v);\n"; } out << "}\n"; } else { out << " put_val(v);\n"; } out << " if (MACHINE_READABLE_OUTPUT) {\n" << " put(\"\");\n" << " }\n" << " }\n"; i++; } } out << " if (MACHINE_READABLE_OUTPUT) {\n" << " put(\"\\n\");\n" << " } else {\n" << " put(\" fired.\\n\");\n" << " }\n" << " return;\n" << " }\n"; out << " rule_taken++;\n"; // Close the quantifier loops. for (auto it = r->quantifiers.rbegin(); it != r->quantifiers.rend(); it++) generate_quantifier_footer(out, *it); out << " }\n"; // update base for future comparison against rule_taken mpz_class inc = 1; for (const Quantifier &q : r->quantifiers) { inc *= q.count(); } base += inc; ++index; } } } } } out << " }\n" << "\n" << " /* give some helpful output for debugging problems with this " "function. */\n" << " fprintf(stderr, \"no rule found to link to state at depth " "%zu\\n\", state_depth(s));\n" << " ASSERT(!\"unreachable\");\n" << "#endif\n" << "}\n\n"; // Generate a function used during debugging out << "static void state_print_field_offsets(void) {\n" << " put(\"\t* state struct is \");\n" << " put_uint(__alignof__(struct state));\n" << " put(\"-byte aligned\\n\");\n"; for (const Ptr &c : m.children) { if (auto v = dynamic_cast(c.get())) out << " put(\"\t* field " << v->name << " is located at state offset " << v->offset << " bits\\n\");\n"; } out << " put(\"\\n\");\n" << "}\n\n"; } rumur-2024.05.07/rumur/src/generate-print.cc000066400000000000000000000453101461637631000204730ustar00rootroot00000000000000#include "../../common/escape.h" #include "generate.h" #include "options.h" #include #include #include #include #include #include #include #include #include using namespace rumur; namespace { // dynamically constructed printf call class Printf { private: std::ostringstream result; public: Printf() { result << "do {"; } explicit Printf(const std::string &s) { result << "do {"; add_str(s); } Printf(const Printf &other) { result << other.result.str(); } Printf &operator=(const Printf &other) { result.str(""); result << other.result.str(); return *this; } Printf(Printf &&) = delete; Printf &operator=(Printf &&) = delete; void add_str(const std::string &s) { result << " put(\"" << escape(s) << "\");"; } void add_val(const std::string &s) { result << " put_val((value_t)(" << s << "));"; } Printf &operator<<(const std::string &s) { add_str(s); return *this; } // construct the final printf call std::string str() const { return result.str() + " } while (0)"; } }; // derive a handle from the given containing handle at the given offset and // width static std::string derive_handle(const std::string &handle, const std::string &offset, mpz_class width) { return "((struct handle){ .base = " + handle + ".base + (" + handle + ".offset + " + offset + ") / CHAR_BIT, .offset = (" + handle + ".offset + " + offset + ") % CHAR_BIT, " + ".width = " + width.get_str() + "ull })"; } static std::string derive_handle(const std::string &handle, mpz_class offset, mpz_class width) { return derive_handle(handle, "((size_t)" + offset.get_str() + ")", width); } // from a handle pointing into the current state, derive a corresponding handle // pointing into the previous state static std::string to_previous(const std::string &h) { return "((struct handle){ .base = (uint8_t*)previous->data + (" + h + ".base - (const uint8_t*)s->data), .offset = " + h + ".offset, .width = " + h + ".width })"; } class Generator : public ConstTypeTraversal { private: std::ostream *out; const Printf prefix; // generated handle to our target in the current state const std::string current_handle; // generated handle to our target in the previous state const std::string previous_handle; const bool support_diff; const bool support_xml; // a counter used for creating unique symbols mpz_class var_counter = 0; // declaration for reading the schedule of the scalarset we are printing const TypeDecl *schedule_type = nullptr; public: Generator(std::ostream &o, const Printf &p, const std::string &h, const std::string &ph, bool s, bool x) : out(&o), prefix(p), current_handle(h), previous_handle(ph), support_diff(s), support_xml(x) {} Generator(const Generator &caller, const Printf &p, const std::string &h, const std::string &ph) : out(caller.out), prefix(p), current_handle(h), previous_handle(ph), support_diff(caller.support_diff), support_xml(caller.support_xml), var_counter(caller.var_counter) {} void visit_array(const Array &n) final { const Ptr t = n.index_type->resolve(); if (auto r = dynamic_cast(t.get())) { const mpz_class lb = r->min->constant_fold(); const mpz_class ub = r->max->constant_fold(); const mpz_class bound = ub - lb + 1; // invent a loop counter const std::string i = "i" + var_counter.get_str(); ++var_counter; // generate a loop that spans the index type *out << "{\n" << " for (size_t " << i << " = 0; " << i << " < " << bound.get_str() << "ull; ++" << i << ") {\n"; // construct a textual description of the current element Printf p = prefix; p << "["; p.add_val("(raw_value_t)" + i + " + (raw_value_t)VALUE_C(" + lb.get_str() + ")"); p << "]"; // construct a dynamic handle to the current element mpz_class w = n.element_type->width(); const std::string o = "(" + i + " * ((size_t)" + w.get_str() + "ull))"; const std::string h = derive_handle(current_handle, o, w); const std::string ph = derive_handle(previous_handle, o, w); // generate the body of the loop (printing of the current element) Generator g(*this, p, h, ph); g.dispatch(*n.element_type); // close the loop *out << " }\n" << "}\n"; return; } if (auto s = dynamic_cast(t.get())) { // figure out if this is a named scalarset (i.e. one eligible for symmetry // reduction) auto id = dynamic_cast(n.index_type.get()); if (id != nullptr) { // remove any indirection (TypeExprID of a TypeExprID) while (auto inner = dynamic_cast(id->referent->value.get())) id = inner; } // invent a symbol we can use for the retrieved schedule const std::string sch = "schedule" + var_counter.get_str(); ++var_counter; // invent a symbol we can use for the retrieved previous schedule const std::string p_sch = "schedule" + var_counter.get_str(); ++var_counter; const mpz_class b = s->bound->constant_fold(); if (id != nullptr) { // open a scope to contain the schedule arrays *out << "{\n"; // generate previous schedule retrieval *out << " size_t " << p_sch << "[" << b.get_str() << "ull];\n" << " /* setup a default identity mapping for when scalarset\n" << " * schedules are not in use\n" << " */\n" << " for (size_t i = 0; i < " << b.get_str() << "ull; ++i) {\n" << " " << p_sch << "[i] = i;\n" << " }\n"; if (support_diff) { *out << " if (USE_SCALARSET_SCHEDULES && previous != NULL) {\n" << " size_t index = schedule_read_" << id->name << "(previous);\n" << " size_t stack[" << b.get_str() << "ull];\n" << " index_to_permutation(index, " << p_sch << ", stack, (size_t)" << b.get_str() << "ull);\n" << " }\n"; } // generate schedule retrieval *out << " size_t " << sch << "[" << b.get_str() << "ull];\n" << " /* setup a default identity mapping for when scalarset\n" << " * schedules are not in use\n" << " */\n" << " for (size_t i = 0; i < " << b.get_str() << "ull; ++i) {\n" << " " << sch << "[i] = i;\n" << " }\n" << " if (USE_SCALARSET_SCHEDULES) {\n" << " size_t index = schedule_read_" << id->name << "(s);\n" << " size_t stack[" << b.get_str() << "ull];\n" << " index_to_permutation(index, " << sch << ", stack, (size_t)" << b.get_str() << "ull);\n" << " }\n"; } // invent a loop counter const std::string i = "i" + var_counter.get_str(); ++var_counter; // generate a loop that spans the index type *out << "{\n" << " for (size_t " << i << " = 0; " << i << " < " << b.get_str() << "ull; ++" << i << ") {\n"; // invent a variable for the permuted value of the counter const std::string j = "j" + var_counter.get_str(); ++var_counter; // determine permuted index of the current element *out << " size_t " << j << ";\n"; if (id != nullptr) { *out << " for (" << j << " = 0; " << j << " < " << b.get_str() << "ull; ++" << j << ") {\n" << " if (" << sch << "[" << j << "] == " << i << ") {\n" << " break;\n" << " }\n" << " }\n" << " assert(" << j << " < " << b.get_str() << "ull &&\n" << " \"failed to find permuted scalarset index\");\n"; } else { *out << " " << j << " = " << i << ";\n"; } // invent a variable for the previous permuted value of the counter const std::string k = "k" + var_counter.get_str(); ++var_counter; // determine previous permuted index of the current element *out << " size_t " << k << ";\n"; if (id != nullptr) { *out << " for (" << k << " = 0; " << k << " < " << b.get_str() << "ull; ++" << k << ") {\n" << " if (" << p_sch << "[" << k << "] == " << i << ") {\n" << " break;\n" << " }\n" << " }\n" << " assert(" << k << " < " << b.get_str() << "ull &&\n" << " \"failed to find permuted scalarset index\");\n"; } else { *out << " " << k << " = " << i << ";\n"; } // construct a textual description of the current element Printf p = prefix; p << "["; if (options.scalarset_schedules && id != nullptr) p << id->name << "_"; p.add_val(i); p << "]"; // construct a dynamic handle to the current element mpz_class w = n.element_type->width(); const std::string o = "(" + j + " * ((size_t)" + w.get_str() + "ull))"; const std::string h = derive_handle(current_handle, o, w); // construct a dynamic handle to the previous value of the current element const std::string po = "(" + k + " * ((size_t)" + w.get_str() + "ull))"; const std::string ph = derive_handle(previous_handle, po, w); // generate the body of the loop (printing of the current element) Generator g(*this, p, h, ph); g.dispatch(*n.element_type); // close the loop *out << " }\n" << "}\n"; // close the scope opened for schedule retrieval if (id != nullptr) *out << "}\n"; return; } if (auto e = dynamic_cast(t.get())) { mpz_class preceding_offset = 0; mpz_class w = n.element_type->width(); for (const std::pair &m : e->members) { Printf p = prefix; p << "[" << m.first << "]"; const std::string h = derive_handle(current_handle, preceding_offset, w); const std::string ph = derive_handle(previous_handle, preceding_offset, w); Generator g(*this, p, h, ph); g.dispatch(*n.element_type); preceding_offset += w; } return; } assert(!"non-range, non-enum used as array index"); } void visit_enum(const Enum &n) final { *out << "{\n" << " raw_value_t v = handle_read_raw(s, " << current_handle << ");\n" << " raw_value_t v_previous = 0;\n"; if (!support_diff) *out << " const struct state *previous = NULL;\n"; *out << " if (previous != NULL) {\n" << " v_previous = handle_read_raw(previous, " << previous_handle << ");\n" << " }\n" << " if (previous == NULL || v != v_previous) {\n" << " if (" << support_xml << " && MACHINE_READABLE_OUTPUT) {\n" << " put(\" &m : n.members) { *out << " } else if (v == VALUE_C(" << (i + 1) << ")) {\n" << " put(\"" << m.first << "\");\n"; i++; } *out << " } else {\n" << " assert(!\"illegal value for enum\");\n" << " }\n" << " if (" << support_xml << " && MACHINE_READABLE_OUTPUT) {\n" << " put(\"\\\"/>\");\n" << " }\n" << " put(\"\\n\");\n" << " }\n" << "}\n"; } void visit_range(const Range &n) final { const std::string lb = n.lower_bound(); const std::string ub = n.upper_bound(); *out << "{\n" << " raw_value_t v = handle_read_raw(s, " << current_handle << ");\n" << " raw_value_t v_previous = 0;\n"; if (!support_diff) *out << " const struct state *previous = NULL;\n"; *out << " if (previous != NULL) {\n" << " v_previous = handle_read_raw(previous, " << previous_handle << ");\n" << " }\n" << " if (previous == NULL || v != v_previous) {\n" << " if (" << support_xml << " && MACHINE_READABLE_OUTPUT) {\n" << " put(\"\");\n" << " }\n" << " put(\"\\n\");\n" << " }\n" << "}\n"; } void visit_record(const Record &n) final { mpz_class preceding_offset = 0; for (auto &f : n.fields) { mpz_class w = f->width(); Printf p = prefix; p << "." << f->name; const std::string h = derive_handle(current_handle, preceding_offset, w); const std::string ph = derive_handle(previous_handle, preceding_offset, w); Generator g(*this, p, h, ph); g.dispatch(*f->type); preceding_offset += w; } } void visit_scalarset(const Scalarset &n) final { const std::string bound = "((size_t)" + n.bound->constant_fold().get_str() + "ull)"; *out << "{\n" << " raw_value_t v = handle_read_raw(s, " << current_handle << ");\n" << " raw_value_t v_previous = 0;\n"; if (!support_diff) *out << " const struct state *previous = NULL;\n"; *out << " if (previous != NULL) {\n" << " v_previous = handle_read_raw(previous, " << previous_handle << ");\n"; // did we identify the schedule mapping for this type? if (schedule_type != nullptr) { *out << " if (USE_SCALARSET_SCHEDULES && v_previous != 0) {\n" << " if (COUNTEREXAMPLE_TRACE == CEX_OFF) {\n" << " assert(PRINTS_SCALARSETS && \"accessing a scalarset \"\n" << " \"schedule which was unanticipated; bug in\"\n" << " \"prints_scalarsets()?\");\n" << " }\n" << " /* we can use the saved schedule to map this value back " "to a\n" << " * more intuitive string for the user\n" << " */\n" << " size_t index = schedule_read_" << schedule_type->name << "(previous);\n" << " size_t schedule[" << bound << "];\n" << " size_t stack[" << bound << "];\n" << " index_to_permutation(index, schedule, stack, " << bound << ");\n" << " ASSERT((size_t)v_previous - 1 < " << bound << " && \"illegal scalarset \"\n" << " \"value found during printing\");\n" << " v_previous = (raw_value_t)schedule[(size_t)v_previous - " "1];\n" << " }\n"; } *out << " }\n" << " if (previous == NULL || v != v_previous) {\n" << " if (" << support_xml << " && MACHINE_READABLE_OUTPUT) {\n" << " put(\"name << "(s);\n" << " size_t schedule[" << bound << "];\n" << " size_t stack[" << bound << "];\n" << " index_to_permutation(index, schedule, stack, " << bound << ");\n" << " if (" << support_xml << " && MACHINE_READABLE_OUTPUT) {\n" << " xml_printf(\"" << escape(schedule_type->name) << "\");\n" << " } else {\n" << " put(\"" << escape(schedule_type->name) << "\");\n" << " }\n" << " ASSERT((size_t)v - 1 < " << bound << " && \"illegal scalarset \"\n" << " \"value found during printing\");\n" << " put(\"_\");\n" << " put_uint(schedule[(size_t)v - 1]);\n"; } *out << " } else {\n" << " put_val(v - 1);\n" << " }\n" << " if (" << support_xml << " && MACHINE_READABLE_OUTPUT) {\n" << " put(\"\\\"/>\");\n" << " }\n" << " put(\"\\n\");\n" << " }\n" << "}\n"; } void visit_typeexprid(const TypeExprID &n) final { if (n.referent == nullptr) throw Error("unresolved type symbol \"" + n.name + "\"", n.loc); // is this type a reference to a (symmetry reduced) scalarset? auto s = dynamic_cast(n.referent->value.get()); if (s != nullptr) { // save this declaration for later reading the schedule of this type schedule_type = n.referent.get(); } dispatch(*n.referent->value); } virtual ~Generator() = default; }; } // namespace void generate_print(std::ostream &out, const TypeExpr &e, const std::string &prefix, const std::string &handle, bool support_diff, bool support_xml) { // construct an equivalent handle to this data in the previous state const std::string ph = to_previous(handle); Generator g(out, Printf(prefix), handle, ph, support_diff, support_xml); g.dispatch(e); } rumur-2024.05.07/rumur/src/generate-property.cc000066400000000000000000000003211461637631000212140ustar00rootroot00000000000000#include "generate.h" #include #include #include using namespace rumur; void generate_property(std::ostream &out, const Property &p) { generate_rvalue(out, *p.expr); } rumur-2024.05.07/rumur/src/generate-quantifier.cc000066400000000000000000000172351461637631000215130ustar00rootroot00000000000000#include "generate.h" #include #include #include #include #include using namespace rumur; void generate_quantifier_header(std::ostream &out, const Quantifier &q) { /* Set up quantifiers. It might be surprising to notice that there is an extra * level of indirection here. A variable 'x' results in loop counter '_ru1_x', * storage array '_ru2_x' and handle 'ru_x'. We use three variables rather * than two in order to avoid rules that modify the ruleset parameters * (uncommon) affecting the loop counter. */ const std::string counter = "_ru1_" + q.name; const std::string block = "_ru2_" + q.name; const std::string handle = "ru_" + q.name; // Calculate the width of the loop counter type. Use the VarDecl that // references to this variable will be referring to. std::string width = "((size_t)" + q.decl->type->width().get_str() + "ull)"; // open a scope to allow us to use the names 'lb', 'ub', and 'step' without // worrying about collisions out << "{\n"; // Write out the upper bound, lower bound, and step in advance. We generate // these here, rather than inline so as to avoid evaluating them twice (once // here and then once in the generate_quantifier_footer) as they may contain // side effects. out << " const value_t lb = "; if (q.type == nullptr) { assert(q.from != nullptr); generate_rvalue(out, *q.from); } else { out << q.type->lower_bound(); } out << ";\n"; out << " const value_t ub = "; if (q.type == nullptr) { assert(q.to != nullptr); generate_rvalue(out, *q.to); } else { out << q.type->upper_bound(); } out << ";\n"; out << " const raw_value_t step = (raw_value_t)"; if (q.step == nullptr) { out << "(ub >= lb ? 1 : -1)"; } else { generate_rvalue(out, *q.step); } out << ";\n"; // if the step was not a generation-time constant, it is possible that it // could work out to be 0 at runtime resulting in an infinite loop out << " if (step == 0) {\n" << " error(s, \"infinite loop due to step being 0\");\n" << " }\n"; // it is also possible we find the iteration goes the wrong way out << "#ifdef __clang__\n" << " #pragma clang diagnostic push\n" << " #pragma clang diagnostic ignored " "\"-Wtautological-unsigned-zero-compare\"\n" << "#elif defined(__GNUC__)\n" << " #pragma GCC diagnostic push\n" << " #pragma GCC diagnostic ignored \"-Wtype-limits\"\n" << "#endif\n" << " if ((ub > lb && (value_t)step < 0) ||\n" << " (ub < lb && (value_t)step > 0)) {\n" << "#ifdef __clang__\n" << " #pragma clang diagnostic pop\n" << "#elif defined(__GNUC__)\n" << " #pragma GCC diagnostic pop\n" << "#endif\n" << " error(s, \"infinite loop due to step being in the wrong " "direction\");\n" << " }\n"; // Type lower bound, as distinct from the iteration lower bound. // bounds. References to this quantified variable will use these when // unpacking its compressed representation, so we need to offset the values we // store from it. const std::string lower = q.decl->type->lower_bound(); const std::string upper = q.decl->type->upper_bound(); out << "#if !defined(__clang__) && defined(__GNUC__)\n" << " #pragma GCC diagnostic push\n" << " #pragma GCC diagnostic ignored \"-Wtype-limits\"\n" << "#endif\n" << " ASSERT(lb >= " << lower << " && lb <= " << upper << " && " "\"iteration lower bound exceeds type limits\");\n" << " ASSERT(ub >= " << lower << " && ub <= " << upper << " && " "\"iteration upper bound exceeds type limits\");\n" << "#if !defined(__clang__) && defined(__GNUC__)\n" << " #pragma GCC diagnostic pop\n" << "#endif\n"; // shorthands for converting between value_t and raw_value_t when we know the // value we have is in range #define V_TO_RV(x) \ ("((raw_value_t)((raw_value_t)(((raw_value_t)" + std::string(x) + \ ") + (raw_value_t)1) - (raw_value_t)(" + q.decl->type->lower_bound() + \ ")))") #define RV_TO_V(x) \ ("((value_t)(" + std::string(x) + " - (raw_value_t)1 + (raw_value_t)(" + \ q.decl->type->lower_bound() + ")))") // construct the pieces of our for-loop header const std::string init = "raw_value_t " + counter + " = " + V_TO_RV("lb"); const std::string cond = counter + " == " + V_TO_RV("lb") + " || " "(lb < ub && " + RV_TO_V(counter) + " <= ub) || " "(lb > ub && " + RV_TO_V(counter) + " >= ub)"; const std::string inc = counter + " += step"; out << " for (" << init << "; " << cond << "; " << inc << ") {\n" << " uint8_t " << block << "[BITS_TO_BYTES(" << width << ")] = { 0 };\n" << " struct handle " << handle << " = { .base = " << block << ", .offset = 0, .width = " << width << " };\n" << " handle_write_raw(s, " << handle << ", " << counter << ");\n"; } void generate_quantifier_footer(std::ostream &out, const Quantifier &q) { const std::string counter = "_ru1_" + q.name; // is this a loop whose last iteration will result in a numeric overflow? std::string will_overflow = RV_TO_V("max_ - step") + " < ub"; // are we on the last iteration? std::string last_iteration = counter + " > RAW_VALUE_MAX - step"; // does this loop count up? const std::string up_count = "ub >= lb && (value_t)step > 0"; out << " /* If this iteration runs right up to the type limits, the last\n" << " * increment will overflow and fail to terminate, so we guard\n" << " * against that here.\n" << " */\n" << " {\n" << " /* GCC issues spurious -Wsign-compare warnings when doing\n" << " * subtraction on raw_value_t literals, so we suppress these\n" << " * by indirecting via this local constant. For more " "information:\n" << " * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=38341\n" << " */\n" << " const raw_value_t max_ = RAW_VALUE_MAX;\n" << " if (" << up_count << " && " << will_overflow << " && " << last_iteration << ") {\n" << " break;\n" << " }\n" << " }\n"; // do the same for if it is a down-counting loop will_overflow = RV_TO_V("min_ - step") + " > ub"; last_iteration = RV_TO_V(counter) + " < " + RV_TO_V("RAW_VALUE_MIN - step"); const std::string down_count = "ub <= lb && (value_t)step < 0"; out << "#ifdef __clang__\n" << " #pragma clang diagnostic push\n" << " #pragma clang diagnostic ignored " "\"-Wtautological-unsigned-zero-compare\"\n" << "#elif defined(__GNUC__)\n" << " #pragma GCC diagnostic push\n" << " #pragma GCC diagnostic ignored \"-Wtype-limits\"\n" << " #pragma GCC diagnostic ignored \"-Wsign-compare\"\n" << "#endif\n" << " {\n" << " /* see above explanation of why we use an extra constant here " "*/\n" << " const raw_value_t min_ = RAW_VALUE_MIN;\n" << " if (" << down_count << " && " << will_overflow << " && " << last_iteration << ") {\n" << "#ifdef __clang__\n" << " #pragma clang diagnostic pop\n" << "#elif defined(__GNUC__)\n" << " #pragma GCC diagnostic pop\n" << "#endif\n" << " break;\n" << " }\n" << " }\n"; out << " }\n" << "}\n"; } #undef V_TO_RV #undef RV_TO_V rumur-2024.05.07/rumur/src/generate-stmt.cc000066400000000000000000000240011461637631000203200ustar00rootroot00000000000000#include "../../common/escape.h" #include "../../common/isa.h" #include "generate.h" #include "options.h" #include "utils.h" #include #include #include #include #include #include #include using namespace rumur; static void clear(std::ostream &out, const TypeExpr &t, const std::string &offset = "((size_t)0)", size_t depth = 0) { const std::string indent = std::string(2 * (depth + 1), ' '); if (t.is_simple()) { out << indent << "handle_write_raw(s, (struct handle){ .base = root.base + " << "(root.offset + " << offset << ") / CHAR_BIT, .offset = " << "(root.offset + " << offset << ") % CHAR_BIT, .width = " << t.width() << "ull }, 1);\n"; return; } const Ptr type = t.resolve(); if (auto a = dynamic_cast(type.get())) { // The number of elements in this array as a C code string mpz_class ic = a->index_type->count() - 1; const std::string ub = "((size_t)" + ic.get_str() + "ull)"; // The bit size of each array element as a C code string const std::string width = "((size_t)" + a->element_type->width().get_str() + "ull)"; // Generate a loop to iterate over all the elements const std::string var = "i" + std::to_string(depth); out << indent << "for (size_t " << var << " = 0; " << var << " < " << ub << "; " << var << "++) {\n"; // Generate code to clear each element const std::string off = offset + " + " + var + " * " + width; clear(out, *a->element_type, off, depth + 1); // Close the loop out << indent << "}\n"; return; } if (auto r = dynamic_cast(type.get())) { std::string off = offset; for (const Ptr &f : r->fields) { // Generate code to clear this field clear(out, *f->type, off, depth); // Jump over this field to get the offset of the next field const std::string width = "((size_t)" + f->type->width().get_str() + "ull)"; off += " + " + width; } return; } assert(!"unreachable"); } namespace { class Generator : public ConstStmtTraversal { private: std::ostream *out; public: Generator(std::ostream &o) : out(&o) {} void visit_aliasstmt(const AliasStmt &s) final { *out << " {\n"; for (auto &a : s.aliases) { *out << " "; generate_decl(*out, *a); *out << ";\n"; } for (auto &st : s.body) { *out << " "; generate_stmt(*out, *st); *out << ";\n"; } *out << " }\n"; } void visit_assignment(const Assignment &s) final { if (s.lhs->type()->is_simple()) { const std::string lb = s.lhs->type()->lower_bound(); const std::string ub = s.lhs->type()->upper_bound(); *out << "handle_write(" << to_C_string(s.loc) << ", rule_name, " << to_C_string(*s.lhs) << ", s, " << lb << ", " << ub << ", "; generate_lvalue(*out, *s.lhs); *out << ", "; generate_rvalue(*out, *s.rhs); *out << ")"; } else { *out << "handle_copy("; generate_lvalue(*out, *s.lhs); *out << ", "; generate_rvalue(*out, *s.rhs); *out << ")"; } } void visit_clear(const Clear &s) final { *out << "do {\n" << " struct handle root = "; generate_lvalue(*out, *s.rhs); *out << ";\n"; clear(*out, *s.rhs->type()); *out << "} while (0)"; } void visit_errorstmt(const ErrorStmt &s) final { *out << "error(s, \"%s\", \"" << escape(s.message) << "\")"; } void visit_for(const For &s) final { generate_quantifier_header(*out, s.quantifier); for (auto &st : s.body) { *out << " "; generate_stmt(*out, *st); *out << ";\n"; } generate_quantifier_footer(*out, s.quantifier); } void visit_if(const If &s) final { bool first = true; for (const IfClause &c : s.clauses) { /* HACK: Equality comparisons against by-value function/procedure * parameters of simple type can result in code generation like: * * if ((ru_x == ...)) { * * On compilers with -Wparentheses-equality (e.g. Apple's Clang 10.0.0) * this generates a spurious warning. To avoid this, we suppress the * duplicate brackets for any comparison using a binary operator. */ bool needs_bracketing = !isa(c.condition); // equality comparison with complex types does not cause double bracketing if (c.condition != nullptr) { if (auto e = dynamic_cast(&*c.condition)) { if (!e->lhs->type()->is_simple()) needs_bracketing = true; } if (auto e = dynamic_cast(&*c.condition)) { if (!e->lhs->type()->is_simple()) needs_bracketing = true; } } if (!first) *out << "else "; if (c.condition != nullptr) { *out << "if "; if (needs_bracketing) *out << "("; generate_rvalue(*out, *c.condition); if (needs_bracketing) *out << ") "; } *out << " {\n"; for (auto &st : c.body) { generate_stmt(*out, *st); *out << ";\n"; } *out << "}\n"; first = false; } } void visit_procedurecall(const ProcedureCall &s) final { generate_rvalue(*out, s.call); } void visit_propertystmt(const PropertyStmt &s) final { switch (s.property.category) { case Property::ASSERTION: *out << "if (__builtin_expect(!"; generate_property(*out, s.property); *out << ", 0)) {\nerror(s, \"Assertion failed: %s:" << s.loc << ": %s\", \"" << escape(input_filename) << "\", "; if (s.message == "") { /* Assertion has no associated text. Use the expression itself * instead. */ *out << to_C_string(*s.property.expr); } else { *out << "\"" << escape(s.message) << "\""; } *out << ");\n}"; break; case Property::ASSUMPTION: *out << "if (__builtin_expect(!"; generate_property(*out, s.property); *out << ", 0)) {\n" << " assert(JMP_BUF_NEEDED && \"longjmping without a setup " "jmp_buf\");\n" << " siglongjmp(checkpoint, 1);\n" << "}"; break; case Property::COVER: *out << "if ("; generate_property(*out, s.property); *out << ") {\n" << " (void)__atomic_fetch_add(&covers[COVER_" << s.property.unique_id << "], 1, __ATOMIC_SEQ_CST);\n" << "}"; break; case Property::LIVENESS: assert(s.property.category != Property::LIVENESS && "liveness property " "illegally appearing in statement instead of at the top level"); break; } } void visit_put(const Put &s) final { if (s.expr == nullptr) { *out << "put(\"" << s.value << "\")"; return; } if (s.expr->is_lvalue()) { // Construct a string containing a handle to this expression std::ostringstream buffer; generate_lvalue(buffer, *s.expr); generate_print(*out, *s.expr->type(), s.expr->to_string(), buffer.str(), false, false); return; } assert(s.expr->type()->is_simple() && "complex non-lvalue in put statement"); const Ptr type = s.expr->type()->resolve(); if (auto e = dynamic_cast(type.get())) { *out << "{\n" << " value_t v = "; generate_rvalue(*out, *s.expr); *out << ";\n"; size_t i = 0; for (const std::pair &m : e->members) { *out << " "; if (i != 0) *out << "else "; *out << "if (v == " << i << ") {\n" << " put(\"" << m.first << "\");\n" << " }\n"; i++; } if (!e->members.empty()) *out << "else {\n" << " assert(\"illegal value read from enum expression\");\n" << "}\n"; *out << "}"; return; } *out << "put_val("; generate_rvalue(*out, *s.expr); *out << ")"; } void visit_return(const Return &s) final { if (s.expr == nullptr) { *out << "return true"; } else { if (s.expr->type()->is_simple()) { *out << "return "; generate_rvalue(*out, *s.expr); } else { /* The caller will have passed us a handle 'ret' to memory they have * allocated. Copy into it now. */ *out << "do {\n" << " handle_copy(ret, "; generate_rvalue(*out, *s.expr); *out << ");\n" << " return ret;\n" << "} while (0)"; } } } void visit_switch(const Switch &s) final { *out << "do {\n" // Mark the following unused in case there are no cases << " value_t v __attribute__((unused)) = "; generate_rvalue(*out, *s.expr); *out << ";\n"; bool first_case = true; for (const SwitchCase &c : s.cases) { *out << " "; if (!first_case) *out << "else "; if (!c.matches.empty()) { *out << "if ("; bool first_match = true; for (auto &m : c.matches) { if (!first_match) *out << " || "; *out << "v == "; generate_rvalue(*out, *m); first_match = false; } *out << ") "; } *out << "{\n"; for (auto &st : c.body) { *out << " "; dispatch(*st); *out << ";\n"; } *out << " }\n"; first_case = false; } *out << "} while (0)"; } void visit_undefine(const Undefine &s) final { *out << "handle_zero("; generate_lvalue(*out, *s.rhs); *out << ")"; } void visit_while(const While &s) final { *out << "while ("; generate_rvalue(*out, *s.condition); *out << ") {\n"; for (auto &st : s.body) { *out << " "; generate_stmt(*out, *st); *out << ";\n"; } *out << "}"; } virtual ~Generator() = default; }; } // namespace void generate_stmt(std::ostream &out, const Stmt &s) { Generator g(out); g.dispatch(s); } rumur-2024.05.07/rumur/src/generate.h000066400000000000000000000032741461637631000172060ustar00rootroot00000000000000#pragma once #include "ValueType.h" #include #include #include #include #include #include #include int output_checker(const std::string &path, const rumur::Model &model, const std::pair &value_types); // Generate prelude definitions to allocate memory for function returns void generate_allocations(std::ostream &out, const rumur::Stmt &stmt); // Helper for calling the above on a body of functions void generate_allocations(std::ostream &out, const std::vector> &stmts); // Generate definition of a ConstDecl or VarDecl void generate_decl(std::ostream &out, const rumur::Decl &d); void generate_function(std::ostream &out, const rumur::Function &f, const std::vector &decls); void generate_model(std::ostream &out, const rumur::Model &m); // Generate C code to print the value of the given type at the given handle. void generate_print(std::ostream &out, const rumur::TypeExpr &e, const std::string &prefix, const std::string &handle, bool support_diff, bool support_xml); void generate_property(std::ostream &out, const rumur::Property &p); void generate_lvalue(std::ostream &out, const rumur::Expr &e); void generate_rvalue(std::ostream &out, const rumur::Expr &e); void generate_quantifier_header(std::ostream &out, const rumur::Quantifier &q); void generate_quantifier_footer(std::ostream &out, const rumur::Quantifier &q); void generate_stmt(std::ostream &out, const rumur::Stmt &s); void generate_cover_array(std::ostream &out, const rumur::Model &model); rumur-2024.05.07/rumur/src/has-start-state.cc000066400000000000000000000006071461637631000205730ustar00rootroot00000000000000#include "has-start-state.h" #include #include using namespace rumur; namespace { class Finder : public ConstTraversal { public: bool result = false; void visit_startstate(const StartState &) final { result = true; } virtual ~Finder() = default; }; } // namespace bool has_start_state(const Model &m) { Finder f; f.dispatch(m); return f.result; } rumur-2024.05.07/rumur/src/has-start-state.h000066400000000000000000000002321461637631000204270ustar00rootroot00000000000000#pragma once #include #include // does this model have at least one start state? bool has_start_state(const rumur::Model &m); rumur-2024.05.07/rumur/src/log.cc000066400000000000000000000016101461637631000163230ustar00rootroot00000000000000#include "log.h" #include "options.h" #include #include #include // A buffer that discards data written to it namespace { class NullBuf : public std::streambuf { public: int overflow(int c) { return c; } }; } // namespace // A stream that discards data written to it NullBuf null_buf; std::ostream null(&null_buf); std::ostream *debug = &null; std::ostream *info = &null; std::ostream *warn = &std::cerr; void set_log_level(LogLevel level) { switch (level) { case LogLevel::SILENT: debug = &null; info = &null; warn = &null; break; case LogLevel::WARNINGS: debug = &null; info = &null; warn = &std::cerr; break; case LogLevel::INFO: debug = &null; info = &std::cerr; warn = &std::cerr; break; case LogLevel::DEBUG: debug = &std::cerr; info = &std::cerr; warn = &std::cerr; break; } } rumur-2024.05.07/rumur/src/log.h000066400000000000000000000003021461637631000161620ustar00rootroot00000000000000#pragma once #include "options.h" #include #include void set_log_level(LogLevel level); extern std::ostream *debug; extern std::ostream *info; extern std::ostream *warn; rumur-2024.05.07/rumur/src/main.cc000066400000000000000000000542031461637631000164740ustar00rootroot00000000000000#include "../../common/help.h" #include "ValueType.h" #include "environ.h" #include "generate.h" #include "has-start-state.h" #include "log.h" #include "optimise-field-ordering.h" #include "options.h" #include "resources.h" #include "smt/except.h" #include "smt/simplify.h" #include "utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace rumur; static std::shared_ptr in; static std::shared_ptr out; static unsigned string_to_percentage(const std::string &s) { int p; try { p = std::stoi(s); if (p < 1 || p > 100) throw std::invalid_argument(""); } catch (std::out_of_range &) { throw std::invalid_argument(""); } return (unsigned)p; } static void parse_args(int argc, char **argv) { for (;;) { enum { OPT_BOUND = 128, OPT_COLOUR, OPT_COUNTEREXAMPLE_TRACE, OPT_DEADLOCK_DETECTION, OPT_MAX_ERRORS, OPT_MONOPOLISE, OPT_OUTPUT_FORMAT, OPT_PACK_STATE, OPT_POINTER_BITS, OPT_REORDER_FIELDS, OPT_SANDBOX, OPT_SCALARSET_SCHEDULES, OPT_SMT_ARG, OPT_SMT_BITVECTORS, OPT_SMT_BUDGET, OPT_SMT_PATH, OPT_SMT_PRELUDE, OPT_SMT_SIMPLIFICATION, OPT_SYMMETRY_REDUCTION, OPT_TRACE, OPT_VALUE_TYPE, OPT_VERSION, }; static struct option opts[] = { {"bound", required_argument, 0, OPT_BOUND}, {"color", required_argument, 0, OPT_COLOUR}, {"colour", required_argument, 0, OPT_COLOUR}, {"counterexample-trace", required_argument, 0, OPT_COUNTEREXAMPLE_TRACE}, {"deadlock-detection", required_argument, 0, OPT_DEADLOCK_DETECTION}, {"debug", no_argument, 0, 'd'}, {"help", no_argument, 0, 'h'}, {"max-errors", required_argument, 0, OPT_MAX_ERRORS}, {"monopolise", no_argument, 0, OPT_MONOPOLISE}, {"monopolize", no_argument, 0, OPT_MONOPOLISE}, {"output", required_argument, 0, 'o'}, {"output-format", required_argument, 0, OPT_OUTPUT_FORMAT}, {"pack-state", required_argument, 0, OPT_PACK_STATE}, {"pointer-bits", required_argument, 0, OPT_POINTER_BITS}, {"quiet", no_argument, 0, 'q'}, {"reorder-fields", required_argument, 0, OPT_REORDER_FIELDS}, {"sandbox", required_argument, 0, OPT_SANDBOX}, {"scalarset-schedules", required_argument, 0, OPT_SCALARSET_SCHEDULES}, {"set-capacity", required_argument, 0, 's'}, {"set-expand-threshold", required_argument, 0, 'e'}, {"smt-arg", required_argument, 0, OPT_SMT_ARG}, {"smt-bitvectors", required_argument, 0, OPT_SMT_BITVECTORS}, {"smt-budget", required_argument, 0, OPT_SMT_BUDGET}, {"smt-path", required_argument, 0, OPT_SMT_PATH}, {"smt-prelude", required_argument, 0, OPT_SMT_PRELUDE}, {"smt-simplification", required_argument, 0, OPT_SMT_SIMPLIFICATION}, {"symmetry-reduction", required_argument, 0, OPT_SYMMETRY_REDUCTION}, {"threads", required_argument, 0, 't'}, {"trace", required_argument, 0, OPT_TRACE}, {"value-type", required_argument, 0, OPT_VALUE_TYPE}, {"verbose", no_argument, 0, 'v'}, {"version", no_argument, 0, OPT_VERSION}, {0, 0, 0, 0}, }; int option_index = 0; int c = getopt_long(argc, argv, "de:ho:qs:t:v", opts, &option_index); if (c == -1) break; switch (c) { case 'd': // --debug options.log_level = LogLevel::DEBUG; set_log_level(options.log_level); break; case 'e': { // --set-expand-threshold ... bool valid = true; try { options.set_expand_threshold = string_to_percentage(optarg); } catch (std::invalid_argument &) { valid = false; } if (!valid) { std::cerr << "invalid --set-expand-threshold argument \"" << optarg << "\"\n"; exit(EXIT_FAILURE); } break; } case 'h': // --help help(doc_rumur_1, doc_rumur_1_len); exit(EXIT_SUCCESS); case 'o': // --output ... out = std::make_shared(optarg); break; case 'q': // --quiet options.log_level = LogLevel::SILENT; set_log_level(options.log_level); break; case 's': { // --set-capacity ... bool valid = true; try { options.set_capacity = optarg; if (options.set_capacity <= 0) valid = false; } catch (std::invalid_argument &) { valid = false; } if (!valid) { std::cerr << "invalid --set-capacity argument \"" << optarg << "\"\n"; exit(EXIT_FAILURE); } break; } case 't': { // --threads ... bool valid = true; try { options.threads = optarg; if (options.threads < 0) valid = false; } catch (std::invalid_argument &) { valid = false; } if (!valid) { std::cerr << "invalid --threads argument \"" << optarg << "\"\n"; exit(EXIT_FAILURE); } break; } case 'v': // --verbose options.log_level = LogLevel::INFO; set_log_level(options.log_level); break; case '?': std::cerr << "run `" << argv[0] << " --help` to see available options\n"; exit(EXIT_FAILURE); case OPT_COLOUR: // --colour ... if (strcmp(optarg, "auto") == 0) { options.color = Color::AUTO; } else if (strcmp(optarg, "on") == 0) { if (options.machine_readable_output) { std::cerr << "colour is not supported in combination with " << "--output-format \"machine readable\"\n"; exit(EXIT_FAILURE); } options.color = Color::ON; } else if (strcmp(optarg, "off") == 0) { options.color = Color::OFF; } else { std::cerr << "invalid --colour argument \"" << optarg << "\"\n" << "valid arguments are \"auto\", \"off\", and \"on\"\n"; exit(EXIT_FAILURE); } break; case OPT_TRACE: // --trace ... if (strcmp(optarg, "handle_reads") == 0) { options.traces |= TC_HANDLE_READS; } else if (strcmp(optarg, "handle_writes") == 0) { options.traces |= TC_HANDLE_WRITES; } else if (strcmp(optarg, "memory_usage") == 0) { options.traces |= TC_MEMORY_USAGE; } else if (strcmp(optarg, "queue") == 0) { options.traces |= TC_QUEUE; } else if (strcmp(optarg, "set") == 0) { options.traces |= TC_SET; } else if (strcmp(optarg, "symmetry_reduction") == 0) { options.traces |= TC_SYMMETRY_REDUCTION; } else if (strcmp(optarg, "all") == 0) { options.traces = uint64_t(-1); } else { std::cerr << "invalid --trace argument \"" << optarg << "\"\n" << "valid arguments are \"handle_reads\", \"handle_writes\", " "\"memory_usage\", \"queue\", \"set\", and " "\"symmetry_reduction\"\n"; exit(EXIT_FAILURE); } break; case OPT_DEADLOCK_DETECTION: // --deadlock-detection ... if (strcmp(optarg, "off") == 0) { options.deadlock_detection = DeadlockDetection::OFF; } else if (strcmp(optarg, "stuck") == 0) { options.deadlock_detection = DeadlockDetection::STUCK; } else if (strcmp(optarg, "stuttering") == 0) { options.deadlock_detection = DeadlockDetection::STUTTERING; } else { std::cerr << "invalid argument to --deadlock-detection, \"" << optarg << "\"\n"; exit(EXIT_FAILURE); } break; case OPT_MONOPOLISE: { // --monopolise long pagesize = sysconf(_SC_PAGESIZE); if (pagesize < 0) { perror("failed to retrieve page size"); exit(EXIT_FAILURE); } long physpages = sysconf(_SC_PHYS_PAGES); if (physpages < 0) { perror("failed to retrieve physical pages"); exit(EXIT_FAILURE); } /* Allocate a set that will eventually cover all of memory upfront. Note * that this will never actually reach 100% occupancy because memory * also needs to contain our code and data as well as the OS. */ options.set_capacity = size_t(pagesize) * size_t(physpages); // Never expand the set. options.set_expand_threshold = 100; break; } case OPT_PACK_STATE: // --pack-state ... if (strcmp(optarg, "on") == 0) { options.pack_state = true; } else if (strcmp(optarg, "off") == 0) { options.pack_state = false; } else { std::cerr << "invalid argument to --pack_state, \"" << optarg << "\"\n"; exit(EXIT_FAILURE); } break; case OPT_POINTER_BITS: // --pointer-bits ... if (strcmp(optarg, "auto") == 0) { options.pointer_bits = 0; } else { bool valid = true; try { options.pointer_bits = optarg; if (options.pointer_bits <= 0) valid = false; } catch (std::invalid_argument &) { valid = false; } if (!valid) { std::cerr << "invalid --pointer-bits argument \"" << optarg << "\"\n"; exit(EXIT_FAILURE); } } break; case OPT_SYMMETRY_REDUCTION: // --symmetry-reduction ... if (strcmp(optarg, "off") == 0) { options.symmetry_reduction = SymmetryReduction::OFF; } else if (strcmp(optarg, "heuristic") == 0) { options.symmetry_reduction = SymmetryReduction::HEURISTIC; } else if (strcmp(optarg, "exhaustive") == 0) { options.symmetry_reduction = SymmetryReduction::EXHAUSTIVE; } else { std::cerr << "invalid argument to --symmetry-reduction, \"" << optarg << "\"\n"; exit(EXIT_FAILURE); } break; case OPT_SANDBOX: // --sandbox ... if (strcmp(optarg, "on") == 0) { options.sandbox_enabled = true; } else if (strcmp(optarg, "off") == 0) { options.sandbox_enabled = false; } else { std::cerr << "invalid argument to --sandbox, \"" << optarg << "\"\n"; exit(EXIT_FAILURE); } break; case OPT_SCALARSET_SCHEDULES: // --scalarset-schedules ... if (strcmp(optarg, "on") == 0) { options.scalarset_schedules = true; } else if (strcmp(optarg, "off") == 0) { options.scalarset_schedules = false; } else { std::cerr << "invalid argument to --scalarset-schedules, \"" << optarg << "\"\n"; exit(EXIT_FAILURE); } break; case OPT_MAX_ERRORS: { // --max-errors ... bool valid = true; try { options.max_errors = optarg; if (options.max_errors <= 0) valid = false; } catch (std::invalid_argument &) { valid = false; } if (!valid) { std::cerr << "invalid --max-errors argument \"" << optarg << "\"\n"; exit(EXIT_FAILURE); } break; } case OPT_COUNTEREXAMPLE_TRACE: // --counterexample-trace ... if (strcmp(optarg, "full") == 0) { options.counterexample_trace = CounterexampleTrace::FULL; } else if (strcmp(optarg, "diff") == 0) { options.counterexample_trace = CounterexampleTrace::DIFF; } else if (strcmp(optarg, "off") == 0) { options.counterexample_trace = CounterexampleTrace::OFF; } else { std::cerr << "invalid argument to --counterexample-trace, \"" << optarg << "\"\n"; exit(EXIT_FAILURE); } break; case OPT_OUTPUT_FORMAT: // --output-format ... if (strcmp(optarg, "machine-readable") == 0) { options.machine_readable_output = true; // Disable colour that would interfere with XML options.color = Color::OFF; } else if (strcmp(optarg, "human-readable") == 0) { options.machine_readable_output = false; } else { std::cerr << "invalid argument to --output-format, \"" << optarg << "\"\n"; exit(EXIT_FAILURE); } break; case OPT_VERSION: // --version std::cout << "Rumur version " << get_version() << "\n"; exit(EXIT_SUCCESS); case OPT_BOUND: { // --bound ... bool valid = true; try { options.bound = optarg; if (options.bound < 0) valid = false; } catch (std::invalid_argument &) { valid = false; } if (!valid) { std::cerr << "invalid --bound argument \"" << optarg << "\"\n"; exit(EXIT_FAILURE); } break; } case OPT_VALUE_TYPE: // --value-type ... options.value_type = optarg; break; case OPT_REORDER_FIELDS: // --reorder-fields ... if (strcmp(optarg, "on") == 0) { options.reorder_fields = true; } else if (strcmp(optarg, "off") == 0) { options.reorder_fields = false; } else { std::cerr << "invalid argument to --reorder-fields, \"" << optarg << "\"\n"; exit(EXIT_FAILURE); } break; case OPT_SMT_ARG: // --smt-arg ... options.smt.args.emplace_back(optarg); if (options.smt.simplification == SmtSimplification::AUTO) { options.smt.simplification = SmtSimplification::ON; } break; case OPT_SMT_BITVECTORS: // --smt-bitvectors ... if (strcmp(optarg, "on") == 0) { options.smt.use_bitvectors = true; } else if (strcmp(optarg, "off") == 0) { options.smt.use_bitvectors = false; } else { std::cerr << "invalid argument to --smt-bitvectors, \"" << optarg << "\"\n"; exit(EXIT_FAILURE); } if (options.smt.simplification == SmtSimplification::AUTO) { options.smt.simplification = SmtSimplification::ON; } break; case OPT_SMT_BUDGET: { // --smt-budget ... bool valid = true; try { options.smt.budget = optarg; if (options.smt.budget < 0) valid = false; } catch (std::invalid_argument &) { valid = false; } if (!valid) { std::cerr << "invalid --smt-budget, \"" << optarg << "\"\n"; exit(EXIT_FAILURE); } if (options.smt.simplification == SmtSimplification::AUTO) { options.smt.simplification = SmtSimplification::ON; } break; } case OPT_SMT_PATH: // --smt-path ... options.smt.path = optarg; if (options.smt.simplification == SmtSimplification::AUTO) { options.smt.simplification = SmtSimplification::ON; } break; case OPT_SMT_PRELUDE: // --smt-prelude ... options.smt.prelude.emplace_back(optarg); if (options.smt.simplification == SmtSimplification::AUTO) { options.smt.simplification = SmtSimplification::ON; } break; case OPT_SMT_SIMPLIFICATION: // --smt-simplification ... if (strcmp(optarg, "on") == 0) { options.smt.simplification = SmtSimplification::ON; } else if (strcmp(optarg, "off") == 0) { options.smt.simplification = SmtSimplification::OFF; } else { std::cerr << "invalid argument to --smt-simplification, \"" << optarg << "\"\n"; exit(EXIT_FAILURE); } break; default: std::cerr << "unexpected error\n"; exit(EXIT_FAILURE); } } if (optind == argc - 1) { struct stat buf; if (stat(argv[optind], &buf) < 0) { std::cerr << "failed to open " << argv[optind] << ": " << strerror(errno) << "\n"; exit(EXIT_FAILURE); } if (S_ISDIR(buf.st_mode)) { std::cerr << "failed to open " << argv[optind] << ": this is a directory\n"; exit(EXIT_FAILURE); } auto inf = std::make_shared(argv[optind]); if (!inf->is_open()) { std::cerr << "failed to open " << argv[optind] << "\n"; exit(EXIT_FAILURE); } input_filename = argv[optind]; in = inf; } if (out == nullptr) { std::cerr << "output file is required\n"; exit(EXIT_FAILURE); } if (options.threads == 0) { // automatic long r = sysconf(_SC_NPROCESSORS_ONLN); if (r < 1) { options.threads = 1; } else { options.threads = r; } } if (options.smt.simplification == SmtSimplification::ON && options.smt.path == "") { *warn << "SMT simplification was enabled but no path was provided to the " << "solver (--smt-path ...), so it will be disabled\n"; options.smt.simplification = SmtSimplification::OFF; } } static bool use_colors() { return options.color == Color::ON || (options.color == Color::AUTO && isatty(STDERR_FILENO)); } static std::string bold() { if (use_colors()) return "\033[1m"; return ""; } static std::string green() { if (use_colors()) return "\033[32m"; return ""; } static std::string red() { if (use_colors()) return "\033[31m"; return ""; } static std::string reset() { if (use_colors()) return "\033[0m"; return ""; } static std::string white() { if (use_colors()) return "\033[37m"; return ""; } static void print_location(const std::string &file, const location &location) { // don't try to open stdin (see default in options.cc) if (file == "") return; std::ifstream f(file); if (!f.is_open()) { // ignore failure here as it's non-critical return; } // the type of position.line and position.column changes across Bison // releases, so avoid some -Wsign-compare warnings by casting them in advance auto loc_line = static_cast(location.begin.line); auto loc_col = static_cast(location.begin.column); std::string line; unsigned long lineno = 0; while (lineno < loc_line) { if (!std::getline(f, line)) return; lineno++; } // print the line, and construct an underline indicating the column location std::ostringstream buf; unsigned long col = 1; for (const char &c : line) { if (col == loc_col) { buf << green() << bold() << "^" << reset(); } else if (col < loc_col) { if (c == '\t') { buf << '\t'; } else { buf << ' '; } } std::cerr << c; col++; } std::cerr << "\n"; std::cerr << buf.str() << "\n"; } int main(int argc, char **argv) { // Parse command line options parse_args(argc, argv); // Parse input model *debug << "parsing input model...\n"; Ptr m; try { m = parse(in == nullptr ? std::cin : *in); } catch (Error &e) { std::cerr << white() << bold() << input_filename << ":" << e.loc << ":" << reset() << " " << red() << bold() << "error:" << reset() << " " << white() << bold() << e.what() << reset() << "\n"; print_location(input_filename, e.loc); return EXIT_FAILURE; } assert(m != nullptr); /* Re-index the model (assign unique identifiers to each node that are used in * generation of the verifier). */ *debug << "re-indexing...\n"; m->reindex(); // resolve symbolic references and validate the model try { *debug << "resolving symbols...\n"; resolve_symbols(*m); *debug << "validating AST...\n"; validate(*m); } catch (Error &e) { std::cerr << white() << bold() << input_filename << ":" << e.loc << ":" << reset() << " " << red() << bold() << "error:" << reset() << " " << white() << bold() << e.what() << reset() << "\n"; print_location(input_filename, e.loc); return EXIT_FAILURE; } // Check whether we have a start state. if (!has_start_state(*m)) *warn << "warning: model has no start state\n"; // run SMT simplification if the user enabled it if (options.smt.simplification == SmtSimplification::ON) { *debug << "SMT simplification...\n"; try { smt::simplify(*m); } catch (smt::BudgetExhausted &) { *info << "SMT solver budget (" << options.smt.budget << "ms) exhausted\n"; } catch (smt::Unsupported &e) { if (e.expr != nullptr) *info << e.expr->loc << ": "; *info << e.what() << "\n"; } } // re-order fields to optimise access to them if (options.reorder_fields) { *debug << "optimising field ordering...\n"; optimise_field_ordering(*m); } // get value_t to use in the checker *debug << "determining value_t type...\n"; std::pair value_types; try { value_types = get_value_type(options.value_type, *m); } catch (std::runtime_error &e) { std::cerr << "invalid --value-type " << options.value_type << ": " << e.what() << "\n"; return EXIT_FAILURE; } *debug << "generating verifier...\n"; assert(out != nullptr); if (output_checker(*out, *m, value_types) != 0) return EXIT_FAILURE; #ifndef __AFL_COMPILER #define __AFL_COMPILER 0 #endif if (__AFL_COMPILER) { // extra steps for when we're being fuzzed // find the C compiler const char *cc = getenv("CC"); if (cc == nullptr) cc = "cc"; // setup an argument vector for calling the C compiler const char *args[] = {cc, "-std=c11", "-x", "c", "-o", "/dev/null", "-march=native", "-Werror=format", "-Werror=sign-compare", "-Werror=type-limits", out->c_str(), #ifdef __x86_64__ "-mcx16", #endif "-lpthread"}; // start the C compiler int r = posix_spawnp(nullptr, args[0], nullptr, nullptr, const_cast(args), get_environ()); if (r != 0) { std::cerr << "posix_spawnp failed: " << strerror(r) << "\n"; abort(); } // wait for it to finish int stat_loc; pid_t pid = wait(&stat_loc); if (pid == -1) { std::cerr << "wait failed\n"; abort(); } // if it terminated abnormally, pass an error up to the fuzzer if (WIFSIGNALED(stat_loc) || WIFSTOPPED(stat_loc)) { std::cerr << "subprocess either signalled or stopped\n"; abort(); } assert(WIFEXITED(stat_loc)); } return EXIT_SUCCESS; } rumur-2024.05.07/rumur/src/max-simple-width.cc000066400000000000000000000034461461637631000207440ustar00rootroot00000000000000#include "max-simple-width.h" #include #include #include #include using namespace rumur; // An AST traversal that learns the maximum simple type width. namespace { class Measurer : public ConstTraversal { public: mpz_class max = 0; /* Nothing required for complex types, but we do need to descend into their * children. */ void visit_array(const Array &n) final { dispatch(*n.index_type); dispatch(*n.element_type); } void visit_enum(const Enum &n) final { mpz_class w = n.width(); if (w > max) max = w; } // we override visit_quantifier in order to also descend into the quantifier’s // decl that the generic traversal logic assumes you do not want to do void visit_quantifier(const Quantifier &n) final { if (n.type != nullptr) dispatch(*n.type); if (n.from != nullptr) dispatch(*n.from); if (n.to != nullptr) dispatch(*n.to); if (n.step != nullptr) dispatch(*n.step); dispatch(*n.decl); } void visit_range(const Range &n) final { mpz_class w = n.width(); if (w > max) max = w; } void visit_record(const Record &n) final { for (auto &f : n.fields) dispatch(*f); } void visit_scalarset(const Scalarset &n) final { mpz_class w = n.width(); if (w > max) max = w; } void visit_typeexprid(const TypeExprID &n) final { if (n.is_simple()) { mpz_class w = n.width(); if (w > max) max = w; } /* We don't need to descend into any children, because the referent will * either (a) already have been encountered or (b) be a built-in like * 'boolean' that is a simple type. */ } }; } // namespace mpz_class max_simple_width(const Model &m) { Measurer t; t.dispatch(m); return t.max; } rumur-2024.05.07/rumur/src/max-simple-width.h000066400000000000000000000014371461637631000206040ustar00rootroot00000000000000#pragma once #include #include #include /** Get the greatest width of any simple type used in a model. * * It may not immediately be clear why you would care about such a property. * However, note that the only handles that are ever actually *read* or * *written* in the generated checker are those for simple typed values. With * this in mind, we can conclude that handle_read_raw and handle_write_raw only * ever need to deal with handles of at most this width. Knowing this value * statically ahead of time enables some interesting optimisations. * * Returns 0 if there are no simple types in the model. This case should only * occur if the state is empty; i.e. if the model has no variables. */ mpz_class max_simple_width(const rumur::Model &m); rumur-2024.05.07/rumur/src/optimise-field-ordering.cc000066400000000000000000000101071461637631000222640ustar00rootroot00000000000000#include "optimise-field-ordering.h" #include "log.h" #include #include #include #include #include #include using namespace rumur; // is this value a power of 2? static bool is_onehot(mpz_class v) { if (v == 0) return false; return mpz_popcount(v.get_mpz_t()) == 1; } // compare two fields based on size static bool comp(const Ptr &a, const Ptr &b) { mpz_class width_a = a->type->width(); mpz_class width_b = b->type->width(); // zero-width fields trump anything else if (width_a == 0) { return true; } else if (width_b == 0) { return false; } // power-of-2 fields trump non-power-of-2 fields if (is_onehot(width_a) && !is_onehot(width_b)) return true; if (!is_onehot(width_a) && is_onehot(width_b)) return false; // otherwise, order based on larger fields first return width_a > width_b; } // sort a collection of fields static void sort(std::vector> &fields) { std::sort(fields.begin(), fields.end(), comp); } // extract a list of the names of fields within a list static std::vector get_names(const std::vector> &decls) { std::vector r; for (const Ptr &d : decls) { if (auto f = dynamic_cast(d.get())) r.push_back(f->name); } return r; } // generate debug output if a list of fields has changed static void notify_changes(const std::vector &original, const std::vector> &sorted) { // extract the current order of the fields const std::vector current = get_names(sorted); // if this has changed since the original, debug-print the changes if (original != current) { *debug << "sorted fields {"; { std::string sep; for (const std::string &f : original) { *debug << sep << f; sep = ", "; } } *debug << "} -> {"; { std::string sep = ""; for (const std::string &f : current) { *debug << sep << f; sep = ", "; } } *debug << "}\n"; } } // a traversal that reorders fields namespace { class Reorderer : public Traversal { public: // The default traversal does not descend into the referent of ExprIDs. // However, we do need to because they may have a copy of a record type whose // fields we have reordered. We need to also encounter the copy and reorder // its fields the same way. void visit_exprid(ExprID &n) final { dispatch(*n.value); } void visit_model(Model &n) final { // first act on our children for (Ptr &c : n.children) dispatch(*c); // extract out the VarDecls std::vector> vars; for (Ptr &c : n.children) { if (auto v = dynamic_cast(c.get())) { auto vp = Ptr::make(*v); vars.push_back(vp); } } const std::vector original = get_names(vars); // sort the variables sort(vars); notify_changes(original, vars); // the offset of each variable within the model state is now inaccurate, so // calculate the new VarDecl -> offset mapping mpz_class offset = 0; std::unordered_map offsets; for (Ptr &v : vars) { offsets[v->name] = offset; offset += v->type->width(); } // apply these updated offsets to the original VarDecls for (Ptr &c : n.children) { if (auto v = dynamic_cast(c.get())) v->offset = offsets[v->name]; } } void visit_record(Record &n) final { // first act on our children for (Ptr &f : n.fields) dispatch(*f); const std::vector original = get_names(n.fields); // sort the fields of the record itself sort(n.fields); notify_changes(original, n.fields); } // like ExprIDs, we also need to force descending into TypeExprID’s referents void visit_typeexprid(TypeExprID &n) final { dispatch(*n.referent); } }; } // namespace void optimise_field_ordering(Model &m) { Reorderer r; r.dispatch(m); } rumur-2024.05.07/rumur/src/optimise-field-ordering.h000066400000000000000000000003251461637631000221270ustar00rootroot00000000000000#pragma once #include #include // optimise the ordering of variables within the model and fields within record // types for faster access void optimise_field_ordering(rumur::Model &m); rumur-2024.05.07/rumur/src/options.cc000066400000000000000000000002601461637631000172350ustar00rootroot00000000000000#include "options.h" #include #include Options options; // default value of "" may be overwritten by main() std::string input_filename = ""; rumur-2024.05.07/rumur/src/options.h000066400000000000000000000055131461637631000171050ustar00rootroot00000000000000#pragma once #include #include #include #include #include enum struct Color { OFF, ON, AUTO, }; enum trace_category_t { TC_HANDLE_READS = 0x1, TC_HANDLE_WRITES = 0x2, TC_MEMORY_USAGE = 0x4, TC_QUEUE = 0x8, TC_SET = 0x10, TC_SYMMETRY_REDUCTION = 0x20, }; enum struct LogLevel { SILENT, WARNINGS, INFO, DEBUG, }; enum struct DeadlockDetection { OFF, STUCK, STUTTERING, }; enum struct CounterexampleTrace { OFF, DIFF, FULL, }; enum struct SymmetryReduction { OFF, HEURISTIC, EXHAUSTIVE, }; enum struct SmtSimplification { OFF, ON, AUTO, }; struct Options { mpz_class threads = 0; LogLevel log_level = LogLevel::WARNINGS; mpz_class set_capacity = 8 * 1024 * 1024; /* Limit (percentage occupancy) at which we expand the capacity of the state * set. */ unsigned set_expand_threshold = 75; // Whether to use ANSI colour codes in the checker's output. Color color = Color::AUTO; // Bitmask of enabled tracing uint64_t traces = 0; // Deadlock detection enabled? DeadlockDetection deadlock_detection = DeadlockDetection::STUTTERING; // Symmetry reduction enabled? SymmetryReduction symmetry_reduction = SymmetryReduction::HEURISTIC; // Use OS mechanisms to sandbox the checker? bool sandbox_enabled = false; // Number of errors to report before exiting. mpz_class max_errors = 1; // How to print counterexample traces CounterexampleTrace counterexample_trace = CounterexampleTrace::DIFF; // Print output as XML? bool machine_readable_output = false; // Limit for exploration. 0 means unbounded. mpz_class bound = 0; // Type used for value_t in the checker std::string value_type = "auto"; // whether to bit-pack members of the state struct bool pack_state = true; // whether to optimise state variable and record fields ordering bool reorder_fields = true; // whether to track schedules during scalarset permutation bool scalarset_schedules = true; // number of relevant bits in a pointer on the target platform (0 == auto) mpz_class pointer_bits = 0; // options related to SMT solver interaction struct { // Path to SMT solver. "" indicates we have no solver. std::string path; // arguments to pass to SMT solver when calling it std::vector args; // total SMT solver execution time allowed in milliseconds mpz_class budget = 30000; // use SMT solver for expression simplification? SmtSimplification simplification = SmtSimplification::AUTO; // text to emit to the solver prior to a SAT problem std::vector prelude; // use BitVecs instead of Ints? bool use_bitvectors = false; } smt; }; extern Options options; // input model's path extern std::string input_filename; rumur-2024.05.07/rumur/src/output.cc000066400000000000000000000201211461637631000171000ustar00rootroot00000000000000#include "../../common/isa.h" #include "ValueType.h" #include "assume-statements-count.h" #include "generate.h" #include "max-simple-width.h" #include "options.h" #include "prints-scalarsets.h" #include "resources.h" #include "symmetry-reduction.h" #include #include #include #include #include #include #include using namespace rumur; static std::ostream &operator<<(std::ostream &out, Color c) { switch (c) { case Color::OFF: out << "OFF"; break; case Color::ON: out << "ON"; break; case Color::AUTO: out << "AUTO"; break; } return out; } static std::ostream &operator<<(std::ostream &out, DeadlockDetection d) { switch (d) { case DeadlockDetection::OFF: out << "DEADLOCK_DETECTION_OFF"; break; case DeadlockDetection::STUCK: out << "DEADLOCK_DETECTION_STUCK"; break; case DeadlockDetection::STUTTERING: out << "DEADLOCK_DETECTION_STUTTERING"; break; } return out; } static std::ostream &operator<<(std::ostream &out, SymmetryReduction s) { switch (s) { case SymmetryReduction::OFF: out << "SYMMETRY_REDUCTION_OFF"; break; case SymmetryReduction::HEURISTIC: out << "SYMMETRY_REDUCTION_HEURISTIC"; break; case SymmetryReduction::EXHAUSTIVE: out << "SYMMETRY_REDUCTION_EXHAUSTIVE"; break; } return out; } static std::ostream &operator<<(std::ostream &out, CounterexampleTrace c) { switch (c) { case CounterexampleTrace::OFF: out << "CEX_OFF"; break; case CounterexampleTrace::DIFF: out << "DIFF"; break; case CounterexampleTrace::FULL: out << "FULL"; break; } return out; } // maximum value state.rule_taken can reach in the generated checker, given the // guarding predicate static mpz_class rule_taken_max(const Model &model, bool (*predicate)(const Rule &r)) { mpz_class total = 0; // flatten all rules for (const Ptr &c : model.children) { if (auto r = dynamic_cast(c.get())) { const std::vector> flattened = r->flatten(); for (const Ptr &f : flattened) { // skip anything that does not match our criteria if (!predicate(*f)) continue; // determine how many rules this generates when quantifiers are expanded mpz_class rules = 1; for (const Quantifier &q : f->quantifiers) { assert(q.constant() && "non-constant quantifier used in rule (unvalidated AST?)"); rules *= q.count(); } total += rules; } } } return total; } // state.rule_taken is overloaded to mean different things depending on whether // the containing state is an initial state or a subsequent one, so we need to // compute the maximum value either of these can induce static mpz_class rule_taken_max_start(const Model &model) { auto is_start = [](const Rule &r) { return isa(&r); }; return rule_taken_max(model, is_start); } static mpz_class rule_taken_max_rule(const Model &model) { auto is_rule = [](const Rule &r) { return isa(&r); }; return rule_taken_max(model, is_rule); } // maximum value needed to be representable in state.rule_taken, accounting for // configuration static mpz_class rule_taken_limit(const Model &model) { // rule taken data is only required when we need counter-example traces if (options.counterexample_trace == CounterexampleTrace::OFF) return 0; mpz_class s_max = rule_taken_max_start(model); mpz_class r_max = rule_taken_max_rule(model); return s_max > r_max ? s_max : r_max; } // number of bits required to store schedules (permutation indices) for all the // scalarsets in the model static mpz_class schedule_bits(const Model &model) { mpz_class bits = 0; // find all our scalarsets const std::vector scalarsets = get_scalarsets(model); // sum their schedule widths for (const TypeDecl *t : scalarsets) bits += get_schedule_width(*t); return bits; } int output_checker(const std::string &path, const Model &model, const std::pair &value_types) { std::ofstream out(path); if (!out) return -1; if (options.log_level < LogLevel::DEBUG) out << "#define NDEBUG 1\n\n"; out // #includes << std::string((const char *)resources_includes_c, resources_includes_c_len) << "\n" // Settings that are used in header.c << "enum { SET_CAPACITY = " << options.set_capacity << "ul };\n\n" << "enum { SET_EXPAND_THRESHOLD = " << options.set_expand_threshold << " };\n\n" << "static const enum { OFF, ON, AUTO } COLOR = " << options.color << ";\n\n" << "enum trace_category_t {\n" << " TC_HANDLE_READS = " << TC_HANDLE_READS << ",\n" << " TC_HANDLE_WRITES = " << TC_HANDLE_WRITES << ",\n" << " TC_MEMORY_USAGE = " << TC_MEMORY_USAGE << ",\n" << " TC_QUEUE = " << TC_QUEUE << ",\n" << " TC_SET = " << TC_SET << ",\n" << " TC_SYMMETRY_REDUCTION = " << TC_SYMMETRY_REDUCTION << ",\n" << "};\n" << "static const uint64_t TRACES_ENABLED = UINT64_C(" << options.traces << ");\n\n" << "static const enum {\n" << " DEADLOCK_DETECTION_OFF,\n" << " DEADLOCK_DETECTION_STUCK,\n" << " DEADLOCK_DETECTION_STUTTERING,\n" << "} DEADLOCK_DETECTION = " << options.deadlock_detection << ";\n\n" << "enum {\n" << " SYMMETRY_REDUCTION_OFF = 0,\n" << " SYMMETRY_REDUCTION_HEURISTIC = 1,\n" << " SYMMETRY_REDUCTION_EXHAUSTIVE = 2,\n" << "};\n" << "#define SYMMETRY_REDUCTION " << options.symmetry_reduction << "\n\n" << "enum { SANDBOX_ENABLED = " << options.sandbox_enabled << " };\n\n" << "enum { MAX_ERRORS = " << options.max_errors << "ul };\n\n" << "enum { THREADS = " << options.threads << "ul };\n\n" << "enum { STATE_SIZE_BITS = " << model.size_bits() << "ul };\n\n" << "enum { ASSUME_STATEMENTS_COUNT = " << assume_statements_count(model) << "ul };\n\n" << "#define LIVENESS_COUNT " << model.liveness_count() << "\n\n" << "#define CEX_OFF 0\n" << "#define DIFF 1\n" << "#define FULL 2\n" << "#define COUNTEREXAMPLE_TRACE " << options.counterexample_trace << "\n\n" << "enum { MACHINE_READABLE_OUTPUT = " << options.machine_readable_output << " };\n\n" << "enum { MAX_SIMPLE_WIDTH = " << max_simple_width(model) << " };\n\n" << "#define BOUND " << options.bound << "\n\n" << "typedef " << value_types.first.c_type << " value_t;\n" << "#define VALUE_MIN " << value_types.first.int_min << "\n" << "#define VALUE_MAX " << value_types.first.int_max << "\n" << "#define VALUE_C(x) ((value_t)" << value_types.first.int_c << "(x))\n" << "#define PRIVAL " << value_types.first.pri << "\n" << "typedef " << value_types.second.c_type << " raw_value_t;\n" << "#define RAW_VALUE_MIN " << value_types.second.int_min << "\n" << "#define RAW_VALUE_MAX " << value_types.second.int_max << "\n" << "#define PRIRAWVAL " << value_types.second.pri << "\n\n" << "#define RULE_TAKEN_LIMIT " << rule_taken_limit(model) << "\n" << "#define PACK_STATE " << (options.pack_state ? 1 : 0) << "\n" << "#define SCHEDULE_BITS " << schedule_bits(model) << "ul\n" << "#define PRINTS_SCALARSETS " << (prints_scalarsets(model) ? "1" : "0") << "\n" << "\n" << "/* whether scalarset schedules should be computed and used during " "printing */\n" << "#define USE_SCALARSET_SCHEDULES (" << (options.scalarset_schedules ? "1" : "0") << " && SYMMETRY_REDUCTION != SYMMETRY_REDUCTION_OFF && \\\n" << " (COUNTEREXAMPLE_TRACE != CEX_OFF || PRINTS_SCALARSETS))\n" << "#define POINTER_BITS " << options.pointer_bits << "\n"; generate_cover_array(out, model); // Static boiler plate code out << std::string((const char *)resources_header_c, resources_header_c_len) << "\n"; // the model itself generate_model(out, model); return 0; } rumur-2024.05.07/rumur/src/prints-scalarsets.cc000066400000000000000000000022171461637631000212270ustar00rootroot00000000000000#include "prints-scalarsets.h" #include "../../common/isa.h" #include #include using namespace rumur; // a traversal that recognises types containing scalarset references namespace { class ContainsScalarset : public ConstTraversal { public: bool result = false; void visit_typeexprid(const TypeExprID &n) final { const Ptr t = n.referent->value->resolve(); result |= isa(t); // we need to descend into the reference in case it is, e.g., an array type // with a scalarset index dispatch(*n.referent); } }; } // namespace static bool contains_scalarset(const TypeExpr &t) { ContainsScalarset c; c.dispatch(t); return c.result; } // a traversal that recognises put statements that rely on scalarsets namespace { class PutsScalarset : public ConstTraversal { public: bool result = false; void visit_put(const Put &n) final { // does this statement rely on a scalarset type? if (n.expr != nullptr) result |= contains_scalarset(*n.expr->type()); } }; } // namespace bool prints_scalarsets(const Model &m) { PutsScalarset p; p.dispatch(m); return p.result; } rumur-2024.05.07/rumur/src/prints-scalarsets.h000066400000000000000000000005631461637631000210730ustar00rootroot00000000000000#include // does this model have any put statements that involve scalarset types? // // A caller can use this to discriminate whether schedules (the chosen // permutation of a scalarset during symmetry reduction) need to be available // during verification even when counterexample traces are not required. bool prints_scalarsets(const rumur::Model &m); rumur-2024.05.07/rumur/src/process.cc000066400000000000000000000212531461637631000172250ustar00rootroot00000000000000#include "process.h" #include "environ.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // compile this file with -DTEST_PROCESS to build a test harness #ifdef TEST_PROCESS #define debug (&std::cerr) #else #include "log.h" #endif enum { READ_FD = 0, WRITE_FD = 1 }; // pipe through which we'll redirect SIGCHLD notifications static int sigchld_pipe[2] = {-1, -1}; // signal handler to redirect SIGCHLD into the above pipe static void handler(int sigchld __attribute__((unused))) { assert(sigchld == SIGCHLD && "SIGCHLD handler received something other than SIGCHLD"); assert(sigchld_pipe[WRITE_FD] != -1 && "SIGCHLD handler called before SIGCHLD pipe has been setup"); ssize_t w __attribute__((unused)) = write(sigchld_pipe[WRITE_FD], "\0", 1); } static bool inited = false; static int init() { if (pipe(sigchld_pipe) < 0) { *debug << "failed to create SIGCHLD pipe\n"; goto fail; } // set the SIGCHLD pipe not to block on reading or writing { int r = sigchld_pipe[READ_FD]; int w = sigchld_pipe[WRITE_FD]; if (fcntl(r, F_SETFL, fcntl(r, F_GETFL) | O_NONBLOCK) == -1 || fcntl(w, F_SETFL, fcntl(w, F_GETFL) | O_NONBLOCK) == -1) { *debug << "failed to set SIGCHLD pipe non-blocking\n"; goto fail; } } // register our redirecting signal handler if (signal(SIGCHLD, handler) == SIG_ERR) { *debug << "failed to set SIGCHLD handler: " << strerror(errno) << "\n"; goto fail; } inited = true; return 0; fail: for (int &fd : sigchld_pipe) { if (fd != -1) { (void)close(fd); fd = -1; } } return -1; } static int max(int a, int b) { return a > b ? a : b; } int run(const std::vector &args, const std::string &input, std::string &output) { if (!inited) { if (init() < 0) return -1; } // setup an argument vector std::vector argv; for (const std::string &a : args) argv.push_back(const_cast(a.c_str())); argv.push_back(nullptr); posix_spawn_file_actions_t fa; int in[2] = {-1, -1}; int out[2] = {-1, -1}; int ret = -1; std::ostringstream buffered_output; off_t input_offset = 0; int err = 0; err = posix_spawn_file_actions_init(&fa); if (err != 0) { *debug << "failed file_actions_init: " << strerror(err) << "\n"; return -1; } // create some pipes we'll use to communicate with the child if (pipe(in) < 0 || pipe(out) < 0) { *debug << "failed pipe: " << strerror(errno) << "\n"; goto done; } // set the ends the parent (us) will use as non-blocking if (fcntl(in[WRITE_FD], F_SETFL, fcntl(in[WRITE_FD], F_GETFL) | O_NONBLOCK) == -1 || fcntl(out[READ_FD], F_SETFL, fcntl(out[READ_FD], F_GETFL) | O_NONBLOCK) == -1) { *debug << "failed to set O_NONBLOCK: " << strerror(errno) << "\n"; goto done; } // have the child close the ends it won't need err = posix_spawn_file_actions_addclose(&fa, in[WRITE_FD]); if (err == 0) err = posix_spawn_file_actions_addclose(&fa, out[READ_FD]); if (err != 0) { *debug << "failed file_actions_addclose: " << strerror(err) << "\n"; goto done; } // replace the child's stdin, stdout and stderr with the pipes err = posix_spawn_file_actions_adddup2(&fa, in[READ_FD], STDIN_FILENO); if (err == 0) err = posix_spawn_file_actions_adddup2(&fa, out[WRITE_FD], STDOUT_FILENO); if (err == 0) err = posix_spawn_file_actions_adddup2(&fa, out[WRITE_FD], STDERR_FILENO); if (err != 0) { *debug << "failed file_actions_adddup2: " << strerror(err) << "\n"; goto done; } // spawn the child pid_t pid; err = posix_spawnp(&pid, argv[0], &fa, nullptr, argv.data(), get_environ()); if (err != 0) { *debug << "failed posix_spawnp: " << strerror(err) << "\n"; goto done; } // close the ends of the pipes we (the parent) don't need (void)close(in[READ_FD]); in[READ_FD] = -1; (void)close(out[WRITE_FD]); out[WRITE_FD] = -1; // now we're ready to sit in an event loop interacting with the child for (;;) { int nfds; // create a set of the out pipe and SIGCHLD to monitor for reading fd_set readfds; FD_ZERO(&readfds); FD_SET(out[READ_FD], &readfds); FD_SET(sigchld_pipe[READ_FD], &readfds); nfds = max(out[READ_FD], sigchld_pipe[READ_FD]); // create a set of only the in pipe (if still open) to monitor for writing fd_set writefds; FD_ZERO(&writefds); if (in[WRITE_FD] != -1) { FD_SET(in[WRITE_FD], &writefds); nfds = max(nfds, in[WRITE_FD]); } // wait for an event if (select(nfds, &readfds, &writefds, nullptr, nullptr) < 0) { // if our select call is correct, any “error” should be an interrupt assert(errno == EAGAIN || errno == EINTR); } // clear any SIGCHLD notification if (FD_ISSET(sigchld_pipe[READ_FD], &readfds)) { char ignored[BUFSIZ]; ssize_t r __attribute__((unused)) = read(sigchld_pipe[READ_FD], ignored, sizeof(ignored)); } // read any data available from the child if (FD_ISSET(out[READ_FD], &readfds)) { ssize_t r; do { char buffer[BUFSIZ]; do { r = read(out[READ_FD], buffer, sizeof(buffer)); } while (r == -1 && errno == EINTR); if (r == -1) { if (errno == EAGAIN) break; *debug << "failed to read from child: " << strerror(errno) << "\n"; goto done; } // retain anything we read for (size_t i = 0; i < (size_t)r; i++) buffered_output << buffer[i]; } while (r > 0); } // write remaining data if the input pipe is ready if (in[WRITE_FD] != -1 && FD_ISSET(in[WRITE_FD], &writefds)) { if ((size_t)input_offset < input.size()) { ssize_t w; do { w = write(in[WRITE_FD], input.c_str() + input_offset, input.size() - input_offset); } while (w == -1 && errno == EINTR); if (w == -1 && errno != EAGAIN) { *debug << "failed to write to child: " << strerror(errno) << "\n"; goto done; } if (w > 0) { input_offset += (off_t)w; if ((size_t)input_offset == input.size()) { // exhausted the input; send the child EOF (void)close(in[WRITE_FD]); in[WRITE_FD] = -1; } } } } // check if the child has exited int status; if (waitpid(pid, &status, WNOHANG) == pid) { #if 0 /* It's useful to uncomment this when testing new SMT modes or new Rumur * syntax, to make a solver failure crash Rumur. Obviously you don't want * this enabled in a production build because the solver is just an * arbitrary user-defined program that's free to fail or crash if it wants * to. */ assert(WIFSTOPPED(status) || (WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS)); #endif if (WIFEXITED(status)) { if (WEXITSTATUS(status) != EXIT_SUCCESS) *debug << "child returned exit status " << WEXITSTATUS(status) << "\n"; break; } if (WIFSIGNALED(status)) { *debug << "child exited due to signal " << WTERMSIG(status) << "\n"; break; } } } // success if we've reached here output = buffered_output.str(); ret = 0; done: for (int &fd : out) { if (fd != -1) { (void)close(fd); fd = -1; } } for (int &fd : in) { if (fd != -1) { (void)close(fd); fd = -1; } } (void)posix_spawn_file_actions_destroy(&fa); return ret; } static int __attribute__((unused)) test_process(int argc, char **argv) { if (argc < 2 || strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "-?") == 0) { std::cerr << "Rumur Process.cc tester\n" << "\n" << " usage: " << argv[0] << " cmd args...\n"; return EXIT_SUCCESS; } // construct argument list std::vector args; for (size_t i = 1; i < (size_t)argc; i++) { assert(argv[i] != nullptr); args.emplace_back(argv[i]); } // read stdin until EOF std::ostringstream input; for (std::string line; std::getline(std::cin, line);) { input << line << "\n"; } // run our designated process std::string output; int r = run(args, input.str(), output); if (r == 0) std::cout << output; return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE; } #ifdef TEST_PROCESS int main(int argc, char **argv) { return test_process(argc, argv); } #endif rumur-2024.05.07/rumur/src/process.h000066400000000000000000000006211461637631000170630ustar00rootroot00000000000000#pragma once #include #include #include /* Run an external process, pass it the given input on stdin and wait for it to * finish. Its stdout data is reported via the output parameter. Note that there * is currently no reporting of the exit status of the process. */ int run(const std::vector &args, const std::string &input, std::string &output); rumur-2024.05.07/rumur/src/resources.h000066400000000000000000000004611461637631000174210ustar00rootroot00000000000000#pragma once #include extern const unsigned char resources_includes_c[]; extern const size_t resources_includes_c_len; extern const unsigned char resources_header_c[]; extern const size_t resources_header_c_len; extern const unsigned char doc_rumur_1[]; extern const size_t doc_rumur_1_len; rumur-2024.05.07/rumur/src/rumur-run000066400000000000000000000217271461637631000171450ustar00rootroot00000000000000#!/usr/bin/env python3 """ Wrapper script for running the Rumur model checker. This script is intended to be installed alongside the `rumur` binary from the Rumur model checker. It can then be used to quickly generate and run a model, as an alternative to having to run the model generation, compilation and execution steps manually. """ import atexit import os import platform import re import shutil import subprocess as sp import sys import tempfile # C compiler CC = shutil.which(os.environ.get("CC", "cc")) def categorise(cc): """ Determine the vendor of a given C compiler """ # Create a temporary area to compile a test file tmp = tempfile.mkdtemp() # Setup the test file src = os.path.join(tmp, "test.c") with open(src, "wt") as f: f.write("""\ #include #include int main(void) { #ifdef __clang__ printf("clang\\n"); #elif defined(__GNUC__) printf("gcc\\n"); #else printf("unknown\\n"); #endif return EXIT_SUCCESS; } """) categorisation = "unknown" # Compile it aout = os.path.join(tmp, "a.out") cc_ret = sp.call([cc, "-o", aout, src], universal_newlines=True, stdout=sp.DEVNULL, stderr=sp.DEVNULL) # Run it if cc_ret == 0: try: categorisation = sp.check_output([aout], universal_newlines=True).strip() except sp.CalledProcessError: pass # Clean up shutil.rmtree(tmp) return categorisation def supports(flag): """check whether the compiler supports a given command line flag""" # a trivial program to ask it to compile program = "int main(void) { return 0; }" # compile it p = sp.Popen([CC, "-o", os.devnull, "-x", "c", "-", flag], stderr=sp.DEVNULL, stdin=sp.PIPE) p.communicate(program.encode("utf-8", "replace")) # check whether compilation succeeded return p.returncode == 0 def needs_libatomic(): """check whether the compiler needs -latomic for a double-word compare-and-swap""" # CAS program to ask it to compile program = """ #include #include // replicate what is in ../resources/header.c #define THREADS 2 #if __SIZEOF_POINTER__ <= 4 typedef uint64_t dword_t; #elif __SIZEOF_POINTER__ <= 8 typedef unsigned __int128 dword_t; #else #error "unexpected pointer size; what scalar type to use for dword_t?" #endif static dword_t atomic_read(dword_t *p) { if (THREADS == 1) { return *p; } #if defined(__x86_64__) || defined(__i386__) || \ (defined(__aarch64__) && defined(__GNUC__) && !defined(__clang__)) /* x86-64: MOV is not guaranteed to be atomic on 128-bit naturally aligned * memory. The way to work around this is apparently the following * degenerate CMPXCHG16B. * i386: __atomic_load_n emits code calling a libatomic function that takes a * lock, making this no longer lock free. Force a CMPXCHG8B by using the * __sync built-in instead. * ARM64: __atomic_load_n emits code calling a libatomic function that takes a * lock, making this no longer lock free. Force a CASP by using the __sync * built-in instead. * * XXX: the obvious (irrelevant) literal to use here is “0” but this triggers * a GCC bug on ARM, https://gcc.gnu.org/bugzilla/show_bug.cgi?id=114310. */ return __sync_val_compare_and_swap(p, 1, 1); #endif return __atomic_load_n(p, __ATOMIC_SEQ_CST); } static void atomic_write(dword_t *p, dword_t v) { if (THREADS == 1) { *p = v; return; } #if defined(__x86_64__) || defined(__i386__) || \ (defined(__aarch64__) && defined(__GNUC__) && !defined(__clang__)) /* As explained above, we need some extra gymnastics to avoid a call to * libatomic on x86-64, i386, and ARM64. */ dword_t expected; dword_t old = 0; do { expected = old; old = __sync_val_compare_and_swap(p, expected, v); } while (expected != old); return; #endif __atomic_store_n(p, v, __ATOMIC_SEQ_CST); } static bool atomic_cas(dword_t *p, dword_t expected, dword_t new) { if (THREADS == 1) { if (*p == expected) { *p = new; return true; } return false; } #if defined(__x86_64__) || defined(__i386__) || \ (defined(__aarch64__) && defined(__GNUC__) && !defined(__clang__)) /* Make GCC >= 7.1 emit cmpxchg on x86-64 and i386. See * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80878. * Make GCC emit a CASP on ARM64 (undocumented?). */ return __sync_bool_compare_and_swap(p, expected, new); #endif return __atomic_compare_exchange_n(p, &expected, new, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); } static dword_t atomic_cas_val(dword_t *p, dword_t expected, dword_t new) { if (THREADS == 1) { dword_t old = *p; if (old == expected) { *p = new; } return old; } #if defined(__x86_64__) || defined(__i386__) || \ (defined(__aarch64__) && defined(__GNUC__) && !defined(__clang__)) /* Make GCC >= 7.1 emit cmpxchg on x86-64 and i386. See * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80878. * Make GCC emit a CASP on ARM64 (undocumented?). */ return __sync_val_compare_and_swap(p, expected, new); #endif (void)__atomic_compare_exchange_n(p, &expected, new, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); return expected; } int main(void) { dword_t target = 0; target = atomic_read(&target); atomic_write(&target, 42); atomic_cas(&target, 42, 0); return (int)atomic_cas_val(&target, 0, 42); } """ # compile it args = [CC, "-x", "c", "-std=c11", "-", "-o", os.devnull] if supports("-march=native"): args.append("-march=native") if supports("-mcx16"): args.append("-mcx16") p = sp.Popen(args, stderr=sp.DEVNULL, stdin=sp.PIPE) p.communicate(program.encode("utf-8", "replace")) # check whether compilation succeeded return p.returncode != 0 def optimisation_flags(): """C compiler optimisation command line options for this platform""" flags = ["-O3"] # optimise code for the current host architecture if supports("-march=native"): flags.append("-march=native") # optimise code for the current host CPU if supports("-mtune=native"): flags.append("-mtune=native") # enable link-time optimisation if supports("-flto"): flags.append("-flto") cc_vendor = categorise(CC) # allow GCC to perform more advanced interprocedural optimisations if cc_vendor == "gcc": flags.append("-fwhole-program") # on platforms that need it made explicit, enable CMPXCHG16B if supports("-mcx16"): flags.append("-mcx16") return flags def has_no_la57(): """ does our hardware lack support for Intel 5-level paging? """ # if we are not on an x86-64 platform, this is irrelevant if platform.machine() not in ("amd64", "x86_64"): return False # read cpuinfo, looking for la57 as a supported flag by the CPU try: with open("/proc/cpuinfo", "rt") as f: for line in f: flags = re.match(r"flags\s*:\s*(?P.*)$", line) if flags is not None: # found the flags field; now is it missing LA57? return re.search(r"\bla57\b", flags.group("flags")) is None except (FileNotFoundError, PermissionError): # procfs is unavailable return False # if we read the entire cpuinfo and did not find the flags field, # conservatively assume we may support LA57 return False def main(args): # Find the Rumur binary rumur_bin = shutil.which(os.path.join( os.path.abspath(os.path.dirname(__file__)), "rumur")) if rumur_bin is None: rumur_bin = shutil.which("rumur") if rumur_bin is None: sys.stderr.write("rumur binary not found\n") return -1 # if the user asked for help or version information, run Rumur directly for arg in args[1:]: if arg.startswith("-h") or arg.startswith("--h") or arg.startswith("--vers"): os.execv(rumur_bin, [rumur_bin] + args[1:]) if CC is None: sys.stderr.write("no C compiler found\n") return -1 argv = [rumur_bin] # if this hardware does not support 5-level paging, we can more aggressively # compress pointers if has_no_la57(): argv += ["--pointer-bits", "48"] argv += args[1:] + ["--output", "/dev/stdout"] # Generate the checker print("Generating the checker...") rumur_proc = sp.Popen(argv, stdin=sp.PIPE, stdout=sp.PIPE) stdout, _ = rumur_proc.communicate() if rumur_proc.returncode != 0: return rumur_proc.returncode checker_c = stdout ok = True # Setup a temporary directory in which to generate the checker tmp = tempfile.mkdtemp() atexit.register(shutil.rmtree, tmp) # Compile the checker if ok: print("Compiling the checker...") aout = os.path.join(tmp, "a.out") argv = [CC, "-std=c11"] + optimisation_flags() + ["-o", aout, "-x", "c", "-", "-lpthread"] if needs_libatomic(): argv.append("-latomic") cc_proc = sp.Popen(argv, stdin=sp.PIPE) cc_proc.communicate(checker_c) ok &= cc_proc.returncode == 0 # Run the checker if ok: print("Running the checker...") ret = sp.call([aout]) ok &= ret == 0 return 0 if ok else -1 if __name__ == "__main__": try: sys.exit(main(sys.argv)) except KeyboardInterrupt: sys.exit(130) rumur-2024.05.07/rumur/src/smt/000077500000000000000000000000001461637631000160405ustar00rootroot00000000000000rumur-2024.05.07/rumur/src/smt/define-enum-members.cc000066400000000000000000000041431461637631000221750ustar00rootroot00000000000000#include "define-enum-members.h" #include "../options.h" #include "logic.h" #include "solver.h" #include "translate.h" #include #include #include #include #include #include using namespace rumur; namespace smt { namespace { class Definer : public ConstTypeTraversal { private: Solver *solver; public: explicit Definer(Solver &solver_) : solver(&solver_) {} void visit_array(const Array &n) final { // define any enum members that occur in the index or element types dispatch(*n.index_type); dispatch(*n.element_type); } void visit_enum(const Enum &n) final { // ignore the built-in bool type that the solver already knows if (n.is_boolean()) return; // emit the members of the enum as integer constants mpz_class index = 0; size_t id = n.unique_id + 1; for (const std::pair &member : n.members) { assert(id < n.unique_id_limit && "unexpected number of enum members"); const std::string name = mangle(member.first, id); const std::string type = integer_type(); const std::string value = numeric_literal(index); *solver << "(declare-fun " << name << " () " << type << ")\n" << "(assert (= " << name << " " << value << "))\n"; index++; id++; } } void visit_range(const Range &) final { // as a primitive, ranges can't contain any enum members } void visit_record(const Record &n) final { // define any enum members that occur in the fields of this type for (const Ptr &field : n.fields) dispatch(*field->type); } void visit_typeexprid(const TypeExprID &) final { /* the referent of a TypeExprID will have occurred earlier in the AST and * already resulted in the relevant enum members being defined. */ } void visit_scalarset(const Scalarset &) final { // as a primitive, scalarsets can't contain any enum members } }; } // namespace void define_enum_members(Solver &solver, const TypeExpr &type) { Definer definer(solver); definer.dispatch(type); } } // namespace smt rumur-2024.05.07/rumur/src/smt/define-enum-members.h000066400000000000000000000004241461637631000220350ustar00rootroot00000000000000#pragma once #include "solver.h" #include #include namespace smt { /* declare any enum members that are syntactically contained under the given * type */ void define_enum_members(Solver &solver, const rumur::TypeExpr &type); } // namespace smt rumur-2024.05.07/rumur/src/smt/define-records.cc000066400000000000000000000033171461637631000212440ustar00rootroot00000000000000#include "define-records.h" #include "solver.h" #include "translate.h" #include "typeexpr-to-smt.h" #include #include #include using namespace rumur; namespace smt { namespace { class Definer : public ConstTypeTraversal { private: Solver *solver; public: explicit Definer(Solver &solver_) : solver(&solver_) {} void visit_array(const Array &n) final { // the index has to be a simple type, so can't contain any records, but the // element might dispatch(*n.element_type); } void visit_enum(const Enum &) final { // nothing to do } void visit_range(const Range &) final { // nothing to do } void visit_record(const Record &n) final { // first, make sure we define any records that are children of this one for (const Ptr &field : n.fields) dispatch(*field->type); // now we're ready to define this one itself // synthesise a name for the SMT type const std::string name = mangle("", n.unique_id); // a name for the constructor (we never actually use this) const std::string ctor = "mk" + name; // declare the type *solver << "(declare-datatypes () ((" << name << " (" << ctor; for (const Ptr &field : n.fields) { const std::string fname = name + "_" + field->name; *solver << " (" << fname << " " << typeexpr_to_smt(*field->type) << ")"; } *solver << "))))\n"; } void visit_typeexprid(const TypeExprID &) final { // nothing to do } void visit_scalarset(const Scalarset &) final { // nothing to do } }; } // namespace void define_records(Solver &solver, const TypeExpr &type) { Definer definer(solver); definer.dispatch(type); } } // namespace smt rumur-2024.05.07/rumur/src/smt/define-records.h000066400000000000000000000002451461637631000211030ustar00rootroot00000000000000#pragma once #include "solver.h" #include #include namespace smt { void define_records(Solver &solver, const rumur::TypeExpr &type); } rumur-2024.05.07/rumur/src/smt/except.h000066400000000000000000000015651461637631000175100ustar00rootroot00000000000000// some exceptions the SMT components can throw #pragma once #include #include #include #include namespace smt { class BudgetExhausted : public std::runtime_error { public: BudgetExhausted() : std::runtime_error("SMT solver budget exhausted") {} }; class Unsupported : public std::runtime_error { public: const rumur::Expr *expr = nullptr; explicit Unsupported() : std::runtime_error("part of an expression was outside the " "currently implemented SMT functionality") {} explicit Unsupported(const rumur::Expr &expr_) : std::runtime_error("SMT solver encountered unsupported expression \"" + expr_.to_string() + "\""), expr(&expr_) {} explicit Unsupported(const std::string &message) : std::runtime_error(message) {} }; } // namespace smt rumur-2024.05.07/rumur/src/smt/logic.cc000066400000000000000000000062651461637631000174550ustar00rootroot00000000000000#include "logic.h" #include "../options.h" #include "except.h" #include #include #include namespace smt { static const size_t BITVECTOR_WIDTH = 64; std::string integer_type() { if (options.smt.use_bitvectors) { return "(_ BitVec " + std::to_string(BITVECTOR_WIDTH) + ")"; } return "Int"; } std::string numeric_literal(const mpz_class &value) { if (value < 0) return "(" + neg() + " " + numeric_literal(-value) + ")"; if (options.smt.use_bitvectors) { return "(_ bv" + value.get_str() + " " + std::to_string(BITVECTOR_WIDTH) + ")"; } return value.get_str(); } std::string add() { if (options.smt.use_bitvectors) { return "bvadd"; } return "+"; } std::string band() { if (options.smt.use_bitvectors) { return "bvand"; } throw Unsupported( "SMT simplification involving bitwise AND is only " "supported when using a bitvector representation (--smt-bitvectors on)"); } std::string bnot() { if (options.smt.use_bitvectors) { return "bvnot"; } throw Unsupported( "SMT simplification involving bitwise NOT is only " "supported when using a bitvector representation (--smt-bitvectors on)"); } std::string bor() { if (options.smt.use_bitvectors) { return "bvor"; } throw Unsupported( "SMT simplification involving bitwise OR is only " "supported when using a bitvector representation (--smt-bitvectors on)"); } std::string bxor() { if (options.smt.use_bitvectors) { return "bvxor"; } throw Unsupported( "SMT simplification involving bitwise XOR is only " "supported when using a bitvector representation (--smt-bitvectors on)"); } std::string div() { if (options.smt.use_bitvectors) { return "bvsdiv"; } // XXX: may cause solvers like CVC4 to fail with an error. Not visible to the // the user unless passing --debug though, so left as-is for now. return "div"; } std::string geq() { if (options.smt.use_bitvectors) { return "bvsge"; } return ">="; } std::string gt() { if (options.smt.use_bitvectors) { return "bvsgt"; } return ">"; } std::string leq() { if (options.smt.use_bitvectors) { return "bvsle"; } return "<="; } std::string lsh() { if (options.smt.use_bitvectors) return "bvshl"; throw Unsupported( "SMT simplification involving left shifts is only " "supported when using a bitvector representation (--smt-bitvectors on)"); } std::string lt() { if (options.smt.use_bitvectors) { return "bvslt"; } return "<"; } std::string mod() { if (options.smt.use_bitvectors) { return "bvsmod"; } return "mod"; } std::string mul() { if (options.smt.use_bitvectors) { return "bvmul"; } return "*"; } std::string neg() { if (options.smt.use_bitvectors) { return "bvneg"; } return "-"; } std::string rsh() { if (options.smt.use_bitvectors) return "bvashr"; throw Unsupported( "SMT simplification involving right shifts is only " "supported when using a bitvector representation (--smt-bitvectors on)"); } std::string sub() { if (options.smt.use_bitvectors) { return "bvsub"; } return "-"; } } // namespace smt rumur-2024.05.07/rumur/src/smt/logic.h000066400000000000000000000007771461637631000173210ustar00rootroot00000000000000#pragma once #include #include #include namespace smt { std::string integer_type(); std::string numeric_literal(const mpz_class &value); std::string add (); std::string band(); std::string bnot(); std::string bor (); std::string bxor(); std::string div (); std::string geq (); std::string gt (); std::string leq (); std::string lsh (); std::string lt (); std::string mod (); std::string mul (); std::string neg (); std::string rsh (); std::string sub (); } // namespace smt rumur-2024.05.07/rumur/src/smt/simplify.cc000066400000000000000000000375461461637631000202220ustar00rootroot00000000000000#include "simplify.h" #include "../log.h" #include "../options.h" #include "define-enum-members.h" #include "define-records.h" #include "except.h" #include "logic.h" #include "solver.h" #include "translate.h" #include "typeexpr-to-smt.h" #include #include #include #include using namespace rumur; namespace smt { /* Simplification logic. We only attempt to replace tautologies with true and * contradictions with false, rather than going further and removing unreachable * code. We assume the C compiler building the generated verifier is clever * enough to make these transformations itself, so we leave them for it. */ namespace { class Simplifier : public BaseTraversal { private: Solver *solver; public: explicit Simplifier(Solver &solver_) : solver(&solver_) {} /* if you are editing the visitation logic, note that the calls to * open_scope/close_scope are intended to match the pattern in * ../../../librumur/src/resolve-symbols.cc so that the SMT solver resolves * ExprIDs/TypeExprIDs the same way we do */ void visit_add(Add &n) final { visit_bexpr(n); } void visit_aliasdecl(AliasDecl &n) final { dispatch(*n.value); simplify(n.value); } void visit_aliasrule(AliasRule &n) final { solver->open_scope(); for (Ptr &alias : n.aliases) { dispatch(*alias); declare_decl(*alias); } for (Ptr &rule : n.rules) dispatch(*rule); solver->close_scope(); } void visit_aliasstmt(AliasStmt &n) final { solver->open_scope(); for (Ptr &alias : n.aliases) { dispatch(*alias); declare_decl(*alias); } for (Ptr &stmt : n.body) dispatch(*stmt); solver->close_scope(); } void visit_and(And &n) final { visit_bexpr(n); } void visit_array(Array &n) final { dispatch(*n.index_type); dispatch(*n.element_type); } void visit_assignment(Assignment &n) final { dispatch(*n.lhs); dispatch(*n.rhs); simplify(n.rhs); } void visit_band(Band &n) final { visit_bexpr(n); } void visit_bnot(Bnot &n) final { visit_uexpr(n); } void visit_bor(Bor &n) final { visit_bexpr(n); } void visit_clear(Clear &n) final { dispatch(*n.rhs); simplify(n.rhs); } void visit_constdecl(ConstDecl &n) final { dispatch(*n.value); simplify(n.value); if (n.type != nullptr) dispatch(*n.type); } void visit_div(Div &n) final { visit_bexpr(n); } void visit_element(Element &n) final { dispatch(*n.array); dispatch(*n.index); simplify(n.array); simplify(n.index); } void visit_enum(Enum &) final { // nothing required (see declare_decl) } void visit_eq(Eq &n) final { visit_bexpr(n); } void visit_errorstmt(ErrorStmt &) final {} void visit_exists(Exists &n) final { solver->open_scope(); dispatch(n.quantifier); dispatch(*n.expr); simplify(n.expr); solver->close_scope(); } void visit_exprid(ExprID &) final {} void visit_field(Field &n) final { dispatch(*n.record); simplify(n.record); } void visit_for(For &n) final { solver->open_scope(); dispatch(n.quantifier); for (Ptr &stmt : n.body) dispatch(*stmt); solver->close_scope(); } void visit_forall(Forall &n) final { solver->open_scope(); dispatch(n.quantifier); dispatch(*n.expr); simplify(n.expr); solver->close_scope(); } void visit_function(Function &n) final { solver->open_scope(); for (Ptr ¶meter : n.parameters) { dispatch(*parameter); declare_decl(*parameter); } if (n.return_type != nullptr) dispatch(*n.return_type); for (Ptr &decl : n.decls) { dispatch(*decl); declare_decl(*decl); } for (Ptr &stmt : n.body) dispatch(*stmt); solver->close_scope(); } void visit_functioncall(FunctionCall &n) final { for (Ptr &arg : n.arguments) { dispatch(*arg); simplify(arg); } } void visit_geq(Geq &n) final { visit_bexpr(n); } void visit_gt(Gt &n) final { visit_bexpr(n); } void visit_if(If &n) final { for (IfClause &c : n.clauses) dispatch(c); } void visit_ifclause(IfClause &n) final { if (n.condition != nullptr) dispatch(*n.condition); for (Ptr &s : n.body) dispatch(*s); if (n.condition != nullptr) simplify(n.condition); } void visit_implication(Implication &n) final { visit_bexpr(n); } void visit_isundefined(IsUndefined &n) final { visit_uexpr(n); } void visit_leq(Leq &n) final { visit_bexpr(n); } void visit_lsh(Lsh &n) final { visit_bexpr(n); } void visit_lt(Lt &n) final { visit_bexpr(n); } void visit_mod(Mod &n) final { visit_bexpr(n); } void visit_model(Model &n) final { solver->open_scope(); for (Ptr &c : n.children) { dispatch(*c); if (auto d = dynamic_cast(c.get())) declare_decl(*d); if (auto f = dynamic_cast(c.get())) declare_func(*f); } solver->open_scope(); } void visit_mul(Mul &n) final { visit_bexpr(n); } void visit_negative(Negative &n) final { visit_uexpr(n); } void visit_neq(Neq &n) final { visit_bexpr(n); } void visit_not(Not &n) final { visit_uexpr(n); } void visit_number(Number &) final {} void visit_or(Or &n) final { visit_bexpr(n); } void visit_procedurecall(ProcedureCall &n) final { dispatch(n.call); } void visit_property(Property &) final { /* properties are printed to the user during verification, so don't simplify * the expression as it may confuse the user that it's not the same as what * they entered */ } void visit_propertyrule(PropertyRule &n) final { for (Quantifier &quantifier : n.quantifiers) dispatch(quantifier); for (Ptr &alias : n.aliases) dispatch(*alias); dispatch(n.property); } void visit_propertystmt(PropertyStmt &n) final { dispatch(n.property); } void visit_put(Put &) final { /* deliberately do nothing here because the expression in a 'put' statement * is displayed to the user during verification and will surprise them if it * has been simplified into something other than what they wrote */ } void visit_quantifier(Quantifier &n) final { if (n.type != nullptr) { dispatch(*n.type); declare_decl(*n.decl); } else { assert(n.from != nullptr); assert(n.to != nullptr); dispatch(*n.from); dispatch(*n.to); if (n.step != nullptr) dispatch(*n.step); simplify(n.from); simplify(n.to); if (n.step != nullptr) simplify(n.step); declare_decl(*n.decl); const std::string name = mangle(n.decl->name, n.decl->unique_id); if (n.from->constant()) { const std::string lb = numeric_literal(n.from->constant_fold()); *solver << "(assert (" << geq() << " " << name << " " << lb << "))\n"; } if (n.to->constant()) { const std::string ub = numeric_literal(n.to->constant_fold()); *solver << "(assert (" << leq() << " " << name << " " << ub << "))\n"; } } } void visit_range(Range &n) final { dispatch(*n.min); dispatch(*n.max); simplify(n.min); simplify(n.max); } void visit_record(Record &n) final { for (Ptr &field : n.fields) dispatch(*field); } void visit_return(Return &n) final { if (n.expr != nullptr) { dispatch(*n.expr); simplify(n.expr); } } void visit_rsh(Rsh &n) final { visit_bexpr(n); } void visit_ruleset(Ruleset &n) final { solver->open_scope(); for (Quantifier &quantifier : n.quantifiers) dispatch(quantifier); for (Ptr &alias : n.aliases) dispatch(*alias); for (Ptr &rule : n.rules) dispatch(*rule); solver->close_scope(); } void visit_scalarset(Scalarset &n) final { dispatch(*n.bound); simplify(n.bound); } void visit_simplerule(SimpleRule &n) final { solver->open_scope(); for (Quantifier &quantifier : n.quantifiers) dispatch(quantifier); for (Ptr &alias : n.aliases) dispatch(*alias); if (n.guard != nullptr) { dispatch(*n.guard); simplify(n.guard); } for (Ptr &decl : n.decls) { dispatch(*decl); declare_decl(*decl); } for (Ptr &stmt : n.body) dispatch(*stmt); solver->close_scope(); } void visit_startstate(StartState &n) final { solver->open_scope(); for (Quantifier &quantifier : n.quantifiers) dispatch(quantifier); for (Ptr &alias : n.aliases) dispatch(*alias); for (Ptr &decl : n.decls) { dispatch(*decl); declare_decl(*decl); } for (Ptr &stmt : n.body) dispatch(*stmt); solver->close_scope(); } void visit_sub(Sub &n) final { visit_bexpr(n); } void visit_switchcase(SwitchCase &n) final { for (Ptr &match : n.matches) { dispatch(*match); simplify(match); } for (Ptr &stmt : n.body) { dispatch(*stmt); } } void visit_switch(Switch &n) final { dispatch(*n.expr); simplify(n.expr); for (SwitchCase &c : n.cases) dispatch(c); } void visit_ternary(Ternary &n) final { dispatch(*n.cond); dispatch(*n.lhs); dispatch(*n.rhs); simplify(n.cond); simplify(n.lhs); simplify(n.rhs); } void visit_typedecl(TypeDecl &n) final { dispatch(*n.value); } void visit_typeexprid(TypeExprID &) final {} void visit_undefine(Undefine &n) final { dispatch(*n.rhs); } void visit_vardecl(VarDecl &n) final { dispatch(*n.type); } void visit_while(While &n) final { dispatch(*n.condition); simplify(n.condition); for (Ptr &stmt : n.body) dispatch(*stmt); } void visit_xor(Xor &n) final { visit_bexpr(n); } private: void visit_bexpr(BinaryExpr &n) { dispatch(*n.lhs); dispatch(*n.rhs); simplify(n.lhs); simplify(n.rhs); } void visit_uexpr(UnaryExpr &n) { dispatch(*n.rhs); simplify(n.rhs); } // try to squash a boolean expression to "True" or "False" void simplify(Ptr &e) { assert(e != nullptr && "attempt to simplify a NULL expression"); // we currently only handle boolean expressions if (!e->is_boolean()) return; // there's no point trying to simplify a literal we will replace with itself if (e->is_literal_true() || e->is_literal_false()) return; std::string claim; try { claim = translate(*e); } catch (Unsupported &) { *info << "skipping SMT simplification of unsupported expression \"" << e->to_string() << "\"\n"; return; } if (solver->is_true(claim)) { *info << "simplifying \"" << e->to_string() << "\" to true\n"; e = make_true(); } else if (solver->is_false(claim)) { *info << "simplifying \"" << e->to_string() << "\" to false\n"; e = make_false(); } } // invent a reference to "true" static Ptr make_true() { return Ptr(True); } // invent a reference to "false" static Ptr make_false() { return Ptr(False); } // declare a variable/type to the solver void declare_decl(const Decl &decl) { if (auto v = dynamic_cast(&decl)) { declare_var(v->name, v->unique_id, *v->type); return; } if (auto c = dynamic_cast(&decl)) { if (c->type == nullptr) { // integer constant assert(c->value->constant() && "non-constant value declared as constant"); const std::string value = numeric_literal(c->value->constant_fold()); const std::string name = mangle(c->name, c->unique_id); *solver << "(declare-fun " << name << " () " << integer_type() << ")\n" << "(assert (= " << name << " " << value << "))\n"; return; } // TODO: enum constants } if (auto t = dynamic_cast(&decl)) { // define any enum members that occur as part of this TypeDecl define_enum_members(*solver, *t->value); // define any records that occur as part of this TypeDecl define_records(*solver, *t->value); const std::string my_name = mangle(t->name, t->unique_id); // nested TypeDecl (i.e. a typedecl of a typedecl) if (auto ref = dynamic_cast(t->value.get())) { const std::string ref_name = mangle(ref->name, ref->referent->unique_id); *solver << "(define-sort " << my_name << " () " << ref_name << ")\n"; } else { // generic type definition *solver << "(define-sort " << my_name << " () " << typeexpr_to_smt(*t->value) << ")\n"; } return; } // TODO throw Unsupported(); } void declare_var(const std::string &name, size_t id, const TypeExpr &type) { // define any enum members that occur as part of the variable's type define_enum_members(*solver, type); // define any records that occur as part of this variable's type define_records(*solver, type); const std::string mangled = mangle(name, id); if (auto t = dynamic_cast(&type)) { // this has a previously defined type, so we know how to declare it const std::string tname = mangle(t->name, t->referent->unique_id); *solver << "(declare-fun " << mangled << " () " << tname << ")\n"; } else { // otherwise declare it generically const std::string tname = typeexpr_to_smt(type); *solver << "(declare-fun " << mangled << " () " << tname << ")\n"; } const Ptr t = type.resolve(); // the solver already knows boolean, so we're done if (t->is_boolean()) return; class ConstraintEmitter : public ConstTypeTraversal { private: Solver *solver; const std::string name; public: ConstraintEmitter(Solver &solver_, const std::string &name_) : solver(&solver_), name(name_) {} void visit_array(const Array &) final { // no constraints required } void visit_enum(const Enum &n) final { // constrain its values based on the number of enum members const std::string zero = numeric_literal(0); *solver << "(assert (" << geq() << " " << name << " " << zero << "))\n"; const std::string size = numeric_literal(n.members.size()); *solver << "(assert (" << lt() << " " << name << " " << size << "))\n"; } void visit_range(const Range &n) final { // if this range's bounds are static, make them known to the solver if (n.constant()) { const std::string lb = numeric_literal(n.min->constant_fold()); const std::string ub = numeric_literal(n.max->constant_fold()); *solver << "(assert (" << geq() << " " << name << " " << lb << "))\n" << "(assert (" << leq() << " " << name << " " << ub << "))\n"; } } void visit_record(const Record &) final { // no constraints required } void visit_scalarset(const Scalarset &n) final { // scalarset values are at least 0 const std::string zero = numeric_literal(0); *solver << "(assert (" << geq() << " " << name << " " << zero << "))\n"; // if this scalarset's bounds are static, make them known to the solver if (n.constant()) { const std::string b = numeric_literal(n.bound->constant_fold()); *solver << "(assert (" << lt() << " " << name << " " << b << "))\n"; } } void visit_typeexprid(const TypeExprID &) final { assert(!"unreachable"); } }; ConstraintEmitter emitter(*solver, mangled); emitter.dispatch(*t); } void declare_func(const Function &) { throw Unsupported(); } }; } // namespace void simplify(Model &m) { // establish our connection to the solver Solver solver; // recursively traverse the model, simplifying as we go Simplifier simplifier(solver); simplifier.dispatch(m); } } // namespace smt rumur-2024.05.07/rumur/src/smt/simplify.h000066400000000000000000000003511461637631000200440ustar00rootroot00000000000000#pragma once #include namespace smt { /* Attempt to simplify expressions within a model using an external * Satisfiability Modulo Theories (SMT) solver. */ void simplify(rumur::Model &model); } // namespace smt rumur-2024.05.07/rumur/src/smt/solver.cc000066400000000000000000000061211461637631000176610ustar00rootroot00000000000000#include "solver.h" #include "../log.h" #include "../options.h" #include "../process.h" #include "except.h" #include #include #include #include #include #include #include #include namespace smt { // some helper functions to contain the visually noisy chrono API typedef std::chrono::time_point timestamp_t; static timestamp_t get_timestamp() { return std::chrono::system_clock::now(); } static mpz_class get_duration(const timestamp_t &start, const timestamp_t &end) { std::chrono::duration duration = end - start; return duration.count() * 1000; } Solver::Result Solver::solve(const std::string &claim, bool expectation) { if (time_used >= options.smt.budget) throw BudgetExhausted(); std::ostringstream query; // disable printing of "success" in response to commands query << "(set-option :print-success false)\n"; // write any prelude the user requested for (const std::string &text : options.smt.prelude) { query << text << "\n"; } // append the declarations etc for (const std::shared_ptr &scope : prelude) query << scope->str(); // set up the main claim query << "(assert " << (expectation ? "(not " : "") << claim << (expectation ? ")" : "") << ")\n" << "(check-sat)\n"; *debug << "checking SMT problem:\n" << query.str(); // construct the call to the solver std::vector args; assert(options.smt.path != "" && "calling SMT solver without having supplied a path to it"); args.push_back(options.smt.path); std::copy(options.smt.args.begin(), options.smt.args.end(), std::back_inserter(args)); auto start = get_timestamp(); std::string output; int r = run(args, query.str(), output); auto end = get_timestamp(); time_used += get_duration(start, end); if (r < 0) { *debug << "SMT solver error\n"; return INCONCLUSIVE; } *debug << "SMT solver said:\n" << output; // look for a "sat" or "unsat" line std::istringstream ss(output); for (std::string line; std::getline(ss, line);) { if (line == "sat") return SAT; if (line == "unsat") return UNSAT; } *debug << "inconclusive result from SMT solver\n"; return INCONCLUSIVE; } bool Solver::is_true(const std::string &claim) { return solve(claim, true) == UNSAT; } bool Solver::is_false(const std::string &claim) { return solve(claim, false) == UNSAT; } Solver &Solver::operator<<(const std::string &s) { assert(!prelude.empty() && "writing SMT content without an open scope"); *prelude[prelude.size() - 1] << s; return *this; } /* you might think we could just use "(push)" and "(pop)" for scoping, but some * SMT solvers only support these in incremental mode and we're calling the * solver in one-shot mode */ void Solver::open_scope() { prelude.push_back(std::make_shared()); } void Solver::close_scope() { assert(!prelude.empty() && "closing a scope when none are open"); prelude.pop_back(); } } // namespace smt rumur-2024.05.07/rumur/src/smt/solver.h000066400000000000000000000024471461637631000175320ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include namespace smt { class Solver { private: std::vector> prelude; mpz_class time_used = 0; enum Result { SAT, UNSAT, INCONCLUSIVE }; /* Using the accrued prelude setup declarations, try to prove that the claim * expression is the expectation. I.e. prove the claim true or false depending * on whether expectation is true or false. For those unfamiliar with SMT * solvers, the way to interpret the result is: * SAT - there is a value(s) for which claim != expectation (proof failed) * UNSAT - for all values claim == expectation (proof succeeded) * INCONCLUSIVE - resource exhaustion, e.g. timeout */ Result solve(const std::string &claim, bool expectation); public: // can this expression be proven always-true? bool is_true(const std::string &claim); // can this expression be proven always-false? bool is_false(const std::string &claim); // add something to the prelude (e.g. a declaration "(declare-fun v () Int)") Solver &operator<<(const std::string &s); // open a new (nested) variable scope void open_scope(); // close a scope void close_scope(); }; } // namespace smt rumur-2024.05.07/rumur/src/smt/translate.cc000066400000000000000000000206161461637631000203510ustar00rootroot00000000000000#include "translate.h" #include "../../../common/isa.h" #include "../options.h" #include "except.h" #include "logic.h" #include #include #include #include #include using namespace rumur; namespace smt { namespace { class Translator : public ConstExprTraversal { private: std::ostringstream buffer; public: std::string str() const { return buffer.str(); } Translator &operator<<(const std::string &s) { buffer << s; return *this; } Translator &operator<<(const Expr &e) { dispatch(e); return *this; } void visit_add(const Add &n) { *this << "(" << add() << " " << *n.lhs << " " << *n.rhs << ")"; } void visit_and(const And &n) { *this << "(and " << *n.lhs << " " << *n.rhs << ")"; } void visit_band(const Band &n) { *this << "(" << band() << " " << *n.lhs << " " << *n.rhs << ")"; } void visit_bnot(const Bnot &n) { *this << "(" << bnot() << " " << *n.rhs << ")"; } void visit_bor(const Bor &n) { *this << "(" << bor() << " " << *n.lhs << " " << *n.rhs << ")"; } void visit_element(const Element &n) { *this << "(select " << *n.array << " " << *n.index << ")"; } void visit_exprid(const ExprID &n) { *this << mangle(n.id, n.value->unique_id); } void visit_eq(const Eq &n) { *this << "(= " << *n.lhs << " " << *n.rhs << ")"; } void visit_exists(const Exists &n) { translate_quantified(n.quantifier, *n.expr, false); } void visit_div(const Div &n) { *this << "(" << div() << " " << *n.lhs << " " << *n.rhs << ")"; } void visit_field(const Field &n) { // the record type that forms the root of this expression will have // previously been defined as a synthesised type (see define-records.cc) // determine the (mangled) SMT name the root was given const Ptr type = n.record->type()->resolve(); const std::string root = mangle("", type->unique_id); // we can now compute the accessor for this field (see define-records.cc) const std::string getter = root + "_" + n.field; // now construct the SMT expression *this << "(" << getter << " " << *n.record << ")"; } void visit_forall(const Forall &n) { translate_quantified(n.quantifier, *n.expr, true); } void visit_functioncall(const FunctionCall &n) { throw Unsupported(n); } void visit_geq(const Geq &n) { *this << "(" << geq() << " " << *n.lhs << " " << *n.rhs << ")"; } void visit_gt(const Gt &n) { *this << "(" << gt() << " " << *n.lhs << " " << *n.rhs << ")"; } void visit_implication(const Implication &n) { *this << "(=> " << *n.lhs << " " << *n.rhs << ")"; } void visit_isundefined(const IsUndefined &n) { throw Unsupported(n); } void visit_leq(const Leq &n) { *this << "(" << leq() << " " << *n.lhs << " " << *n.rhs << ")"; } void visit_lsh(const Lsh &n) { *this << "(" << lsh() << " " << *n.lhs << " " << *n.rhs << ")"; } void visit_lt(const Lt &n) { *this << "(" << lt() << " " << *n.lhs << " " << *n.rhs << ")"; } void visit_mod(const Mod &n) { *this << "(" << mod() << " " << *n.lhs << " " << *n.rhs << ")"; } void visit_mul(const Mul &n) { *this << "(" << mul() << " " << *n.lhs << " " << *n.rhs << ")"; } void visit_negative(const Negative &n) { *this << "(" << neg() << " " << *n.rhs << ")"; } void visit_neq(const Neq &n) { *this << "(not (= " << *n.lhs << " " << *n.rhs << "))"; } void visit_number(const Number &n) { *this << numeric_literal(n.value); } void visit_not(const Not &n) { *this << "(not " << *n.rhs << ")"; } void visit_or(const Or &n) { *this << "(or " << *n.lhs << " " << *n.rhs << ")"; } void visit_rsh(const Rsh &n) { *this << "(" << rsh() << " " << *n.lhs << " " << *n.rhs << ")"; } void visit_sub(const Sub &n) { *this << "(" << sub() << " " << *n.lhs << " " << *n.rhs << ")"; } void visit_ternary(const Ternary &n) { *this << "(ite " << *n.cond << " " << *n.lhs << " " << *n.rhs << ")"; } void visit_xor(const Xor &n) { *this << "(" << bxor() << " " << *n.lhs << " " << *n.rhs << ")"; } private: void translate_quantified(const Quantifier &q, const Expr &e, bool forall) { // find a name for the quantified variable const std::string qname = mangle(q.decl->name, q.decl->unique_id); const std::string qtype = integer_type(); // determine the parts of the expression we will construct that depend on // forall const std::string binder = forall ? "forall" : "exists"; const std::string op = forall ? "or" : "and"; const std::string lb_rel = forall ? lt() : geq(); const std::string ub_rel1 = forall ? gt() : leq(); const std::string ub_rel2 = forall ? geq() : lt(); const std::string step_o = forall ? "(not " : ""; const std::string step_c = forall ? ")" : ""; // “∀q.”/“∃q.” *this << "(" << binder << " ((" << qname << " " << qtype << ")) (" << op; // “q < lb”/“q ≥ lb” *this << " (" << lb_rel << " " << qname << " "; if (q.type != nullptr) { const Ptr t = q.type->resolve(); if (isa(t) || isa(t)) { *this << numeric_literal(0); } else { assert(isa(t) && "non-(range|enum|scalarset) variable in quantifier"); const Range &r = dynamic_cast(*t); assert(r.min != nullptr && "unbounded range type"); *this << *r.min; } } else { assert(q.from != nullptr && "quantified variable has no type and also no lower bound"); *this << *q.from; } *this << ")"; // or/and “q > ub”/“q ≤ ub” *this << " ("; if (q.type != nullptr) { const Ptr t = q.type->resolve(); assert((isa(t) || isa(t) || isa(t)) && "non-(range|enum|scalarset) variable in quantifier"); if (auto n = dynamic_cast(t.get())) { size_t s = n->members.size(); *this << ub_rel2 << " " << qname << " " << numeric_literal(s); } else if (auto r = dynamic_cast(t.get())) { assert(r->max != nullptr && "unbounded range type"); *this << ub_rel1 << " " << qname << " " << *r->max; } else { auto s = dynamic_cast(*t); *this << ub_rel2 << " " << qname << " " << *s.bound; } } else { assert(q.to != nullptr && "quantified variable has no type and also no upper bound"); *this << ub_rel1 << " " << qname << " " << *q.to; } *this << ")"; // or/and “!∃i. q = lb + i * step”/“∃i. q = lb + i * step” const std::string iname = qname + "_iteration"; *this << " " << step_o << "(exists ((" << iname << " " << qtype << ")) (= " << qname << " (" << add() << " "; if (q.type != nullptr) { const Ptr t = q.type->resolve(); if (isa(t) || isa(t)) { *this << numeric_literal(0); } else { assert(isa(t) && "non-(range|enum|scalarset) variable in quantifier"); const Range &r = dynamic_cast(*t); assert(r.min != nullptr && "unbounded range type"); *this << *r.min; } } else { *this << *q.from; } *this << " (" << mul() << " " << iname << " "; if (q.step == nullptr) { *this << numeric_literal(1); } else { *this << *q.step; } *this << "))))" << step_c; // finally the enclosed expression itself *this << " " << e << "))"; } }; } // namespace std::string translate(const Expr &expr) { Translator t; t.dispatch(expr); return t.str(); } static std::string lower(const std::string &s) { std::string s1; for (char c : s) s1 += tolower(c); return s1; } std::string mangle(const std::string &s, size_t id) { // if you're debugging a bad translation to SMT, you can change this to `true` // to get the Murphi name of a symbol output as a comment in the SMT problem const std::string prefix = false ? "; " + s + ":\n" : ""; const std::string l = lower(s); // if this is a boolean literal, the solver already knows of it if (l == "true" || l == "false") return prefix + l; // if this is the boolean type, the solver already knows of it if (l == "boolean") return prefix + "Bool"; // otherwise synthesise a node-unique name for this return prefix + "s" + std::to_string(id); } } // namespace smt rumur-2024.05.07/rumur/src/smt/translate.h000066400000000000000000000005031461637631000202040ustar00rootroot00000000000000#pragma once #include #include #include namespace smt { // translate an expression to its SMTLIB equivalent std::string translate(const rumur::Expr &expr); // name-mangle a symbol to make it a safe SMT variable std::string mangle(const std::string &s, size_t id); } // namespace smt rumur-2024.05.07/rumur/src/smt/typeexpr-to-smt.cc000066400000000000000000000037341461637631000214570ustar00rootroot00000000000000#include "typeexpr-to-smt.h" #include "../options.h" #include "except.h" #include "logic.h" #include "translate.h" #include #include #include #include #include using namespace rumur; namespace smt { namespace { class Translator : public ConstTypeTraversal { private: std::ostringstream out; public: Translator &operator<<(const std::string &s) { out << s; return *this; } Translator &operator<<(const TypeExpr &type) { dispatch(type); return *this; } std::string str() const { return out.str(); } void visit_array(const Array &n) final { *this << "(Array " << *n.index_type << " " << *n.element_type << ")"; } void visit_enum(const Enum &n) final { // the SMT solver already knows the type of booleans if (n.is_boolean()) { *this << "Bool"; return; } /* we assume our caller will emit the members of this enum if they are * relevant to them. */ *this << integer_type(); } void visit_range(const Range &) final { /* we assume our caller will eventually set the lower and upper bound * constraints for this integer if it is relevant to them */ *this << integer_type(); } void visit_record(const Record &n) final { // this type will have been previously constructed using its unique // identifier (see define-records.cc) *this << mangle("", n.unique_id); } void visit_scalarset(const Scalarset &) final { /* A scalarset is nothing special to the SMT solver. That is, we just * declare it as an integer and don't expect the solver to use any symmetry * reasoning. */ *this << integer_type(); } void visit_typeexprid(const TypeExprID &n) final { assert(n.referent != nullptr && "unresolved TypeExprID in AST"); *this << *n.referent->value; } }; } // namespace std::string typeexpr_to_smt(const TypeExpr &type) { Translator t; t.dispatch(type); return t.str(); } } // namespace smt rumur-2024.05.07/rumur/src/smt/typeexpr-to-smt.h000066400000000000000000000003611461637631000213120ustar00rootroot00000000000000#pragma once #include #include #include namespace smt { // translate a Rumur type into the equivalent SMTLIB type expression std::string typeexpr_to_smt(const rumur::TypeExpr &type); } // namespace smt rumur-2024.05.07/rumur/src/symmetry-reduction.cc000066400000000000000000000615661461637631000214450ustar00rootroot00000000000000#include "symmetry-reduction.h" #include "../../common/isa.h" #include "options.h" #include "utils.h" #include #include #include #include #include #include #include #include using namespace rumur; std::vector get_scalarsets(const Model &m) { std::vector ss; for (const Ptr &c : m.children) { if (auto t = dynamic_cast(c.get())) { if (isa(t->value)) ss.push_back(t); } } return ss; } // XXX: GMP 6.2.0 contains a function for this, but we want to retain // compatibility with older GMP static mpz_class factorial(mpz_class v) { assert(v >= 0); mpz_class r = 1; while (v != 0) { r *= v; --v; } return r; } mpz_class get_schedule_width(const TypeDecl &t) { auto s = dynamic_cast(t.value.get()); assert(s != nullptr && "non-scalarset passed to get_schedule_width()"); // how many unique non-undefined values of this scalarset are there? mpz_class bound = s->count() - 1; assert(bound > 0); // how many permutations of this many values are there? mpz_class perm = factorial(bound); assert(perm > 0); // and how many bits do we need to store this many values? return bit_width(perm); } // Generate application of a swap of two state components static void generate_apply_swap(std::ostream &out, const std::string &offset_a, const std::string &offset_b, const TypeExpr &type, size_t depth = 0) { const Ptr t = type.resolve(); const std::string indent = std::string((depth + 1) * 2, ' '); if (t->is_simple()) { out << indent << "if (" << offset_a << " != " << offset_b << ") {\n" << indent << " raw_value_t a = handle_read_raw(s, state_handle(s, " << offset_a << ", " << t->width() << "ull));\n" << indent << " raw_value_t b = handle_read_raw(s, state_handle(s, " << offset_b << ", " << t->width() << "ull));\n" << indent << " handle_write_raw(s, state_handle(s, " << offset_b << ", " << t->width() << "ull), a);\n" << indent << " handle_write_raw(s, state_handle(s, " << offset_a << ", " << t->width() << "ull), b);\n" << indent << "}\n"; return; } if (auto a = dynamic_cast(t.get())) { const std::string var = "i" + std::to_string(depth); mpz_class ic = a->index_type->count() - 1; const std::string len = "((size_t)" + ic.get_str() + "ull)"; const std::string width = "((size_t)" + a->element_type->width().get_str() + "ull)"; out << indent << "for (size_t " << var << " = 0; " << var << " < " << len << "; " << var << "++) {\n"; const std::string off_a = offset_a + " + " + var + " * " + width; const std::string off_b = offset_b + " + " + var + " * " + width; generate_apply_swap(out, off_a, off_b, *a->element_type, depth + 1); out << indent << "}\n"; return; } if (auto r = dynamic_cast(t.get())) { std::string off_a = offset_a; std::string off_b = offset_b; for (const Ptr &f : r->fields) { generate_apply_swap(out, off_a, off_b, *f->type, depth); off_a += " + ((size_t)" + f->width().get_str() + "ull)"; off_b += " + ((size_t)" + f->width().get_str() + "ull)"; } return; } assert(!"missed case in generate_apply_swap"); } static void generate_swap_chunk(std::ostream &out, const TypeExpr &t, const std::string &offset, const TypeDecl &pivot, size_t depth = 0) { const std::string indent((depth + 1) * 2, ' '); if (t.is_simple()) { if (auto s = dynamic_cast(&t)) { if (s->name == pivot.name) { /* This state component has the same type as the pivot. If its value is * one of the pair we are swapping, we need to change it to the other. */ const std::string w = "((size_t)" + t.width().get_str() + "ull)"; const std::string h = "state_handle(s, " + offset + ", " + w + ")"; out << indent << "if (x != y) {\n" << indent << " raw_value_t v = handle_read_raw(s, " << h << ");\n" << indent << " if (v != 0) {\n" << indent << " if (v - 1 == (raw_value_t)x) {\n" << indent << " handle_write_raw(s, " << h << ", y + 1);\n" << indent << " } else if (v - 1 == (raw_value_t)y) {\n" << indent << " handle_write_raw(s, " << h << ", x + 1);\n" << indent << " }\n" << indent << " }\n" << indent << "}\n"; } } // A component of any other simple type is irrelevant. return; } const Ptr type = t.resolve(); if (auto a = dynamic_cast(type.get())) { const std::string w = "((size_t)" + a->element_type->width().get_str() + "ull)"; // If this array is indexed by our pivot type, swap the relevant elements auto s = dynamic_cast(a->index_type.get()); if (s != nullptr && s->name == pivot.name) { const std::string off_x = offset + " + x * " + w; const std::string off_y = offset + " + y * " + w; generate_apply_swap(out, off_x, off_y, *a->element_type, depth); } // Descend into its element to allow further swapping const std::string i = "i" + std::to_string(depth); mpz_class ic = a->index_type->count() - 1; const std::string len = "((size_t)" + ic.get_str() + "ull)"; out << indent << "for (size_t " << i << " = 0; " << i << " < " << len << "; " << i << "++) {\n"; const std::string off = offset + " + " + i + " * " + w; generate_swap_chunk(out, *a->element_type, off, pivot, depth + 1); out << indent << "}\n"; return; } if (auto r = dynamic_cast(type.get())) { std::string off = offset; for (const Ptr &f : r->fields) { generate_swap_chunk(out, *f->type, off, pivot, depth); off += " + ((size_t)" + f->width().get_str() + "ull)"; } return; } assert(!"missed case in generate_swap_chunk"); } static void generate_swap(const Model &m, std::ostream &out, const TypeDecl &pivot) { out << "static void swap_" << pivot.name << "(" << "struct state *s __attribute__((unused)), " << "size_t x __attribute__((unused)), " << "size_t y __attribute__((unused))) {\n"; for (const Ptr &c : m.children) { if (auto v = dynamic_cast(c.get())) { std::string offset = "((size_t)" + v->offset.get_str() + "ull)"; generate_swap_chunk(out, *v->type, offset, pivot); } } out << "}\n\n"; } static void generate_schedule_reader(std::ostream &out, const TypeDecl &pivot, const mpz_class &offset, const mpz_class &width) { out << "static size_t schedule_read_" << pivot.name << "(const struct state *NONNULL s) {\n" << " assert(s != NULL);\n" << " return state_schedule_get(s, " << offset.get_str() << "ul, " << width.get_str() << "ul);\n" << "}\n"; } static void generate_schedule_writer(std::ostream &out, const TypeDecl &pivot, const mpz_class &offset, const mpz_class &width) { out << "static void schedule_write_" << pivot.name << "(struct state *NONNULL s, size_t schedule_index) {\n" << " assert(s != NULL);\n" << " state_schedule_set(s, " << offset.get_str() << "ul, " << width.get_str() << "ul, schedule_index);\n" << "}\n"; } static void generate_loop_header(const TypeDecl &scalarset, size_t index, size_t level, std::ostream &out) { const std::string indent(level * 2, ' '); const Ptr type = scalarset.value->resolve(); auto s = dynamic_cast(type.get()); assert(s != nullptr); const std::string bound = "((size_t)" + s->bound->constant_fold().get_str() + "ull)"; const std::string i = "i" + std::to_string(index); out << indent << "if (state_cmp(&candidate, s) < 0) {\n" << indent << " /* Found a more canonical representation. */\n" << indent << " memcpy(s, &candidate, sizeof(*s));\n" << indent << "}\n\n" << indent << "{\n" << indent << " size_t stack_" << scalarset.name << "[" << bound << "] = { 0 };\n\n" << indent << " size_t schedule_" << scalarset.name << "[" << bound << "] = { 0 };\n" << indent << " if (USE_SCALARSET_SCHEDULES) {\n" << indent << " size_t stack[" << bound << "];\n" << indent << " size_t index = schedule_read_" << scalarset.name << "(&candidate);\n" << indent << " index_to_permutation(index, schedule_" << scalarset.name << ", stack, " << bound << ");\n" << indent << " }\n\n" << indent << " for (size_t " << i << " = 0; " << i << " < " << bound << "; ) {\n" << indent << " if (stack_" << scalarset.name << "[" << i << "] < " << i << ") {\n" << indent << " if (" << i << " % 2 == 0) {\n" << indent << " swap_" << scalarset.name << "(&candidate, 0, " << i << ");\n" << indent << " size_t tmp = schedule_" << scalarset.name << "[0];\n" << indent << " schedule_" << scalarset.name << "[0] = schedule_" << scalarset.name << "[" << i << "];\n" << indent << " schedule_" << scalarset.name << "[" << i << "] = tmp;\n" << indent << " } else {\n" << indent << " swap_" << scalarset.name << "(&candidate, stack_" << scalarset.name << "[" << i << "], " << i << ");\n" << indent << " size_t tmp = schedule_" << scalarset.name << "[stack_" << scalarset.name << "[" << i << "]];\n" << indent << " schedule_" << scalarset.name << "[stack_" << scalarset.name << "[" << i << "]] = schedule_" << scalarset.name << "[" << i << "];\n" << indent << " schedule_" << scalarset.name << "[" << i << "] = tmp;\n" << indent << " }\n" << indent << " /* save selected schedule to map this back for later more\n" << indent << " * comprehensible counterexample traces\n" << indent << " */\n" << indent << " if (USE_SCALARSET_SCHEDULES) {\n" << indent << " size_t stack[" << bound << "];\n" << indent << " size_t working[" << bound << "];\n" << indent << " size_t index = permutation_to_index(schedule_" << scalarset.name << ", stack, working, " << bound << ");\n" << indent << " schedule_write_" << scalarset.name << "(&candidate, index);\n" << indent << " }\n"; } static void generate_loop_footer(const TypeDecl &scalarset, size_t index, size_t level, std::ostream &out) { const std::string indent(level * 2, ' '); assert(isa(scalarset.value->resolve())); const std::string i = "i" + std::to_string(index); out << indent << " stack_" << scalarset.name << "[" << i << "]++;\n" << indent << " " << i << " = 0;\n" << indent << " } else {\n" << indent << " stack_" << scalarset.name << "[" << i << "] = 0;\n" << indent << " " << i << "++;\n" << indent << " }\n" << indent << " }\n" << indent << "}\n"; } static void generate_loop(const std::vector &scalarsets, size_t index, size_t level, std::ostream &out) { if (index < scalarsets.size() - 1) generate_loop(scalarsets, index + 1, level, out); generate_loop_header(*scalarsets[index], index, level, out); if (index < scalarsets.size() - 1) { generate_loop(scalarsets, index + 1, level + 3, out); } else { const std::string indent((level + 3) * 2, ' '); out << indent << "if (state_cmp(&candidate, s) < 0) {\n" << indent << " /* Found a more canonical representation. */\n" << indent << " memcpy(s, &candidate, sizeof(*s));\n" << indent << "}\n\n"; } generate_loop_footer(*scalarsets[index], index, level, out); } static void generate_canonicalise_exhaustive( const std::vector &scalarsets, std::ostream &out) { // Write the function prelude out << "static void state_canonicalise_exhaustive(struct state *s " "__attribute__((unused))) {\n" << "\n" << " assert(s != NULL && \"attempt to canonicalise NULL state\");\n" << "\n"; if (!scalarsets.empty()) { out << " /* A state to store the current permutation we are considering. " "*/\n" << " static _Thread_local struct state candidate;\n" << " memcpy(&candidate, s, sizeof(candidate));\n" << "\n"; generate_loop(scalarsets, 0, 1, out); } // Write the function coda out << "}\n\n"; } static bool is_pivot(const TypeDecl &pivot, const TypeExpr *t) { if (t == nullptr) return false; auto s = dynamic_cast(t); if (s == nullptr) return false; return pivot.name == s->name; } // Generate application of a comparison of two state components static void generate_apply_compare(std::ostream &out, const TypeExpr &type, const std::string &offset_a, const std::string &offset_b, const TypeDecl &pivot, size_t depth = 0, bool used_pivot = false) { const Ptr t = type.resolve(); const std::string indent((depth + 1) * 2, ' '); if (t->is_simple()) { /* Emit a comparison of this state component, unless it's scoped by the same * scalarset as ourselves. If we're already doing a comparison based on this * scalarset, we don't want to reuse it because this value itself will * change if/when the parent (array) component is reshuffled. */ if (!used_pivot || !is_pivot(pivot, &type)) { out << indent << "if (" << offset_a << " != " << offset_b << ") {\n" << indent << " raw_value_t a = handle_read_raw(s, state_handle(s, " << offset_a << ", " << t->width() << "ull));\n" << indent << " raw_value_t b = handle_read_raw(s, state_handle(s, " << offset_b << ", " << t->width() << "ull));\n" << indent << " if (a < b) {\n" << indent << " return -1;\n" << indent << " } else if (a > b) {\n" << indent << " return 1;\n" << indent << " }\n" << indent << "}\n"; } return; } if (auto a = dynamic_cast(t.get())) { if (!used_pivot || !is_pivot(pivot, a->index_type.get())) { used_pivot |= is_pivot(pivot, a->index_type.get()); const std::string var = "i" + std::to_string(depth); mpz_class ic = a->index_type->count() - 1; const std::string len = "((size_t)" + ic.get_str() + "ull)"; const std::string width = "((size_t)" + a->element_type->width().get_str() + "ull)"; out << indent << "for (size_t " << var << " = 0; " << var << " < " << len << "; " << var << "++) {\n"; const std::string off_a = offset_a + " + " + var + " * " + width; const std::string off_b = offset_b + " + " + var + " * " + width; generate_apply_compare(out, *a->element_type, off_a, off_b, pivot, depth + 1, used_pivot); out << indent << "}\n"; } return; } if (auto r = dynamic_cast(t.get())) { std::string off_a = offset_a; std::string off_b = offset_b; for (const Ptr &f : r->fields) { generate_apply_compare(out, *f->type, off_a, off_b, pivot, depth, used_pivot); off_a += " + ((size_t)" + f->width().get_str() + "ull)"; off_b += " + ((size_t)" + f->width().get_str() + "ull)"; } return; } assert(!"missed case in generate_apply_compare"); } // Generate part of a memcmp-style comparator static void generate_compare_chunk(std::ostream &out, const TypeExpr &t, const std::string offset, const TypeDecl &pivot, size_t depth = 0, bool used_pivot = false) { const std::string indent((depth + 1) * 2, ' '); if (t.is_simple()) { /* If this state component has the same type as the pivot, we need to see if * it matches either of the operands. Here, we are basically looking to see * which (if either) of the scalarset elements appears *first* in the state. */ if (is_pivot(pivot, &t) && !used_pivot) { const std::string width = "((size_t)" + t.width().get_str() + "ull)"; out /* Open a scope so we don't need to think about redeclaring/shadowing * 'v'. */ << indent << "{\n" << indent << " raw_value_t v = handle_read_raw(s, state_handle(s, " << offset << ", " << width << "));\n" << indent << " if (v != 0) { /* ignored 'undefined' */\n" << indent << " if (v - 1 == (raw_value_t)x) {\n" << indent << " return -1;\n" << indent << " } else if (v - 1 == (raw_value_t)y) {\n" << indent << " return 1;\n" << indent << " }\n" << indent << " }\n" // Close scope << indent << "}\n"; } // Nothing required for any other simply type. return; } const Ptr type = t.resolve(); if (auto a = dynamic_cast(type.get())) { // The bit size of each array element as a C code string const std::string width = "((size_t)" + a->element_type->width().get_str() + "ull)"; /* If this array is indexed by the pivot type, first compare the relevant * elements. Note, we'll only end up descending if the two elements happen * to be equal. */ if (is_pivot(pivot, a->index_type.get()) && !used_pivot) { const std::string off_x = offset + " + x * " + width; const std::string off_y = offset + " + y * " + width; generate_apply_compare(out, *a->element_type, off_x, off_y, pivot, depth, true); used_pivot = true; } // Descend into its elements to allow further comparison // The number of elements in this array as a C code string mpz_class ic = a->index_type->count() - 1; const std::string ub = "((size_t)" + ic.get_str() + "ull)"; // Generate a loop to iterate over all the elements const std::string var = "i" + std::to_string(depth); out << indent << "for (size_t " << var << " = 0; " << var << " < " << ub << "; " << var << "++) {\n"; // Generate code to compare each element const std::string off = offset + " + " + var + " * " + width; generate_compare_chunk(out, *a->element_type, off, pivot, depth + 1, used_pivot); // Close the loop out << indent << "}\n"; return; } if (auto r = dynamic_cast(type.get())) { std::string off = offset; for (const Ptr &f : r->fields) { // Generate code to compare this field generate_compare_chunk(out, *f->type, off, pivot, depth, used_pivot); // Jump over this field to get the offset of the next field const std::string width = "((size_t)" + f->width().get_str() + "ull)"; off += " + " + width; } return; } assert(!"missed case in generate_compare_chunk"); } /* Generate a memcmp-style comparator for a given scalarset with respect to the * state. */ static void generate_compare(std::ostream &out, const TypeDecl &pivot, const Model &m) { out << "static int compare_" << pivot.name << "(const struct state *s, " << "size_t x, size_t y) {\n" << "\n" << " if (x == y) {\n" << " return 0;\n" << " }\n" << "\n"; for (const Ptr &c : m.children) { if (auto v = dynamic_cast(c.get())) { const std::string offset = "((size_t)" + v->offset.get_str() + "ull)"; generate_compare_chunk(out, *v->type, offset, pivot); } } out // Fall through case where all components were equal << " return 0;\n" << "}\n"; } static void generate_sort(std::ostream &out, const TypeDecl &pivot) { assert(isa(pivot.value->resolve())); out << "static void sort_" << pivot.name << "(struct state *s, " << "size_t *schedule, size_t lower, size_t upper) {\n" << "\n" << " /* If we have nothing to sort, bail out. */\n" << " if (lower >= upper) {\n" << " return;\n" << " }\n" << "\n" << " /* Use Hoare's partitioning algorithm to apply quicksort. */\n" << " size_t pivot = lower;\n" // <- this is "pivot" in the quicksort // sense << " size_t i = lower - 1;\n" << " size_t j = upper + 1;\n" << "\n" << " for (;;) {\n" << "\n" << " do {\n" << " i++;\n" << " assert(i >= lower && i <= upper && \"out of bounds access in " << "sort_" << pivot.name << "()\");\n" << " } while (compare_" << pivot.name << "(s, i, pivot) < 0);\n" << "\n" << " do {\n" << " j--;\n" << " assert(j >= lower && j <= upper && \"out of bounds access in " << "sort_" << pivot.name << "()\");\n" << " } while (compare_" << pivot.name << "(s, j, pivot) > 0);\n" << "\n" << " if (i >= j) {\n" << " break;\n" << " }\n" << "\n" << " /* Swap elements i and j. */\n" << " swap_" << pivot.name << "(s, i, j);\n" << " {\n" << " size_t tmp = schedule[i];\n" << " schedule[i] = schedule[j]; \n" << " schedule[j] = tmp;\n" << " }\n" << " if (i == pivot) {\n" << " pivot = j;\n" << " } else if (j == pivot) {\n" << " pivot = i;\n" << " }\n" << " }\n" << "\n" << " sort_" << pivot.name << "(s, schedule, lower, j);\n" << " sort_" << pivot.name << "(s, schedule, j + 1, upper);\n" << "}\n"; } static void generate_canonicalise_heuristic(const Model &m, const std::vector &scalarsets, std::ostream &out) { for (const TypeDecl *t : scalarsets) { generate_compare(out, *t, m); generate_sort(out, *t); } out << "static void state_canonicalise_heuristic(struct state *s " "__attribute__((unused))) {\n" << "\n" << " assert(s != NULL && \"attempt to canonicalise NULL state\");\n" << "\n"; for (const TypeDecl *t : scalarsets) { const Ptr type = t->value->resolve(); auto s = dynamic_cast(type.get()); assert(s != nullptr); mpz_class bound = s->count() - 1; out << " {\n" << " size_t schedule[(size_t)" << bound.get_str() << "ull];\n" << " for (size_t i = 0; i < sizeof(schedule) / sizeof(schedule[0]); " << "++i) {\n" << " schedule[i] = i;\n" << " }\n" << " if (USE_SCALARSET_SCHEDULES) {\n" << " size_t index = schedule_read_" << t->name << "(s);\n" << " size_t stack[(size_t)" << bound.get_str() << "ull];\n" << " index_to_permutation(index, schedule, stack, " << bound.get_str() << "ull);\n" << " }\n" << " sort_" << t->name << "(s, schedule, 0, ((size_t)" << bound.get_str() << "ull) - 1);\n" << " /* save selected schedule to map this back for later more\n" << " * comprehensible counterexample traces\n" << " */\n" << " if (USE_SCALARSET_SCHEDULES) {\n" << " size_t stack[(size_t)" << bound.get_str() << "ull];\n" << " size_t working[(size_t)" << bound.get_str() << "ull];\n" << " size_t index = permutation_to_index(schedule, stack, " "working, " << bound.get_str() << "ull);\n" << " schedule_write_" << t->name << "(s, index);\n" << " }\n" << " }\n"; } out << "}\n\n"; } void generate_canonicalise(const Model &m, std::ostream &out) { // Find types eligible for use in canonicalisation const std::vector scalarsets = get_scalarsets(m); // Generate functions to swap state elements with respect to each scalarset for (const TypeDecl *t : scalarsets) generate_swap(m, out, *t); // generate functions to read and write schedule (which permutation was // selected) data { mpz_class offset = 0; for (const TypeDecl *t : scalarsets) { const mpz_class width = get_schedule_width(*t); generate_schedule_reader(out, *t, offset, width); generate_schedule_writer(out, *t, offset, width); offset += width; } } generate_canonicalise_exhaustive(scalarsets, out); generate_canonicalise_heuristic(m, scalarsets, out); } rumur-2024.05.07/rumur/src/symmetry-reduction.h000066400000000000000000000012511461637631000212700ustar00rootroot00000000000000#pragma once #include #include #include #include #include // find all the named scalarset declarations in a model std::vector get_scalarsets(const rumur::Model &m); // get the number of bits required to store a permutation index for the given // scalarset mpz_class get_schedule_width(const rumur::TypeDecl &t); /* Generate the `state_canonicalise` function for symmetry reduction. Rumur * generates this whether you have symmetry reduction enabled or not, but it * will only be used when symmetry reduction is enabled. */ void generate_canonicalise(const rumur::Model &m, std::ostream &out); rumur-2024.05.07/rumur/src/utils.cc000066400000000000000000000020261461637631000167040ustar00rootroot00000000000000#include "utils.h" #include "../../common/escape.h" #include "options.h" #include #include #include #include #include #include using namespace rumur; std::string to_C_string(const Expr &expr) { return "\"" + escape(expr.to_string()) + "\""; } static std::string to_string(const location &location) { std::stringstream ss; ss << location; return ss.str(); } std::string to_C_string(const location &location) { return "\"" + escape(input_filename) + ":" + to_string(location) + ": \""; } mpz_class bit_width(const mpz_class &v) { // FIXME: this replicates TypeExpr::width(). It would be nice to consolidate // this functionality somewhere. assert(v >= 0); // if there are 0 or 1 values, the width is trivial if (v <= 1) return 0; /* otherwise, we need the number of bits required to represent the largest * value */ mpz_class largest(v - 1); mpz_class bits(0); while (largest != 0) { bits++; largest >>= 1; } return bits; } rumur-2024.05.07/rumur/src/utils.h000066400000000000000000000006231461637631000165470ustar00rootroot00000000000000#pragma once #include #include #include #include // get a C source code string for this expression std::string to_C_string(const rumur::Expr &expr); // get a C source code string for this location std::string to_C_string(const rumur::location &location); // how many bits are required to store `v` unique values? mpz_class bit_width(const mpz_class &v); rumur-2024.05.07/tests/000077500000000000000000000000001461637631000144365ustar00rootroot00000000000000rumur-2024.05.07/tests/193-2.py000077500000000000000000000021241461637631000154650ustar00rootroot00000000000000#!/usr/bin/env python3 """ This is a variant of 193.m that tests that we descend into TypeExprIDs when reordering record fields. It is hard to trigger a behaviour divergence that is exposed during checking, so instead we check the --debug output of rumur. See also https://github.com/Smattr/rumur/issues/193. """ import os import subprocess as sp import sys # a model containing a TypeExprID, itself referring to a record whose fields # will be reordered MODEL = """ type t1: scalarset(4); t2: record a: boolean; b: t1; c: boolean; end; var x: t2; startstate begin end rule begin end """ def main(): # run Rumur in debug mode output = sp.check_output(["rumur", "--output", os.devnull, "--debug"], stderr=sp.STDOUT, input=MODEL.encode("utf-8", "replace")) output = output.decode("utf-8", "replace") # we should see the fields be reordered once due to encountering the original # TypeDecl (t2) and once due to the TypeExprID (the type of x) assert output.count("sorted fields {a, b, c} -> {a, c, b}") == 2 return 0 if __name__ == "__main__": sys.exit(main()) rumur-2024.05.07/tests/193.m000066400000000000000000000017751461637631000151420ustar00rootroot00000000000000-- This model is a stripped back version of a test case that produced a checker -- assertion failure on commit 4944427734628cf913e8d5eeb54d897033f9eb59. If -- caused record fields to be reordered in a way such that they were no longer -- aligned with references to those fields. If this bug has been introduced, -- this will cause an assertion failure during checking when the verifier is -- generated with --debug. -- -- See https://github.com/Smattr/rumur/issues/193 type t : record a : boolean; b : boolean; c : boolean; endrecord; type t2 : scalarset(4); var p : array [ t2 ] of t; var q : record x : boolean; y : t2; z : boolean; endrecord; startstate q.x := false; for ix : t2 do p[ix].a := false; endfor; endstartstate ruleset r : t2 do rule !q.x & p[r].a & !p[r].c ==> q.y := r; q.x := true; endrule rule !p[r].a & !q.x ==> p[r].a := true; p[r].c := false; endrule endruleset rule q.x ==> q.y := q.y; q.x := false; endrule rumur-2024.05.07/tests/README.rst000066400000000000000000000013471461637631000161320ustar00rootroot00000000000000Integration Tests ================= This directory contains an integration test suite for Rumur. It is expected to be run with the current working directory as your build output directory: .. code-block:: sh cd /my/build/dir cmake /my/source/dir make /my/source/dir/tests/run-tests.py Within this directory are various test cases, each defined in a .m file. It is possible to tweak the expected outcome of a test using specially formatted comments in the source of a test case. E.g. to indicate that Rumur is expected to reject a test case: .. code-block:: murphi -- rumur_exit_code: 1 To see how these comments are interpreted and gain a full understanding of what is possible, consult the test script run-tests.py. rumur-2024.05.07/tests/alias-and-field.m000066400000000000000000000006071461637631000175310ustar00rootroot00000000000000-- This test is designed to provoke a case where murphi2c would generate -- malformed code. If this issue has been reintroduced, murphi2c will produce -- code that causes a malformed expansion of the `x` macro. See also Github -- issue #207. type t: record x: boolean; end; var y: t; startstate begin y.x := true; end; rule begin alias x: y.x do x := !y.x; end; end; rumur-2024.05.07/tests/alias-in-bound.m000066400000000000000000000005551461637631000174230ustar00rootroot00000000000000-- this model provokes a problem first observed on commit -- 538dd85228b98fce4e506883523c0afbf9647e0f wherein an alias of a constant would -- not be recognised as a constant as thus not be usable in a range const N: 2; var x: 0 .. N; startstate begin x := 0; end; alias y: N do ruleset i: 0 .. y do rule begin x := N - i; end; end; end; rumur-2024.05.07/tests/alias-in-bound2.m000066400000000000000000000004331461637631000175000ustar00rootroot00000000000000-- variant on alias-in-bound.m that uses the alias in a local range instead of a -- ruleset range const N: 2; var x: 0 .. N; startstate begin x := 0; end; rule begin alias y: N do for i: 0 .. y do x := N - i; end; end; end; rule begin x := N - x; end; rumur-2024.05.07/tests/alias-literal.m000066400000000000000000000004001461637631000173310ustar00rootroot00000000000000-- test that aliasing a literal works as expected var x: boolean; startstate begin x := true; end; rule begin alias y: true do if y then alias z: 1 do if z = 1 then x := !x; end; end; end; end; end; rumur-2024.05.07/tests/alias-of-alias-rule.m000066400000000000000000000012241461637631000203420ustar00rootroot00000000000000-- This tests for regression of a bug reported against Rumur v2020.03.12. -- Alias rules would have their contained aliases emitted in reverse order -- during code generation. This meant that aliases that referenced earlier -- aliases would be emitted referencing a variable that had not yet been -- defined. If this bug has been reintroduced, this model will pass code -- generation but then the generated code will fail to compile. var x: boolean; startstate begin x := true; end; alias -- these two lines will have their code generated in swapped order if the bug -- has been reintroduced y: x; z: y; do rule begin z := !z; end; end; rumur-2024.05.07/tests/alias-of-alias-rule2.m000066400000000000000000000003401461637631000204220ustar00rootroot00000000000000-- a variant of alias-of-alias-rule.m that uses multiple aliasrules, instead of -- one var x: boolean; startstate begin x := true; end; alias y: x do alias z: y do rule begin z := !z; end; end; end; rumur-2024.05.07/tests/alias-of-alias-stmt.m000066400000000000000000000002631461637631000203640ustar00rootroot00000000000000-- test of an alias of an alias in an alias statement var x: boolean; startstate begin x := true; end; rule begin alias y: x; z: y; do z := !z; end; end; rumur-2024.05.07/tests/amp-amp-and.m000066400000000000000000000002031461637631000166770ustar00rootroot00000000000000-- test that Rumur extension && is accepted var x: boolean startstate begin x := true; end rule begin x := !(x && x); end rumur-2024.05.07/tests/and-mixed.m000066400000000000000000000002661461637631000164660ustar00rootroot00000000000000-- rumur_exit_code: 1 -- test that & with mixed operands is rejected var x: 0 .. 10 y: boolean startstate begin y := true; end rule begin x := 2 & true; y := !y; end rumur-2024.05.07/tests/and-return.m000066400000000000000000000013171461637631000166750ustar00rootroot00000000000000-- This model tests for regression of a bug reported against Rumur v2020.05.18. -- Following the introduction of bitwise operations, like bitwise AND, the token -- '&' is initially parsed as an 'AmbiguousAmp'. A later pass attempts to -- discriminate this into a bitwise AND or a logical AND. However, seemingly -- this did not happen before function return value checking. If this bug has -- been reintroduced, this model will fail code generation with an error like -- "cannot retrieve the type of an unresolved '&' expression". var x: boolean; function foo(): boolean; begin -- the following line is the tricky one to handle return x & x; end; startstate begin x := true; end; rule begin x := !x; end; rumur-2024.05.07/tests/apartment.m000066400000000000000000000100451461637631000166070ustar00rootroot00000000000000/* This model represents a situation I had where a friend was staying with me * and I had lent them one of my keys to get in. It's primarily used as a test * of the 'liveness' keyword. However, a close reading can probably also reveal * the secrets to robbing me. */ type location: enum { APARTMENT, -- inside the apartment ELEVATOR, -- the building elevator GARAGE, -- in the underground garage HALLWAY, -- the hallway on my floor LOBBY, -- the lobby of the building STAIRWELL, -- in the building stairwell STREET, -- in the street outside } person: enum { ME, FRIEND } var -- the location of myself and my friend people: array[person] of location startstate begin -- I start in my apartment people[ME] := APARTMENT; -- my friend is outside people[FRIEND] := STREET; end ruleset p: person do -- anyone can leave if they're in the apartment rule "exit apartment" people[p] = APARTMENT ==> begin people[p] := HALLWAY; end /* either of us can get into the apartment from the hallway because we both * have a key */ rule "enter apartment" people[p] = HALLWAY ==> begin people[p] := APARTMENT; end -- the elevator has no security, except in the garage rule "enter elevator" people[p] = HALLWAY | people[p] = LOBBY ==> begin people[p] := ELEVATOR; end rule "exit elevator to hallway" people[p] = ELEVATOR ==> begin people[p] := HALLWAY; end rule "exit elevator to lobby" people[p] = ELEVATOR ==> begin people[p] := LOBBY; end rule "exit elevator to garage" people[p] = ELEVATOR ==> begin people[p] := GARAGE; end -- the stairwell has no security to get in -- note: not possible to enter the stairwell from the street rule "enter stairwell" people[p] = HALLWAY | people[p] = LOBBY | people[p] = GARAGE ==> begin people[p] := STAIRWELL; end /* the stairwell only requires a key to get out on my level or the lobby, but * it's the same key both of us have a copy of */ rule "exit stairwell to street" people[p] = STAIRWELL ==> begin people[p] := STREET; end rule "exit stairwell to garage" people[p] = STAIRWELL ==> begin people[p] := GARAGE; end rule "exit stairwell to lobby" people[p] = STAIRWELL ==> begin people[p] := LOBBY; end rule "exit stairwell to hallway" people[p] = STAIRWELL ==> begin people[p] := HALLWAY; end -- you can exit the lobby to the street with no key rule "exit lobby to street" people[p] = LOBBY ==> begin people[p] := STREET; end -- anyone can exit the garage onto the street rule "exit garage to street" people[p] = GARAGE ==> begin people[p] := STREET; end end /* you can only enter the lobby from the street with the door fob, which my * friend has */ rule "enter lobby from street" people[FRIEND] = STREET ==> begin people[FRIEND] := LOBBY; end -- my friend can also let me in if I'm outside rule "enter lobby from street with me" people[FRIEND] = STREET & people[ME] = STREET ==> begin people[FRIEND] := LOBBY; people[ME] := LOBBY; end -- you can only enter the garage from the street with the remote, which I have rule "enter garage from street" people[ME] = STREET ==> begin people[ME] := GARAGE; end -- I can let my friend in if he's with me rule "enter garage from street with friend" people[ME] = STREET & people[FRIEND] = STREET ==> begin people[ME] := GARAGE; people[FRIEND] := GARAGE; end /* getting into the elevator from the garage for some reason requires the door * fob, which my friend has */ rule "enter elevator from garage" people[FRIEND] = GARAGE ==> begin people[FRIEND] := ELEVATOR; end -- if we're together, my friend can swipe me in rule "enter elevator from garage together" people[FRIEND] = GARAGE & people[ME] = GARAGE ==> begin people[FRIEND] := ELEVATOR; people[ME] := ELEVATOR; end -- regardless of where we are, I should be able to get back into my apartment liveness "I can get in" people[ME] = APARTMENT -- similarly, my friend should always be able to get in liveness "my friend can get in" people[FRIEND] = APARTMENT rumur-2024.05.07/tests/arithmetic-on-heterogeneous-ranges.m000066400000000000000000000003631461637631000235100ustar00rootroot00000000000000/* Tests that arithmetic between two values of differing range types is * supported. */ var x: 1..10; y: 1..5; startstate begin x := 1; y := 1; end; rule x > 1 ==> begin x := x - y; end; rule x < 10 ==> begin x := x + y; end; rumur-2024.05.07/tests/array-assignment.m000066400000000000000000000003251461637631000201000ustar00rootroot00000000000000-- rumur_flags: ['--deadlock-detection', 'stuck'] type foo_t: array[0 .. 1] of boolean; var x: foo_t; startstate begin x[0] := true; x[1] := false; end; rule begin x[0] := x[1]; x[1] := x[0]; end; rumur-2024.05.07/tests/assert-syntax.m000066400000000000000000000003311461637631000174360ustar00rootroot00000000000000-- Test assertions with both orderings of its arguments var x: boolean; startstate begin x := true; end; rule begin assert x | !x "hello"; x := !x; end; rule begin assert "world" x | !x; x := !x; end; rumur-2024.05.07/tests/assertion-type-limits.m000066400000000000000000000006001461637631000210750ustar00rootroot00000000000000/* This model is designed to provoke a case where the size of the generated * state is 31 bits. On 529f40ddfff9de06c716b093dd229e754940ac9b it was observed * that this generates code in debug mode that generates a compiler warning when * built with -Wtype-limits. */ type foo_t: 0 .. 1073741824; var x: foo_t; startstate begin x := 0; end; rule begin x := 1 - x; end; rumur-2024.05.07/tests/assume-in-ruleset.m000066400000000000000000000003321461637631000201740ustar00rootroot00000000000000-- Test using an assumption inside a ruleset var x: 0 .. 10 startstate begin x := 0; end rule x > 0 ==> begin x := x - 1; end rule x < 4 ==> begin x := x + 1; end ruleset y: 5 .. 10 do assume x != y end rumur-2024.05.07/tests/assume-statement.m000066400000000000000000000002131461637631000201070ustar00rootroot00000000000000-- test using an assumption as a statement var x: 0 .. 2 startstate begin x := 0; end rule begin x := 1 - x; assume x != 2; end rumur-2024.05.07/tests/assume-statement2.m000066400000000000000000000006231461637631000201760ustar00rootroot00000000000000/* Test using an assumption that fails as a statement. This was observed to * cause a segfault or assertion failure on * 138877410d739f1d9fc07ee8f1107f8f3c5676de. */ var x: 0 .. 2 startstate begin x := 0; end rule x > 0 ==> begin x := x - 1; end rule x < 2 ==> begin x := x + 1; assume x != 2; end -- if the assumption is working correctly, this invariant should pass invariant x != 2 rumur-2024.05.07/tests/assume-statement3.m000066400000000000000000000005501461637631000201760ustar00rootroot00000000000000-- Similar to assume-statement2.m, but with the assumption inside a function. var x: 0 .. 2 procedure foo(a: 0 .. 2); begin assume a != 2; end; startstate begin x := 0; end rule x > 0 ==> begin x := x - 1; end rule x < 2 ==> begin x := x + 1; foo(x); end -- if the assumption is working correctly, this invariant should pass invariant x != 2 rumur-2024.05.07/tests/assume-statement4.m000066400000000000000000000006461461637631000202050ustar00rootroot00000000000000/* Similar to assume-statement2.m, but with the assumption inside a nested * function. */ var x: 0 .. 2 procedure foo(a: 0 .. 2); begin assume a != 2; end; procedure bar(b: 0 .. 2); begin foo(b); end; startstate begin x := 0; end rule x > 0 ==> begin x := x - 1; end rule x < 2 ==> begin x := x + 1; bar(x); end -- if the assumption is working correctly, this invariant should pass invariant x != 2 rumur-2024.05.07/tests/bad-alias.m000066400000000000000000000006051461637631000164320ustar00rootroot00000000000000-- rumur_exit_code: 1 /* This model should be rejected due to the incorrect usage of 'y' within the * alias block. However, on commit c65737161d9151d8a69ad718aea370636ff73829 it * was observed that this actually causes an assertion failure instead. This was * originally found by AFL. */ var x: boolean; startstate begin end; rule begin alias y: 1 do y := !y.x; end; end; rumur-2024.05.07/tests/bad-array-index.m000066400000000000000000000005751461637631000175720ustar00rootroot00000000000000-- rumur_exit_code: 1 /* The following model should fail code generation due to the invalid array * index. However, in commit c65737161d9151d8a69ad718aea370636ff73829 it was * observed that this actually causes an assertion failure. This issue was * originally found by AFL. */ type foo_t: array[0 .. 1] of boolean; var x: foo_t; startstate begin x[0][0] := x[1]; end; rumur-2024.05.07/tests/bad-element-lhs-in-or.m000066400000000000000000000003751461637631000206040ustar00rootroot00000000000000-- rumur_exit_code: 1 -- similar to bad-field-lhs-in-or.m, this model would cause a crash on commit -- 427c4cb9424ef9ee065c1a3208639b94175bec54 due to invalid assumptions in -- Element::type() var x: 0 .. 10; startstate begin x := x[0] | x; end; rumur-2024.05.07/tests/bad-enum-print.m000066400000000000000000000010711461637631000174350ustar00rootroot00000000000000-- checker_exit_code: 1 -- checker_output: None if xml else re.compile('enum { HELLO, WORLD }') /* This model attempts to provoke a bug first observed on commit * 42b424c6d5986e44c218152e96b065b056ea494b. Enums would be incorrectly printed * without separating spaces. If this bug has been reintroduced, the following * model will fail with an assertion message that says something incorrect like * "enum { HELLOWORLD }". */ var x: boolean; startstate begin x := true; end; rule begin x := !x; assert forall y: enum { HELLO, WORLD } do false end; end; rumur-2024.05.07/tests/bad-expr-type-ref.m000066400000000000000000000003611461637631000200470ustar00rootroot00000000000000-- rumur_exit_code: 1 /* This test contains an ID expression that incorrectly refers to a typedecl. * This should result in an error. */ type t: boolean; var x: boolean; startstate begin x := true; end; rule begin x := t; end; rumur-2024.05.07/tests/bad-field-lhs-in-or.m000066400000000000000000000013171461637631000202330ustar00rootroot00000000000000-- rumur_exit_code: 1 -- On commit 427c4cb9424ef9ee065c1a3208639b94175bec54, AFL found a crashing -- input that could be reduced to something like the following. During symbol -- resolution, Field::type() would be called while trying to disambiguate the -- '|'. However Field::type() was assuming symbol resolution had already -- completed successfully and thus it was safe to assume its left hand side was -- a record. In this example, the field expression is malformed because the left -- hand side is not a record. If this bug has been re-introduced, this model -- should cause an assertion failure or crash instead of correctly being -- rejected by Rumur. var x: 0 .. 10; startstate begin x := x.y | x; end; rumur-2024.05.07/tests/bad-field.m000066400000000000000000000006151461637631000164250ustar00rootroot00000000000000-- rumur_exit_code: 1 /* The following model should be rejected due to the use of 'x' as if it were a * record. However, on commit c65737161d9151d8a69ad718aea370636ff73829 it was * observed that this actually causes an uncaught exception instead. This was * originally found by AFL. */ type t: enum { A, B }; var x: t; startstate begin x := A; end; rule x.A ==> begin x := B; end; rumur-2024.05.07/tests/bad-function-call.m000066400000000000000000000006541461637631000201030ustar00rootroot00000000000000-- rumur_exit_code: 1 /* Rumur should reject the following model due to the incorrect call of function * foo. However, on commit c65737161d9151d8a69ad718aea370636ff73829 it was * observed that this actually caused an assertion failure. This was originally * found by AFL. */ var x: boolean; function foo(a: boolean): boolean; begin return a; end; startstate begin x := true; end; rule begin x := !foo(x, x); end; rumur-2024.05.07/tests/bad-function-parameter.m000066400000000000000000000010171461637631000211420ustar00rootroot00000000000000-- rumur_exit_code: 1 /* This model is designed to provoke a bug observed on commit * ad4078683bd8737add75a294fec65d8bd2f6b84b, wherein passing a boolean literal * in as a var parameter to a function caused an uncaught exception. Rumur * should correctly detect and handle this case, but if the bug has been * reintroduced an uncaught exception will occur during code generation. */ var x: boolean; procedure foo(var y: boolean); begin end; startstate begin x := true; end; rule begin foo(true); x := !x; end; rumur-2024.05.07/tests/bad-invariant-number.m000066400000000000000000000013011461637631000206140ustar00rootroot00000000000000-- checker_exit_code: 1 -- checker_output: None if xml else re.compile(r'\binvariant 1 failed\b') /* This test checks for the presence of a previous bug first observed in commit * e1b74738ba73df5352622464be47b69db688c633, wherein invariants are incorrectly * numbered. This problem occurs because all properties are counted together, so * non-invariant properties that precede an invariant cause it to get a higher * number than expected. If this problem has been reintroduced, the model will * call the invariant "invariant 3" in its error message. */ var x: boolean startstate begin x := true; end rule begin x := !x; end rule begin x := !x; end assume true assume true invariant x rumur-2024.05.07/tests/bad-lvalue.m000066400000000000000000000004151461637631000166300ustar00rootroot00000000000000-- rumur_exit_code: 1 /* This model deliberately uses something that shouldn't be assignable as the * LHS of an assignment, which should trigger an error. */ const N: 0; var x: boolean; startstate begin x := true; end; rule begin N := 0; x := !x; end; rumur-2024.05.07/tests/basic-aliasrule.m000066400000000000000000000001531461637631000176530ustar00rootroot00000000000000var x: boolean; startstate begin x := true; end; alias y: x do rule begin y := !y; end; end; rumur-2024.05.07/tests/basic-const.m000066400000000000000000000001721461637631000170210ustar00rootroot00000000000000const N: 2; var x: boolean; startstate begin x := true; end; rule begin if N = 2 then x := !x; end; end; rumur-2024.05.07/tests/basic-invariant.m000066400000000000000000000002221461637631000176620ustar00rootroot00000000000000-- rumur_flags: ['--deadlock-detection', 'stuck'] var x: boolean; startstate begin x := true; end; rule begin x := x; end; invariant x; rumur-2024.05.07/tests/basic-ruleset.m000066400000000000000000000002111461637631000173500ustar00rootroot00000000000000type foo_t: 1 .. 10; var x: boolean; startstate begin x := true; end; ruleset y: foo_t do rule begin x := !x; end; end; rumur-2024.05.07/tests/basic-ruleset2.m000066400000000000000000000001621461637631000174370ustar00rootroot00000000000000var x: boolean; startstate begin x := true; end; ruleset y: boolean do rule begin x := y; end; end; rumur-2024.05.07/tests/basic-sandbox.m000066400000000000000000000006551461637631000173370ustar00rootroot00000000000000-- rumur_flags: ['--sandbox', 'on'] -- skip_reason: None if CONFIG['HAS_SANDBOX'] else 'no suitable sandboxing facilities available on this platform' /* Test that the sandboxing functionality is usable. Note that this merely * checks that the sandboxing option results in compilable code, not that the * sandbox itself is actually secure. */ var x: boolean; startstate begin x := true; end; rule begin x := !x; end; rumur-2024.05.07/tests/bfs-vs-dfs.m000066400000000000000000000011321461637631000165630ustar00rootroot00000000000000-- checker_exit_code: 1 /* A model to explore Breadth-First Search vs Depth-First Search as exploration * strategies. This model contains multiple errors, different ones of which will * be found depending on which search strategy you use. */ var x: boolean; y: boolean; z: boolean; w: boolean; startstate begin x := false; y := false; z := false; w := false; end; rule "A" !x ==> begin x := true; end; rule "B" !x ==> begin y := true; end; rule "C" y ==> begin z := true; end; rule "D" z ==> begin w := true; end; rule "E" x ==> begin w := true; end; invariant !w rumur-2024.05.07/tests/bitwise-and-enum.m000066400000000000000000000002601461637631000177620ustar00rootroot00000000000000-- rumur_exit_code: 1 -- test that & on an enum is rejected var x: enum { A, B } y: boolean startstate begin y := true; end rule begin x := A & B; y := !y; end rumur-2024.05.07/tests/bitwise-and.m000066400000000000000000000003031461637631000170160ustar00rootroot00000000000000-- test that & is usable as bitwise and var x: 0 .. 5 y: boolean startstate begin y := true; end rule begin x := 1 & 2; assert x = 0; x := 2 & 3; assert x = 2; y := !y; end rumur-2024.05.07/tests/bitwise-not-enum.m000066400000000000000000000003141461637631000200200ustar00rootroot00000000000000-- rumur_exit_code: 1 -- confirm bitwise NOT is not accepted on non-range types type t: enum { A, B } var x: t y: boolean startstate begin y := true; end rule begin x := ~A; y := !y; end rumur-2024.05.07/tests/bitwise-not.m000066400000000000000000000005521461637631000170620ustar00rootroot00000000000000-- test that Rumur extension bitwise NOT is accepted var x: 0 .. 10 y: boolean startstate begin y := true; end rule begin -- we cannot guarantee any particular value_t, so need to be a bit fuzzy with -- our assertions x := 0; assert ~~x = x; x := 1; assert ~~x = x; x := 2; assert ~~x = x; assert ~x != 0 & ~x != 2; y := !y; end rumur-2024.05.07/tests/bitwise-or-enum.m000066400000000000000000000002601461637631000176400ustar00rootroot00000000000000-- rumur_exit_code: 1 -- test that | on an enum is rejected var x: enum { A, B } y: boolean startstate begin y := true; end rule begin x := A | B; y := !y; end rumur-2024.05.07/tests/bitwise-or.m000066400000000000000000000003411461637631000166760ustar00rootroot00000000000000-- test that | is usable as bitwise OR var x: 0 .. 5 y: boolean startstate begin y := true; end rule begin x := 1 | 2; assert x = 3; x := 2 | 3; assert x = 3; x := 2 | 0; assert x = 2; y := !y; end rumur-2024.05.07/tests/bitwise-xor-enum.m000066400000000000000000000002601461637631000200300ustar00rootroot00000000000000-- rumur_exit_code: 1 -- test that ^ on an enum is rejected var x: enum { A, B } y: boolean startstate begin y := true; end rule begin x := A ^ B; y := !y; end rumur-2024.05.07/tests/bitwise-xor.m000066400000000000000000000004041461637631000170660ustar00rootroot00000000000000-- test that bitwise XOR works as expected var x: 0 .. 5 y: boolean startstate begin y := true; end rule begin x := 1 ^ 2; assert x = 3; x := 2 ^ 3; assert x = 1; x := 2 ^ 0; assert x = 2; x := 3 ^ 0; assert x = 3; y := !y; end rumur-2024.05.07/tests/boolean-array-index.m000066400000000000000000000004511461637631000204540ustar00rootroot00000000000000/* A model that uses an array with a boolean type indexing it. */ type bar_t: 0 .. 1; foo_t: array [boolean] of bar_t; var x: foo_t; startstate begin x[false] := 0; x[true] := 1; end; rule begin x[false] := 0; x[true] := 1; end; rule begin x[false] := 1; x[true] := 0; end; rumur-2024.05.07/tests/boolean-array.m000066400000000000000000000003251461637631000173470ustar00rootroot00000000000000type foo_t: array[0 .. 1] of boolean; var x: foo_t; startstate begin x[0] := true; x[1] := false; end; rule begin x[0] := false; x[1] := true; end; rule begin x[0] := true; x[1] := false; end; rumur-2024.05.07/tests/boolean-case.m000066400000000000000000000002421461637631000171420ustar00rootroot00000000000000-- Test "boolean" in different cases var x: boolean; y: Boolean; z: BOOLEAN; w: BoOlEaN; startstate begin x := true; end; rule begin x := !x; end; rumur-2024.05.07/tests/boolean-const.m000066400000000000000000000015551461637631000173650ustar00rootroot00000000000000/* This model is intended to provoke a bug that was observed on commit * 7eafb5295a1f2f094579ef259b537bd3e4996158. Constants would always be * constructed with no type (i.e. implicitly an unrestricted range). This is * fine for anything except a boolean (non-range) constant. A boolean constant * that is constructed with no type can never be used in a boolean expression * because Rumur thinks it is a numeric value and rejects it. * * If this bug is present, the model will fail code generation with a message * like "condition of if clause is not a boolean expression". If everything is * OK, code generation should complete successfully and the generated verifier * should not find any errors. */ const NOT_TRUE: false; var x: boolean; startstate begin x := true; end; rule begin if NOT_TRUE then x := !x; end; end; rule begin x := !x; end; rumur-2024.05.07/tests/boolean-literal-case.m000066400000000000000000000007171461637631000206030ustar00rootroot00000000000000/* A bug observed in commit f6c5ca123d58d1620ab9faaa804e03e075b6ca62 was that * boolean literals that were not lower case were not understood by Rumur, and * resulted in a syntax error. This model tests that this problem has not been * reintroduced. */ var x: boolean; startstate begin x := true; end; rule begin if TRUE then end; if TrUe then end; if True then end; if FALSE then end; if fAlSe then end; if False then end; x := !x; end; rumur-2024.05.07/tests/boolean-shadow.m000066400000000000000000000003621461637631000175170ustar00rootroot00000000000000-- rumur_exit_code: 1 -- test that it is not possible to shadow the 'boolean' type var x: boolean; y: 0 .. 1; startstate begin x := true; end; rule type boolean: 0 .. 1; var z: boolean; begin z := 0; y := z; x := !x; end; rumur-2024.05.07/tests/bound-basic.m000066400000000000000000000003751461637631000170070ustar00rootroot00000000000000-- rumur_flags: ['--bound', '3'] -- a basic test to confirm the --bound option works var x: 0 .. 10; startstate begin x := 0; end; rule begin x := 10 - x; end; rule x > 0 ==> begin x := x - 1; end; rule x < 10 ==> begin x := x + 1; end; rumur-2024.05.07/tests/bound-illegal.m000066400000000000000000000003021461637631000173250ustar00rootroot00000000000000-- rumur_flags: ['--bound', 'foobar'] -- rumur_exit_code: 1 -- test that illegal --bound options are rejected var x: boolean; startstate begin x := true; end; rule begin x := !x; end; rumur-2024.05.07/tests/bound-limit.m000066400000000000000000000004151461637631000170370ustar00rootroot00000000000000-- rumur_flags: ['--bound', '3'] -- test a --bound that should prevent an invariant being violated var x: 0 .. 10; startstate begin x := 0; end; rule x < 10 ==> begin x := x + 1; end; rule x > 0 ==> begin x := x - 1; end; invariant "x stays small" x < 4; rumur-2024.05.07/tests/bound-limit2.m000066400000000000000000000004641461637631000171250ustar00rootroot00000000000000-- rumur_flags: ['--bound', '3'] -- checker_exit_code: 1 -- test that we can still violate an invariant that is *just* within the --bound var x: 0 .. 10; startstate begin x := 0; end; rule x < 10 ==> begin x := x + 1; end; rule x > 0 ==> begin x := x - 1; end; invariant "x stays small" x < 3; rumur-2024.05.07/tests/bound-unused.m000066400000000000000000000003621461637631000172250ustar00rootroot00000000000000-- rumur_flags: ['--bound', '10'] /* test that it's OK to set a --bound that does not have an effect because the * model finishes earlier than the bound */ var x: boolean; startstate begin x := true; end; rule begin x := !x; end; rumur-2024.05.07/tests/call-no-lvalue.m000066400000000000000000000011721461637631000174300ustar00rootroot00000000000000-- rumur_exit_code: 1 /* This model tries several variations on passing a non-lvalue as a var * parameter. We should reject this. On commit * fe9344f5b723608cd8916bd16c2688f9494ca92a, this causes an uncaught exception. */ var x: boolean; function foo(var y: 0 .. 100): boolean; begin assert y = 42; return true; end; function bar(var z: boolean): boolean; begin assert z; return true; end; procedure baz(var w: 0 .. 100); begin assert w = 42; end; procedure qux(var v: boolean); begin assert v; end; startstate begin x := true; end; rule begin foo(42); bar(true); baz(42); qux(true); x := !x; end; rumur-2024.05.07/tests/cex-boolean-startstate.m000066400000000000000000000005041461637631000212030ustar00rootroot00000000000000-- checker_exit_code: 1 -- checker_output: None if xml else re.compile(r'^Startstate\s+1, y:\s*(true|false) fired.$', re.MULTILINE) -- Similar to cex-boolean.m, but using a ruleset startstate. var x: boolean; ruleset y: boolean do startstate begin x := true; end; end; rule begin x := !x; end; invariant x; rumur-2024.05.07/tests/cex-boolean.m000066400000000000000000000005011461637631000170040ustar00rootroot00000000000000-- checker_exit_code: 1 -- checker_output: None if xml else re.compile(r'^Rule\s+1, y:\s*(true|false) fired.$', re.MULTILINE) -- Similar to cex-enum.m, but with the built-in enum 'boolean'. var x: boolean; startstate begin x := true; end; ruleset y: boolean do rule begin x := !x; end; end; invariant x; rumur-2024.05.07/tests/cex-enum-startstate.m000066400000000000000000000005311461637631000205300ustar00rootroot00000000000000-- checker_exit_code: 1 -- checker_output: None if xml else re.compile(r'^Startstate\s+1, y:\s*[AB] fired.$', re.MULTILINE) -- Similar to cex-enum.m, but with the enum parameter in a startstate. type t: enum { A, B}; var x: boolean; ruleset y: t do startstate begin x := true; end; end; rule begin x := !x; end; invariant x; rumur-2024.05.07/tests/cex-enum.m000066400000000000000000000010451461637631000163350ustar00rootroot00000000000000-- checker_exit_code: 1 -- checker_output: None if xml else re.compile(r'^Rule\s+1, y:\s*[AB] fired.$', re.MULTILINE) /* An example that causes a counter-example trace involving an enum. The * motivation for this is that rulesets quantifying over enums led to enums * being printed as numeric values instead of the enum member (observed on * commit 8bf648a180db4070cc5777dd5c73f1a4635c0ab1). */ type t: enum { A, B}; var x: boolean; startstate begin x := true; end; ruleset y: t do rule begin x := !x; end; end; invariant x; rumur-2024.05.07/tests/clear-complex.m000066400000000000000000000002641461637631000173510ustar00rootroot00000000000000-- Test of the clear statement on a complex type var x: record a: boolean end; startstate begin x.a := true; end; rule begin clear x; end; rule begin x.a := true; end; rumur-2024.05.07/tests/clear-simple.m000066400000000000000000000002411461637631000171660ustar00rootroot00000000000000-- Test of the clear statement on a simple type var x: boolean; startstate begin x := true; end; rule begin clear x; end; rule begin x := true; end; rumur-2024.05.07/tests/comment-escape.m000066400000000000000000000010221461637631000175070ustar00rootroot00000000000000-- This test case attempts to provoke an edge case in murphi2c. '\' is an escape -- marker in C comments, but is not significant in Murphi comments. A naive -- translation to C which preserves comments will emit a malformed comment with -- an escape that swallows the next line of code. var x: boolean; startstate begin x := true; end; rule begin -- single line comment that ends in \ if true then x := !x; end; -- a similar case that also has white space after the \ if false then x := !x; end; end; rumur-2024.05.07/tests/comment-parsing.py000077500000000000000000000042461461637631000201240ustar00rootroot00000000000000#!/usr/bin/env python3 """ test comment retrieval API """ import shutil import subprocess import sys if not shutil.which("murphi-comment-ls"): print("murphi-comment-ls not found") sys.exit(125) # a model without any comments NONE = """ var x: boolean; startstate begin x := true; end; rule begin x := !x; end; """ # a model with a single line comment SINGLE = """ var x: boolean; startstate begin x := -- hello world true; end; rule begin x := !x; end; """ # a model with a multiline comment MULTILINE = """ var x: boolean; startstate begin x := /* hello world */ true; end; rule begin x := !x; end; """ # a model with a single line comment terminated by EOF, not \n SINGLE_EOF = """ var x: boolean; startstate begin x := true; end; rule begin x := !x; end; -- hello world""" # a model with a multiline comment terminated by EOF, not */ MULTILINE_EOF = """ var x: boolean; startstate begin x := true; end; rule begin x := !x; end; /* hello world """ # comments inside strings that should be ignored STRING = """ var x: boolean; startstate "hello -- world" begin x := true; end; rule "hello /* world */" begin x := !x; end; """ # a model with a mixture MIX = """ var x: boolean; startstate begin x := -- hello world /* hello world */true; end; -- hello /* hello world */ /* hello -- hello */ rule begin x := !x; end; """ def process(input): proc = subprocess.Popen(["murphi-comment-ls"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, universal_newlines=True) stdout, _ = proc.communicate(input) assert proc.returncode == 0 return stdout def main(): assert process(NONE) == "" assert process(SINGLE) == "6.8-21: hello world\n" assert process(MULTILINE) == "6.8-24: hello world \n" assert process(SINGLE_EOF) == "11.6-19: hello world\n" assert process(MULTILINE_EOF) == "11.6-20: hello world \n" assert process(STRING) == "" assert process(MIX) == "6.8-21: hello world\n" \ "7.3-19: hello world \n" \ "8.6-31: hello /* hello world */\n" \ "9.1-20: hello -- hello \n" return 0 if __name__ == "__main__": sys.exit(main()) rumur-2024.05.07/tests/compare-array.m000066400000000000000000000006061461637631000173600ustar00rootroot00000000000000-- test comparison between array types type t: array[0 .. 1] of 0 .. 1; var x: t; y: t; startstate begin x[0] := 0; x[1] := 1; y[0] := 0; y[1] := 1; end; rule begin -- test comparison in an if statement if x = y then end; -- test negative comparison if x != y then end; -- test in an assertion assert x = y; x[0] := 1 - x[0]; y[0] := 1 - y[0]; end; rumur-2024.05.07/tests/compare-record.m000066400000000000000000000005541461637631000175220ustar00rootroot00000000000000-- test comparison between record types type t: record x: boolean; end; var x: t; y: t; startstate begin x.x := true; y.x := true; end; rule begin -- test comparison in an if statement if x = y then end; -- test negative comparison if x != y then end; -- test in an assertion assert x = y; x.x := !x.x; y.x := !y.x; end; rumur-2024.05.07/tests/config/000077500000000000000000000000001461637631000157035ustar00rootroot00000000000000rumur-2024.05.07/tests/config/CC000077500000000000000000000001511461637631000161130ustar00rootroot00000000000000#!/usr/bin/env bash # C compiler if [ -z "${CC}" ]; then CC=$(which cc) fi printf '"%s"\n' "${CC}" rumur-2024.05.07/tests/config/CXX000077500000000000000000000001571461637631000162760ustar00rootroot00000000000000#!/usr/bin/env bash # C++ compiler if [ -z "${CXX}" ]; then CXX=$(which c++) fi printf '"%s"\n' "${CXX}" rumur-2024.05.07/tests/config/C_FLAGS000077500000000000000000000015201461637631000167250ustar00rootroot00000000000000#!/usr/bin/env bash # initial flags to pass to our C compiler # first, the default flags printf '["-x", "c", "-std=c11", "-Werror=format", "-Werror=sign-compare", ' printf '"-Werror=type-limits"' # test if the C compiler supports -Werror=enum-conversion ${CC:-cc} -x c -std=c11 -Werror=enum-conversion -o /dev/null - &>/dev/null </dev/null </dev/null </dev/null </dev/null </dev/null if [ $? -ne 0 ]; then printf 'pushd failed\n' >&2 exit 1 fi # create a sandbox testing program cat - >test.c < #include #include #include #include #include #include #include int main(void) { // disable addition of new privileges int r = prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); if (r != 0) { perror("prctl(PR_SET_NO_NEW_PRIVS) failed"); return EXIT_FAILURE; } // a BPF program that allows everything static struct sock_filter filter[] = { // return allow BPF_STMT(BPF_RET|BPF_K, SECCOMP_RET_ALLOW), }; static const struct sock_fprog filter_program = { .len = sizeof(filter) / sizeof(filter[0]), .filter = filter, }; // apply the filter to ourselves r = prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &filter_program, 0, 0); if (r != 0) { perror("prctl(PR_SET_SECCOMP) failed"); return EXIT_FAILURE; } return EXIT_SUCCESS; } EOT # compile the test program if ! ${CC:-cc} -std=c11 test.c &>/dev/null; then popd &>/dev/null rm -rf "${TMP}" printf 'False\n' exit 0 fi # execute the test program if ! ./a.out &>/dev/null; then popd &>/dev/null rm -rf "${TMP}" printf 'False\n' exit 0 fi # clean up popd &>/dev/null rm -rf "${TMP}" printf 'True\n' exit 0 fi # for anything else, we do not have sandbox mechanisms printf 'False\n' rumur-2024.05.07/tests/config/HAS_UCLID000077500000000000000000000002111461637631000171560ustar00rootroot00000000000000#!/usr/bin/env bash # is Uclid5 available? which uclid &>/dev/null if [ $? -eq 0 ]; then printf 'True\n' else printf 'False\n' fi rumur-2024.05.07/tests/config/HAS_VALGRIND000077500000000000000000000002161461637631000175310ustar00rootroot00000000000000#!/usr/bin/env bash # is Valgrind available? which valgrind &>/dev/null if [ $? -eq 0 ]; then printf 'True\n' else printf 'False\n' fi rumur-2024.05.07/tests/config/HAS_XMLLINT000077500000000000000000000002141461637631000174500ustar00rootroot00000000000000#!/usr/bin/env bash # is xmllint available? which xmllint &>/dev/null if [ $? -eq 0 ]; then printf 'True\n' else printf 'False\n' fi rumur-2024.05.07/tests/config/NEEDS_LIBATOMIC000077500000000000000000000100301461637631000200440ustar00rootroot00000000000000#!/usr/bin/env bash # does the toolchain need -latomic to support dword CAS? # repeat the HAS_MARCH_NATIVE logic ${CC:-cc} -x c -std=c11 -march=native -o /dev/null - &>/dev/null </dev/null </dev/null < #include // replicate what is in ../../rumur/resources/header.c #define THREADS 2 #if __SIZEOF_POINTER__ <= 4 typedef uint64_t dword_t; #elif __SIZEOF_POINTER__ <= 8 typedef unsigned __int128 dword_t; #else #error "unexpected pointer size; what scalar type to use for dword_t?" #endif static dword_t atomic_read(dword_t *p) { if (THREADS == 1) { return *p; } #if defined(__x86_64__) || defined(__i386__) || \ (defined(__aarch64__) && defined(__GNUC__) && !defined(__clang__)) /* x86-64: MOV is not guaranteed to be atomic on 128-bit naturally aligned * memory. The way to work around this is apparently the following * degenerate CMPXCHG16B. * i386: __atomic_load_n emits code calling a libatomic function that takes a * lock, making this no longer lock free. Force a CMPXCHG8B by using the * __sync built-in instead. * ARM64: __atomic_load_n emits code calling a libatomic function that takes a * lock, making this no longer lock free. Force a CASP by using the __sync * built-in instead. * * XXX: the obvious (irrelevant) literal to use here is “0” but this triggers * a GCC bug on ARM, https://gcc.gnu.org/bugzilla/show_bug.cgi?id=114310. */ return __sync_val_compare_and_swap(p, 1, 1); #endif return __atomic_load_n(p, __ATOMIC_SEQ_CST); } static void atomic_write(dword_t *p, dword_t v) { if (THREADS == 1) { *p = v; return; } #if defined(__x86_64__) || defined(__i386__) || \ (defined(__aarch64__) && defined(__GNUC__) && !defined(__clang__)) /* As explained above, we need some extra gymnastics to avoid a call to * libatomic on x86-64, i386, and ARM64. */ dword_t expected; dword_t old = 0; do { expected = old; old = __sync_val_compare_and_swap(p, expected, v); } while (expected != old); return; #endif __atomic_store_n(p, v, __ATOMIC_SEQ_CST); } static bool atomic_cas(dword_t *p, dword_t expected, dword_t new) { if (THREADS == 1) { if (*p == expected) { *p = new; return true; } return false; } #if defined(__x86_64__) || defined(__i386__) || \ (defined(__aarch64__) && defined(__GNUC__) && !defined(__clang__)) /* Make GCC >= 7.1 emit cmpxchg on x86-64 and i386. See * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80878. * Make GCC emit a CASP on ARM64 (undocumented?). */ return __sync_bool_compare_and_swap(p, expected, new); #endif return __atomic_compare_exchange_n(p, &expected, new, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); } static dword_t atomic_cas_val(dword_t *p, dword_t expected, dword_t new) { if (THREADS == 1) { dword_t old = *p; if (old == expected) { *p = new; } return old; } #if defined(__x86_64__) || defined(__i386__) || \ (defined(__aarch64__) && defined(__GNUC__) && !defined(__clang__)) /* Make GCC >= 7.1 emit cmpxchg on x86-64 and i386. See * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80878. * Make GCC emit a CASP on ARM64 (undocumented?). */ return __sync_val_compare_and_swap(p, expected, new); #endif (void)__atomic_compare_exchange_n(p, &expected, new, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); return expected; } int main(void) { dword_t target = 0; target = atomic_read(&target); atomic_write(&target, 42); atomic_cas(&target, 42, 0); return (int)atomic_cas_val(&target, 0, 42); } EOT # see if the compiler errored if [ $? -eq 0 ]; then printf 'False\n' else printf 'True\n' fi rumur-2024.05.07/tests/config/README000066400000000000000000000004251461637631000165640ustar00rootroot00000000000000This directory contains scripts that are executed as part of the setup to the test suite. Executable files from this directory are run in alphabetical order and are expected to produce a string that can be eval()ed in Python. For more information, see usage in ../run-tests.py rumur-2024.05.07/tests/config/SMT_ARGS000077500000000000000000000010141461637631000171040ustar00rootroot00000000000000#!/usr/bin/env bash # get SMT arguments for an available solver # preference 1: Z3 if which z3 >/dev/null; then # we leave a blank logic here, as Z3 performs best when not given a logic printf '["--smt-path", "z3", "--smt-arg=-smt2", "--smt-arg=-in"]\n' exit 0 fi # preference 2: CVC4 if which cvc4 &>/dev/null; then printf '["--smt-path", "cvc4", "--smt-arg=--lang=smt2", ' printf '"--smt-arg=--rewrite-divk", "--smt-prelude", "(set-logic AUFLIA)"]\n' exit 0 fi # otherwise, give up printf 'None\n' exit 0 rumur-2024.05.07/tests/config/SMT_BV_ARGS000077500000000000000000000011411461637631000174740ustar00rootroot00000000000000#!/usr/bin/env bash # get bitvector SMT arguments for an available solver # preference 1: Z3 if which z3 >/dev/null; then # we leave a blank logic here, as Z3 performs best when not given a logic printf '["--smt-path", "z3", "--smt-arg=-smt2", "--smt-arg=-in", ' printf '"--smt-bitvectors", "on"]\n' exit 0 fi # preference 2: CVC4 if which cvc4 &>/dev/null; then printf '["--smt-path", "cvc4", "--smt-arg=--lang=smt2", ' printf '"--smt-arg=--rewrite-divk", "--smt-prelude", "(set-logic AUFBV)", ' printf '"--smt-bitvectors", "on"]\n' exit 0 fi # otherwise, give up printf 'None\n' exit 0 rumur-2024.05.07/tests/const-enum.m000066400000000000000000000005621461637631000167070ustar00rootroot00000000000000-- Various unusual usages of a const with enum type type t: enum { A, B, C }; const X: A; var x: t; startstate begin x := A; end; rule begin x := B; end; rule begin x := X; end; rule begin if x = X then x := C; end; end; rule begin switch X case A: x := B; end; end; rule begin switch X case X: x := X; end; end; rumur-2024.05.07/tests/const-of-function-call.m000066400000000000000000000003631461637631000211020ustar00rootroot00000000000000-- rumur_exit_code: 1 -- test that a constant whose value is a function call is rejected var x: boolean; function foo(): boolean; begin return true; end; startstate begin x := true; end; rule const N: foo(); begin x := !x; end; rumur-2024.05.07/tests/cover-basic.m000066400000000000000000000002161461637631000170100ustar00rootroot00000000000000-- Test of a basic cover property var x: boolean; startstate begin x := true; end; rule begin x := !x; end; cover "x was false" !x; rumur-2024.05.07/tests/cover-basic2.m000066400000000000000000000005011461637631000170670ustar00rootroot00000000000000-- checker_output: None if xml else re.compile(r'^\s*cover "x was false" hit 2 times$', re.MULTILINE) -- Test of a cover property that is hit multiple times var x: boolean; y: boolean; startstate begin x := true; y := true; end; rule begin x := !x; end; rule begin y := !y; end; cover "x was false" !x; rumur-2024.05.07/tests/cover-miss.m000066400000000000000000000004031461637631000167000ustar00rootroot00000000000000-- checker_output: None if xml else re.compile(r'^\s*cover "x was 2" not hit$', re.MULTILINE) -- checker_exit_code: 1 -- Test a cover that is never hit var x: 0 .. 2; startstate begin x := 0; end; rule begin x := 1 - x; end; cover "x was 2" x = 2; rumur-2024.05.07/tests/cover-multiple.m000066400000000000000000000004601461637631000175630ustar00rootroot00000000000000-- checker_output: None if xml else re.compile(r'^\s*cover "x was 2" not hit$', re.MULTILINE) -- checker_exit_code: 1 -- Test a set of multiple covers var x: 0 .. 2; startstate begin x := 0; end; rule begin x := 1 - x; end; cover "x was 0" x = 0; cover "x was 1" x = 1; cover "x was 2" x = 2; rumur-2024.05.07/tests/cover-stmt-miss.m000066400000000000000000000004171461637631000176720ustar00rootroot00000000000000-- checker_output: None if xml else re.compile(r'^\s*cover "x was 2" not hit$', re.MULTILINE) -- checker_exit_code: 1 -- Test a cover statement that is never hit var x: 0 .. 2; startstate begin x := 0; end; rule begin x := 1 - x; cover "x was 2" x = 2; end; rumur-2024.05.07/tests/cover-stmt.m000066400000000000000000000003761461637631000167250ustar00rootroot00000000000000-- checker_output: None if xml else re.compile(r'^\s*cover "x was true" hit 1 times$', re.MULTILINE) -- Test a cover property used as a statement var x: boolean; startstate begin x := true; end; rule begin x := !x; cover "x was true" x; end; rumur-2024.05.07/tests/cover-trivial.m000066400000000000000000000003551461637631000174050ustar00rootroot00000000000000-- checker_output: None if xml else re.compile(r'^\s*cover "dummy" hit 2 times$', re.MULTILINE) -- Test a degenerate cover property var x: boolean; startstate begin x := true; end; rule begin x := !x; end; cover "dummy" true; rumur-2024.05.07/tests/diff-trace-arrays.m000066400000000000000000000016031461637631000201170ustar00rootroot00000000000000-- rumur_flags: ['--deadlock-detection', 'off', '--threads', '1'] -- checker_exit_code: 1 -- checker_output: None if xml else re.compile(r'(?!(.|\n)*\b(s\[D\]:\s*3)\b(.|\n)*\b\2\b)') /* This model tests for regression of a bug first observed on commit * 7e5654ddb9ad4f3342d3c1b1abef9a3402df5dee. Certain models using enums to index * arrays which produced a failing counter-example, would result in a diff trace * that repeatedly listed an unchanging value for some members of the array. If * the bug has been re-introduced, this model will print the assignment 'S[D]:3' * multiple times in the trace. */ type t: enum { A, B, C, D, E }; d: 0 .. 4; var s: array[t] of d; startstate begin end; ruleset i: t; v: d do rule isundefined(s[i]) & forall j: t do j = i | isundefined(s[j]) | s[j] < v end ==> begin s[i] := v; end; end; invariant exists i: t do isundefined(s[i]) end rumur-2024.05.07/tests/differing-range-passed-to-function.m000066400000000000000000000002021461637631000233550ustar00rootroot00000000000000var x: 0 .. 10; procedure foo(y: 0 .. 5); begin end; startstate begin x := 0; end; rule begin foo(x); x := 1 - x; end; rumur-2024.05.07/tests/differing-type-return.m000066400000000000000000000007101461637631000210430ustar00rootroot00000000000000-- The following tests a case where we return a variable of a range type that -- differs from the function's return type. This does not exceed the type bounds -- and should be accepted -- it is accepted by CMurphi -- but Rumur at commit -- 84998945e6f0f2f3d15b2c9d3bb50e953ffb5143 rejects it. type t1: 0 .. 10; t2: 0 .. 11; var x: t1; function foo(): t2; begin return x; end; startstate begin x := 0; end; rule begin x := 10 - foo(); end; rumur-2024.05.07/tests/differing-type-return3.m000066400000000000000000000004361461637631000211330ustar00rootroot00000000000000-- A variant of differing-type-return.m where we use the same type for both -- entities but indirect through a type definition. type t1: 0 .. 10; t2: t1; var x: t2; function foo(): t1; begin return x; end; startstate begin x := 0; end; rule begin x := 10 - foo(); end; rumur-2024.05.07/tests/disabled/000077500000000000000000000000001461637631000162055ustar00rootroot00000000000000rumur-2024.05.07/tests/disabled/differing-type-return2.m000066400000000000000000000011521461637631000226750ustar00rootroot00000000000000-- checker_exit_code: 1 -- A variant of differing-type-return.m that should be accepted for code -- generation but then should fail at runtime. -- FIXME: *Should* this actually fail at runtime? The generated code currently -- results in a function that returns an rvalue (value_t), so everything works -- out fine. If the user had actually written this, it would be clear what they -- meant. I'm inclined to think maybe this should just be accepted...? type t1: 0 .. 10; t2: 0 .. 11; var x: t2; function foo(): t1; begin return x; end; startstate begin x := 0; end; rule begin x := 11 - foo(); end; rumur-2024.05.07/tests/disabled/rule-no-begin.m000066400000000000000000000013261461637631000210300ustar00rootroot00000000000000/* The following model contains a simple rule with no 'begin' token. According * to the Murphi grammar [0] this is valid, but neither CMurphi nor Rumur at * time of writing (commit 5222f6ddce51ea66ceda6ecb0e016a94308e835b) are able to * parse this model. The root cause is that this part of the grammar is * ambiguous to an LALR(1) parser, which is what both CMurphi and Rumur are * using. It would be nice to address this weakness in future, but to do so I * think we will need to move to a GLR parser. * * Github: see also #77 "begin on rules is not optional" * * [0]: https://www.cs.ubc.ca/~ajh/courses/cpsc513/assign-token/User.Manual */ var x: boolean; startstate x := true; end; rule x := !x; end; rumur-2024.05.07/tests/disabled/smt-array-compare.m000066400000000000000000000010701461637631000217240ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None /* This is an example that contains a tautology. The SMT bridge should be able * to simplify this to `true`, but it currently cannot. The cause is that we * over-approximate ranges used as array indexes and values. So the SMT bridge * does not know that the values of the array can only be 1. */ var x: array[0 .. 1] of 1 .. 1; y: array[0 .. 1] of 1 .. 1; z: boolean; startstate begin z := true; end; rule begin if x = y then z := !z; end; end; rumur-2024.05.07/tests/disabled/smt-div2.m000066400000000000000000000006261461637631000200340ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- test that the SMT bridge can cope with division by non-constant var x: 4 .. 6; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if 2 * x / x = 2 then y := !y; end; end; rumur-2024.05.07/tests/disabled/smt-mod2.m000066400000000000000000000006211461637631000200240ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- test that the SMT bridge can cope with modulo by a non-constant var x: 8 .. 9; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if x % x = 0 then y := !y; end; end; rumur-2024.05.07/tests/division.m000066400000000000000000000004051461637631000164370ustar00rootroot00000000000000-- If you can believe it, my initial implementation left out the division -- operator. We have this test case just to make sure it works. var x: 0 .. 2; startstate begin x := 2; end; rule begin x := x / 2; end; rule x < 2 ==> begin x := x + 1; end; rumur-2024.05.07/tests/double-semicolon.m000066400000000000000000000005041461637631000200530ustar00rootroot00000000000000/* This model contains an extraneous semi-colon. On commit * 90a95ea10ef3eeb15803954c0f8c9fe267799b94, it was observed that Rumur * incorrectly rejects this as a syntax error. This tests whether the problem * has been reintroduced. */ var x: boolean; startstate begin x := false; end; rule begin x := !x;; end; rumur-2024.05.07/tests/double-semicolon2.m000066400000000000000000000002411461637631000201330ustar00rootroot00000000000000-- similar to double-semicolon.m, but using doubles in other places var x: boolean; ; startstate begin x := false; end;;; rule begin x := !x;; end; ; ; rumur-2024.05.07/tests/duplicate-enum-members.m000066400000000000000000000005071461637631000211620ustar00rootroot00000000000000-- rumur_exit_code: 1 /* This model tests for the presence of a bug that was observed on commit * cf02a36f1c2f4a3726aba23aaa857ed90914ba54, wherein an enum type with duplicate * members is incorrectly accepted. */ type t: enum { A, A }; var x: boolean; startstate begin x := true; end; rule begin x := !x; end; rumur-2024.05.07/tests/duplicate-enum-members2.m000066400000000000000000000003741461637631000212460ustar00rootroot00000000000000-- rumur_exit_code: 1 /* A variation on duplicate-enum-members.m where we use members in different * types that collide. */ type t1: enum { A }; t2: enum { A }; var x: boolean; startstate begin x := true; end; rule begin x := !x; end; rumur-2024.05.07/tests/duplicate-record-fields.m000066400000000000000000000004371461637631000213120ustar00rootroot00000000000000-- rumur_exit_code: 1 -- Test for duplicate field names within a record. This should fail to generate -- a checker. type foo_t: record a: boolean; a: 0 .. 2; end; var x: boolean; y: foo_t; startstate begin x := true; y.a := true; end; rule begin x := !x; end; rumur-2024.05.07/tests/duplicate-startstate.m000066400000000000000000000004211461637631000207570ustar00rootroot00000000000000-- In earlier incarnations of Rumur, models like this would trigger a memory -- leak as the duplicate startstate that was encountered would not be freed. var x: boolean; startstate begin x := true; end; startstate begin x := true; end; rule begin x := !x; end; rumur-2024.05.07/tests/duplicate-state-fields.m000066400000000000000000000003141461637631000211460ustar00rootroot00000000000000-- rumur_exit_code: 1 -- Test for duplicate names within the state. This should fail to generate a -- checker. var a: boolean; a: 0 .. 2; startstate begin a := 0; end; rule begin a := 0; end; rumur-2024.05.07/tests/enum-local-var.m000066400000000000000000000004051461637631000174350ustar00rootroot00000000000000-- rumur_flags: ['--deadlock-detection', 'stuck'] -- This model previously triggered a bug wherein Rumur would segfault during -- code generation. type en: enum { A, B }; var x: en; startstate begin x := A; end; rule var y: en; begin y := A; end; rumur-2024.05.07/tests/error-statement.m000066400000000000000000000002421461637631000177450ustar00rootroot00000000000000-- checker_exit_code: 1 -- Test a basic error statement. var x: boolean; startstate begin x := true; end; rule begin x := !x; error "hello world"; end; rumur-2024.05.07/tests/error-string-injection.m000066400000000000000000000007651461637631000212410ustar00rootroot00000000000000/* This model checks we have not reintroduced a problem first observed on commit * e1b74738ba73df5352622464be47b69db688c633, wherein invariant names involving * printf format codes would cause bad calls to the function error() to be * generated in the verifier. If this problem has been introduced, the generated * model will fail to compile with the given flags. */ var x: boolean; startstate begin x := true; end; rule begin x := !x; end; invariant "test string %s injection" x | !x; rumur-2024.05.07/tests/escaping-expressions.m000066400000000000000000000007721461637631000207730ustar00rootroot00000000000000-- checker_exit_code: 1 /* This model intentionally provokes an out-of-range indexing error, but with an * expression that contains a character that is sensitive in XML. When using the * machine-readable output format on commit * 5ea35510cacf551929203e947922ad5811a639d1 this produced invalid XML. This * tests whether the bug involved has been reintroduced. */ var x: array[0 .. 1] of boolean; startstate begin x[0] := true; x[1] := false; end; rule begin x[3 < 2 ? 3 : 2] := !x[0]; end; rumur-2024.05.07/tests/for-step-0-dynamic.m000066400000000000000000000005211461637631000201300ustar00rootroot00000000000000-- checker_exit_code: 1 -- this model tests that a step in a for loop that is not a generation-time -- constant but ends up being 0 at runtime is caught during checking var y: boolean; startstate begin y := true; end; rule var x: 0 .. 10; begin x := 0; for i := 0 to 2 by x do put "hello world"; end; y := !y; end; rumur-2024.05.07/tests/for-step-0.m000066400000000000000000000003521461637631000165100ustar00rootroot00000000000000-- rumur_exit_code: 1 -- this model tests that a trivial infinite loop is rejected var x: boolean; startstate begin x := true; end; rule begin -- this loop will never finish for i := 0 to 10 by 0 do end; x := !x; end; rumur-2024.05.07/tests/for-step-1.m000066400000000000000000000003471461637631000165150ustar00rootroot00000000000000-- rumur_exit_code: 1 -- this model tests that infinite down-loops are rejected var x: boolean; startstate begin x := true; end; rule begin -- this loop will never finish for i := 10 to 0 by 1 do end; x := !x; end; rumur-2024.05.07/tests/for-step-neg-1.m000066400000000000000000000003731461637631000172630ustar00rootroot00000000000000-- rumur_exit_code: 1 -- this model tests that a loop iterating in the wrong direction is rejected var x: boolean; startstate begin x := true; end; rule begin -- this loop will never finish for i := 0 to 10 by -1 do end; x := !x; end; rumur-2024.05.07/tests/for-step-neg-overflow.m000066400000000000000000000016651461637631000207730ustar00rootroot00000000000000-- rumur_flags: ['--deadlock-detection', 'off'] -- This model is designed to trigger a signed integer overflow in generated code -- if a particular bug has been reintroduced. Based on the numeric literals -- appearing in this model, Rumur should decide it is OK to use int8_t to -- represent scalar values. However, a naïve translation of the for loop will -- result in i temporarily having the value -130, outside the range of int8_t. -- -- Rumur should handle this gracefully, but previously did not. If this issue -- has been reintroduced, this model will result in generated code that causes -- compiler warnings or reads undefined values from i at runtime. var x: -127 .. 0; startstate begin x := 0; end; rule var counter: 0 .. 13; begin counter := 0; for i := 0 to -125 by -10 do x := i; counter := counter + 1; end; -- extra paranoia: check the loop iterated the correct number of times assert counter = 13; end; rumur-2024.05.07/tests/for-step-neg-overflow2.m000066400000000000000000000007441461637631000210520ustar00rootroot00000000000000-- rumur_flags: ['--deadlock-detection', 'off', '--value-type', 'int32_t'] -- Identical to for-step-neg-overflow.m, but forcing a larger type used to -- represent scalars to ensure this also works. var x: -127 .. 0; startstate begin x := 0; end; rule var counter: 0 .. 13; begin counter := 0; for i := 0 to -125 by -10 do x := i; counter := counter + 1; end; -- extra paranoia: check the loop iterated the correct number of times assert counter = 13; end; rumur-2024.05.07/tests/for-step-neg.m000066400000000000000000000002721461637631000171230ustar00rootroot00000000000000-- this model tests that we can write loops that iterate backwards var x: boolean; startstate begin x := true; end; rule begin for i := 9 to 1 by -1 do x := !x; end; end; rumur-2024.05.07/tests/for-variants.m000066400000000000000000000056141461637631000172350ustar00rootroot00000000000000-- rumur_flags: ['--deadlock-detection', 'off'] -- this model tests a variety of for-loop expressions, as some of these have -- proven problematic in the past type t1: 0 .. 2; var x: t1; y: t1; z: 0 .. 3; a: array[t1] of t1; startstate begin -- loop by type for i: t1 do a[i] := i; end; assert a[0] = 0 & a[1] = 1 & a[2] = 2; undefine a; -- loop by inline type for i: 0 .. 2 do a[i] := i; end; assert a[0] = 0 & a[1] = 1 & a[2] = 2; undefine a; -- loop by range for i := 0 to 2 do a[i] := i; end; assert a[0] = 0 & a[1] = 1 & a[2] = 2; undefine a; -- loop that crosses 0 for i := -1 to 1 do a[i + 1] := 1 - i; end; assert a[0] = 2 & a[1] = 1 & a[2] = 0; undefine a; -- range that ends at 0 for i := -2 to 0 do a[-i] := -i; end; assert a[0] = 0 & a[1] = 1 & a[2] = 2; undefine a; -- range that is fully negative for i := -3 to -1 do a[3 + i] := 3 + i; end; assert a[0] = 0 & a[1] = 1 & a[2] = 2; undefine a; -- loop with a step for i := 0 to 2 by 1 do a[i] := i; end; assert a[0] = 0 & a[1] = 1 & a[2] = 2; undefine a; -- loop with a non-1 step for i := 0 to 2 by 2 do a[i] := i; end; assert a[0] = 0 & isundefined(a[1]) & a[2] = 2; undefine a; -- loop that does not start at 0 for i := 1 to 2 do a[i] := i; end; assert isundefined(a[0]) & a[1] = 1 & a[2] = 2; undefine a; -- lower and upper bound equal for i := 0 to 0 do a[i] := i; end; assert a[0] = 0 & isundefined(a[1]) & isundefined(a[2]); undefine a; -- loop whose last step exceeds the upper bound for i := 0 to 2 by 3 do a[i] := i; end; assert a[0] = 0 & isundefined(a[1]) & isundefined(a[2]); undefine a; -- loop that has a variable lower bound x := 0; for i := x to 2 do a[i] := i; end; assert a[0] = 0 & a[1] = 1 & a[2] = 2; undefine a; -- loop that has a variable upper bound y := 2; for i := 0 to y do a[i] := i; end; assert a[0] = 0 & a[1] = 1 & a[2] = 2; undefine a; -- loop that has a variable lower and upper bound x := 0; y := 2; for i := x to y do a[i] := i end; assert a[0] = 0 & a[1] = 1 & a[2] = 2; undefine a; -- FIXME: the cases below currently fail -- loop with variable step z := 1; for i := 0 to 2 by z do a[i] := i; end; assert a[0] = 0 & a[1] = 1 & a[2] = 2; undefine a; -- loop with variable lower bound and step x := 0; z := 1; for i := x to 2 by z do a[i] := i; end; assert a[0] = 0 & a[1] = 1 & a[2] = 2; undefine a; -- loop with variable upper bound and step y := 2; z := 1; for i := 0 to y by z do a[i] := i; end; assert a[0] = 0 & a[1] = 1 & a[2] = 2; undefine a; -- loop with variable bounds and step x := 0; y := 2; z := 1; for i := x to y by z do a[i] := i; end; assert a[0] = 0 & a[1] = 1 & a[2] = 2; undefine a; end; rumur-2024.05.07/tests/fox-goose-beans.m000066400000000000000000000050151461637631000176110ustar00rootroot00000000000000-- rumur_flags: ['--deadlock-detection', 'off'] -- checker_exit_code: 1 /* This is a model of the "Fox, goose and bag of beans puzzle" * https://en.wikipedia.org/wiki/Fox,_goose_and_bag_of_beans_puzzle * * We represent the location of each of the parties (fox, goose, beans and human * chaperone) as either side of the river (east or west) or in the boat itself. * All parties start on the east side of the river. The object is to get all * parties to the west side of the river. The boat can only carry the human by * themselves or with one other party. All of this we encode as transition * rules. * * The two failure cases (the fox eats the goose when left alone with it and the * goose eats the beans when left alone with them) are encoded as assumptions. * The *negation* of the goal itself is encoded as an invariant. Thus what we * should find is that this model *fails*, producing a counterexample trace that * gives a solution to the puzzle. * * One such solution is: * 1. the human crosses to west with the goose * 2. the human crosses back east with nothing * 3. the human crosses west with the beans * 4. the human crosses back east with the goose * 5. the human crosses west with the fox * 6. the human crosses back east with nothing * 7. the human crosses west with goose */ type location: enum { EAST, BOAT, WEST }; var fox: location; goose: location; beans: location; human: location; startstate begin fox := EAST; goose := EAST; beans := EAST; human := EAST; end; rule "set out with fox" fox = human & human != BOAT ==> begin human := BOAT; fox := BOAT; end; rule "set out with goose" goose = human & human != BOAT ==> begin human := BOAT; goose := BOAT; end; rule "set out with beans" beans = human & human != BOAT ==> begin human := BOAT; beans := BOAT; end; rule "set out alone" human != BOAT ==> begin human := BOAT; end; rule "arrive east" human = BOAT ==> begin human := EAST; if fox = BOAT then fox := EAST; end; if goose = BOAT then goose := EAST; end; if beans = BOAT then beans := EAST; end; end; rule "arrive west" human = BOAT ==> begin human := WEST; if fox = BOAT then fox := WEST; end; if goose = BOAT then goose := WEST; end; if beans = BOAT then beans := WEST; end; end; assume "fox prevented from eating goose" fox = goose -> human = fox; assume "goose prevented from eating beans" goose = beans -> human = goose; invariant "goal" fox != WEST | goose != WEST | beans != WEST | human != WEST; rumur-2024.05.07/tests/function-and-field.m000066400000000000000000000005261461637631000202650ustar00rootroot00000000000000-- a variant of alias-and-field.m that demonstrates a similar problem, but using -- a non-read-only function parameter that collides with a field name type t: record x: boolean; end; var y: t; z: boolean; procedure foo(var x: boolean); begin y.x := !x; end; startstate begin y.x := true; end; rule begin foo(y.x); end; rumur-2024.05.07/tests/function-call-in-if.m000066400000000000000000000007201461637631000203510ustar00rootroot00000000000000-- This model tests for a bug first observed in commit -- be143484d0b7d00c1cda9d9f7c5b4a2045eabe2b wherein murphi2c would produce C -- source code that would fail to compile. If the bug has been re-introduced, -- the conditional expression will be emitted in the C file missing surrounding -- brackets. var x: boolean; function foo(): boolean; begin return true; end; startstate begin x := false; end; rule begin if foo() then x := !x; end; end; rumur-2024.05.07/tests/function-in-guard.m000066400000000000000000000022271461637631000201500ustar00rootroot00000000000000/* This model is intended to provoke an issue that was observed on commit * 7eafb5295a1f2f094579ef259b537bd3e4996158. If the issue has been reintroduced, * the generated verifier for this model will cause a warning when compiled * about passing a const object as a non-const parameter. * * The cause of this was that the state is always passed in as the first * parameter to a model function. Specifically, it is passed as a non-const * parameter. Meanwhile, the state variable that is available in the context of * a rule guard is const. Obviously the function "ok" is safe to call in the * guard below because it does not modify any state variables. * * A compiler warning may not seem like a particularly significant issue, but * one of the secondary goals of Rumur is for the generated verifier to always * compile warning-free. The advantage of this is that you can build your * generated verifier with "-W -Wall -Wextra" and any warnings you see are * likely to indicate actual problems in your input model. */ var x: boolean; function ok(): boolean; begin return true; end; startstate begin x := true; end; rule ok() ==> begin x := !x; end; rumur-2024.05.07/tests/function-modifying.m000066400000000000000000000015541461637631000204310ustar00rootroot00000000000000-- call to a function that also takes a var parameter -- -- While developing murphi2uclid, it emerged that an interesting case was a call -- to a value-returning function where the function itself also took a var -- parameter. Examples of this existed elsewhere in the test suite, but only in -- combination with other features not accepted by murph2uclid. This example is -- intended to be acceptable by murphi2uclid, but still exhibit this described -- behaviour. var x: boolean; z: boolean; function foo(var y: boolean): boolean; begin -- we cannot actually modify y and still have this model acceptable to -- murphi2uclid (yes, this is a pretty weird edge case) return !y; end; startstate begin x := true; z := true; end; rule begin -- call the function and store its return value, but also pass it something -- mutable z := foo(x); x := z; end; rumur-2024.05.07/tests/function-modifying2.m000066400000000000000000000003431461637631000205060ustar00rootroot00000000000000-- a variant of function-modifying.m that modifies x twice in one statement var x: boolean; function foo(var y: boolean): boolean; begin return !y; end; startstate begin x := true; end; rule begin x := foo(x); end; rumur-2024.05.07/tests/function-order.m000066400000000000000000000004541461637631000175550ustar00rootroot00000000000000-- rumur_exit_code: 1 -- test that calling a function that is defined later is rejected var x: boolean; function foo(n: boolean): boolean; begin return bar(n); end; function bar(n: boolean): boolean; begin return n; end; startstate begin x := true; end; rule begin x := !foo(x); end; rumur-2024.05.07/tests/function-param-intact.m000066400000000000000000000006111461637631000210150ustar00rootroot00000000000000/* On commit 14de19ce3bb545ddb4857fd91cc3a17a4fa2fccb, it was observed that * scalar values being passed to non-var function parameters were being * calculated incorrectly. This model checks that this bug has not been * reintroduced. */ var x: 1 .. 3; procedure foo(y: 1 .. 3); begin assert y < 3; end; startstate begin x := 1; end; rule begin foo(x); x := 2 - (x - 1); end; rumur-2024.05.07/tests/function-return-ignored.m000066400000000000000000000004431461637631000214040ustar00rootroot00000000000000/* Test calling a function as if it were a procedure (function that does not * return a value). Note, CMurphi does not support this extension. */ var x: boolean; function foo(): boolean; begin return true; end; startstate begin x := true; end; rule begin x := !x; foo(); end; rumur-2024.05.07/tests/function1.m000066400000000000000000000004331461637631000165220ustar00rootroot00000000000000-- Preliminary test of complex-returning function implementation -- Yes, the model below makes no sense. type t: record x: boolean; end; var y: t; function foo(): t; begin return y; end; startstate begin y.x := true; end; rule begin y := foo(); y.x := !y.x; end; rumur-2024.05.07/tests/identifier-case.m000066400000000000000000000003271461637631000176510ustar00rootroot00000000000000-- This test is designed to provoke any issues with handling the -- case-sensitivity of variables. var x: boolean; startstate begin x := true; end; ruleset X: boolean do rule begin x := !X; end; end; rumur-2024.05.07/tests/identifier-case2.m000066400000000000000000000003311461637631000177260ustar00rootroot00000000000000-- This test is designed to provoke any issues with handling the -- case-sensitivity of variables. var x: boolean; startstate begin x := true; end; ruleset X: 0 .. 2 do rule begin x := X = 0; end; end; rumur-2024.05.07/tests/identifier-case3.m000066400000000000000000000004201461637631000177260ustar00rootroot00000000000000-- This test is designed to provoke any issues with handling the -- case-sensitivity of variables. var x: boolean; startstate begin x := true; end; ruleset X: 0 .. 2 do rule begin if X = 0 then x := true; else x := false; end; end; end; rumur-2024.05.07/tests/illegal-array-index.m000066400000000000000000000005371461637631000204530ustar00rootroot00000000000000-- rumur_exit_code: 1 -- This was an illegal model AFL found that caused a crash using commit -- 5ccd2be57847aa173a4741340b359edcc3b54074. type -- Note: array index bounds are back to front foo_t: array[8 .. 1] of boolean; var x: foo_t; startstate begin x[0] := true; x[1] := false; end; rule begin x[0] := x[1]; x[1] := x[0]; end; rumur-2024.05.07/tests/index-out-of-range.m000066400000000000000000000002441461637631000202240ustar00rootroot00000000000000-- checker_exit_code: 1 -- Test indexing into an array out of bounds. var x: array[0 .. 1] of boolean; startstate begin end; rule begin x[3] := false; end; rumur-2024.05.07/tests/invariant-failure-message.m000066400000000000000000000005041461637631000216550ustar00rootroot00000000000000-- checker_exit_code: 1 -- checker_output: None if xml else re.compile(r'^.*invariant "hello world" failed', re.MULTILINE) /* Test that the checker reports the name of an invariant in its failure * message. */ var x: boolean startstate begin x := true; end rule begin x := !x; end invariant "hello world" false rumur-2024.05.07/tests/invariant-syntax.m000066400000000000000000000002751461637631000201370ustar00rootroot00000000000000-- A model that tests invariants with flipped syntax var x: boolean; startstate begin x := true; end; rule begin x := !x; end; invariant "hello" x | !x; invariant x | !x "world"; rumur-2024.05.07/tests/isundefined-array.m000066400000000000000000000003051461637631000202230ustar00rootroot00000000000000-- rumur_exit_code: 1 -- Test of isundefined on arrays, that should not work. var x: array [0 .. 0] of boolean; startstate begin x[0] := true; end; rule begin x[0] := isundefined(x); end; rumur-2024.05.07/tests/isundefined-basic.m000066400000000000000000000004151461637631000201700ustar00rootroot00000000000000-- Basic test of isundefined var x: boolean; startstate begin if isundefined(x) then x := true; else x := false; end; end; rule isundefined(x) ==> begin x := true; end; rule !isundefined(x) ==> begin x := !x; end; rule begin undefine x; end; rumur-2024.05.07/tests/isundefined-decl.m000066400000000000000000000006361461637631000200230ustar00rootroot00000000000000-- Test of isundefined on various decls var x: boolean; function bar(var y: boolean): boolean; begin return isundefined(y); end; function baz(): boolean; var y: boolean; begin return isundefined(y); end; startstate begin x := true; end; rule bar(x) ==> begin x := !x; end; rule baz() ==> begin x := !x; end; rule var y: boolean; begin x := isundefined(y); end; rule begin x := !x; end; rumur-2024.05.07/tests/isundefined-element.m000066400000000000000000000004431461637631000205410ustar00rootroot00000000000000-- Test of isundefined on nested values. var x: array [0 .. 0] of boolean; y: record z: boolean; end; startstate begin x[0] := true; y.z := true; end; rule begin x[0] := isundefined(x[0]); end; rule begin y.z := isundefined(y.z); end; rule begin x[0] := !x[0]; end; rumur-2024.05.07/tests/isundefined-function.m000066400000000000000000000013441461637631000207360ustar00rootroot00000000000000-- Test of using isundefined in various function contexts. type t: record z: boolean; end; var x: boolean; y: t; procedure foo(var a: boolean); begin if isundefined(a) then end; end; procedure bar(a: boolean); begin if isundefined(a) then end; end; function baz(var a: boolean): boolean; begin return isundefined(a); end; function qux(a: boolean): boolean; begin return isundefined(a); end; function quux(a: t): boolean; begin return isundefined(a.z); end; function quuz(var a: t): boolean; begin return isundefined(a.z); end; startstate begin x := true; end; rule begin foo(x); bar(x); baz(x); qux(x); foo(y.z); bar(y.z); baz(y.z); qux(y.z); quux(y); quuz(y); x := !x; end; rumur-2024.05.07/tests/isundefined-record.m000066400000000000000000000003071461637631000203650ustar00rootroot00000000000000-- rumur_exit_code: 1 -- Test of isundefined on records, that should not work. var x: record y: boolean; end; startstate begin x.y := true; end; rule begin x.y := isundefined(x); end; rumur-2024.05.07/tests/isundefined-rvalue.m000066400000000000000000000002611461637631000204040ustar00rootroot00000000000000-- rumur_exit_code: 1 -- Test of isundefined on rvalues, that should not work. var x: boolean; startstate begin x := true; end; rule begin x := isundefined(true); end; rumur-2024.05.07/tests/isundefined-rvalue2.m000066400000000000000000000002571461637631000204730ustar00rootroot00000000000000-- rumur_exit_code: 1 -- Test of isundefined on rvalues, that should not work. var x: boolean; startstate begin x := true; end; rule begin x := isundefined(!x); end; rumur-2024.05.07/tests/keyword-case.m000066400000000000000000000004621461637631000172130ustar00rootroot00000000000000-- This test is designed to provoke any issues with handling the -- case-insensitivity of keywords. var x: boolean; startstate begin x := true; end; rule begin x := !x; end; Rule begin x := !x; end; rUle begin x := !x; end; ruLe begin x := !x; end; rulE begin x := !x; end; RULE begin x := !x; end; rumur-2024.05.07/tests/liveness-in-ruleset.m000066400000000000000000000003511461637631000205300ustar00rootroot00000000000000-- test of using liveness inside a ruleset block var x: 0 .. 10 startstate begin x := 0; end rule x < 10 ==> begin x := x + 1; end rule x > 0 ==> begin x := x - 1; end ruleset y: 0 .. 10 do liveness "x is y" x = y end rumur-2024.05.07/tests/liveness-in-ruleset2.m000066400000000000000000000005341461637631000206150ustar00rootroot00000000000000-- checker_exit_code: 1 -- checker_output: None if xml else re.compile(r'liveness property "x is y" violated') -- variant of liveness-in-ruleset.m designed to fail var x: 0 .. 10 startstate begin x := 0; end rule x < 9 ==> begin x := x + 1; end rule x > 0 ==> begin x := x - 1; end ruleset y: 0 .. 10 do liveness "x is y" x = y end rumur-2024.05.07/tests/liveness-miss1.m000066400000000000000000000004751461637631000175040ustar00rootroot00000000000000-- checker_exit_code: 1 -- checker_output: None if xml else re.compile(r'liveness property "x is 10" violated') -- test of a liveness property that is not hit var x: 0 .. 10 startstate begin x := 0; end rule x < 9 ==> begin x := x + 1; end rule x > 0 ==> begin x := x - 1; end liveness "x is 10" x = 10 rumur-2024.05.07/tests/liveness-miss2.m000066400000000000000000000005201461637631000174740ustar00rootroot00000000000000-- checker_exit_code: 1 -- checker_output: None if xml else re.compile(r'liveness property "x is 10" violated') -- a variant of liveness-miss1.m that leaves two values unreached var x: 0 .. 10 startstate begin x := 0; end rule x < 8 ==> begin x := x + 1; end rule x > 0 ==> begin x := x - 1; end liveness "x is 10" x = 10 rumur-2024.05.07/tests/liveness-miss3.m000066400000000000000000000005451461637631000175040ustar00rootroot00000000000000-- checker_exit_code: 1 -- checker_output: None if xml else re.compile(r'liveness property "x is 7" violated') -- a variant of liveness-miss1.m that covers all values but where 7 is not live var x: 0 .. 10 startstate begin x := 0; end rule x < 10 ==> begin x := x + 1; end rule x > 0 & x != 8 ==> begin x := x - 1; end liveness "x is 7" x = 7 rumur-2024.05.07/tests/liveness-statement.m000066400000000000000000000003751461637631000204530ustar00rootroot00000000000000-- rumur_exit_code: 1 /* A liveness property makes no sense in a statement (as opposed to the top * level) and should be rejected. */ var x: boolean; startstate begin x := true; end; rule begin x := !x; liveness "not allowed" x | !x; end; rumur-2024.05.07/tests/lock-freedom-aarch64.py000077500000000000000000000013461461637631000206140ustar00rootroot00000000000000#!/usr/bin/env python3 """ Version of lock-freedom.py for ARM64, which needs -march=native """ import os import subprocess import sys # this test is only relevant on ≥ARMv8.1a CC = os.environ.get("CC", "cc") argv = [CC, "-std=c11", "-march=armv8.1-a", "-x", "c", "-", "-o", os.devnull] p = subprocess.Popen(argv, stdin=subprocess.PIPE, stderr=subprocess.DEVNULL, universal_newlines=True) p.communicate("int main(void) { return 0; }") if p.returncode != 0: print("only relevant for ≥ARMv8.1a machines") sys.exit(125) # call the generic lock-freedom.py with some customisation lock_freedom_py = os.path.join(os.path.dirname(__file__), "lock-freedom.py") sys.exit(subprocess.call(["python3", lock_freedom_py, "-march=armv8.1-a"])) rumur-2024.05.07/tests/lock-freedom-i386.py000077500000000000000000000017571461637631000200630ustar00rootroot00000000000000#!/usr/bin/env python3 """ Version of lock-freedom.py for 32-bit compilation on x86-64 """ import os import platform import sys import subprocess import textwrap # this test is only relevant on x86-64 if platform.machine() not in ("amd64", "x86_64"): print("only relevant for x86-64 machines") sys.exit(125) # check that we have a multilib compiler capable of targeting i386 CC = os.environ.get("CC", "cc") argv = [CC, "-std=c11", "-m32", "-o", os.devnull, "-x", "c", "-"] program = textwrap.dedent("""\ #include int main(void) { printf("hello world\\n"); return 0; } """) p = subprocess.Popen(argv, stdin=subprocess.PIPE, stderr=subprocess.DEVNULL) p.communicate(program.encode("utf-8", "replace")) if p.returncode != 0: print("compiler cannot target 32-bit code") sys.exit(125) # call the generic lock-freedom.py with some customisation lock_freedom_py = os.path.join(os.path.dirname(__file__), "lock-freedom.py") sys.exit(subprocess.call(["python3", lock_freedom_py, "-m32"])) rumur-2024.05.07/tests/lock-freedom-x86-64.py000077500000000000000000000007601461637631000202370ustar00rootroot00000000000000#!/usr/bin/env python3 """ x86-64 version of lock-freedom.py, which needs -mcx16 """ import os import platform import sys import subprocess # this test is only relevant on x86-64 if platform.machine() not in ("amd64", "x86_64"): print("only relevant for x86-64 machines") sys.exit(125) # call the generic lock-freedom.py with some customisation lock_freedom_py = os.path.join(os.path.dirname(__file__), "lock-freedom.py") sys.exit(subprocess.call(["python3", lock_freedom_py, "-mcx16"])) rumur-2024.05.07/tests/lock-freedom.py000066400000000000000000000027251461637631000173650ustar00rootroot00000000000000#!/usr/bin/env python3 """ Test that a compiled verifier does not depend on libatomic. Uses of __atomic built-ins and C11 atomics can sometimes cause the compiler to emit calls to libatomic instead of inline instructions. This is a problem because we use these in the verifier to implement lock-free algorithms, while the libatomic implementations take locks, defeating the purpose of using them. This test checks that we end up with no libatomic calls in the compiled verifier. """ import os import platform import sys import subprocess # generate a checker for a simple model model = "var x: boolean; startstate begin x := false; end; rule begin x := !x; end;" argv = ["rumur", "--output", "/dev/stdout"] p = subprocess.Popen(argv, stdin=subprocess.PIPE, stdout=subprocess.PIPE) model_c, _ = p.communicate(model.encode("utf-8", "replace")) if p.returncode != 0: print("call to rumur failed") sys.exit(1) # compile it to assembly CC = os.environ.get("CC", "cc") argv = [CC, "-O3", "-std=c11", "-x", "c", "-", "-S", "-o", "/dev/stdout"] \ + sys.argv[1:] p = subprocess.Popen(argv, stdin=subprocess.PIPE, stdout=subprocess.PIPE) model_s, _ = p.communicate(model_c) if p.returncode != 0: print("compilation failed") sys.exit(1) # check for calls to libatomic functions if "__atomic_" in model_s.decode("utf-8", "replace"): print("libatomic calls in generated code were not optimised out:\n{}".format( model_s.decode("utf-8", "replace"))) sys.exit(-1) # pass sys.exit(0) rumur-2024.05.07/tests/loop-variable-nonzero-start.m000066400000000000000000000010211461637631000221650ustar00rootroot00000000000000-- This model tests for the presence of a bug first observed on commit -- fa71679f33aec0cceafb7528d24c03e3b2d315f3. The limits used for unpacking the -- handle related to the loop variable were incorrect, causing loops like the -- one in the following model to turn into no-ops. If this bug has been -- reintroduced, this model will deadlock. var x: 0 .. 10; startstate begin x := 0; end; rule x = 0 ==> begin for y := 5 to 9 do x := x + 1; end; end; rule x = 5 ==> begin x := 0; end; invariant x = 0 | x = 5; rumur-2024.05.07/tests/lsh-basic.m000066400000000000000000000015571461637631000164710ustar00rootroot00000000000000-- basic test of left shift operator (Rumur extension) var x: 0 .. 10 y: 0 .. 4 z: boolean startstate begin x := 0; z := true; end rule begin assert x = 0; -- left shift of 0 should still yield 0 x := x << 0; assert x = 0 "<< 0 yielded wrong value"; x := x << 1; assert x = 0 "<< 1 yielded wrong value"; y := 2; x := x << y; assert x = 0 "<< 2 via variable yielded wrong value"; x := x << -1; assert x = 0 "<< -1 yielded wrong value"; x := x << -4; assert x = 0 "<< -4 yielded wrong value"; -- some general shift cases x := 1 << 2; assert x = 4; x := 2 << 1; assert x = 4; x := 2 << 2; assert x = 8; -- overshifts should always produce 0 x := 0 << 128; assert x = 0; x := 1 << 128; assert x = 0; x := 1 << -128; assert x = 0; x := 2 << 128; assert x = 0; x := y << 128; assert x = 0; z := !z; end rumur-2024.05.07/tests/lsh-boolean.m000066400000000000000000000002461461637631000170210ustar00rootroot00000000000000-- rumur_exit_code: 1 -- test that left shifting a boolean is rejected var x: 0 .. 10 y: boolean startstate begin x := 0; end rule begin x := y << 1; end rumur-2024.05.07/tests/lsh-boolean2.m000066400000000000000000000002661461637631000171050ustar00rootroot00000000000000-- rumur_exit_code: 1 -- test that left shifting by a boolean is rejected var x: 0 .. 10 y: boolean startstate begin x := 0; y := true; end rule begin x := 1 << y; end rumur-2024.05.07/tests/lsh-enum.m000066400000000000000000000002521461637631000163430ustar00rootroot00000000000000-- rumur_exit_code: 1 -- test that left shifting an enum is rejected type t: enum { A, B } var x: t startstate begin x := A; end rule begin x := a << 1; end rumur-2024.05.07/tests/lsh-enum2.m000066400000000000000000000002631461637631000164270ustar00rootroot00000000000000-- rumur_exit_code: 1 -- test that left shifting by an enum is rejected type t: enum { A, B } var x: 0 .. 10 startstate begin x := 0; end rule begin x := 2 << A; end rumur-2024.05.07/tests/math-operators.m000066400000000000000000000003751461637631000175660ustar00rootroot00000000000000-- Test support for some of the alternative operators var x: 0 .. 10; startstate begin x := 0; end; rule x = 0 ∨ x = 1 ==> begin x := x + 1; end; rule ¬(x = 0) ∧ ¬(x = 1) ==> begin x := x - 1; end; invariant x = 0 ∨ ¬(x = 0) rumur-2024.05.07/tests/mixed-aliases.m000066400000000000000000000021631461637631000173430ustar00rootroot00000000000000-- Test of all sorts of alias statement variations. var x: boolean; y: record z: boolean end; procedure foo(a: boolean; var b: boolean); begin -- Alias of a read-only parameter alias w: a do b := !w; end; -- Alias of a writable parameter alias w: b do w := !w; end; end; -- As above for a function, just to make sure there's nothing odd there function bar(a: boolean; var b: boolean): boolean begin alias w: a do b := !w; end; alias w: b do w := !w; end; -- Let's try returning an alias too just for kicks alias w: a do return w; end; -- Unreachable, but test generation of returning an lvalue-eligible alias alias w: b do return w; end; end; startstate begin x := true; y.z := true; end; rule begin -- Alias of a state variable alias w: x do w := !w; end; -- Alias of a complex state variable alias w: y do w.z := !w.z; end; -- Alias of a non-top-level state variable alias w: y.z do w := !w; end; end; ruleset c: boolean do rule begin -- Alias a ruleset parameter alias w: c do x := w; end; end; end; rumur-2024.05.07/tests/multiple-const-decl.m000066400000000000000000000002311461637631000204740ustar00rootroot00000000000000-- Test declaration of multiple constants at once. const A, B: 10; var x: boolean; startstate begin x := true; end; rule begin x := !x; end; rumur-2024.05.07/tests/multiple-deadlocks.m000066400000000000000000000023441461637631000204010ustar00rootroot00000000000000-- rumur_flags: ['--threads', '1', '--max-errors', '2'] -- checker_exit_code: 1 -- checker_output: None if xml else re.compile(r'^Startstate 1 fired\.\nx:0\n----------\n\nRule "foo" fired\.\nx:1\n----------\n\nEnd of the error trace\.(.|\n)*^Startstate 1 fired\.\nx:0\n----------\n\nRule "bar" fired\.\nx:2\n----------\n\nEnd of the error trace\.', re.MULTILINE) /* This model is designed to trigger a bug that was observed on commit * 0f66cb1de6e4eb359fc4aa1ab4d55c8dd951a04d. When attempting to report multiple * errors and finding a deadlock, the same deadlock would simply be repeated for * until the error count had been reached. If this bug has been re-introduced, * model will produce the same error trace containing "foo" twice, instead of * the two possible deadlocks in this model. * * The option tweaking above is intended to... * --threads 1: run single threaded to ensure the deadlocks are found in a * deterministic order. * --max-errors 2: look for more than one error, a necessary condition for * triggering the bug. * checker_output: match both different traces. */ var x: 0 .. 10; startstate begin x := 0; end; rule "foo" x = 0 ==> begin x := x + 1; end; rule "bar" x = 0 ==> begin x := x + 2; end; rumur-2024.05.07/tests/multiple-errors.m000066400000000000000000000006311461637631000177610ustar00rootroot00000000000000-- checker_exit_code: 1 -- A model with multiple errors to test --max-errors option -- TODO: tweak cmd line args to this and regex the output during testing var x: 0 .. 5; startstate begin x := 0; end; rule x < 4 ==> begin x := x + 2; end; rule x > 1 ==> begin x := x - 2; end; rule x < 5 ==> begin x := x + 1; end; rule x > 0 ==> begin x := x - 1; end; invariant x != 5; invariant x != 4; rumur-2024.05.07/tests/multiple-parameters.m000066400000000000000000000002411461637631000206050ustar00rootroot00000000000000var x: boolean; function foo(a: boolean; b: boolean): boolean; begin return a; end; startstate begin x := true; end; rule begin x := !foo(x, x); end; rumur-2024.05.07/tests/multiple-parameters2.m000066400000000000000000000003571461637631000206770ustar00rootroot00000000000000-- Same as multiple-parameters.m but omitting a semi-colon (Rumur extension). var x: boolean; function foo(a: boolean b: boolean): boolean; begin return a; end; startstate begin x := true; end; rule begin x := !foo(x, x); end; rumur-2024.05.07/tests/multiple-type-decls.m000066400000000000000000000002511461637631000205140ustar00rootroot00000000000000-- Test declaration of multiple types at once. type A, B: record x: boolean; end; var x: boolean; startstate begin x := true; end; rule begin x := !x; end; rumur-2024.05.07/tests/multiplication.m000066400000000000000000000004311461637631000176470ustar00rootroot00000000000000/* An early implementation of Rumur accidentally omitted support for * multiplication. This tests that we actually can parse multiplication. */ var x: 1 .. 10; startstate begin x := 1; end; rule x > 1 ==> begin x := x - 1; end; rule x <= 5 ==> begin x := x * 2; end; rumur-2024.05.07/tests/murphi-comment-ls/000077500000000000000000000000001461637631000200165ustar00rootroot00000000000000rumur-2024.05.07/tests/murphi-comment-ls/CMakeLists.txt000066400000000000000000000004731461637631000225620ustar00rootroot00000000000000# this is only usable for testing if we are targeting the host machine if(NOT CMAKE_CROSSCOMPILING) add_executable(murphi-comment-ls main.cc) target_include_directories(murphi-comment-ls PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/../../librumur) target_link_libraries(murphi-comment-ls PRIVATE librumur) endif() rumur-2024.05.07/tests/murphi-comment-ls/README000066400000000000000000000000661461637631000207000ustar00rootroot00000000000000test application for ../../librumur/include/Comment.h rumur-2024.05.07/tests/murphi-comment-ls/main.cc000066400000000000000000000014131461637631000212500ustar00rootroot00000000000000#include #include #include #include #include #include int main(int argc, char **argv) { if (argc > 2 || (argc == 2 && strcmp(argv[1], "--help") == 0)) { std::cerr << "Murphi comment lister\n" << " usage: " << argv[0] << " filename\n"; return EXIT_FAILURE; } std::vector comments; if (argc > 1) { std::ifstream in(argv[1]); if (!in) { std::cerr << "failed to open " << argv[1] << "\n"; return EXIT_FAILURE; } comments = rumur::parse_comments(in); } else { comments = rumur::parse_comments(std::cin); } for (const rumur::Comment &c : comments) std::cout << c.loc << ": " << c.content << "\n"; return EXIT_SUCCESS; } rumur-2024.05.07/tests/murphi2murphi-decompose-array.py000077500000000000000000000017701461637631000227230ustar00rootroot00000000000000#!/usr/bin/env python3 import os import pathlib import re import subprocess # model from the test directory involving an array comparison model = pathlib.Path(__file__).parent / "compare-array.m" assert model.exists() # use the complex comparison decomposition to explode the array comparison in # this model print("+ murphi2murphi --decompose-complex-comparisons {}".format(model)) transformed = subprocess.check_output(["murphi2murphi", "--decompose-complex-comparisons", str(model)]) decoded = transformed.decode("utf-8", "replace") print("transformed model:\n{}".format(decoded)) # the comparisons should have been decomposed into element-wise comparison assert re.search(r"\bx\[i0\] = y\[i0\]", decoded) assert re.search(r"\bx\[i0\] != y\[i0\]", decoded) # the generated model also should be valid syntax for Rumur print("+ rumur --output /dev/null <(transformed model)") p = subprocess.Popen(["rumur", "--output", os.devnull], stdin=subprocess.PIPE) p.communicate(transformed) assert p.returncode == 0 rumur-2024.05.07/tests/murphi2murphi-decompose-record.py000077500000000000000000000017611461637631000230630ustar00rootroot00000000000000#!/usr/bin/env python3 import os import pathlib import re import subprocess # model from the test directory involving a record comparison model = pathlib.Path(__file__).parent / "compare-record.m" assert model.exists() # use the complex comparison decomposition to explode the record comparison in # this model print("+ murphi2murphi --decompose-complex-comparisons {}".format(model)) transformed = subprocess.check_output(["murphi2murphi", "--decompose-complex-comparisons", str(model)]) decoded = transformed.decode("utf-8", "replace") print("transformed model:\n{}".format(decoded)) # the comparisons should have been decomposed into member-wise comparison assert re.search(r"\bx\.x = y\.x\b", decoded) assert re.search(r"\bx\.x != y\.x\b", decoded) # the generated model also should be valid syntax for Rumur print("+ rumur --output /dev/null <(transformed model)") p = subprocess.Popen(["rumur", "--output", os.devnull], stdin=subprocess.PIPE) p.communicate(transformed) assert p.returncode == 0 rumur-2024.05.07/tests/murphi2murphi-explicit-semicolons.py000077500000000000000000000017521461637631000236230ustar00rootroot00000000000000#!/usr/bin/env python3 import os import pathlib import re import subprocess # pick an arbitrary test model that omits semi-colons model = pathlib.Path(__file__).parent / "assume-statement.m" assert model.exists() # use the explicit-semicolons pass to add semi-colons print("+ murphi2murphi --explicit-semicolons {}".format(model)) transformed = subprocess.check_output(["murphi2murphi", "--explicit-semicolons", str(model)]) decoded = transformed.decode("utf-8", "replace") print("transformed model:\n{}".format(decoded)) # we should now find the variable definition and both rules in this model are # semi-colon terminated assert re.search(r"\bx: 0 \.\. 2;$", decoded, re.MULTILINE) assert len(re.findall(r"^end;$", decoded, re.MULTILINE)) == 2 # the generated model also should be valid syntax for Rumur print("+ rumur --output /dev/null <(transformed model)") p = subprocess.Popen(["rumur", "--output", os.devnull], stdin=subprocess.PIPE) p.communicate(transformed) assert p.returncode == 0 rumur-2024.05.07/tests/murphi2murphi-remove-liveness.py000077500000000000000000000017651461637631000227600ustar00rootroot00000000000000#!/usr/bin/env python3 import os import pathlib import subprocess # an arbitrary test model that has a liveness property model = pathlib.Path(__file__).parent / "liveness-miss1.m" assert model.exists() # confirm it contains the liveness property we expect with open(str(model), "rt", encoding="utf-8") as f: assert 'liveness "x is 10" x = 10' in f.read() # use the remove-liveness pass to remove the property print("+ murphi2murphi --remove-liveness {}".format(model)) transformed = subprocess.check_output(["murphi2murphi", "--remove-liveness", str(model)]) decoded = transformed.decode("utf-8", "replace") print("transformed model:\n{}".format(decoded)) # now confirm the property is no longer present assert 'liveness "x is 10" x = 10' not in decoded # the generated model also should be valid syntax for Rumur print("+ rumur --output /dev/null <(transformed model)") p = subprocess.Popen(["rumur", "--output", os.devnull], stdin=subprocess.PIPE) p.communicate(transformed) assert p.returncode == 0 rumur-2024.05.07/tests/murphi2murphi-switch-to-if-const-enum.py000077500000000000000000000016451461637631000242350ustar00rootroot00000000000000#!/usr/bin/env python3 import os import pathlib import re import subprocess # model from the test directory involving some switch examples model = pathlib.Path(__file__).parent / "const-enum.m" assert model.exists() # use the switch-to-if pass to remove the switch statements print("+ murphi2murphi --switch-to-if {}".format(model)) transformed = subprocess.check_output(["murphi2murphi", "--switch-to-if", str(model)]) decoded = transformed.decode("utf-8", "replace") print("transformed model:\n{}".format(decoded)) # we should now be able to find some if statements in the model assert re.search(r"\bif X = A then\b", decoded) assert re.search(r"\bif X = X then\b", decoded) # the generated model also should be valid syntax for Rumur print("+ rumur --output /dev/null <(transformed model)") p = subprocess.Popen(["rumur", "--output", os.devnull], stdin=subprocess.PIPE) p.communicate(transformed) assert p.returncode == 0 rumur-2024.05.07/tests/murphi2murphi-switch-to-if-nested.py000077500000000000000000000016651461637631000234310ustar00rootroot00000000000000#!/usr/bin/env python3 import os import pathlib import re import subprocess # model from the test directory involving some switch examples model = pathlib.Path(__file__).parent / "switch-nested.m" assert model.exists() # use the switch-to-if pass to remove the switch statements print("+ murphi2murphi --switch-to-if {}".format(model)) transformed = subprocess.check_output(["murphi2murphi", "--switch-to-if", str(model)]) decoded = transformed.decode("utf-8", "replace") print("transformed model:\n{}".format(decoded)) # we should now be able to find some if statements in the model assert re.search(r"\bif \(?x = 0\)? | \(?x = 1\)?", decoded) assert re.search(r"\bif x = 0 then\b", decoded) # the generated model also should be valid syntax for Rumur print("+ rumur --output /dev/null <(transformed model)") p = subprocess.Popen(["rumur", "--output", os.devnull], stdin=subprocess.PIPE) p.communicate(transformed) assert p.returncode == 0 rumur-2024.05.07/tests/murphi2murphi-switch-to-if1.py000077500000000000000000000016761461637631000222340ustar00rootroot00000000000000#!/usr/bin/env python3 import os import pathlib import re import subprocess # model from the test directory involving some switch examples model = pathlib.Path(__file__).parent / "switch-stmt1.m" assert model.exists() # use the switch-to-if pass to remove the switch statements print("+ murphi2murphi --switch-to-if {}".format(model)) transformed = subprocess.check_output(["murphi2murphi", "--switch-to-if", str(model)]) decoded = transformed.decode("utf-8", "replace") print("transformed model:\n{}".format(decoded)) # we should now be able to find some if statements in the model assert re.search(r"\bif x = 1 then\b", decoded) assert re.search(r"\belsif \(?x = 3\)? | \(?x = 4\)? then\b", decoded) # the generated model also should be valid syntax for Rumur print("+ rumur --output /dev/null <(transformed model)") p = subprocess.Popen(["rumur", "--output", os.devnull], stdin=subprocess.PIPE) p.communicate(transformed) assert p.returncode == 0 rumur-2024.05.07/tests/murphi2murphi-switch-to-if2.py000077500000000000000000000016521461637631000222270ustar00rootroot00000000000000#!/usr/bin/env python3 import os import pathlib import re import subprocess # model from the test directory involving some switch examples model = pathlib.Path(__file__).parent / "switch-stmt2.m" assert model.exists() # use the switch-to-if pass to remove the switch statements print("+ murphi2murphi --switch-to-if {}".format(model)) transformed = subprocess.check_output(["murphi2murphi", "--switch-to-if", str(model)]) decoded = transformed.decode("utf-8", "replace") print("transformed model:\n{}".format(decoded)) # we should now be able to find some if statements in the model assert re.search(r"\bif x = y then\b", decoded) assert re.search(r"\belsif x = 5 then\b", decoded) # the generated model also should be valid syntax for Rumur print("+ rumur --output /dev/null <(transformed model)") p = subprocess.Popen(["rumur", "--output", os.devnull], stdin=subprocess.PIPE) p.communicate(transformed) assert p.returncode == 0 rumur-2024.05.07/tests/murphi2murphi-unicode-div.py000077500000000000000000000015331461637631000220340ustar00rootroot00000000000000#!/usr/bin/env python3 import os import pathlib import re import subprocess # model from the test directory involving a unicode division model = pathlib.Path(__file__).parent / "unicode-div.m" assert model.exists() # use the ASCII transformation to remove ÷ print("+ murphi2murphi --to-ascii {}".format(model)) transformed = subprocess.check_output(["murphi2murphi", "--to-ascii", str(model)]) decoded = transformed.decode("utf-8", "replace") print("transformed model:\n{}".format(decoded)) # the ÷ operator should have become / assert re.search(r"\bx := 2 / x\b", decoded) assert "÷" not in decoded # the generated model also should be valid syntax for Rumur print("+ rumur --output /dev/null <(transformed model)") p = subprocess.Popen(["rumur", "--output", os.devnull], stdin=subprocess.PIPE) p.communicate(transformed) assert p.returncode == 0 rumur-2024.05.07/tests/murphi2murphi-unicode-div2.py000077500000000000000000000015371461637631000221220ustar00rootroot00000000000000#!/usr/bin/env python3 import os import pathlib import re import subprocess # model from the test directory involving a unicode division model = pathlib.Path(__file__).parent / "unicode-div2.m" assert model.exists() # use the ASCII transformation to remove ∕ print("+ murphi2murphi --to-ascii {}".format(model)) transformed = subprocess.check_output(["murphi2murphi", "--to-ascii", str(model)]) decoded = transformed.decode("utf-8", "replace") print("transformed model:\n{}".format(decoded)) # the ∕ operator should have become / assert re.search(r"\bx := 2 / x\b", decoded) assert "∕" not in decoded # the generated model also should be valid syntax for Rumur print("+ rumur --output /dev/null <(transformed model)") p = subprocess.Popen(["rumur", "--output", os.devnull], stdin=subprocess.PIPE) p.communicate(transformed) assert p.returncode == 0 rumur-2024.05.07/tests/murphi2murphi-unicode-mul.py000077500000000000000000000015421461637631000220470ustar00rootroot00000000000000#!/usr/bin/env python3 import os import pathlib import re import subprocess # model from the test directory involving a unicode multiplication model = pathlib.Path(__file__).parent / "unicode-mul.m" assert model.exists() # use the ASCII transformation to remove × print("+ murphi2murphi --to-ascii {}".format(model)) transformed = subprocess.check_output(["murphi2murphi", "--to-ascii", str(model)]) decoded = transformed.decode("utf-8", "replace") print("transformed model:\n{}".format(decoded)) # the × operator should have become * assert re.search(r"\bx := 2 \* x\b", decoded) assert "×" not in decoded # the generated model also should be valid syntax for Rumur print("+ rumur --output /dev/null <(transformed model)") p = subprocess.Popen(["rumur", "--output", os.devnull], stdin=subprocess.PIPE) p.communicate(transformed) assert p.returncode == 0 rumur-2024.05.07/tests/murphi2murphi-unicode-sub.py000077500000000000000000000015411461637631000220420ustar00rootroot00000000000000#!/usr/bin/env python3 import os import pathlib import re import subprocess # model from the test directory involving a unicode subtraction model = pathlib.Path(__file__).parent / "unicode-sub.m" assert model.exists() # use the ASCII transformation to remove − print("+ murphi2murphi --to-ascii {}".format(model)) transformed = subprocess.check_output(["murphi2murphi", "--to-ascii", str(model)]) decoded = transformed.decode("utf-8", "replace") print("transformed model:\n{}".format(decoded)) # the − operator should have become - assert re.search(r"\bx := 1 - x\b", decoded) assert "−" not in decoded # the generated model also should be valid syntax for Rumur print("+ rumur --output /dev/null <(transformed model)") p = subprocess.Popen(["rumur", "--output", os.devnull], stdin=subprocess.PIPE) p.communicate(transformed) assert p.returncode == 0 rumur-2024.05.07/tests/murphi2murphi-unicode-to-ascii.py000077500000000000000000000015641461637631000227660ustar00rootroot00000000000000#!/usr/bin/env python3 import os import pathlib import re import subprocess # model from the test directory involving a record comparison model = pathlib.Path(__file__).parent / "unicode-assignment.m" assert model.exists() # use the ASCII transformation to remove ≔ print("+ murphi2murphi --to-ascii {}".format(model)) transformed = subprocess.check_output(["murphi2murphi", "--to-ascii", str(model)]) decoded = transformed.decode("utf-8", "replace") print("transformed model:\n{}".format(decoded)) # the ≔ operator should have become := assert re.search(r"\bx := true\b", decoded) assert re.search(r"\bx := !x\b", decoded) # the generated model also should be valid syntax for Rumur print("+ rumur --output /dev/null <(transformed model)") p = subprocess.Popen(["rumur", "--output", os.devnull], stdin=subprocess.PIPE) p.communicate(transformed) assert p.returncode == 0 rumur-2024.05.07/tests/named-assert.m000066400000000000000000000002371461637631000172010ustar00rootroot00000000000000-- A based assertion with a name given to it var x: boolean; startstate begin x := true; end; rule begin assert x | !x "hello world"; x := !x; end; rumur-2024.05.07/tests/negate-complex.m000066400000000000000000000010361461637631000175240ustar00rootroot00000000000000-- rumur_exit_code: 1 /* This tests whether we have reintroduced a bug first noticed against commit * e196ed43199d6d47d36eb9f225017c2123e294c3. The bug incorrectly allowed complex * types to appear as the argument to a negation. If this bug has been * reintroduced Rumur will generate code for this model that will then fail to * compile, when really it should reject the model from the outset. */ var x: record a: boolean; end; startstate begin x.a := true; end; rule begin x.a := !x.a; end; rule begin x := -x; end; rumur-2024.05.07/tests/negate-value-type.m000066400000000000000000000012051461637631000201460ustar00rootroot00000000000000/* This model attempts to provoke a bug first observed on * b1e28bfc4fe1b042f7e3034a1516cd20df789b51. The issue was that negative * literals are considered as the negation of a positive literal and Rumur would * only look at the inner (positive) literal when determining the value type * (value_t). As a result, for the following model it would incorrectly choose * the value type uint8_t that cannot contain -1. If this bug has been * reintroduced, this model will error with a subtraction overflow. */ var x: 0 .. 9; startstate begin x := 0; end; rule x > 0 ==> begin x := x + -1; end; rule x < 9 ==> begin x := x + 1; end; rumur-2024.05.07/tests/negation-of-range.m000066400000000000000000000004141461637631000201130ustar00rootroot00000000000000/* On commit e196ed43199d6d47d36eb9f225017c2123e294c3 this model is incorrectly * rejected. After we fix this bug, this can be used to validate the bug has not * been re-introduced. */ var x: -5 .. 5; startstate begin x := 1; end; rule begin x := -x; end; rumur-2024.05.07/tests/negative-numbers.m000066400000000000000000000002711461637631000200670ustar00rootroot00000000000000const N: -2; type r1: -1 .. 1; r2: -3 .. -2; r3: N .. 1; var x: r3; startstate begin x := N; end; rule begin if x = N then x := -1; else x := N; end; end; rumur-2024.05.07/tests/no-cex-bug.m000066400000000000000000000006551461637631000165660ustar00rootroot00000000000000-- rumur_flags: ['--counterexample-trace', 'off'] -- checker_exit_code: 1 /* This model tries to provoke a bug first observed on commit * e96fd5201027ea9d5027637f8d42800897171d2e. If this bug has been reintroduced, * generating a verifier with `--counterexample-trace off` will result in C code * that fails to compile. */ var x: 0 .. 9; startstate begin x := 0; end; rule begin x := x + 1; end; invariant x != 7; rumur-2024.05.07/tests/no-cex-bug2.m000066400000000000000000000006141461637631000166430ustar00rootroot00000000000000-- rumur_flags: ['--counterexample-trace', 'off'] /* This model tests for a similar bug to no-cex-bug.m. When a liveness property * was used in combination with no counterexample tracing, C code that failed to * compile would be produced. */ var x: 0 .. 9; startstate begin x := 0; end; rule x > 0 ==> begin x := x - 1; end; rule x < 9 ==> begin x := x + 1; end; liveness x = 7; rumur-2024.05.07/tests/non-boolean-condition.m000066400000000000000000000003141461637631000210050ustar00rootroot00000000000000-- rumur_exit_code: 1 -- Test using a non-boolean value in a conditional, which should be rejected. var x: 0 .. 10; startstate begin x := 1; end; rule begin if x then x := 3 - x; end; end; rumur-2024.05.07/tests/non-const-parameters.m000066400000000000000000000007721461637631000207010ustar00rootroot00000000000000/* As observed in commit eb74c568327d08b3619b94e80d6cd91464694f96, Rumur would * fail an assert when generating a model that passes the result of a function * to another function. The following model is designed to trigger this bug if * it is still present. */ type t: record x: boolean; end; var x: boolean; procedure foo(a: t); begin end; function bar(): t; var a: t; begin a.x := true; return a; end; startstate begin x := true; end; rule begin foo(bar()); x := !x; end; rumur-2024.05.07/tests/octal-literal.m000066400000000000000000000002331461637631000173460ustar00rootroot00000000000000-- rumur_exit_code: 1 -- test that invalid octal literals are rejected var x: 0 .. 09; startstate begin x := 1; end; rule begin x := 1 - x; end; rumur-2024.05.07/tests/octal-literal2.m000066400000000000000000000005311461637631000174310ustar00rootroot00000000000000-- checker_exit_code: 1 -- test octal literals are interpreted correctly var x: 0 .. 010; startstate begin x := 0; end; rule begin -- if the upper bound of the type was correctly parsed as an octal, it should -- have been set to 8 and we should trigger an error here due to assigning a -- value above this bound x := 9 - x; end; rumur-2024.05.07/tests/only-booleans.m000066400000000000000000000001221461637631000173700ustar00rootroot00000000000000var x: boolean; startstate begin x := true; end; rule begin x := !x; end; rumur-2024.05.07/tests/only-enum.m000066400000000000000000000002351461637631000165370ustar00rootroot00000000000000-- rumur_flags: ['--deadlock-detection', 'stuck'] type foo_t: enum { A, B }; var x: foo_t; startstate begin x := A; end; rule begin x := B; end; rumur-2024.05.07/tests/only-range-and-array.m000066400000000000000000000003601461637631000205420ustar00rootroot00000000000000-- rumur_flags: ['--deadlock-detection', 'stuck'] type range_t: 0 .. 1; array_t: array[range_t] of range_t; var x: array_t; startstate begin x[0] := 0; x[1] := 0; end; rule begin x[0] := 1 - x[1]; x[1] := 1 - x[0]; end; rumur-2024.05.07/tests/only-range-and-untouched-array.m000066400000000000000000000002371461637631000225410ustar00rootroot00000000000000type range_t: 0 .. 1; array_t: array[range_t] of range_t; var x: range_t; y: array_t; startstate begin x := 0; end; rule begin x := 1 - x; end; rumur-2024.05.07/tests/only-range-and-unused-array.m000066400000000000000000000002211461637631000220370ustar00rootroot00000000000000type range_t: 0 .. 1; array_t: array[range_t] of range_t; var x: range_t; startstate begin x := 0; end; rule begin x := 1 - x; end; rumur-2024.05.07/tests/or-mixed.m000066400000000000000000000002661461637631000163440ustar00rootroot00000000000000-- rumur_exit_code: 1 -- test that | with mixed operands is rejected var x: 0 .. 10 y: boolean startstate begin y := true; end rule begin x := 2 | true; y := !y; end rumur-2024.05.07/tests/or-return.m000066400000000000000000000002651461637631000165540ustar00rootroot00000000000000-- equivalent of and-return.m but with an OR var x: boolean; function foo(): boolean; begin return x | x; end; startstate begin x := true; end; rule begin x := !x; end; rumur-2024.05.07/tests/out-of-range-function-parameter.m000066400000000000000000000005431461637631000227220ustar00rootroot00000000000000-- checker_exit_code: 1 /* This model passes a different range into a non-var parameter (something that * should be allowed), but at runtime the value that is passed is outside the * range of the accepting parameter. */ var x: 0 .. 10; procedure foo(y: 0 .. 5); begin end; startstate begin x := 3; end; rule begin x := 10 - x; foo(x); end; rumur-2024.05.07/tests/out-of-range-function-parameter2.m000066400000000000000000000006541461637631000230070ustar00rootroot00000000000000-- checker_exit_code: 1 /* On 466fe5b288aaa45c9970e1391701dd8653f8ab82 it was observed that the * following model results in generated code that causes a compiler warning when * compiling due to unsigned/signed comparison. */ var x: boolean procedure foo(y: -65537 .. -2); begin if y = 0 then end; end; startstate begin x := true; end; rule var z: 65537 .. 65537; begin z := 65537; foo(z); x := !x; end; rumur-2024.05.07/tests/pack-state-off.m000066400000000000000000000002771461637631000174260ustar00rootroot00000000000000-- rumur_flags: ['--pack-state', 'off', '--bound', '100'] -- basic test that using an unpacked state works var x: boolean; startstate begin x := true; end; rule begin x := !x; end; rumur-2024.05.07/tests/pipe-pipe-or.m000066400000000000000000000002031461637631000171150ustar00rootroot00000000000000-- test that Rumur extension || is accepted var x: boolean startstate begin x := true; end rule begin x := !(x || x); end rumur-2024.05.07/tests/procedure-call-in-expr.m000066400000000000000000000006541461637631000211020ustar00rootroot00000000000000-- rumur_exit_code: 1 /* This model incorrectly uses a call to a procedure within an expression, as if * the procedure returned a number. In commit * 366762cc01cfbb09193ec23ba9762260b458b016, this triggers a bug where Rumur * thinks this is a valid model and generates a verifier that does not compile. */ var x: 0 .. 10; procedure foo(); begin end; startstate begin x := 1; end; rule begin x := 10 - foo(); end; rumur-2024.05.07/tests/put-stmt.m000066400000000000000000000002421461637631000164070ustar00rootroot00000000000000-- Test put statements get parsed correctly. var x: boolean; startstate begin x := false; end; rule begin put "hello world"; put "\n"; x := !x; end; rumur-2024.05.07/tests/put-stmt2.m000066400000000000000000000004331461637631000164730ustar00rootroot00000000000000-- Test put statements get parsed correctly. var x: 0 .. 10; startstate begin x := 0; end; rule x > 0 ==> begin put "in first rule, x is "; put x; put "\n"; x := x - 1; end; rule x < 10 ==> begin put "in second rule, x is "; put x; put "\n"; x := x + 1; end; rumur-2024.05.07/tests/put-stmt3.m000066400000000000000000000002231461637631000164710ustar00rootroot00000000000000-- Test put statements get parsed correctly. var x: boolean; startstate begin put x; x := false; end; rule begin put x; x := !x; end; rumur-2024.05.07/tests/put-stmt4.m000066400000000000000000000033331461637631000164770ustar00rootroot00000000000000-- checker_output: None if xml else re.compile(r'z\[6\]:false') -- Test put statements get parsed correctly. The expected output is just chosen -- to match one of the array entries that is correlated with mis-implementing -- this feature. E.g. a first attempt at implementing iteration for array -- printing made the mistake of inaccurate indexing resulting in "z[6]:true". type e: enum { A, B }; var w: e; x: boolean; y: record a: boolean; b: boolean; end; z: array [-4.. 7] of boolean; -- a more complex type c: array[boolean] of record r: 0..1; s: array[3..4] of 0..3; end; startstate begin put "printing an uninitialised boolean...\n"; put x; put "\n"; x := false; put "printing an initialised boolean...\n"; put x; put "\n"; put "printing an uninitialised record...\n"; put y; put "\n"; y.a := true; put "printing a partially initialised record...\n"; put y; put "\n"; y.b := true; put "printing a fully initialised record...\n"; put y; put "\n"; put "printing an uninitialised array...\n"; put z; put "\n"; for i: -4 .. 1 do z[i] := true; end; put "printing a partially initialised array\n"; put z; put "\n"; for i: 2 .. 7 do z[i] := false; end; put "printing a fully initialised array\n"; put z; put "\n"; put "printing an uninitialised enum...\n"; put w; put "\n"; w := B; put "printing an initialised enum...\n"; put w; put "\n"; put "printing an uninitialised complex type...\n"; put c; put "\n"; c[false].r := 0; c[false].s[3] := 1; c[false].s[4] := 2; c[true].r := 1; c[true].s[3] := 2; c[true].s[4] := 3; put "printing an initialised complex type...\n"; put c; put "\n"; end; rule begin x := !x; end; rumur-2024.05.07/tests/put-string-injection.m000066400000000000000000000007751461637631000207210ustar00rootroot00000000000000/* This model is designed to trigger a problem first observed on * 0f66cb1de6e4eb359fc4aa1ab4d55c8dd951a04d wherein a crafted call to put could * result in the generation of code that would corrupt its own stack. If this * problem has been re-introduced, the generated code for this model will * trigger a compilation error when passing -Werror=format. */ var s: 0 .. 10; a: array[0 .. 10] of boolean; startstate begin a[0] := true; s := 1; end; rule begin put a[4%s]; a[0] := !a[0]; end; rumur-2024.05.07/tests/quantifier-signed-range.m000066400000000000000000000011721461637631000213250ustar00rootroot00000000000000-- rumur_flags: ['--deadlock-detection', 'off'] -- checker_output: None if xml else re.compile(r'\b3 states\b') /* This model tests that quantifiers that pass over zero (i.e. negative start * and positive end) function correctly. In * 99529844092fcbe1bbbfb3170c7b9a8364a6d055, a bug was observed that caused * loops generated from such quantifiers to iterate 0 times. This originated * from a change in 79579fd5ee7cc3c120439b5d3187a09ffd5dcd6e. This model checks * that such a problem has not been reintroduced. */ var x: -1 .. 1; startstate begin x := 0; end; ruleset y: -1 .. 1 do rule begin x := y; end; end; rumur-2024.05.07/tests/range-and.m000066400000000000000000000006701461637631000164530ustar00rootroot00000000000000-- While addressing the issue described in and-return.m, it was observed that -- ranges were not handled correctly with respect to disambiguation either. If -- this problem still exists (or has been reintroduced) this model will -- incorrectly fail code generation with an error like "cannot constant fold an -- unresolved '&' expression". type t: 0 .. 1 & 1; var x: t; startstate begin x := 1; end; rule begin x := 1 - x; end; rumur-2024.05.07/tests/read-undefined.m000066400000000000000000000002061461637631000174640ustar00rootroot00000000000000-- checker_exit_code: 1 -- Test reading of an undefined value. var x: boolean; startstate begin end; rule begin x := !x; end; rumur-2024.05.07/tests/read-undefined2.m000066400000000000000000000002461461637631000175520ustar00rootroot00000000000000-- checker_exit_code: 1 -- Test reading of an undefined record value. var x: record a: boolean; end; startstate begin end; rule begin x.a := !x.a; end; rumur-2024.05.07/tests/read-undefined3.m000066400000000000000000000002431461637631000175500ustar00rootroot00000000000000-- checker_exit_code: 1 -- Test reading of an undefined array value. var x: array[0 .. 1] of boolean; startstate begin end; rule begin x[0] := !x[0]; end; rumur-2024.05.07/tests/recursion1.m000066400000000000000000000005731461637631000167130ustar00rootroot00000000000000-- test a recursive function definition var x: 0 .. 55; function fib(n: 0 .. 10): 0 .. 100; begin if n = 0 then return 0; elsif n = 1 then return 1; else return fib(n - 1) + fib(n - 2); end; end; startstate begin x := 0; end; rule x <= 10 ==> begin x := fib(x); end; rule begin x := 2; end; rule begin x := 5; end; rule begin x := 10; end; rumur-2024.05.07/tests/recursion2.m000066400000000000000000000005261461637631000167120ustar00rootroot00000000000000-- test a recursive procedure definition var x: 0 .. 55; procedure set(n: 0 .. 10); begin if n = 0 then x := 0; elsif n = 1 then x := 1; else set(n - 2); end; end; startstate begin x := 0; end; rule x <= 10 ==> begin set(x); end; rule begin x := 2; end; rule begin x := 5; end; rule begin x := 10; end; rumur-2024.05.07/tests/recursion3.m000066400000000000000000000006171461637631000167140ustar00rootroot00000000000000-- rumur_exit_code: 1 -- test that mutual recursion is rejected var x: boolean; function is_even(n: 0 .. 100): boolean; begin if n = 0 then return true; else return is_odd(n - 1); end; end; function is_odd(n: 0 .. 100): boolean; begin if n = 0 then return false; else return is_even(n - 1); end; end; startstate begin x := true; end; rule begin x := !x; end; rumur-2024.05.07/tests/recursion4.m000066400000000000000000000014301461637631000167070ustar00rootroot00000000000000-- This model tests a case of recursion where the recursive function has a prior -- call to a non-recursive function. This is interesting because at time of -- writing (commit 084489251754d9ccd9a23d7b3fba0989d338eb80) the recursive call -- to bar will be resolved to an incomplete, unresolved definition of bar. I.e. -- a version of bar in which the call to foo still has an unresolved target. It -- is suspected that this might be an error where a subtle bug could be provoked -- in future, so this test was introduced to guard against that. var x: boolean; function foo(x: boolean): boolean; begin return !x; end; function bar(x: boolean): boolean; begin foo(x); if x then bar(!x); end; return !x; end; startstate begin x := true; end; rule begin x := bar(x); end; rumur-2024.05.07/tests/recursion5.m000066400000000000000000000005551461637631000167170ustar00rootroot00000000000000-- a variant on recursion4.m where the recursive function is used as a guard var x: boolean; function foo(x: boolean): boolean; begin return !x; end; function bar(x: boolean): boolean; begin foo(x); if x then bar(!x); end; return !x; end; startstate begin x := true; end; rule begin x := bar(x); end; rule bar(x) ==> begin x := !x; end; rumur-2024.05.07/tests/reference-function-parameter.m000066400000000000000000000002221461637631000223470ustar00rootroot00000000000000var x: boolean; function foo(y: boolean): boolean; begin return y; end; startstate begin x := true; end; rule begin x := !foo(x); end; rumur-2024.05.07/tests/reference-function-parameter2.m000066400000000000000000000002261461637631000224350ustar00rootroot00000000000000var x: boolean; function foo(var y: boolean): boolean; begin return y; end; startstate begin x := true; end; rule begin x := !foo(x); end; rumur-2024.05.07/tests/reference-function-parameter3.m000066400000000000000000000002331461637631000224340ustar00rootroot00000000000000var x: boolean; function foo(var y: boolean): boolean; begin y := !y; return y; end; startstate begin x := true; end; rule begin foo(x); end; rumur-2024.05.07/tests/regression-bad-assumption-check.m000066400000000000000000000007071461637631000227770ustar00rootroot00000000000000-- rumur_flags: ['--deadlock-detection', 'stuck'] /* There was a previous bug wherein assumption checks were done on the preceding * state of a rule, rather than on the final state. This model tests that we * have not re-introduced this bug. */ var x: boolean; startstate x := true; end; rule begin x := !x; end; rule begin x := x; end; -- This assumption should guarantee that the invariant below it never triggers. assume x invariant x rumur-2024.05.07/tests/return-expression-from-rule.m000066400000000000000000000003541461637631000222400ustar00rootroot00000000000000-- rumur_exit_code: 1 -- Testing return with an expression within a rule. This is illegal and should -- fail. var x: boolean; startstate begin x := true; end; rule begin x := true; return 3; x := false; end; invariant x; rumur-2024.05.07/tests/return-from-rule.m000066400000000000000000000003311461637631000200360ustar00rootroot00000000000000-- rumur_flags: ['--deadlock-detection', 'stuck'] -- Testing return statements within rules. var x: boolean; startstate begin x := true; end; rule begin x := true; return; x := false; end; invariant x; rumur-2024.05.07/tests/return-from-ruleset.m000066400000000000000000000004161461637631000205560ustar00rootroot00000000000000-- rumur_flags: ['--deadlock-detection', 'stuck'] -- Testing return statements within rules within rulesets. var x: boolean; startstate begin x := true; end; ruleset y: boolean do rule begin x := true; return; x := false; end; end; invariant x; rumur-2024.05.07/tests/return-from-startstate.m000066400000000000000000000003371461637631000212730ustar00rootroot00000000000000-- rumur_flags: ['--deadlock-detection', 'stuck'] -- Testing return statements within startstates. var x: boolean; startstate begin x := true; return; x := false; end; rule begin x := true; end; invariant x; rumur-2024.05.07/tests/rsh-and.m000066400000000000000000000007061461637631000161530ustar00rootroot00000000000000-- While addressing the issue described in and-return.m, it was discovered that -- this problem also affected right shifts. If the problem still exists (or has -- been reintroduced) for right shifts, this model will fail code generation -- with an error like "cannot retrieve the type of an unresolved '&' expression" var x: 0 .. 2; startstate begin x := 1; end; rule x = 1 ==> begin x := 4 >> (x & 1); end; rule x = 2 ==> begin x := 1; end; rumur-2024.05.07/tests/rsh-basic.m000066400000000000000000000017051461637631000164720ustar00rootroot00000000000000-- basic test of right shift operator (Rumur extension) var x: -10 .. 10 y: 0 .. 4 z: boolean startstate begin z := true; end rule begin x := 0; -- right shift of 0 should still yield 0 x := x >> 0; assert x = 0 ">> 0 yielded wrong value"; x := x >> 1; assert x = 0 ">> 1 yielded wrong value"; y := 2; x := x >> y; assert x = 0 ">> 2 via variable yielded wrong value"; x := x >> -1; assert x = 0 ">> -1 yielded wrong value"; x := x >> -4; assert x = 0 ">> -4 yielded wrong value"; -- some general shift cases x := 1 >> 1; assert x = 0; x := 2 >> 1; assert x = 1; x := 4 >> 2; assert x = 1; -- overshifts should always produce 0 x := 0 >> 128; assert x = 0; x := 1 >> 128; assert x = 0; x := 1 >> -128; assert x = 0; x := 2 >> 128; assert x = 0; x := y >> 128; assert x = 0; -- some negative shift cases x := -1 >> 1; assert x = -1; x := -3 >> 1; assert x = -2; z := !z; end rumur-2024.05.07/tests/rsh-boolean.m000066400000000000000000000002471461637631000170300ustar00rootroot00000000000000-- rumur_exit_code: 1 -- test that right shifting a boolean is rejected var x: 0 .. 10 y: boolean startstate begin x := 0; end rule begin x := y >> 1; end rumur-2024.05.07/tests/rsh-boolean2.m000066400000000000000000000002671461637631000171140ustar00rootroot00000000000000-- rumur_exit_code: 1 -- test that right shifting by a boolean is rejected var x: 0 .. 10 y: boolean startstate begin x := 0; y := true; end rule begin x := 1 >> y; end rumur-2024.05.07/tests/rsh-enum.m000066400000000000000000000002531461637631000163520ustar00rootroot00000000000000-- rumur_exit_code: 1 -- test that right shifting an enum is rejected type t: enum { A, B } var x: t startstate begin x := A; end rule begin x := a >> 1; end rumur-2024.05.07/tests/rsh-enum2.m000066400000000000000000000002641461637631000164360ustar00rootroot00000000000000-- rumur_exit_code: 1 -- test that right shifting by an enum is rejected type t: enum { A, B } var x: 0 .. 10 startstate begin x := 0; end rule begin x := 2 >> A; end rumur-2024.05.07/tests/rule-duplicate-name.m000066400000000000000000000004261461637631000204530ustar00rootroot00000000000000-- Test that duplicate rule names are accepted. Note that we define the two -- rules such that omitting either will cause a deadlock during checking. var x: boolean; startstate begin x := true; end; rule "foo" begin x := true; end; rule "foo" begin x := false; end; rumur-2024.05.07/tests/ruleset-invariant.m000066400000000000000000000004131461637631000202660ustar00rootroot00000000000000-- A model involving an invariant nested in a ruleset. This is relatively rare -- construct, but is compliant with the grammar. var x: boolean; startstate begin x := true; end; rule begin x := !x; end; ruleset y: boolean do invariant x = y | x = !y; end; rumur-2024.05.07/tests/ruleset-startstate.m000066400000000000000000000001601461637631000204700ustar00rootroot00000000000000var x: boolean; ruleset y: boolean do startstate begin x := y; end; end; rule begin x := !x; end; rumur-2024.05.07/tests/ruleset-trace.m000066400000000000000000000013601461637631000173730ustar00rootroot00000000000000-- checker_exit_code: 1 -- checker_output: None if xml else re.compile(r'^Rule\s+1\s*,\s*y\s*:\s*1\s*,\s*z\s*:\s*8\s*,\s*w\s*:\s*-32\s+fired\b', re.MULTILINE) /* As of v2019.01.12, Rumur printed counterexample traces involving rulesets * without showing the values of the parameters to the ruleset rules. This made * debugging such traces virtually impossible. This tests that we correctly * print a trace showing the values of such parameters. * * Github: issue #105 "missing parameters in ruleset trace" */ type t1: 0 .. 1; t2: 4 .. 10; t3: -44 .. -2 var x: boolean; startstate begin x := true; end; ruleset y: t1; z: t2; w: t3 do rule begin assert y != 1 | z != 8 | w != -32 "failed assertion"; x := !x; end; end; rumur-2024.05.07/tests/ruleset-trace2.m000066400000000000000000000015141461637631000174560ustar00rootroot00000000000000-- checker_exit_code: 1 -- checker_output: None if xml else re.compile(r'^Rule\s+2\s*,\s*y\s*:\s*1\s*,\s*z\s*:\s*8\s*,\s*w\s*:\s*-32\s+fired\b', re.MULTILINE) /* Similar to ruleset-trace.m, but with a benign first ruleset that ensures the * ruleset we're expecting to appear in the trace does not end up being index 1. * This is relevant with respect to how Rumur works internally, numbering rule * transitions via the rule_taken variable. */ type t1: 0 .. 1; t2: 4 .. 10; t3: -44 .. -2 var x: boolean; startstate begin x := true; end; -- an innocuous rule to make sure our problematic ruleset below will not get a -- trivial index ruleset y: t1; z: t2 do rule begin x := !x; end; end; ruleset y: t1; z: t2; w: t3 do rule begin assert y != 1 | z != 8 | w != -32 "failed assertion"; x := !x; end; end; rumur-2024.05.07/tests/ruleset-trace3.m000066400000000000000000000010131461637631000174510ustar00rootroot00000000000000-- checker_exit_code: 1 -- checker_output: None if xml else re.compile(r'^Startstate\s+1\s*,\s*y\s*:\s*1\s*,\s*z\s*:\s*8\s*,\s*w\s*:\s*-32\s+fired\b', re.MULTILINE) /* This is a similar test to ruleset-trace.m but using a ruleset-contained * startstate, instead of an ordinary rule. */ type t1: 0 .. 1; t2: 4 .. 10; t3: -44 .. -2 var x: boolean; ruleset y: t1; z: t2; w: t3 do startstate begin assert y != 1 | z != 8 | w != -32 "failed assertion"; x := true; end; end; rule begin x := !x; end; rumur-2024.05.07/tests/rumur-run-model.py000077500000000000000000000007461461637631000200740ustar00rootroot00000000000000#!/usr/bin/env python3 """ test that rumur-run can check a basic model """ import os import subprocess as sp import sys RUMUR_RUN = os.path.join(os.path.dirname(__file__), "../rumur/src/rumur-run") MODEL = """ var x: boolean; startstate begin x := true; end; rule begin x := !x; end; """ def main(): p = sp.Popen(["python3", RUMUR_RUN], stdin=sp.PIPE) p.communicate(MODEL.encode("utf-8", "replace")) return p.returncode if __name__ == "__main__": sys.exit(main()) rumur-2024.05.07/tests/rumur-run-version.py000077500000000000000000000005031461637631000204500ustar00rootroot00000000000000#!/usr/bin/env python3 """ basic test that rumur-run can execute successfully """ import os import subprocess import sys RUMUR_RUN = os.path.join(os.path.dirname(__file__), "../rumur/src/rumur-run") def main(): subprocess.check_call(["python3", RUMUR_RUN, "--version"]) if __name__ == "__main__": sys.exit(main()) rumur-2024.05.07/tests/run-tests.py000077500000000000000000000447171461637631000167740ustar00rootroot00000000000000#!/usr/bin/env python3 """ integration test suite Despite using Python’s unittest, this is not a set of unit tests. The Python module is simply a nice low-overhead testing framework. """ import codecs import multiprocessing import os from pathlib import Path import re import subprocess as sp import sys import tempfile import unittest CPUS = multiprocessing.cpu_count() VERIFIER_RNG = Path(__file__).resolve().parent / "../misc/verifier.rng" MURPHI2XML_RNG = Path(__file__).resolve().parent / "../misc/murphi2xml.rng" # test configuration variables, set during main CONFIG = {} def enc(s): return s.encode("utf-8", "replace") def dec(s): return s.decode("utf-8", "replace") def run(args, stdin = None): """ run a command and return its result """ if stdin is not None: stdin = enc(stdin) env = {k: v for k, v in os.environ.items()} env.update({k: str(v) for k, v in CONFIG.items()}) p = sp.Popen([str(a) for a in args], stdout=sp.PIPE, stderr=sp.PIPE, stdin=sp.PIPE, env=env) stdout, stderr = p.communicate(stdin) return p.returncode, dec(stdout), dec(stderr) def parse_test_options(src, debug = False, multithreaded = False, xml = False): """ extract test tweaks and directives from leading comments in a test input """ with open(str(src), "rt", encoding="utf-8") as f: for line in f: # recognise “-- rumur_flags: …” etc lines m = re.match(r"\s*--\s*(?P[a-zA-Z_]\w*)\s*:(?P.*)$", line) if m is None: break yield m.group("key"), eval(m.group("value").strip()) class executable(unittest.TestCase): """ test cases involving running a custom executable file """ def _run(self, testcase): assert os.access(str(testcase), os.X_OK), "non-executable test case " \ "{} attached to executable class".format(testcase) ret, stdout, stderr = run([testcase]) output = "{}{}".format(stdout, stderr) if ret == 125: self.skipTest(output.strip()) elif ret != 0: self.fail(output) class murphi2c(unittest.TestCase): """ test cases for murphi2c """ def _run(self, testcase): tweaks = {k: v for k, v in parse_test_options(testcase)} # there is no C equivalent of isundefined, because an implicit assumption in # the C representation is that you do not rely on undefined values with open(str(testcase), "rt", encoding="utf-8") as f: should_fail = re.search(r"\bisundefined\b", f.read()) is not None args = ["murphi2c", testcase] if CONFIG["HAS_VALGRIND"]: args = ["valgrind", "--leak-check=full", "--show-leak-kinds=all", "--error-exitcode=42"] + args ret, stdout, stderr = run(args) if CONFIG["HAS_VALGRIND"]: if ret == 42: self.fail("Memory leak:\n{}{}".format(stdout, stderr)) # if rumur was expected to reject this model, we allow murphi2c to fail if tweaks.get("rumur_exit_code", 0) == 0 and not should_fail and ret != 0: self.fail("Unexpected murphi2c exit status {}:\n{}{}" .format(ret, stdout, stderr)) if should_fail and ret == 0: self.fail("Unexpected murphi2c exit status {}:\n{}{}" .format(ret, stdout, stderr)) if ret != 0: return # ask the C compiler if this is valid args = [CONFIG["CC"]] + CONFIG["C_FLAGS"] + ["-c", "-o", os.devnull, "-"] ret, out, err = run(args, stdout) if ret != 0: self.fail("C compilation failed:\n{}{}\nProgram:\n{}" .format(out, err, stdout)) class murphi2cHeader(unittest.TestCase): """ test cases for murphi2c --header """ def _run(self, testcase): tweaks = {k: v for k, v in parse_test_options(testcase)} # there is no C equivalent of isundefined, because an implicit assumption in # the C representation is that you do not rely on undefined values with open(str(testcase), "rt", encoding="utf-8") as f: should_fail = re.search(r"\bisundefined\b", f.read()) is not None args = ["murphi2c", "--header", testcase] if CONFIG["HAS_VALGRIND"]: args = ["valgrind", "--leak-check=full", "--show-leak-kinds=all", "--error-exitcode=42"] + args ret, stdout, stderr = run(args) if CONFIG["HAS_VALGRIND"]: if ret == 42: self.fail("Memory leak:\n{}{}".format(stdout, stderr)) # if rumur was expected to reject this model, we allow murphi2c to fail if tweaks.get("rumur_exit_code", 0) == 0 and not should_fail and ret != 0: self.fail("Unexpected murphi2c exit status {}:\n{}{}" .format(ret, stdout, stderr)) if should_fail and ret == 0: self.fail("Unexpected murphi2c exit status {}:\n{}{}" .format(ret, stdout, stderr)) if ret != 0: return with tempfile.TemporaryDirectory() as tmp: # write the header to a temporary file header = Path(tmp) / "header.h" with open(str(header), "wt", encoding="utf-8") as f: f.write(stdout) # ask the C compiler if the header is valid main_c = '#include "{}"\nint main(void) {{ return 0; }}\n'.format(header) args = [CONFIG["CC"]] + CONFIG["C_FLAGS"] + ["-o", os.devnull, "-"] ret, stdout, stderr = run(args, main_c) if ret != 0: self.fail("C compilation failed:\n{}{}".format(stdout, stderr)) # ask the C++ compiler if it is valid there too ret, stdout, stderr = run([CONFIG["CXX"], "-std=c++11", "-o", os.devnull, "-x", "c++", "-", "-Werror=format", "-Werror=sign-compare", "-Werror=type-limits"], main_c) if ret != 0: self.fail("C++ compilation failed:\n{}{}".format(stdout, stderr)) class murphi2uclid(unittest.TestCase): """ test cases for murphi2uclid """ def _run(self, testcase): tweaks = {k: v for k, v in parse_test_options(testcase)} # test cases for which murphi2uclid is expected to fail MURPHI2UCLID_FAIL = ( # contains `<<` or `>>` "lsh-basic.m", "rsh-and.m", "rsh-basic.m", "smt-bv-lsh.m", "smt-bv-rsh.m", # contains `/` "division.m", "smt-bv-div.m", "smt-bv-div2.m", "smt-div.m", "unicode-div.m", "unicode-div2.m", # contains `%` "put-string-injection.m", "smt-bv-mod.m", "smt-bv-mod2.m", "smt-mod.m", # contains alias statements "alias-and-field.m", "alias-in-bound.m", "alias-in-bound2.m", "alias-literal.m", "alias-of-alias-rule.m", "alias-of-alias-rule2.m", "alias-of-alias-stmt.m", "basic-aliasrule.m", "mixed-aliases.m", # `clear` of a complex type "clear-complex.m", # contains `cover` "cover-basic.m", "cover-basic2.m", "cover-miss.m", "cover-multiple.m", "cover-stmt.m", "cover-stmt-miss.m", "cover-trivial.m", "string-injection.m", # contains `isundefined` "diff-trace-arrays.m", "isundefined-basic.m", "isundefined-decl.m", "isundefined-element.m", "isundefined-function.m", "for-variants.m", "scalarset-cex.m", "scalarset-schedules-off.m", "scalarset-schedules-off-2.m", # contains `put` "for-step-0-dynamic.m", "put-stmt.m", "put-stmt2.m", "put-stmt3.m", "put-stmt4.m", "scalarset-put.m", # contains early return from a function/procedure/rule "return-from-rule.m", "return-from-ruleset.m", "return-from-startstate.m", # `exists` or `forall` with non-1 step "smt-bv-exists4.m", "smt-bv-forall4.m", "smt-exists4.m", "smt-forall4.m", # `liveness` inside a `ruleset` "liveness-in-ruleset.m", "liveness-in-ruleset2.m", ) # test cases fo which Uclid5 is expected to fail UCLID_FAIL = ( # contains a record field with the same name as a variable # https://github.com/uclid-org/uclid/issues/99 "compare-record.m", "smt-array-of-record.m", "smt-record-bool-field.m", "smt-record-bool-field2.m", "smt-record-enum-field.m", "smt-record-enum-field2.m", "smt-record-of-array.m", "smt-record-range-field.m", "smt-record-range-field2.m", # recursive function calls "recursion2.m", "recursion4.m", # reference to a field of an array element "193.m", # function calls within expressions "differing-type-return.m", "differing-type-return3.m", "function-call-in-if.m", "function-in-guard.m", "multiple-parameters.m", "multiple-parameters2.m", "non-const-parameters.m", "recursion1.m", "recursion5.m", "reference-function-parameter.m", "reference-function-parameter2.m", "section-order4.m", "section-order5.m", "section-order10.m", "type-shadowing2.m", # modifies a mutable parameter within a function, which is not valid # within a Uclid5 procedure "reference-function-parameter3.m", ) args = ["murphi2uclid", testcase] if CONFIG["HAS_VALGRIND"]: args = ["valgrind", "--leak-check=full", "--show-leak-kinds=all", "--error-exitcode=42"] + args ret, stdout, stderr = run(args) if CONFIG["HAS_VALGRIND"]: if ret == 42: self.fail("Memory leak:\n{}{}".format(stdout, stderr)) # if rumur was expected to reject this model, we allow murphi2uclid to fail should_fail = testcase.name in MURPHI2UCLID_FAIL could_fail = tweaks.get("rumur_exit_code", 0) != 0 or should_fail if not could_fail and ret != 0: self.fail("Unexpected murphi2uclid exit status {}:\n{}{}" .format(ret, stdout, stderr)) if should_fail and ret == 0: self.fail("Unexpected murphi2uclid exit status {}:\n{}{}" .format(ret, stdout, stderr)) if ret != 0: return # if we do not have Uclid5 available, skip the remainder of the test if not CONFIG["HAS_UCLID"]: self.skipTest("uclid not available for validation") with tempfile.TemporaryDirectory() as tmp: # write the Uclid5 source to a temporary file src = Path(tmp) / "source.ucl" with open(str(src), "wt", encoding="utf-8") as f: f.write(stdout) # ask Uclid if the source is valid ret, stdout, stderr = run(["uclid", src]) if testcase.name in UCLID_FAIL and ret == 0: self.fail("uclid unexpectedly succeeded:\n{}{}".format(stdout, stderr)) if testcase.name not in UCLID_FAIL and ret != 0: self.fail("uclid failed:\n{}{}".format(stdout, stderr)) class murphi2xml(unittest.TestCase): """ test cases for murphi2xml """ def _run(self, testcase): tweaks = {k: v for k, v in parse_test_options(testcase)} args = ["murphi2xml", testcase] if CONFIG["HAS_VALGRIND"]: args = ["valgrind", "--leak-check=full", "--show-leak-kinds=all", "--error-exitcode=42"] + args ret, stdout, stderr = run(args) if CONFIG["HAS_VALGRIND"]: if ret == 42: self.fail("Memory leak:\n{}{}".format(stdout, stderr)) # if rumur was expected to reject this model, we allow murphi2xml to fail if tweaks.get("rumur_exit_code", 0) == 0 and ret != 0: self.fail("Unexpected murphi2xml exit status {}:\n{}{}" .format(ret, stdout, stderr)) if ret != 0: return # murphi2xml will have written XML to its stdout xmlcontent = stdout # See if we have xmllint if not CONFIG["HAS_XMLLINT"]: self.skipTest("xmllint not available for validation") # Validate the XML ret, stdout, stderr = run(["xmllint", "--relaxng", MURPHI2XML_RNG, "--noout", "-"], xmlcontent) if ret != 0: self.fail("Failed to validate:\n{}{}".format(stdout, stderr)) class rumur(unittest.TestCase): """ test cases involving generating a checker and running it """ def _run_param(self, testcase, debug, optimised, multithreaded, xml): tweaks = {k: v for k, v in parse_test_options(testcase, debug, multithreaded, xml)} if tweaks.get("skip_reason") is not None: self.skipTest(tweaks["skip_reason"]) # build up arguments to call rumur args = ["rumur", "--output", "/dev/stdout", testcase] if debug: args += ["--debug"] if xml: args += ["--output-format", "machine-readable"] if multithreaded and CPUS == 1: args +=["--threads", "2"] elif not multithreaded: args += ["--threads", "1"] args += tweaks.get("rumur_flags", []) if CONFIG["HAS_VALGRIND"]: args = ["valgrind", "--leak-check=full", "--show-leak-kinds=all", "--error-exitcode=42"] + args # call rumur ret, stdout, stderr = run(args) if CONFIG["HAS_VALGRIND"]: if ret == 42: self.fail("Memory leak:\n{}{}".format(stdout, stderr)) if ret != tweaks.get("rumur_exit_code", 0): self.fail("Rumur failed:\n{}{}".format(stdout, stderr)) # if we expected to fail, we are done if ret != 0: return model_c = stdout with tempfile.TemporaryDirectory() as tmp: # build up arguments to call the C compiler model_bin = Path(tmp) / "model.exe" args = [CONFIG["CC"]] + CONFIG["C_FLAGS"] if optimised: args += ["-O3"] args += ["-o", model_bin, "-", "-lpthread"] if CONFIG["NEEDS_LIBATOMIC"]: args += ["-latomic"] # call the C compiler ret, stdout, stderr = run(args, model_c) if ret != 0: self.fail("C compilation failed:\n{}{}".format(stdout, stderr)) # now run the model itself ret, stdout, stderr = run([model_bin]) if ret != tweaks.get("checker_exit_code", 0): self.fail("Unexpected checker exit status {}:\n{}{}" .format(ret, stdout, stderr)) # if the test has a stdout expectation, check that now if tweaks.get("checker_output") is not None: if tweaks["checker_output"].search(stdout) is None: self.fail("Checker output did not match expectation regex:\n{}{}" .format(stdout, stderr)) # coarse grained check for whether the model contains a `put` statement that # could screw up XML validation with open(str(testcase), "rt", encoding="utf-8") as f: has_put = re.search(r"\bput\b", f.read()) is not None if xml and not has_put: model_xml = stdout if not CONFIG["HAS_XMLLINT"]: self.skipTest("xmllint not available") # validate the XML args = ["xmllint", "--relaxng", VERIFIER_RNG, "--noout", "-"] ret, stdout, stderr = run(args, model_xml) if ret != 0: self.fail("Failed to XML-validate machine reachable output:\n{}{}" .format(stdout, stderr)) class rumurSingleThreaded(rumur): def _run(self, testcase): self._run_param(testcase, False, False, False, False) class rumurDebugSingleThreaded(rumur): def _run(self, testcase): self._run_param(testcase, True, False, False, False) class rumurOptimisedSingleThreaded(rumur): def _run(self, testcase): self._run_param(testcase, False, True, False, False) class rumurDebugOptimisedSingleThreaded(rumur): def _run(self, testcase): self._run_param(testcase, True, True, False, False) class rumurMultithreaded(rumur): def _run(self, testcase): self._run_param(testcase, False, False, True, False) class rumurDebugMultithreaded(rumur): def _run(self, testcase): self._run_param(testcase, True, False, True, False) class rumurOptimisedMultithreaded(rumur): def _run(self, testcase): self._run_param(testcase, False, True, True, False) class rumurDebugOptimisedMultithreaded(rumur): def _run(self, testcase): self._run_param(testcase, True, True, True, False) class rumurSingleThreadedXML(rumur): def _run(self, testcase): self._run_param(testcase, False, False, False, True) class rumurOptimisedSingleThreadedXML(rumur): def _run(self, testcase): self._run_param(testcase, False, True, False, True) class rumurMultithreadedXML(rumur): def _run(self, testcase): self._run_param(testcase, False, False, True, True) class rumurOptimisedMultithreadedXML(rumur): def _run(self, testcase): self._run_param(testcase, False, True, True, True) def make_name(t): """ name mangle a path into a valid test case name """ safe_name = re.sub(r"[^a-zA-Z0-9]", "_", t.name) return "test_{}".format(safe_name) def main(): # setup stdout to make encoding errors non-fatal sys.stdout = codecs.getwriter("utf-8")(sys.stdout.buffer, "replace") # parse configuration global CONFIG for p in sorted((Path(__file__).parent / "config").iterdir()): # skip subdirectories if p.is_dir(): continue # skip non-executable files if not os.access(str(p), os.X_OK): continue CONFIG[p.name] = eval(sp.check_output([str(p)])) sys.stderr.write(f"configuration variable {p.name} = {CONFIG[p.name]}\n") # find files in our directory root = Path(__file__).parent for p in sorted(root.iterdir()): # skip directories if p.is_dir(): continue # skip ourselves if os.path.samefile(str(p), __file__): continue name = make_name(p) # if this is executable, treat it as a test case if os.access(str(p), os.X_OK): assert not hasattr(executable, name), \ "name collision involving executable.{}".format(name) setattr(executable, name, lambda self, p=p: self._run(p)) # if this is not a model, skip the remaining generic logic if p.suffix != ".m": continue for c in (rumurSingleThreaded, rumurDebugSingleThreaded, rumurOptimisedSingleThreaded, rumurDebugOptimisedSingleThreaded, rumurMultithreaded, rumurDebugMultithreaded, rumurOptimisedMultithreaded, rumurDebugOptimisedMultithreaded, rumurSingleThreadedXML, rumurOptimisedSingleThreadedXML, rumurMultithreadedXML, rumurOptimisedMultithreadedXML, ): assert not hasattr(c, name), \ "name collision involving rumur.{}".format(name) setattr(c, name, lambda self, p=p: self._run(p)) assert not hasattr(murphi2c, name), \ "name collision involving murphi2c.{}".format(name) setattr(murphi2c, name, lambda self, p=p: self._run(p)) assert not hasattr(murphi2cHeader, name), \ "name collision involving murphi2cHeader.{}".format(name) setattr(murphi2cHeader, name, lambda self, p=p: self._run(p)) assert not hasattr(murphi2uclid, name), \ "name collision involving murphi2uclid.{}".format(name) setattr(murphi2uclid, name, lambda self, p=p: self._run(p)) assert not hasattr(murphi2xml, name), \ "name collision involving murphi2xml.{}".format(name) setattr(murphi2xml, name, lambda self, p=p: self._run(p)) unittest.main() if __name__ == "__main__": main() rumur-2024.05.07/tests/scalarset-cex.m000066400000000000000000000010501461637631000173460ustar00rootroot00000000000000-- checker_exit_code: 1 -- checker_output: None if xml or multithreaded else re.compile(r'\ba\b.*?\bz:\s*t_6\b(.|\n)*?\bx\[t_6]:\s*true\b(.|\n)*?\bc\b.*?\bz:\s*t_6\b(.|\n)*?\by:\s*t_6', re.MULTILINE) type t: scalarset(10); var x: array[t] of boolean; y: t; startstate begin for z: t do x[z] := false; end; end; ruleset z: t do rule "a" begin x[z] := true; end; rule "b" begin x[z] := false; end; rule "c" begin y := z; end; end; invariant forall z: t do isundefined(y) | isundefined(x[z]) | y != z | !x[z] end; rumur-2024.05.07/tests/scalarset-put.m000066400000000000000000000014141461637631000174030ustar00rootroot00000000000000-- checker_output: None if xml else re.compile(r'^y:\s*Undefined$(.|\n)*?^y:\s*t_9$', re.MULTILINE) -- Test that we can print a scalarset variable. The initial implementation of -- scalarset remapping caused code to be emitted in an order that had put -- statements calling a function that had not yet been defined. This caused a -- compilation failure of the generated verifier. If this problem has been -- re-introduced, the code generated from this model will fail to compile. type t: scalarset(10); var x: boolean; y: t; startstate begin -- print it while it is undefined put y; -- assign it a value for z: t do y := z; end; -- print it now that it has a value put y; x := true; end; rule begin -- print it during a rule put y; x := !x; end; rumur-2024.05.07/tests/scalarset-schedules-off-2.m000066400000000000000000000012151461637631000214600ustar00rootroot00000000000000-- checker_exit_code: 1 -- rumur_flags: ['--scalarset-schedules', 'off'] -- checker_output: None if xml or multithreaded else re.compile(r'^((?!t_)(.|\n))*$') -- A variant of scalarset-schedules-off.m that checks startstate parameters are -- also not printed symbolically. type t: scalarset(10); var x: array[t] of boolean; y: t; ruleset w: t do startstate begin for z: t do x[z] := false; end; end; end; ruleset z: t do rule "a" begin x[z] := true; end; rule "b" begin x[z] := false; end; rule "c" begin y := z; end; end; invariant forall z: t do isundefined(y) | isundefined(x[z]) | y != z | !x[z] end; rumur-2024.05.07/tests/scalarset-schedules-off.m000066400000000000000000000013731461637631000213260ustar00rootroot00000000000000-- checker_exit_code: 1 -- rumur_flags: ['--scalarset-schedules', 'off'] -- checker_output: None if xml or multithreaded else re.compile(r'^((?!t_)(.|\n))*$') -- A variant of scalarset-cex.m to check that switching off scalarset schedules -- still produces a verifier that compiles and runs. The regex we use above -- demands that the symbolic value of a scalarset, e.g. "t_3", never appears in -- the output. type t: scalarset(10); var x: array[t] of boolean; y: t; startstate begin for z: t do x[z] := false; end; end; ruleset z: t do rule "a" begin x[z] := true; end; rule "b" begin x[z] := false; end; rule "c" begin y := z; end; end; invariant forall z: t do isundefined(y) | isundefined(x[z]) | y != z | !x[z] end; rumur-2024.05.07/tests/scalarset-trivial.m000066400000000000000000000004351461637631000202470ustar00rootroot00000000000000-- rumur_flags: ['--deadlock-detection', 'stuck'] -- There's no utility to the scalarset in this model. It's purely to validate -- that a scalarset can exist. type t: scalarset(2); var x: t; ruleset i: t do startstate begin x := i; end; end; rule begin x := x; end; rumur-2024.05.07/tests/scalarset-undefined.m000066400000000000000000000011671461637631000205410ustar00rootroot00000000000000-- rumur_flags: ['--counterexample-trace', 'off'] -- checker_exit_code: 1 -- This tests deliberately reading an undefined value from a scalarset-indexed -- array. While this should pretty obviously fail, when an error message is -- printed about the failure it needs to describe the failing scenario. If we -- have implemented this incorrectly, it may try to access the 'schedule' (which -- permutation of a scalarset is in use) which is unavailable when -- counterexample traces are off. type t: scalarset(10); var y: array[t] of boolean; startstate begin end; ruleset w: t do rule begin y[w] := !y[w]; end; end; rumur-2024.05.07/tests/section-order.m000066400000000000000000000003541461637631000173730ustar00rootroot00000000000000-- test that defining a function prior to variables/constants/types (Rumur -- extension) is accepted function foo(): boolean; begin return true; end; var x: boolean; startstate begin x := true; end; rule begin x := !x; end; rumur-2024.05.07/tests/section-order10.m000066400000000000000000000016201461637631000175310ustar00rootroot00000000000000-- The following tests an edge case where function parameters do *not* shadow -- global symbols, but *do* shadow global symbols if sections are reordered. -- I.e. if all variable and constant definitions were hoisted to the top of the -- file. When implementing the patch series to lift the restriction on section -- ordering, it was observed that the optimisation that reorders record fields -- for faster access (--reorder-fields ...) caused variable definitions to be -- hoisted in this way. This was previously no problem, but now may cause some -- surprises like this and needs careful handling. var x: boolean; function foo(y: boolean): boolean; begin assert y = true | y = false; return y; end; const y: 5; function bar(z: boolean): boolean; begin assert z = true | z = false; return z; end; var z: 0 .. 10; startstate begin x := true; z := 8; end; rule begin x := !bar(foo(x)); end; rumur-2024.05.07/tests/section-order2.m000066400000000000000000000003221461637631000174500ustar00rootroot00000000000000-- test that defining a function after rules (Rumur extension) is accepted var x: boolean; startstate begin x := true; end; function foo(): boolean; begin return true; end; rule begin x := !x; end; rumur-2024.05.07/tests/section-order3.m000066400000000000000000000002471461637631000174570ustar00rootroot00000000000000-- test that defining a const after rules (Rumur extension) is allowed var x: boolean; startstate begin x := true; end; const N: 3; rule begin x := !x; end; rumur-2024.05.07/tests/section-order4.m000066400000000000000000000003101461637631000174470ustar00rootroot00000000000000-- similar to section-order2.m, but the function is used var x: boolean; startstate begin x := true; end; function foo(): boolean; begin return true; end; rule begin x := foo() & !x; end; rumur-2024.05.07/tests/section-order5.m000066400000000000000000000003251461637631000174560ustar00rootroot00000000000000-- similar to section-order4.m, but the function references a state variable var x: boolean; startstate begin x := true; end; function foo(): boolean; begin return x; end; rule begin x := !foo(); end; rumur-2024.05.07/tests/section-order6.m000066400000000000000000000003441461637631000174600ustar00rootroot00000000000000-- rumur_exit_code: 1 -- similar to section-order2.m, but the function is called illegally var x: boolean; startstate begin x := foo(); end; function foo(): boolean; begin return true; end; rule begin x := !x; end; rumur-2024.05.07/tests/section-order7.m000066400000000000000000000003261461637631000174610ustar00rootroot00000000000000-- rumur_exit_code: 1 -- test that an illegal variable reference out of section order is rejected var x: boolean; startstate begin x := true; y := true; end; var y: boolean; rule begin x := !x; end; rumur-2024.05.07/tests/section-order8.m000066400000000000000000000003641461637631000174640ustar00rootroot00000000000000-- rumur_exit_code: 1 -- test that an illegaly out of section order reference within a function is -- rejected function foo(): boolean; begin return x; end; var x: boolean; startstate begin x := true; end; rule begin x := !x; end; rumur-2024.05.07/tests/section-order9.m000066400000000000000000000003721461637631000174640ustar00rootroot00000000000000-- rumur_exit_code: 1 -- test that functions within rulesets are rejected var x: boolean; startstate begin x := true; end; ruleset y: boolean do function foo(): boolean; begin return true; end; rule begin x := y; end; end; rumur-2024.05.07/tests/simple-deadlock.m000066400000000000000000000001641461637631000176520ustar00rootroot00000000000000-- checker_exit_code: 1 var x: boolean; startstate begin x := true; end; rule x ==> begin x := false; end; rumur-2024.05.07/tests/smart-quotes.m000066400000000000000000000006361461637631000172650ustar00rootroot00000000000000-- checker_exit_code: 1 -- checker_output: None if xml else re.compile(r'^Rule "foo" fired', re.MULTILINE) -- Test that smart quotes can be used to delimit a string. This test case works -- by deliberately triggering a model error that should cause a backtrace -- containing a correctly parsed version of the rule name. var x: boolean startstate begin x := true; end rule “foo” begin x := false; end rumur-2024.05.07/tests/smt-add.m000066400000000000000000000006371461637631000161530ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None -- test that the SMT bridge can cope with addition var x: 0 .. 1; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if x + 1 = 1 | x + 1 = 2 then y := !y; end; end; rumur-2024.05.07/tests/smt-array-bool-index.m000066400000000000000000000010551461637631000205720ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None /* test whether the SMT bridge can simplify expressions involving arrays with * boolean indices */ type t: array[boolean] of 0 .. 1; var x: t; y: boolean; startstate begin y := true; end; rule begin /* if the SMT bridge is working correctly, it will simplify the condition in * the following expression to `true` avoiding the read of an undefined * variable */ if x[true] = x[true] then y := !y; end; end; rumur-2024.05.07/tests/smt-array-bool-value-and-index.m000066400000000000000000000010661461637631000224460ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None /* test whether the SMT bridge can simplify expressions involving arrays with * bool values and indices */ type t: array[boolean] of boolean; var x: t; y: boolean; startstate begin y := true; end; rule begin /* if the SMT bridge is working correctly, it will simplify the condition in * the following expression to `true` avoiding the read of an undefined * variable */ if x[true] = x[true] then y := !y; end; end; rumur-2024.05.07/tests/smt-array-bool-value.m000066400000000000000000000010461461637631000205770ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None /* test whether the SMT bridge can simplify expressions involving arrays with * boolean values */ type t: array[0 .. 1] of boolean; var x: t; y: boolean; startstate begin y := true; end; rule begin /* if the SMT bridge is working correctly, it will simplify the condition in * the following expression to `true` avoiding the read of an undefined * variable */ if x[0] = x[0] then y := !y; end; end; rumur-2024.05.07/tests/smt-array-enum-index.m000066400000000000000000000010521461637631000206000ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None /* test whether the SMT bridge can simplify expressions involving arrays with * enum indices */ type t: array[enum { A, B }] of 0 .. 1; var x: t; y: boolean; startstate begin y := true; end; rule begin /* if the SMT bridge is working correctly, it will simplify the condition in * the following expression to `true` avoiding the read of an undefined * variable */ if x[A] = x[A] then y := !y; end; end; rumur-2024.05.07/tests/smt-array-enum-index2.m000066400000000000000000000010241461637631000206610ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None -- variant of smt-array-enum-index.m that pre-defines the enum type type e: enum { A, B }; t: array[e] of 0 .. 1; var x: t; y: boolean; startstate begin y := true; end; rule begin /* if the SMT bridge is working correctly, it will simplify the condition in * the following expression to `true` avoiding the read of an undefined * variable */ if x[A] = x[A] then y := !y; end; end; rumur-2024.05.07/tests/smt-array-enum-value-and-index.m000066400000000000000000000010741461637631000224560ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None /* test whether the SMT bridge can simplify expressions involving arrays with * enum values and indices */ type t: array[enum { C, D }] of enum { A, B }; var x: t; y: boolean; startstate begin y := true; end; rule begin /* if the SMT bridge is working correctly, it will simplify the condition in * the following expression to `true` avoiding the read of an undefined * variable */ if x[C] = x[C] then y := !y; end; end; rumur-2024.05.07/tests/smt-array-enum-value-and-index2.m000066400000000000000000000010561461637631000225400ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None -- variant of smt-array-enum-value-and-index.m which pre-defines the enums type e1: enum { C, D }; e2: enum { A, B }; t: array[e1] of e2; var x: t; y: boolean; startstate begin y := true; end; rule begin /* if the SMT bridge is working correctly, it will simplify the condition in * the following expression to `true` avoiding the read of an undefined * variable */ if x[C] = x[C] then y := !y; end; end; rumur-2024.05.07/tests/smt-array-enum-value.m000066400000000000000000000010511461637631000206040ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None /* test whether the SMT bridge can simplify expressions involving arrays with * enum values */ type t: array[0 .. 1] of enum { A, B }; var x: t; y: boolean; startstate begin y := true; end; rule begin /* if the SMT bridge is working correctly, it will simplify the condition in * the following expression to `true` avoiding the read of an undefined * variable */ if x[0] = x[0] then y := !y; end; end; rumur-2024.05.07/tests/smt-array-enum-value2.m000066400000000000000000000010171461637631000206700ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None -- variant of smt-array-enum-value.m that pre-defines the enum type e: enum { A, B }; t: array[0 .. 1] of e; var x: t; y: boolean; startstate begin y := true; end; rule begin /* if the SMT bridge is working correctly, it will simplify the condition in * the following expression to `true` avoiding the read of an undefined * variable */ if x[0] = x[0] then y := !y; end; end; rumur-2024.05.07/tests/smt-array-of-record.m000066400000000000000000000007411461637631000204130ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None -- test the SMT bridge can cope with arrays of records var x: array[0 .. 1] of record x: 0 .. 1; end; y: boolean; startstate begin y := true; end; rule begin -- if the SMT bridge is working correctly, it will simplify the following -- expression to 'true' avoiding the read of an undefined variable if x[0].x = x[0].x then y := !y; end; end; rumur-2024.05.07/tests/smt-array-range.m000066400000000000000000000010121461637631000176170ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None -- test whether the SMT bridge can simplify expressions involving arrays type t: array[0 .. 1] of 0 .. 1; var x: t; y: boolean; startstate begin y := true; end; rule begin /* if the SMT bridge is working correctly, it will simplify the condition in * the following expression to `true` avoiding the read of an undefined * variable */ if x[0] = x[0] then y := !y; end; end; rumur-2024.05.07/tests/smt-array-range2.m000066400000000000000000000010031461637631000177010ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None -- slight variant on smt-array-range.m with a pre-defined type type r: 0 .. 1; t: array[r] of r; var x: t; y: boolean; startstate begin y := true; end; rule begin /* if the SMT bridge is working correctly, it will simplify the condition in * the following expression to `true` avoiding the read of an undefined * variable */ if x[0] = x[0] then y := !y; end; end; rumur-2024.05.07/tests/smt-bool-literal.m000066400000000000000000000014121461637631000200000ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None /* This model attempts to provoke a bug first observed on commit * 5d4f1939ddc5d5d9336f0ce35e953c51e8b5aeca. The SMT bridge did not correctly * understand the boolean literals and would form malformed problems when seeing * them. If this problem has been introduced, this model will fail verification * by reading an undefined variable. */ var x: boolean; y: boolean; startstate begin x := true; end; rule begin /* if the SMT bridge correctly deals with boolean literals, the following * expression should be simplified to true and avoid the access to an * undefined variable */ if y = true | y = false then x := !x; end; end; rumur-2024.05.07/tests/smt-bv-add.m000066400000000000000000000007021461637631000165510ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_BV_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_BV_ARGS'] is None else None -- test that the SMT bridge can cope with addition when using a bitvector logic var x: 0 .. 1; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if x + 1 = 1 | x + 1 = 2 then y := !y; end; end; rumur-2024.05.07/tests/smt-bv-band.m000066400000000000000000000007121461637631000167260ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_BV_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_BV_ARGS'] is None else None -- test that the SMT bridge can simplify bitwise AND when using bitvector logic var x: 1 .. 2 y: boolean startstate begin y := true; end rule begin -- if the SMT bridge can handle bitwise AND, it should simplify the following -- to `true`, avoiding an undefined read if (x & 1) = 1 | x = 2 then y := !y; end; end rumur-2024.05.07/tests/smt-bv-bitwise-not.m000066400000000000000000000006271461637631000202730ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_BV_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_BV_ARGS'] is None else None -- test that the SMT bridge can deal with bitwise NOT var x: 0 .. 10 y: boolean startstate begin y := true; end rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if ~~x = x then y := !y; end; end rumur-2024.05.07/tests/smt-bv-bor.m000066400000000000000000000007161461637631000166100ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_BV_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_BV_ARGS'] is None else None -- test that the SMT bridge can simplify bitwise OR when using bitvector logic var x: 1 .. 2 y: boolean startstate begin y := true; end rule begin -- if the SMT bridge can handle bitwise OR, it should simplify the following -- to `true`, avoiding an undefined read if (x | 1) = 1 | (x | 1) = 3 then y := !y; end; end rumur-2024.05.07/tests/smt-bv-div.m000066400000000000000000000007021461637631000166030ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_BV_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_BV_ARGS'] is None else None -- test that the SMT bridge can cope with division when using a bitvector logic var x: 4 .. 6; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if x / 2 = 2 | x / 2 = 3 then y := !y; end; end; rumur-2024.05.07/tests/smt-bv-div2.m000066400000000000000000000007161461637631000166720ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_BV_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_BV_ARGS'] is None else None -- test that the SMT bridge can cope with division by non-constant when using a -- bitvector logic var x: 4 .. 6; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if 2 * x / x = 2 then y := !y; end; end; rumur-2024.05.07/tests/smt-bv-exists.m000066400000000000000000000007461461637631000173500ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_BV_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_BV_ARGS'] is None else None -- equivalent of smt-exists.m but using --smt-bitvectors on var x: boolean; y: boolean; startstate begin x := true; end; rule begin -- if the SMT bridge is working correctly, it should simplify the condition as -- a tautology into true, avoiding the read of an undefined variable if y | exists z: 1 .. 2 do z = 1 end then x := !x; end; end; rumur-2024.05.07/tests/smt-bv-exists2.m000066400000000000000000000007651461637631000174330ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_BV_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_BV_ARGS'] is None else None -- equivalent of smt-exists2.m but using --smt-bitvectors on type t: 1 .. 2; var x: boolean; y: boolean; startstate begin x := true; end; rule begin -- if the SMT bridge is working correctly, it should simplify the condition as -- a tautology into true, avoiding the read of an undefined variable if y | exists z: t do z = 1 end then x := !x; end; end; rumur-2024.05.07/tests/smt-bv-exists3.m000066400000000000000000000007511461637631000174270ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_BV_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_BV_ARGS'] is None else None -- equivalent of smt-exists3.m but using --smt-bitvectors on var x: boolean; y: boolean; startstate begin x := true; end; rule begin -- if the SMT bridge is working correctly, it should simplify the condition as -- a tautology into true, avoiding the read of an undefined variable if y | exists z := 1 to 2 do z = 1 end then x := !x; end; end; rumur-2024.05.07/tests/smt-bv-exists4.m000066400000000000000000000007571461637631000174360ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_BV_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_BV_ARGS'] is None else None -- equivalent of smt-exists4.m but using --smt-bitvectors on var x: boolean; y: boolean; startstate begin x := true; end; rule begin -- if the SMT bridge is working correctly, it should simplify the condition as -- a tautology into true, avoiding the read of an undefined variable if y | !exists z := 2 to 4 by 2 do z = 3 end then x := !x; end; end; rumur-2024.05.07/tests/smt-bv-forall.m000066400000000000000000000007561461637631000173110ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_BV_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_BV_ARGS'] is None else None -- equivalent of smt-forall.m but using --smt-bitvectors on var x: boolean; y: boolean; startstate begin x := true; end; rule begin -- if the SMT bridge is working correctly, it should simplify the condition as -- a tautology into true, avoiding the read of an undefined variable if y | forall z: 1 .. 2 do z = 1 | z = 2 end then x := !x; end; end; rumur-2024.05.07/tests/smt-bv-forall2.m000066400000000000000000000007751461637631000173740ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_BV_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_BV_ARGS'] is None else None -- equivalent of smt-forall2.m but using --smt-bitvectors on type t: 1 .. 2; var x: boolean; y: boolean; startstate begin x := true; end; rule begin -- if the SMT bridge is working correctly, it should simplify the condition as -- a tautology into true, avoiding the read of an undefined variable if y | forall z: t do z = 1 | z = 2 end then x := !x; end; end; rumur-2024.05.07/tests/smt-bv-forall3.m000066400000000000000000000007611461637631000173700ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_BV_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_BV_ARGS'] is None else None -- equivalent of smt-forall3.m but using --smt-bitvectors on var x: boolean; y: boolean; startstate begin x := true; end; rule begin -- if the SMT bridge is working correctly, it should simplify the condition as -- a tautology into true, avoiding the read of an undefined variable if y | forall z := 1 to 2 do z = 1 | z = 2 end then x := !x; end; end; rumur-2024.05.07/tests/smt-bv-forall4.m000066400000000000000000000007661461637631000173760ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_BV_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_BV_ARGS'] is None else None -- equivalent of smt-forall4.m but using --smt-bitvectors on var x: boolean; y: boolean; startstate begin x := true; end; rule begin -- if the SMT bridge is working correctly, it should simplify the condition as -- a tautology into true, avoiding the read of an undefined variable if y | forall z := 2 to 4 by 2 do z = 2 | z = 4 end then x := !x; end; end; rumur-2024.05.07/tests/smt-bv-geq.m000066400000000000000000000006661461637631000166060ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_BV_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_BV_ARGS'] is None else None -- test that the SMT bridge can cope with >= when using a bitvector logic var x: 4 .. 6; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if x >= 4 & 6 >= x then y := !y; end; end; rumur-2024.05.07/tests/smt-bv-gt.m000066400000000000000000000006631461637631000164410ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_BV_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_BV_ARGS'] is None else None -- test that the SMT bridge can cope with > when using a bitvector logic var x: 4 .. 6; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if x > 3 & 7 > x then y := !y; end; end; rumur-2024.05.07/tests/smt-bv-leq.m000066400000000000000000000006661461637631000166130ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_BV_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_BV_ARGS'] is None else None -- test that the SMT bridge can cope with <= when using a bitvector logic var x: 4 .. 6; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if 4 <= x & x <= 6 then y := !y; end; end; rumur-2024.05.07/tests/smt-bv-lsh.m000066400000000000000000000007151461637631000166130ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_BV_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_BV_ARGS'] is None else None -- test that the SMT bridge can simplify left shifts when using bitvector logic var x: 1 .. 2 y: boolean startstate begin y := true; end rule begin -- if the SMT bridge can handle left shift, it should simplify the following -- to `true`, avoiding an undefined read if x << 1 = 2 | x << 1 = 4 then y := !y; end; end rumur-2024.05.07/tests/smt-bv-lt.m000066400000000000000000000006641461637631000164470ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_BV_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_BV_ARGS'] is None else None -- test that the SMT bridge can cope with <= when using a bitvector logic var x: 4 .. 6; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if 3 < x & x < 7 then y := !y; end; end; rumur-2024.05.07/tests/smt-bv-mod.m000066400000000000000000000007001461637631000165760ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_BV_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_BV_ARGS'] is None else None -- test that the SMT bridge can cope with modulo when using a bitvector logic var x: 8 .. 9; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if x % 5 = 3 | x % 5 = 4 then y := !y; end; end; rumur-2024.05.07/tests/smt-bv-mod2.m000066400000000000000000000007111461637631000166620ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_BV_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_BV_ARGS'] is None else None -- test that the SMT bridge can cope with modulo by a non-constant when using -- a bitvector logic var x: 8 .. 9; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if x % x = 0 then y := !y; end; end; rumur-2024.05.07/tests/smt-bv-mul.m000066400000000000000000000007121461637631000166170ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_BV_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_BV_ARGS'] is None else None -- test that the SMT bridge can cope with multiplication when using a bitvector logic var x: 8 .. 9; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if x * 5 = 40 | x * 5 = 45 then y := !y; end; end; rumur-2024.05.07/tests/smt-bv-neg.m000066400000000000000000000006761461637631000166040ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_BV_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_BV_ARGS'] is None else None -- test that the SMT bridge can cope with negation when using a bitvector logic var x: 8 .. 9; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if -x = -8 | -x = -9 then y := !y; end; end; rumur-2024.05.07/tests/smt-bv-negative-literal.m000066400000000000000000000005101461637631000212520ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_BV_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_BV_ARGS'] is None else None -- similar to smt-negative-literal, but for --smt-bitvectors on const N: -10; var x: boolean; y: boolean; startstate begin y := true; end; rule begin if x | !x then y := !y; end; end; rumur-2024.05.07/tests/smt-bv-rsh.m000066400000000000000000000007161461637631000166220ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_BV_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_BV_ARGS'] is None else None -- test that the SMT bridge can simplify right shifts when using bitvector logic var x: 1 .. 2 y: boolean startstate begin y := true; end rule begin -- if the SMT bridge can handle left shift, it should simplify the following -- to `true`, avoiding an undefined read if x >> 1 = 1 | x >> 1 = 0 then y := !y; end; end rumur-2024.05.07/tests/smt-bv-sub.m000066400000000000000000000007051461637631000166150ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_BV_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_BV_ARGS'] is None else None -- test that the SMT bridge can cope with subtraction when using a bitvector logic var x: 8 .. 9; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if x - 1 = 7 | x - 1 = 8 then y := !y; end; end; rumur-2024.05.07/tests/smt-bv-xor.m000066400000000000000000000007201461637631000166310ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_BV_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_BV_ARGS'] is None else None -- test that the SMT bridge can simplify bitwise XOR when using bitvector logic var x: 1 .. 2 y: boolean startstate begin y := true; end rule begin -- if the SMT bridge can handle bitwise XOR, it should simplify the following -- to `true`, avoiding an undefined read if (x ^ 3) = 1 | (x ^ 3) = 2 then y := !y; end; end rumur-2024.05.07/tests/smt-const.m000066400000000000000000000010411461637631000165370ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None /* test that the SMT bridge is capable of dealing with formulas involving * integer constants */ const C: 2; var x: 2 .. 2; y: boolean; startstate begin y := true; end; rule begin -- if the SMT bridge can deal with constants, it should judge this condition -- to be tautological and turn it into just `true`, avoiding an error from -- reading x while it is undefined if x = C then y := !y end; end; rumur-2024.05.07/tests/smt-div.m000066400000000000000000000006371461637631000162050ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None -- test that the SMT bridge can cope with division var x: 4 .. 6; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if x / 2 = 2 | x / 2 = 3 then y := !y; end; end; rumur-2024.05.07/tests/smt-enum-typeexprid.m000066400000000000000000000006761461637631000205650ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None -- a version of smt-enum.m using a typedef type t: enum { A }; var x: t; y: boolean; startstate begin y := true; end; rule begin -- if the SMT bridge can deal with enums, it should turn the following -- condition into `true`, avoiding a read of an undefined value from x if x = A then y := !y; end; end; rumur-2024.05.07/tests/smt-enum.m000066400000000000000000000007021461637631000163600ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None -- test that the SMT bridge is capable of dealing with enums var x: enum { A }; y: boolean; startstate begin y := true; end; rule begin -- if the SMT bridge can deal with enums, it should turn the following -- condition into `true`, avoiding a read of an undefined value from x if x = A then y := !y; end; end; rumur-2024.05.07/tests/smt-enum2-typeexprid.m000066400000000000000000000007121461637631000206360ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None -- a version of smt-enum2.m using a typedef type t: enum { A, B }; var x: t; y: boolean; startstate begin y := true; end; rule begin -- if the SMT bridge can deal with enums, it should turn the following -- condition into `true`, avoiding a read of an undefined value from x if x = A | x = B then y := !y; end; end; rumur-2024.05.07/tests/smt-enum2-typeexprid2.m000066400000000000000000000007411461637631000207220ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None -- a double indirected version of smt-enum2-typeexprid.m type t: enum { A, B }; t2: t; var x: t2; y: boolean; startstate begin y := true; end; rule begin -- if the SMT bridge can deal with enums, it should turn the following -- condition into `true`, avoiding a read of an undefined value from x if x = A | x = B then y := !y; end; end; rumur-2024.05.07/tests/smt-enum2.m000066400000000000000000000007001461637631000164400ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None -- slightly more involved version of smt-enum.m var x: enum { A, B }; y: boolean; startstate begin y := true; end; rule begin -- if the SMT bridge can deal with enums, it should turn the following -- condition into `true`, avoiding a read of an undefined value from x if x = A | x = B then y := !y; end; end; rumur-2024.05.07/tests/smt-exists.m000066400000000000000000000007411461637631000167360ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None -- test that the SMT bridge can deal with exists expressions var x: boolean; y: boolean; startstate begin x := true; end; rule begin -- if the SMT bridge is working correctly, it should simplify the condition as -- a tautology into true, avoiding the read of an undefined variable if y | exists z: 1 .. 2 do z = 1 end then x := !x; end; end; rumur-2024.05.07/tests/smt-exists2.m000066400000000000000000000007511461637631000170210ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None -- similar to smt-exists, but using a pre-defined type type t: 1 .. 2; var x: boolean; y: boolean; startstate begin x := true; end; rule begin -- if the SMT bridge is working correctly, it should simplify the condition as -- a tautology into true, avoiding the read of an undefined variable if y | exists z: t do z = 1 end then x := !x; end; end; rumur-2024.05.07/tests/smt-exists3.m000066400000000000000000000007311461637631000170200ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None -- a variant of smt-exists.m using an inline range var x: boolean; y: boolean; startstate begin x := true; end; rule begin -- if the SMT bridge is working correctly, it should simplify the condition as -- a tautology into true, avoiding the read of an undefined variable if y | exists z := 1 to 2 do z = 1 end then x := !x; end; end; rumur-2024.05.07/tests/smt-exists4.m000066400000000000000000000007551461637631000170270ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None -- a variant of smt-exists.m using an inline range with stepping var x: boolean; y: boolean; startstate begin x := true; end; rule begin -- if the SMT bridge is working correctly, it should simplify the condition as -- a tautology into true, avoiding the read of an undefined variable if y | !exists z := 2 to 4 by 2 do z = 3 end then x := !x; end; end; rumur-2024.05.07/tests/smt-forall.m000066400000000000000000000007511461637631000166770ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None -- test that the SMT bridge can deal with forall expressions var x: boolean; y: boolean; startstate begin x := true; end; rule begin -- if the SMT bridge is working correctly, it should simplify the condition as -- a tautology into true, avoiding the read of an undefined variable if y | forall z: 1 .. 2 do z = 1 | z = 2 end then x := !x; end; end; rumur-2024.05.07/tests/smt-forall2.m000066400000000000000000000007611461637631000167620ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None -- similar to smt-forall, but using a pre-defined type type t: 1 .. 2; var x: boolean; y: boolean; startstate begin x := true; end; rule begin -- if the SMT bridge is working correctly, it should simplify the condition as -- a tautology into true, avoiding the read of an undefined variable if y | forall z: t do z = 1 | z = 2 end then x := !x; end; end; rumur-2024.05.07/tests/smt-forall3.m000066400000000000000000000007411461637631000167610ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None -- a variant of smt-forall.m using an inline range var x: boolean; y: boolean; startstate begin x := true; end; rule begin -- if the SMT bridge is working correctly, it should simplify the condition as -- a tautology into true, avoiding the read of an undefined variable if y | forall z := 1 to 2 do z = 1 | z = 2 end then x := !x; end; end; rumur-2024.05.07/tests/smt-forall4.m000066400000000000000000000007641461637631000167670ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None -- a variant of smt-forall.m using an inline range with stepping var x: boolean; y: boolean; startstate begin x := true; end; rule begin -- if the SMT bridge is working correctly, it should simplify the condition as -- a tautology into true, avoiding the read of an undefined variable if y | forall z := 2 to 4 by 2 do z = 2 | z = 4 end then x := !x; end; end; rumur-2024.05.07/tests/smt-geq.m000066400000000000000000000006231461637631000161720ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None -- test that the SMT bridge can cope with >= var x: 4 .. 6; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if x >= 4 & 6 >= x then y := !y; end; end; rumur-2024.05.07/tests/smt-gt.m000066400000000000000000000006201461637631000160250ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None -- test that the SMT bridge can cope with > var x: 4 .. 6; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if x > 3 & 7 > x then y := !y; end; end; rumur-2024.05.07/tests/smt-leq.m000066400000000000000000000006231461637631000161770ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None -- test that the SMT bridge can cope with <= var x: 4 .. 6; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if x <= 6 & 4 <= x then y := !y; end; end; rumur-2024.05.07/tests/smt-lt.m000066400000000000000000000006211461637631000160330ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None -- test that the SMT bridge can cope with <= var x: 4 .. 6; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if x < 7 & 3 < x then y := !y; end; end; rumur-2024.05.07/tests/smt-mod.m000066400000000000000000000006351461637631000162000ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None -- test that the SMT bridge can cope with modulo var x: 8 .. 9; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if x % 5 = 3 | x % 5 = 4 then y := !y; end; end; rumur-2024.05.07/tests/smt-mul.m000066400000000000000000000006471461637631000162210ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None -- test that the SMT bridge can cope with multiplication var x: 8 .. 9; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if x * 5 = 40 | x * 5 = 45 then y := !y; end; end; rumur-2024.05.07/tests/smt-neg.m000066400000000000000000000006331461637631000161700ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None -- test that the SMT bridge can cope with negation var x: 8 .. 9; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if -x = -8 | -x = -9 then y := !y; end; end; rumur-2024.05.07/tests/smt-negative-literal.m000066400000000000000000000013141461637631000206500ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None -- As observed on commit 852de6485322fe6e0dfaa8efa0109f23f634bf3f, models with -- negative literal values would cause malformed SMT problems to be constructed. -- This model tests that this bug has not been reintroduced. Note that the -- negative literal does not actually need to be used in the model for the bug -- to manifest. const N: -10; var x: boolean; y: boolean; startstate begin y := true; end; rule begin -- the SMT bridge should be able to optimise the following conditional into -- `true` preventing read of an undefined value if x | !x then y := !y; end; end; rumur-2024.05.07/tests/smt-nested-array.m000066400000000000000000000010501461637631000200070ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None -- test whether the SMT bridge can simplify expressions involving nested arrays type t: array[0 .. 1] of array[0 .. 1] of 0 .. 1; var x: t; y: boolean; startstate begin y := true; end; rule begin /* if the SMT bridge is working correctly, it will simplify the condition in * the following expression to `true` avoiding the read of an undefined * variable */ if x[0][0] = x[0][0] then y := !y; end; end; rumur-2024.05.07/tests/smt-range.m000066400000000000000000000007041461637631000165120ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None -- test that the SMT bridge can deal with integer range types var x: 2 .. 10; y: boolean; startstate begin y := true; end; rule begin -- if the SMT bridge can deal with integer ranges, it should turn the -- following condition into `true`, avoiding a read of x which is undefined if x > 1 then y := !y; end; end; rumur-2024.05.07/tests/smt-record-bool-field.m000066400000000000000000000010451461637631000207050ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None -- test whether the SMT bridge can simplify expressions involving records with -- boolean fields type t: record x: boolean; end; var x: t; y: boolean; startstate begin y := true; end; rule begin /* if the SMT bridge is working correctly, it will simplify the condition in * the following expression to `true` avoiding the read of an undefined * variable */ if x.x = x.x then y := !y; end; end; rumur-2024.05.07/tests/smt-record-bool-field2.m000066400000000000000000000007711461637631000207740ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None -- variant on smt-record-bool-field.m type t: record x: boolean; end; var x: t; y: boolean; startstate begin y := true; end; rule begin /* if the SMT bridge is working correctly, it will simplify the condition in * the following expression to `true` avoiding the read of an undefined * variable */ if x.x = true | x.x = false then y := !y; end; end; rumur-2024.05.07/tests/smt-record-enum-field.m000066400000000000000000000010511461637631000207130ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None -- test whether the SMT bridge can simplify expressions involving records with -- enum members type t: record x: enum { A, B }; end; var x: t; y: boolean; startstate begin y := true; end; rule begin /* if the SMT bridge is working correctly, it will simplify the condition in * the following expression to `true` avoiding the read of an undefined * variable */ if x.x = x.x then y := !y; end; end; rumur-2024.05.07/tests/smt-record-enum-field2.m000066400000000000000000000010271461637631000210000ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None -- variant of smt-record-enum-field.m that pre-defines the enum type type e: enum { A, B }; t: record x: e; end; var x: t; y: boolean; startstate begin y := true; end; rule begin /* if the SMT bridge is working correctly, it will simplify the condition in * the following expression to `true` avoiding the read of an undefined * variable */ if x.x = x.x then y := !y; end; end; rumur-2024.05.07/tests/smt-record-of-array.m000066400000000000000000000007411461637631000204130ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None -- test the SMT bridge can cope with records of arrays var x: record x: array[0 .. 1] of 0 .. 1; end; y: boolean; startstate begin y := true; end; rule begin -- if the SMT bridge is working correctly, it will simplify the following -- expression to 'true' avoiding the read of an undefined variable if x.x[0] = x.x[0] then y := !y; end; end; rumur-2024.05.07/tests/smt-record-range-field.m000066400000000000000000000010431461637631000210440ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None -- test whether the SMT bridge can simplify expressions involving records with -- range fieldss type t: record x: 0 .. 1; end; var x: t; y: boolean; startstate begin y := true; end; rule begin /* if the SMT bridge is working correctly, it will simplify the condition in * the following expression to `true` avoiding the read of an undefined * variable */ if x.x = x.x then y := !y; end; end; rumur-2024.05.07/tests/smt-record-range-field2.m000066400000000000000000000010211461637631000211220ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None -- slight variant on smt-record-range-field.m with a pre-defined type type r: 0 .. 1; t: record x: r; end; var x: t; y: boolean; startstate begin y := true; end; rule begin /* if the SMT bridge is working correctly, it will simplify the condition in * the following expression to `true` avoiding the read of an undefined * variable */ if x.x = x.x then y := !y; end; end; rumur-2024.05.07/tests/smt-shadowed-decl.m000066400000000000000000000013331461637631000201200ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None -- This model tests a scenario where one declaration shadows another during -- translation across the SMT bridge. If everything works correctly, Rumur -- should form a valid SMT problem and the SMT solver should find that the -- condition in the if statement is a tautology and reduce it to "true", -- removing the read of an undefined variable. However, on commit -- 4ff47d10ee40a4947c3ee0463fddbf6f0fee1857 it was observed that this generates -- an invalid SMT problem. var x: boolean; y: boolean; startstate begin y := true; end; rule var x: boolean; begin if x | !x then y := !y; end; end; rumur-2024.05.07/tests/smt-simplify.m000066400000000000000000000010121461637631000172430ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None /* This model tests whether Rumur is capable of simplifying simple conditions at * code generation time. If it is, then it will replace the `y = y` check with * true and this model will pass. If not, the check will remain and cause a read * of an undefined value at runtime. */ var x: boolean; y: boolean; startstate begin x := true; end; rule begin if y = y then x := !x; end; end; rumur-2024.05.07/tests/smt-sub.m000066400000000000000000000006421461637631000162100ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None -- test that the SMT bridge can cope with subtraction var x: 8 .. 9; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if x - 1 = 7 | x - 1 = 8 then y := !y; end; end; rumur-2024.05.07/tests/smt-typedecl-boolean.m000066400000000000000000000013641461637631000206470ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None -- This model is designed to provoke a problem first observed on -- 787f074328874a470d595576ae9e8b16837582f4, where the SMT bridge would -- incorrectly mangle 'boolean' as part of a TypeDecl. This would lead to SMT -- problems including the undefined symbol 'ru_boolean'. type bit: boolean; var x: bit; y: bit; startstate begin x := true; end; rule begin -- if the SMT bridge correctly understands the problem passed to it, it should -- detect the following as a tautology causing simplification to collapse it -- to 'true' avoid the read of an undefined variable if y = true | y = false then x := !x; end; end; rumur-2024.05.07/tests/smt-typeexprid.m000066400000000000000000000004571461637631000176200ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None -- similar to smt-range.m but using a named type type r: 2 .. 10; var x: r; y: boolean; startstate begin y := true; end; rule begin if x > 1 then y := !y; end; end; rumur-2024.05.07/tests/smt-typeexprid2.m000066400000000000000000000004711461637631000176760ustar00rootroot00000000000000-- rumur_flags: CONFIG['SMT_ARGS'] -- skip_reason: 'no SMT solver available' if CONFIG['SMT_ARGS'] is None else None -- a double indirected version of smt-typeexprid.m type r: 2 .. 10; s: r; var x: s; y: boolean; startstate begin y := true; end; rule begin if x > 1 then y := !y; end; end; rumur-2024.05.07/tests/state-reorder.m000066400000000000000000000020151461637631000173720ustar00rootroot00000000000000-- skip_reason: 'N/A in XML mode' if xml else 'N/A in non-debug mode' if not debug else None -- checker_output: re.compile(r'\bfield x is located at state offset 2 bits$(.|\n)*\bfield y is located at state offset 0 bits$', re.MULTILINE) -- This tests for a regression of a problem first observed on commit -- 4944427734628cf913e8d5eeb54d897033f9eb59. When state variables should have -- been reordered during optimisation, they were not. If this bug has been -- reintroduced, the debug output will incorrectly list: -- -- * field y is located at state offset 30 bits -- * field x is located at state offset 0 bits type t: scalarset(10); var -- this field has a width of 30 bits in generated code and should get -- automatically reordered to come after the second field... x: array[t] of 0 .. 5; -- ...that has a width of 2 bits — a power of two — and hence should come -- first y: boolean; startstate begin y := true; end; ruleset z: t do rule begin x[z] := 5; end; end; rule begin y := !y; end; rumur-2024.05.07/tests/strace-sandbox.sh000077500000000000000000000043411461637631000177140ustar00rootroot00000000000000#!/usr/bin/env bash # run a sandboxed checker under strace # # When the Linux seccomp sandbox causes the checker to terminate because it made # an unauthorised system call, it is difficult to debug what happened without # stracing the process to see the denied system call. This test case automates # this work flow. If the checker runs fine with the sandbox enabled, this test # case is irrelevant. However, if the basic-sandbox.m test fails, this test will # hopefully automatically diagnose the failure. The purpose of this existing # within the test suite itself is to debug failures that occur within a CI # environment you do not have access to or cannot easily replicate, like the # Debian auto builders. # check we have strace if ! which strace &>/dev/null; then printf 'strace not available\n' exit 125 fi # check whether sandboxing is available if [ "${HAS_SANDBOX}" != "True" ]; then printf 'seccomp sandboxing not supported\n' exit 125 fi # echo commands set -x # create a temporary space to work in TMP=$(mktemp -d) pushd ${TMP} # create a basic model cat - >model.m < begin x.a := !x.a; x.b := !x.b; end; rule begin undefine x; y := false; end; rumur-2024.05.07/tests/unicode-assignment.m000066400000000000000000000002151461637631000204060ustar00rootroot00000000000000-- Test the alternative form of the assignment operator var x: boolean; startstate begin x ≔ true; end; rule begin x ≔ !x; end; rumur-2024.05.07/tests/unicode-div.m000066400000000000000000000001661461637631000170250ustar00rootroot00000000000000-- test of unicode division symbol var x: 1 .. 2; startstate begin x := 1; end; rule begin x := 2 ÷ x; end; rumur-2024.05.07/tests/unicode-div2.m000066400000000000000000000002001461637631000170740ustar00rootroot00000000000000-- test of unicode division symbol, solidus var x: 1 .. 2; startstate begin x := 1; end; rule begin x := 2 ∕ x; end; rumur-2024.05.07/tests/unicode-mul.m000066400000000000000000000002501461637631000170320ustar00rootroot00000000000000-- test of unicode multiplication symbol var x: 1 .. 2; startstate begin x := 1; end; rule begin if x = 2 then x := 1; else x := 2 × x; end; end; rumur-2024.05.07/tests/unicode-sub.m000066400000000000000000000001721461637631000170310ustar00rootroot00000000000000-- test of unicode subtraction symbol var x: 0 .. 1; startstate begin x := 0; end; rule begin x := 1 − x; end; rumur-2024.05.07/tests/unused-record.m000066400000000000000000000002001461637631000173630ustar00rootroot00000000000000type foo_t: record x: boolean; end; var y: boolean; startstate begin y := false; end; rule begin y := !y; end; rumur-2024.05.07/tests/var-case.m000066400000000000000000000005641461637631000163220ustar00rootroot00000000000000-- This test is designed to provoke any issues with handling the -- case-sensitivity of variables. var state: boolean; State: boolean; sTate: boolean; stAte: boolean; staTe: boolean; statE: boolean; STATE: boolean; startstate begin state := true; State := false; end; rule begin state := !state; State := !State; end; invariant state != State; rumur-2024.05.07/tests/while-stmt1.m000066400000000000000000000006201461637631000167700ustar00rootroot00000000000000-- Basic test that while statements work var x: boolean; startstate begin x := true; end; rule begin -- While statement using a state variable while x do x := !x; end; -- While statement that uses an expression (non-lvalue) while (x | !x) & !x do x := !x; end; -- While statement using a constant while false do x := !x; end; end; rule begin x := !x; end; rumur-2024.05.07/tests/while-stmt2.m000066400000000000000000000005031461637631000167710ustar00rootroot00000000000000-- Test of a while statement using ranges var x: 0 .. 10; startstate begin x := 1; end; rule begin -- While statement using a state variable while x + 1 < 10 do x := x + 1; end; -- While statement using a constant expression while 1 + 1 > 10 do end; x := 5; end; rule begin x := 6 - x; end; rumur-2024.05.07/tests/while-stmt3.m000066400000000000000000000005261461637631000167770ustar00rootroot00000000000000-- Test that while statements work with record-nested fields var x: record y: boolean; end; z: record w: 0 .. 10; end; startstate begin x.y := true; z.w := 1; end; rule begin while x.y do x.y := !x.y; end; while z.w < 2 do z.w := 6 - z.w; end; end; rule begin x.y := !x.y; z.w := 6 - z.w; end; rumur-2024.05.07/tests/while-stmt4.m000066400000000000000000000002621461637631000167750ustar00rootroot00000000000000-- rumur_exit_code: 1 -- Test that ranges in while loops are rejected var x: 0 .. 10; startstate begin x := 5; end; rule begin while x do x := 6 - x; end; end; rumur-2024.05.07/tests/while-stmt5.m000066400000000000000000000003321461637631000167740ustar00rootroot00000000000000-- rumur_exit_code: 1 -- Test that complex types in while expressions are rejected var x: record y: boolean; end; startstate begin x.y := true; end; rule begin while x do x.y := !x.y; end; end; rumur-2024.05.07/tests/write-out-of-range.m000066400000000000000000000002171461637631000202470ustar00rootroot00000000000000-- checker_exit_code: 1 -- Test writing a value out of range. var x: 0..1; startstate begin x := 0; end; rule begin x := x + 1; end; rumur-2024.05.07/tests/write-out-of-range2.m000066400000000000000000000002701461637631000203300ustar00rootroot00000000000000-- checker_exit_code: 1 -- Test writing a value out of range into a record. var x: record a: 0..1; end; startstate begin x.a := 0; end; rule begin x.a := x.a + 1; end; rumur-2024.05.07/tests/write-out-of-range3.m000066400000000000000000000002651461637631000203350ustar00rootroot00000000000000-- checker_exit_code: 1 -- Test writing a value out of range into an array. var x: array[0..1] of 0..1; startstate begin x[0] := 0; end; rule begin x[0] := x[0] + 1; end; rumur-2024.05.07/tests/xml-escape-increment.m000066400000000000000000000011771461637631000206420ustar00rootroot00000000000000-- checker_output: re.compile(r'Rule "foo"') if xml else None -- checker_exit_code: 1 /* This tests for the presence of a bug first observed on commit * d3517bbf0c443242704979a4684777a9f1aeddfa. Rule names and other strings dealt * with by xml_escape() in the verifier were treated incorrectly. For characters * that do not need escaping, the output pointer is not incremented, resulting * in them being omitted. If the bug has been re-introduced, you will not see * Rule foo indicated in the counter-example trace. */ var x: boolean; startstate begin x := true; end; rule "foo" begin x := !x; end; invariant x; rumur-2024.05.07/tests/xor-mixed.m000066400000000000000000000002661461637631000165340ustar00rootroot00000000000000-- rumur_exit_code: 1 -- test that ^ with mixed operands is rejected var x: 0 .. 10 y: boolean startstate begin y := true; end rule begin x := 2 ^ true; y := !y; end