diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 6b3dd2fa508..102ee1cc4bb 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -52,7 +52,6 @@ // See https://containers.dev/features for a list of all available features "features": { - "fish": "latest", "java": "17", "python": "latest" }, diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5b6c64fc76d..86f5d42bfd6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -110,7 +110,7 @@ jobs: # Run a subset of the tests with the purification optimization enabled # to ensure that we do not introduce regressions. purification-tests: - needs: [fmt-check, clippy-check, check-deps, smir-check, quick-tests] + #needs: [fmt-check, clippy-check, check-deps, smir-check, quick-tests] runs-on: ubuntu-latest env: PRUSTI_ENABLE_PURIFICATION_OPTIMIZATION: true @@ -162,6 +162,36 @@ jobs: # python x.py test --all pass/pure-fn/ref-mut-arg.rs # python x.py test --all pass/rosetta/Ackermann_function.rs # python x.py test --all pass/rosetta/Heapsort.rs + - name: custom_heap_encoding + env: + PRUSTI_VIPER_BACKEND: carbon + PRUSTI_CUSTOM_HEAP_ENCODING: true + PRUSTI_TRACE_WITH_SYMBOLIC_EXECUTION: false + PRUSTI_PURIFY_WITH_SYMBOLIC_EXECUTION: false + run: | + python x.py test custom_heap_encoding + - name: purify_with_symbolic_execution + env: + PRUSTI_VIPER_BACKEND: carbon + PRUSTI_CUSTOM_HEAP_ENCODING: false + PRUSTI_PURIFY_WITH_SYMBOLIC_EXECUTION: true + run: | + python x.py test custom_heap_encoding + - name: custom_heap_encoding and purify_with_symbolic_execution + env: + PRUSTI_VIPER_BACKEND: carbon + PRUSTI_CUSTOM_HEAP_ENCODING: true + PRUSTI_PURIFY_WITH_SYMBOLIC_EXECUTION: true + run: | + python x.py test custom_heap_encoding + - name: trace_with_symbolic_execution + env: + PRUSTI_VIPER_BACKEND: silicon + PRUSTI_CUSTOM_HEAP_ENCODING: false + PRUSTI_TRACE_WITH_SYMBOLIC_EXECUTION: false + PRUSTI_PURIFY_WITH_SYMBOLIC_EXECUTION: false + run: | + python x.py test custom_heap_encoding - name: Run with purification. env: PRUSTI_VIPER_BACKEND: silicon diff --git a/Cargo.lock b/Cargo.lock index f96d098ad6e..9eebb683d4b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -43,7 +43,7 @@ version = "0.1.0" dependencies = [ "compiletest_rs", "derive_more", - "env_logger", + "env_logger 0.10.0", "glob", "log", "prusti-rustc-interface", @@ -914,18 +914,51 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" +[[package]] +name = "dogged" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2638df109789fe360f0d9998c5438dd19a36678aaf845e46f285b688b1a1657a" + [[package]] name = "dunce" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bd4b30a6560bbd9b4620f4de34c3f14f60848e58a9b7216801afcb4c7b31c3c" +[[package]] +name = "egg" +version = "0.9.3" +source = "git+https://github.com/vakaras/egg.git?branch=from_enodes_with_explanations#3d24f905a2724dde6ac2ddcf438ad9bd638b5bda" +dependencies = [ + "env_logger 0.9.3", + "fxhash", + "hashbrown", + "indexmap", + "instant", + "log", + "smallvec", + "symbol_table", + "symbolic_expressions", + "thiserror", +] + [[package]] name = "either" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +[[package]] +name = "ena" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c533630cf40e9caa44bd91aadc88a75d75a4c3a12b4cfde353cbed41daa1e1f1" +dependencies = [ + "dogged", + "log", +] + [[package]] name = "encoding_rs" version = "0.8.32" @@ -935,6 +968,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "env_logger" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" +dependencies = [ + "log", +] + [[package]] name = "env_logger" version = "0.10.0" @@ -1183,6 +1225,15 @@ dependencies = [ "slab", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -2121,7 +2172,7 @@ name = "prusti" version = "0.2.2" dependencies = [ "chrono", - "env_logger", + "env_logger 0.10.0", "lazy_static", "log", "prusti-common", @@ -2198,7 +2249,7 @@ version = "0.1.0" dependencies = [ "bincode", "clap", - "env_logger", + "env_logger 0.10.0", "lazy_static", "log", "num_cpus", @@ -2241,7 +2292,7 @@ version = "0.2.0" dependencies = [ "cargo-test-support", "compiletest_rs", - "env_logger", + "env_logger 0.10.0", "log", "prusti", "prusti-launch", @@ -2271,9 +2322,12 @@ dependencies = [ name = "prusti-viper" version = "0.1.0" dependencies = [ + "analysis", "backtrace", "derive_more", "diffy", + "egg", + "ena", "itertools", "lazy_static", "log", @@ -2284,6 +2338,7 @@ dependencies = [ "prusti-rustc-interface", "prusti-server", "regex", + "rsmt2", "rustc-hash", "serde", "serde_json", @@ -2495,6 +2550,15 @@ dependencies = [ "serde", ] +[[package]] +name = "rsmt2" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2efb7d3e5fdbdc6a38a6026853350e3fa03f8ff791affe6f5aa5f2d590216f9e" +dependencies = [ + "error-chain", +] + [[package]] name = "rust-ini" version = "0.18.0" @@ -2882,6 +2946,22 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "symbol_table" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32bf088d1d7df2b2b6711b06da3471bc86677383c57b27251e18c56df8deac14" +dependencies = [ + "ahash", + "hashbrown", +] + +[[package]] +name = "symbolic_expressions" +version = "5.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c68d531d83ec6c531150584c42a4290911964d5f0d79132b193b67252a23b71" + [[package]] name = "syn" version = "1.0.109" @@ -2920,7 +3000,7 @@ dependencies = [ name = "systest" version = "0.1.0" dependencies = [ - "env_logger", + "env_logger 0.10.0", "error-chain", "jni", "jni-gen", @@ -2980,7 +3060,7 @@ dependencies = [ "clap", "color-backtrace", "csv", - "env_logger", + "env_logger 0.10.0", "failure", "glob", "log", @@ -3431,7 +3511,7 @@ version = "0.1.0" dependencies = [ "bencher", "bincode", - "env_logger", + "env_logger 0.10.0", "error-chain", "futures", "jni", @@ -3450,7 +3530,7 @@ dependencies = [ name = "viper-sys" version = "0.1.0" dependencies = [ - "env_logger", + "env_logger 0.10.0", "error-chain", "jni", "jni-gen", diff --git a/benchmark_silicon/.gitignore b/benchmark_silicon/.gitignore new file mode 100644 index 00000000000..2d98968b221 --- /dev/null +++ b/benchmark_silicon/.gitignore @@ -0,0 +1,2 @@ +env +*.swp diff --git a/benchmark_silicon/AnalyzeReport.ipynb b/benchmark_silicon/AnalyzeReport.ipynb new file mode 100644 index 00000000000..0a85863a412 --- /dev/null +++ b/benchmark_silicon/AnalyzeReport.ipynb @@ -0,0 +1,318 @@ +{ + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Analyze results of `bechmark_silicon.py`" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "# Load the results from the JSON file\n", + "report = pd.read_json('report.json')" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
algorithmsidentifierfile_path
6[]1119../viperserver/silicon/silver/src/test/resources/transformations/Macros/Hygienic/nestedRef.vpr
8[]1130../viperserver/silicon/silver/src/test/resources/transformations/Macros/Expansion/simple2Ref.vpr
9[]731../viperserver/silicon/silver/src/test/resources/all/issues/silicon/0203.vpr
11[]1141../viperserver/silicon/silver/src/test/resources/transformations/FoldConstants/simple.vpr
12[]1249../viperserver/silicon/silver/src/test/resources/termination/methods/loops/loopCondition.vpr
............
1067[]769../viperserver/silicon/silver/src/test/resources/all/issues/silicon/0328b.vpr
1068[]808../viperserver/silicon/silver/src/test/resources/all/issues/silicon/0045.vpr
1069[]678../viperserver/silicon/silver/src/test/resources/all/issues/silver/0168_lib.vpr
1073[]491../viperserver/silicon/silver/src/test/resources/all/sets/sets.vpr
1079[]1077../viperserver/silicon/silver/src/test/resources/transformations/CopyPropagation/simple.vpr
\n", + "

289 rows × 3 columns

\n", + "
" + ], + "text/plain": [ + " algorithms identifier \n", + "6 [] 1119 \\\n", + "8 [] 1130 \n", + "9 [] 731 \n", + "11 [] 1141 \n", + "12 [] 1249 \n", + "... ... ... \n", + "1067 [] 769 \n", + "1068 [] 808 \n", + "1069 [] 678 \n", + "1073 [] 491 \n", + "1079 [] 1077 \n", + "\n", + " file_path \n", + "6 ../viperserver/silicon/silver/src/test/resources/transformations/Macros/Hygienic/nestedRef.vpr \n", + "8 ../viperserver/silicon/silver/src/test/resources/transformations/Macros/Expansion/simple2Ref.vpr \n", + "9 ../viperserver/silicon/silver/src/test/resources/all/issues/silicon/0203.vpr \n", + "11 ../viperserver/silicon/silver/src/test/resources/transformations/FoldConstants/simple.vpr \n", + "12 ../viperserver/silicon/silver/src/test/resources/termination/methods/loops/loopCondition.vpr \n", + "... ... \n", + "1067 ../viperserver/silicon/silver/src/test/resources/all/issues/silicon/0328b.vpr \n", + "1068 ../viperserver/silicon/silver/src/test/resources/all/issues/silicon/0045.vpr \n", + "1069 ../viperserver/silicon/silver/src/test/resources/all/issues/silver/0168_lib.vpr \n", + "1073 ../viperserver/silicon/silver/src/test/resources/all/sets/sets.vpr \n", + "1079 ../viperserver/silicon/silver/src/test/resources/transformations/CopyPropagation/simple.vpr \n", + "\n", + "[289 rows x 3 columns]" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Count how many empty lists are in `algorithms` column\n", + "report['algorithms'].apply(lambda x: len(x)).value_counts()\n", + "# report[['algorithms', 'identifier']]\n", + "# Show the rows where `algorithms` is empty\n", + "pd.set_option('display.max_colwidth', None)\n", + "report[report['algorithms'].apply(lambda x: len(x) == 0)][['algorithms', 'identifier', 'file_path']]" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "# Iterate over algorithms and event kinds\n", + "all_event_kinds = set()\n", + "decide_and_or_per_algorithm = []\n", + "for row in report.itertuples():\n", + " algorithms_used = set()\n", + " for (resource, algorithm) in row.algorithms:\n", + " algorithms_used.add(algorithm)\n", + " for event_kinds in row.event_kinds:\n", + " event_kinds = dict(event_kinds)\n", + " all_event_kinds.update(event_kinds.keys())\n", + " decide_and_or_per_algorithm.append((str(list(sorted(algorithms_used))), event_kinds.get('DecideAndOr', 0)))\n", + "# decide_and_or_per_algorithm as DataFrame\n", + "decide_and_or_per_algorithm = pd.DataFrame(decide_and_or_per_algorithm, columns=['algorithms', 'DecideAndOr'])" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(0.0, 200.0)" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABngAAAN9CAYAAACw9He/AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAACcG0lEQVR4nOz9e5xWZb0//r9mBhxAAUVFYKcySmnFpGa78QRIOiKGisPU9lRqbrPysFOsNnw6qPWBDsq2zI671DxQX2kcjbLERBi30k7bmlS2xcAO4gFNkIM0zNy/P/rM/JxmQEeBYTHP5+Mxj+G+rmut9b5P3PfixbWuslKpVAoAAAAAAACFUd7TBQAAAAAAANA9Ah4AAAAAAICCEfAAAAAAAAAUjIAHAAAAAACgYAQ8AAAAAAAABSPgAQAAAAAAKBgBDwAAAAAAQMEIeAAAAAAAAApGwAMAAAAAAFAwAh4AANjOlJWV5dJLL+3pMrZ799xzT8rKynLPPfdsctyll16asrKyrFixYusU9grOPPPMjBw5sseO3/Z4dGfstvLYAQDAtkTAAwAAr9J1112XsrKyDj9Dhw7N+PHjc8cdd/R0ea/bb3/721x66aVZtmxZT5dCLzNjxow0Njb2dBkAAFAoAh4AAOimyy+/PDfccEO+973v5eMf/3ieffbZHHfccZk7d25Pl/a6/Pa3v81ll10m4GGL+uQnP5l169Z1aBPwAABA9/Xp6QIAAKBoJk6cmHe84x3tt88+++zssccemT17diZNmtSDlRXHhg0b0tramh122KGnS2ErWbNmTXbcccf06dMnffo4FQUAgNfLDB4AAHiddt555/Tv37/TP1qvWbMmU6dOzZ577pnKysrst99+ueKKK1IqlZIk69aty/7775/999+/w4yG559/PsOHD89hhx2WlpaWJH9fN2WnnXbKH/7wh0yYMCE77rhjRowYkcsvv7x9f5vyP//zP5k4cWIGDRqUnXbaKUcddVQWLVrU3n/dddflPe95T5Jk/Pjx7Zege6X1ZW655Za85S1vSb9+/TJ69OjceuutndZ4WbZsWcrKynLFFVfkqquuyr777pvKysr89re/TZLcfffdGTNmTHbcccfsvPPOOfHEE/O73/2uw3E2tm5MV+u5lJWV5fzzz89NN92U/fbbL/369cvBBx+chQsXdtr+L3/5Sz7wgQ9kjz32SGVlZd761rfmu9/9bqdxf/7znzN58uTsuOOOGTp0aC666KKsX79+k4/NP1qxYkXe+973ZtCgQdl1113zb//2b3nppZfa+8eNG5cDDjigy23322+/TJgwYZP7v+222/Lud787I0aMSGVlZfbdd9989rOfbX8Nbcpzzz2X973vfRk0aFB23nnnnHHGGXn44YdTVlaW6667rsPYV/N8tT0vv/3tb3Pqqadml112yRFHHNGhr01ZWVnWrFmT66+/vv11d+aZZ3bY3wsvvJAzzzwzO++8cwYPHpyzzjora9eu7TCm7Xlve032798/hx56aB555JEkyTe/+c2MGjUq/fr1y5FHHtlpptpjjz2WKVOmZNiwYenXr1/e8IY35OSTT87KlStf8fEDAICe4L9NAQBAN61cuTIrVqxIqVTKM888k6uvvjqrV6/O6aef3j6mVCrlhBNOyPz583P22WfnwAMPzM9+9rN87GMfy1/+8pf8x3/8R/r375/rr78+hx9+eP7P//k/mTVrVpLkvPPOy8qVK3PdddeloqKifZ8tLS059thjc8ghh+SLX/xifvrTn+Yzn/lMNmzYkMsvv3yj9f7mN7/JmDFjMmjQoHz84x9P3759881vfjNHHnlkFixYkJqamowdOzYXXnhhvvKVr2T69Ol585vfnCTtv7vy4x//OP/yL/+S6urqzJw5M3/9619z9tln55/+6Z+6HH/ttdfmpZdeygc/+MFUVlZmyJAhueuuuzJx4sTss88+ufTSS7Nu3bpcffXVOfzww/OrX/2qy1Dn1ViwYEF+8IMf5MILL0xlZWW+9rWv5dhjj81///d/Z/To0UmSp59+Ooccckh7MLD77rvnjjvuyNlnn51Vq1blox/9aJK/B3FHHXVU/vjHP+bCCy/MiBEjcsMNN+Tuu+/uVk3vfe97M3LkyMycOTOLFi3KV77ylfz1r3/N9773vSTJ+973vpxzzjlZvHhxe41J8stf/jL/+7//m09+8pOb3P91112XnXbaKRdffHF22mmn3H333fn0pz+dVatW5Utf+tJGt2ttbc3xxx+f//7v/86HP/zh7L///rnttttyxhlndBrb3efrPe95T974xjdmxowZGw0ib7jhhvzrv/5r3vnOd+aDH/xgkmTfffft9NhVVVVl5syZ+dWvfpX//M//zNChQ/OFL3yhw7impqbcfvvtOe+885IkM2fOzKRJk/Lxj388X/va1/KRj3wkf/3rX/PFL34xH/jAB9qfw7/97W+ZMGFC1q9fnwsuuCDDhg3LX/7yl8ydOzcvvPBCBg8evMnHHgAAekQJAAB4Va699tpSkk4/lZWVpeuuu67D2MbGxlKS0uc+97kO7fX19aWysrLSkiVL2tumTZtWKi8vLy1cuLB0yy23lJKUrrrqqg7bnXHGGaUkpQsuuKC9rbW1tfTud7+7tMMOO5SeffbZ9vYkpc985jPttydPnlzaYYcdSo8//nh725NPPlkaOHBgaezYse1tbceeP3/+q3o8qqurS294wxtKL774YnvbPffcU0pS2nvvvdvbli5dWkpSGjRoUOmZZ57psI8DDzywNHTo0NJzzz3X3vbwww+XysvLS+9///s73P+X77PNZz7zmdI/nta0PS8PPPBAe9sTTzxR6tevX+mkk05qbzv77LNLw4cPL61YsaLD9ieffHJp8ODBpbVr15ZKpVLpqquuKiUp/X//3//XPmbNmjWlUaNGvarHq63GE044oUP7Rz7ykVKS0sMPP1wqlUqlF154odSvX7/SJz7xiQ7jLrzwwtKOO+5YWr169SaP01bvy5177rmlAQMGlF566aX2tn98LH/4wx92es21tLSU3vWud5WSlK699tr29lf7fLXd51NOOWWjj8fL7bjjjqUzzjhjo2M/8IEPdGg/6aSTSrvuumuHtrb34tKlS9vbvvnNb5aSlIYNG1ZatWpVe/u0adNKSdrH/s///E8pSemWW27pVAMAAGyrXKINAAC66Zprrsm8efMyb9683HjjjRk/fnz+9V//NQ0NDe1jfvKTn6SioiIXXnhhh22nTp2aUqmUO+64o73t0ksvzVvf+tacccYZ+chHPpJx48Z12q7N+eef3/7ntpknf/vb33LXXXd1Ob6lpSV33nlnJk+enH322ae9ffjw4Tn11FNz7733ZtWqVd1+DJ588sk88sgjef/735+ddtqpvX3cuHGprq7ucpspU6Zk9913b7+9fPnyPPTQQznzzDMzZMiQ9va3ve1tqa2tzU9+8pNu19Xm0EMPzcEHH9x+e6+99sqJJ56Yn/3sZ2lpaUmpVMoPf/jDHH/88SmVSlmxYkX7z4QJE7Jy5cr86le/SvL353L48OGpr69v39+AAQPaZ5u8Wm2zStpccMEF7ftPksGDB+fEE0/M7Nmz22e7tLS05Ac/+EH75eE2pX///u1/fvHFF7NixYqMGTMma9euzaOPPrrR7X7605+mb9++Oeecc9rbysvLO9X7Wp6vD33oQ5us+dX6x/2MGTMmzz33XKfX7lFHHdVhFlFNTU2Sv7/2Bg4c2Kn9D3/4Q5K0z9D52c9+1unSbwAAsK0S8AAAQDe9853vzNFHH52jjz46p512Wn784x/nLW95S3vYkiRPPPFERowY0eEflZP//yXPnnjiifa2HXbYId/97nezdOnSvPjii7n22ms7rSuT/P0f3V8e0iTJm970piTptJ5Im2effTZr167Nfvvt16nvzW9+c1pbW/OnP/3p1d/5/6et/lGjRnXq66otSaqqqrrcx8ZqW7FiRdasWdPt2pLkjW98Y6e2N73pTVm7dm2effbZPPvss3nhhRfyrW99K7vvvnuHn7POOitJ8swzz7TXOWrUqE7PSVd1d6emfffdN+Xl5R2eu/e///354x//mKampiR/vyTa008/nfe9732vuP/f/OY3OemkkzJ48OAMGjQou+++e/tlAze1jswTTzyR4cOHZ8CAAR3a//F5fC3P1z8+56/VXnvt1eH2LrvskiT561//uslxbcHNnnvu2WV72/ZVVVW5+OKL85//+Z/ZbbfdMmHChFxzzTXW3wEAYJsm4AEAgNepvLw848ePz/Lly/PYY4+9pn387Gc/S5K89NJLr3kf27qXzzDprq4Cr+TvM1xei9bW1iTJ6aef3j4b6x9/Dj/88Ndc76vR1X2aMGFC9thjj9x4441JkhtvvDHDhg3L0Ucfvcl9vfDCCxk3blwefvjhXH755fnRj36UefPmta9R03Z/t7bX85y/3MvXonq50j+s67Oxca9m+yuvvDK//vWvM3369Kxbty4XXnhh3vrWt+bPf/7za6waAAC2rD49XQAAAGwPNmzYkCRZvXp1kmTvvffOXXfdlRdffLHDLJ62S2Xtvffe7W2//vWvc/nll+ess87KQw89lH/913/NI4880mlh99bW1vzhD39on7WTJP/7v/+bJJ0Wt2+z++67Z8CAAfn973/fqe/RRx9NeXl5++yGjYUoXWmrf8mSJZ36umrb1D42Vttuu+3WflmyXXbZJS+88EKncS+fCfVyXYVk//u//5sBAwa0XyZu4MCBaWlpecXwZO+9987ixYtTKpU6PEZd1b0pjz32WIcZLUuWLElra2uH566ioiKnnnpqrrvuunzhC19IY2NjzjnnnI0GFG3uueeePPfcc2loaMjYsWPb25cuXfqKde29996ZP39+1q5d22EWzz8+j915vrqrO6+9Lam6ujrV1dX55Cc/mfvuuy+HH354vvGNb+Rzn/tcT5cGAACdmMEDAACvU3Nzc+68887ssMMO7ZdgO+6449LS0pKvfvWrHcb+x3/8R8rKyjJx4sT2bc8888yMGDEiX/7yl3Pdddfl6aefzkUXXdTlsV6+v1KplK9+9avp27dvjjrqqC7HV1RU5Jhjjsltt93W4VJgTz/9dG6++eYcccQRGTRoUJK0/+N8V0HKPxoxYkRGjx6d733ve+2hVpIsWLAgjzzyyCtun/x9HaADDzww119/fYdjLl68OHfeeWeOO+649rZ99903K1euzK9//ev2tuXLl+fWW2/tct/3339/+xo6SfKnP/0pt912W4455phUVFSkoqIiU6ZMyQ9/+MMsXry40/bPPvts+5+PO+64PPnkk5kzZ05729q1a/Otb33rVd3PNtdcc02H21dffXWStL8W2rzvfe/LX//615x77rlZvXp1+2XWNqUtAHr5jJS//e1v+drXvvaK206YMCHNzc359re/3d7W2traqd7uPF/dteOOO76q192WsmrVqvaQtk11dXXKy8uzfv36HqoKAAA2zQweAADopjvuuKN9Js4zzzyTm2++OY899lj+/d//vT0sOf744zN+/Pj8n//zf7Js2bIccMABufPOO3Pbbbflox/9aPbdd98kyec+97k89NBD+fnPf56BAwfmbW97Wz796U/nk5/8ZOrr6zv8o3m/fv3y05/+NGeccUZqampyxx135Mc//nGmT5/ePiulK5/73Ocyb968HHHEEfnIRz6SPn365Jvf/GbWr1+fL37xi+3jDjzwwFRUVOQLX/hCVq5cmcrKyrzrXe/K0KFDu9zvjBkzcuKJJ+bwww/PWWedlb/+9a/56le/mtGjR3cIfTblS1/6UiZOnJhDDz00Z599dtatW5err746gwcPzqWXXto+7uSTT84nPvGJnHTSSbnwwguzdu3afP3rX8+b3vSmDkFOm9GjR2fChAm58MILU1lZ2R50XHbZZe1jPv/5z2f+/PmpqanJOeeck7e85S15/vnn86tf/Sp33XVXnn/++STJOeeck69+9at5//vfnwcffDDDhw/PDTfc0GnNmleydOnSnHDCCTn22GNz//3358Ybb8ypp56aAw44oMO4gw46KKNHj84tt9ySN7/5zXn729/+ivs+7LDDsssuu+SMM87IhRdemLKystxwww2dLmHWlcmTJ+ed73xnpk6dmiVLlmT//ffP7bff3n7/Xz675tU+X9118MEH56677sqsWbMyYsSIVFVVpaam5jXvr7vuvvvunH/++XnPe96TN73pTdmwYUNuuOGG9iAQAAC2SSUAAOBVufbaa0tJOvz069evdOCBB5a+/vWvl1pbWzuMf/HFF0sXXXRRacSIEaW+ffuW3vjGN5a+9KUvtY978MEHS3369CldcMEFHbbbsGFD6Z//+Z9LI0aMKP31r38tlUql0hlnnFHacccdS48//njpmGOOKQ0YMKC0xx57lD7zmc+UWlpaOmyfpPSZz3ymQ9uvfvWr0oQJE0o77bRTacCAAaXx48eX7rvvvk738dvf/nZpn332KVVUVJSSlObPn7/Jx+T73/9+af/99y9VVlaWRo8eXbr99ttLU6ZMKe2///7tY5YuXVpKUvrSl77U5T7uuuuu0uGHH17q379/adCgQaXjjz++9Nvf/rbTuDvvvLM0evTo0g477FDab7/9SjfeeGPpM5/5TOkfT2uSlM4777zSjTfeWHrjG99YqqysLB100EFd3penn366dN5555X23HPPUt++fUvDhg0rHXXUUaVvfetbHcY98cQTpRNOOKE0YMCA0m677Vb6t3/7t9JPf/rTV/UYtdX429/+tlRfX18aOHBgaZdddimdf/75pXXr1nW5zRe/+MVSktKMGTM2ue+X+6//+q/SIYccUurfv39pxIgRpY9//OOln/3sZ51qPOOMM0p77713h22fffbZ0qmnnloaOHBgafDgwaUzzzyz9F//9V+lJKXvf//7Hca+muer7T4/++yzG308Xu7RRx8tjR07ttS/f/9SktIZZ5yxyf20vReXLl3a3tb2vL/cxl578+fPLyUp3XLLLaVSqVT6wx/+UPrABz5Q2nfffUv9+vUrDRkypDR+/PjSXXfd1al+AADYVpSVSq/iv3QBAAA96swzz8ycOXNe9cyYnnTggQdm9913z7x583rk+GVlZTnvvPM6XR6vSL785S/noosuyrJly7LXXnv1SA2NjY056aSTcu+99+bwww/vkRoAAICNswYPAADwmjQ3N3dat+See+7Jww8/nCOPPLJnitoOlEqlfOc738m4ceO2Wrizbt26DrdbWlpy9dVXZ9CgQa/qEnEAAMDWZw0eAADgNfnLX/6So48+OqeffnpGjBiRRx99NN/4xjcybNiwfOhDH+rp8gpnzZo1uf322zN//vw88sgjue2227basS+44IKsW7cuhx56aNavX5+Ghobcd999mTFjRvr377/V6gAAAF49AQ8AAPCa7LLLLjn44IPzn//5n3n22Wez44475t3vfnc+//nPZ9ddd+3p8grn2Wefzamnnpqdd94506dPzwknnLDVjv2ud70rV155ZebOnZuXXnopo0aNytVXX53zzz9/q9UAAAB0jzV4AAAAAAAACsYaPAAAAAAAAAUj4AEAAAAAACgYAQ8AAAAAAEDBCHgAAAAAAAAKRsADAAAAAABQMAIeAAAAAACAghHwAAAAAAAAFIyABwAAAAAAoGAEPAAAAAAAAAUj4AEAAAAAACgYAQ8AAAAAAEDBCHgAAAAAAAAKRsADAAAAAABQMAIeAAAAAACAghHwAAAAAAAAFIyABwAAAAAAoGAEPAAAAAAAAAUj4AEAAAAAACgYAQ8AAAAAAEDBCHgAAAAAAAAKRsADAAAAAABQMAIeAAAAAACAghHwAAAAAAAAFIyABwAAAAAAoGAEPAAAAAAAAAUj4AEAAAAAACgYAQ8AAAAAAEDBCHgAAAAAAAAKRsADAAAAAABQMAIeAAAAAACAghHwAAAAAAAAFIyABwAAAAAAoGAEPAAAAAAAAAUj4AEAAAAAACgYAQ8AAAAAAEDBCHgAAAAAAAAKRsADAAAAAABQMAIeAAAAAACAghHwAAAAAAAAFIyABwAA2G4sW7YsZWVlue66615x7JlnnpmRI0du8Zo2h7Kyslx66aU9XQYAALANEfAAAACb1XXXXZeysrL2n379+mXEiBGZMGFCvvKVr+TFF1/s6RI3q3e+850pKyvL17/+9a1+7N/85jc5/fTT80//9E+prKzMiBEjctppp+U3v/nNVq8FAADYuvr0dAEAAMD26fLLL09VVVWam5vz1FNP5Z577slHP/rRzJo1K7fffnve9ra3bfZj7r333lm3bl369u272ffdlcceeyy//OUvM3LkyNx000358Ic/vFWOmyQNDQ055ZRTMmTIkJx99tmpqqrKsmXL8p3vfCdz5szJ97///Zx00klbrR4AAGDrEvAAAABbxMSJE/OOd7yj/fa0adNy9913Z9KkSTnhhBPyu9/9Lv3799+sx2ybMbS13HjjjRk6dGiuvPLK1NfXZ9myZVvlsm+PP/543ve+92WfffbJwoULs/vuu7f3/du//VvGjBmT973vffn1r3+dffbZZ6P7WbNmTXbcccctXi8AALD5uUQbAACw1bzrXe/Kpz71qTzxxBO58cYb29sfffTR1NfXZ8iQIenXr1/e8Y535Pbbb++0/QsvvJCLLrooI0eOTGVlZd7whjfk/e9/f1asWJFk42vwNDY2ZvTo0enXr19Gjx6dW2+9tcv6Wltbc9VVV+Wtb31r+vXrlz322CPnnntu/vrXv3Y5/uabb059fX0mTZqUwYMH5+abb+405tJLL01ZWVmWLFmSM888MzvvvHMGDx6cs846K2vXru0wdv369bnooouy++67Z+DAgTnhhBPy5z//udM+v/SlL2Xt2rX51re+1SHcSZLddtst3/zmN7NmzZp88Ytf7FTHb3/725x66qnZZZddcsQRR3R5vwAAgG2fgAcAANiq3ve+9yVJ7rzzziR/X0fmkEMOye9+97v8+7//e6688srsuOOOmTx5cocgZvXq1RkzZkyuvvrqHHPMMfnyl7+cD33oQ3n00Ue7DEHa3HnnnZkyZUrKysoyc+bMTJ48OWeddVYeeOCBTmPPPffcfOxjH8vhhx+eL3/5yznrrLNy0003ZcKECWlubu4w9he/+EWWLFmSU045JTvssEPq6upy0003bbSO9773vXnxxRczc+bMvPe97811112Xyy67rMOYf/3Xf81VV12VY445Jp///OfTt2/fvPvd7+60rx/96EcZOXJkxowZ0+Wxxo4dm5EjR+bHP/5xp773vOc9Wbt2bWbMmJFzzjlno/UCAADbNpdoAwAAtqo3vOENGTx4cB5//PEkf7+k2F577ZVf/vKXqaysTJJ85CMfyRFHHJFPfOIT7evIfOlLX8rixYvT0NDQYW2ZT37ykymVShs93ic+8YnsscceuffeezN48OAkybhx43LMMcdk7733bh9377335j//8z9z00035dRTT21vHz9+fI499tjccsstHdpvvPHG7Lnnnjn88MOTJCeffHK++93v5qGHHsqBBx7YqY6DDjoo3/nOd9pvP/fcc/nOd76TL3zhC0mShx9+ODfeeGM+8pGP5JprrkmSnHfeeTnttNPy61//un27lStX5sknn8yJJ564qYc5b3vb23L77bfnxRdfzMCBA9vbDzjggC5nGgEAAMViBg8AALDV7bTTTnnxxRfz/PPP5+67726f3bJixYqsWLEizz33XCZMmJDHHnssf/nLX5IkP/zhD3PAAQd0CHfalJWVdXmc5cuX56GHHsoZZ5zRHu4kSW1tbd7ylrd0GHvLLbdk8ODBqa2tba9jxYoVOfjgg7PTTjtl/vz57WM3bNiQH/zgB/mXf/mX9mO/613vytChQzc6i+dDH/pQh9tjxozJc889l1WrViVJfvKTnyRJLrzwwg7jPvrRj3a4/eKLLyZJh9CmK239bfvfWB0AAEAxCXgAAICtbvXq1Rk4cGCWLFmSUqmUT33qU9l99907/HzmM59JkjzzzDNJkscffzyjR4/u1nGeeOKJJMkb3/jGTn377bdfh9uPPfZYVq5cmaFDh3aqZfXq1e11JH+/7Nuzzz6bd77znVmyZEmWLFmSpUuXZvz48Zk9e3ZaW1s7HW+vvfbqcHuXXXZJkvb1fZ544omUl5dn33333WSdbcFNW9CzMRsLgqqqqja5HQAAUAwu0QYAAGxVf/7zn7Ny5cqMGjWqPQi55JJLMmHChC7Hjxo1aqvU1drauskZOLvvvnv7n9vGvPe97+1y7IIFCzJ+/PgObRUVFV2O3dTl5boyePDgDB8+vMNl27ry61//Ov/0T/+UQYMGdWjv379/t44HAABsmwQ8AADAVnXDDTckSSZMmJB99tknSdK3b98cffTRm9xu3333zeLFi7t1rLY1dh577LFOfb///e877f+uu+7K4YcfvskQZM2aNbntttvyL//yL6mvr+/Uf+GFF+amm27qFPC8mlpbW1vz+OOPd5i18491JsmkSZPy7W9/O/fee2+OOOKITv1NTU1ZtmxZzj333G7VAAAAFIdLtAEAAFvN3Xffnc9+9rOpqqrKaaedlqFDh+bII4/MN7/5zSxfvrzT+Geffbb9z1OmTMnDDz+cW2+9tdO4jc2CGT58eA488MBcf/31WblyZXv7vHnz8tvf/rbD2Pe+971paWnJZz/72U772bBhQ1544YUkya233po1a9bkvPPOS319faefSZMm5Yc//GHWr1//qh6TNhMnTkySfOUrX+nQftVVV3Ua+7GPfSz9+/fPueeem+eee65D3/PPP58PfehDGTBgQD72sY91qwYAAKA4zOABAAC2iDvuuCOPPvpoNmzYkKeffjp333135s2bl7333ju33357+vXrlyS55pprcsQRR6S6ujrnnHNO9tlnnzz99NO5//778+c//zkPP/xwkr+HGnPmzMl73vOefOADH8jBBx+c559/Prfffnu+8Y1v5IADDuiyjpkzZ+bd7353jjjiiHzgAx/I888/n6uvvjpvfetbs3r16vZx48aNy7nnnpuZM2fmoYceyjHHHJO+ffvmscceyy233JIvf/nLqa+vz0033ZRdd901hx12WJfHO+GEE/Ltb387P/7xj1NXV/eqH68DDzwwp5xySr72ta9l5cqVOeyww/Lzn/88S5Ys6TT2jW98Y66//vqcdtppqa6uztlnn52qqqosW7Ys3/nOd7JixYrMnj2703o+AADA9kPAAwAAbBGf/vSnkyQ77LBDhgwZkurq6lx11VU566yzMnDgwPZxb3nLW/LAAw/ksssuy3XXXZfnnnsuQ4cOzUEHHdS+jyTZaaed0tTUlM985jO59dZbc/3112fo0KE56qij8oY3vGGjdRx77LG55ZZb8slPfjLTpk3Lvvvum2uvvTa33XZb7rnnng5jv/GNb+Tggw/ON7/5zUyfPj19+vTJyJEjc/rpp+fwww/PM888k7vuuiunnHLKRtfUOeqoozJgwIDceOON3Qp4kuS73/1udt9999x0001pbGzMu971rvz4xz/Onnvu2Wnse97znuy///6ZOXNme6iz6667Zvz48Zk+fXpGjx7drWMDAADFUlbq7oqeAAAAAAAA9Chr8AAAAAAAABSMgAcAAAAAAKBgBDwAAAAAAAAF062AZ+bMmfnnf/7nDBw4MEOHDs3kyZPz+9//vsOYl156Keedd1523XXX7LTTTpkyZUqefvrpDmP++Mc/5t3vfncGDBiQoUOH5mMf+1g2bNjw+u8NAAAAAABAL9CtgGfBggU577zzsmjRosybNy/Nzc055phjsmbNmvYxF110UX70ox/llltuyYIFC/Lkk0+mrq6uvb+lpSXvfve787e//S333Xdfrr/++lx33XX59Kc/vfnuFQAAAAAAwHasrFQqlV7rxs8++2yGDh2aBQsWZOzYsVm5cmV233333Hzzzamvr0+SPProo3nzm9+c+++/P4ccckjuuOOOTJo0KU8++WT22GOPJMk3vvGNfOITn8izzz6bHXbYYfPcMwAAAAAAgO1Un9ez8cqVK5MkQ4YMSZI8+OCDaW5uztFHH90+Zv/9989ee+3VHvDcf//9qa6ubg93kmTChAn58Ic/nN/85jc56KCDOh1n/fr1Wb9+ffvt1tbWPP/889l1111TVlb2eu4CAAAAAADANqFUKuXFF1/MiBEjUl6+6YuwveaAp7W1NR/96Edz+OGHZ/To0UmSp556KjvssEN23nnnDmP32GOPPPXUU+1jXh7utPW39XVl5syZueyyy15rqQAAAAAAAIXxpz/9KW94wxs2OeY1BzznnXdeFi9enHvvvfe17uJVmzZtWi6++OL22ytXrsxee+2VpUuXZuDAgVv8+FAkj/zpr3n/9f+T751xUKr33KWnywGgAFpaWtLU1JT58+dn/PjxGTNmTCoqKnq6LAAKoLm5uf3zo2/fvj1dDgAF4fMDNu7FF19MVVXVq8o+XlPAc/7552fu3LlZuHBhhwRp2LBh+dvf/pYXXnihwyyep59+OsOGDWsf89///d8d9vf000+393WlsrIylZWVndqHDBmSQYMGvZa7ANutnVcn5ZUDsvMuu2TXXXft6XIAKIgTTjghffr0yXHHHecEC6CHrF27No8++mhPl9Etq9etz0OPL88/7bU0O/XvfN6+Ldt///0zYMCAni4DoFdqbm7OgAEDsuuuuzr/gH/Q9p54NcvTdCvgKZVKueCCC3LrrbfmnnvuSVVVVYf+gw8+OH379s3Pf/7zTJkyJUny+9//Pn/84x9z6KGHJkkOPfTQ/N//+3/zzDPPZOjQoUmSefPmZdCgQXnLW97SnXIAAABgu/Hoo4/m4IMP7ukyXpMv9nQBr8GDDz6Yt7/97T1dBgDAa9atgOe8887LzTffnNtuuy0DBw5sXzNn8ODB6d+/fwYPHpyzzz47F198cfvsmgsuuCCHHnpoDjnkkCTJMccck7e85S153/vely9+8Yt56qmn8slPfjLnnXdel7N0AAAAoDfYf//98+CDD/Z0Gd3y++Uv5OJbHsms91Rnv+E793Q53bL//vv3dAkAAK9LtwKer3/960mSI488skP7tddemzPPPDNJ8h//8R8pLy/PlClTsn79+kyYMCFf+9rX2sdWVFRk7ty5+fCHP5xDDz00O+64Y84444xcfvnlr++eAAAAQIENGDCgcDNKyp94LpVN6/Lm0QfkwL1dIhoAYGvq9iXaXkm/fv1yzTXX5JprrtnomL333js/+clPunNoAAAAAAAA/p/yni4AAAAAAACA7hHwAAAAAAAAFIyABwAAAAAAoGAEPAAAAAAAAAUj4AEAAAAAACgYAQ8AAAAAAEDBCHgAAAAAAAAKRsADAAAAAABQMAIeAAAAAACAghHwAAAAAAAAFIyABwAAAAAAoGAEPAAAAAAAAAUj4AEAAAAAACgYAQ8AAAAAAEDBCHgAAAAAAAAKRsADAAAAAABQMAIeAAAAAACAghHwAAAAAAAAFIyABwAAAAAAoGAEPAAAAAAAAAUj4AEAAAAAACgYAQ8AAAAAAFtFS0tLFixYkIULF2bBggVpaWnp6ZKgsAQ8AAAAAABscQ0NDRk1alRqa2sza9as1NbWZtSoUWloaOjp0qCQBDwAAAAAAGxRDQ0Nqa+vT3V1dZqamjJ79uw0NTWluro69fX1Qh54DQQ8AAAAAABsMS0tLZk6dWomTZqUxsbG1NTUpH///qmpqUljY2MmTZqUSy65xOXaoJsEPAAAAAAAbDFNTU1ZtmxZpk+fnvLyjv8kXV5enmnTpmXp0qVpamrqoQqhmAQ8AAAAAABsMcuXL0+SjB49usv+tva2ccCrI+ABAAAAAGCLGT58eJJk8eLFXfa3tbeNA14dAQ8AAAAAAFvMmDFjMnLkyMyYMSOtra0d+lpbWzNz5sxUVVVlzJgxPVQhFJOABwAAAACALaaioiJXXnll5s6dm8mTJ2fRokVZt25dFi1alMmTJ2fu3Lm54oorUlFR0dOlQqH06ekCAAAAAADYvtXV1WXOnDmZOnVqxo4d295eVVWVOXPmpK6urgerg2IS8AAAAAAAsMXV1dXlxBNPzPz583PHHXdk4sSJGT9+vJk78BoJeAAAAAAA2CoqKioybty4rFmzJuPGjRPuwOtgDR4AAAAAAICCEfAAAAAAAAAUjIAHAAAAAACgYAQ8AAAAAAAABSPgAQAAAAAAKBgBDwAAAAAAQMEIeAAAAAAAAApGwAMAAAAAAFAwAh4AAAAAAICCEfAAAAAAAAAUjIAHAAAAAACgYAQ8AAAAAAAABSPgAQAAAAAAKBgBDwAAAAAAQMEIeAAAAAAAAApGwAMAAAAAAFAwAh4AAAAAAICCEfAAAAAAAAAUjIAHAAAAAACgYAQ8AAAAAAAABSPgAQAAAAAAKBgBDwAAAAAAQMEIeAAAAAAAAApGwAMAAAAAAFAwAh4AAAAAAICCEfAAAAAAAAAUjIAHAAAAAACgYAQ8AAAAAAAABSPgAQAAAAAAKBgBDwAAAAAAQMEIeAAAAAAAAApGwAMAAAAAAFAwAh4AAAAAAICCEfAAAAAAAAAUjIAHAAAAAACgYAQ8AAAAAAAABSPgAQAAAAAAKBgBDwAAAAAAQMEIeAAAAAAAAApGwAMAAAAAAFAwAh4AAAAAAICCEfAAAAAAAAAUjIAHAAAAAACgYAQ8AAAAAAAABSPgAQAAAAAAKBgBDwAAAAAAQMEIeAAAAAAAAApGwAMAAAAAAFAwAh4AAAAAAICC6XbAs3Dhwhx//PEZMWJEysrK0tjY2KG/rKysy58vfelL7WNGjhzZqf/zn//8674zAAAAAAAAvUG3A541a9bkgAMOyDXXXNNl//Llyzv8fPe7301ZWVmmTJnSYdzll1/eYdwFF1zw2u4BAAAAAABAL9OnuxtMnDgxEydO3Gj/sGHDOty+7bbbMn78+Oyzzz4d2gcOHNhpLAAAAAAAAK+s2wFPdzz99NP58Y9/nOuvv75T3+c///l89rOfzV577ZVTTz01F110Ufr06bqc9evXZ/369e23V61alSRpbm5Oc3PzlikeCmrDhg3tv70/AHi12j4zfHYA0B3OPwB4LZx/wMZ1532xRQOe66+/PgMHDkxdXV2H9gsvvDBvf/vbM2TIkNx3332ZNm1ali9fnlmzZnW5n5kzZ+ayyy7r1H7nnXdmwIABW6R2KKo/rU6SPlm0aFH+srinqwGgaObNm9fTJQBQIM4/AHg9nH9AZ2vXrn3VY8tKpVLptR6orKwst956ayZPntxl//7775/a2tpcffXVm9zPd7/73Zx77rlZvXp1KisrO/V3NYNnzz33zIoVKzJo0KDXWj5slx7+4/Op//YDmXPOO3LAXkN6uhwACqK5uTnz5s1LbW1t+vbt29PlAFAQzj8AeC2cf8DGrVq1KrvttltWrlz5ivnHFpvB09TUlN///vf5wQ9+8Ipja2pqsmHDhixbtiz77bdfp/7Kysoug5++ffv6CwD+QdulDvv06eP9AUC3+X4FQHc4/wDg9XD+AZ115z1RvqWK+M53vpODDz44BxxwwCuOfeihh1JeXp6hQ4duqXIAAAAAAAC2G92ewbN69eosWbKk/fbSpUvz0EMPZciQIdlrr72S/H0K0S233JIrr7yy0/b3339/fvGLX2T8+PEZOHBg7r///lx00UU5/fTTs8suu7yOuwIAAAAAANA7dDvgeeCBBzJ+/Pj22xdffHGS5Iwzzsh1112XJPn+97+fUqmUU045pdP2lZWV+f73v59LL70069evT1VVVS666KL2/QAAAAAAALBp3Q54jjzyyJRKpU2O+eAHP5gPfvCDXfa9/e1vz6JFi7p7WAAAAAAAAP6fLbYGDwAAAAAAAFuGgAcAAAAAAKBgBDwAAAAAAAAFI+ABAAAAAAAoGAEPAAAAAABAwQh4AAAAAAAACkbAAwAAAAAAUDACHgAAAAAAgIIR8AAAAAAAABSMgAcAAAAAAKBgBDwAAAAAAAAFI+ABAAAAAAAoGAEPAAAAAABAwQh4AAAAAAAACkbAAwAAAAAAUDACHgAAAAAAgIIR8AAAAAAAABSMgAcAAAAAAKBgBDwAAAAAAAAFI+ABAAAAAAAoGAEPAAAAAABAwQh4AAAAAAAACkbAAwAAAAAAUDACHgAAAAAAgIIR8AAAAAAAABSMgAcAAAAAAKBgBDwAAAAAAAAFI+ABAAAAAAAoGAEPAAAAAABAwQh4AAAAAAAACkbAAwAAAAAAUDACHgAAAAAAgIIR8AAAAAAAABSMgAcAAAAAAKBgBDwAAAAAAAAFI+ABAAAAAAAoGAEPAAAAAABAwQh4AAAAAAAACkbAAwAAAAAAUDACHgAAAAAAgIIR8AAAAAAAABSMgAcAAAAAAKBgBDwAAAAAAAAFI+ABAAAAAAAoGAEPAAAAAABAwQh4AAAAAAAACkbAAwAAAAAAUDACHgAAAAAAgIIR8AAAAAAAABSMgAcAAAAAAKBgBDwAAAAAAAAFI+ABAAAAAAAoGAEPAAAAAABAwQh4AAAAAAAACkbAAwAAAAAAUDACHgAAAAAAgIIR8AAAAAAAsFW0tLRkwYIFWbhwYRYsWJCWlpaeLgkKS8ADAAAAAMAW19DQkFGjRqW2tjazZs1KbW1tRo0alYaGhp4uDQpJwAMAAAAAwBbV0NCQ+vr6VFdXp6mpKbNnz05TU1Oqq6tTX18v5IHXQMADAAAAAMAW09LSkqlTp2bSpElpbGxMTU1N+vfvn5qamjQ2NmbSpEm55JJLXK4NuknAAwAAAADAFtPU1JRly5Zl+vTpKS/v+E/S5eXlmTZtWpYuXZqmpqYeqhCKScADAAAAAMAWs3z58iTJ6NGju+xva28bB7w6Ah4AAAAAALaY4cOHJ0kWL17cZX9be9s44NUR8AAAAAAAsMWMGTMmI0eOzIwZM9La2tqhr7W1NTNnzkxVVVXGjBnTQxVCMQl4AAAAAADYYioqKnLllVdm7ty5mTx5chYtWpR169Zl0aJFmTx5cubOnZsrrrgiFRUVPV0qFEqfni4AAAAAAIDtW11dXebMmZOpU6dm7Nix7e1VVVWZM2dO6urqerA6KCYBDwAAAAAAW1xdXV1OPPHEzJ8/P3fccUcmTpyY8ePHm7kDr5GABwAAAACAraKioiLjxo3LmjVrMm7cOOEOvA7W4AEAAAAAACgYAQ8AAAAAAEDBCHgAAAAAAAAKRsADAAAAAABQMAIeAAAAAACAghHwAAAAAAAAFIyABwAAAAAAoGAEPAAAAAAAAAUj4AEAAAAAACgYAQ8AAAAAAEDBCHgAAAAAAAAKRsADAAAAAABQMAIeAAAAAACAghHwAAAAAAAAFIyABwAAAAAAoGC6HfAsXLgwxx9/fEaMGJGysrI0NjZ26D/zzDNTVlbW4efYY4/tMOb555/PaaedlkGDBmXnnXfO2WefndWrV7+uOwIAAAAAANBbdDvgWbNmTQ444IBcc801Gx1z7LHHZvny5e0/s2fP7tB/2mmn5Te/+U3mzZuXuXPnZuHChfngBz/Y/eoBAAAAAAB6oT7d3WDixImZOHHiJsdUVlZm2LBhXfb97ne/y09/+tP88pe/zDve8Y4kydVXX53jjjsuV1xxRUaMGNHdkgAAAAAAAHqVbgc8r8Y999yToUOHZpdddsm73vWufO5zn8uuu+6aJLn//vuz8847t4c7SXL00UenvLw8v/jFL3LSSSd12t/69euzfv369turVq1KkjQ3N6e5uXlL3AUorA0bNrT/9v4A4NVq+8zw2QFAdzj/AOC1cP4BG9ed98VmD3iOPfbY1NXVpaqqKo8//nimT5+eiRMn5v77709FRUWeeuqpDB06tGMRffpkyJAheeqpp7rc58yZM3PZZZd1ar/zzjszYMCAzX0XoND+tDpJ+mTRokX5y+KergaAopk3b15PlwBAgTj/AOD1cP4Bna1du/ZVj93sAc/JJ5/c/ufq6uq87W1vy7777pt77rknRx111Gva57Rp03LxxRe33161alX23HPPHHPMMRk0aNDrrhm2Jw//8fnkkQdyyCGH5IC9hvR0OQAURHNzc+bNm5fa2tr07du3p8sBoCCcfwDwWjj/gI1ru4LZq7FFLtH2cvvss0922223LFmyJEcddVSGDRuWZ555psOYDRs25Pnnn9/ouj2VlZWprKzs1N63b19/AcA/6NOnT/tv7w8Ausv3KwC6w/kHAK+H8w/orDvvifItWEeS5M9//nOee+65DB8+PEly6KGH5oUXXsiDDz7YPubuu+9Oa2trampqtnQ5AAAAAAAAhdftGTyrV6/OkiVL2m8vXbo0Dz30UIYMGZIhQ4bksssuy5QpUzJs2LA8/vjj+fjHP55Ro0ZlwoQJSZI3v/nNOfbYY3POOefkG9/4Rpqbm3P++efn5JNPzogRIzbfPQMAAAAAANhOdXsGzwMPPJCDDjooBx10UJLk4osvzkEHHZRPf/rTqaioyK9//euccMIJedOb3pSzzz47Bx98cJqamjpcYu2mm27K/vvvn6OOOirHHXdcjjjiiHzrW9/afPcKAAAAAABgO9btGTxHHnlkSqXSRvt/9rOfveI+hgwZkptvvrm7hwYAAAAAACBbYQ0eAAAAAAAANi8BDwAAAAAAQMEIeAAAAAAAAApGwAMAAAAAAFAwAh4AAAAAAICCEfAAAAAAAAAUjIAHAAAAAACgYAQ8AAAAAAAABSPgAQAAAAAAKBgBDwAAAAAAQMEIeAAAAAAAAApGwAMAAAAAAFAwAh4AAAAAAICCEfAAAAAAAAAUjIAHAAAAAACgYAQ8AAAAAAAABSPgAQAAAAAAKBgBDwAAAAAAQMEIeAAAAAAAAApGwAMAAAAAAFAwAh4AAAAAAICCEfAAAAAAAAAUjIAHAAAAAACgYAQ8AAAAAAAABSPgAQAAAAAAKBgBDwAAAAAAQMEIeAAAermWlpYsWLAgCxcuzIIFC9LS0tLTJQEAAACvQMADANCLNTQ0ZNSoUamtrc2sWbNSW1ubUaNGpaGhoadLAwAAADZBwAMA0Es1NDSkvr4+1dXVaWpqyuzZs9PU1JTq6urU19cLeQAAAGAbJuABAOiFWlpaMnXq1EyaNCmNjY2pqalJ//79U1NTk8bGxkyaNCmXXHKJy7UBAADANkrAAwDQCzU1NWXZsmWZPn16yss7fiUsLy/PtGnTsnTp0jQ1NfVQhQAAAMCmCHgAAHqh5cuXJ0lGjx7dZX9be9s4AAAAYNsi4AEA6IWGDx+eJFm8eHGX/W3tbeMAAACAbYuABwCgFxozZkxGjhyZGTNmpLW1tUNfa2trZs6cmaqqqowZM6aHKgQAAAA2RcADANALVVRU5Morr8zcuXMzefLkLFq0KOvWrcuiRYsyefLkzJ07N1dccUUqKip6ulQAAACgC316ugAAAHpGXV1d5syZk6lTp2bs2LHt7VVVVZkzZ07q6up6sDoAAABgUwQ8AAC9WF1dXU488cTMnz8/d9xxRyZOnJjx48ebuQMAAADbOJdoAwAAAAAAKBgBDwBAL9bQ0JBRo0altrY2s2bNSm1tbUaNGpWGhoaeLg0AAADYBAEPAEAv1dDQkPr6+lRXV6epqSmzZ89OU1NTqqurU19fL+QBAACAbZiABwCgF2ppacnUqVMzadKkNDY2pqamJv37909NTU0aGxszadKkXHLJJWlpaenpUgEAAIAuCHgAAHqhpqamLFu2LNOnT095ecevhOXl5Zk2bVqWLl2apqamHqoQAAAA2BQBDwBAL7R8+fIkyejRo7vsb2tvGwcAAABsWwQ8AAC90PDhw5Mkixcv7rK/rb1tHAAAALBtEfAAAPRCY8aMyciRIzNjxoy0trZ26Gttbc3MmTNTVVWVMWPG9FCFAAAAwKYIeAAAeqGKiopceeWVmTt3biZPnpxFixZl3bp1WbRoUSZPnpy5c+fmiiuuSEVFRU+XCgAAAHShT08XAABAz6irq8ucOXMyderUjB07tr29qqoqc+bMSV1dXQ9WBwAAAGyKgAcAoBerq6vLiSeemPnz5+eOO+7IxIkTM378eDN3AAAAYBsn4AEA6OUqKioybty4rFmzJuPGjRPuAAAAQAFYgwcAAAAAAKBgBDwAAAAAAAAFI+ABAAAAAAAoGAEPAAAAAABAwQh4AAAAAAAACkbAAwAAAAAAUDACHgAAAAAAgIIR8AAAAAAAABSMgAcAAAAAAKBgBDwAAAAAAAAFI+ABAAAAAAAoGAEPAAAAAABAwQh4AAAAAAAACkbAAwAAAAAAUDACHgAAAAAAgIIR8AAAAAAAABSMgAcAAAAAAKBgBDwAAAAAAAAFI+ABAAAAAAAoGAEPAAAAAABAwQh4AAAAAAAACkbAAwAAAAAAUDACHgAAAAAAgIIR8AAAAAAAABSMgAcAAAAAAKBgBDwAAAAAAAAFI+ABAAAAAAAoGAEPAAAAAABAwQh4AAAAAAAACkbAAwAAAAAAUDACHgAAAAAAgIIR8AAAAAAAABSMgAcAAAAAAKBgBDwAAAAAAAAF0+2AZ+HChTn++OMzYsSIlJWVpbGxsb2vubk5n/jEJ1JdXZ0dd9wxI0aMyPvf//48+eSTHfYxcuTIlJWVdfj5/Oc//7rvDAAAAAAAQG/Q7YBnzZo1OeCAA3LNNdd06lu7dm1+9atf5VOf+lR+9atfpaGhIb///e9zwgkndBp7+eWXZ/ny5e0/F1xwwWu7BwAAAAAAAL1Mn+5uMHHixEycOLHLvsGDB2fevHkd2r761a/mne98Z/74xz9mr732am8fOHBghg0b1t3DAwAAAAAA9HrdDni6a+XKlSkrK8vOO+/cof3zn/98PvvZz2avvfbKqaeemosuuih9+nRdzvr167N+/fr226tWrUry90vCNTc3b7HaoYg2bNjQ/tv7A4BXq+0zw2cHAN3h/AOA18L5B2xcd94XWzTgeemll/KJT3wip5xySgYNGtTefuGFF+btb397hgwZkvvuuy/Tpk3L8uXLM2vWrC73M3PmzFx22WWd2u+8884MGDBgi9UPRfSn1UnSJ4sWLcpfFvd0NQAUzT/OxgaATXH+AcDr4fwDOlu7du2rHltWKpVKr/VAZWVlufXWWzN58uROfc3NzZkyZUr+/Oc/55577ukQ8Pyj7373uzn33HOzevXqVFZWdurvagbPnnvumRUrVmxyv9AbPfzH51P/7Qcy55x35IC9hvR0OQAURHNzc+bNm5fa2tr07du3p8sBoCCcfwDwWjj/gI1btWpVdtttt6xcufIV848tMoOnubk5733ve/PEE0/k7rvvfsUiampqsmHDhixbtiz77bdfp/7Kysoug5++ffv6CwD+QdulDvv06eP9AUC3+X4FQHc4/wDg9XD+AZ115z2x2QOetnDnsccey/z587Prrru+4jYPPfRQysvLM3To0M1dDgAAAAAAwHan2wHP6tWrs2TJkvbbS5cuzUMPPZQhQ4Zk+PDhqa+vz69+9avMnTs3LS0teeqpp5IkQ4YMyQ477JD7778/v/jFLzJ+/PgMHDgw999/fy666KKcfvrp2WWXXTbfPQMAAAAAANhOdTvgeeCBBzJ+/Pj22xdffHGS5Iwzzsill16a22+/PUly4IEHdthu/vz5OfLII1NZWZnvf//7ufTSS7N+/fpUVVXloosuat8PAAAAAAAAm9btgOfII49MqVTaaP+m+pLk7W9/exYtWtTdwwIAAAAAAPD/lPd0AQAAAAAAAHSPgAcAAAAAAKBgBDwAAAAAAAAFI+ABAAAAAAAoGAEPAAAAAABAwQh4AAAAAAAACkbAAwAAAAAAUDACHgAAAAAAgIIR8AAAAAAAABSMgAcAAAAAAKBgBDwAAL1cS0tLFixYkIULF2bBggVpaWnp6ZIAAACAVyDgAQDoxRoaGjJq1KjU1tZm1qxZqa2tzahRo9LQ0NDTpQEAAACbIOABAOilGhoaUl9fn+rq6jQ1NWX27NlpampKdXV16uvrhTwAAACwDRPwAAD0Qi0tLZk6dWomTZqUxsbG1NTUpH///qmpqUljY2MmTZqUSy65xOXaAAAAYBsl4AEA6IWampqybNmyTJ8+PeXlHb8SlpeXZ9q0aVm6dGmampp6qEIAAABgUwQ8AAC90PLly5Mko0eP7rK/rb1tHAAAALBtEfAAAPRCw4cPT5IsXry4y/629rZxAAAAwLZFwAMA0AuNGTMmI0eOzIwZM9La2tqhr7W1NTNnzkxVVVXGjBnTQxUCAAAAmyLgAQDohSoqKnLllVdm7ty5mTx5chYtWpR169Zl0aJFmTx5cubOnZsrrrgiFRUVPV0qAAAA0IU+PV0AAAA9o66uLnPmzMnUqVMzduzY9vaqqqrMmTMndXV1PVgdAAAAsCkCHgCAXqyuri4nnnhi5s+fnzvuuCMTJ07M+PHjzdwBAACAbZyABwCgl6uoqMi4ceOyZs2ajBs3TrgDAAAABWANHgAAAAAAgIIR8AAAAAAAABSMgAcAAAAAAKBgBDwAAAAAAAAFI+ABAAAAAAAoGAEPAAAAAABAwQh4AAAAAAAACkbAAwAAAAAAUDACHgAAAAAAgIIR8AAAAAAAABSMgAcAAAAAAKBgBDwAAAAAAAAFI+ABAAAAAAAoGAEPAAAAAABAwQh4AAAAAAAACkbAAwAAAAAAUDACHgAAAAAAgIIR8AAAAAAAABSMgAcAAAAAAKBgBDwAAAAAAAAFI+ABAAAAAAAoGAEPAAAAAABAwQh4AAAAAAAACkbAAwAAAAAAUDACHgAAAAAAgIIR8AAAAAAAABSMgAcAAAAAAKBgBDwAAAAAAAAFI+ABAAAAAAAoGAEPAAAAAABAwQh4AAAAAAAACkbAAwAAAAAAUDACHgAAAAAAgIIR8AAAAAAAABSMgAcAAAAAAKBgBDwAAAAAAAAFI+ABAAAAAAAoGAEPAAAAAABAwQh4AAAAAAAACkbAAwAAAAAAUDACHgAAAAAAgIIR8AAAAAAAABSMgAcAAAAAAKBgBDwAAAAAAAAFI+ABAAAAAAAoGAEPAAAAAABAwQh4AAAAAAAACkbAAwAAAAAAUDACHgAAAAAAgILp09MFAAAAwJawdMWarFm/oafL2K49/uya9t99+vgnhi1px8o+qdptx54uAwDYhvj2BQAAwHZn6Yo1GX/FPT1dRq8xdc4jPV1CrzD/kiOFPABAOwEPAAAA2522mTtX/cuBGTV0px6uZvu1Zt36zL3n/kw68tDs2L+yp8vZbi15ZnU++oOHzEgDADoQ8AAAALDdGjV0p4z+p8E9XcZ2q7m5OU/tnrx9713St2/fni4HAKBXKe/pAgAAAAAAAOgeAQ8AAAAAAEDBCHgAAAAAAAAKRsADAAAAAABQMAIeAAAAAACAghHwAAAAAAAAFIyABwAAAAAAoGAEPAAAAAAAAAXT7YBn4cKFOf744zNixIiUlZWlsbGxQ3+pVMqnP/3pDB8+PP3798/RRx+dxx57rMOY559/PqeddloGDRqUnXfeOWeffXZWr179uu4IAAAAAABAb9HtgGfNmjU54IADcs0113TZ/8UvfjFf+cpX8o1vfCO/+MUvsuOOO2bChAl56aWX2secdtpp+c1vfpN58+Zl7ty5WbhwYT74wQ++9nsBAAAAAADQi/Tp7gYTJ07MxIkTu+wrlUq56qqr8slPfjInnnhikuR73/te9thjjzQ2Nubkk0/O7373u/z0pz/NL3/5y7zjHe9Iklx99dU57rjjcsUVV2TEiBGv4+4AAAAAAABs/7od8GzK0qVL89RTT+Xoo49ubxs8eHBqampy//335+STT87999+fnXfeuT3cSZKjjz465eXl+cUvfpGTTjqp037Xr1+f9evXt99etWpVkqS5uTnNzc2b8y5A4W3YsKH9t/cHAK9W22eGzw5ge+F78dbh82Pr8HoGtjc+P2DjuvO+2KwBz1NPPZUk2WOPPTq077HHHu19Tz31VIYOHdqxiD59MmTIkPYx/2jmzJm57LLLOrXfeeedGTBgwOYoHbYbf1qdJH2yaNGi/GVxT1cDQNHMmzevp0sA2Czavhffe++9eWKnnq5m++fzY8vyega2Vz4/oLO1a9e+6rGbNeDZUqZNm5aLL764/faqVauy55575phjjsmgQYN6sDLY9jz8x+eTRx7IIYcckgP2GtLT5QBQEM3NzZk3b15qa2vTt2/fni4H4HX7zZOrcsUji3LEEUfkrSOcN24pPj+2Dq9nYHvj8wM2ru0KZq/GZg14hg0bliR5+umnM3z48Pb2p59+OgceeGD7mGeeeabDdhs2bMjzzz/fvv0/qqysTGVlZaf2vn37+gsA/kGfPn3af3t/ANBdvl8B2wvfi7cunx9bltczsL3y+QGddec9Ub45D1xVVZVhw4bl5z//eXvbqlWr8otf/CKHHnpokuTQQw/NCy+8kAcffLB9zN13353W1tbU1NRsznIAAAAAAAC2S92ewbN69eosWbKk/fbSpUvz0EMPZciQIdlrr73y0Y9+NJ/73Ofyxje+MVVVVfnUpz6VESNGZPLkyUmSN7/5zTn22GNzzjnn5Bvf+Eaam5tz/vnn5+STT86IESM22x0DAAAAAADYXnU74HnggQcyfvz49ttta+OcccYZue666/Lxj388a9asyQc/+MG88MILOeKII/LTn/40/fr1a9/mpptuyvnnn5+jjjoq5eXlmTJlSr7yla9shrsDAAAAAACw/et2wHPkkUemVCpttL+srCyXX355Lr/88o2OGTJkSG6++ebuHhoAAAAAAIBs5jV4AAAAAAAA2PIEPAAAAAAAAAUj4AEAAAAAACgYAQ8AAAAAAEDBCHgAAAAAAAAKRsADAAAAAABQMAIeAIBerqWlJQsWLMjChQuzYMGCtLS09HRJAAAAwCsQ8AAA9GINDQ0ZNWpUamtrM2vWrNTW1mbUqFFpaGjo6dIAAACATRDwAAD0Ug0NDamvr091dXWampoye/bsNDU1pbq6OvX19UIeAAAA2IYJeAAAeqGWlpZMnTo1kyZNSmNjY2pqatK/f//U1NSksbExkyZNyiWXXOJybQAAALCNEvAAAPRCTU1NWbZsWaZPn57y8o5fCcvLyzNt2rQsXbo0TU1NPVQhAAAAsCkCHgCAXmj58uVJktGjR3fZ39beNg4AAADYtgh4AAB6oeHDhydJFi9e3GV/W3vbOAAAAGDbIuABAOiFxowZk5EjR2bGjBlpbW3t0Nfa2pqZM2emqqoqY8aM6aEKAQAAgE0R8AAA9EIVFRW58sorM3fu3EyePDmLFi3KunXrsmjRokyePDlz587NFVdckYqKip4uFQAAAOhCn54uAACAnlFXV5c5c+Zk6tSpGTt2bHt7VVVV5syZk7q6uh6sDgAAANgUAQ8AQC9WV1eXE088MfPnz88dd9yRiRMnZvz48WbuAAAAwDZOwAMA0MtVVFRk3LhxWbNmTcaNGyfcAQAAgAKwBg8AAAAAAEDBCHgAAAAAAAAKRsADAAAAAABQMAIeAAAAAACAghHwAAAAAAAAFIyABwAAAAAAoGAEPAAAAAAAAAUj4AEAAAAAACgYAQ8AAAAAAEDBCHgAAAAAAAAKRsADAAAAAABQMAIeAAAAAACAghHwAAAAAAAAFEyfni6A3mXpijVZs35DT5exXXv82TXtv/v08Rbfknas7JOq3Xbs6TIAAAAAgF7Iv/6y1SxdsSbjr7inp8voNabOeaSnS+gV5l9ypJAHAAAAANjqBDxsNW0zd676lwMzauhOPVzN9mvNuvWZe8/9mXTkodmxf2VPl7PdWvLM6nz0Bw+ZkQYAAAAA9AgBD1vdqKE7ZfQ/De7pMrZbzc3NeWr35O1775K+ffv2dDkAAAAAAGwB5T1dAAAAAAAAAN0j4AEAAAAAACgYAQ8AAAAAAEDBCHgAAAAAAAAKRsADAAAAAABQMAIeAAAAAACAghHwAAAAAAAAFIyABwAAAAAAoGAEPAAAAAAAAAUj4AEAAAAAACgYAQ8AAAAAAEDBCHgAAAAAAAAKRsADAAAAAABQMAIeAAAAAACAghHwAAAAAAAAFIyABwAAAAAAoGAEPAAAAAAAAAUj4AEAAAAAACgYAQ8AAAAAAEDBCHgAAAAAAAAKRsADAAAAAABQMAIeAAAAAACAghHwAAAAAAAAFIyABwAAAAAAoGAEPAAAAAAAAAUj4AEAAAAAACgYAQ8AAAAAAEDBCHgAAAAAAAAKRsADAAAAAABQMAIeAAAAAACAghHwAAAAAAAAFIyABwAAAAAAoGAEPAAAAAAAAAUj4AEAAAAAACgYAQ8AAAAAAEDBCHgAAAAAAAAKRsADAAAAAABQMAIeAAAAAACAghHwAAAAAAAAFIyABwAAAAAAoGAEPAAAAAAAAAUj4AEAAAAAACgYAQ8AAAAAAEDBCHgAAAAAAAAKRsADAAAAAABQMAIeAAAAAACAghHwAAAAAAAAFMxmD3hGjhyZsrKyTj/nnXdekuTII4/s1PehD31oc5cBAAAAAACw3eqzuXf4y1/+Mi0tLe23Fy9enNra2rznPe9pbzvnnHNy+eWXt98eMGDA5i4DAAAAAABgu7XZA57dd9+9w+3Pf/7z2XfffTNu3Lj2tgEDBmTYsGGb+9AAAAAAAAC9wmYPeF7ub3/7W2688cZcfPHFKSsra2+/6aabcuONN2bYsGE5/vjj86lPfWqTs3jWr1+f9evXt99etWpVkqS5uTnNzc1b7g6wWW3YsKH9t+dty2l7bD3GW5bXM7C98fkBbG98X9s6fH5sHV7PwPbG5wdsXHfeF1s04GlsbMwLL7yQM888s73t1FNPzd57750RI0bk17/+dT7xiU/k97//fRoaGja6n5kzZ+ayyy7r1H7nnXe6vFuB/Gl1kvTJvffemyd26ulqtn/z5s3r6RK2a17PwPbK5wewvfB9bevy+bFleT0D2yufH9DZ2rVrX/XYslKpVNpShUyYMCE77LBDfvSjH210zN13352jjjoqS5Ysyb777tvlmK5m8Oy5555ZsWJFBg0atNnrZsv4zZOrMvnri9L44UPy1hGety2lubk58+bNS21tbfr27dvT5Wy3vJ6B7Y3PD2B74/va1uHzY+vwega2Nz4/YONWrVqV3XbbLStXrnzF/GOLzeB54oknctddd21yZk6S1NTUJMkmA57KyspUVlZ2au/bt6+/AAqkT58+7b89b1ue98eW5fUMbK98fgDbC9/Xti6fH1uW1zOwvfL5AZ115z1RvqWKuPbaazN06NC8+93v3uS4hx56KEkyfPjwLVUKAACb0NLSkgULFmThwoVZsGBBWlpaerokAAAA4BVskYCntbU11157bc4444z2/2WSJI8//ng++9nP5sEHH8yyZcty++235/3vf3/Gjh2bt73tbVuiFAAANqGhoSGjRo1KbW1tZs2aldra2owaNeoVZ2EDAAAAPWuLBDx33XVX/vjHP+YDH/hAh/Yddtghd911V4455pjsv//+mTp1aqZMmbLJNXoAANgyGhoaUl9fn+rq6jQ1NWX27NlpampKdXV16uvrhTwAAACwDdsia/Acc8wxKZVKndr33HPPLFiwYEscEgCAbmhpacnUqVMzadKkNDY2pqWlJc8991xqamrS2NiYyZMn55JLLsmJJ56YioqKni4XAAAA+AdbbA0eAAC2XU1NTVm2bFmmT5+e8vKOXwnLy8szbdq0LF26NE1NTT1UIQAAALApAh4AgF5o+fLlSZLRo0d32d/W3jYOAAAA2LYIeAAAeqHhw4cnSRYvXtxlf1t72zgAAABg2yLgAQDohcaMGZORI0dmxowZaW1t7dDX2tqamTNnpqqqKmPGjOmhCgEAAIBNEfAAAPRCFRUVufLKKzN37txMnjw5ixYtyrp167Jo0aJMnjw5c+fOzRVXXJGKioqeLhUAAADoQp+eLgAAgJ5RV1eXOXPmZOrUqRk7dmx7e1VVVebMmZO6uroerA4AAADYFAEPAEAvVldXlxNPPDHz58/PHXfckYkTJ2b8+PFm7gAAAMA2TsADANDLVVRUZNy4cVmzZk3GjRsn3AEAAIACsAYPAAAAAABAwQh4AAAAAAAACkbAAwAAAAAAUDACHgAAAAAAgIIR8AAAAAAAABSMgAcAAAAAAKBgBDwAAAAAAAAFI+ABAOjlWlpasmDBgixcuDALFixIS0tLT5cEAAAAvAIBDwBAL9bQ0JBRo0altrY2s2bNSm1tbUaNGpWGhoaeLg0AAADYBAEPAEAv1dDQkPr6+lRXV6epqSmzZ89OU1NTqqurU19fL+QBAACAbZiABwCgF2ppacnUqVMzadKkNDY2pqamJv37909NTU0aGxszadKkXHLJJS7XBgAAANsoAQ8AQC/U1NSUZcuWZfr06Skv7/iVsLy8PNOmTcvSpUvT1NTUQxUCAAAAmyLgAQDohZYvX54kGT16dJf9be1t4wAAAIBti4AHAKAXGj58eJJk8eLFXfa3tbeNAwAAALYtAh4AgF5ozJgxGTlyZGbMmJHW1tYOfa2trZk5c2aqqqoyZsyYHqoQAAAA2BQBDwBAL1RRUZErr7wyc+fOzeTJk7No0aKsW7cuixYtyuTJkzN37txcccUVqaio6OlSAQAAgC706ekCAADoGXV1dZkzZ06mTp2asWPHtrdXVVVlzpw5qaur68HqAAAAgE0R8AAA9GJ1dXU58cQTM3/+/Nxxxx2ZOHFixo8fb+YOAAAAbOMEPAAAvVxFRUXGjRuXNWvWZNy4ccIdAAAAKABr8AAAAAAAABSMgAcAAAAAAKBgBDwAAAAAAAAFI+ABAAAAAAAoGAEPAAAAAABAwQh4AAAAAAAACkbAAwAAAAAAUDACHgAAAAAAgIIR8AAAAAAAABSMgAcAAAAAAKBgBDwAAAAAAAAFI+ABAAAAAAAoGAEPAAAAAABAwQh4AAAAAAAACkbAAwAAAAAAUDACHgAAAAAAgIIR8AAAAAAAABSMgAcAAAAAAKBgBDwAAAAAAAAFI+ABAAAAAAAoGAEPAAAAAABAwQh4AAB6uZaWlixYsCALFy7MggUL0tLS0tMlAQAAAK9AwAMA0Is1NDRk1KhRqa2tzaxZs1JbW5tRo0aloaGhp0sDAAAANkHAAwDQSzU0NKS+vj7V1dVpamrK7Nmz09TUlOrq6tTX1wt5AAAAYBsm4AEA6IVaWloyderUTJo0KY2NjampqUn//v1TU1OTxsbGTJo0KZdcconLtQEAAMA2SsADANALNTU1ZdmyZZk+fXpKpVKHNXhKpVKmTZuWpUuXpqmpqadLBQAAALog4AEA6IWWL1+eJHn88ce7XIPnD3/4Q4dxAAAAwLZFwAMA0AsNHz48SXL66ad3uQbP6aef3mEcAAAAsG3p09MFAACw9R122GHp06dPdt111zQ0NKRUKuW5555LTU1NGhoa8oY3vCHPPfdcDjvssJ4uFQAAAOiCgAcAoBe67777smHDhjzzzDM56aSTUltbm8ceeyxPPPFE5s2bl2eeeSalUin33XdfjjzyyJ4uFwAAAPgHAh4AgF6obW2dCy+8MNdcc03mzp3b3tenT59ceOGF+fKXv2wNHgAAANhGCXgAAHqhtrV1vvzlL2fSpEk55phj8r//+79505velDvvvDNf/vKXO4wDAAAAti0CHgCAXujla/DceuutKZVK+clPfpLjjjsuH/7wh63BAwAAANu48p4uAACAre/la/DU1dVl0aJFWbduXRYtWpS6uro888wz2bBhQ+67776eLhUAANiOtLS0ZMGCBVm4cGEWLFiQlpaWni4JCkvAAwDQC7WtrXPDDTfkkUceydixY3PKKadk7NixWbx4cW644YYO4wAAAF6vhoaGjBo1KrW1tZk1a1Zqa2szatSoNDQ09HRpUEgCHgCAXqhtbZ199903S5Ysybx583LxxRdn3rx5eeyxx7LPPvt0GAcAAPB6NDQ0pL6+PtXV1Wlqasrs2bPT1NSU6urq1NfXC3ngNRDwAAD0QmPGjMnIkSMzY8aMtLa2duhrbW3NzJkzU1VVlTFjxvRQhQAAwPaipaUlU6dOzaRJk9LY2Jiampr0798/NTU1aWxszKRJk3LJJZe4XBt0U5+eLgAAgK2voqIiV155ZaZMmZLBgwdn3bp1SZJZs2alf//+WbduXX74wx+moqKihysFAACKrqmpKcuWLcvs2bNTXl7eIcgpLy/PtGnTcthhh6WpqSlHHnlkzxUKBWMGDwBAL1ZWVtZlW1ftAAAAr0Xb2p6jR4/usr+t3Rqg0D0CHgCAXujll0hYuXJlhzV4XnjhBZdIAAAANpu2tT0XL17cZX9buzVAoXsEPAAAvVDbJRKmT5+e8vKOXwnbLpGwdOnSNDU19VCFAADA9uLla4A2NzdnwYIFWbhwYRYsWJDm5mZrgMJrZA0eAIBeqO3SB48//nhOOeWULFu2LMnf1+AZOXJkPve5z3UYBwAA8FpZAxS2DDN4AAB6obZLH5x++umprq5OU1NTZs+enaamplRXV+f000/vMA4AAOD1sgYobF4CHgCAXuiwww5Lnz59sscee6ShoSE1NTXp379/ampq0tDQkD322CN9+vTJYYcd1tOlAgAABWcNUNgyBDwAAL3Qfffdlw0bNuSZZ55JXV1dFi1alHXr1mXRokWpq6vLM888kw0bNuS+++7r6VIBAICCe/kaoH379s24ceMyduzYjBs3Ln379rUGKLxGAh4AgF6obW2dG264IY888kjGjh2bU045JWPHjs3ixYtzww03dBgHAADwWrWdV4wePbrL/rZ25x/QPQIeAIBeqG1tnX333TdLlizpcImExx57LPvss0+HcQAAAK9V23nF4sWLu+xva3f+Ad0j4AEA6IXGjBmTkSNHZsaMGSkrK+twiYSysrLMnDkzVVVVGTNmTE+XCgAAFNzLzz9aW1s79LW2tjr/gNdIwAMA0AtVVFTkyiuvzNy5czN58uQOa/BMnjw5c+fOzRVXXJGKioqeLhUAACg45x+wZfTp6QIAAOgZdXV1mTNnTqZOnZqxY8e2t1dVVWXOnDmpq6vrweoAAIDtifMP2PwEPAAAvVhdXV1OPPHEzJ8/P3fccUcmTpyY8ePH+59zAADAZuf8AzYvAQ8AAAAAAFtFRUVFxo0blzVr1mTcuHHCHXgdrMEDANCLNTQ0ZNSoUamtrc2sWbNSW1ubUaNGpaGhoadLAwAAADZBwAMA0Es1NDSkvr4+1dXVaWpqyuzZs9PU1JTq6urU19cLeQAAAGAbJuABAOiFWlpaMnXq1EyaNCmNjY2pqalJ//79U1NTk8bGxkyaNCmXXHJJWlpaerpUAAAAoAsCHgCAXqipqSnLli3L9OnTU17e8StheXl5pk2blqVLl6apqamHKgQAAAA2ZbMHPJdeemnKyso6/Oy///7t/S+99FLOO++87Lrrrtlpp50yZcqUPP3005u7DAAANmH58uVJktGjR3fZ39beNg4AAADYtmyRGTxvfetbs3z58vafe++9t73voosuyo9+9KPccsstWbBgQZ588snU1dVtiTIAANiI4cOHJ0kWL17cZX9be9s4AAAAYNvSZ4vstE+fDBs2rFP7ypUr853vfCc333xz3vWudyVJrr322rz5zW/OokWLcsghh2yJcgAA+AdjxozJyJEjM2PGjDQ2Nnboa21tzcyZM1NVVZUxY8b0TIEAAADAJm2RgOexxx7LiBEj0q9fvxx66KGZOXNm9tprrzz44INpbm7O0Ucf3T52//33z1577ZX7779/owHP+vXrs379+vbbq1atSpI0Nzenubl5S9wFtoANGza0//a8bTltj63HeMvyega2B1/4whdy8skn54QTTsjUqVOzbt263Hvvvbnyyivzk5/8JN///vfT2tqa1tbWni4VoNt8X9s6nH9sHV7PwPbG5wdsXHfeF5s94Kmpqcl1112X/fbbL8uXL89ll12WMWPGZPHixXnqqaeyww47ZOedd+6wzR577JGnnnpqo/ucOXNmLrvssk7td955ZwYMGLC57wJbyJ9WJ0mf3HvvvXlip56uZvs3b968ni5hu+b1DGwPKisr8/GPfzzXXntt++zq5O/fzT7+8Y+nsrIyP/nJT3qwQoDXzve1rcv5x5bl9Qxsr3x+QGdr16591WPLSqVSaQvWkhdeeCF77713Zs2alf79++ess87qMBsnSd75zndm/Pjx+cIXvtDlPrqawbPnnntmxYoVGTRo0JYsn83oN0+uyuSvL0rjhw/JW0d43raU5ubmzJs3L7W1tenbt29Pl7Pd8noGtictLS2555572j8/jjzyyFRUVPR0WQCvi+9rW4fzj63D6xnY3vj8gI1btWpVdtttt6xcufIV848tcom2l9t5553zpje9KUuWLEltbW3+9re/5YUXXugwi+fpp5/ucs2eNpWVlamsrOzU3rdvX38BFEifPn3af3vetjzvjy3L6xnYnvTt2zdHHXVU1q9fn6OOOsrfa8B2wfe1rcv5x5bl9Qxsr3x+QGfdeU+Ub8E6kiSrV6/O448/nuHDh+fggw9O37598/Of/7y9//e//33++Mc/5tBDD93SpQAAAAAA0INaWlqyYMGCLFy4MAsWLEhLS0tPlwSFtdkDnksuuSQLFizIsmXLct999+Wkk05KRUVFTjnllAwePDhnn312Lr744syfPz8PPvhgzjrrrBx66KE55JBDNncpAAAAAABsIxoaGjJq1KjU1tZm1qxZqa2tzahRo9LQ0NDTpUEhbfaA589//nNOOeWU7Lfffnnve9+bXXfdNYsWLcruu++eJPmP//iPTJo0KVOmTMnYsWMzbNgwb2AAAAAAgO1YQ0ND6uvrU11dnaampsyePTtNTU2prq5OfX29fyOG12Czr8Hz/e9/f5P9/fr1yzXXXJNrrrlmcx8aAAAAAIBtTEtLS6ZOnZpJkyalsbHx/9fe3UdFXef9H38NMwyI97GJFCok2dpKut1RGncWdSQ6jCNb63a13elVuWYJ6Ka5eZNpBxE228zKNru6cevCCXcRXbnScNJwLddWqzU1sChCwxJFFBjm98ce5tckKiL4ZZjn4xxPM5/PG+bFiH1nePP5fORyuVRdXa3Y2FgVFBTIZrMpKytLaWlpMpvNRscFfEaHn8EDAAAAAAAAAPBfTqdT5eXlmjlzpgICvH8kHRAQoBkzZqisrExOp9OghIBvosEDAAAAAAAAAOgwlZWVkqRhw4a1ON883lwHoHVo8AAAAAAAAAAAOkx4eLgkadeuXS3ON4831wFoHRo8AAAAAAAAAIAOExcXp8jISC1YsEBNTU1ec01NTVq4cKGioqIUFxdnUELAN9HgAQAAAAAAAAB0GLPZrMWLF6uwsFA2m02lpaWqq6tTaWmpbDabCgsLlZOTI7PZbHRUwKdYjA4AAAAAAAAAAOja7Ha78vPzlZmZqfj4eM94VFSU8vPzZbfbDUwH+CYaPAAAAAAAAACADme325WWlqaNGzdq7dq1GjNmjJKSkli5A7QRDR4AAAAAAAAAwHlhNpuVkJCg2tpaJSQk0NwBzgFn8AAAAAAAAAAAAPgYGjwAAAAAAAAAAAA+hgYPAAAAAAAAAACAj6HBAwAAAAAAAAAA4GNo8AAAAAAAAAAAAPgYGjwAAAAAAAAAAAA+hgYPAAAAAAAAAACAj6HBAwAAAAAAAAAA4GNo8AAAAAAAAAAAAPgYGjwAAAAAAAAAAAA+xmJ0AAAAABirvr5ezz77rDZs2KC9e/fq4YcfltVqNToWAAAAAAA4DVbwAAAA+LHp06ere/fuysrKUlFRkbKystS9e3dNnz7d6GgAAAAAAOA0WMEDAADgp6ZPn65FixYpLCxMc+fOVVBQkE6cOKHZs2dr0aJFkqTs7GyDUwIAAAAAgJawggcAAMAP1dfXKy8vT2FhYaqoqNB9992nvn376r777lNFRYXCwsKUl5en+vp6o6MCAAAAAIAW0OABAADwQ0uXLlVjY6Pmz58vi8V7UbfFYtG8efPU2NiopUuXGpQQAAAAQFfkcrlUUlKiTZs2qaSkRC6Xy+hIgM+iwQMAAOCH9u3bJ0lKTU1tcb55vLkOAAAAAM6Vw+FQdHS0kpOTlZubq+TkZEVHR8vhcBgdDfBJNHgAAAD80ODBgyVJhYWFLc43jzfXAQAAAMC5cDgcSk9PV0xMjJxOp1auXCmn06mYmBilp6fT5AHagAYPAACAH5o0aZIsFotmzZqlxsZGr7nGxkY98cQTslgsmjRpkkEJAQAAAHQVLpdLmZmZSk1NVUFBgWJjY9WtWzfFxsaqoKBAqampysrKYrs24CzR4AEAAPBDVqtVU6dOVVVVlSIiIrR8+XIdOnRIy5cvV0REhKqqqjR16lRZrVajowIAAADwcU6nU+Xl5Zo5c6YCArx/JB0QEKAZM2aorKxMTqfToISAb7KcuQQAAABdUXZ2tiQpLy/Pa6WOxWLRtGnTPPMAAAAAcC4qKyslScOGDWtxvnm8uQ5A67CCBwAAwI9lZ2ertrZWOTk5SklJUU5Ojmpra2nuAAAAAGg34eHhkqRdu3a1ON883lwHoHVYwQMAAODnrFarpkyZoujoaKWkpCgwMNDoSAAAAAC6kLi4OEVGRmrBggV6++239dxzz2nDhg3au3evfve732nhwoWKiopSXFyc0VEBn0KDBwAAAAAAAADQYcxmsxYvXqxx48YpJCREbrdbklRUVKRp06bJ7XZr1apVMpvNBicFfAtbtAEAAAAAAAAAOlRpaakkyWQyeY0HBAR4zQNoPRo8AAAAAAAAAIAOU19fr7y8PIWFhenYsWMqLi5WRkaGiouLVVtbq7CwMOXl5am+vt7oqIBPocEDAADg51wul0pKSrRp0yaVlJTI5XIZHQkAAABAF7J06VI1NjZq/vz5CgoKUkJCguLj45WQkKCgoCDNmzdPjY2NWrp0qdFRAZ9CgwcAAMCPORwORUdHKzk5Wbm5uUpOTlZ0dLQcDofR0QAAAAB0Efv27ZMkpaamtjjfPN5cB6B1aPAAAAD4KYfDofT0dMXExMjpdGrlypVyOp2KiYlReno6TR4AAAAA7WLw4MGSpMLCwhbnm8eb6wC0Dg0eAAAAP+RyuZSZmanU1FQVFBQoNjZW3bp1U2xsrAoKCpSamqqsrCy2awMAAABwziZNmiSLxaJZs2apsbHRa66xsVFPPPGELBaLJk2aZFBCwDfR4AEAAPBDTqdT5eXlmjlzptxut9cZPG63WzNmzFBZWZmcTqfRUQEAAAD4OKvVqqlTp6qqqkoRERFavny5Dh06pOXLlysiIkJVVVWaOnWqrFar0VEBn2IxOgAAAADOv8rKSkn/2eN6/PjxKi8vlyTl5uYqMjJS8+fP96oDAAAAgHORnZ0tScrLy/NaqWOxWDRt2jTPPIDWYwUPAACAHwoPD5ck3XXXXS2ewXPXXXd51QEAAADAucrOzlZtba1ycnKUkpKinJwc1dbW0twB2ogVPAAAAH5o5MiRslgsCg0NlcPhkNvtVnV1tWJjY+VwOBQREaHq6mqNHDnS6KgAAAAAuhCr1aopU6YoOjpaKSkpCgwMNDoS4LNYwQMAAOCHtmzZosbGRlVVVclut6u0tFR1dXUqLS2V3W5XVVWVGhsbtWXLFqOjAgAAAACAFtDgAQAA8EPNZ+u8/vrr2rlzp+Lj4zV+/HjFx8dr165dev31173qAAAAAABA50KDBwAAwA81n60zePBg7d27V8XFxcrIyFBxcbH27NmjSy65xKsOAAAAAAB0LjR4AAAA/FBcXJwiIyO1YMECmUwmJSQkKD4+XgkJCTKZTFq4cKGioqIUFxdndFQAAAAAANACGjwAAAB+yGw2a/HixSosLJTNZvM6g8dms6mwsFA5OTkym81GRwUAAAAAAC2wGB0AAAAAxrDb7crPz1dmZqbi4+M941FRUcrPz5fdbjcwHQAAAAAAOB0aPAAAAH7MbrcrLS1NGzdu1Nq1azVmzBglJSWxcgcAAAAAgE6OLdoAAAAAAAAAAAB8DA0eAAAAP+ZwOBQdHa3k5GTl5uYqOTlZ0dHRcjgcRkcDAAAAAACnQYMHAADATzkcDqWnpysmJkZOp1MrV66U0+lUTEyM0tPTafIAAAAAANCJ0eABAADwQy6XS5mZmUpNTVVBQYFiY2PVrVs3xcbGqqCgQKmpqcrKypLL5TI6KgAAAAAAaAENHgAAAD/kdDpVXl6umTNnKiDA+yVhQECAZsyYobKyMjmdToMSAgAAAACA06HBAwAA4IcqKyslScOGDWtxvnm8uQ4AAAAAAHQuNHgAAAD8UHh4uCRp165dLc43jzfXAQAAAACAzoUGDwAAgB+Ki4tTZGSkFixYoKamJq+5pqYmLVy4UFFRUYqLizMoIQAAAAAAOB2L0QEAAABw/pnNZi1evFjp6elKS0tTcnKy9uzZo/3796u4uFhr1qxRfn6+zGaz0VEBAAAAAEALaPAAAAD4KbvdrqysLOXl5amwsNAzbrFYlJWVJbvdbmA6AAAAAABwOjR4AAAA/JTD4VBOTo5uvfVWzwqeSy+9VMXFxcrJydF1111HkwcAAAAAgE6KM3gAAAD8kMvlUmZmplJTU+VwOHT55ZfLarXq8ssvl8PhUGpqqrKysuRyuYyOCgAAAAAAWkCDBwAAwA85nU6Vl5dr5MiRGjJkiJKTk5Wbm6vk5GQNGTJE119/vcrKyuR0Oo2OCgAAAAAAWkCDBwAAwA9VVlZKkmbOnKmYmBg5nU6tXLlSTqdTMTExevzxx73qAAAAAABA50KDBwAAwA/169dPkjRq1CgVFBQoNjZW3bp1U2xsrAoKCjRq1CivOgAAAAAA0LnQ4AEAAMBJ3G630REAAAAAAMBp0OABAADwQwcOHJAkvf/++7LZbCotLVVdXZ1KS0tls9m0efNmrzoAAAAAANC50OABAADwQ+Hh4ZKkhQsXaufOnYqPj9f48eMVHx+vXbt2acGCBV51AAAAAACgc6HBAwAA4Ifi4uIUGRmpLVu26PPPP1dxcbEyMjJUXFys3bt364MPPlBUVJTi4uKMjgoAAAAAAFpAgwcAAMAPmc1mLV68WIWFhRo3bpyCgoJ0zTXXKCgoSOPGjVNhYaFycnJkNpuNjgoAAAAAAFpgMToAAAAAjGG325Wfn6/MzEzFx8d7xqOiopSfny+73W5gOgAAAAAAcDo0eAAAAPyY3W5XWlqaNm7cqLVr12rMmDFKSkpi5Q4AAAAAAJ0cDR4AAAA/ZzablZCQoNraWiUkJNDcAQAAAADAB3AGDwAAgJ9zuVwqKSnRpk2bVFJSIpfLZXQkAAAAAABwBjR4AAAA/JjD4VB0dLSSk5OVm5ur5ORkRUdHy+FwGB0NAAAAAACcBg0eAAAAP+VwOJSenq6YmBg5nU6tXLlSTqdTMTExSk9Pp8kDAAAAAEAnRoMHAADAD7lcLmVmZio1NVUFBQWKjY1Vt27dFBsbq4KCAqWmpiorK4vt2gAAAAAA6KRo8AAAAPghp9Op8vJyzZw5UwEB3i8JAwICNGPGDJWVlcnpdBqUEAAAAAAAnA4NHgAAAD9UWVkpSRo2bFiL883jzXUAAAAAAKBzocEDAADgh8LDwyVJu3btanG+eby5DgAAAAAAdC40eAAAAPxQXFycIiMjtWDBAjU1NXnNNTU1aeHChYqKilJcXJxBCQEAAAAAwOnQ4AEAAPBDZrNZixcvVmFhoWw2m0pLS1VXV6fS0lLZbDYVFhYqJydHZrPZ6KgAAAAAAKAFFqMDAAAAwBh2u135+fnKzMxUfHy8ZzwqKkr5+fmy2+0GpgMAAAAAAKdDgwcAAMCP2e12paWlaePGjVq7dq3GjBmjpKQkVu4AAAAAANDJsUUbAAAAAAAAAOC8cLlcKikp0aZNm1RSUiKXy2V0JMBntXuDZ+HChbrmmmvUs2dP9evXTzabTbt37/aqSUxMlMlk8vrz4IMPtncUAAAAnIHD4VB0dLSSk5OVm5ur5ORkRUdHy+FwGB0NAAAAQBfD+w+gfbV7g6ekpES/+93vVFpaquLiYjU0NOjmm29WbW2tV93EiRNVWVnp+ZOdnd3eUQAAAHAaDodD6enpiomJkdPp1MqVK+V0OhUTE6P09HTeZAEAAABoN7z/ANpfu5/Bs27dOq/7K1asUL9+/fTRRx95Hd4bEhKi/v37t/fDAwAAoBVcLpcyMzOVmpqqgoICuVwuVVdXKzY2VgUFBbLZbMrKylJaWhrn8QAAAAA4J7z/ADpGuzd4furw4cOSpAsuuMBr/I033tDrr7+u/v3767bbbtMf/vAHhYSEtPg5Tpw4oRMnTnju19TUSJIaGhrU0NDQQcnR3hobGz3/5e+t4zQ/tzzHHYvvZwC+rqSkROXl5XrttdfkcrlOun5MmzZN8fHx2rhxoxISEoyMCgBtwuu184P3H+cH388AfN2P33/U19frvffe06ZNmxQUFKTExETefwA/cjbX+g5t8DQ1NenRRx/VqFGjNGzYMM/4b37zGw0aNEgXXXSR/vWvf+n3v/+9du/efcpleAsXLtTcuXNPGl+/fv0pm0LofL46KkkWvf/++9rfw+g0XV9xcbHREbo0vp8B+LpNmzZJkioqKlRdXe0Zb75+1NXVSZLWrl170la7AOALeL12fvH+o2Px/QzA1zW//3A4HBo3bpwOHDggScrNzVW/fv105513SuL9ByBJx44da3Wtye12uzsqyEMPPaS1a9fq/fffV0RExCnrNmzYoBtvvFF79+7V4MGDT5pvaQXPgAED9N1336lXr14dkh3t75NvamR7vlQFD12nX1zE31tHaWhoUHFxsZKTkxUYGGh0nC6L72cAvq6kpETJyclyOp2KjY096fpRWlqq+Ph4FRcX8xt0AHwSr9fOD95/nB98PwPwdc3vP0wmk1JSUpSVlaVvv/1W/fv3V05OjoqKiuR2u3n/Aeg//Y+f/exnOnz48Bn7Hx22gmfy5MkqLCzUpk2bTtvckaTY2FhJOmWDJygoSEFBQSeNBwYG8gLSh1gsFs9/+XvrePz76Fh8PwPwdUlJSYqMjFR2drYKCgo844GBgTKbzVq0aJGioqKUlJTEHtgAfBKv184v3n90LL6fAfi6+Ph4WSwWhYaGatWqVXI6ndq2bZvGjBmjVatWadCgQaqurlZ8fDz/n4PfO5t/A+3e4HG73Xr44Yf1zjvv6L333lNUVNQZP2bHjh2SpPDw8PaOAwAAgBaYzWYtXrxY6enpstlsmjZtmurq6lRaWqpFixapsLBQ+fn5NHcAAAAAnLMtW7aosbFRVVVV6tu3r2dL6NzcXHXr1s1zf8uWLUpMTDQwKeBb2r3B87vf/U5vvvmmVq9erZ49e+rbb7+VJPXu3VvdunXTvn379OabbyolJUWhoaH617/+palTpyo+Pl5XXHFFe8cBAADAKdjtduXn5yszM1Px8fGe8aioKOXn58tutxuYDgAAAEBXUVlZeco5k8nUqjoAJwto70/4/PPP6/Dhw0pMTFR4eLjnz1tvvSVJslqt+r//+z/dfPPN+vnPf67MzEyNGzdOf/vb39o7CgAAAM7Abrdr7969Ki4uVkZGhoqLi7Vnzx6aOwAAAADaTb9+/SRJN9xwgw4fPuz1/uOHH37QqFGjvOoAtE6HbNF2OgMGDFBJSUl7PywAAADayGw2KyEhQbW1tUpISGBbNgAAAAAdpqX3Hz9exQOg9dp9BQ8AAAAAAAAAAM0OHDggSdq8ebNsNptKS0s9Z4DabDZt3rzZqw5A69DgAQAAAAAAAAB0mPDwcEnSggULtHPnTsXHx2v8+PGKj4/Xrl279NRTT3nVAWgdGjwAAAAAAAAAgA4TFxenyMhIbdmyRZ9//rnXGTy7d+/WBx98oKioKMXFxRkdFfApNHgAAAAAAAAAAB3GbDZr8eLFKiwslN1u16effqr6+np9+umnstvtKiwsVE5ODueBAmfJYnQAAAAAAAAAAEDXZrfblZWVpby8PBUWFnrGLRaLsrKyZLfbDUwH+CYaPAAAAAAAAACADuVwOJSTk6Nbb71VycnJ2rNnjy699FIVFxcrJydH1113HU0e4CzR4AEAAAAAAAAAdBiXy6XMzEylpqaqoKBALpdLRUVFSklJ0eTJk2Wz2ZSVlaW0tDS2aQPOAmfwAAAA+Lm6ujpNmTJFc+bM0ZQpU1RXV2d0JAAAAABdiNPpVHl5uWbOnKmAAO8fSQcEBGjGjBkqKyuT0+k0KCHgm2jwAAAA+DGbzaaQkBAtW7ZMO3bs0LJlyxQSEiKbzWZ0NAAAAABdRGVlpSRp2LBhLc43jzfXAWgdGjwAAAB+ymazafXq1bJarZo+fbqef/55TZ8+XVarVatXr6bJAwAAAKBdhIeHS5J27drV4nzzeHMdgNahwQMAAOCH6urqPM2d6upq9evXT3/729/Ur18/VVdXe5o8bNcGAAAA4FzFxcUpMjJSCxYsUFNTk9dcU1OTFi5cqKioKMXFxRmUEPBNNHgAAAD80LRp0yRJv/zlL9W3b19lZWWpqKhIWVlZ6tu3r4YPH+5VBwAAAABtZTabtXjxYhUWFspms6m0tFR1dXUqLS2VzWZTYWGhcnJyZDabjY4K+BSL0QEAAABw/u3Zs0eStHXrVoWFhWnu3LkKCgrSiRMnNHv2bG3bts2rDgAAAADOhd1uV35+vjIzMxUfH+8Zj4qKUn5+vux2u4HpAN9EgwcAAMAPXXLJJZKkkJAQVVRUyO12q6ioSCkpKbr//vvVu3dvHTt2zFMHAAAAAOfKbrcrLS1NGzdu1Nq1azVmzBglJSWxcgdoIxo8AAAAfigqKkqSVF9fr6amJplMJs9cU1OTTpw44VUHAAAAAO3BbDYrISFBtbW1SkhIoLkDnAPO4AEAAPBDX331lSSpsbFRPXv21MyZM/X1119r5syZ6tmzp1wul1cdAAAAAADoXFjBAwAA4IcGDx4sSRoxYoR27NihnJwcr/krrrhC//rXvzx1AAAAAACgc2EFDwAAgB+aNGmSLBaLKisrVVNTowcffFAjRozQgw8+qJqaGlVVVclisWjSpElGRwUAAAAAAC2gwQMAAOCHrFarpk6dqqqqKkVHR+vgwYO64IILdPDgQUVHR6uqqkpTp06V1Wo1OioAAAAAAGgBW7QBAAD4qezsbL333nvatm2bVq1a5TV3zTXXKDs726BkAAAAAADgTFjBAwAA4KemT5+ubdu2qV+/fkpPT9fo0aOVnp6ufv36adu2bZo+fbrREQEAAAAAwCmwggcAAMAP1dfXKy8vT2FhYaqoqJDb7VZRUZFSUlJkMpkUERGhvLw8zZ8/n23aAAAAAADohFjBAwAA4IeWLl2qxsZGzZ8/XxaL9+/8WCwWzZs3T42NjVq6dKlBCQEAAAB0RS6XSyUlJdq0aZNKSkrkcrmMjgT4LBo8AAAAfmjfvn2SpNTU1Bbnm8eb6wAAAADgXDkcDkVHRys5OVm5ublKTk5WdHS0HA6H0dEAn0SDBwAAwA8NHjxYklRYWNjifPN4cx0AAAAAnAuHw6H09HTFxMTI6XRq5cqVcjqdiomJUXp6Ok0eoA1o8AAAAPihSZMmyWKxaNasWTpx4oTXFgknTpzQE088IYvFokmTJhkdFQAAAICPc7lcyszMVGpqqgoKChQbG6tu3bopNjZWBQUFSk1NVVZWFtu1AWfJcuYSAAAAdDVWq1VTp07VokWLFBISoqamJklSbm6uAgIC1NTUpGnTpslqtRqcFAAAAICvczqdKi8v18qVKxUQEODVyAkICNCMGTM0cuRIOZ1OJSYmGhcU8DGs4AEAAPBT1113nSTJ7XZ7jTffb54HAAAAgHNRWVkpSRo2bFiL883jzXUAWocGDwAAgB9q3iLhtttu07Fjx5STk6OUlBTl5OTo2LFjuu2229giAQAAAEC7CA8PlyTt2rWrxfnm8eY6AK1DgwcAAMAPNW+RMHPmTAUGBmr48OH6+c9/ruHDhyswMFAzZsxQWVmZnE6n0VEBAAAA+Li4uDhFRkZqwYIFnu2hmzU1NWnhwoWKiopSXFycQQkB30SDBwAAwA81b32wb98+RUdHKzk5Wbm5uUpOTlZ0dLS++OILrzoAAAAAaCuz2azFixersLBQNptNpaWlqqurU2lpqWw2mwoLC5WTkyOz2Wx0VMCnWIwOAAAAgPOveeuDu+66S6mpqXrttddUUVGhiIgIZWdn66677vKqAwAAAIBzYbfblZ+fr8zMTMXHx3vGo6KilJ+fL7vdbmA6wDfR4AEAAPBDI0eOlMViUWhoqBwOh9xut6qrqxUbGyuHw6GIiAhVV1dr5MiRRkcFAAAA0EXY7XalpaVp48aNWrt2rcaMGaOkpCRW7gBtRIMHAADAD23ZskWNjY2qqqpSWlqarFar9u3bpxUrVqi+vl5VVVWeusTERGPDAgAAAOgyzGazEhISVFtbq4SEBJo7wDmgwQMAAOCHms/WufLKK1VUVOQZ37lzp2d8+/btnMEDAAAAAEAnRYMHAADADzWfrbN9+3YFBgZq3LhxCgkJ0bFjx7Rq1Spt377dqw4AAAAAAHQuNHgAAAD80NVXXy1JMplMqqmpkdlsVlFRkVJSUvTKK68oJCREbrfbUwcAAAAAADoXGjwAAAB+6LHHHpMkud1u/epXv1JycrL27Nmj/fv3q7i4WG6321P3pz/9ycioANAmJ1zHFRD8tcpqdisguIfRcbqsxsZGfdP4jT479JksFn7E0FHKao4qIPhrnXAdl9Tb6DgAAKCT4NUXAACAH9qzZ48kaeLEiXrllVdUWFjombNYLLr//vv18ssve+oAwNd8U7tf3aOe1cx/GJ3EPyxdt9ToCF1e9yjpm9oRukphRkcBAACdBA0eAAAAP3TppZdq/fr1eumll5Samqqbb75Zn3/+uYYMGaL169fr5Zdf9tQBgC+6qPsg1ZY9rGfuGKHB/VjB01EaGxu1+f3NGnXDKFbwdKB9B47qkbd26KKkQUZHAQAAnQivvgAAAPzQ008/reeee04mk0lvvPGGXnrpJX3xxReKjIzUG2+8oT59+sjtduvpp582OioAtEmQOVhNxy9WVK/LdHkoW1p1lIaGBpVZyjT0gqEKDAw0Ok6X1XT8sJqOH1SQOdjoKAAAoBOhwQMAAOCHPvzwQ0n/OYOnd+///4PPoqIiZWVledUlJiae73gAAAAAAOAMAowOAAAAgPOvsrKyXesAAAAAAMD5RYMHAADAD/3sZz+TJPXt21c1NTV68MEHNWLECD344IOqqalR3759veoAAAAAAEDnQoMHAADAD+3cuVOSNHDgQAUHB2vcuHEaPXq0xo0bp+DgYA0YMMCrDgAAAAAAdC6cwQMAAOCHysrKJEkff/yxevfurbq6OklSbm6uunXr5rnfXAcAAAAAADoXVvAAAAD4ocGDB59yzmQytaoOAAAAAAAYhwYPAACAH3rggQckSVarVdXV1SouLlZGRoaKi4v13XffyWq1etUBAAAAAIDOhS3aAAAA/NDWrVslSfX19YqMjFRcXJy+//577d+/X3feeafq6+s9dYmJiQYmBQAAANCV1NXVKSMjQ6WlpVq3bp1nm2gAZ48GDwAAgB+qrKyUJF155ZXavn27Vq1a5TXfPN5cBwAAAADnymazafXq1Z77O3bs0LJly5SWlqaCggLjggE+igYPAACAHwoPD5ckbd++Xf369VNCQoKqq6sVGhqqkpISbd++3asOAAAAAM5Fc3PHarXq0UcfVVRUlMrKyvTHP/5Rq1evls1mo8kDnCUaPAAAAH4oNjZW0n/O4Pnqq69kMplUVFSklJQUud1u9ezZU/X19Z46AAAAAGiruro6T3PnyJEjnvcf999/v5588kn17NlTq1evVl1dHdu1AWchwOgAAAAAOP9eeOEFSVJDQ4PS09NVWlqquro6lZaWKj09XQ0NDV51AAAAANBW06ZNkyRlZGRIkpYsWaIXX3xRS5YskSQ9+uijXnUAWocVPAAAAH5o3759kqSXXnpJ8+fPV3x8vGcuKipKL774oiZOnOipAwAAAIC22rNnjyTpu+++U/fu3dXY2ChJKioq0mOPPaa7777bqw5A67CCBwAAwA8NHjxYkuR2u7V3714VFxcrIyNDxcXF2rNnj5qamrzqAAAAAKCtLr30UknS8uXLFRoaqmXLlumVV17RsmXLFBoaqpdfftmrDkDr0OABAADwQ5MmTZLFYtGsWbPkdruVkJCg+Ph4JSQkyO1264knnpDFYtGkSZOMjgoAAADAxz311FOe2//+97919OhR/e///q+OHj2qf//73y3WATgztmjDeXPCdVwBwV+rrGa3AoJ7GB2ny2psbNQ3jd/os0OfyWLhn3hHKas5qoDgr3XCdVxSb6PjAMBZs1qtmjp1qhYtWqSIiAjNnj1bwcHBWr58uebOnauqqipNmzZNVqvV6KgAAAAAfNwrr7ziud23b1/P7aKiImVlZXnVNZ/HA+DM+Okvzptvavere9SzmvkPo5P4h6XrlhodocvrHiV9UztCVynM6CgA0CbZ2dmSpLy8PK+VOhaLRdOmTfPMAwAAAMC5aO3ZnpwBCpwdGjw4by7qPki1ZQ/rmTtGaHA/VvB0lMbGRm1+f7NG3TCKFTwdaN+Bo3rkrR26KGmQ0VEA4JxkZ2dr/vz5evbZZ7VhwwaNHj1aDz/8MCt3AAAAALSbQYP+8/OTnj176uuvv9b06dNVWlqq6667TtnZ2br44ot15MgRTx2A1uGnvzhvgszBajp+saJ6XabLQ9nSqqM0NDSozFKmoRcMVWBgoNFxuqym44fVdPyggszBRkcBAAAAAADwCSaTSVarVePGjVNISIjGjBnDL5cB54AGDwAAgB+bPn268vLy1NjYKOk/e2A/9thjmjp1Klu0AQAAAGgX+/fvlyTV1NQoJCRETU1NkqTc3FwFBAR47jfXAWgdGjwAAAB+avr06Vq0aJHCwsI0d+5cBQUF6cSJE5o9e7YWLVokSTR5AAAAAJyzwYMHe2673W6vuR/f/3EdgDMLMDoAAAAAzr/6+nrl5eUpLCxMFRUVuu+++9S3b1/dd999qqioUFhYmPLy8lRfX290VAAAAAA+7oEHHpAkWa1W1dTUKCcnRykpKcrJyVFNTY1nm7bmOgCtQ4MHAADADy1dulSNjY2aP3++mpqatGTJEr344otasmSJmpqaNG/ePDU2Nmrp0qVGRwUAAADg47Zu3SrpP79oFh0drYqKCkVERKiiokLR0dGeXyxrrgPQOmzRBgAA4If27dsnSdq+fbseeuihk87gmThxolcdAAAAALRVZWWlJOnWW2/VmjVr9Mc//tFrvnm8uQ5A69DgAQAA8EPNe1s///zzLZ7B8/zzz3vVAQAAAEBbhYeHS5LWrFmjW2+9VVFRUfr88881ZMgQlZWVac2aNV51AFqHBg8AAIAfmjBhgqZOnSqTyaTy8nKZzWYVFRUpJSVFd999t0JCQuR2uzVhwgSjowIAAADwcSNHjpTFYlFoaKgKCgrkdrs97z9MJpMiIiJUXV2tkSNHGh0V8Ck0eAAAAPzQ8uXLJUlut1sDBw5UY2Ojamtr1b17d1ksFrndbk/do48+amBSAAAAAL5uy5Ytamxs1IEDBzR27FglJydrz5492r9/v4qLi3XgwAG53W5t2bJFiYmJRscFfAYNHgAAAD/UfLZOYGCgDh486BlvPtw0MDBQDQ0NnMEDAAAA4Jw1n60zZcoUPffccyosLPTMWSwWTZkyRc888wxn8ABniQYPAACAH2o+W6ehoUGSFBYWposvvlhff/21qqqqPOOcwQMAAADgXDWfrfPMM88oNTVVN998s+cMnvXr1+uZZ57xqgPQOgFGBwAAAMD596tf/cpzu6KiQmPHjlVTU5PGjh2rioqKFusAAAAAoC2az+AJCwvTm2++qU8//VTvv/++Pv30U7355psKCwuTxWLhDB7gLLGCBwAAwA/dcMMNntsRERGe2zt27NCyZcu86srKys5rNgAAAABdS/MZPFVVVerVq5dn/KfvPziDBzg7rOABAADwQz8+d6c96gAAAADgVFp7tg5n8ABnhwYPAACAHwoNDfXcPnLkiHJycpSSkqKcnBwdOXKkxToAAAAAaIs+ffpIkgIDA3Xs2DEVFxcrIyNDxcXFOnbsmAIDA73qALQODR4AAAA/9ONtD/bt26dZs2apqKhIs2bN0r59+1qsAwAAAIC2+Otf/ypJGjhwoIKCgpSQkKD4+HglJCQoKChIAwcO9KoD0Do0eAAAAPzQt99+67k9YsQIHT9+XJJ0/PhxjRgxosU6AAAAAGiLL774QtJ/frnMZrOptLRUdXV1Ki0tlc1m8/ySWXMdgNahwQMAAOCHLr300natAwAAAIBTaX5fMXbsWO3cuVPx8fEaP3684uPjtWvXLtlsNq86AK1DgwcAAMAPTZo0yXN7+/btGjRokIKDgzVo0CBt3769xToAAAAAaItFixZJktasWaNPP/3U6wyeTz75REVFRV51AFrHYnQAAAAAnH9XXXWV5/bVV1+t4OBgNTQ06ODBg7r66qu96urq6oyICAAAAKCL6Natm9LS0rR69Wr17t1bAwcO1LFjx7R69Wp9+eWXamhoUFpamrp162Z0VMCn0OABAADwQydOnPDcbmpq0rFjxyTJ89+W6gAAAACgrQoKCtSnTx8dPnzYc+ZOs969e6ugoMCYYIAPY4s2AAAAPxQUFOR1v0ePHho4cKB69Ohx2joAAAAAaItrr71Whw8flslk0pAhQzx/TCaTDh8+rGuvvdboiIDPocEDAADghzZs2OC5vXv3bh06dEhLlizRoUOHtHv37hbrAAAAAKAtjh49qm3btslkMunYsWPatWuXsrOztWvXLh07dkwmk0nbtm3T0aNHjY4K+BQaPAAAAH5o7NixntuXXXaZQkND5XA4FBoaqssuu6zFOgAAAABoi7vuukuS9F//9V8KDg72mgsODtZvfvMbrzoArWNog+e5555TZGSkgoODFRsbq3/84x9GxgEAAPAbP/zwg9f9I0eO6H/+53905MiR09YBAAAAwNlqPnMnKyurxfmMjAyvOgCtY1iD56233lJGRoZmz56t7du3a/jw4brlllt04MABoyIBAAD4jT59+kiSoqKi9MUXX3h+iy44OFhffPGFBg0a5FUHAAAAAG01ePBgSVJOTk6L87m5uV51AFrHsAZPbm6uJk6cqHvvvVeXX365li1bppCQEP35z382KhIAAIDf2LFjhySprKxMffv2VU1NjQoKClRTU6O+fftq//79XnUAAAAA0FavvfaaJOn111/X8ePHveaOHz+uN99806sOQOtYjHjQ+vp6ffTRR5oxY4ZnLCAgQDfddJM++OCDk+pPnDihEydOeO4fPnxYknTo0CE1NDR0fGC0i28P/KCmE8f0wWf79cP3vYyO0yrHjx/T/jLfWhra1OjSJ598qgPH3AqwmI2Oc1YGRQ1WcHCI0TFa5YvvatV04ph++P57VQc3Gh0H6NIqa45o9ScfGx3jrNQerdEXn/7L6BhnFBLZXS5Xo/pf0V/duvfUxdGX657HJqiu9oiCBgTJbLbo0aWLjY55RpdcfoW69/CN1xaS1K9nkMb8fJi6WboZHQXo0nj/cX7w/uP84P0HcP7w/qPjhP1ygH747oD6DOmjiy8ZogvCBykj7w/6+ovPZY2wqs/P+mlC9lyjY54R7z/Q0Zq3Tne73WesNblbU9XOvvnmG1188cXasmWLrr/+es/49OnTVVJSoq1bt3rVz5kzR3Pndv5/3AAAAAAAAAAAAOfqq6++UkRExGlrDFnBc7ZmzJjhOWhLkpqamnTo0CGFhobKZDIZmAzofGpqajRgwAB99dVX6tXLd36bAABgLK4fAIC24PoBAGgLrh/Aqbndbh05ckQXXXTRGWsNafD87Gc/k9lsVlVVldd4VVWV+vfvf1J9UFCQgoKCvMY48Bc4vV69enGBBACcNa4fAIC24PoBAGgLrh9Ay3r37t2quoAOztEiq9Wqq666Su+++65nrKmpSe+++67Xlm0AAAAAAAAAAAA4mWFbtGVkZOjuu+/W1VdfrWuvvVZ//OMfVVtbq3vvvdeoSAAAAAAAAAAAAD7BsAbPHXfcoYMHD+qJJ57Qt99+qxEjRmjdunUKCwszKhLQJQQFBWn27NknbWsIAMDpcP0AALQF1w8AQFtw/QDah8ntdruNDgEAAAAAAAAAAIDWM+QMHgAAAAAAAAAAALQdDR4AAAAAAAAAAAAfQ4MHAAAAAAAAAADAx9DgAX4kMTFRJpNJJpNJO3bsMDpOq5lMJhUUFBgd45zdc889nue/K3w9AHC2fPE6dK7/z46MjPR8zT/88EO75QIAtI5R1x5e+wOA//npNae8vNxzf8SIEUbHA3wSDR7gJyZOnKjKykoNGzZMkjwXm64kMTFRK1asaHV9Rz0Hc+bM0T333OO5/8wzz6iysrLdHwcAfMmPr0P+cA3atm2bVq1aZVwgAMB5ufbw2h8AIHlfcwYMGKDKykplZmYaHQvwWRajAwCdTUhIiPr379+un7O+vl5Wq7VdP2dX1Lt3b/Xu3dvoGABgqPa+DnX2a9CFF16oCy64wOgYAODXOuI90Jnw2h8A/NNPrzn9+/dXjx49DEwE+DZW8ABt8NJLL2nAgAEKCQnR2LFjlZubqz59+njm58yZoxEjRmj58uWKiopScHCwJOmHH37QhAkTdOGFF6pXr14aPXq0Pv74Y6/PvXr1al155ZUKDg7WJZdcorlz56qxsdEzv2fPHsXHxys4OFiXX365iouLvT5+9OjRmjx5stfYwYMHZbVa9e6777bbc7BixQoNHDjQ8xwsXry4xefghRde8DxXt99+uw4fPtxuGQDAH3ENAgCcb7z2BwAA6Jxo8ABnafPmzXrwwQf1yCOPaMeOHUpOTtZTTz11Ut3evXu1atUqORwOz17Wv/rVr3TgwAGtXbtWH330ka688krdeOONOnTokCTJ6XTqt7/9rR555BF9+umneuGFF7RixQrP529qapLdbpfVatXWrVu1bNky/f73v/d63AkTJujNN9/UiRMnPGOvv/66Lr74Yo0ePbpdnoOtW7fq/vvv1+TJk7Vjxw4lJSVp/vz5LT4Hb7/9tv72t79p3bp1+uc//6lJkya1SwYA8EdcgwAA5xuv/QEAADoxNwCPhIQE9yOPPHLamjvuuMN96623eo3deeed7t69e3vuz5492x0YGOg+cOCAZ8zpdLp79erlPn78uNfHDh482P3CCy+43W63+8Ybb3QvWLDAa/61115zh4eHu91ut/vvf/+722KxuL/++mvP/Nq1a92S3O+8847b7Xa76+rq3H379nW/9dZbnporrrjCPWfOnNN/8Wdh/Pjx7pSUFK+xO+6446TnwGw2uysqKryyBgQEuCsrK0/7+X/89QCAPznTdairXoM2btzoluT+/vvvT1sHAGh/Z7r28NofANBeTnXNmT17tnv48OHnPQ/QFbCCBzhLu3fv1rXXXus19tP7kjRo0CBdeOGFnvsff/yxjh49qtDQUPXo0cPzp6ysTPv27fPUzJs3z2u++fC5Y8eO6bPPPtOAAQN00UUXeT7v9ddf7/W4wcHBuuuuu/TnP/9ZkrR9+3bt2rXL60DTc/XZZ58pNjbWa+ynOSRp4MCBuvjii71qmpqatHv37nbLAgD+hGsQAOB847U/AABA52UxOgDQVXXv3t3r/tGjRxUeHq733nvvpNrm/auPHj2quXPnym63n1TTfIZCa0yYMEEjRoxQRUWFXnnlFY0ePVqDBg06q/wAAN/FNQgAAAAAgK6PBg9wli677DJt27bNa+yn91ty5ZVX6ttvv5XFYlFkZOQpa3bv3q3o6OgW54cOHaqvvvpKlZWVCg8PlySVlpaeVBcTE6Orr75aL730kt5880396U9/OmO+szF06FBt3brVa6ylHF9++aW++eYbz297l5aWKiAgQJdddlm75gEAf8E1CABwvvHaHwAAoPOiwQOcpYcffljx8fHKzc3Vbbfdpg0bNmjt2rUymUyn/bibbrpJ119/vWw2m7KzszVkyBB98803WrNmjcaOHaurr75aTzzxhFJTUzVw4EClp6crICBAH3/8sXbt2qX58+frpptu0pAhQ3T33Xdr0aJFqqmp0eOPP97i402YMEGTJ09W9+7dNXbs2HZ9DqZMmaJRo0YpJydHaWlp+vvf/65169adVBccHKy7775bOTk5qqmp0ZQpU3T77berf//+7ZoHAPwF1yAAwPnGa38AAIDOizN4gLM0atQoLVu2TLm5uRo+fLjWrVunqVOnnnH7GpPJpKKiIsXHx+vee+/VkCFD9Otf/1r79+9XWFiYJOmWW25RYWGh1q9fr2uuuUbXXXed8vLyPFvbBAQE6J133lFdXZ2uvfZaTZgwQU899VSLjzd+/HhZLBaNHz/+jNnuueceJSYmtvo5uO666/TSSy/pmWee0fDhw7V+/XrNmjXrpLro6GjZ7XalpKTo5ptv1hVXXKGlS5e2+nEAAN664jUIANC58dofAACg82IFD9AGEydO1MSJE73u/3hLmzlz5mjOnDknfVzPnj21ZMkSLVmy5JSf+5ZbbtEtt9xyyvkhQ4bI6XR6jbnd7pPqvvvuOx0/flz333//6b4USVJZWZmSkpLOWPdj9913n+677z7P/RUrVrRY99BDD+mhhx46q88NADi1rnYNAgB0frz2BwAA6JxYwQP8xNKlS9WjRw/t3LnzlDU5OTn6+OOPtXfvXj377LN69dVXdffdd5/HlKfW0NCgb7/9VrNmzdJ1112nK6+88rT1hw8f1r59+5SVlXWeEp7agw8+qB49ehgdAwAMdabrUFe6BknSL37xC40ZM+Y8pAMAnEpr3gO1N177A4B/+vE158svv1SPHj20YMECo2MBPosVPMCPvPHGG6qrq5MkDRw48JR1//jHP5Sdna0jR47okksu0ZIlSzRhwoTzFfO0Nm/erKSkJA0ZMkT5+flnrO/du7cqKirOQ7IzmzdvnqfR1HyANwD4k9Zch7rSNUiSioqK1NDQIEnq1atXR8YDALSgte+B2huv/QHA//z0mhMQEKAdO3ZIkoKCggxMBvguk7ulfTUAAAAAAAAAAADQabFFGwAAAAAAAAAAgI+hwQMAAAAAAAAAAOBjaPAAAAAAAAAAAAD4GBo8AAAAAAAAAAAAPoYGDwAAAACfU15eLpPJpB07dnSax0pMTNSjjz7a4XkAAAAAQKLBAwAAAACnNWDAAFVWVmrYsGGSpPfee08mk0k//PCDscEAAAAA+DWL0QEAAAAAoLOqr6+X1WpV//79jY4CAAAAAF5YwQMAAACgU1q3bp1uuOEG9enTR6GhoUpNTdW+fftOWf/Xv/5Vl156qYKDg5WUlKRXX331pJU2q1at0i9+8QsFBQUpMjJSixcv9vockZGRevLJJ/Xb3/5WvXr10n//9397bdFWXl6upKQkSVLfvn1lMpl0zz33eD6+qalJ06dP1wUXXKD+/ftrzpw5Xp/fZDLphRdeUGpqqkJCQjR06FB98MEH2rt3rxITE9W9e3eNHDnS6+v8+OOPlZSUpJ49e6pXr1666qqr9OGHH7b9iQUAAADQJdDgAQAAANAp1dbWKiMjQx9++KHeffddBQQEaOzYsWpqajqptqysTOnp6bLZbPr444/1wAMP6PHHH/eq+eijj3T77bfr17/+tXbu3Kk5c+boD3/4g1asWOFVl5OTo+HDh+uf//yn/vCHP3jNDRgwQKtWrZIk7d69W5WVlXrmmWc886+++qq6d++urVu3Kjs7W/PmzVNxcbHX52huIO3YsUM///nP9Zvf/EYPPPCAZsyYoQ8//FBut1uTJ0/21N95552KiIjQtm3b9NFHH+mxxx5TYGBgm55TAAAAAF2Hye12u40OAQAAAABn8t133+nCCy/Uzp071aNHD0VFRemf//ynRowYoccee0xr1qzRzp07PfWzZs3SU089pe+//159+vTRnXfeqYMHD2r9+vWemunTp2vNmjX65JNPJP1nBc8vf/lLvfPOO56a8vJyr8d67733lJSU5Pm8zRITE+VyueR0Oj1j1157rUaPHq2nn35a0n9W8MyaNUtPPvmkJKm0tFTXX3+9Xn75Zd13332SpL/85S+69957VVdXJ0nq1auXnn32Wd19993t/IwCAAAA8GWs4AEAAADQKe3Zs0fjx4/XJZdcol69eikyMlKS9OWXX55Uu3v3bl1zzTVeY9dee63X/c8++0yjRo3yGhs1apT27Nkjl8vlGbv66qvbnPmKK67wuh8eHq4DBw6csiYsLEySFBMT4zV2/Phx1dTUSJIyMjI0YcIE3XTTTXr66adPu00dAAAAAP9BgwcAAABAp3Tbbbfp0KFDeumll7R161Zt3bpVklRfX9+hj9u9e/c2f+xPt04zmUwnbSn34xqTyXTKseaPmzNnjj755BPdeuut2rBhgy6//HKvFUYAAAAA/BMNHgAAAACdTnV1tXbv3q1Zs2bpxhtv1NChQ/X999+fsv6yyy7Thx9+6DW2bds2r/tDhw7V5s2bvcY2b96sIUOGyGw2tzqb1WqVJK9VPx1tyJAhmjp1qtavXy+73a5XXnnlvD02AAAAgM6JBg8AAACATqdv374KDQ3Viy++qL1792rDhg3KyMg4Zf0DDzygf//73/r973+vzz//XG+//bZWrFgh6f+viMnMzNS7776rJ598Up9//rleffVV/elPf1JWVtZZZRs0aJBMJpMKCwt18OBBHT16tM1f55nU1dVp8uTJeu+997R//35t3rxZ27Zt09ChQzvsMQEAAAD4Bho8AAAAADqdgIAA/eUvf9FHH32kYcOGaerUqVq0aNEp66OiopSfny+Hw6ErrrhCzz//vB5//HFJUlBQkCTpyiuv1Ntvv62//OUvGjZsmJ544gnNmzdP99xzz1llu/jiizV37lw99thjCgsL0+TJk9v8dZ6J2WxWdXW1fvvb32rIkCG6/fbbNWbMGM2dO7fDHhMAAACAbzC53W630SEAAAAAoL099dRTWrZsmb766iujowAAAABAu7MYHQAAAAAA2sPSpUt1zTXXKDQ0VJs3b9aiRYs6dHUNAAAAABiJBg8AAACALmHPnj2aP3++Dh06pIEDByozM1MzZswwOhYAAAAAdAi2aAMAAAAAAAAAAPAxAUYHAAAAAAAAAAAAwNmhwQMAAAAAAAAAAOBjaPAAAAAAAAAAAAD4GBo8AAAAAAAAAAAAPoYGDwAAAAAAAAAAgI+hwQMAAAAAAAAAAOBjaPAAAAAAAAAAAAD4GBo8AAAAAAAAAAAAPoYGDwAAAAAAAAAAgI/5fwru91RMZGVPAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Show a box plot of the number of DecideAndOr events per algorithm\n", + "ax = decide_and_or_per_algorithm.boxplot(by='algorithms', column='DecideAndOr', figsize=(20, 10))\n", + "ax.set_ylim(0, 200)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
DecideAndOr
algorithms
['qp']13.0
['greedy', 'qp']8.0
['greedy']0.0
[]0.0
\n", + "
" + ], + "text/plain": [ + " DecideAndOr\n", + "algorithms \n", + "['qp'] 13.0\n", + "['greedy', 'qp'] 8.0\n", + "['greedy'] 0.0\n", + "[] 0.0" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Compute the median number of DecideAndOr events per algorithm\n", + "decide_and_or_per_algorithm.groupby('algorithms').median().sort_values(by='DecideAndOr', ascending=False)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "env", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.2" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/benchmark_silicon/Makefile b/benchmark_silicon/Makefile new file mode 100644 index 00000000000..a1af4696b08 --- /dev/null +++ b/benchmark_silicon/Makefile @@ -0,0 +1,3 @@ +env: + python3 -m venv env + env/bin/pip install jupyter notebook mypy pandas pylint matplotlib z3-solver diff --git a/benchmark_silicon/benchmark_silicon.py b/benchmark_silicon/benchmark_silicon.py new file mode 100644 index 00000000000..724e7572910 --- /dev/null +++ b/benchmark_silicon/benchmark_silicon.py @@ -0,0 +1,357 @@ +#!/usr/bin/python3 + +import argparse +import os +import glob +from pathlib import Path +import csv +import subprocess +import json +import datetime + +def is_test_ignored(file_path): + for line in open(file_path): + if 'IgnoreFile(/silicon' in line: + return True + if 'IgnoreFile(/Silicon' in line: + return True + if 'IgnoreFile(/silver' in line: + return True + if 'IgnoreFile(/Silver' in line: + return True + return False + +class Test: + + def __init__(self, identifier, file_path): + self.identifier = identifier + self.file_path = file_path + self.is_ignored = is_test_ignored(file_path) + self.args = None + self.result = None + self.stdout = None + self.stderr = None + self.start_time = None + self.end_time = None + self.duration = None + self.algorithms = [] + self.wands = [] + self.log_files = [] + self.trace_files = [] + self.event_kinds = [] + self.smt2_events = [] + + def into_row(self): + return [ + self.file_path, + self.is_ignored, + self.args, + self.result, + self.stdout, + self.stderr, + self.algorithms, + self.wands, + ] + + def into_dict(self): + return { + 'identifier': self.identifier, + 'file_path': str(self.file_path), + 'is_ignored': self.is_ignored, + 'args': self.args, + 'result': self.result, + 'stdout': self.stdout, + 'stderr': self.stderr, + 'start_time': str(self.start_time) if self.start_time else None, + 'end_time': str(self.end_time) if self.end_time else None, + 'duration': self.duration.total_seconds() if self.duration else None, + 'algorithms': self.algorithms, + 'wands': self.wands, + 'log_files': self.log_files, + 'trace_files': self.trace_files, + 'event_kinds': self.event_kinds, + 'smt2_events': self.smt2_events, + } + + def execute( + self, + viper_server_jar, + z3_exe, + temp_directory, + silicon_flags, + ): + """ + java + -Xss1024m -Xmx4024m \\ + -cp viper_tools/backends/viperserver.jar \\ + viper.silicon.SiliconRunner \\ + --z3Exe z3-4.8.7-x64-ubuntu-16.04/bin/z3 \\ + --numberOfParallelVerifiers=1 \\ + --logLevel TRACE \\ + --tempDirectory log/viper_tmp/deadlock \\ + --maskHeapMode \\ + file + """ + args = [ + 'java', + '-Xss1024m', + '-Xmx4024m', + '-cp', viper_server_jar, + 'viper.silicon.SiliconRunner', + '--z3Exe', z3_exe, + '--numberOfParallelVerifiers=1', + '--logLevel', 'TRACE', + '--enableTempDirectory', + '--tempDirectory', temp_directory, + ] + silicon_flags + [self.file_path] + self.args = ' '.join(str(arg) for arg in args) + try: + self.stdout = os.path.join(temp_directory, 'stdout') + self.stderr = os.path.join(temp_directory, 'stderr') + stdout = open(self.stdout, 'w') + stderr = open(self.stderr, 'w') + self.start_time = datetime.datetime.now() + result = subprocess.run( + args, + timeout=120, + stdout=stdout, + stderr=stderr, + ) + self.end_time = datetime.datetime.now() + self.duration = self.end_time - self.start_time + except subprocess.TimeoutExpired: + self.result = 'timeout' + if result.returncode == 0: + self.result = 'success' + else: + self.result = 'failure' + + def analyze_log(self): + with open(self.stdout) as fp: + for line in fp: + if ' - Predicate ' in line: + suffix = line.split(' - Predicate ')[1].strip() + (predicate, algorithm) = suffix.split(' algorithm ') + self.algorithms.append((predicate, algorithm)) + if ' - Field ' in line: + suffix = line.split(' - Field ')[1].strip() + (predicate, algorithm) = suffix.split(' algorithm ') + self.algorithms.append((predicate, algorithm)) + if ' - Quantified wands: ' in line: + wands_count = line.split(' - Quantified wands: ')[1] + self.wands.append(int(wands_count)) + + def count_push_pop_operations(self, temp_directory): + for log_file in sorted(glob.glob(os.path.join(temp_directory, 'logfile-*.smt2'))): + push_count = 0 + pop_count = 0 + with open(log_file) as fp: + for line in fp: + if line.startswith('(push) ;'): + push_count += 1 + if line.startswith('(pop) ;'): + pop_count += 1 + self.smt2_events.append({ + 'push': push_count, + 'pop': pop_count, + }) + + def generate_z3_traces(self, z3_exe, temp_directory): + for log_file in sorted(glob.glob(os.path.join(temp_directory, 'logfile-*.smt2'))): + self.log_files.append(log_file) + trace_file = log_file.replace('.smt2', '.trace') + self.trace_files.append(trace_file) + self.run_z3(z3_exe, log_file, trace_file) + assert os.path.exists(trace_file) + + def run_z3(self, z3_exe, log_file, trace_file): + args = [ + z3_exe, + 'trace=true', + 'proof=true', + 'trace-file-name=' + trace_file, + log_file, + ] + subprocess.run( + args, + timeout=600, + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + + def parce_z3_traces(self): + if os.path.exists('target/release/smt-log-analyzer'): + analyzer = 'target/release/smt-log-analyzer' + elif os.path.exists('target/debug/smt-log-analyzer'): + analyzer = 'target/debug/smt-log-analyzer' + else: + raise Exception('not found smt-log-analyzer') + for trace_file in self.trace_files: + args = [ + analyzer, + trace_file, + ] + subprocess.run( + args, + timeout=120, + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + self.parse_event_kinds(trace_file) + + def parse_event_kinds(self, trace_file): + event_kinds = [] + with open(trace_file + '.event-kinds.csv') as fp: + for line in fp: + (event, count) = line.strip().split(',') + if event == 'Event Kind': + continue + event_kinds.append((event, int(count))) + self.event_kinds.append(event_kinds) + +def collect_tests(viper_tests_path): + print(viper_tests_path, flush=True) + tests = [] + for file_path in Path(viper_tests_path).rglob('*.vpr'): + test = Test(len(tests), file_path) + tests.append(test) + return tests + +def write_report_csv(tests, report_path): + with open(report_path, 'w') as fp: + writer = csv.writer(fp) + writer.writerow([ + 'File', + 'Ignored', + 'Command', + 'Result', + 'Stdout', + 'Stderr', + 'Algorithms', + 'Wands', + ]) + for test in tests: + writer.writerow(test.into_row()) + +def write_report_json(tests, report_path): + with open(report_path, 'w') as fp: + json.dump([test.into_dict() for test in tests], fp, sort_keys=True, indent=4) + +def execute_tests( + tests, + workspace, + viper_server_jar, + z3_exe, + silicon_flags, + ): + for test in tests: + if not test.is_ignored: + print(test.file_path, datetime.datetime.now(), flush=True) + try: + temp_directory = os.path.join(workspace, f'test-{test.identifier:04}') + os.mkdir(temp_directory) + test.execute( + viper_server_jar, + z3_exe, + temp_directory, + silicon_flags, + ) + test.analyze_log() + test.count_push_pop_operations(temp_directory) + test.generate_z3_traces(z3_exe, temp_directory) + test.parce_z3_traces() # Call Rust SMT analyzer and use its CSV. + except Exception as e: + print(e) + +def analyze_test_results(workspace): + tests = [] + for directory in os.listdir(workspace): + if not directory.startswith('test-'): + continue + temp_directory = os.path.join(workspace, directory) + log_file = os.path.join(temp_directory, 'logfile-00.smt2') + if not os.path.exists(log_file): + continue + file_path = None + with open(log_file) as fp: + for line in fp: + if line.startswith('; Input file:'): + file_path = line.split('; Input file:')[1].strip() + break + if file_path is None: + continue + identifier = int(directory.split('-')[1]) + test = Test(identifier, file_path) + tests.append(test) + test.stdout = os.path.join(temp_directory, 'stdout') + test.stderr = os.path.join(temp_directory, 'stderr') + test.analyze_log() + test.log_files = list(sorted(glob.glob(os.path.join(temp_directory, 'logfile-*.smt2')))) + test.trace_files = list(sorted(glob.glob(os.path.join(temp_directory, 'logfile-*.trace')))) + for trace_file in test.trace_files: + try: + test.parse_event_kinds(trace_file) + except Exception as e: + print(e) + return tests + +def parse_args(): + parser = argparse.ArgumentParser(description="Benchmark Silicon Z3 statistics.") + parser.add_argument( + "--viper-server-jar", + help="path of the Viper server JAR", + default='viper_tools/backends/viperserver_meilers_silicarbon.jar', + ) + parser.add_argument( + "--z3-exe", + help="path to Z3", + default='viper_tools/z3/bin/z3', + ) + parser.add_argument( + "--viper-tests", + help="path to Viper tests folder", + default='../viperserver/silicon/silver/src/test/resources/', + ) + parser.add_argument( + "--report-csv", + help="output path of the CSV file", + default='../workspace/report.csv', + ) + parser.add_argument( + "--report-json", + help="output path of the JSON file", + default='../workspace/report.json', + ) + parser.add_argument( + "--workspace", + help="the workspace directory", + default='../workspace', + ) + return parser.parse_args() + +def main(): + args = parse_args() + if not os.path.exists(args.workspace): + tests = collect_tests(args.viper_tests) + os.mkdir(args.workspace) + try: + execute_tests( + tests, + args.workspace, + args.viper_server_jar, + args.z3_exe, + [], #['--maskHeapMode'], + ) + finally: + write_report_csv(tests, args.report_csv) + write_report_json(tests, args.report_json) + else: + tests = analyze_test_results(args.workspace) + write_report_csv(tests, args.report_csv) + write_report_json(tests, args.report_json) + +if __name__ == '__main__': + main() + diff --git a/benchmark_silicon/generate_sums.py b/benchmark_silicon/generate_sums.py new file mode 100644 index 00000000000..651103476c2 --- /dev/null +++ b/benchmark_silicon/generate_sums.py @@ -0,0 +1,196 @@ +#!/usr/bin/python3 + +import z3 +import datetime + +class State: + def __init__(self): + self.address_sort = z3.DeclareSort('Address') + self.perm_sort = z3.RealSort() + self._full_permission = z3.RealVal("1") + self._no_permission = z3.RealVal("0") + self.solver = z3.Solver() + self.locations = [] + self.permissions = [] + self.sum = [] + self.non_negativity_assumptions = [] + self.exhaled_location = self.fresh_location() + + def fresh_location(self): + location = z3.Const(f"address${len(self.locations)}", self.address_sort) + self.locations.append(location) + return location + + def fresh_permission(self): + permission = z3.Const(f"permission${len(self.permissions)}", self.perm_sort) + self.permissions.append(permission) + return permission + + def full_permission(self): + return self._full_permission + + def add_summand(self, location, permission): + self.sum.append( + z3.If(self.exhaled_location == location, permission, self._no_permission) + ) + + def is_non_negative_assertion(self): + sum_expr = self._no_permission + for summand in self.sum: + sum_expr = sum_expr + summand + return sum_expr >= self._no_permission + + def generate_sum(self): + sum_expr = self._no_permission + for summand in self.sum: + sum_expr = sum_expr + summand + return sum_expr + + def is_full_assertion(self): + sum_expr = self._no_permission + for summand in self.sum: + sum_expr = sum_expr + summand + return sum_expr >= self._full_permission + + def add_non_negativity_assumption(self): + self.solver.push() + assertion = self.is_non_negative_assertion() + self.solver.add(assertion) + + def check_assertion(self, assertion): + assertion = z3.Not(assertion) + # print(assertion) + self.solver.push() + self.solver.add(assertion) + print("checking: {assertion}") +# print(self.solver) + start = datetime.datetime.now() + result = self.solver.check() + self.solver.pop() + end = datetime.datetime.now() + print(f" start: {start}") + print(f" end: {end}") + print(f" duration: {end-start}") + return result + + def check_assertion2(self, assertion): + assertion = z3.Not(assertion) + solver = z3.Solver() + solver.add(assertion) + start = datetime.datetime.now() + result = solver.check() + end = datetime.datetime.now() + print(f" start: {start}") + print(f" end: {end}") + print(f" duration: {end-start}") + return result + +def construct_sum(exhaled_location, locations, permission, no_permission): + sum_expr = no_permission + for location in locations: + sum_expr = sum_expr + z3.If( + exhaled_location == location, + permission, + no_permission, + ) + return sum_expr + +def check_size(size, add_group_assumptions): +# print(f"size: {size}") + state = State() + locations = [] + for _ in range(size): + locations.append(state.fresh_location()) + assertion = z3.BoolVal(True) + for (i, exhaled_location) in enumerate(locations): + # print(i, exhaled_location) + inhaled_sum = construct_sum( + exhaled_location, locations, state._full_permission, state._no_permission) + # print(inhaled_sum) + exhaled_sum = construct_sum( + exhaled_location, locations[:i], -state._full_permission, state._no_permission) + # print(exhaled_sum) + check = inhaled_sum + exhaled_sum >= state._full_permission + # print(check) + assertion = z3.And(assertion, check) + + if add_group_assumptions: + for (j, location_group) in enumerate(locations): + inhaled = z3.If( + exhaled_location == location_group, + state._full_permission, + state._no_permission, + ) + exhaled = z3.If( + exhaled_location == location_group, + -state._full_permission, + state._no_permission, + ) + if j < i: + conjunct = inhaled + exhaled >= 0 + else: + conjunct = inhaled >= 0 + # print(conjunct) + assertion = z3.And(assertion, conjunct) + +# print(assertion) + assertion = z3.Not(assertion) + state.solver.add(assertion) + start = datetime.datetime.now() + result = state.solver.check() + end = datetime.datetime.now() +# print(f" start: {start}") +# print(f" end: {end}") +# print(f" duration: {end-start}") +# print(result) + print(f"Size {size} completed in {end-start} with {result}") + # add_group_assumptions=False + # Size 1 completed in 0:00:00.005373 + # Size 2 completed in 0:00:00.006233 + # Size 3 completed in 0:00:00.006431 + # Size 4 completed in 0:00:00.009251 + # Size 5 completed in 0:00:00.011879 + # Size 6 completed in 0:00:00.013903 + # Size 7 completed in 0:00:00.018198 + # Size 8 completed in 0:00:00.020426 + # Size 9 completed in 0:00:00.027054 + # Size 10 completed in 0:00:00.045473 + # Size 11 completed in 0:00:00.078226 + # Size 12 completed in 0:00:00.149595 + # Size 13 completed in 0:00:00.280547 + # Size 14 completed in 0:00:00.558921 + # Size 15 completed in 0:00:01.145783 + # Size 16 completed in 0:00:02.419031 + # Size 17 completed in 0:00:05.444189 + # Size 18 completed in 0:00:10.202696 + # Size 19 completed in 0:00:28.442166 + # Size 20 completed in 0:01:32.388904 + # add_group_assumptions=True + # Size 1 completed in 0:00:00.006661 + # Size 2 completed in 0:00:00.008673 + # Size 3 completed in 0:00:00.008395 + # Size 4 completed in 0:00:00.012773 + # Size 5 completed in 0:00:00.012729 + # Size 6 completed in 0:00:00.014022 + # Size 7 completed in 0:00:00.012290 + # Size 8 completed in 0:00:00.016343 + # Size 9 completed in 0:00:00.021220 + # Size 10 completed in 0:00:00.024776 + # Size 11 completed in 0:00:00.032785 + # Size 12 completed in 0:00:00.041238 + # Size 13 completed in 0:00:00.046568 + # Size 14 completed in 0:00:00.069082 + # Size 15 completed in 0:00:00.457555 + # Size 16 completed in 0:00:00.212668 + # Size 17 completed in 0:00:00.139427 + # Size 18 completed in 0:00:00.176176 + # Size 19 completed in 0:00:00.180726 + +def main(): + state = State() + + for i in range(1, 20): + check_size(i, True) + +if __name__ == '__main__': + main() diff --git a/benchmark_silicon/generate_sums2.py b/benchmark_silicon/generate_sums2.py new file mode 100644 index 00000000000..7b0b247991c --- /dev/null +++ b/benchmark_silicon/generate_sums2.py @@ -0,0 +1,243 @@ +#!env/bin/python3 + +# Based on https://microsoft.github.io/z3guide/programming/Example%20Programs/User%20Propagator/ + +import datetime +import union_find +import z3 + +address_sort = z3.DeclareSort('Address') +permission_mask_sort = z3.DeclareSort('PermissionMask') +perm_sort = z3.RealSort() +prop_sort = z3.BoolSort() +perm_empty = z3.PropagateFunction( + "perm_empty", permission_mask_sort, prop_sort) +perm_update = z3.PropagateFunction( + "perm_update", permission_mask_sort, address_sort, perm_sort, permission_mask_sort, prop_sort) +# perm_lookup = z3.PropagateFunction( +# "perm_lookup", permission_mask_sort, address_sort, perm_sort) +perm_read = z3.PropagateFunction( + "perm_read", permission_mask_sort, address_sort, perm_sort, prop_sort) +no_permission = z3.RealVal("0") +full_permission = z3.RealVal("1") + +def location(index): + return z3.Const(f"address${index}", address_sort) +permission_mask_counter = 0 +def perm_mask(): + global permission_mask_counter + permission_mask_counter += 1 + return z3.Const(f"perm_mask${permission_mask_counter}", permission_mask_sort) +perm_counter = 0 +def perm_amount(): + global perm_counter + perm_counter += 1 + return z3.Const(f"perm_amount${perm_counter}", perm_sort) + +class PermissionGrouping(z3.UserPropagateBase): + + def __init__(self, s=None, ctx=None, group_terms=False): + z3.UserPropagateBase.__init__(self, s, ctx) + self.add_fixed(lambda x, v : self._fixed(x, v)) + self.add_final(lambda : self._final()) + self.add_eq(lambda x, y : self._eq(x, y)) + self.add_created(lambda t : self._created(t)) + self.decide = None # It seems that UserPropagateBase is missing a field declaration. Monkey Patch for the resque! + self.add_decide(lambda t : self._decide(t)) + self._empty_masks = set() + self._mask_derived_from = {} + self._group_terms = group_terms + self.push_count = 0 + self.pop_count = 0 + self.decide_count = 0 + self.lim = [] + self.trail = [] + self.uf = union_find.UnionFind(self.trail) + + def push(self): + self.push_count += 1 + self.lim += [len(self.trail)] + + def pop(self, n): + self.pop_count += n + head = self.lim[len(self.lim) - n] + while len(self.trail) > head: + self.trail[-1]() + self.trail.pop(-1) + self.lim = self.lim[0:len(self.lim)-n] + + def _decide(self, _): + # This callback seems to be broken in the current version of z3. + self.decide_count += 1 + + def fresh(self, new_ctx): + TODO + + def _fixed(self, x, v): + # print("fixed: ", x, " := ", v) + assert z3.is_true(v) + if x.decl().eq(perm_empty): + mask = x.arg(0) + assert mask in self._empty_masks + elif x.decl().eq(perm_update): + mask = x.arg(0) + address = x.arg(1) + permission = x.arg(2) + new_mask = x.arg(3) + self._mask_derived_from[new_mask] = (mask, address, permission) + # elif x.decl().eq(perm_lookup): + # mask = x.arg(0) + # address = x.arg(1) + # self.add(mask) + # self.add(address) + # self.add(x) + elif x.decl().eq(perm_read): + mask = x.arg(0) + address = x.arg(1) + value = x.arg(2) + groups = {} + def compute_sum(mask): + if mask in self._empty_masks: + return 0 + else: + (update_mask, update_address, update_permission) = self._mask_derived_from[mask] + summand = z3.If(address == update_address, update_permission, no_permission) + if self._group_terms: + node = self.uf.node(update_address) + root_term = self.uf.find(node).term + if root_term not in groups: + groups[root_term] = [] + groups[root_term] += [(summand, update_permission)] + return summand + compute_sum(update_mask) + assumption = value == compute_sum(mask) + if self._group_terms: + # print("groups:", groups) + for group in groups.values(): + sum_expression = z3.Sum([summand for (summand, _) in group]) + sum_value = z3.simplify(sum([value for (_, value) in group])) + if sum_value.eq(no_permission) or sum_value.eq(full_permission): + assumption = z3.And(assumption, sum_expression >= 0) + # print("learned assumption:", assumption) + self.propagate(assumption, [x]) + else: + TODO + + def _final(self): + TODO + + def _eq(self, x, y): + # print(f"_eq!: {x} {v}") + self.uf.merge(x, y) + + def _created(self, t): + if t.decl().eq(perm_empty): + mask = t.arg(0) + self.add(mask) + self._empty_masks.add(mask) + elif t.decl().eq(perm_update): + mask = t.arg(0) + address = t.arg(1) + permission = t.arg(2) + new_mask = t.arg(3) + self.uf.node(address) + self.add(mask) + self.add(address) + self.add(permission) + self.add(new_mask) + # elif t.decl().eq(perm_lookup): + # mask = t.arg(0) + # address = t.arg(1) + # self.add(mask) + # self.add(address) + # self.add(t) + elif t.decl().eq(perm_read): + mask = t.arg(0) + address = t.arg(1) + self.uf.node(address) + value = t.arg(2) + self.add(mask) + self.add(address) + self.add(value) + else: + TODO + +def check_size(size, group_terms): + solver = z3.Solver() + pg = PermissionGrouping(solver, group_terms=group_terms) + + mask = perm_mask() + solver.add(perm_empty(mask)) + + addresses = [] + for i in range(size): + address = location(i) + addresses.append(address) + # inhale acc(address) + new_mask = perm_mask() + solver.add(perm_update(mask, address, 1.0, new_mask)) + mask = new_mask + + checks = z3.BoolVal(True) + for (i, address) in enumerate(addresses): + # exhale acc(address) + value = perm_amount() + solver.add(perm_read(mask, address, value)) + checks = z3.And(checks, value >= 1) + new_mask = perm_mask() + solver.add(perm_update(mask, address, -1.0, new_mask)) + mask = new_mask + + solver.add(z3.Not(checks)) + + start = datetime.datetime.now() + result = solver.check() + end = datetime.datetime.now() + + # print(solver) + # print(solver.check()) + print(f"Size {size} completed in {end-start} (decide: {pg.decide_count} push: {pg.push_count} pop: {pg.pop_count}) with {result}") + +def main(): + # check_size(3, False) + for i in range(3, 20): + check_size(i, True) + # group_terms=False + # Size 3 completed in 0:00:00.006197 (push: 11 pop: 11) with unsat + # Size 4 completed in 0:00:00.016547 (push: 50 pop: 50) with unsat + # Size 5 completed in 0:00:00.026013 (push: 136 pop: 136) with unsat + # Size 6 completed in 0:00:00.049699 (push: 264 pop: 264) with unsat + # Size 7 completed in 0:00:00.082637 (push: 403 pop: 403) with unsat + # Size 8 completed in 0:00:00.138415 (push: 599 pop: 599) with unsat + # Size 9 completed in 0:00:00.225748 (push: 914 pop: 914) with unsat + # Size 10 completed in 0:00:00.387808 (push: 1476 pop: 1476) with unsat + # Size 11 completed in 0:00:00.695106 (push: 2542 pop: 2542) with unsat + # Size 12 completed in 0:00:01.142290 (push: 3737 pop: 3737) with unsat + # Size 13 completed in 0:00:02.020809 (push: 6086 pop: 6086) with unsat + # Size 14 completed in 0:00:03.464235 (push: 10355 pop: 10355) with unsat + # Size 15 completed in 0:00:06.745241 (push: 17012 pop: 17012) with unsat + # Size 16 completed in 0:00:12.223822 (push: 31490 pop: 31490) with unsat + # Size 17 completed in 0:00:28.458352 (push: 71376 pop: 71376) with unsat + # Size 18 completed in 0:01:07.913775 (push: 163310 pop: 163310) with unsat + # Size 19 completed in 0:02:37.331497 (push: 370474 pop: 370474) with unsat + # group_terms=True + # Size 3 completed in 0:00:00.008096 (push: 8 pop: 8) with unsat + # Size 4 completed in 0:00:00.014301 (push: 26 pop: 26) with unsat + # Size 5 completed in 0:00:00.023871 (push: 59 pop: 59) with unsat + # Size 6 completed in 0:00:00.036188 (push: 126 pop: 126) with unsat + # Size 7 completed in 0:00:00.062952 (push: 312 pop: 312) with unsat + # Size 8 completed in 0:00:00.073902 (push: 335 pop: 335) with unsat + # Size 9 completed in 0:00:00.110649 (push: 625 pop: 625) with unsat + # Size 10 completed in 0:00:00.137818 (push: 733 pop: 733) with unsat + # Size 11 completed in 0:00:00.163656 (push: 687 pop: 687) with unsat + # Size 12 completed in 0:00:00.170364 (push: 1058 pop: 1058) with unsat + # Size 13 completed in 0:00:00.226454 (push: 1301 pop: 1301) with unsat + # Size 14 completed in 0:00:00.251802 (push: 1556 pop: 1556) with unsat + # Size 15 completed in 0:00:00.342975 (push: 1925 pop: 1925) with unsat + # Size 16 completed in 0:00:00.341066 (push: 2791 pop: 2791) with unsat + # Size 17 completed in 0:00:00.394620 (push: 2708 pop: 2708) with unsat + # Size 18 completed in 0:00:00.448467 (push: 3258 pop: 3258) with unsat + # Size 19 completed in 0:00:00.471308 (push: 2988 pop: 2988) with unsat + + +if __name__ == '__main__': + main() diff --git a/benchmark_silicon/union_find.py b/benchmark_silicon/union_find.py new file mode 100644 index 00000000000..ffd54d00db1 --- /dev/null +++ b/benchmark_silicon/union_find.py @@ -0,0 +1,98 @@ +# Taken from https://microsoft.github.io/z3guide/programming/Example%20Programs/User%20Propagator/ + +class Node: + def __init__(self, a): + self.term = a + self.id = a.get_id() + self.root = self + self.size = 1 + self.value = None + + def __eq__(self, other): + return self.id == other.id + + def __ne__(self, other): + return self.id != other.id + + def to_string(self): + return f"{self.term} -> r:{self.root.term}" + + def __str__(self): + return self.to_string() + +class UnionFind: + def __init__(self, trail): + self._nodes = {} + self.trail = trail + + def node(self, a): + if a in self._nodes: + return self._nodes[a] + n = Node(a) + self._nodes[a] = n + def undo(): + del self._nodes[a] + self.trail.append(undo) + return n + + def merge(self, a, b): + a = self.node(a) + b = self.node(b) + a = self.find(a) + b = self.find(b) + if a == b: + return + if a.size < b.size: + a, b = b, a + if a.value is not None and b.value is not None: + print("Merging two values", a, a.value, b, b.value) + os._exit() + value = a.value + if b.value is not None: + value = b.value + old_root = b.root + old_asize = a.size + old_bvalue = b.value + old_avalue = a.value + b.root = a.root + b.value = value + a.value = value + a.size += b.size + def undo(): + b.root = old_root + a.size = old_asize + b.value = old_bvalue + a.value = old_avalue + self.trail.append(undo) + + # skip path compression to keep the example basic + def find(self, a): + assert isinstance(a, Node) + root = a.root + while root != root.root: + root = root.root + return root + + def set_value(self, a): + n = self.find(self.node(a)) + if n.value is not None: + return + def undo(): + n.value = None + n.value = a + self.trail.append(undo) + + def get_value(self, a): + return self.find(self.node(a)).value + + def root_term(self, a): + return self.find(self.node(a)).term + + def __str__(self): + return self.to_string() + + def __repr__(self): + return self.to_string() + + def to_string(self): + return "\n".join([n.to_string() for t, n in self._nodes.items()]) diff --git a/jni-gen/systest/tests/jvm_builtin_classes.rs b/jni-gen/systest/tests/jvm_builtin_classes.rs index 523fe7d12ed..b40bf76d8be 100644 --- a/jni-gen/systest/tests/jvm_builtin_classes.rs +++ b/jni-gen/systest/tests/jvm_builtin_classes.rs @@ -10,7 +10,7 @@ fn string_to_jobject<'a>(env: &JNIEnv<'a>, string: &str) -> JNIResult(env: &JNIEnv<'a>, obj: JObject) -> JNIResult { +fn jobject_to_string(env: &JNIEnv<'_>, obj: JObject) -> JNIResult { Ok(String::from(env.get_string(JString::from(obj))?)) } diff --git a/prusti-common/src/vir/low_to_viper/ast.rs b/prusti-common/src/vir/low_to_viper/ast.rs index 6e56e887b01..9fcbf8c7edb 100644 --- a/prusti-common/src/vir/low_to_viper/ast.rs +++ b/prusti-common/src/vir/low_to_viper/ast.rs @@ -1,4 +1,5 @@ -use super::{Context, ToViper, ToViperDecl}; +use super::{calculate_hash_with_position, Context, ToViper, ToViperDecl}; +use std::collections::BTreeMap; use viper::{self, AstFactory}; use vir::low::ast::{ expression::{self, Expression}, @@ -11,17 +12,22 @@ use vir::low::ast::{ }; impl<'a, 'v> ToViper<'v, viper::Predicate<'v>> for &'a PredicateDecl { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Predicate<'v> { - ast.predicate( + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Predicate<'v> { + let mut annotations = BTreeMap::new(); + if &self.name != "LifetimeToken" { + annotations.insert("qpresource".to_string(), Vec::new()); + } + ast.predicate_with_annotations( &self.name, &self.parameters.to_viper_decl(context, ast), self.body.as_ref().map(|body| body.to_viper(context, ast)), + &annotations, ) } } impl<'a, 'v> ToViper<'v, viper::Function<'v>> for &'a FunctionDecl { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Function<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Function<'v> { ast.function( &self.name, &self.parameters.to_viper_decl(context, ast), @@ -35,7 +41,7 @@ impl<'a, 'v> ToViper<'v, viper::Function<'v>> for &'a FunctionDecl { } impl<'v> ToViper<'v, Vec>> for Vec { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> Vec> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> Vec> { self.iter() .map(|statement| statement.to_viper(context, ast)) .collect() @@ -43,9 +49,14 @@ impl<'v> ToViper<'v, Vec>> for Vec { } impl<'v> ToViper<'v, viper::Stmt<'v>> for Statement { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Stmt<'v> { - match self { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Stmt<'v> { + let statement_hash = calculate_hash_with_position(self); + if let Some(viper_statement) = context.get_cached_statement(statement_hash, self) { + return viper_statement; + } + let viper_statement = match self { Statement::Comment(statement) => statement.to_viper(context, ast), + Statement::Label(statement) => statement.to_viper(context, ast), Statement::LogEvent(statement) => statement.to_viper(context, ast), Statement::Assume(statement) => statement.to_viper(context, ast), Statement::Assert(statement) => statement.to_viper(context, ast), @@ -57,18 +68,32 @@ impl<'v> ToViper<'v, viper::Stmt<'v>> for Statement { Statement::Conditional(statement) => statement.to_viper(context, ast), Statement::MethodCall(statement) => statement.to_viper(context, ast), Statement::Assign(statement) => statement.to_viper(context, ast), - } + Statement::MaterializePredicate(statement) => { + unreachable!("should have been purified out: {statement}") + } + Statement::CaseSplit(statement) => { + unreachable!("should have been purified out: {statement}") + } + }; + context.cache_statement(statement_hash, self, viper_statement); + viper_statement } } impl<'v> ToViper<'v, viper::Stmt<'v>> for statement::Comment { - fn to_viper(&self, _context: Context, ast: &AstFactory<'v>) -> viper::Stmt<'v> { + fn to_viper(&self, _context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Stmt<'v> { ast.comment(&self.comment) } } +impl<'v> ToViper<'v, viper::Stmt<'v>> for statement::Label { + fn to_viper(&self, _context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Stmt<'v> { + ast.label(&self.label, &[]) + } +} + impl<'v> ToViper<'v, viper::Stmt<'v>> for statement::LogEvent { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Stmt<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Stmt<'v> { assert!( self.expression.is_domain_func_app(), "The log event has to be a domain function application: {self}" @@ -78,7 +103,7 @@ impl<'v> ToViper<'v, viper::Stmt<'v>> for statement::LogEvent { } impl<'v> ToViper<'v, viper::Stmt<'v>> for statement::Assume { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Stmt<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Stmt<'v> { assert!( !self.position.is_default(), "Statement with default position: {self}" @@ -91,7 +116,7 @@ impl<'v> ToViper<'v, viper::Stmt<'v>> for statement::Assume { } impl<'v> ToViper<'v, viper::Stmt<'v>> for statement::Assert { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Stmt<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Stmt<'v> { assert!( !self.position.is_default(), "Statement with default position: {self}" @@ -104,7 +129,7 @@ impl<'v> ToViper<'v, viper::Stmt<'v>> for statement::Assert { } impl<'v> ToViper<'v, viper::Stmt<'v>> for statement::Inhale { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Stmt<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Stmt<'v> { assert!( !self.position.is_default(), "Statement with default position: {self}" @@ -117,7 +142,7 @@ impl<'v> ToViper<'v, viper::Stmt<'v>> for statement::Inhale { } impl<'v> ToViper<'v, viper::Stmt<'v>> for statement::Exhale { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Stmt<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Stmt<'v> { assert!( !self.position.is_default(), "Statement with default position: {self}" @@ -130,11 +155,16 @@ impl<'v> ToViper<'v, viper::Stmt<'v>> for statement::Exhale { } impl<'v> ToViper<'v, viper::Stmt<'v>> for statement::Fold { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Stmt<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Stmt<'v> { assert!( !self.position.is_default(), "Statement with default position: {self}" ); + assert!( + self.expression.is_predicate_access_predicate(), + "fold {}", + self.expression + ); ast.fold_with_pos( self.expression.to_viper(context, ast), self.position.to_viper(context, ast), @@ -143,11 +173,16 @@ impl<'v> ToViper<'v, viper::Stmt<'v>> for statement::Fold { } impl<'v> ToViper<'v, viper::Stmt<'v>> for statement::Unfold { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Stmt<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Stmt<'v> { assert!( !self.position.is_default(), "Statement with default position: {self}" ); + assert!( + self.expression.is_predicate_access_predicate(), + "unfold {}", + self.expression + ); ast.unfold_with_pos( self.expression.to_viper(context, ast), self.position.to_viper(context, ast), @@ -156,7 +191,7 @@ impl<'v> ToViper<'v, viper::Stmt<'v>> for statement::Unfold { } impl<'v> ToViper<'v, viper::Stmt<'v>> for statement::ApplyMagicWand { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Stmt<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Stmt<'v> { assert!( !self.position.is_default(), "Statement with default position: {self}" @@ -169,7 +204,7 @@ impl<'v> ToViper<'v, viper::Stmt<'v>> for statement::ApplyMagicWand { } impl<'v> ToViper<'v, viper::Stmt<'v>> for statement::Conditional { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Stmt<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Stmt<'v> { assert!( !self.position.is_default(), "Statement with default position: {self}" @@ -183,7 +218,7 @@ impl<'v> ToViper<'v, viper::Stmt<'v>> for statement::Conditional { } impl<'v> ToViper<'v, viper::Stmt<'v>> for statement::MethodCall { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Stmt<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Stmt<'v> { assert!( !self.position.is_default(), "Statement with default position: {self}" @@ -198,7 +233,11 @@ impl<'v> ToViper<'v, viper::Stmt<'v>> for statement::MethodCall { } impl<'v> ToViper<'v, viper::Stmt<'v>> for statement::Assign { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Stmt<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Stmt<'v> { + assert!( + !self.position.is_default(), + "Statement with default position: {self}" + ); let target_expression = Expression::local(self.target.clone(), self.position); ast.abstract_assign( target_expression.to_viper(context, ast), @@ -208,7 +247,7 @@ impl<'v> ToViper<'v, viper::Stmt<'v>> for statement::Assign { } impl<'v> ToViper<'v, Vec>> for Vec { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> Vec> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> Vec> { self.iter() .map(|expression| expression.to_viper(context, ast)) .collect() @@ -216,7 +255,11 @@ impl<'v> ToViper<'v, Vec>> for Vec { } impl<'v> ToViper<'v, viper::Expr<'v>> for Expression { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Expr<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Expr<'v> { + let expression_hash = calculate_hash_with_position(self); + if let Some(viper_expression) = context.get_cached_expression(expression_hash, self) { + return viper_expression; + } let expression = match self { Expression::Local(expression) => expression.to_viper(context, ast), // Expression::Field(expression) => expression.to_viper(context, ast), @@ -225,30 +268,32 @@ impl<'v> ToViper<'v, viper::Expr<'v>> for Expression { Expression::MagicWand(expression) => expression.to_viper(context, ast), Expression::PredicateAccessPredicate(expression) => expression.to_viper(context, ast), // Expression::FieldAccessPredicate(expression) => expression.to_viper(context, ast), - // Expression::Unfolding(expression) => expression.to_viper(context, ast), + Expression::Unfolding(expression) => expression.to_viper(context, ast), Expression::UnaryOp(expression) => expression.to_viper(context, ast), Expression::BinaryOp(expression) => expression.to_viper(context, ast), Expression::PermBinaryOp(expression) => expression.to_viper(context, ast), Expression::ContainerOp(expression) => expression.to_viper(context, ast), Expression::Conditional(expression) => expression.to_viper(context, ast), Expression::Quantifier(expression) => expression.to_viper(context, ast), - // Expression::LetExpr(expression) => expression.to_viper(context, ast), + Expression::LetExpr(expression) => expression.to_viper(context, ast), Expression::FuncApp(expression) => expression.to_viper(context, ast), Expression::DomainFuncApp(expression) => expression.to_viper(context, ast), // Expression::InhaleExhale(expression) => expression.to_viper(context, ast), x => unimplemented!("{:?}", x), }; - if crate::config::simplify_encoding() { + let viper_expression = if crate::config::simplify_encoding() { ast.simplified_expression(expression) } else { expression - } + }; + context.cache_expression(expression_hash, self, viper_expression); + viper_expression } } impl<'v> ToViper<'v, viper::Expr<'v>> for expression::Local { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Expr<'v> { - if self.variable.name == "__result" { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Expr<'v> { + if self.variable.is_result_variable() { ast.result_with_pos( self.variable.ty.to_viper(context, ast), self.position.to_viper(context, ast), @@ -264,7 +309,7 @@ impl<'v> ToViper<'v, viper::Expr<'v>> for expression::Local { } impl<'v> ToViper<'v, viper::Expr<'v>> for expression::LabelledOld { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Expr<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Expr<'v> { if let Some(label) = &self.label { ast.labelled_old_with_pos( self.base.to_viper(context, ast), @@ -278,7 +323,7 @@ impl<'v> ToViper<'v, viper::Expr<'v>> for expression::LabelledOld { } impl<'v> ToViper<'v, viper::Expr<'v>> for expression::Constant { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Expr<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Expr<'v> { match &self.ty { Type::Int => match &self.value { expression::ConstantValue::Bool(_) => { @@ -327,7 +372,7 @@ impl<'v> ToViper<'v, viper::Expr<'v>> for expression::Constant { } impl<'v> ToViper<'v, viper::Expr<'v>> for expression::MagicWand { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Expr<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Expr<'v> { ast.magic_wand_with_pos( self.left.to_viper(context, ast), self.right.to_viper(context, ast), @@ -337,7 +382,7 @@ impl<'v> ToViper<'v, viper::Expr<'v>> for expression::MagicWand { } impl<'v> ToViper<'v, viper::Expr<'v>> for expression::PredicateAccessPredicate { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Expr<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Expr<'v> { let location = ast.predicate_access(&self.arguments.to_viper(context, ast), &self.name); if context.inside_trigger { location @@ -351,8 +396,18 @@ impl<'v> ToViper<'v, viper::Expr<'v>> for expression::PredicateAccessPredicate { } } +impl<'v> ToViper<'v, viper::Expr<'v>> for expression::Unfolding { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Expr<'v> { + ast.unfolding_with_pos( + self.predicate.to_viper(context, ast), + self.base.to_viper(context, ast), + self.position.to_viper(context, ast), + ) + } +} + impl<'v> ToViper<'v, viper::Expr<'v>> for expression::UnaryOp { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Expr<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Expr<'v> { match self.op_kind { expression::UnaryOpKind::Minus => ast.minus_with_pos( self.argument.to_viper(context, ast), @@ -367,7 +422,7 @@ impl<'v> ToViper<'v, viper::Expr<'v>> for expression::UnaryOp { } impl<'v> ToViper<'v, viper::Expr<'v>> for expression::BinaryOp { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Expr<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Expr<'v> { match self.op_kind { expression::BinaryOpKind::EqCmp => ast.eq_cmp_with_pos( self.left.to_viper(context, ast), @@ -444,7 +499,7 @@ impl<'v> ToViper<'v, viper::Expr<'v>> for expression::BinaryOp { } impl<'v> ToViper<'v, viper::Expr<'v>> for expression::PermBinaryOp { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Expr<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Expr<'v> { match self.op_kind { expression::PermBinaryOpKind::Add => ast.perm_add( self.left.to_viper(context, ast), @@ -467,7 +522,7 @@ impl<'v> ToViper<'v, viper::Expr<'v>> for expression::PermBinaryOp { } impl<'v> ToViper<'v, viper::Expr<'v>> for expression::Conditional { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Expr<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Expr<'v> { ast.cond_exp_with_pos( self.guard.to_viper(context, ast), self.then_expr.to_viper(context, ast), @@ -478,7 +533,7 @@ impl<'v> ToViper<'v, viper::Expr<'v>> for expression::Conditional { } impl<'v> ToViper<'v, viper::Expr<'v>> for expression::Quantifier { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Expr<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Expr<'v> { let variables = self.variables.to_viper_decl(context, ast); let triggers = self .triggers @@ -499,17 +554,30 @@ impl<'v> ToViper<'v, viper::Expr<'v>> for expression::Quantifier { } impl<'v, 'a> ToViper<'v, viper::Trigger<'v>> for (&'a expression::Trigger, Position) { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Trigger<'v> { - let trigger_context = context.set_inside_trigger(); - ast.trigger_with_pos( - &self.0.terms.to_viper(trigger_context, ast)[..], + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Trigger<'v> { + let old_value = context.set_inside_trigger(); + let trigger = ast.trigger_with_pos( + &self.0.terms.to_viper(context, ast)[..], self.1.to_viper(context, ast), + ); + context.reset_inside_trigger(old_value); + trigger + } +} + +impl<'v> ToViper<'v, viper::Expr<'v>> for expression::LetExpr { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Expr<'v> { + ast.let_expr_with_pos( + self.variable.to_viper_decl(context, ast), + self.def.to_viper(context, ast), + self.body.to_viper(context, ast), + self.position.to_viper(context, ast), ) } } impl<'v> ToViper<'v, viper::Expr<'v>> for expression::FuncApp { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Expr<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Expr<'v> { ast.func_app( &self.function_name, &self.arguments.to_viper(context, ast), @@ -520,7 +588,7 @@ impl<'v> ToViper<'v, viper::Expr<'v>> for expression::FuncApp { } impl<'v> ToViper<'v, viper::Expr<'v>> for expression::DomainFuncApp { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Expr<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Expr<'v> { ast.domain_func_app2( &self.function_name, &self.arguments.to_viper(context, ast), @@ -533,81 +601,142 @@ impl<'v> ToViper<'v, viper::Expr<'v>> for expression::DomainFuncApp { } impl<'v> ToViper<'v, viper::Expr<'v>> for expression::ContainerOp { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Expr<'v> { - let element_type = || match &self.container_type { - Type::Seq(ty::Seq { element_type, .. }) - | Type::Set(ty::Set { element_type, .. }) - | Type::MultiSet(ty::MultiSet { element_type, .. }) => { - element_type.to_viper(context, ast) - } - _ => unreachable!("{}", self.container_type), - }; - let key_value_types = || match &self.container_type { - Type::Map(ty::Map { key_type, val_type }) => { - let key_type = key_type.to_viper(context, ast); - let val_type = val_type.to_viper(context, ast); - (key_type, val_type) + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Expr<'v> { + fn element_type<'v>( + container_type: &Type, + context: &mut Context<'v>, + ast: &AstFactory<'v>, + ) -> viper::Type<'v> { + match container_type { + Type::Seq(ty::Seq { element_type, .. }) + | Type::Set(ty::Set { element_type, .. }) + | Type::MultiSet(ty::MultiSet { element_type, .. }) => { + element_type.to_viper(context, ast) + } + _ => unreachable!(), } - _ => unreachable!(), - }; - let arg = |idx| (&self.operands[idx] as &Expression).to_viper(context, ast); - let args = || { - self.operands + } + // let element_type = || match &self.container_type { + // Type::Seq(ty::Seq { element_type, .. }) + // | Type::Set(ty::Set { element_type, .. }) + // | Type::MultiSet(ty::MultiSet { element_type, .. }) => { + // element_type.to_viper(context, ast) + // } + // _ => unreachable!("{}", self.container_type), + // }; + // let key_value_types = || match &self.container_type { + // Type::Map(ty::Map { key_type, val_type }) => { + // let key_type = key_type.to_viper(context, ast); + // let val_type = val_type.to_viper(context, ast); + // (key_type, val_type) + // } + // _ => unreachable!(), + // }; + fn arg<'v>( + this: &expression::ContainerOp, + idx: usize, + context: &mut Context<'v>, + ast: &AstFactory<'v>, + ) -> viper::Expr<'v> { + (&this.operands[idx] as &Expression).to_viper(context, ast) + } + fn args<'v>( + this: &expression::ContainerOp, + context: &mut Context<'v>, + ast: &AstFactory<'v>, + ) -> Vec> { + this.operands .iter() .map(|operand| operand.to_viper(context, ast)) .collect::>() - }; + } + // let arg = |idx| (&self.operands[idx] as &Expression).to_viper(context, ast); + // let args = || { + // self.operands + // .iter() + // .map(|operand| operand.to_viper(context, ast)) + // .collect::>() + // }; match self.kind { - expression::ContainerOpKind::SeqEmpty => ast.empty_seq(element_type()), + expression::ContainerOpKind::SeqEmpty => { + ast.empty_seq(element_type(&self.container_type, context, ast)) + } expression::ContainerOpKind::SeqConstructor => { - let elements = args(); + let elements = args(self, context, ast); if elements.is_empty() { - ast.empty_seq(element_type()) + ast.empty_seq(element_type(&self.container_type, context, ast)) } else { ast.explicit_seq(&elements) } } - expression::ContainerOpKind::SeqIndex => ast.seq_index(arg(0), arg(1)), - expression::ContainerOpKind::SeqConcat => ast.seq_append(arg(0), arg(1)), - expression::ContainerOpKind::SeqLen => ast.seq_length(arg(0)), + expression::ContainerOpKind::SeqIndex => { + ast.seq_index(arg(self, 0, context, ast), arg(self, 1, context, ast)) + } + expression::ContainerOpKind::SeqConcat => { + ast.seq_append(arg(self, 0, context, ast), arg(self, 1, context, ast)) + } + expression::ContainerOpKind::SeqLen => ast.seq_length(arg(self, 0, context, ast)), expression::ContainerOpKind::MapEmpty => { - let (key_ty, val_ty) = key_value_types(); - ast.empty_map(key_ty, val_ty) + // let (key_ty, val_ty) = key_value_types(); + let Type::Map(ty::Map { key_type, val_type }) = &self.container_type else { + unreachable!() + }; + let key_type = key_type.to_viper(context, ast); + let val_type = val_type.to_viper(context, ast); + ast.empty_map(key_type, val_type) + } + expression::ContainerOpKind::MapUpdate => ast.update_map( + arg(self, 0, context, ast), + arg(self, 1, context, ast), + arg(self, 2, context, ast), + ), + expression::ContainerOpKind::MapContains => { + ast.map_contains(arg(self, 0, context, ast), arg(self, 1, context, ast)) + } + expression::ContainerOpKind::MapLookup => { + ast.lookup_map(arg(self, 0, context, ast), arg(self, 1, context, ast)) + } + expression::ContainerOpKind::MapLen => ast.map_len(arg(self, 0, context, ast)), + expression::ContainerOpKind::SetEmpty => { + ast.empty_set(element_type(&self.container_type, context, ast)) } - expression::ContainerOpKind::MapUpdate => ast.update_map(arg(0), arg(1), arg(2)), - expression::ContainerOpKind::MapContains => ast.map_contains(arg(0), arg(1)), - expression::ContainerOpKind::MapLookup => ast.lookup_map(arg(0), arg(1)), - expression::ContainerOpKind::MapLen => ast.map_len(arg(0)), - expression::ContainerOpKind::SetEmpty => ast.empty_set(element_type()), expression::ContainerOpKind::SetConstructor => { - let elements = args(); + let elements = args(self, context, ast); if elements.is_empty() { - ast.empty_set(element_type()) + ast.empty_set(element_type(&self.container_type, context, ast)) } else { ast.explicit_set(&elements) } } expression::ContainerOpKind::SetUnion | expression::ContainerOpKind::MultiSetUnion => { - ast.any_set_union(arg(0), arg(1)) + ast.any_set_union(arg(self, 0, context, ast), arg(self, 1, context, ast)) } expression::ContainerOpKind::SetIntersection | expression::ContainerOpKind::MultiSetIntersection => { - ast.any_set_intersection(arg(0), arg(1)) + ast.any_set_intersection(arg(self, 0, context, ast), arg(self, 1, context, ast)) } expression::ContainerOpKind::SetSubset - | expression::ContainerOpKind::MultiSetSubset => ast.any_set_subset(arg(0), arg(1)), + | expression::ContainerOpKind::MultiSetSubset => { + ast.any_set_subset(arg(self, 0, context, ast), arg(self, 1, context, ast)) + } expression::ContainerOpKind::SetMinus | expression::ContainerOpKind::MultiSetMinus => { - ast.any_set_minus(arg(0), arg(1)) + ast.any_set_minus(arg(self, 0, context, ast), arg(self, 1, context, ast)) } expression::ContainerOpKind::SetContains - | expression::ContainerOpKind::MultiSetContains => ast.any_set_contains(arg(0), arg(1)), + | expression::ContainerOpKind::MultiSetContains => { + ast.any_set_contains(arg(self, 0, context, ast), arg(self, 1, context, ast)) + } expression::ContainerOpKind::SetCardinality - | expression::ContainerOpKind::MultiSetCardinality => ast.any_set_cardinality(arg(0)), - expression::ContainerOpKind::MultiSetEmpty => ast.empty_multiset(element_type()), + | expression::ContainerOpKind::MultiSetCardinality => { + ast.any_set_cardinality(arg(self, 0, context, ast)) + } + expression::ContainerOpKind::MultiSetEmpty => { + ast.empty_multiset(element_type(&self.container_type, context, ast)) + } expression::ContainerOpKind::MultiSetConstructor => { - let elements = args(); + let elements = args(self, context, ast); if elements.is_empty() { - ast.empty_multiset(element_type()) + ast.empty_multiset(element_type(&self.container_type, context, ast)) } else { ast.explicit_multiset(&elements) } @@ -617,13 +746,13 @@ impl<'v> ToViper<'v, viper::Expr<'v>> for expression::ContainerOp { } impl<'v> ToViper<'v, viper::Position<'v>> for Position { - fn to_viper(&self, _context: Context, ast: &AstFactory<'v>) -> viper::Position<'v> { + fn to_viper(&self, _context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Position<'v> { ast.identifier_position(self.line, self.column, self.id.to_string()) } } impl<'v> ToViper<'v, viper::Type<'v>> for Type { - fn to_viper(&self, _context: Context, ast: &AstFactory<'v>) -> viper::Type<'v> { + fn to_viper(&self, _context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Type<'v> { match self { Type::Int => ast.int_type(), Type::Bool => ast.bool_type(), @@ -659,7 +788,7 @@ impl<'v> ToViper<'v, viper::Type<'v>> for Type { impl<'v> ToViperDecl<'v, Vec>> for Vec { fn to_viper_decl( &self, - context: Context, + context: &mut Context<'v>, ast: &AstFactory<'v>, ) -> Vec> { self.iter() @@ -669,7 +798,11 @@ impl<'v> ToViperDecl<'v, Vec>> for Vec { } impl<'v> ToViperDecl<'v, viper::LocalVarDecl<'v>> for VariableDecl { - fn to_viper_decl(&self, context: Context, ast: &AstFactory<'v>) -> viper::LocalVarDecl<'v> { + fn to_viper_decl( + &self, + context: &mut Context<'v>, + ast: &AstFactory<'v>, + ) -> viper::LocalVarDecl<'v> { ast.local_var_decl(&self.name, self.ty.to_viper(context, ast)) } } diff --git a/prusti-common/src/vir/low_to_viper/cfg.rs b/prusti-common/src/vir/low_to_viper/cfg.rs index 95a937d337c..6244c35504c 100644 --- a/prusti-common/src/vir/low_to_viper/cfg.rs +++ b/prusti-common/src/vir/low_to_viper/cfg.rs @@ -1,6 +1,7 @@ use super::{Context, ToViper, ToViperDecl}; use viper::{self, AstFactory}; use vir::{ + common::cfg::Cfg, legacy::RETURN_LABEL, low::{ ast::position::Position, @@ -9,27 +10,32 @@ use vir::{ }; impl<'a, 'v> ToViper<'v, viper::Method<'v>> for &'a ProcedureDecl { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Method<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Method<'v> { let mut statements: Vec = vec![]; let mut declarations: Vec = vec![]; for local in &self.locals { declarations.push(local.to_viper_decl(context, ast).into()); } - for block in &self.basic_blocks { - declarations.push(block.label.to_viper_decl(context, ast).into()); - statements.push(block.label.to_viper(context, ast)); + let traversal_order = self.get_topological_sort(); + for label in &traversal_order { + let block = self.basic_blocks.get(label).unwrap(); + declarations.push(label.to_viper_decl(context, ast).into()); + statements.push(label.to_viper(context, ast)); statements.extend(block.statements.to_viper(context, ast)); statements.push(block.successor.to_viper(context, ast)); } statements.push(ast.label(RETURN_LABEL, &[])); declarations.push(ast.label(RETURN_LABEL, &[]).into()); + for label in &self.custom_labels { + declarations.push(label.to_viper_decl(context, ast).into()); + } let body = Some(ast.seqn(&statements, &declarations)); ast.method(&self.name, &[], &[], &[], &[], body) } } impl<'v> ToViper<'v, viper::Stmt<'v>> for Successor { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Stmt<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Stmt<'v> { match self { Successor::Goto(target) => ast.goto(&target.name), Successor::GotoSwitch(targets) => { @@ -54,19 +60,19 @@ impl<'v> ToViper<'v, viper::Stmt<'v>> for Successor { } impl<'v> ToViperDecl<'v, viper::Stmt<'v>> for Label { - fn to_viper_decl(&self, _context: Context, ast: &AstFactory<'v>) -> viper::Stmt<'v> { + fn to_viper_decl(&self, _context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Stmt<'v> { ast.label(&self.name, &[]) } } impl<'v> ToViper<'v, viper::Stmt<'v>> for Label { - fn to_viper(&self, _context: Context, ast: &AstFactory<'v>) -> viper::Stmt<'v> { + fn to_viper(&self, _context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Stmt<'v> { ast.label(&self.name, &[]) } } impl<'a, 'v> ToViper<'v, viper::Method<'v>> for &'a MethodDecl { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Method<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Method<'v> { let body = self .body .as_ref() diff --git a/prusti-common/src/vir/low_to_viper/domain.rs b/prusti-common/src/vir/low_to_viper/domain.rs index 6a3e4dee445..26221cc415a 100644 --- a/prusti-common/src/vir/low_to_viper/domain.rs +++ b/prusti-common/src/vir/low_to_viper/domain.rs @@ -1,20 +1,26 @@ use super::{Context, ToViper, ToViperDecl}; use viper::{self, AstFactory}; -use vir::low::{DomainAxiomDecl, DomainDecl, DomainFunctionDecl}; +use vir::low::{DomainAxiomDecl, DomainDecl, DomainFunctionDecl, DomainRewriteRuleDecl}; impl<'a, 'v> ToViper<'v, viper::Domain<'v>> for &'a DomainDecl { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Domain<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Domain<'v> { + let mut axioms = (&self.name, &self.axioms).to_viper(context, ast); + axioms.extend((&self.name, &self.rewrite_rules).to_viper(context, ast)); ast.domain( &self.name, &(&self.name, &self.functions).to_viper(context, ast), - &(&self.name, &self.axioms).to_viper(context, ast), + &axioms, &[], ) } } impl<'a, 'v> ToViper<'v, Vec>> for (&'a String, &'a Vec) { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> Vec> { + fn to_viper( + &self, + context: &mut Context<'v>, + ast: &AstFactory<'v>, + ) -> Vec> { self.1 .iter() .map(|function| (self.0, function).to_viper(context, ast)) @@ -23,7 +29,7 @@ impl<'a, 'v> ToViper<'v, Vec>> for (&'a String, &'a Vec ToViper<'v, viper::DomainFunc<'v>> for (&'a String, &'a DomainFunctionDecl) { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::DomainFunc<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::DomainFunc<'v> { let (domain_name, function) = self; ast.domain_func( &function.name, @@ -38,7 +44,11 @@ impl<'a, 'v> ToViper<'v, viper::DomainFunc<'v>> for (&'a String, &'a DomainFunct impl<'a, 'v> ToViper<'v, Vec>> for (&'a String, &'a Vec) { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> Vec> { + fn to_viper( + &self, + context: &mut Context<'v>, + ast: &AstFactory<'v>, + ) -> Vec> { self.1 .iter() .map(|axiom| (self.0, axiom).to_viper(context, ast)) @@ -47,7 +57,11 @@ impl<'a, 'v> ToViper<'v, Vec>> } impl<'a, 'v> ToViper<'v, viper::NamedDomainAxiom<'v>> for (&'a String, &'a DomainAxiomDecl) { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::NamedDomainAxiom<'v> { + fn to_viper( + &self, + context: &mut Context<'v>, + ast: &AstFactory<'v>, + ) -> viper::NamedDomainAxiom<'v> { let (domain_name, axiom) = self; if let Some(comment) = &axiom.comment { ast.named_domain_axiom_with_comment( @@ -61,3 +75,41 @@ impl<'a, 'v> ToViper<'v, viper::NamedDomainAxiom<'v>> for (&'a String, &'a Domai } } } + +impl<'a, 'v> ToViper<'v, Vec>> + for (&'a String, &'a Vec) +{ + fn to_viper( + &self, + context: &mut Context<'v>, + ast: &AstFactory<'v>, + ) -> Vec> { + self.1 + .iter() + .filter(|rule| !rule.egg_only) + .map(|axiom| (self.0, axiom).to_viper(context, ast)) + .collect() + } +} + +impl<'a, 'v> ToViper<'v, viper::NamedDomainAxiom<'v>> for (&'a String, &'a DomainRewriteRuleDecl) { + fn to_viper( + &self, + context: &mut Context<'v>, + ast: &AstFactory<'v>, + ) -> viper::NamedDomainAxiom<'v> { + let (domain_name, rewrite_rule) = self; + assert!(!rewrite_rule.egg_only); + let axiom = rewrite_rule.convert_into_axiom(); + if let Some(comment) = &axiom.comment { + ast.named_domain_axiom_with_comment( + &axiom.name, + axiom.body.to_viper(context, ast), + domain_name, + comment, + ) + } else { + ast.named_domain_axiom(&axiom.name, axiom.body.to_viper(context, ast), domain_name) + } + } +} diff --git a/prusti-common/src/vir/low_to_viper/mod.rs b/prusti-common/src/vir/low_to_viper/mod.rs index 147cc6cf2dc..992acec505c 100644 --- a/prusti-common/src/vir/low_to_viper/mod.rs +++ b/prusti-common/src/vir/low_to_viper/mod.rs @@ -1,26 +1,114 @@ +use rustc_hash::FxHashMap; use viper::AstFactory; +use vir::{common::traits::HashWithPosition, low as vir_low}; mod ast; mod cfg; mod domain; mod program; -#[derive(Clone, Copy, Default, Debug)] -pub struct Context { +#[derive(Clone, Default)] +pub struct Context<'v> { inside_trigger: bool, + expression_cache: FxHashMap<(u64, bool), viper::Expr<'v>>, + expression_cache_validation: FxHashMap<(u64, bool), vir_low::Expression>, + statement_cache: FxHashMap>, + statement_cache_validation: FxHashMap, } -impl Context { - pub fn set_inside_trigger(mut self) -> Self { +impl<'v> Context<'v> { + pub fn set_inside_trigger(&mut self) -> bool { + let old_value = self.inside_trigger; self.inside_trigger = true; - self + old_value + } + + pub fn reset_inside_trigger(&mut self, old_value: bool) { + self.inside_trigger = old_value; + } + + pub fn get_cached_expression( + &self, + expression_hash: u64, + expression: &vir_low::Expression, + ) -> Option> { + let viper_expression = self + .expression_cache + .get(&(expression_hash, self.inside_trigger)) + .cloned(); + if cfg!(debug_assertions) && viper_expression.is_some() { + let cached_expression = self + .expression_cache_validation + .get(&(expression_hash, self.inside_trigger)) + .unwrap(); + assert_eq!(cached_expression, expression); + } + viper_expression + } + + fn cache_expression( + &mut self, + expression_hash: u64, + expression: &vir_low::Expression, + viper_expression: viper::Expr<'v>, + ) { + if cfg!(debug_assertions) { + assert!(self + .expression_cache_validation + .insert((expression_hash, self.inside_trigger), expression.clone()) + .is_none()); + } + assert!(self + .expression_cache + .insert((expression_hash, self.inside_trigger), viper_expression) + .is_none()); + } + + pub fn get_cached_statement( + &self, + statement_hash: u64, + statement: &vir_low::Statement, + ) -> Option> { + let viper_statement = self.statement_cache.get(&statement_hash).cloned(); + if cfg!(debug_assertions) && viper_statement.is_some() { + let cached_statement = self + .statement_cache_validation + .get(&statement_hash) + .unwrap(); + assert_eq!(cached_statement, statement); + } + viper_statement + } + + fn cache_statement( + &mut self, + statement_hash: u64, + statement: &vir_low::Statement, + viper_statement: viper::Stmt<'v>, + ) { + if cfg!(debug_assertions) { + assert!(self + .statement_cache_validation + .insert(statement_hash, statement.clone()) + .is_none()); + } + assert!(self + .statement_cache + .insert(statement_hash, viper_statement) + .is_none()); } } pub trait ToViper<'v, T> { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> T; + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> T; } pub trait ToViperDecl<'v, T> { - fn to_viper_decl(&self, context: Context, ast: &AstFactory<'v>) -> T; + fn to_viper_decl(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> T; +} + +pub(super) fn calculate_hash_with_position(t: &T) -> u64 { + let mut s = std::collections::hash_map::DefaultHasher::new(); + HashWithPosition::hash(t, &mut s); + std::hash::Hasher::finish(&s) } diff --git a/prusti-common/src/vir/low_to_viper/program.rs b/prusti-common/src/vir/low_to_viper/program.rs index 3aa9743a897..fe441154481 100644 --- a/prusti-common/src/vir/low_to_viper/program.rs +++ b/prusti-common/src/vir/low_to_viper/program.rs @@ -3,7 +3,7 @@ use viper::{self, AstFactory}; use vir::low::program::Program; impl<'v> ToViper<'v, viper::Program<'v>> for Program { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Program<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Program<'v> { let Program { name: _, check_mode: _, @@ -17,11 +17,11 @@ impl<'v> ToViper<'v, viper::Program<'v>> for Program { .iter() .map(|domain| domain.to_viper(context, ast)) .collect(); - let viper_methods: Vec<_> = procedures + let mut viper_methods: Vec<_> = procedures .iter() .map(|procedure| procedure.to_viper(context, ast)) - .chain(methods.iter().map(|method| method.to_viper(context, ast))) .collect(); + viper_methods.extend(methods.iter().map(|method| method.to_viper(context, ast))); let viper_predicates: Vec<_> = predicates .iter() .map(|predicate| predicate.to_viper(context, ast)) diff --git a/prusti-common/src/vir/optimizations/folding/expressions.rs b/prusti-common/src/vir/optimizations/folding/expressions.rs index 40dd44d3cfd..9efda1f1a0f 100644 --- a/prusti-common/src/vir/optimizations/folding/expressions.rs +++ b/prusti-common/src/vir/optimizations/folding/expressions.rs @@ -23,6 +23,7 @@ use crate::{ use log::{debug, trace}; use rustc_hash::{FxHashMap, FxHashSet}; use std::{cmp::Ordering, mem}; +use vir::common::builtin_constants::DISCRIMINANT_FIELD_NAME; pub trait FoldingOptimizer { #[must_use] @@ -180,7 +181,7 @@ fn check_requirements_conflict( ast::PlaceComponent::Variant(..), ast::PlaceComponent::Field(ast::Field { name, .. }, _), ) => { - if name == "discriminant" { + if name == DISCRIMINANT_FIELD_NAME { debug!("guarded permission: {} {}", place1, place2); // If we are checking discriminant, this means that the // permission is guarded. diff --git a/prusti-common/src/vir/program.rs b/prusti-common/src/vir/program.rs index c7bc8765128..a06113f781d 100644 --- a/prusti-common/src/vir/program.rs +++ b/prusti-common/src/vir/program.rs @@ -21,18 +21,20 @@ impl Program { } } pub fn get_check_mode(&self) -> vir::common::check_mode::CheckMode { + // FIXME: Remove because this is not needed anymore. match self { - Program::Legacy(_) => vir::common::check_mode::CheckMode::Both, + Program::Legacy(_) => vir::common::check_mode::CheckMode::MemorySafetyWithFunctional, Program::Low(program) => program.check_mode, } } pub fn get_name_with_check_mode(&self) -> String { + // FIXME: Remove because this is not needed anymore. format!("{}-{}", self.get_name(), self.get_check_mode()) } } impl<'v> ToViper<'v, viper::Program<'v>> for Program { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Program<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Program<'v> { match self { Program::Legacy(program) => program.to_viper(context, ast), Program::Low(program) => program.to_viper(context, ast), diff --git a/prusti-common/src/vir/to_viper.rs b/prusti-common/src/vir/to_viper.rs index 3d0d814e6e7..4f34c066b9b 100644 --- a/prusti-common/src/vir/to_viper.rs +++ b/prusti-common/src/vir/to_viper.rs @@ -21,7 +21,7 @@ use vir::common::identifier::WithIdentifier; impl<'v> ToViper<'v, viper::Program<'v>> for Program { #[tracing::instrument(name = "Program::to_viper", level = "debug", skip_all)] - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Program<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Program<'v> { let mut domains = self.domains.to_viper(context, ast); domains.extend(self.backend_types.to_viper(context, ast)); let fields = self.fields.to_viper(context, ast); @@ -92,13 +92,13 @@ impl<'v> ToViper<'v, viper::Position<'v>> for Position { #[tracing::instrument(name = "Position::to_viper", level = "trace", skip_all, fields( line = %self.line(), column = %self.column(), id = %self.id() ))] - fn to_viper(&self, _context: Context, ast: &AstFactory<'v>) -> viper::Position<'v> { + fn to_viper(&self, _context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Position<'v> { ast.identifier_position(self.line(), self.column(), self.id().to_string()) } } impl<'v> ToViper<'v, viper::Type<'v>> for Type { - fn to_viper(&self, _context: Context, ast: &AstFactory<'v>) -> viper::Type<'v> { + fn to_viper(&self, _context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Type<'v> { match self { Type::Int => ast.int_type(), Type::Bool => ast.bool_type(), @@ -130,8 +130,8 @@ impl<'v> ToViper<'v, viper::Type<'v>> for Type { } impl<'v, 'a, 'b> ToViper<'v, viper::Expr<'v>> for (&'a LocalVar, &'b Position) { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Expr<'v> { - if self.0.name == "__result" { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Expr<'v> { + if self.0.name == vir::common::builtin_constants::RESULT_VARIABLE_NAME { ast.result_with_pos( self.0.typ.to_viper(context, ast), self.1.to_viper(context, ast), @@ -147,20 +147,24 @@ impl<'v, 'a, 'b> ToViper<'v, viper::Expr<'v>> for (&'a LocalVar, &'b Position) { } impl<'v> ToViperDecl<'v, viper::LocalVarDecl<'v>> for LocalVar { - fn to_viper_decl(&self, context: Context, ast: &AstFactory<'v>) -> viper::LocalVarDecl<'v> { + fn to_viper_decl( + &self, + context: &mut Context<'v>, + ast: &AstFactory<'v>, + ) -> viper::LocalVarDecl<'v> { ast.local_var_decl(&self.name, self.typ.to_viper(context, ast)) } } impl<'v> ToViper<'v, viper::Field<'v>> for Field { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Field<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Field<'v> { ast.field(&self.name, self.typ.to_viper(context, ast)) } } impl<'v> ToViper<'v, viper::Stmt<'v>> for Stmt { #[tracing::instrument(name = "Stmt::to_viper", level = "trace", skip(context, ast))] - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Stmt<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Stmt<'v> { match self { Stmt::Comment(ref comment) => ast.comment(comment), Stmt::Label(ref label) => ast.label(label, &[]), @@ -231,10 +235,15 @@ impl<'v> ToViper<'v, viper::Stmt<'v>> for Stmt { // access to the needed paths. fn stmt_to_viper_in_packge<'v>( stmt: &Stmt, - context: Context, + context: &mut Context<'v>, ast: &AstFactory<'v>, ) -> viper::Stmt<'v> { - let create_footprint_asserts = |expr: &Expr, perm| -> Vec { + fn create_footprint_asserts<'v>( + context: &mut Context<'v>, + ast: &AstFactory<'v>, + expr: &Expr, + perm: PermAmount, + ) -> Vec> { expr.compute_footprint(perm) .into_iter() .map(|access| { @@ -243,10 +252,11 @@ impl<'v> ToViper<'v, viper::Stmt<'v>> for Stmt { assert.to_viper(context, ast) }) .collect() - }; + } match stmt { Stmt::Assign(ref lhs, ref rhs, _) => { - let mut stmts = create_footprint_asserts(rhs, PermAmount::Read); + let mut stmts = + create_footprint_asserts(context, ast, rhs, PermAmount::Read); stmts.push(ast.abstract_assign( lhs.to_viper(context, ast), rhs.to_viper(context, ast), @@ -255,7 +265,8 @@ impl<'v> ToViper<'v, viper::Stmt<'v>> for Stmt { } Stmt::Exhale(ref expr, ref pos) => { assert!(!pos.is_default()); - let mut stmts = create_footprint_asserts(expr, PermAmount::Read); + let mut stmts = + create_footprint_asserts(context, ast, expr, PermAmount::Read); stmts.push( ast.exhale(expr.to_viper(context, ast), pos.to_viper(context, ast)), ); @@ -265,7 +276,8 @@ impl<'v> ToViper<'v, viper::Stmt<'v>> for Stmt { assert_eq!(args.len(), 1); let place = &args[0]; assert!(place.is_place()); - let mut stmts = create_footprint_asserts(place, PermAmount::Read); + let mut stmts = + create_footprint_asserts(context, ast, place, PermAmount::Read); stmts.push(ast.fold_with_pos( ast.predicate_access_predicate_with_pos( ast.predicate_access_with_pos( @@ -346,7 +358,7 @@ impl<'v> ToViper<'v, viper::Stmt<'v>> for Stmt { } impl<'v> ToViper<'v, viper::Expr<'v>> for PermAmount { - fn to_viper(&self, _context: Context, ast: &AstFactory<'v>) -> viper::Expr<'v> { + fn to_viper(&self, _context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Expr<'v> { match self { PermAmount::Write => ast.full_perm(), PermAmount::Read => ast.func_app("read$", &[], ast.perm_type(), ast.no_position()), @@ -360,7 +372,7 @@ impl<'v> ToViper<'v, viper::Expr<'v>> for PermAmount { impl<'v> ToViper<'v, viper::Expr<'v>> for Expr { #[tracing::instrument(name = "Expr::to_viper", level = "trace", skip(context, ast))] - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Expr<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Expr<'v> { let expr = match self { Expr::Local(ref local_var, ref pos) => (local_var, pos).to_viper(context, ast), Expr::Variant(ref base, ref field, ref pos) => ast.field_access_with_pos( @@ -763,7 +775,7 @@ impl<'v> ToViper<'v, viper::Expr<'v>> for Expr { } impl<'v, 'a, 'b> ToViper<'v, viper::Trigger<'v>> for (&'a Trigger, &'b Position) { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Trigger<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Trigger<'v> { ast.trigger_with_pos( &self.0.elements().to_viper(context, ast)[..], self.1.to_viper(context, ast), @@ -772,7 +784,7 @@ impl<'v, 'a, 'b> ToViper<'v, viper::Trigger<'v>> for (&'a Trigger, &'b Position) } impl<'v, 'a, 'b> ToViper<'v, viper::Expr<'v>> for (&'a Const, &'b Position) { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Expr<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Expr<'v> { match self.0 { Const::Bool(true) => ast.true_lit_with_pos(self.1.to_viper(context, ast)), Const::Bool(false) => ast.false_lit_with_pos(self.1.to_viper(context, ast)), @@ -808,7 +820,7 @@ impl<'v, 'a, 'b> ToViper<'v, viper::Expr<'v>> for (&'a Const, &'b Position) { impl<'v> ToViper<'v, viper::Predicate<'v>> for Predicate { #[tracing::instrument(name = "Predicate::to_viper", level = "debug", skip_all)] - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Predicate<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Predicate<'v> { match self { Predicate::Struct(p) => p.to_viper(context, ast), Predicate::Enum(p) => p.to_viper(context, ast), @@ -825,7 +837,7 @@ impl<'v> ToViper<'v, viper::Predicate<'v>> for StructPredicate { level = "trace", skip(context, ast) )] - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Predicate<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Predicate<'v> { ast.predicate( &self.name, &[self.this.to_viper_decl(context, ast)], @@ -836,7 +848,7 @@ impl<'v> ToViper<'v, viper::Predicate<'v>> for StructPredicate { impl<'v> ToViper<'v, viper::Predicate<'v>> for EnumPredicate { #[tracing::instrument(name = "EnumPredicate::to_viper", level = "trace", skip(context, ast))] - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Predicate<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Predicate<'v> { ast.predicate( &self.name, &[self.this.to_viper_decl(context, ast)], @@ -847,7 +859,7 @@ impl<'v> ToViper<'v, viper::Predicate<'v>> for EnumPredicate { impl<'a, 'v> ToViper<'v, viper::Method<'v>> for &'a BodylessMethod { #[tracing::instrument(name = "BodylessMethod::to_viper", level = "trace", skip(context, ast))] - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Method<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Method<'v> { ast.method( &self.name, &self.formal_args.to_viper_decl(context, ast), @@ -861,7 +873,7 @@ impl<'a, 'v> ToViper<'v, viper::Method<'v>> for &'a BodylessMethod { impl<'a, 'v> ToViper<'v, viper::Function<'v>> for &'a Function { #[tracing::instrument(name = "Function::to_viper", level = "debug", skip_all)] - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Function<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Function<'v> { ast.function( &self.get_identifier(), &self.formal_args.to_viper_decl(context, ast), @@ -876,7 +888,7 @@ impl<'a, 'v> ToViper<'v, viper::Function<'v>> for &'a Function { impl<'a, 'v> ToViper<'v, viper::Domain<'v>> for &'a Domain { #[tracing::instrument(name = "Domain::to_viper", level = "debug", skip_all)] - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Domain<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Domain<'v> { ast.domain( &self.name, &self.functions.to_viper(context, ast), @@ -888,7 +900,7 @@ impl<'a, 'v> ToViper<'v, viper::Domain<'v>> for &'a Domain { impl<'a, 'v> ToViper<'v, viper::DomainFunc<'v>> for &'a DomainFunc { #[tracing::instrument(name = "DomainFunc::to_viper", level = "trace", skip(context, ast))] - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::DomainFunc<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::DomainFunc<'v> { ast.domain_func( &self.get_identifier(), &self.formal_args.to_viper_decl(context, ast), @@ -901,7 +913,11 @@ impl<'a, 'v> ToViper<'v, viper::DomainFunc<'v>> for &'a DomainFunc { impl<'a, 'v> ToViper<'v, viper::NamedDomainAxiom<'v>> for &'a DomainAxiom { #[tracing::instrument(name = "DomainAxiom::to_viper", level = "trace", skip(context, ast))] - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::NamedDomainAxiom<'v> { + fn to_viper( + &self, + context: &mut Context<'v>, + ast: &AstFactory<'v>, + ) -> viper::NamedDomainAxiom<'v> { if let Some(comment) = &self.comment { ast.named_domain_axiom_with_comment( &self.name, @@ -920,7 +936,7 @@ impl<'a, 'v> ToViper<'v, viper::NamedDomainAxiom<'v>> for &'a DomainAxiom { } impl<'a, 'v> ToViper<'v, viper::Domain<'v>> for &'a BackendType { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Domain<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Domain<'v> { ast.backend_type( &self.name, &self.functions.to_viper(context, ast), @@ -930,7 +946,7 @@ impl<'a, 'v> ToViper<'v, viper::Domain<'v>> for &'a BackendType { } impl<'a, 'v> ToViper<'v, viper::DomainFunc<'v>> for &'a BackendFuncDecl { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::DomainFunc<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::DomainFunc<'v> { ast.backend_func( &self.get_identifier(), &self.formal_args.to_viper_decl(context, ast), @@ -944,13 +960,13 @@ impl<'a, 'v> ToViper<'v, viper::DomainFunc<'v>> for &'a BackendFuncDecl { // Vectors impl<'v> ToViper<'v, Vec>> for Vec { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> Vec> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> Vec> { self.iter().map(|x| x.to_viper(context, ast)).collect() } } impl<'v, 'a, 'b> ToViper<'v, Vec>> for (&'a Vec, &'b Position) { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> Vec> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> Vec> { self.0 .iter() .map(|x| (x, self.1).to_viper(context, ast)) @@ -959,7 +975,7 @@ impl<'v, 'a, 'b> ToViper<'v, Vec>> for (&'a Vec, &'b P } impl<'v, 'a, 'b> ToViper<'v, Vec>> for (&'a Vec, &'b Position) { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> Vec> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> Vec> { self.0 .iter() .map(|x| (x, self.1).to_viper(context, ast)) @@ -970,7 +986,7 @@ impl<'v, 'a, 'b> ToViper<'v, Vec>> for (&'a Vec, &'b impl<'v> ToViperDecl<'v, Vec>> for Vec { fn to_viper_decl( &self, - context: Context, + context: &mut Context<'v>, ast: &AstFactory<'v>, ) -> Vec> { self.iter().map(|x| x.to_viper_decl(context, ast)).collect() @@ -978,62 +994,78 @@ impl<'v> ToViperDecl<'v, Vec>> for Vec { } impl<'v> ToViper<'v, Vec>> for Vec { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> Vec> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> Vec> { self.iter().map(|x| x.to_viper(context, ast)).collect() } } impl<'v> ToViper<'v, Vec>> for Vec { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> Vec> { + fn to_viper( + &self, + context: &mut Context<'v>, + ast: &AstFactory<'v>, + ) -> Vec> { self.iter().map(|x| x.to_viper(context, ast)).collect() } } impl<'v> ToViper<'v, Vec>> for Vec { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> Vec> { + fn to_viper( + &self, + context: &mut Context<'v>, + ast: &AstFactory<'v>, + ) -> Vec> { self.iter().map(|x| x.to_viper(context, ast)).collect() } } impl<'v> ToViper<'v, Vec>> for Vec { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> Vec> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> Vec> { self.iter().map(|x| x.to_viper(context, ast)).collect() } } impl<'v> ToViper<'v, Vec>> for Vec { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> Vec> { + fn to_viper( + &self, + context: &mut Context<'v>, + ast: &AstFactory<'v>, + ) -> Vec> { self.iter().map(|x| x.to_viper(context, ast)).collect() } } impl<'v> ToViper<'v, Vec>> for Vec { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> Vec> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> Vec> { self.iter().map(|x| x.to_viper(context, ast)).collect() } } impl<'v> ToViper<'v, Vec>> for Vec { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> Vec> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> Vec> { self.iter().map(|x| x.to_viper(context, ast)).collect() } } impl<'v> ToViper<'v, Vec>> for Vec { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> Vec> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> Vec> { self.iter().map(|x| x.to_viper(context, ast)).collect() } } impl<'v> ToViper<'v, Vec>> for Vec { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> Vec> { + fn to_viper( + &self, + context: &mut Context<'v>, + ast: &AstFactory<'v>, + ) -> Vec> { self.iter().map(|x| x.to_viper(context, ast)).collect() } } impl<'a, 'v> ToViper<'v, viper::Method<'v>> for &'a CfgMethod { #[tracing::instrument(name = "CfgMethod::to_viper", level = "debug", skip_all)] - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> viper::Method<'v> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> viper::Method<'v> { let mut blocks_ast: Vec = vec![]; let mut declarations: Vec = vec![]; @@ -1100,8 +1132,8 @@ impl<'a, 'v> ToViper<'v, viper::Method<'v>> for &'a CfgMethod { fn cfg_method_convert_basic_block_path<'v>( cfg_method: &CfgMethod, mut path: Vec, - context: Context, - ast: &'v AstFactory, + context: &mut Context<'v>, + ast: &AstFactory<'v>, blocks_ast: &mut Vec>, declarations: &mut Vec>, ) { @@ -1189,7 +1221,7 @@ fn cfg_method_convert_basic_block_path<'v>( } impl<'v> ToViper<'v, Vec>> for Vec { - fn to_viper(&self, context: Context, ast: &AstFactory<'v>) -> Vec> { + fn to_viper(&self, context: &mut Context<'v>, ast: &AstFactory<'v>) -> Vec> { self.iter().map(|x| x.to_viper(context, ast)).collect() } } @@ -1198,13 +1230,13 @@ fn index_to_label(basic_block_labels: &[String], index: usize) -> String { basic_block_labels[index].clone() } -fn successor_to_viper<'a>( - context: Context, - ast: &'a AstFactory, +fn successor_to_viper<'v>( + context: &mut Context<'v>, + ast: &AstFactory<'v>, index: usize, basic_block_labels: &[String], successor: &Successor, -) -> viper::Stmt<'a> { +) -> viper::Stmt<'v> { match *successor { Successor::Undefined => panic!( "CFG block '{}' has no successor.", @@ -1213,7 +1245,7 @@ fn successor_to_viper<'a>( Successor::Return => ast.goto(RETURN_LABEL), Successor::Goto(target) => ast.goto(&basic_block_labels[target.index()]), Successor::GotoSwitch(ref successors, ref default_target) => { - let mut stmts: Vec> = vec![]; + let mut stmts: Vec> = vec![]; for (test, target) in successors { let goto = ast.seqn(&[ast.goto(&basic_block_labels[target.index()])], &[]); let skip = ast.seqn(&[], &[]); @@ -1227,13 +1259,13 @@ fn successor_to_viper<'a>( } } -fn block_to_viper<'a>( - context: Context, - ast: &'a AstFactory, +fn block_to_viper<'v>( + context: &mut Context<'v>, + ast: &AstFactory<'v>, basic_block_labels: &[String], block: &CfgBlock, index: usize, -) -> viper::Stmt<'a> { +) -> viper::Stmt<'v> { let label = &basic_block_labels[index]; let mut stmts: Vec = vec![ // To put a bit of white space between blocks. @@ -1288,7 +1320,7 @@ fn unsigned_max_for_size(size: BitVectorSize) -> u128 { } fn unsigned_bv_to_signed_int<'v>( - context: Context, + context: &mut Context<'v>, ast: &AstFactory<'v>, size: BitVectorSize, value: &Expr, diff --git a/prusti-contracts/prusti-contracts-proc-macros/src/lib.rs b/prusti-contracts/prusti-contracts-proc-macros/src/lib.rs index 9a50a2f3f8c..59b8fbdfd0f 100644 --- a/prusti-contracts/prusti-contracts-proc-macros/src/lib.rs +++ b/prusti-contracts/prusti-contracts-proc-macros/src/lib.rs @@ -10,18 +10,68 @@ pub fn requires(_attr: TokenStream, tokens: TokenStream) -> TokenStream { tokens } +#[cfg(not(feature = "prusti"))] +#[proc_macro_attribute] +pub fn structural_requires(_attr: TokenStream, tokens: TokenStream) -> TokenStream { + tokens +} + +/// FIXME: Remove +#[cfg(not(feature = "prusti"))] +#[proc_macro_attribute] +pub fn not_require(_attr: TokenStream, tokens: TokenStream) -> TokenStream { + tokens +} + #[cfg(not(feature = "prusti"))] #[proc_macro_attribute] pub fn invariant(_attr: TokenStream, tokens: TokenStream) -> TokenStream { tokens } +#[cfg(not(feature = "prusti"))] +#[proc_macro_attribute] +pub fn structural_invariant(_attr: TokenStream, tokens: TokenStream) -> TokenStream { + tokens +} + +#[cfg(not(feature = "prusti"))] +#[proc_macro_attribute] +pub fn broken_invariant(_attr: TokenStream, tokens: TokenStream) -> TokenStream { + tokens +} + #[cfg(not(feature = "prusti"))] #[proc_macro_attribute] pub fn ensures(_attr: TokenStream, tokens: TokenStream) -> TokenStream { tokens } +#[cfg(not(feature = "prusti"))] +#[proc_macro_attribute] +pub fn panic_ensures(_attr: TokenStream, tokens: TokenStream) -> TokenStream { + tokens +} + +#[cfg(not(feature = "prusti"))] +#[proc_macro_attribute] +pub fn structural_panic_ensures(_attr: TokenStream, tokens: TokenStream) -> TokenStream { + tokens +} + +#[cfg(not(feature = "prusti"))] +#[proc_macro_attribute] +pub fn structural_ensures(_attr: TokenStream, tokens: TokenStream) -> TokenStream { + tokens +} + +/// FIXME: Remove +#[cfg(not(feature = "prusti"))] +#[proc_macro_attribute] +pub fn not_ensure(_attr: TokenStream, tokens: TokenStream) -> TokenStream { + tokens +} + #[cfg(not(feature = "prusti"))] #[proc_macro_attribute] pub fn after_expiry(_attr: TokenStream, tokens: TokenStream) -> TokenStream { @@ -52,18 +102,48 @@ pub fn verified(_attr: TokenStream, tokens: TokenStream) -> TokenStream { tokens } +#[cfg(not(feature = "prusti"))] +#[proc_macro_attribute] +pub fn non_verified_pure(_attr: TokenStream, tokens: TokenStream) -> TokenStream { + tokens +} + +#[cfg(not(feature = "prusti"))] +#[proc_macro_attribute] +pub fn no_panic(_attr: TokenStream, tokens: TokenStream) -> TokenStream { + tokens +} + +#[cfg(not(feature = "prusti"))] +#[proc_macro_attribute] +pub fn no_panic_ensures_postcondition(_attr: TokenStream, tokens: TokenStream) -> TokenStream { + tokens +} + #[cfg(not(feature = "prusti"))] #[proc_macro] pub fn body_invariant(_tokens: TokenStream) -> TokenStream { TokenStream::new() } +#[cfg(not(feature = "prusti"))] +#[proc_macro] +pub fn structural_body_invariant(_tokens: TokenStream) -> TokenStream { + TokenStream::new() +} + #[cfg(not(feature = "prusti"))] #[proc_macro] pub fn prusti_assert(_tokens: TokenStream) -> TokenStream { TokenStream::new() } +#[cfg(not(feature = "prusti"))] +#[proc_macro] +pub fn prusti_structural_assert(_tokens: TokenStream) -> TokenStream { + TokenStream::new() +} + #[cfg(not(feature = "prusti"))] #[proc_macro] pub fn prusti_assume(_tokens: TokenStream) -> TokenStream { @@ -76,6 +156,36 @@ pub fn prusti_refute(_tokens: TokenStream) -> TokenStream { TokenStream::new() } +#[cfg(not(feature = "prusti"))] +#[proc_macro] +pub fn prusti_structural_assume(_tokens: TokenStream) -> TokenStream { + TokenStream::new() +} + +#[cfg(not(feature = "prusti"))] +#[proc_macro] +pub fn prusti_split_on(_tokens: TokenStream) -> TokenStream { + TokenStream::new() +} + +#[cfg(not(feature = "prusti"))] +#[proc_macro] +pub fn materialize_predicate(_tokens: TokenStream) -> TokenStream { + TokenStream::new() +} + +#[cfg(not(feature = "prusti"))] +#[proc_macro] +pub fn quantified_predicate(_tokens: TokenStream) -> TokenStream { + TokenStream::new() +} + +#[cfg(not(feature = "prusti"))] +#[proc_macro] +pub fn assume_allocation_never_fails(_tokens: TokenStream) -> TokenStream { + TokenStream::new() +} + #[cfg(not(feature = "prusti"))] #[proc_macro_attribute] pub fn refine_trait_spec(_attr: TokenStream, tokens: TokenStream) -> TokenStream { @@ -113,164 +223,738 @@ pub fn ghost(_tokens: TokenStream) -> TokenStream { } #[cfg(not(feature = "prusti"))] -#[proc_macro_attribute] -pub fn print_counterexample(_attr: TokenStream, tokens: TokenStream) -> TokenStream { - tokens +#[proc_macro] +pub fn on_drop_unwind(_tokens: TokenStream) -> TokenStream { + TokenStream::new() } #[cfg(not(feature = "prusti"))] -#[proc_macro_attribute] -pub fn terminates(_attr: TokenStream, _tokens: TokenStream) -> TokenStream { +#[proc_macro] +pub fn before_drop(_tokens: TokenStream) -> TokenStream { TokenStream::new() } #[cfg(not(feature = "prusti"))] #[proc_macro] -pub fn body_variant(_tokens: TokenStream) -> TokenStream { +pub fn after_drop(_tokens: TokenStream) -> TokenStream { TokenStream::new() } -// ---------------------- -// --- PRUSTI ENABLED --- +#[cfg(not(feature = "prusti"))] +#[proc_macro] +pub fn with_finally(_tokens: TokenStream) -> TokenStream { + TokenStream::new() +} -#[cfg(feature = "prusti")] -use prusti_specs::{rewrite_prusti_attributes, SpecAttributeKind}; +#[cfg(not(feature = "prusti"))] +#[proc_macro] +pub fn checked(_tokens: TokenStream) -> TokenStream { + TokenStream::new() +} -#[cfg(feature = "prusti")] -#[proc_macro_attribute] -pub fn requires(attr: TokenStream, tokens: TokenStream) -> TokenStream { - rewrite_prusti_attributes(SpecAttributeKind::Requires, attr.into(), tokens.into()).into() +#[cfg(not(feature = "prusti"))] +#[proc_macro] +pub fn checked(_tokens: TokenStream) -> TokenStream { + TokenStream::new() } -#[cfg(feature = "prusti")] +#[cfg(not(feature = "prusti"))] #[proc_macro_attribute] -pub fn ensures(attr: TokenStream, tokens: TokenStream) -> TokenStream { - rewrite_prusti_attributes(SpecAttributeKind::Ensures, attr.into(), tokens.into()).into() +pub fn print_counterexample(_attr: TokenStream, tokens: TokenStream) -> TokenStream { + tokens } -#[cfg(feature = "prusti")] +#[cfg(not(feature = "prusti"))] #[proc_macro_attribute] -pub fn after_expiry(attr: TokenStream, tokens: TokenStream) -> TokenStream { - rewrite_prusti_attributes(SpecAttributeKind::AfterExpiry, attr.into(), tokens.into()).into() +pub fn terminates(_attr: TokenStream, _tokens: TokenStream) -> TokenStream { + TokenStream::new() } -#[cfg(feature = "prusti")] -#[proc_macro_attribute] -pub fn assert_on_expiry(attr: TokenStream, tokens: TokenStream) -> TokenStream { - rewrite_prusti_attributes( - SpecAttributeKind::AssertOnExpiry, - attr.into(), - tokens.into(), - ) - .into() +#[cfg(not(feature = "prusti"))] +#[proc_macro] +pub fn body_variant(_tokens: TokenStream) -> TokenStream { + TokenStream::new() } -#[cfg(feature = "prusti")] -#[proc_macro_attribute] -pub fn pure(attr: TokenStream, tokens: TokenStream) -> TokenStream { - rewrite_prusti_attributes(SpecAttributeKind::Pure, attr.into(), tokens.into()).into() +#[cfg(not(feature = "prusti"))] +#[proc_macro] +pub fn manually_manage(_tokens: TokenStream) -> TokenStream { + TokenStream::new() } -#[cfg(feature = "prusti")] -#[proc_macro_attribute] -pub fn trusted(attr: TokenStream, tokens: TokenStream) -> TokenStream { - prusti_specs::trusted(attr.into(), tokens.into()).into() +#[cfg(not(feature = "prusti"))] +#[proc_macro] +pub fn pack(_tokens: TokenStream) -> TokenStream { + TokenStream::new() } -#[cfg(feature = "prusti")] -#[proc_macro_attribute] -pub fn verified(attr: TokenStream, tokens: TokenStream) -> TokenStream { - rewrite_prusti_attributes(SpecAttributeKind::Verified, attr.into(), tokens.into()).into() +#[cfg(not(feature = "prusti"))] +#[proc_macro] +pub fn unpack(_tokens: TokenStream) -> TokenStream { + TokenStream::new() } -#[cfg(feature = "prusti")] +#[cfg(not(feature = "prusti"))] #[proc_macro] -pub fn body_invariant(tokens: TokenStream) -> TokenStream { - prusti_specs::body_invariant(tokens.into()).into() +pub fn obtain(_tokens: TokenStream) -> TokenStream { + TokenStream::new() } -#[cfg(feature = "prusti")] +#[cfg(not(feature = "prusti"))] #[proc_macro] -pub fn prusti_assert(tokens: TokenStream) -> TokenStream { - prusti_specs::prusti_assertion(tokens.into()).into() +pub fn pack_ref(_tokens: TokenStream) -> TokenStream { + TokenStream::new() } -#[cfg(feature = "prusti")] +#[cfg(not(feature = "prusti"))] #[proc_macro] -pub fn prusti_assume(tokens: TokenStream) -> TokenStream { - prusti_specs::prusti_assume(tokens.into()).into() +pub fn unpack_ref(_tokens: TokenStream) -> TokenStream { + TokenStream::new() } -#[cfg(feature = "prusti")] +#[cfg(not(feature = "prusti"))] #[proc_macro] -pub fn prusti_refute(tokens: TokenStream) -> TokenStream { - prusti_specs::prusti_refutation(tokens.into()).into() +pub fn pack_mut_ref(_tokens: TokenStream) -> TokenStream { + TokenStream::new() } -#[cfg(feature = "prusti")] +#[cfg(not(feature = "prusti"))] #[proc_macro] -pub fn closure(tokens: TokenStream) -> TokenStream { - prusti_specs::closure(tokens.into()).into() +pub fn unpack_mut_ref(_tokens: TokenStream) -> TokenStream { + TokenStream::new() } -#[cfg(feature = "prusti")] -#[proc_macro_attribute] -pub fn refine_trait_spec(attr: TokenStream, tokens: TokenStream) -> TokenStream { - prusti_specs::refine_trait_spec(attr.into(), tokens.into()).into() +#[cfg(not(feature = "prusti"))] +#[proc_macro] +pub fn pack_mut_ref_obligation(_tokens: TokenStream) -> TokenStream { + TokenStream::new() } -#[cfg(feature = "prusti")] -#[proc_macro_attribute] -pub fn extern_spec(attr: TokenStream, tokens: TokenStream) -> TokenStream { - prusti_specs::extern_spec(attr.into(), tokens.into()).into() +#[cfg(not(feature = "prusti"))] +#[proc_macro] +pub fn unpack_mut_ref_obligation(_tokens: TokenStream) -> TokenStream { + TokenStream::new() } -#[cfg(feature = "prusti")] -#[proc_macro_attribute] -pub fn invariant(attr: TokenStream, tokens: TokenStream) -> TokenStream { - prusti_specs::invariant(attr.into(), tokens.into()).into() +#[cfg(not(feature = "prusti"))] +#[proc_macro] +pub fn take_lifetime(_tokens: TokenStream) -> TokenStream { + TokenStream::new() } -#[cfg(feature = "prusti")] +#[cfg(not(feature = "prusti"))] #[proc_macro] -pub fn predicate(tokens: TokenStream) -> TokenStream { - prusti_specs::predicate(tokens.into()).into() +pub fn end_loan(_tokens: TokenStream) -> TokenStream { + TokenStream::new() } -#[cfg(feature = "prusti")] -#[proc_macro_attribute] -pub fn model(_attr: TokenStream, tokens: TokenStream) -> TokenStream { - prusti_specs::type_model(_attr.into(), tokens.into()).into() +#[cfg(not(feature = "prusti"))] +#[proc_macro] +pub fn set_lifetime_for_raw_pointer_reference_casts(_tokens: TokenStream) -> TokenStream { + TokenStream::new() } -#[cfg(feature = "prusti")] -#[proc_macro_attribute] -pub fn refine_spec(attr: TokenStream, tokens: TokenStream) -> TokenStream { - rewrite_prusti_attributes(SpecAttributeKind::RefineSpec, attr.into(), tokens.into()).into() +#[cfg(not(feature = "prusti"))] +#[proc_macro] +pub fn attach_drop_lifetime(_tokens: TokenStream) -> TokenStream { + TokenStream::new() } -#[cfg(feature = "prusti")] +#[cfg(not(feature = "prusti"))] #[proc_macro] -pub fn ghost(tokens: TokenStream) -> TokenStream { - prusti_specs::ghost(tokens.into()).into() +pub fn join(_tokens: TokenStream) -> TokenStream { + TokenStream::new() } -#[cfg(feature = "prusti")] -#[proc_macro_attribute] -pub fn print_counterexample(attr: TokenStream, tokens: TokenStream) -> TokenStream { - prusti_specs::print_counterexample(attr.into(), tokens.into()).into() +#[cfg(not(feature = "prusti"))] +#[proc_macro] +pub fn join_range(_tokens: TokenStream) -> TokenStream { + TokenStream::new() } -#[cfg(feature = "prusti")] -#[proc_macro_attribute] -pub fn terminates(attr: TokenStream, tokens: TokenStream) -> TokenStream { - rewrite_prusti_attributes(SpecAttributeKind::Terminates, attr.into(), tokens.into()).into() +#[cfg(not(feature = "prusti"))] +#[proc_macro] +pub fn split(_tokens: TokenStream) -> TokenStream { + TokenStream::new() } -#[cfg(feature = "prusti")] +#[cfg(not(feature = "prusti"))] #[proc_macro] -pub fn body_variant(tokens: TokenStream) -> TokenStream { - prusti_specs::body_variant(tokens.into()).into() +pub fn split_range(_tokens: TokenStream) -> TokenStream { + TokenStream::new() +} + +#[cfg(not(feature = "prusti"))] +#[proc_macro] +pub fn stash_range(_tokens: TokenStream) -> TokenStream { + TokenStream::new() +} + +#[cfg(not(feature = "prusti"))] +#[proc_macro] +pub fn restore_stash_range(_tokens: TokenStream) -> TokenStream { + TokenStream::new() +} + +#[cfg(not(feature = "prusti"))] +#[proc_macro] +pub fn close_ref(_tokens: TokenStream) -> TokenStream { + TokenStream::new() +} + +#[cfg(not(feature = "prusti"))] +#[proc_macro] +pub fn open_ref(_tokens: TokenStream) -> TokenStream { + TokenStream::new() +} + +#[cfg(not(feature = "prusti"))] +#[proc_macro] +pub fn close_mut_ref(_tokens: TokenStream) -> TokenStream { + TokenStream::new() +} + +#[cfg(not(feature = "prusti"))] +#[proc_macro] +pub fn open_mut_ref(_tokens: TokenStream) -> TokenStream { + TokenStream::new() +} + +#[cfg(not(feature = "prusti"))] +#[proc_macro] +pub fn restore_mut_borrowed(_tokens: TokenStream) -> TokenStream { + TokenStream::new() +} + +#[cfg(not(feature = "prusti"))] +#[proc_macro] +pub fn resolve(_tokens: TokenStream) -> TokenStream { + TokenStream::new() +} + +#[cfg(not(feature = "prusti"))] +#[proc_macro] +pub fn resolve_range(_tokens: TokenStream) -> TokenStream { + TokenStream::new() +} + +#[cfg(not(feature = "prusti"))] +#[proc_macro] +pub fn forget_initialization(_tokens: TokenStream) -> TokenStream { + TokenStream::new() +} + +#[cfg(not(feature = "prusti"))] +#[proc_macro] +pub fn forget_initialization_range(_tokens: TokenStream) -> TokenStream { + TokenStream::new() +} + +#[cfg(not(feature = "prusti"))] +#[proc_macro] +pub fn restore(_tokens: TokenStream) -> TokenStream { + TokenStream::new() +} + +#[cfg(not(feature = "prusti"))] +#[proc_macro] +pub fn set_union_active_field(_tokens: TokenStream) -> TokenStream { + TokenStream::new() +} + +// ---------------------- +// --- PRUSTI ENABLED --- + +#[cfg(feature = "prusti")] +use prusti_specs::{rewrite_prusti_attributes, SpecAttributeKind}; + +#[cfg(feature = "prusti")] +#[proc_macro_attribute] +pub fn requires(attr: TokenStream, tokens: TokenStream) -> TokenStream { + rewrite_prusti_attributes(SpecAttributeKind::Requires, attr.into(), tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro_attribute] +pub fn structural_requires(attr: TokenStream, tokens: TokenStream) -> TokenStream { + rewrite_prusti_attributes( + SpecAttributeKind::StructuralRequires, + attr.into(), + tokens.into(), + ) + .into() +} + +/// FIXME: Remove. +#[cfg(feature = "prusti")] +#[proc_macro_attribute] +pub fn not_require(attr: TokenStream, tokens: TokenStream) -> TokenStream { + rewrite_prusti_attributes(SpecAttributeKind::NotRequire, attr.into(), tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro_attribute] +pub fn ensures(attr: TokenStream, tokens: TokenStream) -> TokenStream { + rewrite_prusti_attributes(SpecAttributeKind::Ensures, attr.into(), tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro_attribute] +pub fn panic_ensures(attr: TokenStream, tokens: TokenStream) -> TokenStream { + rewrite_prusti_attributes(SpecAttributeKind::PanicEnsures, attr.into(), tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro_attribute] +pub fn structural_ensures(attr: TokenStream, tokens: TokenStream) -> TokenStream { + rewrite_prusti_attributes( + SpecAttributeKind::StructuralEnsures, + attr.into(), + tokens.into(), + ) + .into() +} + +#[cfg(feature = "prusti")] +#[proc_macro_attribute] +pub fn structural_panic_ensures(attr: TokenStream, tokens: TokenStream) -> TokenStream { + rewrite_prusti_attributes( + SpecAttributeKind::StructuralPanicEnsures, + attr.into(), + tokens.into(), + ) + .into() +} + +/// FIXME: Remove. +#[cfg(feature = "prusti")] +#[proc_macro_attribute] +pub fn not_ensure(attr: TokenStream, tokens: TokenStream) -> TokenStream { + rewrite_prusti_attributes(SpecAttributeKind::NotEnsure, attr.into(), tokens.into()).into() +} + +/// FIXME: Cleanup. +#[cfg(feature = "prusti")] +#[proc_macro_attribute] +pub fn broken_invariant(attr: TokenStream, tokens: TokenStream) -> TokenStream { + rewrite_prusti_attributes(SpecAttributeKind::NotRequire, attr.into(), tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro_attribute] +pub fn after_expiry(attr: TokenStream, tokens: TokenStream) -> TokenStream { + rewrite_prusti_attributes(SpecAttributeKind::AfterExpiry, attr.into(), tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro_attribute] +pub fn assert_on_expiry(attr: TokenStream, tokens: TokenStream) -> TokenStream { + rewrite_prusti_attributes( + SpecAttributeKind::AssertOnExpiry, + attr.into(), + tokens.into(), + ) + .into() +} + +#[cfg(feature = "prusti")] +#[proc_macro_attribute] +pub fn pure(attr: TokenStream, tokens: TokenStream) -> TokenStream { + rewrite_prusti_attributes(SpecAttributeKind::Pure, attr.into(), tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro_attribute] +pub fn trusted(attr: TokenStream, tokens: TokenStream) -> TokenStream { + prusti_specs::trusted(attr.into(), tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro_attribute] +pub fn verified(attr: TokenStream, tokens: TokenStream) -> TokenStream { + rewrite_prusti_attributes(SpecAttributeKind::Verified, attr.into(), tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro_attribute] +pub fn non_verified_pure(attr: TokenStream, tokens: TokenStream) -> TokenStream { + rewrite_prusti_attributes( + SpecAttributeKind::NonVerifiedPure, + attr.into(), + tokens.into(), + ) + .into() +} + +#[cfg(feature = "prusti")] +#[proc_macro_attribute] +pub fn no_panic(attr: TokenStream, tokens: TokenStream) -> TokenStream { + rewrite_prusti_attributes(SpecAttributeKind::NoPanic, attr.into(), tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro_attribute] +pub fn no_panic_ensures_postcondition(attr: TokenStream, tokens: TokenStream) -> TokenStream { + rewrite_prusti_attributes( + SpecAttributeKind::NoPanicEnsuresPostcondition, + attr.into(), + tokens.into(), + ) + .into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn body_invariant(tokens: TokenStream) -> TokenStream { + prusti_specs::body_invariant(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn structural_body_invariant(tokens: TokenStream) -> TokenStream { + prusti_specs::structural_body_invariant(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn prusti_assert(tokens: TokenStream) -> TokenStream { + prusti_specs::prusti_assertion(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn prusti_structural_assert(tokens: TokenStream) -> TokenStream { + prusti_specs::prusti_structural_assert(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn prusti_assume(tokens: TokenStream) -> TokenStream { + prusti_specs::prusti_assume(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn prusti_refute(tokens: TokenStream) -> TokenStream { + prusti_specs::prusti_refutation(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn prusti_structural_assume(tokens: TokenStream) -> TokenStream { + prusti_specs::prusti_structural_assume(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn prusti_split_on(tokens: TokenStream) -> TokenStream { + prusti_specs::prusti_split_on(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn materialize_predicate(tokens: TokenStream) -> TokenStream { + prusti_specs::materialize_predicate(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn quantified_predicate(tokens: TokenStream) -> TokenStream { + prusti_specs::quantified_predicate(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn assume_allocation_never_fails(tokens: TokenStream) -> TokenStream { + prusti_specs::assume_allocation_never_fails(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn closure(tokens: TokenStream) -> TokenStream { + prusti_specs::closure(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro_attribute] +pub fn refine_trait_spec(attr: TokenStream, tokens: TokenStream) -> TokenStream { + prusti_specs::refine_trait_spec(attr.into(), tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro_attribute] +pub fn extern_spec(attr: TokenStream, tokens: TokenStream) -> TokenStream { + prusti_specs::extern_spec(attr.into(), tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro_attribute] +pub fn invariant(attr: TokenStream, tokens: TokenStream) -> TokenStream { + prusti_specs::invariant(attr.into(), tokens.into(), false).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro_attribute] +pub fn structural_invariant(attr: TokenStream, tokens: TokenStream) -> TokenStream { + prusti_specs::invariant(attr.into(), tokens.into(), true).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn predicate(tokens: TokenStream) -> TokenStream { + prusti_specs::predicate(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro_attribute] +pub fn model(_attr: TokenStream, tokens: TokenStream) -> TokenStream { + prusti_specs::type_model(_attr.into(), tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro_attribute] +pub fn refine_spec(attr: TokenStream, tokens: TokenStream) -> TokenStream { + rewrite_prusti_attributes(SpecAttributeKind::RefineSpec, attr.into(), tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn ghost(tokens: TokenStream) -> TokenStream { + prusti_specs::ghost(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn on_drop_unwind(tokens: TokenStream) -> TokenStream { + prusti_specs::on_drop_unwind(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn before_drop(tokens: TokenStream) -> TokenStream { + prusti_specs::before_drop(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn after_drop(tokens: TokenStream) -> TokenStream { + prusti_specs::after_drop(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn with_finally(tokens: TokenStream) -> TokenStream { + prusti_specs::with_finally(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn checked(tokens: TokenStream) -> TokenStream { + prusti_specs::checked(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro_attribute] +pub fn print_counterexample(attr: TokenStream, tokens: TokenStream) -> TokenStream { + prusti_specs::print_counterexample(attr.into(), tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro_attribute] +pub fn terminates(attr: TokenStream, tokens: TokenStream) -> TokenStream { + rewrite_prusti_attributes(SpecAttributeKind::Terminates, attr.into(), tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn body_variant(tokens: TokenStream) -> TokenStream { + prusti_specs::body_variant(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn manually_manage(tokens: TokenStream) -> TokenStream { + prusti_specs::manually_manage(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn pack(tokens: TokenStream) -> TokenStream { + prusti_specs::pack(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn unpack(tokens: TokenStream) -> TokenStream { + prusti_specs::unpack(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn obtain(tokens: TokenStream) -> TokenStream { + prusti_specs::obtain(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn pack_ref(tokens: TokenStream) -> TokenStream { + prusti_specs::pack_ref(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn unpack_ref(tokens: TokenStream) -> TokenStream { + prusti_specs::unpack_ref(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn pack_mut_ref(tokens: TokenStream) -> TokenStream { + prusti_specs::pack_mut_ref(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn unpack_mut_ref(tokens: TokenStream) -> TokenStream { + prusti_specs::unpack_mut_ref(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn pack_mut_ref_obligation(tokens: TokenStream) -> TokenStream { + prusti_specs::pack_mut_ref_obligation(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn unpack_mut_ref_obligation(tokens: TokenStream) -> TokenStream { + prusti_specs::unpack_mut_ref_obligation(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn take_lifetime(tokens: TokenStream) -> TokenStream { + prusti_specs::take_lifetime(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn end_loan(tokens: TokenStream) -> TokenStream { + prusti_specs::end_loan(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn set_lifetime_for_raw_pointer_reference_casts(tokens: TokenStream) -> TokenStream { + prusti_specs::set_lifetime_for_raw_pointer_reference_casts(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn attach_drop_lifetime(tokens: TokenStream) -> TokenStream { + prusti_specs::attach_drop_lifetime(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn join(tokens: TokenStream) -> TokenStream { + prusti_specs::join(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn join_range(tokens: TokenStream) -> TokenStream { + prusti_specs::join_range(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn split(tokens: TokenStream) -> TokenStream { + prusti_specs::split(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn split_range(tokens: TokenStream) -> TokenStream { + prusti_specs::split_range(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn stash_range(tokens: TokenStream) -> TokenStream { + prusti_specs::stash_range(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn restore_stash_range(tokens: TokenStream) -> TokenStream { + prusti_specs::restore_stash_range(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn close_ref(tokens: TokenStream) -> TokenStream { + prusti_specs::close_ref(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn open_ref(tokens: TokenStream) -> TokenStream { + prusti_specs::open_ref(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn close_mut_ref(tokens: TokenStream) -> TokenStream { + prusti_specs::close_mut_ref(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn open_mut_ref(tokens: TokenStream) -> TokenStream { + prusti_specs::open_mut_ref(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn restore_mut_borrowed(tokens: TokenStream) -> TokenStream { + prusti_specs::restore_mut_borrowed(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn resolve(tokens: TokenStream) -> TokenStream { + prusti_specs::resolve(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn resolve_range(tokens: TokenStream) -> TokenStream { + prusti_specs::resolve_range(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn forget_initialization(tokens: TokenStream) -> TokenStream { + prusti_specs::forget_initialization(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn forget_initialization_range(tokens: TokenStream) -> TokenStream { + prusti_specs::forget_initialization_range(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn restore(tokens: TokenStream) -> TokenStream { + prusti_specs::restore(tokens.into()).into() +} + +#[cfg(feature = "prusti")] +#[proc_macro] +pub fn set_union_active_field(tokens: TokenStream) -> TokenStream { + prusti_specs::set_union_active_field(tokens.into()).into() } // Ensure that you've also crated a transparent `#[cfg(not(feature = "prusti"))]` diff --git a/prusti-contracts/prusti-contracts/src/core_spec.rs b/prusti-contracts/prusti-contracts/src/core_spec.rs index 61fa73e42b8..a8165b62649 100644 --- a/prusti-contracts/prusti-contracts/src/core_spec.rs +++ b/prusti-contracts/prusti-contracts/src/core_spec.rs @@ -4,15 +4,349 @@ use crate::*; impl ::core::result::Result { #[pure] #[ensures(result == matches!(self, Ok(_)))] + #[no_panic] + #[no_panic_ensures_postcondition] fn is_ok(&self) -> bool; #[pure] #[ensures(result == matches!(self, Err(_)))] + #[no_panic] + #[no_panic_ensures_postcondition] fn is_err(&self) -> bool; } #[extern_spec] impl ::core::result::Result { #[requires(matches!(self, Ok(_)))] + #[ensures(match self { + Ok(value) => result === value, + Err(_) => false, + })] + #[no_panic_ensures_postcondition] fn unwrap(self) -> T; } + +#[extern_spec] +impl ::core::option::Option { + #[requires(matches!(self, Some(_)))] + #[ensures(match self { + Some(value) => result === value, + None => false, + })] + #[no_panic_ensures_postcondition] + fn unwrap(self) -> T; +} + +// Crashes ☹ +type Pointer = *const T; +#[extern_spec] +impl Pointer { + #[trusted] + #[terminates] + #[pure] + // FIXME: This is needed because this function is special cased only in the + // pure encoder and not in the impure one. + #[ensures(result == self.is_null())] + #[no_panic] + #[no_panic_ensures_postcondition] + fn is_null(self) -> bool; + + #[trusted] + #[terminates] + #[pure] + // FIXME: Check provenance. + #[structural_requires(Int::new_isize(count) * Int::new_usize(std::mem::size_of::()) <= Int::new_isize(isize::MAX))] + #[ensures(result == address_offset(self, Int::new_isize(count)))] + #[no_panic] + #[no_panic_ensures_postcondition] + unsafe fn offset(self, count: isize) -> *const T; + + #[trusted] + #[terminates] + #[pure] + // FIXME: Properly specify the wrapping arithmetic. + #[ensures(result == address_offset(self, Int::new_isize(count)))] + #[no_panic] + #[no_panic_ensures_postcondition] + fn wrapping_offset(self, count: isize) -> *const T; + + #[trusted] + #[terminates] + #[pure] + // FIXME: Check provenance. + #[structural_requires(same_allocation(self, origin))] + // #[structural_requires(address_from(self, origin) * Int::new_usize(std::mem::size_of::()) <= Int::new_isize(isize::MAX))] + #[structural_requires(multiply_int(address_from(self, origin), Int::new_usize(std::mem::size_of::())) <= Int::new_isize(isize::MAX))] + #[structural_requires(address_from(self, origin) >= Int::new_isize(0))] + #[ensures(Int::new_isize(result) == address_from(self, origin))] + #[no_panic] + #[no_panic_ensures_postcondition] + unsafe fn offset_from(self, origin: *const T) -> isize; + + #[trusted] + #[terminates] + #[pure] + // FIXME: Check provenance. + // #[structural_requires(Int::new_usize(count) * Int::new_usize(std::mem::size_of::()) <= Int::new_isize(isize::MAX))] + #[structural_requires(multiply_int(Int::new_usize(count), Int::new_usize(std::mem::size_of::())) <= Int::new_usize(usize::MAX))] + #[ensures(result == address_offset(self, Int::new_usize(count)))] + #[no_panic] + #[no_panic_ensures_postcondition] + unsafe fn add(self, count: usize) -> *const T; +} + +type MutPointer = *mut T; +#[extern_spec] +impl MutPointer { + #[trusted] + #[terminates] + #[pure] + // FIXME: This is needed because this function is special cased only in the + // pure encoder and not in the impure one. + #[ensures(result == self.is_null())] + #[no_panic] + #[no_panic_ensures_postcondition] + fn is_null(self) -> bool; + + #[trusted] + #[terminates] + #[pure] + // FIXME: Check provenance. + #[structural_requires(Int::new_isize(count) * Int::new_usize(std::mem::size_of::()) <= Int::new_isize(isize::MAX))] + #[ensures(result == address_offset_mut(self, Int::new_isize(count)))] + #[no_panic] + #[no_panic_ensures_postcondition] + unsafe fn offset(self, count: isize) -> *mut T; + + #[trusted] + #[terminates] + #[pure] + // FIXME: Properly specify the wrapping arithmetic. + #[ensures(result == address_offset_mut(self, Int::new_isize(count)))] + #[no_panic] + #[no_panic_ensures_postcondition] + fn wrapping_offset(self, count: isize) -> *mut T; + + #[trusted] + #[terminates] + #[pure] + // FIXME: Check provenance. + // #[structural_requires(Int::new_usize(count) * Int::new_usize(std::mem::size_of::()) <= Int::new_isize(isize::MAX))] + #[structural_requires(multiply_int(Int::new_usize(count), Int::new_usize(std::mem::size_of::())) <= Int::new_usize(usize::MAX))] + #[ensures(result == address_offset_mut(self, Int::new_usize(count)))] + #[no_panic] + #[no_panic_ensures_postcondition] + unsafe fn add(self, count: usize) -> *mut T; + + #[trusted] + #[terminates] + #[pure] + // FIXME: Check provenance. + #[structural_requires(same_allocation(self, origin))] + #[structural_requires(multiply_int(address_from(self, origin), Int::new_usize(std::mem::size_of::())) <= Int::new_isize(isize::MAX))] + #[structural_requires(address_from(self, origin) >= Int::new_isize(0))] + #[ensures(Int::new_isize(result) == address_from(self, origin))] + #[no_panic] + #[no_panic_ensures_postcondition] + unsafe fn offset_from(self, origin: *const T) -> isize; + + #[no_panic] + #[no_panic_ensures_postcondition] + #[structural_requires(raw!(*self, std::mem::size_of::()))] + #[structural_ensures(own!(*self))] + #[structural_ensures(unsafe { eval_in!(own!(*self), &*self) } === &val)] + pub unsafe fn write(self, val: T); +} + +#[extern_spec] +impl usize { + #[terminates] + #[pure] + #[no_panic] + #[no_panic_ensures_postcondition] + fn is_power_of_two(self) -> bool; + + #[terminates] + #[pure] + #[no_panic] + #[no_panic_ensures_postcondition] + #[ensures(if multiply_int(Int::new_usize(self), Int::new_usize(rhs)) <= Int::new_usize(usize::MAX) { + result == Some(multiply_usize(self, rhs)) + } else { + let none = None; + result == none + })] + fn checked_mul(self, rhs: usize) -> Option; + + #[terminates] + #[pure] + #[no_panic] + #[no_panic_ensures_postcondition] + #[ensures(if Int::new_usize(self) + Int::new_usize(rhs) <= Int::new_usize(usize::MAX) { + result == Some(self + rhs) + } else { + let none = None; + result == none + })] + fn checked_add(self, rhs: usize) -> Option; + + #[terminates] + #[pure] + #[no_panic] + #[no_panic_ensures_postcondition] + #[ensures(if Int::new_usize(0) <= Int::new_usize(self) - Int::new_usize(rhs) { + result == Some(self - rhs) + } else { + let none = None; + result == none + })] + fn checked_sub(self, rhs: usize) -> Option; + + #[terminates] + #[pure] + #[no_panic] + #[no_panic_ensures_postcondition] + #[ensures(self >= rhs ==> result == self - rhs)] + fn wrapping_sub(self, rhs: usize) -> usize; + + #[terminates] + #[pure] + #[no_panic] + #[no_panic_ensures_postcondition] + #[ensures(Int::new_usize(self) + Int::new_usize(rhs) <= Int::new_usize(usize::MAX) ==> result == self + rhs)] + fn wrapping_add(self, rhs: usize) -> usize; +} + +#[extern_spec] +impl ::core::ptr::NonNull { + #[trusted] + #[terminates] + #[pure] + #[no_panic] + #[no_panic_ensures_postcondition] + pub fn dangling() -> Self; + + #[trusted] + #[terminates] + #[pure] + #[no_panic] + #[no_panic_ensures_postcondition] + pub fn as_ptr(self) -> *mut T; +} + +#[extern_spec] +mod core { + mod mem { + #[pure] + #[no_panic] + #[no_panic_ensures_postcondition] + #[terminates] + // FIXME: This is needed because this function is special cased only in the + // pure encoder and not in the impure one. + #[ensures(result == core::mem::size_of::())] + pub fn size_of() -> usize; + + #[pure] + #[no_panic] + #[no_panic_ensures_postcondition] + #[terminates] + // FIXME: What are the guarantees? + // https://doc.rust-lang.org/std/mem/fn.align_of.html says nothing… + #[ensures(result > 0)] + // FIXME: This is needed because this function is special cased only in the + // pure encoder and not in the impure one. + #[ensures(result == core::mem::align_of::())] + #[ensures(result.is_power_of_two())] + pub fn align_of() -> usize; + } + mod ptr { + #[pure] + #[no_panic] + #[no_panic_ensures_postcondition] + #[ensures(result.is_null())] + pub fn null() -> *const T; + + #[no_panic] + #[no_panic_ensures_postcondition] + #[structural_requires(own!(*src))] + #[structural_ensures(raw!(*src, std::mem::size_of::()))] + #[structural_ensures(unsafe { old(eval_in!(own!(*src), &*src)) } === &result)] + pub unsafe fn read(src: *const T) -> T; + + #[no_panic] + #[no_panic_ensures_postcondition] + #[structural_requires(raw!(*dst, std::mem::size_of::()))] + #[structural_ensures(own!(*dst))] + #[structural_ensures(unsafe { eval_in!(own!(*dst), &*dst) } === &src)] + pub unsafe fn write(dst: *mut T, src: T); + + #[structural_requires(own!(*to_drop))] + #[structural_ensures(raw!(*to_drop, std::mem::size_of::()))] + pub unsafe fn drop_in_place(to_drop: *mut T); + } +} + +#[extern_spec] +impl std::alloc::Layout { + #[ensures(result.size() == core::mem::size_of::())] + #[ensures(result.align() == core::mem::align_of::())] + #[no_panic] + #[no_panic_ensures_postcondition] + fn new() -> std::alloc::Layout; + + // #[requires(core::mem::size_of::() == 4 && core::mem::align_of::() == 4)] // FIXME: We currently support only i32. + // Documentation: https://doc.rust-lang.org/reference/type-layout.html#array-layout + #[requires(n * core::mem::size_of::() <= (isize::MAX as usize))] + #[ensures( + (n * core::mem::size_of::() <= (isize::MAX as usize)) == result.is_ok() + )] + #[ensures(match result { + Ok(layout) => { + layout.size() == n * core::mem::size_of::() && + layout.align() == core::mem::align_of::() + }, + Err(_) => true, + })] + #[no_panic] + #[no_panic_ensures_postcondition] + fn array(n: usize) -> Result; + + #[pure] + #[no_panic] + #[no_panic_ensures_postcondition] + fn size(&self) -> usize; + + #[pure] + #[no_panic] + #[no_panic_ensures_postcondition] + fn align(&self) -> usize; +} + +#[extern_spec] +mod std { + mod alloc { + // “It’s undefined behavior if global allocators unwind.” + // https://doc.rust-lang.org/std/alloc/trait.GlobalAlloc.html + #[no_panic] + #[structural_requires( + raw!(*ptr, layout.size()) && + raw_dealloc!(*ptr, layout.size(), layout.align()) + )] + pub unsafe fn dealloc(ptr: *mut u8, layout: std::alloc::Layout); + + // “It’s undefined behavior if global allocators unwind.” + // https://doc.rust-lang.org/std/alloc/trait.GlobalAlloc.html + #[no_panic] + #[no_panic_ensures_postcondition] + #[structural_requires( + layout.size() > 0 + )] + #[ensures( + !result.is_null() ==> ( + raw!(*result, layout.size()) && + raw_dealloc!(*result, layout.size(), layout.align()) + ) + )] + pub unsafe fn alloc(layout: std::alloc::Layout) -> *mut u8; + } +} diff --git a/prusti-contracts/prusti-contracts/src/lib.rs b/prusti-contracts/prusti-contracts/src/lib.rs index 28ab1bce27d..315dd184f07 100644 --- a/prusti-contracts/prusti-contracts/src/lib.rs +++ b/prusti-contracts/prusti-contracts/src/lib.rs @@ -1,11 +1,34 @@ -#![no_std] +// #![no_std] FIXME -/// A macro for writing a precondition on a function. +/// A macro for writing a functional precondition on a function. pub use prusti_contracts_proc_macros::requires; -/// A macro for writing a postcondition on a function. +/// A macro for writing a structural precondition on an unsafe function. +pub use prusti_contracts_proc_macros::structural_requires; + +/// A macro to indicate that the type invariant is not required by the function. +/// FIXME: Remove +pub use prusti_contracts_proc_macros::not_require; + +/// A macro for writing a functional postcondition on a function. pub use prusti_contracts_proc_macros::ensures; +/// A macro for writing a functional panic postcondition on a function. +pub use prusti_contracts_proc_macros::panic_ensures; + +/// A macro for writing a structural postcondition on an unsafe function. +pub use prusti_contracts_proc_macros::structural_ensures; + +/// A macro for writing a structural panic postcondition on an unsafe function. +pub use prusti_contracts_proc_macros::structural_panic_ensures; + +/// A macro to indicate that the type invariant is not ensured by the function. +/// FIXME: Remove +pub use prusti_contracts_proc_macros::not_ensure; + +/// A macro to indicate that the type invariant is broken. +pub use prusti_contracts_proc_macros::broken_invariant; + /// A macro for writing a pledge on a function. pub use prusti_contracts_proc_macros::after_expiry; @@ -18,21 +41,59 @@ pub use prusti_contracts_proc_macros::pure; /// A macro for marking a function as trusted. pub use prusti_contracts_proc_macros::trusted; +/// A macro for marking that a function never panics. +pub use prusti_contracts_proc_macros::no_panic; + +/// A macro for marking that if a function did not panic, then we can soundly +/// assume its postcondition even if the precondition did not hold. (This +/// basically means that we check the postcondition in memory safety mode.) +pub use prusti_contracts_proc_macros::no_panic_ensures_postcondition; + /// A macro for marking a function as opted into verification. pub use prusti_contracts_proc_macros::verified; +/// A macro for marking a pure function as to be non-verified, but axiomatized +/// when the configuration flag `opt_in_verification` is true. +pub use prusti_contracts_proc_macros::non_verified_pure; + /// A macro for type invariants. pub use prusti_contracts_proc_macros::invariant; +/// A macro for structural type invariants. A type with a structural +/// invariant needs to be managed manually by the user. +pub use prusti_contracts_proc_macros::structural_invariant; + /// A macro for writing a loop body invariant. pub use prusti_contracts_proc_macros::body_invariant; +/// A macro for writing a structural loop body invariant. +pub use prusti_contracts_proc_macros::structural_body_invariant; + /// A macro for writing assertions using the full prusti specifications pub use prusti_contracts_proc_macros::prusti_assert; +/// A macro for writing structural assertions using prusti syntax +pub use prusti_contracts_proc_macros::prusti_structural_assert; + /// A macro for writing assumptions using prusti syntax pub use prusti_contracts_proc_macros::prusti_assume; +/// A macro for writing structural assumptions using prusti syntax +pub use prusti_contracts_proc_macros::prusti_structural_assume; + +/// A macro for case splitting on some expressions. +pub use prusti_contracts_proc_macros::prusti_split_on; + +/// A macro for telling Prusti purification to materialize a predicate instance. +pub use prusti_contracts_proc_macros::materialize_predicate; + +/// A macro for telling Prusti purification that we have a predicate instance +/// coming from the quantifier. +pub use prusti_contracts_proc_macros::quantified_predicate; + +/// A macro that tells Prusti to assume that the allocation never fails. +pub use prusti_contracts_proc_macros::assume_allocation_never_fails; + /// A macro for writing refutations using prusti syntax pub use prusti_contracts_proc_macros::prusti_refute; @@ -57,6 +118,24 @@ pub use prusti_contracts_proc_macros::refine_spec; /// but omitted during compilation. pub use prusti_contracts_proc_macros::ghost; +/// A macro for defining a ghost block that is executed when a specified place +/// is dropped. +pub use prusti_contracts_proc_macros::on_drop_unwind; + +/// A macro for defining a ghost block that is executed just before dropping the specified value. +pub use prusti_contracts_proc_macros::before_drop; + +/// A macro for defining a ghost block that is executed just after dropping the specified value. +pub use prusti_contracts_proc_macros::after_drop; + +/// A macro for defining a ghost block that is executed when the execution +/// leaves the block including via panic. +pub use prusti_contracts_proc_macros::with_finally; + +/// A macro that enables precondition checking when verifying in memory safety +/// mode. +pub use prusti_contracts_proc_macros::checked; + /// A macro to customize how a struct or enum should be printed in a counterexample pub use prusti_contracts_proc_macros::print_counterexample; @@ -66,6 +145,102 @@ pub use prusti_contracts_proc_macros::terminates; /// A macro to annotate body variant of a loop to prove termination pub use prusti_contracts_proc_macros::body_variant; +/// A macro to mark the place as manually managed. +pub use prusti_contracts_proc_macros::manually_manage; + +/// A macro to manually pack a place capability. +pub use prusti_contracts_proc_macros::pack; + +/// A macro to manually unpack a place capability. +pub use prusti_contracts_proc_macros::unpack; + +/// Tell Prusti to obtain the specified capability. +pub use prusti_contracts_proc_macros::obtain; + +/// A macro to manually pack a place capability. +pub use prusti_contracts_proc_macros::pack_ref; + +/// A macro to manually unpack a place capability. +pub use prusti_contracts_proc_macros::unpack_ref; + +/// A macro to manually pack a place capability. +pub use prusti_contracts_proc_macros::pack_mut_ref; + +/// A macro to manually unpack a place capability. +pub use prusti_contracts_proc_macros::unpack_mut_ref; + +/// A macro to manually pack a place capability. +pub use prusti_contracts_proc_macros::pack_mut_ref_obligation; + +/// A macro to manually unpack a place capability. +pub use prusti_contracts_proc_macros::unpack_mut_ref_obligation; + +/// A macro to obtain a lifetime of a place. +pub use prusti_contracts_proc_macros::take_lifetime; + +/// A macro to end a lifetime. Note: this macro can be used only in on panic and +/// finally blocks of `with_finally!`. +pub use prusti_contracts_proc_macros::end_loan; + +/// Set the lifetime of the place to be used for all raw pointer to reference +/// casts. +pub use prusti_contracts_proc_macros::set_lifetime_for_raw_pointer_reference_casts; + +/// Attach the lifetime to the drop handler. +pub use prusti_contracts_proc_macros::attach_drop_lifetime; + +/// A macro to manually join a place capability. +pub use prusti_contracts_proc_macros::join; + +/// A macro to manually join a range of memory blocks into one. +pub use prusti_contracts_proc_macros::join_range; + +/// A macro to manually split a place capability. +pub use prusti_contracts_proc_macros::split; + +/// A macro to manually split a memory block into a range of memory blocks. +pub use prusti_contracts_proc_macros::split_range; + +/// A macro to stash away a range of own capabilities to get access to +/// underlying raw memory. +pub use prusti_contracts_proc_macros::stash_range; + +/// A macro to restore the stash away a range of own capabilities. +pub use prusti_contracts_proc_macros::restore_stash_range; + +/// A macro to manually close a reference. +pub use prusti_contracts_proc_macros::close_ref; + +/// A macro to manually open a reference. +pub use prusti_contracts_proc_macros::open_ref; + +/// A macro to manually close a reference. +pub use prusti_contracts_proc_macros::close_mut_ref; + +/// A macro to manually open a reference. +pub use prusti_contracts_proc_macros::open_mut_ref; + +/// A macro to apply the inheritance rule to the specified place. +pub use prusti_contracts_proc_macros::restore_mut_borrowed; + +/// A macro to manually resolve a reference. +pub use prusti_contracts_proc_macros::resolve; + +/// A macro to manually resolve a range of references. +pub use prusti_contracts_proc_macros::resolve_range; + +/// A macro to forget that a place is initialized. +pub use prusti_contracts_proc_macros::forget_initialization; + +/// A macro to forget that a range of places are initialized. +pub use prusti_contracts_proc_macros::forget_initialization_range; + +/// A macro to restore a place capability. +pub use prusti_contracts_proc_macros::restore; + +/// A macro to set a specific field of the union as active. +pub use prusti_contracts_proc_macros::set_union_active_field; + #[cfg(not(feature = "prusti"))] mod private { use core::marker::PhantomData; @@ -119,6 +294,23 @@ mod private { pub struct Ghost { _phantom: PhantomData, } + + pub struct GhostDrop; + + impl Drop for GhostDrop { + fn drop(&mut self) {} + } + + /// A type allowing to refer to a lifetime in places where Rust syntax does + /// not allow it. It should not be possible to construct from Rust code, + /// hence the private unit inside. + pub struct Lifetime(()); + + /// A methematical type representing a machine byte. + pub struct Byte(()); + + /// A methematical type representing a sequence of machine bytes. + pub struct Bytes(()); } #[cfg(feature = "prusti")] @@ -131,15 +323,21 @@ mod private { /// A macro for defining a closure with a specification. pub use prusti_contracts_proc_macros::{closure, pure, trusted}; - pub fn prusti_set_union_active_field(_arg: T) { - unreachable!(); - } + // pub fn prusti_set_union_active_field(_arg: T) { + // unreachable!(); + // } #[pure] pub fn prusti_terminates_trusted() -> Int { Int::new(1) } + /// A type allowing to refer to a lifetime in places where Rust syntax does + /// not allow it. It should not be possible to construct from Rust code, + /// hence the private unit inside. + #[derive(Copy, Clone)] + pub struct Lifetime(()); + /// a mathematical (unbounded) integer type /// it should not be constructed from running rust code, hence the private unit inside #[derive(Copy, Clone, PartialEq, Eq)] @@ -153,6 +351,18 @@ mod private { pub fn new_usize(_: usize) -> Self { panic!() } + + pub fn new_isize(_: isize) -> Self { + panic!() + } + + pub fn to_usize(&self) -> usize { + panic!() + } + + pub fn to_isize(&self) -> isize { + panic!() + } } macro_rules! __int_dummy_trait_impls__ { @@ -325,6 +535,20 @@ mod private { panic!() } } + + pub struct GhostDrop; + + impl Drop for GhostDrop { + fn drop(&mut self) {} + } + + /// A methematical type representing a machine byte. + #[derive(Copy, Clone, PartialEq, Eq)] + pub struct Byte(()); + + /// A methematical type representing a sequence of machine bytes. + #[derive(Copy, Clone, PartialEq, Eq)] + pub struct Bytes(()); } /// This function is used to evaluate an expression in the context just @@ -368,4 +592,558 @@ pub fn snapshot_equality(_l: T, _r: T) -> bool { true } +#[doc(hidden)] +#[trusted] +pub fn prusti_manually_manage(_arg: T) { + unreachable!(); +} + +#[doc(hidden)] +#[trusted] +pub fn prusti_pack_place(_arg: T) { + unreachable!(); +} + +#[doc(hidden)] +#[trusted] +pub fn prusti_unpack_place(_arg: T) { + unreachable!(); +} + +#[doc(hidden)] +#[trusted] +pub fn prusti_obtain_place(_arg: T) { + unreachable!(); +} + +#[doc(hidden)] +#[trusted] +pub fn prusti_pack_ref_place(_lifetime_name: &'static str, _arg: T) { + unreachable!(); +} + +#[doc(hidden)] +#[trusted] +pub fn prusti_unpack_ref_place(_lifetime_name: &'static str, _arg: T) { + unreachable!(); +} + +#[doc(hidden)] +#[trusted] +pub fn prusti_pack_mut_ref_place(_lifetime_name: &'static str, _arg: T) { + unreachable!(); +} + +#[doc(hidden)] +#[trusted] +pub fn prusti_unpack_mut_ref_place(_lifetime_name: &'static str, _arg: T) { + unreachable!(); +} + +#[doc(hidden)] +#[trusted] +pub fn prusti_pack_mut_ref_place_obligation(_lifetime_name: &'static str, _arg: T) { + unreachable!(); +} + +#[doc(hidden)] +#[trusted] +pub fn prusti_unpack_mut_ref_place_obligation(_lifetime_name: &'static str, _arg: T) { + unreachable!(); +} + +#[doc(hidden)] +#[trusted] +pub fn prusti_take_lifetime(_arg: T, _lifetime_name: &'static str) -> Lifetime { + unreachable!(); +} + +#[doc(hidden)] +#[trusted] +pub fn prusti_end_loan(_lifetime_name: &'static str) { + unreachable!(); +} + +#[doc(hidden)] +#[trusted] +pub fn prusti_set_lifetime_for_raw_pointer_reference_casts(_arg: T) { + unreachable!(); +} + +#[doc(hidden)] +#[trusted] +pub fn prusti_attach_drop_lifetime(_guard: T1, _reference: T2) { + unreachable!(); +} + +#[doc(hidden)] +#[trusted] +pub fn prusti_join_place(_arg: T) { + unreachable!(); +} + +#[doc(hidden)] +#[trusted] +pub fn prusti_join_range(_arg: T, _start_index: usize, _end_index: usize) { + unreachable!(); +} + +#[doc(hidden)] +#[trusted] +pub fn prusti_split_place(_arg: T) { + unreachable!(); +} + +#[doc(hidden)] +#[trusted] +pub fn prusti_split_range(_arg: T, _start_index: usize, _end_index: usize) { + unreachable!(); +} + +#[doc(hidden)] +#[trusted] +pub fn prusti_stash_range( + _arg: T, + _start_index: usize, + _end_index: usize, + _witness: &'static str, +) { + unreachable!(); +} + +#[doc(hidden)] +#[trusted] +pub fn prusti_restore_stash_range(_arg: T, _new_start_index: usize, _witness: &'static str) { + unreachable!(); +} + +#[doc(hidden)] +#[trusted] +/// We need to pass `_arg` to make sure the lifetime covers the closing of the +/// reference. +pub fn prusti_close_ref_place(_arg: T, _witness: &'static str) { + unreachable!(); +} + +#[doc(hidden)] +#[trusted] +pub fn prusti_open_ref_place(_lifetime: &'static str, _arg: T, _witness: &'static str) { + unreachable!(); +} + +#[doc(hidden)] +#[trusted] +/// We need to pass `_arg` to make sure the lifetime covers the closing of the +/// reference. +pub fn prusti_close_mut_ref_place(_arg: T, _witness: &'static str) { + unreachable!(); +} + +#[doc(hidden)] +#[trusted] +pub fn prusti_open_mut_ref_place(_lifetime: &'static str, _arg: T, _witness: &'static str) { + unreachable!(); +} + +#[doc(hidden)] +#[trusted] +pub fn prusti_restore_mut_borrowed(_referencing: T1, _referenced: T2) { + unreachable!(); +} + +#[doc(hidden)] +#[trusted] +pub fn prusti_resolve(_arg: T) { + unreachable!(); +} + +#[doc(hidden)] +#[trusted] +pub fn prusti_materialize_predicate(_arg: T) { + unreachable!(); +} + +#[doc(hidden)] +#[trusted] +pub fn prusti_quantified_predicate(_arg: T) { + unreachable!(); +} + +#[doc(hidden)] +#[trusted] +#[no_panic] +#[no_panic_ensures_postcondition] +#[ensures(allocation_never_fails())] +pub fn prusti_assume_allocation_never_fails() { + unreachable!(); +} + +#[doc(hidden)] +#[trusted] +pub fn prusti_resolve_range( + _lifetime: &'static str, + _arg: T, + _predicate_range_start_index: usize, + _predicate_range_end_index: usize, + _start_index: usize, + _end_index: usize, +) { + unreachable!(); +} + +#[doc(hidden)] +#[trusted] +#[pure] +pub fn prusti_forget_initialization(_arg: T) { + unreachable!(); +} + +#[doc(hidden)] +#[trusted] +#[pure] +pub fn prusti_forget_initialization_range(_address: T, _start: usize, _end: usize) { + unreachable!(); +} + +#[doc(hidden)] +#[trusted] +pub fn prusti_on_drop_unwind(_arg: T) { + unreachable!(); +} + +#[doc(hidden)] +#[trusted] +pub fn prusti_before_drop(_arg: T) { + unreachable!(); +} + +#[doc(hidden)] +#[trusted] +pub fn prusti_after_drop(_arg: T) { + unreachable!(); +} + +#[doc(hidden)] +#[trusted] +pub fn prusti_restore_place(_arg1: T, _arg2: T) { + unreachable!(); +} + +#[doc(hidden)] +#[trusted] +pub fn prusti_set_union_active_field(_arg: T) { + unreachable!(); +} + +/// Indicates that the expression should be evaluated assuming that the given +/// predicate is present. +#[doc(hidden)] +#[trusted] +pub fn prusti_eval_in(_predicate: bool, _expression: T) -> T { + unreachable!(); +} + +/// Indicates that the expression should be evaluated assuming that the given +/// quantified predicate is present. +#[doc(hidden)] +#[trusted] +pub fn prusti_eval_in_quantified(_predicate: bool, _expression: T) -> T { + unreachable!(); +} + +#[macro_export] +macro_rules! eval_in { + ($predicate:expr, $expression:expr) => { + $crate::prusti_eval_in($predicate, $expression) + }; +} + +#[macro_export] +macro_rules! eval_in_quantified { + ($predicate:expr, $expression:expr) => { + $crate::prusti_eval_in_quantified($predicate, $expression) + }; +} + +/// Indicates that the parameter's or return value invariant is broken. +#[doc(hidden)] +#[trusted] +#[pure] +pub fn prusti_broken_invariant(_place: T) -> bool { + unreachable!(); +} + +#[doc(hidden)] +#[trusted] +pub fn prusti_old_local(_local: &T) -> T { + unreachable!(); +} + +#[macro_export] +macro_rules! old_local { + ($local:expr) => { + $crate::prusti_old_local(unsafe { &$local }) + }; +} + +/// Indicates that we have the `own` capability to the specified place. +#[doc(hidden)] +#[trusted] +pub fn prusti_own(_place: T) -> bool { + unreachable!(); +} + +#[macro_export] +macro_rules! own { + ($place:expr) => { + $crate::prusti_own(unsafe { core::ptr::addr_of!($place) }) + }; +} + +/// Indicates that we have the `own` capability to the specified range. +#[doc(hidden)] +#[trusted] +pub fn prusti_own_range(_address: T, _start: usize, _end: usize) -> bool { + unreachable!(); +} + +#[macro_export] +macro_rules! own_range { + ($address:expr, $end:expr) => { + $crate::prusti_own_range($address, 0, $end) + }; + ($address:expr, $start:expr, $end:expr) => { + $crate::prusti_own_range($address, $start, $end) + }; +} + +/// Indicates that we have the shared reference capability to the specified +/// place. +#[doc(hidden)] +#[trusted] +pub fn prusti_shr(_place: T) -> bool { + unreachable!(); +} + +#[macro_export] +macro_rules! shr { + ($place:expr) => { + $crate::prusti_shr(unsafe { core::ptr::addr_of!($place) }) + }; +} + +/// Indicates that we have the unique reference capability to the specified +/// place. +#[doc(hidden)] +#[trusted] +pub fn prusti_unq(_lifetime: T1, _place: T2) -> bool { + unreachable!(); +} + +/// Indicates that we have the unique reference capability to the specified +/// place. +#[doc(hidden)] +#[trusted] +pub fn prusti_unq_real_lifetime(_lifetime: T1, _place: T2) -> bool { + unreachable!(); +} + +#[macro_export] +macro_rules! unq { + ($lifetime:ident, $place:expr) => { + $crate::prusti_unq(stringify!($lifetime), unsafe { + core::ptr::addr_of!($place) + }) + }; + ($lifetime:lifetime, $place:expr) => { + $crate::prusti_unq_real_lifetime(stringify!($lifetime), unsafe { + core::ptr::addr_of!($place) + }) + }; +} + +/// Indicates that we have the unique reference capability to the specified range. +#[doc(hidden)] +#[trusted] +pub fn prusti_unq_real_lifetime_range( + _lifetime: L, + _address: T, + _start: usize, + _end: usize, +) -> bool { + unreachable!(); +} + +/// Deref a raw pointer with the specified offset. +#[doc(hidden)] +#[trusted] +pub unsafe fn prusti_deref_own(_address: *const T, _index: usize) -> T { + unreachable!(); +} + +#[macro_export] +macro_rules! deref_own { + ($address:expr, $index:expr) => { + unsafe { $crate::prusti_deref_own($address, $index) } + }; +} + +/// Obtain the bytes of the specified memory block. +#[doc(hidden)] +#[trusted] +pub fn prusti_bytes(_address: T, _length: usize) -> Bytes { + unreachable!(); +} + +#[macro_export] +macro_rules! bytes { + ($address:expr, $length:expr) => { + $crate::prusti_bytes(unsafe { core::ptr::addr_of!($address) }, $length) + }; +} + +/// Obtain the bytes of the specified memory block. +#[doc(hidden)] +#[trusted] +#[terminates] +#[no_panic] +pub fn prusti_bytes_ptr(_pointer: *const T, _length: usize) -> Bytes { + unreachable!(); +} + +#[macro_export] +macro_rules! bytes_ptr { + ($pointer:expr, $length:expr) => { + $crate::prusti_bytes_ptr(unsafe { $pointer }, $length) + }; +} + +/// Read the byte at the given index. +/// +/// FIXME: This function does not check bounds. Instead, it returns garbage in +/// case of out-of-bounds +pub fn read_byte(_bytes: Bytes, _index: usize) -> Byte { + unreachable!(); +} + +/// Check whether `element_address` is contained in the range starting at +/// `start_address` and having the specified size. +pub fn range_contains( + _start_address: *const T, + _range_size: usize, + _element_address: *const T, +) -> bool { + unreachable!(); +} + +/// Indicates that we have the `raw` capability to the specified address. +#[doc(hidden)] +#[trusted] +pub fn prusti_raw(_address: T, _size: usize) -> bool { + true +} + +#[macro_export] +macro_rules! raw { + ($place:expr, $size: expr) => { + $crate::prusti_raw(unsafe { core::ptr::addr_of!($place) }, $size) + }; +} + +/// Indicates that we have the `raw` capability to the specified range. +#[doc(hidden)] +#[trusted] +pub fn prusti_raw_range(_address: T, _size: usize, _start: usize, _end: usize) -> bool { + unreachable!(); +} + +/// Indicates that we have the `raw` capability for locations for which the +/// condition holds. +#[doc(hidden)] +#[trusted] +pub fn prusti_raw_range_guarded( + _address: T, + _size: usize, + _trigger_set: S, + _closure: F, +) -> bool { + unreachable!(); +} + +/// Indicates that we have the capability to deallocate. +#[doc(hidden)] +#[trusted] +pub fn prusti_raw_dealloc(_address: T, _size: usize) -> bool { + true +} + +#[macro_export] +macro_rules! raw_dealloc { + ($place:expr, $size: expr, $align: expr) => { + $crate::prusti_raw_dealloc(unsafe { core::ptr::addr_of!($place) }, $size) + }; +} + +/// Temporarily unpacks the owned predicate at the given location. +#[doc(hidden)] +#[trusted] +pub fn prusti_unpacking(_place: T, _body: U) -> U { + unimplemented!() +} + +#[macro_export] +macro_rules! unpacking { + ($place:expr, $body: expr) => { + $crate::prusti_unpacking(unsafe { core::ptr::addr_of!($place) }, $body) + }; +} + +/// A ghost operation for computing an offset of the pointer. +pub fn address_offset_mut(_ptr: *mut T, _count: Int) -> *mut T { + unreachable!(); +} + +/// A ghost operation for computing an offset of the pointer. +pub fn address_offset(_ptr: *const T, _count: Int) -> *const T { + unreachable!(); +} + +/// A ghost operation for computing the distance between two pointers in the units of `T`. +pub fn address_from(_ptr: *const T, _origin: *const T) -> Int { + unreachable!(); +} + +/// A ghost operation for expressing that the two pointers belong to the same allocation. +pub fn same_allocation(_ptr1: *const T, _ptr2: *const T) -> bool { + unreachable!(); +} + +/// A ghost operation for expressing that the address belongs to a fresh +/// allocation (different from all others). +pub fn fresh_allocation(_ptr1: *const T) -> bool { + unreachable!(); +} + +#[pure] +#[terminates] +pub fn multiply_int(_left: Int, _right: Int) -> Int { + unreachable!(); +} + +#[pure] +#[terminates] +#[requires(multiply_int(Int::new_usize(left), Int::new_usize(right)) <= Int::new_usize(usize::MAX))] +#[ensures(multiply_int(Int::new_usize(left), Int::new_usize(right)) == Int::new_usize(result))] +pub fn multiply_usize(left: usize, right: usize) -> usize { + unreachable!(); +} + +#[trusted] +#[pure] +#[no_panic] +#[no_panic_ensures_postcondition] +pub fn allocation_never_fails() -> bool { + unreachable!(); +} + pub use private::*; diff --git a/prusti-contracts/prusti-specs/src/lib.rs b/prusti-contracts/prusti-specs/src/lib.rs index e2383573575..67ae1811893 100644 --- a/prusti-contracts/prusti-specs/src/lib.rs +++ b/prusti-contracts/prusti-specs/src/lib.rs @@ -14,6 +14,7 @@ mod extern_spec_rewriter; mod type_cond_specs; mod parse_closure_macro; mod parse_quote_spanned; +mod parse_ghost_macros; mod predicate; mod rewriter; mod span_overrider; @@ -23,6 +24,7 @@ mod type_model; mod user_provided_type_params; mod print_counterexample; +use parse_ghost_macros::{OnDropUnwind, WithFinally}; use proc_macro2::{Span, TokenStream, TokenTree}; use quote::{quote, quote_spanned, ToTokens}; use rewriter::AstRewriter; @@ -70,7 +72,13 @@ fn extract_prusti_attributes( if let Ok(attr_kind) = attr.path.segments[idx].ident.to_string().try_into() { let tokens = match attr_kind { SpecAttributeKind::Requires + | SpecAttributeKind::StructuralRequires | SpecAttributeKind::Ensures + | SpecAttributeKind::PanicEnsures + | SpecAttributeKind::StructuralEnsures + | SpecAttributeKind::StructuralPanicEnsures + | SpecAttributeKind::NotRequire + | SpecAttributeKind::NotEnsure | SpecAttributeKind::AfterExpiry | SpecAttributeKind::AssertOnExpiry | SpecAttributeKind::RefineSpec => { @@ -87,7 +95,10 @@ fn extract_prusti_attributes( | SpecAttributeKind::Terminates | SpecAttributeKind::Trusted | SpecAttributeKind::Predicate - | SpecAttributeKind::Verified => { + | SpecAttributeKind::Verified + | SpecAttributeKind::NonVerifiedPure + | SpecAttributeKind::NoPanic + | SpecAttributeKind::NoPanicEnsuresPostcondition => { assert!(attr.tokens.is_empty(), "Unexpected shape of an attribute."); attr.tokens } @@ -162,13 +173,30 @@ fn generate_spec_and_assertions( for (attr_kind, attr_tokens) in prusti_attributes.drain(..) { let rewriting_result = match attr_kind { SpecAttributeKind::Requires => generate_for_requires(attr_tokens, item), + SpecAttributeKind::StructuralRequires => { + generate_for_structural_requires(attr_tokens, item) + } SpecAttributeKind::Ensures => generate_for_ensures(attr_tokens, item), + SpecAttributeKind::PanicEnsures => generate_for_panic_ensures(attr_tokens, item), + SpecAttributeKind::StructuralEnsures => { + generate_for_structural_ensures(attr_tokens, item) + } + SpecAttributeKind::StructuralPanicEnsures => { + generate_for_structural_panic_ensures(attr_tokens, item) + } + SpecAttributeKind::NotRequire => generate_for_not_require(attr_tokens, item), + SpecAttributeKind::NotEnsure => generate_for_not_ensure(attr_tokens, item), SpecAttributeKind::AfterExpiry => generate_for_after_expiry(attr_tokens, item), SpecAttributeKind::AssertOnExpiry => generate_for_assert_on_expiry(attr_tokens, item), SpecAttributeKind::Pure => generate_for_pure(attr_tokens, item), SpecAttributeKind::Verified => generate_for_verified(attr_tokens, item), + SpecAttributeKind::NonVerifiedPure => generate_for_non_verified_pure(attr_tokens, item), SpecAttributeKind::Terminates => generate_for_terminates(attr_tokens, item), SpecAttributeKind::Trusted => generate_for_trusted(attr_tokens, item), + SpecAttributeKind::NoPanic => generate_for_no_panic(attr_tokens, item), + SpecAttributeKind::NoPanicEnsuresPostcondition => { + generate_for_no_panic_ensures_postcondition(attr_tokens, item) + } // Predicates are handled separately below; the entry in the SpecAttributeKind enum // only exists so we successfully parse it and emit an error in // `check_incompatible_attrs`; so we'll never reach here. @@ -201,6 +229,45 @@ fn generate_for_requires(attr: TokenStream, item: &untyped::AnyFnItem) -> Genera )) } +/// Generate spec items and attributes to typecheck the and later retrieve "structural_requires" annotations. +fn generate_for_structural_requires( + attr: TokenStream, + item: &untyped::AnyFnItem, +) -> GeneratedResult { + let mut rewriter = rewriter::AstRewriter::new(); + let spec_id = rewriter.generate_spec_id(); + let spec_id_str = spec_id.to_string(); + let spec_item = + rewriter.process_assertion(rewriter::SpecItemType::Precondition, spec_id, attr, item)?; + Ok(( + vec![spec_item], + vec![parse_quote_spanned! {item.span()=> + #[prusti::pre_structural_spec_id_ref = #spec_id_str] + }], + )) +} + +/// Generate spec items and attributes to typecheck and later retrieve +/// "not_require" annotations. +fn generate_for_not_require(attr: TokenStream, item: &untyped::AnyFnItem) -> GeneratedResult { + let attr = quote! { prusti_broken_invariant(#attr) }; + let mut rewriter = rewriter::AstRewriter::new(); + let spec_id = rewriter.generate_spec_id(); + let spec_id_str = spec_id.to_string(); + let spec_item = rewriter.process_assertion( + rewriter::SpecItemType::BrokenPrecondition, + spec_id, + attr, + item, + )?; + Ok(( + vec![spec_item], + vec![parse_quote_spanned! {item.span()=> + #[prusti::pre_broken_spec_id_ref = #spec_id_str] + }], + )) +} + /// Generate spec items and attributes to typecheck the and later retrieve "ensures" annotations. fn generate_for_ensures(attr: TokenStream, item: &untyped::AnyFnItem) -> GeneratedResult { let mut rewriter = rewriter::AstRewriter::new(); @@ -216,6 +283,81 @@ fn generate_for_ensures(attr: TokenStream, item: &untyped::AnyFnItem) -> Generat )) } +/// Generate spec items and attributes to typecheck the and later retrieve +/// "panic_ensures" annotations. +fn generate_for_panic_ensures(attr: TokenStream, item: &untyped::AnyFnItem) -> GeneratedResult { + let mut rewriter = rewriter::AstRewriter::new(); + let spec_id = rewriter.generate_spec_id(); + let spec_id_str = spec_id.to_string(); + let spec_item = + rewriter.process_assertion(rewriter::SpecItemType::Postcondition, spec_id, attr, item)?; + Ok(( + vec![spec_item], + vec![parse_quote_spanned! {item.span()=> + #[prusti::post_panic_spec_id_ref = #spec_id_str] + }], + )) +} + +/// Generate spec items and attributes to typecheck the and later retrieve +/// "structural_ensures" annotations. +fn generate_for_structural_ensures( + attr: TokenStream, + item: &untyped::AnyFnItem, +) -> GeneratedResult { + let mut rewriter = rewriter::AstRewriter::new(); + let spec_id = rewriter.generate_spec_id(); + let spec_id_str = spec_id.to_string(); + let spec_item = + rewriter.process_assertion(rewriter::SpecItemType::Postcondition, spec_id, attr, item)?; + Ok(( + vec![spec_item], + vec![parse_quote_spanned! {item.span()=> + #[prusti::post_structural_spec_id_ref = #spec_id_str] + }], + )) +} + +/// Generate spec items and attributes to typecheck the and later retrieve +/// "structural_ensures" annotations. +fn generate_for_structural_panic_ensures( + attr: TokenStream, + item: &untyped::AnyFnItem, +) -> GeneratedResult { + let mut rewriter = rewriter::AstRewriter::new(); + let spec_id = rewriter.generate_spec_id(); + let spec_id_str = spec_id.to_string(); + let spec_item = + rewriter.process_assertion(rewriter::SpecItemType::Postcondition, spec_id, attr, item)?; + Ok(( + vec![spec_item], + vec![parse_quote_spanned! {item.span()=> + #[prusti::post_structural_panic_spec_id_ref = #spec_id_str] + }], + )) +} + +/// Generate spec items and attributes to typecheck and later retrieve +/// "not_ensure" annotations. +fn generate_for_not_ensure(attr: TokenStream, item: &untyped::AnyFnItem) -> GeneratedResult { + let attr = quote! { prusti_broken_invariant(#attr) }; + let mut rewriter = rewriter::AstRewriter::new(); + let spec_id = rewriter.generate_spec_id(); + let spec_id_str = spec_id.to_string(); + let spec_item = rewriter.process_assertion( + rewriter::SpecItemType::BrokenPostcondition, + spec_id, + attr, + item, + )?; + Ok(( + vec![spec_item], + vec![parse_quote_spanned! {item.span()=> + #[prusti::post_broken_spec_id_ref = #spec_id_str] + }], + )) +} + /// Generate spec items and attributes to typecheck and later retrieve "after_expiry" annotations. fn generate_for_after_expiry(attr: TokenStream, item: &untyped::AnyFnItem) -> GeneratedResult { let mut rewriter = rewriter::AstRewriter::new(); @@ -314,6 +456,23 @@ fn generate_for_verified(attr: TokenStream, item: &untyped::AnyFnItem) -> Genera )) } +/// Generate spec items and attributes to typecheck and later retrieve "non_verified_pure" annotations. +fn generate_for_non_verified_pure(attr: TokenStream, item: &untyped::AnyFnItem) -> GeneratedResult { + if !attr.is_empty() { + return Err(syn::Error::new( + attr.span(), + "the `#[non_verified_pure]` attribute does not take parameters", + )); + } + + Ok(( + vec![], + vec![parse_quote_spanned! {item.span()=> + #[prusti::non_verified_pure] + }], + )) +} + /// Generate spec items and attributes to typecheck and later retrieve "pure" annotations, but encoded as a referenced separate function that type-conditional spec refinements can apply trait bounds to. fn generate_for_pure_refinements(item: &untyped::AnyFnItem) -> GeneratedResult { let mut rewriter = rewriter::AstRewriter::new(); @@ -346,6 +505,45 @@ fn generate_for_trusted(attr: TokenStream, item: &untyped::AnyFnItem) -> Generat )) } +/// Generate spec items and attributes to typecheck and later retrieve +/// "no_panic" annotations. +fn generate_for_no_panic(attr: TokenStream, item: &untyped::AnyFnItem) -> GeneratedResult { + if !attr.is_empty() { + return Err(syn::Error::new( + attr.span(), + "the `#[no_panic]` attribute does not take parameters", + )); + } + + Ok(( + vec![], + vec![parse_quote_spanned! {item.span()=> + #[prusti::no_panic] + }], + )) +} + +/// Generate spec items and attributes to typecheck and later retrieve +/// "no_panic_ensures_postcondition" annotations. +fn generate_for_no_panic_ensures_postcondition( + attr: TokenStream, + item: &untyped::AnyFnItem, +) -> GeneratedResult { + if !attr.is_empty() { + return Err(syn::Error::new( + attr.span(), + "the `#[no_panic_ensures_postcondition]` attribute does not take parameters", + )); + } + + Ok(( + vec![], + vec![parse_quote_spanned! {item.span()=> + #[prusti::no_panic_ensures_postcondition] + }], + )) +} + /// Generate spec items and attributes to typecheck and later retrieve "trusted" annotations. fn generate_for_trusted_for_types(attr: TokenStream, item: &syn::DeriveInput) -> GeneratedResult { if !attr.is_empty() { @@ -422,6 +620,10 @@ pub fn body_invariant(tokens: TokenStream) -> TokenStream { generate_expression_closure(&AstRewriter::process_loop_invariant, tokens) } +pub fn structural_body_invariant(tokens: TokenStream) -> TokenStream { + generate_expression_closure(&AstRewriter::process_structural_loop_invariant, tokens) +} + pub fn prusti_assertion(tokens: TokenStream) -> TokenStream { generate_expression_closure(&AstRewriter::process_prusti_assertion, tokens) } @@ -434,6 +636,18 @@ pub fn prusti_refutation(tokens: TokenStream) -> TokenStream { generate_expression_closure(&AstRewriter::process_prusti_refutation, tokens) } +pub fn prusti_structural_assert(tokens: TokenStream) -> TokenStream { + generate_expression_closure(&AstRewriter::process_prusti_structural_assertion, tokens) +} + +pub fn prusti_structural_assume(tokens: TokenStream) -> TokenStream { + generate_expression_closure(&AstRewriter::process_prusti_structural_assumption, tokens) +} + +pub fn prusti_split_on(tokens: TokenStream) -> TokenStream { + generate_expression_closure(&AstRewriter::process_prusti_split, tokens) +} + /// Generates the TokenStream encoding an expression using prusti syntax /// Used for body invariants, assertions, and assumptions fn generate_expression_closure( @@ -453,6 +667,23 @@ fn generate_expression_closure( } } +fn prusti_specification_expression( + tokens: TokenStream, +) -> syn::Result<(SpecificationId, TokenStream)> { + let mut rewriter = rewriter::AstRewriter::new(); + let spec_id = rewriter.generate_spec_id(); + let closure = rewriter.process_prusti_specification_expression(spec_id, tokens)?; + let callsite_span = Span::call_site(); + let tokens = quote_spanned! {callsite_span=> + #[allow(unused_must_use, unused_variables, unused_braces, unused_parens)] + #[prusti::specs_version = #SPECS_VERSION] + if false { + #closure + } + }; + Ok((spec_id, tokens)) +} + pub fn closure(tokens: TokenStream) -> TokenStream { let cl_spec: ClosureWithSpec = handle_result!(syn::parse(tokens.into())); let callsite_span = Span::call_site(); @@ -710,7 +941,7 @@ pub fn trusted(attr: TokenStream, tokens: TokenStream) -> TokenStream { } } -pub fn invariant(attr: TokenStream, tokens: TokenStream) -> TokenStream { +pub fn invariant(attr: TokenStream, tokens: TokenStream, is_structural: bool) -> TokenStream { let mut rewriter = rewriter::AstRewriter::new(); let spec_id = rewriter.generate_spec_id(); let spec_id_str = spec_id.to_string(); @@ -721,41 +952,60 @@ pub fn invariant(attr: TokenStream, tokens: TokenStream) -> TokenStream { // clippy false positive (https://github.com/rust-lang/rust-clippy/issues/10577) #[allow(clippy::redundant_clone)] let item_ident = item.ident.clone(); - + let item_name_structural = if is_structural { + "structural" + } else { + "non_structural" + }; let item_name = syn::Ident::new( - &format!("prusti_invariant_item_{item_ident}_{spec_id}"), + &format!("prusti_invariant_item_{item_name_structural}_{item_ident}_{spec_id}"), item_span, ); let attr = handle_result!(parse_prusti(attr)); + let is_structural_tokens = if is_structural { + quote_spanned!(item_span => #[prusti::type_invariant_structural]) + } else { + quote_spanned!(item_span => #[prusti::type_invariant_non_structural]) + }; // TODO: move some of this to AstRewriter? // see AstRewriter::generate_spec_item_fn for explanation of syntax below let spec_item: syn::ItemFn = parse_quote_spanned! {item_span=> #[allow(unused_must_use, unused_parens, unused_variables, dead_code, non_snake_case)] #[prusti::spec_only] #[prusti::type_invariant_spec] + #is_structural_tokens #[prusti::spec_id = #spec_id_str] fn #item_name(self) -> bool { !!((#attr) : bool) } }; - // clippy false positive (https://github.com/rust-lang/rust-clippy/issues/10577) - #[allow(clippy::redundant_clone)] - let generics = item.generics.clone(); + let generics = &item.generics; + + let mut generic_params = generics.params.clone(); + for param in &mut generic_params { + match param { + syn::GenericParam::Type(param) => { + param.attrs = Vec::new(); + param.colon_token = None; + param.bounds = syn::punctuated::Punctuated::new(); + param.eq_token = None; + param.default = None; + } + syn::GenericParam::Lifetime(param) => { + param.attrs = Vec::new(); + param.colon_token = None; + param.bounds = syn::punctuated::Punctuated::new(); + } + syn::GenericParam::Const(_) => {} + } + } - let generics_idents = generics - .params - .iter() - .filter_map(|generic_param| match generic_param { - syn::GenericParam::Type(type_param) => Some(type_param.ident.clone()), - _ => None, - }) - .collect::>(); // TODO: similarly to extern_specs, don't generate an actual impl let item_impl: syn::ItemImpl = parse_quote_spanned! {item_span=> - impl #generics #item_ident < #generics_idents > { + impl #generics #item_ident < #generic_params > { #spec_item } }; @@ -865,15 +1115,36 @@ fn extract_prusti_attributes_for_types( if let Ok(attr_kind) = attr.path.segments[0].ident.to_string().try_into() { let tokens = match attr_kind { SpecAttributeKind::Requires => unreachable!("requires on type"), + SpecAttributeKind::StructuralRequires => { + unreachable!("structural requires on type") + } SpecAttributeKind::Ensures => unreachable!("ensures on type"), + SpecAttributeKind::PanicEnsures => unreachable!("panic_ensures on type"), + SpecAttributeKind::StructuralEnsures => { + unreachable!("structural ensures on type") + } + SpecAttributeKind::StructuralPanicEnsures => { + unreachable!("structural panic_ensures on type") + } SpecAttributeKind::AfterExpiry => unreachable!("after_expiry on type"), SpecAttributeKind::AssertOnExpiry => unreachable!("assert_on_expiry on type"), SpecAttributeKind::RefineSpec => unreachable!("refine_spec on type"), SpecAttributeKind::Pure => unreachable!("pure on type"), SpecAttributeKind::Verified => unreachable!("verified on type"), + SpecAttributeKind::NonVerifiedPure => unreachable!("non_verified_pure on type"), SpecAttributeKind::Invariant => unreachable!("invariant on type"), SpecAttributeKind::Predicate => unreachable!("predicate on type"), SpecAttributeKind::Terminates => unreachable!("terminates on type"), + SpecAttributeKind::NoPanic => unreachable!("no_panic on type"), + SpecAttributeKind::NoPanicEnsuresPostcondition => { + unreachable!("no_panic_ensures_postcondition on type") + } + SpecAttributeKind::NotRequire => { + unreachable!("not_require on type") + } + SpecAttributeKind::NotEnsure => { + unreachable!("not_ensure on type") + } SpecAttributeKind::Trusted | SpecAttributeKind::Model => { assert!(attr.tokens.is_empty(), "Unexpected shape of an attribute."); attr.tokens @@ -910,16 +1181,25 @@ fn generate_spec_and_assertions_for_types( for (attr_kind, attr_tokens) in prusti_attributes.drain(..) { let rewriting_result = match attr_kind { SpecAttributeKind::Requires => unreachable!(), + SpecAttributeKind::StructuralRequires => unreachable!(), SpecAttributeKind::Ensures => unreachable!(), + SpecAttributeKind::PanicEnsures => unreachable!(), + SpecAttributeKind::StructuralEnsures => unreachable!(), + SpecAttributeKind::StructuralPanicEnsures => unreachable!(), SpecAttributeKind::AfterExpiry => unreachable!(), SpecAttributeKind::AssertOnExpiry => unreachable!(), SpecAttributeKind::Pure => unreachable!(), SpecAttributeKind::Verified => unreachable!(), + SpecAttributeKind::NonVerifiedPure => unreachable!(), SpecAttributeKind::Predicate => unreachable!(), SpecAttributeKind::Invariant => unreachable!(), SpecAttributeKind::RefineSpec => unreachable!(), SpecAttributeKind::Terminates => unreachable!(), SpecAttributeKind::Trusted => generate_for_trusted_for_types(attr_tokens, item), + SpecAttributeKind::NoPanic => unreachable!(), + SpecAttributeKind::NoPanicEnsuresPostcondition => unreachable!(), + SpecAttributeKind::NotRequire => unreachable!(), + SpecAttributeKind::NotEnsure => unreachable!(), SpecAttributeKind::Model => generate_for_model(attr_tokens, item), SpecAttributeKind::PrintCounterexample => { generate_for_print_counterexample(attr_tokens, item) @@ -1013,11 +1293,23 @@ pub fn print_counterexample(attr: TokenStream, tokens: TokenStream) -> TokenStre .to_compile_error() } } -pub fn ghost(tokens: TokenStream) -> TokenStream { - let mut rewriter = rewriter::AstRewriter::new(); + +fn ghost_with_annotation( + tokens: TokenStream, + annotation: TokenStream, + wrap_result_in_ghost: bool, + begin_marker: TokenStream, + end_marker: TokenStream, + spec_id: Option, +) -> TokenStream { let callsite_span = Span::call_site(); - let spec_id = rewriter.generate_spec_id(); + let spec_id = if let Some(spec_id) = spec_id { + spec_id + } else { + let mut rewriter = rewriter::AstRewriter::new(); + rewriter.generate_spec_id() + }; let spec_id_str = spec_id.to_string(); let make_closure = |kind| { @@ -1106,15 +1398,21 @@ pub fn ghost(tokens: TokenStream) -> TokenStream { exit_errors.push(*break_span); } - let begin = make_closure(quote! {ghost_begin}); - let end = make_closure(quote! {ghost_end}); + let begin = make_closure(begin_marker); + let end = make_closure(end_marker); + let ghost_result = if wrap_result_in_ghost { + quote! {Ghost::new(#tokens)} + } else { + quote! {#tokens} + }; if exit_errors.is_empty() { quote_spanned! {callsite_span=> { #begin + #annotation #[prusti::specs_version = #SPECS_VERSION] - let ghost_result = Ghost::new(#tokens); + let ghost_result = #ghost_result; #end ghost_result } @@ -1132,3 +1430,643 @@ pub fn ghost(tokens: TokenStream) -> TokenStream { syn_errors } } + +pub fn ghost(tokens: TokenStream) -> TokenStream { + ghost_with_annotation( + tokens, + quote! {}, + true, + quote! {ghost_begin}, + quote! {ghost_end}, + None, + ) +} + +macro_rules! parse_expressions { + ($tokens: expr, $separator: ty => $( $expr:ident ),* ) => { + let parser = syn::punctuated::Punctuated::::parse_terminated; + let expressions = handle_result!(syn::parse::Parser::parse2(parser, $tokens)); + let mut expressions: Vec<_> = expressions.into_pairs().map(|pair| pair.into_value()).collect(); + expressions.reverse(); + $( + let $expr = handle_result!( + expressions + .pop() + .ok_or_else(|| syn::Error::new(Span::call_site(), "Expected more expressions")) + ); + )* + } +} + +pub fn on_drop_unwind(tokens: TokenStream) -> TokenStream { + let OnDropUnwind { + dropped_place, + block, + } = handle_result!(syn::parse2(tokens)); + ghost_with_annotation( + quote! { #block }, + unsafe_spec_function_call(quote! { + prusti_on_drop_unwind(std::ptr::addr_of!(#dropped_place)) + }), + false, + quote! {specification_region_begin}, + quote! {specification_region_end}, + None, + ) +} + +pub fn before_drop(tokens: TokenStream) -> TokenStream { + let OnDropUnwind { + dropped_place, + block, + } = handle_result!(syn::parse2(tokens)); + ghost_with_annotation( + quote! { #block }, + unsafe_spec_function_call(quote! { + prusti_before_drop(std::ptr::addr_of!(#dropped_place)) + }), + false, + quote! {specification_region_begin}, + quote! {specification_region_end}, + None, + ) +} + +pub fn after_drop(tokens: TokenStream) -> TokenStream { + let OnDropUnwind { + dropped_place, + block, + } = handle_result!(syn::parse2(tokens)); + ghost_with_annotation( + quote! { #block }, + unsafe_spec_function_call(quote! { + prusti_after_drop(std::ptr::addr_of!(#dropped_place)) + }), + false, + quote! {specification_region_begin}, + quote! {specification_region_end}, + None, + ) +} + +pub fn with_finally(tokens: TokenStream) -> TokenStream { + let WithFinally { + executed_block, + on_panic_block, + finally_block_at_panic_start, + finally_block_at_resume, + } = handle_result!(syn::parse2(tokens)); + let mut rewriter = rewriter::AstRewriter::new(); + let on_panic_spec_id = rewriter.generate_spec_id(); + let on_panic_spec_id_str = on_panic_spec_id.to_string(); + let finally_at_panic_start_spec_id = rewriter.generate_spec_id(); + let finally_at_panic_start_spec_id_str = finally_at_panic_start_spec_id.to_string(); + let finally_at_resume_spec_id = rewriter.generate_spec_id(); + let finally_at_resume_spec_id_str = finally_at_resume_spec_id.to_string(); + let make_closure = |kind| { + quote! { + #[allow(unused_must_use, unused_variables, unused_braces, unused_parens)] + if false { + #[prusti::spec_only] + #[prusti::#kind] + #[prusti::on_panic_spec_id = #on_panic_spec_id_str] + #[prusti::finally_at_panic_start_spec_id = #finally_at_panic_start_spec_id_str] + #[prusti::finally_at_resume_spec_id = #finally_at_resume_spec_id_str] + || -> () {}; + } + } + }; + let executed_block_begin = make_closure(quote! {try_finally_executed_block_begin}); + let executed_block_end = make_closure(quote! {try_finally_executed_block_end}); + let on_panic_ghost_block = ghost_with_annotation( + quote! { #on_panic_block }, + quote! {}, + false, + quote! {specification_region_begin}, + quote! {specification_region_end}, + Some(on_panic_spec_id), + ); + let finally_at_panic_start_ghost_block = ghost_with_annotation( + quote! { #finally_block_at_panic_start }, + quote! {}, + false, + quote! {specification_region_begin}, + quote! {specification_region_end}, + Some(finally_at_panic_start_spec_id), + ); + let finally_at_resume_ghost_block = ghost_with_annotation( + quote! { #finally_block_at_resume }, + quote! {}, + false, + quote! {specification_region_begin}, + quote! {specification_region_end}, + Some(finally_at_resume_spec_id), + ); + quote! { + #executed_block_begin + #(#executed_block)* + #executed_block_end + #on_panic_ghost_block + #finally_at_panic_start_ghost_block + #finally_at_resume_ghost_block + } +} + +pub fn checked(tokens: TokenStream) -> TokenStream { + let mut rewriter = rewriter::AstRewriter::new(); + let spec_id = rewriter.generate_spec_id(); + let spec_id_str = spec_id.to_string(); + let make_closure = |kind| { + quote! { + #[allow(unused_must_use, unused_variables, unused_braces, unused_parens)] + if false { + #[prusti::spec_only] + #[prusti::#kind] + #[prusti::spec_id = #spec_id_str] + || -> () {}; + } + } + }; + let checked_block_begin = make_closure(quote! {checked_block_begin}); + let checked_block_end = make_closure(quote! {checked_block_end}); + let tokens = quote! { + { + #checked_block_begin + let result = { #tokens }; + #checked_block_end + result + } + }; + tokens +} + +pub fn manually_manage(tokens: TokenStream) -> TokenStream { + generate_place_function(tokens, quote! {prusti_manually_manage}) +} + +pub fn pack(tokens: TokenStream) -> TokenStream { + generate_place_function(tokens, quote! {prusti_pack_place}) +} + +pub fn unpack(tokens: TokenStream) -> TokenStream { + generate_place_function(tokens, quote! {prusti_unpack_place}) +} + +pub fn obtain(tokens: TokenStream) -> TokenStream { + generate_place_function(tokens, quote! {prusti_obtain_place}) +} + +pub fn pack_ref(tokens: TokenStream) -> TokenStream { + // generate_place_function(tokens, quote! {prusti_pack_ref_place}) + pack_unpack_ref(tokens, quote! {prusti_pack_ref_place}) +} + +pub fn unpack_ref(tokens: TokenStream) -> TokenStream { + // generate_place_function(tokens, quote! {prusti_unpack_ref_place}) + pack_unpack_ref(tokens, quote! {prusti_unpack_ref_place}) +} + +pub fn pack_mut_ref(tokens: TokenStream) -> TokenStream { + // generate_place_function(tokens, quote! {prusti_pack_mut_ref_place}) + pack_unpack_ref(tokens, quote! {prusti_pack_mut_ref_place}) +} + +pub fn unpack_mut_ref(tokens: TokenStream) -> TokenStream { + // // generate_place_function(tokens, quote!{prusti_unpack_mut_ref_place}) + // let (lifetime_name, reference) = + // handle_result!(parse_two_expressions::(tokens)); + // let lifetime_name_str = handle_result!(expression_to_string(&lifetime_name)); + // unsafe_spec_function_call(quote! {` + // prusti_unpack_mut_ref_place(#lifetime_name_str, std::ptr::addr_of!(#reference)) + // }) + pack_unpack_ref(tokens, quote! {prusti_unpack_mut_ref_place}) +} + +pub fn pack_mut_ref_obligation(tokens: TokenStream) -> TokenStream { + // generate_place_function(tokens, quote! {prusti_pack_mut_ref_place}) + pack_unpack_ref(tokens, quote! {prusti_pack_mut_ref_place_obligation}) +} + +pub fn unpack_mut_ref_obligation(tokens: TokenStream) -> TokenStream { + pack_unpack_ref(tokens, quote! {prusti_unpack_mut_ref_place_obligation}) +} + +fn pack_unpack_ref(tokens: TokenStream, function: TokenStream) -> TokenStream { + // let (lifetime_name, reference) = + // handle_result!(parse_two_expressions::(tokens)); + parse_expressions!(tokens, syn::Token![,] => lifetime_name, reference); + let lifetime_name_str = handle_result!(expression_to_string(&lifetime_name)); + unsafe_spec_function_call(quote! { + #function(#lifetime_name_str, std::ptr::addr_of!(#reference)) + }) +} + +// fn parse_two_expressions( +// tokens: TokenStream, +// ) -> syn::Result<(syn::Expr, syn::Expr)> { +// // let parser = syn::punctuated::Punctuated::::parse_terminated; +// // let mut expressions = syn::parse::Parser::parse2(parser, tokens)?; +// // let second = expressions +// // .pop() +// // .ok_or_else(|| syn::Error::new(Span::call_site(), "Expected two expressions"))?; +// // let first = expressions +// // .pop() +// // .ok_or_else(|| syn::Error::new(Span::call_site(), "Expected two expressions"))?; +// // Ok((first.into_value(), second.into_value())) +// parse_expressions!(tokens, Separator => first, second); +// Ok((first, second)) +// } + +// fn parse_three_expressions( +// tokens: TokenStream, +// ) -> syn::Result<(syn::Expr, syn::Expr, syn::Expr)> { +// // let parser = syn::punctuated::Punctuated::::parse_terminated; +// // let mut expressions = syn::parse::Parser::parse2(parser, tokens)?; +// // let third = expressions +// // .pop() +// // .ok_or_else(|| syn::Error::new(Span::call_site(), "Expected three expressions"))?; +// // let second = expressions +// // .pop() +// // .ok_or_else(|| syn::Error::new(Span::call_site(), "Expected three expressions"))?; +// // let first = expressions +// // .pop() +// // .ok_or_else(|| syn::Error::new(Span::call_site(), "Expected three expressions"))?; +// // Ok((first.into_value(), second.into_value(), third.into_value())) +// parse_expressions!(tokens, Separator => first, second, third); +// Ok((first, second, third)) +// } + +// fn parse_four_expressions( +// tokens: TokenStream, +// ) -> syn::Result<(syn::Expr, syn::Expr, syn::Expr, syn::Expr)> { +// // let parser = syn::punctuated::Punctuated::::parse_terminated; +// // let mut expressions = syn::parse::Parser::parse2(parser, tokens)?; +// // let fourth = expressions +// // .pop() +// // .ok_or_else(|| syn::Error::new(Span::call_site(), "Expected four expressions"))?; +// // let third = expressions +// // .pop() +// // .ok_or_else(|| syn::Error::new(Span::call_site(), "Expected four expressions"))?; +// // let second = expressions +// // .pop() +// // .ok_or_else(|| syn::Error::new(Span::call_site(), "Expected four expressions"))?; +// // let first = expressions +// // .pop() +// // .ok_or_else(|| syn::Error::new(Span::call_site(), "Expected four expressions"))?; +// // Ok(( +// // first.into_value(), +// // second.into_value(), +// // third.into_value(), +// // fourth.into_value(), +// // )) +// parse_expressions!(tokens, Separator => first, second, third, fourth); +// Ok((first, second, third, fourth)) +// } + +fn expression_to_string(expr: &syn::Expr) -> syn::Result { + if let syn::Expr::Path(syn::ExprPath { + qself: None, path, .. + }) = expr + { + if let Some(ident) = path.get_ident() { + return Ok(ident.to_string()); + } + } + Err(syn::Error::new(expr.span(), "needs to be an identifier")) +} + +pub fn unsafe_spec_function_call(call: TokenStream) -> TokenStream { + let callsite_span = Span::call_site(); + quote_spanned! { callsite_span => + #[allow(unused_must_use, unused_variables)] + #[prusti::specs_version = #SPECS_VERSION] + if false { + #[prusti::spec_only] + || -> bool { true }; + unsafe { #call }; + } + } +} + +pub fn take_lifetime(tokens: TokenStream) -> TokenStream { + parse_expressions!(tokens, syn::Token![,] => reference, lifetime_name); + // let (reference, lifetime_name) = + // handle_result!(parse_two_expressions::(tokens)); + let lifetime_name_str = handle_result!(expression_to_string(&lifetime_name)); + unsafe_spec_function_call(quote! { + prusti_take_lifetime(std::ptr::addr_of!(#reference), #lifetime_name_str) + }) + // let parser = syn::punctuated::Punctuated::]>::parse_terminated; + // let mut args = handle_result!(syn::parse::Parser::parse2(parser, tokens)); + // let lifetime = if let Some(lifetime) = args.pop() { + // lifetime.into_value() + // } else { + // return syn::Error::new( + // args.span(), + // "`take_lifetime!` needs to contain two arguments `` and ``" + // ).to_compile_error(); + // }; + // let lifetime_str = if let syn::Expr::Path(syn::ExprPath { qself: None, path, ..}) = lifetime { + // if let Some(ident) = path.get_ident() { + // ident.to_string() + // } else { + // return syn::Error::new( + // path.span(), + // "lifetime name needs to be an identifier" + // ).to_compile_error(); + // } + // } else { + // return syn::Error::new( + // lifetime.span(), + // "lifetime name needs to be an identifier" + // ).to_compile_error(); + // }; + // let reference = if let Some(reference) = args.pop() { + // reference.into_value() + // } else { + // return syn::Error::new( + // args.span(), + // "`take_lifetime!` needs to contain two arguments `` and ``" + // ).to_compile_error(); + // }; + // let callsite_span = Span::call_site(); + // quote_spanned! { callsite_span => + // #[allow(unused_must_use, unused_variables)] + // #[prusti::specs_version = #SPECS_VERSION] + // if false { + // #[prusti::spec_only] + // || -> bool { true }; + // unsafe { prusti_take_lifetime(std::ptr::addr_of!(#reference), #lifetime_str) }; + // } + // } +} + +pub fn end_loan(tokens: TokenStream) -> TokenStream { + parse_expressions!(tokens, syn::Token![,] => lifetime_name); + let lifetime_name_str = handle_result!(expression_to_string(&lifetime_name)); + unsafe_spec_function_call(quote! { + prusti_end_loan(#lifetime_name_str) + }) +} + +pub fn set_lifetime_for_raw_pointer_reference_casts(tokens: TokenStream) -> TokenStream { + unsafe_spec_function_call(quote! { + prusti_set_lifetime_for_raw_pointer_reference_casts(std::ptr::addr_of!(#tokens)) + }) +} + +pub fn attach_drop_lifetime(tokens: TokenStream) -> TokenStream { + parse_expressions!(tokens, syn::Token![,] => drop, reference); + unsafe_spec_function_call(quote! { + prusti_attach_drop_lifetime(std::ptr::addr_of!(#drop), std::ptr::addr_of!(#reference)) + }) +} + +pub fn join(tokens: TokenStream) -> TokenStream { + generate_place_function(tokens, quote! {prusti_join_place}) +} + +pub fn join_range(tokens: TokenStream) -> TokenStream { + parse_expressions!(tokens, syn::Token![,] => pointer, start_index, end_index); + // let (pointer, start_index, end_index) = + // handle_result!(parse_three_expressions::(tokens)); + unsafe_spec_function_call(quote! { + prusti_join_range(std::ptr::addr_of!(#pointer), {#start_index}, #end_index) + }) +} + +pub fn split(tokens: TokenStream) -> TokenStream { + generate_place_function(tokens, quote! {prusti_split_place}) +} + +pub fn split_range(tokens: TokenStream) -> TokenStream { + parse_expressions!(tokens, syn::Token![,] => pointer, start_index, end_index); + // let (pointer, start_index, end_index) = + // handle_result!(parse_three_expressions::(tokens)); + unsafe_spec_function_call(quote! { + prusti_split_range(std::ptr::addr_of!(#pointer), {#start_index}, #end_index) + }) +} + +/// FIXME: For `start_index` and `end_index`, we should do the same as for +/// `body_invariant!`. +pub fn stash_range(tokens: TokenStream) -> TokenStream { + parse_expressions!(tokens, syn::Token![,] => pointer, start_index, end_index, witness); + // let (pointer, start_index, end_index, witness) = + // handle_result!(parse_four_expressions::(tokens)); + let witness_str = handle_result!(expression_to_string(&witness)); + unsafe_spec_function_call(quote! { + prusti_stash_range( + std::ptr::addr_of!(#pointer), + {#start_index}, + {#end_index}, + #witness_str + ) + }) +} + +/// FIXME: For `new_start_index`, we should do the same as for +/// `body_invariant!`. +pub fn restore_stash_range(tokens: TokenStream) -> TokenStream { + parse_expressions!(tokens, syn::Token![,] => pointer, new_start_index, witness); + // let (pointer, new_start_index, witness) = + // handle_result!(parse_three_expressions::(tokens)); + let witness_str = handle_result!(expression_to_string(&witness)); + unsafe_spec_function_call(quote! { + prusti_restore_stash_range(std::ptr::addr_of!(#pointer), {#new_start_index}, #witness_str) + }) +} + +pub fn materialize_predicate(tokens: TokenStream) -> TokenStream { + let (spec_id, predicate_closure) = handle_result!(prusti_specification_expression(tokens)); + let spec_id_str = spec_id.to_string(); + let call = unsafe_spec_function_call(quote! { prusti_materialize_predicate(#spec_id_str) }); + quote! { + #call; + #predicate_closure + } +} + +pub fn quantified_predicate(tokens: TokenStream) -> TokenStream { + let (spec_id, predicate_closure) = handle_result!(prusti_specification_expression(tokens)); + let spec_id_str = spec_id.to_string(); + let call = unsafe_spec_function_call(quote! { prusti_quantified_predicate(#spec_id_str) }); + quote! { + #call; + #predicate_closure + } +} + +pub fn assume_allocation_never_fails(tokens: TokenStream) -> TokenStream { + if !tokens.is_empty() { + return syn::Error::new( + tokens.span(), + "`assume_allocation_never_fails` does not take any arguments", + ) + .to_compile_error(); + } + unsafe_spec_function_call(quote! { + prusti_assume_allocation_never_fails() + }) +} + +fn close_any_ref(tokens: TokenStream, function: TokenStream) -> TokenStream { + parse_expressions!(tokens, syn::Token![,] => reference, witness); + // let (reference, witness) = handle_result!(parse_two_expressions::(tokens)); + let witness_str = handle_result!(expression_to_string(&witness)); + let (spec_id, reference_closure) = handle_result!(prusti_specification_expression( + quote! { unsafe { &#reference } } + )); + let spec_id_str = spec_id.to_string(); + let call = unsafe_spec_function_call(quote! { #function(#spec_id_str, #witness_str) }); + quote! { + #call; + #reference_closure + } +} + +pub fn close_ref(tokens: TokenStream) -> TokenStream { + close_any_ref(tokens, quote! {prusti_close_ref_place}) +} + +pub fn close_mut_ref(tokens: TokenStream) -> TokenStream { + close_any_ref(tokens, quote! {prusti_close_mut_ref_place}) +} + +fn open_any_ref(tokens: TokenStream, function: TokenStream) -> TokenStream { + parse_expressions!(tokens, syn::Token![,] => lifetime_name, reference, witness); + // let (lifetime_name, reference, witness) = + // handle_result!(parse_three_expressions::(tokens)); + let lifetime_name_str = handle_result!(expression_to_string(&lifetime_name)); + let witness_str = handle_result!(expression_to_string(&witness)); + let (spec_id, reference_closure) = handle_result!(prusti_specification_expression( + quote! { unsafe { &#reference } } + )); + let spec_id_str = spec_id.to_string(); + let call = unsafe_spec_function_call(quote! { + #function(#lifetime_name_str, #spec_id_str, #witness_str) + }); + quote! { + #reference_closure; + #call + } +} + +pub fn open_ref(tokens: TokenStream) -> TokenStream { + open_any_ref(tokens, quote! {prusti_open_ref_place}) +} + +pub fn open_mut_ref(tokens: TokenStream) -> TokenStream { + open_any_ref(tokens, quote! {prusti_open_mut_ref_place}) +} + +pub fn restore_mut_borrowed(tokens: TokenStream) -> TokenStream { + parse_expressions!(tokens, syn::Token![,] => referencing_place, referenced_place); + let (referencing_place_spec_id, referencing_place_closure) = handle_result!( + prusti_specification_expression(quote! { unsafe { &#referencing_place } }) + ); + let (referenced_place_spec_id, referenced_place_closure) = handle_result!( + prusti_specification_expression(quote! { unsafe { &#referenced_place } }) + ); + let referencing_place_spec_id_str = referencing_place_spec_id.to_string(); + let referenced_place_spec_id_str = referenced_place_spec_id.to_string(); + let call = unsafe_spec_function_call( + quote! { prusti_restore_mut_borrowed(#referencing_place_spec_id_str, #referenced_place_spec_id_str) }, + ); + quote! { + #referencing_place_closure; + #call; + #referenced_place_closure + } +} + +pub fn resolve(tokens: TokenStream) -> TokenStream { + generate_place_function(tokens, quote! {prusti_resolve}) +} + +pub fn resolve_range(tokens: TokenStream) -> TokenStream { + parse_expressions!(tokens, syn::Token![,] => + lifetime_name, + pointer, + predicate_range_start_index, + predicate_range_end_index, + start_index, + end_index + ); + // let (lifetime_name, pointer, base_index, start_index, end_index) = + // handle_result!(parse_five_expressions::(tokens)); + let lifetime_name_str = handle_result!(expression_to_string(&lifetime_name)); + unsafe_spec_function_call(quote! { + prusti_resolve_range( + #lifetime_name_str, + std::ptr::addr_of!(#pointer), + {#predicate_range_start_index}, + {#predicate_range_end_index}, + {#start_index}, + {#end_index}, + ) + }) +} + +pub fn set_union_active_field(tokens: TokenStream) -> TokenStream { + generate_place_function(tokens, quote! {prusti_set_union_active_field}) +} + +pub fn forget_initialization(tokens: TokenStream) -> TokenStream { + generate_place_function(tokens, quote! {prusti_forget_initialization}) +} + +pub fn forget_initialization_range(tokens: TokenStream) -> TokenStream { + parse_expressions!(tokens, syn::Token![,] => pointer, start_index, end_index); + unsafe_spec_function_call(quote! { + prusti_forget_initialization_range( + std::ptr::addr_of!(#pointer), + {#start_index}, + {#end_index}, + ) + }) +} + +fn generate_place_function(tokens: TokenStream, function: TokenStream) -> TokenStream { + let callsite_span = Span::call_site(); + quote_spanned! { callsite_span => + #[allow(unused_must_use, unused_variables)] + #[prusti::specs_version = #SPECS_VERSION] + if false { + #[prusti::spec_only] + || -> bool { true }; + unsafe { #function(std::ptr::addr_of!(#tokens)) }; + } + } +} + +pub fn restore(tokens: TokenStream) -> TokenStream { + let parser = syn::punctuated::Punctuated::::parse_terminated; + let mut args = handle_result!(syn::parse::Parser::parse2(parser, tokens)); + let restored_place = if let Some(restored_place) = args.pop() { + restored_place.into_value() + } else { + return syn::Error::new( + args.span(), + "`restore!` needs to contain two arguments `` and ``" + ).to_compile_error(); + }; + let borrowing_place = if let Some(borrowing_place) = args.pop() { + borrowing_place.into_value() + } else { + return syn::Error::new( + args.span(), + "`restore!` needs to contain two arguments `` and ``" + ).to_compile_error(); + }; + let callsite_span = Span::call_site(); + quote_spanned! { callsite_span => + #[allow(unused_must_use, unused_variables)] + #[prusti::specs_version = #SPECS_VERSION] + if false { + #[prusti::spec_only] + || -> bool { true }; + unsafe { prusti_restore_place(std::ptr::addr_of!(#borrowing_place), std::ptr::addr_of!(#restored_place)) }; + } + } +} diff --git a/prusti-contracts/prusti-specs/src/parse_ghost_macros.rs b/prusti-contracts/prusti-specs/src/parse_ghost_macros.rs new file mode 100644 index 00000000000..835902e8cbd --- /dev/null +++ b/prusti-contracts/prusti-specs/src/parse_ghost_macros.rs @@ -0,0 +1,46 @@ +use syn::parse::{Parse, ParseStream}; + +pub(crate) struct OnDropUnwind { + pub dropped_place: syn::Expr, + pub block: syn::Block, +} + +impl Parse for OnDropUnwind { + fn parse(input: ParseStream) -> syn::Result { + let dropped_place = input.parse()?; + let token = input.parse::]>()?; + let statements = syn::Block::parse_within(input)?; + let block = syn::Block { + brace_token: syn::token::Brace { + span: token.spans[0], + }, + stmts: statements, + }; + Ok(Self { + dropped_place, + block, + }) + } +} + +pub(crate) struct WithFinally { + pub executed_block: Vec, + pub on_panic_block: syn::Block, + pub finally_block_at_panic_start: syn::Block, + pub finally_block_at_resume: syn::Block, +} + +impl Parse for WithFinally { + fn parse(input: ParseStream) -> syn::Result { + let executed_block: syn::Block = input.parse()?; + let on_panic_block = input.parse()?; + let finally_block_at_panic_start = input.parse()?; + let finally_block_at_resume = input.parse()?; + Ok(Self { + executed_block: executed_block.stmts, + on_panic_block, + finally_block_at_panic_start, + finally_block_at_resume, + }) + } +} diff --git a/prusti-contracts/prusti-specs/src/rewriter.rs b/prusti-contracts/prusti-specs/src/rewriter.rs index 16128256439..c947fc28136 100644 --- a/prusti-contracts/prusti-specs/src/rewriter.rs +++ b/prusti-contracts/prusti-specs/src/rewriter.rs @@ -18,6 +18,8 @@ pub(crate) struct AstRewriter { pub enum SpecItemType { Precondition, Postcondition, + BrokenPrecondition, + BrokenPostcondition, Pledge, Predicate(TokenStream), Termination, @@ -28,6 +30,8 @@ impl std::fmt::Display for SpecItemType { match self { SpecItemType::Precondition => write!(f, "pre"), SpecItemType::Postcondition => write!(f, "post"), + SpecItemType::BrokenPrecondition => write!(f, "broken_pre"), + SpecItemType::BrokenPostcondition => write!(f, "broken_post"), SpecItemType::Pledge => write!(f, "pledge"), SpecItemType::Predicate(_) => write!(f, "pred"), SpecItemType::Termination => write!(f, "term"), @@ -135,7 +139,9 @@ impl AstRewriter { spec_item.sig.generics = item.sig().generics.clone(); spec_item.sig.inputs = item.sig().inputs.clone(); match spec_type { - SpecItemType::Postcondition | SpecItemType::Pledge => { + SpecItemType::Postcondition + | SpecItemType::BrokenPostcondition + | SpecItemType::Pledge => { let fn_arg = self.generate_result_arg(item); spec_item.sig.inputs.push(fn_arg); } @@ -237,6 +243,19 @@ impl AstRewriter { self.process_prusti_expression(quote! {loop_body_invariant_spec}, spec_id, tokens) } + /// Parse a loop invariant into a Rust expression + pub fn process_structural_loop_invariant( + &mut self, + spec_id: SpecificationId, + tokens: TokenStream, + ) -> syn::Result { + self.process_prusti_expression( + quote! {loop_structural_body_invariant_spec}, + spec_id, + tokens, + ) + } + /// Parse a prusti assertion into a Rust expression pub fn process_prusti_assertion( &mut self, @@ -246,6 +265,15 @@ impl AstRewriter { self.process_prusti_expression(quote! {prusti_assertion}, spec_id, tokens) } + /// Parse a prusti structural assertion into a Rust expression + pub fn process_prusti_structural_assertion( + &mut self, + spec_id: SpecificationId, + tokens: TokenStream, + ) -> syn::Result { + self.process_prusti_expression(quote! {prusti_structural_assertion}, spec_id, tokens) + } + /// Parse a prusti assumption into a Rust expression pub fn process_prusti_assumption( &mut self, @@ -264,6 +292,44 @@ impl AstRewriter { self.process_prusti_expression(quote! {prusti_refutation}, spec_id, tokens) } + /// Parse a prusti structural assumption into a Rust expression + pub fn process_prusti_structural_assumption( + &mut self, + spec_id: SpecificationId, + tokens: TokenStream, + ) -> syn::Result { + self.process_prusti_expression(quote! {prusti_structural_assumption}, spec_id, tokens) + } + + /// Parse a prusti structural assumption into a Rust expression + pub fn process_prusti_split( + &mut self, + spec_id: SpecificationId, + tokens: TokenStream, + ) -> syn::Result { + self.process_prusti_expression(quote! {prusti_case_split}, spec_id, tokens) + } + + /// Parse a prusti expression used as an argument to some ghost operation + pub fn process_prusti_specification_expression( + &mut self, + spec_id: SpecificationId, + tokens: TokenStream, + ) -> syn::Result { + let expr = parse_prusti(tokens)?; + let spec_id_str = spec_id.to_string(); + Ok(quote_spanned! {expr.span()=> + { + #[prusti::spec_only] + #[prusti::prusti_specification_expression] + #[prusti::spec_id = #spec_id_str] + || { + #expr + }; + } + }) + } + fn process_prusti_expression( &mut self, kind: TokenStream, diff --git a/prusti-contracts/prusti-specs/src/spec_attribute_kind.rs b/prusti-contracts/prusti-specs/src/spec_attribute_kind.rs index f286cbd317b..483488c1c88 100644 --- a/prusti-contracts/prusti-specs/src/spec_attribute_kind.rs +++ b/prusti-contracts/prusti-specs/src/spec_attribute_kind.rs @@ -18,6 +18,15 @@ pub enum SpecAttributeKind { Terminates = 10, PrintCounterexample = 11, Verified = 12, + NoPanic = 13, + NoPanicEnsuresPostcondition = 14, + NotRequire = 15, + NotEnsure = 16, + NonVerifiedPure = 17, + StructuralRequires = 18, + StructuralEnsures = 19, + PanicEnsures = 20, + StructuralPanicEnsures = 21, } impl TryFrom for SpecAttributeKind { @@ -26,7 +35,11 @@ impl TryFrom for SpecAttributeKind { fn try_from(name: String) -> Result { match name.as_str() { "requires" => Ok(SpecAttributeKind::Requires), + "structural_requires" => Ok(SpecAttributeKind::StructuralRequires), "ensures" => Ok(SpecAttributeKind::Ensures), + "panic_ensures" => Ok(SpecAttributeKind::PanicEnsures), + "structural_ensures" => Ok(SpecAttributeKind::StructuralEnsures), + "structural_panic_ensures" => Ok(SpecAttributeKind::StructuralPanicEnsures), "after_expiry" => Ok(SpecAttributeKind::AfterExpiry), "assert_on_expiry" => Ok(SpecAttributeKind::AssertOnExpiry), "pure" => Ok(SpecAttributeKind::Pure), @@ -37,6 +50,11 @@ impl TryFrom for SpecAttributeKind { "model" => Ok(SpecAttributeKind::Model), "print_counterexample" => Ok(SpecAttributeKind::PrintCounterexample), "verified" => Ok(SpecAttributeKind::Verified), + "non_verified_pure" => Ok(SpecAttributeKind::NonVerifiedPure), + "no_panic" => Ok(SpecAttributeKind::NoPanic), + "no_panic_ensures_postcondition" => Ok(SpecAttributeKind::NoPanicEnsuresPostcondition), + "not_require" => Ok(SpecAttributeKind::NotRequire), + "not_ensure" => Ok(SpecAttributeKind::NotEnsure), _ => Err(name), } } diff --git a/prusti-contracts/prusti-specs/src/specifications/common.rs b/prusti-contracts/prusti-specs/src/specifications/common.rs index dca66437e79..ab6c72fc10d 100644 --- a/prusti-contracts/prusti-specs/src/specifications/common.rs +++ b/prusti-contracts/prusti-specs/src/specifications/common.rs @@ -55,7 +55,13 @@ pub struct SpecificationId(Uuid); #[derive(Debug, Clone, Copy)] pub enum SpecIdRef { Precondition(SpecificationId), + StructuralPrecondition(SpecificationId), Postcondition(SpecificationId), + PanicPostcondition(SpecificationId), + StructuralPostcondition(SpecificationId), + StructuralPanicPostcondition(SpecificationId), + BrokenPrecondition(SpecificationId), + BrokenPostcondition(SpecificationId), Purity(SpecificationId), Pledge { lhs: Option, diff --git a/prusti-contracts/prusti-specs/src/specifications/preparser.rs b/prusti-contracts/prusti-specs/src/specifications/preparser.rs index 3bfebc60992..5fdfa572496 100644 --- a/prusti-contracts/prusti-specs/src/specifications/preparser.rs +++ b/prusti-contracts/prusti-specs/src/specifications/preparser.rs @@ -149,6 +149,10 @@ impl PrustiTokenStream { PrustiToken::Quantifier(ident.span(), Quantifier::Forall), (TokenTree::Ident(ident), _, _, _) if ident == "exists" => PrustiToken::Quantifier(ident.span(), Quantifier::Exists), + (TokenTree::Ident(ident), _, _, _) if ident == "raw_range" => + PrustiToken::QuantifiedPermission(ident.span(), QuantifiedPermission::Raw), + (TokenTree::Ident(ident), _, _, _) if ident == "unq_range" => + PrustiToken::QuantifiedPermission(ident.span(), QuantifiedPermission::UniqueRef), (TokenTree::Punct(punct), _, _, _) if punct.as_char() == ',' && punct.spacing() == Alone => PrustiToken::BinOp(punct.span(), PrustiBinaryOp::Rust(RustOp::Comma)), @@ -314,6 +318,36 @@ impl PrustiTokenStream { kind.translate(span, triggers, args, body) } + Some(PrustiToken::QuantifiedPermission(span, kind)) => { + let mut stream = self.pop_group(Delimiter::Parenthesis).ok_or_else(|| { + error( + span, + "expected parenthesized expression after quantified permission", + ) + })?; + let mut tokens = VecDeque::new(); + let mut closure_args = None; + while let Some(token) = stream.tokens.front() { + if token.is_closure_brace() { + closure_args = stream.pop_closure_args(); + break; + } + tokens.push_back(stream.tokens.pop_front().unwrap()); + } + let args = Self { + tokens, + source_span: self.source_span, + }; + if let Some(closure_args) = closure_args { + let triggers = stream.extract_triggers()?; + let index = closure_args.parse()?; + let body = stream.parse()?; + kind.translate_guarded_range(span, args, triggers, index, body)? + } else { + kind.translate_usize_range(span, args)? + } + } + Some(PrustiToken::SpecEnt(span, _)) | Some(PrustiToken::CallDesc(span, _)) => { return err(span, "unexpected operator") } @@ -372,6 +406,9 @@ impl PrustiTokenStream { Some(PrustiToken::Quantifier(span, _)) => { return err(*span, "unexpected quantifier") } + Some(PrustiToken::QuantifiedPermission(span, _)) => { + return err(*span, "unexpected quantified permission") + } None => break, }; @@ -499,6 +536,23 @@ impl PrustiTokenStream { res } + fn split_head(mut self, split_on: PrustiBinaryOp) -> syn::Result<(Self, Self)> { + if self.tokens.is_empty() { + return err(self.source_span, "no tokens"); + } + let mut head = Self { + tokens: VecDeque::new(), + source_span: self.source_span, + }; + while let Some(token) = self.tokens.pop_front() { + if matches!(token, PrustiToken::BinOp(_, t) if t == split_on) { + return Ok((head, self)); + } + head.tokens.push_back(token); + } + err(self.source_span, "not enough tokens") + } + fn extract_triggers(&mut self) -> syn::Result>> { let len = self.tokens.len(); if len < 4 { @@ -649,6 +703,7 @@ enum PrustiToken { // TODO: add note about unops not sharing a variant, descriptions ... Outer(Span), Quantifier(Span, Quantifier), + QuantifiedPermission(Span, QuantifiedPermission), SpecEnt(Span, bool), CallDesc(Span, bool), } @@ -767,6 +822,83 @@ impl Quantifier { } } +#[derive(Debug, Clone)] +enum QuantifiedPermission { + Raw, + UniqueRef, +} + +impl QuantifiedPermission { + fn translate_guarded_range( + &self, + span: Span, + arguments: PrustiTokenStream, + triggers: Vec>, + index: TokenStream, + body: TokenStream, + ) -> syn::Result { + let full_span = join_spans(span, body.span()); + let trigger_sets = triggers + .into_iter() + .map(|set| { + let triggers = TokenStream::from_iter(set.into_iter().map(|trigger| { + quote_spanned! { trigger.span() => + #[prusti::spec_only] | #index: usize | ( #trigger ), } + })); + quote_spanned! { full_span => ( #triggers ) } + }) + .collect::>(); + let body = quote_spanned! { body.span() => ((#body): bool) }; + let tokens = match self { + Self::Raw => { + let arguments = arguments.parse()?; + quote_spanned! { full_span => ::prusti_contracts::prusti_raw_range_guarded( + #arguments + ( #( #trigger_sets, )* ), + #[prusti::spec_only] | #index: usize | -> bool { #body } + ) } + } + Self::UniqueRef => { + let (lifetime, tail) = arguments.split_head(PrustiBinaryOp::Rust(RustOp::Comma))?; + let lifetime = lifetime.parse()?; + let arguments = tail.parse()?; + // FIXME: This code is untested and missing the implementation. + quote_spanned! { full_span => ::prusti_contracts::prusti_unq_real_lifetime_range_guarded( + stringify!(#lifetime), + #arguments + ( #( #trigger_sets, )* ), + #[prusti::spec_only] | #index: usize | -> bool { #body } + ) } + } + }; + Ok(tokens) + } + + fn translate_usize_range( + &self, + span: Span, + arguments: PrustiTokenStream, + ) -> syn::Result { + let full_span = join_spans(span, arguments.source_span); + let tokens = match self { + Self::Raw => { + let arguments = arguments.parse()?; + quote_spanned! { full_span => ::prusti_contracts::prusti_raw_range(#arguments) } + } + Self::UniqueRef => { + let (lifetime, tail) = arguments.split_head(PrustiBinaryOp::Rust(RustOp::Comma))?; + let lifetime = lifetime.parse()?; + let arguments = tail.parse()?; + quote_spanned! { full_span => ::prusti_contracts::prusti_unq_real_lifetime_range( + stringify!(#lifetime), + #arguments + ) } + } + }; + Ok(tokens) + } +} + // For Prusti-specific operators, in [operator2], [operator3], and [operator4] // we mainly care about the spacing of the last [Punct], as this lets us // know that the last character is not itself part of an actual Rust @@ -803,6 +935,7 @@ impl PrustiToken { | Self::BinOp(span, _) | Self::Outer(span) | Self::Quantifier(span, _) + | Self::QuantifiedPermission(span, _) | Self::SpecEnt(span, _) | Self::CallDesc(span, _) => *span, Self::Token(tree) => tree.span(), diff --git a/prusti-interface/src/environment/body.rs b/prusti-interface/src/environment/body.rs index 3327ca82e6c..35f44ebd079 100644 --- a/prusti-interface/src/environment/body.rs +++ b/prusti-interface/src/environment/body.rs @@ -1,3 +1,4 @@ +use crate::environment::{borrowck::facts::BorrowckFacts, mir_storage}; use prusti_common::config; use prusti_rustc_interface::{ macros::{TyDecodable, TyEncodable}, @@ -10,8 +11,6 @@ use prusti_rustc_interface::{ use rustc_hash::FxHashMap; use std::{cell::RefCell, collections::hash_map::Entry, rc::Rc}; -use crate::environment::{borrowck::facts::BorrowckFacts, mir_storage}; - /// Stores any possible MIR body (from the compiler) that /// Prusti might want to work with. Cheap to clone #[derive(Clone, TyEncodable, TyDecodable)] @@ -139,11 +138,15 @@ impl<'tcx> EnvBody<'tcx> { /// Get local MIR body of spec or pure functions. Retrieves the body from /// the compiler (relatively cheap). fn load_local_mir(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> MirBody<'tcx> { - let body = tcx - .mir_promoted(ty::WithOptConstParam::unknown(def_id)) - .0 - .borrow(); - MirBody(Rc::new(body.clone())) + if config::unsafe_core_proof() { + Self::load_local_mir_with_facts(tcx, def_id).body + } else { + let body = tcx + .mir_promoted(ty::WithOptConstParam::unknown(def_id)) + .0 + .borrow(); + MirBody(Rc::new(body.clone())) + } } fn get_monomorphised( @@ -163,6 +166,7 @@ impl<'tcx> EnvBody<'tcx> { substs: SubstsRef<'tcx>, caller_def_id: Option, body: MirBody<'tcx>, + keep_lifetimes: bool, ) -> MirBody<'tcx> { if let Entry::Vacant(v) = self.monomorphised_bodies @@ -171,8 +175,14 @@ impl<'tcx> EnvBody<'tcx> { { let monomorphised = if let Some(caller_def_id) = caller_def_id { let param_env = self.tcx.param_env(caller_def_id); - self.tcx - .subst_and_normalize_erasing_regions(substs, param_env, body.0) + if keep_lifetimes { + use prusti_rustc_interface::middle::ty::TypeVisitableExt; + assert!(!body.0.has_projections(), "unimplemented: projections are not supported because normalizing them erases lifetimes"); + ty::EarlyBinder(body.0).subst(self.tcx, substs) + } else { + self.tcx + .subst_and_normalize_erasing_regions(substs, param_env, body.0) + } } else { ty::EarlyBinder(body.0).subst(self.tcx, substs) }; @@ -184,12 +194,27 @@ impl<'tcx> EnvBody<'tcx> { /// Get the MIR body of a local impure function, without any substitutions. pub fn get_impure_fn_body_identity(&self, def_id: LocalDefId) -> MirBody<'tcx> { - let mut impure = self.local_impure_fns.borrow_mut(); - impure - .entry(def_id) - .or_insert_with(|| Self::load_local_mir_with_facts(self.tcx, def_id)) - .body - .clone() + // let mut impure = self.local_impure_fns.borrow_mut(); + // impure + // .entry(def_id) + // .or_insert_with(|| Self::load_local_mir_with_facts(self.tcx, def_id)) + // .body + // .clone() + self.borrow_impure_fn_body_identity(def_id).clone() + } + + /// Borrow the MIR body of a local impure function, without any substitutions. + pub fn borrow_impure_fn_body_identity( + &self, + def_id: LocalDefId, + ) -> std::cell::RefMut> { + let impure = self.local_impure_fns.borrow_mut(); + std::cell::RefMut::map(impure, |impure| { + &mut impure + .entry(def_id) + .or_insert_with(|| Self::load_local_mir_with_facts(self.tcx, def_id)) + .body + }) } /// Get the MIR body of a local impure function, monomorphised @@ -199,7 +224,13 @@ impl<'tcx> EnvBody<'tcx> { return body; } let body = self.get_impure_fn_body_identity(def_id); - self.set_monomorphised(def_id.to_def_id(), substs, None, body) + self.set_monomorphised( + def_id.to_def_id(), + substs, + None, + body, + config::unsafe_core_proof(), + ) } fn get_closure_body_identity(&self, def_id: DefId) -> MirBody<'tcx> { @@ -210,23 +241,41 @@ impl<'tcx> EnvBody<'tcx> { let mut closures = self.local_closures.borrow_mut(); closures .entry(local_def_id) - .or_insert_with(|| Self::load_local_mir(self.tcx, local_def_id)) + .or_insert_with(|| { + if config::unsafe_core_proof() { + Self::load_local_mir_with_facts(self.tcx, local_def_id).body + } else { + Self::load_local_mir(self.tcx, local_def_id) + } + }) .clone() } /// Get the MIR body of a local closure (e.g. loop invariant or trigger), /// monomorphised with the given type substitutions. - pub fn get_closure_body( + pub fn get_closure_body_lifetimes_opt( &self, def_id: DefId, substs: SubstsRef<'tcx>, caller_def_id: DefId, + keep_lifetimes: bool, ) -> MirBody<'tcx> { if let Some(body) = self.get_monomorphised(def_id, substs, Some(caller_def_id)) { return body; } let body = self.get_closure_body_identity(def_id); - self.set_monomorphised(def_id, substs, Some(caller_def_id), body) + self.set_monomorphised(def_id, substs, Some(caller_def_id), body, keep_lifetimes) + } + + /// Get the MIR body of a local closure (e.g. loop invariant or trigger), + /// monomorphised with the given type substitutions. + pub fn get_closure_body( + &self, + def_id: DefId, + substs: SubstsRef<'tcx>, + caller_def_id: DefId, + ) -> MirBody<'tcx> { + self.get_closure_body_lifetimes_opt(def_id, substs, caller_def_id, false) } /// Get the MIR body of a local or external pure function, @@ -241,7 +290,13 @@ impl<'tcx> EnvBody<'tcx> { return body; } let body = self.pure_fns.expect(def_id); - self.set_monomorphised(def_id, substs, Some(caller_def_id), body) + self.set_monomorphised( + def_id, + substs, + Some(caller_def_id), + body, + config::unsafe_core_proof(), + ) } /// Get the MIR body of a local or external expression (e.g. any spec or predicate), @@ -259,7 +314,13 @@ impl<'tcx> EnvBody<'tcx> { .specs .get(def_id) .unwrap_or_else(|| self.predicates.expect(def_id)); - self.set_monomorphised(def_id, substs, Some(caller_def_id), body) + self.set_monomorphised( + def_id, + substs, + Some(caller_def_id), + body, + config::unsafe_core_proof(), + ) } /// Get the MIR body of a local or external spec (pres/posts/pledges/type-specs), @@ -274,7 +335,13 @@ impl<'tcx> EnvBody<'tcx> { return body; } let body = self.specs.expect(def_id); - self.set_monomorphised(def_id, substs, Some(caller_def_id), body) + self.set_monomorphised( + def_id, + substs, + Some(caller_def_id), + body, + config::unsafe_core_proof(), + ) } /// Get Polonius facts of a local procedure. @@ -301,16 +368,24 @@ impl<'tcx> EnvBody<'tcx> { if self.specs.local.contains_key(&def_id) { return; } - self.specs - .local - .insert(def_id, Self::load_local_mir(self.tcx, def_id)); + let body = if config::unsafe_core_proof() { + Self::load_local_mir_with_facts(self.tcx, def_id).body + } else { + Self::load_local_mir(self.tcx, def_id) + }; + self.specs.local.insert(def_id, body); } pub(crate) fn load_predicate_body(&mut self, def_id: LocalDefId) { assert!(!self.predicates.local.contains_key(&def_id)); - self.predicates - .local - .insert(def_id, Self::load_local_mir(self.tcx, def_id)); + self.predicates.local.insert( + def_id, + if config::unsafe_core_proof() { + Self::load_local_mir_with_facts(self.tcx, def_id).body + } else { + Self::load_local_mir(self.tcx, def_id) + }, + ); } pub(crate) fn load_pure_fn_body(&mut self, def_id: LocalDefId) { diff --git a/prusti-interface/src/environment/debug_utils/to_text.rs b/prusti-interface/src/environment/debug_utils/to_text.rs index b0aee910c0a..bcac454a5b1 100644 --- a/prusti-interface/src/environment/debug_utils/to_text.rs +++ b/prusti-interface/src/environment/debug_utils/to_text.rs @@ -1,6 +1,6 @@ use crate::environment::mir_body::borrowck::facts::Point; use std::collections::{BTreeMap, BTreeSet}; -use vir::common::graphviz::escape_html; +use vir::common::{builtin_constants::ERASED_LIFETIME_NAME, graphviz::escape_html}; pub trait ToText { fn to_text(&self) -> String; @@ -189,7 +189,7 @@ impl<'tcx> ToText for prusti_rustc_interface::middle::ty::Region<'tcx> { prusti_rustc_interface::middle::ty::RePlaceholder(_) => { unimplemented!("RePlaceholder: {self}"); } - prusti_rustc_interface::middle::ty::ReErased => String::from("lft_erased"), + prusti_rustc_interface::middle::ty::ReErased => String::from(ERASED_LIFETIME_NAME), prusti_rustc_interface::middle::ty::ReError(_) => { unimplemented!("ReError: {}", format!("{self}")); } diff --git a/prusti-interface/src/environment/loops.rs b/prusti-interface/src/environment/loops.rs index 884ac907275..f47f6d61516 100644 --- a/prusti-interface/src/environment/loops.rs +++ b/prusti-interface/src/environment/loops.rs @@ -124,6 +124,9 @@ impl<'b, 'tcx> Visitor<'tcx> for AccessCollector<'b, 'tcx> { NonMutatingUse(mir::visit::NonMutatingUseContext::Copy) => PlaceAccessKind::Read, NonMutatingUse(mir::visit::NonMutatingUseContext::Move) => PlaceAccessKind::Move, NonMutatingUse(mir::visit::NonMutatingUseContext::Inspect) => PlaceAccessKind::Read, + NonMutatingUse(mir::visit::NonMutatingUseContext::AddressOf) => { + PlaceAccessKind::Read + } NonMutatingUse(mir::visit::NonMutatingUseContext::SharedBorrow) => { PlaceAccessKind::SharedBorrow } diff --git a/prusti-interface/src/environment/mir_body/borrowck/facts/mod.rs b/prusti-interface/src/environment/mir_body/borrowck/facts/mod.rs index a923649b160..5edcc66072c 100644 --- a/prusti-interface/src/environment/mir_body/borrowck/facts/mod.rs +++ b/prusti-interface/src/environment/mir_body/borrowck/facts/mod.rs @@ -5,6 +5,7 @@ use rustc_hash::FxHashMap; pub mod patch; pub mod validation; +pub mod replace_terminator; pub type Region = ::Origin; pub type Loan = ::Loan; @@ -104,4 +105,12 @@ impl LocationTable { pub fn location_to_point(&self, location: RichLocation) -> Point { self.points[&location] } + + pub fn iter_points(&self) -> impl Iterator + '_ { + self.locations.keys().copied() + } + + pub fn iter_locations(&self) -> impl Iterator + '_ { + self.points.keys().copied() + } } diff --git a/prusti-interface/src/environment/mir_body/borrowck/facts/patch.rs b/prusti-interface/src/environment/mir_body/borrowck/facts/patch.rs index 86229969ac0..a1f5e0bb0bf 100644 --- a/prusti-interface/src/environment/mir_body/borrowck/facts/patch.rs +++ b/prusti-interface/src/environment/mir_body/borrowck/facts/patch.rs @@ -257,6 +257,22 @@ pub fn apply_patch_to_borrowck<'tcx>( .collect(); borrowck_input_facts.cfg_edge.sort(); + + // Recompute `var_dropped_at` facts. + borrowck_input_facts.var_dropped_at.clear(); + for (block, data) in patched_body.basic_blocks.iter_enumerated() { + match data.terminator().kind { + mir::TerminatorKind::Drop { place, .. } => { + let point = lt_patcher.start_point(block.index(), data.statements.len()); + if let Some(variable) = place.as_local() { + borrowck_input_facts.var_dropped_at.push((variable, point)); + } else { + // FIXME. + } + } + _ => {} + } + } } struct LocationTablePatcher<'a> { diff --git a/prusti-interface/src/environment/mir_body/borrowck/facts/replace_terminator.rs b/prusti-interface/src/environment/mir_body/borrowck/facts/replace_terminator.rs new file mode 100644 index 00000000000..e380dd246fa --- /dev/null +++ b/prusti-interface/src/environment/mir_body/borrowck/facts/replace_terminator.rs @@ -0,0 +1,56 @@ +use prusti_rustc_interface::middle::mir; + +#[derive(Debug, Clone)] +pub struct ReplaceTerminatorDesugaring { + /// The location of the `Drop` terminator that replaces the `DropAndReplace` + /// terminator. + pub replacing_drop_location: mir::Location, + /// The location of the target block of the new `Drop` terminator. + pub target_block: mir::BasicBlock, + /// The location of the unwinding block of the new `Drop` terminator. + pub unwinding_block: mir::BasicBlock, +} + +pub fn collect_replace_terminators<'tcx>( + _old_body: &mir::Body<'tcx>, + new_body: &mir::Body<'tcx>, +) -> Vec { + let mut replace_terminator_locations = Vec::new(); + for (index, new_block) in new_body.basic_blocks.iter_enumerated() { + if let mir::TerminatorKind::Drop { + place, + target, + unwind: Some(unwind), + } = new_block.terminator().kind + { + let target_block_data = &new_body.basic_blocks[target]; + let unwind_block_data = &new_body.basic_blocks[unwind]; + if target_block_data.statements.len() == 1 && unwind_block_data.statements.len() == 1 { + if let mir::StatementKind::Assign(box (target_place, _)) = + target_block_data.statements[0].kind + { + if let mir::StatementKind::Assign(box (unwind_place, _)) = + unwind_block_data.statements[0].kind + { + // FIXME: Check whether I can use + // `DesugaringKind::Replace` to reliably detect this + // case instead. https://github.com/rust-lang/rust/pull/107844 + if place == target_place && place == unwind_place { + // This is likely a desugaring of a `DropAndReplace` terminator. + let desugaring = ReplaceTerminatorDesugaring { + replacing_drop_location: mir::Location { + block: index, + statement_index: new_block.statements.len(), + }, + target_block: target, + unwinding_block: unwind, + }; + replace_terminator_locations.push(desugaring); + } + } + } + } + } + } + replace_terminator_locations +} diff --git a/prusti-interface/src/environment/mir_body/borrowck/lifetimes/graphviz.rs b/prusti-interface/src/environment/mir_body/borrowck/lifetimes/graphviz.rs index 3f0c4c7fb2a..5e9679102c3 100644 --- a/prusti-interface/src/environment/mir_body/borrowck/lifetimes/graphviz.rs +++ b/prusti-interface/src/environment/mir_body/borrowck/lifetimes/graphviz.rs @@ -28,6 +28,8 @@ pub trait LifetimesGraphviz { fn get_original_lifetimes(&self) -> Vec; fn location_to_point(&self, location: RichLocation) -> Point; fn get_loan_live_at(&self, location: RichLocation) -> Vec; + fn get_loan_killed_at(&self, location: RichLocation) -> Vec; + fn get_loan_successfully_killed_at(&self, location: RichLocation) -> Vec; fn get_origin_contains_loan_at( &self, location: RichLocation, @@ -107,6 +109,14 @@ impl LifetimesGraphviz for Lifetimes { Lifetimes::get_loan_live_at(self, location) } + fn get_loan_killed_at(&self, location: RichLocation) -> Vec { + Lifetimes::get_loan_killed_at(self, location) + } + + fn get_loan_successfully_killed_at(&self, location: RichLocation) -> Vec { + Lifetimes::get_loan_successfully_killed_at(self, location) + } + fn get_origin_contains_loan_at( &self, location: RichLocation, diff --git a/prusti-interface/src/environment/mir_body/borrowck/lifetimes/mod.rs b/prusti-interface/src/environment/mir_body/borrowck/lifetimes/mod.rs index 76adaf2c361..e4e66deda11 100644 --- a/prusti-interface/src/environment/mir_body/borrowck/lifetimes/mod.rs +++ b/prusti-interface/src/environment/mir_body/borrowck/lifetimes/mod.rs @@ -1,5 +1,6 @@ use super::facts::{ - AllInputFacts, BorrowckFacts, Loan, LocationTable, Point, Region, RichLocation, + replace_terminator::ReplaceTerminatorDesugaring, AllInputFacts, BorrowckFacts, Loan, + LocationTable, Point, Region, RichLocation, }; use crate::environment::debug_utils::to_text::{opaque_lifetime_string, ToText}; use prusti_rustc_interface::middle::mir; @@ -12,6 +13,9 @@ pub use self::graphviz::LifetimesGraphviz; pub struct Lifetimes { facts: BorrowckFacts, + /// We ignore the loans that are either successfully killed or outlive the + /// function body. This is currently an heuristic to avoid f-equalize rule. + ignored_loans: BTreeSet, } pub struct LifetimeWithInclusions { @@ -21,7 +25,12 @@ pub struct LifetimeWithInclusions { } impl Lifetimes { - pub fn new(mut input_facts: AllInputFacts, location_table: LocationTable) -> Self { + pub fn new<'tcx>( + mut input_facts: AllInputFacts, + location_table: LocationTable, + replace_terminator_locations: Vec, + body: &mir::Body<'tcx>, + ) -> Self { let entry_block = mir::START_BLOCK; let entry_point = location_table.location_to_point(RichLocation::Start(mir::Location { block: entry_block, @@ -37,15 +46,128 @@ impl Lifetimes { .subset_base .push((*origin1, *origin2, entry_point)); } - let output_facts = prusti_rustc_interface::polonius_engine::Output::compute( + let mut output_facts = prusti_rustc_interface::polonius_engine::Output::compute( &input_facts, prusti_rustc_interface::polonius_engine::Algorithm::Naive, true, ); assert!(output_facts.errors.is_empty()); - Self { + for ReplaceTerminatorDesugaring { + replacing_drop_location, + target_block, + unwinding_block, + } in replace_terminator_locations + { + let drop_point = + location_table.location_to_point(RichLocation::Mid(replacing_drop_location)); + let alive_origins = output_facts.origin_live_on_entry[&drop_point].clone(); + let origin_contains_loan_at = output_facts + .origin_contains_loan_at + .get(&drop_point) + .cloned() + .unwrap_or_default(); + let mut copy_to_point = |target_point| { + let target_alive_origins = output_facts + .origin_live_on_entry + .entry(target_point) + .or_default(); + for origin in &alive_origins { + if !target_alive_origins.contains(origin) { + target_alive_origins.push(*origin); + let target_origin_contains_loan_at = output_facts + .origin_contains_loan_at + .entry(target_point) + .or_default(); + if let Some(loan_set) = origin_contains_loan_at.get(origin) { + let old_loan_set = + target_origin_contains_loan_at.insert(*origin, loan_set.clone()); + if let Some(old_loan_set) = old_loan_set { + assert_eq!(&old_loan_set, loan_set); + } + } + } + } + let drop_loan_live_at = output_facts.loan_live_at[&drop_point].clone(); + let target_loan_live_at = output_facts.loan_live_at.get_mut(&target_point).unwrap(); + target_loan_live_at.extend(drop_loan_live_at); + }; + let mut copy_to_location = |location: mir::Location| { + copy_to_point(location_table.location_to_point(RichLocation::Start(location))); + copy_to_point(location_table.location_to_point(RichLocation::Mid(location))); + }; + // Note: This code assumes that the desugaring of `DropAndReplace` + // on both target and unwinding paths have a single statement that + // does the replace. This is asserted in + // prusti-interface/src/environment/mir_body/borrowck/facts/replace_terminator.rs + copy_to_location(mir::Location { + block: target_block, + statement_index: 0, + }); + copy_to_location(mir::Location { + block: target_block, + statement_index: 1, + }); + copy_to_location(mir::Location { + block: unwinding_block, + statement_index: 0, + }); + copy_to_location(mir::Location { + block: unwinding_block, + statement_index: 1, + }); + } + let mut lifetimes = Self { facts: BorrowckFacts::new(input_facts, output_facts, location_table), + ignored_loans: BTreeSet::new(), + }; + { + // Compute the successfully killed loans. + let all_locations: Vec<_> = lifetimes.facts.location_table.iter_locations().collect(); + for location in all_locations { + let killed_loans = lifetimes.get_loan_successfully_killed_at(location); + lifetimes.ignored_loans.extend(killed_loans); + } + } + { + // Compute the loans that outlive the function. + let entry_loans = lifetimes.get_loan_live_at(RichLocation::Start(mir::Location { + block: entry_block, + statement_index: 0, + })); + let mut exit_locations = Vec::new(); + for (block, data) in body.basic_blocks.iter_enumerated() { + match data.terminator().kind { + mir::TerminatorKind::Goto { .. } + | mir::TerminatorKind::SwitchInt { .. } + | mir::TerminatorKind::Drop { .. } + | mir::TerminatorKind::Call { .. } + | mir::TerminatorKind::Assert { .. } + | mir::TerminatorKind::Yield { .. } + | mir::TerminatorKind::GeneratorDrop + | mir::TerminatorKind::FalseEdge { .. } + | mir::TerminatorKind::FalseUnwind { .. } + | mir::TerminatorKind::InlineAsm { .. } => {} + mir::TerminatorKind::Resume + | mir::TerminatorKind::Abort + | mir::TerminatorKind::Return + | mir::TerminatorKind::Unreachable => { + exit_locations.push(mir::Location { + block, + statement_index: data.statements.len(), + }); + } + } + } + for location in exit_locations { + let loans = lifetimes.get_loan_live_at(RichLocation::Mid(location)); + for loan in loans { + if !entry_loans.contains(&loan) { + lifetimes.ignored_loans.insert(loan); + } + } + } } + lifetimes } pub fn get_loan_live_at_start(&self, location: mir::Location) -> BTreeSet { @@ -157,6 +279,32 @@ impl Lifetimes { } } + fn get_loan_killed_at(&self, location: RichLocation) -> Vec { + let point = self.location_to_point(location); + self.facts + .input_facts + .loan_killed_at + .iter() + .flat_map(|&(loan, p)| if p == point { Some(loan) } else { None }) + .collect() + } + + fn get_loan_successfully_killed_at(&self, location: RichLocation) -> Vec { + let live_loans = self.get_loan_live_at(location); + let killed_loans = self.get_loan_killed_at(location); + killed_loans + .into_iter() + .filter(|killed_loan| live_loans.contains(killed_loan)) + .collect() + } + + pub fn get_all_ignored_loans(&self) -> Vec { + self.ignored_loans + .iter() + .map(|loan| opaque_lifetime_string(loan.index())) + .collect() + } + fn get_origin_contains_loan_at( &self, location: RichLocation, diff --git a/prusti-interface/src/environment/mir_body/graphviz.rs b/prusti-interface/src/environment/mir_body/graphviz.rs index 132ef24ae45..bd365857607 100644 --- a/prusti-interface/src/environment/mir_body/graphviz.rs +++ b/prusti-interface/src/environment/mir_body/graphviz.rs @@ -1,5 +1,7 @@ use super::borrowck::{ - facts::{AllInputFacts, LocationTable, RichLocation}, + facts::{ + replace_terminator::ReplaceTerminatorDesugaring, AllInputFacts, LocationTable, RichLocation, + }, lifetimes::{Lifetimes, LifetimesGraphviz}, }; use crate::environment::debug_utils::to_text::{ @@ -12,8 +14,14 @@ pub fn to_graphviz<'tcx>( borrowck_input_facts: &AllInputFacts, location_table: &LocationTable, mir: &mir::Body<'tcx>, + replace_terminator_locations: &[ReplaceTerminatorDesugaring], ) -> Graph { - let lifetimes = Lifetimes::new(borrowck_input_facts.clone(), location_table.clone()); + let lifetimes = Lifetimes::new( + borrowck_input_facts.clone(), + location_table.clone(), + replace_terminator_locations.to_vec(), + mir, + ); let mut graph = Graph::with_columns(&[ "location", @@ -25,6 +33,8 @@ pub fn to_graphviz<'tcx>( "subset", "origin_live_on_entry", "original lifetimes", + "killed", + "successfully killed", "derived lifetimes", ]); @@ -123,6 +133,12 @@ fn visit_statement( let origin_live_on_entry_mid = lifetimes.get_origin_live_on_entry(RichLocation::Mid(location)); let loan_live_at_start = lifetimes.get_loan_live_at(RichLocation::Start(location)); let loan_live_at_mid = lifetimes.get_loan_live_at(RichLocation::Mid(location)); + let loan_killed_at_start = lifetimes.get_loan_killed_at(RichLocation::Start(location)); + let loan_killed_at_mid = lifetimes.get_loan_killed_at(RichLocation::Mid(location)); + let loan_successfully_killed_at_start = + lifetimes.get_loan_successfully_killed_at(RichLocation::Start(location)); + let loan_successfully_killed_at_mid = + lifetimes.get_loan_successfully_killed_at(RichLocation::Mid(location)); let origin_contains_loan_at_start = lifetimes.get_origin_contains_loan_at(RichLocation::Start(location)); let origin_contains_loan_at_mid = @@ -138,6 +154,11 @@ fn visit_statement( row_builder_start.set("subset", subset_start.to_text()); row_builder_start.set("origin_live_on_entry", origin_live_on_entry_start.to_text()); row_builder_start.set("original lifetimes", loans_to_text(&loan_live_at_start)); + row_builder_start.set("killed", loans_to_text(&loan_killed_at_start)); + row_builder_start.set( + "successfully killed", + loans_to_text(&loan_successfully_killed_at_start), + ); row_builder_start.set( "derived lifetimes", loan_containment_to_text(&origin_contains_loan_at_start), @@ -154,6 +175,11 @@ fn visit_statement( row_builder_end.set("subset", subset_mid.to_text()); row_builder_end.set("origin_live_on_entry", origin_live_on_entry_mid.to_text()); row_builder_end.set("original lifetimes", loans_to_text(&loan_live_at_mid)); + row_builder_end.set("killed", loans_to_text(&loan_killed_at_mid)); + row_builder_end.set( + "successfully killed", + loans_to_text(&loan_successfully_killed_at_mid), + ); row_builder_end.set( "derived lifetimes", loan_containment_to_text(&origin_contains_loan_at_mid), diff --git a/prusti-interface/src/environment/mir_body/patch/mod.rs b/prusti-interface/src/environment/mir_body/patch/mod.rs index cec7949a0a1..08af411138d 100644 --- a/prusti-interface/src/environment/mir_body/patch/mod.rs +++ b/prusti-interface/src/environment/mir_body/patch/mod.rs @@ -1,4 +1,8 @@ -use super::borrowck::facts::{patch::apply_patch_to_borrowck, AllInputFacts, LocationTable}; +use super::borrowck::facts::{ + patch::apply_patch_to_borrowck, + replace_terminator::{collect_replace_terminators, ReplaceTerminatorDesugaring}, + AllInputFacts, LocationTable, +}; use prusti_rustc_interface::middle::mir; mod compiler; @@ -10,7 +14,7 @@ pub fn apply_patch<'tcx>( body: &mir::Body<'tcx>, borrowck_input_facts: &mut AllInputFacts, location_table: &mut LocationTable, -) -> mir::Body<'tcx> { +) -> (mir::Body<'tcx>, Vec) { let mut patched_body = body.clone(); patch.clone().apply(&mut patched_body); apply_patch_to_borrowck( @@ -20,5 +24,6 @@ pub fn apply_patch<'tcx>( body, &mut patched_body, ); - patched_body + let replace_terminators = collect_replace_terminators(body, &patched_body); + (patched_body, replace_terminators) } diff --git a/prusti-interface/src/environment/mir_dump/mod.rs b/prusti-interface/src/environment/mir_dump/mod.rs index 67dc6b7a6ae..1759a90add0 100644 --- a/prusti-interface/src/environment/mir_dump/mod.rs +++ b/prusti-interface/src/environment/mir_dump/mod.rs @@ -32,7 +32,7 @@ fn populate_graph(env: &Environment<'_>, def_id: DefId) -> Option { { let input_facts = facts.input_facts.borrow().as_ref().unwrap().clone(); let location_table = LocationTable::new(facts.location_table.borrow().as_ref().unwrap()); - Some(to_graphviz(&input_facts, &location_table, mir)) + Some(to_graphviz(&input_facts, &location_table, mir, &Vec::new())) } else { None } diff --git a/prusti-interface/src/environment/mir_storage.rs b/prusti-interface/src/environment/mir_storage.rs index 495cde9f21d..1e765adaa45 100644 --- a/prusti-interface/src/environment/mir_storage.rs +++ b/prusti-interface/src/environment/mir_storage.rs @@ -45,7 +45,8 @@ pub(super) unsafe fn retrieve_mir_body<'tcx>( ) -> BodyWithBorrowckFacts<'tcx> { let body_with_facts: BodyWithBorrowckFacts<'static> = SHARED_STATE.with(|state| { let mut map = state.borrow_mut(); - map.remove(&def_id).unwrap() + map.remove(&def_id) + .unwrap_or_else(|| panic!("not found: {def_id:?}")) }); // SAFETY: See the module level comment. unsafe { std::mem::transmute(body_with_facts) } diff --git a/prusti-interface/src/environment/mod.rs b/prusti-interface/src/environment/mod.rs index 7f1acf512fe..dded4565e3b 100644 --- a/prusti-interface/src/environment/mod.rs +++ b/prusti-interface/src/environment/mod.rs @@ -35,8 +35,10 @@ pub use self::{ loops_utils::*, name::EnvName, procedure::{ - get_loop_invariant, is_ghost_begin_marker, is_ghost_end_marker, is_loop_invariant_block, - is_loop_variant_block, is_marked_specification_block, BasicBlockIndex, Procedure, + get_loop_invariant, is_checked_block_begin_marker, is_checked_block_end_marker, + is_ghost_begin_marker, is_ghost_end_marker, is_loop_invariant_block, is_loop_variant_block, + is_marked_specification_block, is_specification_begin_marker, is_specification_end_marker, + is_try_finally_begin_marker, is_try_finally_end_marker, BasicBlockIndex, Procedure, }, query::EnvQuery, }; @@ -141,21 +143,19 @@ impl<'tcx> Environment<'tcx> { called_def_id: ProcedureDefId, call_substs: SubstsRef<'tcx>, ) -> bool { - if called_def_id == caller_def_id { - true - } else { + if called_def_id != caller_def_id && called_def_id.is_local() { let param_env = self.tcx().param_env(caller_def_id); if let Some(instance) = self .tcx() .resolve_instance(param_env.and((called_def_id, call_substs))) .unwrap() { - self.tcx() - .mir_callgraph_reachable((instance, caller_def_id.expect_local())) - } else { - true + return self + .tcx() + .mir_callgraph_reachable((instance, caller_def_id.expect_local())); } } + called_def_id.is_local() // FIXME: Currently assuming that external ids are not recursive. } /// Get the current version of the `prusti` crate diff --git a/prusti-interface/src/environment/procedure.rs b/prusti-interface/src/environment/procedure.rs index fbb3537a7fc..d664abeb027 100644 --- a/prusti-interface/src/environment/procedure.rs +++ b/prusti-interface/src/environment/procedure.rs @@ -19,6 +19,7 @@ use prusti_rustc_interface::{ }, span::Span, }; +use prusti_specs::specifications::untyped::SpecificationId; /// Index of a Basic Block pub type BasicBlockIndex = mir::BasicBlock; @@ -250,6 +251,7 @@ pub fn get_loop_invariant<'tcx>( ) -> Option<( ProcedureDefId, prusti_rustc_interface::middle::ty::subst::SubstsRef<'tcx>, + bool, )> { for stmt in &bb_data.statements { if let StatementKind::Assign(box ( @@ -257,13 +259,19 @@ pub fn get_loop_invariant<'tcx>( Rvalue::Aggregate(box AggregateKind::Closure(def_id, substs), _), )) = &stmt.kind { - if is_spec_closure(env_query, *def_id) - && crate::utils::has_prusti_attr( + if is_spec_closure(env_query, *def_id) { + if crate::utils::has_prusti_attr( env_query.get_attributes(def_id), "loop_body_invariant_spec", - ) - { - return Some((*def_id, substs)); + ) { + return Some((*def_id, substs, false)); + } + if crate::utils::has_prusti_attr( + env_query.get_attributes(def_id), + "loop_structural_body_invariant_spec", + ) { + return Some((*def_id, substs, true)); + } } } } @@ -286,6 +294,66 @@ pub fn is_ghost_end_marker<'tcx>(env_query: EnvQuery, bb: &BasicBlockData<'tcx>) is_spec_block_kind(env_query, bb, "ghost_end") } +/// Returns specification id. +pub fn is_specification_begin_marker<'tcx>( + env_query: EnvQuery, + bb_data: &BasicBlockData<'tcx>, +) -> Option { + let kind = "specification_region_begin"; + get_spec_block_kind_id(env_query, bb_data, kind) +} + +pub fn is_specification_end_marker<'tcx>(env_query: EnvQuery, bb: &BasicBlockData<'tcx>) -> bool { + is_spec_block_kind(env_query, bb, "specification_region_end") +} + +pub fn is_try_finally_begin_marker<'tcx>( + env_query: EnvQuery, + bb_data: &BasicBlockData<'tcx>, +) -> Option<(SpecificationId, SpecificationId, SpecificationId)> { + let kind = "try_finally_executed_block_begin"; + for stmt in &bb_data.statements { + if let StatementKind::Assign(box ( + _, + Rvalue::Aggregate(box AggregateKind::Closure(def_id, _), _), + )) = &stmt.kind + { + let attrs = env_query.get_attributes(def_id); + if is_spec_closure(env_query, *def_id) && crate::utils::has_prusti_attr(attrs, kind) { + let on_panic_spec_id_string = + crate::utils::read_prusti_attr("on_panic_spec_id", attrs).unwrap(); + let on_panic_spec_id = on_panic_spec_id_string.try_into().unwrap(); + let finally_at_panic_spec_id_string = + crate::utils::read_prusti_attr("finally_at_panic_start_spec_id", attrs) + .unwrap(); + let finally_at_panic_spec_id = finally_at_panic_spec_id_string.try_into().unwrap(); + let finally_at_resume_spec_id_string = + crate::utils::read_prusti_attr("finally_at_resume_spec_id", attrs).unwrap(); + let finally_at_resume_spec_id = + finally_at_resume_spec_id_string.try_into().unwrap(); + return Some(( + on_panic_spec_id, + finally_at_panic_spec_id, + finally_at_resume_spec_id, + )); + } + } + } + None +} + +pub fn is_try_finally_end_marker<'tcx>(env_query: EnvQuery, bb: &BasicBlockData<'tcx>) -> bool { + is_spec_block_kind(env_query, bb, "try_finally_executed_block_end") +} + +pub fn is_checked_block_begin_marker<'tcx>(env_query: EnvQuery, bb: &BasicBlockData<'tcx>) -> bool { + is_spec_block_kind(env_query, bb, "checked_block_begin") +} + +pub fn is_checked_block_end_marker<'tcx>(env_query: EnvQuery, bb: &BasicBlockData<'tcx>) -> bool { + is_spec_block_kind(env_query, bb, "checked_block_end") +} + fn is_spec_block_kind(env_query: EnvQuery, bb_data: &BasicBlockData, kind: &str) -> bool { for stmt in &bb_data.statements { if let StatementKind::Assign(box ( @@ -303,6 +371,28 @@ fn is_spec_block_kind(env_query: EnvQuery, bb_data: &BasicBlockData, kind: &str) false } +fn get_spec_block_kind_id( + env_query: EnvQuery, + bb_data: &BasicBlockData, + kind: &str, +) -> Option { + for stmt in &bb_data.statements { + if let StatementKind::Assign(box ( + _, + Rvalue::Aggregate(box AggregateKind::Closure(def_id, _), _), + )) = &stmt.kind + { + let attrs = env_query.get_attributes(def_id); + if is_spec_closure(env_query, *def_id) && crate::utils::has_prusti_attr(attrs, kind) { + let spec_id_string = crate::utils::read_prusti_attr("spec_id", attrs).unwrap(); + let spec_id = spec_id_string.try_into().unwrap(); + return Some(spec_id); + } + } + } + None +} + #[derive(Debug)] struct BasicBlockNode { successors: FxHashSet, diff --git a/prusti-interface/src/environment/query.rs b/prusti-interface/src/environment/query.rs index 4afeb53d4bb..63f2148456f 100644 --- a/prusti-interface/src/environment/query.rs +++ b/prusti-interface/src/environment/query.rs @@ -144,6 +144,42 @@ impl<'tcx> EnvQuery<'tcx> { .is_some() } + /// Returns true iff `def_id` is an implementation of `Drop::drop` method + pub fn is_drop_method_impl(self, def_id: impl IntoParam) -> bool { + let trait_id = self + .tcx + .impl_of_method(def_id.into_param()) + .and_then(|impl_id| self.tcx.trait_id_of_impl(impl_id)); + if let Some(trait_id) = trait_id { + let drop_trait_id = self.tcx.lang_items().drop_trait().unwrap(); + trait_id == drop_trait_id + } else { + false + } + } + + pub fn get_drop_method_id(self, ty: ty::Ty<'tcx>) -> Option { + let drop_trait_id = self.tcx.lang_items().drop_trait().unwrap(); + for item_impl in self.tcx.all_impls(drop_trait_id) { + for item in self.tcx.associated_items(item_impl).in_definition_order() { + let method_def_id = item.def_id; + let method_sig = self.tcx.fn_sig(method_def_id); + let self_type = method_sig.skip_binder().input(0).skip_binder(); + match self_type.kind() { + ty::TyKind::Ref(_, target_type, _) => { + if *target_type == ty { + return Some(method_def_id); + } + } + _ => { + unimplemented!(); + } + } + } + } + None + } + /// Returns true iff `def_id` is an unsafe function. pub fn is_unsafe_function(self, def_id: impl IntoParam) -> bool { self.tcx diff --git a/prusti-interface/src/lib.rs b/prusti-interface/src/lib.rs index 0b077ce0921..b3b89bda0fc 100644 --- a/prusti-interface/src/lib.rs +++ b/prusti-interface/src/lib.rs @@ -9,6 +9,7 @@ #![deny(unused_must_use)] #![deny(unsafe_op_in_unsafe_fn)] #![warn(clippy::disallowed_types)] +#![allow(clippy::nonminimal_bool)] #![feature(rustc_private)] #![feature(box_patterns)] #![feature(control_flow_enum)] diff --git a/prusti-interface/src/specs/mod.rs b/prusti-interface/src/specs/mod.rs index d7125c83353..e1813215ae0 100644 --- a/prusti-interface/src/specs/mod.rs +++ b/prusti-interface/src/specs/mod.rs @@ -43,6 +43,9 @@ struct ProcedureSpecRefs { pure: bool, abstract_predicate: bool, trusted: bool, + non_verified_pure: bool, + no_panic: bool, + no_panic_ensures_postcondition: bool, } impl From<&ProcedureSpecRefs> for ProcedureSpecificationKind { @@ -60,6 +63,7 @@ impl From<&ProcedureSpecRefs> for ProcedureSpecificationKind { #[derive(Debug, Default)] struct TypeSpecRefs { invariants: Vec, + structural_invariants: Vec, trusted: bool, model: Option<(String, LocalDefId)>, countexample_print: Vec<(Option, LocalDefId)>, @@ -79,13 +83,20 @@ pub struct SpecCollector<'a, 'tcx> { /// Map from functions/loops/types to their specifications. procedure_specs: FxHashMap, loop_specs: Vec, + loop_structural_specs: Vec, loop_variants: Vec, type_specs: FxHashMap, prusti_assertions: Vec, + prusti_structural_assertions: Vec, prusti_assumptions: Vec, prusti_refutations: Vec, + prusti_structural_assumptions: Vec, + prusti_case_splits: Vec, ghost_begin: Vec, ghost_end: Vec, + specification_region_begin: Vec, + specification_region_end: Vec, + specification_expression: Vec, } impl<'a, 'tcx> SpecCollector<'a, 'tcx> { @@ -96,13 +107,20 @@ impl<'a, 'tcx> SpecCollector<'a, 'tcx> { spec_functions: FxHashMap::default(), procedure_specs: FxHashMap::default(), loop_specs: vec![], + loop_structural_specs: vec![], loop_variants: vec![], type_specs: FxHashMap::default(), prusti_assertions: vec![], + prusti_structural_assertions: vec![], prusti_assumptions: vec![], prusti_refutations: vec![], + prusti_structural_assumptions: vec![], + prusti_case_splits: vec![], ghost_begin: vec![], ghost_end: vec![], + specification_region_begin: vec![], + specification_region_end: vec![], + specification_expression: vec![], } } @@ -121,8 +139,11 @@ impl<'a, 'tcx> SpecCollector<'a, 'tcx> { self.determine_type_specs(&mut def_spec); self.determine_prusti_assertions(&mut def_spec); self.determine_prusti_assumptions(&mut def_spec); + self.determine_case_splits(&mut def_spec); self.determine_prusti_refutations(&mut def_spec); self.determine_ghost_begin_ends(&mut def_spec); + self.determine_specification_region_begin_ends(&mut def_spec); + self.determine_specification_expressions(&mut def_spec); // TODO: remove spec functions (make sure none are duplicated or left over) // Load all local spec MIR bodies, for export and later use self.ensure_local_mirs_fetched(&def_spec); @@ -144,12 +165,46 @@ impl<'a, 'tcx> SpecCollector<'a, 'tcx> { SpecIdRef::Precondition(spec_id) => { spec.add_precondition(*self.spec_functions.get(spec_id).unwrap(), self.env); } + SpecIdRef::StructuralPrecondition(spec_id) => { + spec.add_structural_precondition( + *self.spec_functions.get(spec_id).unwrap(), + self.env, + ); + } SpecIdRef::Postcondition(spec_id) => { spec.add_postcondition( *self.spec_functions.get(spec_id).unwrap(), self.env, ); } + SpecIdRef::PanicPostcondition(spec_id) => { + spec.add_panic_postcondition( + *self.spec_functions.get(spec_id).unwrap(), + self.env, + ); + } + SpecIdRef::StructuralPostcondition(spec_id) => { + spec.add_structural_postcondition( + *self.spec_functions.get(spec_id).unwrap(), + self.env, + ); + } + SpecIdRef::StructuralPanicPostcondition(spec_id) => { + spec.add_structural_panic_postcondition( + *self.spec_functions.get(spec_id).unwrap(), + self.env, + ); + } + SpecIdRef::BrokenPrecondition(spec_id) => { + spec.add_broken_precondition( + self.spec_functions.get(spec_id).unwrap().to_def_id(), + ); + } + SpecIdRef::BrokenPostcondition(spec_id) => { + spec.add_broken_postcondition( + self.spec_functions.get(spec_id).unwrap().to_def_id(), + ); + } SpecIdRef::Purity(spec_id) => { spec.add_purity(*self.spec_functions.get(spec_id).unwrap(), self.env); } @@ -168,12 +223,15 @@ impl<'a, 'tcx> SpecCollector<'a, 'tcx> { ))); } SpecIdRef::Terminates(spec_id) => { - spec.set_terminates(*self.spec_functions.get(spec_id).unwrap()); + spec.set_terminates(self.spec_functions.get(spec_id).unwrap().to_def_id()); } } } spec.set_trusted(refs.trusted); + spec.set_non_verified_pure(refs.non_verified_pure); + spec.set_no_panic(refs.no_panic); + spec.set_no_panic_ensures_postcondition(refs.no_panic_ensures_postcondition); if let Some(kind) = kind_override { spec.set_kind(kind); @@ -218,7 +276,19 @@ impl<'a, 'tcx> SpecCollector<'a, 'tcx> { for local_id in self.loop_specs.iter() { def_spec.loop_specs.insert( local_id.to_def_id(), - typed::LoopSpecification::Invariant(*local_id), + typed::LoopSpecification::Invariant { + def_id: *local_id, + is_structural: false, + }, + ); + } + for local_id in self.loop_structural_specs.iter() { + def_spec.loop_specs.insert( + local_id.to_def_id(), + typed::LoopSpecification::Invariant { + def_id: *local_id, + is_structural: true, + }, ); } for local_id in self.loop_variants.iter() { @@ -231,7 +301,9 @@ impl<'a, 'tcx> SpecCollector<'a, 'tcx> { fn determine_type_specs(&self, def_spec: &mut typed::DefSpecificationMap) { for (type_id, refs) in self.type_specs.iter() { - if !refs.invariants.is_empty() && !prusti_common::config::enable_type_invariants() { + if !(refs.invariants.is_empty() && refs.structural_invariants.is_empty()) + && !prusti_common::config::enable_type_invariants() + { let span = self.env.query.get_def_span(*type_id); PrustiError::unsupported( "Type invariants need to be enabled with the feature flag `enable_type_invariants`", @@ -251,6 +323,13 @@ impl<'a, 'tcx> SpecCollector<'a, 'tcx> { .map(LocalDefId::to_def_id) .collect(), ), + structural_invariant: SpecificationItem::Inherent( + refs.structural_invariants + .clone() + .into_iter() + .map(LocalDefId::to_def_id) + .collect(), + ), trusted: SpecificationItem::Inherent(refs.trusted), model: refs.model.clone(), counterexample_print: refs.countexample_print.clone(), @@ -259,21 +338,51 @@ impl<'a, 'tcx> SpecCollector<'a, 'tcx> { } } fn determine_prusti_assertions(&self, def_spec: &mut typed::DefSpecificationMap) { - for local_id in self.prusti_assertions.iter() { + for local_id in &self.prusti_assertions { + def_spec.prusti_assertions.insert( + local_id.to_def_id(), + typed::PrustiAssertion { + assertion: *local_id, + is_structural: false, + }, + ); + } + for local_id in &self.prusti_structural_assertions { def_spec.prusti_assertions.insert( local_id.to_def_id(), typed::PrustiAssertion { assertion: *local_id, + is_structural: true, }, ); } } fn determine_prusti_assumptions(&self, def_spec: &mut typed::DefSpecificationMap) { - for local_id in self.prusti_assumptions.iter() { + for local_id in &self.prusti_assumptions { def_spec.prusti_assumptions.insert( local_id.to_def_id(), typed::PrustiAssumption { assumption: *local_id, + is_structural: false, + }, + ); + } + for local_id in &self.prusti_structural_assumptions { + def_spec.prusti_assumptions.insert( + local_id.to_def_id(), + typed::PrustiAssumption { + assumption: *local_id, + is_structural: true, + }, + ); + } + } + fn determine_case_splits(&self, def_spec: &mut typed::DefSpecificationMap) { + for local_id in &self.prusti_case_splits { + def_spec.prusti_case_splits.insert( + local_id.to_def_id(), + typed::PrustiCaseSplit { + assertion: *local_id, }, ); } @@ -301,6 +410,30 @@ impl<'a, 'tcx> SpecCollector<'a, 'tcx> { .insert(local_id.to_def_id(), typed::GhostEnd { marker: *local_id }); } } + fn determine_specification_region_begin_ends(&self, def_spec: &mut typed::DefSpecificationMap) { + for local_id in self.specification_region_begin.iter() { + def_spec.specification_region_begin.insert( + local_id.to_def_id(), + typed::SpecificationRegionBegin { marker: *local_id }, + ); + } + for local_id in self.specification_region_end.iter() { + def_spec.specification_region_end.insert( + local_id.to_def_id(), + typed::SpecificationRegionEnd { marker: *local_id }, + ); + } + } + fn determine_specification_expressions(&self, def_spec: &mut typed::DefSpecificationMap) { + for local_id in self.specification_expression.iter() { + def_spec.specification_expression.insert( + local_id.to_def_id(), + typed::SpecificationExpression { + expression: *local_id, + }, + ); + } + } fn ensure_local_mirs_fetched(&mut self, def_spec: &typed::DefSpecificationMap) { let (specs, pure_fns, predicates) = def_spec.defid_for_export(); @@ -376,11 +509,47 @@ fn get_procedure_spec_ids(def_id: DefId, attrs: &[ast::Attribute]) -> Option Option intravisit::Visitor<'tcx> for SpecCollector<'a, 'tcx> { if has_prusti_attr(attrs, "loop_body_invariant_spec") { self.loop_specs.push(local_id); } + // Collect loop specifications + if has_prusti_attr(attrs, "loop_structural_body_invariant_spec") { + self.loop_structural_specs.push(local_id); + } if has_prusti_attr(attrs, "loop_body_variant_spec") { self.loop_variants.push(local_id); } @@ -498,11 +684,20 @@ impl<'a, 'tcx> intravisit::Visitor<'tcx> for SpecCollector<'a, 'tcx> { let hir = self.env.query.hir(); let impl_id = hir.parent_id(hir.parent_id(self_id)); let type_id = get_type_id_from_impl_node(hir.get(impl_id)).unwrap(); - self.type_specs - .entry(type_id.as_local().unwrap()) - .or_default() - .invariants - .push(local_id); + if has_prusti_attr(attrs, "type_invariant_structural") { + self.type_specs + .entry(type_id.as_local().unwrap()) + .or_default() + .structural_invariants + .push(local_id); + } else { + assert!(has_prusti_attr(attrs, "type_invariant_non_structural")); + self.type_specs + .entry(type_id.as_local().unwrap()) + .or_default() + .invariants + .push(local_id); + } } // Collect trusted type flag @@ -535,6 +730,10 @@ impl<'a, 'tcx> intravisit::Visitor<'tcx> for SpecCollector<'a, 'tcx> { self.prusti_assertions.push(local_id); } + if has_prusti_attr(attrs, "prusti_structural_assertion") { + self.prusti_structural_assertions.push(local_id); + } + if has_prusti_attr(attrs, "prusti_assumption") { self.prusti_assumptions.push(local_id); } @@ -543,6 +742,14 @@ impl<'a, 'tcx> intravisit::Visitor<'tcx> for SpecCollector<'a, 'tcx> { self.prusti_refutations.push(local_id); } + if has_prusti_attr(attrs, "prusti_structural_assumption") { + self.prusti_structural_assumptions.push(local_id); + } + + if has_prusti_attr(attrs, "prusti_case_split") { + self.prusti_case_splits.push(local_id); + } + if has_prusti_attr(attrs, "ghost_begin") { self.ghost_begin.push(local_id); } @@ -550,6 +757,18 @@ impl<'a, 'tcx> intravisit::Visitor<'tcx> for SpecCollector<'a, 'tcx> { if has_prusti_attr(attrs, "ghost_end") { self.ghost_end.push(local_id); } + + if has_prusti_attr(attrs, "specification_region_begin") { + self.specification_region_begin.push(local_id); + } + + if has_prusti_attr(attrs, "specification_region_end") { + self.specification_region_end.push(local_id); + } + + if has_prusti_attr(attrs, "prusti_specification_expression") { + self.specification_expression.push(local_id); + } } else { // Don't collect specs "for" spec items diff --git a/prusti-interface/src/specs/typed.rs b/prusti-interface/src/specs/typed.rs index 2041266c555..2386df956f3 100644 --- a/prusti-interface/src/specs/typed.rs +++ b/prusti-interface/src/specs/typed.rs @@ -17,9 +17,13 @@ pub struct DefSpecificationMap { pub type_specs: FxHashMap, pub prusti_assertions: FxHashMap, pub prusti_assumptions: FxHashMap, + pub prusti_case_splits: FxHashMap, pub prusti_refutations: FxHashMap, pub ghost_begin: FxHashMap, pub ghost_end: FxHashMap, + pub specification_region_begin: FxHashMap, + pub specification_region_end: FxHashMap, + pub specification_expression: FxHashMap, } impl DefSpecificationMap { @@ -47,6 +51,10 @@ impl DefSpecificationMap { self.prusti_assumptions.get(def_id) } + pub fn get_case_split(&self, def_id: &DefId) -> Option<&PrustiCaseSplit> { + self.prusti_case_splits.get(def_id) + } + pub fn get_refutation(&self, def_id: &DefId) -> Option<&PrustiRefutation> { self.prusti_refutations.get(def_id) } @@ -59,6 +67,21 @@ impl DefSpecificationMap { self.ghost_end.get(def_id) } + pub fn get_specification_region_begin( + &self, + def_id: &DefId, + ) -> Option<&SpecificationRegionBegin> { + self.specification_region_begin.get(def_id) + } + + pub fn get_specification_region_end(&self, def_id: &DefId) -> Option<&SpecificationRegionEnd> { + self.specification_region_end.get(def_id) + } + + pub fn get_specification_expression(&self, def_id: &DefId) -> Option<&SpecificationExpression> { + self.specification_expression.get(def_id) + } + pub(crate) fn defid_for_export( &self, ) -> ( @@ -79,17 +102,41 @@ impl DefSpecificationMap { if let Some(pres) = spec.pres.extract_with_selective_replacement() { specs.extend(pres); } + if let Some(pres) = spec.structural_pres.extract_with_selective_replacement() { + specs.extend(pres); + } if let Some(posts) = spec.posts.extract_with_selective_replacement() { specs.extend(posts); } + if let Some(posts) = spec.panic_posts.extract_with_selective_replacement() { + specs.extend(posts); + } + if let Some(posts) = spec.structural_posts.extract_with_selective_replacement() { + specs.extend(posts); + } + if let Some(posts) = spec + .structural_panic_posts + .extract_with_selective_replacement() + { + specs.extend(posts); + } + if let Some(broken_pres) = spec.broken_pres.extract_with_selective_replacement() { + specs.extend(broken_pres); + } + if let Some(broken_posts) = spec.broken_posts.extract_with_selective_replacement() { + specs.extend(broken_posts); + } if let Some(Some(term)) = spec.terminates.extract_with_selective_replacement() { - specs.push(term.to_def_id()); + specs.push(*term); } if let Some(pledges) = spec.pledges.extract_with_selective_replacement() { specs.extend(pledges.iter().filter_map(|pledge| pledge.lhs)); specs.extend(pledges.iter().map(|pledge| pledge.rhs)); } - let is_trusted = spec.trusted.extract_inherit().expect("Expected trusted") + let is_trusted = ( + spec.trusted.extract_inherit().expect("Expected trusted") && + !spec.non_verified_pure.extract_inherit().expect("Expected non_verified_pure") + ) // It has to be non-extern_spec which is trusted (since extern_specs are always trusted) && (*def_id == spec.source || !def_id.is_local()); if spec.kind.is_pure().expect("Expected pure") && !is_trusted { @@ -106,6 +153,12 @@ impl DefSpecificationMap { if let Some(invariants) = spec.invariant.extract_with_selective_replacement() { specs.extend(invariants); } + if let Some(invariants) = spec + .structural_invariant + .extract_with_selective_replacement() + { + specs.extend(invariants); + } } (specs, pure_fns, predicates) } @@ -171,6 +224,11 @@ impl DefSpecificationMap { .values() .map(|spec| format!("{spec:?}")) .collect(); + let case_splits: Vec<_> = self + .prusti_case_splits + .values() + .map(|spec| format!("{spec:?}")) + .collect(); let refutations: Vec<_> = self .prusti_refutations .values() @@ -182,6 +240,7 @@ impl DefSpecificationMap { values.extend(type_specs); values.extend(asserts); values.extend(assumptions); + values.extend(case_splits); values.extend(refutations); if hide_uuids { let uuid = @@ -209,10 +268,19 @@ pub struct ProcedureSpecification { pub source: DefId, pub kind: SpecificationItem, pub pres: SpecificationItem>, + pub structural_pres: SpecificationItem>, pub posts: SpecificationItem>, + pub panic_posts: SpecificationItem>, + pub structural_posts: SpecificationItem>, + pub structural_panic_posts: SpecificationItem>, pub pledges: SpecificationItem>, pub trusted: SpecificationItem, - pub terminates: SpecificationItem>, + pub non_verified_pure: SpecificationItem, + pub no_panic: SpecificationItem, + pub no_panic_ensures_postcondition: SpecificationItem, + pub broken_pres: SpecificationItem>, + pub broken_posts: SpecificationItem>, + pub terminates: SpecificationItem>, pub purity: SpecificationItem>, // for type-conditional spec refinements } @@ -224,9 +292,18 @@ impl ProcedureSpecification { // defaults to an impure function kind: SpecificationItem::Inherent(ProcedureSpecificationKind::Impure), pres: SpecificationItem::Empty, + structural_pres: SpecificationItem::Empty, posts: SpecificationItem::Empty, + panic_posts: SpecificationItem::Empty, + structural_posts: SpecificationItem::Empty, + structural_panic_posts: SpecificationItem::Empty, + broken_pres: SpecificationItem::Empty, + broken_posts: SpecificationItem::Empty, pledges: SpecificationItem::Empty, trusted: SpecificationItem::Inherent(false), + non_verified_pure: SpecificationItem::Inherent(false), + no_panic: SpecificationItem::Inherent(false), + no_panic_ensures_postcondition: SpecificationItem::Inherent(false), terminates: SpecificationItem::Inherent(None), purity: SpecificationItem::Inherent(None), } @@ -264,7 +341,10 @@ impl ProcedureSpecificationKind { #[derive(Debug, Clone)] pub enum LoopSpecification { - Invariant(LocalDefId), + Invariant { + def_id: LocalDefId, + is_structural: bool, + }, Variant(LocalDefId), } @@ -276,6 +356,7 @@ pub struct TypeSpecification { // `extern_spec` for type invs is supported it could differ. pub source: DefId, pub invariant: SpecificationItem>, + pub structural_invariant: SpecificationItem>, pub trusted: SpecificationItem, pub model: Option<(String, LocalDefId)>, pub counterexample_print: Vec<(Option, LocalDefId)>, @@ -286,6 +367,7 @@ impl TypeSpecification { TypeSpecification { source, invariant: SpecificationItem::Empty, + structural_invariant: SpecificationItem::Empty, trusted: SpecificationItem::Inherent(false), model: None, counterexample_print: vec![], @@ -296,11 +378,18 @@ impl TypeSpecification { #[derive(Debug, Clone)] pub struct PrustiAssertion { pub assertion: LocalDefId, + pub is_structural: bool, } #[derive(Debug, Clone)] pub struct PrustiAssumption { pub assumption: LocalDefId, + pub is_structural: bool, +} + +#[derive(Debug, Clone)] +pub struct PrustiCaseSplit { + pub assertion: LocalDefId, } #[derive(Debug, Clone)] @@ -318,6 +407,21 @@ pub struct GhostEnd { pub marker: LocalDefId, } +#[derive(Debug, Clone)] +pub struct SpecificationRegionBegin { + pub marker: LocalDefId, +} + +#[derive(Debug, Clone)] +pub struct SpecificationRegionEnd { + pub marker: LocalDefId, +} + +#[derive(Debug, Clone)] +pub struct SpecificationExpression { + pub expression: LocalDefId, +} + /// The base container to store a contract of a procedure. /// A contract can be divided into multiple specifications: /// - **Base spec**: A spec without constraints. @@ -433,6 +537,26 @@ impl SpecGraph { } } + /// Attaches the structural precondition `structural_pre` to this + /// [SpecGraph]. + /// + /// If this precondition has a constraint it will be attached to the + /// corresponding constrained spec, otherwise just to the base spec. + pub fn add_structural_precondition<'tcx>(&mut self, pre: LocalDefId, env: &Environment<'tcx>) { + match self.get_constraint(pre, env) { + None => { + self.base_spec.structural_pres.push(pre.to_def_id()); + // Preconditions are explicitly not copied (as opposed to postconditions) + // This would always violate behavioral subtyping rules + } + Some(constraint) => { + self.get_constrained_spec_mut(constraint) + .structural_pres + .push(pre.to_def_id()); + } + } + } + /// Attaches the postcondition `post` to this [SpecGraph]. /// /// If this postcondition has a constraint it will be attached to the corresponding @@ -453,6 +577,89 @@ impl SpecGraph { } } + /// Attaches the panic postcondition `panic_post` to this [SpecGraph]. + /// + /// If this panic postcondition has a constraint it will be attached to the + /// corresponding constrained spec **and** the base spec, otherwise just to + /// the base spec. + pub fn add_panic_postcondition<'tcx>(&mut self, post: LocalDefId, env: &Environment<'tcx>) { + match self.get_constraint(post, env) { + None => { + self.base_spec.panic_posts.push(post.to_def_id()); + self.specs_with_constraints + .values_mut() + .for_each(|s| s.panic_posts.push(post.to_def_id())); + } + Some(obligation) => { + self.get_constrained_spec_mut(obligation) + .panic_posts + .push(post.to_def_id()); + } + } + } + + /// Attaches the structural postcondition `structural_post` to this + /// [SpecGraph]. + /// + /// If this structural postcondition has a constraint it will be attached to + /// the corresponding constrained spec **and** the base spec, otherwise just + /// to the base spec. + pub fn add_structural_postcondition<'tcx>( + &mut self, + post: LocalDefId, + env: &Environment<'tcx>, + ) { + match self.get_constraint(post, env) { + None => { + self.base_spec.structural_posts.push(post.to_def_id()); + self.specs_with_constraints + .values_mut() + .for_each(|s| s.structural_posts.push(post.to_def_id())); + } + Some(obligation) => { + self.get_constrained_spec_mut(obligation) + .structural_posts + .push(post.to_def_id()); + } + } + } + + /// Attaches the panic structural postcondition `structural_panic_post` to this + /// [SpecGraph]. + /// + /// If this structural postcondition has a constraint it will be attached to + /// the corresponding constrained spec **and** the base spec, otherwise just + /// to the base spec. + pub fn add_structural_panic_postcondition<'tcx>( + &mut self, + post: LocalDefId, + env: &Environment<'tcx>, + ) { + match self.get_constraint(post, env) { + None => { + self.base_spec.structural_panic_posts.push(post.to_def_id()); + self.specs_with_constraints + .values_mut() + .for_each(|s| s.structural_panic_posts.push(post.to_def_id())); + } + Some(obligation) => { + self.get_constrained_spec_mut(obligation) + .structural_panic_posts + .push(post.to_def_id()); + } + } + } + + /// Sets the broken precondition for the base spec and all constrained specs. + pub fn add_broken_precondition(&mut self, broken_precondition: DefId) { + self.base_spec.broken_pres.push(broken_precondition); + } + + /// Sets the broken precondition for the base spec and all constrained specs. + pub fn add_broken_postcondition(&mut self, broken_postcondition: DefId) { + self.base_spec.broken_posts.push(broken_postcondition); + } + pub fn add_purity<'tcx>(&mut self, purity: LocalDefId, env: &Environment<'tcx>) { match self.get_constraint(purity, env) { None => { @@ -486,8 +693,35 @@ impl SpecGraph { .for_each(|s| s.trusted.set(trusted)); } + /// Sets the non_verified_pure flag for the base spec and all constrained specs. + pub fn set_non_verified_pure(&mut self, non_verified_pure: bool) { + self.base_spec.non_verified_pure.set(non_verified_pure); + self.specs_with_constraints + .values_mut() + .for_each(|s| s.non_verified_pure.set(non_verified_pure)); + } + + /// Sets the no_panic flag for the base spec and all constrained specs. + pub fn set_no_panic(&mut self, no_panic: bool) { + self.base_spec.no_panic.set(no_panic); + self.specs_with_constraints + .values_mut() + .for_each(|s| s.no_panic.set(no_panic)); + } + + /// Sets the no_panic_ensures_postcondition flag for the base spec and all constrained specs. + pub fn set_no_panic_ensures_postcondition(&mut self, no_panic_ensures_postcondition: bool) { + self.base_spec + .no_panic_ensures_postcondition + .set(no_panic_ensures_postcondition); + self.specs_with_constraints.values_mut().for_each(|s| { + s.no_panic_ensures_postcondition + .set(no_panic_ensures_postcondition) + }); + } + /// Sets the termination flag for the base spec and all constrained specs. - pub fn set_terminates(&mut self, terminates: LocalDefId) { + pub fn set_terminates(&mut self, terminates: DefId) { self.base_spec.terminates.set(Some(terminates)); self.specs_with_constraints .values_mut() @@ -776,10 +1010,33 @@ impl Refinable for ProcedureSpecification { ProcedureSpecification { source: self.source, pres: self.pres.refine(replace_empty(&EMPTYL, &other.pres)), + structural_pres: self + .structural_pres + .refine(replace_empty(&EMPTYL, &other.structural_pres)), posts: self.posts.refine(replace_empty(&EMPTYL, &other.posts)), + panic_posts: self + .panic_posts + .refine(replace_empty(&EMPTYL, &other.panic_posts)), + structural_posts: self + .structural_posts + .refine(replace_empty(&EMPTYL, &other.structural_posts)), + structural_panic_posts: self + .structural_panic_posts + .refine(replace_empty(&EMPTYL, &other.structural_panic_posts)), + broken_pres: self + .broken_pres + .refine(replace_empty(&EMPTYL, &other.broken_pres)), + broken_posts: self + .broken_posts + .refine(replace_empty(&EMPTYL, &other.broken_posts)), pledges: self.pledges.refine(replace_empty(&EMPTYP, &other.pledges)), kind: self.kind.refine(&other.kind), trusted: self.trusted.refine(&other.trusted), + non_verified_pure: self.non_verified_pure.refine(&other.non_verified_pure), + no_panic: self.no_panic.refine(&other.no_panic), + no_panic_ensures_postcondition: self + .no_panic_ensures_postcondition + .refine(&other.no_panic_ensures_postcondition), terminates: self.terminates.refine(&other.terminates), purity: self.purity.refine(&other.purity), } diff --git a/prusti-server/src/backend.rs b/prusti-server/src/backend.rs index 5a36ad6c399..2a152e5fd70 100644 --- a/prusti-server/src/backend.rs +++ b/prusti-server/src/backend.rs @@ -21,7 +21,8 @@ impl<'a> Backend<'a> { ast_utils.with_local_frame(16, || { let ast_factory = context.new_ast_factory(); - let viper_program = program.to_viper(LoweringContext::default(), &ast_factory); + let context = &mut LoweringContext::default(); + let viper_program = program.to_viper(context, &ast_factory); if config::dump_viper_program() { stopwatch.start_next("dumping viper program"); diff --git a/prusti-server/src/process_verification.rs b/prusti-server/src/process_verification.rs index 446407c5087..1a9973f389b 100644 --- a/prusti-server/src/process_verification.rs +++ b/prusti-server/src/process_verification.rs @@ -49,9 +49,8 @@ pub fn process_verification_request<'v, 't: 'v>( let build_or_dump_viper_program = || { let mut stopwatch = Stopwatch::start("prusti-server", "construction of JVM objects"); let ast_factory = verification_context.new_ast_factory(); - let viper_program = request - .program - .to_viper(prusti_common::vir::LoweringContext::default(), &ast_factory); + let mut context = prusti_common::vir::LoweringContext::default(); + let viper_program = request.program.to_viper(&mut context, &ast_factory); if config::dump_viper_program() { stopwatch.start_next("dumping viper program"); @@ -106,7 +105,7 @@ pub fn process_verification_request<'v, 't: 'v>( let mut backend = match request.backend_config.backend { VerificationBackend::Carbon | VerificationBackend::Silicon => Backend::Viper( new_viper_verifier( - request.program.get_name(), + &request.program.get_name_with_check_mode(), verification_context, request.backend_config, ), @@ -165,11 +164,22 @@ fn new_viper_verifier<'v, 't: 'v>( //"--printTranslatedProgram".to_string(), ]) } - VerificationBackend::Carbon => verifier_args.extend(vec![ - "--boogieOpt".to_string(), - format!("/logPrefix {log_dir_str}"), - //"--print".to_string(), "./log/boogie_program/program.bpl".to_string(), - ]), + VerificationBackend::Carbon => { + let mut found_boogie_opt = false; + for arg in &mut verifier_args { + if arg.starts_with("--boogieOpt") { + arg.push_str(&format!(" /logPrefix:{log_dir_str}")); + found_boogie_opt = true; + } + } + if !found_boogie_opt { + verifier_args.extend(vec![ + "--boogieOpt".to_string(), + format!("/logPrefix {log_dir_str}"), + //"--print".to_string(), "./log/boogie_program/program.bpl".to_string(), + ]) + } + } } } else { report_path = None; diff --git a/prusti-server/src/verification_request.rs b/prusti-server/src/verification_request.rs index 6f33840ea70..31331416d64 100644 --- a/prusti-server/src/verification_request.rs +++ b/prusti-server/src/verification_request.rs @@ -40,6 +40,14 @@ impl ViperBackendConfig { if config::use_more_complete_exhale() { verifier_args.push("--enableMoreCompleteExhale".to_string()); } + if config::use_carbon_qps() { + verifier_args.push("--maskHeapMode".to_string()); + // verifier_args.push("--carbonQPs".to_string()); + // verifier_args.push("--carbonFunctions".to_string()); + } + if config::use_z3_api() { + verifier_args.push("--prover=Z3-API".to_string()); + } if config::counterexample() { verifier_args.push("--counterexample".to_string()); verifier_args.push("mapped".to_string()); @@ -48,7 +56,6 @@ impl ViperBackendConfig { verifier_args.push("--numberOfParallelVerifiers".to_string()); verifier_args.push(number.to_string()); } - verifier_args.extend(vec![ "--assertTimeout".to_string(), config::assert_timeout().to_string(), @@ -56,9 +63,12 @@ impl ViperBackendConfig { // model.partial changes the default case of functions in counterexamples // to #unspecified format!( - "smt.qi.eager_threshold={} model.partial={}", + "smt.qi.eager_threshold={} model.partial={} \ + smt.arith.nl={} smt.arith.nl.gb={}", config::smt_qi_eager_threshold(), - config::counterexample() + config::counterexample(), + config::smt_use_nonlinear_arithmetic_solver(), + config::smt_use_nonlinear_arithmetic_solver(), ), "--logLevel".to_string(), "ERROR".to_string(), @@ -70,7 +80,13 @@ impl ViperBackendConfig { } } VerificationBackend::Carbon => { - verifier_args.extend(vec!["--disableAllocEncoding".to_string()]); + verifier_args.extend(vec![ + "--disableAllocEncoding".to_string(), + format!( + "--boogieOpt=/proverOpt:O:smt.qi.eager_threshold={}", + config::smt_qi_eager_threshold() + ), + ]); } } Self { diff --git a/prusti-tests/tests/compiletest.rs b/prusti-tests/tests/compiletest.rs index c1dafc5a37b..942e7ffce69 100644 --- a/prusti-tests/tests/compiletest.rs +++ b/prusti-tests/tests/compiletest.rs @@ -197,6 +197,11 @@ fn test_runner(_tests: &[&()]) { run_verification_overflow("verify_overflow", &filter); save_verification_cache(); + // Test the verifier with overflow checks enabled. + println!("[verify_overflow]"); + run_verification_overflow("verify_overflow_core_proof", &filter); + save_verification_cache(); + // Test the verifier with test cases that only partially verify due to known open issues. // The purpose of these tests is two-fold: 1. these tests help prevent potential further // regressions, because the tests also test code paths not covered by other tests; and diff --git a/prusti-tests/tests/verify_overflow/fail/core_proof/arithmetic.rs b/prusti-tests/tests/verify_overflow/fail/core_proof/arithmetic.rs index cad371c3c2c..dbb9ddfddcd 100644 --- a/prusti-tests/tests/verify_overflow/fail/core_proof/arithmetic.rs +++ b/prusti-tests/tests/verify_overflow/fail/core_proof/arithmetic.rs @@ -17,7 +17,13 @@ fn test2() { } fn test3(a: i32, b: i32) -> i32 { - a + b //~ ERROR assertion might fail with "attempt to add with overflow" + a + b //~ ERROR: the operation may overflow or underflow +} + +#[requires(-100 < a && a < 100)] +#[requires(-100 < b && b < 100)] +fn test3_core_proof(a: i32, b: i32) -> i32 { + a + b //~ ERROR: the operation may overflow or underflow } fn test4() { diff --git a/prusti-tests/tests/verify_overflow/fail/core_proof/custom_heap_encoding/simple.rs b/prusti-tests/tests/verify_overflow/fail/core_proof/custom_heap_encoding/simple.rs new file mode 100644 index 00000000000..0afc4a2d794 --- /dev/null +++ b/prusti-tests/tests/verify_overflow/fail/core_proof/custom_heap_encoding/simple.rs @@ -0,0 +1,17 @@ +// compile-flags: -Punsafe_core_proof=true + +use prusti_contracts::*; + +unsafe fn test_assert1() { + let a = 5; + assert!(a == 5); +} + +unsafe fn test_assert2() { + let a = 5; + assert!(a == 6); //~ ERROR: the asserted expression might not hold +} + +#[trusted] +fn main() {} + diff --git a/prusti-tests/tests/verify_overflow/fail/core_proof/forall.rs b/prusti-tests/tests/verify_overflow/fail/core_proof/forall.rs new file mode 100644 index 00000000000..14a44776c62 --- /dev/null +++ b/prusti-tests/tests/verify_overflow/fail/core_proof/forall.rs @@ -0,0 +1,29 @@ +// compile-flags: -Punsafe_core_proof=true + +use prusti_contracts::*; + +fn test_forall_1() -> usize { + let res = 5; + prusti_assert!( + forall(|x: usize| true) || false + ); + res +} + +fn test_forall_2() -> usize { + let res = 5; + prusti_assert!( + forall(|x: usize| x >= 0) + ); + res +} + +fn test_forall_3() -> usize { + let res = 5; + prusti_assert!( + forall(|x: usize| x >= 1) //~ ERROR: the asserted expression might not hold + ); + res +} + +fn main() {} diff --git a/prusti-tests/tests/verify_overflow/fail/core_proof/framing.rs b/prusti-tests/tests/verify_overflow/fail/core_proof/framing.rs new file mode 100644 index 00000000000..83c1d7dfd57 --- /dev/null +++ b/prusti-tests/tests/verify_overflow/fail/core_proof/framing.rs @@ -0,0 +1,408 @@ +// compile-flags: -Punsafe_core_proof=true -Penable_type_invariants=true + +#![deny(unsafe_op_in_unsafe_fn)] + +use prusti_contracts::*; + +// TODO: Check only on the definition side. Add tests. + +//#[ensures(!result.is_null() ==> own!((*result).x) && unsafe { (*result).x } == 5)] +//unsafe fn test01() -> *mut Pair { + //let p = unsafe { + //alloc(std::mem::size_of::(), std::mem::align_of::()) + //}; + //let pair = (p as *mut Pair); + //if !pair.is_null() { + //split!(*pair); + //unsafe { (*pair).x = 5; } + //} + //pair +//} + +//#[ensures(!result.is_null() ==> unsafe { (*result).x } == 5)] //~ ERROR: the place must be framed by permissions +//unsafe fn test01_non_framed() -> *mut Pair { + //let p = unsafe { + //alloc(std::mem::size_of::(), std::mem::align_of::()) + //}; + //let pair = (p as *mut Pair); + //if !pair.is_null() { + //split!(*pair); + //unsafe { (*pair).x = 5; } + //} + //pair +//} + +//#[ensures(!result.is_null() ==> own!(*result) && unsafe { (*result).x } == 5)] +//unsafe fn test02() -> *mut Pair { + //let p = unsafe { + //alloc(std::mem::size_of::(), std::mem::align_of::()) + //}; + //let pair = (p as *mut Pair); + //if !pair.is_null() { + //split!(*pair); + //unsafe { (*pair).y = 4; } + //unsafe { (*pair).x = 5; } + //pack!(*pair); + //} + //pair +//} + +//#[ensures(!result.is_null() ==> own!(*result) && unsafe { (*result).x } == 5)] +//unsafe fn test02_missing_pack() -> *mut Pair { //~ ERROR: there might be insufficient permission to dereference a raw pointer + //let p = unsafe { + //alloc(std::mem::size_of::(), std::mem::align_of::()) + //}; + //let pair = (p as *mut Pair); + //if !pair.is_null() { + //split!(*pair); + //unsafe { (*pair).y = 4; } + //unsafe { (*pair).x = 5; } + //} + //pair +//} + +//#[ensures(!result.is_null() ==> unsafe { (*result).x } == 5)] //~ ERROR: there might be insufficient permission to dereference a raw pointer + ////^ ERROR: the postcondition might not be self-framing +//unsafe fn test02_non_framed() -> *mut Pair { + //let p = unsafe { + //alloc(std::mem::size_of::(), std::mem::align_of::()) + //}; + //let pair = (p as *mut Pair); + //if !pair.is_null() { + //split!(*pair); + //unsafe { (*pair).y = 4; } + //unsafe { (*pair).x = 5; } + //pack!(*pair); + //} + //pair +//} + +//#[ensures(!result.is_null() ==> own!(*result) && unsafe { (*result).x } == 5)] //~ ERROR: only unsafe functions can use permissions in their contracts +//fn test02_safe() -> *mut Pair { + //let p = unsafe { + //alloc(std::mem::size_of::(), std::mem::align_of::()) + //}; + //let pair = (p as *mut Pair); + //if !pair.is_null() { + //split!(*pair); + //unsafe { (*pair).y = 4; } + //unsafe { (*pair).x = 5; } + //pack!(*pair); + //} + //pair +//} + +//#[ensures(!result.is_null() ==> //~ ERROR: permission predicates can be only in positive positions + //own!(*result) && unsafe { !(*result).is_null() } ==> + //own!(**result) && unsafe { (**result).x } == 5)] +//unsafe fn test03() -> *mut *mut Pair { + //let pp = unsafe { + //alloc(std::mem::size_of::<*mut Pair>(), std::mem::align_of::<*mut Pair>()) + //}; + //let ppair = (pp as *mut *mut Pair); + //ppair +//} + +//#[ensures(!result.is_null() ==> + //own!(*result) && ( + //unsafe { !(*result).is_null() } ==> + //own!(**result) && unsafe { (**result).x } == 5))] +//unsafe fn test04() -> *mut *mut Pair { + //let p = unsafe { + //alloc(std::mem::size_of::(), std::mem::align_of::()) + //}; + //let pp = unsafe { + //alloc(std::mem::size_of::<*mut Pair>(), std::mem::align_of::<*mut Pair>()) + //}; + //let pair = (p as *mut Pair); + //let ppair = (pp as *mut *mut Pair); + //let mut v = 0; + //if !ppair.is_null() { + //if !pair.is_null() { + //split!(*pair); + //unsafe { (*pair).y = 4; } + //unsafe { (*pair).x = 5; } + //pack!(*pair); + //} + //unsafe { *ppair = pair; } + //if !pair.is_null() { + //unpack!(**ppair); + //unsafe { v = (**ppair).x; } + //pack!(**ppair); + //} + //} + //ppair +//} + +//#[ensures(!result.is_null() ==> //~ ERROR: postcondition might not hold + //own!(*result) && ( + //unsafe { !(*result).is_null() } ==> + //own!(**result) && unsafe { (**result).x } == 6))] +//unsafe fn test04_wrong_value() -> *mut *mut Pair { + //let p = unsafe { + //alloc(std::mem::size_of::(), std::mem::align_of::()) + //}; + //let pp = unsafe { + //alloc(std::mem::size_of::<*mut Pair>(), std::mem::align_of::<*mut Pair>()) + //}; + //let pair = (p as *mut Pair); + //let ppair = (pp as *mut *mut Pair); + //let mut v = 0; + //if !ppair.is_null() { + //if !pair.is_null() { + //split!(*pair); + //unsafe { (*pair).y = 4; } + //unsafe { (*pair).x = 5; } + //pack!(*pair); + //} + //unsafe { *ppair = pair; } + //if !pair.is_null() { + //unpack!(**ppair); + //unsafe { v = (**ppair).x; } + //pack!(**ppair); + //} + //} + //ppair +//} + +//#[ensures(!result.1.is_null() ==> + //own!(*result.1) && ( + //unsafe { !(*result.1).is_null() } ==> + //own!(**result.1) && unsafe { (**result.1).x } == 5 && + //result.0 == 5))] +//unsafe fn test05() -> (i32, *mut *mut Pair) { + //let p = unsafe { + //alloc(std::mem::size_of::(), std::mem::align_of::()) + //}; + //let pp = unsafe { + //alloc(std::mem::size_of::<*mut Pair>(), std::mem::align_of::<*mut Pair>()) + //}; + //let pair = (p as *mut Pair); + //let ppair = (pp as *mut *mut Pair); + //let mut v = 0; + //if !ppair.is_null() { + //if !pair.is_null() { + //split!(*pair); + //unsafe { (*pair).y = 4; } + //unsafe { (*pair).x = 5; } + //pack!(*pair); + //} + //unsafe { *ppair = pair; } + //if !pair.is_null() { + //unpack!(**ppair); + //unsafe { v = (**ppair).x; } + //pack!(**ppair); + //} + //} + //(v, ppair) +//} + +//#[ensures(!result.1.is_null() ==> //~ ERROR: postcondition might not hold + //own!(*result.1) && ( + //unsafe { !(*result.1).is_null() } ==> + //own!(**result.1) && unsafe { (**result.1).x } == 6 && + //result.0 == 5))] +//unsafe fn test05_wrong_value_1() -> (i32, *mut *mut Pair) { + //let p = unsafe { + //alloc(std::mem::size_of::(), std::mem::align_of::()) + //}; + //let pp = unsafe { + //alloc(std::mem::size_of::<*mut Pair>(), std::mem::align_of::<*mut Pair>()) + //}; + //let pair = (p as *mut Pair); + //let ppair = (pp as *mut *mut Pair); + //let mut v = 0; + //if !ppair.is_null() { + //if !pair.is_null() { + //split!(*pair); + //unsafe { (*pair).y = 4; } + //unsafe { (*pair).x = 5; } + //pack!(*pair); + //} + //unsafe { *ppair = pair; } + //if !pair.is_null() { + //unpack!(**ppair); + //unsafe { v = (**ppair).x; } + //pack!(**ppair); + //} + //} + //(v, ppair) +//} + +//#[ensures(!result.1.is_null() ==> //~ ERROR: postcondition might not hold + //own!(*result.1) && ( + //unsafe { !(*result.1).is_null() } ==> + //own!(**result.1) && unsafe { (**result.1).x } == 5 && + //result.0 == 6))] +//unsafe fn test05_wrong_value_2() -> (i32, *mut *mut Pair) { + //let p = unsafe { + //alloc(std::mem::size_of::(), std::mem::align_of::()) + //}; + //let pp = unsafe { + //alloc(std::mem::size_of::<*mut Pair>(), std::mem::align_of::<*mut Pair>()) + //}; + //let pair = (p as *mut Pair); + //let ppair = (pp as *mut *mut Pair); + //let mut v = 0; + //if !ppair.is_null() { + //if !pair.is_null() { + //split!(*pair); + //unsafe { (*pair).y = 4; } + //unsafe { (*pair).x = 5; } + //pack!(*pair); + //} + //unsafe { *ppair = pair; } + //if !pair.is_null() { + //unpack!(**ppair); + //unsafe { v = (**ppair).x; } + //pack!(**ppair); + //} + //} + //(v, ppair) +//} + +//#[structural_invariant(!self.p.is_null() ==> own!(*self.p) && unsafe { (*self.p).x } == 5)] +//struct T6 { + //p: *mut Pair, +//} + +//fn test06(_: T6) {} + +//#[structural_invariant(!self.p.is_null() ==> unsafe { (*self.p).x } == 5)] +//struct T6MissingOwn { //~ ERROR: there might be insufficient permission to dereference a raw pointer + //p: *mut Pair, +//} + +//fn test06_missing_own(_: T6MissingOwn) {} + +//#[structural_invariant(!self.p.is_null() ==> own!(*self.p))] +#[structural_invariant(!self.p.is_null() ==> own!(*self.p) && unsafe {(*self.p).x} == 5)] +struct T4 { + p: *mut Pair, +} + +//#[ensures(!result.p.is_null() ==> unsafe { (*result.p).x } == 5)] +//unsafe fn test04() -> T4 { + //let p = unsafe { + //alloc(std::mem::size_of::(), std::mem::align_of::()) + //}; + //let pair = (p as *mut Pair); + //if !pair.is_null() { + //split!(*pair); + //unsafe { (*pair).y = 4; } + //unsafe { (*pair).x = 5; } + //pack!(*pair); + //} + //T4 { p: pair } +//} + +//#[ensures(unsafe { (*result.p).x } == 5)] +//unsafe fn test04_not_framed() -> T4 { //~ ERROR: there might be insufficient permission to dereference a raw pointer + ////^ ERROR: the postcondition might not be self-framing. + //let p = unsafe { + //alloc(std::mem::size_of::(), std::mem::align_of::()) + //}; + //let pair = (p as *mut Pair); + //if !pair.is_null() { + //split!(*pair); + //unsafe { (*pair).y = 4; } + //unsafe { (*pair).x = 5; } + //pack!(*pair); + //} + //T4 { p: pair } +//} + +#[structural_invariant(!self.p.is_null() ==> own!((*self.p).x))] +struct T5 { + p: *mut Pair, +} + +#[ensures(!result.p.is_null() ==> unsafe { (*result.p).x } == 5)] +fn test05_safe() -> T5 { + let p = unsafe { + alloc(std::mem::size_of::(), std::mem::align_of::()) + }; + let pair = (p as *mut Pair); + if !pair.is_null() { + split!(*pair); + //unsafe { (*pair).y = 4; } + unsafe { (*pair).x = 5; } + //pack!(*pair); + } + T5 { p: pair } +} + +//#[ensures(unsafe { (*result.p).x } == 5)] //~ ERROR: postcondition might not hold +//fn test04_safe_not_framed() -> T4 { //~ ERROR: there might be insufficient permission to dereference a raw pointer + //let p = unsafe { + //alloc(std::mem::size_of::(), std::mem::align_of::()) + //}; + //let pair = (p as *mut Pair); + //if !pair.is_null() { + //split!(*pair); + //unsafe { (*pair).y = 4; } + //unsafe { (*pair).x = 5; } + //pack!(*pair); + //} + //T4 { p: pair } +//} + +//#[structural_invariant(!self.p.is_null() ==> own!((*self.p).x))] +//struct T2 { + //p: *mut Pair, +//} + +//#[ensures(!result.p.is_null() ==> framed!((*result.p).x, unsafe { (*result.p).x }) == 5)] +//fn test03() -> T1 { + //let p = unsafe { + //alloc(std::mem::size_of::(), std::mem::align_of::()) + //}; + //let pair = (p as *mut Pair); + //if !pair.is_null() { + //split!(*pair); + //unsafe { (*pair).y = 4; } + //unsafe { (*pair).x = 5; } + //pack!(*pair); + //} + //T1 { p: pair } +//} + +//#[ensures(!result.p.is_null() ==> unsafe { (*result.p).x } == 5)] //~ ERROR: Permissions +//fn test03_non_framed() -> T1 { + //let p = unsafe { + //alloc(std::mem::size_of::(), std::mem::align_of::()) + //}; + //let pair = (p as *mut Pair); + //if !pair.is_null() { + //split!(*pair); + //unsafe { (*pair).x = 5; } + //} + //T1 { p: pair } +//} + +#[trusted] +#[requires(align > 0)] +#[ensures(!result.is_null() ==> ( + raw!(*result, size) && + raw_dealloc!(*result, size, align) +))] +// https://doc.rust-lang.org/alloc/alloc/fn.alloc.html +unsafe fn alloc(size: usize, align: usize) -> *mut u8 { + unimplemented!(); +} + +#[trusted] +#[requires( + raw!(*ptr, size) && + raw_dealloc!(*ptr, size, align) +)] +unsafe fn dealloc(ptr: *mut u8, size: usize, align: usize) { + unimplemented!(); +} + +struct Pair { + x: i32, + y: i32, +} + +fn main() {} diff --git a/prusti-tests/tests/verify_overflow/fail/core_proof/framing/functions.rs b/prusti-tests/tests/verify_overflow/fail/core_proof/framing/functions.rs new file mode 100644 index 00000000000..25deada0649 --- /dev/null +++ b/prusti-tests/tests/verify_overflow/fail/core_proof/framing/functions.rs @@ -0,0 +1,47 @@ +// compile-flags: -Punsafe_core_proof=true -Penable_type_invariants=true + +#![deny(unsafe_op_in_unsafe_fn)] + +use prusti_contracts::*; + +#[structural_invariant(!self.p.is_null() ==> own!(*self.p))] +struct T1 { + p: *mut i32, +} + +#[pure] +fn test1_get_p(x: &T1) -> i32 { + if self.p.is_null() { + 0 + } else { + unsafe { *self.p } + } +} + + +#[trusted] +#[requires(align > 0)] +#[ensures(!result.is_null() ==> ( + raw!(*result, size) && + raw_dealloc!(*result, size, align) +))] +// https://doc.rust-lang.org/alloc/alloc/fn.alloc.html +unsafe fn alloc(size: usize, align: usize) -> *mut u8 { + unimplemented!(); +} + +#[trusted] +#[requires( + raw!(*ptr, size) && + raw_dealloc!(*ptr, size, align) +)] +unsafe fn dealloc(ptr: *mut u8, size: usize, align: usize) { + unimplemented!(); +} + +struct Pair { + x: i32, + y: i32, +} + +fn main() {} diff --git a/prusti-tests/tests/verify_overflow/fail/core_proof/framing/simple.rs b/prusti-tests/tests/verify_overflow/fail/core_proof/framing/simple.rs new file mode 100644 index 00000000000..cd8b3c7991e --- /dev/null +++ b/prusti-tests/tests/verify_overflow/fail/core_proof/framing/simple.rs @@ -0,0 +1,244 @@ +// compile-flags: -Punsafe_core_proof=true -Penable_type_invariants=true + +#![deny(unsafe_op_in_unsafe_fn)] + +use prusti_contracts::*; + +#[ensures(!result.is_null() ==> unsafe { *result } == 5)] //~ ERROR: the place must be framed by permissions +fn test01_safe() -> *mut i32 { + let p_alloc = unsafe { + alloc(std::mem::size_of::(), std::mem::align_of::()) + }; + let p = (p_alloc as *mut i32); + if !p.is_null() { + unsafe { *p = 5; } + } + p +} + +#[ensures(!result.is_null() ==> own!(*result) && unsafe { *result } == 5)] //~ ERROR: only unsafe functions can use permissions in their contracts +fn test02_safe() -> *mut i32 { + let p_alloc = unsafe { + alloc(std::mem::size_of::(), std::mem::align_of::()) + }; + let p = (p_alloc as *mut i32); + if !p.is_null() { + unsafe { *p = 5; } + } + p +} + +#[ensures(!result.is_null() ==> own!(*result) && unsafe { *result } == 5)] +unsafe fn test03_unsafe() -> *mut i32 { + let p_alloc = unsafe { + alloc(std::mem::size_of::(), std::mem::align_of::()) + }; + let p = (p_alloc as *mut i32); + if !p.is_null() { + unsafe { *p = 5; } + } + p +} + +#[ensures(!result.is_null() ==> own!(*result) && unsafe { *result } == 6)] //~ ERROR: postcondition might not hold. +unsafe fn test04_unsafe() -> *mut i32 { + let p_alloc = unsafe { + alloc(std::mem::size_of::(), std::mem::align_of::()) + }; + let p = (p_alloc as *mut i32); + if !p.is_null() { + unsafe { *p = 5; } + } + p +} + +unsafe fn test05_unsafe() -> *mut i32 { + let p_alloc = unsafe { + alloc(std::mem::size_of::(), std::mem::align_of::()) + }; + let p = (p_alloc as *mut i32); + unsafe { *p = 5; } //~ ERROR: the accessed memory location must be allocated and uninitialized + p +} + +#[ensures(own!(*result))] +unsafe fn test06_unsafe() -> *mut i32 { //~ ERROR: there might be insufficient permission to dereference a raw pointer + let p_alloc = unsafe { + alloc(std::mem::size_of::(), std::mem::align_of::()) + }; + let p = (p_alloc as *mut i32); + if !p.is_null() { + unsafe { *p = 5; } + } + p +} + +unsafe fn test07_unsafe() -> *mut i32 { + let p_alloc = unsafe { + alloc(std::mem::size_of::(), std::mem::align_of::()) + }; + let p = (p_alloc as *mut i32); + if !p.is_null() { + unsafe { *p = 5; } + assert!(unsafe { *p } == 5); + } + p +} + +fn test07_safe() -> *mut i32 { + let p_alloc = unsafe { + alloc(std::mem::size_of::(), std::mem::align_of::()) + }; + let p = (p_alloc as *mut i32); + if !p.is_null() { + unsafe { *p = 5; } + assert!(unsafe { *p } == 5); + } + p +} + +fn callee() {} + +unsafe fn test08_unsafe() -> *mut i32 { + let p_alloc = unsafe { + alloc(std::mem::size_of::(), std::mem::align_of::()) + }; + let p = (p_alloc as *mut i32); + if !p.is_null() { + unsafe { *p = 5; } + callee(); + assert!(unsafe { *p } == 5); + } + p +} + +fn test08_safe() -> *mut i32 { + let p_alloc = unsafe { + alloc(std::mem::size_of::(), std::mem::align_of::()) + }; + let p = (p_alloc as *mut i32); + if !p.is_null() { + unsafe { *p = 5; } + callee(); + // Calling non-pure functions havoc the heap when in permissions + // are disabled: + assert!(unsafe { *p } == 5); //~ ERROR: the type invariant of the constructed object might not hold + //^ ERROR: the type invariant of the constructed object might not hold + } + p +} + +#[pure] +#[terminates] +fn pure_callee() {} + +unsafe fn test09_unsafe() -> *mut i32 { + let p_alloc = unsafe { + alloc(std::mem::size_of::(), std::mem::align_of::()) + }; + let p = (p_alloc as *mut i32); + if !p.is_null() { + unsafe { *p = 5; } + pure_callee(); + assert!(unsafe { *p } == 5); + } + p +} + +fn test09_safe() -> *mut i32 { + let p_alloc = unsafe { + alloc(std::mem::size_of::(), std::mem::align_of::()) + }; + let p = (p_alloc as *mut i32); + if !p.is_null() { + unsafe { *p = 5; } + pure_callee(); + assert!(unsafe { *p } == 5); + } + p +} + +#[ensures(!result.0.is_null() ==> unsafe { *result.0 } == 5)] //~ ERROR: the place must be framed by permissions +fn test11_safe() -> (*mut i32, *mut i32) { + let p_alloc = unsafe { + alloc(std::mem::size_of::(), std::mem::align_of::()) + }; + let p = (p_alloc as *mut i32); + if !p.is_null() { + unsafe { *p = 5; } + } + (p, p) +} + +#[ensures(!result.0.is_null() ==> own!(*result.0) && unsafe { *result.0 } == 5)] //~ ERROR: only unsafe functions can use permissions in their contracts +fn test12_safe() -> (*mut i32, *mut i32) { + let p_alloc = unsafe { + alloc(std::mem::size_of::(), std::mem::align_of::()) + }; + let p = (p_alloc as *mut i32); + if !p.is_null() { + unsafe { *p = 5; } + } + (p, p) +} + +#[ensures(!result.0.is_null() ==> own!(*result.0) && unsafe { *result.0 } == 5)] +unsafe fn test13_unsafe() -> (*mut i32, *mut i32) { + let p_alloc = unsafe { + alloc(std::mem::size_of::(), std::mem::align_of::()) + }; + let p = (p_alloc as *mut i32); + if !p.is_null() { + unsafe { *p = 5; } + } + (p, p) +} + +// Note: This works and `test14_unsafe_semantic_aliasing` fails because +// framing of unsafe function postconditions is done by Viper. +#[ensures(result.0 == result.1)] +#[ensures(!result.0.is_null() ==> own!(*result.0) && unsafe { *result.1 } == 5)] +unsafe fn test13_unsafe_semantic_aliasing() -> (*mut i32, *mut i32) { + let p_alloc = unsafe { + alloc(std::mem::size_of::(), std::mem::align_of::()) + }; + let p = (p_alloc as *mut i32); + if !p.is_null() { + unsafe { *p = 5; } + } + (p, p) +} + +#[ensures(!result.0.is_null() ==> own!(*result.0) && unsafe { *result.1 } == 5)] //~ ERROR: the postcondition might not be self-framing. +unsafe fn test14_unsafe_semantic_aliasing() -> (*mut i32, *mut i32) { + let p_alloc = unsafe { + alloc(std::mem::size_of::(), std::mem::align_of::()) + }; + let p = (p_alloc as *mut i32); + if !p.is_null() { + unsafe { *p = 5; } + } + (p, p) +} + +#[trusted] +#[requires(align > 0)] +#[ensures(!result.is_null() ==> ( + raw!(*result, size) && + raw_dealloc!(*result, size, align) +))] +// https://doc.rust-lang.org/alloc/alloc/fn.alloc.html +unsafe fn alloc(size: usize, align: usize) -> *mut u8 { + unimplemented!(); +} + +#[trusted] +#[requires( + raw!(*ptr, size) && + raw_dealloc!(*ptr, size, align) +)] +unsafe fn dealloc(ptr: *mut u8, size: usize, align: usize) { + unimplemented!(); +} + +fn main() {} diff --git a/prusti-tests/tests/verify_overflow/fail/core_proof/invariants.rs b/prusti-tests/tests/verify_overflow/fail/core_proof/invariants.rs new file mode 100644 index 00000000000..8467b7b605f --- /dev/null +++ b/prusti-tests/tests/verify_overflow/fail/core_proof/invariants.rs @@ -0,0 +1,556 @@ +// compile-flags: -Punsafe_core_proof=true -Penable_type_invariants=true +// -Pverify_specifications_with_core_proof=true +// -Puse_snapshot_parameters_in_predicates=true + +use prusti_contracts::*; + +// struct T1 { +// f: i32, +// } + +// fn test1(mut a: T1) -> T1 { +// let b = std::ptr::addr_of_mut!(a); +// unpack!(*b); +// unpack!((*b).f); +// unsafe { (*b).f = 4; } +// pack!(*b); +// restore!(*b, a); +// assert!(a.f == 4); +// a +// } + +// fn test2(mut a: T1) -> T1 { +// let b = std::ptr::addr_of_mut!(a); +// unpack!(*b); +// forget_initialization!((*b).f); +// unsafe { (*b).f = 4; } +// pack!(*b); +// restore!(*b, a); +// assert!(a.f == 4); +// a +// } + +// fn test3(mut a: T1) -> T1 { +// let b = std::ptr::addr_of_mut!(a); +// unpack!(*b); +// unpack!((*b).f); +// unsafe { (*b).f = 4; } +// pack!(*b); +// restore!(*b, a); +// assert!(a.f == 5); //~ ERROR: the asserted expression might not hold +// a +// } + +// fn test4(mut a: T1) -> T1 { +// let b = std::ptr::addr_of_mut!(a); +// unpack!(*b); +// forget_initialization!((*b).f); +// unsafe { (*b).f = 4; } +// pack!(*b); +// restore!(*b, a); +// assert!(a.f == 5); //~ ERROR: the asserted expression might not hold +// a +// } + +// fn test5(mut a: T1) -> T1 { +// let b = std::ptr::addr_of_mut!(a); +// unpack!(*b); +// forget_initialization!((*b).f); +// unsafe { (*b).f = 4; } +// assert!( unsafe { (*b).f } == 4); +// pack!(*b); +// restore!(*b, a); +// a +// } + +// fn test6(mut a: T1) -> T1 { +// let b = std::ptr::addr_of_mut!(a); +// unpack!(*b); +// forget_initialization!((*b).f); +// assert!( unsafe { (*b).f } == 4); //~ ERROR: the asserted expression might not hold +// //^ ERROR: the accessed place may not be allocated or initialized +// unsafe { (*b).f = 4; } +// pack!(*b); +// restore!(*b, a); +// a +// } + +// fn test7(mut a: T1) -> T1 { +// let b = std::ptr::addr_of_mut!(a); +// unpack!(*b); +// assert!( unsafe { (*b).f } == 4); //~ ERROR: the asserted expression might not hold +// forget_initialization!((*b).f); +// unsafe { (*b).f = 4; } +// pack!(*b); +// restore!(*b, a); +// a +// } + +// #[requires(b ==> own!(*p))] +// #[ensures(b ==> ((own!(*p)) && unsafe { *p } == 4))] +// unsafe fn test8(p: *mut i32, b: bool) { +// if b { +// forget_initialization!(*p); +// unsafe { *p = 4 }; +// } +// } + +// #[ensures(result.f == 4)] +// fn test9(mut a: T1) -> T1 { +// let b = std::ptr::addr_of_mut!(a.f); +// unsafe { test8(b, true); } +// restore!(*b, a.f); +// a +// } + +// #[requires(b ==> own!(*p))] +// #[ensures(b ==> ((own!(*p)) && unsafe { *p } == 5))] //~ ERROR: postcondition might not hold. +// unsafe fn test10(p: *mut i32, b: bool) { +// if b { +// forget_initialization!(*p); +// unsafe { *p = 4 }; +// } +// } + +// #[ensures(result.f == 5)] //~ ERROR: postcondition might not hold. +// fn test11(mut a: T1) -> T1 { +// let b = std::ptr::addr_of_mut!(a.f); +// unsafe { test8(b, true); } +// restore!(*b, a.f); +// a +// } + +// struct T2 { +// f: i32, +// g: i32, +// } + +// #[ensures(result.f == 4 && result.g == a.g)] +// fn test12(mut a: T2) -> T2 { +// let b = std::ptr::addr_of_mut!(a); +// unpack!(*b); +// unpack!((*b).f); +// unsafe { (*b).f = 4; } +// pack!(*b); +// restore!(*b, a); +// assert!(a.f == 4); +// a +// } + +// #[ensures(result.f == 5 && result.g == a.g)] +// fn test13(mut a: T2) -> T2 { //~ ERROR: postcondition might not hold. +// let b = std::ptr::addr_of_mut!(a); +// unpack!(*b); +// unpack!((*b).f); +// unsafe { (*b).f = 4; } +// pack!(*b); +// restore!(*b, a); +// assert!(a.f == 4); +// a +// } + +// #[requires(b ==> (own!(*p) && unsafe { *p } < 20))] +// #[ensures(b ==> (own!(*p) && unsafe { *p } == old(unsafe { *p }) + 1))] +// unsafe fn test14(p: *mut i32, b: bool) { +// if b { +// // FIXME: unsafe { *p += 1 }; +// let tmp = unsafe { *p }; +// forget_initialization!(*p); +// unsafe { *p = tmp + 1 }; +// } +// } + +// #[ensures(result.f == 7)] +// fn test15(mut a: T1) -> T1 { +// a.f = 6; +// let b = std::ptr::addr_of_mut!(a.f); +// unsafe { test14(b, true); } +// restore!(*b, a.f); +// a +// } + +// #[ensures(result.f == 8)] +// fn test16(mut a: T1) -> T1 { +// a.f = 6; +// let b = std::ptr::addr_of_mut!(a.f); +// unsafe { test14(b, true); } +// restore!(*b, a.f); +// a +// } + +// fn alloc_client() { +// let size = std::mem::size_of::(); +// let align = std::mem::align_of::(); +// let ptr = unsafe { alloc(size, align) }; +// if !ptr.is_null() { +// unsafe { *(ptr as *mut u16) = 42; } +// assert!(unsafe { *(ptr as *mut u16) } == 42); +// let ptr_u16 = (ptr as *mut u16); +// forget_initialization!(*ptr_u16); // FIXME: We should support (ptr as *mut u16). +// unsafe { dealloc(ptr, size, align) }; +// } +// } + +// fn alloc_client2() { +// let size = std::mem::size_of::(); +// let align = std::mem::align_of::(); +// let ptr = unsafe { alloc(size, align) }; +// if !ptr.is_null() { +// unsafe { *(ptr as *mut u16) = 42; } +// assert!(unsafe { *(ptr as *mut u16) } == 43); //~ ERROR: the asserted expression might not hold +// let ptr_u16 = (ptr as *mut u16); +// forget_initialization!(*ptr_u16); // FIXME: We should support (ptr as *mut u16). +// unsafe { dealloc(ptr, size, align) }; +// } +// } + +// fn alloc_client3() { +// let size = std::mem::size_of::(); +// let align = std::mem::align_of::(); +// let ptr = unsafe { alloc(size, align) }; +// unsafe { *(ptr as *mut u16) = 42; } //~ ERROR: the accessed memory location must be allocated and uninitialized +// } + +// #[requires(x < 5)] +// unsafe fn check_x(x: u32) {} + +// #[structural_invariant(self.x < 5)] +// struct T3 { +// x: u32, +// } + +// fn test17(a: T3) { +// unpack!(a); +// unsafe { check_x(a.x) } +// pack!(a); +// forget_initialization!(a); +// } + +// #[structural_invariant( +// !self.p1.is_null() ==> ( +// raw!(*self.p1, std::mem::size_of::()) && +// raw_dealloc!(*self.p1, std::mem::size_of::(), std::mem::align_of::()) +// ) +// )] +// #[structural_invariant( +// !self.p2.is_null() ==> ( +// own!(*self.p2) && +// raw_dealloc!(*self.p2, std::mem::size_of::(), std::mem::align_of::()) +// ) +// )] +// struct T4 { +// p1: *mut i32, +// p2: *mut i32, +// } + +// impl T4 { +// fn new() -> Self { +// let p1 = unsafe { +// alloc(std::mem::size_of::(), std::mem::align_of::()) +// }; +// let p2 = unsafe { +// alloc(std::mem::size_of::(), std::mem::align_of::()) +// }; +// if !p2.is_null() { +// unsafe { *(p2 as *mut i32) = 42; } +// } +// Self { p1: (p1 as *mut i32), p2: (p2 as *mut i32) } +// } +// } + +// #[structural_invariant( +// !self.p2.is_null() ==> ( +// own!(*self.p2) && +// raw_dealloc!(*self.p2, std::mem::size_of::(), std::mem::align_of::()) && +// unsafe { *self.p2 == 42 } && +// 1 == 1 && +// 2 == 2 && +// 3 == 3 && +// 4 == 4 && +// 5 == 5 && +// 6 == 6 +// ) +// )] +// struct T5 { +// p2: *mut i32, +// } + +// impl T5 { +// fn new() -> Self { +// let p2 = unsafe { +// alloc(std::mem::size_of::(), std::mem::align_of::()) +// }; +// if !p2.is_null() { +// unsafe { *(p2 as *mut i32) = 42; } +// } +// Self { p2: (p2 as *mut i32) } +// } +// fn new_fail() -> Self { +// let p2 = unsafe { +// alloc(std::mem::size_of::(), std::mem::align_of::()) +// }; +// if !p2.is_null() { +// unsafe { *(p2 as *mut i32) = 43; } +// } +// Self { p2: (p2 as *mut i32) } //~ ERROR: The type invariant of the constructed object might not hold +// } +// } + +#[structural_invariant( + !self.p.is_null() ==> ( + raw_dealloc!(*self.p, std::mem::size_of::(), std::mem::align_of::()) && + raw!((*self.p).x, std::mem::size_of::()) && + own!((*self.p).y) && + unsafe { (*self.p).y } == self.v + ) +)] +struct T6 { + v: i32, + p: *mut Pair, +} + +impl T6 { + #[ensures(result.v == 42)] + #[ensures( + unpacking!( + result, + !result.p.is_null() ==> + (unpacking!((*result.p).y, unsafe { (*result.p).y }) == 42) + ) + )] + fn new() -> Self { + let p2 = unsafe { + alloc(std::mem::size_of::(), std::mem::align_of::()) + }; + let p = (p2 as *mut Pair); + if !p2.is_null() { + split!(*p); + unsafe { (*p).y = 42; } + } + Self { p, v: 42 } + } +// #[ensures(result.v == 42)] +// #[ensures( +// unpacking!( //~ ERROR: postcondition might not hold. +// result, +// !result.p.is_null() ==> +// (unpacking!((*result.p).y, unsafe { (*result.p).y }) == 43) +// ) +// )] +// fn new_fail_wrong_value() -> Self { +// let p2 = unsafe { +// alloc(std::mem::size_of::(), std::mem::align_of::()) +// }; +// let p = (p2 as *mut Pair); +// if !p2.is_null() { +// split!(*p); +// unsafe { (*p).y = 42; } +// } +// Self { p, v: 42 } +// } +// #[ensures(result.v == 42)] +// #[ensures( +// !result.p.is_null() ==> +// (unpacking!((*result.p).y, unsafe { (*result.p).y }) == 42) +// )] +// fn new_fail_missing_outer_unpacking() -> Self { //~ ERROR: there might be insufficient permission to dereference a raw pointer +// let p2 = unsafe { +// alloc(std::mem::size_of::(), std::mem::align_of::()) +// }; +// let p = (p2 as *mut Pair); +// if !p2.is_null() { +// split!(*p); +// unsafe { (*p).y = 42; } +// } +// Self { p, v: 42 } +// } + #[ensures(result.v == 42)] + #[ensures( + unpacking!( + result, + !result.p.is_null() ==> + (unsafe { (*result.p).y } == 42) + ) + )] + fn new_fail_missing_inner_unpacking() -> Self { + let p2 = unsafe { + alloc(std::mem::size_of::(), std::mem::align_of::()) + }; + let p = (p2 as *mut Pair); + if !p2.is_null() { + split!(*p); + unsafe { (*p).y = 42; } + } + Self { p, v: 42 } + } +// #[ensures(result.v == 42)] +// #[ensures( +// unpacking!( +// result, +// !result.p.is_null() ==> +// (unpacking!(*result.p, unsafe { (*result.p).y }) == 42) +// ) +// )] +// fn new_fail_wrong_inner_unpacking() -> Self { //~ ERROR: there might be insufficient permission to dereference a raw pointer +// let p2 = unsafe { +// alloc(std::mem::size_of::(), std::mem::align_of::()) +// }; +// let p = (p2 as *mut Pair); +// if !p2.is_null() { +// split!(*p); +// unsafe { (*p).y = 42; } +// } +// Self { p, v: 42 } +// } + + //#[ensures(result.v == 42)] + //#[ensures(!result.p.is_null() ==> (unsafe { (*result.p).y } == 42))] //~ ERROR: there might be insufficient permission to dereference a raw pointer + //fn new_fail1() -> Self { + //let p2 = unsafe { + //alloc(std::mem::size_of::(), std::mem::align_of::()) + //}; + //let p = (p2 as *mut Pair); + //if !p2.is_null() { + //split!(*p); + //unsafe { (*p).y = 42; } + //} + //Self { p, v: 42 } + //} + //fn new_fail() -> Self { + //let p2 = unsafe { + //alloc(std::mem::size_of::(), std::mem::align_of::()) + //}; + //let p = (p2 as *mut Pair); + //if !p2.is_null() { + //split!(*p); + //unsafe { (*p).y = 43; } + //} + //Self { p, v: 42 } //~ ERROR: The type invariant of the constructed object might not hold + //} + //#[ensures(result.v == 42)] + //#[ensures((unsafe { (*result.p).y } == 42))] //~ ERROR: there might be insufficient permission to dereference a raw pointer + ////^ ERROR: postcondition might not hold + //fn new_fail2() -> Self { + //let p2 = unsafe { + //alloc(std::mem::size_of::(), std::mem::align_of::()) + //}; + //let p = (p2 as *mut Pair); + //if !p2.is_null() { + //split!(*p); + //unsafe { (*p).y = 42; } + //} + //Self { p, v: 42 } + //} + + //#[ensures(result.v == 42)] + //// TODO: Make sure to distinguish unpacking!((*result.p), ...) from + //// unpacking!((*result.p).y, ...) + //#[ensures((unpacking!(result.p, unsafe { (*result.p).y }) == 42))] + //fn new_fail3() -> Self { + //let p2 = unsafe { + //alloc(std::mem::size_of::(), std::mem::align_of::()) + //}; + //let p = (p2 as *mut Pair); + //if !p2.is_null() { + //split!(*p); + //unsafe { (*p).y = 42; } + //} + //Self { p, v: 42 } + //} + + //#[ensures(result.v == 42)] + //// TODO: Make sure to distinguish unpacking!((*result.p), ...) from + //// unpacking!((*result.p).y, ...) + //#[ensures((unpacking!((*result.p).y, unsafe { (*result.p).y }) == 42))] + //fn new_fail4() -> Self { + //let p2 = unsafe { + //alloc(std::mem::size_of::(), std::mem::align_of::()) + //}; + //let p = (p2 as *mut Pair); + //if !p2.is_null() { + //split!(*p); + //unsafe { (*p).y = 42; } + //} + //Self { p, v: 42 } + //} + // #[pure] + // fn value(&self) -> i32 { + // if self.p2.is_null() { + // 0 + // } else { + // unsafe { *self.p2 } + // } + // } +} + +// #[structural_invariant( +// !self.p2.is_null() ==> ( +// own!(*self.p2) && +// raw_dealloc!(*self.p2, std::mem::size_of::(), std::mem::align_of::()) && +// unsafe { *self.p2 } == self.v +// ) +// )] +// struct T6 { +// v: i32, +// p2: *mut i32, +// } + +// impl T6 { +// // #[ensures(result.v == 42)] +// // #[ensures(!result.p2.is_null() ==> (unsafe { *result.p2 } == 42))] +// // fn new() -> Self { +// // let p2 = unsafe { +// // alloc(std::mem::size_of::(), std::mem::align_of::()) +// // }; +// // if !p2.is_null() { +// // unsafe { *(p2 as *mut i32) = 42; } +// // } +// // Self { p2: (p2 as *mut i32), v: 42 } +// // } +// #[ensures(result.v == 42)] +// #[ensures(unsafe { *result.p2 } == 42)] //~ ERROR: Permissions +// fn new_fail() -> Self { +// let p2 = unsafe { +// alloc(std::mem::size_of::(), std::mem::align_of::()) +// }; +// if !p2.is_null() { +// unsafe { *(p2 as *mut i32) = 42; } +// } +// Self { p2: (p2 as *mut i32), v: 42 } +// } +// // #[pure] +// // fn value(&self) -> i32 { +// // if self.p2.is_null() { +// // 0 +// // } else { +// // unsafe { *self.p2 } +// // } +// // } +// } + +#[trusted] +#[requires(align > 0)] +#[ensures(!result.is_null() ==> ( + raw!(*result, size) && + raw_dealloc!(*result, size, align) +))] +// https://doc.rust-lang.org/alloc/alloc/fn.alloc.html +unsafe fn alloc(size: usize, align: usize) -> *mut u8 { + unimplemented!(); +} + +#[trusted] +#[requires( + raw!(*ptr, size) && + raw_dealloc!(*ptr, size, align) +)] +unsafe fn dealloc(ptr: *mut u8, size: usize, align: usize) { + unimplemented!(); +} + +struct Pair { + x: i32, + y: i32, +} + +fn main() {} diff --git a/prusti-tests/tests/verify_overflow/fail/core_proof/invariants2.rs b/prusti-tests/tests/verify_overflow/fail/core_proof/invariants2.rs new file mode 100644 index 00000000000..e6f09180adf --- /dev/null +++ b/prusti-tests/tests/verify_overflow/fail/core_proof/invariants2.rs @@ -0,0 +1,38 @@ +// compile-flags: -Punsafe_core_proof=true -Penable_type_invariants=true -Pverify_specifications_with_core_proof=true +// +// These tests need core-proof for specs. + +use prusti_contracts::*; + +struct T1 { + f: i32, +} + +fn test01(mut a: T1, mut b: T1) { + let z = b.f; + let x = std::ptr::addr_of_mut!(a); + let y = std::ptr::addr_of_mut!(b); + unpack!(*x); + unpack!((*x).f); + unsafe { (*x).f = 4; } + pack!(*x); + restore!(*x, a); + restore!(*y, b); + assert!(a.f == 4); + assert!(z == b.f); +} + +fn test02(mut a: T1, mut b: T1) { + let z = b.f; + let x = std::ptr::addr_of_mut!(a); + let y = std::ptr::addr_of_mut!(b); + unpack!(*x); + unpack!((*x).f); + unsafe { (*x).f = 4; } + pack!(*x); + restore!(*x, a); + restore!(*y, b); + assert!(a.f == 5); //~ ERROR: the asserted expression might not hold +} + +fn main() {} diff --git a/prusti-tests/tests/verify_overflow/fail/core_proof/lifetimes/simple.rs b/prusti-tests/tests/verify_overflow/fail/core_proof/lifetimes/simple.rs index 3f3dbaf4d6a..06938634564 100644 --- a/prusti-tests/tests/verify_overflow/fail/core_proof/lifetimes/simple.rs +++ b/prusti-tests/tests/verify_overflow/fail/core_proof/lifetimes/simple.rs @@ -9,6 +9,7 @@ pub fn mutable_borrow() { *x = 2; assert!(*x == 2); } + pub fn mutable_borrow_assert_false() { let mut a = 4; let x = &mut a; @@ -16,6 +17,21 @@ pub fn mutable_borrow_assert_false() { assert!(*x == 4); //~ ERROR: the asserted expression might not hold } +pub fn mutable_borrow_2() { + let mut a = 4; + let x = &mut a; + *x = 2; + assert!(*x == 2); + assert!(a == 2); +} + +pub fn mutable_borrow_2_assert_false() { + let mut a = 4; + let x = &mut a; + *x = 2; + assert!(a == 4); //~ ERROR: the asserted expression might not hold +} + pub fn mutable_reborrow() { let mut a = 4; let mut x = &mut a; @@ -23,6 +39,7 @@ pub fn mutable_reborrow() { *y = 3; assert!(*y == 3); } + pub fn mutable_reborrow_assert_false() { let mut a = 4; let mut x = &mut a; @@ -31,12 +48,31 @@ pub fn mutable_reborrow_assert_false() { assert!(*y == 4); //~ ERROR: the asserted expression might not hold } +pub fn mutable_reborrow_2() { + let mut a = 4; + let mut x = &mut a; + let y = &mut (*x); + *y = 3; + assert!(*y == 3); + assert!(a == 3); +} + +pub fn mutable_reborrow_2_assert_false() { + let mut a = 4; + let mut x = &mut a; + let y = &mut (*x); + *y = 3; + assert!(*y == 3); + assert!(a == 4); //~ ERROR: the asserted expression might not hold +} + pub fn shared_borrow() { let mut a = 4; let x = &a; let y = &a; assert!(*y == 4); } + pub fn shared_borrow_assert_false() { let mut a = 4; let x = &a; @@ -51,6 +87,7 @@ pub fn shared_reborrow() { let z = &(*x); assert!(*z == 4); } + pub fn shared_reborrow_assert_false() { let mut a = 4; let x = &a; @@ -65,6 +102,7 @@ pub fn simple_references() { let mut c = &mut b; let mut d = &mut c; } + pub fn simple_references_assert_false() { let mut a = 4; let mut b = &mut a; diff --git a/prusti-tests/tests/verify_overflow/fail/core_proof/pointers.rs b/prusti-tests/tests/verify_overflow/fail/core_proof/pointers.rs index adfb04fd917..014c8fae2a4 100644 --- a/prusti-tests/tests/verify_overflow/fail/core_proof/pointers.rs +++ b/prusti-tests/tests/verify_overflow/fail/core_proof/pointers.rs @@ -10,18 +10,22 @@ use prusti_contracts::*; fn test1() { let a = 4u32; - let _x = std::ptr::addr_of!(a); + let x = std::ptr::addr_of!(a); + restore!(*x, a); } fn test2() { let mut a = 4u32; - let _x = std::ptr::addr_of_mut!(a); + let x = std::ptr::addr_of_mut!(a); + restore!(*x, a); } fn test3() { let a = 4u32; let x = std::ptr::addr_of!(a); + restore!(*x, a); let y = std::ptr::addr_of!(a); + restore!(*y, a); assert!(x == y); } @@ -29,7 +33,9 @@ fn test4() { let a = 4u32; let b = 4u32; let x = std::ptr::addr_of!(a); + restore!(*x, a); let y = std::ptr::addr_of!(b); + restore!(*y, b); assert!(x == y); //~ ERROR } @@ -37,7 +43,9 @@ fn test5() { let a = 4u32; let b = 4u32; let x = std::ptr::addr_of!(a); + restore!(*x, a); let y = std::ptr::addr_of!(b); + restore!(*y, b); assert!(x != y); //~ ERROR } @@ -45,7 +53,9 @@ fn test6() { let a = 4u32; let b = 4u32; let x = std::ptr::addr_of!(a); + restore!(*x, a); let y = std::ptr::addr_of!(b); + restore!(*y, b); assert!(!(x == y)); //~ ERROR } @@ -64,6 +74,8 @@ fn test7() { let x = std::ptr::addr_of!(a); let y = std::ptr::addr_of!(c.f.g); assert!(x != y); //~ ERROR + restore!(*x, a); + restore!(*y, c.f.g); } fn test8() { @@ -73,6 +85,8 @@ fn test8() { let x = std::ptr::addr_of!(a); let y = std::ptr::addr_of!(c.f.g); assert!(!(x == y)); //~ ERROR + restore!(*x, a); + restore!(*y, c.f.g); } fn test9() { @@ -82,6 +96,8 @@ fn test9() { let x = std::ptr::addr_of!(a); let y = std::ptr::addr_of!(c.f.g); assert!(x == y); //~ ERROR + restore!(*x, a); + restore!(*y, c.f.g); } fn main() {} diff --git a/prusti-tests/tests/verify_overflow/pass/core_proof/custom_heap_encoding/performance_test.rs b/prusti-tests/tests/verify_overflow/pass/core_proof/custom_heap_encoding/performance_test.rs new file mode 100644 index 00000000000..30184e1bfbd --- /dev/null +++ b/prusti-tests/tests/verify_overflow/pass/core_proof/custom_heap_encoding/performance_test.rs @@ -0,0 +1,972 @@ +// compile-flags: -Punsafe_core_proof=true -Pverification_deadline=120 + +use prusti_contracts::*; + +struct T {} + +//fn test001() { + //let a = T{}; + //let a = a; +//} + +//fn test002() { + //let a = T{}; + //let a = a; + //let a = a; +//} + +//fn test003() { + //let a = T{}; + //let a = a; + //let a = a; + //let a = a; +//} + +//fn test004() { + //let a = T{}; + //let a = a; + //let a = a; + //let a = a; + //let a = a; +//} + +//fn test005() { + //let a = T{}; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; +//} + +//fn test006() { + //let a = T{}; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; +//} + +//fn test007() { + //let a = T{}; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; +//} + +//fn test008() { + //let a = T{}; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; +//} + +//fn test009() { + //let a = T{}; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; +//} + +//fn test010() { + //let a = T{}; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; +//} + +//fn test011() { + //let a = T{}; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; +//} + +//fn test012() { + //let a = T{}; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; +//} +//fn test013() { + //let a = T{}; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; +//} +//fn test014() { + //let a = T{}; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; +//} +//fn test015() { + //let a = T{}; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; +//} + +//fn test016() { + //let a = T{}; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; +//} +//fn test017() { + //let a = T{}; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; +//} +//fn test018() { + //let a = T{}; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; +//} +//fn test019() { + //let a = T{}; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; +//} +//fn test020() { + //let a = T{}; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; +//} +//fn test021() { + //let a = T{}; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; +//} +//fn test022() { + //let a = T{}; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; +//} +//fn test023() { + //let a = T{}; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; +//} +//fn test024() { + //let a = T{}; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; +//} +//fn test025() { + //let a = T{}; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; +//} +//fn test030() { + //let a = T{}; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; +//} + +//fn test040() { + //let a = T{}; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; +//} + +//fn test050() { + //let a = T{}; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; +//} + +//fn test060() { + //let a = T{}; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; +//} + +//fn test070() { + //let a = T{}; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; +//} + +//fn test080() { + //let a = T{}; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; +//} + +//fn test090() { + //let a = T{}; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; + //let a = a; +//} + +fn test100() { + let a = T{}; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; + let a = a; +} + +#[trusted] +fn main() {} + diff --git a/prusti-tests/tests/verify_overflow/pass/core_proof/pointers.rs b/prusti-tests/tests/verify_overflow/pass/core_proof/pointers.rs index 842b551257f..bfcbc5b01e3 100644 --- a/prusti-tests/tests/verify_overflow/pass/core_proof/pointers.rs +++ b/prusti-tests/tests/verify_overflow/pass/core_proof/pointers.rs @@ -4,18 +4,22 @@ use prusti_contracts::*; fn test1() { let a = 4u32; - let _x = std::ptr::addr_of!(a); + let x = std::ptr::addr_of!(a); + restore!(*x, a); } fn test2() { let mut a = 4u32; - let _x = std::ptr::addr_of_mut!(a); + let x = std::ptr::addr_of_mut!(a); + restore!(*x, a); } fn test3() { let a = 4u32; let x = std::ptr::addr_of!(a); + restore!(*x, a); let y = std::ptr::addr_of!(a); + restore!(*y, a); assert!(x == y); } diff --git a/prusti-tests/tests/verify_overflow/pass/core_proof/types.rs b/prusti-tests/tests/verify_overflow/pass/core_proof/types.rs index 180a246a332..2b53cb97d63 100644 --- a/prusti-tests/tests/verify_overflow/pass/core_proof/types.rs +++ b/prusti-tests/tests/verify_overflow/pass/core_proof/types.rs @@ -1,4 +1,5 @@ // compile-flags: -Punsafe_core_proof=true -Pverify_types=true +// -Puse_snapshot_parameters_in_predicates=true use prusti_contracts::*; @@ -16,117 +17,117 @@ struct T3 { struct T4<'a> { f: &'a mut T1, - g: &'a T1, + //g: &'a T1, } struct T5<'a, 'b, 'c> { f: &'a mut T1, g: &'c mut T4<'b>, - h: &'a T1, - i: &'c T4<'b>, -} - -struct T6<'a, 'b, 'c> { - f: &'a mut T1, - g: &'a mut &'b mut T1, - h: &'a mut &'b mut &'c mut T1, - i: &'a T1, - j: &'a &'b T1, - k: &'a &'b &'c T1, -} - -struct T7<'a> { - f: &'a mut T1, - g: &'a mut &'a mut T1, - h: &'a mut &'a mut &'a mut T1, - i: &'a T1, - j: &'a &'a T1, - k: &'a &'a &'a T1, -} - -struct T8 { - f: [i32; 10], - g: [[T2; 10]; 10], - h: [[[T2; 10]; 10]; 10], -} - -enum T9 { - F([i32; 10]), - G([[T2; 10]; 10]), - H([[[T2; 10]; 10]; 10]), -} - -struct T10 { - f: [T8; 10], - g: [[T8; 10]; 10], - h: [[[T9; 10]; 10]; 10], -} - -struct T11<'a, 'b, 'c, 'd> { - f: &'a mut [&'b mut T8; 10], - g: &'a mut [&'b mut [&'c mut T9; 10]; 10], - h: &'a mut [&'b mut [&'c mut [&'d mut T9; 10]; 10]; 10], - i: &'a [&'b T8; 10], - j: &'a [&'b [&'c T9; 10]; 10], - k: &'a [&'b [&'c [&'d T9; 10]; 10]; 10], -} - -struct T12<'a> { - f: &'a mut [&'a mut T9; 10], - g: &'a mut [&'a mut [&'a mut T9; 10]; 10], - h: &'a mut [&'a mut [&'a mut [&'a mut T9; 10]; 10]; 10], - i: &'a [&'a T9; 10], - j: &'a [&'a [&'a T9; 10]; 10], - k: &'a [&'a [&'a [&'a T9; 10]; 10]; 10], -} - -struct T13 { - f: (), - g: (T1, T2, T3<(T2, T1)>), -} - -struct T14<'a, 'b, 'c> { - f: &'a mut (), - g: &'a mut (&'b mut T1, &'c mut T2), - i: &'a (), - j: &'a (&'b T1, &'c T2), -} + //h: &'a T1, + //i: &'c T4<'b>, +} + +//struct T6<'a, 'b, 'c> { + //f: &'a mut T1, + //g: &'a mut &'b mut T1, + //h: &'a mut &'b mut &'c mut T1, + //i: &'a T1, + //j: &'a &'b T1, + //k: &'a &'b &'c T1, +//} + +//struct T7<'a> { + //f: &'a mut T1, + //g: &'a mut &'a mut T1, + //h: &'a mut &'a mut &'a mut T1, + //i: &'a T1, + //j: &'a &'a T1, + //k: &'a &'a &'a T1, +//} + +//struct T8 { + //f: [i32; 10], + //g: [[T2; 10]; 10], + //h: [[[T2; 10]; 10]; 10], +//} + +//enum T9 { + //F([i32; 10]), + //G([[T2; 10]; 10]), + //H([[[T2; 10]; 10]; 10]), +//} + +//struct T10 { + //f: [T8; 10], + //g: [[T8; 10]; 10], + //h: [[[T9; 10]; 10]; 10], +//} + +//struct T11<'a, 'b, 'c, 'd> { + //f: &'a mut [&'b mut T8; 10], + //g: &'a mut [&'b mut [&'c mut T9; 10]; 10], + //h: &'a mut [&'b mut [&'c mut [&'d mut T9; 10]; 10]; 10], + //i: &'a [&'b T8; 10], + //j: &'a [&'b [&'c T9; 10]; 10], + //k: &'a [&'b [&'c [&'d T9; 10]; 10]; 10], +//} + +//struct T12<'a> { + //f: &'a mut [&'a mut T9; 10], + //g: &'a mut [&'a mut [&'a mut T9; 10]; 10], + //h: &'a mut [&'a mut [&'a mut [&'a mut T9; 10]; 10]; 10], + //i: &'a [&'a T9; 10], + //j: &'a [&'a [&'a T9; 10]; 10], + //k: &'a [&'a [&'a [&'a T9; 10]; 10]; 10], +//} + +//struct T13 { + //f: (), + //g: (T1, T2, T3<(T2, T1)>), +//} + +//struct T14<'a, 'b, 'c> { + //f: &'a mut (), + //g: &'a mut (&'b mut T1, &'c mut T2), + //i: &'a (), + //j: &'a (&'b T1, &'c T2), +//} struct T15<'a> { f: &'a mut [T1], g: &'a mut [T1; 10], - i: &'a [T1], - j: &'a [T1; 10], -} - -struct T16<'a, 'b> { - f: &'a mut [&'b mut T1], - g: &'a mut [&'b mut T1; 10], - i: &'a [&'b T1], - j: &'a [&'b T1; 10], -} - -enum T17<'a, 'b> { - Left (&'a mut [T1; 10]), - Right (&'b mut [T2; 10]), - SharedLeft (&'a [T1; 10]), - SharedRight (&'b [T2; 10]), -} - -enum T18<'a> { - Left(&'a mut [T1]), - Right([T2; 10]), -} - -union T19<'a, 'b> { - f: &'a mut [&'b mut T1], - g: &'a mut [&'b mut T1; 10], -} - -struct T20 { - f: *mut u8, - g: *mut [u8], -} + //i: &'a [T1], + //j: &'a [T1; 10], +} + +//struct T16<'a, 'b> { + //f: &'a mut [&'b mut T1], + //g: &'a mut [&'b mut T1; 10], + //i: &'a [&'b T1], + //j: &'a [&'b T1; 10], +//} + +//enum T17<'a, 'b> { + //Left (&'a mut [T1; 10]), + //Right (&'b mut [T2; 10]), + //SharedLeft (&'a [T1; 10]), + //SharedRight (&'b [T2; 10]), +//} + +//enum T18<'a> { + //Left(&'a mut [T1]), + //Right([T2; 10]), +//} + +//union T19<'a, 'b> { + //f: &'a mut [&'b mut T1], + //g: &'a mut [&'b mut T1; 10], +//} + +//struct T20 { + //f: *mut u8, + //g: *mut [u8], +//} #[trusted] fn main() {} diff --git a/prusti-utils/src/config.rs b/prusti-utils/src/config.rs index 65a6bc9e624..b5ebc709ec0 100644 --- a/prusti-utils/src/config.rs +++ b/prusti-utils/src/config.rs @@ -103,7 +103,11 @@ lazy_static::lazy_static! { settings.set_default("quiet", false).unwrap(); settings.set_default("assert_timeout", 10_000).unwrap(); settings.set_default("smt_qi_eager_threshold", 1000).unwrap(); - settings.set_default("use_more_complete_exhale", true).unwrap(); + settings.set_default("smt_use_nonlinear_arithmetic_solver", false).unwrap(); + settings.set_default("define_multiply_int", false).unwrap(); + settings.set_default("use_more_complete_exhale", false).unwrap(); + settings.set_default("use_carbon_qps", true).unwrap(); + settings.set_default("use_z3_api", false).unwrap(); settings.set_default("skip_unsupported_features", false).unwrap(); settings.set_default("internal_errors_as_warnings", false).unwrap(); settings.set_default("allow_unreachable_unsupported_code", false).unwrap(); @@ -114,19 +118,47 @@ lazy_static::lazy_static! { settings.set_default("json_communication", false).unwrap(); settings.set_default("optimizations", "all").unwrap(); settings.set_default("intern_names", true).unwrap(); + settings.set_default("create_missing_storage_live", false).unwrap(); settings.set_default("enable_purification_optimization", false).unwrap(); // settings.set_default("enable_manual_axiomatization", false).unwrap(); settings.set_default("unsafe_core_proof", false).unwrap(); + settings.set_default("custom_heap_encoding", false).unwrap(); + settings.set_default("custom_heap_encoding_omit_injective", true).unwrap(); + settings.set_default("trace_with_symbolic_execution", false).unwrap(); + settings.set_default("trace_with_symbolic_execution_new", true).unwrap(); + settings.set_default("purify_with_symbolic_execution", false).unwrap(); + settings.set_default("symbolic_execution_single_method", true).unwrap(); + settings.set_default("symbolic_execution_multiple_methods_max", 100).unwrap(); + settings.set_default("symbolic_execution_leak_check", true).unwrap(); + settings.set_default("symbolic_execution_simp_valid_expr", false).unwrap(); + settings.set_default("panic_on_failed_exhale", false).unwrap(); + settings.set_default("panic_on_failed_exhale_materialization", true).unwrap(); + settings.set_default("end_borrow_view_shift_non_aliased", true).unwrap(); + settings.set_default("materialize_on_failed_exhale", false).unwrap(); + settings.set_default("ignore_whether_exhale_is_unconditional", false).unwrap(); + settings.set_default("error_non_linear_arithmetic_simp", true).unwrap(); + settings.set_default("expand_quantifiers", false).unwrap(); + settings.set_default("clean_labels", true).unwrap(); + settings.set_default("merge_consecutive_statements", true).unwrap(); + settings.set_default("merge_consecutive_statements_same_pos", true).unwrap(); + settings.set_default("merge_consecutive_statements_only_inhale", true).unwrap(); + settings.set_default("report_symbolic_execution_failures", false).unwrap(); + settings.set_default("report_symbolic_execution_purification", false).unwrap(); settings.set_default("verify_core_proof", true).unwrap(); settings.set_default("verify_specifications", true).unwrap(); + settings.set_default("verify_postcondition_frame_check", true).unwrap(); settings.set_default("verify_types", false).unwrap(); - settings.set_default("verify_specifications_with_core_proof", false).unwrap(); + settings.set_default("verify_specifications_with_core_proof", true).unwrap(); settings.set_default("verify_specifications_backend", "Silicon").unwrap(); settings.set_default("use_eval_axioms", true).unwrap(); settings.set_default("inline_caller_for", false).unwrap(); + settings.set_default("use_snapshot_parameters_in_predicates", false).unwrap(); settings.set_default("check_no_drops", false).unwrap(); settings.set_default("enable_type_invariants", false).unwrap(); + settings.set_default("allow_prusti_assume", false).unwrap(); + settings.set_default("allow_assuming_allocation_never_fails", false).unwrap(); settings.set_default("use_new_encoder", true).unwrap(); + settings.set_default("function_gas_amount", 2).unwrap(); settings.set_default::>("number_of_parallel_verifiers", None).unwrap(); settings.set_default::>("min_prusti_version", None).unwrap(); @@ -168,6 +200,15 @@ lazy_static::lazy_static! { settings.set_default::>("verify_only_basic_block_path", vec![]).unwrap(); settings.set_default::>("delete_basic_blocks", vec![]).unwrap(); + // Svirpti settings. + settings.set_default("svirpti_smt_solver", "z3").unwrap(); + settings.set_default::>("svirpti_smt_solver_log", None).unwrap(); + settings.set_default("svirpti_stop_on_first_error", false).unwrap(); + settings.set_default("svirpti_use_pseudo_boolean_heap", false).unwrap(); + settings.set_default("svirpti_enable_smoke_check", false).unwrap(); + settings.set_default("svirpti_enable_manual_triggering", false).unwrap(); + settings.set_default("svirpti_remove_unnecessary_axioms", false).unwrap(); + // Get the list of all allowed flags. let mut allowed_keys = get_keys(&settings); allowed_keys.insert("server_max_stored_verifiers".to_string()); @@ -500,6 +541,17 @@ pub fn smt_qi_eager_threshold() -> u64 { read_setting("smt_qi_eager_threshold") } +/// Disable or enable the non-linear arithmetic solver by setting Z3 +/// `smt.arith.nl` and `smt.arith.nl.gb` values to the given one. +pub fn smt_use_nonlinear_arithmetic_solver() -> bool { + read_setting("smt_use_nonlinear_arithmetic_solver") +} + +/// Define `multiply_int` as a multiplication. +pub fn define_multiply_int() -> bool { + read_setting("define_multiply_int") +} + /// Maximum time (in milliseconds) for the verifier to spend on checks. /// Set to None uses the verifier's default value. Maps to the verifier command-line /// argument `--checkTimeout`. @@ -513,8 +565,33 @@ pub fn check_timeout() -> Option { /// See [`consolidate`](https://github.com/viperproject/silicon/blob/f48de7f6e2d90d9020812869c713a5d3e2035995/src/main/scala/rules/StateConsolidator.scala#L29-L46). /// Equivalent to the verifier command-line argument /// `--enableMoreCompleteExhale`. +/// +/// Note: This option conflicts with `use_carbon_qps`. pub fn use_more_complete_exhale() -> bool { - read_setting("use_more_complete_exhale") + let result = read_setting("use_more_complete_exhale"); + assert!( + !(result && read_setting::("use_carbon_qps")), + "use_more_complete_exhale and use_carbon_qps are mutually exclusive" + ); + result +} + +/// When enabled, a Carbon QPs version of Silicon is used. Equivalent to the +/// Silicon command-line argument `--carbonQPs`. +/// +/// Note: This option conflicts with `use_more_complete_exhale`. +pub fn use_carbon_qps() -> bool { + let result = read_setting("use_carbon_qps"); + assert!( + !(result && read_setting::("use_more_complete_exhale")), + "use_more_complete_exhale and use_carbon_qps are mutually exclusive" + ); + result +} + +/// When enabled, Z3 is used via API. +pub fn use_z3_api() -> bool { + read_setting("use_z3_api") } /// When enabled, prints the items collected for verification. @@ -732,6 +809,18 @@ pub fn optimizations() -> Optimizations { opt } +/// The Rust compiler does not guarantee that each `StorageDead` is dominated by +/// a `StorageLive`: +/// +/// * https://github.com/rust-lang/rust/issues/99160 +/// * https://github.com/rust-lang/rust/issues/98896 +/// +/// This option controls whether we should create fake `StorageLive` statements +/// in such cases. +pub fn create_missing_storage_live() -> bool { + read_setting("create_missing_storage_live") +} + /// When enabled, impure methods are optimized using the purification /// optimization, which tries to convert heap operations to pure (snapshot- /// based) operations. @@ -870,6 +959,210 @@ pub fn unsafe_core_proof() -> bool { read_setting("unsafe_core_proof") } +/// Use symbolic execution to split the procedure into traces that are verified +/// separately. +/// +/// **Note:** This option is taken into account only when `unsafe_core_proof` is +/// true. +pub fn trace_with_symbolic_execution() -> bool { + read_setting("trace_with_symbolic_execution") || purify_with_symbolic_execution() +} + +pub fn trace_with_symbolic_execution_new() -> bool { + read_setting("trace_with_symbolic_execution_new") +} + +/// Use symbolic execution based purification. +/// +/// **Note:** This option is taken into account only when `unsafe_core_proof` is +/// true. +/// +/// **Note:** This option automatically enables +/// `trace_with_symbolic_execution`. +pub fn purify_with_symbolic_execution() -> bool { + read_setting("purify_with_symbolic_execution") +} + +/// Puts all symbolic execution traces into a single method. +/// +/// **Note:** This option is taken into account only when `unsafe_core_proof` is +/// true. +pub fn symbolic_execution_single_method() -> bool { + read_setting("symbolic_execution_single_method") +} + +/// If `symbolic_execution_single_method` is true, this option specifies the +/// upper bound on the number of generated methods. +/// +/// **Note:** This option is taken into account only when `unsafe_core_proof` is +/// true. +pub fn symbolic_execution_multiple_methods_max() -> u16 { + read_setting("symbolic_execution_multiple_methods_max") +} + +/// Performs predicate leak check during symbolic execution. +/// +/// **Note:** This option is taken into account only when +/// `purify_with_symbolic_execution` is true. +pub fn symbolic_execution_leak_check() -> bool { + read_setting("symbolic_execution_leak_check") +} + +/// Simmplifies snapshot expressions by using knowledge what expressions are +/// valid. +/// +/// **Note:** This option is taken into account only when +/// `purify_with_symbolic_execution` is true. +/// +/// **Note:** This optimization is not fully implemented and benchmarking on a +/// completely safe Rust code shows only negligible performance difference +/// (Silicon: 35s → 36s, Carbon: 23s → 22s). +pub fn symbolic_execution_simp_valid_expr() -> bool { + read_setting("symbolic_execution_simp_valid_expr") +} + +/// Panics if symbolic execution failed to purify out an exhale. +/// +/// **Note:** This option is taken into account only when +/// `purify_with_symbolic_execution` is true. +pub fn panic_on_failed_exhale() -> bool { + read_setting("panic_on_failed_exhale") +} + +/// Panics if symbolic execution failed to purify out an exhale and it resulted +/// in a materialization of resources. In other words, if symbolic execution +/// failed to exhale an aliased resource. +/// +/// **Note:** This option is taken into account only when +/// `purify_with_symbolic_execution` is true. +pub fn panic_on_failed_exhale_materialization() -> bool { + read_setting("panic_on_failed_exhale_materialization") +} + +/// Treat end-borrow view shift as non-aliased. +/// +/// **Note:** This option does not affect soundness. Setting it to true makes +/// the encoding faster, but more incomplete. +/// +/// **Note:** This option is taken into account only when +/// `purify_with_symbolic_execution` is true. +pub fn end_borrow_view_shift_non_aliased() -> bool { + read_setting("end_borrow_view_shift_non_aliased") +} + +/// If symbolic execution fails to purify out an exhale, materialize the exhale. +/// This means that the purifier will emit inhale statements for all chunks it has and an exhale statement for the chunk it failed to exhale. +/// +/// **Note:** This option is taken into account only when +/// `purify_with_symbolic_execution` is true. +pub fn materialize_on_failed_exhale() -> bool { + read_setting("materialize_on_failed_exhale") +} + +/// If this option is false, purification purifies out only exhales that are +/// guaranteed to succeed because we know that on all incoming branches we have +/// the necessary heap chunk. Setting this option to `true` is sound (because we +/// still ask the verifier to check that the permission variable has the value +/// we expect on all traces), but it can lead to incompletenesses when the +/// purifier fails to merge the heap chunks due to incomplete solver. +/// +/// **Note:** This option is taken into account only when +/// `purify_with_symbolic_execution` is true. +pub fn ignore_whether_exhale_is_unconditional() -> bool { + read_setting("ignore_whether_exhale_is_unconditional") +} + +/// Error when simplifying non-linear arithmetic fails. +/// +/// **Note:** This option is taken into account only when +/// `purify_with_symbolic_execution` is true. +pub fn error_non_linear_arithmetic_simp() -> bool { + read_setting("error_non_linear_arithmetic_simp") +} + +/// Whether to expand the asserted quantifiers (skolemize them out). +/// +/// **Note:** This option is taken into account only when `unsafe_core_proof` +/// is true. +pub fn expand_quantifiers() -> bool { + read_setting("expand_quantifiers") +} + +/// Whether to remove unused label statements. +/// +/// **Note:** This option is taken into account only when `unsafe_core_proof` +/// is true. +pub fn clean_labels() -> bool { + read_setting("clean_labels") +} + +/// Whether to merge consequative Viper statements. For example: +/// +/// ```viper +/// inhale A +/// inhale B +/// ``` +/// +/// becomes +/// +/// ```viper +/// inhale A && B +/// ``` +/// +/// **Note:** This option is taken into account only when `unsafe_core_proof` +/// is true. +pub fn merge_consecutive_statements() -> bool { + read_setting("merge_consecutive_statements") +} + +/// When merging consequative statements, merge only statements that have the same position. +pub fn merge_consecutive_statements_same_pos() -> bool { + read_setting("merge_consecutive_statements_same_pos") +} + +/// When merging consequative statements, merge only inhale and assume +/// statements. +pub fn merge_consecutive_statements_only_inhale() -> bool { + read_setting("merge_consecutive_statements_only_inhale") +} + +/// Report an error when failing to purify a predicate in symbolic execution. +/// +/// **Note:** This option requires `purify_with_symbolic_execution` to be +/// enabled. +pub fn report_symbolic_execution_failures() -> bool { + let result: bool = read_setting("report_symbolic_execution_failures"); + assert!(!result || purify_with_symbolic_execution()); + result +} + +/// Add comments at the places where predicates were purified by the symbolic +/// execution. +/// +/// **Note:** This option requires `purify_with_symbolic_execution` to be +/// enabled. +pub fn report_symbolic_execution_purification() -> bool { + assert!(purify_with_symbolic_execution() || trace_with_symbolic_execution()); + read_setting("report_symbolic_execution_purification") +} + +/// Use custom heap encoding. +/// +/// **Note:** This option is taken into account only when `unsafe_core_proof` is +/// true. +pub fn custom_heap_encoding() -> bool { + read_setting("custom_heap_encoding") +} + +/// Whether to omit QP injectivity functions when generating the custom heap +/// encoding. +/// +/// **Note:** This option is taken into account only when `custom_heap_encoding` is +/// true. +pub fn custom_heap_encoding_omit_injective() -> bool { + read_setting("custom_heap_encoding_omit_injective") +} + /// Whether the core proof (memory safety) should be verified. /// /// **Note:** This option is taken into account only when `unsafe_core_proof` is @@ -886,6 +1179,17 @@ pub fn verify_specifications() -> bool { read_setting("verify_specifications") } +/// Whether the postcondition framing should be verified. +/// +/// **Note:** This option is taken into account only when `unsafe_core_proof` is +/// true. +/// +/// **Note:** This option is taken into account only when `verify_core_proof` is +/// true. +pub fn verify_postcondition_frame_check() -> bool { + read_setting("verify_postcondition_frame_check") +} + /// Whether the types should be verified. /// /// **Note:** This option is taken into account only when `unsafe_core_proof` is @@ -924,6 +1228,14 @@ pub fn inline_caller_for() -> bool { read_setting("inline_caller_for") } +/// Whether to make the snapshot, an explicit parameter of the predicate. +/// +/// **Note:** This option is taken into account only when `unsafe_core_proof` is +/// true. +pub fn use_snapshot_parameters_in_predicates() -> bool { + read_setting("use_snapshot_parameters_in_predicates") +} + /// When enabled, replaces calls to the drop function with `assert false`. /// /// **Note:** This option is used only for testing. @@ -931,6 +1243,23 @@ pub fn check_no_drops() -> bool { read_setting("check_no_drops") } +/// When enabled, allows using `prusti_assume` and `prusti_structural_assume` +/// macros. +/// +/// **Note:** This option is taken into account only when `unsafe_core_proof` is +/// true. +pub fn allow_prusti_assume() -> bool { + read_setting("allow_prusti_assume") +} + +/// When enabled, allows using `assume_allocation_never_fails` macro. +/// +/// **Note:** This option is taken into account only when `unsafe_core_proof` is +/// true. +pub fn allow_assuming_allocation_never_fails() -> bool { + read_setting("allow_assuming_allocation_never_fails") +} + /// When enabled, Prusti uses the new VIR encoder. /// /// This is a temporary configuration flag. @@ -943,6 +1272,14 @@ pub fn use_new_encoder() -> bool { read_setting("use_new_encoder") } +/// How many times to unroll the pure function definitions. +/// +/// **Note:** This option is taken into account only when `unsafe_core_proof` is +/// true. +pub fn function_gas_amount() -> u32 { + read_setting("function_gas_amount") +} + /// How many parallel verifiers Silicon should use. pub fn number_of_parallel_verifiers() -> Option { read_setting("number_of_parallel_verifiers") @@ -958,6 +1295,41 @@ pub fn delete_basic_blocks() -> Vec { read_setting("delete_basic_blocks") } +/// The path to the SMT solver to be used by Svirpti. +pub fn svirpti_smt_solver() -> String { + read_setting("svirpti_smt_solver") +} + +/// The path to the log file in which Svirpti should log all communications with the SMT solver. +pub fn svirpti_smt_solver_log() -> Option { + read_setting("svirpti_smt_solver_log") +} + +/// Stop when the first verification error is found. +pub fn svirpti_stop_on_first_error() -> bool { + read_setting("svirpti_stop_on_first_error") +} + +/// Use the encoding of the heap based on pseudo-boolean theory. +pub fn svirpti_use_pseudo_boolean_heap() -> bool { + read_setting("svirpti_use_pseudo_boolean_heap") +} + +/// Try asserting `false` after each statement and report an error if succeed. +pub fn svirpti_enable_smoke_check() -> bool { + read_setting("svirpti_enable_smoke_check") +} + +/// Pre-instantiate quantifiers manually to reduce the work for Z3. +pub fn svirpti_enable_manual_triggering() -> bool { + read_setting("svirpti_enable_manual_triggering") +} + +/// Remove axioms which are supported by the manual instantiation of quantifiers. +pub fn svirpti_remove_unnecessary_axioms() -> bool { + read_setting("svirpti_remove_unnecessary_axioms") +} + /// When enabled, features not supported by Prusti will be reported as warnings /// rather than errors. pub fn skip_unsupported_features() -> bool { diff --git a/prusti-utils/src/utils/identifiers.rs b/prusti-utils/src/utils/identifiers.rs index ca1910ef10b..96a2ba7c681 100644 --- a/prusti-utils/src/utils/identifiers.rs +++ b/prusti-utils/src/utils/identifiers.rs @@ -16,4 +16,5 @@ pub fn encode_identifier(ident: String) -> String { .replace(' ', "$space$") .replace('&', "$amp$") .replace('*', "$star$") + .replace('\'', "$tick$") } diff --git a/prusti-viper/Cargo.toml b/prusti-viper/Cargo.toml index 8ff1f75d43a..7c780fac006 100644 --- a/prusti-viper/Cargo.toml +++ b/prusti-viper/Cargo.toml @@ -19,6 +19,7 @@ prusti-server = { path = "../prusti-server" } prusti-rustc-interface = { path = "../prusti-rustc-interface" } vir-crate = { package = "vir", path = "../vir" } tracing = { path = "../tracing" } +analysis = { path = "../analysis" } num-traits = "0.2" regex = "1.7" serde = "1.0" @@ -28,6 +29,9 @@ rustc-hash = "1.1.0" derive_more = "0.99.16" itertools = "0.10.3" once_cell = "1.17.1" +egg = { git = "https://github.com/vakaras/egg.git", branch = "from_enodes_with_explanations" } +ena = { version = "0.14.2", features = ["persistent"] } +rsmt2 = "0.16.2" [dev-dependencies] lazy_static = "1.4" diff --git a/prusti-viper/src/encoder/counterexamples/counterexample_translation.rs b/prusti-viper/src/encoder/counterexamples/counterexample_translation.rs index c211eecedd5..267ee735e7c 100644 --- a/prusti-viper/src/encoder/counterexamples/counterexample_translation.rs +++ b/prusti-viper/src/encoder/counterexamples/counterexample_translation.rs @@ -18,6 +18,7 @@ use prusti_rustc_interface::{ use rustc_hash::FxHashMap; use std::iter; use viper::silicon_counterexample::*; +use vir_crate::common::builtin_constants::DISCRIMINANT_FIELD_NAME; use DiscriminantsStateInterface; pub fn backtranslate( @@ -358,7 +359,7 @@ impl<'ce, 'tcx> CounterexampleTranslator<'ce, 'tcx> { let mut field_entries = vec![]; let mut variant = None; - let mut opt_discriminant = self.translate_int(map.get("discriminant")); + let mut opt_discriminant = self.translate_int(map.get(DISCRIMINANT_FIELD_NAME)); //need to find a discriminant to do something if opt_discriminant.is_none() { //try to find disc in the associated local variable diff --git a/prusti-viper/src/encoder/counterexamples/interface.rs b/prusti-viper/src/encoder/counterexamples/interface.rs index 183cd20cc55..a0958fc3d0c 100644 --- a/prusti-viper/src/encoder/counterexamples/interface.rs +++ b/prusti-viper/src/encoder/counterexamples/interface.rs @@ -1,13 +1,11 @@ +use prusti_interface::data::ProcedureDefId; use rustc_hash::FxHashMap; -use vir_crate::{ - common::check_mode::CheckMode, - low::{self as vir_low}, -}; +use vir_crate::low::{self as vir_low}; #[derive(Default)] pub(crate) struct MirProcedureMapping { //Map of all variables assigned in this basic block - mapping: FxHashMap>, + mapping: FxHashMap>, } #[derive(Debug)] @@ -24,7 +22,7 @@ impl MirProcedureMapping { procedure .basic_blocks .iter() - .map(|basic_block| { + .map(|(label, basic_block)| { let mut stmts = Vec::new(); for statement in &basic_block.statements { @@ -51,7 +49,7 @@ impl MirProcedureMapping { } }; BasicBlock { - label: basic_block.label.name.clone(), + label: label.name.clone(), successor, stmts, } @@ -84,27 +82,32 @@ impl MirProcedureMapping { } pub(crate) trait MirProcedureMappingInterface { - fn add_mapping(&mut self, program: &vir_low::Program); - fn get_mapping(&self, proc_name: String) -> Option<&Vec>; + fn add_mapping(&mut self, proc_def_id: ProcedureDefId, program: &vir_low::Program); + fn get_mapping(&self, def_id: ProcedureDefId) -> Option<&Vec>; } impl<'v, 'tcx: 'v> MirProcedureMappingInterface for super::super::Encoder<'v, 'tcx> { - fn add_mapping(&mut self, program: &vir_low::Program) { + fn add_mapping(&mut self, proc_def_id: ProcedureDefId, program: &vir_low::Program) { if let Some(vir_low_procedure) = program.procedures.first() { //at the moment a counterexample is only produced for the specifications-poof - if matches!(program.check_mode, CheckMode::Specifications) - || matches!(program.check_mode, CheckMode::Both) + if program.check_mode.check_specifications() + // matches!(program.check_mode, CheckMode::Specifications) + // || matches!(program.check_mode, CheckMode::Both) { let procedure_new = self .mir_procedure_mapping .translate_procedure_decl(vir_low_procedure); + // FIXME: `proc_def_id` is not unique. We should use program + // + procedure name here instead. However, this requires + // refactoring all code to include this information for all + // positions. self.mir_procedure_mapping .mapping - .insert(program.name.clone(), procedure_new); + .insert(proc_def_id, procedure_new); } } } - fn get_mapping(&self, proc_name: String) -> Option<&Vec> { - self.mir_procedure_mapping.mapping.get(&proc_name) + fn get_mapping(&self, def_id: ProcedureDefId) -> Option<&Vec> { + self.mir_procedure_mapping.mapping.get(&def_id) } } diff --git a/prusti-viper/src/encoder/counterexamples/mapping.rs b/prusti-viper/src/encoder/counterexamples/mapping.rs index eb3df45a7be..7be05f0bc97 100644 --- a/prusti-viper/src/encoder/counterexamples/mapping.rs +++ b/prusti-viper/src/encoder/counterexamples/mapping.rs @@ -52,8 +52,7 @@ impl<'ce, 'tcx, 'v> VarMappingInterface for super::counterexample_translation_refactored::CounterexampleTranslator<'ce, 'tcx, 'v> { fn create_mapping(&mut self, proc_def_id: ProcedureDefId, encoder: &Encoder) { - let name = encoder.env().name.get_absolute_item_name(proc_def_id); - if let Some(mir_procedure_mapping) = encoder.get_mapping(name) { + if let Some(mir_procedure_mapping) = encoder.get_mapping(proc_def_id) { for basic_block in mir_procedure_mapping { let label = &basic_block.label; diff --git a/prusti-viper/src/encoder/encoder.rs b/prusti-viper/src/encoder/encoder.rs index c6ff636ec35..deb4b3f73dc 100644 --- a/prusti-viper/src/encoder/encoder.rs +++ b/prusti-viper/src/encoder/encoder.rs @@ -244,12 +244,44 @@ impl<'v, 'tcx> Encoder<'v, 'tcx> { pub fn get_core_proof_programs(&mut self) -> Vec { if config::counterexample() && config::unsafe_core_proof(){ self.take_core_proof_programs().into_iter().map( - | program | { - self.add_mapping(&program); + |( def_id, program )| { + if let Some(def_id) = def_id { + self.add_mapping(def_id, &program); + } prusti_common::vir::program::Program::Low(program) }).collect() } else { - self.take_core_proof_programs().into_iter().map(prusti_common::vir::program::Program::Low).collect() + self.take_core_proof_programs().into_iter().map( + |(_, program)| { + prusti_common::vir::program::Program::Low(program) + }).collect() + } + } + + pub fn verify_core_proof_programs(&mut self) -> prusti_interface::data::VerificationResult { + assert!(config::unsafe_core_proof()); + assert_eq!(config::viper_backend(), "svirpti"); + let verification_results = self.take_verification_results(); + if verification_results.iter().all(|(_, result)| result.is_success()) { + if self.count_encoding_errors() > 0 { + prusti_interface::data::VerificationResult::Failure + } else { + prusti_interface::data::VerificationResult::Success + } + } else { + let mut prusti_errors = Vec::new(); + for (_def_id, result) in verification_results { + for error in result.get_errors() { + let prusti_error = self.error_manager().translate_verification_error(&error.as_viper_verification_error()); + prusti_errors.push(prusti_error); + } + } + prusti_errors.sort(); + for prusti_error in prusti_errors { + debug!("Prusti error: {:?}", prusti_error); + prusti_error.emit(&self.env.diagnostic); + } + prusti_interface::data::VerificationResult::Failure } } @@ -697,6 +729,18 @@ impl<'v, 'tcx> Encoder<'v, 'tcx> { } } + /// Returns true iff `def_id` is a function that uses raw pointers. + fn is_internally_unsafe_function(&self, def_id: ProcedureDefId) -> bool { + let mir = self.env.body.borrow_impure_fn_body_identity(def_id.expect_local()); + for local_decl in &mir.local_decls { + if let prusti_rustc_interface::middle::ty::TyKind::RawPtr(_) = + local_decl.ty.kind() + { + return true; + } + } + false + } #[tracing::instrument(level = "debug", skip(self))] pub fn process_encoding_queue(&mut self) { if let Err(error) = self.initialize() { @@ -714,30 +758,50 @@ impl<'v, 'tcx> Encoder<'v, 'tcx> { assert!(substs.is_empty()); if config::unsafe_core_proof() { - if self.env.query.is_unsafe_function(proc_def_id) { - if let Err(error) = self.encode_lifetimes_core_proof(proc_def_id, CheckMode::Both) { + if config::verify_core_proof() { + if let Err(error) = self.encode_lifetimes_core_proof(proc_def_id, CheckMode::MemorySafety) { self.register_encoding_error(error); - debug!("Error encoding function: {:?} {}", proc_def_id, CheckMode::Both); + debug!("Error encoding function: {:?} {}", proc_def_id, CheckMode::MemorySafety); } - } else { - if config::verify_core_proof() { - if let Err(error) = self.encode_lifetimes_core_proof(proc_def_id, CheckMode::CoreProof) { - self.register_encoding_error(error); - debug!("Error encoding function: {:?} {}", proc_def_id, CheckMode::CoreProof); - } - } - if config::verify_specifications() { - let check_mode = if config::verify_specifications_with_core_proof() { - CheckMode::Both - } else { - CheckMode::Specifications - }; - if let Err(error) = self.encode_lifetimes_core_proof(proc_def_id, check_mode) { - self.register_encoding_error(error); - debug!("Error encoding function: {:?} {}", proc_def_id, check_mode); - } + } + if config::verify_specifications() { + if let Err(error) = self.encode_lifetimes_core_proof(proc_def_id, CheckMode::MemorySafetyWithFunctional) { + self.register_encoding_error(error); + debug!("Error encoding function: {:?} {}", proc_def_id, CheckMode::MemorySafetyWithFunctional); } } + // if self.env.query.is_unsafe_function(proc_def_id) { + // if let Err(error) = self.encode_lifetimes_core_proof(proc_def_id, CheckMode::UnsafeSafety) { + // self.register_encoding_error(error); + // debug!("Error encoding function: {:?} {}", proc_def_id, CheckMode::UnsafeSafety); + // } + // } else if config::verify_specifications_with_core_proof() || self.is_internally_unsafe_function(proc_def_id) { + // if config::verify_core_proof() { + // if let Err(error) = self.encode_lifetimes_core_proof(proc_def_id, CheckMode::MemorySafety) { + // self.register_encoding_error(error); + // debug!("Error encoding function: {:?} {}", proc_def_id, CheckMode::MemorySafety); + // } + // } + // if config::verify_specifications() { + // if let Err(error) = self.encode_lifetimes_core_proof(proc_def_id, CheckMode::MemorySafetyWithFunctional) { + // self.register_encoding_error(error); + // debug!("Error encoding function: {:?} {}", proc_def_id, CheckMode::MemorySafetyWithFunctional); + // } + // } + // } else { + // if config::verify_core_proof() { + // if let Err(error) = self.encode_lifetimes_core_proof(proc_def_id, CheckMode::PurificationSoudness) { + // self.register_encoding_error(error); + // debug!("Error encoding function: {:?} {}", proc_def_id, CheckMode::PurificationSoudness); + // } + // } + // if config::verify_specifications() { + // if let Err(error) = self.encode_lifetimes_core_proof(proc_def_id, CheckMode::PurificationFunctional) { + // self.register_encoding_error(error); + // debug!("Error encoding function: {:?} {}", proc_def_id, CheckMode::PurificationFunctional); + // } + // } + // } continue; } @@ -792,9 +856,9 @@ impl<'v, 'tcx> Encoder<'v, 'tcx> { } EncodingTask::Type { ty } => { if config::unsafe_core_proof() && config::verify_core_proof() && config::verify_types() { - if let Err(error) = self.encode_core_proof_for_type(ty, CheckMode::CoreProof) { + if let Err(error) = self.encode_core_proof_for_type(ty, CheckMode::MemorySafety) { self.register_encoding_error(error); - debug!("Error encoding type: {:?} {}", ty, CheckMode::CoreProof); + debug!("Error encoding type: {:?} {}", ty, CheckMode::MemorySafety); } } } diff --git a/prusti-viper/src/encoder/errors/error_manager.rs b/prusti-viper/src/encoder/errors/error_manager.rs index 53d5e0e6c3d..dcd2457a299 100644 --- a/prusti-viper/src/encoder/errors/error_manager.rs +++ b/prusti-viper/src/encoder/errors/error_manager.rs @@ -45,6 +45,7 @@ pub enum PanicCause { pub enum BuiltinMethodKind { WriteConstant, MovePlace, + CopyPlace, IntoMemoryBlock, SplitMemoryBlock, JoinMemoryBlock, @@ -52,6 +53,7 @@ pub enum BuiltinMethodKind { ChangeUniqueRefPlace, DuplicateFracRef, Assign, + RestoreRawBorrowed, } /// In case of verification error, this enum will contain additional information @@ -62,16 +64,28 @@ pub enum ErrorCtxt { Panic(PanicCause), /// A Viper `exhale expr` that encodes the call of a Rust procedure with precondition `expr` ExhaleMethodPrecondition, + /// A Viper `exhale expr` that encodes the call of a Rust procedure with + /// precondition `expr`; missing permission. + ExhaleMethodPreconditionPermissionExhale, /// An error when assuming method's functional specification. UnexpectedAssumeMethodPrecondition, /// An error when assuming method's functional specification. UnexpectedAssumeMethodPostcondition, /// A Viper `assert expr` that encodes the call of a Rust procedure with precondition `expr` AssertMethodPostcondition, + /// A Viper `assert expr` that encodes the call of a Rust procedure with + /// precondition `expr`; missing permission. + AssertMethodPostconditionPermissionExhale, + /// A Viper `assert expr` that encodes the call of a Rust procedure with + /// precondition `expr` when the precondition is not required to hold. + AssertMethodPostconditionNoPanic, /// A Viper `assert expr` that encodes the call of a Rust procedure with precondition `expr` AssertMethodPostconditionTypeInvariants, /// A Viper `exhale expr` that encodes the end of a Rust procedure with postcondition `expr` ExhaleMethodPostcondition, + /// A Viper `exhale expr` that encodes the end of a Rust procedure with + /// postcondition `expr`; missing permission. + ExhaleMethodPostconditionPermissionExhale, /// A generic loop invariant error. LoopInvariant, /// A Viper `exhale expr` that exhales the permissions of a loop invariant `expr` @@ -161,8 +175,12 @@ pub enum ErrorCtxt { UnfoldUnionVariant, /// Failed to call a procedure. ProcedureCall, + /// Failed to call a procedure due to a missing permission. + ProcedureCallPermissionExhale, /// Failed to call a drop handler. DropCall, + /// Failed to call a drop handler due to a missing permission, + DropCallPermissionExhale, /// Failed to encode lifetimes LifetimeEncoding, /// Failed to encode LifetimeTake @@ -181,6 +199,10 @@ pub enum ErrorCtxt { CloseMutRef, /// Failed to encode CloseFracRef CloseFracRef, + /// Closing a reference failed. + CloseRef, + /// Opening a reference failed. + OpenRef, /// Failed to set an active variant of an union. SetEnumVariant, /// A user assumption raised an error @@ -188,6 +210,51 @@ pub enum ErrorCtxt { /// The state that fold-unfold algorithm deduced as unreachable, is actually /// reachable. UnreachableFoldingState, + /// A user-specified pack operation failed. + Pack, + /// A user-specified unpack operation failed. + Unpack, + /// A user-specified forget-initialization operation failed. + ForgetInitialization, + /// Restore a place borrowed via raw pointer. + RestoreRawBorrowed, + /// Restore a place borrowed via mutable reference. + RestoreMutBorrowed, + /// An error in the definition of the type invariant. + TypeInvariantDefinition, + /// Pointer dereference in the postcondition is not framed by permissions. + /// + /// Note: This can also be reported when the underlying solver failing to + /// prove that the postcondition implies itself. + MethodPostconditionFraming, + /// An unexpected error when assuming false to end method postcondition + /// framing check. + UnexpectedAssumeEndMethodPostconditionFraming, + StashRange, + RestoreStashRange, + JoinRange, + SplitRange, + UnexpectedSpecificationExpression, + Resolve, + ExhaleNonAliasedPredicate, + UnexpectedUnreachable, + /// A function annotated with #[no_panic] may panic. + NoPanicPanics, + /// Precondition of a checked binary operation failed. + CheckedBinaryOpPrecondition, + /// Materializing a predicate for purification algorithm failed. + MaterializePredicate, + /// An unexpected failure while assuming that the allocation cannot fail. + UnexpectedAssumeAllocationNeverFails, + /// A failure when case splitting. + CaseSplit, + /// An unfold of a UniqueRef predicate, which is an illegal (unsound) + /// operation if the struct has a structural invariant. + IllegalUnfoldUniqueRef, + // /// Permission error when dereferencing a raw pointer. + // EnsureOwnedPredicate, + /// An error when calling std::mem::forget. + MemForget, } /// The error manager @@ -240,6 +307,11 @@ impl<'tcx> ErrorManager<'tcx> { self.error_contexts.insert(pos.id(), error_ctxt); } + pub fn get_error(&mut self, pos: Position) -> ErrorCtxt { + assert_ne!(pos, Position::default(), "Trying to obtain an error on a default position"); + self.error_contexts[&pos.id()].clone() + } + /// Creates a new position with `error_ctxt` that is linked to `pos`. This /// method is used for setting the surrounding context position of an /// expression's position. @@ -403,6 +475,7 @@ impl<'tcx> ErrorManager<'tcx> { .set_help("This might be a bug in the Rust compiler.") } + ("exhale.failed:assertion.false", ErrorCtxt::ExhaleMethodPrecondition) | ("assert.failed:assertion.false", ErrorCtxt::ExhaleMethodPrecondition) => { PrustiError::verification("precondition might not hold.", error_span) .set_failing_assertion(opt_cause_span) @@ -558,11 +631,26 @@ impl<'tcx> ErrorManager<'tcx> { .set_failing_assertion(opt_cause_span) } - ("assert.failed:assertion.false", ErrorCtxt::AssertMethodPostcondition) => { + ("assert.failed:assertion.false", ErrorCtxt::AssertMethodPostcondition) + |("exhale.failed:assertion.false", ErrorCtxt::AssertMethodPostcondition)=> { PrustiError::verification("postcondition might not hold.".to_string(), error_span) .push_primary_span(opt_cause_span) } + ("assert.failed:assertion.false", ErrorCtxt::AssertMethodPostconditionNoPanic) + |("exhale.failed:assertion.false", ErrorCtxt::AssertMethodPostconditionNoPanic)=> { + PrustiError::verification("postcondition might not hold when precondition is not assumed.".to_string(), error_span) + .push_primary_span(opt_cause_span) + .set_help("Postcondition needs to hold without precondition because of `#[no_panic_ensures_postcondition]` annotation.") + } + + ("inhale.failed:insufficient.permission", ErrorCtxt::MethodPostconditionFraming) + | ("application.precondition:insufficient.permission", ErrorCtxt::MethodPostconditionFraming) => { + PrustiError::verification("the postcondition might not be self-framing.".to_string(), error_span) + .push_primary_span(opt_cause_span) + .set_help("This error might be also caused by prover failing to prove that the postcondition implies itself") + } + ( "assert.failed:assertion.false", ErrorCtxt::AssertMethodPostconditionTypeInvariants, @@ -644,6 +732,7 @@ impl<'tcx> ErrorManager<'tcx> { ).set_failing_assertion(opt_cause_span) } + ("assert.failed:map.key.contains", ErrorCtxt::Panic(PanicCause::Assert)) | ("inhale.failed:map.key.contains", _) => { PrustiError::verification( "the key might not be in the map".to_string(), @@ -705,6 +794,117 @@ impl<'tcx> ErrorManager<'tcx> { ) } + ("assert.failed:assertion.false", ErrorCtxt::ExhaleNonAliasedPredicate) => { + PrustiError::verification( + "there might be insufficient permission to a place".to_string(), + error_span + ) + } + + // ("assert.failed:assertion.false", ErrorCtxt::AssertMethodPostconditionPermissionExhale) => { + // PrustiError::verification( + // "there might be insufficient permission to a place".to_string(), + // error_span + // ) + // } + + ("exhale.failed:insufficient.permission", ErrorCtxt::CopyPlace) | + ("exhale.failed:insufficient.permission", ErrorCtxt::MovePlace) | + ("assert.failed:assertion.false", ErrorCtxt::CopyPlace) | + ("assert.failed:assertion.false", ErrorCtxt::MovePlace) | + ("call.precondition:insufficient.permission", ErrorCtxt::CopyPlace) | + ("call.precondition:insufficient.permission", ErrorCtxt::MovePlace) => { + PrustiError::verification( + "the accessed place may not be allocated or initialized".to_string(), + error_span + ).set_failing_assertion(opt_cause_span) + } + + ("assert.failed:assertion.false", ErrorCtxt::WritePlace) | + ("exhale.failed:insufficient.permission", ErrorCtxt::WritePlace) | + ("call.precondition:insufficient.permission", ErrorCtxt::WritePlace) => { + PrustiError::verification( + "the accessed memory location must be allocated and uninitialized".to_string(), + error_span + ).set_failing_assertion(opt_cause_span) + } + + ("assert.failed:assertion.false", ErrorCtxt::AssertMethodPostconditionPermissionExhale) | + ("exhale.failed:insufficient.permission", ErrorCtxt::AssertMethodPostcondition) | + ("exhale.failed:insufficient.permission", ErrorCtxt::AssertMethodPostconditionNoPanic) | + ("exhale.failed:insufficient.permission", ErrorCtxt::Assign) | + ("application.precondition:insufficient.permission", ErrorCtxt::AssertMethodPostcondition) | + ("application.precondition:insufficient.permission", ErrorCtxt::TypeInvariantDefinition) => { + PrustiError::verification( + "there might be insufficient permission to dereference a raw pointer".to_string(), + error_span + ).set_failing_assertion(opt_cause_span) + } + + ("assert.failed:assertion.false", ErrorCtxt::ExhaleMethodPreconditionPermissionExhale) | + ("exhale.failed:insufficient.permission", ErrorCtxt::ExhaleMethodPrecondition) => { + PrustiError::verification( + "the permission specified in the precondition might be missing".to_string(), + error_span + ).set_failing_assertion(opt_cause_span) + } + + ("exhale.failed:insufficient.permission", ErrorCtxt::AssertLoopInvariantOnEntry) => { + PrustiError::verification( + "the permission specified in the loop invariant might be missing on entry".to_string(), + error_span + ).set_failing_assertion(opt_cause_span) + } + + ("call.precondition:assertion.false", ErrorCtxt::Assign | ErrorCtxt::CopyPlace) => { + PrustiError::verification( + "the type invariant of the constructed object might not hold".to_string(), + error_span + ).set_failing_assertion(opt_cause_span) + } + + ("call.precondition:insufficient.permission", ErrorCtxt::LifetimeEncoding) => { + PrustiError::verification( + "there might be insufficient permission to a lifetime token".to_string(), + error_span + ).set_failing_assertion(opt_cause_span) + .set_help("This could be caused by an unclosed reference.") + } + + ("assert.failed:assertion.false", ErrorCtxt::UnexpectedStorageDead) | + ("exhale.failed:insufficient.permission", ErrorCtxt::UnexpectedStorageDead) | + ("application.precondition:insufficient.permission", ErrorCtxt::UnexpectedStorageDead) => { + PrustiError::verification( + "there might be insufficient permission to a place".to_string(), + error_span + ).set_failing_assertion(opt_cause_span) + .set_help("This could be caused by lifetime contraints not matching the real borrows.") + } + + ("assert.failed:assertion.false", ErrorCtxt::NoPanicPanics) => { + PrustiError::verification( + "the function may panic".to_string(), + error_span + ).set_failing_assertion(opt_cause_span) + .set_help("The function is required not to panic because of `#[no_panic]` annotation.") + } + + ("assert.failed:assertion.false", ErrorCtxt::IllegalUnfoldUniqueRef) => { + PrustiError::verification( + "illegal unpack".to_string(), + error_span + ).set_failing_assertion(opt_cause_span) + .set_help("It is unsound to unpack mutable references into structs with structural invariants.") + } + + ("exhale.failed:assertion.false", ErrorCtxt::CheckedBinaryOpPrecondition) | + ("call.precondition:assertion.false", ErrorCtxt::CheckedBinaryOpPrecondition) => { + PrustiError::verification( + "the operation may overflow or underflow".to_string(), + error_span + ).set_failing_assertion(opt_cause_span) + } + (full_err_id, ErrorCtxt::Unexpected) => { PrustiError::internal( format!( diff --git a/prusti-viper/src/encoder/foldunfold/requirements.rs b/prusti-viper/src/encoder/foldunfold/requirements.rs index ba63e01139a..2faa94b6eb3 100644 --- a/prusti-viper/src/encoder/foldunfold/requirements.rs +++ b/prusti-viper/src/encoder/foldunfold/requirements.rs @@ -12,7 +12,10 @@ use crate::encoder::foldunfold::{ use log::debug; use rustc_hash::FxHashSet; use std::iter::FromIterator; -use vir_crate::polymorphic::{self as vir, PermAmount}; +use vir_crate::{ + common::builtin_constants::DISCRIMINANT_FIELD_NAME, + polymorphic::{self as vir, PermAmount}, +}; pub trait RequiredStmtPermissionsGetter { /// Returns the permissions required for the statement to be well-defined. @@ -261,7 +264,7 @@ fn get_all_required_expr_permissions( let (base_reqs, base_discr) = get_all_required_expr_permissions(base, preds); reqs.extend(base_reqs); discr.extend(base_discr); - if field.name == "discriminant" { + if field.name == DISCRIMINANT_FIELD_NAME { debug_assert!(base.is_place()); discr.insert(base.clone()); } diff --git a/prusti-viper/src/encoder/high/builtin_functions/encoder.rs b/prusti-viper/src/encoder/high/builtin_functions/encoder.rs index 3bdd18aa1a5..440a61827a8 100644 --- a/prusti-viper/src/encoder/high/builtin_functions/encoder.rs +++ b/prusti-viper/src/encoder/high/builtin_functions/encoder.rs @@ -93,8 +93,8 @@ pub(super) fn encode_builtin_function_def(kind: BuiltinFunctionHighKind) -> vir_ BuiltinFunctionHighKind::SliceLen { slice_pred_type, .. } => { - let self_var = vir_high::VariableDecl::new("self", slice_pred_type); - let result_var = vir_high::VariableDecl::new("__result", vir_high::Type::MInt); + let self_var = vir_high::VariableDecl::self_variable(slice_pred_type); + let result_var = vir_high::VariableDecl::result_variable(vir_high::Type::MInt); vir_high::FunctionDecl { name: fn_name, diff --git a/prusti-viper/src/encoder/high/lower/expression.rs b/prusti-viper/src/encoder/high/lower/expression.rs index 3f53a8d5354..f39293ddd1e 100644 --- a/prusti-viper/src/encoder/high/lower/expression.rs +++ b/prusti-viper/src/encoder/high/lower/expression.rs @@ -33,6 +33,9 @@ impl IntoPolymorphic for vir_high::Expression { vir_high::Expression::Deref(expression) => { vir_poly::Expr::Field(expression.lower(encoder)) } + vir_high::Expression::Final(expression) => { + unimplemented!("not supported lowering: {}", expression); + } vir_high::Expression::AddrOf(expression) => { vir_poly::Expr::AddrOf(expression.lower(encoder)) } @@ -75,6 +78,15 @@ impl IntoPolymorphic for vir_high::Expression { vir_high::Expression::Downcast(expression) => { vir_poly::Expr::Downcast(expression.lower(encoder)) } + vir_high::Expression::AccPredicate(_expression) => { + todo!() + } + vir_high::Expression::Unfolding(_expression) => { + todo!() + } + vir_high::Expression::EvalIn(_expression) => { + todo!() + } } } } @@ -161,6 +173,9 @@ impl IntoPolymorphic for vir_high::expression::ConstantValue { vir_high::expression::ConstantValue::BigInt(value) => { vir_poly::Const::BigInt(value.clone()) } + vir_high::expression::ConstantValue::String(_) => { + unreachable!("String constants are not supported"); + } vir_high::expression::ConstantValue::FnPtr => vir_poly::Const::FnPtr, } } diff --git a/prusti-viper/src/encoder/high/lower/predicates.rs b/prusti-viper/src/encoder/high/lower/predicates.rs index 0c0cc278330..7cdceabcb23 100644 --- a/prusti-viper/src/encoder/high/lower/predicates.rs +++ b/prusti-viper/src/encoder/high/lower/predicates.rs @@ -40,9 +40,7 @@ impl IntoPredicates for vir_high::TypeDecl { vir_high::TypeDecl::Never => construct_never_predicate(encoder), vir_high::TypeDecl::Closure(ty_decl) => ty_decl.lower(ty, encoder), vir_high::TypeDecl::Unsupported(ty_decl) => ty_decl.lower(ty, encoder), - vir_high::TypeDecl::Trusted(_ty_decl) => { - unreachable!("Trusted types are not supported") - } + vir_high::TypeDecl::Trusted(ty_decl) => ty_decl.lower(ty, encoder), } } } @@ -269,3 +267,14 @@ impl IntoPredicates for vir_high::type_decl::Unsupported { Ok(vec![predicate]) } } + +impl IntoPredicates for vir_high::type_decl::Trusted { + fn lower( + &self, + ty: &vir_high::Type, + encoder: &impl HighTypeEncoderInterfacePrivate, + ) -> Predicates { + let predicate = Predicate::new_abstract(ty.lower(encoder)); + Ok(vec![predicate]) + } +} diff --git a/prusti-viper/src/encoder/high/lower/ty.rs b/prusti-viper/src/encoder/high/lower/ty.rs index 02cf6358cf0..210ce3c0f12 100644 --- a/prusti-viper/src/encoder/high/lower/ty.rs +++ b/prusti-viper/src/encoder/high/lower/ty.rs @@ -15,6 +15,9 @@ impl IntoPolymorphic for vir_high::Type { vir_high::Type::MPerm => { unreachable!("Permissions are used only in the unsafe core proof") } + vir_high::Type::MByte | vir_high::Type::MBytes => { + unreachable!("Bytes are used only in the unsafe core proof") + } vir_high::Type::Bool => vir_poly::Type::typed_ref("bool"), vir_high::Type::Int(int) => vir_poly::Type::typed_ref(int.to_string().to_lowercase()), vir_high::Type::Sequence(ty) => vir_poly::Type::Seq(vir_poly::SeqType { diff --git a/prusti-viper/src/encoder/high/procedures/inference/action.rs b/prusti-viper/src/encoder/high/procedures/inference/action.rs index c4d421aab4b..146a85e0617 100644 --- a/prusti-viper/src/encoder/high/procedures/inference/action.rs +++ b/prusti-viper/src/encoder/high/procedures/inference/action.rs @@ -11,6 +11,7 @@ pub(in super::super) enum Action { /// Convert the specified `Owned(place)` into `MemoryBlock(place)`. OwnedIntoMemoryBlock(ConversionState), RestoreMutBorrowed(RestorationState), + RestoreRawBorrowed(RawRestorationState), Unreachable(UnreachableState), } @@ -29,6 +30,13 @@ impl std::fmt::Display for Action { Action::RestoreMutBorrowed(RestorationState { place, .. }) => { write!(f, "RestoreMutBorrowed({place})") } + Action::RestoreRawBorrowed(RawRestorationState { + borrowing_place, + borrowed_place, + .. + }) => { + write!(f, "RestoreRawBorrowed({borrowing_place}, {borrowed_place})") + } Action::Unreachable(_) => write!(f, "Unreachable"), } } @@ -53,6 +61,14 @@ pub(in super::super) struct ConversionState { pub(in super::super) struct RestorationState { pub(in super::super) lifetime: vir_typed::ty::LifetimeConst, pub(in super::super) place: vir_typed::Expression, + pub(in super::super) is_reborrow: bool, + pub(in super::super) condition: Option, +} + +#[derive(Debug)] +pub(in super::super) struct RawRestorationState { + pub(in super::super) borrowing_place: vir_typed::Expression, + pub(in super::super) borrowed_place: vir_typed::Expression, pub(in super::super) condition: Option, } @@ -81,6 +97,10 @@ impl Action { condition: Some(condition.clone()), ..state }), + Self::RestoreRawBorrowed(state) => Self::RestoreRawBorrowed(RawRestorationState { + condition: Some(condition.clone()), + ..state + }), Self::Unreachable(state) => Self::Unreachable(UnreachableState { condition: Some(condition.clone()), ..state @@ -124,10 +144,23 @@ impl Action { pub(in super::super) fn restore_mut_borrowed( lifetime: vir_typed::ty::LifetimeConst, place: vir_typed::Expression, + is_reborrow: bool, ) -> Self { Self::RestoreMutBorrowed(RestorationState { lifetime, place, + is_reborrow, + condition: None, + }) + } + + pub(in super::super) fn restore_raw_borrowed( + borrowing_place: vir_typed::Expression, + borrowed_place: vir_typed::Expression, + ) -> Self { + Self::RestoreRawBorrowed(RawRestorationState { + borrowing_place, + borrowed_place, condition: None, }) } diff --git a/prusti-viper/src/encoder/high/procedures/inference/ensurer.rs b/prusti-viper/src/encoder/high/procedures/inference/ensurer.rs index 5209826b1f7..5b0fd6e2bc8 100644 --- a/prusti-viper/src/encoder/high/procedures/inference/ensurer.rs +++ b/prusti-viper/src/encoder/high/procedures/inference/ensurer.rs @@ -11,7 +11,7 @@ use crate::encoder::{ use log::debug; use prusti_rustc_interface::errors::MultiSpan; use vir_crate::{ - common::position::Positioned, + common::{builtin_constants::ADDRESS_FIELD_NAME, position::Positioned}, middle as vir_mid, typed::{self as vir_typed, operations::ty::Typed}, }; @@ -69,7 +69,7 @@ pub(in super::super) fn try_ensure_enum_discriminant_by_unfolding( permission_kind: PermissionKind, ) -> SpannedEncodingResult<(Option>, Vec)> { let mut actions = Vec::new(); - match state.get_predicates_state(place)? { + match state.get_predicates_state_mut(place)? { PredicateState::Unconditional(unconditional_predicate_state) => { if check_contains_place(unconditional_predicate_state, place, permission_kind)? || unconditional_predicate_state @@ -136,11 +136,12 @@ pub(in super::super) fn ensure_required_permission( let (place, permission_kind) = match required_permission { Permission::MemoryBlock(place) => (place, PermissionKind::MemoryBlock), Permission::Owned(place) => (place, PermissionKind::Owned), - Permission::MutBorrowed(borrow) => unreachable!("requiring a borrow: {}", borrow), + Permission::Blocked(borrow) => unreachable!("requiring a blocked place: {}", borrow), + Permission::RawBlocked(place) => unreachable!("requiring a raw blocked place: {}", place), }; let base = place.get_base().erase_lifetime(); - match state.get_predicates_state(&place)? { + match state.get_predicates_state_mut(&place)? { PredicateState::Unconditional(unconditional_predicate_state) => { if ensure_permission_in_state( context, @@ -284,7 +285,7 @@ fn check_contains_place( let address_place = vir_typed::Expression::field( place.clone(), vir_typed::FieldDecl::new( - "address$", + ADDRESS_FIELD_NAME, 0usize, vir_typed::Type::Int(vir_typed::ty::Int::Usize), ), @@ -401,12 +402,28 @@ fn ensure_permission_in_state( actions.push(Action::fold(permission_kind, place.clone(), enum_variant)); predicate_state.insert(permission_kind, place)?; false - } else if let Some((prefix, lifetime)) = predicate_state.contains_blocked(&place)? { + } else if let Some((prefix, lifetime, is_reborrow)) = + predicate_state.contains_blocked(&place)? + { let prefix = prefix.clone(); let lifetime = lifetime.clone(); predicate_state.remove_mut_borrowed(&prefix)?; predicate_state.insert(PermissionKind::Owned, prefix.clone())?; - actions.push(Action::restore_mut_borrowed(lifetime, prefix.clone())); + actions.push(Action::restore_mut_borrowed( + lifetime, + prefix.clone(), + is_reborrow, + )); + ensure_permission_in_state(context, predicate_state, place, permission_kind, actions)? + } else if let Some((prefix, borrowing_place)) = predicate_state.contains_raw_blocked(&place)? { + let prefix = prefix.clone(); + let borrowing_place = borrowing_place.clone(); + predicate_state.remove_raw_blocked(&prefix)?; + predicate_state.insert(PermissionKind::Owned, prefix.clone())?; + actions.push(Action::restore_raw_borrowed( + borrowing_place, + prefix.clone(), + )); ensure_permission_in_state(context, predicate_state, place, permission_kind, actions)? } else if permission_kind == PermissionKind::MemoryBlock && can_place_be_ensured_in(context, &place, PermissionKind::Owned, predicate_state)? diff --git a/prusti-viper/src/encoder/high/procedures/inference/eval_using.rs b/prusti-viper/src/encoder/high/procedures/inference/eval_using.rs new file mode 100644 index 00000000000..806083a06a1 --- /dev/null +++ b/prusti-viper/src/encoder/high/procedures/inference/eval_using.rs @@ -0,0 +1,308 @@ +use super::{ensurer::Context, state::PredicateState}; +use crate::{ + encoder::{ + errors::SpannedEncodingResult, + high::procedures::inference::{ + ensurer::ExpandedPermissionKind, permission::PermissionKind, + }, + }, + error_incorrect, error_internal, +}; +use vir_crate::{ + common::{builtin_constants::ADDRESS_FIELD_NAME, position::Positioned}, + typed::{self as vir_typed, operations::ty::Typed}, +}; + +pub(super) fn wrap_in_eval_using( + context: &mut impl Context, + state: &mut super::state::FoldUnfoldState, + mut expression: vir_typed::Expression, +) -> SpannedEncodingResult { + let accessed_places = strip_dereferences(expression.collect_all_places_with_old_locals()); + let mut framing_places = Vec::new(); + let mut context_kinds = Vec::new(); + for accessed_place in accessed_places { + if let Some(old_wrap) = check_old_wraps_and_return_first(&accessed_place, None) { + let context_kind = match old_wrap { + vir_typed::Expression::LabelledOld(vir_typed::LabelledOld { + base: box vir_typed::Expression::Local(_), + .. + // }) => vir_typed::EvalInContextKind::Old, + // _ => vir_typed::EvalInContextKind::OldOpenedRefPredicate, + }) => vir_typed::EvalInContextKind::Predicate, + _ => vir_typed::EvalInContextKind::OpenedRefPredicate, + }; + if !framing_places.contains(old_wrap) { + // FIXME: We should look up the actual state of the place in the + // old state instead of just assuming that it is fully folded + // (which works most of the time because old refers to + // preconditions). + framing_places.push(old_wrap.clone()); + context_kinds.push(context_kind); + } + continue; + } + let predicates_state = + if let Some(predicate_state) = state.try_get_predicates_state(&accessed_place)? { + predicate_state + } else { + // If `place` is not known to fold-unfold, then it is not an encoded + // Rust place, but a ghost variable emitted by the encoding. + // + // FIXME: Instead of relying on fold-unfold, we should distinguish + // between Rust places and places emitted as part of our encoding. + continue; + }; + match predicates_state { + PredicateState::Unconditional(state) => { + collect_framing_places_from_a_state( + context, + state, + &accessed_place, + &mut framing_places, + &mut context_kinds, + expression.position(), + )?; + } + PredicateState::Conditional(states) => { + let mut states_iter = states.values(); + let first_state = states_iter.next().unwrap(); + if states_iter.all(|state| { + state.owned_equal(first_state) + && state.memory_block_stack_equal(first_state) + && state.blocked_equal(first_state) + && state.raw_blocked_equal(first_state) + }) { + collect_framing_places_from_a_state( + context, + first_state, + &accessed_place, + &mut framing_places, + &mut context_kinds, + expression.position(), + )?; + } else { + // FIXME: For now pick the first one that works. + let mut found = false; + for state in states.values() { + if collect_framing_places_from_a_state( + context, + state, + &accessed_place, + &mut framing_places, + &mut context_kinds, + expression.position(), + ) + .is_ok() + { + found = true; + break; + } + } + if !found { + unimplemented!("Conditional predicate state: {predicates_state}"); + } + } + } + } + } + let position = expression.position(); + while let Some(framing_place) = framing_places.pop() { + let kind = context_kinds.pop().unwrap(); + let context = if matches!( + kind, + vir_typed::EvalInContextKind::Predicate + | vir_typed::EvalInContextKind::OpenedRefPredicate // | vir_typed::EvalInContextKind::Old + // | vir_typed::EvalInContextKind::OldOpenedRefPredicate + ) { + if let vir_typed::Expression::LabelledOld(labelled_old) = framing_place { + vir_typed::Expression::labelled_old( + labelled_old.label, + vir_typed::Expression::acc_predicate( + vir_typed::Predicate::owned_non_aliased(*labelled_old.base, position), + position, + ), + position, + ) + } else { + vir_typed::Expression::acc_predicate( + vir_typed::Predicate::owned_non_aliased(framing_place, position), + position, + ) + } + } else { + framing_place + }; + expression = vir_typed::Expression::eval_in(context, kind, expression, position); + } + Ok(expression) +} + +fn collect_framing_places_from_a_state( + context: &mut impl Context, + state: &super::state::PredicateStateOnPath, + accessed_place: &vir_typed::Expression, + framing_places: &mut Vec, + context_kinds: &mut Vec, + position: vir_typed::Position, +) -> SpannedEncodingResult<()> { + if let Some(framing_place) = state.find_prefix(PermissionKind::Owned, accessed_place) { + if !framing_places.contains(&framing_place) { + framing_places.push(framing_place); + if state.is_opened_ref(accessed_place)?.is_some() { + context_kinds.push(vir_typed::EvalInContextKind::OpenedRefPredicate); + } else { + context_kinds.push(vir_typed::EvalInContextKind::Predicate); + } + } + } else if let Some(_) = state.find_prefix(PermissionKind::MemoryBlock, accessed_place) { + let span = context.get_span(position).unwrap(); + error_internal!(span => format!("found an uninitialized place in specification: {accessed_place}")); + } else if state.contains_blocked(accessed_place)?.is_some() { + let span = context.get_span(position).unwrap(); + error_incorrect!(span => "cannot use specifications to trigger unblocking of a blocked place"); + } else if accessed_place.is_address_field() { + // Address field, just ignore it. + } else { + // Find a lowest place that can be a parent of `accessed_place` and that + // could be assembled from `state`. + let mut root_framing_place = accessed_place; + while state + .contains_non_discriminant_with_prefix(PermissionKind::Owned, root_framing_place) + .is_none() + { + if let Some(parent_accessed_place) = root_framing_place.get_parent_ref() { + root_framing_place = parent_accessed_place; + } else { + break; + } + } + // TODO: Make generic by making recursive. + if let Some(witness) = + state.contains_non_discriminant_with_prefix(PermissionKind::Owned, root_framing_place) + { + assert!( + !root_framing_place.get_type().has_variants(), + "unimplemented" + ); + for (kind, framing_place) in context.expand_place(root_framing_place, witness)? { + assert_eq!(kind, ExpandedPermissionKind::Same); + assert!( + state.contains(PermissionKind::Owned, &framing_place), + "TODO: make recursive: framing_place={framing_place} root_framing_place={root_framing_place} state:\n{state}" + ); + + // FIXME: Code duplication. + if !framing_places.contains(&framing_place) { + framing_places.push(framing_place); + if state.is_opened_ref(accessed_place)?.is_some() { + context_kinds.push(vir_typed::EvalInContextKind::OpenedRefPredicate); + } else { + context_kinds.push(vir_typed::EvalInContextKind::Predicate); + } + } + } + framing_places.push(root_framing_place.clone()); + context_kinds.push(vir_typed::EvalInContextKind::SafeConstructor); + } else { + unimplemented!( + "TODO: A proper error message that failed to assemble a place from a state" + ); + } + } + Ok(()) +} + +fn check_old_wraps_and_return_first<'a>( + accessed_place: &'a vir_typed::Expression, + current_label: Option<&'a str>, +) -> Option<&'a vir_typed::Expression> { + if let vir_typed::Expression::LabelledOld(vir_typed::LabelledOld { base, label, .. }) = + accessed_place + { + if let Some(current_label) = current_label { + assert_eq!( + label, current_label, + "the current implementation assumes that all labels are the same" + ); + } + let result = check_old_wraps_and_return_first(base, Some(label)); + if result.is_some() { + result + } else { + Some(accessed_place) + } + } else if let Some(parent) = accessed_place.get_parent_ref_step_into_old() { + check_old_wraps_and_return_first(parent, current_label) + } else { + None + } +} + +/// Does the following clean-up actions: +/// +/// 1. Strips places up to the first raw pointer dereference. We need to do this +/// because accesses below raw pointer dereferences are not guarded by PCS. +/// 2. Strips places up to the first reference dereference. We can do this +/// because having a capability to a reference requires having a capability +/// to entire subtree below it because things cannot be moved out of a +/// reference in safe code. +fn strip_dereferences(places: Vec) -> Vec { + let mut expanded_places = Vec::new(); + #[derive(PartialEq, Eq, Debug)] + enum SearchResult { + FoundReferenceOrPointer, + FoundOld, + FoundNothing, + } + /// Returns `true` if the place is below a raw pointer dereference and + /// should not be considered. + fn expand_place( + place: &vir_typed::Expression, + expanded_places: &mut Vec, + ) -> SearchResult { + if let Some(parent) = place.get_parent_ref_step_into_old() { + let parent_result = expand_place(parent, expanded_places); + if parent_result != SearchResult::FoundNothing { + return parent_result; + } + if place.is_labelled_old() { + return SearchResult::FoundOld; + } + match parent.get_type() { + vir_typed::Type::Reference(_) => { + let address_place = vir_typed::Expression::field( + parent.clone(), + vir_typed::FieldDecl::new( + ADDRESS_FIELD_NAME, + 0usize, + vir_typed::Type::Int(vir_typed::ty::Int::Usize), + ), + parent.position(), + ); + expanded_places.push(address_place); + SearchResult::FoundNothing + } + vir_typed::Type::Pointer(_) => { + assert!(place.is_deref(), "{place}"); + expanded_places.push(parent.clone()); + SearchResult::FoundReferenceOrPointer + } + _ => SearchResult::FoundNothing, + } + } else { + SearchResult::FoundNothing + } + } + for place in places { + if expand_place(&place, &mut expanded_places) != SearchResult::FoundReferenceOrPointer { + expanded_places.push(place); + } + } + expanded_places +} + +// struct Wrapper<'p, 'v, 'tcx> { +// encoder: &'p mut Encoder<'v, 'tcx>, +// state: &'p mut super::state::FoldUnfoldState, +// } diff --git a/prusti-viper/src/encoder/high/procedures/inference/mod.rs b/prusti-viper/src/encoder/high/procedures/inference/mod.rs index 7ec3bc3e724..abd844f8f33 100644 --- a/prusti-viper/src/encoder/high/procedures/inference/mod.rs +++ b/prusti-viper/src/encoder/high/procedures/inference/mod.rs @@ -25,6 +25,8 @@ mod permission; mod semantics; mod state; mod visitor; +mod unfolding_expressions; +mod eval_using; #[tracing::instrument(level = "debug", skip(encoder, procedure), fields(procedure = %procedure))] pub(super) fn infer_shape_operations<'v, 'tcx: 'v>( diff --git a/prusti-viper/src/encoder/high/procedures/inference/permission.rs b/prusti-viper/src/encoder/high/procedures/inference/permission.rs index 31c88f8379f..87f070e2695 100644 --- a/prusti-viper/src/encoder/high/procedures/inference/permission.rs +++ b/prusti-viper/src/encoder/high/procedures/inference/permission.rs @@ -8,8 +8,8 @@ pub(in super::super) enum Permission { /// in the final encoding the place can be represented not only with /// `Owned`, but also with `UniqueRef` and `FracRef` predicates. Owned(vir_typed::Expression), - /// TODO: Rename MutBorrowed into `Blocked`. - MutBorrowed(MutBorrowed), + Blocked(Blocked), + RawBlocked(RawBlocked), } impl Permission { @@ -27,16 +27,25 @@ impl Permission { match self { Self::MemoryBlock(place) => place, Self::Owned(place) => place, - Self::MutBorrowed(MutBorrowed { place, .. }) => place, + Self::Blocked(Blocked { place, .. }) => place, + Self::RawBlocked(RawBlocked { borrowed_place, .. }) => borrowed_place, } } } #[derive(Debug, Clone, derive_more::Display, PartialEq, Eq, PartialOrd, Ord)] -#[display(fmt = "MutBorrowed({lifetime}, {place})")] -pub(in super::super) struct MutBorrowed { +#[display(fmt = "Blocked({lifetime}, {place})")] +pub(in super::super) struct Blocked { pub(in super::super) lifetime: vir_typed::ty::LifetimeConst, pub(in super::super) place: vir_typed::Expression, + pub(in super::super) is_reborrow: bool, +} + +#[derive(Debug, Clone, derive_more::Display, PartialEq, Eq, PartialOrd, Ord)] +#[display(fmt = "RawBlocked({borrowing_place}, {borrowed_place})")] +pub(in super::super) struct RawBlocked { + pub(in super::super) borrowing_place: vir_typed::Expression, + pub(in super::super) borrowed_place: vir_typed::Expression, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -44,3 +53,14 @@ pub(in super::super) enum PermissionKind { MemoryBlock, Owned, } + +impl Permission { + pub(in super::super) fn place(&self) -> &vir_typed::Expression { + match self { + Permission::MemoryBlock(place) => place, + Permission::Owned(place) => place, + Permission::Blocked(Blocked { place, .. }) => place, + Permission::RawBlocked(RawBlocked { borrowed_place, .. }) => borrowed_place, + } + } +} diff --git a/prusti-viper/src/encoder/high/procedures/inference/semantics.rs b/prusti-viper/src/encoder/high/procedures/inference/semantics.rs index 2f800993b0e..d29ee6949d3 100644 --- a/prusti-viper/src/encoder/high/procedures/inference/semantics.rs +++ b/prusti-viper/src/encoder/high/procedures/inference/semantics.rs @@ -1,9 +1,10 @@ -use super::permission::{MutBorrowed, Permission}; +use super::permission::{Blocked, Permission, RawBlocked}; use crate::encoder::{ errors::SpannedEncodingResult, high::to_typed::types::HighToTypedTypeEncoderInterface, Encoder, }; +use std::collections::BTreeMap; use vir_crate::{ - common::position::Positioned, + common::{builtin_constants::ADDRESS_FIELD_NAME, position::Positioned}, typed::{self as vir_typed, operations::ty::Typed}, }; @@ -18,9 +19,30 @@ pub(in super::super) fn collect_permission_changes<'v, 'tcx>( &mut consumed_permissions, &mut produced_permissions, )?; + consumed_permissions.retain(|permission| !permission.place().is_behind_pointer_dereference()); + produced_permissions.retain(|permission| !permission.place().is_behind_pointer_dereference()); + // remove_after_pointer_deref(&mut consumed_permissions); + // remove_after_pointer_deref(&mut produced_permissions); Ok((consumed_permissions, produced_permissions)) } +// fn remove_after_pointer_deref(permissions: &mut Vec) { +// permissions.retain_mut(|permission| { +// match permission { +// Permission::MemoryBlock(place) => { +// !place.is_behind_pointer_dereference() +// } +// Permission::Owned(place) => { +// if let Some(pointer_place) = place.get_first_dereferenced_pointer() { +// *place = pointer_place.clone(); +// } +// true +// } +// Permission::Blocked(_) => unreachable!(), +// } +// }); +// } + trait CollectPermissionChanges { #[allow(clippy::ptr_arg)] // Clippy false positive. fn collect<'v, 'tcx>( @@ -45,10 +67,16 @@ impl CollectPermissionChanges for vir_typed::Statement { vir_typed::Statement::OldLabel(statement) => { statement.collect(encoder, consumed_permissions, produced_permissions) } - vir_typed::Statement::Inhale(statement) => { + vir_typed::Statement::InhalePredicate(statement) => { + statement.collect(encoder, consumed_permissions, produced_permissions) + } + vir_typed::Statement::ExhalePredicate(statement) => { + statement.collect(encoder, consumed_permissions, produced_permissions) + } + vir_typed::Statement::InhaleExpression(statement) => { statement.collect(encoder, consumed_permissions, produced_permissions) } - vir_typed::Statement::Exhale(statement) => { + vir_typed::Statement::ExhaleExpression(statement) => { statement.collect(encoder, consumed_permissions, produced_permissions) } vir_typed::Statement::Consume(statement) => { @@ -60,6 +88,9 @@ impl CollectPermissionChanges for vir_typed::Statement { vir_typed::Statement::GhostHavoc(statement) => { statement.collect(encoder, consumed_permissions, produced_permissions) } + vir_typed::Statement::HeapHavoc(statement) => { + statement.collect(encoder, consumed_permissions, produced_permissions) + } vir_typed::Statement::GhostAssign(statement) => { statement.collect(encoder, consumed_permissions, produced_permissions) } @@ -93,12 +124,54 @@ impl CollectPermissionChanges for vir_typed::Statement { vir_typed::Statement::SetUnionVariant(statement) => { statement.collect(encoder, consumed_permissions, produced_permissions) } + vir_typed::Statement::Pack(statement) => { + statement.collect(encoder, consumed_permissions, produced_permissions) + } + vir_typed::Statement::Unpack(statement) => { + statement.collect(encoder, consumed_permissions, produced_permissions) + } + vir_typed::Statement::Obtain(statement) => { + statement.collect(encoder, consumed_permissions, produced_permissions) + } + vir_typed::Statement::Join(statement) => { + statement.collect(encoder, consumed_permissions, produced_permissions) + } + vir_typed::Statement::JoinRange(statement) => { + statement.collect(encoder, consumed_permissions, produced_permissions) + } + vir_typed::Statement::Split(statement) => { + statement.collect(encoder, consumed_permissions, produced_permissions) + } + vir_typed::Statement::SplitRange(statement) => { + statement.collect(encoder, consumed_permissions, produced_permissions) + } + vir_typed::Statement::StashRange(statement) => { + statement.collect(encoder, consumed_permissions, produced_permissions) + } + vir_typed::Statement::StashRangeRestore(statement) => { + statement.collect(encoder, consumed_permissions, produced_permissions) + } + vir_typed::Statement::ForgetInitialization(statement) => { + statement.collect(encoder, consumed_permissions, produced_permissions) + } + vir_typed::Statement::ForgetInitializationRange(statement) => { + statement.collect(encoder, consumed_permissions, produced_permissions) + } + vir_typed::Statement::RestoreRawBorrowed(statement) => { + statement.collect(encoder, consumed_permissions, produced_permissions) + } vir_typed::Statement::NewLft(statement) => { statement.collect(encoder, consumed_permissions, produced_permissions) } vir_typed::Statement::EndLft(statement) => { statement.collect(encoder, consumed_permissions, produced_permissions) } + vir_typed::Statement::DeadReference(statement) => { + statement.collect(encoder, consumed_permissions, produced_permissions) + } + vir_typed::Statement::DeadReferenceRange(statement) => { + statement.collect(encoder, consumed_permissions, produced_permissions) + } vir_typed::Statement::DeadLifetime(statement) => { statement.collect(encoder, consumed_permissions, produced_permissions) } @@ -123,16 +196,39 @@ impl CollectPermissionChanges for vir_typed::Statement { vir_typed::Statement::CloseFracRef(statement) => { statement.collect(encoder, consumed_permissions, produced_permissions) } + vir_typed::Statement::RestoreMutBorrowed(statement) => { + statement.collect(encoder, consumed_permissions, produced_permissions) + } vir_typed::Statement::BorShorten(statement) => { statement.collect(encoder, consumed_permissions, produced_permissions) } vir_typed::Statement::LifetimeReturn(statement) => { statement.collect(encoder, consumed_permissions, produced_permissions) } + vir_typed::Statement::MaterializePredicate(statement) => { + statement.collect(encoder, consumed_permissions, produced_permissions) + } + vir_typed::Statement::EncodingAction(statement) => { + statement.collect(encoder, consumed_permissions, produced_permissions) + } + vir_typed::Statement::CaseSplit(statement) => { + statement.collect(encoder, consumed_permissions, produced_permissions) + } } } } +impl CollectPermissionChanges for vir_typed::HeapHavoc { + fn collect<'v, 'tcx>( + &self, + _encoder: &mut Encoder<'v, 'tcx>, + _consumed_permissions: &mut Vec, + _produced_permissions: &mut Vec, + ) -> SpannedEncodingResult<()> { + Ok(()) + } +} + impl CollectPermissionChanges for vir_typed::GhostHavoc { fn collect<'v, 'tcx>( &self, @@ -190,17 +286,29 @@ fn extract_managed_predicate_place( vir_typed::Predicate::OwnedNonAliased(predicate) => { Ok(Some(Permission::Owned(predicate.place.clone()))) } + vir_typed::Predicate::UniqueRef(predicate) => { + Ok(Some(Permission::Owned(predicate.place.clone()))) + } + vir_typed::Predicate::FracRef(predicate) => { + Ok(Some(Permission::Owned(predicate.place.clone()))) + } vir_typed::Predicate::MemoryBlockStackDrop(_) | vir_typed::Predicate::LifetimeToken(_) | vir_typed::Predicate::MemoryBlockHeap(_) - | vir_typed::Predicate::MemoryBlockHeapDrop(_) => { + | vir_typed::Predicate::MemoryBlockHeapRange(_) + | vir_typed::Predicate::MemoryBlockHeapRangeGuarded(_) + | vir_typed::Predicate::MemoryBlockHeapDrop(_) + | vir_typed::Predicate::OwnedRange(_) + | vir_typed::Predicate::OwnedSet(_) + | vir_typed::Predicate::UniqueRefRange(_) + | vir_typed::Predicate::FracRefRange(_) => { // Unmanaged predicates. Ok(None) } } } -impl CollectPermissionChanges for vir_typed::Inhale { +impl CollectPermissionChanges for vir_typed::InhalePredicate { fn collect<'v, 'tcx>( &self, _encoder: &mut Encoder<'v, 'tcx>, @@ -212,7 +320,7 @@ impl CollectPermissionChanges for vir_typed::Inhale { } } -impl CollectPermissionChanges for vir_typed::Exhale { +impl CollectPermissionChanges for vir_typed::ExhalePredicate { fn collect<'v, 'tcx>( &self, _encoder: &mut Encoder<'v, 'tcx>, @@ -224,6 +332,28 @@ impl CollectPermissionChanges for vir_typed::Exhale { } } +impl CollectPermissionChanges for vir_typed::InhaleExpression { + fn collect<'v, 'tcx>( + &self, + _encoder: &mut Encoder<'v, 'tcx>, + _consumed_permissions: &mut Vec, + _produced_permissions: &mut Vec, + ) -> SpannedEncodingResult<()> { + Ok(()) + } +} + +impl CollectPermissionChanges for vir_typed::ExhaleExpression { + fn collect<'v, 'tcx>( + &self, + _encoder: &mut Encoder<'v, 'tcx>, + _consumed_permissions: &mut Vec, + _produced_permissions: &mut Vec, + ) -> SpannedEncodingResult<()> { + Ok(()) + } +} + impl CollectPermissionChanges for vir_typed::Consume { fn collect<'v, 'tcx>( &self, @@ -294,6 +424,9 @@ impl CollectPermissionChanges for vir_typed::CopyPlace { consumed_permissions: &mut Vec, produced_permissions: &mut Vec, ) -> SpannedEncodingResult<()> { + // if let Some(source_pointer_place) = self.source.get_first_dereferenced_pointer() { + + // } consumed_permissions.push(Permission::MemoryBlock(self.target.clone())); consumed_permissions.push(Permission::Owned(self.source.clone())); produced_permissions.push(Permission::Owned(self.target.clone())); @@ -355,6 +488,17 @@ impl CollectPermissionChanges for vir_typed::Assign { } else { produced_permissions.push(Permission::Owned(self.target.clone())); } + if let vir_typed::Rvalue::AddressOf(value) = &self.value { + produced_permissions.push(Permission::RawBlocked(RawBlocked { + borrowing_place: self + .target + .clone() + .deref(value.place.get_type().clone(), self.target.position()), + borrowed_place: value.place.clone(), + })); + consumed_permissions.push(Permission::Owned(value.place.clone())); + return Ok(()); + } self.value .collect(encoder, consumed_permissions, produced_permissions) } @@ -383,6 +527,9 @@ impl CollectPermissionChanges for vir_typed::Rvalue { Self::Len(rvalue) => { rvalue.collect(encoder, consumed_permissions, produced_permissions) } + Self::Cast(rvalue) => { + rvalue.collect(encoder, consumed_permissions, produced_permissions) + } Self::UnaryOp(rvalue) => { rvalue.collect(encoder, consumed_permissions, produced_permissions) } @@ -423,10 +570,16 @@ impl CollectPermissionChanges for vir_typed::ast::rvalue::Reborrow { produced_permissions: &mut Vec, ) -> SpannedEncodingResult<()> { consumed_permissions.push(Permission::Owned(self.deref_place.clone())); - if self.uniqueness.is_unique() { - produced_permissions.push(Permission::MutBorrowed(MutBorrowed { + if self + .deref_place + .get_deref_uniqueness() + .unwrap_or(self.uniqueness) + .is_unique() + { + produced_permissions.push(Permission::Blocked(Blocked { lifetime: self.new_borrow_lifetime.clone(), place: self.deref_place.clone(), + is_reborrow: true, })); } else { produced_permissions.push(Permission::Owned(self.deref_place.clone())); @@ -443,9 +596,10 @@ impl CollectPermissionChanges for vir_typed::ast::rvalue::Ref { produced_permissions: &mut Vec, ) -> SpannedEncodingResult<()> { consumed_permissions.push(Permission::Owned(self.place.clone())); - produced_permissions.push(Permission::MutBorrowed(MutBorrowed { + produced_permissions.push(Permission::Blocked(Blocked { lifetime: self.new_borrow_lifetime.clone(), place: self.place.clone(), + is_reborrow: false, })); Ok(()) } @@ -455,8 +609,8 @@ impl CollectPermissionChanges for vir_typed::ast::rvalue::AddressOf { fn collect<'v, 'tcx>( &self, _encoder: &mut Encoder<'v, 'tcx>, - consumed_permissions: &mut Vec, - produced_permissions: &mut Vec, + _consumed_permissions: &mut Vec, + _produced_permissions: &mut Vec, ) -> SpannedEncodingResult<()> { // To take an address of a place on a stack, it must not be moved out. // The following fails to compile: @@ -475,9 +629,10 @@ impl CollectPermissionChanges for vir_typed::ast::rvalue::AddressOf { // let _x = std::ptr::addr_of!(c); // } // ``` - consumed_permissions.push(Permission::Owned(self.place.clone())); - produced_permissions.push(Permission::Owned(self.place.clone())); - Ok(()) + unreachable!("Should be handled by the caller.") + // consumed_permissions.push(Permission::Owned(self.place.clone())); + // produced_permissions.push(Permission::RawBlocked(self.place.clone())); + // Ok(()) } } @@ -494,6 +649,19 @@ impl CollectPermissionChanges for vir_typed::ast::rvalue::Len { } } +impl CollectPermissionChanges for vir_typed::ast::rvalue::Cast { + fn collect<'v, 'tcx>( + &self, + encoder: &mut Encoder<'v, 'tcx>, + consumed_permissions: &mut Vec, + produced_permissions: &mut Vec, + ) -> SpannedEncodingResult<()> { + self.operand + .collect(encoder, consumed_permissions, produced_permissions)?; + Ok(()) + } +} + impl CollectPermissionChanges for vir_typed::ast::rvalue::UnaryOp { fn collect<'v, 'tcx>( &self, @@ -617,6 +785,283 @@ impl CollectPermissionChanges for vir_typed::SetUnionVariant { } } +fn add_struct_expansion( + place: &vir_typed::Expression, + struct_decl: vir_typed::ast::type_decl::Struct, + permissions: &mut Vec, +) { + let position = place.position(); + let ty = place.get_type(); + let ty_lifetimes = ty.get_lifetimes_top_level_only(); + assert_eq!(ty_lifetimes.len(), struct_decl.lifetimes.len()); + let lifetime_replacement_map: BTreeMap<_, _> = ty_lifetimes + .iter() + .zip(struct_decl.lifetimes.into_iter()) + .map(|(ty_lifetime, struct_lifetime)| (struct_lifetime, ty_lifetime.clone())) + .collect(); + for mut field in struct_decl.fields { + field.ty = field.ty.replace_lifetimes(&lifetime_replacement_map); + permissions.push(Permission::Owned(vir_typed::Expression::field( + place.clone(), + field, + position, + ))); + } +} + +impl CollectPermissionChanges for vir_typed::Pack { + fn collect<'v, 'tcx>( + &self, + encoder: &mut Encoder<'v, 'tcx>, + consumed_permissions: &mut Vec, + produced_permissions: &mut Vec, + ) -> SpannedEncodingResult<()> { + if self.place.is_behind_pointer_dereference() { + produced_permissions.push(Permission::Owned(self.place.clone())); + } else { + let type_decl = encoder.encode_type_def_typed(self.place.get_type())?; + match type_decl { + vir_typed::TypeDecl::Struct(decl) => { + // if decl.is_manually_managed_type() { + produced_permissions.push(Permission::Owned(self.place.clone())); + add_struct_expansion(&self.place, decl, consumed_permissions); + // } else { + // produced_permissions.push(Permission::Owned(self.place.clone())); + // add_struct_expansion(&self.place, decl.fields, consumed_permissions); + // // unimplemented!( + // // "Unpacking an automatically managed type: {}\n{}", + // // self.place, + // // self.place.get_type(), + // // ); + // } + } + vir_typed::TypeDecl::Reference(_) => { + // FIXME: Code duplication with + // prusti-viper/src/encoder/high/procedures/inference/visitor/context.rs + produced_permissions.push(Permission::Owned(self.place.clone())); + let ty = self.place.get_type(); + let target_type = ty.clone().unwrap_reference().target_type; + let deref_place = vir_typed::Expression::deref( + self.place.clone(), + *target_type, + self.place.position(), + ); + let address_place = vir_typed::Expression::field( + self.place.clone(), + vir_typed::FieldDecl::new( + ADDRESS_FIELD_NAME, + 0usize, + vir_typed::Type::Int(vir_typed::ty::Int::Usize), + ), + self.place.position(), + ); + consumed_permissions.push(Permission::Owned(address_place)); + consumed_permissions.push(Permission::Owned(deref_place)); + } + _ => { + unimplemented!( + "Report a proper error message that only structs can be unfolded: {:?}", + self.place + ); + } + } + } + Ok(()) + } +} + +impl CollectPermissionChanges for vir_typed::Unpack { + fn collect<'v, 'tcx>( + &self, + encoder: &mut Encoder<'v, 'tcx>, + consumed_permissions: &mut Vec, + produced_permissions: &mut Vec, + ) -> SpannedEncodingResult<()> { + if self.place.is_behind_pointer_dereference() { + consumed_permissions.push(Permission::Owned(self.place.clone())); + } else { + let type_decl = encoder.encode_type_def_typed(self.place.get_type())?; + if let vir_typed::TypeDecl::Struct(decl) = type_decl { + if decl.is_manually_managed_type() { + consumed_permissions.push(Permission::Owned(self.place.clone())); + if !matches!( + self.predicate_kind, + vir_typed::ast::statement::PredicateKind::UniqueRef(_) + ) || self.with_obligation.is_some() + { + // unique_mut_ref resolves fields of types with invariants. + add_struct_expansion(&self.place, decl, produced_permissions); + } + } else { + consumed_permissions.push(Permission::Owned(self.place.clone())); + add_struct_expansion(&self.place, decl, produced_permissions); + // unimplemented!( + // "Unpacking an automatically managed type: {}\n{}", + // self.place, + // self.place.get_type() + // ); + } + } else { + unimplemented!( + "Report a proper error message that only structs can be unfolded: {}", + self.place + ); + } + } + Ok(()) + } +} + +impl CollectPermissionChanges for vir_typed::Obtain { + fn collect<'v, 'tcx>( + &self, + _encoder: &mut Encoder<'v, 'tcx>, + consumed_permissions: &mut Vec, + produced_permissions: &mut Vec, + ) -> SpannedEncodingResult<()> { + consumed_permissions.push(Permission::Owned(self.place.clone())); + produced_permissions.push(Permission::Owned(self.place.clone())); + Ok(()) + } +} + +impl CollectPermissionChanges for vir_typed::Join { + fn collect<'v, 'tcx>( + &self, + _encoder: &mut Encoder<'v, 'tcx>, + _consumed_permissions: &mut Vec, + _produced_permissions: &mut Vec, + ) -> SpannedEncodingResult<()> { + if !self.place.is_behind_pointer_dereference() { + unimplemented!( + "Report a proper error message that only memory blocks behind \ + a raw pointer could be joined by hand: {}", + self.place + ); + } + Ok(()) + } +} + +impl CollectPermissionChanges for vir_typed::JoinRange { + fn collect<'v, 'tcx>( + &self, + _encoder: &mut Encoder<'v, 'tcx>, + _consumed_permissions: &mut Vec, + _produced_permissions: &mut Vec, + ) -> SpannedEncodingResult<()> { + Ok(()) + } +} + +impl CollectPermissionChanges for vir_typed::Split { + fn collect<'v, 'tcx>( + &self, + _encoder: &mut Encoder<'v, 'tcx>, + _consumed_permissions: &mut Vec, + _produced_permissions: &mut Vec, + ) -> SpannedEncodingResult<()> { + if !self.place.is_behind_pointer_dereference() { + unimplemented!( + "Report a proper error message that only memory blocks behind \ + a raw pointer could be split by hand: {}", + self.place + ); + } + Ok(()) + } +} + +impl CollectPermissionChanges for vir_typed::SplitRange { + fn collect<'v, 'tcx>( + &self, + _encoder: &mut Encoder<'v, 'tcx>, + _consumed_permissions: &mut Vec, + _produced_permissions: &mut Vec, + ) -> SpannedEncodingResult<()> { + Ok(()) + } +} + +impl CollectPermissionChanges for vir_typed::StashRange { + fn collect<'v, 'tcx>( + &self, + _encoder: &mut Encoder<'v, 'tcx>, + _consumed_permissions: &mut Vec, + _produced_permissions: &mut Vec, + ) -> SpannedEncodingResult<()> { + Ok(()) + } +} + +impl CollectPermissionChanges for vir_typed::StashRangeRestore { + fn collect<'v, 'tcx>( + &self, + _encoder: &mut Encoder<'v, 'tcx>, + _consumed_permissions: &mut Vec, + _produced_permissions: &mut Vec, + ) -> SpannedEncodingResult<()> { + Ok(()) + } +} + +impl CollectPermissionChanges for vir_typed::ForgetInitialization { + fn collect<'v, 'tcx>( + &self, + encoder: &mut Encoder<'v, 'tcx>, + consumed_permissions: &mut Vec, + _produced_permissions: &mut Vec, + ) -> SpannedEncodingResult<()> { + if self.place.is_behind_pointer_dereference() { + consumed_permissions.push(Permission::Owned(self.place.clone())); + } else { + let type_decl = encoder.encode_type_def_typed(self.place.get_type())?; + if let vir_typed::TypeDecl::Struct(decl) = &type_decl { + if decl.is_manually_managed_type() { + consumed_permissions.push(Permission::Owned(self.place.clone())); + } else { + unimplemented!( + "Forgetting initialization of an automatically managed type: {:?}\n{:?}", + self.place, + type_decl + ); + } + } else { + unimplemented!( + "Report a proper error message that only structs can be unfolded: {:?}", + self.place + ); + } + } + Ok(()) + } +} + +impl CollectPermissionChanges for vir_typed::ForgetInitializationRange { + fn collect<'v, 'tcx>( + &self, + _encoder: &mut Encoder<'v, 'tcx>, + _consumed_permissions: &mut Vec, + _produced_permissions: &mut Vec, + ) -> SpannedEncodingResult<()> { + Ok(()) + } +} + +impl CollectPermissionChanges for vir_typed::RestoreRawBorrowed { + fn collect<'v, 'tcx>( + &self, + _encoder: &mut Encoder<'v, 'tcx>, + _consumed_permissions: &mut Vec, + _produced_permissions: &mut Vec, + ) -> SpannedEncodingResult<()> { + unreachable!("Outdated code"); + // consumed_permissions.push(Permission::RawBlocked(self.restored_place.clone())); + // produced_permissions.push(Permission::Owned(self.restored_place.clone())); + // Ok(()) + } +} + impl CollectPermissionChanges for vir_typed::NewLft { fn collect<'v, 'tcx>( &self, @@ -639,6 +1084,32 @@ impl CollectPermissionChanges for vir_typed::EndLft { } } +impl CollectPermissionChanges for vir_typed::DeadReference { + fn collect<'v, 'tcx>( + &self, + _encoder: &mut Encoder<'v, 'tcx>, + consumed_permissions: &mut Vec, + _produced_permissions: &mut Vec, + ) -> SpannedEncodingResult<()> { + consumed_permissions.push(Permission::Owned(self.target.clone())); + // // FIXME: This is a lie: the permission is actually gone, we should not + // // require it. + // produced_permissions.push(Permission::Owned(self.target.clone())); + Ok(()) + } +} + +impl CollectPermissionChanges for vir_typed::DeadReferenceRange { + fn collect<'v, 'tcx>( + &self, + _encoder: &mut Encoder<'v, 'tcx>, + _consumed_permissions: &mut Vec, + _produced_permissions: &mut Vec, + ) -> SpannedEncodingResult<()> { + Ok(()) + } +} + impl CollectPermissionChanges for vir_typed::DeadLifetime { fn collect<'v, 'tcx>( &self, @@ -683,6 +1154,40 @@ impl CollectPermissionChanges for vir_typed::LifetimeReturn { } } +impl CollectPermissionChanges for vir_typed::MaterializePredicate { + fn collect<'v, 'tcx>( + &self, + _encoder: &mut Encoder<'v, 'tcx>, + _consumed_permissions: &mut Vec, + _produced_permissions: &mut Vec, + ) -> SpannedEncodingResult<()> { + // Materialize predicate is applied only to non-managed predicates. + Ok(()) + } +} + +impl CollectPermissionChanges for vir_typed::EncodingAction { + fn collect<'v, 'tcx>( + &self, + _encoder: &mut Encoder<'v, 'tcx>, + _consumed_permissions: &mut Vec, + _produced_permissions: &mut Vec, + ) -> SpannedEncodingResult<()> { + unreachable!(); + } +} + +impl CollectPermissionChanges for vir_typed::CaseSplit { + fn collect<'v, 'tcx>( + &self, + _encoder: &mut Encoder<'v, 'tcx>, + _consumed_permissions: &mut Vec, + _produced_permissions: &mut Vec, + ) -> SpannedEncodingResult<()> { + Ok(()) + } +} + impl CollectPermissionChanges for vir_typed::ObtainMutRef { fn collect<'v, 'tcx>( &self, @@ -750,6 +1255,18 @@ impl CollectPermissionChanges for vir_typed::CloseFracRef { } } +impl CollectPermissionChanges for vir_typed::RestoreMutBorrowed { + fn collect<'v, 'tcx>( + &self, + _encoder: &mut Encoder<'v, 'tcx>, + _consumed_permissions: &mut Vec, + produced_permissions: &mut Vec, + ) -> SpannedEncodingResult<()> { + produced_permissions.push(Permission::Owned(self.referenced_place.clone())); + Ok(()) + } +} + impl CollectPermissionChanges for vir_typed::BorShorten { fn collect<'v, 'tcx>( &self, diff --git a/prusti-viper/src/encoder/high/procedures/inference/state/fold_unfold_state.rs b/prusti-viper/src/encoder/high/procedures/inference/state/fold_unfold_state.rs index d60abfc6b5c..ac2ec0fb9fc 100644 --- a/prusti-viper/src/encoder/high/procedures/inference/state/fold_unfold_state.rs +++ b/prusti-viper/src/encoder/high/procedures/inference/state/fold_unfold_state.rs @@ -10,7 +10,7 @@ use vir_crate::{ use super::PredicateState; -#[derive(Clone)] +#[derive(Clone, Debug)] pub(in super::super::super) struct FoldUnfoldState { /// If this state is a merge of multiple incoming states, then /// `incoming_labels` contains the list of basic blocks from where the @@ -20,6 +20,12 @@ pub(in super::super::super) struct FoldUnfoldState { /// support only stack allocations. They can be uniquely identified by /// `VariableDecl` of their base. predicates: BTreeMap, + /// The stack of opened reference permissions. This is used as an heuristic + /// to fill in permission amounts for pointer dereferences. + /// + /// The first element of the tuple is the expression that is opened, it is + /// used only for error reporting and debugging. + opened_ref_permission: Vec<(vir_typed::Expression, Option)>, } impl std::fmt::Display for FoldUnfoldState { @@ -41,6 +47,7 @@ impl FoldUnfoldState { Self { incoming_labels: Vec::new(), predicates: Default::default(), + opened_ref_permission: Vec::new(), } } @@ -234,7 +241,16 @@ impl FoldUnfoldState { new_incoming_conditional, ); } - PredicateState::Conditional(states) + { + // Check whether all states are the same. + let mut states_iter = states.values(); + let first_state = states_iter.next().unwrap(); + if states_iter.all(|state| state.equal_ignoring_dead_lifetimes(first_state)) { + PredicateState::Unconditional(states.into_values().next().unwrap()) + } else { + PredicateState::Conditional(states) + } + } }; self.predicates.insert(root, merged_state); } @@ -260,7 +276,7 @@ impl FoldUnfoldState { ) -> SpannedEncodingResult<()> { self.check_no_default_position(); let place = permission.get_place(); - if let Some(state) = self.try_get_predicates_state(place)? { + if let Some(state) = self.try_get_predicates_state_mut(place)? { state.insert_permission(permission)?; } else { let base = place.get_base().erase_lifetime(); @@ -290,7 +306,7 @@ impl FoldUnfoldState { ) -> SpannedEncodingResult<()> { self.check_no_default_position(); let place = permission.get_place(); - if let Some(state) = self.try_get_predicates_state(place)? { + if let Some(state) = self.try_get_predicates_state_mut(place)? { state.remove_permission(permission)?; if state.is_empty() { let base = place.get_base().erase_lifetime(); @@ -302,6 +318,49 @@ impl FoldUnfoldState { Ok(()) } + pub(in super::super) fn open_ref( + &mut self, + place: vir_typed::Expression, + predicate_permission_amount: Option, + ) -> SpannedEncodingResult<()> { + if !place.is_behind_pointer_dereference() { + let state = self.get_predicates_state_mut(&place)?; + state.open_ref(place.clone(), predicate_permission_amount.clone())?; + } + self.opened_ref_permission + .push((place, predicate_permission_amount)); + Ok(()) + } + + pub(in super::super) fn close_ref( + &mut self, + place: &vir_typed::Expression, + ) -> SpannedEncodingResult> { + let (opened_place, permission) = self.opened_ref_permission.pop().unwrap(); + assert_eq!(place, &opened_place); + if !place.is_behind_pointer_dereference() { + let state = self.get_predicates_state_mut(place)?; + let precise_permission = state.close_ref(place)?; + assert_eq!(permission, precise_permission); + } + Ok(permission) + } + + pub(in super::super) fn is_opened_ref( + &self, + place: &vir_typed::Expression, + ) -> SpannedEncodingResult>> { + if !place.is_behind_pointer_dereference() { + let state = self.get_predicates_state(place)?; + state.is_opened_ref(place) + } else { + Ok(self + .opened_ref_permission + .last() + .map(|(_, permission)| permission)) + } + } + pub(in super::super) fn iter_mut( &mut self, ) -> SpannedEncodingResult> { @@ -309,16 +368,16 @@ impl FoldUnfoldState { Ok(self.predicates.values_mut()) } - pub(in super::super) fn get_predicates_state( + pub(in super::super) fn get_predicates_state_mut( &mut self, place: &vir_typed::Expression, ) -> SpannedEncodingResult<&mut PredicateState> { Ok(self - .try_get_predicates_state(place)? + .try_get_predicates_state_mut(place)? .unwrap_or_else(|| unreachable!("place: {place}"))) } - pub(super) fn try_get_predicates_state( + pub(super) fn try_get_predicates_state_mut( &mut self, place: &vir_typed::Expression, ) -> SpannedEncodingResult> { @@ -327,6 +386,26 @@ impl FoldUnfoldState { Ok(self.predicates.get_mut(&base)) } + pub(in super::super) fn try_get_predicates_state( + &self, + place: &vir_typed::Expression, + ) -> SpannedEncodingResult> { + self.check_no_default_position(); + let base = place.get_base().erase_lifetime(); + let state = self.predicates.get(&base); + Ok(state) + } + + pub(in super::super) fn get_predicates_state( + &self, + place: &vir_typed::Expression, + ) -> SpannedEncodingResult<&PredicateState> { + let state = self + .try_get_predicates_state(place)? + .unwrap_or_else(|| unreachable!("place: {place}")); + Ok(state) + } + pub(in super::super) fn remove_empty_states( &mut self, variable: &vir_typed::VariableDecl, diff --git a/prusti-viper/src/encoder/high/procedures/inference/state/mod.rs b/prusti-viper/src/encoder/high/procedures/inference/state/mod.rs index 5dd943f51a7..ba105565c5f 100644 --- a/prusti-viper/src/encoder/high/procedures/inference/state/mod.rs +++ b/prusti-viper/src/encoder/high/procedures/inference/state/mod.rs @@ -7,5 +7,5 @@ pub(super) use self::{ fold_unfold_state::FoldUnfoldState, places::{PlaceWithDeadLifetimes, Places}, predicate_state::PredicateState, - predicate_state_on_path::PredicateStateOnPath, + predicate_state_on_path::{DeadLifetimeReport, PredicateStateOnPath}, }; diff --git a/prusti-viper/src/encoder/high/procedures/inference/state/places.rs b/prusti-viper/src/encoder/high/procedures/inference/state/places.rs index a2a0c30e64f..1fd6dc96122 100644 --- a/prusti-viper/src/encoder/high/procedures/inference/state/places.rs +++ b/prusti-viper/src/encoder/high/procedures/inference/state/places.rs @@ -27,6 +27,7 @@ impl Places { } pub(in super::super) fn insert(&mut self, place: vir_typed::Expression) -> bool { + place.check_no_erased_lifetime(); self.places .insert(place.clone().erase_lifetime(), place) .is_none() diff --git a/prusti-viper/src/encoder/high/procedures/inference/state/predicate_state.rs b/prusti-viper/src/encoder/high/procedures/inference/state/predicate_state.rs index acf3150f68f..19547eb4ce9 100644 --- a/prusti-viper/src/encoder/high/procedures/inference/state/predicate_state.rs +++ b/prusti-viper/src/encoder/high/procedures/inference/state/predicate_state.rs @@ -2,9 +2,11 @@ use super::PredicateStateOnPath; use crate::encoder::{ errors::SpannedEncodingResult, high::procedures::inference::permission::Permission, }; - use std::collections::BTreeMap; -use vir_crate::middle::{self as vir_mid}; +use vir_crate::{ + middle::{self as vir_mid}, + typed::{self as vir_typed}, +}; #[derive(Clone, Debug)] pub(in super::super) enum PredicateState { @@ -88,6 +90,54 @@ impl PredicateState { Ok(()) } + pub(super) fn open_ref( + &mut self, + place: vir_typed::Expression, + predicate_permission_amount: Option, + ) -> SpannedEncodingResult<()> { + match self { + PredicateState::Unconditional(state) => { + state.open_ref(place, predicate_permission_amount)?; + } + PredicateState::Conditional(_) => { + unimplemented!("place: {place} \n\nstate: {self}"); + } + } + Ok(()) + } + + pub(super) fn close_ref( + &mut self, + place: &vir_typed::Expression, + ) -> SpannedEncodingResult> { + match self { + PredicateState::Unconditional(state) => state.close_ref(place), + PredicateState::Conditional(_) => { + unimplemented!(); + } + } + } + + pub(super) fn is_opened_ref( + &self, + place: &vir_typed::Expression, + ) -> SpannedEncodingResult>> { + match self { + PredicateState::Unconditional(state) => state.is_opened_ref(place), + PredicateState::Conditional(states) => { + let mut states_iter = states.values(); + let is_opened_ref_first = states_iter.next().unwrap().is_opened_ref(place)?; + for state in states_iter { + let is_opened_ref = state.is_opened_ref(place)?; + if is_opened_ref != is_opened_ref_first { + unimplemented!(); + } + } + Ok(is_opened_ref_first) + } + } + } + pub(super) fn is_empty(&self) -> bool { let mut empty = true; self.foreach(|state| empty = empty && state.is_empty()); diff --git a/prusti-viper/src/encoder/high/procedures/inference/state/predicate_state_on_path.rs b/prusti-viper/src/encoder/high/procedures/inference/state/predicate_state_on_path.rs index 9bf3c3b7524..6745f038846 100644 --- a/prusti-viper/src/encoder/high/procedures/inference/state/predicate_state_on_path.rs +++ b/prusti-viper/src/encoder/high/procedures/inference/state/predicate_state_on_path.rs @@ -1,24 +1,43 @@ use super::{ - super::permission::{MutBorrowed, Permission, PermissionKind}, + super::permission::{Blocked, Permission, PermissionKind}, places::PlaceWithDeadLifetimes, Places, }; -use crate::encoder::errors::SpannedEncodingResult; +use crate::encoder::{ + errors::SpannedEncodingResult, high::procedures::inference::permission::RawBlocked, +}; use log::debug; use std::collections::{BTreeMap, BTreeSet}; -use vir_crate::typed::{ - self as vir_typed, - operations::{lifetimes::WithLifetimes, ty::Typed}, +use vir_crate::{ + common::display, + typed::{ + self as vir_typed, + operations::{lifetimes::WithLifetimes, ty::Typed}, + }, }; #[derive(Clone, Default, Debug, PartialEq, Eq)] pub(in super::super) struct PredicateStateOnPath { owned_non_aliased: Places, memory_block_stack: Places, - mut_borrowed: BTreeMap, + /// A map from opened reference places to a permission variable or `None` if + /// the place was opened with full permission. + opened_references: BTreeMap>, + /// place → (lifetime, is_reborrow) + blocked: BTreeMap, + /// Places that are blocked by a raw pointer. + raw_blocked: BTreeMap, dead_lifetimes: BTreeSet, } +pub(in super::super) struct DeadLifetimeReport { + pub(in super::super) dead_references: BTreeSet, + pub(in super::super) dead_dereferences: BTreeSet, + pub(in super::super) places_with_dead_lifetimes: Vec, + pub(in super::super) blocked_dead_dereferences: + BTreeMap, +} + impl std::fmt::Display for PredicateStateOnPath { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { writeln!( @@ -37,9 +56,29 @@ impl std::fmt::Display for PredicateStateOnPath { for place in &self.memory_block_stack { writeln!(f, " {place}")?; } - writeln!(f, " mut_borrowed ({}):", self.mut_borrowed.len())?; - for (place, lifetime) in &self.mut_borrowed { - writeln!(f, " &{lifetime} {place}")?; + writeln!( + f, + " opened_references ({}):", + self.opened_references.len() + )?; + for (place, permission) in &self.opened_references { + writeln!( + f, + " {place}: {}", + display::option!(permission, "{}", "none") + )?; + } + writeln!(f, " blocked ({}):", self.blocked.len())?; + for (place, (lifetime, is_reborrow)) in &self.blocked { + writeln!( + f, + " &{lifetime} {} {place}", + (if *is_reborrow { "reborrow" } else { "" }) + )?; + } + writeln!(f, " raw blocked ({}):", self.raw_blocked.len())?; + for (borrowed_place, borrowing_place) in &self.raw_blocked { + writeln!(f, " {borrowed_place} by {borrowing_place}")?; } Ok(()) } @@ -55,14 +94,16 @@ impl PredicateStateOnPath { pub(super) fn is_empty(&self) -> bool { self.owned_non_aliased.is_empty() && self.memory_block_stack.is_empty() - && self.mut_borrowed.is_empty() + // && self.opened_references.is_empty() + && self.blocked.is_empty() + && self.raw_blocked.is_empty() } pub(super) fn contains_only_leakable(&self) -> bool { self.memory_block_stack.is_empty() && self.owned_non_aliased.iter().all(|place| { // `UniqueRef` and `FracRef` predicates can be leaked. - place.get_dereference_base().is_some() + place.get_last_dereferenced_reference().is_some() }) } @@ -101,8 +142,20 @@ impl PredicateStateOnPath { ) -> SpannedEncodingResult<()> { assert!(place.is_place()); assert!( - self.mut_borrowed.remove(place).is_some(), - "not found in mut_borrowed: {place}", + self.blocked.remove(place).is_some(), + "not found in blocked: {place}", + ); + Ok(()) + } + + pub(in super::super) fn remove_raw_blocked( + &mut self, + place: &vir_typed::Expression, + ) -> SpannedEncodingResult<()> { + assert!(place.is_place()); + assert!( + self.raw_blocked.remove(place).is_some(), + "not found in raw blocked: {place}", ); Ok(()) } @@ -129,8 +182,24 @@ impl PredicateStateOnPath { Permission::Owned(place) => { assert!(self.owned_non_aliased.insert(place)); } - Permission::MutBorrowed(MutBorrowed { lifetime, place }) => { - assert!(self.mut_borrowed.insert(place, lifetime).is_none()); + Permission::Blocked(Blocked { + lifetime, + place, + is_reborrow, + }) => { + assert!(self + .blocked + .insert(place, (lifetime, is_reborrow)) + .is_none()); + } + Permission::RawBlocked(RawBlocked { + borrowing_place, + borrowed_place, + }) => { + assert!(self + .raw_blocked + .insert(borrowed_place, borrowing_place) + .is_none()); } } } @@ -143,10 +212,57 @@ impl PredicateStateOnPath { Permission::Owned(place) => { assert!(self.owned_non_aliased.remove(place)); } - Permission::MutBorrowed(_) => { + Permission::Blocked(_) => { unreachable!() } + Permission::RawBlocked(_) => { + unreachable!() + } + } + } + + pub(super) fn open_ref( + &mut self, + place: vir_typed::Expression, + predicate_permission_amount: Option, + ) -> SpannedEncodingResult<()> { + assert!(place.is_place()); + assert!(self.owned_non_aliased.contains(&place)); + for opened_place in self.opened_references.keys() { + if opened_place.has_prefix(&place) || place.has_prefix(opened_place) { + unimplemented!("FIXME: a proper error message: failed to open {place} because {opened_place} is already opened"); + } + } + assert!(self + .opened_references + .insert(place, predicate_permission_amount) + .is_none()); + Ok(()) + } + + pub(super) fn close_ref( + &mut self, + place: &vir_typed::Expression, + ) -> SpannedEncodingResult> { + assert!(place.is_place()); + assert!(self.owned_non_aliased.contains(place) || place.is_behind_pointer_dereference()); + let predicate_permission_amount = self + .opened_references + .remove(place) + .unwrap_or_else(|| unreachable!("place is not opened: {}", place)); + Ok(predicate_permission_amount) + } + + pub(in super::super) fn is_opened_ref( + &self, + place: &vir_typed::Expression, + ) -> SpannedEncodingResult>> { + for (opened_place, permission) in &self.opened_references { + if place.has_prefix(opened_place) { + return Ok(Some(permission)); + } } + Ok(None) } pub(in super::super) fn contains( @@ -251,9 +367,31 @@ impl PredicateStateOnPath { pub(in super::super) fn contains_blocked( &self, place: &vir_typed::Expression, - ) -> SpannedEncodingResult> + ) -> SpannedEncodingResult> { - Ok(self.mut_borrowed.iter().find(|(p, _)| { + Ok(self + .blocked + .iter() + .find(|(p, _)| { + let prefix_expr = match p { + vir_typed::Expression::BuiltinFuncApp(vir_typed::BuiltinFuncApp { + function: vir_typed::BuiltinFunc::Index, + type_arguments: _, + arguments, + .. + }) => &arguments[0], + _ => *p, + }; + place.has_prefix(prefix_expr) || prefix_expr.has_prefix(place) + }) + .map(|(place, (lifetime, reborrow))| (place, lifetime, *reborrow))) + } + + pub(in super::super) fn contains_raw_blocked( + &self, + place: &vir_typed::Expression, + ) -> SpannedEncodingResult> { + Ok(self.raw_blocked.iter().find(|(p, _)| { let prefix_expr = match p { vir_typed::Expression::BuiltinFuncApp(vir_typed::BuiltinFuncApp { function: vir_typed::BuiltinFunc::Index, @@ -270,7 +408,7 @@ impl PredicateStateOnPath { pub(in super::super) fn clear(&mut self) -> SpannedEncodingResult<()> { self.owned_non_aliased.clear(); self.memory_block_stack.clear(); - self.mut_borrowed.clear(); + self.blocked.clear(); self.check_no_default_position(); Ok(()) } @@ -283,7 +421,7 @@ impl PredicateStateOnPath { { expr.check_no_default_position(); } - for place in self.mut_borrowed.keys() { + for place in self.blocked.keys() { place.check_no_default_position(); } } @@ -374,6 +512,8 @@ impl PredicateStateOnPath { /// Note: since `y` is borrowing not `x`, but `a.f`, `x` can dye /// before `y`. /// + /// FIXME: We do the same as in 2.1. + /// /// In this case, we need to forget about `UniqueRef` parts (delete /// them from fold-unfold state) and replace with `MemoryBlock(x)` /// because we know that this is what we have in the verifier's @@ -386,30 +526,54 @@ impl PredicateStateOnPath { pub(in super::super) fn mark_lifetime_dead( &mut self, lifetime: &vir_typed::ty::LifetimeConst, - ) -> (BTreeSet, Vec) { - assert!( - !self.dead_lifetimes.contains(lifetime), - "The lifetime {lifetime} is already dead." - ); - let all_dead_references: Vec<_> = self + ) -> SpannedEncodingResult> { + if self.dead_lifetimes.contains(lifetime) { + // The lifetime is already dead on this trace. + return Ok(None); + } + let dead_references: BTreeSet<_> = self + .owned_non_aliased + .drain_filter(|place| { + if let vir_typed::Type::Reference(reference_type) = place.get_type() { + &reference_type.lifetime == lifetime + } else { + false + } + }) + .collect(); + for reference in &dead_references { + self.insert(PermissionKind::MemoryBlock, reference.clone())?; + } + let all_dead_dereferences: Vec<_> = self .owned_non_aliased .drain_filter(|place| place.is_deref_of_lifetime(lifetime)) .collect(); + let blocked_all_dead_dereferences: Vec<_> = self + .blocked + .drain_filter(|place, _| place.is_deref_of_lifetime(lifetime)) + .collect(); // Case 2.1. - let mut dead_references = BTreeSet::new(); + let mut dead_dereferences = BTreeSet::new(); + let mut blocked_dead_dereferences = BTreeMap::new(); // Case 2.2. let mut partial_dead_references = BTreeSet::new(); - for place in all_dead_references { - if let vir_typed::Expression::Deref(vir_typed::Deref { box base, .. }) = &place { - if let vir_typed::Type::Reference(vir_typed::ty::Reference { - lifetime: lft, .. - }) = base.get_type() - { - if lifetime == lft { - self.memory_block_stack.insert(base.clone()); - dead_references.insert(place); - continue; - } + for place in all_dead_dereferences { + // if let vir_typed::Expression::Deref(vir_typed::Deref { box base, .. }) = &place { + // if let vir_typed::Type::Reference(vir_typed::ty::Reference { + // lifetime: lft, .. + // }) = base.get_type() + // { + // if lifetime == lft { + // self.memory_block_stack.insert(base.clone()); + // dead_references.insert(place); + // continue; + // } + // } + // } + // partial_dead_references.insert(place.into_ref_with_lifetime(lifetime)); + if let Some((deref_lifetime, _)) = place.get_dereference_kind() { + if &deref_lifetime == lifetime { + dead_dereferences.insert(place.clone()); } } partial_dead_references.insert(place.into_ref_with_lifetime(lifetime)); @@ -417,6 +581,14 @@ impl PredicateStateOnPath { for place in partial_dead_references { self.memory_block_stack.insert(place); } + for (place, (reborrowing_lifetime, is_reborrow)) in blocked_all_dead_dereferences { + assert!(is_reborrow, "place: {place}"); + if let Some((deref_lifetime, _)) = place.get_dereference_kind() { + if &deref_lifetime == lifetime { + blocked_dead_dereferences.insert(place.clone(), reborrowing_lifetime); + } + } + } // Case 1. let mut places_with_dead_lifetimes = Vec::new(); for place in &self.owned_non_aliased { @@ -430,6 +602,34 @@ impl PredicateStateOnPath { } self.dead_lifetimes.insert(lifetime.clone()); self.check_consistency(); - (dead_references, places_with_dead_lifetimes) + Ok(Some(DeadLifetimeReport { + dead_references, + dead_dereferences, + places_with_dead_lifetimes, + blocked_dead_dereferences, + })) + } + + pub(in super::super) fn equal_ignoring_dead_lifetimes(&self, other: &Self) -> bool { + self.owned_equal(other) + && self.memory_block_stack_equal(other) + && self.blocked_equal(other) + && self.opened_references == other.opened_references + } + + pub(in super::super) fn owned_equal(&self, other: &Self) -> bool { + self.owned_non_aliased == other.owned_non_aliased + } + + pub(in super::super) fn memory_block_stack_equal(&self, other: &Self) -> bool { + self.memory_block_stack == other.memory_block_stack + } + + pub(in super::super) fn blocked_equal(&self, other: &Self) -> bool { + self.blocked == other.blocked + } + + pub(in super::super) fn raw_blocked_equal(&self, other: &Self) -> bool { + self.raw_blocked == other.raw_blocked } } diff --git a/prusti-viper/src/encoder/high/procedures/inference/unfolding_expressions.rs b/prusti-viper/src/encoder/high/procedures/inference/unfolding_expressions.rs new file mode 100644 index 00000000000..5fb5fdf47ee --- /dev/null +++ b/prusti-viper/src/encoder/high/procedures/inference/unfolding_expressions.rs @@ -0,0 +1,200 @@ +use crate::encoder::errors::{SpannedEncodingError, SpannedEncodingResult}; +use vir_crate::{ + common::position::Positioned, + typed::{ + self as vir_typed, + operations::ty::Typed, + visitors::{ + default_fallible_fold_binary_op, default_fallible_fold_expression, + ExpressionFallibleFolder, + }, + }, +}; + +pub(super) fn add_unfolding_expressions( + expression: vir_typed::Expression, +) -> SpannedEncodingResult { + // let mut ensurer = Ensurer { + // syntactically_framed_places: Vec::new(), + // }; + // ensurer.fallible_fold_expression(expression) + Ok(expression) +} + +struct Ensurer { + syntactically_framed_places: Vec, +} + +impl Ensurer { + fn add_unfolding( + &self, + place: vir_typed::Expression, + ) -> SpannedEncodingResult { + for framing_place in &self.syntactically_framed_places { + let mut unfolding_stack = Vec::new(); + if self.add_syntactic_unfolding_rec(&place, framing_place, &mut unfolding_stack)? { + let place = self.apply_unfolding_stack(place, unfolding_stack); + return Ok(place); + } + } + let mut unfolding_stack = Vec::new(); + self.add_self_unfolding_rec(&place, &mut unfolding_stack)?; + let place = self.apply_unfolding_stack(place, unfolding_stack); + Ok(place) + } + + fn apply_unfolding_stack( + &self, + mut place: vir_typed::Expression, + unfolding_stack: Vec, + ) -> vir_typed::Expression { + for unfolded_place in unfolding_stack { + let position = place.position(); + place = vir_typed::Expression::unfolding( + vir_typed::Predicate::owned_non_aliased(unfolded_place, position), + place, + position, + ); + } + place + } + + fn add_syntactic_unfolding_rec( + &self, + place: &vir_typed::Expression, + framing_place: &vir_typed::Expression, + unfolding_stack: &mut Vec, + ) -> SpannedEncodingResult { + if place == framing_place { + return Ok(true); + } else if !place.is_deref() { + if let Some(parent) = place.get_parent_ref() { + if self.add_syntactic_unfolding_rec(framing_place, parent, unfolding_stack)? { + unfolding_stack.push(parent.clone()); + return Ok(true); + } + } + }; + Ok(false) + } + + /// Just unfold on all levels except on deref. + /// + /// FIXME: This should take into account what places are actually framed by + /// the structural invariant. For example, if the invariant contains + /// `own!((*self.p).x)` (that is, it frames only one field of the struct), + /// then we currently will generate one unfolding too many (we would + /// generate unfolding of `self.p` even though we should not). + fn add_self_unfolding_rec( + &self, + place: &vir_typed::Expression, + unfolding_stack: &mut Vec, + ) -> SpannedEncodingResult<()> { + if let Some(parent) = place.get_parent_ref() { + if !parent.get_type().is_pointer() { + unfolding_stack.push(parent.clone()); + } + self.add_self_unfolding_rec(parent, unfolding_stack)?; + } + Ok(()) + } + + // fn add_unfolding(&self, place: vir_typed::Expression) -> SpannedEncodingResult { + // for framing_place in &self.syntactically_framed_places { + // let mut unfolding_stack = Vec::new(); + // if let Some(mut new_place) = self.add_unfolding_rec(&place, framing_place, &mut unfolding_stack)? { + // eprintln!("place: {}", place); + // eprintln!("new_place: {}", new_place); + // for unfolded_place in unfolding_stack { + // eprintln!(" unfolded_place: {}", unfolded_place); + // let position =new_place.position(); + // new_place = vir_typed::Expression::unfolding( + // vir_typed::Predicate::owned_non_aliased(unfolded_place, position), + // new_place, position); + // } + // eprintln!("final_place: {}", new_place); + // return Ok(new_place); + // } + // } + // Ok(place) + // } + + // fn add_unfolding_rec(&self, place: &vir_typed::Expression, framing_place: &vir_typed::Expression, + // unfolding_stack: &mut Vec, + // ) -> SpannedEncodingResult> { + // let new_place = if place == framing_place { + // Some(place.clone()) + // } else { + // if place.is_deref() { + // None + // } else { + // if let Some(parent) = place.get_parent_ref() { + // let result = self.add_unfolding_rec(framing_place, parent, unfolding_stack)?; + // if result.is_some() { + // unfolding_stack.push(parent.clone()); + // } + // result + // } else { + // None + // } + // } + // }; + // Ok(new_place) + // } +} + +impl ExpressionFallibleFolder for Ensurer { + type Error = SpannedEncodingError; + + fn fallible_fold_expression( + &mut self, + expression: vir_typed::Expression, + ) -> Result { + if expression.is_place() && expression.get_last_dereferenced_pointer().is_some() { + self.add_unfolding(expression) + } else { + default_fallible_fold_expression(self, expression) + } + } + + fn fallible_fold_binary_op( + &mut self, + mut binary_op: vir_typed::BinaryOp, + ) -> Result { + match binary_op.op_kind { + vir_typed::BinaryOpKind::And => { + if let vir_typed::Expression::AccPredicate(acc_predicate) = &*binary_op.left { + match &*acc_predicate.predicate { + vir_typed::Predicate::LifetimeToken(_) + | vir_typed::Predicate::MemoryBlockStack(_) + | vir_typed::Predicate::MemoryBlockStackDrop(_) + | vir_typed::Predicate::MemoryBlockHeap(_) + | vir_typed::Predicate::MemoryBlockHeapRange(_) + | vir_typed::Predicate::MemoryBlockHeapRangeGuarded(_) + | vir_typed::Predicate::MemoryBlockHeapDrop(_) => { + default_fallible_fold_binary_op(self, binary_op) + } + vir_typed::Predicate::OwnedNonAliased(predicate) => { + let place = predicate.place.clone(); + binary_op.left = self.fallible_fold_expression_boxed(binary_op.left)?; + self.syntactically_framed_places.push(place); + binary_op.right = + self.fallible_fold_expression_boxed(binary_op.right)?; + self.syntactically_framed_places.pop(); + Ok(binary_op) + } + vir_typed::Predicate::OwnedRange(_) => todo!(), + vir_typed::Predicate::OwnedSet(_) => todo!(), + vir_typed::Predicate::UniqueRef(_) => todo!(), + vir_typed::Predicate::UniqueRefRange(_) => todo!(), + vir_typed::Predicate::FracRef(_) => todo!(), + vir_typed::Predicate::FracRefRange(_) => todo!(), + } + } else { + default_fallible_fold_binary_op(self, binary_op) + } + } + _ => default_fallible_fold_binary_op(self, binary_op), + } + } +} diff --git a/prusti-viper/src/encoder/high/procedures/inference/visitor/context.rs b/prusti-viper/src/encoder/high/procedures/inference/visitor/context.rs index 7e3d987ed60..e11c07f5687 100644 --- a/prusti-viper/src/encoder/high/procedures/inference/visitor/context.rs +++ b/prusti-viper/src/encoder/high/procedures/inference/visitor/context.rs @@ -6,7 +6,7 @@ use crate::encoder::{ }; use prusti_rustc_interface::errors::MultiSpan; use vir_crate::{ - common::position::Positioned, + common::{builtin_constants::ADDRESS_FIELD_NAME, position::Positioned}, typed::{self as vir_typed, operations::ty::Typed}, }; @@ -21,8 +21,7 @@ impl<'p, 'v, 'tcx> super::super::ensurer::Context for Visitor<'p, 'v, 'tcx> { // lifetime variables, but concrete values from `ty`. However, for this // it seams we need to use Rust compiler's `SubstsRef` design, which // means one more refactoring… - let normalized_type = ty.normalize_type(); - let type_decl = self.encoder.encode_type_def_typed(&normalized_type)?; + let type_decl = self.encoder.encode_type_def_typed(ty)?; fn expand_fields<'a>( place: &vir_typed::Expression, fields: impl Iterator, @@ -47,14 +46,29 @@ impl<'p, 'v, 'tcx> super::super::ensurer::Context for Visitor<'p, 'v, 'tcx> { let expansion = match type_decl { vir_typed::TypeDecl::Bool | vir_typed::TypeDecl::Int(_) - | vir_typed::TypeDecl::Float(_) - | vir_typed::TypeDecl::Pointer(_) => { - // Primitive type. Convert. - vec![(ExpandedPermissionKind::MemoryBlock, place.clone())] + | vir_typed::TypeDecl::Float(_) => { + // Primitive type. + unreachable!(); + } + vir_typed::TypeDecl::Pointer(_) => { + let target_type = ty.clone().unwrap_pointer().target_type; + let deref_place = + vir_typed::Expression::deref(place.clone(), *target_type, place.position()); + vec![(ExpandedPermissionKind::Same, deref_place)] } vir_typed::TypeDecl::Trusted(_) => unimplemented!("ty: {}", ty), vir_typed::TypeDecl::TypeVar(_) => unimplemented!("ty: {}", ty), - vir_typed::TypeDecl::Struct(decl) => expand_fields(place, decl.fields.iter()), + vir_typed::TypeDecl::Struct(decl) => { + // if decl.is_manually_managed_type() { + // let place_span = self.get_span(guiding_place.position()).unwrap(); + // let error = SpannedEncodingError::incorrect( + // "types with structural invariants are required to be managed manually", + // place_span, + // ); + // return Err(error); + // } + expand_fields(place, decl.fields.iter()) + } vir_typed::TypeDecl::Enum(decl) => { let position = place.position(); let variant_name = place.get_variant_name(guiding_place); @@ -98,7 +112,7 @@ impl<'p, 'v, 'tcx> super::super::ensurer::Context for Visitor<'p, 'v, 'tcx> { let address_place = vir_typed::Expression::field( place.clone(), vir_typed::FieldDecl::new( - "address$", + ADDRESS_FIELD_NAME, 0usize, vir_typed::Type::Int(vir_typed::ty::Int::Usize), ), @@ -111,7 +125,6 @@ impl<'p, 'v, 'tcx> super::super::ensurer::Context for Visitor<'p, 'v, 'tcx> { } vir_typed::TypeDecl::Sequence(_) => unimplemented!("ty: {}", ty), vir_typed::TypeDecl::Map(_) => unimplemented!("ty: {}", ty), - vir_typed::TypeDecl::Never => unimplemented!("ty: {}", ty), vir_typed::TypeDecl::Closure(_) => unimplemented!("ty: {}", ty), vir_typed::TypeDecl::Unsupported(_) => unimplemented!("ty: {}", ty), }; diff --git a/prusti-viper/src/encoder/high/procedures/inference/visitor/mod.rs b/prusti-viper/src/encoder/high/procedures/inference/visitor/mod.rs index 01938a7dc88..3d4c727932e 100644 --- a/prusti-viper/src/encoder/high/procedures/inference/visitor/mod.rs +++ b/prusti-viper/src/encoder/high/procedures/inference/visitor/mod.rs @@ -1,31 +1,49 @@ use super::{ + action::RawRestorationState, ensurer::{ ensure_required_permission, ensure_required_permissions, try_ensure_enum_discriminant_by_unfolding, }, - state::{FoldUnfoldState, PlaceWithDeadLifetimes, PredicateState, PredicateStateOnPath}, + state::{ + DeadLifetimeReport, FoldUnfoldState, PlaceWithDeadLifetimes, PredicateState, + PredicateStateOnPath, + }, }; -use crate::encoder::{ - errors::SpannedEncodingResult, - high::procedures::inference::{ - action::{Action, ConversionState, FoldingActionState, RestorationState, UnreachableState}, - permission::PermissionKind, - semantics::collect_permission_changes, +use crate::{ + encoder::{ + errors::{SpannedEncodingError, SpannedEncodingResult}, + high::{ + procedures::inference::{ + action::{ + Action, ConversionState, FoldingActionState, RestorationState, UnreachableState, + }, + permission::PermissionKind, + semantics::collect_permission_changes, + }, + to_typed::types::HighToTypedTypeEncoderInterface, + }, + Encoder, }, - Encoder, + error_unsupported, }; use log::debug; use prusti_common::config; -use prusti_rustc_interface::hir::def_id::DefId; +use prusti_rustc_interface::{errors::MultiSpan, hir::def_id::DefId}; use rustc_hash::{FxHashMap, FxHashSet}; use std::collections::{btree_map::Entry, BTreeMap}; use vir_crate::{ - common::{display::cjoin, position::Positioned}, + common::{cfg::Cfg, check_mode::CheckMode, display::cjoin, position::Positioned}, middle::{ self as vir_mid, - operations::{TypedToMiddleExpression, TypedToMiddleStatement, TypedToMiddleType}, + operations::{ + ty::Typed, TypedToMiddleExpression, TypedToMiddlePredicate, TypedToMiddleStatement, + TypedToMiddleType, + }, + }, + typed::{ + self as vir_typed, ast::predicate::visitors::PredicateFallibleWalker, + visitors::ExpressionFallibleWalker, }, - typed::{self as vir_typed}, }; mod context; @@ -34,6 +52,7 @@ mod debugging; pub(super) struct Visitor<'p, 'v, 'tcx> { encoder: &'p mut Encoder<'v, 'tcx>, _proc_def_id: DefId, + check_mode: Option, state_at_entry: BTreeMap, /// Used only for debugging purposes. state_at_exit: BTreeMap, @@ -55,6 +74,7 @@ impl<'p, 'v, 'tcx> Visitor<'p, 'v, 'tcx> { Self { encoder, _proc_def_id: proc_def_id, + check_mode: None, state_at_entry: Default::default(), state_at_exit: Default::default(), procedure_name: None, @@ -74,6 +94,7 @@ impl<'p, 'v, 'tcx> Visitor<'p, 'v, 'tcx> { entry_state: FoldUnfoldState, ) -> SpannedEncodingResult { self.procedure_name = Some(procedure.name.clone()); + self.check_mode = Some(procedure.check_mode); let mut path_disambiguators = BTreeMap::new(); for ((from, to), value) in procedure.get_path_disambiguators() { @@ -114,9 +135,16 @@ impl<'p, 'v, 'tcx> Visitor<'p, 'v, 'tcx> { self.render_crash_graphviz(Some(&label_markers)); } let check_mode = procedure.check_mode; + let non_aliased_places = procedure + .non_aliased_places + .into_iter() + .map(|place| place.typed_to_middle_expression(self.encoder)) + .collect::>()?; let new_procedure = vir_mid::ProcedureDecl { name: self.procedure_name.take().unwrap(), check_mode, + position: procedure.position, + non_aliased_places, entry: self.entry_label.take().unwrap(), exit: self.lower_label(&procedure.exit), basic_blocks: std::mem::take(&mut self.basic_blocks), @@ -166,9 +194,14 @@ impl<'p, 'v, 'tcx> Visitor<'p, 'v, 'tcx> { .remove(self.current_label.as_ref().unwrap()) .unwrap() }; + let mut skip_automatic_close_ref = Vec::new(); for statement in old_block.statements { - self.lower_statement(statement, &mut state)?; + self.lower_statement(statement, &mut state, &mut skip_automatic_close_ref)?; } + assert!( + skip_automatic_close_ref.is_empty(), + "Automatic opening of references cannot span multiple blocks." + ); let successor_blocks = self.current_successors()?; assert!( !successor_blocks.is_empty() || state.contains_only_leakable(), @@ -189,6 +222,7 @@ impl<'p, 'v, 'tcx> Visitor<'p, 'v, 'tcx> { &mut self, statement: vir_typed::Statement, state: &mut FoldUnfoldState, + skip_automatic_close_ref: &mut Vec, ) -> SpannedEncodingResult<()> { assert!( statement.is_comment() || statement.is_leak_all() || !statement.position().is_default(), @@ -196,6 +230,7 @@ impl<'p, 'v, 'tcx> Visitor<'p, 'v, 'tcx> { ); if let vir_typed::Statement::DeadLifetime(dead_lifetime) = statement { self.process_dead_lifetime(dead_lifetime, state)?; + self.add_statements_to_current_block(); return Ok(()); } if let vir_typed::Statement::Assign(vir_typed::Assign { @@ -205,6 +240,7 @@ impl<'p, 'v, 'tcx> Visitor<'p, 'v, 'tcx> { }) = statement { self.process_assign_discriminant(target, discriminant, position, state)?; + self.add_statements_to_current_block(); return Ok(()); } let (consumed_permissions, produced_permissions) = @@ -215,12 +251,32 @@ impl<'p, 'v, 'tcx> Visitor<'p, 'v, 'tcx> { cjoin(&consumed_permissions), cjoin(&produced_permissions) ); + match &statement { + vir_typed::Statement::OpenMutRef(open_mut_ref_statement) + if !open_mut_ref_statement.is_user_written + && state + .is_opened_ref(&open_mut_ref_statement.place)? + .is_some() => + { + skip_automatic_close_ref.push(open_mut_ref_statement.place.clone()); + return Ok(()); + } + vir_typed::Statement::CloseMutRef(close_mut_ref_statement) + if !close_mut_ref_statement.is_user_written + && skip_automatic_close_ref.contains(&close_mut_ref_statement.place) => + { + let place = skip_automatic_close_ref.pop().unwrap(); + assert_eq!(place, close_mut_ref_statement.place); + return Ok(()); + } + _ => {} + } state.check_consistency(); let actions = ensure_required_permissions(self, state, consumed_permissions.clone())?; - self.process_actions(actions)?; + self.process_actions(state, actions)?; state.remove_permissions(&consumed_permissions)?; state.insert_permissions(produced_permissions)?; - match &statement { + match statement { vir_typed::Statement::ObtainMutRef(_) => { // The requirements already performed the needed changes. } @@ -232,7 +288,7 @@ impl<'p, 'v, 'tcx> Visitor<'p, 'v, 'tcx> { // the end of the exit block). state.clear()?; } - vir_typed::Statement::SetUnionVariant(variant_statement) => { + vir_typed::Statement::SetUnionVariant(ref variant_statement) => { let position = variant_statement.position(); // Split the memory block for the union itself. let parent = variant_statement.variant_place.get_parent_ref().unwrap(); @@ -257,11 +313,560 @@ impl<'p, 'v, 'tcx> Visitor<'p, 'v, 'tcx> { self.current_statements .push(statement.typed_to_middle_statement(self.encoder)?); } + vir_typed::Statement::Pack(pack_statement) => { + // state.remove_manually_managed(&pack_statement.place)?; + let position = pack_statement.position(); + let permission = + self.get_permission_for_maybe_opened_place(state, &pack_statement.place)?; + let place = pack_statement + .place + .clone() + .typed_to_middle_expression(self.encoder)?; + // let permission = pack_statement + // .permission + // .map(|permission| permission.clone().typed_to_middle_expression(self.encoder)) + // .transpose()?; + // let encoded_statement = vir_mid::Statement::fold_owned(place, None, position); + // FIXME: Code duplication. + let mut additional_statements = Vec::new(); + let encoded_statement = match pack_statement.predicate_kind { + vir_typed::ast::statement::PredicateKind::Owned => { + vir_mid::Statement::fold_owned(place, None, permission, position) + } + vir_typed::ast::statement::PredicateKind::UniqueRef(predicate_kind) => { + // let first_reference = place + // .get_first_dereferenced_reference() + // .expect("TODO: Report a proper error"); + // let vir_mid::Type::Reference(reference) = first_reference.get_type() else { + // unreachable!() + // }; + // let lifetime = reference.lifetime.clone(); + if let Some(obligation) = pack_statement.with_obligation { + // FIXME: This should be done at the lowering to low instead of here and with permission amount that is unknown like with open. + let permission = obligation.typed_to_middle_expression(self.encoder)?; + let lifetime = predicate_kind + .lifetime + .clone() + .typed_to_middle_type(self.encoder)?; + let lifetime_token = + vir_mid::Predicate::lifetime_token(lifetime, permission, position); + let inhale = + vir_mid::Statement::inhale_predicate(lifetime_token, position); + additional_statements.push(inhale); + } + vir_mid::Statement::fold_ref( + place, + predicate_kind.lifetime.typed_to_middle_type(self.encoder)?, + vir_mid::ty::Uniqueness::Unique, + None, + position, + ) + } + vir_typed::ast::statement::PredicateKind::FracRef(_) => todo!(), + }; + self.current_statements.push(encoded_statement); + self.current_statements.extend(additional_statements); + } + vir_typed::Statement::Unpack(unpack_statement) => { + // state.insert_manually_managed(unpack_statement.place.clone())?; + let position = unpack_statement.position(); + let permission = + self.get_permission_for_maybe_opened_place(state, &unpack_statement.place)?; + let place = unpack_statement + .place + .clone() + .typed_to_middle_expression(self.encoder)?; + let mut additional_statements = Vec::new(); + // let permission = unpack_statement + // .permission + // .map(|permission| permission.clone().typed_to_middle_expression(self.encoder)) + // .transpose()?; + // FIXME: Code duplication. + let encoded_statement = match unpack_statement.predicate_kind { + vir_typed::ast::statement::PredicateKind::Owned => { + vir_mid::Statement::unfold_owned(place, None, permission, position) + } + vir_typed::ast::statement::PredicateKind::UniqueRef(predicate_kind) => { + // let first_reference = place + // .get_first_dereferenced_reference() + // .expect("TODO: Report a proper error"); + // let vir_mid::Type::Reference(reference) = first_reference.get_type() else { + // unreachable!() + // }; + // let lifetime = reference.lifetime.clone(); + if let vir_typed::TypeDecl::Struct(type_decl) = + self.encoder.encode_type_def_typed( + vir_typed::operations::ty::Typed::get_type(&unpack_statement.place), + )? + { + if let Some(invariant) = type_decl.structural_invariant { + if let Some(obligation) = unpack_statement.with_obligation { + // FIXME: This should be done at the lowering to low instead of here and with permission amount that is unknown like with open. + let permission = + obligation.typed_to_middle_expression(self.encoder)?; + let lifetime = predicate_kind + .lifetime + .clone() + .typed_to_middle_type(self.encoder)?; + let lifetime_token = vir_mid::Predicate::lifetime_token( + lifetime, permission, position, + ); + let exhale = vir_mid::Statement::exhale_predicate( + lifetime_token, + position, + ); + additional_statements.push(exhale); + } else { + struct Checker { + span: MultiSpan, + }; + impl ExpressionFallibleWalker for Checker { + type Error = SpannedEncodingError; + fn fallible_walk_expression( + &mut self, + expression: &vir_typed::Expression, + ) -> SpannedEncodingResult<()> + { + if expression.is_place() { + if expression.is_behind_pointer_dereference() { + error_unsupported!(self.span.clone() => "Invariant cannot contain a place behind a dereference"); + } + Ok(()) + } else { + vir_typed::visitors::default_fallible_walk_expression( + self, expression, + ) + } + } + } + impl PredicateFallibleWalker for Checker { + type Error = SpannedEncodingError; + fn fallible_walk_expression( + &mut self, + expression: &vir_typed::Expression, + ) -> SpannedEncodingResult<()> + { + ExpressionFallibleWalker::fallible_walk_expression( + self, expression, + ) + } + } + let span = self + .encoder + .error_manager() + .position_manager() + .get_span(position.into()) + .cloned() + .unwrap(); + let mut checker = Checker { span }; + for expression in &invariant { + ExpressionFallibleWalker::fallible_walk_expression( + &mut checker, + expression, + )?; + } + for field in type_decl.fields { + let field = + field.typed_to_middle_expression(self.encoder)?; + let field_place = place.clone().field(field, position); + additional_statements.push( + vir_mid::Statement::dead_reference( + field_place, + None, + None, + position, + ), + ); + } + } + } + } + vir_mid::Statement::unfold_ref( + place, + predicate_kind.lifetime.typed_to_middle_type(self.encoder)?, + vir_mid::ty::Uniqueness::Unique, + None, + true, + position, + ) + } + vir_typed::ast::statement::PredicateKind::FracRef(predicate_kind) => { + vir_mid::Statement::unfold_ref( + place, + predicate_kind.lifetime.typed_to_middle_type(self.encoder)?, + vir_mid::ty::Uniqueness::Shared, + None, + true, + position, + ) + } + }; + self.current_statements.push(encoded_statement); + self.current_statements.extend(additional_statements); + } + vir_typed::Statement::Obtain(_) => { + // Nothing to do because the fold-unfold already handled it. + } + vir_typed::Statement::Join(join_statement) => { + let position = join_statement.position(); + let place = join_statement + .place + .typed_to_middle_expression(self.encoder)?; + let encoded_statement = vir_mid::Statement::join_block(place, None, None, position); + self.current_statements.push(encoded_statement); + } + vir_typed::Statement::Split(split_statement) => { + let position = split_statement.position(); + let place = split_statement + .place + .typed_to_middle_expression(self.encoder)?; + let encoded_statement = + vir_mid::Statement::split_block(place, None, None, position); + self.current_statements.push(encoded_statement); + } + vir_typed::Statement::ForgetInitialization(forget_statement) => { + // state.insert_manually_managed(forget_statement.place.clone())?; + let position = forget_statement.position(); + let place = forget_statement + .place + .typed_to_middle_expression(self.encoder)?; + let encoded_statement = + vir_mid::Statement::convert_owned_into_memory_block(place, None, position); + self.current_statements.push(encoded_statement); + } + vir_typed::Statement::ForgetInitializationRange(forget_statement) => { + let position = forget_statement.position(); + let address = forget_statement + .address + .typed_to_middle_expression(self.encoder)?; + let start_index = forget_statement + .start_index + .typed_to_middle_expression(self.encoder)?; + let end_index = forget_statement + .end_index + .typed_to_middle_expression(self.encoder)?; + let encoded_statement = vir_mid::Statement::range_convert_owned_into_memory_block( + address, + start_index, + end_index, + position, + ); + self.current_statements.push(encoded_statement); + } + vir_typed::Statement::InhalePredicate(inhale_statement) => { + if let vir_typed::Predicate::OwnedNonAliased(predicate) = + &inhale_statement.predicate + { + if predicate.place.get_last_dereferenced_reference().is_some() { + // We are inhale Owned of a pointer dereference. This, + // currently, can happen only in the encoding of + // `Drop::drop` where we replace `&mut self` with `self` + // by opening it. Therefore, we need to mark `self` as + // openned. + let base = predicate.place.get_base(); + assert_eq!(base.name, "_1", "self should be _1, got: {base}"); + state.open_ref(predicate.place.clone(), None)?; + } + } + let inhale_statement = inhale_statement.typed_to_middle_statement(self.encoder)?; + self.current_statements + .push(vir_mid::Statement::InhalePredicate(inhale_statement)); + } + vir_typed::Statement::InhaleExpression(mut inhale_statement) => { + // if self.check_mode.unwrap() != CheckMode::PurificationFunctional { + // // inhale_statement.expression = + // // super::unfolding_expressions::add_unfolding_expressions( + // // inhale_statement.expression, + // // )?; + // inhale_statement.expression = super::eval_using::wrap_in_eval_using( + // self.encoder, + // state, + // inhale_statement.expression, + // )?; + // } + inhale_statement.expression = super::eval_using::wrap_in_eval_using( + self, + state, + inhale_statement.expression, + )?; + let inhale_statement = inhale_statement.typed_to_middle_statement(self.encoder)?; + self.current_statements + .push(vir_mid::Statement::InhaleExpression(inhale_statement)); + } + vir_typed::Statement::ExhaleExpression(mut exhale_statement) => { + // if self.check_mode.unwrap() != CheckMode::PurificationFunctional { + // // exhale_statement.expression = + // // super::unfolding_expressions::add_unfolding_expressions( + // // exhale_statement.expression, + // // )?; + // exhale_statement.expression = super::eval_using::wrap_in_eval_using( + // self.encoder, + // state, + // exhale_statement.expression, + // )?; + // } + exhale_statement.expression = super::eval_using::wrap_in_eval_using( + self, + state, + exhale_statement.expression, + )?; + let exhale_statement = exhale_statement.typed_to_middle_statement(self.encoder)?; + self.current_statements + .push(vir_mid::Statement::ExhaleExpression(exhale_statement)); + } + vir_typed::Statement::Assert(mut assert_statement) => { + // if self.check_mode.unwrap() != CheckMode::PurificationFunctional { + // assert_statement.expression = super::eval_using::wrap_in_eval_using( + // self.encoder, + // state, + // assert_statement.expression, + // )?; + // // super::unfolding_expressions::add_unfolding_expressions( + // // assert_statement.expression, + // // )?; + // } + assert_statement.expression = super::eval_using::wrap_in_eval_using( + self, + state, + assert_statement.expression, + )?; + let assert_statement = assert_statement.typed_to_middle_statement(self.encoder)?; + self.current_statements + .push(vir_mid::Statement::Assert(assert_statement)); + } + vir_typed::Statement::OpenMutRef(open_mut_ref_statement) => { + if !open_mut_ref_statement.is_user_written + && state + .is_opened_ref(&open_mut_ref_statement.place)? + .is_some() + { + // skip_automatic_close_ref.push(open_mut_ref_statement.place.clone()); + unreachable!(); + } else { + state.open_ref(open_mut_ref_statement.place.clone(), None)?; + let lifetime = open_mut_ref_statement + .lifetime + .typed_to_middle_type(self.encoder)?; + let lifetime_token_permission = open_mut_ref_statement + .lifetime_token_permission + .typed_to_middle_expression(self.encoder)?; + let place = open_mut_ref_statement + .place + .typed_to_middle_expression(self.encoder)?; + let position = open_mut_ref_statement.position; + let encoded_statement = vir_mid::Statement::open_mut_ref( + lifetime, + lifetime_token_permission, + place, + position, + ); + self.current_statements.push(encoded_statement); + } + } + vir_typed::Statement::CloseMutRef(close_mut_ref_statement) => { + if !close_mut_ref_statement.is_user_written + && skip_automatic_close_ref.contains(&close_mut_ref_statement.place) + { + unreachable!(); + // let place = skip_automatic_close_ref.pop().unwrap(); + // assert_eq!(place, close_mut_ref_statement.place); + } else { + assert!(state.close_ref(&close_mut_ref_statement.place)?.is_none()); + let lifetime = close_mut_ref_statement + .lifetime + .typed_to_middle_type(self.encoder)?; + let lifetime_token_permission = close_mut_ref_statement + .lifetime_token_permission + .typed_to_middle_expression(self.encoder)?; + let place = close_mut_ref_statement + .place + .typed_to_middle_expression(self.encoder)?; + let position = close_mut_ref_statement.position; + let encoded_statement = vir_mid::Statement::close_mut_ref( + lifetime, + lifetime_token_permission, + place, + position, + ); + self.current_statements.push(encoded_statement); + } + } + vir_typed::Statement::OpenFracRef(open_frac_ref_statement) => { + if !open_frac_ref_statement.is_user_written + && state + .is_opened_ref(&open_frac_ref_statement.place)? + .is_some() + { + skip_automatic_close_ref.push(open_frac_ref_statement.place); + } else { + state.open_ref( + open_frac_ref_statement.place.clone(), + Some(open_frac_ref_statement.predicate_permission_amount.clone()), + )?; + let lifetime = open_frac_ref_statement + .lifetime + .typed_to_middle_type(self.encoder)?; + let predicate_permission_amount = open_frac_ref_statement + .predicate_permission_amount + .typed_to_middle_expression(self.encoder)?; + let lifetime_token_permission = open_frac_ref_statement + .lifetime_token_permission + .typed_to_middle_expression(self.encoder)?; + let place = open_frac_ref_statement + .place + .typed_to_middle_expression(self.encoder)?; + let position = open_frac_ref_statement.position; + let encoded_statement = vir_mid::Statement::open_frac_ref( + lifetime, + predicate_permission_amount, + lifetime_token_permission, + place, + position, + ); + self.current_statements.push(encoded_statement); + } + } + vir_typed::Statement::CloseFracRef(close_frac_ref_statement) => { + if !close_frac_ref_statement.is_user_written + && skip_automatic_close_ref.contains(&close_frac_ref_statement.place) + { + let place = skip_automatic_close_ref.pop().unwrap(); + assert_eq!(place, close_frac_ref_statement.place); + } else { + let predicate_permission_amount = + state.close_ref(&close_frac_ref_statement.place)?; + assert_eq!( + predicate_permission_amount.unwrap(), + close_frac_ref_statement.predicate_permission_amount + ); + let lifetime = close_frac_ref_statement + .lifetime + .typed_to_middle_type(self.encoder)?; + let predicate_permission_amount = close_frac_ref_statement + .predicate_permission_amount + .typed_to_middle_expression(self.encoder)?; + let lifetime_token_permission = close_frac_ref_statement + .lifetime_token_permission + .typed_to_middle_expression(self.encoder)?; + let place = close_frac_ref_statement + .place + .typed_to_middle_expression(self.encoder)?; + let position = close_frac_ref_statement.position; + let encoded_statement = vir_mid::Statement::close_frac_ref( + lifetime, + lifetime_token_permission, + place, + predicate_permission_amount, + position, + ); + self.current_statements.push(encoded_statement); + } + } + vir_typed::Statement::CopyPlace(copy_place_statement) => { + let target_place = copy_place_statement + .target + .typed_to_middle_expression(self.encoder)?; + let source_permission = self + .get_permission_for_maybe_opened_place(state, ©_place_statement.source)?; + // if let Some(predicate_permission_amount) = + // state.is_opened_ref(©_place_statement.source)? + // { + // predicate_permission_amount + // .as_ref() + // .map(|amount| amount.clone().typed_to_middle_expression(self.encoder)) + // .transpose()? + // } else { + // None + // }; + let source_place = copy_place_statement + .source + .typed_to_middle_expression(self.encoder)?; + let encoded_statement = vir_mid::Statement::copy_place( + target_place, + source_place, + source_permission, + copy_place_statement.position, + ); + self.current_statements.push(encoded_statement); + } + vir_typed::Statement::Havoc(havoc_statement) => { + // The procedure encoder provides only Owned predicates. Based + // on the place and whether the reference is opened or not, we + // produce the actual predicate. + let predicate = match havoc_statement.predicate { + vir_typed::Predicate::LifetimeToken(_) => todo!(), + vir_typed::Predicate::MemoryBlockStack(predicate) => { + vir_mid::Predicate::MemoryBlockStack( + predicate.typed_to_middle_predicate(self.encoder)?, + ) + } + vir_typed::Predicate::MemoryBlockStackDrop(_) => todo!(), + vir_typed::Predicate::MemoryBlockHeap(_) => todo!(), + vir_typed::Predicate::MemoryBlockHeapRange(_) => todo!(), + vir_typed::Predicate::MemoryBlockHeapRangeGuarded(_) => todo!(), + vir_typed::Predicate::MemoryBlockHeapDrop(_) => todo!(), + vir_typed::Predicate::OwnedNonAliased(predicate) => { + // TODO: Take into account whether the reference is opened or not. + if let Some((lifetime, uniqueness)) = predicate.place.get_dereference_kind() + { + let lifetime = lifetime.typed_to_middle_type(self.encoder)?; + let place = predicate.place.typed_to_middle_expression(self.encoder)?; + match uniqueness { + vir_typed::ty::Uniqueness::Unique => { + vir_mid::Predicate::unique_ref( + lifetime, + place, + predicate.position, + ) + } + vir_typed::ty::Uniqueness::Shared => vir_mid::Predicate::frac_ref( + lifetime, + place, + predicate.position, + ), + } + } else { + vir_mid::Predicate::OwnedNonAliased( + predicate.typed_to_middle_predicate(self.encoder)?, + ) + } + } + vir_typed::Predicate::OwnedRange(_) => todo!(), + vir_typed::Predicate::OwnedSet(_) => todo!(), + vir_typed::Predicate::UniqueRef(_) => todo!(), + vir_typed::Predicate::UniqueRefRange(_) => todo!(), + vir_typed::Predicate::FracRef(_) => todo!(), + vir_typed::Predicate::FracRefRange(_) => todo!(), + }; + let encoded_statement = + vir_mid::Statement::havoc(predicate, havoc_statement.position); + self.current_statements.push(encoded_statement); + } + vir_typed::Statement::MaterializePredicate(mut materialize_predicate_statement) => { + let Some(location) = materialize_predicate_statement.predicate.get_heap_location_mut() else { + unreachable!(); + }; + *location = super::eval_using::wrap_in_eval_using(self, state, location.clone())?; + let predicate = materialize_predicate_statement + .predicate + .typed_to_middle_predicate(self.encoder)?; + let encoded_statement = vir_mid::Statement::materialize_predicate( + predicate, + materialize_predicate_statement.check_that_exists, + materialize_predicate_statement.position, + ); + self.current_statements.push(encoded_statement); + } _ => { self.current_statements .push(statement.typed_to_middle_statement(self.encoder)?); } } + self.add_statements_to_current_block(); + Ok(()) + } + + fn add_statements_to_current_block(&mut self) { let new_block = self .basic_blocks .get_mut(self.current_label.as_ref().unwrap()) @@ -269,11 +874,29 @@ impl<'p, 'v, 'tcx> Visitor<'p, 'v, 'tcx> { new_block .statements .extend(std::mem::take(&mut self.current_statements)); - Ok(()) + } + + fn get_permission_for_maybe_opened_place( + &self, + state: &FoldUnfoldState, + place: &vir_typed::Expression, + ) -> SpannedEncodingResult> { + if let Some(predicate_permission_amount) = state.is_opened_ref(place)? { + Ok(predicate_permission_amount + .as_ref() + .map(|amount| amount.clone().typed_to_middle_expression(self.encoder)) + .transpose()?) + } else { + Ok(None) + } } #[tracing::instrument(level = "debug", skip(self, actions))] - fn process_actions(&mut self, actions: Vec) -> SpannedEncodingResult<()> { + fn process_actions( + &mut self, + state: &FoldUnfoldState, + actions: Vec, + ) -> SpannedEncodingResult<()> { for action in actions { debug!(" action: {}", action); let statement = match action { @@ -285,18 +908,57 @@ impl<'p, 'v, 'tcx> Visitor<'p, 'v, 'tcx> { }) => { if let Some((lifetime, uniqueness)) = place.get_dereference_kind() { let position = place.position(); - vir_mid::Statement::unfold_ref( - place.typed_to_middle_expression(self.encoder)?, - lifetime.typed_to_middle_type(self.encoder)?, - uniqueness.typed_to_middle_type(self.encoder)?, - condition, - position, - ) + if let Some(predicate_permission_amount) = state.is_opened_ref(&place)? { + let predicate_permission_amount = predicate_permission_amount + .as_ref() + .map(|amount| { + amount.clone().typed_to_middle_expression(self.encoder) + }) + .transpose()?; + vir_mid::Statement::unfold_owned( + place.typed_to_middle_expression(self.encoder)?, + condition, + predicate_permission_amount, + position, + ) + } else { + let has_invariant = { + if let vir_typed::TypeDecl::Struct(type_decl) = + self.encoder.encode_type_def_typed( + vir_typed::operations::ty::Typed::get_type(&place), + )? + { + type_decl.structural_invariant.is_some() + } else { + false + } + }; + if uniqueness.is_unique() && has_invariant { + // TODO: Check that contains the type invariant. + let span = self + .encoder + .error_manager() + .position_manager() + .get_span(position.into()) + .cloned() + .unwrap(); + error_unsupported!(span => "cannot automatically unpack a unique reference of a type with invariant"); + } + vir_mid::Statement::unfold_ref( + place.typed_to_middle_expression(self.encoder)?, + lifetime.typed_to_middle_type(self.encoder)?, + uniqueness.typed_to_middle_type(self.encoder)?, + condition, + false, + position, + ) + } } else { let position = place.position(); vir_mid::Statement::unfold_owned( place.typed_to_middle_expression(self.encoder)?, condition, + None, position, ) } @@ -309,18 +971,34 @@ impl<'p, 'v, 'tcx> Visitor<'p, 'v, 'tcx> { }) => { if let Some((lifetime, uniqueness)) = place.get_dereference_kind() { let position = place.position(); - vir_mid::Statement::fold_ref( - place.typed_to_middle_expression(self.encoder)?, - lifetime.typed_to_middle_type(self.encoder)?, - uniqueness.typed_to_middle_type(self.encoder)?, - condition, - position, - ) + if let Some(predicate_permission_amount) = state.is_opened_ref(&place)? { + let predicate_permission_amount = predicate_permission_amount + .as_ref() + .map(|amount| { + amount.clone().typed_to_middle_expression(self.encoder) + }) + .transpose()?; + vir_mid::Statement::fold_owned( + place.typed_to_middle_expression(self.encoder)?, + condition, + predicate_permission_amount, + position, + ) + } else { + vir_mid::Statement::fold_ref( + place.typed_to_middle_expression(self.encoder)?, + lifetime.typed_to_middle_type(self.encoder)?, + uniqueness.typed_to_middle_type(self.encoder)?, + condition, + position, + ) + } } else { let position = place.position(); vir_mid::Statement::fold_owned( place.typed_to_middle_expression(self.encoder)?, condition, + None, position, ) } @@ -368,12 +1046,28 @@ impl<'p, 'v, 'tcx> Visitor<'p, 'v, 'tcx> { Action::RestoreMutBorrowed(RestorationState { lifetime, place, + is_reborrow, condition, }) => { let position = place.position(); vir_mid::Statement::restore_mut_borrowed( lifetime.typed_to_middle_type(self.encoder)?, place.typed_to_middle_expression(self.encoder)?, + is_reborrow, + None, + condition, + position, + ) + } + Action::RestoreRawBorrowed(RawRestorationState { + borrowing_place, + borrowed_place, + condition, + }) => { + let position = borrowed_place.position(); + vir_mid::Statement::restore_raw_borrowed( + borrowing_place.typed_to_middle_expression(self.encoder)?, + borrowed_place.typed_to_middle_expression(self.encoder)?, condition, position, ) @@ -397,13 +1091,38 @@ impl<'p, 'v, 'tcx> Visitor<'p, 'v, 'tcx> { state: &mut PredicateStateOnPath, condition: Option, ) -> SpannedEncodingResult<()> { - let (dead_references, places_with_dead_lifetimes) = - state.mark_lifetime_dead(&statement.lifetime); + let Some(DeadLifetimeReport {dead_dereferences, dead_references, places_with_dead_lifetimes, blocked_dead_dereferences}) = + state.mark_lifetime_dead(&statement.lifetime)? else { + return Ok(()); + }; for place in dead_references { + let place = place.typed_to_middle_expression(self.encoder)?; + let target_position = place.position(); + let vir_mid::Type::Reference(reference_type) = place.get_type() else { + unreachable!(); + }; + let target_type = (*reference_type.target_type).clone(); + self.current_statements + .push(vir_mid::Statement::unfold_owned( + place.clone(), + condition.clone(), + None, + statement.position, + )); + self.current_statements + .push(vir_mid::Statement::dead_reference( + place.deref(target_type, target_position), + None, + condition.clone(), + statement.position, + )); + } + for place in dead_dereferences { let place = place.typed_to_middle_expression(self.encoder)?; self.current_statements .push(vir_mid::Statement::dead_reference( place, + None, condition.clone(), statement.position, )); @@ -418,6 +1137,17 @@ impl<'p, 'v, 'tcx> Visitor<'p, 'v, 'tcx> { statement.position, )); } + for (place, reborrowing_lifetime) in blocked_dead_dereferences { + let place = place.typed_to_middle_expression(self.encoder)?; + let reborowing_lifetime = reborrowing_lifetime.typed_to_middle_type(self.encoder)?; + self.current_statements + .push(vir_mid::Statement::dead_reference( + place, + Some(reborowing_lifetime), + condition.clone(), + statement.position, + )); + } Ok(()) } @@ -468,7 +1198,7 @@ impl<'p, 'v, 'tcx> Visitor<'p, 'v, 'tcx> { super::permission::Permission::new(target.clone(), PermissionKind::MemoryBlock), &mut actions, )?; - self.process_actions(actions)?; + self.process_actions(state, actions)?; state.remove_permission(&super::permission::Permission::new( target.clone(), PermissionKind::MemoryBlock, diff --git a/prusti-viper/src/encoder/high/procedures/interface.rs b/prusti-viper/src/encoder/high/procedures/interface.rs index f4ec5ba3f56..cd2b37d2b4f 100644 --- a/prusti-viper/src/encoder/high/procedures/interface.rs +++ b/prusti-viper/src/encoder/high/procedures/interface.rs @@ -82,9 +82,16 @@ impl<'v, 'tcx: 'v> Private for super::super::super::Encoder<'v, 'tcx> { self.lower_block(block)?, ); } + let non_aliased_places = procedure_high + .non_aliased_places + .into_iter() + .map(|place| place.high_to_typed_expression(self)) + .collect::>()?; let procedure_typed = vir_typed::ProcedureDecl { name: procedure_high.name, check_mode: procedure_high.check_mode, + position: procedure_high.position, + non_aliased_places, entry: procedure_high.entry.high_to_typed_statement(self)?, exit: procedure_high.exit.high_to_typed_statement(self)?, basic_blocks, @@ -98,7 +105,7 @@ pub(crate) trait HighProcedureEncoderInterface<'tcx> { &mut self, proc_def_id: DefId, check_mode: CheckMode, - ) -> SpannedEncodingResult; + ) -> SpannedEncodingResult>; fn encode_type_core_proof( &mut self, ty: ty::Ty<'tcx>, @@ -112,12 +119,15 @@ impl<'v, 'tcx: 'v> HighProcedureEncoderInterface<'tcx> for super::super::super:: &mut self, proc_def_id: DefId, check_mode: CheckMode, - ) -> SpannedEncodingResult { - let procedure_high = self.encode_procedure_core_proof_high(proc_def_id, check_mode)?; - let procedure_typed = self.procedure_high_to_typed(procedure_high)?; - let procedure = - super::inference::infer_shape_operations(self, proc_def_id, procedure_typed)?; - Ok(procedure) + ) -> SpannedEncodingResult> { + let mut procedures = Vec::new(); + for procedure_high in self.encode_procedure_core_proof_high(proc_def_id, check_mode)? { + let procedure_typed = self.procedure_high_to_typed(procedure_high)?; + let procedure = + super::inference::infer_shape_operations(self, proc_def_id, procedure_typed)?; + procedures.push(procedure); + } + Ok(procedures) } fn encode_type_core_proof( @@ -125,7 +135,7 @@ impl<'v, 'tcx: 'v> HighProcedureEncoderInterface<'tcx> for super::super::super:: ty: ty::Ty<'tcx>, check_mode: CheckMode, ) -> SpannedEncodingResult { - assert_eq!(check_mode, CheckMode::CoreProof); + assert_eq!(check_mode, CheckMode::MemorySafety); let ty_high = self.encode_type_high(ty)?; ty_high.high_to_middle(self) } diff --git a/prusti-viper/src/encoder/high/pure_functions/interface.rs b/prusti-viper/src/encoder/high/pure_functions/interface.rs index 6454aaf88d7..1f215ab7ed7 100644 --- a/prusti-viper/src/encoder/high/pure_functions/interface.rs +++ b/prusti-viper/src/encoder/high/pure_functions/interface.rs @@ -93,24 +93,25 @@ impl<'v, 'tcx: 'v> HighPureFunctionEncoderInterface<'tcx> fn encode_subslice_call( &self, container: vir_high::Expression, - range: vir_high::Expression, + _range: vir_high::Expression, ) -> EncodingResult { // FIXME: Should use encode_builtin_function_use. - let name = "subslice"; - let element_type = extract_container_element_type(&container)?; - let pure_lifetime = vir_high::ty::LifetimeConst::erased(); - let return_type = vir_high::Type::reference( - pure_lifetime, - vir_high::ty::Uniqueness::Shared, - // FIXME: add slice lifetimes for subslice_call - vir_high::Type::slice(element_type.clone(), vec![]), - ); - Ok(vir_high::Expression::function_call( - name, - vec![element_type.clone()], - vec![container, range], - return_type, - )) + let _name = "subslice"; + let _element_type = extract_container_element_type(&container)?; + // let pure_lifetime = vir_high::ty::LifetimeConst::erased(); + let _pure_lifetime = unimplemented!(); + // let return_type = vir_high::Type::reference( + // pure_lifetime, + // vir_high::ty::Uniqueness::Shared, + // // FIXME: add slice lifetimes for subslice_call + // vir_high::Type::slice(element_type.clone(), vec![]), + // ); + // Ok(vir_high::Expression::function_call( + // name, + // vec![element_type.clone()], + // vec![container, range], + // return_type, + // )) } /// Encode len of a slice. diff --git a/prusti-viper/src/encoder/high/to_typed/expression.rs b/prusti-viper/src/encoder/high/to_typed/expression.rs index 9d148071769..cd0ba792370 100644 --- a/prusti-viper/src/encoder/high/to_typed/expression.rs +++ b/prusti-viper/src/encoder/high/to_typed/expression.rs @@ -1,7 +1,10 @@ use crate::encoder::errors::{SpannedEncodingError, SpannedEncodingResult}; use vir_crate::{ - high as vir_high, typed as vir_typed, - typed::operations::{HighToTypedExpressionLowerer, HighToTypedType}, + high as vir_high, + typed::{ + self as vir_typed, + operations::{HighToTypedExpressionLowerer, HighToTypedPredicateLowerer, HighToTypedType}, + }, }; impl<'v, 'tcx> HighToTypedExpressionLowerer for crate::encoder::Encoder<'v, 'tcx> { @@ -64,4 +67,11 @@ impl<'v, 'tcx> HighToTypedExpressionLowerer for crate::encoder::Encoder<'v, 'tcx index: variant_index.index, }) } + + fn high_to_typed_expression_predicate( + &mut self, + predicate: vir_high::Predicate, + ) -> Result { + self.high_to_typed_predicate_predicate(predicate) + } } diff --git a/prusti-viper/src/encoder/high/to_typed/predicate.rs b/prusti-viper/src/encoder/high/to_typed/predicate.rs index 46a7812bd2c..a04a40c986e 100644 --- a/prusti-viper/src/encoder/high/to_typed/predicate.rs +++ b/prusti-viper/src/encoder/high/to_typed/predicate.rs @@ -29,4 +29,18 @@ impl<'v, 'tcx> HighToTypedPredicateLowerer for crate::encoder::Encoder<'v, 'tcx> name: lifetime_const.name, }) } + + fn high_to_typed_predicate_trigger( + &mut self, + trigger: vir_high::Trigger, + ) -> Result { + trigger.high_to_typed_expression(self) + } + + fn high_to_typed_predicate_variable_decl( + &mut self, + variable: vir_high::VariableDecl, + ) -> Result { + variable.high_to_typed_expression(self) + } } diff --git a/prusti-viper/src/encoder/high/to_typed/statement.rs b/prusti-viper/src/encoder/high/to_typed/statement.rs index 56b3bd135de..7f0443ca45b 100644 --- a/prusti-viper/src/encoder/high/to_typed/statement.rs +++ b/prusti-viper/src/encoder/high/to_typed/statement.rs @@ -71,4 +71,14 @@ impl<'v, 'tcx> HighToTypedStatementLowerer for crate::encoder::Encoder<'v, 'tcx> ) -> Result { operand.high_to_typed_rvalue(self) } + + fn high_to_typed_statement_uniqueness( + &mut self, + uniqueness: vir_high::ty::Uniqueness, + ) -> Result { + Ok(match uniqueness { + vir_high::ty::Uniqueness::Shared => vir_typed::ty::Uniqueness::Shared, + vir_high::ty::Uniqueness::Unique => vir_typed::ty::Uniqueness::Unique, + }) + } } diff --git a/prusti-viper/src/encoder/high/to_typed/ty.rs b/prusti-viper/src/encoder/high/to_typed/ty.rs index 54a4e722c8f..c75448757ef 100644 --- a/prusti-viper/src/encoder/high/to_typed/ty.rs +++ b/prusti-viper/src/encoder/high/to_typed/ty.rs @@ -28,12 +28,23 @@ impl<'v, 'tcx> HighToTypedTypeLowerer for crate::encoder::Encoder<'v, 'tcx> { ) -> Result { let arguments = ty.arguments.high_to_typed_type(self)?; Ok(vir_typed::Type::struct_( - self.generate_tuple_name(&arguments)?, + // self.generate_tuple_name(&arguments)?, + "Tuple".to_string(), arguments, ty.lifetimes.high_to_typed_type(self)?, )) } + fn high_to_typed_type_type_never( + &mut self, + ) -> Result { + Ok(vir_typed::Type::struct_( + "Never".to_string(), + Vec::new(), + Vec::new(), + )) + } + fn high_to_typed_type_expression( &mut self, expression: vir_high::Expression, @@ -104,6 +115,9 @@ impl<'v, 'tcx> TypedToHighTypeUpperer for crate::encoder::Encoder<'v, 'tcx> { vir_typed::expression::ConstantValue::Float(value) => { vir_high::expression::ConstantValue::Float(value) } + vir_typed::expression::ConstantValue::String(value) => { + vir_high::expression::ConstantValue::String(value) + } vir_typed::expression::ConstantValue::FnPtr => { vir_high::expression::ConstantValue::FnPtr } diff --git a/prusti-viper/src/encoder/high/to_typed/type_decl.rs b/prusti-viper/src/encoder/high/to_typed/type_decl.rs index 45564009fff..a3b022c7d0b 100644 --- a/prusti-viper/src/encoder/high/to_typed/type_decl.rs +++ b/prusti-viper/src/encoder/high/to_typed/type_decl.rs @@ -49,16 +49,38 @@ impl<'v, 'tcx> HighToTypedTypeDeclLowerer for crate::encoder::Encoder<'v, 'tcx> &mut self, decl: vir_high::type_decl::Tuple, ) -> Result { + let size = if decl.arguments.is_empty() { + Some(0) + } else { + None + }; let arguments = decl.arguments.high_to_typed_type(self)?; Ok(vir_typed::TypeDecl::struct_( self.generate_tuple_name(&arguments)?, decl.lifetimes.high_to_typed_type(self)?, decl.const_parameters.high_to_typed_expression(self)?, + None, arguments .into_iter() .enumerate() .map(|(index, ty)| vir_typed::FieldDecl::new(format!("tuple_{index}"), index, ty)) .collect(), + size, + Default::default(), + )) + } + + fn high_to_typed_type_decl_type_decl_never( + &mut self, + ) -> Result { + Ok(vir_typed::TypeDecl::struct_( + "Never".to_owned(), + Vec::new(), + Vec::new(), + Some(vec![false.into()]), + Vec::new(), + None, + Default::default(), )) } @@ -131,4 +153,11 @@ impl<'v, 'tcx> HighToTypedTypeDeclLowerer for crate::encoder::Encoder<'v, 'tcx> variants: decl.variants.high_to_typed_type_decl(self)?, }) } + + fn high_to_typed_type_decl_position( + &mut self, + position: vir_high::Position, + ) -> Result { + Ok(position) + } } diff --git a/prusti-viper/src/encoder/high/to_typed/types/interface.rs b/prusti-viper/src/encoder/high/to_typed/types/interface.rs index 286bf1a57bf..e2ad8e50382 100644 --- a/prusti-viper/src/encoder/high/to_typed/types/interface.rs +++ b/prusti-viper/src/encoder/high/to_typed/types/interface.rs @@ -34,8 +34,15 @@ impl<'v, 'tcx: 'v> HighToTypedTypeEncoderInterface &mut self, ty: &vir_typed::Type, ) -> SpannedEncodingResult { - let high_type = &self.typed_type_encoder_state.encoded_types_inverse[ty]; - let type_decl_high = self.encode_type_def_high(high_type)?; + let normalized_type = ty.normalize_type(); + let high_type = &self + .typed_type_encoder_state + .encoded_types_inverse + .get(&normalized_type) + .unwrap_or_else(|| { + panic!("Type {normalized_type} was not encoded by the HighToTypedTypeEncoder",) + }); + let type_decl_high = self.encode_type_def_high(high_type, true)?; type_decl_high.high_to_typed_type_decl(self) } diff --git a/prusti-viper/src/encoder/high/types/fields.rs b/prusti-viper/src/encoder/high/types/fields.rs index 3ad47790c61..8ae1353f389 100644 --- a/prusti-viper/src/encoder/high/types/fields.rs +++ b/prusti-viper/src/encoder/high/types/fields.rs @@ -55,6 +55,8 @@ pub(crate) fn create_value_field(ty: vir::Type) -> EncodingResult { unreachable!() } diff --git a/prusti-viper/src/encoder/high/types/interface.rs b/prusti-viper/src/encoder/high/types/interface.rs index b97144bb328..dad56cf3c59 100644 --- a/prusti-viper/src/encoder/high/types/interface.rs +++ b/prusti-viper/src/encoder/high/types/interface.rs @@ -1,5 +1,7 @@ use crate::encoder::{ - errors::{EncodingError, EncodingResult, SpannedEncodingResult, WithSpan}, + errors::{ + EncodingError, EncodingResult, SpannedEncodingError, SpannedEncodingResult, WithSpan, + }, high::{ lower::{predicates::IntoPredicates, IntoPolymorphic}, to_middle::HighToMiddle, @@ -10,11 +12,14 @@ use crate::encoder::{ #[rustfmt::skip] use prusti_common::{config, report::log}; use prusti_rustc_interface::{errors::MultiSpan, middle::ty}; -use rustc_hash::FxHashMap; +use rustc_hash::{FxHashMap, FxHashSet}; use std::cell::RefCell; use vir_crate::{ high as vir_high, - middle::{self as vir_mid}, + middle::{ + self as vir_mid, ast::predicate::visitors::PredicateFallibleWalker, + visitors::ExpressionFallibleWalker, + }, polymorphic as vir_poly, }; @@ -65,7 +70,7 @@ impl<'v, 'tcx: 'v> HighTypeEncoderInterfacePrivate for super::super::super::Enco let encoded_type = &self.high_type_encoder_state.lowered_types_inverse.borrow() [predicate_name] .clone(); - let encoded_type_decl = self.encode_type_def_high(encoded_type)?; + let encoded_type_decl = self.encode_type_def_high(encoded_type, false)?; // FIXME: Change not to use `with_default_span` here. let predicates = encoded_type_decl .lower(encoded_type, self) @@ -147,6 +152,12 @@ pub(crate) trait HighTypeEncoderInterface<'tcx> { fn get_type_definition_span_mid(&self, ty: &vir_mid::Type) -> SpannedEncodingResult; fn get_type_decl_mid(&mut self, ty: &vir_mid::Type) -> SpannedEncodingResult; + fn has_invariant_mid(&mut self, ty: &vir_mid::Type) -> SpannedEncodingResult; + /// Get the places on which parts of the invariant depend. + fn get_invariant_constrained_places_mid( + &mut self, + ty: &vir_mid::Type, + ) -> SpannedEncodingResult>; } impl<'v, 'tcx: 'v> HighTypeEncoderInterface<'tcx> for super::super::super::Encoder<'v, 'tcx> { @@ -274,7 +285,6 @@ impl<'v, 'tcx: 'v> HighTypeEncoderInterface<'tcx> for super::super::super::Encod vir_mid::TypeDecl::Struct(decl) => decl.fields.is_empty(), vir_mid::TypeDecl::Enum(decl) => decl.variants.is_empty(), vir_mid::TypeDecl::Array(_decl) => unimplemented!(), - vir_mid::TypeDecl::Never => true, vir_mid::TypeDecl::Closure(_) => unimplemented!(), vir_mid::TypeDecl::Unsupported(_) => unimplemented!(), }) @@ -289,7 +299,110 @@ impl<'v, 'tcx: 'v> HighTypeEncoderInterface<'tcx> for super::super::super::Encod ) -> SpannedEncodingResult { let high_type = self.decode_type_mid_into_high(ty.erase_lifetimes().erase_const_generics())?; - let high_type_decl = self.encode_type_def_high(&high_type)?; + let high_type_decl = self.encode_type_def_high(&high_type, true)?; high_type_decl.high_to_middle(self) } + fn has_invariant_mid(&mut self, ty: &vir_mid::Type) -> SpannedEncodingResult { + let high_type = + self.decode_type_mid_into_high(ty.erase_lifetimes().erase_const_generics())?; + let high_type_decl = self.encode_type_def_high(&high_type, true)?; + match high_type_decl { + vir_high::TypeDecl::Struct(decl) => Ok(decl.structural_invariant.is_some()), + _ => Ok(false), + } + } + fn get_invariant_constrained_places_mid( + &mut self, + ty: &vir_mid::Type, + ) -> SpannedEncodingResult> { + struct Collector { + places: FxHashSet, + } + impl ExpressionFallibleWalker for Collector { + type Error = SpannedEncodingError; + fn fallible_walk_expression( + &mut self, + expression: &vir_mid::Expression, + ) -> Result<(), Self::Error> { + if expression.is_place() { + assert!( + expression.get_last_dereference().is_none(), + "unimplemented: {expression}" + ); + self.places.insert(expression.clone()); + Ok(()) + } else { + vir_mid::visitors::default_fallible_walk_expression(self, expression) + } + } + fn fallible_walk_eval_in( + &mut self, + eval_in: &vir_mid::EvalIn, + ) -> Result<(), Self::Error> { + unimplemented!("eval_in in structural invariant: {eval_in}"); + } + fn fallible_walk_deref_enum( + &mut self, + deref: &vir_mid::Deref, + ) -> Result<(), Self::Error> { + if deref.ty.is_pointer() { + unimplemented!("raw pointer deref in structural invariant: {deref}"); + } else { + ExpressionFallibleWalker::fallible_walk_expression(self, &deref.base) + } + } + fn fallible_walk_predicate( + &mut self, + predicate: &vir_mid::Predicate, + ) -> Result<(), Self::Error> { + match predicate { + vir_mid::Predicate::LifetimeToken(_) => todo!(), + vir_mid::Predicate::MemoryBlockStack(_) => todo!(), + vir_mid::Predicate::MemoryBlockStackDrop(_) => todo!(), + vir_mid::Predicate::MemoryBlockHeap(_) => todo!(), + vir_mid::Predicate::MemoryBlockHeapRange(_) => todo!(), + vir_mid::Predicate::MemoryBlockHeapRangeGuarded(_) => todo!(), + vir_mid::Predicate::MemoryBlockHeapDrop(predicate) => { + let deref = predicate.address.get_last_dereferenced_pointer().unwrap(); + ExpressionFallibleWalker::fallible_walk_expression(self, deref) + } + vir_mid::Predicate::OwnedNonAliased(predicate) => { + let deref = predicate.place.get_last_dereferenced_pointer().unwrap(); + ExpressionFallibleWalker::fallible_walk_expression(self, deref) + } + vir_mid::Predicate::OwnedRange(_) => todo!(), + vir_mid::Predicate::OwnedSet(_) => todo!(), + vir_mid::Predicate::UniqueRef(_) => todo!(), + vir_mid::Predicate::UniqueRefRange(_) => todo!(), + vir_mid::Predicate::FracRef(_) => todo!(), + vir_mid::Predicate::FracRefRange(_) => todo!(), + } + } + } + impl PredicateFallibleWalker for Collector { + type Error = SpannedEncodingError; + fn fallible_walk_expression( + &mut self, + expression: &vir_mid::Expression, + ) -> Result<(), Self::Error> { + ExpressionFallibleWalker::fallible_walk_expression(self, expression) + } + } + let type_decl = self.get_type_decl_mid(ty)?; + let vir_mid::TypeDecl::Struct(decl) = type_decl else { + unreachable!( + "type {:?} is not a struct", + type_decl + ); + }; + let invariant = decl.structural_invariant.unwrap(); + let mut collector = Collector { + places: FxHashSet::default(), + }; + for expression in invariant { + ExpressionFallibleWalker::fallible_walk_expression(&mut collector, &expression)?; + } + let constrained_places = collector.places.into_iter().collect(); + Ok(constrained_places) + } } diff --git a/prusti-viper/src/encoder/middle/core_proof/addresses/encoder.rs b/prusti-viper/src/encoder/middle/core_proof/addresses/encoder.rs index 0d7790a1916..6162c8cf46e 100644 --- a/prusti-viper/src/encoder/middle/core_proof/addresses/encoder.rs +++ b/prusti-viper/src/encoder/middle/core_proof/addresses/encoder.rs @@ -1,15 +1,46 @@ use super::{super::utils::place_domain_encoder::PlaceExpressionDomainEncoder, AddressesInterface}; -use crate::encoder::{errors::SpannedEncodingResult, middle::core_proof::lowerer::Lowerer}; +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::{ + lowerer::Lowerer, pointers::PointersInterface, references::ReferencesInterface, + snapshots::IntoProcedureSnapshot, + }, +}; use vir_crate::{ low as vir_low, - middle::{self as vir_mid}, + middle::{self as vir_mid, operations::ty::Typed}, }; -pub(super) struct PlaceAddressEncoder {} +#[derive(Debug, Clone, PartialEq, Eq)] +enum EncodingContext { + Procedure, + Predicate { self_address: vir_low::Expression }, +} + +pub(super) struct PlaceAddressEncoder { + old_label: Option, + encoding_context: EncodingContext, +} + +impl PlaceAddressEncoder { + pub(super) fn new_in_procedure() -> Self { + Self { + old_label: None, + encoding_context: EncodingContext::Procedure, + } + } + + pub(super) fn new_in_predicate(self_address: vir_low::Expression) -> Self { + Self { + old_label: None, + encoding_context: EncodingContext::Predicate { self_address }, + } + } +} impl PlaceExpressionDomainEncoder for PlaceAddressEncoder { - fn domain_name(&mut self, _lowerer: &mut Lowerer) -> &str { - "Address" + fn domain_name(&mut self, lowerer: &mut Lowerer) -> &str { + lowerer.address_domain() } fn encode_local( @@ -17,16 +48,32 @@ impl PlaceExpressionDomainEncoder for PlaceAddressEncoder { local: &vir_mid::expression::Local, lowerer: &mut Lowerer, ) -> SpannedEncodingResult { - lowerer.root_address(local) + match &self.encoding_context { + EncodingContext::Procedure => lowerer.root_address(local, &self.old_label), + EncodingContext::Predicate { self_address } => { + assert!(self.old_label.is_none()); + assert!(local.variable.is_self_variable()); + Ok(self_address.clone()) + } + } } fn encode_deref( &mut self, - _deref: &vir_mid::expression::Deref, - _lowerer: &mut Lowerer, + deref: &vir_mid::expression::Deref, + lowerer: &mut Lowerer, _arg: vir_low::Expression, ) -> SpannedEncodingResult { - unreachable!("The address cannot be dereferenced; use the value instead.") + // FIXME: Code duplication with AddressesInterface::extract_root_address + // FIXME: Code duplication with AssertionEncoder. + let base_snapshot = deref.base.to_procedure_snapshot(lowerer)?; + let ty = deref.base.get_type(); + let result = if ty.is_reference() { + lowerer.reference_address(ty, base_snapshot, deref.position)? + } else { + lowerer.pointer_address(ty, base_snapshot, deref.position)? + }; + Ok(result) } fn encode_array_index_axioms( @@ -36,4 +83,12 @@ impl PlaceExpressionDomainEncoder for PlaceAddressEncoder { ) -> SpannedEncodingResult<()> { Ok(()) } + + fn encode_labelled_old( + &mut self, + _expression: &vir_mid::expression::LabelledOld, + _lowerer: &mut Lowerer, + ) -> SpannedEncodingResult { + todo!() + } } diff --git a/prusti-viper/src/encoder/middle/core_proof/addresses/interface.rs b/prusti-viper/src/encoder/middle/core_proof/addresses/interface.rs index 93b37fb1b7c..49f969ebb17 100644 --- a/prusti-viper/src/encoder/middle/core_proof/addresses/interface.rs +++ b/prusti-viper/src/encoder/middle/core_proof/addresses/interface.rs @@ -4,33 +4,102 @@ use super::{ use crate::encoder::{ errors::SpannedEncodingResult, middle::core_proof::{ - lowerer::{DomainsLowererInterface, Lowerer, VariablesLowererInterface}, - references::ReferencesInterface, - snapshots::IntoProcedureSnapshot, + lowerer::{DomainsLowererInterface, Lowerer}, + pointers::PointersInterface, + snapshots::SnapshotVariablesInterface, + type_layouts::TypeLayoutsInterface, }, }; use vir_crate::{ + common::{ + builtin_constants::{ADDRESS_DOMAIN_NAME, ALLOCATION_DOMAIN_NAME}, + expression::{BinaryOperationHelpers, QuantifierHelpers}, + position::Positioned, + }, low as vir_low, - middle::{self as vir_mid, operations::ty::Typed}, + middle::{self as vir_mid}, }; pub(in super::super) trait AddressesInterface { + fn address_domain(&self) -> &'static str; + fn allocation_domain(&self) -> &'static str; fn address_type(&mut self) -> SpannedEncodingResult; + fn allocation_type(&mut self) -> SpannedEncodingResult; + fn address_null( + &mut self, + position: vir_low::Position, + ) -> SpannedEncodingResult; + fn address_offset( + &mut self, + size: vir_low::Expression, + address: vir_low::Expression, + offset: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult; + fn offset_from_address( + &mut self, + size: vir_low::Expression, + address: vir_low::Expression, + from_address: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult; + fn index_into_allocation( + &mut self, + size: vir_low::Expression, + address: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult; + fn address_allocation( + &mut self, + address: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult; + fn fresh_allocation( + &mut self, + position: vir_low::Position, + ) -> SpannedEncodingResult; + fn address_range_contains( + &mut self, + base_address: vir_low::Expression, + start_index: vir_low::Expression, + end_index: vir_low::Expression, + element_size: vir_low::Expression, + checked_address: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult; + fn pointer_range_contains( + &mut self, + base_address: vir_low::Expression, + element_size: vir_low::Expression, + range_length: vir_low::Expression, + checked_address: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult; /// Constructs a variable representing the address of the given MIR-level /// variable. fn root_address( &mut self, local: &vir_mid::expression::Local, + old_label: &Option, ) -> SpannedEncodingResult; /// Get the variable representing the root address of this place. fn extract_root_address( &mut self, place: &vir_mid::Expression, ) -> SpannedEncodingResult; + /// Emits code that represents the place's address. This method is supposed + /// to be used in procedures for places whose root addresses are tracked + /// with SSA variables. For addresses inside predicates, use + /// `encode_expression_as_place_address_in_predicate`. fn encode_expression_as_place_address( &mut self, place: &vir_mid::Expression, ) -> SpannedEncodingResult; + // fn encode_expression_as_place_address_in_predicate( + // &mut self, + // place: &vir_mid::Expression, + // self_address: vir_low::Expression, + // ) -> SpannedEncodingResult; fn encode_field_address( &mut self, base_type: &vir_mid::Type, @@ -45,45 +114,465 @@ pub(in super::super) trait AddressesInterface { base_address: vir_low::Expression, position: vir_mid::Position, ) -> SpannedEncodingResult; + fn encode_index_address( + &mut self, + base_type: &vir_mid::Type, + base_address: vir_low::Expression, + index: vir_low::Expression, + position: vir_mid::Position, + ) -> SpannedEncodingResult; +} + +impl<'p, 'v: 'p, 'tcx: 'v> Lowerer<'p, 'v, 'tcx> { + fn encode_address_axioms(&mut self) -> SpannedEncodingResult<()> { + if !self.address_state.are_address_axioms_encoded { + self.address_state.are_address_axioms_encoded = true; + use vir_low::macros::*; + let size_type = self.size_type()?; + let address_type = self.address_type()?; + let allocation_type = self.allocation_type()?; + var_decls! { + allocation: Allocation, + address: Address, + index: Int, + element_size: {size_type} + } + let position = vir_low::Position::default(); + { + // Address constructor is injective with respect to allocation + // and index. Both inverse functions are total, which is + // important for the performance. + { + // ``` + // forall allocation, index, element_size :: + // {address_constructor(allocation, index, element_size)} + // get_allocation(address_constructor(allocation, index, element_size)) == allocation && + // get_index(address_constructor(allocation, index, element_size), element_size) == index + // ``` + let address_constructor = self.create_domain_func_app( + ADDRESS_DOMAIN_NAME, + "address_constructor$", + vec![ + allocation.clone().into(), + index.clone().into(), + element_size.clone().into(), + ], + address_type.clone(), + position, + )?; + let get_allocation = self.create_domain_func_app( + ADDRESS_DOMAIN_NAME, + "get_allocation$", + vec![address_constructor.clone()], + allocation_type.clone(), + position, + )?; + let get_index = self.create_domain_func_app( + ADDRESS_DOMAIN_NAME, + "get_index$", + vec![address_constructor.clone(), element_size.clone().into()], + vir_low::Type::Int, + position, + )?; + let body = vir_low::Expression::forall( + vec![allocation.clone(), index.clone(), element_size.clone()], + vec![vir_low::Trigger::new(vec![address_constructor])], + expr! { ([get_allocation] == allocation) && ([get_index] == index) }, + ); + let axiom = vir_low::DomainAxiomDecl::new( + None, + "address_constructor$injectivity1", + body, + ); + self.declare_axiom(ADDRESS_DOMAIN_NAME, axiom)?; + } + { + // ``` + // forall address, element_size :: + // {address_constructor(get_allocation(address), get_index(address, element_size), element_size)} + // address_constructor(get_allocation(address), get_index(address, element_size), element_size) == address + // ``` + let get_allocation = self.create_domain_func_app( + ADDRESS_DOMAIN_NAME, + "get_allocation$", + vec![address.clone().into()], + allocation_type.clone(), + position, + )?; + let get_index = self.create_domain_func_app( + ADDRESS_DOMAIN_NAME, + "get_index$", + vec![address.clone().into(), element_size.clone().into()], + vir_low::Type::Int, + position, + )?; + let address_constructor = self.create_domain_func_app( + ADDRESS_DOMAIN_NAME, + "address_constructor$", + vec![ + get_allocation, + index.clone().into(), + element_size.clone().into(), + ], + address_type.clone(), + position, + )?; + let body = vir_low::Expression::forall( + vec![address.clone(), index.clone(), element_size.clone()], + vec![vir_low::Trigger::new(vec![address_constructor.clone()])], + expr! { ([get_index] == index) ==> ([address_constructor] == address) }, + ); + let axiom = vir_low::DomainAxiomDecl::new( + None, + "address_constructor$injectivity2", + body, + ); + self.declare_axiom(ADDRESS_DOMAIN_NAME, axiom)?; + } + } + { + // Define range_contains function, which is used for defining + // quantified permissions. + // ``` + // forall base_address, start_index, end_index, element_size, checked_address :: + // {address_range_contains(base_address, start_index, end_index, element_size, checked_address)} + // address_range_contains(base_address, start_index, end_index, element_size, checked_address) == + // (get_allocation(base_address) == get_allocation(checked_address) && + // get_index(base_address, element_size) + start_index <= get_index(checked_address, element_size)) && + // get_index(checked_address, element_size)) < get_index(base_address, element_size) + end_index + // ) + // ``` + var_decls! { + base_address: Address, + start_index: Int, + end_index: Int, + checked_address: Address + } + let address_range_contains = self.create_domain_func_app( + ADDRESS_DOMAIN_NAME, + "address_range_contains$", + vec![ + base_address.clone().into(), + start_index.clone().into(), + end_index.clone().into(), + element_size.clone().into(), + checked_address.clone().into(), + ], + vir_low::Type::Bool, + position, + )?; + let get_allocation_base_address = self.create_domain_func_app( + ADDRESS_DOMAIN_NAME, + "get_allocation$", + vec![base_address.clone().into()], + allocation_type.clone(), + position, + )?; + let get_allocation_checked_address = self.create_domain_func_app( + ADDRESS_DOMAIN_NAME, + "get_allocation$", + vec![checked_address.clone().into()], + allocation_type.clone(), + position, + )?; + let get_index_base_address = self.create_domain_func_app( + ADDRESS_DOMAIN_NAME, + "get_index$", + vec![base_address.clone().into(), element_size.clone().into()], + vir_low::Type::Int, + position, + )?; + let get_index_checked_address = self.create_domain_func_app( + ADDRESS_DOMAIN_NAME, + "get_index$", + vec![checked_address.clone().into(), element_size.clone().into()], + vir_low::Type::Int, + position, + )?; + let definition = expr! { + (([get_allocation_base_address] == [get_allocation_checked_address]) && + (([get_index_base_address.clone()] + start_index) <= [get_index_checked_address.clone()])) && + ([get_index_checked_address] < ([get_index_base_address] + end_index)) + }; + let body = vir_low::Expression::forall( + vec![ + base_address, + start_index, + end_index, + element_size.clone(), + checked_address, + ], + vec![vir_low::Trigger::new(vec![address_range_contains.clone()])], + expr! { [address_range_contains] == [definition] }, + ); + let axiom = + vir_low::DomainAxiomDecl::new(None, "address_range_contains$definition", body); + self.declare_axiom(ADDRESS_DOMAIN_NAME, axiom)?; + } + { + // Define `offset_address` function, which is used for computing + // new addresses by offsetting them. + // ``` + // forall address, offset, element_size :: + // {offset_address(address, offset, element_size)} + // offset_address(address, offset, element_size) == + // address_constructor(get_allocation(address), get_index(address, element_size) + offset, element_size) + // ``` + var_decls! { + offset: Int + } + let offset_address = self.create_domain_func_app( + ADDRESS_DOMAIN_NAME, + "offset_address$", + vec![ + address.clone().into(), + offset.clone().into(), + element_size.clone().into(), + ], + address_type.clone(), + position, + )?; + let get_allocation = self.create_domain_func_app( + ADDRESS_DOMAIN_NAME, + "get_allocation$", + vec![address.clone().into()], + allocation_type, + position, + )?; + let get_index = self.create_domain_func_app( + ADDRESS_DOMAIN_NAME, + "get_index$", + vec![address.clone().into(), element_size.clone().into()], + vir_low::Type::Int, + position, + )?; + let definition = self.create_domain_func_app( + ADDRESS_DOMAIN_NAME, + "address_constructor$", + vec![ + get_allocation, + vir_low::Expression::add(get_index, offset.clone().into()), + element_size.clone().into(), + ], + address_type, + position, + )?; + let body = vir_low::Expression::forall( + vec![address, offset, element_size], + vec![vir_low::Trigger::new(vec![offset_address.clone()])], + expr! { [offset_address] == [definition] }, + ); + let axiom = vir_low::DomainAxiomDecl::new(None, "offset_address$definition", body); + self.declare_axiom(ADDRESS_DOMAIN_NAME, axiom)?; + } + } + Ok(()) + } } impl<'p, 'v: 'p, 'tcx: 'v> AddressesInterface for Lowerer<'p, 'v, 'tcx> { + fn address_domain(&self) -> &'static str { + ADDRESS_DOMAIN_NAME + } + fn allocation_domain(&self) -> &'static str { + ALLOCATION_DOMAIN_NAME + } fn address_type(&mut self) -> SpannedEncodingResult { - self.domain_type("Address") + self.domain_type(self.address_domain()) + } + fn allocation_type(&mut self) -> SpannedEncodingResult { + self.domain_type(self.allocation_domain()) + } + fn address_null( + &mut self, + position: vir_low::Position, + ) -> SpannedEncodingResult { + let address_type = self.address_type()?; + self.create_domain_func_app( + ADDRESS_DOMAIN_NAME, + "null_address$", + Vec::new(), + address_type, + position, + ) + } + fn address_offset( + &mut self, + size: vir_low::Expression, + address: vir_low::Expression, + offset: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult { + let address_type = self.address_type()?; + self.encode_address_axioms()?; + self.create_domain_func_app( + ADDRESS_DOMAIN_NAME, + "offset_address$", + vec![address, offset, size], + address_type, + position, + ) + } + fn offset_from_address( + &mut self, + size: vir_low::Expression, + address: vir_low::Expression, + from_address: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult { + self.encode_address_axioms()?; + let index1 = self.index_into_allocation(size.clone(), address, position)?; + let index2 = self.index_into_allocation(size, from_address, position)?; + let offset = vir_low::Expression::subtract(index1, index2); + Ok(offset) + } + fn index_into_allocation( + &mut self, + size: vir_low::Expression, + address: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult { + self.encode_address_axioms()?; + self.create_domain_func_app( + ADDRESS_DOMAIN_NAME, + "get_index$", + vec![address, size], + vir_low::Type::Int, + position, + ) + } + fn address_allocation( + &mut self, + address: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult { + self.encode_address_axioms()?; + let allocation_type = self.allocation_type()?; + self.create_domain_func_app( + ADDRESS_DOMAIN_NAME, + "get_allocation$", + vec![address], + allocation_type, + position, + ) + } + fn fresh_allocation( + &mut self, + position: vir_low::Position, + ) -> SpannedEncodingResult { + self.encode_address_axioms()?; + let allocation_type = self.allocation_type()?; + self.address_state.fresh_allocation_counter += 1; + self.create_domain_func_app( + ADDRESS_DOMAIN_NAME, + "fresh_allocation$", + vec![self.address_state.fresh_allocation_counter.into()], + allocation_type, + position, + ) + } + fn address_range_contains( + &mut self, + base_address: vir_low::Expression, + start_index: vir_low::Expression, + end_index: vir_low::Expression, + element_size: vir_low::Expression, + checked_address: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult { + self.encode_address_axioms()?; + self.create_domain_func_app( + ADDRESS_DOMAIN_NAME, + "address_range_contains$", + vec![ + base_address, + start_index, + end_index, + element_size, + checked_address, + ], + vir_low::Type::Bool, + position, + ) + } + fn pointer_range_contains( + &mut self, + base_address: vir_low::Expression, + element_size: vir_low::Expression, + range_length: vir_low::Expression, + checked_address: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult { + // let start_index = self.create_domain_func_app( + // ADDRESS_DOMAIN_NAME, + // "get_index$", + // vec![base_address.clone(), element_size.clone()], + // vir_low::Type::Int, + // position, + // )?; + // let end_index = vir_low::Expression::add(start_index.clone(), range_length); + let start_index = 0.into(); + let end_index = range_length; + self.address_range_contains( + base_address, + start_index, + end_index, + element_size, + checked_address, + position, + ) } fn root_address( &mut self, local: &vir_mid::expression::Local, + old_label: &Option, ) -> SpannedEncodingResult { - let name = format!("{}$address", local.variable.name); - let ty = self.address_type()?; - let address_variable = self.create_variable(name, ty)?; + let address_variable = + self.address_variable_version_at_label(&local.variable.name, old_label)?; Ok(vir_low::Expression::local(address_variable, local.position)) } fn extract_root_address( &mut self, place: &vir_mid::Expression, ) -> SpannedEncodingResult { - assert!(place.is_place()); - let result = match place { - vir_mid::Expression::Local(local) => self.root_address(local)?, - vir_mid::Expression::LabelledOld(_) => unimplemented!(), - vir_mid::Expression::Deref(deref) => { - let base_snapshot = deref.base.to_procedure_snapshot(self)?; - self.reference_address(deref.base.get_type(), base_snapshot, Default::default())? - } - _ => self.extract_root_address(place.get_parent_ref().unwrap())?, - }; - Ok(result) + unimplemented!("outdated code: {place}"); + // assert!(place.is_place()); + // let result = match place { + // vir_mid::Expression::Local(local) => self.root_address(local, &None)?, + // vir_mid::Expression::LabelledOld(_) => unimplemented!(), + // vir_mid::Expression::Deref(deref) => { + // // FIXME: Code duplication with PlaceAddressEncoder + // let mut place_encoder = + // PlaceToSnapshot::for_address(PredicateKind::Owned); + // let base_snapshot = + // place_encoder.expression_to_snapshot(self, &deref.base, false)?; + // // let base_snapshot = deref.base.to_procedure_snapshot(self)?; + // let ty = deref.base.get_type(); + // if ty.is_reference() { + // self.reference_address(ty, base_snapshot, place.position())? + // } else { + // self.pointer_address(ty, base_snapshot, place.position())? + // } + // } + // _ => self.extract_root_address(place.get_parent_ref().unwrap())?, + // }; + // Ok(result) } - /// Emits code that represents the place's address. fn encode_expression_as_place_address( &mut self, place: &vir_mid::Expression, ) -> SpannedEncodingResult { - let mut encoder = PlaceAddressEncoder {}; + let mut encoder = PlaceAddressEncoder::new_in_procedure(); encoder.encode_expression(place, self) } + // fn encode_expression_as_place_address_in_predicate( + // &mut self, + // place: &vir_mid::Expression, + // self_address: vir_low::Expression, + // ) -> SpannedEncodingResult { + // let mut encoder = PlaceAddressEncoder::new_in_predicate(self_address); + // encoder.encode_expression(place, self) + // } fn encode_field_address( &mut self, base_type: &vir_mid::Type, @@ -91,7 +580,13 @@ impl<'p, 'v: 'p, 'tcx: 'v> AddressesInterface for Lowerer<'p, 'v, 'tcx> { base_address: vir_low::Expression, position: vir_mid::Position, ) -> SpannedEncodingResult { - self.encode_field_access_function_app("Address", base_address, base_type, field, position) + self.encode_field_access_function_app( + ADDRESS_DOMAIN_NAME, + base_address, + base_type, + field, + position, + ) } fn encode_enum_variant_address( &mut self, @@ -101,11 +596,27 @@ impl<'p, 'v: 'p, 'tcx: 'v> AddressesInterface for Lowerer<'p, 'v, 'tcx> { position: vir_mid::Position, ) -> SpannedEncodingResult { self.encode_variant_access_function_app( - "Address", + ADDRESS_DOMAIN_NAME, base_address, base_type, variant, position, ) } + fn encode_index_address( + &mut self, + base_type: &vir_mid::Type, + base_address: vir_low::Expression, + index: vir_low::Expression, + position: vir_mid::Position, + ) -> SpannedEncodingResult { + // FIXME: This implementation is most likely wrong. Test it properly. + let vir_mid::Type::Pointer(pointer_type) = base_type else { + unreachable!() + }; + let size = self + .encode_type_size_expression2(&pointer_type.target_type, &*pointer_type.target_type)?; + let start_address = self.pointer_address(base_type, base_address, position)?; + self.address_offset(size, start_address, index, position) + } } diff --git a/prusti-viper/src/encoder/middle/core_proof/addresses/mod.rs b/prusti-viper/src/encoder/middle/core_proof/addresses/mod.rs index 472846c6772..8285a383997 100644 --- a/prusti-viper/src/encoder/middle/core_proof/addresses/mod.rs +++ b/prusti-viper/src/encoder/middle/core_proof/addresses/mod.rs @@ -1,4 +1,5 @@ mod encoder; mod interface; +mod state; -pub(super) use self::interface::AddressesInterface; +pub(super) use self::{interface::AddressesInterface, state::AddressState}; diff --git a/prusti-viper/src/encoder/middle/core_proof/addresses/state.rs b/prusti-viper/src/encoder/middle/core_proof/addresses/state.rs new file mode 100644 index 00000000000..d58565e9f4b --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/addresses/state.rs @@ -0,0 +1,6 @@ +#[derive(Default)] +pub(in super::super) struct AddressState { + pub(super) are_address_axioms_encoded: bool, + // pub(super) is_address_range_contains_axiom_encoded: bool, + pub(super) fresh_allocation_counter: usize, +} diff --git a/prusti-viper/src/encoder/middle/core_proof/adts/interface.rs b/prusti-viper/src/encoder/middle/core_proof/adts/interface.rs index 3b64a1054dc..de658b5eee9 100644 --- a/prusti-viper/src/encoder/middle/core_proof/adts/interface.rs +++ b/prusti-viper/src/encoder/middle/core_proof/adts/interface.rs @@ -1,7 +1,11 @@ use crate::encoder::{ errors::SpannedEncodingResult, - middle::core_proof::lowerer::{DomainsLowererInterface, Lowerer}, + middle::core_proof::{ + function_gas::FunctionGasInterface, + lowerer::{DomainsLowererInterface, Lowerer}, + }, }; +use prusti_common::config; use rustc_hash::FxHashSet; use std::borrow::Cow; use vir_crate::{ @@ -35,6 +39,11 @@ pub(in super::super) trait AdtsInterface { domain_name: &str, variant_name: &str, ) -> SpannedEncodingResult; + fn adt_snapshot_equality_variant_name( + &mut self, + domain_name: &str, + variant_name: &str, + ) -> SpannedEncodingResult; fn adt_destructor_main_name( &mut self, domain_name: &str, @@ -87,6 +96,14 @@ pub(in super::super) trait AdtsInterface { parameter_type: vir_low::Type, argument: vir_low::Expression, ) -> SpannedEncodingResult; + fn adt_snapshot_equality_variant_call( + &mut self, + domain_name: &str, + variant_name: &str, + left: vir_low::Expression, + right: vir_low::Expression, + gas: vir_low::Expression, + ) -> SpannedEncodingResult; // Registration. @@ -123,6 +140,7 @@ pub(in super::super) trait AdtsInterface { &mut self, domain_name: &str, variant_name: &str, + // operation: Option<(vir_mid::BinaryOpKind, vir_mid::Type)>, use_main_constructor_destructors: bool, parameters: Vec, generate_injectivity_axioms: bool, @@ -144,6 +162,13 @@ impl<'p, 'v: 'p, 'tcx: 'v> AdtsInterface for Lowerer<'p, 'v, 'tcx> { ) -> SpannedEncodingResult { Ok(format!("constructor${domain_name}${variant_name}")) } + fn adt_snapshot_equality_variant_name( + &mut self, + domain_name: &str, + variant_name: &str, + ) -> SpannedEncodingResult { + Ok(format!("snapshot_equality${domain_name}${variant_name}")) + } fn adt_destructor_variant_name( &mut self, domain_name: &str, @@ -182,6 +207,29 @@ impl<'p, 'v: 'p, 'tcx: 'v> AdtsInterface for Lowerer<'p, 'v, 'tcx> { parameter_type, )) } + fn adt_snapshot_equality_variant_call( + &mut self, + domain_name: &str, + variant_name: &str, + left: vir_low::Expression, + right: vir_low::Expression, + gas: vir_low::Expression, + ) -> SpannedEncodingResult { + let name = self.adt_snapshot_equality_variant_name(domain_name, variant_name)?; + // FIXME: This is a hack: this function call should be in + // SnapshotAdtsInterface::snapshot_equality_call. However, we cannot do + // that because the actual call is done only by the symbolic execution + // (only it has enough information to emit the comparison). + self.snapshots_state + .snapshot_domains_info + .register_snapshot_equality(domain_name, &name)?; + Ok(vir_low::Expression::domain_function_call( + domain_name.to_string(), + name, + vec![left, right, gas], + vir_low::Type::Bool, + )) + } fn adt_register_main_constructor( &mut self, domain_name: &str, @@ -203,6 +251,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> AdtsInterface for Lowerer<'p, 'v, 'tcx> { self.adt_register_variant_constructor( domain_name, "", + // None, false, parameters, generate_injectivity_axioms, @@ -213,6 +262,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> AdtsInterface for Lowerer<'p, 'v, 'tcx> { &mut self, domain_name: &str, variant_name: &str, + // operation: Option<(vir_mid::BinaryOpKind, vir_mid::Type)>, use_main_constructor_destructors: bool, parameters: Vec, generate_injectivity_axioms: bool, @@ -264,21 +314,32 @@ impl<'p, 'v: 'p, 'tcx: 'v> AdtsInterface for Lowerer<'p, 'v, 'tcx> { )?; } - // Injectivity axioms. - if parameters.is_empty() { - // No need to generate injectivity axioms if the constructor has no parameters. - return Ok(()); - } + // Snapshot equality. This function is used to trigger extensionality + // axioms for types that have Viper sequences as their constituents. + let snapshot_equality_name = + self.adt_snapshot_equality_variant_name(domain_name, variant_name)?; + let left = vir_low::VariableDecl::new("left", ty.clone()); + let right = vir_low::VariableDecl::new("right", ty.clone()); + let gas = self.function_gas_parameter()?; + self.declare_domain_function( + domain_name, + Cow::Owned(snapshot_equality_name), + false, + Cow::Owned(vec![left, right, gas]), + Cow::Owned(vir_low::Type::Bool), + )?; + // Injectivity axioms. if generate_injectivity_axioms { // We do not generate injectivity axioms for alternative // constructors (that would be unsound). use vir_low::macros::*; // Bottom-up injectivity axiom. - { + if !parameters.is_empty() { + // We need something to quantify over, so parameters cannot be empty. let mut triggers = Vec::new(); - let mut conjuncts = Vec::new(); + // let mut conjuncts = Vec::new(); let constructor_call = self.adt_constructor_variant_call( domain_name, variant_name, @@ -287,6 +348,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> AdtsInterface for Lowerer<'p, 'v, 'tcx> { .map(|argument| argument.clone().into()) .collect(), )?; + triggers.push(vir_low::Trigger::new(vec![constructor_call.clone()])); for parameter in ¶meters { let destructor_call = self.adt_destructor_variant_call( domain_name, @@ -295,25 +357,37 @@ impl<'p, 'v: 'p, 'tcx: 'v> AdtsInterface for Lowerer<'p, 'v, 'tcx> { parameter.ty.clone(), constructor_call.clone(), )?; - triggers.push(vir_low::Trigger::new(vec![constructor_call.clone()])); - conjuncts.push(expr! { [destructor_call] == parameter }); + // conjuncts.push(expr! { [destructor_call] == parameter }); + let axiom = vir_low::DomainRewriteRuleDecl { + comment: None, + name: format!( + "{constructor_name}$bottom_up_injectivity_axiom${}", + parameter.name + ), + egg_only: false, + variables: parameters.clone(), + triggers: Some(triggers.clone()), + source: destructor_call, + target: parameter.clone().into(), + }; + self.declare_rewrite_rule(domain_name, axiom)?; } - let body = vir_low::Expression::forall( - parameters.clone(), - triggers, - conjuncts.into_iter().conjoin(), - ); - let axiom = vir_low::DomainAxiomDecl { - comment: None, - name: format!("{constructor_name}$bottom_up_injectivity_axiom"), - body, - }; - self.declare_axiom(domain_name, axiom)?; + // let body = vir_low::Expression::forall( + // parameters.clone(), + // triggers, + // conjuncts.into_iter().conjoin(), + // ); + // let axiom = vir_low::DomainAxiomDecl { + // comment: None, + // name: format!("{constructor_name}$bottom_up_injectivity_axiom"), + // body, + // }; + // self.declare_axiom(domain_name, axiom)?; } // Top-down injectivity axiom. - var_decls! { value: {ty} }; + var_decls! { value: {ty.clone()} }; let (trigger_guard, guard) = if let Some(guard_constructor) = top_down_injectivity_guard { let (trigger, guard) = guard_constructor(domain_name, &value)?; @@ -344,12 +418,26 @@ impl<'p, 'v: 'p, 'tcx: 'v> AdtsInterface for Lowerer<'p, 'v, 'tcx> { } let constructor_call = self.adt_constructor_variant_call(domain_name, variant_name, arguments)?; + if parameters.is_empty() { + if let Some(guard) = &trigger_guard { + triggers.push(vir_low::Trigger::new(vec![guard.clone()])); + } else { + unimplemented!("figure out what triggers to choose!"); + } + } + if !config::use_snapshot_parameters_in_predicates() && !parameters.is_empty() { + triggers.push(vir_low::Trigger::new(vec![constructor_call.clone()])); + } let equality = expr! { value == [constructor_call] }; let forall_body = if let Some(guard) = guard { expr! { [guard] ==> [equality] } } else { equality }; + assert!( + !triggers.is_empty(), + "empty triggers for {constructor_name}" + ); let axiom = vir_low::DomainAxiomDecl { comment: None, name: format!("{constructor_name}$top_down_injectivity_axiom"), @@ -363,6 +451,76 @@ impl<'p, 'v: 'p, 'tcx: 'v> AdtsInterface for Lowerer<'p, 'v, 'tcx> { ); } + // Snapshot equality axioms. + { + use vir_low::macros::*; + var_decls! { left: {ty.clone()}, right: {ty} }; + let gas = self.function_gas_parameter()?; + let gas_succ = self.add_function_gas_level(gas.clone().into())?; + let snapshot_equality_call_gas = self.adt_snapshot_equality_variant_call( + domain_name, + variant_name, + left.clone().into(), + right.clone().into(), + gas.clone().into(), + )?; + let snapshot_equality_call_gas_succ = self.adt_snapshot_equality_variant_call( + domain_name, + variant_name, + left.clone().into(), + right.clone().into(), + gas_succ, + )?; + let snapshot_equality_def = if parameters.is_empty() { + true.into() + } else { + let mut parameter_equalities = Vec::new(); + for parameter in ¶meters { + let destructor_call_left = self.adt_destructor_variant_call( + domain_name, + destructor_variant_name, + ¶meter.name, + parameter.ty.clone(), + left.clone().into(), + )?; + let destructor_call_right = self.adt_destructor_variant_call( + domain_name, + destructor_variant_name, + ¶meter.name, + parameter.ty.clone(), + right.clone().into(), + )?; + parameter_equalities.push(expr! { + [destructor_call_left] == [destructor_call_right] + }); + } + expr! { + ([snapshot_equality_call_gas.clone()] + == + [parameter_equalities.into_iter().conjoin()]) + } + }; + let axiom = vir_low::DomainAxiomDecl { + comment: Some( + "Used to trigger extensionality for snapshots that contain Viper sequences." + .to_string(), + ), + name: format!("{constructor_name}$snapshot_equality_axiom"), + body: vir_low::Expression::forall( + vec![left.clone(), right.clone(), gas], + vec![vir_low::Trigger::new(vec![ + snapshot_equality_call_gas_succ.clone() + ])], + expr![ + ([snapshot_equality_call_gas.clone()] == [snapshot_equality_call_gas_succ]) + && (([snapshot_equality_call_gas] == (left == right)) + && [snapshot_equality_def]) + ], + ), + }; + self.declare_axiom(domain_name, axiom)?; + } + Ok(()) } } diff --git a/prusti-viper/src/encoder/middle/core_proof/arithmetic_wrappers/interface.rs b/prusti-viper/src/encoder/middle/core_proof/arithmetic_wrappers/interface.rs new file mode 100644 index 00000000000..153078a2344 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/arithmetic_wrappers/interface.rs @@ -0,0 +1,201 @@ +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::lowerer::{DomainsLowererInterface, Lowerer}, +}; +use prusti_common::config; +use vir_crate::{ + common::expression::{BinaryOperationHelpers, QuantifierHelpers}, + low::{self as vir_low}, +}; + +const DOMAIN_NAME: &str = "ArithmeticWrappers"; +const ADD_FUNC_NAME: &str = "add_wrapper$"; +const MUL_FUNC_NAME: &str = "mul_wrapper$"; + +pub(in super::super) trait ArithmeticWrappersInterface { + fn int_add_call( + &mut self, + left: vir_low::Expression, + right: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult; + fn int_mul_call( + &mut self, + left: vir_low::Expression, + right: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult; +} + +impl<'p, 'v: 'p, 'tcx: 'v> ArithmeticWrappersInterface for Lowerer<'p, 'v, 'tcx> { + fn int_add_call( + &mut self, + left: vir_low::Expression, + right: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult { + if !self.arithmetic_wrapper_state.is_add_encoded { + self.arithmetic_wrapper_state.is_add_encoded = true; + use vir_low::macros::*; + var_decls!(left: Int, right: Int); + let call = self.create_domain_func_app( + DOMAIN_NAME, + ADD_FUNC_NAME, + vec![left.clone().into(), right.clone().into()], + vir_low::Type::Int, + Default::default(), + )?; + let body = vir_low::Expression::forall( + vec![left.clone(), right.clone()], + vec![vir_low::Trigger::new(vec![call.clone()])], + vir_low::Expression::equals( + call, + vir_low::Expression::add(left.into(), right.into()), + ), + ); + let axiom = vir_low::DomainAxiomDecl::new(None, "add_wrapper$definition", body); + self.declare_axiom(DOMAIN_NAME, axiom)?; + } + self.create_domain_func_app( + DOMAIN_NAME, + ADD_FUNC_NAME, + vec![left, right], + vir_low::Type::Int, + position, + ) + } + fn int_mul_call( + &mut self, + left: vir_low::Expression, + right: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult { + if !self.arithmetic_wrapper_state.is_mul_encoded { + self.arithmetic_wrapper_state.is_mul_encoded = true; + use vir_low::macros::*; + var_decls!(left: Int, right: Int); + let call = self.create_domain_func_app( + DOMAIN_NAME, + MUL_FUNC_NAME, + vec![left.clone().into(), right.clone().into()], + vir_low::Type::Int, + Default::default(), + )?; + { + let call_commutative = self.create_domain_func_app( + DOMAIN_NAME, + MUL_FUNC_NAME, + vec![right.clone().into(), left.clone().into()], + vir_low::Type::Int, + Default::default(), + )?; + let body = vir_low::Expression::forall( + vec![left.clone(), right.clone()], + vec![vir_low::Trigger::new(vec![call.clone()])], + vir_low::Expression::equals(call.clone(), call_commutative), + ); + let axiom = vir_low::DomainAxiomDecl::new(None, "mul_wrapper$commutativity", body); + self.declare_axiom(DOMAIN_NAME, axiom)?; + } + { + var_decls!(value: Int); + let call_zero_left = self.create_domain_func_app( + DOMAIN_NAME, + MUL_FUNC_NAME, + vec![0.into(), value.clone().into()], + vir_low::Type::Int, + Default::default(), + )?; + let call_zero_right = self.create_domain_func_app( + DOMAIN_NAME, + MUL_FUNC_NAME, + vec![value.clone().into(), 0.into()], + vir_low::Type::Int, + Default::default(), + )?; + let body = vir_low::Expression::forall( + vec![value], + vec![ + vir_low::Trigger::new(vec![call_zero_left.clone()]), + vir_low::Trigger::new(vec![call_zero_right.clone()]), + ], + vir_low::Expression::and( + vir_low::Expression::equals(call_zero_left, 0.into()), + vir_low::Expression::equals(call_zero_right, 0.into()), + ), + ); + let axiom = vir_low::DomainAxiomDecl::new(None, "mul_wrapper$zero", body); + self.declare_axiom(DOMAIN_NAME, axiom)?; + } + { + var_decls!(common: Int, a: Int, b: Int); + let call_first = self.create_domain_func_app( + DOMAIN_NAME, + MUL_FUNC_NAME, + vec![common.clone().into(), a.clone().into()], + vir_low::Type::Int, + Default::default(), + )?; + let call_second = self.create_domain_func_app( + DOMAIN_NAME, + MUL_FUNC_NAME, + vec![common.clone().into(), b.clone().into()], + vir_low::Type::Int, + Default::default(), + )?; + let body = vir_low::Expression::forall( + vec![common.clone(), a.clone(), b.clone()], + vec![vir_low::Trigger::new(vec![ + call_first.clone(), + call_second.clone(), + ])], + expr! { + ([0.into()] < common) ==> ((a <= b) ==> ([call_first] <= [call_second])) + }, + ); + let axiom = + vir_low::DomainAxiomDecl::new(None, "mul_wrapper$non_negative_range", body); + self.declare_axiom(DOMAIN_NAME, axiom)?; + } + { + var_decls!(a: Int, b: Int); + let call = self.create_domain_func_app( + DOMAIN_NAME, + MUL_FUNC_NAME, + vec![a.clone().into(), b.clone().into()], + vir_low::Type::Int, + Default::default(), + )?; + let body = vir_low::Expression::forall( + vec![a.clone(), b.clone()], + vec![vir_low::Trigger::new(vec![call.clone()])], + expr! { + (([0.into()] < a) && ([0.into()] < b)) ==> (([a.into()] < [call.clone()]) && ([b.into()] < [call])) + }, + ); + let axiom = + vir_low::DomainAxiomDecl::new(None, "mul_wrapper$positive_increases", body); + self.declare_axiom(DOMAIN_NAME, axiom)?; + } + if config::define_multiply_int() { + let body = vir_low::Expression::forall( + vec![left.clone(), right.clone()], + vec![vir_low::Trigger::new(vec![call.clone()])], + vir_low::Expression::equals( + call, + vir_low::Expression::multiply(left.into(), right.into()), + ), + ); + let axiom = vir_low::DomainAxiomDecl::new(None, "mul_wrapper$definition", body); + self.declare_axiom(DOMAIN_NAME, axiom)?; + } + } + self.create_domain_func_app( + DOMAIN_NAME, + MUL_FUNC_NAME, + vec![left, right], + vir_low::Type::Int, + position, + ) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/arithmetic_wrappers/mod.rs b/prusti-viper/src/encoder/middle/core_proof/arithmetic_wrappers/mod.rs new file mode 100644 index 00000000000..c5597a5f943 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/arithmetic_wrappers/mod.rs @@ -0,0 +1,4 @@ +mod interface; +mod state; + +pub(super) use self::{interface::ArithmeticWrappersInterface, state::ArithmeticWrappersState}; diff --git a/prusti-viper/src/encoder/middle/core_proof/arithmetic_wrappers/state.rs b/prusti-viper/src/encoder/middle/core_proof/arithmetic_wrappers/state.rs new file mode 100644 index 00000000000..553319c0ced --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/arithmetic_wrappers/state.rs @@ -0,0 +1,5 @@ +#[derive(Default)] +pub(in super::super) struct ArithmeticWrappersState { + pub(super) is_add_encoded: bool, + pub(super) is_mul_encoded: bool, +} diff --git a/prusti-viper/src/encoder/middle/core_proof/block_markers/interface.rs b/prusti-viper/src/encoder/middle/core_proof/block_markers/interface.rs index a36ee77e3dd..6c9d1a700f8 100644 --- a/prusti-viper/src/encoder/middle/core_proof/block_markers/interface.rs +++ b/prusti-viper/src/encoder/middle/core_proof/block_markers/interface.rs @@ -1,6 +1,6 @@ use crate::encoder::{ errors::SpannedEncodingResult, - middle::core_proof::lowerer::{Lowerer, VariablesLowererInterface}, + middle::core_proof::{lowerer::Lowerer, snapshots::SnapshotVariablesInterface}, }; use vir_crate::{ common::expression::{ExpressionIterator, UnaryOperationHelpers}, @@ -12,7 +12,7 @@ pub(in super::super) trait BlockMarkersInterface { fn create_block_marker( &mut self, label: &vir_mid::BasicBlockId, - ) -> SpannedEncodingResult; + ) -> SpannedEncodingResult; fn lower_block_marker_condition( &mut self, condition: vir_mid::BlockMarkerCondition, @@ -23,8 +23,12 @@ impl<'p, 'v: 'p, 'tcx: 'v> BlockMarkersInterface for Lowerer<'p, 'v, 'tcx> { fn create_block_marker( &mut self, label: &vir_mid::BasicBlockId, - ) -> SpannedEncodingResult { - self.create_variable(format!("{label}$marker"), vir_low::Type::Bool) + ) -> SpannedEncodingResult { + // self.create_variable(format!("{label}$marker"), vir_low::Type::Bool) + Ok(vir_mid::VariableDecl::new( + format!("{label}$marker"), + vir_mid::Type::MBool, + )) } fn lower_block_marker_condition( &mut self, @@ -33,6 +37,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> BlockMarkersInterface for Lowerer<'p, 'v, 'tcx> { let mut conjuncts: Vec = Vec::new(); for element in condition.elements { let marker = self.create_block_marker(&element.basic_block_id)?; + let marker = self.current_snapshot_variable_version(&marker)?; let condition = if element.visited { marker.into() } else { diff --git a/prusti-viper/src/encoder/middle/core_proof/builtin_methods/assertion_encoder.rs b/prusti-viper/src/encoder/middle/core_proof/builtin_methods/assertion_encoder.rs new file mode 100644 index 00000000000..2db1310e849 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/builtin_methods/assertion_encoder.rs @@ -0,0 +1,462 @@ +use crate::encoder::{ + errors::SpannedEncodingResult, + high::types::HighTypeEncoderInterface, + middle::core_proof::{ + builtin_methods::CallContext, + lowerer::Lowerer, + places::PlacesInterface, + pointers::PointersInterface, + predicates::{PredicatesMemoryBlockInterface, PredicatesOwnedInterface}, + references::ReferencesInterface, + snapshots::{IntoSnapshotLowerer, SnapshotValuesInterface, SnapshotVariablesInterface}, + }, +}; + +use std::collections::BTreeMap; +use vir_crate::{ + common::{expression::BinaryOperationHelpers, position::Positioned}, + low::{self as vir_low}, + middle::{self as vir_mid, operations::ty::Typed}, +}; + +// TODO: Delete this file. +pub(in super::super::super) struct AssertionEncoder<'a> { + /// A map from field names to arguments that are being assigned to these + /// fields. + field_arguments: BTreeMap, + heap: &'a Option, + result_value: Option, + replace_self_with_result_value: bool, + in_function: bool, +} + +impl<'a> AssertionEncoder<'a> { + pub(in super::super::super) fn new( + decl: &vir_mid::type_decl::Struct, + operand_values: Vec, + heap: &'a Option, + ) -> Self { + let mut field_arguments = BTreeMap::default(); + // assert_eq!(decl.fields.len(), operand_values.len()); FIXME: Split + // into two assertion encoders: one that uses result value and one that + // usess field_arguments. + for (field, operand) in decl.fields.iter().zip(operand_values.into_iter()) { + assert!(field_arguments + .insert(field.name.clone(), operand) + .is_none()); + } + Self { + field_arguments, + heap, + result_value: None, + replace_self_with_result_value: false, + in_function: false, + } + } + + // FIXME: Code duplication. + fn pointer_deref_into_address<'p, 'v, 'tcx>( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + place: &vir_mid::Expression, + ) -> SpannedEncodingResult { + if let Some(deref_place) = place.get_last_dereferenced_pointer() { + let base_snapshot = self.expression_to_snapshot(lowerer, deref_place, true)?; + let ty = deref_place.get_type(); + lowerer.pointer_address(ty, base_snapshot, place.position()) + // match deref_place { + // vir_mid::Expression::Deref(deref) => { + // let base_snapshot = self.expression_to_snapshot(lowerer, &deref.base, true)?; + // let ty = deref.base.get_type(); + // assert!(ty.is_pointer()); + // lowerer.pointer_address(ty, base_snapshot, place.position()) + // } + // _ => unreachable!(), + // } + } else { + unreachable!() + } + // PlaceExpressionDomainEncoder::encode_expression(self, place, lowerer) + } + + pub(super) fn address_in_heap<'p, 'v, 'tcx>( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + pointer_place: &vir_mid::Expression, + ) -> SpannedEncodingResult { + let pointer = self.expression_to_snapshot(lowerer, pointer_place, true)?; + let address = + lowerer.pointer_address(pointer_place.get_type(), pointer, pointer_place.position())?; + let in_heap = vir_low::Expression::container_op_no_pos( + vir_low::ContainerOpKind::MapContains, + self.heap.as_ref().unwrap().ty.clone(), + vec![self.heap.clone().unwrap().into(), address], + ); + Ok(in_heap) + } + + // pub(in super::super::super) fn set_in_function(&mut self) { + // assert!(!self.in_function); + // self.in_function = true; + // } + + pub(in super::super::super) fn set_result_value( + &mut self, + result_value: vir_low::VariableDecl, + ) { + assert!(self.result_value.is_none()); + self.result_value = Some(result_value); + } + + pub(super) fn unset_result_value(&mut self) { + assert!(self.result_value.is_some()); + self.result_value = None; + } + + fn acc_predicate_to_snapshot_precondition<'p, 'v, 'tcx>( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + acc_predicate: &vir_mid::AccPredicate, + expect_math_bool: bool, + ) -> SpannedEncodingResult { + assert!(expect_math_bool); + let expression = match &*acc_predicate.predicate { + vir_mid::Predicate::OwnedNonAliased(_predicate) => { + unimplemented!("Outdated code? TODO: Remove?"); + // let ty = predicate.place.get_type(); + // let place = lowerer.encode_expression_as_place(&predicate.place)?; + // // eprintln!("predicate: {}", predicate); + // let root_address = self.pointer_deref_into_address(lowerer, &predicate.place)?; + // // eprintln!("root_address2: {}", root_address); + // // let deref = predicate.place.clone().unwrap_deref(); + // // let base_snapshot = + // // self.expression_to_snapshot(lowerer, &deref.base, expect_math_bool)?; + // // let snapshot = lowerer.pointer_target_snapshot_in_heap( + // // deref.base.get_type(), + // // self.heap.clone(), + // // base_snapshot, + // // deref.position, + // // )?; + + // let snapshot = if config::use_snapshot_parameters_in_predicates() { + // self.expression_to_snapshot(lowerer, &predicate.place, expect_math_bool)? + // } else { + // // FIXME: cleanup code + // if lowerer.use_heap_variable()? { + // let deref = predicate.place.clone().unwrap_deref(); + // let base_snapshot = + // self.expression_to_snapshot(lowerer, &deref.base, expect_math_bool)?; + + // lowerer.pointer_target_snapshot_in_heap( + // deref.base.get_type(), + // self.heap.clone().unwrap(), + // base_snapshot, + // deref.position, + // )? + // } else { + // true.into() + // } + // }; + + // if lowerer.use_heap_variable()? { + // // let snapshot = self.expression_to_snapshot(lowerer, &predicate.place, expect_math_bool)?; + // lowerer.owned_non_aliased( + // CallContext::BuiltinMethod, + // ty, + // ty, + // place, + // root_address, + // snapshot, + // None, + // )? + // } else { + // lowerer.owned_non_aliased( + // CallContext::BuiltinMethod, + // ty, + // ty, + // place, + // root_address, + // snapshot, + // None, + // )? + // } + } + vir_mid::Predicate::MemoryBlockHeap(predicate) => { + let place = lowerer.encode_expression_as_place(&predicate.address)?; + let root_address = self.pointer_deref_into_address(lowerer, &predicate.address)?; + use vir_low::macros::*; + let compute_address = ty!(Address); + let address = expr! { + ComputeAddress::compute_address([place], [root_address]) + }; + let size = + self.expression_to_snapshot(lowerer, &predicate.size, expect_math_bool)?; + lowerer.encode_memory_block_acc(address, size, acc_predicate.position)? + } + vir_mid::Predicate::MemoryBlockHeapDrop(predicate) => { + // FIXME: Why this does not match the encoding of MemoryBlockHeap? + let address = self.pointer_deref_into_address(lowerer, &predicate.address)?; + let size = + self.expression_to_snapshot(lowerer, &predicate.size, expect_math_bool)?; + lowerer.encode_memory_block_heap_drop_acc(address, size, acc_predicate.position)? + } + _ => unimplemented!("{acc_predicate}"), + }; + Ok(expression) + } + + fn acc_predicate_to_snapshot_postcondition<'p, 'v, 'tcx>( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + acc_predicate: &vir_mid::AccPredicate, + expect_math_bool: bool, + ) -> SpannedEncodingResult { + assert!(expect_math_bool); + let expression = match &*acc_predicate.predicate { + vir_mid::Predicate::OwnedNonAliased(predicate) => { + let position = predicate.position; + let ty = predicate.place.get_type(); + let place = lowerer.encode_expression_as_place(&predicate.place)?; + let old_value = self.replace_self_with_result_value; + self.replace_self_with_result_value = true; + let root_address_self = + self.pointer_deref_into_address(lowerer, &predicate.place)?; + self.replace_self_with_result_value = old_value; + let snap_call_self = lowerer.owned_non_aliased_snap( + CallContext::BuiltinMethod, + ty, + ty, + place.clone(), + root_address_self, + position, + )?; + if self.in_function { + let snap_call_result_value = + self.expression_to_snapshot(lowerer, &predicate.place, expect_math_bool)?; + vir_low::Expression::equals(snap_call_result_value, snap_call_self) + } else { + let root_address_parameter = + self.pointer_deref_into_address(lowerer, &predicate.place)?; + let snap_call_parameter = lowerer.owned_non_aliased_snap( + CallContext::BuiltinMethod, + ty, + ty, + place, + root_address_parameter, + position, + )?; + vir_low::Expression::equals( + snap_call_parameter, + vir_low::Expression::labelled_old(None, snap_call_self, position), + ) + } + } + vir_mid::Predicate::MemoryBlockHeap(_) | vir_mid::Predicate::MemoryBlockHeapDrop(_) => { + true.into() + } + _ => unimplemented!("{acc_predicate}"), + }; + Ok(expression) + } +} + +// impl<'a> PlaceExpressionDomainEncoder for AssertionEncoder<'a> { +// fn domain_name(&mut self, lowerer: &mut Lowerer) -> &str { +// lowerer.address_domain() +// } + +// fn encode_local( +// &mut self, +// local: &vir_mid::expression::Local, +// lowerer: &mut Lowerer, +// ) -> SpannedEncodingResult { +// lowerer.root_address(local, &None) +// } + +// fn encode_deref( +// &mut self, +// deref: &vir_mid::expression::Deref, +// lowerer: &mut Lowerer, +// _arg: vir_low::Expression, +// ) -> SpannedEncodingResult { +// let base_snapshot = self.expression_to_snapshot(lowerer, &deref.base, true)?; +// let ty = deref.base.get_type(); +// let result = if ty.is_reference() { +// lowerer.reference_address(ty, base_snapshot, deref.position)? +// } else { +// lowerer.pointer_address(ty, base_snapshot, deref.position)? +// }; +// Ok(result) +// } + +// fn encode_labelled_old( +// &mut self, +// _expression: &vir_mid::expression::LabelledOld, +// _lowerer: &mut Lowerer, +// ) -> SpannedEncodingResult { +// todo!() +// } + +// fn encode_array_index_axioms( +// &mut self, +// _base_type: &vir_mid::Type, +// _lowerer: &mut Lowerer, +// ) -> SpannedEncodingResult<()> { +// todo!() +// } +// } + +impl<'a, 'p, 'v: 'p, 'tcx: 'v> IntoSnapshotLowerer<'p, 'v, 'tcx> for AssertionEncoder<'a> { + fn variable_to_snapshot( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + variable: &vir_mid::VariableDecl, + ) -> SpannedEncodingResult { + if self.replace_self_with_result_value || self.in_function { + assert!(variable.is_self_variable()); + Ok(self.result_value.clone().unwrap()) + } else { + Ok(vir_low::VariableDecl { + name: variable.name.clone(), + ty: self.type_to_snapshot(lowerer, &variable.ty)?, + }) + } + } + + fn labelled_old_to_snapshot( + &mut self, + _lowerer: &mut Lowerer<'p, 'v, 'tcx>, + _old: &vir_mid::LabelledOld, + _expect_math_bool: bool, + ) -> SpannedEncodingResult { + todo!() + } + + fn func_app_to_snapshot( + &mut self, + _lowerer: &mut Lowerer<'p, 'v, 'tcx>, + _app: &vir_mid::FuncApp, + _expect_math_bool: bool, + ) -> SpannedEncodingResult { + todo!() + } + + fn acc_predicate_to_snapshot( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + acc_predicate: &vir_mid::AccPredicate, + expect_math_bool: bool, + ) -> SpannedEncodingResult { + let expression = if self.result_value.is_some() { + self.acc_predicate_to_snapshot_postcondition(lowerer, acc_predicate, expect_math_bool)? + } else { + self.acc_predicate_to_snapshot_precondition(lowerer, acc_predicate, expect_math_bool)? + }; + Ok(expression) + } + + fn field_to_snapshot( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + field: &vir_mid::Field, + expect_math_bool: bool, + ) -> SpannedEncodingResult { + match &*field.base { + vir_mid::Expression::Local(local) + if !self.replace_self_with_result_value && !self.in_function => + { + assert!(local.variable.is_self_variable()); + Ok(self.field_arguments[&field.field.name].clone()) + // if self.replace_self_with_result_value { + // Ok(self.result_value.clone().unwrap().into()) + // } else + // {Ok(self.field_arguments[&field.field.name].clone())} + } + _ => { + // FIXME: Code duplication because Rust does not have syntax for calling + // overriden methods. + let base_snapshot = + self.expression_to_snapshot(lowerer, &field.base, expect_math_bool)?; + let result = if field.field.is_discriminant() { + let ty = field.base.get_type(); + // FIXME: Create a method for obtainging the discriminant type. + let type_decl = lowerer.encoder.get_type_decl_mid(ty)?; + let enum_decl = type_decl.unwrap_enum(); + let discriminant_call = + lowerer.obtain_enum_discriminant(base_snapshot, ty, field.position)?; + lowerer.construct_constant_snapshot( + &enum_decl.discriminant_type, + discriminant_call, + field.position, + )? + } else { + lowerer.obtain_struct_field_snapshot( + field.base.get_type(), + &field.field, + base_snapshot, + field.position, + )? + }; + self.ensure_bool_expression(lowerer, field.get_type(), result, expect_math_bool) + } + } + } + + fn deref_to_snapshot( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + deref: &vir_mid::Deref, + expect_math_bool: bool, + ) -> SpannedEncodingResult { + let base_snapshot = self.expression_to_snapshot(lowerer, &deref.base, expect_math_bool)?; + let ty = deref.base.get_type(); + let result = if ty.is_reference() { + lowerer.reference_target_current_snapshot(ty, base_snapshot, Default::default())? + } else if lowerer.use_heap_variable()? { + lowerer.pointer_target_snapshot_in_heap( + deref.base.get_type(), + self.heap.clone().unwrap(), + base_snapshot, + deref.position, + )? + } else { + // eprintln!("deref: {}", deref); + // unimplemented!() + true.into() // TODO + }; + self.ensure_bool_expression(lowerer, deref.get_type(), result, expect_math_bool) + } + + fn owned_non_aliased_snap( + &mut self, + _lowerer: &mut Lowerer<'p, 'v, 'tcx>, + _ty: &vir_mid::Type, + _pointer_snapshot: &vir_mid::Expression, + ) -> SpannedEncodingResult { + unimplemented!() + } + + fn call_context(&self) -> CallContext { + CallContext::BuiltinMethod + } + + fn push_bound_variables( + &mut self, + _variables: &[vir_mid::VariableDecl], + ) -> SpannedEncodingResult<()> { + todo!() + } + + fn pop_bound_variables(&mut self) -> SpannedEncodingResult<()> { + todo!() + } + + // fn unfolding_to_snapshot( + // &mut self, + // _lowerer: &mut Lowerer<'p, 'v, 'tcx>, + // _unfolding: &vir_mid::Unfolding, + // _expect_math_bool: bool, + // ) -> SpannedEncodingResult { + // todo!() + // } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/change_unique_ref_place.rs b/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/change_unique_ref_place.rs index 2a11f96b51b..c46690821cb 100644 --- a/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/change_unique_ref_place.rs +++ b/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/change_unique_ref_place.rs @@ -6,7 +6,7 @@ use crate::encoder::{ lifetimes::LifetimesInterface, lowerer::Lowerer, places::PlacesInterface, - predicates::UniqueRefUseBuilder, + predicates::PredicatesOwnedInterface, references::ReferencesInterface, snapshots::{IntoPureSnapshot, IntoSnapshot}, }, @@ -40,8 +40,8 @@ impl<'l, 'p, 'v, 'tcx> ChangeUniqueRefPlaceMethodBuilder<'l, 'p, 'v, 'tcx> { type_decl: &'l vir_mid::TypeDecl, error_kind: BuiltinMethodKind, ) -> SpannedEncodingResult { - let target_place = vir_low::VariableDecl::new("target_place", lowerer.place_type()?); - let source_place = vir_low::VariableDecl::new("source_place", lowerer.place_type()?); + let target_place = vir_low::VariableDecl::new("target_place", lowerer.place_option_type()?); + let source_place = vir_low::VariableDecl::new("source_place", lowerer.place_option_type()?); let source_snapshot = vir_low::VariableDecl::new("source_snapshot", ty.to_snapshot(lowerer)?); let inner = @@ -72,27 +72,27 @@ impl<'l, 'p, 'v, 'tcx> ChangeUniqueRefPlaceMethodBuilder<'l, 'p, 'v, 'tcx> { pub(in super::super::super::super) fn add_same_address_precondition( &mut self, ) -> SpannedEncodingResult<()> { - use vir_low::macros::*; - let root_address = self.inner.lowerer.reference_address( - self.inner.ty, - self.source_snapshot.clone().into(), - self.inner.position, - )?; - let deref_source_place = self - .inner - .lowerer - .reference_deref_place(self.source_place.clone().into(), self.inner.position)?; - let deref_target_place = self - .inner - .lowerer - .reference_deref_place(self.target_place.clone().into(), self.inner.position)?; - let source_address = - self.compute_address_expression(deref_source_place, root_address.clone()); - let target_address = self.compute_address_expression(deref_target_place, root_address); - let expression = expr! { - [target_address] == [source_address] - }; - self.add_precondition(expression); + // use vir_low::macros::*; + // let root_address = self.inner.lowerer.reference_address( + // self.inner.ty, + // self.source_snapshot.clone().into(), + // self.inner.position, + // )?; + // let deref_source_place = self + // .inner + // .lowerer + // .reference_deref_place(self.source_place.clone().into(), self.inner.position)?; + // let deref_target_place = self + // .inner + // .lowerer + // .reference_deref_place(self.target_place.clone().into(), self.inner.position)?; + // let source_address = + // self.compute_address_expression(deref_source_place, root_address.clone()); + // let target_address = self.compute_address_expression(deref_target_place, root_address); + // let expression = expr! { + // [target_address] == [source_address] + // }; + // self.add_precondition(expression); Ok(()) } @@ -106,6 +106,11 @@ impl<'l, 'p, 'v, 'tcx> ChangeUniqueRefPlaceMethodBuilder<'l, 'p, 'v, 'tcx> { self.source_snapshot.clone().into(), self.inner.position, )?; + let slice_len = self.inner.lowerer.reference_slice_len( + self.inner.ty, + self.source_snapshot.clone().into(), + self.inner.position, + )?; let deref_source_place = self .inner .lowerer @@ -133,37 +138,62 @@ impl<'l, 'p, 'v, 'tcx> ChangeUniqueRefPlaceMethodBuilder<'l, 'p, 'v, 'tcx> { .lowerer .encode_lifetime_const_into_pure_is_alive_variable(lifetime)?; let lifetime = lifetime.to_pure_snapshot(self.inner.lowerer)?; - let mut builder = UniqueRefUseBuilder::new( - self.lowerer(), + let source_expression = self.inner.lowerer.unique_ref_with_current_snapshot( CallContext::BuiltinMethod, &target_type, &target_type_decl, - deref_source_place, + deref_source_place.clone(), root_address.clone(), current_snapshot.clone(), - final_snapshot.clone(), lifetime.clone().into(), + slice_len.clone(), + None, + self.inner.position, )?; - builder.add_lifetime_arguments()?; - builder.add_const_arguments()?; - let source_expression = builder.build(); - - self.add_precondition(expr! { [lifetime_alive.clone().into()] ==> [source_expression] }); - let mut builder = UniqueRefUseBuilder::new( - self.lowerer(), + let source_final_expression = self.inner.lowerer.unique_ref_snap( + CallContext::BuiltinMethod, + &target_type, + &target_type_decl, + deref_source_place, + root_address.clone(), + lifetime.clone().into(), + slice_len.clone(), + true, + self.inner.position, + )?; + self.add_precondition(expr! { lifetime_alive ==> [source_expression] }); + self.add_precondition(expr! { + lifetime_alive ==> + ([final_snapshot.clone()] == [source_final_expression]) + }); + let target_expression = self.inner.lowerer.unique_ref_with_current_snapshot( + CallContext::BuiltinMethod, + &target_type, + &target_type_decl, + deref_target_place.clone(), + root_address.clone(), + current_snapshot, + lifetime.clone().into(), + slice_len.clone(), + None, + self.inner.position, + )?; + let target_final_expression = self.inner.lowerer.unique_ref_snap( CallContext::BuiltinMethod, &target_type, &target_type_decl, deref_target_place, root_address, - current_snapshot, - final_snapshot, lifetime.into(), + slice_len, + true, + self.inner.position, )?; - builder.add_lifetime_arguments()?; - builder.add_const_arguments()?; - let target_expression = builder.build(); - self.add_postcondition(expr! { [lifetime_alive.into()] ==> [target_expression] }); + self.add_postcondition(expr! { lifetime_alive ==> [target_expression] }); + self.add_postcondition(expr! { + lifetime_alive ==> + ([final_snapshot] == [target_final_expression]) + }); Ok(()) } } diff --git a/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/common.rs b/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/common.rs index 79631192179..20d7abf8660 100644 --- a/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/common.rs +++ b/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/common.rs @@ -232,13 +232,14 @@ where fn add_join_memory_block_call( &mut self, - place: &vir_low::VariableDecl, - root_address: &vir_low::VariableDecl, + _place: &vir_low::VariableDecl, + address: &vir_low::VariableDecl, + // root_address: &vir_low::VariableDecl, snapshot: &vir_low::VariableDecl, ) -> SpannedEncodingResult<()> { let inner = self.inner(); inner.lowerer.encode_memory_block_join_method(inner.ty)?; - let address = inner.compute_address(place, root_address); + // let address = inner.compute_address(place, root_address); let discriminant_call = inner.discriminant(snapshot)?; let mut builder = BuiltinMethodCallBuilder::new( inner.lowerer, @@ -248,7 +249,7 @@ where inner.type_decl, inner.position, )?; - builder.add_argument(address); + builder.add_argument(address.clone().into()); builder.add_full_permission_argument(); if let Some(discriminant_call) = discriminant_call { builder.add_argument(discriminant_call); diff --git a/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/copy_place.rs b/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/copy_place.rs index 2ae1cebfedc..2dc9dab4f79 100644 --- a/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/copy_place.rs +++ b/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/copy_place.rs @@ -5,10 +5,10 @@ use super::{ use crate::encoder::{ errors::{BuiltinMethodKind, SpannedEncodingResult}, middle::core_proof::{ + addresses::AddressesInterface, builtin_methods::{BuiltinMethodCallsInterface, BuiltinMethodsInterface, CallContext}, lowerer::Lowerer, places::PlacesInterface, - predicates::PredicatesOwnedInterface, snapshots::SnapshotValuesInterface, }, }; @@ -63,7 +63,7 @@ impl<'l, 'p, 'v, 'tcx> CopyPlaceMethodBuilder<'l, 'p, 'v, 'tcx> { self.inner .inner .parameters - .push(self.inner.target_root_address.clone()); + .push(self.inner.target_address.clone()); self.inner .inner .parameters @@ -71,7 +71,7 @@ impl<'l, 'p, 'v, 'tcx> CopyPlaceMethodBuilder<'l, 'p, 'v, 'tcx> { self.inner .inner .parameters - .push(self.inner.source_root_address.clone()); + .push(self.inner.source_address.clone()); self.inner .inner .parameters @@ -104,21 +104,40 @@ impl<'l, 'p, 'v, 'tcx> CopyPlaceMethodBuilder<'l, 'p, 'v, 'tcx> { pub(in super::super::super::super) fn create_source_owned( &mut self, ) -> SpannedEncodingResult { - self.inner.inner.lowerer.owned_non_aliased( - CallContext::BuiltinMethod, - self.inner.inner.ty, - self.inner.inner.type_decl, - self.inner.source_place.clone().into(), - self.inner.source_root_address.clone().into(), - self.inner.source_snapshot.clone().into(), - Some(self.source_permission_amount.clone().into()), - ) + self.inner + .create_source_owned(false, Some(self.source_permission_amount.clone().into())) + // self.inner.inner.lowerer.owned_non_aliased( + // CallContext::BuiltinMethod, + // self.inner.inner.ty, + // self.inner.inner.type_decl, + // self.inner.source_place.clone().into(), + // self.inner.source_address.clone().into(), + // Some(self.source_permission_amount.clone().into()), + // self.inner.inner.position, + // ) + } + + pub(in super::super::super::super) fn create_source_owned_predicate( + &mut self, + ) -> SpannedEncodingResult { + self.inner + .create_source_owned(true, Some(self.source_permission_amount.clone().into())) + // self.inner.inner.lowerer.owned_non_aliased( + // CallContext::BuiltinMethod, + // self.inner.inner.ty, + // self.inner.inner.type_decl, + // self.inner.source_place.clone().into(), + // self.inner.source_address.clone().into(), + // Some(self.source_permission_amount.clone().into()), + // self.inner.inner.position, + // ) } pub(in super::super::super::super) fn create_target_owned( &mut self, + must_be_predicate: bool, ) -> SpannedEncodingResult { - self.inner.create_target_owned() + self.inner.create_target_owned(must_be_predicate) } pub(in super::super::super::super) fn add_target_validity_postcondition( @@ -158,12 +177,24 @@ impl<'l, 'p, 'v, 'tcx> CopyPlaceMethodBuilder<'l, 'p, 'v, 'tcx> { self.inner.source_place.clone().into(), self.inner.inner.position, )?; + let source_field_address = self.inner.inner.lowerer.encode_field_address( + self.inner.inner.ty, + field, + self.inner.source_address.clone().into(), + self.inner.inner.position, + )?; let target_field_place = self.inner.inner.lowerer.encode_field_place( self.inner.inner.ty, field, self.inner.target_place.clone().into(), self.inner.inner.position, )?; + let target_field_address = self.inner.inner.lowerer.encode_field_address( + self.inner.inner.ty, + field, + self.inner.target_address.clone().into(), + self.inner.inner.position, + )?; let source_field_snapshot = self.inner.inner.lowerer.obtain_struct_field_snapshot( self.inner.inner.ty, field, @@ -176,13 +207,21 @@ impl<'l, 'p, 'v, 'tcx> CopyPlaceMethodBuilder<'l, 'p, 'v, 'tcx> { &field.ty, self.inner.inner.position, target_field_place, - self.inner.target_root_address.clone().into(), + target_field_address, source_field_place, - self.inner.source_root_address.clone().into(), + source_field_address, source_field_snapshot, self.source_permission_amount.clone().into(), )?; self.add_statement(statement); Ok(()) } + + pub(in super::super::super::super) fn duplicate_frac_ref( + &mut self, + lifetime: &vir_mid::ty::LifetimeConst, + ) -> SpannedEncodingResult<()> { + self.inner + .duplicate_frac_ref(lifetime, Some(self.source_permission_amount.clone().into())) + } } diff --git a/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/duplicate_frac_ref.rs b/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/duplicate_frac_ref.rs index 58f11ea5cff..5de2e222037 100644 --- a/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/duplicate_frac_ref.rs +++ b/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/duplicate_frac_ref.rs @@ -2,15 +2,13 @@ use super::common::{BuiltinMethodBuilder, BuiltinMethodBuilderMethods}; use crate::encoder::{ errors::{BuiltinMethodKind, SpannedEncodingResult}, middle::core_proof::{ - builtin_methods::CallContext, - lowerer::Lowerer, - places::PlacesInterface, - predicates::FracRefUseBuilder, - references::ReferencesInterface, - snapshots::{IntoPureSnapshot, IntoSnapshot}, + addresses::AddressesInterface, builtin_methods::CallContext, lowerer::Lowerer, + places::PlacesInterface, predicates::PredicatesOwnedInterface, + references::ReferencesInterface, snapshots::IntoPureSnapshot, }, }; use vir_crate::{ + common::expression::BinaryOperationHelpers, low::{self as vir_low}, middle as vir_mid, }; @@ -19,7 +17,9 @@ pub(in super::super::super::super) struct DuplicateFracRefMethodBuilder<'l, 'p, inner: BuiltinMethodBuilder<'l, 'p, 'v, 'tcx>, target_place: vir_low::VariableDecl, source_place: vir_low::VariableDecl, - source_snapshot: vir_low::VariableDecl, + // source_snapshot: vir_low::VariableDecl, + address: vir_low::VariableDecl, + source_permission_amount: vir_low::VariableDecl, } impl<'l, 'p, 'v, 'tcx> BuiltinMethodBuilderMethods<'l, 'p, 'v, 'tcx> @@ -39,17 +39,22 @@ impl<'l, 'p, 'v, 'tcx> DuplicateFracRefMethodBuilder<'l, 'p, 'v, 'tcx> { type_decl: &'l vir_mid::TypeDecl, error_kind: BuiltinMethodKind, ) -> SpannedEncodingResult { - let target_place = vir_low::VariableDecl::new("target_place", lowerer.place_type()?); - let source_place = vir_low::VariableDecl::new("source_place", lowerer.place_type()?); - let source_snapshot = - vir_low::VariableDecl::new("source_snapshot", ty.to_snapshot(lowerer)?); + let target_place = vir_low::VariableDecl::new("target_place", lowerer.place_option_type()?); + let source_place = vir_low::VariableDecl::new("source_place", lowerer.place_option_type()?); + // let source_snapshot = + // vir_low::VariableDecl::new("source_snapshot", ty.to_snapshot(lowerer)?); + let address = vir_low::VariableDecl::new("address", lowerer.address_type()?); + let source_permission_amount = + vir_low::VariableDecl::new("source_permission_amount", vir_low::Type::Perm); let inner = BuiltinMethodBuilder::new(lowerer, kind, method_name, ty, type_decl, error_kind)?; Ok(Self { inner, target_place, source_place, - source_snapshot, + // source_snapshot, + address, + source_permission_amount, }) } @@ -62,47 +67,52 @@ impl<'l, 'p, 'v, 'tcx> DuplicateFracRefMethodBuilder<'l, 'p, 'v, 'tcx> { ) -> SpannedEncodingResult<()> { self.inner.parameters.push(self.target_place.clone()); self.inner.parameters.push(self.source_place.clone()); - self.inner.parameters.push(self.source_snapshot.clone()); + // self.inner.parameters.push(self.source_snapshot.clone()); + self.inner.parameters.push(self.address.clone()); + self.inner + .parameters + .push(self.source_permission_amount.clone()); self.create_lifetime_parameters()?; self.create_const_parameters()?; Ok(()) } - pub(in super::super::super::super) fn add_same_address_precondition( - &mut self, - ) -> SpannedEncodingResult<()> { - use vir_low::macros::*; - let root_address = self.inner.lowerer.reference_address( - self.inner.ty, - self.source_snapshot.clone().into(), - self.inner.position, - )?; - let deref_source_place = self - .inner - .lowerer - .reference_deref_place(self.source_place.clone().into(), self.inner.position)?; - let deref_target_place = self - .inner - .lowerer - .reference_deref_place(self.target_place.clone().into(), self.inner.position)?; - let source_address = - self.compute_address_expression(deref_source_place, root_address.clone()); - let target_address = self.compute_address_expression(deref_target_place, root_address); - let expression = expr! { - [target_address] == [source_address] - }; - self.add_precondition(expression); - Ok(()) - } + // pub(in super::super::super::super) fn add_same_address_precondition( + // &mut self, + // ) -> SpannedEncodingResult<()> { + // use vir_low::macros::*; + // let address = self.inner.lowerer.reference_address( + // self.inner.ty, + // self.source_snapshot.clone().into(), + // self.inner.position, + // )?; + // let deref_source_place = self + // .inner + // .lowerer + // .reference_deref_place(self.source_place.clone().into(), self.inner.position)?; + // let deref_target_place = self + // .inner + // .lowerer + // .reference_deref_place(self.target_place.clone().into(), self.inner.position)?; + // let source_address = + // self.compute_address_expression(deref_source_place, root_address.clone()); + // let target_address = self.compute_address_expression(deref_target_place, root_address); + // let expression = expr! { + // [target_address] == [source_address] + // }; + // self.add_precondition(expression); + // Ok(()) + // } pub(in super::super::super::super) fn add_frac_ref_pre_postcondition( &mut self, ) -> SpannedEncodingResult<()> { - let root_address = self.inner.lowerer.reference_address( - self.inner.ty, - self.source_snapshot.clone().into(), - self.inner.position, - )?; + // let address = self.inner.lowerer.reference_address( + // self.inner.ty, + // self.source_snapshot.clone().into(), + // self.inner.position, + // )?; + let address: vir_low::Expression = self.address.clone().into(); let deref_source_place = self .inner .lowerer @@ -115,41 +125,102 @@ impl<'l, 'p, 'v, 'tcx> DuplicateFracRefMethodBuilder<'l, 'p, 'v, 'tcx> { .inner .lowerer .reference_deref_place(self.target_place.clone().into(), self.inner.position)?; - let current_snapshot = self.inner.lowerer.reference_target_current_snapshot( - self.inner.ty, - self.source_snapshot.clone().into(), - self.inner.position, - )?; + // let _current_snapshot = self.inner.lowerer.reference_target_current_snapshot( + // self.inner.ty, + // self.source_snapshot.clone().into(), + // self.inner.position, + // )?; let lifetime = lifetime.to_pure_snapshot(self.inner.lowerer)?; - let mut builder = FracRefUseBuilder::new( - self.lowerer(), + // let mut builder = FracRefUseBuilder::new( + // self.lowerer(), + // CallContext::BuiltinMethod, + // &target_type, + // &target_type_decl, + // deref_source_place, + // address.clone(), + // // current_snapshot.clone(), + // lifetime.clone().into(), + // )?; + // builder.add_lifetime_arguments()?; + // builder.add_const_arguments()?; + // let source_expression = builder.build()?; + let TODO_source_slice_len = None; + let source_expression = self.inner.lowerer.frac_ref( CallContext::BuiltinMethod, &target_type, &target_type_decl, - deref_source_place, - root_address.clone(), - current_snapshot.clone(), + deref_source_place.clone(), + address.clone(), lifetime.clone().into(), + TODO_source_slice_len.clone(), + Some(self.source_permission_amount.clone().into()), + self.inner.position, )?; - builder.add_lifetime_arguments()?; - builder.add_const_arguments()?; - let source_expression = builder.build(); self.add_precondition(source_expression.clone()); self.add_postcondition(source_expression); - let mut builder = FracRefUseBuilder::new( - self.lowerer(), + // let mut builder = FracRefUseBuilder::new( + // self.lowerer(), + // CallContext::BuiltinMethod, + // &target_type, + // &target_type_decl, + // deref_target_place, + // address, + // // current_snapshot, + // lifetime.into(), + // )?; + // builder.add_lifetime_arguments()?; + // builder.add_const_arguments()?; + // let target_expression = builder.build(); + let TODO_target_slice_len = None; + let target_expression = self.inner.lowerer.frac_ref( + CallContext::BuiltinMethod, + &target_type, + &target_type_decl, + deref_target_place.clone(), + address.clone(), + lifetime.clone().into(), + TODO_target_slice_len.clone(), + None, + self.inner.position, + )?; + self.add_postcondition(target_expression); + let source_snapshot = self.inner.lowerer.frac_ref_snap( + CallContext::BuiltinMethod, + &target_type, + &target_type_decl, + deref_source_place, + address.clone(), + lifetime.clone().into(), + TODO_source_slice_len, + self.inner.position, + )?; + let target_snapshot = self.inner.lowerer.frac_ref_snap( CallContext::BuiltinMethod, &target_type, &target_type_decl, deref_target_place, - root_address, - current_snapshot, + address, lifetime.into(), + TODO_target_slice_len, + self.inner.position, )?; - builder.add_lifetime_arguments()?; - builder.add_const_arguments()?; - let target_expression = builder.build(); - self.add_postcondition(target_expression); + let snapshot_preserved = vir_low::Expression::equals( + source_snapshot.clone(), + vir_low::Expression::labelled_old(None, source_snapshot.clone(), self.inner.position), + ); + self.add_postcondition(snapshot_preserved); + let snapshot_equality = vir_low::Expression::equals(source_snapshot, target_snapshot); + self.add_postcondition(snapshot_equality); + Ok(()) + } + + pub(in super::super::super::super) fn add_permission_amount_positive_precondition( + &mut self, + ) -> SpannedEncodingResult<()> { + let expression = self + .inner + .create_permission_amount_positive(&self.source_permission_amount)?; + self.add_precondition(expression); Ok(()) } } diff --git a/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/memory_block_into.rs b/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/memory_block_into.rs index 7e753f27d06..536850443eb 100644 --- a/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/memory_block_into.rs +++ b/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/memory_block_into.rs @@ -9,7 +9,7 @@ use crate::encoder::{ }, lowerer::Lowerer, places::PlacesInterface, - predicates::OwnedNonAliasedUseBuilder, + predicates::PredicatesOwnedInterface, snapshots::{IntoSnapshot, SnapshotValuesInterface}, }, }; @@ -21,7 +21,7 @@ use vir_crate::{ pub(in super::super::super::super) struct IntoMemoryBlockMethodBuilder<'l, 'p, 'v, 'tcx> { inner: BuiltinMethodBuilder<'l, 'p, 'v, 'tcx>, place: vir_low::VariableDecl, - root_address: vir_low::VariableDecl, + address: vir_low::VariableDecl, snapshot: vir_low::VariableDecl, } @@ -42,15 +42,15 @@ impl<'l, 'p, 'v, 'tcx> IntoMemoryBlockMethodBuilder<'l, 'p, 'v, 'tcx> { type_decl: &'l vir_mid::TypeDecl, error_kind: BuiltinMethodKind, ) -> SpannedEncodingResult { - let place = vir_low::VariableDecl::new("place", lowerer.place_type()?); - let root_address = vir_low::VariableDecl::new("root_address", lowerer.address_type()?); + let place = vir_low::VariableDecl::new("place", lowerer.place_option_type()?); + let address = vir_low::VariableDecl::new("address", lowerer.address_type()?); let snapshot = vir_low::VariableDecl::new("snapshot", ty.to_snapshot(lowerer)?); let inner = BuiltinMethodBuilder::new(lowerer, kind, method_name, ty, type_decl, error_kind)?; Ok(Self { inner, place, - root_address, + address, snapshot, }) } @@ -63,7 +63,7 @@ impl<'l, 'p, 'v, 'tcx> IntoMemoryBlockMethodBuilder<'l, 'p, 'v, 'tcx> { &mut self, ) -> SpannedEncodingResult<()> { self.inner.parameters.push(self.place.clone()); - self.inner.parameters.push(self.root_address.clone()); + self.inner.parameters.push(self.address.clone()); self.inner.parameters.push(self.snapshot.clone()); self.create_lifetime_parameters()?; self.create_const_parameters()?; @@ -73,25 +73,37 @@ impl<'l, 'p, 'v, 'tcx> IntoMemoryBlockMethodBuilder<'l, 'p, 'v, 'tcx> { // FIXME: Remove code duplication with create_source_owned. pub(in super::super::super::super) fn create_owned( &mut self, + exclude_snapshot_equality: bool, ) -> SpannedEncodingResult { - let mut builder = OwnedNonAliasedUseBuilder::new( - self.inner.lowerer, - CallContext::BuiltinMethod, - self.inner.ty, - self.inner.type_decl, - self.place.clone().into(), - self.root_address.clone().into(), - self.snapshot.clone().into(), - )?; - builder.add_lifetime_arguments()?; - builder.add_const_arguments()?; - Ok(builder.build()) + if exclude_snapshot_equality { + self.inner.lowerer.owned_non_aliased_full_vars( + CallContext::BuiltinMethod, + self.inner.ty, + self.inner.type_decl, + &self.place, + &self.address, + self.inner.position, + ) + } else { + self.inner + .lowerer + .owned_non_aliased_full_vars_with_snapshot( + CallContext::BuiltinMethod, + self.inner.ty, + self.inner.type_decl, + &self.place, + &self.address, + &self.snapshot, + self.inner.position, + ) + } } pub(in super::super::super::super) fn create_target_memory_block( &mut self, ) -> SpannedEncodingResult { - self.create_memory_block(self.compute_address(&self.place, &self.root_address)) + // self.create_memory_block(self.compute_address(&self.place, &self.address)) + self.create_memory_block(self.address.clone().into()) } pub(in super::super::super::super) fn add_into_memory_block_call_for_field( @@ -104,6 +116,12 @@ impl<'l, 'p, 'v, 'tcx> IntoMemoryBlockMethodBuilder<'l, 'p, 'v, 'tcx> { self.place.clone().into(), self.inner.position, )?; + let field_address = self.inner.lowerer.encode_field_address( + self.inner.ty, + field, + self.address.clone().into(), + self.inner.position, + )?; let field_snapshot = self.inner.lowerer.obtain_struct_field_snapshot( self.inner.ty, field, @@ -122,7 +140,7 @@ impl<'l, 'p, 'v, 'tcx> IntoMemoryBlockMethodBuilder<'l, 'p, 'v, 'tcx> { self.inner.position, )?; builder.add_argument(field_place); - builder.add_argument(self.root_address.clone().into()); + builder.add_argument(field_address); builder.add_argument(field_snapshot); builder.add_lifetime_arguments()?; builder.add_const_arguments()?; @@ -152,6 +170,12 @@ impl<'l, 'p, 'v, 'tcx> IntoMemoryBlockMethodBuilder<'l, 'p, 'v, 'tcx> { self.place.clone().into(), self.inner.position, )?; + let variant_address = self.inner.lowerer.encode_enum_variant_address( + self.inner.ty, + &variant_index, + self.address.clone().into(), + self.inner.position, + )?; let variant_snapshot = self.inner.lowerer.obtain_enum_variant_snapshot( self.inner.ty, &variant_index, @@ -171,7 +195,7 @@ impl<'l, 'p, 'v, 'tcx> IntoMemoryBlockMethodBuilder<'l, 'p, 'v, 'tcx> { self.inner.position, )?; builder.add_argument(variant_place); - builder.add_argument(self.root_address.clone().into()); + builder.add_argument(variant_address); builder.add_argument(variant_snapshot); builder.add_lifetime_arguments()?; builder.add_const_arguments()?; @@ -196,6 +220,12 @@ impl<'l, 'p, 'v, 'tcx> IntoMemoryBlockMethodBuilder<'l, 'p, 'v, 'tcx> { self.place.clone().into(), self.inner.position, )?; + let discriminant_address = self.inner.lowerer.encode_field_address( + self.inner.ty, + &discriminant_field, + self.address.clone().into(), + self.inner.position, + )?; let discriminant_call = self.inner.lowerer.obtain_enum_discriminant( self.snapshot.clone().into(), self.inner.ty, @@ -215,7 +245,7 @@ impl<'l, 'p, 'v, 'tcx> IntoMemoryBlockMethodBuilder<'l, 'p, 'v, 'tcx> { self.inner.position, )?; builder.add_argument(discriminant_place); - builder.add_argument(self.root_address.clone().into()); + builder.add_argument(discriminant_address); builder.add_argument(discriminant_snashot); builder.add_lifetime_arguments()?; builder.add_const_arguments()?; @@ -228,6 +258,6 @@ impl<'l, 'p, 'v, 'tcx> IntoMemoryBlockMethodBuilder<'l, 'p, 'v, 'tcx> { &mut self, ) -> SpannedEncodingResult<()> { self.inner - .add_join_memory_block_call(&self.place, &self.root_address, &self.snapshot) + .add_join_memory_block_call(&self.place, &self.address, &self.snapshot) } } diff --git a/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/memory_block_range_join.rs b/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/memory_block_range_join.rs new file mode 100644 index 00000000000..1b17eb08639 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/memory_block_range_join.rs @@ -0,0 +1,382 @@ +use super::{ + common::{BuiltinMethodBuilder, BuiltinMethodBuilderMethods}, + memory_block_range_split_join_common::MemoryBlockRangeSplitJoinMethodBuilder, + memory_block_split_join_common::BuiltinMethodSplitJoinBuilderMethods, +}; +use crate::encoder::{ + errors::{BuiltinMethodKind, SpannedEncodingResult}, + middle::core_proof::{ + addresses::AddressesInterface, lowerer::Lowerer, + predicates::PredicatesMemoryBlockInterface, snapshots::SnapshotValuesInterface, + triggers::TriggersInterface, type_layouts::TypeLayoutsInterface, + }, +}; +use vir_crate::{ + common::expression::{BinaryOperationHelpers, QuantifierHelpers}, + low::{self as vir_low}, + middle as vir_mid, +}; + +pub(in super::super::super::super) struct MemoryBlockRangeJoinMethodBuilder<'l, 'p, 'v, 'tcx> { + inner: MemoryBlockRangeSplitJoinMethodBuilder<'l, 'p, 'v, 'tcx>, +} + +impl<'l, 'p, 'v, 'tcx> BuiltinMethodBuilderMethods<'l, 'p, 'v, 'tcx> + for MemoryBlockRangeJoinMethodBuilder<'l, 'p, 'v, 'tcx> +{ + fn inner(&mut self) -> &mut BuiltinMethodBuilder<'l, 'p, 'v, 'tcx> { + self.inner.inner() + } +} + +impl<'l, 'p, 'v, 'tcx> BuiltinMethodSplitJoinBuilderMethods<'l, 'p, 'v, 'tcx> + for MemoryBlockRangeJoinMethodBuilder<'l, 'p, 'v, 'tcx> +{ +} + +impl<'l, 'p, 'v, 'tcx> MemoryBlockRangeJoinMethodBuilder<'l, 'p, 'v, 'tcx> { + pub(in super::super::super::super) fn new( + lowerer: &'l mut Lowerer<'p, 'v, 'tcx>, + kind: vir_low::MethodKind, + method_name: &'l str, + ty: &'l vir_mid::Type, + type_decl: &'l vir_mid::TypeDecl, + error_kind: BuiltinMethodKind, + ) -> SpannedEncodingResult { + Ok(Self { + inner: MemoryBlockRangeSplitJoinMethodBuilder::new( + lowerer, + kind, + method_name, + ty, + type_decl, + error_kind, + )?, + }) + } + + pub(in super::super::super::super) fn build(self) -> vir_low::MethodDecl { + self.inner.build() + } + + pub(in super::super::super::super) fn create_parameters( + &mut self, + ) -> SpannedEncodingResult<()> { + self.inner.create_parameters() + } + + // pub(in super::super::super::super) fn add_permission_amount_positive_precondition( + // &mut self, + // ) -> SpannedEncodingResult<()> { + // self.inner.add_permission_amount_positive_precondition() + // } + + pub(in super::super::super::super) fn add_whole_memory_block_postcondition( + &mut self, + ) -> SpannedEncodingResult<()> { + let memory_block = self.inner.create_whole_block_acc()?; + self.add_postcondition(memory_block); + Ok(()) + } + + pub(in super::super::super::super) fn add_memory_block_range_precondition( + &mut self, + ) -> SpannedEncodingResult<()> { + let memory_block_range = self.inner.create_memory_block_range_acc()?; + self.add_precondition(memory_block_range); + Ok(()) + } + + // FIXME: Code duplication. + pub(in super::super::super::super) fn add_byte_values_preserved_postcondition( + &mut self, + ) -> SpannedEncodingResult<()> { + use vir_low::macros::*; + let element_size = self + .inner + .inner + .lowerer + .encode_type_size_expression2(self.inner.inner.ty, self.inner.inner.type_decl)?; + let length = self.inner.length()?; + let whole_size = self + .inner + .inner + .lowerer + .encode_type_size_expression_repetitions( + self.inner.inner.ty, + self.inner.inner.type_decl, + length, + self.inner.inner.position, + )?; + let size_type = self.inner.inner.lowerer.size_type_mid()?; + var_decls! { + index: Int, + byte_index: Int + } + let address: vir_low::Expression = self.inner.address.clone().into(); + let element_address = self.inner.inner.lowerer.address_offset( + element_size.clone(), + address.clone(), + index.clone().into(), + self.inner.inner.position, + )?; + let start_index = self.inner.inner.lowerer.obtain_constant_value( + &size_type, + self.inner.start_index.clone().into(), + self.inner.inner.position, + )?; + let end_index = self.inner.inner.lowerer.obtain_constant_value( + &size_type, + self.inner.end_index.clone().into(), + self.inner.inner.position, + )?; + let element_bytes = self + .inner + .inner + .lowerer + .encode_memory_block_bytes_expression(element_address, element_size.clone())?; + let whole_bytes = self + .inner + .inner + .lowerer + .encode_memory_block_bytes_expression(address, whole_size)?; + let read_element_byte = self.inner.inner.lowerer.encode_read_byte_expression_int( + vir_low::Expression::labelled_old(None, element_bytes, self.inner.inner.position), + byte_index.clone().into(), + self.inner.inner.position, + )?; + let block_size = self.inner.inner.lowerer.obtain_constant_value( + &size_type, + element_size.clone(), + self.inner.inner.position, + )?; + let block_start_index = vir_low::Expression::multiply(block_size, index.clone().into()); + let whole_byte_index = + vir_low::Expression::add(block_start_index, byte_index.clone().into()); + // let whole_byte_index = self.inner.inner.lowerer.create_domain_func_app( + // "Arithmetic", + // "mul_add", + // vec![block_size, index.clone().into(), byte_index.clone().into()], + // vir_low::Type::Int, + // self.inner.inner.position, + // )?; + let read_whole_byte = self.inner.inner.lowerer.encode_read_byte_expression_int( + whole_bytes, + whole_byte_index, + self.inner.inner.position, + )?; + let element_size_int = self.inner.inner.lowerer.obtain_constant_value( + &size_type, + element_size, + self.inner.inner.position, + )?; + let body = expr!( + ((([start_index] <= index) && (index < [end_index])) && + (([0.into()] <= byte_index) && (byte_index < [element_size_int]))) ==> + ([read_element_byte.clone()] == [read_whole_byte]) + ); + // let trigger = self.inner.inner.lowerer.encode_read_byte_expression_int( + // element_bytes, + // byte_index.clone().into(), + // self.inner.inner.position, + // )?; + let trigger = read_element_byte; + let pure_trigger = self.inner.inner.lowerer.call_trigger_function( + "memory_block_range_join_trigger", + vec![index.clone().into(), byte_index.clone().into()], + self.inner.inner.position, + )?; + let expression = vir_low::Expression::forall( + vec![index, byte_index], + vec![ + vir_low::Trigger::new(vec![trigger]), + vir_low::Trigger::new(vec![pure_trigger]), + ], + body, + ); + self.add_postcondition(expression); + Ok(()) + } + + // pub(in super::super::super::super) fn add_padding_memory_block_precondition( + // &mut self, + // ) -> SpannedEncodingResult<()> { + // let expression = self.inner.create_padding_memory_block_acc()?; + // self.add_precondition(expression); + // Ok(()) + // } + + // pub(in super::super::super::super) fn add_field_memory_block_precondition( + // &mut self, + // field: &vir_mid::FieldDecl, + // ) -> SpannedEncodingResult<()> { + // let field_block = self.inner.create_field_memory_block_acc(field)?; + // self.add_precondition(field_block); + // Ok(()) + // } + + // pub(in super::super::super::super) fn add_discriminant_precondition( + // &mut self, + // decl: &vir_mid::type_decl::Enum, + // ) -> SpannedEncodingResult<()> { + // let discriminant_block = self.inner.create_discriminant_acc(decl)?; + // self.add_precondition(discriminant_block); + // Ok(()) + // } + + // pub(in super::super::super::super) fn add_variant_memory_block_precondition( + // &mut self, + // discriminant_value: vir_mid::DiscriminantValue, + // variant: &vir_mid::type_decl::Struct, + // ) -> SpannedEncodingResult<()> { + // let expression = self + // .inner + // .create_variant_memory_block_acc(discriminant_value, variant)?; + // self.add_precondition(expression); + // Ok(()) + // } + + // pub(in super::super::super::super) fn create_field_to_bytes_equality( + // &mut self, + // field: &vir_mid::FieldDecl, + // ) -> SpannedEncodingResult { + // let expression = self.inner.create_field_to_bytes_equality(field)?; + // Ok(vir_low::Expression::labelled_old_no_pos(None, expression)) + // } + + // pub(in super::super::super::super) fn add_fields_to_bytes_equalities_postcondition( + // &mut self, + // field_to_bytes_equalities: Vec, + // ) -> SpannedEncodingResult<()> { + // use vir_low::macros::*; + // let address = self.inner.address(); + // let inner = self.inner(); + // let to_bytes = ty! { Bytes }; + // let ty = inner.ty; + // let size_of = inner + // .lowerer + // .encode_type_size_expression2(inner.ty, inner.type_decl)?; + // let memory_block_bytes = inner + // .lowerer + // .encode_memory_block_bytes_expression(address, size_of)?; + // let bytes_quantifier = expr! { + // forall( + // snapshot: {ty.to_snapshot(inner.lowerer)?} :: + // [ { (Snap::to_bytes(snapshot)) } ] + // [ field_to_bytes_equalities.into_iter().conjoin() ] ==> + // ([memory_block_bytes] == (Snap::to_bytes(snapshot))) + // ) + // }; + // self.add_postcondition(bytes_quantifier); + // Ok(()) + // } + + // pub(in super::super::super::super) fn create_variant_to_bytes_equality( + // &mut self, + // discriminant_value: vir_mid::DiscriminantValue, + // variant: &vir_mid::type_decl::Struct, + // decl: &vir_mid::type_decl::Enum, + // safety: vir_mid::ty::EnumSafety, + // ) -> SpannedEncodingResult { + // use vir_low::macros::*; + // let discriminant = self.inner.discriminant.as_ref().unwrap(); + // let ty = self.inner.inner.ty; + // let to_bytes = ty! { Bytes }; + // let snapshot: vir_low::Expression = + // var! { snapshot: {self.inner.inner.ty.to_snapshot(self.inner.inner.lowerer)?} }.into(); + // let variant_index = variant.name.clone().into(); + // let variant_snapshot = self.inner.inner.lowerer.obtain_enum_variant_snapshot( + // ty, + // &variant_index, + // snapshot.clone(), + // self.inner.inner.position, + // )?; + // let variant_address = self.inner.inner.lowerer.encode_enum_variant_address( + // self.inner.inner.ty, + // &variant_index, + // self.inner.address.clone().into(), + // self.inner.inner.position, + // )?; + // let variant_type = &self.inner.inner.ty.clone().variant(variant_index); + // let variant_size_of = self + // .inner + // .inner + // .lowerer + // .encode_type_size_expression2(variant_type, variant)?; + // let memory_block_variant_bytes = self + // .inner + // .inner + // .lowerer + // .encode_memory_block_bytes_expression(variant_address, variant_size_of)?; + // let memory_block_bytes = self + // .inner + // .inner + // .create_memory_block_bytes(self.inner.address.clone().into())?; + // let discriminant_to_bytes = if safety.is_enum() { + // let discriminant_type = &decl.discriminant_type; + // let discriminant_size_of = self + // .inner + // .inner + // .lowerer + // .encode_type_size_expression2(&decl.discriminant_type, &decl.discriminant_type)?; + // let discriminant_field = decl.discriminant_field(); + // let discriminant_address = self.inner.inner.lowerer.encode_field_address( + // self.inner.inner.ty, + // &discriminant_field, + // self.inner.address.clone().into(), + // self.inner.inner.position, + // )?; + // let memory_block_discriminant_bytes = self + // .inner + // .inner + // .lowerer + // .encode_memory_block_bytes_expression(discriminant_address, discriminant_size_of)?; + // let discriminant_call = self.inner.inner.lowerer.obtain_enum_discriminant( + // snapshot.clone(), + // self.inner.inner.ty, + // self.inner.inner.position, + // )?; + // let discriminant_snapshot = self.inner.inner.lowerer.construct_constant_snapshot( + // discriminant_type, + // discriminant_call, + // self.inner.inner.position, + // )?; + // expr! { + // ((old([memory_block_discriminant_bytes])) == + // (Snap::to_bytes([discriminant_snapshot]))) + // } + // } else { + // true.into() + // }; + // let expression = expr! { + // (discriminant == [discriminant_value.into()]) ==> + // ( + // ( + // [discriminant_to_bytes] && + // ((old([memory_block_variant_bytes])) == + // (Snap::to_bytes([variant_snapshot]))) + // ) ==> + // ([memory_block_bytes] == (Snap::to_bytes([snapshot]))) + // ) + // }; + // Ok(expression) + // } + + // pub(in super::super::super::super) fn add_variants_to_bytes_equalities_postcondition( + // &mut self, + // variant_to_bytes_equalities: Vec, + // ) -> SpannedEncodingResult<()> { + // use vir_low::macros::*; + // let ty = self.inner.inner.ty; + // let to_bytes = ty! { Bytes }; + // let expression = expr! { + // forall( + // snapshot: {ty.to_snapshot(self.inner.inner.lowerer)?} :: + // [ { (Snap::to_bytes(snapshot)) } ] + // [ variant_to_bytes_equalities.into_iter().conjoin() ] + // ) + // }; + // self.add_postcondition(expression); + // Ok(()) + // } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/memory_block_range_split.rs b/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/memory_block_range_split.rs new file mode 100644 index 00000000000..a1065f9334b --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/memory_block_range_split.rs @@ -0,0 +1,195 @@ +use super::{ + common::{BuiltinMethodBuilder, BuiltinMethodBuilderMethods}, + memory_block_range_split_join_common::MemoryBlockRangeSplitJoinMethodBuilder, + memory_block_split_join_common::BuiltinMethodSplitJoinBuilderMethods, +}; +use crate::encoder::{ + errors::{BuiltinMethodKind, SpannedEncodingResult}, + middle::core_proof::{ + addresses::AddressesInterface, lowerer::Lowerer, + predicates::PredicatesMemoryBlockInterface, snapshots::SnapshotValuesInterface, + triggers::TriggersInterface, type_layouts::TypeLayoutsInterface, + }, +}; +use vir_crate::{ + common::expression::{BinaryOperationHelpers, QuantifierHelpers}, + low::{self as vir_low}, + middle as vir_mid, +}; + +pub(in super::super::super::super) struct MemoryBlockRangeSplitMethodBuilder<'l, 'p, 'v, 'tcx> { + inner: MemoryBlockRangeSplitJoinMethodBuilder<'l, 'p, 'v, 'tcx>, +} + +impl<'l, 'p, 'v, 'tcx> BuiltinMethodBuilderMethods<'l, 'p, 'v, 'tcx> + for MemoryBlockRangeSplitMethodBuilder<'l, 'p, 'v, 'tcx> +{ + fn inner(&mut self) -> &mut BuiltinMethodBuilder<'l, 'p, 'v, 'tcx> { + self.inner.inner() + } +} + +impl<'l, 'p, 'v, 'tcx> BuiltinMethodSplitJoinBuilderMethods<'l, 'p, 'v, 'tcx> + for MemoryBlockRangeSplitMethodBuilder<'l, 'p, 'v, 'tcx> +{ +} + +impl<'l, 'p, 'v, 'tcx> MemoryBlockRangeSplitMethodBuilder<'l, 'p, 'v, 'tcx> { + pub(in super::super::super::super) fn new( + lowerer: &'l mut Lowerer<'p, 'v, 'tcx>, + kind: vir_low::MethodKind, + method_name: &'l str, + ty: &'l vir_mid::Type, + type_decl: &'l vir_mid::TypeDecl, + error_kind: BuiltinMethodKind, + ) -> SpannedEncodingResult { + Ok(Self { + inner: MemoryBlockRangeSplitJoinMethodBuilder::new( + lowerer, + kind, + method_name, + ty, + type_decl, + error_kind, + )?, + }) + } + + pub(in super::super::super::super) fn build(self) -> vir_low::MethodDecl { + self.inner.build() + } + + pub(in super::super::super::super) fn create_parameters( + &mut self, + ) -> SpannedEncodingResult<()> { + self.inner.create_parameters() + } + + // pub(in super::super::super::super) fn add_permission_amount_positive_precondition( + // &mut self, + // ) -> SpannedEncodingResult<()> { + // self.inner.add_permission_amount_positive_precondition() + // } + + pub(in super::super::super::super) fn add_whole_memory_block_precondition( + &mut self, + ) -> SpannedEncodingResult<()> { + let memory_block = self.inner.create_whole_block_acc()?; + self.add_precondition(memory_block); + Ok(()) + } + + pub(in super::super::super::super) fn add_memory_block_range_postcondition( + &mut self, + ) -> SpannedEncodingResult<()> { + let memory_block_range = self.inner.create_memory_block_range_acc()?; + self.add_postcondition(memory_block_range); + Ok(()) + } + + // FIXME: Code duplication. + pub(in super::super::super::super) fn add_byte_values_preserved_postcondition( + &mut self, + ) -> SpannedEncodingResult<()> { + use vir_low::macros::*; + let element_size = self + .inner + .inner + .lowerer + .encode_type_size_expression2(self.inner.inner.ty, self.inner.inner.type_decl)?; + let length = self.inner.length()?; + let whole_size = self + .inner + .inner + .lowerer + .encode_type_size_expression_repetitions( + self.inner.inner.ty, + self.inner.inner.type_decl, + length, + self.inner.inner.position, + )?; + let size_type = self.inner.inner.lowerer.size_type_mid()?; + var_decls! { + index: Int, + byte_index: Int + } + let address: vir_low::Expression = self.inner.address.clone().into(); + let element_address = self.inner.inner.lowerer.address_offset( + element_size.clone(), + address.clone(), + index.clone().into(), + self.inner.inner.position, + )?; + let trigger_element_address = self + .inner + .inner + .lowerer + .trigger_expression(element_address.clone(), self.inner.inner.position)?; + // let predicate = + // self.encode_memory_block_acc(element_address.clone(), size.clone(), position)?; + let start_index = self.inner.inner.lowerer.obtain_constant_value( + &size_type, + self.inner.start_index.clone().into(), + self.inner.inner.position, + )?; + let end_index = self.inner.inner.lowerer.obtain_constant_value( + &size_type, + self.inner.end_index.clone().into(), + self.inner.inner.position, + )?; + let element_bytes = self + .inner + .inner + .lowerer + .encode_memory_block_bytes_expression(element_address, element_size.clone())?; + let whole_bytes = self + .inner + .inner + .lowerer + .encode_memory_block_bytes_expression(address, whole_size)?; + let read_element_byte = self.inner.inner.lowerer.encode_read_byte_expression_int( + element_bytes, + byte_index.clone().into(), + self.inner.inner.position, + )?; + let block_size = self.inner.inner.lowerer.obtain_constant_value( + &size_type, + element_size.clone(), + self.inner.inner.position, + )?; + let block_start_index = vir_low::Expression::multiply(block_size, index.clone().into()); + let whole_byte_index = + vir_low::Expression::add(block_start_index, byte_index.clone().into()); + // let whole_byte_index = self.inner.inner.lowerer.create_domain_func_app( + // "Arithmetic", + // "mul_add", + // vec![block_size, index.clone().into(), byte_index.clone().into()], + // vir_low::Type::Int, + // self.inner.inner.position, + // )?; + let read_whole_byte = self.inner.inner.lowerer.encode_read_byte_expression_int( + vir_low::Expression::labelled_old(None, whole_bytes, self.inner.inner.position), + whole_byte_index, + self.inner.inner.position, + )?; + let element_size_int = self.inner.inner.lowerer.obtain_constant_value( + &size_type, + element_size, + self.inner.inner.position, + )?; + let body = expr!( + [trigger_element_address] && + (((([start_index] <= index) && (index < [end_index])) && + (([0.into()] <= byte_index) && (byte_index < [element_size_int]))) ==> + ([read_element_byte.clone()] == [read_whole_byte])) + ); + let trigger = read_element_byte; + let expression = vir_low::Expression::forall( + vec![index, byte_index], + vec![vir_low::Trigger::new(vec![trigger])], + body, + ); + self.add_postcondition(expression); + Ok(()) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/memory_block_range_split_join_common.rs b/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/memory_block_range_split_join_common.rs new file mode 100644 index 00000000000..ba0065af7f9 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/memory_block_range_split_join_common.rs @@ -0,0 +1,256 @@ +use super::common::{BuiltinMethodBuilder, BuiltinMethodBuilderMethods}; +use crate::encoder::{ + errors::{BuiltinMethodKind, SpannedEncodingResult}, + middle::core_proof::{ + addresses::AddressesInterface, lowerer::Lowerer, + predicates::PredicatesMemoryBlockInterface, snapshots::SnapshotValuesInterface, + type_layouts::TypeLayoutsInterface, + }, +}; +use vir_crate::{ + low::{self as vir_low}, + middle as vir_mid, +}; + +pub(in super::super) struct MemoryBlockRangeSplitJoinMethodBuilder<'l, 'p, 'v, 'tcx> { + pub(super) inner: BuiltinMethodBuilder<'l, 'p, 'v, 'tcx>, + pub(super) address: vir_low::VariableDecl, + pub(super) start_index: vir_low::VariableDecl, + pub(super) end_index: vir_low::VariableDecl, +} + +impl<'l, 'p, 'v, 'tcx> BuiltinMethodBuilderMethods<'l, 'p, 'v, 'tcx> + for MemoryBlockRangeSplitJoinMethodBuilder<'l, 'p, 'v, 'tcx> +{ + fn inner(&mut self) -> &mut BuiltinMethodBuilder<'l, 'p, 'v, 'tcx> { + &mut self.inner + } +} + +pub(in super::super) trait BuiltinMethodSplitJoinBuilderMethods<'l, 'p, 'v, 'tcx>: + Sized + BuiltinMethodBuilderMethods<'l, 'p, 'v, 'tcx> +where + 'p: 'l, + 'v: 'p, + 'tcx: 'v, +{ +} + +impl<'l, 'p, 'v, 'tcx> MemoryBlockRangeSplitJoinMethodBuilder<'l, 'p, 'v, 'tcx> { + pub(in super::super) fn new( + lowerer: &'l mut Lowerer<'p, 'v, 'tcx>, + kind: vir_low::MethodKind, + method_name: &'l str, + ty: &'l vir_mid::Type, + type_decl: &'l vir_mid::TypeDecl, + error_kind: BuiltinMethodKind, + ) -> SpannedEncodingResult { + let address = vir_low::VariableDecl::new("address", lowerer.address_type()?); + let size_type = lowerer.size_type()?; + let start_index = vir_low::VariableDecl::new("start_index", size_type.clone()); + let end_index = vir_low::VariableDecl::new("end_index", size_type); + let inner = + BuiltinMethodBuilder::new(lowerer, kind, method_name, ty, type_decl, error_kind)?; + Ok(Self { + inner, + address, + start_index, + end_index, + }) + } + + pub(in super::super) fn build(self) -> vir_low::MethodDecl { + self.inner.build() + } + + // pub(in super::super) fn address(&self) -> vir_low::Expression { + // self.address.clone().into() + // } + + pub(in super::super) fn create_parameters(&mut self) -> SpannedEncodingResult<()> { + self.inner.parameters.push(self.address.clone()); + self.inner.parameters.push(self.start_index.clone()); + self.inner.parameters.push(self.end_index.clone()); + Ok(()) + } + + pub(in super::super) fn length(&mut self) -> SpannedEncodingResult { + let size_type = self.inner.lowerer.size_type_mid()?; + self.inner.lowerer.construct_binary_op_snapshot( + vir_mid::BinaryOpKind::Sub, + &size_type, + &size_type, + self.end_index.clone().into(), + self.start_index.clone().into(), + self.inner.position, + ) + } + + // pub(in super::super) fn add_permission_amount_positive_precondition( + // &mut self, + // ) -> SpannedEncodingResult<()> { + // let expression = self + // .inner + // .create_permission_amount_positive(&self.permission_amount)?; + // self.add_precondition(expression); + // Ok(()) + // } + + pub(in super::super) fn create_whole_block_acc( + &mut self, + ) -> SpannedEncodingResult { + // self.create_memory_block(self.address.clone().into()) + use vir_low::macros::*; + let length = self.length()?; + let inner = self.inner(); + let size_of = inner.lowerer.encode_type_size_expression_repetitions( + inner.ty, + inner.type_decl, + length, + inner.position, + )?; + let address = &self.address; + Ok(expr! { + acc(MemoryBlock(address, [size_of])) + }) + } + + pub(in super::super) fn create_memory_block_range_acc( + &mut self, + ) -> SpannedEncodingResult { + // self.create_memory_block(self.address.clone().into()) + let size_of = self + .inner + .lowerer + .encode_type_size_expression2(self.inner.ty, self.inner.type_decl)?; + self.inner.lowerer.encode_memory_block_range_acc( + self.address.clone().into(), + size_of, + self.start_index.clone().into(), + self.end_index.clone().into(), + self.inner.position, + ) + } + + // pub(in super::super) fn padding_size(&mut self) -> SpannedEncodingResult { + // self.inner + // .lowerer + // .encode_type_padding_size_expression(self.inner.ty) + // } + + // pub(in super::super) fn create_padding_memory_block_acc( + // &mut self, + // ) -> SpannedEncodingResult { + // use vir_low::macros::*; + // let address = self.address.clone().into(); + // let padding_size = self.padding_size()?; + // let permission_amount = self.permission_amount.clone().into(); + // let expression = expr! { + // acc(MemoryBlock([address], [padding_size]), [permission_amount]) + // }; + // Ok(expression) + // } + + // pub(in super::super) fn create_field_memory_block_acc( + // &mut self, + // field: &vir_mid::FieldDecl, + // ) -> SpannedEncodingResult { + // use vir_low::macros::*; + // let field_address = self.inner.lowerer.encode_field_address( + // self.inner.ty, + // field, + // self.address.clone().into(), + // self.inner.position, + // )?; + // let field_size_of = self + // .inner + // .lowerer + // .encode_type_size_expression2(&field.ty, &field.ty)?; + // let permission_amount = self.permission_amount.clone().into(); + // let field_block = expr! { + // acc(MemoryBlock([field_address], [field_size_of]), [permission_amount]) + // }; + // Ok(field_block) + // } + + // pub(in super::super) fn create_discriminant_acc( + // &mut self, + // decl: &vir_mid::type_decl::Enum, + // ) -> SpannedEncodingResult { + // use vir_low::macros::*; + // let discriminant_size_of = self + // .inner + // .lowerer + // .encode_type_size_expression2(&decl.discriminant_type, &decl.discriminant_type)?; + // let discriminant_field = decl.discriminant_field(); + // let discriminant_address = self.inner.lowerer.encode_field_address( + // self.inner.ty, + // &discriminant_field, + // self.address.clone().into(), + // self.inner.position, + // )?; + // let discriminant_block = expr! { + // acc(MemoryBlock([discriminant_address], [discriminant_size_of])) + // }; + // Ok(discriminant_block) + // } + + // pub(in super::super) fn create_variant_memory_block_acc( + // &mut self, + // discriminant_value: vir_mid::DiscriminantValue, + // variant: &vir_mid::type_decl::Struct, + // ) -> SpannedEncodingResult { + // use vir_low::macros::*; + // let variant_index = variant.name.clone().into(); + // let variant_address = self.inner.lowerer.encode_enum_variant_address( + // self.inner.ty, + // &variant_index, + // self.address.clone().into(), + // Default::default(), + // )?; + // let variant_type = self.inner.ty.clone().variant(variant_index); + // let variant_size_of = self + // .inner + // .lowerer + // // .encode_type_size_expression(&variant_type)?; + // // FIXME: This is probably wrong: test enums containing arrays. + // .encode_type_size_expression2(&variant_type, &variant_type)?; + // let discriminant = self.discriminant.as_ref().unwrap().clone().into(); + // let expression = expr! { + // ([discriminant] == [discriminant_value.into()]) ==> + // (acc(MemoryBlock([variant_address], [variant_size_of]))) + // }; + // Ok(expression) + // } + + // pub(in super::super) fn create_field_to_bytes_equality( + // &mut self, + // field: &vir_mid::FieldDecl, + // ) -> SpannedEncodingResult { + // use vir_low::macros::*; + // let address = self.address(); + // let inner = self.inner(); + // inner.lowerer.encode_snapshot_to_bytes_function(inner.ty)?; + // let field_address = + // inner + // .lowerer + // .encode_field_address(inner.ty, field, address, inner.position)?; + // let field_size_of = inner + // .lowerer + // .encode_type_size_expression2(&field.ty, &field.ty)?; + // let memory_block_field_bytes = inner + // .lowerer + // .encode_memory_block_bytes_expression(field_address, field_size_of)?; + // let snapshot = var! { snapshot: {inner.ty.to_snapshot(inner.lowerer)?} }.into(); + // let field_snapshot = inner.lowerer.obtain_struct_field_snapshot( + // inner.ty, + // field, + // snapshot, + // inner.position, + // )?; + // let to_bytes = ty! { Bytes }; + // Ok(expr! { + // (([memory_block_field_bytes])) == (Snap<(&field.ty)>::to_bytes([field_snapshot])) + // }) + // } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/memory_block_split_join_common.rs b/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/memory_block_split_join_common.rs index b3710b33b4e..922eabfd05e 100644 --- a/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/memory_block_split_join_common.rs +++ b/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/memory_block_split_join_common.rs @@ -51,10 +51,7 @@ impl<'l, 'p, 'v, 'tcx> MemoryBlockSplitJoinMethodBuilder<'l, 'p, 'v, 'tcx> { let permission_amount = vir_low::VariableDecl::new("permission_amount", vir_low::Type::Perm); let discriminant = if ty.has_variants() { - Some(vir_low::VariableDecl::new( - "discriminant", - vir_low::Type::Int, - )) + Some(vir_low::VariableDecl::discriminant_variable()) } else { None }; diff --git a/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/mod.rs b/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/mod.rs index 2dcd5ae34cc..6238132d5a6 100644 --- a/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/mod.rs +++ b/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/mod.rs @@ -5,9 +5,13 @@ pub(super) mod duplicate_frac_ref; pub(super) mod memory_block_copy; pub(super) mod memory_block_into; pub(super) mod memory_block_join; +pub(super) mod memory_block_range_join; +pub(super) mod memory_block_range_split_join_common; pub(super) mod memory_block_split; +pub(super) mod memory_block_range_split; pub(super) mod memory_block_split_join_common; pub(super) mod move_copy_place_common; pub(super) mod move_place; +pub(super) mod restore_raw_borrowed; pub(super) mod write_address_constant; pub(super) mod write_place_constant; diff --git a/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/move_copy_place_common.rs b/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/move_copy_place_common.rs index 55d21ef39af..e699f7a69a1 100644 --- a/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/move_copy_place_common.rs +++ b/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/move_copy_place_common.rs @@ -7,9 +7,11 @@ use crate::encoder::{ middle::core_proof::{ addresses::AddressesInterface, builtin_methods::{calls::interface::CallContext, BuiltinMethodsInterface}, + lifetimes::LifetimesInterface, lowerer::Lowerer, places::PlacesInterface, - predicates::OwnedNonAliasedUseBuilder, + predicates::PredicatesOwnedInterface, + references::ReferencesInterface, snapshots::{IntoSnapshot, SnapshotValidityInterface}, }, }; @@ -21,9 +23,9 @@ use vir_crate::{ pub(in super::super::super::super) struct MoveCopyPlaceMethodBuilder<'l, 'p, 'v, 'tcx> { pub(super) inner: BuiltinMethodBuilder<'l, 'p, 'v, 'tcx>, pub(super) target_place: vir_low::VariableDecl, - pub(super) target_root_address: vir_low::VariableDecl, + pub(super) target_address: vir_low::VariableDecl, pub(super) source_place: vir_low::VariableDecl, - pub(super) source_root_address: vir_low::VariableDecl, + pub(super) source_address: vir_low::VariableDecl, pub(super) source_snapshot: vir_low::VariableDecl, } @@ -44,12 +46,10 @@ impl<'l, 'p, 'v, 'tcx> MoveCopyPlaceMethodBuilder<'l, 'p, 'v, 'tcx> { type_decl: &'l vir_mid::TypeDecl, error_kind: BuiltinMethodKind, ) -> SpannedEncodingResult { - let target_place = vir_low::VariableDecl::new("target_place", lowerer.place_type()?); - let target_root_address = - vir_low::VariableDecl::new("target_root_address", lowerer.address_type()?); - let source_place = vir_low::VariableDecl::new("source_place", lowerer.place_type()?); - let source_root_address = - vir_low::VariableDecl::new("source_root_address", lowerer.address_type()?); + let target_place = vir_low::VariableDecl::new("target_place", lowerer.place_option_type()?); + let target_address = vir_low::VariableDecl::new("target_address", lowerer.address_type()?); + let source_place = vir_low::VariableDecl::new("source_place", lowerer.place_option_type()?); + let source_address = vir_low::VariableDecl::new("source_address", lowerer.address_type()?); let source_snapshot = vir_low::VariableDecl::new("source_snapshot", ty.to_snapshot(lowerer)?); let inner = @@ -57,9 +57,9 @@ impl<'l, 'p, 'v, 'tcx> MoveCopyPlaceMethodBuilder<'l, 'p, 'v, 'tcx> { Ok(Self { inner, target_place, - target_root_address, + target_address, source_place, - source_root_address, + source_address, source_snapshot, }) } @@ -71,44 +71,116 @@ impl<'l, 'p, 'v, 'tcx> MoveCopyPlaceMethodBuilder<'l, 'p, 'v, 'tcx> { pub(in super::super::super::super) fn create_target_memory_block( &mut self, ) -> SpannedEncodingResult { - self.create_memory_block( - self.compute_address(&self.target_place, &self.target_root_address), - ) + self.create_memory_block(self.target_address.clone().into()) } pub(in super::super::super::super) fn create_source_owned( &mut self, + exclude_snapshot_equality: bool, + permission_amount: Option, ) -> SpannedEncodingResult { - let mut builder = OwnedNonAliasedUseBuilder::new( - self.inner.lowerer, - CallContext::BuiltinMethod, - self.inner.ty, - self.inner.type_decl, - self.source_place.clone().into(), - self.source_root_address.clone().into(), - self.source_snapshot.clone().into(), - )?; - builder.add_lifetime_arguments()?; - builder.add_const_arguments()?; - Ok(builder.build()) + if exclude_snapshot_equality { + self.inner.lowerer.owned_non_aliased( + CallContext::BuiltinMethod, + self.inner.ty, + self.inner.type_decl, + self.source_place.clone().into(), + self.source_address.clone().into(), + permission_amount, + self.inner.position, + ) + } else { + self.inner.lowerer.owned_non_aliased_with_snapshot( + CallContext::BuiltinMethod, + self.inner.ty, + self.inner.type_decl, + self.source_place.clone().into(), + self.source_address.clone().into(), + self.source_snapshot.clone().into(), + permission_amount, + self.inner.position, + ) + } + // let predicate = self.inner.lowerer.owned_non_aliased( + // CallContext::BuiltinMethod, + // self.inner.ty, + // self.inner.type_decl, + // self.source_place.clone().into(), + // self.source_address.clone().into(), + // permission_amount, + // self.inner.position, + // )?; + // let expression = if exclude_snapshot_equality { + // predicate + // } else { + // let snap_call = self.inner.lowerer.owned_non_aliased_snap( + // CallContext::BuiltinMethod, + // self.inner.ty, + // self.inner.type_decl, + // self.source_place.clone().into(), + // self.source_address.clone().into(), + // self.inner.position, + // )?; + // vir_low::Expression::and( + // predicate, + // vir_low::Expression::equals(self.source_snapshot.clone().into(), snap_call), + // ) + // }; + // Ok(expression) } // FIXME: Remove duplicates with other builders. pub(in super::super::super::super) fn create_target_owned( &mut self, + exclude_snapshot_equality: bool, ) -> SpannedEncodingResult { - let mut builder = OwnedNonAliasedUseBuilder::new( - self.inner.lowerer, - CallContext::BuiltinMethod, - self.inner.ty, - self.inner.type_decl, - self.target_place.clone().into(), - self.target_root_address.clone().into(), - self.source_snapshot.clone().into(), - )?; - builder.add_lifetime_arguments()?; - builder.add_const_arguments()?; - Ok(builder.build()) + if exclude_snapshot_equality { + self.inner.lowerer.owned_non_aliased_full_vars( + CallContext::BuiltinMethod, + self.inner.ty, + self.inner.type_decl, + &self.target_place, + &self.target_address, + self.inner.position, + ) + } else { + self.inner + .lowerer + .owned_non_aliased_full_vars_with_snapshot( + CallContext::BuiltinMethod, + self.inner.ty, + self.inner.type_decl, + &self.target_place, + &self.target_address, + &self.source_snapshot, + self.inner.position, + ) + } + // let predicate = self.inner.lowerer.owned_non_aliased_full_vars( + // CallContext::BuiltinMethod, + // self.inner.ty, + // self.inner.type_decl, + // &self.target_place, + // &self.target_address, + // self.inner.position, + // )?; + // let expression = if exclude_snapshot_equality { + // predicate + // } else { + // let snap_call = self.inner.lowerer.owned_non_aliased_snap( + // CallContext::BuiltinMethod, + // self.inner.ty, + // self.inner.type_decl, + // self.target_place.clone().into(), + // self.target_address.clone().into(), + // self.inner.position, + // )?; + // vir_low::Expression::and( + // predicate, + // vir_low::Expression::equals(self.source_snapshot.clone().into(), snap_call), + // ) + // }; + // Ok(expression) } // FIXME: Remove duplicate with add_source_validity_precondition @@ -130,8 +202,8 @@ impl<'l, 'p, 'v, 'tcx> MoveCopyPlaceMethodBuilder<'l, 'p, 'v, 'tcx> { self.inner .lowerer .encode_memory_block_copy_method(self.inner.ty)?; - let source_address = self.compute_address(&self.source_place, &self.source_root_address); - let target_address = self.compute_address(&self.target_place, &self.target_root_address); + // let source_address = self.compute_address(&self.source_place, &self.source_address); + // let target_address = self.compute_address(&self.target_place, &self.target_address); let mut builder = BuiltinMethodCallBuilder::new( self.inner.lowerer, CallContext::BuiltinMethod, @@ -140,8 +212,8 @@ impl<'l, 'p, 'v, 'tcx> MoveCopyPlaceMethodBuilder<'l, 'p, 'v, 'tcx> { self.inner.type_decl, self.inner.position, )?; - builder.add_argument(source_address); - builder.add_argument(target_address); + builder.add_argument(self.source_address.clone().into()); + builder.add_argument(self.target_address.clone().into()); if let Some(source_permission_amount) = source_permission_amount { builder.add_argument(source_permission_amount); } else { @@ -159,7 +231,7 @@ impl<'l, 'p, 'v, 'tcx> MoveCopyPlaceMethodBuilder<'l, 'p, 'v, 'tcx> { self.inner .lowerer .encode_memory_block_split_method(self.inner.ty)?; - let target_address = self.compute_address(&self.target_place, &self.target_root_address); + // let target_address = self.compute_address(&self.target_place, &self.target_address); let discriminant_call = self.inner.discriminant(&self.source_snapshot)?; let mut builder = BuiltinMethodCallBuilder::new( self.inner.lowerer, @@ -169,7 +241,7 @@ impl<'l, 'p, 'v, 'tcx> MoveCopyPlaceMethodBuilder<'l, 'p, 'v, 'tcx> { self.inner.type_decl, self.inner.position, )?; - builder.add_argument(target_address); + builder.add_argument(self.target_address.clone().into()); builder.add_full_permission_argument(); if let Some(discriminant_call) = discriminant_call { builder.add_argument(discriminant_call); @@ -185,7 +257,7 @@ impl<'l, 'p, 'v, 'tcx> MoveCopyPlaceMethodBuilder<'l, 'p, 'v, 'tcx> { self.inner .lowerer .encode_memory_block_join_method(self.inner.ty)?; - let source_address = self.compute_address(&self.source_place, &self.source_root_address); + // let source_address = self.compute_address(&self.source_place, &self.source_address); let discriminant_call = self.inner.discriminant(&self.source_snapshot)?; let mut builder = BuiltinMethodCallBuilder::new( self.inner.lowerer, @@ -195,7 +267,7 @@ impl<'l, 'p, 'v, 'tcx> MoveCopyPlaceMethodBuilder<'l, 'p, 'v, 'tcx> { self.inner.type_decl, self.inner.position, )?; - builder.add_argument(source_address); + builder.add_argument(self.source_address.clone().into()); builder.add_full_permission_argument(); if let Some(discriminant_call) = discriminant_call { builder.add_argument(discriminant_call); @@ -204,4 +276,53 @@ impl<'l, 'p, 'v, 'tcx> MoveCopyPlaceMethodBuilder<'l, 'p, 'v, 'tcx> { self.add_statement(statement); Ok(()) } + + pub(in super::super::super::super) fn duplicate_frac_ref( + &mut self, + lifetime: &vir_mid::ty::LifetimeConst, + source_permission_amount: Option, + ) -> SpannedEncodingResult<()> { + let lifetime_alive = self + .inner + .lowerer + .encode_lifetime_const_into_pure_is_alive_variable(lifetime)?; + self.add_precondition(lifetime_alive.into()); + self.inner + .lowerer + .encode_duplicate_frac_ref_method(self.inner.ty)?; + let address = self.inner.lowerer.reference_address( + self.inner.ty, + self.source_snapshot.clone().into(), + self.inner.position, + )?; + let mut builder = BuiltinMethodCallBuilder::new( + self.inner.lowerer, + CallContext::BuiltinMethod, + "duplicate_frac_ref", + self.inner.ty, + self.inner.type_decl, + self.inner.position, + )?; + builder.add_argument(self.target_place.clone().into()); + builder.add_argument(self.source_place.clone().into()); + // builder.add_argument(self.source_snapshot.clone().into()); + builder.add_argument(address); + if let Some(source_permission_amount) = source_permission_amount { + builder.add_argument(source_permission_amount); + } else { + builder.add_argument(vir_low::Expression::full_permission()); + } + builder.add_lifetime_arguments()?; + builder.add_const_arguments()?; + let statement = builder.build(); + // let guarded_statement = vir_low::Statement::conditional( + // lifetime_alive.into(), + // vec![statement], + // Vec::new(), + // self.inner.position, + // ); + // self.add_statement(guarded_statement); + self.add_statement(statement); + Ok(()) + } } diff --git a/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/move_place.rs b/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/move_place.rs index 79778481385..17a8f91d57a 100644 --- a/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/move_place.rs +++ b/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/move_place.rs @@ -6,15 +6,20 @@ use super::{ use crate::encoder::{ errors::{BuiltinMethodKind, SpannedEncodingResult}, middle::core_proof::{ + addresses::AddressesInterface, builtin_methods::{ calls::interface::CallContext, BuiltinMethodCallsInterface, BuiltinMethodsInterface, }, + lifetimes::LifetimesInterface, lowerer::Lowerer, places::PlacesInterface, + predicates::PredicatesOwnedInterface, + references::ReferencesInterface, snapshots::SnapshotValuesInterface, }, }; use vir_crate::{ + common::expression::UnaryOperationHelpers, low::{self as vir_low}, middle as vir_mid, }; @@ -59,7 +64,7 @@ impl<'l, 'p, 'v, 'tcx> MovePlaceMethodBuilder<'l, 'p, 'v, 'tcx> { self.inner .inner .parameters - .push(self.inner.target_root_address.clone()); + .push(self.inner.target_address.clone()); self.inner .inner .parameters @@ -67,7 +72,7 @@ impl<'l, 'p, 'v, 'tcx> MovePlaceMethodBuilder<'l, 'p, 'v, 'tcx> { self.inner .inner .parameters - .push(self.inner.source_root_address.clone()); + .push(self.inner.source_address.clone()); self.inner .inner .parameters @@ -91,20 +96,23 @@ impl<'l, 'p, 'v, 'tcx> MovePlaceMethodBuilder<'l, 'p, 'v, 'tcx> { &mut self, ) -> SpannedEncodingResult { self.create_memory_block( - self.compute_address(&self.inner.source_place, &self.inner.source_root_address), + self.inner.source_address.clone().into(), // self.compute_address(&self.inner.source_place, &self.inner.source_address), ) } pub(in super::super::super::super) fn create_source_owned( &mut self, + exclude_snapshot_equality: bool, ) -> SpannedEncodingResult { - self.inner.create_source_owned() + self.inner + .create_source_owned(exclude_snapshot_equality, None) } pub(in super::super::super::super) fn create_target_owned( &mut self, + exclude_snapshot_equality: bool, ) -> SpannedEncodingResult { - self.inner.create_target_owned() + self.inner.create_target_owned(exclude_snapshot_equality) } pub(in super::super::super::super) fn add_target_validity_postcondition( @@ -130,7 +138,7 @@ impl<'l, 'p, 'v, 'tcx> MovePlaceMethodBuilder<'l, 'p, 'v, 'tcx> { // .lowerer // .encode_type_size_expression2(self.inner.inner.ty, self.inner.inner.type_decl)?; // let source_address = - // self.compute_address(&self.inner.source_place, &self.inner.source_root_address); + // self.compute_address(&self.inner.source_place, &self.inner.source_address); // let bytes = self // .inner // .inner @@ -173,12 +181,24 @@ impl<'l, 'p, 'v, 'tcx> MovePlaceMethodBuilder<'l, 'p, 'v, 'tcx> { self.inner.source_place.clone().into(), self.inner.inner.position, )?; + let source_field_address = self.inner.inner.lowerer.encode_field_address( + self.inner.inner.ty, + field, + self.inner.source_address.clone().into(), + self.inner.inner.position, + )?; let target_field_place = self.inner.inner.lowerer.encode_field_place( self.inner.inner.ty, field, self.inner.target_place.clone().into(), self.inner.inner.position, )?; + let target_field_address = self.inner.inner.lowerer.encode_field_address( + self.inner.inner.ty, + field, + self.inner.target_address.clone().into(), + self.inner.inner.position, + )?; let source_field_snapshot = self.inner.inner.lowerer.obtain_struct_field_snapshot( self.inner.inner.ty, field, @@ -191,9 +211,9 @@ impl<'l, 'p, 'v, 'tcx> MovePlaceMethodBuilder<'l, 'p, 'v, 'tcx> { &field.ty, self.inner.inner.position, target_field_place, - self.inner.target_root_address.clone().into(), + target_field_address, source_field_place, - self.inner.source_root_address.clone().into(), + source_field_address, source_field_snapshot, )?; self.add_statement(statement); @@ -221,12 +241,24 @@ impl<'l, 'p, 'v, 'tcx> MovePlaceMethodBuilder<'l, 'p, 'v, 'tcx> { self.inner.target_place.clone().into(), self.inner.inner.position, )?; + let target_variant_address = self.inner.inner.lowerer.encode_enum_variant_address( + self.inner.inner.ty, + &variant_index, + self.inner.target_address.clone().into(), + self.inner.inner.position, + )?; let source_variant_place = self.inner.inner.lowerer.encode_enum_variant_place( self.inner.inner.ty, &variant_index, self.inner.source_place.clone().into(), self.inner.inner.position, )?; + let source_variant_address = self.inner.inner.lowerer.encode_enum_variant_address( + self.inner.inner.ty, + &variant_index, + self.inner.source_address.clone().into(), + self.inner.inner.position, + )?; let source_variant_snapshot = self.inner.inner.lowerer.obtain_enum_variant_snapshot( self.inner.inner.ty, &variant_index, @@ -248,9 +280,9 @@ impl<'l, 'p, 'v, 'tcx> MovePlaceMethodBuilder<'l, 'p, 'v, 'tcx> { )?; builder.set_guard(condition); builder.add_argument(target_variant_place); - builder.add_argument(self.inner.target_root_address.clone().into()); + builder.add_argument(target_variant_address); builder.add_argument(source_variant_place); - builder.add_argument(self.inner.source_root_address.clone().into()); + builder.add_argument(source_variant_address); builder.add_argument(source_variant_snapshot); builder.add_lifetime_arguments()?; builder.add_const_arguments()?; @@ -273,19 +305,30 @@ impl<'l, 'p, 'v, 'tcx> MovePlaceMethodBuilder<'l, 'p, 'v, 'tcx> { self.inner.inner.ty, self.inner.inner.position, )?; - let target_discriminant_place = self.inner.inner.lowerer.encode_field_place( self.inner.inner.ty, &discriminant_field, self.inner.target_place.clone().into(), self.inner.inner.position, )?; + let target_discriminant_address = self.inner.inner.lowerer.encode_field_address( + self.inner.inner.ty, + &discriminant_field, + self.inner.target_address.clone().into(), + self.inner.inner.position, + )?; let source_discriminant_place = self.inner.inner.lowerer.encode_field_place( self.inner.inner.ty, &discriminant_field, self.inner.source_place.clone().into(), self.inner.inner.position, )?; + let source_discriminant_address = self.inner.inner.lowerer.encode_field_address( + self.inner.inner.ty, + &discriminant_field, + self.inner.source_address.clone().into(), + self.inner.inner.position, + )?; let source_discriminant_snashot = self.inner.inner.lowerer.construct_constant_snapshot( &decl.discriminant_type, discriminant_call, @@ -300,9 +343,9 @@ impl<'l, 'p, 'v, 'tcx> MovePlaceMethodBuilder<'l, 'p, 'v, 'tcx> { self.inner.inner.position, )?; builder.add_argument(target_discriminant_place); - builder.add_argument(self.inner.target_root_address.clone().into()); + builder.add_argument(target_discriminant_address); builder.add_argument(source_discriminant_place); - builder.add_argument(self.inner.source_root_address.clone().into()); + builder.add_argument(source_discriminant_address); builder.add_argument(source_discriminant_snashot); builder.add_lifetime_arguments()?; builder.add_const_arguments()?; @@ -342,28 +385,95 @@ impl<'l, 'p, 'v, 'tcx> MovePlaceMethodBuilder<'l, 'p, 'v, 'tcx> { Ok(()) } - pub(in super::super::super::super) fn duplicate_frac_ref( + pub(in super::super::super::super) fn add_dead_lifetime_hack( &mut self, + lifetime: &vir_mid::ty::LifetimeConst, ) -> SpannedEncodingResult<()> { - self.inner + use vir_low::macros::*; + let lifetime_alive = self + .inner .inner .lowerer - .encode_duplicate_frac_ref_method(self.inner.inner.ty)?; - let mut builder = BuiltinMethodCallBuilder::new( - self.inner.inner.lowerer, + .encode_lifetime_const_into_pure_is_alive_variable(lifetime)?; + let guard = vir_low::Expression::not(lifetime_alive.into()); + let source_current_snapshot = self.inner.inner.lowerer.reference_target_current_snapshot( + self.inner.inner.ty, + self.inner.source_snapshot.clone().into(), + self.inner.inner.position, + )?; + let source_final_snapshot = self.inner.inner.lowerer.reference_target_final_snapshot( + self.inner.inner.ty, + self.inner.source_snapshot.clone().into(), + self.inner.inner.position, + )?; + let target_snapshot = self.inner.inner.lowerer.owned_non_aliased_snap( CallContext::BuiltinMethod, - "duplicate_frac_ref", self.inner.inner.ty, self.inner.inner.type_decl, + self.inner.target_place.clone().into(), + self.inner.target_address.clone().into(), self.inner.inner.position, )?; - builder.add_argument(self.inner.target_place.clone().into()); - builder.add_argument(self.inner.source_place.clone().into()); - builder.add_argument(self.inner.source_snapshot.clone().into()); - builder.add_lifetime_arguments()?; - builder.add_const_arguments()?; - let statement = builder.build(); + let target_current_snapshot = self.inner.inner.lowerer.reference_target_current_snapshot( + self.inner.inner.ty, + target_snapshot.clone(), + self.inner.inner.position, + )?; + let target_final_snapshot = self.inner.inner.lowerer.reference_target_final_snapshot( + self.inner.inner.ty, + target_snapshot, + self.inner.inner.position, + )?; + let body = vec![ + vir_low::Statement::comment( + "FIXME: This is a hack. Because the lifetime is dead, the reference \ + is dangling and there is no predicate that would witness that \ + the value of the dereference is the source of the dereference. \ + This is also the reason why it is sound just to assume that the \ + two are equal. A proper solution should use a custom equality function \ + that equates the targets only if lifetimes are alive." + .to_string(), + ), + stmtp! { self.inner.inner.position => + assume ([source_current_snapshot] == [target_current_snapshot]) + }, + stmtp! { self.inner.inner.position => + assume ([source_final_snapshot] == [target_final_snapshot]) + }, + // assume destructor$Snap$ref$Unique$slice$struct$m_T1$$$target_current(source_snapshot) == destructor$Snap$ref$Unique$slice$struct$m_T1$$$target_current(snap_owned_non_aliased$ref$Unique$slice$struct$m_T1$(target_place, target_address, lft_early_bound_0$alive, lft_early_bound_0)) + + // assume destructor$Snap$ref$Unique$slice$struct$m_T1$$$target_final(source_snapshot) == destructor$Snap$ref$Unique$slice$struct$m_T1$$$target_final(snap_owned_non_aliased$ref$Unique$slice$struct$m_T1$(target_place, target_address, lft_early_bound_0$alive, lft_early_bound_0)) + ]; + let statement = + vir_low::Statement::conditional(guard, body, Vec::new(), self.inner.inner.position); self.add_statement(statement); Ok(()) } + + pub(in super::super::super::super) fn duplicate_frac_ref( + &mut self, + lifetime: &vir_mid::ty::LifetimeConst, + ) -> SpannedEncodingResult<()> { + self.inner.duplicate_frac_ref(lifetime, None) + // self.inner + // .inner + // .lowerer + // .encode_duplicate_frac_ref_method(self.inner.inner.ty)?; + // let mut builder = BuiltinMethodCallBuilder::new( + // self.inner.inner.lowerer, + // CallContext::BuiltinMethod, + // "duplicate_frac_ref", + // self.inner.inner.ty, + // self.inner.inner.type_decl, + // self.inner.inner.position, + // )?; + // builder.add_argument(self.inner.target_place.clone().into()); + // builder.add_argument(self.inner.source_place.clone().into()); + // builder.add_argument(self.inner.source_snapshot.clone().into()); + // builder.add_lifetime_arguments()?; + // builder.add_const_arguments()?; + // let statement = builder.build(); + // self.add_statement(statement); + // Ok(()) + } } diff --git a/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/restore_raw_borrowed.rs b/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/restore_raw_borrowed.rs new file mode 100644 index 00000000000..5dc3022fb5f --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/restore_raw_borrowed.rs @@ -0,0 +1,123 @@ +use super::common::{BuiltinMethodBuilder, BuiltinMethodBuilderMethods}; +use crate::encoder::{ + errors::{BuiltinMethodKind, SpannedEncodingResult}, + middle::core_proof::{ + addresses::AddressesInterface, + builtin_methods::CallContext, + lowerer::Lowerer, + places::PlacesInterface, + predicates::{PredicatesOwnedInterface, RestorationInterface}, + snapshots::IntoSnapshot, + }, +}; +use vir_crate::{ + low::{self as vir_low}, + middle as vir_mid, +}; + +pub(in super::super::super::super) struct RestoreRawBorrowedMethodBuilder<'l, 'p, 'v, 'tcx> { + inner: BuiltinMethodBuilder<'l, 'p, 'v, 'tcx>, + borrowing_address: vir_low::VariableDecl, + restored_place: vir_low::VariableDecl, + // restored_root_address: vir_low::VariableDecl, + snapshot: vir_low::VariableDecl, +} + +impl<'l, 'p, 'v, 'tcx> BuiltinMethodBuilderMethods<'l, 'p, 'v, 'tcx> + for RestoreRawBorrowedMethodBuilder<'l, 'p, 'v, 'tcx> +{ + fn inner(&mut self) -> &mut BuiltinMethodBuilder<'l, 'p, 'v, 'tcx> { + &mut self.inner + } +} + +impl<'l, 'p, 'v, 'tcx> RestoreRawBorrowedMethodBuilder<'l, 'p, 'v, 'tcx> { + pub(in super::super::super::super) fn new( + lowerer: &'l mut Lowerer<'p, 'v, 'tcx>, + kind: vir_low::MethodKind, + method_name: &'l str, + ty: &'l vir_mid::Type, + type_decl: &'l vir_mid::TypeDecl, + error_kind: BuiltinMethodKind, + ) -> SpannedEncodingResult { + let borrowing_address = vir_low::VariableDecl::new("address", lowerer.address_type()?); + let restored_place = + vir_low::VariableDecl::new("restored_place", lowerer.place_option_type()?); + // let restored_root_address = + // vir_low::VariableDecl::new("restored_root_address", lowerer.address_type()?); + let snapshot = vir_low::VariableDecl::new("snapshot", ty.to_snapshot(lowerer)?); + let inner = + BuiltinMethodBuilder::new(lowerer, kind, method_name, ty, type_decl, error_kind)?; + Ok(Self { + inner, + borrowing_address, + restored_place, + // restored_root_address, + snapshot, + }) + } + + pub(in super::super::super::super) fn build(self) -> vir_low::MethodDecl { + self.inner.build() + } + + pub(in super::super::super::super) fn create_parameters( + &mut self, + ) -> SpannedEncodingResult<()> { + self.inner.parameters.push(self.borrowing_address.clone()); + self.inner.parameters.push(self.restored_place.clone()); + // self.inner + // .parameters + // .push(self.restored_root_address.clone()); + self.inner.parameters.push(self.snapshot.clone()); + self.create_lifetime_parameters()?; + self.create_const_parameters()?; + Ok(()) + } + + pub(in super::super::super::super) fn add_aliased_source_precondition( + &mut self, + ) -> SpannedEncodingResult<()> { + let aliased_predicate = self.inner.lowerer.owned_aliased( + CallContext::BuiltinMethod, + self.inner.ty, + self.inner.ty, + self.borrowing_address.clone().into(), + None, + self.inner.position, + )?; + self.add_precondition(aliased_predicate); + Ok(()) + } + + pub(in super::super::super::super) fn add_shift_precondition( + &mut self, + ) -> SpannedEncodingResult<()> { + let restore_raw_borrowed = self.inner.lowerer.restore_raw_borrowed( + self.inner.ty, + self.restored_place.clone().into(), + self.borrowing_address.clone().into(), + // self.restored_root_address.clone().into(), + )?; + self.add_precondition(restore_raw_borrowed); + Ok(()) + } + + pub(crate) fn add_non_aliased_target_postcondition(&mut self) -> SpannedEncodingResult<()> { + let non_aliased_predicate = self + .inner + .lowerer + .owned_non_aliased_full_vars_with_snapshot( + CallContext::BuiltinMethod, + self.inner.ty, + self.inner.ty, + &self.restored_place, + &self.borrowing_address, + // &self.restored_root_address, + &self.snapshot, + self.inner.position, + )?; + self.add_postcondition(non_aliased_predicate); + Ok(()) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/write_place_constant.rs b/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/write_place_constant.rs index 7d0a79c8a42..362cfd60f46 100644 --- a/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/write_place_constant.rs +++ b/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/decls/write_place_constant.rs @@ -9,7 +9,7 @@ use crate::encoder::{ builtin_methods::{BuiltinMethodsInterface, CallContext}, lowerer::Lowerer, places::PlacesInterface, - predicates::{OwnedNonAliasedUseBuilder, PredicatesOwnedInterface}, + predicates::PredicatesOwnedInterface, snapshots::{IntoSnapshot, SnapshotValidityInterface, SnapshotValuesInterface}, }, }; @@ -21,7 +21,7 @@ use vir_crate::{ pub(in super::super::super::super) struct WritePlaceConstantMethodBuilder<'l, 'p, 'v, 'tcx> { inner: BuiltinMethodBuilder<'l, 'p, 'v, 'tcx>, target_place: vir_low::VariableDecl, - target_root_address: vir_low::VariableDecl, + target_address: vir_low::VariableDecl, source_snapshot: vir_low::VariableDecl, } @@ -42,9 +42,8 @@ impl<'l, 'p, 'v, 'tcx> WritePlaceConstantMethodBuilder<'l, 'p, 'v, 'tcx> { type_decl: &'l vir_mid::TypeDecl, error_kind: BuiltinMethodKind, ) -> SpannedEncodingResult { - let target_place = vir_low::VariableDecl::new("target_place", lowerer.place_type()?); - let target_root_address = - vir_low::VariableDecl::new("target_root_address", lowerer.address_type()?); + let target_place = vir_low::VariableDecl::new("target_place", lowerer.place_option_type()?); + let target_address = vir_low::VariableDecl::new("target_address", lowerer.address_type()?); let source_snapshot = vir_low::VariableDecl::new("source_snapshot", ty.to_snapshot(lowerer)?); let inner = @@ -52,7 +51,7 @@ impl<'l, 'p, 'v, 'tcx> WritePlaceConstantMethodBuilder<'l, 'p, 'v, 'tcx> { Ok(Self { inner, target_place, - target_root_address, + target_address, source_snapshot, }) } @@ -65,7 +64,7 @@ impl<'l, 'p, 'v, 'tcx> WritePlaceConstantMethodBuilder<'l, 'p, 'v, 'tcx> { &mut self, ) -> SpannedEncodingResult<()> { self.inner.parameters.push(self.target_place.clone()); - self.inner.parameters.push(self.target_root_address.clone()); + self.inner.parameters.push(self.target_address.clone()); self.inner.parameters.push(self.source_snapshot.clone()); self.create_lifetime_parameters()?; self.create_const_parameters()?; @@ -84,29 +83,49 @@ impl<'l, 'p, 'v, 'tcx> WritePlaceConstantMethodBuilder<'l, 'p, 'v, 'tcx> { &mut self, ) -> SpannedEncodingResult { self.create_memory_block( - self.compute_address(&self.target_place, &self.target_root_address), + self.target_address.clone().into(), + // self.compute_address(&self.target_place, &self.target_address), ) } // FIXME: Remove duplicates with other builders. pub(in super::super::super::super) fn create_target_owned( &mut self, + exclude_snapshot_equality: bool, ) -> SpannedEncodingResult { - self.inner - .lowerer - .mark_owned_non_aliased_as_unfolded(self.inner.ty)?; - let mut builder = OwnedNonAliasedUseBuilder::new( - self.inner.lowerer, - CallContext::BuiltinMethod, - self.inner.ty, - self.inner.type_decl, - self.target_place.clone().into(), - self.target_root_address.clone().into(), - self.source_snapshot.clone().into(), - )?; - builder.add_lifetime_arguments()?; - builder.add_const_arguments()?; - Ok(builder.build()) + if exclude_snapshot_equality { + self.inner.lowerer.owned_non_aliased_full_vars( + CallContext::BuiltinMethod, + self.inner.ty, + self.inner.type_decl, + &self.target_place, + &self.target_address, + self.inner.position, + ) + } else { + self.inner + .lowerer + .owned_non_aliased_full_vars_with_snapshot( + CallContext::BuiltinMethod, + self.inner.ty, + self.inner.type_decl, + &self.target_place, + &self.target_address, + &self.source_snapshot, + self.inner.position, + ) + } + // self.inner + // .lowerer + // .mark_owned_predicate_as_unfolded(self.inner.ty)?; + // self.inner.lowerer.owned_non_aliased_full_vars( + // CallContext::BuiltinMethod, + // self.inner.ty, + // self.inner.type_decl, + // &self.target_place, + // &self.target_address, + // self.inner.position, + // ) } pub(in super::super::super::super) fn add_source_validity_precondition( @@ -149,7 +168,7 @@ impl<'l, 'p, 'v, 'tcx> WritePlaceConstantMethodBuilder<'l, 'p, 'v, 'tcx> { self.inner .lowerer .encode_memory_block_split_method(self.inner.ty)?; - let target_address = self.compute_address(&self.target_place, &self.target_root_address); + // let target_address = self.compute_address(&self.target_place, &self.target_address); let discriminant_call = self.discriminant()?; let mut builder = BuiltinMethodCallBuilder::new( self.inner.lowerer, @@ -159,7 +178,7 @@ impl<'l, 'p, 'v, 'tcx> WritePlaceConstantMethodBuilder<'l, 'p, 'v, 'tcx> { self.inner.type_decl, self.inner.position, )?; - builder.add_argument(target_address); + builder.add_argument(self.target_address.clone().into()); builder.add_full_permission_argument(); if let Some(discriminant_call) = discriminant_call { builder.add_argument(discriminant_call); @@ -182,6 +201,12 @@ impl<'l, 'p, 'v, 'tcx> WritePlaceConstantMethodBuilder<'l, 'p, 'v, 'tcx> { self.target_place.clone().into(), self.inner.position, )?; + let target_field_address = self.inner.lowerer.encode_field_address( + self.inner.ty, + field, + self.target_address.clone().into(), + self.inner.position, + )?; let source_field_snapshot = self.inner.lowerer.obtain_struct_field_snapshot( self.inner.ty, field, @@ -198,7 +223,7 @@ impl<'l, 'p, 'v, 'tcx> WritePlaceConstantMethodBuilder<'l, 'p, 'v, 'tcx> { self.inner.position, )?; builder.add_argument(target_field_place); - builder.add_argument(self.target_root_address.clone().into()); + builder.add_argument(target_field_address); builder.add_argument(source_field_snapshot); builder.add_lifetime_arguments()?; builder.add_const_arguments()?; @@ -213,7 +238,7 @@ impl<'l, 'p, 'v, 'tcx> WritePlaceConstantMethodBuilder<'l, 'p, 'v, 'tcx> { self.inner .lowerer .encode_write_address_constant_method(self.inner.ty)?; - let address = self.compute_address(&self.target_place, &self.target_root_address); + // let address = self.compute_address(&self.target_place, &self.target_address); let mut builder = BuiltinMethodCallBuilder::new( self.inner.lowerer, CallContext::BuiltinMethod, @@ -222,7 +247,7 @@ impl<'l, 'p, 'v, 'tcx> WritePlaceConstantMethodBuilder<'l, 'p, 'v, 'tcx> { self.inner.type_decl, self.inner.position, )?; - builder.add_argument(address); + builder.add_argument(self.target_address.clone().into()); builder.add_argument(self.source_snapshot.clone().into()); builder.add_lifetime_arguments()?; builder.add_const_arguments()?; diff --git a/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/mod.rs b/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/mod.rs index 5152118b533..acde36ccc11 100644 --- a/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/mod.rs +++ b/prusti-viper/src/encoder/middle/core_proof/builtin_methods/builders/mod.rs @@ -8,7 +8,10 @@ pub(in super::super) use self::decls::{ memory_block_copy::MemoryBlockCopyMethodBuilder, memory_block_into::IntoMemoryBlockMethodBuilder, memory_block_join::MemoryBlockJoinMethodBuilder, + memory_block_range_join::MemoryBlockRangeJoinMethodBuilder, + memory_block_range_split::MemoryBlockRangeSplitMethodBuilder, memory_block_split::MemoryBlockSplitMethodBuilder, move_place::MovePlaceMethodBuilder, + restore_raw_borrowed::RestoreRawBorrowedMethodBuilder, write_address_constant::WriteAddressConstantMethodBuilder, write_place_constant::WritePlaceConstantMethodBuilder, }; diff --git a/prusti-viper/src/encoder/middle/core_proof/builtin_methods/calls/interface.rs b/prusti-viper/src/encoder/middle/core_proof/builtin_methods/calls/interface.rs index 7b9234efc8d..9d9c1dc6610 100644 --- a/prusti-viper/src/encoder/middle/core_proof/builtin_methods/calls/interface.rs +++ b/prusti-viper/src/encoder/middle/core_proof/builtin_methods/calls/interface.rs @@ -98,6 +98,20 @@ pub(in super::super::super) trait BuiltinMethodCallsInterface { ) -> SpannedEncodingResult where G: WithLifetimes + WithConstArguments; + + #[allow(clippy::too_many_arguments)] + fn call_restore_raw_borrowed_method( + &mut self, + context: CallContext, + ty: &vir_mid::Type, + generics: &G, + position: vir_low::Position, + address: vir_low::Expression, + restored_place: vir_low::Expression, + snapshot: vir_low::Expression, + ) -> SpannedEncodingResult + where + G: WithLifetimes + WithConstArguments; } impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodCallsInterface for Lowerer<'p, 'v, 'tcx> { @@ -108,7 +122,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodCallsInterface for Lowerer<'p, 'v, 'tcx> generics: &G, position: vir_low::Position, target_place: vir_low::Expression, - target_root_address: vir_low::Expression, + target_address: vir_low::Expression, source_snapshot: vir_low::Expression, ) -> SpannedEncodingResult where @@ -123,7 +137,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodCallsInterface for Lowerer<'p, 'v, 'tcx> position, )?; builder.add_argument(target_place); - builder.add_argument(target_root_address); + builder.add_argument(target_address); builder.add_argument(source_snapshot); builder.add_lifetime_arguments()?; builder.add_const_arguments()?; @@ -164,9 +178,9 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodCallsInterface for Lowerer<'p, 'v, 'tcx> generics: &G, position: vir_low::Position, target_place: vir_low::Expression, - target_root_address: vir_low::Expression, + target_address: vir_low::Expression, source_place: vir_low::Expression, - source_root_address: vir_low::Expression, + source_address: vir_low::Expression, source_snapshot: vir_low::Expression, source_permission_amount: vir_low::Expression, ) -> SpannedEncodingResult @@ -176,9 +190,9 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodCallsInterface for Lowerer<'p, 'v, 'tcx> let mut builder = BuiltinMethodCallBuilder::new(self, context, "copy_place", ty, generics, position)?; builder.add_argument(target_place); - builder.add_argument(target_root_address); + builder.add_argument(target_address); builder.add_argument(source_place); - builder.add_argument(source_root_address); + builder.add_argument(source_address); builder.add_argument(source_snapshot); builder.add_argument(source_permission_amount); builder.add_lifetime_arguments()?; @@ -194,7 +208,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodCallsInterface for Lowerer<'p, 'v, 'tcx> position: vir_low::Position, guard: Option, place: vir_low::Expression, - root_address: vir_low::Expression, + address: vir_low::Expression, snapshot: vir_low::Expression, ) -> SpannedEncodingResult where @@ -209,7 +223,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodCallsInterface for Lowerer<'p, 'v, 'tcx> position, )?; builder.add_argument(place); - builder.add_argument(root_address); + builder.add_argument(address); builder.add_argument(snapshot); builder.add_lifetime_arguments()?; builder.add_const_arguments()?; @@ -249,4 +263,31 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodCallsInterface for Lowerer<'p, 'v, 'tcx> builder.add_const_arguments()?; Ok(builder.build()) } + + fn call_restore_raw_borrowed_method( + &mut self, + context: CallContext, + ty: &vir_mid::Type, + generics: &G, + position: vir_low::Position, + address: vir_low::Expression, + restored_place: vir_low::Expression, + snapshot: vir_low::Expression, + ) -> SpannedEncodingResult + where + G: WithLifetimes + WithConstArguments, + { + let mut builder = BuiltinMethodCallBuilder::new( + self, + context, + "restore_raw_borrowed", + ty, + generics, + position, + )?; + builder.add_argument(address); + builder.add_argument(restored_place); + builder.add_argument(snapshot); + Ok(builder.build()) + } } diff --git a/prusti-viper/src/encoder/middle/core_proof/builtin_methods/interface.rs b/prusti-viper/src/encoder/middle/core_proof/builtin_methods/interface.rs index 30f6a32d4d0..b0bade827c2 100644 --- a/prusti-viper/src/encoder/middle/core_proof/builtin_methods/interface.rs +++ b/prusti-viper/src/encoder/middle/core_proof/builtin_methods/interface.rs @@ -1,7 +1,7 @@ use super::{ builders::{ ChangeUniqueRefPlaceMethodBuilder, DuplicateFracRefMethodBuilder, - MemoryBlockCopyMethodBuilder, + MemoryBlockCopyMethodBuilder, RestoreRawBorrowedMethodBuilder, }, BuiltinMethodCallsInterface, CallContext, }; @@ -13,35 +13,49 @@ use crate::encoder::{ block_markers::BlockMarkersInterface, builtin_methods::builders::{ BuiltinMethodBuilderMethods, CopyPlaceMethodBuilder, IntoMemoryBlockMethodBuilder, - MemoryBlockJoinMethodBuilder, MemoryBlockSplitMethodBuilder, MovePlaceMethodBuilder, - WriteAddressConstantMethodBuilder, WritePlaceConstantMethodBuilder, + MemoryBlockJoinMethodBuilder, MemoryBlockRangeJoinMethodBuilder, + MemoryBlockRangeSplitMethodBuilder, MemoryBlockSplitMethodBuilder, + MovePlaceMethodBuilder, WriteAddressConstantMethodBuilder, + WritePlaceConstantMethodBuilder, }, compute_address::ComputeAddressInterface, + const_generics::ConstGenericsInterface, errors::ErrorsInterface, + footprint::FootprintInterface, lifetimes::LifetimesInterface, lowerer::{ DomainsLowererInterface, Lowerer, MethodsLowererInterface, PredicatesLowererInterface, VariablesLowererInterface, }, places::PlacesInterface, + pointers::PointersInterface, predicates::{ - OwnedNonAliasedUseBuilder, PredicatesMemoryBlockInterface, PredicatesOwnedInterface, + OwnedNonAliasedSnapCallBuilder, OwnedNonAliasedUseBuilder, PredicatesAliasingInterface, + PredicatesMemoryBlockInterface, PredicatesOwnedInterface, RestorationInterface, }, references::ReferencesInterface, snapshots::{ - BuiltinFunctionsInterface, IntoBuiltinMethodSnapshot, IntoProcedureFinalSnapshot, - IntoProcedureSnapshot, IntoPureSnapshot, IntoSnapshot, SnapshotBytesInterface, + AssertionToSnapshotConstructor, BuiltinFunctionsInterface, IntoBuiltinMethodSnapshot, + IntoProcedureSnapshot, IntoPureSnapshot, IntoSnapshot, IntoSnapshotLowerer, + PlaceToSnapshot, PredicateKind, SelfFramingAssertionToSnapshot, SnapshotBytesInterface, SnapshotValidityInterface, SnapshotValuesInterface, SnapshotVariablesInterface, }, + triggers::TriggersInterface, type_layouts::TypeLayoutsInterface, + viewshifts::ViewShiftsInterface, }, + mir::errors::ErrorInterface, }; use itertools::Itertools; -use rustc_hash::FxHashSet; +use rustc_hash::{FxHashMap, FxHashSet}; use vir_crate::{ common::{ - expression::{ExpressionIterator, UnaryOperationHelpers}, + builtin_constants::LIFETIME_DOMAIN_NAME, + expression::{ + BinaryOperationHelpers, ExpressionIterator, QuantifierHelpers, UnaryOperationHelpers, + }, identifier::WithIdentifier, + position::Positioned, }, low::{self as vir_low, macros::method_name}, middle::{ @@ -50,6 +64,9 @@ use vir_crate::{ }, }; +// FIXME: Move this to some proper place. It is shared with the snap function +// encoder. + #[derive(Default)] pub(in super::super) struct BuiltinMethodsState { encoded_write_place_constant_methods: FxHashSet, @@ -59,9 +76,12 @@ pub(in super::super) struct BuiltinMethodsState { encoded_duplicate_frac_ref_method: FxHashSet, encoded_write_address_constant_methods: FxHashSet, encoded_owned_non_aliased_havoc_methods: FxHashSet, + encoded_unique_ref_havoc_methods: FxHashSet, encoded_memory_block_copy_methods: FxHashSet, encoded_memory_block_split_methods: FxHashSet, + encoded_memory_block_range_split_methods: FxHashSet, encoded_memory_block_join_methods: FxHashSet, + encoded_memory_block_range_join_methods: FxHashSet, encoded_memory_block_havoc_methods: FxHashSet, encoded_into_memory_block_methods: FxHashSet, encoded_assign_methods: FxHashSet, @@ -73,7 +93,12 @@ pub(in super::super) struct BuiltinMethodsState { encoded_lft_tok_sep_take_methods: FxHashSet, encoded_lft_tok_sep_return_methods: FxHashSet, encoded_open_close_mut_ref_methods: FxHashSet, + encoded_restore_raw_borrowed_methods: FxHashSet, encoded_bor_shorten_methods: FxHashSet, + encoded_stashed_owned_aliased_predicates: FxHashSet, + encoded_assign_method_names: FxHashMap, + reborrow_target_variables: + FxHashMap, } trait Private { @@ -115,7 +140,7 @@ trait Private { lft_count: usize, ) -> SpannedEncodingResult; fn encode_assign_method_name( - &self, + &mut self, ty: &vir_mid::Type, value: &vir_mid::Rvalue, ) -> SpannedEncodingResult; @@ -127,6 +152,10 @@ trait Private { &self, ty: &vir_mid::Type, ) -> SpannedEncodingResult; + fn encode_havoc_unique_ref_method_name( + &self, + ty: &vir_mid::Type, + ) -> SpannedEncodingResult; fn encode_assign_method( &mut self, method_name: &str, @@ -232,11 +261,62 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { let perm_amount = value .lifetime_token_permission .to_procedure_snapshot(self)?; - self.encode_place_arguments(arguments, &value.deref_place, false)?; - if value.uniqueness.is_unique() { - let snapshot_final = value.deref_place.to_procedure_final_snapshot(self)?; - arguments.push(snapshot_final); + arguments.push(self.encode_expression_as_place(&value.deref_place)?); + // arguments.push(self.extract_root_address(&value.deref_place)?); + arguments.push(self.encode_expression_as_place_address(&value.deref_place)?); + // self.encode_place_arguments(arguments, &value.deref_place, false)?; + // if self.check_mode.unwrap() == CheckMode::PurificationFunctional { + // arguments.push(value.deref_place.to_procedure_snapshot(self)?); + // } else { + let place = self.encode_expression_as_place(&value.deref_place)?; + // let root_address = self.extract_root_address(&value.deref_place)?; + let address = self.encode_expression_as_place_address(&value.deref_place)?; + let ty = value.deref_place.get_type(); + let TODO_target_slice_len = None; + match value + .deref_place + .get_deref_uniqueness() + .unwrap_or(value.uniqueness) + { + vir_mid::ty::Uniqueness::Unique => { + let snapshot_current = self.unique_ref_snap( + CallContext::Procedure, + ty, + ty, + place, + address, + deref_lifetime.clone().into(), + TODO_target_slice_len, + false, + value.deref_place.position(), + )?; + arguments.push(snapshot_current); + let mut place_encoder = + PlaceToSnapshot::for_place(PredicateKind::UniqueRef { + lifetime: deref_lifetime.clone().into(), + is_final: true, + }); + let snapshot_final = + place_encoder.expression_to_snapshot(self, &value.deref_place, true)?; + // let snapshot_final = + // value.deref_place.to_procedure_final_snapshot(self)?; + arguments.push(snapshot_final); + } + vir_mid::ty::Uniqueness::Shared => { + let snapshot_current = self.frac_ref_snap( + CallContext::Procedure, + ty, + ty, + place, + address, + deref_lifetime.clone().into(), + TODO_target_slice_len, + value.deref_place.position(), + )?; + arguments.push(snapshot_current); + } } + // } arguments.extend(self.create_lifetime_arguments( CallContext::Procedure, value.deref_place.get_type(), @@ -279,6 +359,9 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { }; arguments.push(len); } + vir_mid::Rvalue::Cast(value) => { + self.encode_operand_arguments(arguments, &value.operand, true)?; + } vir_mid::Rvalue::UnaryOp(value) => { self.encode_operand_arguments(arguments, &value.argument, true)?; } @@ -312,6 +395,10 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { for operand in &aggr_value.operands { self.encode_operand_arguments(arguments, operand, false)?; } + if self.use_heap_variable()? && aggr_value.ty.is_struct() { + let heap = self.heap_variable_version_at_label(&None)?; + arguments.push(heap.into()); + } } } Ok(()) @@ -344,7 +431,8 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { permission: &Option, ) -> SpannedEncodingResult<()> { arguments.push(self.encode_expression_as_place(place)?); - arguments.push(self.extract_root_address(place)?); + arguments.push(self.encode_expression_as_place_address(place)?); + // arguments.push(self.extract_root_address(place)?); if let Some(variable) = permission { arguments.push(variable.to_procedure_snapshot(self)?.into()); } else { @@ -361,8 +449,11 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { encode_lifetime_arguments: bool, ) -> SpannedEncodingResult<()> { arguments.push(self.encode_expression_as_place(expression)?); - arguments.push(self.extract_root_address(expression)?); - arguments.push(expression.to_procedure_snapshot(self)?); + // arguments.push(self.extract_root_address(expression)?); + arguments.push(self.encode_expression_as_place_address(expression)?); + let mut place_encoder = PlaceToSnapshot::for_place(PredicateKind::Owned); + let snapshot = place_encoder.expression_to_snapshot(self, expression, false)?; + arguments.push(snapshot); if encode_lifetime_arguments { arguments.extend( self.create_lifetime_arguments(CallContext::Procedure, expression.get_type())?, @@ -389,11 +480,11 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { Ok(format!("lft_tok_sep_return${lft_count}")) } fn encode_assign_method_name( - &self, + &mut self, ty: &vir_mid::Type, value: &vir_mid::Rvalue, ) -> SpannedEncodingResult { - Ok(format!( + let full_name = format!( "assign${}${}$${}${}${}", ty.get_identifier(), value.get_identifier(), @@ -411,7 +502,23 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { .into_iter() .map(|arg| arg.to_string()) .join("$"), - )) + ); + if let Some(short_name) = self + .builtin_methods_state + .encoded_assign_method_names + .get(&full_name) + { + Ok(short_name.clone()) + } else { + let short_name = format!( + "assign${}", + self.builtin_methods_state.encoded_assign_method_names.len() + ); + self.builtin_methods_state + .encoded_assign_method_names + .insert(full_name, short_name.clone()); + Ok(short_name) + } } fn encode_consume_operand_method_name( &self, @@ -425,6 +532,12 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { ) -> SpannedEncodingResult { Ok(format!("havoc_owned${}", ty.get_identifier())) } + fn encode_havoc_unique_ref_method_name( + &self, + ty: &vir_mid::Type, + ) -> SpannedEncodingResult { + Ok(format!("havoc_unique_ref${}", ty.get_identifier())) + } fn encode_assign_method( &mut self, method_name: &str, @@ -445,23 +558,24 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { .insert(method_name.to_string()); self.encode_compute_address(ty)?; - self.mark_owned_non_aliased_as_unfolded(ty)?; + self.mark_owned_predicate_as_unfolded(ty)?; let span = self.encoder.get_type_definition_span_mid(ty)?; let position = self.register_error( span, ErrorCtxt::UnexpectedBuiltinMethod(BuiltinMethodKind::MovePlace), ); use vir_low::macros::*; - let compute_address = ty!(Address); + // let compute_address = ty!(Address); let size_of = self.encode_type_size_expression2(ty, ty)?; var_decls! { - target_place: Place, + target_place: PlaceOption, target_address: Address }; let mut parameters = vec![target_place.clone(), target_address.clone()]; var_decls! { result_value: {ty.to_snapshot(self)?} }; let mut pres = vec![ - expr! { acc(MemoryBlock((ComputeAddress::compute_address(target_place, target_address)), [size_of])) }, + // expr! { acc(MemoryBlock((ComputeAddress::compute_address(target_place, target_address)), [size_of])) }, + expr! { acc(MemoryBlock(target_address, [size_of])) }, ]; let mut posts = Vec::new(); match value { @@ -508,13 +622,14 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { let snap = self.encode_lifetime_const_into_procedure_variable(lifetime)?; lifetimes_ty.push(snap.into()); } - let predicate = self.owned_non_aliased_full_vars( + let predicate = self.owned_non_aliased_full_vars_with_snapshot( CallContext::BuiltinMethod, ty, ty, &target_place, &target_address, &result_value, + position, )?; posts.push(predicate); self.encode_assign_method_rvalue( @@ -623,33 +738,114 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { vir_mid::Rvalue::AddressOf(value) => { let ty = value.place.get_type(); var_decls! { - operand_place: Place, + operand_place: PlaceOption, operand_address: Address, operand_value: { ty.to_snapshot(self)? } }; - let predicate = self.owned_non_aliased_full_vars( + let non_aliased_predicate = self.owned_non_aliased_full_vars_with_snapshot( CallContext::BuiltinMethod, ty, ty, &operand_place, &operand_address, &operand_value, + position, )?; - let compute_address = ty!(Address); - let address = - expr! { ComputeAddress::compute_address(operand_place, operand_address) }; - pres.push(predicate.clone()); - posts.push(predicate); + pres.push(non_aliased_predicate); + let aliased_predicate = self.owned_aliased( + CallContext::BuiltinMethod, + ty, + ty, + operand_address.clone().into(), + None, + position, + )?; + let aliased_predicate_snapshot = self.owned_aliased_snap( + CallContext::BuiltinMethod, + ty, + ty, + operand_address.clone().into(), + position, + )?; + let restore_raw_borrowed = self.restore_raw_borrowed( + ty, + operand_place.clone().into(), + operand_address.clone().into(), + )?; + posts.push(aliased_predicate); + posts.push(restore_raw_borrowed); + posts.push(expr! { [aliased_predicate_snapshot] == operand_value }); parameters.push(operand_place); - parameters.push(operand_address); + parameters.push(operand_address.clone()); parameters.push(operand_value); - self.construct_constant_snapshot(result_type, address, position)? + self.construct_constant_snapshot( + result_type, + operand_address.clone().into(), + position, + )? } vir_mid::Rvalue::Len(_value) => { var_decls! { length: {self.size_type()?} }; parameters.push(length.clone()); length.into() } + vir_mid::Rvalue::Cast(value) => { + let source_type = value.operand.expression.get_type(); + let operand_value = self.encode_assign_operand( + parameters, + pres, + posts, + 1, + &value.operand, + position, + true, + )?; + match (&value.ty, source_type) { + (vir_mid::Type::Pointer(_), vir_mid::Type::Pointer(_)) => { + let address = + self.pointer_address(source_type, operand_value.into(), position)?; + self.construct_constant_snapshot(result_type, address, position)? + } + (vir_mid::Type::Int(target_int), vir_mid::Type::Int(_source_int)) => { + let number = self.obtain_constant_value( + source_type, + operand_value.into(), + position, + )?; + let (lower_bound, upper_bound) = match target_int { + vir_mid::ty::Int::U8 => (u8::MIN.into(), u8::MAX.into()), + vir_mid::ty::Int::U16 => (u16::MIN.into(), u16::MAX.into()), + vir_mid::ty::Int::U32 => (u32::MIN.into(), u32::MAX.into()), + vir_mid::ty::Int::U64 => (u64::MIN.into(), u64::MAX.into()), + vir_mid::ty::Int::U128 => (u128::MIN.into(), u128::MAX.into()), + vir_mid::ty::Int::Usize => (usize::MIN.into(), usize::MAX.into()), + vir_mid::ty::Int::I8 => (i8::MIN.into(), i8::MAX.into()), + vir_mid::ty::Int::I16 => (i16::MIN.into(), i16::MAX.into()), + vir_mid::ty::Int::I32 => (i32::MIN.into(), i32::MAX.into()), + vir_mid::ty::Int::I64 => (i64::MIN.into(), i64::MAX.into()), + vir_mid::ty::Int::I128 => (i128::MIN.into(), i128::MAX.into()), + vir_mid::ty::Int::Isize => (isize::MIN.into(), isize::MAX.into()), + _ => unimplemented!("{target_int}"), + }; + pres.push(expr! { [lower_bound] <= [number.clone()] }); // FIXME: use the MIN value of the target platform. + pres.push(expr! { [number.clone()] <= [upper_bound] }); // FIXME: use the MAX value of the target platform. + self.construct_constant_snapshot(result_type, number, position)? + } + // ( + // vir_mid::Type::Int(vir_mid::ty::Int::Isize), + // vir_mid::Type::Int(vir_mid::ty::Int::Usize), + // ) => { + // let number = self.obtain_constant_value( + // source_type, + // operand_value.into(), + // position, + // )?; + // pres.push(expr! { [number.clone()] <= [isize::MAX.into()] }); // FIXME: use the MAX value of the target. + // self.construct_constant_snapshot(result_type, number, position)? + // } + (t, s) => unimplemented!("({t}) {s}"), + } + } vir_mid::Rvalue::UnaryOp(value) => { let operand_value = self.encode_assign_operand( parameters, @@ -720,12 +916,12 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { vir_mid::Rvalue::Discriminant(value) => { let ty = value.place.get_type(); var_decls! { - operand_place: Place, + operand_place: PlaceOption, operand_address: Address, operand_permission: Perm, operand_value: { ty.to_snapshot(self)? } }; - let predicate = self.owned_non_aliased( + let predicate = self.owned_non_aliased_with_snapshot( CallContext::BuiltinMethod, ty, ty, @@ -733,6 +929,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { operand_address.clone().into(), operand_value.clone().into(), Some(operand_permission.clone().into()), + position, )?; pres.push(expr! { [vir_low::Expression::no_permission()] < operand_permission @@ -775,7 +972,39 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { self.construct_enum_snapshot(&value.ty, variant_constructor, position)? } vir_mid::Type::Struct(_) => { - self.construct_struct_snapshot(&value.ty, arguments, position)? + let decl = self.encoder.get_type_decl_mid(&value.ty)?.unwrap_struct(); + if let Some(invariant) = decl.structural_invariant { + assert_eq!(arguments.len(), decl.fields.len()); + // Assert the invariant for the struct in the precondition. + let mut invariant_encoder = + SelfFramingAssertionToSnapshot::for_assign_precondition( + arguments.clone(), + decl.fields.clone(), + ); + for assertion in &invariant { + let encoded_assertion = invariant_encoder + .expression_to_snapshot(self, assertion, true)?; + pres.push(encoded_assertion); + } + // Create the snapshot constructor. + let deref_fields = + self.structural_invariant_to_deref_fields(&invariant)?; + let mut constructor_encoder = + AssertionToSnapshotConstructor::for_assign_aggregate_postcondition( + result_type, + arguments, + decl.fields, + deref_fields, + position, + ); + let invariant_expression = invariant.into_iter().conjoin(); + let permission_expression = + invariant_expression.convert_into_permission_expression(); + constructor_encoder + .expression_to_snapshot_constructor(self, &permission_expression)? + } else { + self.construct_struct_snapshot(&value.ty, arguments, position)? + } } vir_mid::Type::Array(value_ty) => vir_low::Expression::container_op( vir_low::ContainerOpKind::SeqConstructor, @@ -785,9 +1014,6 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { ), ty => unimplemented!("{}", ty), }; - posts.push( - self.encode_snapshot_valid_call_for_type(assigned_value.clone(), result_type)?, - ); assigned_value } }; @@ -811,25 +1037,26 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { // is unknown. use vir_low::macros::*; var_decls! { - target_place: Place, + target_place: PlaceOption, target_address: Address }; - let compute_address = ty!(Address); + // let compute_address = ty!(Address); let type_decl = self.encoder.get_type_decl_mid(ty)?.unwrap_struct(); let (operation_result_field, flag_field) = { let mut iter = type_decl.fields.iter(); (iter.next().unwrap(), iter.next().unwrap()) }; - let flag_place = - self.encode_field_place(ty, flag_field, target_place.clone().into(), position)?; + let flag_place = self.encode_field_place(ty, flag_field, target_place.into(), position)?; + let flag_address = + self.encode_field_address(ty, flag_field, target_address.clone().into(), position)?; let flag_value = self.obtain_struct_field_snapshot( ty, flag_field, result_value.clone().into(), position, )?; - let result_address = - expr! { (ComputeAddress::compute_address(target_place, target_address)) }; + let result_address: vir_low::Expression = target_address.into(); + // expr! { (ComputeAddress::compute_address(target_place, target_address)) }; let operation_result_address = self.encode_field_address( ty, operation_result_field, @@ -854,9 +1081,16 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { posts.push( expr! { acc(MemoryBlock([operation_result_address.clone()], [size_of_result.clone()])) }, ); - posts.push( - expr! { acc(OwnedNonAliased([flag_place], target_address, [flag_value.clone()])) }, - ); + posts.push(self.owned_non_aliased_with_snapshot( + CallContext::BuiltinMethod, + flag_type, + flag_type, + flag_place, + flag_address, + flag_value.clone(), + None, + position, + )?); let operand_left = self.encode_assign_operand(parameters, pres, posts, 1, &value.left, position, true)?; let operand_right = @@ -876,13 +1110,19 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { vir_low::Expression::not(validity.clone()), position, )?; - let operation_result_value_condition = expr! { - [validity] ==> ([operation_result_value.clone()] == [operation_result]) - }; + { + // We verify absence of overflows in all modes because the + // panic/non-panic behaviour depends on the compiler flags. + pres.push(validity); + posts.push(expr! { [operation_result_value.clone()] == [operation_result] }); + // let operation_result_value_condition = expr! { + // [validity] ==> ([operation_result_value.clone()] == [operation_result]) + // }; + // posts.push(operation_result_value_condition); + } let flag_value_condition = expr! { [flag_value] == [flag_result] }; - posts.push(operation_result_value_condition); posts.push(flag_value_condition); let bytes = self.encode_memory_block_bytes_expression(operation_result_address, size_of_result)?; @@ -906,12 +1146,26 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { ) -> SpannedEncodingResult<()> { use vir_low::macros::*; let reference_type = result_type.clone().unwrap_reference(); + let operand_uniqueness = if let Some(uniqueness) = value.deref_place.get_deref_uniqueness() + { + uniqueness + } else { + // Reborrowing via a raw pointer. Just assume that its uniqueness matches the target. + reference_type.uniqueness + }; + let is_last_deref_pointer = value + .deref_place + .get_last_dereference() + .unwrap() + .base + .get_type() + .is_pointer(); let ty = value.deref_place.get_type(); var_decls! { - target_place: Place, + target_place: PlaceOption, target_address: Address, - operand_place: Place, - operand_root_address: Address, + operand_place: PlaceOption, + operand_address: Address, operand_snapshot_current: { ty.to_snapshot(self)? }, operand_snapshot_final: { ty.to_snapshot(self)? }, // use only for unique references lifetime_perm: Perm @@ -920,64 +1174,133 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { let deref_lifetime = value.deref_lifetime.to_pure_snapshot(self)?; let lifetime_token = self.encode_lifetime_token(new_borrow_lifetime.clone(), lifetime_perm.clone().into())?; - let deref_predicate = if reference_type.uniqueness.is_unique() { - self.unique_ref_full_vars( + let deref_predicate = if operand_uniqueness.is_unique() { + self.unique_ref_full_vars_with_current_snapshot( CallContext::BuiltinMethod, ty, ty, &operand_place, - &operand_root_address, + &operand_address, &operand_snapshot_current, - &operand_snapshot_final, &deref_lifetime, + None, + position, )? } else { - self.frac_ref_full_vars( + self.frac_ref_full_vars_with_current_snapshot( CallContext::BuiltinMethod, ty, ty, &operand_place, - &operand_root_address, + &operand_address, &operand_snapshot_current, &deref_lifetime, + None, + position, )? }; let valid_result = self.encode_snapshot_valid_call_for_type(result_value.clone().into(), result_type)?; let new_reference_predicate = { - self.mark_owned_non_aliased_as_unfolded(result_type)?; + self.mark_owned_predicate_as_unfolded(result_type)?; let mut builder = OwnedNonAliasedUseBuilder::new( + self, + CallContext::BuiltinMethod, + result_type, + ty, + target_place.clone().into(), + target_address.clone().into(), + position, + )?; + // builder.add_snapshot_argument(result_value.clone().into())?; + builder.add_custom_argument(true.into())?; + builder.add_custom_argument(new_borrow_lifetime.clone().into())?; + builder.add_lifetime_arguments()?; + builder.add_const_arguments()?; + let predicate = builder.build()?; + let mut builder = OwnedNonAliasedSnapCallBuilder::new( self, CallContext::BuiltinMethod, result_type, ty, target_place.into(), target_address.into(), - result_value.clone().into(), + position, )?; builder.add_custom_argument(true.into())?; builder.add_custom_argument(new_borrow_lifetime.clone().into())?; builder.add_lifetime_arguments()?; builder.add_const_arguments()?; - builder.build() + let snapshot = builder.build()?; + expr! { [predicate] && (result_value == [snapshot]) } }; let restoration = { - let final_snapshot = self.reference_target_final_snapshot( - result_type, - result_value.clone().into(), - position, - )?; - let validity = self.encode_snapshot_valid_call_for_type(final_snapshot, ty)?; - if reference_type.uniqueness.is_unique() { - expr! { - wand( - (acc(DeadLifetimeToken(new_borrow_lifetime))) --* ( - [deref_predicate.clone()] && - [validity] && + // if operand_uniqueness.is_unique() && reference_type.uniqueness.is_unique() { + if operand_uniqueness.is_unique() { + if is_last_deref_pointer { + // We currently cannot allow restoring reborrowed raw + // pointers (it would be unsound) because we resolve + // inheritance immediately (see the comment below). + true.into() + } else { + let final_snapshot = match reference_type.uniqueness { + vir_mid::ty::Uniqueness::Unique => self.reference_target_final_snapshot( + result_type, + result_value.clone().into(), + position, + )?, + vir_mid::ty::Uniqueness::Shared => { + // let reference_type = value + // .deref_place + // .get_last_dereferenced_reference() + // .unwrap() + // .get_type(); + self.reference_target_current_snapshot( + result_type, + // reference_type, + result_value.clone().into(), + position, + )? + } + }; + let deref_predicate = self.unique_ref_with_current_snapshot( + CallContext::BuiltinMethod, + ty, + ty, + operand_place.clone().into(), + operand_address.clone().into(), + final_snapshot.clone(), + deref_lifetime.clone().into(), + None, + None, + position, + )?; + let validity = + self.encode_snapshot_valid_call_for_type(final_snapshot.clone(), ty)?; + let mut arguments = vec![ + new_borrow_lifetime.clone().into(), + operand_place.clone().into(), + operand_address.clone().into(), + // operand_snapshot_current.clone().into(), + final_snapshot, + deref_lifetime.clone().into(), + ]; + arguments + .extend(self.create_lifetime_arguments(CallContext::BuiltinMethod, ty)?); + arguments.extend(self.create_const_arguments(CallContext::BuiltinMethod, ty)?); + self.encode_view_shift_return( + &format!("end$reborrow${}", ty.get_identifier()), + arguments, + vec![expr! { acc(DeadLifetimeToken(new_borrow_lifetime)) }], + vec![ + deref_predicate, + validity, // DeadLifetimeToken is duplicable and does not get consumed. - (acc(DeadLifetimeToken(new_borrow_lifetime))) - ) - ) + expr! { acc(DeadLifetimeToken(new_borrow_lifetime)) }, + ], + vir_low::PredicateKind::EndBorrowViewShift, + position, + )? } } else { deref_predicate.clone() @@ -986,7 +1309,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { let reference_target_address = self.reference_address(result_type, result_value.clone().into(), position)?; posts.push(expr! { - operand_root_address == [reference_target_address] + operand_address == [reference_target_address] }); let reference_target_current_snapshot = self.reference_target_current_snapshot( result_type, @@ -996,16 +1319,53 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { posts.push(expr! { operand_snapshot_current == [reference_target_current_snapshot] }); - let reference_target_final_snapshot = self.reference_target_final_snapshot( - result_type, - result_value.clone().into(), - position, - )?; - if reference_type.uniqueness.is_unique() { + if operand_uniqueness.is_unique() + && reference_type.uniqueness.is_unique() + && is_last_deref_pointer + { + let reference_target_final_snapshot = self.reference_target_final_snapshot( + result_type, + result_value.clone().into(), + position, + )?; + // This is sound because we do not generate the inheritance for + // reborrows of raw pointers. In other words, we resolve the + // reborrow immediatelly after it is created by destroying the + // inheritance. This covers most of our use cases. A proper + // solution would be to make the fold-unfold algorithm to emit + // statements that explicitly resolve inheritance once it is + // clear that it will not be used and give an annotation that + // allows the user to do the same in unsafe code. posts.push(expr! { operand_snapshot_final == [reference_target_final_snapshot] }); } + // if operand_uniqueness.is_unique() { + // if reference_type.uniqueness.is_unique() { + // // We now generate inharitance, so the following would be unsound: + // // let reference_target_final_snapshot = self.reference_target_final_snapshot( + // // result_type, + // // result_value.clone().into(), + // // position, + // // )?; + // // // This is sound because we do not generate the inheritance for + // // // reborrows of unique references. In other words, we resolve the + // // // reborrow immediatelly after it is created by destroying the + // // // inheritance. This covers most of our use cases. A proper solution + // // // would be to make the fold-unfold algorithm to emit statements + // // // that explicitly resolve inheritance once it is clear that it will + // // // not be used and give an annotation that allows the user to do the + // // // same in unsafe code. + // // posts.push(expr! { + // // operand_snapshot_final == [reference_target_final_snapshot] + // // }); + // } else { + // // The snapshot is guaranteed not to change. + // posts.push(expr! { + // operand_snapshot_final == operand_snapshot_current + // }); + // } + // } pres.push(expr! { [vir_low::Expression::no_permission()] < lifetime_perm }); @@ -1024,9 +1384,9 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { posts.push(valid_result); posts.push(restoration); parameters.push(operand_place); - parameters.push(operand_root_address); + parameters.push(operand_address); parameters.push(operand_snapshot_current); - if reference_type.uniqueness.is_unique() { + if operand_uniqueness.is_unique() { parameters.push(operand_snapshot_final); } parameters.extend(self.create_lifetime_parameters(ty)?); @@ -1063,40 +1423,57 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { use vir_low::macros::*; let ty = value.place.get_type(); var_decls! { - target_place: Place, + target_place: PlaceOption, target_address: Address, - operand_place: Place, - operand_root_address: Address, + operand_place: PlaceOption, + operand_address: Address, operand_snapshot: { ty.to_snapshot(self)? }, lifetime_perm: Perm }; let new_borrow_lifetime = value.new_borrow_lifetime.to_pure_snapshot(self)?; let lifetime_token = self.encode_lifetime_token(new_borrow_lifetime.clone(), lifetime_perm.clone().into())?; - let predicate = self.owned_non_aliased_full_vars( + let predicate = self.owned_non_aliased_full_vars_with_snapshot( CallContext::BuiltinMethod, ty, ty, &operand_place, - &operand_root_address, + &operand_address, &operand_snapshot, + position, )?; let reference_predicate = { - self.mark_owned_non_aliased_as_unfolded(result_type)?; + self.mark_owned_predicate_as_unfolded(result_type)?; let mut builder = OwnedNonAliasedUseBuilder::new( + self, + CallContext::BuiltinMethod, + result_type, + ty, + target_place.clone().into(), + target_address.clone().into(), + position, + )?; + // builder.add_snapshot_argument(result_value.clone().into())?; + builder.add_custom_argument(true.into())?; + builder.add_custom_argument(new_borrow_lifetime.clone().into())?; + builder.add_lifetime_arguments()?; + builder.add_const_arguments()?; + let predicate = builder.build()?; + let mut builder = OwnedNonAliasedSnapCallBuilder::new( self, CallContext::BuiltinMethod, result_type, ty, target_place.into(), target_address.into(), - result_value.clone().into(), + position, )?; builder.add_custom_argument(true.into())?; builder.add_custom_argument(new_borrow_lifetime.clone().into())?; builder.add_lifetime_arguments()?; builder.add_const_arguments()?; - builder.build() + let snapshot = builder.build()?; + expr! { [predicate] && (result_value == [snapshot]) } }; let restoration = { let restoration_snapshot = if value.uniqueness.is_unique() { @@ -1112,31 +1489,54 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { position, )? }; - let restored_predicate = self.owned_non_aliased( + let restored_predicate = self.owned_non_aliased_with_snapshot( CallContext::BuiltinMethod, ty, ty, operand_place.clone().into(), - operand_root_address.clone().into(), + operand_address.clone().into(), restoration_snapshot.clone(), None, + position, )?; - let validity = self.encode_snapshot_valid_call_for_type(restoration_snapshot, ty)?; - expr! { - wand( - (acc(DeadLifetimeToken(new_borrow_lifetime))) --* ( - [restored_predicate] && - [validity] && - // DeadLifetimeToken is duplicable and does not get consumed. - (acc(DeadLifetimeToken(new_borrow_lifetime))) - ) - ) - } + let validity = + self.encode_snapshot_valid_call_for_type(restoration_snapshot.clone(), ty)?; + let mut arguments = vec![ + new_borrow_lifetime.clone().into(), + operand_place.clone().into(), + operand_address.clone().into(), + restoration_snapshot, + ]; + arguments.extend(self.create_lifetime_arguments(CallContext::BuiltinMethod, ty)?); + arguments.extend(self.create_const_arguments(CallContext::BuiltinMethod, ty)?); + self.encode_view_shift_return( + &format!("end$borrow${}", ty.get_identifier()), + arguments, + vec![expr! { acc(DeadLifetimeToken(new_borrow_lifetime)) }], + vec![ + restored_predicate, + validity, + // DeadLifetimeToken is duplicable and does not get consumed. + expr! { acc(DeadLifetimeToken(new_borrow_lifetime)) }, + ], + vir_low::PredicateKind::EndBorrowViewShift, + position, + )? + // expr! { + // wand( + // (acc(DeadLifetimeToken(new_borrow_lifetime))) --* ( + // [restored_predicate] && + // [validity] && + // // DeadLifetimeToken is duplicable and does not get consumed. + // (acc(DeadLifetimeToken(new_borrow_lifetime))) + // ) + // ) + // } }; let reference_target_address = self.reference_address(result_type, result_value.clone().into(), position)?; posts.push(expr! { - operand_root_address == [reference_target_address] + operand_address == [reference_target_address] }); // Note: We do not constraint the final snapshot, because it is fresh. let reference_target_current_snapshot = self.reference_target_current_snapshot( @@ -1165,7 +1565,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { posts.push(restoration); posts.push(result_validity); parameters.push(operand_place); - parameters.push(operand_root_address); + parameters.push(operand_address); parameters.push(operand_snapshot); parameters.extend(self.create_lifetime_parameters(ty)?); parameters.push(new_borrow_lifetime); @@ -1192,7 +1592,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { posts: &mut Vec, operand_counter: u32, operand: &vir_mid::Operand, - _position: vir_low::Position, + position: vir_low::Position, add_lifetime_parameters: bool, ) -> SpannedEncodingResult { use vir_low::macros::*; @@ -1201,26 +1601,28 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { match operand.kind { vir_mid::OperandKind::Copy | vir_mid::OperandKind::Move => { let place = self.encode_assign_operand_place(operand_counter)?; - let root_address = self.encode_assign_operand_address(operand_counter)?; - let predicate = self.owned_non_aliased_full_vars( + let address = self.encode_assign_operand_address(operand_counter)?; + let predicate = self.owned_non_aliased_full_vars_with_snapshot( CallContext::BuiltinMethod, ty, ty, &place, - &root_address, + &address, &snapshot, + position, )?; pres.push(predicate.clone()); let post_predicate = if operand.kind == vir_mid::OperandKind::Copy { predicate } else { - let compute_address = ty!(Address); + // let compute_address = ty!(Address); let size_of = self.encode_type_size_expression2(ty, ty)?; - expr! { acc(MemoryBlock((ComputeAddress::compute_address(place, root_address)), [size_of])) } + // expr! { acc(MemoryBlock((ComputeAddress::compute_address(place, root_address)), [size_of])) } + expr! { acc(MemoryBlock(address, [size_of])) } }; posts.push(post_predicate); parameters.push(place); - parameters.push(root_address); + parameters.push(address); parameters.push(snapshot.clone()); if add_lifetime_parameters { parameters.extend(self.create_lifetime_parameters(ty)?); @@ -1242,7 +1644,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { ) -> SpannedEncodingResult { Ok(vir_low::VariableDecl::new( format!("operand{operand_counter}_place"), - self.place_type()?, + self.place_option_type()?, )) } fn encode_assign_operand_address( @@ -1270,7 +1672,15 @@ pub(in super::super) trait BuiltinMethodsInterface { fn encode_memory_block_copy_method(&mut self, ty: &vir_mid::Type) -> SpannedEncodingResult<()>; fn encode_memory_block_split_method(&mut self, ty: &vir_mid::Type) -> SpannedEncodingResult<()>; + fn encode_memory_block_range_split_method( + &mut self, + ty: &vir_mid::Type, + ) -> SpannedEncodingResult<()>; fn encode_memory_block_join_method(&mut self, ty: &vir_mid::Type) -> SpannedEncodingResult<()>; + fn encode_memory_block_range_join_method( + &mut self, + ty: &vir_mid::Type, + ) -> SpannedEncodingResult<()>; fn encode_move_place_method(&mut self, ty: &vir_mid::Type) -> SpannedEncodingResult<()>; fn encode_change_unique_ref_place_method( @@ -1293,6 +1703,7 @@ pub(in super::super) trait BuiltinMethodsInterface { &mut self, ty: &vir_mid::Type, ) -> SpannedEncodingResult<()>; + fn encode_havoc_unique_ref_method(&mut self, ty: &vir_mid::Type) -> SpannedEncodingResult<()>; fn encode_havoc_memory_block_method_name( &mut self, ty: &vir_mid::Type, @@ -1307,6 +1718,10 @@ pub(in super::super) trait BuiltinMethodsInterface { value: vir_mid::Rvalue, position: vir_low::Position, ) -> SpannedEncodingResult<()>; + fn get_reborrow_target_variable( + &self, + lifetime: &vir_mid::ty::LifetimeConst, + ) -> SpannedEncodingResult<(vir_low::VariableDecl, vir_mid::Type)>; fn encode_consume_method_call( &mut self, statements: &mut Vec, @@ -1325,6 +1740,10 @@ pub(in super::super) trait BuiltinMethodsInterface { predicate: vir_mid::VariableDecl, position: vir_low::Position, ) -> SpannedEncodingResult<()>; + fn encode_restore_raw_borrowed_method( + &mut self, + ty: &vir_mid::Type, + ) -> SpannedEncodingResult<()>; fn encode_open_frac_bor_atomic_method( &mut self, ty: &vir_mid::Type, @@ -1342,6 +1761,30 @@ pub(in super::super) trait BuiltinMethodsInterface { &mut self, ty_with_lifetime: &vir_mid::Type, ) -> SpannedEncodingResult<()>; + fn encode_stash_range_call( + &mut self, + statements: &mut Vec, + ty: &vir_mid::Type, + pointer_value: vir_low::Expression, + start_index: vir_low::Expression, + end_index: vir_low::Expression, + label: String, + position: vir_low::Position, + ) -> SpannedEncodingResult<()>; + + fn encode_restore_stash_range_call( + &mut self, + statements: &mut Vec, + ty: &vir_mid::Type, + old_pointer_value: vir_low::Expression, + old_start_index: vir_low::Expression, + old_end_index: vir_low::Expression, + label: String, + new_address: vir_low::Expression, + new_start_index: vir_low::Expression, + new_end_index: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult<()>; } impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodsInterface for Lowerer<'p, 'v, 'tcx> { @@ -1451,9 +1894,9 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodsInterface for Lowerer<'p, 'v, 'tcx> { ); let target_memory_block = builder.create_target_memory_block()?; builder.add_precondition(target_memory_block); - let source_owned = builder.create_source_owned()?; + let source_owned = builder.create_source_owned(false)?; builder.add_precondition(source_owned); - let target_owned = builder.create_target_owned()?; + let target_owned = builder.create_target_owned(false)?; builder.add_postcondition(target_owned); let source_memory_block = builder.create_source_memory_block()?; builder.add_postcondition(source_memory_block); @@ -1461,7 +1904,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodsInterface for Lowerer<'p, 'v, 'tcx> { builder.add_target_validity_postcondition()?; if has_body { builder.create_body(); - let source_owned = builder.create_source_owned()?; + let source_owned = builder.create_source_owned(true)?; builder.add_statement(vir_low::Statement::unfold_no_pos(source_owned)); } match &type_decl { @@ -1474,13 +1917,17 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodsInterface for Lowerer<'p, 'v, 'tcx> { builder.add_memory_block_copy_call()?; } vir_mid::TypeDecl::Reference(vir_mid::type_decl::Reference { - uniqueness, .. + uniqueness, + lifetimes, + .. }) => { builder.add_memory_block_copy_call()?; if uniqueness.is_unique() { builder.change_unique_ref_place()?; } else { - builder.duplicate_frac_ref()?; + // FIXME: Have a getter for the first lifetime. + let lifetime = &lifetimes[0]; + builder.duplicate_frac_ref(lifetime)?; } } vir_mid::TypeDecl::TypeVar(_) @@ -1511,8 +1958,20 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodsInterface for Lowerer<'p, 'v, 'tcx> { _ => unimplemented!("{type_decl:?}"), } if has_body { - let target_owned = builder.create_target_owned()?; + let target_owned = builder.create_target_owned(true)?; builder.add_statement(vir_low::Statement::fold_no_pos(target_owned)); + if let vir_mid::TypeDecl::Reference(vir_mid::type_decl::Reference { + uniqueness, + lifetimes, + .. + }) = &type_decl + { + if uniqueness.is_unique() { + // FIXME: Have a getter for the first lifetime. + let lifetime = &lifetimes[0]; + builder.add_dead_lifetime_hack(lifetime)?; + } + } } let method = builder.build(); self.declare_method(method)?; @@ -1578,7 +2037,8 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodsInterface for Lowerer<'p, 'v, 'tcx> { BuiltinMethodKind::DuplicateFracRef, )?; builder.create_parameters()?; - builder.add_same_address_precondition()?; + builder.add_permission_amount_positive_precondition()?; + // builder.add_same_address_precondition()?; builder.add_frac_ref_pre_postcondition()?; let method = builder.build(); self.declare_method(method)?; @@ -1607,7 +2067,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodsInterface for Lowerer<'p, 'v, 'tcx> { "copy_place", &normalized_type, &type_decl, - BuiltinMethodKind::MovePlace, + BuiltinMethodKind::CopyPlace, )?; builder.create_parameters()?; // FIXME: To generate body for arrays, we would need to generate a @@ -1624,12 +2084,12 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodsInterface for Lowerer<'p, 'v, 'tcx> { let source_owned = builder.create_source_owned()?; builder.add_precondition(source_owned.clone()); builder.add_postcondition(source_owned); - let target_owned = builder.create_target_owned()?; + let target_owned = builder.create_target_owned(false)?; builder.add_postcondition(target_owned); builder.add_target_validity_postcondition()?; if has_body { builder.create_body(); - let source_owned = builder.create_source_owned()?; + let source_owned = builder.create_source_owned_predicate()?; builder.add_statement(vir_low::Statement::unfold_no_pos(source_owned)); } match &type_decl { @@ -1641,18 +2101,32 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodsInterface for Lowerer<'p, 'v, 'tcx> { | vir_mid::TypeDecl::Map(_) => { builder.add_memory_block_copy_call()?; } + vir_mid::TypeDecl::Reference(vir_mid::type_decl::Reference { + uniqueness, + lifetimes, + .. + }) => { + // FIXME: Have a getter for the first lifetime. + let lifetime = &lifetimes[0]; + assert!(uniqueness.is_shared()); + builder.add_memory_block_copy_call()?; + builder.duplicate_frac_ref(lifetime)?; + } vir_mid::TypeDecl::Struct(decl) => { builder.add_split_target_memory_block_call()?; for field in &decl.fields { builder.add_copy_place_call_for_field(field)?; } } + vir_mid::TypeDecl::Trusted(_) | vir_mid::TypeDecl::TypeVar(_) => { + assert!(!has_body); + } _ => unimplemented!("{type_decl:?}"), } if has_body { - let target_owned = builder.create_target_owned()?; + let target_owned = builder.create_target_owned(true)?; builder.add_statement(vir_low::Statement::fold_no_pos(target_owned)); - let source_owned = builder.create_source_owned()?; + let source_owned = builder.create_source_owned_predicate()?; builder.add_statement(vir_low::Statement::fold_no_pos(source_owned)); } let method = builder.build(); @@ -1698,7 +2172,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodsInterface for Lowerer<'p, 'v, 'tcx> { let target_memory_block = builder.create_target_memory_block()?; builder.add_precondition(target_memory_block); builder.add_source_validity_precondition()?; - let target_owned = builder.create_target_owned()?; + let target_owned = builder.create_target_owned(false)?; builder.add_postcondition(target_owned); if has_body { builder.create_body(); @@ -1729,7 +2203,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodsInterface for Lowerer<'p, 'v, 'tcx> { _ => unimplemented!("{type_decl:?}"), } if has_body { - let target_owned = builder.create_target_owned()?; + let target_owned = builder.create_target_owned(true)?; builder.add_statement(vir_low::Statement::fold_no_pos(target_owned)); } let method = builder.build(); @@ -1754,30 +2228,92 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodsInterface for Lowerer<'p, 'v, 'tcx> { let method_name = self.encode_havoc_owned_non_aliased_method_name(ty)?; let type_decl = self.encoder.get_type_decl_mid(ty)?; var_decls! { - place: Place, + place: PlaceOption, root_address: Address, old_snapshot: {ty.to_snapshot(self)?}, fresh_snapshot: {ty.to_snapshot(self)?} }; + let position = vir_low::Position::default(); let validity = self.encode_snapshot_valid_call_for_type(fresh_snapshot.clone().into(), ty)?; let mut parameters = vec![place.clone(), root_address.clone(), old_snapshot.clone()]; parameters.extend(self.create_lifetime_parameters(&type_decl)?); - let predicate_in = self.owned_non_aliased_full_vars( + let predicate_in = self.owned_non_aliased_full_vars_with_snapshot( CallContext::BuiltinMethod, ty, &type_decl, &place, &root_address, &old_snapshot, + position, + )?; + let predicate_out = self.owned_non_aliased_full_vars_with_snapshot( + CallContext::BuiltinMethod, + ty, + &type_decl, + &place, + &root_address, + &fresh_snapshot, + position, + )?; + let method = vir_low::MethodDecl::new( + method_name, + vir_low::MethodKind::Havoc, + parameters, + vec![fresh_snapshot.clone()], + vec![predicate_in], + vec![predicate_out, validity], + None, + ); + self.declare_method(method)?; + } + Ok(()) + } + fn encode_havoc_unique_ref_method(&mut self, ty: &vir_mid::Type) -> SpannedEncodingResult<()> { + let ty_identifier = ty.get_identifier(); + if !self + .builtin_methods_state + .encoded_unique_ref_havoc_methods + .contains(&ty_identifier) + { + self.builtin_methods_state + .encoded_unique_ref_havoc_methods + .insert(ty_identifier); + use vir_low::macros::*; + let method_name = self.encode_havoc_unique_ref_method_name(ty)?; + let type_decl = self.encoder.get_type_decl_mid(ty)?; + var_decls! { + place: PlaceOption, + root_address: Address, + lifetime: Lifetime, + fresh_snapshot: {ty.to_snapshot(self)?} + }; + let position = vir_low::Position::default(); + let validity = + self.encode_snapshot_valid_call_for_type(fresh_snapshot.clone().into(), ty)?; + let mut parameters = vec![place.clone(), root_address.clone(), lifetime.clone()]; + parameters.extend(self.create_lifetime_parameters(&type_decl)?); + let TODO_target_slice_len = None; + let predicate_in = self.unique_ref_full_vars( + CallContext::BuiltinMethod, + ty, + &type_decl, + &place, + &root_address, + &lifetime, + TODO_target_slice_len.clone(), + position, )?; - let predicate_out = self.owned_non_aliased_full_vars( + let predicate_out = self.unique_ref_full_vars_with_current_snapshot( CallContext::BuiltinMethod, ty, &type_decl, &place, &root_address, &fresh_snapshot, + &lifetime, + TODO_target_slice_len, + position, )?; let method = vir_low::MethodDecl::new( method_name, @@ -1810,6 +2346,8 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodsInterface for Lowerer<'p, 'v, 'tcx> { .encoded_memory_block_split_methods .insert(ty_identifier); + self.encode_compute_address(ty)?; + let type_decl = self.encoder.get_type_decl_mid(ty)?; let normalized_type = ty.normalize_type(); let mut builder = MemoryBlockSplitMethodBuilder::new( @@ -1851,45 +2389,84 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodsInterface for Lowerer<'p, 'v, 'tcx> { } Ok(()) } - fn encode_memory_block_join_method(&mut self, ty: &vir_mid::Type) -> SpannedEncodingResult<()> { + fn encode_memory_block_range_split_method( + &mut self, + ty: &vir_mid::Type, + ) -> SpannedEncodingResult<()> { let ty_identifier = ty.get_identifier(); if !self .builtin_methods_state - .encoded_memory_block_join_methods + .encoded_memory_block_range_split_methods .contains(&ty_identifier) { - assert!( - !ty.is_trusted() && !ty.is_type_var(), - "Trying to join an abstract type." - ); + // assert!( + // !ty.is_trusted() && !ty.is_type_var(), + // "Trying to split an abstract type." + // ); self.builtin_methods_state - .encoded_memory_block_join_methods + .encoded_memory_block_range_split_methods .insert(ty_identifier); + self.encode_compute_address(ty)?; let type_decl = self.encoder.get_type_decl_mid(ty)?; let normalized_type = ty.normalize_type(); - let mut builder = MemoryBlockJoinMethodBuilder::new( + let mut builder = MemoryBlockRangeSplitMethodBuilder::new( self, vir_low::MethodKind::LowMemoryOperation, - "memory_block_join", + "memory_block_range_split", &normalized_type, &type_decl, BuiltinMethodKind::JoinMemoryBlock, )?; builder.create_parameters()?; - builder.add_permission_amount_positive_precondition()?; - builder.add_whole_memory_block_postcondition()?; - match &type_decl { - vir_mid::TypeDecl::Struct(decl) => { - builder.add_padding_memory_block_precondition()?; - let mut field_to_bytes_equalities = Vec::new(); - for field in &decl.fields { - builder.add_field_memory_block_precondition(field)?; - field_to_bytes_equalities - .push(builder.create_field_to_bytes_equality(field)?); - } - builder - .add_fields_to_bytes_equalities_postcondition(field_to_bytes_equalities)?; + builder.add_whole_memory_block_precondition()?; + builder.add_memory_block_range_postcondition()?; + builder.add_byte_values_preserved_postcondition()?; + let method = builder.build(); + self.declare_method(method)?; + } + Ok(()) + } + fn encode_memory_block_join_method(&mut self, ty: &vir_mid::Type) -> SpannedEncodingResult<()> { + let ty_identifier = ty.get_identifier(); + if !self + .builtin_methods_state + .encoded_memory_block_join_methods + .contains(&ty_identifier) + { + assert!( + !ty.is_trusted() && !ty.is_type_var(), + "Trying to join an abstract type." + ); + self.builtin_methods_state + .encoded_memory_block_join_methods + .insert(ty_identifier); + + self.encode_compute_address(ty)?; + let type_decl = self.encoder.get_type_decl_mid(ty)?; + let normalized_type = ty.normalize_type(); + let mut builder = MemoryBlockJoinMethodBuilder::new( + self, + vir_low::MethodKind::LowMemoryOperation, + "memory_block_join", + &normalized_type, + &type_decl, + BuiltinMethodKind::JoinMemoryBlock, + )?; + builder.create_parameters()?; + builder.add_permission_amount_positive_precondition()?; + builder.add_whole_memory_block_postcondition()?; + match &type_decl { + vir_mid::TypeDecl::Struct(decl) => { + builder.add_padding_memory_block_precondition()?; + let mut field_to_bytes_equalities = Vec::new(); + for field in &decl.fields { + builder.add_field_memory_block_precondition(field)?; + field_to_bytes_equalities + .push(builder.create_field_to_bytes_equality(field)?); + } + builder + .add_fields_to_bytes_equalities_postcondition(field_to_bytes_equalities)?; } vir_mid::TypeDecl::Enum(decl) => { builder.add_padding_memory_block_precondition()?; @@ -1919,6 +2496,44 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodsInterface for Lowerer<'p, 'v, 'tcx> { } Ok(()) } + fn encode_memory_block_range_join_method( + &mut self, + ty: &vir_mid::Type, + ) -> SpannedEncodingResult<()> { + let ty_identifier = ty.get_identifier(); + if !self + .builtin_methods_state + .encoded_memory_block_range_join_methods + .contains(&ty_identifier) + { + // assert!( + // !ty.is_trusted() && !ty.is_type_var(), + // "Trying to join an abstract type." + // ); + self.builtin_methods_state + .encoded_memory_block_range_join_methods + .insert(ty_identifier); + + self.encode_compute_address(ty)?; + let type_decl = self.encoder.get_type_decl_mid(ty)?; + let normalized_type = ty.normalize_type(); + let mut builder = MemoryBlockRangeJoinMethodBuilder::new( + self, + vir_low::MethodKind::LowMemoryOperation, + "memory_block_range_join", + &normalized_type, + &type_decl, + BuiltinMethodKind::JoinMemoryBlock, + )?; + builder.create_parameters()?; + builder.add_memory_block_range_precondition()?; + builder.add_whole_memory_block_postcondition()?; + builder.add_byte_values_preserved_postcondition()?; + let method = builder.build(); + self.declare_method(method)?; + } + Ok(()) + } fn encode_havoc_memory_block_method_name( &mut self, ty: &vir_mid::Type, @@ -1982,7 +2597,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodsInterface for Lowerer<'p, 'v, 'tcx> { .encoded_into_memory_block_methods .insert(ty_identifier); - self.mark_owned_non_aliased_as_unfolded(ty)?; + self.mark_owned_predicate_as_unfolded(ty)?; let type_decl = self.encoder.get_type_decl_mid(ty)?; let normalized_type = ty.normalize_type(); @@ -1996,7 +2611,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodsInterface for Lowerer<'p, 'v, 'tcx> { )?; builder.create_parameters()?; builder.add_const_parameters_validity_precondition()?; - let predicate = builder.create_owned()?; + let predicate = builder.create_owned(false)?; builder.add_precondition(predicate); let memory_block = builder.create_target_memory_block()?; builder.add_postcondition(memory_block); @@ -2011,7 +2626,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodsInterface for Lowerer<'p, 'v, 'tcx> { ); if has_body { builder.create_body(); - let predicate = builder.create_owned()?; + let predicate = builder.create_owned(true)?; builder.add_statement(vir_low::Statement::unfold_no_pos(predicate)); } match &type_decl { @@ -2066,11 +2681,18 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodsInterface for Lowerer<'p, 'v, 'tcx> { let method_name = self.encode_assign_method_name(target.get_type(), &value)?; self.encode_assign_method(&method_name, target.get_type(), &value)?; let target_place = self.encode_expression_as_place(&target)?; - let target_address = self.extract_root_address(&target)?; + let target_address = self.encode_expression_as_place_address(&target)?; + // let target_address = self.extract_root_address(&target)?; let mut arguments = vec![target_place.clone(), target_address.clone()]; self.encode_rvalue_arguments(&target, &mut arguments, &value)?; let target_value_type = target.get_type().to_snapshot(self)?; let result_value = self.create_new_temporary_variable(target_value_type)?; + let position = match &value { + vir_mid::Rvalue::CheckedBinaryOp(_) => self + .encoder + .change_error_context(position, ErrorCtxt::CheckedBinaryOpPrecondition), + _ => position, + }; let assign_statement = vir_low::Statement::method_call( method_name, arguments, @@ -2087,7 +2709,8 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodsInterface for Lowerer<'p, 'v, 'tcx> { vir_mid::Expression::field(value.place.clone(), discriminant_field, position) }; let source_place = self.encode_expression_as_place(&source)?; - let source_root_address = self.extract_root_address(&source)?; + // let source_root_address = self.extract_root_address(&source)?; + let source_address = self.encode_expression_as_place_address(&source)?; let source_permission_amount = if let Some(source_permission) = &value.source_permission { source_permission.to_procedure_snapshot(self)?.into() @@ -2104,17 +2727,16 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodsInterface for Lowerer<'p, 'v, 'tcx> { target_place, target_address, source_place, - source_root_address, + source_address, source_snapshot.clone(), source_permission_amount, )?]; - let new_snapshot = self.new_snapshot_variable_version(&target.get_base(), position)?; - self.encode_snapshot_update_with_new_snapshot( + let new_snapshot = self.encode_snapshot_update_with_new_snapshot( &mut copy_place_statements, &target, source_snapshot, position, - Some(new_snapshot.clone()), + // Some(new_snapshot.clone()), )?; if let Some(conditions) = value.use_field { let mut disjuncts = Vec::new(); @@ -2122,12 +2744,12 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodsInterface for Lowerer<'p, 'v, 'tcx> { disjuncts.push(self.lower_block_marker_condition(condition)?); } let mut else_branch = vec![assign_statement]; - self.encode_snapshot_update_with_new_snapshot( + let new_snapshot_else_branch = self.encode_snapshot_update_with_new_snapshot( &mut else_branch, &target, result_value.into(), position, - Some(new_snapshot), + // Some(new_snapshot), )?; statements.push(vir_low::Statement::conditional( disjuncts.into_iter().disjoin(), @@ -2135,6 +2757,10 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodsInterface for Lowerer<'p, 'v, 'tcx> { else_branch, position, )); + statements.push(vir_low::Statement::assume( + vir_low::Expression::equals(new_snapshot, new_snapshot_else_branch), + position, + )); } else { // Use field unconditionally. statements.extend(copy_place_statements); @@ -2147,25 +2773,95 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodsInterface for Lowerer<'p, 'v, 'tcx> { result_value.clone().into(), position, )?; - if let vir_mid::Rvalue::Ref(value) = value { - let snapshot = if value.uniqueness.is_unique() { - self.reference_target_final_snapshot( - target.get_type(), - result_value.into(), - position, - )? - } else { - self.reference_target_current_snapshot( - target.get_type(), - result_value.into(), - position, - )? - }; - self.encode_snapshot_update(statements, &value.place, snapshot, position)?; + // if let vir_mid::Rvalue::Ref(value) = value { + // let snapshot = if value.uniqueness.is_unique() { + // self.reference_target_final_snapshot( + // target.get_type(), + // result_value.into(), + // position, + // )? + // } else { + // self.reference_target_current_snapshot( + // target.get_type(), + // result_value.into(), + // position, + // )? + // }; + // self.encode_snapshot_update(statements, &value.place, snapshot, position)?; + // } + match value { + vir_mid::Rvalue::Ref(value) => { + let snapshot = if value.uniqueness.is_unique() { + self.reference_target_final_snapshot( + target.get_type(), + result_value.into(), + position, + )? + } else { + self.reference_target_current_snapshot( + target.get_type(), + result_value.into(), + position, + )? + }; + self.encode_snapshot_update(statements, &value.place, snapshot, position)?; + } + vir_mid::Rvalue::Reborrow(value) => { + assert!(self + .builtin_methods_state + .reborrow_target_variables + .insert( + value.new_borrow_lifetime, + (result_value, target.get_type().clone()) + ) + .is_none()); + } + // vir_mid::Rvalue::AddressOf(value) => { + // let address = self.pointer_address( + // target.get_type(), + // result_value.clone().into(), + // position, + // )?; + // let heap = self.heap_variable_version_at_label(&None)?; + // // statements.push(vir_low::Statement::assume( + // // vir_low::Expression::container_op_no_pos( + // // vir_low::ContainerOpKind::MapContains, + // // heap.ty.clone(), + // // vec![heap.into(), address], + // // ), + // // position, + // // )); + // let heap_chunk = self.pointer_target_snapshot( + // target.get_type(), + // &None, + // result_value.into(), + // position, + // )?; + // statements.push(vir_low::Statement::assume( + // vir_low::Expression::equals( + // heap_chunk, + // value.place.to_procedure_snapshot(self)?, + // ), + // position, + // )); + // } + _ => {} } } Ok(()) } + fn get_reborrow_target_variable( + &self, + lifetime: &vir_mid::ty::LifetimeConst, + ) -> SpannedEncodingResult<(vir_low::VariableDecl, vir_mid::Type)> { + let variable_with_type = self + .builtin_methods_state + .reborrow_target_variables + .get(lifetime) + .cloned() + .unwrap(); + Ok(variable_with_type) + } fn encode_consume_method_call( &mut self, statements: &mut Vec, @@ -2176,6 +2872,29 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodsInterface for Lowerer<'p, 'v, 'tcx> { self.encode_consume_operand_method(&method_name, &operand, position)?; let mut arguments = Vec::new(); self.encode_operand_arguments(&mut arguments, &operand, true)?; + { + // Mark the produced MemoryBlock as non-aliasable instance. + assert!( + matches!(operand.kind, vir_mid::OperandKind::Move | vir_mid::OperandKind::Constant), + "Our assumption that all consume operands are either moves or constants is wrong: {operand}." + ); + if vir_mid::OperandKind::Move == operand.kind + && !operand.expression.is_behind_pointer_dereference() + { + // assert!( + // operand.expression.is_local(), + // "Our assumption that all consume operands are moves of locals is wrong: {operand}." + // ); + self.mark_place_as_used_in_memory_block(&operand.expression)?; + } + // let ty = operand.expression.get_type(); + // let address = self.encode_expression_as_place_address(&operand.expression)?; + // let size = self.encode_type_size_expression2(ty, ty)?; + // let predicate_acc = self + // .encode_memory_block_acc(address, size, position)? + // .unwrap_predicate_access_predicate(); + // self.mark_predicate_as_non_aliased(predicate_acc)?; + } statements.push(vir_low::Statement::method_call( method_name, arguments, @@ -2194,9 +2913,10 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodsInterface for Lowerer<'p, 'v, 'tcx> { vir_mid::Predicate::OwnedNonAliased(predicate) => { let ty = predicate.place.get_type(); self.encode_havoc_owned_non_aliased_method(ty)?; - self.mark_owned_non_aliased_as_unfolded(ty)?; + self.mark_owned_predicate_as_unfolded(ty)?; let place = self.encode_expression_as_place(&predicate.place)?; - let address = self.extract_root_address(&predicate.place)?; + // let address = self.extract_root_address(&predicate.place)?; + let address = self.encode_expression_as_place_address(&predicate.place)?; let old_snapshot = predicate.place.to_procedure_snapshot(self)?; let snapshot_type = ty.to_snapshot(self)?; let fresh_snapshot = self.create_new_temporary_variable(snapshot_type)?; @@ -2216,6 +2936,35 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodsInterface for Lowerer<'p, 'v, 'tcx> { position, )?; } + vir_mid::Predicate::UniqueRef(predicate) => { + let ty = predicate.place.get_type(); + self.encode_havoc_unique_ref_method(ty)?; + self.mark_unique_ref_as_used(ty)?; + let lifetime = + self.encode_lifetime_const_into_procedure_variable(predicate.lifetime)?; + let place = self.encode_expression_as_place(&predicate.place)?; + let address = self.encode_expression_as_place_address(&predicate.place)?; + let snapshot_type = ty.to_snapshot(self)?; + let fresh_snapshot = self.create_new_temporary_variable(snapshot_type)?; + let method_name = self.encode_havoc_unique_ref_method_name(ty)?; + let mut arguments = vec![place, address, lifetime.into()]; + arguments.extend(self.create_lifetime_arguments(CallContext::Procedure, ty)?); + statements.push(vir_low::Statement::method_call( + method_name, + arguments, + vec![fresh_snapshot.clone().into()], + position, + )); + self.encode_snapshot_update( + statements, + &predicate.place, + fresh_snapshot.into(), + position, + )?; + } + vir_mid::Predicate::FracRef(_) => { + // Fractional references are immutable, so havoc is a no-op. + } vir_mid::Predicate::MemoryBlockStack(predicate) => { let ty = predicate.place.get_type(); self.encode_havoc_memory_block_method(ty)?; @@ -2262,63 +3011,184 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodsInterface for Lowerer<'p, 'v, 'tcx> { lifetime: Lifetime, lifetime_perm: Perm, owned_perm: Perm, - place: Place, - root_address: Address, + place: PlaceOption, + address: Address, current_snapshot: {ty.to_snapshot(self)?} }; + let position = vir_low::Position::default(); let lifetime_access = expr! { acc(LifetimeToken(lifetime), lifetime_perm) }; - let frac_ref_access = self.frac_ref_full_vars( + let TODO_target_slice_len = None; + let frac_ref_access = self.frac_ref_full_vars_with_current_snapshot( CallContext::BuiltinMethod, ty, - ty, + &type_decl, &place, - &root_address, + &address, ¤t_snapshot, &lifetime, + TODO_target_slice_len.clone(), + position, )?; - let owned_access = self.owned_non_aliased( + let prestate_snapshot = vir_low::Expression::labelled_old_no_pos( + None, + self.frac_ref_snap( + CallContext::BuiltinMethod, + ty, + &type_decl, + place.clone().into(), + address.clone().into(), + lifetime.clone().into(), + TODO_target_slice_len, + position, + )?, + ); + let owned_access = self.owned_non_aliased_with_snapshot( CallContext::BuiltinMethod, ty, &type_decl, place.clone().into(), - root_address.clone().into(), - current_snapshot.clone().into(), + address.clone().into(), + prestate_snapshot, Some(owned_perm.clone().into()), + position, )?; - let method = vir_low::MethodDecl::new( - self.encode_open_frac_bor_atomic_method_name(ty)?, - vir_low::MethodKind::MirOperation, - vec![ + let owned_access_viewshift = self.owned_non_aliased( + CallContext::BuiltinMethod, + ty, + &type_decl, + place.clone().into(), + address.clone().into(), + Some(owned_perm.clone().into()), + position, + )?; + let mut parameters = vec![ + lifetime.clone(), + lifetime_perm.clone(), + place.clone(), + address.clone(), + current_snapshot.clone(), + ]; + parameters.extend(self.create_lifetime_parameters(&type_decl)?); + parameters.extend(self.create_const_parameters(&type_decl)?); + let close_frac_ref_predicate = expr! { + acc(CloseFracRef( lifetime, - lifetime_perm.clone(), + lifetime_perm, place, - root_address, + address, current_snapshot, - ], + owned_perm + )) + }; + let lifetime_perm_bounds = expr! { + ([vir_low::Expression::no_permission()] < lifetime_perm) && + (lifetime_perm < [vir_low::Expression::full_permission()]) + }; + let owned_perm_bounds = expr! { + ([vir_low::Expression::no_permission()] < owned_perm) && + (owned_perm < [vir_low::Expression::full_permission()]) + }; + let method = vir_low::MethodDecl::new( + self.encode_open_frac_bor_atomic_method_name(ty)?, + vir_low::MethodKind::MirOperation, + parameters, vec![owned_perm.clone()], vec![ - expr! { - [vir_low::Expression::no_permission()] < lifetime_perm - }, + lifetime_perm_bounds.clone(), lifetime_access.clone(), frac_ref_access.clone(), ], vec![ - expr! { - owned_perm < [vir_low::Expression::full_permission()] - }, - expr! { - [vir_low::Expression::no_permission()] < owned_perm - }, - owned_access.clone(), - vir_low::Expression::magic_wand_no_pos( - owned_access, - expr! { [lifetime_access] && [frac_ref_access] }, - ), + owned_perm_bounds.clone(), + owned_access, + close_frac_ref_predicate.clone(), + // vir_low::Expression::magic_wand_no_pos( + // owned_access_viewshift, + // expr! { [lifetime_access] && [frac_ref_access] }, + // ), ], None, ); self.declare_method(method)?; + { + let close_frac_ref_predicate_decl = vir_low::PredicateDecl::new( + predicate_name! { CloseFracRef }, + vir_low::PredicateKind::CloseFracRef, + vec![ + lifetime.clone(), + lifetime_perm.clone(), + place.clone(), + address.clone(), + current_snapshot.clone(), + owned_perm.clone(), + ], + None, + ); + self.declare_predicate(close_frac_ref_predicate_decl)?; + let mut parameters = vec![ + lifetime.clone(), + lifetime_perm, + place.clone(), + address.clone(), + current_snapshot.clone(), + owned_perm, + ]; + parameters.extend(self.create_lifetime_parameters(&type_decl)?); + parameters.extend(self.create_const_parameters(&type_decl)?); + // Apply the viewshift encoded in the `CloseFracRef` predicate. + let close_method = vir_low::MethodDecl::new( + method_name! { close_frac_ref }, + vir_low::MethodKind::MirOperation, + parameters, + Vec::new(), + vec![ + lifetime_perm_bounds, + owned_perm_bounds, + close_frac_ref_predicate, + owned_access_viewshift, + ], + vec![lifetime_access, frac_ref_access], + None, + ); + self.declare_method(close_method)?; + } + } + Ok(()) + } + + fn encode_restore_raw_borrowed_method( + &mut self, + ty: &vir_mid::Type, + ) -> SpannedEncodingResult<()> { + let ty_identifier = ty.get_identifier(); + if !self + .builtin_methods_state + .encoded_restore_raw_borrowed_methods + .contains(&ty_identifier) + { + self.builtin_methods_state + .encoded_restore_raw_borrowed_methods + .insert(ty_identifier); + + self.encode_restore_raw_borrowed_transition_predicate(ty)?; + + let type_decl = self.encoder.get_type_decl_mid(ty)?; + let normalized_type = ty.normalize_type(); + + let mut builder = RestoreRawBorrowedMethodBuilder::new( + self, + vir_low::MethodKind::LowMemoryOperation, + "restore_raw_borrowed", + &normalized_type, + &type_decl, + BuiltinMethodKind::RestoreRawBorrowed, + )?; + builder.create_parameters()?; + builder.add_aliased_source_precondition()?; + builder.add_shift_precondition()?; + builder.add_non_aliased_target_postcondition()?; + let method = builder.build(); + self.declare_method(method)?; } Ok(()) } @@ -2363,7 +3233,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodsInterface for Lowerer<'p, 'v, 'tcx> { lifetimes_expr, ); let intersect = self.create_domain_func_app( - "Lifetime", + LIFETIME_DOMAIN_NAME, "intersect", vec![lifetime_set], ty!(Lifetime), @@ -2419,7 +3289,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodsInterface for Lowerer<'p, 'v, 'tcx> { lifetimes_expr, ); let intersect = self.create_domain_func_app( - "Lifetime", + LIFETIME_DOMAIN_NAME, "intersect", vec![lifetime_set], ty!(Lifetime), @@ -2554,28 +3424,31 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodsInterface for Lowerer<'p, 'v, 'tcx> { var_decls! { lifetime: Lifetime, lifetime_perm: Perm, - place: Place, - root_address: Address, + place: PlaceOption, + address: Address, current_snapshot: {ty.to_snapshot(self)?}, final_snapshot: {ty.to_snapshot(self)?} }; - let owned_predicate = self.owned_non_aliased_full_vars( + let position = vir_low::Position::default(); + let owned_predicate = self.owned_non_aliased_full_vars_with_snapshot( CallContext::BuiltinMethod, ty, &type_decl, &place, - &root_address, + &address, ¤t_snapshot, + position, )?; - let unique_ref_predicate = self.unique_ref_full_vars( + let unique_ref_predicate = self.unique_ref_full_vars_with_current_snapshot( CallContext::BuiltinMethod, ty, &type_decl, &place, - &root_address, + &address, ¤t_snapshot, - &final_snapshot, &lifetime, + None, + position, )?; let open_method = vir_low::MethodDecl::new( method_name! { open_mut_ref }, @@ -2584,7 +3457,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodsInterface for Lowerer<'p, 'v, 'tcx> { lifetime.clone(), lifetime_perm.clone(), place.clone(), - root_address.clone(), + address.clone(), current_snapshot.clone(), final_snapshot.clone(), ], @@ -2618,7 +3491,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodsInterface for Lowerer<'p, 'v, 'tcx> { lifetime, lifetime_perm, place, - root_address, + address, final_snapshot ))}, ], @@ -2629,11 +3502,12 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodsInterface for Lowerer<'p, 'v, 'tcx> { { let close_mut_ref_predicate = vir_low::PredicateDecl::new( predicate_name! { CloseMutRef }, + vir_low::PredicateKind::WithoutSnapshotWhole, vec![ lifetime.clone(), lifetime_perm.clone(), place.clone(), - root_address.clone(), + address.clone(), final_snapshot.clone(), ], None, @@ -2647,7 +3521,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodsInterface for Lowerer<'p, 'v, 'tcx> { lifetime.clone(), lifetime_perm.clone(), place.clone(), - root_address.clone(), + address.clone(), current_snapshot.clone(), final_snapshot.clone(), ], @@ -2658,7 +3532,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodsInterface for Lowerer<'p, 'v, 'tcx> { lifetime, lifetime_perm, place, - root_address, + address, final_snapshot ))}, owned_predicate, @@ -2693,8 +3567,8 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodsInterface for Lowerer<'p, 'v, 'tcx> { lft: Lifetime, old_lft: Lifetime, lifetime_perm: Perm, - place: Place, - root_address: Address, + place: PlaceOption, + address: Address, current_snapshot: {target_type.to_snapshot(self)?}, final_snapshot: {target_type.to_snapshot(self)?} } @@ -2703,12 +3577,13 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodsInterface for Lowerer<'p, 'v, 'tcx> { old_lft.clone(), lifetime_perm.clone(), place.clone(), - root_address.clone(), + address.clone(), current_snapshot.clone(), ]; if reference_type.uniqueness.is_unique() { - parameters.push(final_snapshot.clone()); + parameters.push(final_snapshot); } + let position = vir_low::Position::default(); let mut pres = vec![ expr! { [vir_low::Expression::no_permission()] < lifetime_perm }, expr! { lifetime_perm < [vir_low::Expression::full_permission()] }, @@ -2717,44 +3592,73 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodsInterface for Lowerer<'p, 'v, 'tcx> { ]; let mut posts = vec![expr! { acc(LifetimeToken(lft), lifetime_perm)}]; if reference_type.uniqueness.is_unique() { - pres.push(self.unique_ref_full_vars( + pres.push(self.unique_ref_full_vars_with_current_snapshot( CallContext::BuiltinMethod, target_type, target_type, &place, - &root_address, + &address, ¤t_snapshot, - &final_snapshot, &old_lft, + None, + position, )?); - posts.push(self.unique_ref_full_vars( + posts.push(self.unique_ref_full_vars_with_current_snapshot( CallContext::BuiltinMethod, target_type, target_type, &place, - &root_address, + &address, ¤t_snapshot, - &final_snapshot, &lft, + None, + position, )?); + let old_final_snapshot = self.unique_ref_snap( + CallContext::BuiltinMethod, + target_type, + target_type, + place.clone().into(), + address.clone().into(), + old_lft.clone().into(), + None, + true, + position, + )?; + let new_final_snapshot = self.unique_ref_snap( + CallContext::BuiltinMethod, + target_type, + target_type, + place.clone().into(), + address.clone().into(), + lft.clone().into(), + None, + true, + position, + )?; + posts.push(expr! { [old_final_snapshot] == [new_final_snapshot] }); } else { - pres.push(self.frac_ref_full_vars( + pres.push(self.frac_ref_full_vars_with_current_snapshot( CallContext::BuiltinMethod, target_type, target_type, &place, - &root_address, + &address, ¤t_snapshot, &old_lft, + None, + position, )?); - posts.push(self.frac_ref_full_vars( + posts.push(self.frac_ref_full_vars_with_current_snapshot( CallContext::BuiltinMethod, target_type, target_type, &place, - &root_address, + &address, ¤t_snapshot, &lft, + None, + position, )?); } parameters.extend(self.create_lifetime_parameters(target_type)?); @@ -2771,4 +3675,458 @@ impl<'p, 'v: 'p, 'tcx: 'v> BuiltinMethodsInterface for Lowerer<'p, 'v, 'tcx> { } Ok(()) } + + fn encode_stash_range_call( + &mut self, + statements: &mut Vec, + ty: &vir_mid::Type, + pointer_value: vir_low::Expression, + start_index: vir_low::Expression, + end_index: vir_low::Expression, + label: String, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + use vir_low::macros::*; + statements.push(vir_low::Statement::comment(format!( + "Stash range call for {label}" + ))); + // statements.push(vir_low::Statement::label(label.clone(), position)); + let exhale_owned = vir_low::Statement::exhale( + self.owned_aliased_range( + CallContext::Procedure, + ty, + ty, + pointer_value.clone(), + start_index.clone(), + end_index.clone(), + None, + position, + )?, + position, + ); + statements.push(exhale_owned); + let vir_mid::Type::Pointer(pointer_type) = ty else { + unreachable!("ty: {}", ty); + }; + let target_type = &*pointer_type.target_type; + let ty_identifier = target_type.get_identifier(); + if !self + .builtin_methods_state + .encoded_stashed_owned_aliased_predicates + .contains(&ty_identifier) + { + self.builtin_methods_state + .encoded_stashed_owned_aliased_predicates + .insert(ty_identifier); + let predicate = vir_low::PredicateDecl::new( + predicate_name! { StashedOwnedAliased }, + vir_low::PredicateKind::WithoutSnapshotWhole, + vec![ + var! { index: Int }, + var! { bytes: Bytes }, + var! { snapshot: { target_type.to_snapshot(self)? } }, + ], + None, + ); + self.declare_predicate(predicate)?; + } + let start_address = self.pointer_address(ty, pointer_value, position)?; + let size = self.encode_type_size_expression2(target_type, target_type)?; + let inhale_raw = vir_low::Statement::inhale( + self.encode_memory_block_range_acc( + start_address.clone(), + size.clone(), + start_index.clone(), + end_index.clone(), + position, + )?, + position, + ); + statements.push(inhale_raw); + let inhale_stash = { + let size_type = self.size_type_mid()?; + var_decls! { + index: Int + } + // let start_address = self.pointer_address( + // ty, + // pointer_value, + // position, + // )?; + let element_address = + self.address_offset(size.clone(), start_address, index.clone().into(), position)?; + let start_index = self.obtain_constant_value(&size_type, start_index, position)?; + let end_index = self.obtain_constant_value(&size_type, end_index, position)?; + let bytes = self.encode_memory_block_bytes_expression(element_address.clone(), size)?; + let snapshot = vir_low::Expression::labelled_old( + Some(label), + self.owned_aliased_snap( + CallContext::Procedure, + target_type, + target_type, + element_address.clone(), + position, + )?, + position, + ); + let stash_predicate = expr! { + acc(StashedOwnedAliased( + index, + [bytes], + [snapshot] + )) + }; + let body = expr!( + (([start_index] <= index) && (index < [end_index])) ==> + [stash_predicate] + ); + let expression = vir_low::Expression::forall( + vec![index], + vec![vir_low::Trigger::new(vec![element_address])], + body, + ); + vir_low::Statement::inhale(expression, position) + }; + statements.push(inhale_stash); + // statements.push(vir_low::Statement::label( + // format!("{}$post", label), + // position, + // )); + Ok(()) + } + + fn encode_restore_stash_range_call( + &mut self, + statements: &mut Vec, + ty: &vir_mid::Type, + old_pointer_value: vir_low::Expression, + old_start_index: vir_low::Expression, + _old_end_index: vir_low::Expression, + label: String, + new_pointer_value: vir_low::Expression, + new_start_index_usize: vir_low::Expression, + new_end_index_usize: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + statements.push(vir_low::Statement::comment(format!( + "Restore stash for {label}" + ))); + let label_post = format!("{label}$post"); + use vir_low::macros::*; + let vir_mid::Type::Pointer(pointer_type) = ty else { + unreachable!("ty: {}", ty); + }; + let size_type = self.size_type_mid()?; + let target_type = &*pointer_type.target_type; + let size = self.encode_type_size_expression2(target_type, target_type)?; + let old_start_address = self.pointer_address(ty, old_pointer_value, position)?; + let new_start_address = self.pointer_address(ty, new_pointer_value.clone(), position)?; + let new_start_index = + self.obtain_constant_value(&size_type, new_start_index_usize.clone(), position)?; + let new_end_index = + self.obtain_constant_value(&size_type, new_end_index_usize.clone(), position)?; + // let new_end_index = vir_low::Expression::add( + // new_start_index.clone(), + // vir_low::Expression::labelled_old( + // Some(label.clone()), + // vir_low::Expression::subtract( + // self.obtain_constant_value(&size_type, old_end_index, position)?, + // self.obtain_constant_value(&size_type, old_start_index.clone(), position)?, + // ), + // position, + // ), + // ); + { + // For performance reasons, we do not have global extensionality + // assumptions, but assume them when needed. + + // FIXME: Instead of having the assumption as a quantifier, assert + // that bytes are equal for the entire range and then assume that + // the byte blocks are equal. + var_decls! { + index: Int, + byte_index: Int + } + let new_element_address = self.address_offset( + size.clone(), + new_start_address.clone(), + index.clone().into(), + position, + )?; + let new_element_address_pointer = + self.address_to_pointer(ty, new_element_address.clone(), position)?; + let new_element_address_wrapped = + self.pointer_address(ty, new_element_address_pointer, position)?; + let old_index = vir_low::Expression::add( + vir_low::Expression::labelled_old( + Some(label.clone()), + self.obtain_constant_value(&size_type, old_start_index.clone(), position)?, + position, + ), + vir_low::Expression::subtract(index.clone().into(), new_start_index.clone()), + ); + let old_element_address = + self.address_offset(size.clone(), old_start_address.clone(), old_index, position)?; + let new_block_bytes = self + .encode_memory_block_bytes_expression(new_element_address.clone(), size.clone())?; + let new_block_bytes_wrapped = self + .encode_memory_block_bytes_expression(new_element_address_wrapped, size.clone())?; + let old_block_bytes = + self.encode_memory_block_bytes_expression(old_element_address, size.clone())?; + let old_block_bytes = + vir_low::Expression::labelled_old(Some(label_post), old_block_bytes, position); + let element_size_int = + self.obtain_constant_value(&size_type, size.clone(), position)?; + let new_read_element_byte = self.encode_read_byte_expression_int( + new_block_bytes.clone(), + byte_index.clone().into(), + position, + )?; + let new_read_element_byte_wrapped = self.encode_read_byte_expression_int( + new_block_bytes_wrapped, + byte_index.clone().into(), + position, + )?; + let old_read_element_byte = self.encode_read_byte_expression_int( + old_block_bytes.clone(), + byte_index.clone().into(), + position, + )?; + let memory_block_range_join_trigger = self.call_trigger_function( + "memory_block_range_join_trigger", + vec![index.clone().into(), byte_index.clone().into()], + position, + )?; + let index_usize = + self.construct_constant_snapshot(&size_type, index.clone().into(), position)?; + let index_is_usize = self.obtain_constant_value(&size_type, index_usize, position)?; + let bytes_equal_body = expr!( + ((([new_start_index.clone()] <= index) && (index < [new_end_index.clone()])) && + (([0.into()] <= byte_index) && (byte_index < [element_size_int]))) ==> + ([memory_block_range_join_trigger] && + ([index_is_usize] == index) && + ([new_read_element_byte_wrapped.clone()] == [old_read_element_byte]) && + ([new_read_element_byte.clone()] == [new_read_element_byte_wrapped]) + ) + ); + let bytes_equal = vir_low::Expression::forall( + vec![index.clone(), byte_index], + vec![vir_low::Trigger::new(vec![new_read_element_byte])], + bytes_equal_body, + ); + let assert_byte_equality = + vir_low::Statement::assert_no_pos(bytes_equal).set_default_position(position); + statements.push(assert_byte_equality); + let body = expr!( + (([new_start_index.clone()] <= index) && (index < [new_end_index.clone()])) ==> + // ( + ([new_block_bytes] == [old_block_bytes]) + // == [bytes_equal]) + ); + let expression = vir_low::Expression::forall( + vec![index], + vec![vir_low::Trigger::new(vec![new_element_address])], + body, + ); + let assume_extensionality = vir_low::Statement::assume(expression, position); + statements.push(assume_extensionality); + }; + let exhale_stash = { + var_decls! { + index: Int + } + // let start_address = self.pointer_address( + // ty, + // pointer_value, + // position, + // )?; + let old_index = vir_low::Expression::add( + vir_low::Expression::labelled_old( + Some(label.clone()), + self.obtain_constant_value(&size_type, old_start_index.clone(), position)?, + position, + ), + vir_low::Expression::subtract(index.clone().into(), new_start_index.clone()), + ); + let old_element_address = self.address_offset( + size.clone(), + old_start_address.clone(), + old_index.clone(), + position, + )?; + let new_element_address = self.address_offset( + size.clone(), + new_start_address.clone(), + index.clone().into(), + position, + )?; + // let start_index = self.obtain_constant_value(&size_type, start_index, position)?; + // let end_index = self.obtain_constant_value(&size_type, end_index, position)?; + let bytes = self + .encode_memory_block_bytes_expression(new_element_address.clone(), size.clone())?; + let snapshot = vir_low::Expression::labelled_old( + Some(label.clone()), + self.owned_aliased_snap( + CallContext::Procedure, + target_type, + target_type, + old_element_address, + position, + )?, + position, + ); + let stash_predicate = expr! { + acc(StashedOwnedAliased( + [old_index], + [bytes], + [snapshot] + )) + }; + let body = expr!( + (([new_start_index.clone()] <= index) && (index < [new_end_index.clone()])) ==> + [stash_predicate] + ); + let expression = vir_low::Expression::forall( + vec![index], + vec![vir_low::Trigger::new(vec![new_element_address])], + body, + ); + vir_low::Statement::exhale(expression, position) + }; + statements.push(exhale_stash); + // FIXME: Code duplication with encode_memory_block_range_acc. + let exhale_raw = { + // var_decls! { + // index: Int + // } + // let element_address = self.address_offset( + // size.clone(), + // new_start_address.clone(), + // index.clone().into(), + // position, + // )?; + // let predicate = + // self.encode_memory_block_acc(element_address.clone(), size.clone(), position)?; + // // let new_start_index = self.obtain_constant_value(&size_type, new_start_address.clone(), position)?; + // // let end_index = self.obtain_constant_value(&size_type, end_index, position)?; + // let body = expr!( + // (([new_start_index.clone()] <= index) && (index < [new_end_index.clone()])) ==> [predicate] + // ); + // let expression = vir_low::Expression::forall( + // vec![index], + // vec![vir_low::Trigger::new(vec![element_address])], + // body, + // ); + let expression = self.encode_memory_block_range_acc( + new_start_address.clone(), + size.clone(), + new_start_index_usize.clone(), + new_end_index_usize.clone(), + position, + )?; + vir_low::Statement::exhale( + expression, + // self.encode_memory_block_range_acc( + // new_start_address.clone(), + // size, + // new_start_index.clone(), + // new_end_index.clone(), + // position, + // )?, + position, + ) + }; + statements.push(exhale_raw); + let inhale_owned = { + // var_decls! { + // index: Int + // } + // let element_address = self.address_offset( + // size.clone(), + // new_start_address.clone(), + // index.clone().into(), + // position, + // )?; + // let predicate = self.owned_aliased( + // CallContext::Procedure, + // target_type, + // target_type, + // element_address.clone(), + // None, + // position, + // )?; + // // let new_start_index = self.obtain_constant_value(&size_type, new_start_address.clone(), position)?; + // // let end_index = self.obtain_constant_value(&size_type, end_index, position)?; + // let body = expr!( + // (([new_start_index.clone()] <= index) && (index < [new_end_index.clone()])) ==> + // [predicate] + // ); + // let expression = vir_low::Expression::forall( + // vec![index], + // vec![vir_low::Trigger::new(vec![element_address])], + // body, + // ); + let expression = self.owned_aliased_range( + CallContext::Procedure, + ty, + ty, + new_pointer_value, + new_start_index_usize, + new_end_index_usize, + None, + position, + )?; + vir_low::Statement::inhale(expression, position) + }; + statements.push(inhale_owned); + let inhale_snapshot_preserved = { + var_decls! { + index: Int + } + let new_element_address = self.address_offset( + size.clone(), + new_start_address, + index.clone().into(), + position, + )?; + let old_index = vir_low::Expression::add( + vir_low::Expression::labelled_old( + Some(label.clone()), + self.obtain_constant_value(&size_type, old_start_index, position)?, + position, + ), + vir_low::Expression::subtract(index.clone().into(), new_start_index.clone()), + ); + let old_element_address = + self.address_offset(size, old_start_address, old_index, position)?; + let new_snapshot = self.owned_aliased_snap( + CallContext::Procedure, + target_type, + target_type, + new_element_address.clone(), + position, + )?; + let old_snapshot = self.owned_aliased_snap( + CallContext::Procedure, + target_type, + target_type, + old_element_address, + position, + )?; + let old_snapshot = + vir_low::Expression::labelled_old(Some(label), old_snapshot, position); + let body = expr!( + (([new_start_index] <= index) && (index < [new_end_index])) ==> + ([new_snapshot] == [old_snapshot]) + ); + let expression = vir_low::Expression::forall( + vec![index], + vec![vir_low::Trigger::new(vec![new_element_address])], + body, + ); + vir_low::Statement::inhale(expression, position) + }; + statements.push(inhale_snapshot_preserved); + Ok(()) + } } diff --git a/prusti-viper/src/encoder/middle/core_proof/builtin_methods/mod.rs b/prusti-viper/src/encoder/middle/core_proof/builtin_methods/mod.rs index a04aa21f1cd..7f84d0111ec 100644 --- a/prusti-viper/src/encoder/middle/core_proof/builtin_methods/mod.rs +++ b/prusti-viper/src/encoder/middle/core_proof/builtin_methods/mod.rs @@ -1,3 +1,4 @@ +mod assertion_encoder; mod builders; mod calls; mod interface; diff --git a/prusti-viper/src/encoder/middle/core_proof/casts/interface.rs b/prusti-viper/src/encoder/middle/core_proof/casts/interface.rs new file mode 100644 index 00000000000..f53ff8e6e71 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/casts/interface.rs @@ -0,0 +1,85 @@ +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::{ + lowerer::{DomainsLowererInterface, Lowerer}, + snapshots::{IntoSnapshot, SnapshotValidityInterface, SnapshotValuesInterface}, + }, +}; +use prusti_common::config; +use vir_crate::{ + common::{expression::QuantifierHelpers, identifier::WithIdentifier}, + low as vir_low, middle as vir_mid, +}; + +const DOMAIN_NAME: &str = "Casts"; + +pub(in super::super) trait CastsInterface { + fn cast_int_to_int( + &mut self, + source_type: &vir_mid::Type, + destination_type: &vir_mid::Type, + arg: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult; +} + +impl<'p, 'v: 'p, 'tcx: 'v> CastsInterface for Lowerer<'p, 'v, 'tcx> { + fn cast_int_to_int( + &mut self, + source_type: &vir_mid::Type, + destination_type: &vir_mid::Type, + argument: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult { + let identifier = ( + source_type.get_identifier(), + destination_type.get_identifier(), + ); + let function_name = format!("cast${}${}", identifier.0, identifier.1); + let return_type = destination_type.to_snapshot(self)?; + if !self.casts_state.encoded_casts.contains(&identifier) { + self.casts_state.encoded_casts.insert(identifier); + use vir_low::macros::*; + var_decls!(parameter: {source_type.to_snapshot(self)?}); + let call = self.create_domain_func_app( + DOMAIN_NAME, + function_name.clone(), + vec![parameter.clone().into()], + return_type.clone(), + Default::default(), + )?; + let parameter_int = self.obtain_constant_value( + source_type, + parameter.clone().into(), + Default::default(), + )?; + let parameter_dst = self.construct_constant_snapshot( + destination_type, + parameter_int, + Default::default(), + )?; + let validity = if config::check_overflows() { + self.encode_snapshot_valid_call_for_type(parameter_dst.clone(), destination_type)? + } else { + true.into() + }; + let body = vir_low::Expression::forall( + vec![parameter], + vec![vir_low::Trigger::new(vec![call.clone()])], + expr! { + [validity] ==> ([call] == [parameter_dst]) + }, + ); + let axiom = + vir_low::DomainAxiomDecl::new(None, format!("{function_name}$definition"), body); + self.declare_axiom(DOMAIN_NAME, axiom)?; + } + self.create_domain_func_app( + DOMAIN_NAME, + function_name, + vec![argument], + return_type, + position, + ) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/casts/mod.rs b/prusti-viper/src/encoder/middle/core_proof/casts/mod.rs new file mode 100644 index 00000000000..8e82db10f5b --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/casts/mod.rs @@ -0,0 +1,4 @@ +mod interface; +mod state; + +pub(super) use self::{interface::CastsInterface, state::CastsState}; diff --git a/prusti-viper/src/encoder/middle/core_proof/casts/state.rs b/prusti-viper/src/encoder/middle/core_proof/casts/state.rs new file mode 100644 index 00000000000..8aa3f3b921e --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/casts/state.rs @@ -0,0 +1,6 @@ +use rustc_hash::FxHashSet; + +#[derive(Default)] +pub(in super::super) struct CastsState { + pub(super) encoded_casts: FxHashSet<(String, String)>, +} diff --git a/prusti-viper/src/encoder/middle/core_proof/compute_address/interface.rs b/prusti-viper/src/encoder/middle/core_proof/compute_address/interface.rs index 24fa0641fc7..9913cc4d52e 100644 --- a/prusti-viper/src/encoder/middle/core_proof/compute_address/interface.rs +++ b/prusti-viper/src/encoder/middle/core_proof/compute_address/interface.rs @@ -1,89 +1,120 @@ use crate::encoder::{ errors::SpannedEncodingResult, high::types::HighTypeEncoderInterface, - middle::core_proof::{ - addresses::AddressesInterface, lowerer::Lowerer, places::PlacesInterface, - references::ReferencesInterface, snapshots::IntoSnapshot, - }, + middle::core_proof::{addresses::AddressesInterface, lowerer::Lowerer}, }; use rustc_hash::FxHashSet; -use vir_crate::{common::identifier::WithIdentifier, low as vir_low, middle as vir_mid}; +use vir_crate::{ + common::identifier::WithIdentifier, + low::{self as vir_low}, + middle as vir_mid, +}; #[derive(Default)] pub(in super::super) struct ComputeAddressState { - pub(super) encoded_types: FxHashSet, + pub(super) encoded_types: FxHashSet, pub(super) encoded_roots: FxHashSet, - pub(super) axioms: Vec, + pub(super) rewrite_rules: Vec, } impl ComputeAddressState { /// Construct the final domain. pub(in super::super) fn destruct(self) -> Option { + // None if self.encoded_types.is_empty() && self.encoded_roots.is_empty() { None } else { Some(vir_low::DomainDecl { name: "ComputeAddress".to_string(), - functions: vec![vir_low::DomainFunctionDecl { - name: "compute_address".to_string(), - is_unique: false, - parameters: vir_low::macros::vars! { - place: Place, - address: Address + functions: vec![ + // vir_low::DomainFunctionDecl { + // name: "compute_address".to_string(), + // is_unique: false, + // parameters: vir_low::macros::vars! { + // place: PlaceOption, + // address: Address + // }, + // return_type: vir_low::macros::ty! { Address }, + // }, + vir_low::DomainFunctionDecl { + name: "address_is_non_aliased".to_string(), + is_unique: false, + parameters: vir_low::macros::vars! { + address: Address + }, + return_type: vir_low::Type::Bool, }, - return_type: vir_low::macros::ty! { Address }, - }], - axioms: self.axioms, + ], + axioms: Vec::new(), + rewrite_rules: self.rewrite_rules, }) } } } -trait Private { - fn encode_compute_address_axiom_for_field( - &mut self, - ty: &vir_mid::Type, - field: &vir_mid::FieldDecl, - ) -> SpannedEncodingResult; -} +impl<'p, 'v: 'p, 'tcx: 'v> Lowerer<'p, 'v, 'tcx> { + // fn encode_compute_address_axiom_for_field( + // &mut self, + // ty: &vir_mid::Type, + // field: &vir_mid::FieldDecl, + // ) -> SpannedEncodingResult { + // use vir_low::macros::*; + // let compute_address = ty!(Address); + // var_decls! { + // place: Place, + // address: Address + // }; + // let place_option = self.place_option_some_constructor(place.clone().into())?; + // let field_place = + // self.encode_field_place(ty, field, place_option.clone(), Default::default())?; + // let field_address = self.encode_field_address( + // ty, + // field, + // expr! { ComputeAddress::compute_address([place_option], address) }, + // Default::default(), + // )?; + // let source = expr! { (ComputeAddress::compute_address([field_place], address)) }; + // Ok(vir_low::DomainRewriteRuleDecl { + // comment: None, + // name: format!( + // "{}${}$compute_address_axiom", + // ty.get_identifier(), + // field.name + // ), + // egg_only: false, + // variables: vec![place, address], + // triggers: None, + // source, + // target: field_address, + // }) + // } -impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { - fn encode_compute_address_axiom_for_field( + fn encode_propagate_address_non_aliased_axiom_for_field( &mut self, ty: &vir_mid::Type, field: &vir_mid::FieldDecl, - ) -> SpannedEncodingResult { + ) -> SpannedEncodingResult { use vir_low::macros::*; - let compute_address = ty!(Address); - let body = expr! { - forall( - place: Place, address: Address :: - raw_code { - let field_place = self.encode_field_place( - ty, - field, - place.clone().into(), - Default::default() - )?; - let field_address = self.encode_field_address( - ty, - field, - expr! { ComputeAddress::compute_address(place, address) }, - Default::default(), - )?; - } - [ { (ComputeAddress::compute_address([field_place.clone()], address)) } ] - (ComputeAddress::compute_address([field_place], address)) == [field_address] - ) + let address_is_non_aliased = ty!(Bool); + var_decls! { + address: Address }; - Ok(vir_low::DomainAxiomDecl { + let field_address = + self.encode_field_address(ty, field, address.clone().into(), Default::default())?; + let source = expr! { (ComputeAddress::address_is_non_aliased([field_address])) }; + let target = expr! { (ComputeAddress::address_is_non_aliased(address)) }; + Ok(vir_low::DomainRewriteRuleDecl { comment: None, name: format!( - "{}${}$compute_address_axiom", + "{}${}$address_is_non_aliased_axiom", ty.get_identifier(), field.name ), - body, + egg_only: true, + variables: vec![address], + triggers: None, + source, + target, }) } } @@ -98,15 +129,15 @@ pub(in super::super) trait ComputeAddressInterface { impl<'p, 'v: 'p, 'tcx: 'v> ComputeAddressInterface for Lowerer<'p, 'v, 'tcx> { fn encode_compute_address(&mut self, ty: &vir_mid::Type) -> SpannedEncodingResult<()> { - let ty_without_lifetime = ty.normalize_type(); + let ty_identifier = ty.get_identifier(); if !self .compute_address_state .encoded_types - .contains(&ty_without_lifetime) + .contains(&ty_identifier) { self.compute_address_state .encoded_types - .insert(ty_without_lifetime); + .insert(ty_identifier); let type_decl = self.encoder.get_type_decl_mid(ty)?; match type_decl { @@ -122,91 +153,152 @@ impl<'p, 'v: 'p, 'tcx: 'v> ComputeAddressInterface for Lowerer<'p, 'v, 'tcx> { } vir_mid::TypeDecl::Struct(decl) => { for field in &decl.fields { - let axiom = self.encode_compute_address_axiom_for_field(ty, field)?; - self.compute_address_state.axioms.push(axiom); + // let axiom = self.encode_compute_address_axiom_for_field(ty, field)?; + // self.compute_address_state.rewrite_rules.push(axiom); + let axiom = + self.encode_propagate_address_non_aliased_axiom_for_field(ty, field)?; + self.compute_address_state.rewrite_rules.push(axiom); self.encode_compute_address(&field.ty)?; } } vir_mid::TypeDecl::Enum(decl) => { + // FIXME: Encode address_is_non_aliased axioms for enum variants. if decl.safety.is_enum() { let discriminant_field = decl.discriminant_field(); - let axiom = - self.encode_compute_address_axiom_for_field(ty, &discriminant_field)?; - self.compute_address_state.axioms.push(axiom); + // let axiom = + // self.encode_compute_address_axiom_for_field(ty, &discriminant_field)?; + // self.compute_address_state.rewrite_rules.push(axiom); + let axiom = self.encode_propagate_address_non_aliased_axiom_for_field( + ty, + &discriminant_field, + )?; + self.compute_address_state.rewrite_rules.push(axiom); } self.encode_compute_address(&decl.discriminant_type)?; for variant in &decl.variants { use vir_low::macros::*; - let compute_address = ty!(Address); - let body = expr! { - forall( - place: Place, address: Address :: - raw_code { - let variant_index = variant.name.clone().into(); - let variant_place = self.encode_enum_variant_place( - ty, - &variant_index, - place.clone().into(), - Default::default() - )?; - let variant_address = self.encode_enum_variant_address( - ty, - &variant_index, - expr! { ComputeAddress::compute_address(place, address) }, - Default::default(), - )?; - } - [ { (ComputeAddress::compute_address([variant_place.clone()], address)) } ] - (ComputeAddress::compute_address([variant_place], address)) == [variant_address] - ) + let address_is_non_aliased = ty!(Bool); + var_decls! { + address: Address + } + let variant_index = variant.name.clone().into(); + let variant_address = self.encode_enum_variant_address( + ty, + &variant_index, + address.clone().into(), + Default::default(), + )?; + let source = expr! { + (ComputeAddress::address_is_non_aliased([variant_address])) }; - let axiom = vir_low::DomainAxiomDecl { + let target = expr! { + (ComputeAddress::address_is_non_aliased(address)) + }; + let axiom = vir_low::DomainRewriteRuleDecl { comment: None, name: format!( "{}${}$compute_address_axiom", ty.get_identifier(), variant.name ), - body, + egg_only: true, + variables: vec![address], + triggers: None, + source, + target, }; - self.compute_address_state.axioms.push(axiom); + self.compute_address_state.rewrite_rules.push(axiom); let variant_ty = ty.clone().variant(variant.name.clone().into()); self.encode_compute_address(&variant_ty)?; + // self.encode_compute_address(&decl.discriminant_type)?; + // for variant in &decl.variants { + // use vir_low::macros::*; + // let compute_address = ty!(Address); + // var_decls! { + // place: Place, + // address: Address + // } + // let variant_index = variant.name.clone().into(); + // let variant_place = self.encode_enum_variant_place( + // ty, + // &variant_index, + // place.clone().into(), + // Default::default(), + // )?; + // let variant_address = self.encode_enum_variant_address( + // ty, + // &variant_index, + // expr! { ComputeAddress::compute_address(place, address) }, + // Default::default(), + // )?; + // let source = expr! { + // (ComputeAddress::compute_address([variant_place], address)) + // }; + // let axiom = vir_low::DomainRewriteRuleDecl { + // comment: None, + // name: format!( + // "{}${}$compute_address_axiom", + // ty.get_identifier(), + // variant.name + // ), + // egg_only: false, + // variables: vec![place, address], + // triggers: None, + // source, + // target: variant_address, + // }; + // self.compute_address_state.rewrite_rules.push(axiom); + // let variant_ty = ty.clone().variant(variant.name.clone().into()); + // self.encode_compute_address(&variant_ty)?; } } vir_mid::TypeDecl::Array(_decl) => { // FIXME: Doing nothing is probably wrong. } vir_mid::TypeDecl::Reference(_reference) => { - use vir_low::macros::*; - let compute_address = ty!(Address); - let body = expr! { - forall( - place: Place, snapshot: {ty.to_snapshot(self)?} :: - raw_code { - let position = vir_low::Position::default(); - let deref_place = self.reference_deref_place( - place.clone().into(), position)?; - let address = self.reference_address( - ty, - snapshot.clone().into(), - position, - )?; - } - [ { (ComputeAddress::compute_address( - [deref_place.clone()], [address.clone()])) } ] - (ComputeAddress::compute_address( - [deref_place], [address.clone()])) == [address] - ) - }; - let axiom = vir_low::DomainAxiomDecl { - comment: None, - name: format!("{}$compute_address_axiom", ty.get_identifier(),), - body, - }; - self.compute_address_state.axioms.push(axiom); + // use vir_low::macros::*; + // let compute_address = ty!(Address); + // let _body = expr! { + // forall( + // place: Place, snapshot: {ty.to_snapshot(self)?} :: + // raw_code { + // let position = vir_low::Position::default(); + // let deref_place = self.reference_deref_place( + // place.clone().into(), position)?; + // let address = self.reference_address( + // ty, + // snapshot.clone().into(), + // position, + // )?; + // } + // [ { (ComputeAddress::compute_address( + // [deref_place.clone()], [address.clone()])) } ] + // (ComputeAddress::compute_address( + // [deref_place], [address.clone()])) == [address] + // ) + // }; + // var_decls! { + // place: Place, + // snapshot: {ty.to_snapshot(self)?} + // } + // let position = vir_low::Position::default(); + // let deref_place = self.reference_deref_place(place.clone().into(), position)?; + // let address = self.reference_address(ty, snapshot.clone().into(), position)?; + // let source = expr! { + // (ComputeAddress::compute_address( + // [deref_place], [address.clone()])) + // }; + // let axiom = vir_low::DomainRewriteRuleDecl { + // comment: None, + // name: format!("{}$compute_address_axiom", ty.get_identifier(),), + // egg_only: false, + // variables: vec![place, snapshot], + // triggers: None, + // source, + // target: address, + // }; + // self.compute_address_state.rewrite_rules.push(axiom); } - // vir_mid::TypeDecl::Never => {}, // vir_mid::TypeDecl::Closure(Closure) => {}, // vir_mid::TypeDecl::Unsupported(Unsupported) => {}, x => unimplemented!("{:?}", x), @@ -216,36 +308,39 @@ impl<'p, 'v: 'p, 'tcx: 'v> ComputeAddressInterface for Lowerer<'p, 'v, 'tcx> { } fn encode_compute_address_for_place_root( &mut self, - place_root: &vir_low::Expression, + _place_root: &vir_low::Expression, ) -> SpannedEncodingResult<()> { - if self - .compute_address_state - .encoded_roots - .contains(place_root) - { - return Ok(()); - } - self.compute_address_state - .encoded_roots - .insert(place_root.clone()); - use vir_low::macros::*; - let compute_address = ty! { Address }; - let body = expr! { - forall( - address: Address :: - [ { (ComputeAddress::compute_address([place_root.clone()], address)) } ] - (ComputeAddress::compute_address([place_root.clone()], address)) == address - ) - }; - let axiom = vir_low::DomainAxiomDecl { - comment: None, - name: format!( - "root${}$compute_address_axiom", - self.compute_address_state.encoded_roots.len() - ), - body, - }; - self.compute_address_state.axioms.push(axiom); + // debug_assert_eq!(place_root.get_type(), &self.place_type()?); + // if self + // .compute_address_state + // .encoded_roots + // .contains(place_root) + // { + // return Ok(()); + // } + // self.compute_address_state + // .encoded_roots + // .insert(place_root.clone()); + // use vir_low::macros::*; + // let compute_address = ty! { Address }; + // var_decls! { + // address: Address + // } + // let place_option_root = self.place_option_some_constructor(place_root.clone())?; + // let source = expr! { (ComputeAddress::compute_address([place_option_root], address)) }; + // let axiom = vir_low::DomainRewriteRuleDecl { + // comment: None, + // name: format!( + // "root${}$compute_address_axiom", + // self.compute_address_state.encoded_roots.len() + // ), + // egg_only: false, + // variables: vec![address.clone()], + // triggers: None, + // source, + // target: address.into(), + // }; + // self.compute_address_state.rewrite_rules.push(axiom); Ok(()) } } diff --git a/prusti-viper/src/encoder/middle/core_proof/footprint/interface.rs b/prusti-viper/src/encoder/middle/core_proof/footprint/interface.rs new file mode 100644 index 00000000000..46fe4db3698 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/footprint/interface.rs @@ -0,0 +1,370 @@ +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::{lowerer::Lowerer, snapshots::IntoSnapshot}, +}; +use rustc_hash::FxHashMap; +use std::collections::{BTreeMap, BTreeSet}; +use vir_crate::{ + common::position::Positioned, + low as vir_low, + middle::{self as vir_mid, operations::ty::Typed, visitors::ExpressionFolder}, +}; + +struct FootprintComputation<'l, 'p, 'v, 'tcx> { + _lowerer: &'l mut Lowerer<'p, 'v, 'tcx>, + // parameters: &'l BTreeMap, + deref_field_fresh_index_counters: BTreeMap, + deref_field_indices: BTreeMap, + derefs: Vec, +} + +// FIXME: Delete. +impl<'l, 'p, 'v, 'tcx> FootprintComputation<'l, 'p, 'v, 'tcx> { + fn new( + lowerer: &'l mut Lowerer<'p, 'v, 'tcx>, + parameters: &'l BTreeMap, + ) -> Self { + let deref_field_fresh_index_counters = parameters + .iter() + .map(|(parameter, decl)| (parameter.clone(), decl.fields.len())) + .collect(); + Self { + _lowerer: lowerer, + // parameters, + deref_field_fresh_index_counters, + deref_field_indices: Default::default(), + derefs: Default::default(), + } + } + + fn extract_base_variable<'a>( + &self, + place: &'a vir_mid::Expression, + ) -> &'a vir_mid::VariableDecl { + match place { + vir_mid::Expression::Local(expression) => &expression.variable, + _ => unimplemented!(), + } + } + + // FIXME: This should be using `own` places. + fn create_deref_field(&mut self, deref: &vir_mid::Deref) -> vir_mid::Expression { + match &*deref.base { + vir_mid::Expression::Field(expression) => { + let variable = self.extract_base_variable(&expression.base); + let deref_field_name = format!("{}$deref", expression.field.name); + let deref_variable = vir_mid::VariableDecl::new(deref_field_name, deref.ty.clone()); + let field_index = self.compute_deref_field_index(deref, variable, &deref_variable); + vir_mid::Expression::field( + (*expression.base).clone(), + vir_mid::FieldDecl { + name: deref_variable.name, + index: field_index, + ty: deref_variable.ty, + }, + expression.position, + ) + } + _ => unimplemented!(), + } + } + + fn compute_deref_field_index( + &mut self, + deref: &vir_mid::Deref, + variable: &vir_mid::VariableDecl, + deref_variable: &vir_mid::VariableDecl, + ) -> usize { + if let Some(index) = self.deref_field_indices.get(deref_variable) { + *index + } else { + let counter = self + .deref_field_fresh_index_counters + .get_mut(variable) + .unwrap(); + let index = *counter; + *counter += 1; + self.deref_field_indices + .insert(deref_variable.clone(), index); + self.derefs.push(deref.clone()); + index + } + } + + fn into_deref_fields(self) -> Vec<(vir_mid::VariableDecl, usize)> { + let mut deref_fields: Vec<_> = self.deref_field_indices.into_iter().collect(); + deref_fields.sort_by_key(|(_, index)| *index); + deref_fields + } + + fn into_derefs(self) -> Vec { + self.derefs + } +} + +impl<'l, 'p, 'v, 'tcx> vir_mid::visitors::ExpressionFolder + for FootprintComputation<'l, 'p, 'v, 'tcx> +{ + fn fold_acc_predicate_enum( + &mut self, + acc_predicate: vir_mid::AccPredicate, + ) -> vir_mid::Expression { + match *acc_predicate.predicate { + vir_mid::Predicate::LifetimeToken(_) => { + unimplemented!() + } + vir_mid::Predicate::MemoryBlockStack(_) + | vir_mid::Predicate::MemoryBlockStackDrop(_) + | vir_mid::Predicate::MemoryBlockHeap(_) + | vir_mid::Predicate::MemoryBlockHeapRange(_) + | vir_mid::Predicate::MemoryBlockHeapRangeGuarded(_) + | vir_mid::Predicate::MemoryBlockHeapDrop(_) => true.into(), + vir_mid::Predicate::OwnedNonAliased(predicate) => { + let position = predicate.place.position(); + let place = self.fold_expression(predicate.place); + vir_mid::Expression::builtin_func_app( + vir_mid::BuiltinFunc::IsValid, + Vec::new(), + vec![place], + vir_mid::Type::Bool, + position, + ) + // match predicate.place { + // vir_mid::Expression::Deref(deref) => { + // let deref_field = self.create_deref_field(&deref); + // let app = vir_mid::Expression::builtin_func_app( + // vir_mid::BuiltinFunc::IsValid, + // Vec::new(), + // vec![deref_field], + // vir_mid::Type::Bool, + // deref.position, + // ); + // app + // }} + // _ => unimplemented!(), + } + vir_mid::Predicate::OwnedRange(predicate) => { + unimplemented!("predicate: {}", predicate); + } + vir_mid::Predicate::OwnedSet(predicate) => { + unimplemented!("predicate: {}", predicate); + } + vir_mid::Predicate::UniqueRef(predicate) => { + unimplemented!("predicate: {}", predicate); + } + vir_mid::Predicate::UniqueRefRange(predicate) => { + unimplemented!("predicate: {}", predicate); + } + vir_mid::Predicate::FracRef(predicate) => { + unimplemented!("predicate: {}", predicate); + } + vir_mid::Predicate::FracRefRange(predicate) => { + unimplemented!("predicate: {}", predicate); + } + } + } + + fn fold_deref_enum(&mut self, deref: vir_mid::Deref) -> vir_mid::Expression { + if deref.base.get_type().is_pointer() { + self.create_deref_field(&deref) + } else { + vir_mid::Expression::Deref(self.fold_deref(deref)) + } + } +} + +struct Predicate<'l, 'p, 'v, 'tcx> { + lowerer: &'l mut Lowerer<'p, 'v, 'tcx>, +} + +impl<'l, 'p, 'v, 'tcx> Predicate<'l, 'p, 'v, 'tcx> { + fn new(lowerer: &'l mut Lowerer<'p, 'v, 'tcx>) -> Self { + Self { lowerer } + } + + // FIXME: Code duplication. + fn extract_base_variable<'a>( + &self, + place: &'a vir_mid::Expression, + ) -> &'a vir_mid::VariableDecl { + match place { + vir_mid::Expression::Local(expression) => &expression.variable, + _ => unimplemented!(), + } + } +} + +impl<'l, 'p, 'v, 'tcx> vir_mid::visitors::ExpressionFolder for Predicate<'l, 'p, 'v, 'tcx> { + // fn fold_field_enum(&mut self, field: vir_mid::Field) -> vir_mid::Expression { + // match &*field.base { + // vir_mid::Expression::Local(local) => { + // assert!(local.variable.is_self_variable()); + // let position = field.position; + // let app = vir_mid::Expression::builtin_func_app( + // vir_mid::BuiltinFunc::GetSnapshot, + // Vec::new(), + // vec![deref_field], + // vir_mid::Type::Bool, + // position, + // ); + // app + // } + // _ => vir_mid::visitors::default_fold_field(self, field), + // } + // } + // fn fold_acc_predicate_enum( + // &mut self, + // acc_predicate: vir_mid::AccPredicate, + // ) -> vir_mid::Expression { + // match *acc_predicate.predicate { + // vir_mid::Predicate::LifetimeToken(_) => { + // unimplemented!() + // } + // vir_mid::Predicate::MemoryBlockStack(_) + // | vir_mid::Predicate::MemoryBlockStackDrop(_) + // | vir_mid::Predicate::MemoryBlockHeap(_) + // | vir_mid::Predicate::MemoryBlockHeapDrop(_) => true.into(), + // vir_mid::Predicate::OwnedNonAliased(predicate) => match predicate.place { + // vir_mid::Expression::Deref(deref) => { + // let deref_field = self.create_deref_field(&deref); + // let app = vir_mid::Expression::builtin_func_app( + // vir_mid::BuiltinFunc::IsValid, + // Vec::new(), + // vec![deref_field], + // vir_mid::Type::Bool, + // deref.position, + // ); + // app + // } + // _ => unimplemented!(), + // }, + // } + // } +} + +fn compute_parameter_name( + place: &vir_mid::Expression, + parameter_name: &mut String, + func_app_names: &mut FxHashMap, +) -> SpannedEncodingResult<()> { + match place { + vir_mid::Expression::Deref(expression) => { + compute_parameter_name(&expression.base, parameter_name, func_app_names)?; + parameter_name.push_str("$deref"); + } + vir_mid::Expression::Field(expression) => { + compute_parameter_name(&expression.base, parameter_name, func_app_names)?; + parameter_name.push('$'); + parameter_name.push_str(&expression.field.name); + } + vir_mid::Expression::Local(expression) => { + assert!(expression.variable.is_self_variable()); + } + vir_mid::Expression::FuncApp(expression) => { + if !func_app_names.contains_key(expression) { + let name = format!("$func_app${}", func_app_names.len()); + func_app_names.insert(expression.clone(), name); + } + parameter_name.push_str(&func_app_names[expression]); + } + _ => { + unimplemented!("{place}"); + } + } + Ok(()) +} + +impl<'p, 'v: 'p, 'tcx: 'v> Lowerer<'p, 'v, 'tcx> { + /// Computes the parameter that corresponds to the value of + /// the dereferenced place. + fn compute_deref_field_from_place( + &mut self, + place: &vir_mid::Expression, + func_app_names: &mut FxHashMap, + ) -> SpannedEncodingResult<(String, vir_low::Type)> { + let mut parameter_name = String::new(); + compute_parameter_name(place, &mut parameter_name, func_app_names)?; + Ok((parameter_name, place.get_type().to_snapshot(self)?)) + } + + /// Computes the parameter that corresponds to the value of + /// the dereferenced place. + fn compute_deref_field_from_range_address( + &mut self, + address: &vir_mid::Expression, + func_app_names: &mut FxHashMap, + ) -> SpannedEncodingResult<(String, vir_low::Type)> { + let mut parameter_name = String::new(); + compute_parameter_name(address, &mut parameter_name, func_app_names)?; + let vir_mid::Type::Pointer(ty) = address.get_type() else { + unreachable!(); + }; + let element_type = ty.target_type.to_snapshot(self)?; + Ok((parameter_name, vir_low::Type::seq(element_type))) + } +} + +pub(in super::super) struct DerefOwned { + pub(in super::super) place: vir_mid::Expression, + pub(in super::super) field_name: String, + pub(in super::super) field_type: vir_low::Type, +} + +pub(in super::super) struct DerefOwnedRange { + pub(in super::super) address: vir_mid::Expression, + pub(in super::super) field_name: String, + pub(in super::super) field_type: vir_low::Type, +} + +pub(in super::super) type DerefFields = (Vec, Vec); + +pub(in super::super) trait FootprintInterface { + fn structural_invariant_to_deref_fields( + &mut self, + invariant: &[vir_mid::Expression], + ) -> SpannedEncodingResult; +} + +impl<'p, 'v: 'p, 'tcx: 'v> FootprintInterface for Lowerer<'p, 'v, 'tcx> { + /// For the given invariant, compute the deref fields. This is done by + /// finding all `own` predicates and creating variables for them. + /// + /// The order of the returned fields is guaranteed to be the same for the + /// same invariant. + fn structural_invariant_to_deref_fields( + &mut self, + invariant: &[vir_mid::Expression], + ) -> SpannedEncodingResult { + let mut func_app_names = FxHashMap::default(); + let mut owned_places = BTreeSet::default(); + let mut owned_range_addresses = BTreeSet::default(); + for expression in invariant { + let (new_owned_places, new_owned_range_addresses) = expression.collect_owned_places(); + owned_places.extend(new_owned_places); + owned_range_addresses.extend(new_owned_range_addresses); + } + let mut owned_fields = Vec::new(); + for owned_place in owned_places { + let (name, ty) = + self.compute_deref_field_from_place(&owned_place, &mut func_app_names)?; + owned_fields.push(DerefOwned { + place: owned_place, + field_name: name, + field_type: ty, + }); + } + let mut owned_range_fields = Vec::new(); + for owned_range_address in owned_range_addresses { + let (name, ty) = self.compute_deref_field_from_range_address( + &owned_range_address, + &mut func_app_names, + )?; + owned_range_fields.push(DerefOwnedRange { + address: owned_range_address, + field_name: name, + field_type: ty, + }); + } + Ok((owned_fields, owned_range_fields)) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/footprint/mod.rs b/prusti-viper/src/encoder/middle/core_proof/footprint/mod.rs new file mode 100644 index 00000000000..19b038bcf5c --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/footprint/mod.rs @@ -0,0 +1,3 @@ +mod interface; + +pub(super) use self::interface::{DerefFields, DerefOwned, DerefOwnedRange, FootprintInterface}; diff --git a/prusti-viper/src/encoder/middle/core_proof/heap/interface.rs b/prusti-viper/src/encoder/middle/core_proof/heap/interface.rs new file mode 100644 index 00000000000..b24b43dbe04 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/heap/interface.rs @@ -0,0 +1,164 @@ +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::lowerer::{DomainsLowererInterface, Lowerer}, +}; +use vir_crate::{common::expression::QuantifierHelpers, low as vir_low}; + +const HEAP_DOMAIN_NAME: &str = "Heap$"; +const HEAP_LOOKUP_FUNCTION_NAME: &str = "heap$lookup"; +const HEAP_UPDATE_FUNCTION_NAME: &str = "heap$update"; +const HEAP_CHUNK_TYPE_NAME: &str = "HeapChunk$"; + +pub(in super::super) trait Private { + fn encode_heap_axioms(&mut self) -> SpannedEncodingResult<()>; +} + +impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { + fn encode_heap_axioms(&mut self) -> SpannedEncodingResult<()> { + if !self.heap_state.is_heap_encoded { + self.heap_state.is_heap_encoded = true; + + let position = vir_low::Position::default(); + use vir_low::macros::*; + let heap_type = self.heap_type()?; + let heap_chunk_type = self.heap_chunk_type()?; + var_decls!( + heap: {heap_type.clone()}, + address: Address, + chunk: {heap_chunk_type.clone()} + ); + let update_call = self.create_domain_func_app( + HEAP_DOMAIN_NAME, + HEAP_UPDATE_FUNCTION_NAME, + vec![ + heap.clone().into(), + address.clone().into(), + chunk.clone().into(), + ], + heap_type, + position, + )?; + { + let lookup_call = self.create_domain_func_app( + HEAP_DOMAIN_NAME, + HEAP_LOOKUP_FUNCTION_NAME, + vec![update_call.clone(), address.clone().into()], + heap_chunk_type.clone(), + position, + )?; + + // forall heap: Heap$, addr: Address, chunk: HeapChunk$ :: + // { heap$lookup(heap$update(heap, addr, chunk), addr) } + // heap$lookup(heap$update(heap, addr, chunk), addr) == chunk + let axiom_update_value = vir_low::DomainAxiomDecl { + comment: None, + name: "heap$update_value$axiom".to_string(), + body: QuantifierHelpers::forall( + vec![heap.clone(), address.clone(), chunk.clone()], + vec![vir_low::Trigger::new(vec![lookup_call.clone()])], + expr! { + [lookup_call] == chunk + }, + ), + }; + self.declare_axiom(HEAP_DOMAIN_NAME, axiom_update_value)?; + } + { + var_decls!(address2: Address); + let lookup_call_original = self.create_domain_func_app( + HEAP_DOMAIN_NAME, + HEAP_LOOKUP_FUNCTION_NAME, + vec![heap.clone().into(), address2.clone().into()], + heap_chunk_type.clone(), + position, + )?; + let lookup_call_updated = self.create_domain_func_app( + HEAP_DOMAIN_NAME, + HEAP_LOOKUP_FUNCTION_NAME, + vec![update_call, address2.clone().into()], + heap_chunk_type, + position, + )?; + // forall heap: Heap$, addr1: Address, addr2: Address, chunk: HeapChunk$ :: + // { heap$lookup(heap$update(heap, addr1, chunk), addr2) } + // addr1 != addr2 ==> + // heap$lookup(heap$update(heap, addr1, chunk), addr2) == heap$lookup(heap, addr2) + let axiom_preserve_value = vir_low::DomainAxiomDecl { + name: "heap$update_preserve_value$axiom".to_string(), + comment: None, + body: QuantifierHelpers::forall( + vec![heap, address.clone(), address2.clone(), chunk], + vec![vir_low::Trigger::new(vec![lookup_call_updated.clone()])], + expr! { + (address != address2) ==> + ([lookup_call_updated] == [lookup_call_original]) + }, + ), + }; + self.declare_axiom(HEAP_DOMAIN_NAME, axiom_preserve_value)?; + } + } + Ok(()) + } +} + +pub(in super::super) trait HeapInterface { + fn heap_lookup( + &mut self, + heap: vir_low::Expression, + address: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult; + fn heap_update( + &mut self, + heap: vir_low::Expression, + address: vir_low::Expression, + value: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult; + fn heap_chunk_type(&mut self) -> SpannedEncodingResult; + fn heap_type(&mut self) -> SpannedEncodingResult; +} + +impl<'p, 'v: 'p, 'tcx: 'v> HeapInterface for Lowerer<'p, 'v, 'tcx> { + fn heap_lookup( + &mut self, + _heap: vir_low::Expression, + _address: vir_low::Expression, + _position: vir_low::Position, + ) -> SpannedEncodingResult { + unimplemented!("outdated-code"); + // self.encode_heap_axioms()?; + // let return_type = self.heap_chunk_type()?; + // self.create_domain_func_app( + // HEAP_DOMAIN_NAME, + // HEAP_LOOKUP_FUNCTION_NAME, + // vec![heap, address], + // return_type, + // position, + // ) + } + fn heap_update( + &mut self, + heap: vir_low::Expression, + address: vir_low::Expression, + value: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult { + self.encode_heap_axioms()?; + let return_type = self.heap_type()?; + self.create_domain_func_app( + HEAP_DOMAIN_NAME, + HEAP_UPDATE_FUNCTION_NAME, + vec![heap, address, value], + return_type, + position, + ) + } + fn heap_chunk_type(&mut self) -> SpannedEncodingResult { + self.domain_type(HEAP_CHUNK_TYPE_NAME) + } + fn heap_type(&mut self) -> SpannedEncodingResult { + self.domain_type(HEAP_DOMAIN_NAME) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/heap/mod.rs b/prusti-viper/src/encoder/middle/core_proof/heap/mod.rs new file mode 100644 index 00000000000..0548285daf0 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/heap/mod.rs @@ -0,0 +1,4 @@ +mod interface; +mod state; + +pub(super) use self::{interface::HeapInterface, state::HeapState}; diff --git a/prusti-viper/src/encoder/middle/core_proof/heap/state.rs b/prusti-viper/src/encoder/middle/core_proof/heap/state.rs new file mode 100644 index 00000000000..2faba742830 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/heap/state.rs @@ -0,0 +1,4 @@ +#[derive(Default)] +pub(in super::super) struct HeapState { + pub(super) is_heap_encoded: bool, +} diff --git a/prusti-viper/src/encoder/middle/core_proof/interface.rs b/prusti-viper/src/encoder/middle/core_proof/interface.rs index d48b7a5499c..b616de478c4 100644 --- a/prusti-viper/src/encoder/middle/core_proof/interface.rs +++ b/prusti-viper/src/encoder/middle/core_proof/interface.rs @@ -1,18 +1,23 @@ +use super::svirpti::VerificationResult; use crate::encoder::{ errors::SpannedEncodingResult, high::procedures::HighProcedureEncoderInterface, mir::specifications::SpecificationsInterface, }; -use log::debug; +use log::{debug, info}; use prusti_common::config; +use prusti_interface::data::ProcedureDefId; use prusti_rustc_interface::{hir::def_id::DefId, middle::ty}; use vir_crate::{ - common::{check_mode::CheckMode, identifier::WithIdentifier}, + common::{check_mode::CheckMode, identifier::WithIdentifier, validator::Validator}, low::{self as vir_low}, }; #[derive(Default)] pub(crate) struct MidCoreProofEncoderState { - encoded_programs: Vec, + /// Encoded programs for the case when Viper is used as a backend. + encoded_programs: Vec<(Option, vir_low::Program)>, + /// Verification results for the case when Svirpti is used as a backend. + verification_results: Vec<(Option, VerificationResult)>, } pub(crate) trait MidCoreProofEncoderInterface<'tcx> { @@ -26,7 +31,8 @@ pub(crate) trait MidCoreProofEncoderInterface<'tcx> { ty: ty::Ty<'tcx>, check_mode: CheckMode, ) -> SpannedEncodingResult<()>; - fn take_core_proof_programs(&mut self) -> Vec; + fn take_core_proof_programs(&mut self) -> Vec<(Option, vir_low::Program)>; + fn take_verification_results(&mut self) -> Vec<(Option, VerificationResult)>; } impl<'v, 'tcx: 'v> MidCoreProofEncoderInterface<'tcx> for super::super::super::Encoder<'v, 'tcx> { @@ -43,29 +49,234 @@ impl<'v, 'tcx: 'v> MidCoreProofEncoderInterface<'tcx> for super::super::super::E ); return Ok(()); } - let procedure = self.encode_procedure_core_proof(proc_def_id, check_mode)?; - let super::lowerer::LoweringResult { - procedures, - domains, - functions, - predicates, - methods, - } = super::lowerer::lower_procedure(self, proc_def_id, procedure)?; - let mut program = vir_low::Program { - name: self.env().name.get_absolute_item_name(proc_def_id), - check_mode, - procedures, - domains, - predicates, - functions, - methods, - }; - if config::inline_caller_for() { - super::transformations::inline_functions::inline_caller_for(&mut program); + for procedure in self.encode_procedure_core_proof(proc_def_id, check_mode)? { + info!( + "Lowering procedure: {} ({proc_def_id:?} {check_mode:?})", + procedure.name + ); + let name = procedure.name.clone(); + let super::lowerer::LoweringResult { + procedures, + domains, + functions, + predicates, + methods, + domains_info: _, + predicates_info, + snapshot_domains_info, + extensionality_gas_constant, + } = super::lowerer::lower_procedure(self, proc_def_id, procedure)?; + let mut program = vir_low::Program { + name, + // name: self.env().name.get_absolute_item_name(proc_def_id), + check_mode, + procedures, + domains, + predicates, + functions, + methods, + }; + let source_filename = self.env().name.source_file_name(); + program.assert_valid_debug(); + if config::trace_with_symbolic_execution() + || config::custom_heap_encoding() + || config::viper_backend() == "svirpti" + { + program = super::transformations::desugar_method_calls::desugar_method_calls( + &source_filename, + program, + ); + program = super::transformations::desugar_fold_unfold::desugar_fold_unfold( + &source_filename, + program, + &predicates_info.owned_predicates_info, + ); + program = super::transformations::desugar_implications::desugar_implications( + &source_filename, + program, + ); + program = super::transformations::desugar_conditionals::desugar_conditionals( + &source_filename, + program, + ); + } + if config::inline_caller_for() + || config::trace_with_symbolic_execution() + || config::custom_heap_encoding() + || config::viper_backend() == "svirpti" + { + super::transformations::inline_functions::inline_caller_for( + &source_filename, + &mut program, + ); + } + if config::viper_backend() == "svirpti" { + assert!( + !config::trace_with_symbolic_execution(), + "Incompattible setting: trace_with_symbolic_execution and svirpti backend" + ); + program = + super::transformations::make_all_jumps_nondeterministic::make_all_jumps_nondeterministic( + &source_filename, + program + ); + program = super::transformations::merge_consequent_blocks::merge_consequent_blocks( + &source_filename, + program, + ); + if config::expand_quantifiers() { + program = super::transformations::expand_quantifiers::expand_quantifiers( + &source_filename, + program, + ); + } + // We have to execute this pass because some of the transformations + // generate nested old expressions, which cause problems when + // triggering. + program = super::transformations::clean_old::clean_old(&source_filename, program); + // We have to execute this pass because some of the transformations + // generate unused variables whose types are not defined. + program = super::transformations::clean_variables::clean_variables( + &source_filename, + program, + ); + if config::clean_labels() { + program = super::transformations::clean_labels::clean_labels( + &source_filename, + program, + ); + } + if config::merge_consecutive_statements() { + program = super::transformations::merge_statements::merge_statements( + &source_filename, + program, + ); + } + program = super::transformations::case_splits::desugar_case_splits( + &source_filename, + program, + )?; + program = super::transformations::desugar_containers::desugar_containers( + &source_filename, + program, + ); + let (program, predicate_domains_info) = + super::transformations::predicate_domains::define_predicate_domains( + &source_filename, + program, + &predicates_info.owned_predicates_info, + ); + let program = super::transformations::name_quantifiers::name_quantifiers( + &source_filename, + program, + ); + let result = super::svirpti::verify_program( + self, + &source_filename, + program, + predicate_domains_info, + predicates_info.non_aliased_memory_block_addresses.clone(), + &snapshot_domains_info, + predicates_info.owned_predicates_info.clone(), + &extensionality_gas_constant, + )?; + self.mid_core_proof_encoder_state + .verification_results + .push((Some(proc_def_id), result)); + } else { + if config::trace_with_symbolic_execution() { + if config::trace_with_symbolic_execution_new() { + program = + super::transformations::make_all_jumps_nondeterministic::make_all_jumps_nondeterministic( + &source_filename, + program + ); + program = + super::transformations::merge_consequent_blocks::merge_consequent_blocks( + &source_filename, + program, + ); + program = + super::transformations::symbolic_execution_new::purify_with_symbolic_execution( + self, + &source_filename, + program, + predicates_info.non_aliased_memory_block_addresses.clone(), + &snapshot_domains_info, + predicates_info.owned_predicates_info.clone(), + &extensionality_gas_constant, + )?; + } else { + program = + super::transformations::symbolic_execution::purify_with_symbolic_execution( + self, + &source_filename, + program, + predicates_info.non_aliased_memory_block_addresses.clone(), + &snapshot_domains_info, + predicates_info.owned_predicates_info.clone(), + &extensionality_gas_constant, + 2, + )?; + } + // program = + // super::transformations::symbolic_execution::purify_with_symbolic_execution( + // &source_filename, + // program, + // predicates_info.non_aliased_memory_block_addresses, + // &snapshot_domains_info, + // predicates_info.owned_predicates_info.clone(), + // 2, + // )?; + } + if config::custom_heap_encoding() { + program = super::transformations::desugar_conditionals::desugar_conditionals( + &source_filename, + program, + ); + super::transformations::custom_heap_encoding::custom_heap_encoding( + self, + &mut program, + predicates_info.owned_predicates_info, + )?; + } + if config::expand_quantifiers() { + program = super::transformations::expand_quantifiers::expand_quantifiers( + &source_filename, + program, + ); + } + // We have to execute this pass because some of the transformations + // generate nested old expressions, which cause problems when + // triggering. + program = super::transformations::clean_old::clean_old(&source_filename, program); + // We have to execute this pass because some of the transformations + // generate unused variables whose types are not defined. + program = super::transformations::clean_variables::clean_variables( + &source_filename, + program, + ); + if config::clean_labels() { + program = super::transformations::clean_labels::clean_labels( + &source_filename, + program, + ); + } + if config::merge_consecutive_statements() { + program = super::transformations::merge_statements::merge_statements( + &source_filename, + program, + ); + } + program = super::transformations::case_splits::desugar_case_splits( + &source_filename, + program, + )?; + self.mid_core_proof_encoder_state + .encoded_programs + .push((Some(proc_def_id), program)); + } } - self.mid_core_proof_encoder_state - .encoded_programs - .push(program); Ok(()) } @@ -100,6 +311,10 @@ impl<'v, 'tcx: 'v> MidCoreProofEncoderInterface<'tcx> for super::super::super::E functions, predicates, methods, + domains_info: _, + snapshot_domains_info: _, + predicates_info: _, + extensionality_gas_constant: _, } = super::lowerer::lower_type(self, def_id, ty, check_copy)?; assert!(procedures.is_empty()); let mut program = vir_low::Program { @@ -112,15 +327,23 @@ impl<'v, 'tcx: 'v> MidCoreProofEncoderInterface<'tcx> for super::super::super::E methods, }; if config::inline_caller_for() { - super::transformations::inline_functions::inline_caller_for(&mut program); + let source_filename = self.env().name.source_file_name(); + super::transformations::inline_functions::inline_caller_for( + &source_filename, + &mut program, + ); } self.mid_core_proof_encoder_state .encoded_programs - .push(program); + .push((None, program)); Ok(()) } - fn take_core_proof_programs(&mut self) -> Vec { + fn take_core_proof_programs(&mut self) -> Vec<(Option, vir_low::Program)> { std::mem::take(&mut self.mid_core_proof_encoder_state.encoded_programs) } + + fn take_verification_results(&mut self) -> Vec<(Option, VerificationResult)> { + std::mem::take(&mut self.mid_core_proof_encoder_state.verification_results) + } } diff --git a/prusti-viper/src/encoder/middle/core_proof/into_low/cfg.rs b/prusti-viper/src/encoder/middle/core_proof/into_low/cfg.rs index e413408a0be..4be78016597 100644 --- a/prusti-viper/src/encoder/middle/core_proof/into_low/cfg.rs +++ b/prusti-viper/src/encoder/middle/core_proof/into_low/cfg.rs @@ -1,23 +1,39 @@ use super::IntoLow; use crate::encoder::{ - errors::SpannedEncodingResult, + errors::{ErrorCtxt, SpannedEncodingResult}, high::types::HighTypeEncoderInterface, middle::core_proof::{ addresses::AddressesInterface, block_markers::BlockMarkersInterface, builtin_methods::{BuiltinMethodCallsInterface, BuiltinMethodsInterface, CallContext}, + const_generics::ConstGenericsInterface, + labels::LabelsInterface, lifetimes::LifetimesInterface, lowerer::{Lowerer, VariablesLowererInterface}, places::PlacesInterface, - predicates::{PredicatesMemoryBlockInterface, PredicatesOwnedInterface}, + pointers::PointersInterface, + predicates::{ + PredicatesAliasingInterface, PredicatesMemoryBlockInterface, PredicatesOwnedInterface, + }, references::ReferencesInterface, snapshots::{ - IntoProcedureBoolExpression, IntoProcedureFinalSnapshot, IntoProcedureSnapshot, + IntoProcedureAssertion, IntoProcedureBoolExpression, IntoProcedureSnapshot, + IntoSnapshotLowerer, PlaceToSnapshot, PredicateKind, SelfFramingAssertionToSnapshot, SnapshotValidityInterface, SnapshotValuesInterface, SnapshotVariablesInterface, }, + transformations::encoder_context::EncoderContext, + triggers::TriggersInterface, + type_layouts::TypeLayoutsInterface, + viewshifts::ViewShiftsInterface, }, }; +use prusti_common::config; use vir_crate::{ + common::{ + expression::{BinaryOperationHelpers, QuantifierHelpers}, + identifier::WithIdentifier, + validator::Validator, + }, low::{self as vir_low}, middle::{self as vir_mid, operations::ty::Typed}, }; @@ -43,22 +59,44 @@ impl IntoLow for vir_mid::Statement { use vir_low::{macros::*, Statement}; match self { Self::Comment(statement) => Ok(vec![Statement::comment(statement.comment)]), - Self::OldLabel(label) => { - lowerer.save_old_label(label.name)?; - Ok(Vec::new()) + Self::OldLabel(statement) => { + lowerer.save_old_label(statement.name.clone())?; + lowerer.save_custom_label(statement.name.clone())?; + Ok(vec![vir_low::Statement::label( + statement.name, + statement.position, + )]) } - Self::Inhale(statement) => { - if let vir_mid::Predicate::OwnedNonAliased(owned) = &statement.predicate { - lowerer.mark_owned_non_aliased_as_unfolded(owned.place.get_type())?; + Self::InhalePredicate(statement) => { + let mut statements = Vec::new(); + match &statement.predicate { + vir_mid::Predicate::OwnedNonAliased(predicate) => { + lowerer.mark_owned_predicate_as_unfolded(predicate.place.get_type())?; + } + // vir_mid::Predicate::MemoryBlockStack(predicate) => { + // // let predicate_acc = predicate + // // .clone() + // // .into_low(lowerer)? + // // .unwrap_predicate_access_predicate(); + // lowerer.mark_place_as_used_in_memory_block(&predicate.place)?; + // } + _ => (), } - Ok(vec![Statement::inhale( + statements.push(Statement::inhale( statement.predicate.into_low(lowerer)?, statement.position, - )]) + )); + Ok(statements) } - Self::Exhale(statement) => { - if let vir_mid::Predicate::OwnedNonAliased(owned) = &statement.predicate { - lowerer.mark_owned_non_aliased_as_unfolded(owned.place.get_type())?; + Self::ExhalePredicate(statement) => { + match &statement.predicate { + vir_mid::Predicate::OwnedNonAliased(owned) => { + lowerer.mark_owned_predicate_as_unfolded(owned.place.get_type())?; + } + // vir_mid::Predicate::MemoryBlockStack(predicate) => { + // lowerer.mark_place_as_used_in_memory_block(&predicate.place)?; + // } + _ => (), } Ok(vec![Statement::exhale( statement.predicate.into_low(lowerer)?, @@ -92,15 +130,65 @@ impl IntoLow for vir_mid::Statement { )?; Ok(statements) } - Self::Assume(statement) => Ok(vec![Statement::assume( - statement.expression.to_procedure_bool_expression(lowerer)?, - statement.position, - )]), - Self::Assert(statement) => { - let assert = Statement::assert( - statement.expression.to_procedure_bool_expression(lowerer)?, + Self::HeapHavoc(statement) => { + let new_version = lowerer.new_heap_variable_version(statement.position)?; + Ok(vec![vir_low::Statement::comment(format!( + "new heap version: {new_version}" + ))]) + } + Self::InhaleExpression(statement) => { + let mut assertion_encoder = + SelfFramingAssertionToSnapshot::for_inhale_exhale_expression(statement.label); + let assertion = assertion_encoder.expression_to_snapshot( + lowerer, + &statement.expression, + true, + )?; + let inhale = + Statement::inhale_no_pos(assertion).set_default_position(statement.position); + Ok(vec![inhale]) + } + Self::ExhaleExpression(statement) => { + let mut assertion_encoder = + SelfFramingAssertionToSnapshot::for_inhale_exhale_expression(statement.label); + let assertion = assertion_encoder.expression_to_snapshot( + lowerer, + &statement.expression, + true, + )?; + // let assertion = statement.expression.to_procedure_assertion(lowerer)?; + let exhale = + Statement::exhale_no_pos(assertion).set_default_position(statement.position); + Ok(vec![exhale]) + } + Self::Assume(statement) => { + assert!( + statement.expression.is_pure(), + "must be pure: {}", + statement.expression + ); + let low_statement = Statement::assume( + statement.expression.to_procedure_assertion(lowerer)?, statement.position, ); + low_statement.assert_valid_debug(); + Ok(vec![low_statement]) + } + Self::Assert(statement) => { + assert!( + statement.expression.is_pure(), + "must be pure: {}", + statement.expression + ); + let mut assertion_encoder = + SelfFramingAssertionToSnapshot::for_inhale_exhale_expression(None); + let assertion = assertion_encoder.expression_to_snapshot( + lowerer, + &statement.expression, + true, + )?; + assertion.assert_valid_debug(); + let assert = Statement::assert(assertion, statement.position); let low_statement = if let Some(condition) = statement.condition { let low_condition = lowerer.lower_block_marker_condition(condition)?; Statement::conditional( @@ -112,6 +200,7 @@ impl IntoLow for vir_mid::Statement { } else { assert }; + low_statement.assert_valid_debug(); Ok(vec![low_statement]) } // FIXME: Instead of having a statement per predicate kind, have two @@ -119,19 +208,30 @@ impl IntoLow for vir_mid::Statement { // argument. Self::FoldOwned(statement) => { let ty = statement.place.get_type(); - lowerer.mark_owned_non_aliased_as_unfolded(ty)?; + lowerer.mark_owned_predicate_as_unfolded(ty)?; let place = lowerer.encode_expression_as_place(&statement.place)?; - let root_address = lowerer.extract_root_address(&statement.place)?; - let snapshot = statement.place.to_procedure_snapshot(lowerer)?; + let address = lowerer.encode_expression_as_place_address(&statement.place)?; + let permission_amount = if let Some(permission) = statement.permission { + permission.to_procedure_snapshot(lowerer)?.into() + } else { + vir_low::Expression::full_permission() + }; + // let root_address = lowerer.extract_root_address(&statement.place)?; + // let mut place_encoder = + // PlaceToSnapshot::for_place(PredicateKind::Owned); + // let snapshot = + // place_encoder.expression_to_snapshot(lowerer, &statement.place, false)?; + // let snapshot = statement.place.to_procedure_snapshot(lowerer)?; let predicate = lowerer.owned_non_aliased( CallContext::Procedure, ty, ty, place, - root_address, - snapshot, - None, + address, + Some(permission_amount), + statement.position, )?; + assert!(predicate.is_predicate_access_predicate()); let mut low_statement = vir_low::Statement::fold_no_pos(predicate); if let Some(condition) = statement.condition { let low_condition = lowerer.lower_block_marker_condition(condition)?; @@ -145,19 +245,30 @@ impl IntoLow for vir_mid::Statement { } Self::UnfoldOwned(statement) => { let ty = statement.place.get_type(); - lowerer.mark_owned_non_aliased_as_unfolded(ty)?; + lowerer.mark_owned_predicate_as_unfolded(ty)?; + // let root_address = lowerer.extract_root_address(&statement.place)?; let place = lowerer.encode_expression_as_place(&statement.place)?; - let root_address = lowerer.extract_root_address(&statement.place)?; - let snapshot = statement.place.to_procedure_snapshot(lowerer)?; + let address = lowerer.encode_expression_as_place_address(&statement.place)?; + let permission_amount = if let Some(permission) = statement.permission { + permission.to_procedure_snapshot(lowerer)?.into() + } else { + vir_low::Expression::full_permission() + }; + // let mut place_encoder = + // PlaceToSnapshot::for_place(PredicateKind::Owned); + // let snapshot = + // place_encoder.expression_to_snapshot(lowerer, &statement.place, false)?; + // let snapshot = statement.place.to_procedure_snapshot(lowerer)?; let predicate = lowerer.owned_non_aliased( CallContext::Procedure, ty, ty, place, - root_address, - snapshot, - None, + address, + Some(permission_amount), + statement.position, )?; + assert!(predicate.is_predicate_access_predicate()); let mut low_statement = vir_low::Statement::unfold_no_pos(predicate); if let Some(condition) = statement.condition { let low_condition = lowerer.lower_block_marker_condition(condition)?; @@ -171,33 +282,57 @@ impl IntoLow for vir_mid::Statement { } Self::FoldRef(statement) => { let ty = statement.place.get_type(); - lowerer.mark_owned_non_aliased_as_unfolded(ty)?; + lowerer.mark_owned_predicate_as_unfolded(ty)?; let lifetime = lowerer.encode_lifetime_const_into_procedure_variable(statement.lifetime)?; let place = lowerer.encode_expression_as_place(&statement.place)?; - let root_address = lowerer.extract_root_address(&statement.place)?; - let current_snapshot = statement.place.to_procedure_snapshot(lowerer)?; + let address = lowerer.encode_expression_as_place_address(&statement.place)?; + // let root_address = lowerer.extract_root_address(&statement.place)?; + // let current_snapshot = statement.place.to_procedure_snapshot(lowerer)?; let predicate = if statement.uniqueness.is_shared() { + // let mut place_encoder = + // PlaceToSnapshot::for_place(PredicateKind::FracRef { + // lifetime: lifetime.clone().into(), + // }); + // let current_snapshot = + // place_encoder.expression_to_snapshot(lowerer, &statement.place, false)?; lowerer.frac_ref( CallContext::Procedure, ty, ty, place, - root_address, - current_snapshot, + address, lifetime.into(), + None, + None, + statement.position, )? } else { - let final_snapshot = statement.place.to_procedure_final_snapshot(lowerer)?; + // let mut place_encoder = + // PlaceToSnapshot::for_place(PredicateKind::UniqueRef { + // lifetime: lifetime.clone().into(), + // is_final: false, + // }); + // let current_snapshot = + // place_encoder.expression_to_snapshot(lowerer, &statement.place, false)?; + // let mut place_encoder = + // PlaceToSnapshot::for_place(PredicateKind::UniqueRef { + // lifetime: lifetime.clone().into(), + // is_final: false, + // }); + // let final_snapshot = + // place_encoder.expression_to_snapshot(lowerer, &statement.place, true)?; + // let final_snapshot = statement.place.to_procedure_final_snapshot(lowerer)?; lowerer.unique_ref( CallContext::Procedure, ty, ty, place, - root_address, - current_snapshot, - final_snapshot, + address, lifetime.into(), + None, + None, + statement.position, )? }; let mut low_statement = vir_low::Statement::fold_no_pos(predicate); @@ -213,36 +348,65 @@ impl IntoLow for vir_mid::Statement { } Self::UnfoldRef(statement) => { let ty = statement.place.get_type(); - lowerer.mark_owned_non_aliased_as_unfolded(ty)?; + lowerer.mark_owned_predicate_as_unfolded(ty)?; let lifetime = lowerer.encode_lifetime_const_into_procedure_variable(statement.lifetime)?; let place = lowerer.encode_expression_as_place(&statement.place)?; - let root_address = lowerer.extract_root_address(&statement.place)?; - let current_snapshot = statement.place.to_procedure_snapshot(lowerer)?; - let predicate = if statement.uniqueness.is_shared() { - lowerer.frac_ref( + let address = lowerer.encode_expression_as_place_address(&statement.place)?; + let mut low_statement = if statement.uniqueness.is_shared() { + let predicate = lowerer.frac_ref( CallContext::Procedure, ty, ty, place, - root_address, - current_snapshot, + address, lifetime.into(), - )? - } else { - let final_snapshot = statement.place.to_procedure_final_snapshot(lowerer)?; - lowerer.unique_ref( + None, + None, + statement.position, + )?; + vir_low::Statement::unfold_no_pos(predicate) + } else if !lowerer.encoder.has_invariant_mid(ty)? || true { + let predicate = lowerer.unique_ref( CallContext::Procedure, ty, ty, place, - root_address, - current_snapshot, - final_snapshot, + address, lifetime.into(), - )? + None, + None, + statement.position, + )?; + vir_low::Statement::unfold_no_pos(predicate) + } else if statement.is_user_written { + assert!(statement.condition.is_none()); + let predicate = lowerer.unique_ref( + CallContext::Procedure, + ty, + ty, + place, + address, + lifetime.into(), + None, + None, + statement.position, + )?; + let _unfold_statement = vir_low::Statement::unfold_no_pos(predicate); + for contrained_place in + lowerer.encoder.get_invariant_constrained_places_mid(ty)? + { + eprintln!("contrained_place: {}", contrained_place); + } + unimplemented!(); + // return Ok(vec![unfold_statement]); + } else { + let position = lowerer.encoder.change_error_context( + statement.position, + ErrorCtxt::IllegalUnfoldUniqueRef, + ); + vir_low::Statement::assert_no_pos(false.into()).set_default_position(position) }; - let mut low_statement = vir_low::Statement::unfold_no_pos(predicate); if let Some(condition) = statement.condition { let low_condition = lowerer.lower_block_marker_condition(condition)?; low_statement = vir_low::Statement::conditional_no_pos( @@ -303,6 +467,26 @@ impl IntoLow for vir_mid::Statement { }; Ok(vec![low_statement]) } + Self::JoinRange(statement) => { + let ty = statement.address.get_type(); + let vir_mid::Type::Pointer(pointer_type) = ty else { + unreachable!() + }; + let target_type = &*pointer_type.target_type; + lowerer.encode_memory_block_range_join_method(target_type)?; + let pointer_value = statement.address.to_procedure_snapshot(lowerer)?; + let start_address = + lowerer.pointer_address(ty, pointer_value, statement.position)?; + let start_index = statement.start_index.to_procedure_snapshot(lowerer)?; + let end_index = statement.end_index.to_procedure_snapshot(lowerer)?; + let low_statement = stmtp! { + statement.position => + call memory_block_range_join( + [start_address], [start_index], [end_index] + ) + }; + Ok(vec![low_statement]) + } Self::SplitBlock(statement) => { let ty = statement.place.get_type(); lowerer.encode_memory_block_split_method(ty)?; @@ -353,12 +537,37 @@ impl IntoLow for vir_mid::Statement { }; Ok(vec![low_statement]) } + Self::SplitRange(statement) => { + let ty = statement.address.get_type(); + let vir_mid::Type::Pointer(pointer_type) = ty else { + unreachable!() + }; + let target_type = &*pointer_type.target_type; + lowerer.encode_memory_block_range_split_method(target_type)?; + let pointer_value = statement.address.to_procedure_snapshot(lowerer)?; + let start_address = + lowerer.pointer_address(ty, pointer_value, statement.position)?; + let start_index = statement.start_index.to_procedure_snapshot(lowerer)?; + let end_index = statement.end_index.to_procedure_snapshot(lowerer)?; + let low_statement = stmtp! { + statement.position => + call memory_block_range_split( + [start_address], [start_index], [end_index] + ) + }; + Ok(vec![low_statement]) + } Self::ConvertOwnedIntoMemoryBlock(statement) => { let ty = statement.place.get_type(); lowerer.encode_into_memory_block_method(ty)?; let place = lowerer.encode_expression_as_place(&statement.place)?; - let root_address = lowerer.extract_root_address(&statement.place)?; - let snapshot = statement.place.to_procedure_snapshot(lowerer)?; + let address = lowerer.encode_expression_as_place_address(&statement.place)?; + // let root_address = lowerer.extract_root_address(&statement.place)?; + // let snapshot = statement.place.to_procedure_snapshot(lowerer)?; + let mut place_encoder = PlaceToSnapshot::for_place(PredicateKind::Owned); + // SelfFramingAssertionToSnapshot::for_place_expression(); + let snapshot = + place_encoder.expression_to_snapshot(lowerer, &statement.place, false)?; let low_condition = statement .condition .map(|condition| lowerer.lower_block_marker_condition(condition)) @@ -370,84 +579,232 @@ impl IntoLow for vir_mid::Statement { statement.position, low_condition, place, - root_address, + address, snapshot, )?; Ok(vec![low_statement.set_default_position(statement.position)]) } + Self::RangeConvertOwnedIntoMemoryBlock(statement) => { + let ty = statement.address.get_type(); + // let vir_mid::Type::Pointer(pointer_type) = ty else { + // unreachable!() + // }; + // let target_type = &*pointer_type.target_type; + let start_address = statement.address.to_procedure_snapshot(lowerer)?; + let start_index = statement.start_index.to_procedure_snapshot(lowerer)?; + let end_index = statement.end_index.to_procedure_snapshot(lowerer)?; + let owned_range = lowerer.owned_aliased_range( + CallContext::Procedure, + ty, + ty, + // target_type, + start_address.clone(), + start_index.clone(), + end_index.clone(), + None, + statement.position, + )?; + // let size_of = lowerer.encode_type_size_expression2(target_type, target_type)?; + let memory_block_range = lowerer.memory_block_range( + ty, + start_address, + start_index, + end_index, + statement.position, + )?; + // FIXME: This should be a builtin method call. + let statements = vec![ + vir_low::Statement::exhale(owned_range, statement.position), + vir_low::Statement::inhale(memory_block_range, statement.position), + ]; + Ok(statements) + } Self::RestoreMutBorrowed(statement) => { let ty = statement.place.get_type(); lowerer.encode_into_memory_block_method(ty)?; let place = lowerer.encode_expression_as_place(&statement.place)?; - let root_address = lowerer.extract_root_address(&statement.place)?; - let current_snapshot = statement.place.to_procedure_snapshot(lowerer)?; - let lifetime = - lowerer.encode_lifetime_const_into_procedure_variable(statement.lifetime)?; - let validity = - lowerer.encode_snapshot_valid_call_for_type(current_snapshot.clone(), ty)?; - let restored_predicate = if let Some((deref_lifetime, uniqueness)) = - statement.place.get_dereference_kind() - { - let deref_lifetime = lowerer - .encode_lifetime_const_into_procedure_variable(deref_lifetime)? - .into(); - if uniqueness.is_unique() { - let final_snapshot = - statement.place.to_procedure_final_snapshot(lowerer)?; - lowerer.unique_ref( - CallContext::Procedure, - ty, - ty, - place, - root_address, - current_snapshot, - final_snapshot, - deref_lifetime, + let address = lowerer.encode_expression_as_place_address(&statement.place)?; + // let root_address = lowerer.extract_root_address(&statement.place)?; + let borrowing_lifetime = lowerer + .encode_lifetime_const_into_procedure_variable(statement.lifetime.clone())?; + let current_snapshot = if let Some(borrowing_place) = statement.borrowing_place { + let vir_mid::Type::Reference(reference_type) = borrowing_place.get_type() else { + unreachable!(); + }; + let target_type = (*reference_type.target_type).clone(); + let deref_place = borrowing_place.deref_no_pos(target_type); + let mut place_encoder = PlaceToSnapshot::for_place(PredicateKind::UniqueRef { + lifetime: borrowing_lifetime.clone().into(), + is_final: true, + }); + place_encoder.expression_to_snapshot(lowerer, &deref_place, false)? + } else { + statement.place.to_procedure_snapshot(lowerer)? + }; + // let validity = + // lowerer.encode_snapshot_valid_call_for_type(current_snapshot.clone(), ty)?; + // let restored_predicate = if let Some((deref_lifetime, uniqueness)) = + // statement.place.get_dereference_kind() + // { + // let deref_lifetime = lowerer + // .encode_lifetime_const_into_procedure_variable(deref_lifetime)? + // .into(); + // if uniqueness.is_unique() { + // let final_snapshot = + // statement.place.to_procedure_final_snapshot(lowerer)?; + // lowerer.unique_ref_with_current_snapshot( + // CallContext::Procedure, + // ty, + // ty, + // place.clone(), + // address.clone(), + // current_snapshot.clone(), + // deref_lifetime, + // None, // FIXME + // None, + // statement.position, + // )? + // } else { + // lowerer.frac_ref_with_current_snapshot( + // CallContext::Procedure, + // ty, + // ty, + // place.clone(), + // address.clone(), + // current_snapshot.clone(), + // deref_lifetime, + // None, // FIXME + // None, + // statement.position, + // )? + // } + // } else { + // lowerer.owned_non_aliased_with_snapshot( + // CallContext::Procedure, + // ty, + // ty, + // place.clone(), + // address.clone(), + // current_snapshot.clone(), + // None, + // statement.position, + // )? + // }; + let low_condition = if let Some(condition) = statement.condition { + Some(lowerer.lower_block_marker_condition(condition)?) + // stmtp! { + // statement.position => + // apply (acc(DeadLifetimeToken(lifetime))) --* ( + // [restored_predicate] && + // [validity] && + // (acc(DeadLifetimeToken(lifetime))) + // ) + // } + } else { + // stmtp! { + // statement.position => + // apply (acc(DeadLifetimeToken(lifetime))) --* ( + // [restored_predicate] && + // [validity] && + // (acc(DeadLifetimeToken(lifetime))) + // ) + // } + None + }; + let (mut arguments, name) = if statement.is_reborrow { + let (lifetime, uniqueness) = statement.place.get_dereference_kind().unwrap(); + assert!(uniqueness.is_unique()); + let lifetime = + lowerer.encode_lifetime_const_into_procedure_variable(lifetime)?; + let (reborrowing_final_snapshot, reborrowing_type) = + lowerer.get_reborrow_target_variable(&statement.lifetime)?; + // let reference_type = statement + // .place + // .get_last_dereferenced_reference() + // .unwrap() + // .get_type(); + let reborrowing_final_snapshot = if reborrowing_type.is_unique_reference() { + lowerer.reference_target_final_snapshot( + &reborrowing_type, + reborrowing_final_snapshot.into(), + statement.position, )? } else { - lowerer.frac_ref( - CallContext::Procedure, - ty, - ty, - place, - root_address, - current_snapshot, - deref_lifetime, + lowerer.reference_target_current_snapshot( + &reborrowing_type, + reborrowing_final_snapshot.into(), + statement.position, )? - } - } else { - lowerer.owned_non_aliased( - CallContext::Procedure, - ty, - ty, + }; + let arguments = vec![ + borrowing_lifetime.into(), place, - root_address, - current_snapshot, - None, - )? - }; - let low_statement = if let Some(condition) = statement.condition { - let low_condition = lowerer.lower_block_marker_condition(condition)?; - stmtp! { - statement.position => - apply (acc(DeadLifetimeToken(lifetime))) --* ( - [restored_predicate] && - [validity] && - (acc(DeadLifetimeToken(lifetime))) - ) - } + address, + reborrowing_final_snapshot, + lifetime.into(), + ]; + (arguments, "reborrow") } else { - stmtp! { - statement.position => - apply (acc(DeadLifetimeToken(lifetime))) --* ( - [restored_predicate] && - [validity] && - (acc(DeadLifetimeToken(lifetime))) - ) - } + let arguments = + vec![borrowing_lifetime.into(), place, address, current_snapshot]; + (arguments, "borrow") }; + arguments.extend(lowerer.create_lifetime_arguments(CallContext::Procedure, ty)?); + arguments.extend(lowerer.create_const_arguments(CallContext::Procedure, ty)?); + let view_shift_name = format!("end${}${}", name, ty.get_identifier()); + let low_statement = lowerer.encode_apply_view_shift( + &view_shift_name, + low_condition, + arguments, + statement.position, + )?; Ok(vec![low_statement]) } + Self::RestoreRawBorrowed(statement) => { + let ty = statement.restored_place.get_type(); + lowerer.encode_restore_raw_borrowed_method(ty)?; + // let borrowing_place_parent = statement.borrowing_place.get_parent_ref().unwrap(); + // let borrowing_snapshot = borrowing_place_parent.to_procedure_snapshot(lowerer)?; + // let borrowing_address = lowerer.pointer_address( + // borrowing_place_parent.get_type(), + // borrowing_snapshot, + // statement.position, + // )?; + let restored_place = + lowerer.encode_expression_as_place(&statement.restored_place)?; + let restored_address = + lowerer.encode_expression_as_place_address(&statement.restored_place)?; + // let restored_root_address = + // lowerer.extract_root_address(&statement.restored_place)?; + // let snapshot = statement.borrowing_place.to_procedure_snapshot(lowerer)?; + let mut place_encoder = PlaceToSnapshot::for_place(PredicateKind::Owned); + let aliased_snapshot = place_encoder.expression_to_snapshot( + lowerer, + &statement.borrowing_place, + false, + )?; + let restored_snapshot = place_encoder.expression_to_snapshot( + lowerer, + &statement.restored_place, + false, + )?; + let mut statements = vec![lowerer.call_restore_raw_borrowed_method( + CallContext::Procedure, + ty, + ty, + statement.position, + restored_address, + restored_place, + aliased_snapshot, + )?]; + lowerer.encode_snapshot_update( + &mut statements, + &statement.restored_place, + restored_snapshot, + statement.position, + )?; + Ok(statements) + } Self::MovePlace(statement) => { // TODO: Remove code duplication with Self::CopyPlace let target_ty = statement.target.get_type(); @@ -457,27 +814,36 @@ impl IntoLow for vir_mid::Statement { assert_eq!(target_ty_without_lifetime, source_ty_without_lifetime); lowerer.encode_move_place_method(target_ty)?; let target_place = lowerer.encode_expression_as_place(&statement.target)?; - let target_root_address = lowerer.extract_root_address(&statement.target)?; + let target_address = + lowerer.encode_expression_as_place_address(&statement.target)?; + // let target_root_address = lowerer.extract_root_address(&statement.target)?; let source_place = lowerer.encode_expression_as_place(&statement.source)?; - let source_root_address = lowerer.extract_root_address(&statement.source)?; - let source_snapshot = statement.source.to_procedure_snapshot(lowerer)?; - let mut statements = vec![lowerer.call_move_place_method( + let source_address = + lowerer.encode_expression_as_place_address(&statement.source)?; + // let source_root_address = lowerer.extract_root_address(&statement.source)?; + // let source_snapshot = statement.source.to_procedure_snapshot(lowerer)?; + let mut place_encoder = PlaceToSnapshot::for_place(PredicateKind::Owned); + // SelfFramingAssertionToSnapshot::for_place_expression(); + let source_snapshot = + place_encoder.expression_to_snapshot(lowerer, &statement.source, false)?; + let mut statements = Vec::new(); + lowerer.encode_snapshot_update( + &mut statements, + &statement.target, + source_snapshot.clone(), + statement.position, + )?; + statements.push(lowerer.call_move_place_method( CallContext::Procedure, target_ty, target_ty, statement.position, target_place, - target_root_address, + target_address, source_place, - source_root_address, - source_snapshot.clone(), - )?]; - lowerer.encode_snapshot_update( - &mut statements, - &statement.target, + source_address, source_snapshot, - statement.position, - )?; + )?); Ok(statements) } Self::CopyPlace(statement) => { @@ -487,34 +853,46 @@ impl IntoLow for vir_mid::Statement { assert_eq!(target_ty, source_ty); lowerer.encode_copy_place_method(target_ty)?; let target_place = lowerer.encode_expression_as_place(&statement.target)?; - let target_root_address = lowerer.extract_root_address(&statement.target)?; + // let target_root_address = lowerer.extract_root_address(&statement.target)?; + let target_address = + lowerer.encode_expression_as_place_address(&statement.target)?; let source_place = lowerer.encode_expression_as_place(&statement.source)?; - let source_root_address = lowerer.extract_root_address(&statement.source)?; + // let source_root_address = lowerer.extract_root_address(&statement.source)?; + let source_address = + lowerer.encode_expression_as_place_address(&statement.source)?; let source_permission_amount = if let Some(source_permission) = statement.source_permission { source_permission.to_procedure_snapshot(lowerer)?.into() } else { vir_low::Expression::full_permission() }; - let source_snapshot = statement.source.to_procedure_snapshot(lowerer)?; - let mut statements = vec![lowerer.call_copy_place_method( + // let source_snapshot = statement.source.to_procedure_snapshot(lowerer)?; + let mut place_encoder = PlaceToSnapshot::for_place(PredicateKind::Owned); + // SelfFramingAssertionToSnapshot::for_place_expression(); + let _source_snapshot = + place_encoder.expression_to_snapshot(lowerer, &statement.source, false)?; + let mut place_encoder = PlaceToSnapshot::for_place(PredicateKind::Owned); + let source_snapshot = + place_encoder.expression_to_snapshot(lowerer, &statement.source, false)?; + let mut statements = Vec::new(); + lowerer.encode_snapshot_update( + &mut statements, + &statement.target, + source_snapshot.clone(), + statement.position, + )?; + statements.push(lowerer.call_copy_place_method( CallContext::Procedure, target_ty, target_ty, statement.position, target_place, - target_root_address, + target_address, source_place, - source_root_address, - source_snapshot.clone(), - source_permission_amount, - )?]; - lowerer.encode_snapshot_update( - &mut statements, - &statement.target, + source_address, source_snapshot, - statement.position, - )?; + source_permission_amount, + )?); Ok(statements) } Self::WritePlace(statement) => { @@ -523,7 +901,9 @@ impl IntoLow for vir_mid::Statement { assert_eq!(target_ty, source_ty); lowerer.encode_write_place_constant_method(target_ty)?; let target_place = lowerer.encode_expression_as_place(&statement.target)?; - let target_root_address = lowerer.extract_root_address(&statement.target)?; + let target_address = + lowerer.encode_expression_as_place_address(&statement.target)?; + // let target_address = lowerer.extract_root_address(&statement.target)?; let source_snapshot = statement.value.to_procedure_snapshot(lowerer)?; let mut statements = vec![lowerer.call_write_place_constant_method( CallContext::Procedure, @@ -531,7 +911,7 @@ impl IntoLow for vir_mid::Statement { target_ty, statement.position, target_place, - target_root_address, + target_address, source_snapshot.clone(), )?]; lowerer.encode_snapshot_update( @@ -574,12 +954,7 @@ impl IntoLow for vir_mid::Statement { let variant_index = variant_place.clone().unwrap_variant().variant_index; let union_place = variant_place.get_parent_ref().unwrap(); let mut statements = Vec::new(); - lowerer.encode_snapshot_havoc( - &mut statements, - union_place, - statement.position, - None, - )?; + lowerer.encode_snapshot_havoc(&mut statements, union_place, statement.position)?; let snapshot = union_place.to_procedure_snapshot(lowerer)?; let discriminant = lowerer.obtain_enum_discriminant( snapshot, @@ -606,6 +981,60 @@ impl IntoLow for vir_mid::Statement { )?; Ok(stmts) } + Self::StashRange(statement) => { + let ty = statement.address.get_type(); + let pointer_value = statement.address.to_procedure_snapshot(lowerer)?; + let start_index = statement.start_index.to_procedure_snapshot(lowerer)?; + let end_index = statement.end_index.to_procedure_snapshot(lowerer)?; + let mut statements = Vec::new(); + lowerer.encode_stash_range_call( + &mut statements, + ty, + pointer_value, + start_index, + end_index, + statement.label, + statement.position, + )?; + Ok(statements) + } + Self::StashRangeRestore(statement) => { + assert_eq!( + statement.old_address.get_type(), + statement.new_address.get_type() + ); + let ty = statement.old_address.get_type(); + let old_pointer_value = statement.old_address.to_procedure_snapshot(lowerer)?; + let old_start_index = statement.old_start_index.to_procedure_snapshot(lowerer)?; + let old_end_index = statement.old_end_index.to_procedure_snapshot(lowerer)?; + let new_address = statement.new_address.to_procedure_snapshot(lowerer)?; + let new_start_index = statement.new_start_index.to_procedure_snapshot(lowerer)?; + let new_end_index = vir_mid::Expression::add( + statement.new_start_index.clone(), + vir_mid::Expression::labelled_old_no_pos( + statement.old_label.clone(), + vir_mid::Expression::subtract( + statement.old_end_index.clone(), + statement.old_start_index.clone(), + ), + ), + ); + let new_end_index = new_end_index.to_procedure_snapshot(lowerer)?; + let mut statements = Vec::new(); + lowerer.encode_restore_stash_range_call( + &mut statements, + ty, + old_pointer_value, + old_start_index, + old_end_index, + statement.old_label, + new_address, + new_start_index, + new_end_index, + statement.position, + )?; + Ok(statements) + } Self::NewLft(statement) => { let targets = vec![vir_low::Expression::local_no_pos( statement.target.to_procedure_snapshot(lowerer)?, @@ -635,29 +1064,128 @@ impl IntoLow for vir_mid::Statement { let (lifetime, uniqueness) = statement.target.get_dereference_kind().unwrap(); let lifetime = lowerer.encode_lifetime_const_into_procedure_variable(lifetime)?; let place = lowerer.encode_expression_as_place(&statement.target)?; - let root_address = lowerer.extract_root_address(&statement.target)?; - let current_snapshot = statement.target.to_procedure_snapshot(lowerer)?; + let address = lowerer.encode_expression_as_place_address(&statement.target)?; + // let root_address = lowerer.extract_root_address(&statement.target)?; // TODO: These should be method calls. let statements = match uniqueness { vir_mid::ty::Uniqueness::Unique => { - let final_snapshot = - statement.target.to_procedure_final_snapshot(lowerer)?; - let predicate = lowerer.unique_ref( - CallContext::Procedure, - ty, - ty, - place, - root_address, - current_snapshot.clone(), - final_snapshot.clone(), - lifetime.into(), + let mut place_encoder = + PlaceToSnapshot::for_place(PredicateKind::UniqueRef { + lifetime: lifetime.clone().into(), + is_final: true, + }); + let final_snapshot = place_encoder.expression_to_snapshot( + lowerer, + &statement.target, + false, )?; + // let final_snapshot = + // statement.target.to_procedure_final_snapshot(lowerer)?; + let label = lowerer.fresh_label("dead_reference_label")?; + lowerer.save_old_label(label.clone())?; + let (current_snapshot, predicate) = if let Some(reborrowing_lifetime) = + statement.is_blocked_by_reborrow + { + // let reference_type = statement + // .target + // .get_last_dereferenced_reference() + // .unwrap() + // .get_type(); + let (reborrowing_final_snapshot, reborrowing_type) = + lowerer.get_reborrow_target_variable(&reborrowing_lifetime)?; + // let reborrowing_final_snapshot = lowerer + // .reference_target_final_snapshot( + // &reborrowing_type, + // reborrowing_final_snapshot.clone().into(), + // statement.position, + // )?; + let reborrowing_final_snapshot = + if reborrowing_type.is_unique_reference() { + lowerer.reference_target_final_snapshot( + &reborrowing_type, + reborrowing_final_snapshot.into(), + statement.position, + )? + } else { + lowerer.reference_target_current_snapshot( + &reborrowing_type, + reborrowing_final_snapshot.into(), + statement.position, + )? + }; + let reborrowing_lifetime = lowerer + .encode_lifetime_const_into_procedure_variable( + reborrowing_lifetime, + )?; + let mut arguments = vec![ + reborrowing_lifetime.into(), + place, + address, + // current_snapshot.clone(), + reborrowing_final_snapshot.clone(), + lifetime.into(), + ]; + arguments.extend( + lowerer.create_lifetime_arguments(CallContext::Procedure, ty)?, + ); + arguments.extend( + lowerer.create_const_arguments(CallContext::Procedure, ty)?, + ); + let view_shift_name = format!("end$reborrow${}", ty.get_identifier()); + let predicate = lowerer.encode_view_shift_predicate( + &view_shift_name, + arguments, + statement.position, + )?; + (reborrowing_final_snapshot, predicate) + } else { + let current_snapshot = lowerer.unique_ref_snap( + CallContext::Procedure, + ty, + ty, + place.clone(), + address.clone(), + lifetime.clone().into(), + None, // FIXME: This should be a proper value + false, + statement.position, + )?; + let current_snapshot = vir_low::Expression::labelled_old_no_pos( + Some(label.clone()), + current_snapshot, + ); + let predicate = lowerer.unique_ref( + CallContext::Procedure, + ty, + ty, + place, + address, + lifetime.into(), + None, // FIXME: This should be a proper value + None, + statement.position, + )?; + (current_snapshot, predicate) + }; + // let predicate = lowerer.unique_ref_with_current_snapshot( + // CallContext::Procedure, + // ty, + // ty, + // place, + // address, + // current_snapshot.clone(), + // lifetime.into(), + // None, // FIXME: This should be a proper value + // None, + // statement.position, + // )?; lowerer.mark_unique_ref_as_used(ty)?; let mut statements = vec![ vir_low::Statement::comment(format!( "dead reference: {}", statement.target )), + vir_low::Statement::label(label, statement.position), vir_low::Statement::exhale_no_pos(predicate) .set_default_position(statement.position), stmtp! { @@ -682,9 +1210,11 @@ impl IntoLow for vir_mid::Statement { ty, ty, place, - root_address, - current_snapshot, + address, lifetime.into(), + None, // FIXME: This should be a proper value + None, + statement.position, )?; let low_statement = vir_low::Statement::exhale_no_pos(predicate) .set_default_position(statement.position); @@ -703,6 +1233,166 @@ impl IntoLow for vir_mid::Statement { }; Ok(statements) } + Self::DeadReferenceRange(statement) => { + let ty = statement.address.get_type(); + let vir_mid::Type::Pointer(pointer_type) = ty else { + unreachable!() + }; + let comment = + vir_low::Statement::comment(format!("dead range reference: {}", statement)); + let target_type = &*pointer_type.target_type; + // let pointer_value = statement.address.to_procedure_snapshot(lowerer)?; + let start_address = statement.address.to_procedure_snapshot(lowerer)?; + // let start_address = + // lowerer.pointer_address(ty, pointer_value, statement.position)?; + let predicate_range_start_index = statement + .predicate_range_start_index + .to_procedure_snapshot(lowerer)?; + let predicate_range_end_index = statement + .predicate_range_end_index + .to_procedure_snapshot(lowerer)?; + let start_index = statement.start_index.to_procedure_snapshot(lowerer)?; + let end_index = statement.end_index.to_procedure_snapshot(lowerer)?; + let lifetime = + lowerer.encode_lifetime_const_into_procedure_variable(statement.lifetime)?; + let statements = match statement.uniqueness { + vir_mid::ty::Uniqueness::Unique => { + let predicate = lowerer.unique_ref_range( + CallContext::Procedure, + ty, + target_type, + start_address.clone(), + start_index.clone(), + end_index.clone(), + lifetime.clone().into(), + None, + statement.position, + )?; + let final_snapshot = lowerer.unique_ref_range_snap( + CallContext::Procedure, + ty, + target_type, + start_address.clone(), + start_index.clone(), + end_index.clone(), + lifetime.clone().into(), + true, + statement.position, + )?; + let current_snapshot = lowerer.unique_ref_range_snap( + CallContext::Procedure, + ty, + target_type, + start_address.clone(), + start_index.clone(), + end_index.clone(), + lifetime.clone().into(), + false, + statement.position, + )?; + let trigger_base = lowerer.unique_ref_range_snap( + CallContext::Procedure, + ty, + target_type, + start_address, + predicate_range_start_index, + predicate_range_end_index, + lifetime.into(), + true, + statement.position, + )?; + let snapshot_seq_type = + vir_low::operations::ty::Typed::get_type(¤t_snapshot).clone(); + let label = lowerer.fresh_label("dead_reference_label")?; + lowerer.save_old_label(label.clone())?; + let current_snapshot = vir_low::Expression::labelled_old_no_pos( + Some(label.clone()), + current_snapshot, + ); + var_decls! { + index: Int + } + let trigger = vir_low::Expression::container_op( + vir_low::ContainerOpKind::SeqIndex, + snapshot_seq_type.clone(), + vec![trigger_base, index.clone().into()], + statement.position, + ); + let size_type = lowerer.size_type_mid()?; + let start_index_int = lowerer.obtain_constant_value( + &size_type, + start_index, + statement.position, + )?; + let end_index_int = lowerer.obtain_constant_value( + &size_type, + end_index, + statement.position, + )?; + let element_index = vir_low::Expression::subtract( + index.clone().into(), + start_index_int.clone(), + ); + let current_snapshot_element = vir_low::Expression::container_op( + vir_low::ContainerOpKind::SeqIndex, + snapshot_seq_type.clone(), + vec![current_snapshot, element_index.clone()], + statement.position, + ); + let final_snapshot_element = vir_low::Expression::container_op( + vir_low::ContainerOpKind::SeqIndex, + snapshot_seq_type, + vec![final_snapshot, element_index], + statement.position, + ); + // We need this to ensure that Silicon does not complain + // that the trigger is invalid. + let trigger_validity = + lowerer.trigger_expression(trigger.clone(), statement.position)?; + let quantifier_body = expr! { + (([start_index_int] <= index) && (index < [end_index_int])) ==> + ( + ([trigger_validity]) && + ([current_snapshot_element] == [final_snapshot_element]) + ) + }; + let quantifier = vir_low::Expression::forall( + vec![index], + vec![vir_low::Trigger::new(vec![trigger])], + quantifier_body, + ); + quantifier.assert_valid_debug(); + vec![ + comment, + vir_low::Statement::label(label, statement.position), + vir_low::Statement::exhale_no_pos(predicate) + .set_default_position(statement.position), + stmtp! { + statement.position => + assume [quantifier] + // assume ([current_snapshot] == [final_snapshot]) + }, + ] + } + vir_mid::ty::Uniqueness::Shared => { + let predicate = lowerer.frac_ref_range( + CallContext::Procedure, + ty, + target_type, + start_address, + start_index, + end_index, + lifetime.into(), + None, + statement.position, + )?; + let low_statement = vir_low::Statement::exhale_no_pos(predicate) + .set_default_position(statement.position); + vec![comment, low_statement] + } + }; + Ok(statements) + } Self::DeadLifetime(_statement) => { // TODO: This should resolve the lifetime for statement.target // instead of just marking the lifetime as dead. Once that is @@ -723,69 +1413,75 @@ impl IntoLow for vir_mid::Statement { )]) } Self::LifetimeTake(statement) => { - if statement.value.len() == 1 { - let value = vir_low::Expression::local_no_pos( - statement - .value - .first() - .unwrap() - .to_procedure_snapshot(lowerer)?, - ); - let statements = vec![Statement::assign( - lowerer - .new_snapshot_variable_version(&statement.target, statement.position)?, - value, - statement.position, - )]; - Ok(statements) - } else { - lowerer.encode_lft_tok_sep_take_method(statement.value.len())?; - let mut arguments: Vec = vec![]; - for lifetime in &statement.value { - arguments.push(vir_low::Expression::local_no_pos( - lifetime.to_procedure_snapshot(lowerer)?, - )); - } - let perm_amount = statement - .lifetime_token_permission - .to_procedure_snapshot(lowerer)?; - arguments.push(perm_amount); - let statements = vec![Statement::method_call( - format!("lft_tok_sep_take${}", statement.value.len()), - arguments.clone(), - vec![lowerer - .new_snapshot_variable_version(&statement.target, statement.position)? - .into()], - statement.position, - )]; - Ok(statements) + // if statement.value.len() == 1 { + // let value = vir_low::Expression::local_no_pos( + // statement + // .value + // .first() + // .unwrap() + // .to_procedure_snapshot(lowerer)?, + // ); + // let statements = vec![Statement::assume( + // vir_low::Expression::equals( + // lowerer + // .new_snapshot_variable_version( + // &statement.target, + // statement.position, + // )? + // .into(), + // value, + // ), + // statement.position, + // )]; + // Ok(statements) + // } else { + lowerer.encode_lft_tok_sep_take_method(statement.value.len())?; + let mut arguments: Vec = vec![]; + for lifetime in &statement.value { + arguments.push(vir_low::Expression::local_no_pos( + lifetime.to_procedure_snapshot(lowerer)?, + )); } + let perm_amount = statement + .lifetime_token_permission + .to_procedure_snapshot(lowerer)?; + arguments.push(perm_amount); + let statements = vec![Statement::method_call( + format!("lft_tok_sep_take${}", statement.value.len()), + arguments.clone(), + vec![lowerer + .new_snapshot_variable_version(&statement.target, statement.position)? + .into()], + statement.position, + )]; + Ok(statements) + // } } Self::LifetimeReturn(statement) => { - if statement.value.len() > 1 { - lowerer.encode_lft_tok_sep_return_method(statement.value.len())?; - let mut arguments: Vec = - vec![vir_low::Expression::local_no_pos( - statement.target.to_procedure_snapshot(lowerer)?, - )]; - for lifetime in &statement.value { - arguments.push(vir_low::Expression::local_no_pos( - lifetime.to_procedure_snapshot(lowerer)?, - )); - } - let perm_amount = statement - .lifetime_token_permission - .to_procedure_snapshot(lowerer)?; - arguments.push(perm_amount); - Ok(vec![Statement::method_call( - format!("lft_tok_sep_return${}", statement.value.len()), - arguments, - vec![], - statement.position, - )]) - } else { - Ok(vec![]) + // if statement.value.len() > 1 { + lowerer.encode_lft_tok_sep_return_method(statement.value.len())?; + let mut arguments: Vec = + vec![vir_low::Expression::local_no_pos( + statement.target.to_procedure_snapshot(lowerer)?, + )]; + for lifetime in &statement.value { + arguments.push(vir_low::Expression::local_no_pos( + lifetime.to_procedure_snapshot(lowerer)?, + )); } + let perm_amount = statement + .lifetime_token_permission + .to_procedure_snapshot(lowerer)?; + arguments.push(perm_amount); + Ok(vec![Statement::method_call( + format!("lft_tok_sep_return${}", statement.value.len()), + arguments, + vec![], + statement.position, + )]) + // } else { + // Ok(vec![]) + // } } Self::OpenFracRef(statement) => { let ty = statement.place.get_type(); @@ -796,21 +1492,28 @@ impl IntoLow for vir_mid::Statement { .lifetime_token_permission .to_procedure_snapshot(lowerer)?; let place = lowerer.encode_expression_as_place(&statement.place)?; - let address = lowerer.extract_root_address(&statement.place)?; - let current_snapshot = statement.place.to_procedure_snapshot(lowerer)?; + let address = lowerer.encode_expression_as_place_address(&statement.place)?; + // let address = lowerer.extract_root_address(&statement.place)?; let targets = vec![statement .predicate_permission_amount .to_procedure_snapshot(lowerer)? .into()]; + let mut arguments = vec![lifetime.clone().into(), perm_amount, place, address]; + // if lowerer.check_mode.unwrap() == CheckMode::PurificationSoudness { + // let mut assertion_encoder = SelfFramingAssertionToSnapshot::for_place_expression(); + let mut place_encoder = PlaceToSnapshot::for_place(PredicateKind::FracRef { + lifetime: lifetime.into(), + }); + // let current_snapshot = statement.place.to_procedure_snapshot(lowerer)?; + let current_snapshot = + place_encoder.expression_to_snapshot(lowerer, &statement.place, true)?; + arguments.push(current_snapshot); + arguments.extend(lowerer.create_lifetime_arguments(CallContext::Procedure, ty)?); + arguments.extend(lowerer.create_const_arguments(CallContext::Procedure, ty)?); + // } Ok(vec![Statement::method_call( method_name!(frac_bor_atomic_acc), - vec![ - lifetime.into(), - perm_amount, - place, - address, - current_snapshot, - ], + arguments, targets, statement.position, )]) @@ -823,38 +1526,63 @@ impl IntoLow for vir_mid::Statement { .lifetime_token_permission .to_procedure_snapshot(lowerer)?; let place = lowerer.encode_expression_as_place(&statement.place)?; - let root_address = lowerer.extract_root_address(&statement.place)?; - let current_snapshot = statement.place.to_procedure_snapshot(lowerer)?; + let address = lowerer.encode_expression_as_place_address(&statement.place)?; + let mut place_encoder = PlaceToSnapshot::for_place(PredicateKind::Owned); + // let mut place_encoder = SelfFramingAssertionToSnapshot::for_place_expression(); + let current_snapshot = + place_encoder.expression_to_snapshot(lowerer, &statement.place, true)?; + // let current_snapshot = statement.place.to_procedure_snapshot(lowerer)?; + // let root_address = lowerer.extract_root_address(&statement.place)?; let tmp_frac_ref_perm = statement .predicate_permission_amount .to_procedure_snapshot(lowerer)?; - let owned_predicate = lowerer.owned_non_aliased( - CallContext::Procedure, - ty, - ty, - place.clone(), - root_address.clone(), - current_snapshot.clone(), - Some(tmp_frac_ref_perm.into()), - )?; - let frac_predicate = lowerer.frac_ref( - CallContext::Procedure, - ty, - ty, + // let owned_predicate = lowerer.owned_non_aliased( + // CallContext::Procedure, + // ty, + // ty, + // place.clone(), + // address.clone(), + // Some(tmp_frac_ref_perm.into()), + // statement.position, + // )?; + // let frac_predicate = lowerer.frac_ref_with_current_snapshot( + // CallContext::Procedure, + // ty, + // ty, + // place, + // address, + // current_snapshot, + // lifetime.clone().into(), + // None, + // None, + // statement.position, + // )?; + let mut arguments = vec![ + lifetime.into(), + perm_amount, place, - root_address, + address, current_snapshot, - lifetime.clone().into(), - )?; - Ok(vec![stmtp! { - statement.position => - apply ( - [owned_predicate] - ) --* ( - (acc(LifetimeToken(lifetime), [perm_amount])) && - [frac_predicate] - ) - }]) + tmp_frac_ref_perm.into(), + ]; + arguments.extend(lowerer.create_lifetime_arguments(CallContext::Procedure, ty)?); + arguments.extend(lowerer.create_const_arguments(CallContext::Procedure, ty)?); + let statements = vec![Statement::method_call( + method_name!(close_frac_ref), + arguments, + Vec::new(), + statement.position, + )]; + Ok(statements) + // Ok(vec![stmtp! { + // statement.position => + // apply ( + // [owned_predicate] + // ) --* ( + // (acc(LifetimeToken(lifetime), [perm_amount])) && + // [frac_predicate] + // ) + // }]) } Self::OpenMutRef(statement) => { let ty = statement.place.get_type(); @@ -865,9 +1593,24 @@ impl IntoLow for vir_mid::Statement { .lifetime_token_permission .to_procedure_snapshot(lowerer)?; let place = lowerer.encode_expression_as_place(&statement.place)?; - let address = lowerer.extract_root_address(&statement.place)?; - let current_snapshot = statement.place.to_procedure_snapshot(lowerer)?; - let final_snapshot = statement.place.to_procedure_final_snapshot(lowerer)?; + let address = lowerer.encode_expression_as_place_address(&statement.place)?; + // let address = lowerer.extract_root_address(&statement.place)?; + let mut place_encoder = PlaceToSnapshot::for_place(PredicateKind::UniqueRef { + lifetime: lifetime.clone().into(), + is_final: false, + }); + // SelfFramingAssertionToSnapshot::for_place_expression(); + let current_snapshot = + place_encoder.expression_to_snapshot(lowerer, &statement.place, false)?; + // let current_snapshot = statement.place.to_procedure_snapshot(lowerer)?; + // let final_snapshot = statement.place.to_procedure_final_snapshot(lowerer)?; + let mut place_encoder = PlaceToSnapshot::for_place(PredicateKind::UniqueRef { + lifetime: lifetime.clone().into(), + is_final: true, + }); + // SelfFramingAssertionToSnapshot::for_place_expression(); + let final_snapshot = + place_encoder.expression_to_snapshot(lowerer, &statement.place, false)?; let statements = vec![stmtp! { statement.position => call open_mut_ref( lifetime, @@ -889,9 +1632,21 @@ impl IntoLow for vir_mid::Statement { .lifetime_token_permission .to_procedure_snapshot(lowerer)?; let place = lowerer.encode_expression_as_place(&statement.place)?; - let address = lowerer.extract_root_address(&statement.place)?; - let current_snapshot = statement.place.to_procedure_snapshot(lowerer)?; - let final_snapshot = statement.place.to_procedure_final_snapshot(lowerer)?; + let address = lowerer.encode_expression_as_place_address(&statement.place)?; + // let address = lowerer.extract_root_address(&statement.place)?; + // let current_snapshot = statement.place.to_procedure_snapshot(lowerer)?; + // let final_snapshot = statement.place.to_procedure_final_snapshot(lowerer)?; + let mut place_encoder = PlaceToSnapshot::for_place(PredicateKind::Owned); + // SelfFramingAssertionToSnapshot::for_place_expression(); + let current_snapshot = + place_encoder.expression_to_snapshot(lowerer, &statement.place, false)?; + let mut place_encoder = PlaceToSnapshot::for_place(PredicateKind::UniqueRef { + lifetime: lifetime.clone().into(), + is_final: true, + }); + // SelfFramingAssertionToSnapshot::for_place_expression(); + let final_snapshot = + place_encoder.expression_to_snapshot(lowerer, &statement.place, false)?; let statements = vec![stmtp! { statement.position => call close_mut_ref( lifetime, @@ -952,6 +1707,72 @@ impl IntoLow for vir_mid::Statement { )?]; Ok(statements) } + Self::MaterializePredicate(statement) => { + let mut statements = Vec::new(); + if config::purify_with_symbolic_execution() { + // Predicate::into_low assumes that the predicate is non-aliased while here we + // need an aliased version. + let predicate = match statement.predicate { + vir_mid::Predicate::OwnedNonAliased(predicate) => { + let address = + lowerer.encode_expression_as_place_address(&predicate.place)?; + let ty = predicate.place.get_type(); + lowerer.owned_aliased( + CallContext::Procedure, + ty, + ty, + address, + None, + predicate.position, + )? + } + vir_mid::Predicate::UniqueRef(predicate) => { + let place = + lowerer.place_option_none_constructor(statement.position)?; + let mut assertion_encoder = + SelfFramingAssertionToSnapshot::for_inhale_exhale_expression(None); + let address = assertion_encoder + .pointer_deref_into_address(lowerer, &predicate.place)?; + let lifetime = lowerer.encode_lifetime_const_into_procedure_variable( + predicate.lifetime, + )?; + let ty = predicate.place.get_type(); + lowerer.unique_ref( + CallContext::Procedure, + ty, + ty, + place, + address, + lifetime.into(), + None, + None, + predicate.position, + )? + } + vir_mid::Predicate::FracRef(predicate) => { + unimplemented!("{predicate}"); + } + _ => statement.predicate.into_low(lowerer)?, + }; + statements.push(vir_low::Statement::materialize_predicate( + predicate, + statement.check_that_exists, + statement.position, + )); + } + Ok(statements) + } + Self::CaseSplit(statement) => { + assert!( + statement.expression.is_pure(), + "must be pure: {}", + statement.expression + ); + Ok(vec![Statement::case_split( + statement.expression.to_procedure_assertion(lowerer)?, + statement.position, + )]) + } } } } @@ -962,60 +1783,137 @@ impl IntoLow for vir_mid::Predicate { self, lowerer: &mut Lowerer<'p, 'v, 'tcx>, ) -> SpannedEncodingResult { - use vir_low::macros::*; - use vir_mid::Predicate; let result = match self { - Predicate::LifetimeToken(predicate) => { - lowerer.encode_lifetime_token_predicate()?; - let lifetime = - lowerer.encode_lifetime_const_into_procedure_variable(predicate.lifetime)?; - let permission = predicate.permission.to_procedure_snapshot(lowerer)?; - expr! { acc(LifetimeToken([lifetime.into()]), [permission])} - .set_default_position(predicate.position) - } - Predicate::MemoryBlockStack(predicate) => { - lowerer.encode_memory_block_predicate()?; - let place = lowerer.encode_expression_as_place_address(&predicate.place)?; - let size = predicate.size.to_procedure_snapshot(lowerer)?; - expr! { acc(MemoryBlock([place], [size]))}.set_default_position(predicate.position) - } - Predicate::MemoryBlockStackDrop(predicate) => { - let place = lowerer.encode_expression_as_place_address(&predicate.place)?; - let size = predicate.size.to_procedure_snapshot(lowerer)?; - lowerer.encode_memory_block_stack_drop_acc(place, size, predicate.position)? - } - Predicate::MemoryBlockHeap(predicate) => { + Self::LifetimeToken(predicate) => predicate.into_low(lowerer)?, + Self::MemoryBlockStack(predicate) => predicate.into_low(lowerer)?, + Self::MemoryBlockStackDrop(predicate) => predicate.into_low(lowerer)?, + Self::MemoryBlockHeap(predicate) => predicate.into_low(lowerer)?, + Self::MemoryBlockHeapRange(predicate) => { unimplemented!("predicate: {}", predicate); } - Predicate::MemoryBlockHeapDrop(predicate) => { + Self::MemoryBlockHeapRangeGuarded(predicate) => { unimplemented!("predicate: {}", predicate); } - Predicate::OwnedNonAliased(predicate) => { - let place = lowerer.encode_expression_as_place(&predicate.place)?; - let root_address = lowerer.extract_root_address(&predicate.place)?; - let snapshot = predicate.place.to_procedure_snapshot(lowerer)?; - let ty = predicate.place.get_type(); - let valid = lowerer.encode_snapshot_valid_call_for_type(snapshot.clone(), ty)?; - let low_predicate = lowerer.owned_non_aliased( - CallContext::Procedure, - ty, - ty, - place, - root_address, - snapshot, - None, - )?; - exprp! { - predicate.position => - [low_predicate] && - [valid] - } - } + Self::MemoryBlockHeapDrop(predicate) => predicate.into_low(lowerer)?, + Self::OwnedNonAliased(predicate) => predicate.into_low(lowerer)?, + Self::OwnedRange(_) => todo!(), + Self::OwnedSet(_) => todo!(), + Self::UniqueRef(_) => todo!(), + Self::UniqueRefRange(_) => todo!(), + Self::FracRef(_) => todo!(), + Self::FracRefRange(_) => todo!(), }; Ok(result) } } +impl IntoLow for vir_mid::ast::predicate::LifetimeToken { + type Target = vir_low::Expression; + + fn into_low<'p, 'v: 'p, 'tcx: 'v>( + self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + ) -> SpannedEncodingResult { + use vir_low::macros::*; + lowerer.encode_lifetime_token_predicate()?; + let lifetime = lowerer.encode_lifetime_const_into_procedure_variable(self.lifetime)?; + let permission = self.permission.to_procedure_snapshot(lowerer)?; + Ok(expr! { acc(LifetimeToken([lifetime.into()]), [permission])} + .set_default_position(self.position)) + } +} + +impl IntoLow for vir_mid::ast::predicate::OwnedNonAliased { + type Target = vir_low::Expression; + + fn into_low<'p, 'v: 'p, 'tcx: 'v>( + self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + ) -> SpannedEncodingResult { + use vir_low::macros::*; + lowerer.mark_place_as_used_in_memory_block(&self.place)?; + let place = lowerer.encode_expression_as_place(&self.place)?; + let address = lowerer.encode_expression_as_place_address(&self.place)?; + // let root_address = lowerer.extract_root_address(&self.place)?; + let snapshot = self.place.to_procedure_snapshot(lowerer)?; + let ty = self.place.get_type(); + let valid = lowerer.encode_snapshot_valid_call_for_type(snapshot.clone(), ty)?; + let low_predicate = lowerer.owned_non_aliased_with_snapshot( + CallContext::Procedure, + ty, + ty, + place, + address, + snapshot, + None, + self.position, + )?; + Ok(exprp! { + self.position => + [low_predicate] && + [valid] + }) + } +} + +impl IntoLow for vir_mid::ast::predicate::MemoryBlockStack { + type Target = vir_low::Expression; + + fn into_low<'p, 'v: 'p, 'tcx: 'v>( + self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + ) -> SpannedEncodingResult { + lowerer.encode_memory_block_predicate()?; + lowerer.mark_place_as_used_in_memory_block(&self.place)?; + let place = lowerer.encode_expression_as_place_address(&self.place)?; + let size = self.size.to_procedure_snapshot(lowerer)?; + lowerer.encode_memory_block_acc(place, size, self.position) + } +} + +impl IntoLow for vir_mid::ast::predicate::MemoryBlockStackDrop { + type Target = vir_low::Expression; + + fn into_low<'p, 'v: 'p, 'tcx: 'v>( + self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + ) -> SpannedEncodingResult { + let place = lowerer.encode_expression_as_place_address(&self.place)?; + let size = self.size.to_procedure_snapshot(lowerer)?; + lowerer.encode_memory_block_stack_drop_acc(place, size, self.position) + } +} + +impl IntoLow for vir_mid::ast::predicate::MemoryBlockHeap { + type Target = vir_low::Expression; + + fn into_low<'p, 'v: 'p, 'tcx: 'v>( + self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + ) -> SpannedEncodingResult { + let mut assertion_encoder = + SelfFramingAssertionToSnapshot::for_inhale_exhale_expression(None); + let address = assertion_encoder.pointer_deref_into_address(lowerer, &self.address)?; + let size = self.size.to_procedure_snapshot(lowerer)?; + lowerer.encode_memory_block_acc(address, size, self.position) + } +} + +impl IntoLow for vir_mid::ast::predicate::MemoryBlockHeapDrop { + type Target = vir_low::Expression; + + fn into_low<'p, 'v: 'p, 'tcx: 'v>( + self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + ) -> SpannedEncodingResult { + let mut assertion_encoder = + SelfFramingAssertionToSnapshot::for_inhale_exhale_expression(None); + let address = assertion_encoder.pointer_deref_into_address(lowerer, &self.address)?; + let size = self.size.to_procedure_snapshot(lowerer)?; + lowerer.encode_memory_block_heap_drop_acc(address, size, self.position) + } +} + impl IntoLow for vir_mid::BasicBlockId { type Target = vir_low::Label; fn into_low<'p, 'v: 'p, 'tcx: 'v>( diff --git a/prusti-viper/src/encoder/middle/core_proof/labels/interface.rs b/prusti-viper/src/encoder/middle/core_proof/labels/interface.rs new file mode 100644 index 00000000000..5a452e928cf --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/labels/interface.rs @@ -0,0 +1,22 @@ +use crate::encoder::{errors::SpannedEncodingResult, middle::core_proof::lowerer::Lowerer}; +use vir_crate::low as vir_low; + +pub(in super::super) trait LabelsInterface { + fn fresh_label(&mut self, prefix: &str) -> SpannedEncodingResult; + fn save_custom_label(&mut self, label: String) -> SpannedEncodingResult<()>; +} + +impl<'p, 'v: 'p, 'tcx: 'v> LabelsInterface for Lowerer<'p, 'v, 'tcx> { + fn fresh_label(&mut self, prefix: &str) -> SpannedEncodingResult { + let label = format!("{}{}", prefix, self.labels_state.counter); + self.save_custom_label(label.clone())?; + self.labels_state.counter += 1; + Ok(label) + } + + fn save_custom_label(&mut self, label: String) -> SpannedEncodingResult<()> { + let label = vir_low::Label::new(label); + assert!(self.labels_state.labels.insert(label)); + Ok(()) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/labels/mod.rs b/prusti-viper/src/encoder/middle/core_proof/labels/mod.rs new file mode 100644 index 00000000000..381f69bf617 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/labels/mod.rs @@ -0,0 +1,4 @@ +mod interface; +mod state; + +pub(super) use self::{interface::LabelsInterface, state::LabelsState}; diff --git a/prusti-viper/src/encoder/middle/core_proof/labels/state.rs b/prusti-viper/src/encoder/middle/core_proof/labels/state.rs new file mode 100644 index 00000000000..fb9247ef7e3 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/labels/state.rs @@ -0,0 +1,14 @@ +use std::collections::BTreeSet; +use vir_crate::low as vir_low; + +#[derive(Default)] +pub(in super::super) struct LabelsState { + pub(super) counter: u64, + pub(super) labels: BTreeSet, +} + +impl LabelsState { + pub(crate) fn destruct(self) -> Vec { + self.labels.into_iter().collect() + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/lifetimes/interface.rs b/prusti-viper/src/encoder/middle/core_proof/lifetimes/interface.rs index c4c01f4bf16..a7a1fffe64b 100644 --- a/prusti-viper/src/encoder/middle/core_proof/lifetimes/interface.rs +++ b/prusti-viper/src/encoder/middle/core_proof/lifetimes/interface.rs @@ -8,7 +8,10 @@ use crate::encoder::{ }; use std::collections::{BTreeMap, VecDeque}; use vir_crate::{ - common::expression::{BinaryOperationHelpers, QuantifierHelpers}, + common::{ + builtin_constants::LIFETIME_DOMAIN_NAME, + expression::{BinaryOperationHelpers, QuantifierHelpers}, + }, low as vir_low, middle as vir_mid, middle::operations::lifetimes::WithLifetimes, }; @@ -114,7 +117,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { impl<'p, 'v: 'p, 'tcx: 'v> LifetimesInterface for Lowerer<'p, 'v, 'tcx> { fn lifetime_domain_name(&self) -> SpannedEncodingResult { - Ok("Lifetime".to_string()) + Ok(LIFETIME_DOMAIN_NAME.to_string()) } fn lifetime_type(&mut self) -> SpannedEncodingResult { @@ -131,7 +134,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> LifetimesInterface for Lowerer<'p, 'v, 'tcx> { &mut self, lft_count: usize, ) -> SpannedEncodingResult> { - let ty = self.domain_type("Lifetime")?; + let ty = self.domain_type(LIFETIME_DOMAIN_NAME)?; let mut lifetimes: Vec = vec![]; for i in 1..(lft_count + 1) { lifetimes.push(vir_low::VariableDecl::new(format!("lft_{i}"), ty.clone())); @@ -203,7 +206,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> LifetimesInterface for Lowerer<'p, 'v, 'tcx> { var_decls!(lft_2: Lifetime); let arguments: Vec = vec![lft_1.into(), lft_2.into()]; self.create_domain_func_app( - "Lifetime", + LIFETIME_DOMAIN_NAME, "included", arguments, vir_low::ty::Type::Bool, @@ -219,7 +222,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> LifetimesInterface for Lowerer<'p, 'v, 'tcx> { use vir_low::macros::*; var_decls!(lft: Lifetime); let quantifier_body = self.create_domain_func_app( - "Lifetime", + LIFETIME_DOMAIN_NAME, "included", vec![lft.clone().into(), lft.clone().into()], vir_low::ty::Type::Bool, @@ -234,7 +237,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> LifetimesInterface for Lowerer<'p, 'v, 'tcx> { quantifier_body, ), }; - self.declare_axiom("Lifetime", axiom)?; + self.declare_axiom(LIFETIME_DOMAIN_NAME, axiom)?; Ok(()) } @@ -272,7 +275,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> LifetimesInterface for Lowerer<'p, 'v, 'tcx> { body, ), }; - self.declare_axiom("Lifetime", axiom)?; + self.declare_axiom(LIFETIME_DOMAIN_NAME, axiom)?; } { var_decls! { @@ -302,7 +305,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> LifetimesInterface for Lowerer<'p, 'v, 'tcx> { body, ), }; - self.declare_axiom("Lifetime", axiom)?; + self.declare_axiom(LIFETIME_DOMAIN_NAME, axiom)?; } { var_decls! { @@ -329,7 +332,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> LifetimesInterface for Lowerer<'p, 'v, 'tcx> { body, ), }; - self.declare_axiom("Lifetime", axiom)?; + self.declare_axiom(LIFETIME_DOMAIN_NAME, axiom)?; } Ok(()) } @@ -339,6 +342,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> LifetimesInterface for Lowerer<'p, 'v, 'tcx> { self.lifetimes_state.is_lifetime_token_encoded = true; let predicate = vir_low::PredicateDecl::new( "LifetimeToken", + vir_low::PredicateKind::LifetimeToken, vec![vir_low::VariableDecl::new( "lifetime", self.lifetime_type()?, @@ -348,6 +352,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> LifetimesInterface for Lowerer<'p, 'v, 'tcx> { self.declare_predicate(predicate)?; let predicate = vir_low::PredicateDecl::new( "DeadLifetimeToken", + vir_low::PredicateKind::DeadLifetimeToken, vec![vir_low::VariableDecl::new( "lifetime", self.lifetime_type()?, @@ -388,11 +393,12 @@ impl<'p, 'v: 'p, 'tcx: 'v> LifetimesInterface for Lowerer<'p, 'v, 'tcx> { .contains_key(&is_alive_variable) { let variable = self.initial_snapshot_variable_version(&is_alive_variable)?; + let position = self.procedure_position.unwrap(); self.lifetimes_state .lifetime_is_alive_initialization .insert( is_alive_variable.clone(), - vir_low::Statement::assign_no_pos(variable, true.into()), + vir_low::Statement::assume(variable.into(), position), ); } is_alive_variable.to_procedure_snapshot(self) diff --git a/prusti-viper/src/encoder/middle/core_proof/lowerer/domains/interface.rs b/prusti-viper/src/encoder/middle/core_proof/lowerer/domains/interface.rs index dc5f061e9f8..230139539fe 100644 --- a/prusti-viper/src/encoder/middle/core_proof/lowerer/domains/interface.rs +++ b/prusti-viper/src/encoder/middle/core_proof/lowerer/domains/interface.rs @@ -1,41 +1,45 @@ use crate::encoder::{errors::SpannedEncodingResult, middle::core_proof::lowerer::Lowerer}; use std::collections::{BTreeMap, BTreeSet}; use vir_crate::{ - common::identifier::WithIdentifier, + common::{identifier::WithIdentifier, validator::Validator}, low::{self as vir_low}, middle as vir_mid, }; +#[derive(Default)] +pub(in super::super::super) struct DomainsInfo {} + #[derive(Default)] pub(in super::super) struct DomainsLowererState { functions: BTreeSet, domains: BTreeMap, + domains_info: DomainsInfo, } impl DomainsLowererState { - pub fn destruct(self) -> Vec { - self.domains.into_values().collect() + pub fn destruct(self) -> (Vec, DomainsInfo) { + (self.domains.into_values().collect(), self.domains_info) } } -trait DomainsLowererInterfacePrivate { - /// Returns a borrow of a domain. Creates the domain if it does not exist. - fn borrow_domain( - &mut self, - domain_name: String, - ) -> SpannedEncodingResult<&mut vir_low::DomainDecl>; - fn create_domain_func_app_custom( - &mut self, - domain_name: String, - function_name: String, - arguments: Vec, - return_type: vir_low::Type, - is_unique: bool, - position: vir_low::Position, - ) -> SpannedEncodingResult; -} +// trait DomainsLowererInterfacePrivate { +// /// Returns a borrow of a domain. Creates the domain if it does not exist. +// fn borrow_domain( +// &mut self, +// domain_name: String, +// ) -> SpannedEncodingResult<&mut vir_low::DomainDecl>; +// fn create_domain_func_app_custom( +// &mut self, +// domain_name: String, +// function_name: String, +// arguments: Vec, +// return_type: vir_low::Type, +// is_unique: bool, +// position: vir_low::Position, +// ) -> SpannedEncodingResult; +// } -impl<'p, 'v: 'p, 'tcx: 'v> DomainsLowererInterfacePrivate for Lowerer<'p, 'v, 'tcx> { +impl<'p, 'v: 'p, 'tcx: 'v> Lowerer<'p, 'v, 'tcx> { fn borrow_domain( &mut self, domain_name: String, @@ -44,7 +48,9 @@ impl<'p, 'v: 'p, 'tcx: 'v> DomainsLowererInterfacePrivate for Lowerer<'p, 'v, 't .domains_state .domains .entry(domain_name.clone()) - .or_insert_with(|| vir_low::DomainDecl::new(domain_name, Vec::new(), Vec::new())); + .or_insert_with(|| { + vir_low::DomainDecl::new(domain_name, Vec::new(), Vec::new(), Vec::new()) + }); Ok(domain) } @@ -84,6 +90,11 @@ pub(in super::super::super) trait DomainsLowererInterface { domain_name: &str, axiom: vir_low::DomainAxiomDecl, ) -> SpannedEncodingResult<()>; + fn declare_rewrite_rule( + &mut self, + domain_name: &str, + axiom: vir_low::DomainRewriteRuleDecl, + ) -> SpannedEncodingResult<()>; fn insert_domain_function( &mut self, domain_name: &str, @@ -97,6 +108,15 @@ pub(in super::super::super) trait DomainsLowererInterface { parameters: std::borrow::Cow<'_, Vec>, return_type: std::borrow::Cow<'_, vir_low::Type>, ) -> SpannedEncodingResult<()>; + // /// Declare a domain function that is a binary operator. + // fn declare_domain_function_maybe_binary_op( + // &mut self, + // domain_name: &str, + // function_name: std::borrow::Cow<'_, String>, + // operation: Option<(vir_mid::BinaryOpKind, vir_mid::Type)>, + // parameters: std::borrow::Cow<'_, Vec>, + // return_type: std::borrow::Cow<'_, vir_low::Type>, + // ) -> SpannedEncodingResult<()>; fn create_domain_func_app( &mut self, domain_name: impl ToString, @@ -153,10 +173,20 @@ impl<'p, 'v: 'p, 'tcx: 'v> DomainsLowererInterface for Lowerer<'p, 'v, 'tcx> { domain_name: &str, axiom: vir_low::DomainAxiomDecl, ) -> SpannedEncodingResult<()> { + axiom.assert_valid_debug(); let domain = self.domains_state.domains.get_mut(domain_name).unwrap(); domain.axioms.push(axiom); Ok(()) } + fn declare_rewrite_rule( + &mut self, + domain_name: &str, + axiom: vir_low::DomainRewriteRuleDecl, + ) -> SpannedEncodingResult<()> { + let domain = self.domains_state.domains.get_mut(domain_name).unwrap(); + domain.rewrite_rules.push(axiom); + Ok(()) + } fn insert_domain_function( &mut self, domain_name: &str, @@ -194,6 +224,33 @@ impl<'p, 'v: 'p, 'tcx: 'v> DomainsLowererInterface for Lowerer<'p, 'v, 'tcx> { } Ok(()) } + // fn declare_domain_function_maybe_binary_op( + // &mut self, + // domain_name: &str, + // function_name: std::borrow::Cow<'_, String>, + // operation: Option<(vir_mid::BinaryOpKind, vir_mid::Type)>, + // parameters: std::borrow::Cow<'_, Vec>, + // return_type: std::borrow::Cow<'_, vir_low::Type>, + // ) -> SpannedEncodingResult<()> { + // if !self.domains_state.functions.contains(&*function_name) { + // if let Some((op, ty)) = operation { + // assert!(self + // .domains_state + // .domains_info + // .snapshot_binary_operators + // .insert(function_name.to_string(), (op, ty),) + // .is_none()); + // } + // self.declare_domain_function( + // domain_name, + // function_name, + // false, + // parameters, + // return_type, + // )?; + // } + // Ok(()) + // } /// Note: You are likely to want to call one of this function's wrappers. fn create_domain_func_app( &mut self, diff --git a/prusti-viper/src/encoder/middle/core_proof/lowerer/domains/mod.rs b/prusti-viper/src/encoder/middle/core_proof/lowerer/domains/mod.rs index 3dffb1d6e1a..f40ce218924 100644 --- a/prusti-viper/src/encoder/middle/core_proof/lowerer/domains/mod.rs +++ b/prusti-viper/src/encoder/middle/core_proof/lowerer/domains/mod.rs @@ -1,4 +1,4 @@ mod interface; -pub(in super::super) use self::interface::DomainsLowererInterface; pub(super) use self::interface::DomainsLowererState; +pub(in super::super) use self::interface::{DomainsInfo, DomainsLowererInterface}; diff --git a/prusti-viper/src/encoder/middle/core_proof/lowerer/functions/interface.rs b/prusti-viper/src/encoder/middle/core_proof/lowerer/functions/interface.rs index 3c428d40d8a..46cbc5b17e6 100644 --- a/prusti-viper/src/encoder/middle/core_proof/lowerer/functions/interface.rs +++ b/prusti-viper/src/encoder/middle/core_proof/lowerer/functions/interface.rs @@ -5,14 +5,19 @@ use crate::encoder::{ function_gas::FunctionGasInterface, lowerer::{DomainsLowererInterface, Lowerer}, snapshots::{ - IntoPureBoolExpression, IntoPureSnapshot, IntoSnapshot, SnapshotValidityInterface, + FramedExpressionToSnapshot, IntoPureBoolExpression, IntoPureSnapshot, IntoSnapshot, + IntoSnapshotLowerer, SnapshotValidityInterface, }, types::TypesInterface, }, }; +use prusti_common::config; use std::collections::BTreeMap; use vir_crate::{ - common::expression::{ExpressionIterator, QuantifierHelpers}, + common::{ + expression::{ExpressionIterator, QuantifierHelpers}, + identifier::WithIdentifier, + }, low::{self as vir_low}, middle as vir_mid, }; @@ -28,16 +33,7 @@ impl FunctionsLowererState { } } -trait Private { - fn caller_function_name(&mut self, function_name: &str) -> String; - fn ensure_pure_function_lowered(&mut self, function_name: String) -> SpannedEncodingResult<()>; - fn ensure_all_types_lowered( - &mut self, - function_decl: &vir_mid::FunctionDecl, - ) -> SpannedEncodingResult<()>; -} - -impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { +impl<'p, 'v: 'p, 'tcx: 'v> Lowerer<'p, 'v, 'tcx> { fn caller_function_name(&mut self, function_name: &str) -> String { format!("caller_for${function_name}") } @@ -65,7 +61,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { posts.push(result_validity); let gas = self.function_gas_parameter()?; let gas_expression = gas.clone().into(); - let gas_amount = self.function_gas_constant(2)?; + let gas_amount = self.function_gas_constant(config::function_gas_amount())?; let caller_for_pres: Vec<_> = pres .clone() .into_iter() @@ -124,7 +120,19 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { return_type, ); let body = if let Some(body) = function_decl.body { - expr! { ([call.clone()] == [body.to_pure_snapshot(self)?]) } + // eprintln!("body: {body}"); + let framing_variables = &function_decl.parameters; + // for variable in framing_variables { + // eprintln!("variable: {variable}"); + // } + // let deref_fields = self.framing_variable_deref_fields(framing_variables)?; + // for (e, name, ty) in &deref_fields { + // eprintln!("field: {} {} {}", e, name, ty); + // } + let mut body_encoder = + FramedExpressionToSnapshot::for_function_body(framing_variables); + let encoded_body = body_encoder.expression_to_snapshot(self, &body, false)?; + expr! { ([call.clone()] == [encoded_body]) } } else { true.into() }; @@ -174,6 +182,11 @@ pub(in super::super::super) trait FunctionsLowererInterface { position: vir_low::Position, ) -> SpannedEncodingResult; fn declare_function(&mut self, function: vir_low::FunctionDecl) -> SpannedEncodingResult<()>; + fn construct_function_name( + &mut self, + function_name_prefix: &str, + ty: &vir_mid::Type, + ) -> SpannedEncodingResult; } impl<'p, 'v: 'p, 'tcx: 'v> FunctionsLowererInterface for Lowerer<'p, 'v, 'tcx> { @@ -194,6 +207,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> FunctionsLowererInterface for Lowerer<'p, 'v, 'tcx> { self.ensure_pure_function_lowered(function_name)?; Ok(vir_low::expression::FuncApp { function_name: caller_function_name, + context: vir_low::FuncAppContext::Default, arguments, parameters, return_type, @@ -234,4 +248,11 @@ impl<'p, 'v: 'p, 'tcx: 'v> FunctionsLowererInterface for Lowerer<'p, 'v, 'tcx> { .insert(function.name.clone(), function); Ok(()) } + fn construct_function_name( + &mut self, + function_name_prefix: &str, + ty: &vir_mid::Type, + ) -> SpannedEncodingResult { + Ok(format!("{}${}", function_name_prefix, ty.get_identifier())) + } } diff --git a/prusti-viper/src/encoder/middle/core_proof/lowerer/mod.rs b/prusti-viper/src/encoder/middle/core_proof/lowerer/mod.rs index 341045a7d4f..8d2eaca3ac0 100644 --- a/prusti-viper/src/encoder/middle/core_proof/lowerer/mod.rs +++ b/prusti-viper/src/encoder/middle/core_proof/lowerer/mod.rs @@ -3,25 +3,45 @@ use self::{ predicates::PredicatesLowererState, variables::VariablesLowererState, }; use super::{ + addresses::AddressState, adts::AdtsState, + arithmetic_wrappers::ArithmeticWrappersState, + block_markers::BlockMarkersInterface, builtin_methods::BuiltinMethodsState, + casts::CastsState, compute_address::ComputeAddressState, + heap::HeapState, into_low::IntoLow, + labels::LabelsState, lifetimes::LifetimesState, + permissions::PermissionsState, places::PlacesState, - predicates::{PredicatesMemoryBlockInterface, PredicatesOwnedInterface, PredicatesState}, - snapshots::{SnapshotVariablesInterface, SnapshotsState}, + predicates::{PredicatesOwnedInterface, PredicatesState}, + snapshots::{SnapshotDomainsInfo, SnapshotVariablesInterface, SnapshotsState}, + triggers::TriggersState, + type_layouts::TypeLayoutsState, types::TypesState, + viewshifts::ViewShiftsState, }; use crate::encoder::{ - errors::SpannedEncodingResult, middle::core_proof::builtin_methods::BuiltinMethodsInterface, + errors::{ErrorCtxt, SpannedEncodingResult}, + middle::core_proof::{ + builtin_methods::BuiltinMethodsInterface, + function_gas::FunctionGasInterface, + predicates::{PredicateInfo, PredicatesAliasingInterface}, + snapshots::IntoSnapshot, + }, + mir::errors::ErrorInterface, Encoder, }; +use log::info; use prusti_rustc_interface::hir::def_id::DefId; -use rustc_hash::{FxHashMap, FxHashSet}; +use rustc_hash::FxHashMap; use std::collections::BTreeMap; use vir_crate::{ - common::{cfg::Cfg, check_mode::CheckMode, graphviz::ToGraphviz}, + common::{ + cfg::Cfg, check_mode::CheckMode, expression::UnaryOperationHelpers, graphviz::ToGraphviz, + }, low::{self as vir_low, operations::ty::Typed}, middle as vir_mid, }; @@ -33,8 +53,10 @@ mod predicates; mod variables; pub(super) use self::{ - domains::DomainsLowererInterface, functions::FunctionsLowererInterface, - methods::MethodsLowererInterface, predicates::PredicatesLowererInterface, + domains::{DomainsInfo, DomainsLowererInterface}, + functions::FunctionsLowererInterface, + methods::MethodsLowererInterface, + predicates::PredicatesLowererInterface, variables::VariablesLowererInterface, }; @@ -44,6 +66,10 @@ pub(super) struct LoweringResult { pub(super) functions: Vec, pub(super) predicates: Vec, pub(super) methods: Vec, + pub(super) snapshot_domains_info: SnapshotDomainsInfo, + pub(super) domains_info: DomainsInfo, + pub(super) predicates_info: PredicateInfo, + pub(super) extensionality_gas_constant: vir_low::Expression, } pub(super) fn lower_procedure<'p, 'v: 'p, 'tcx: 'v>( @@ -51,6 +77,7 @@ pub(super) fn lower_procedure<'p, 'v: 'p, 'tcx: 'v>( def_id: DefId, procedure: vir_mid::ProcedureDecl, ) -> SpannedEncodingResult { + info!("Lowering procedure {} ({def_id:?})", procedure.name); let lowerer = self::Lowerer::new(encoder); let mut result = lowerer.lower_procedure(def_id, procedure)?; if let Some(path) = prusti_common::config::execute_only_failing_trace() { @@ -88,6 +115,7 @@ pub(super) fn lower_type<'p, 'v: 'p, 'tcx: 'v>( pub(super) struct Lowerer<'p, 'v: 'p, 'tcx: 'v> { pub(super) encoder: &'p mut Encoder<'v, 'tcx>, pub(super) def_id: Option, + pub(super) procedure_position: Option, pub(super) check_mode: Option, variables_state: VariablesLowererState, functions_state: FunctionsLowererState, @@ -102,6 +130,15 @@ pub(super) struct Lowerer<'p, 'v: 'p, 'tcx: 'v> { pub(super) adts_state: AdtsState, pub(super) lifetimes_state: LifetimesState, pub(super) places_state: PlacesState, + pub(super) heap_state: HeapState, + pub(super) address_state: AddressState, + pub(super) labels_state: LabelsState, + pub(super) view_shifts_state: ViewShiftsState, + pub(super) arithmetic_wrapper_state: ArithmeticWrappersState, + pub(super) casts_state: CastsState, + pub(super) triggers_state: TriggersState, + pub(super) permissions_state: PermissionsState, + pub(super) type_layouts_state: TypeLayoutsState, } impl<'p, 'v: 'p, 'tcx: 'v> Lowerer<'p, 'v, 'tcx> { @@ -109,6 +146,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> Lowerer<'p, 'v, 'tcx> { Self { encoder, def_id: None, + procedure_position: None, check_mode: None, variables_state: Default::default(), functions_state: Default::default(), @@ -123,6 +161,15 @@ impl<'p, 'v: 'p, 'tcx: 'v> Lowerer<'p, 'v, 'tcx> { adts_state: Default::default(), lifetimes_state: Default::default(), places_state: Default::default(), + heap_state: Default::default(), + address_state: Default::default(), + labels_state: Default::default(), + view_shifts_state: Default::default(), + arithmetic_wrapper_state: Default::default(), + casts_state: Default::default(), + triggers_state: Default::default(), + permissions_state: Default::default(), + type_layouts_state: Default::default(), } } @@ -131,7 +178,13 @@ impl<'p, 'v: 'p, 'tcx: 'v> Lowerer<'p, 'v, 'tcx> { def_id: DefId, mut procedure: vir_mid::ProcedureDecl, ) -> SpannedEncodingResult { + assert!( + !procedure.position.is_default(), + "procedure {def_id:?} without position" + ); self.def_id = Some(def_id); + self.procedure_position = Some(procedure.position); + self.set_non_aliased_places(std::mem::take(&mut procedure.non_aliased_places))?; let mut basic_blocks_map = BTreeMap::new(); let mut basic_block_edges = BTreeMap::new(); let predecessors = procedure.predecessors_owned(); @@ -142,22 +195,30 @@ impl<'p, 'v: 'p, 'tcx: 'v> Lowerer<'p, 'v, 'tcx> { self.set_current_block_for_snapshots(label, &predecessors, &mut basic_block_edges)?; let basic_block = procedure.basic_blocks.remove(label).unwrap(); let marker = self.create_block_marker(label)?; - marker_initialisation.push(vir_low::Statement::assign_no_pos( - marker.clone(), - false.into(), + marker_initialisation.push(vir_low::Statement::assume( + vir_low::Expression::not(self.initial_snapshot_variable_version(&marker)?.into()), + procedure.position, )); let mut statements = vec![ - vir_low::Statement::assign_no_pos(marker.clone(), true.into()), + // vir_low::Statement::assign(marker.clone(), true.into(), procedure.position), + vir_low::Statement::assume( + self.new_snapshot_variable_version(&marker, procedure.position)? + .into(), + procedure.position, + ), // We need to use a function call here because Silicon optimizes // out assignments to pure variables and our Z3 wrapper does not // see them. - vir_low::Statement::log_event(self.create_domain_func_app( - "MarkerCalls", - format!("basic_block_marker${}", marker.name), - vec![], - vir_low::Type::Bool, - Default::default(), - )?), + vir_low::Statement::log_event( + self.create_domain_func_app( + "MarkerCalls", + format!("basic_block_marker${}", marker.name), + vec![], + vir_low::Type::Bool, + procedure.position, + )?, + procedure.position, + ), ]; for statement in basic_block.statements { statements.extend(statement.into_low(&mut self)?); @@ -170,67 +231,128 @@ impl<'p, 'v: 'p, 'tcx: 'v> Lowerer<'p, 'v, 'tcx> { std::mem::swap(entry_block_statements, &mut marker_initialisation); entry_block_statements.extend(marker_initialisation); - let mut basic_blocks = Vec::new(); + let mut basic_blocks = BTreeMap::new(); for basic_block_id in traversal_order { let (statements, mut successor) = basic_blocks_map.remove(&basic_block_id).unwrap(); let label = basic_block_id.clone().into_low(&mut self)?; if let Some(intermediate_blocks) = basic_block_edges.remove(&basic_block_id) { - for (successor_label, successor_statements) in intermediate_blocks { + for (successor_label, equalities) in intermediate_blocks { let successor_label = successor_label.into_low(&mut self)?; let intermediate_block_label = vir_low::Label::new(format!( "label__from__{}__to__{}", label.name, successor_label.name )); successor.replace_label(&successor_label, intermediate_block_label.clone()); - basic_blocks.push(vir_low::BasicBlock { - label: intermediate_block_label, - statements: successor_statements, - successor: vir_low::Successor::Goto(successor_label), - }); + let mut successor_statements = Vec::new(); + for (variable_name, ty, position, old_version, new_version) in equalities { + let new_variable = self.create_snapshot_variable_low( + &variable_name, + ty.clone(), + new_version, + )?; + let old_variable = self.create_snapshot_variable_low( + &variable_name, + ty.clone(), + old_version, + )?; + let position = self.encoder.change_error_context( + // FIXME: Get a more precise span. + position, + ErrorCtxt::Unexpected, + ); + let statement = vir_low::macros::stmtp! { + position => assume (new_variable == old_variable) + }; + successor_statements.push(statement); + } + basic_blocks.insert( + intermediate_block_label, + vir_low::BasicBlock { + statements: successor_statements, + successor: vir_low::Successor::Goto(successor_label), + }, + ); } } - basic_blocks.push(vir_low::BasicBlock { + basic_blocks.insert( label, - statements, - successor, - }); - } - let mut removed_functions = FxHashSet::default(); - if procedure.check_mode == CheckMode::Specifications { - removed_functions.insert(self.encode_memory_block_bytes_function_name()?); + vir_low::BasicBlock { + statements, + successor, + }, + ); } - let mut predicates = self.collect_owned_predicate_decls()?; - basic_blocks[0].statements.splice( + let entry = procedure.entry.clone().into_low(&mut self)?; + let exit = procedure.exit.clone().into_low(&mut self)?; + // let mut removed_functions = FxHashSet::default(); + // if procedure.check_mode == CheckMode::PurificationFunctional { + // removed_functions.insert(self.encode_memory_block_bytes_function_name()?); + // } + let bool_type = (vir_mid::Type::Bool).to_snapshot(&mut self)?; + let extensionality_gas_constant = self.function_gas_constant(3)?; + let (mut predicates, owned_predicates_info) = self.collect_owned_predicate_decls()?; + let predicates_info = PredicateInfo { + non_aliased_memory_block_addresses: self.take_non_aliased_memory_block_addresses()?, + owned_predicates_info, + }; + basic_blocks.get_mut(&entry).unwrap().statements.splice( 0..0, self.lifetimes_state.lifetime_is_alive_initialization(), ); - let mut domains = self.domains_state.destruct(); + if prusti_common::config::dump_debug_info() { + let source_filename = self.encoder.env().name.source_file_name(); + prusti_common::report::log::report_with_writer( + "graphviz_method_vir_low_before_perm_desugaring", + format!("{}.{}.dot", source_filename, procedure.name), + |writer| basic_blocks.to_graphviz(writer).unwrap(), + ); + } + let mut snapshot_domains_info = self.snapshots_state.destruct(); + snapshot_domains_info.bool_type = Some(bool_type); + let (mut domains, domains_info) = self.domains_state.destruct(); domains.extend(self.compute_address_state.destruct()); predicates.extend(self.predicates_state.destruct()); - let mut lowered_procedure = vir_low::ProcedureDecl { + let lowered_procedure = vir_low::ProcedureDecl { name: procedure.name, + position: procedure.position, locals: self.variables_state.destruct(), + custom_labels: self.labels_state.destruct(), basic_blocks, + entry, + exit, }; - let mut methods = self.methods_state.destruct(); - let mut functions = self.functions_state.destruct(); - if procedure.check_mode == CheckMode::Specifications { - super::transformations::remove_predicates::remove_predicates( - &mut lowered_procedure, - &mut methods, - &removed_functions, - std::mem::take(&mut predicates), - ); - functions.retain(|function| !removed_functions.contains(&function.name)); - }; - Ok(LoweringResult { + let methods = self.methods_state.destruct(); + let functions = self.functions_state.destruct(); + // if procedure.check_mode == CheckMode::PurificationFunctional { + // removed_functions.extend( + // functions + // .iter() + // .filter(|function| function.kind == vir_low::FunctionKind::Snap) + // .map(|function| function.name.clone()), + // ); + // super::transformations::remove_predicates::remove_predicates( + // &mut lowered_procedure, + // &mut methods, + // &removed_functions, + // std::mem::take(&mut predicates), + // ); + // functions.retain(|function| !removed_functions.contains(&function.name)); + // }; + let result = LoweringResult { procedures: vec![lowered_procedure], domains, functions, predicates, methods, - }) + domains_info, + snapshot_domains_info, + predicates_info, + extensionality_gas_constant, + }; + self.def_id = None; + self.procedure_position = None; + Ok(result) } fn create_parameters(&self, arguments: &[vir_low::Expression]) -> Vec { @@ -243,12 +365,12 @@ impl<'p, 'v: 'p, 'tcx: 'v> Lowerer<'p, 'v, 'tcx> { .collect() } - fn create_block_marker( - &mut self, - label: &vir_mid::BasicBlockId, - ) -> SpannedEncodingResult { - self.create_variable(format!("{label}$marker"), vir_low::Type::Bool) - } + // fn create_block_marker( + // &mut self, + // label: &vir_mid::BasicBlockId, + // ) -> SpannedEncodingResult { + // self.create_variable(format!("{label}$marker"), vir_low::Type::Bool) + // } /// If `check_copy` is true, encode `copy` builtin method. fn lower_type( @@ -258,21 +380,31 @@ impl<'p, 'v: 'p, 'tcx: 'v> Lowerer<'p, 'v, 'tcx> { check_copy: bool, ) -> SpannedEncodingResult { self.def_id = def_id; - self.mark_owned_non_aliased_as_unfolded(&ty)?; + self.mark_owned_predicate_as_unfolded(&ty)?; self.encode_move_place_method(&ty)?; if check_copy { self.encode_copy_place_method(&ty)?; } - let mut predicates = self.collect_owned_predicate_decls()?; - let mut domains = self.domains_state.destruct(); + let extensionality_gas_constant = self.function_gas_constant(3)?; + let (mut predicates, owned_predicates_info) = self.collect_owned_predicate_decls()?; + let snapshot_domains_info = self.snapshots_state.destruct(); + let (mut domains, domains_info) = self.domains_state.destruct(); domains.extend(self.compute_address_state.destruct()); predicates.extend(self.predicates_state.destruct()); + let predicates_info = PredicateInfo { + owned_predicates_info, + non_aliased_memory_block_addresses: Default::default(), + }; Ok(LoweringResult { procedures: Vec::new(), domains, functions: self.functions_state.destruct(), predicates, methods: self.methods_state.destruct(), + predicates_info, + snapshot_domains_info, + domains_info, + extensionality_gas_constant, }) } } diff --git a/prusti-viper/src/encoder/middle/core_proof/lowerer/variables/interface.rs b/prusti-viper/src/encoder/middle/core_proof/lowerer/variables/interface.rs index 12374a09824..5b594acd781 100644 --- a/prusti-viper/src/encoder/middle/core_proof/lowerer/variables/interface.rs +++ b/prusti-viper/src/encoder/middle/core_proof/lowerer/variables/interface.rs @@ -24,6 +24,7 @@ pub(in super::super::super) trait VariablesLowererInterface { name: String, ty: vir_low::Type, ) -> SpannedEncodingResult; + fn register_variable(&mut self, variable: &vir_low::VariableDecl) -> SpannedEncodingResult<()>; fn create_new_temporary_variable( &mut self, ty: vir_low::Type, @@ -43,6 +44,14 @@ impl<'p, 'v: 'p, 'tcx: 'v> VariablesLowererInterface for Lowerer<'p, 'v, 'tcx> { } Ok(vir_low::VariableDecl::new(name, ty)) } + fn register_variable(&mut self, variable: &vir_low::VariableDecl) -> SpannedEncodingResult<()> { + if !self.variables_state.variables.contains_key(&variable.name) { + self.variables_state + .variables + .insert(variable.name.clone(), variable.ty.clone()); + } + Ok(()) + } fn create_new_temporary_variable( &mut self, ty: vir_low::Type, diff --git a/prusti-viper/src/encoder/middle/core_proof/mod.rs b/prusti-viper/src/encoder/middle/core_proof/mod.rs index 2a36c53a2d3..94bd9026bc7 100644 --- a/prusti-viper/src/encoder/middle/core_proof/mod.rs +++ b/prusti-viper/src/encoder/middle/core_proof/mod.rs @@ -5,12 +5,14 @@ mod builtin_methods; mod compute_address; mod const_generics; mod errors; +mod footprint; mod function_gas; mod interface; mod into_low; mod lifetimes; mod lowerer; mod places; +mod pointers; mod predicates; mod references; mod snapshots; @@ -18,5 +20,13 @@ mod transformations; mod type_layouts; mod types; mod utils; +mod heap; +mod labels; +mod viewshifts; +mod arithmetic_wrappers; +mod casts; +mod triggers; +mod permissions; +mod svirpti; pub(crate) use self::interface::{MidCoreProofEncoderInterface, MidCoreProofEncoderState}; diff --git a/prusti-viper/src/encoder/middle/core_proof/permissions/interface.rs b/prusti-viper/src/encoder/middle/core_proof/permissions/interface.rs new file mode 100644 index 00000000000..efcbd1a6ecc --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/permissions/interface.rs @@ -0,0 +1,40 @@ +use crate::encoder::{errors::SpannedEncodingResult, middle::core_proof::lowerer::Lowerer}; +use vir_crate::low::{self as vir_low}; + +const DOMAIN_NAME: &str = "WildcardPermission"; +const FUNCTION_NAME: &str = "wildcard_permission"; + +pub(in super::super) trait PermissionsInterface { + fn wildcard_permission(&mut self) -> SpannedEncodingResult; +} + +impl<'p, 'v: 'p, 'tcx: 'v> PermissionsInterface for Lowerer<'p, 'v, 'tcx> { + fn wildcard_permission(&mut self) -> SpannedEncodingResult { + // if !self.permissions_state.is_wildcard_function_encoded { + // self.permissions_state.is_wildcard_function_encoded = true; + // use vir_low::macros::*; + // let call = self.create_domain_func_app( + // DOMAIN_NAME, + // FUNCTION_NAME, + // vec![], + // vir_low::Type::Perm, + // Default::default(), + // )?; + // let body = expr! { + // ([vir_low::Expression::no_permission()] < [call.clone()]) && + // ([call] < [vir_low::Expression::full_permission()]) + // }; + // let axiom = + // vir_low::DomainAxiomDecl::new(None, format!("{}$definition", FUNCTION_NAME), body); + // self.declare_axiom(DOMAIN_NAME, axiom)?; + // } + // self.create_domain_func_app( + // DOMAIN_NAME, + // FUNCTION_NAME, + // vec![], + // vir_low::Type::Perm, + // Default::default(), + // ) + Ok(vir_low::Expression::wildcard_permission()) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/permissions/mod.rs b/prusti-viper/src/encoder/middle/core_proof/permissions/mod.rs new file mode 100644 index 00000000000..1f1b63a72cf --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/permissions/mod.rs @@ -0,0 +1,4 @@ +mod interface; +mod state; + +pub(super) use self::{interface::PermissionsInterface, state::PermissionsState}; diff --git a/prusti-viper/src/encoder/middle/core_proof/permissions/state.rs b/prusti-viper/src/encoder/middle/core_proof/permissions/state.rs new file mode 100644 index 00000000000..385af86f919 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/permissions/state.rs @@ -0,0 +1,4 @@ +#[derive(Default)] +pub(in super::super) struct PermissionsState { + pub(super) is_wildcard_function_encoded: bool, +} diff --git a/prusti-viper/src/encoder/middle/core_proof/places/encoder.rs b/prusti-viper/src/encoder/middle/core_proof/places/encoder.rs index 4204479138d..5fc9e366406 100644 --- a/prusti-viper/src/encoder/middle/core_proof/places/encoder.rs +++ b/prusti-viper/src/encoder/middle/core_proof/places/encoder.rs @@ -7,15 +7,16 @@ use crate::encoder::{ }, }; use vir_crate::{ + common::builtin_constants::{PLACE_DOMAIN_NAME, PLACE_OPTION_DOMAIN_NAME}, low as vir_low, - middle::{self as vir_mid}, + middle::{self as vir_mid, operations::ty::Typed}, }; pub(super) struct PlaceEncoder {} impl PlaceExpressionDomainEncoder for PlaceEncoder { fn domain_name(&mut self, _lowerer: &mut Lowerer) -> &str { - "Place" + PLACE_OPTION_DOMAIN_NAME } fn encode_local( @@ -26,14 +27,15 @@ impl PlaceExpressionDomainEncoder for PlaceEncoder { let function_name = format!("{}$place", local.variable.name); let return_type = lowerer.place_type()?; let place_root = lowerer.create_unique_domain_func_app( - "Place", + PLACE_DOMAIN_NAME, function_name, vec![], return_type, local.position, )?; lowerer.encode_compute_address_for_place_root(&place_root)?; - Ok(place_root) + let place_option_root = lowerer.place_option_some_constructor(place_root.clone())?; + Ok(place_option_root) } fn encode_deref( @@ -42,7 +44,13 @@ impl PlaceExpressionDomainEncoder for PlaceEncoder { lowerer: &mut Lowerer, arg: vir_low::Expression, ) -> SpannedEncodingResult { - lowerer.encode_deref_place(arg, deref.position) + if deref.base.get_type().is_reference() { + lowerer.encode_deref_place(arg, deref.position) + } else { + assert!(deref.base.get_type().is_pointer()); + lowerer.place_option_none_constructor(deref.position) + // lowerer.encode_aliased_place_root(deref.position) + } } fn encode_array_index_axioms( @@ -52,4 +60,12 @@ impl PlaceExpressionDomainEncoder for PlaceEncoder { ) -> SpannedEncodingResult<()> { lowerer.encode_place_array_index_axioms(ty) } + + fn encode_labelled_old( + &mut self, + expression: &vir_mid::expression::LabelledOld, + lowerer: &mut Lowerer, + ) -> SpannedEncodingResult { + self.encode_expression(&expression.base, lowerer) + } } diff --git a/prusti-viper/src/encoder/middle/core_proof/places/interface.rs b/prusti-viper/src/encoder/middle/core_proof/places/interface.rs index 66796753217..c26749ab88f 100644 --- a/prusti-viper/src/encoder/middle/core_proof/places/interface.rs +++ b/prusti-viper/src/encoder/middle/core_proof/places/interface.rs @@ -10,8 +10,13 @@ use crate::encoder::{ }; use prusti_rustc_interface::data_structures::fx::FxHashSet; use vir_crate::{ - common::{expression::QuantifierHelpers, identifier::WithIdentifier}, - low::{self as vir_low, macros::var_decls}, + common::{ + builtin_constants::{PLACE_DOMAIN_NAME, PLACE_OPTION_DOMAIN_NAME}, + expression::QuantifierHelpers, + identifier::WithIdentifier, + position::Positioned, + }, + low::{self as vir_low, macros::var_decls, operations::ty::Typed}, middle::{self as vir_mid}, }; @@ -19,10 +24,21 @@ use vir_crate::{ pub(in super::super) struct PlacesState { /// For which types array index axioms were generated. array_index_axioms: FxHashSet, + /// Encoded simp rules for propagating `none` place. + encoded_none_simp_rules: FxHashSet, } pub(in super::super) trait PlacesInterface { fn place_type(&mut self) -> SpannedEncodingResult; + fn place_option_type(&mut self) -> SpannedEncodingResult; + fn place_option_some_constructor( + &mut self, + place: vir_low::Expression, + ) -> SpannedEncodingResult; + fn place_option_none_constructor( + &mut self, + position: vir_mid::Position, + ) -> SpannedEncodingResult; fn encode_expression_as_place( &mut self, place: &vir_mid::Expression, @@ -54,19 +70,58 @@ pub(in super::super) trait PlacesInterface { position: vir_mid::Position, ) -> SpannedEncodingResult; fn encode_place_array_index_axioms(&mut self, ty: &vir_mid::Type) -> SpannedEncodingResult<()>; + // fn encode_aliased_place_root( + // &mut self, + // position: vir_low::Position, + // ) -> SpannedEncodingResult; } impl<'p, 'v: 'p, 'tcx: 'v> PlacesInterface for Lowerer<'p, 'v, 'tcx> { fn place_type(&mut self) -> SpannedEncodingResult { - self.domain_type("Place") + self.domain_type(PLACE_DOMAIN_NAME) + } + fn place_option_type(&mut self) -> SpannedEncodingResult { + self.domain_type(PLACE_OPTION_DOMAIN_NAME) + } + fn place_option_some_constructor( + &mut self, + place: vir_low::Expression, + ) -> SpannedEncodingResult { + debug_assert_eq!(place.get_type(), &self.place_type()?); + let place_option_type = self.place_option_type()?; + let position = place.position(); + self.create_domain_func_app( + PLACE_OPTION_DOMAIN_NAME, + "place_option_some", + vec![place], + place_option_type, + position, + ) + } + fn place_option_none_constructor( + &mut self, + position: vir_mid::Position, + ) -> SpannedEncodingResult { + let place_option_type = self.place_option_type()?; + self.create_domain_func_app( + PLACE_OPTION_DOMAIN_NAME, + "place_option_none", + Vec::new(), + place_option_type, + position, + ) } /// Emits code that represents the place. fn encode_expression_as_place( &mut self, place: &vir_mid::Expression, ) -> SpannedEncodingResult { - let mut encoder = PlaceEncoder {}; - encoder.encode_expression(place, self) + if place.is_behind_pointer_dereference() { + self.place_option_none_constructor(place.position()) + } else { + let mut encoder = PlaceEncoder {}; + encoder.encode_expression(place, self) + } } fn encode_field_place( &mut self, @@ -75,7 +130,46 @@ impl<'p, 'v: 'p, 'tcx: 'v> PlacesInterface for Lowerer<'p, 'v, 'tcx> { base_place: vir_low::Expression, position: vir_mid::Position, ) -> SpannedEncodingResult { - self.encode_field_access_function_app("Place", base_place, base_type, field, position) + debug_assert_eq!(base_place.get_type(), &self.place_option_type()?); + let rule_name = format!( + "place_option_none${}$field${}", + base_type.get_identifier(), + field.name + ); + if !self + .places_state + .encoded_none_simp_rules + .contains(&rule_name) + { + self.places_state + .encoded_none_simp_rules + .insert(rule_name.clone()); + let none_place = self.place_option_none_constructor(position)?; + let source = self.encode_field_access_function_app( + PLACE_OPTION_DOMAIN_NAME, + none_place.clone(), + base_type, + field, + position, + )?; + let axiom = vir_low::DomainRewriteRuleDecl::new( + None, + rule_name, + false, + Vec::new(), + None, + source, + none_place, + ); + self.declare_rewrite_rule(PLACE_OPTION_DOMAIN_NAME, axiom)?; + } + self.encode_field_access_function_app( + PLACE_OPTION_DOMAIN_NAME, + base_place, + base_type, + field, + position, + ) } fn encode_enum_variant_place( &mut self, @@ -84,16 +178,23 @@ impl<'p, 'v: 'p, 'tcx: 'v> PlacesInterface for Lowerer<'p, 'v, 'tcx> { base_place: vir_low::Expression, position: vir_mid::Position, ) -> SpannedEncodingResult { - self.encode_variant_access_function_app("Place", base_place, base_type, variant, position) + self.encode_variant_access_function_app( + PLACE_OPTION_DOMAIN_NAME, + base_place, + base_type, + variant, + position, + ) } fn encode_deref_place( &mut self, base_place: vir_low::Expression, position: vir_mid::Position, ) -> SpannedEncodingResult { - let return_type = self.place_type()?; + debug_assert_eq!(base_place.get_type(), &self.place_option_type()?); + let return_type = self.place_option_type()?; self.create_domain_func_app( - "Place", + PLACE_OPTION_DOMAIN_NAME, "deref_reference_place", vec![base_place], return_type, @@ -107,7 +208,13 @@ impl<'p, 'v: 'p, 'tcx: 'v> PlacesInterface for Lowerer<'p, 'v, 'tcx> { index: vir_low::Expression, position: vir_mid::Position, ) -> SpannedEncodingResult { - self.encode_index_access_function_app("Place", base_place, base_type, index, position) + self.encode_index_access_function_app( + PLACE_OPTION_DOMAIN_NAME, + base_place, + base_type, + index, + position, + ) } fn encode_place_array_index_axioms(&mut self, ty: &vir_mid::Type) -> SpannedEncodingResult<()> { let identifier = ty.get_identifier(); @@ -122,21 +229,21 @@ impl<'p, 'v: 'p, 'tcx: 'v> PlacesInterface for Lowerer<'p, 'v, 'tcx> { index: { size_type.clone() } }; let function_app = self.encode_index_access_function_app( - "Place", + PLACE_DOMAIN_NAME, place.clone().into(), ty, index.clone().into(), position, )?; let place_inverse = self.create_domain_func_app( - "Place", + PLACE_DOMAIN_NAME, format!("index_place$${}$$inv_place", ty.get_identifier()), vec![function_app.clone()], place_type, position, )?; let index_inverse = self.create_domain_func_app( - "Place", + PLACE_DOMAIN_NAME, format!("index_place$${}$$inv_index", ty.get_identifier()), vec![function_app.clone()], size_type, @@ -155,8 +262,24 @@ impl<'p, 'v: 'p, 'tcx: 'v> PlacesInterface for Lowerer<'p, 'v, 'tcx> { name: format!("index_place$${}$$injectivity_axiom", ty.get_identifier()), body, }; - self.declare_axiom("Place", axiom)?; + self.declare_axiom(PLACE_DOMAIN_NAME, axiom)?; } Ok(()) } + // fn encode_aliased_place_root( + // &mut self, + // _position: vir_low::Position, + // ) -> SpannedEncodingResult { + // unimplemented!(); + // // let return_type = self.place_type()?; + // // let place_root = self.create_domain_func_app( + // // PLACE_OPTION_DOMAIN_NAME, + // // "aliased_place_root", + // // vec![], + // // return_type, + // // position, + // // )?; + // // self.encode_compute_address_for_place_root(&place_root)?; + // // Ok(place_root) + // } } diff --git a/prusti-viper/src/encoder/middle/core_proof/pointers/interface.rs b/prusti-viper/src/encoder/middle/core_proof/pointers/interface.rs new file mode 100644 index 00000000000..b7626db8365 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/pointers/interface.rs @@ -0,0 +1,199 @@ +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::{ + addresses::AddressesInterface, + heap::HeapInterface, + lowerer::{DomainsLowererInterface, Lowerer}, + snapshots::{IntoSnapshot, SnapshotValuesInterface, SnapshotVariablesInterface}, + type_layouts::TypeLayoutsInterface, + }, +}; +use vir_crate::{ + common::identifier::WithIdentifier, + low as vir_low, + middle::{self as vir_mid}, +}; + +pub(in super::super) trait PointersInterface { + fn pointer_address( + &mut self, + pointer_type: &vir_mid::Type, + snapshot: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult; + fn address_to_pointer( + &mut self, + pointer_type: &vir_mid::Type, + address: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult; + fn pointer_slice_len( + &mut self, + pointer_type: &vir_mid::Type, + snapshot: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult; + fn pointer_target_snapshot_in_heap( + &mut self, + ty: &vir_mid::Type, + heap: vir_low::VariableDecl, + snapshot: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult; + fn pointer_target_snapshot( + &mut self, + ty: &vir_mid::Type, + old_label: &Option, + snapshot: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult; + fn pointer_target_as_snapshot_field( + &mut self, + framing_type: &vir_mid::Type, + deref_field_name: &str, + deref_type: vir_low::Type, + framing_place_snapshot: vir_low::Expression, + position: vir_mid::Position, + ) -> SpannedEncodingResult; + fn heap_chunk_to_snapshot( + &mut self, + ty: &vir_mid::Type, + heap_chunk: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult; + fn address_in_heap( + &mut self, + heap: vir_low::VariableDecl, + pointer_place: &vir_mid::Expression, + ) -> SpannedEncodingResult; +} + +impl<'p, 'v: 'p, 'tcx: 'v> PointersInterface for Lowerer<'p, 'v, 'tcx> { + fn pointer_address( + &mut self, + pointer_type: &vir_mid::Type, + snapshot: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult { + assert!(pointer_type.is_pointer()); + // self.obtain_constant_value(pointer_type, snapshot, position) + let address_type = self.address_type()?; + self.obtain_parameter_snapshot(pointer_type, "address", address_type, snapshot, position) + } + fn address_to_pointer( + &mut self, + pointer_type: &vir_mid::Type, + address: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult { + assert!(pointer_type.is_pointer()); + self.construct_struct_snapshot(pointer_type, vec![address], position) + } + fn pointer_slice_len( + &mut self, + pointer_type: &vir_mid::Type, + snapshot: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult { + assert!(pointer_type.is_pointer_to_slice()); + let len_type = self.size_type()?; + self.obtain_parameter_snapshot(pointer_type, "len", len_type, snapshot, position) + } + fn pointer_target_snapshot_in_heap( + &mut self, + ty: &vir_mid::Type, + heap: vir_low::VariableDecl, + snapshot: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult { + let address = self.pointer_address(ty, snapshot, position)?; + let heap_chunk = self.heap_lookup(heap.into(), address, position)?; + // let heap_chunk = vir_low::Expression::container_op_no_pos( + // vir_low::ContainerOpKind::MapLookup, + // heap.ty.clone(), + // vec![heap.into(), address], + // ); + let pointer_type = ty.clone().unwrap_pointer(); + self.heap_chunk_to_snapshot(&pointer_type.target_type, heap_chunk, position) + } + fn pointer_target_snapshot( + &mut self, + ty: &vir_mid::Type, + old_label: &Option, + snapshot: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult { + if self.use_heap_variable()? { + // let address = self.pointer_address(ty, snapshot, position)?; + let heap = self.heap_variable_version_at_label(old_label)?; + // let heap_chunk = vir_low::Expression::container_op_no_pos( + // vir_low::ContainerOpKind::MapLookup, + // heap.ty.clone(), + // vec![heap.into(), address], + // ); + // let pointer_type = ty.clone().unwrap_pointer(); + // self.heap_chunk_to_snapshot(&pointer_type.target_type, heap_chunk, position) + self.pointer_target_snapshot_in_heap(ty, heap, snapshot, position) + } else { + unimplemented!(); + // let address = self.pointer_address(ty, snapshot, position)?; + // let pointer_type = ty.clone().unwrap_pointer(); + // let target_type = &*pointer_type.target_type; + // self.owned_aliased_snap( + // CallContext::Procedure, + // target_type, + // target_type, + // address, + // position, + // ) + } + } + fn pointer_target_as_snapshot_field( + &mut self, + framing_type: &vir_mid::Type, + deref_field_name: &str, + deref_type: vir_low::Type, + framing_place_snapshot: vir_low::Expression, + position: vir_mid::Position, + ) -> SpannedEncodingResult { + self.obtain_parameter_snapshot( + framing_type, + deref_field_name, + deref_type, + framing_place_snapshot, + position, + ) + } + fn heap_chunk_to_snapshot( + &mut self, + ty: &vir_mid::Type, + heap_chunk: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult { + let return_type = ty.to_snapshot(self)?; + self.create_domain_func_app( + // FIXME: Use HEAP_CHUNK_TYPE_NAME here. + "HeapChunk$", + format!("heap_chunk_to${}", ty.get_identifier()), + vec![heap_chunk], + return_type, + position, + ) + } + fn address_in_heap( + &mut self, + _heap: vir_low::VariableDecl, + _pointer_place: &vir_mid::Expression, + ) -> SpannedEncodingResult { + todo!("Delete"); + // let pointer = pointer_place.to_pure_snapshot(self)?; + // let address = + // self.pointer_address(pointer_place.get_type(), pointer, pointer_place.position())?; + // let in_heap = vir_low::Expression::container_op_no_pos( + // vir_low::ContainerOpKind::MapContains, + // heap.ty.clone(), + // vec![heap.into(), address], + // ); + // Ok(in_heap) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/pointers/mod.rs b/prusti-viper/src/encoder/middle/core_proof/pointers/mod.rs new file mode 100644 index 00000000000..0e0b37ac78b --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/pointers/mod.rs @@ -0,0 +1,3 @@ +mod interface; + +pub(super) use self::interface::PointersInterface; diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/aliasing/interface.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/aliasing/interface.rs new file mode 100644 index 00000000000..578eef9671a --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/aliasing/interface.rs @@ -0,0 +1,75 @@ +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::{addresses::AddressesInterface, lowerer::Lowerer}, +}; +use rustc_hash::FxHashSet; +use vir_crate::{low as vir_low, middle as vir_mid}; + +#[derive(Default)] +pub(in super::super) struct PredicatesAliasingState { + non_aliased_places: Vec, + non_aliased_memory_block_addresses: FxHashSet, +} + +pub(in super::super::super) trait PredicatesAliasingInterface { + fn set_non_aliased_places( + &mut self, + places: Vec, + ) -> SpannedEncodingResult<()>; + fn mark_place_as_used_in_memory_block( + &mut self, + place: &vir_mid::Expression, + ) -> SpannedEncodingResult<()>; + fn take_non_aliased_memory_block_addresses( + &mut self, + ) -> SpannedEncodingResult>; +} + +impl<'p, 'v: 'p, 'tcx: 'v> PredicatesAliasingInterface for Lowerer<'p, 'v, 'tcx> { + fn set_non_aliased_places( + &mut self, + places: Vec, + ) -> SpannedEncodingResult<()> { + assert!( + self.predicates_encoding_state + .aliasing + .non_aliased_places + .is_empty(), + "Predicates aliasing state is already initialized." + ); + self.predicates_encoding_state.aliasing.non_aliased_places = places; + Ok(()) + } + + fn mark_place_as_used_in_memory_block( + &mut self, + place: &vir_mid::Expression, + ) -> SpannedEncodingResult<()> { + for non_aliased_place in &self.predicates_encoding_state.aliasing.non_aliased_places { + if place.has_prefix(non_aliased_place) { + let address = self.encode_expression_as_place_address(place)?; + self.predicates_encoding_state + .aliasing + .non_aliased_memory_block_addresses + .insert(address); + return Ok(()); + } + } + Ok(()) + } + + fn take_non_aliased_memory_block_addresses( + &mut self, + ) -> SpannedEncodingResult> { + self.predicates_encoding_state + .aliasing + .non_aliased_places + .clear(); + Ok(std::mem::take( + &mut self + .predicates_encoding_state + .aliasing + .non_aliased_memory_block_addresses, + )) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/aliasing/mod.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/aliasing/mod.rs new file mode 100644 index 00000000000..5fa538f1fc7 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/aliasing/mod.rs @@ -0,0 +1,4 @@ +mod interface; + +pub(in super::super) use self::interface::PredicatesAliasingInterface; +pub(super) use self::interface::PredicatesAliasingState; diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/memory_block/interface.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/memory_block/interface.rs index 8bbf051fcdb..84a4bb864f3 100644 --- a/prusti-viper/src/encoder/middle/core_proof/predicates/memory_block/interface.rs +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/memory_block/interface.rs @@ -5,11 +5,21 @@ use crate::encoder::{ lowerer::{ DomainsLowererInterface, FunctionsLowererInterface, Lowerer, PredicatesLowererInterface, }, + permissions::PermissionsInterface, + pointers::PointersInterface, + snapshots::SnapshotValuesInterface, type_layouts::TypeLayoutsInterface, }, }; -use rustc_hash::FxHashSet; -use vir_crate::low as vir_low; +use rustc_hash::{FxHashMap, FxHashSet}; +use vir_crate::{ + common::{ + builtin_constants::{BYTES_DOMAIN_NAME, BYTE_DOMAIN_NAME, MEMORY_BLOCK_PREDICATE_NAME}, + expression::{BinaryOperationHelpers, QuantifierHelpers}, + }, + low::{self as vir_low, operations::ty::Typed}, + middle as vir_mid, +}; #[derive(Default)] pub(in super::super) struct PredicatesMemoryBlockState { @@ -17,24 +27,11 @@ pub(in super::super) struct PredicatesMemoryBlockState { is_memory_block_bytes_encoded: bool, } -trait Private { - fn encode_generic_memory_block_predicate( - &mut self, - predicate_name: &str, - ) -> SpannedEncodingResult<()>; - fn encode_generic_memory_block_acc( - &mut self, - predicate_name: &str, - place: vir_low::Expression, - size: vir_low::Expression, - position: vir_low::Position, - ) -> SpannedEncodingResult; -} - -impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { +impl<'p, 'v: 'p, 'tcx: 'v> Lowerer<'p, 'v, 'tcx> { fn encode_generic_memory_block_predicate( &mut self, predicate_name: &str, + predicate_kind: vir_low::PredicateKind, ) -> SpannedEncodingResult<()> { if !self .predicates_encoding_state @@ -48,6 +45,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { .insert(predicate_name.to_string()); let predicate = vir_low::PredicateDecl::new( predicate_name, + predicate_kind, vec![ vir_low::VariableDecl::new("address", self.address_type()?), vir_low::VariableDecl::new("size", self.size_type()?), @@ -61,11 +59,12 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { fn encode_generic_memory_block_acc( &mut self, predicate_name: &str, + predicate_kind: vir_low::PredicateKind, place: vir_low::Expression, size: vir_low::Expression, position: vir_low::Position, ) -> SpannedEncodingResult { - self.encode_generic_memory_block_predicate(predicate_name)?; + self.encode_generic_memory_block_predicate(predicate_name, predicate_kind)?; let expression = vir_low::Expression::predicate_access_predicate( predicate_name.to_string(), vec![place, size], @@ -78,13 +77,71 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { pub(in super::super::super) trait PredicatesMemoryBlockInterface { fn bytes_type(&mut self) -> SpannedEncodingResult; + fn byte_type(&mut self) -> SpannedEncodingResult; + fn encode_read_byte_expression_usize( + &mut self, + bytes: vir_low::Expression, + index: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult; + fn encode_read_byte_expression_int( + &mut self, + bytes: vir_low::Expression, + index: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult; fn encode_memory_block_predicate(&mut self) -> SpannedEncodingResult<()>; + fn encode_memory_block_acc( + &mut self, + place: vir_low::Expression, + size: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult; + fn encode_memory_block_range_acc_int_index( + &mut self, + initial_address: vir_low::Expression, + size: vir_low::Expression, + start_index: vir_low::Expression, + end_index: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult; + fn encode_memory_block_range_acc( + &mut self, + address: vir_low::Expression, + size: vir_low::Expression, + start_index: vir_low::Expression, + end_index: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult; + /// Parameters match owned_aliased_range. + fn memory_block_range( + &mut self, + ty: &vir_mid::Type, + address: vir_low::Expression, + start_index: vir_low::Expression, + end_index: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult; + fn encode_memory_block_range_guarded_acc( + &mut self, + address: vir_low::Expression, + size: vir_low::Expression, + index_variable: vir_low::VariableDecl, + guard: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult; fn encode_memory_block_stack_drop_acc( &mut self, place: vir_low::Expression, size: vir_low::Expression, position: vir_low::Position, ) -> SpannedEncodingResult; + fn encode_memory_block_heap_drop_acc( + &mut self, + place: vir_low::Expression, + size: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult; fn encode_memory_block_bytes_function_name(&mut self) -> SpannedEncodingResult; fn encode_memory_block_bytes_expression( &mut self, @@ -95,10 +152,205 @@ pub(in super::super::super) trait PredicatesMemoryBlockInterface { impl<'p, 'v: 'p, 'tcx: 'v> PredicatesMemoryBlockInterface for Lowerer<'p, 'v, 'tcx> { fn bytes_type(&mut self) -> SpannedEncodingResult { - self.domain_type("Bytes") + self.domain_type(BYTES_DOMAIN_NAME) + } + fn byte_type(&mut self) -> SpannedEncodingResult { + self.domain_type(BYTE_DOMAIN_NAME) + } + fn encode_read_byte_expression_usize( + &mut self, + bytes: vir_low::Expression, + index_usize: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult { + debug_assert_eq!(index_usize.get_type(), &self.size_type()?); + let byte_type = self.byte_type()?; + self.create_domain_func_app( + BYTE_DOMAIN_NAME, + "Byte$read_byte", + vec![bytes, index_usize], + byte_type, + position, + ) + } + fn encode_read_byte_expression_int( + &mut self, + bytes: vir_low::Expression, + index: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult { + assert_eq!(index.get_type(), &vir_low::Type::Int); + let size_type = self.size_type_mid()?; + let index_usize = self.construct_constant_snapshot(&size_type, index, position)?; + self.encode_read_byte_expression_usize(bytes, index_usize, position) } fn encode_memory_block_predicate(&mut self) -> SpannedEncodingResult<()> { - self.encode_generic_memory_block_predicate("MemoryBlock") + self.encode_generic_memory_block_predicate( + MEMORY_BLOCK_PREDICATE_NAME, + vir_low::PredicateKind::MemoryBlock, + ) + } + fn encode_memory_block_acc( + &mut self, + place: vir_low::Expression, + size: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult { + self.encode_generic_memory_block_acc( + MEMORY_BLOCK_PREDICATE_NAME, + vir_low::PredicateKind::MemoryBlock, + place, + size, + position, + ) + } + fn encode_memory_block_range_acc_int_index( + &mut self, + initial_address: vir_low::Expression, + size: vir_low::Expression, + start_index: vir_low::Expression, + end_index: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult { + use vir_low::macros::*; + // let size_type = self.size_type_mid()?; + // var_decls! { + // index: Int + // } + // let element_address = + // self.address_offset(size.clone(), address, index.clone().into(), position)?; + // let predicate = self.encode_memory_block_acc(element_address.clone(), size, position)?; + // let start_index = self.obtain_constant_value(&size_type, start_index, position)?; + // let end_index = self.obtain_constant_value(&size_type, end_index, position)?; + // let body = expr!( + // (([start_index] <= index) && (index < [end_index])) ==> [predicate] + // ); + // let expression = vir_low::Expression::forall( + // vec![index], + // vec![vir_low::Trigger::new(vec![element_address])], + // body, + // ); + // Ok(expression) + var_decls! { + element_address: Address + } + let predicate = + self.encode_memory_block_acc(element_address.clone().into(), size.clone(), position)?; + let guard = self.address_range_contains( + initial_address, + start_index, + end_index, + size, + element_address.clone().into(), + position, + )?; + let body = expr!([guard] ==> [predicate.clone()]); + let expression = vir_low::Expression::forall( + vec![element_address], + vec![vir_low::Trigger::new(vec![predicate])], + body, + ); + Ok(expression) + } + fn encode_memory_block_range_acc( + &mut self, + initial_address: vir_low::Expression, + size: vir_low::Expression, + start_index: vir_low::Expression, + end_index: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult { + let size_type = self.size_type_mid()?; + let start_index = self.obtain_constant_value(&size_type, start_index, position)?; + let end_index = self.obtain_constant_value(&size_type, end_index, position)?; + self.encode_memory_block_range_acc_int_index( + initial_address, + size, + start_index, + end_index, + position, + ) + } + fn memory_block_range( + &mut self, + ty: &vir_mid::Type, + address: vir_low::Expression, + start_index: vir_low::Expression, + end_index: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult { + let vir_mid::Type::Pointer(pointer_type) = ty else { + unreachable!() + }; + let target_type = &*pointer_type.target_type; + let initial_address = self.pointer_address(ty, address, position)?; + let size_of = self.encode_type_size_expression2(target_type, target_type)?; + self.encode_memory_block_range_acc( + initial_address, + size_of, + start_index, + end_index, + position, + ) + } + fn encode_memory_block_range_guarded_acc( + &mut self, + address: vir_low::Expression, + size: vir_low::Expression, + index_variable: vir_low::VariableDecl, + guard: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult { + use vir_low::macros::*; + let size_type = self.size_type_mid()?; + // var_decls! { + // index: Int + // } + // let element_address = + // self.address_offset(size.clone(), address, index.clone().into(), position)?; + // let predicate = self.encode_memory_block_acc(element_address.clone(), size, position)?; + // let index_variable_replacement = + // self.construct_constant_snapshot(&size_type, index.clone().into(), position)?; + // let replacements = std::iter::once((&index_variable, &index_variable_replacement)) + // .collect::>(); + // let guard = expr! { + // ([0.into()] <= index) && [guard.substitute_variables(&replacements)] + // }; + // let body = expr!([guard] ==> [predicate]); + // let expression = vir_low::Expression::forall( + // vec![index], + // vec![vir_low::Trigger::new(vec![element_address])], + // body, + // ); + var_decls! { + element_address: Address + } + let start_index = self.index_into_allocation(size.clone(), address.clone(), position)?; + let index = vir_low::Expression::subtract( + self.index_into_allocation(size.clone(), element_address.clone().into(), position)?, + start_index, + ); + let index_variable_replacement = + self.construct_constant_snapshot(&size_type, index.clone(), position)?; + let replacements = std::iter::once((&index_variable, &index_variable_replacement)) + .collect::>(); + let element_allocation = + self.address_allocation(element_address.clone().into(), position)?; + let address_allocation = self.address_allocation(address, position)?; + let guard = expr! { + (([0.into()] <= [index]) && + ([element_allocation] == [address_allocation])) && + [guard.substitute_variables(&replacements)] + }; + let predicate = + self.encode_memory_block_acc(element_address.clone().into(), size, position)?; + let body = expr!([guard] ==> [predicate.clone()]); + let expression = vir_low::Expression::forall( + vec![element_address], + vec![vir_low::Trigger::new(vec![predicate])], + body, + ); + Ok(expression) } fn encode_memory_block_stack_drop_acc( &mut self, @@ -106,7 +358,27 @@ impl<'p, 'v: 'p, 'tcx: 'v> PredicatesMemoryBlockInterface for Lowerer<'p, 'v, 't size: vir_low::Expression, position: vir_low::Position, ) -> SpannedEncodingResult { - self.encode_generic_memory_block_acc("MemoryBlockStackDrop", place, size, position) + self.encode_generic_memory_block_acc( + "MemoryBlockStackDrop", + vir_low::PredicateKind::WithoutSnapshotWholeNonAliased, + place, + size, + position, + ) + } + fn encode_memory_block_heap_drop_acc( + &mut self, + address: vir_low::Expression, + size: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult { + self.encode_generic_memory_block_acc( + "MemoryBlockHeapDrop", + vir_low::PredicateKind::WithoutSnapshotWhole, + address, + size, + position, + ) } fn encode_memory_block_bytes_function_name(&mut self) -> SpannedEncodingResult { Ok("MemoryBlock$bytes".to_string()) @@ -122,6 +394,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> PredicatesMemoryBlockInterface for Lowerer<'p, 'v, 't .is_memory_block_bytes_encoded { use vir_low::macros::*; + let wildcard_permission = self.wildcard_permission()?; let mut function = function! { MemoryBlockBytes => bytes( address: Address, @@ -129,7 +402,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> PredicatesMemoryBlockInterface for Lowerer<'p, 'v, 't ): Bytes requires (acc( MemoryBlock(address, size), - [vir_low::Expression::wildcard_permission()] + [wildcard_permission] )); }; function.name = "MemoryBlock$bytes".to_string(); diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/mod.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/mod.rs index 95565515e2e..798e8667747 100644 --- a/prusti-viper/src/encoder/middle/core_proof/predicates/mod.rs +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/mod.rs @@ -1,11 +1,28 @@ mod memory_block; mod owned; +mod restoration; mod state; +mod aliasing; + +use rustc_hash::FxHashSet; +use std::collections::BTreeMap; +use vir_crate::low as vir_low; pub(super) use self::{ + aliasing::PredicatesAliasingInterface, memory_block::PredicatesMemoryBlockInterface, owned::{ - FracRefUseBuilder, OwnedNonAliasedUseBuilder, PredicatesOwnedInterface, UniqueRefUseBuilder, + OwnedNonAliasedSnapCallBuilder, OwnedNonAliasedUseBuilder, OwnedPredicateInfo, + PredicatesOwnedInterface, SnapshotFunctionInfo, }, + restoration::RestorationInterface, state::PredicatesState, }; + +/// Addidional information about the predicates used by purification +/// optimizations. +#[derive(Clone, Debug)] +pub(super) struct PredicateInfo { + pub(super) owned_predicates_info: BTreeMap, + pub(super) non_aliased_memory_block_addresses: FxHashSet, +} diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/common/function_decl.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/common/function_decl.rs new file mode 100644 index 00000000000..95caca41fdf --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/common/function_decl.rs @@ -0,0 +1,523 @@ +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::{ + addresses::AddressesInterface, + arithmetic_wrappers::ArithmeticWrappersInterface, + footprint::FootprintInterface, + lifetimes::LifetimesInterface, + lowerer::Lowerer, + places::PlacesInterface, + pointers::PointersInterface, + snapshots::{ + AssertionToSnapshotConstructor, IntoPureSnapshot, IntoSnapshot, PredicateKind, + SnapshotValidityInterface, SnapshotValuesInterface, + }, + type_layouts::TypeLayoutsInterface, + }, +}; +use vir_crate::{ + common::{ + expression::{BinaryOperationHelpers, ExpressionIterator, QuantifierHelpers}, + identifier::WithIdentifier, + validator::Validator, + }, + low::{self as vir_low}, + middle::{self as vir_mid, operations::ty::Typed}, +}; + +/// A builder for creating snapshot function declarations. +pub(in super::super::super) struct FunctionDeclBuilder<'l, 'p, 'v, 'tcx> { + pub(in super::super) lowerer: &'l mut Lowerer<'p, 'v, 'tcx>, + pub(in super::super) function_name: &'l str, + pub(in super::super) ty: &'l vir_mid::Type, + pub(in super::super) type_decl: &'l vir_mid::TypeDecl, + pub(in super::super) parameters: Vec, + // pub(in super::super) pres: Vec, + /// The predicate for which this function is a snapshot. + pub(in super::super) predicate: Option, + /// Postconditions that we can assume always when we have the predicate. + pub(in super::super) snapshot_posts: Vec, + /// Postconditions defining the body of the snapshot that are put inside + /// `unfolding self.predicate in ...`. + pub(in super::super) snapshot_body_posts: Vec, + // pub(in super::super) conjuncts: Option>, FIXME: We have no body. + pub(in super::super) position: vir_low::Position, + pub(in super::super) place: vir_low::VariableDecl, + pub(in super::super) address: vir_low::VariableDecl, + pub(in super::super) owned_snapshot_functions_to_encode: Vec, + pub(in super::super) owned_range_snapshot_functions_to_encode: Vec, +} + +impl<'l, 'p, 'v, 'tcx> FunctionDeclBuilder<'l, 'p, 'v, 'tcx> { + pub(in super::super::super) fn new( + lowerer: &'l mut Lowerer<'p, 'v, 'tcx>, + function_name: &'l str, + ty: &'l vir_mid::Type, + type_decl: &'l vir_mid::TypeDecl, + position: vir_low::Position, + ) -> SpannedEncodingResult { + let place = vir_low::VariableDecl::new("place", lowerer.place_option_type()?); + let address = vir_low::VariableDecl::new("address", lowerer.address_type()?); + Ok(Self { + function_name, + ty, + type_decl, + parameters: Vec::new(), + predicate: None, + snapshot_posts: Vec::new(), + snapshot_body_posts: Vec::new(), + // pres: Vec::new(), + // posts: Vec::new(), + // conjuncts: None, + position, + lowerer, + place, + address, + owned_snapshot_functions_to_encode: Vec::new(), + owned_range_snapshot_functions_to_encode: Vec::new(), + }) + } + + pub(in super::super::super) fn get_snapshot_postconditions( + &self, + ) -> SpannedEncodingResult> { + Ok(self.snapshot_posts.clone()) + } + + pub(in super::super::super) fn get_snapshot_body( + &self, + ) -> SpannedEncodingResult> { + Ok(self.snapshot_body_posts.clone()) + } + + pub(in super::super::super) fn build(self) -> SpannedEncodingResult { + let return_type = self.ty.to_snapshot(self.lowerer)?; + let mut pres = Vec::new(); + let mut posts = self.snapshot_posts; + if let Some(predicate) = self.predicate { + for snapshot_body_post in self.snapshot_body_posts { + posts.push(vir_low::Expression::unfolding( + predicate.clone(), + snapshot_body_post, + self.position, + )); + } + pres.push(vir_low::Expression::PredicateAccessPredicate(predicate)); + } else { + posts.extend(self.snapshot_body_posts); + }; + let function = vir_low::FunctionDecl { + name: format!("{}${}", self.function_name, self.ty.get_identifier()), + kind: vir_low::FunctionKind::Snap, + parameters: self.parameters, + body: None, + pres, + // body: self + // .conjuncts + // .map(|conjuncts| conjuncts.into_iter().conjoin()), + // pres: self.pres, + // posts: self.posts, + posts, + return_type, + }; + Ok(function) + } + + pub(in super::super) fn create_lifetime_parameters(&mut self) -> SpannedEncodingResult<()> { + self.parameters + .extend(self.lowerer.create_lifetime_parameters(self.type_decl)?); + Ok(()) + } + + pub(in super::super) fn create_const_parameters(&mut self) -> SpannedEncodingResult<()> { + for parameter in self.type_decl.get_const_parameters() { + self.parameters + .push(parameter.to_pure_snapshot(self.lowerer)?); + } + Ok(()) + } + + pub(in super::super) fn add_precondition( + &mut self, + predicate: vir_low::Expression, + ) -> SpannedEncodingResult<()> { + // self.pres.push(assertion); + assert!( + self.predicate.is_none(), + "precondition already set: {:?}", + self.predicate + ); + let vir_low::Expression::PredicateAccessPredicate(predicate) = predicate else { + unreachable!("Must be a predicate: {predicate}"); + }; + self.predicate = Some(predicate); + Ok(()) + } + + pub(in super::super) fn add_snapshot_postcondition( + &mut self, + assertion: vir_low::Expression, + ) -> SpannedEncodingResult<()> { + self.snapshot_posts.push(assertion); + Ok(()) + } + + pub(in super::super::super) fn add_snapshot_body_postcondition( + &mut self, + assertion: vir_low::Expression, + ) -> SpannedEncodingResult<()> { + self.snapshot_body_posts.push(assertion); + Ok(()) + } + + pub(in super::super) fn array_length_int( + &mut self, + array_length_mid: &vir_mid::VariableDecl, + ) -> SpannedEncodingResult { + let array_length = array_length_mid.to_pure_snapshot(self.lowerer)?; + let size_type_mid = self.lowerer.size_type_mid()?; + self.lowerer + .obtain_constant_value(&size_type_mid, array_length.into(), self.position) + } + + pub(in super::super) fn result_type(&mut self) -> SpannedEncodingResult { + self.ty.to_snapshot(self.lowerer) + } + + pub(in super::super) fn result(&mut self) -> SpannedEncodingResult { + Ok(vir_low::VariableDecl::result_variable(self.result_type()?)) + } + + pub(in super::super) fn add_validity_postcondition(&mut self) -> SpannedEncodingResult<()> { + let result = self.result()?; + let validity = self + .lowerer + .encode_snapshot_valid_call_for_type(result.into(), self.ty)?; + self.add_snapshot_postcondition(validity) + } + + pub(in super::super) fn add_snapshot_len_equal_to_postcondition( + &mut self, + array_length_mid: &vir_mid::VariableDecl, + ) -> SpannedEncodingResult<()> { + use vir_low::macros::*; + let snapshot = self.result()?; + let snapshot_length = self + .lowerer + .obtain_array_len_snapshot(snapshot.into(), self.position)?; + let array_length_int = self.array_length_int(array_length_mid)?; + let expression = expr! { + ([array_length_int] == [snapshot_length]) + }; + self.add_snapshot_postcondition(expression) + } + + pub(in super::super) fn create_field_snap_call( + &mut self, + field: &vir_mid::FieldDecl, + snap_call: impl FnOnce( + &mut Self, + &vir_mid::FieldDecl, + vir_low::Expression, + vir_low::Expression, + ) -> SpannedEncodingResult, + ) -> SpannedEncodingResult { + let field_place = self.lowerer.encode_field_place( + self.ty, + field, + self.place.clone().into(), + self.position, + )?; + let field_address = self.lowerer.encode_field_address( + self.ty, + field, + self.address.clone().into(), + self.position, + )?; + snap_call(self, field, field_place, field_address) + // let target_slice_len = self.slice_len_expression()?; + // self.lowerer.frac_ref_snap( + // CallContext::BuiltinMethod, + // &field.ty, + // &field.ty, + // field_place, + // self.root_address.clone().into(), + // self.reference_lifetime.clone().into(), + // target_slice_len, + // ) + } + + pub(in super::super) fn create_field_snapshot_equality( + &mut self, + field: &vir_mid::FieldDecl, + snap_call: impl FnOnce( + &mut Self, + &vir_mid::FieldDecl, + vir_low::Expression, + vir_low::Expression, + ) -> SpannedEncodingResult, + ) -> SpannedEncodingResult { + use vir_low::macros::*; + let result = self.result()?; + let field_snapshot = self.lowerer.obtain_struct_field_snapshot( + self.ty, + field, + result.into(), + self.position, + )?; + let snap_call = self.create_field_snap_call(field, snap_call)?; + Ok(expr! { + [field_snapshot] == [snap_call] + }) + } + + pub(in super::super::super) fn create_discriminant_snapshot_equality( + &mut self, + decl: &vir_mid::type_decl::Enum, + snap_call: impl FnOnce( + &mut FunctionDeclBuilder, + &vir_mid::type_decl::Enum, + vir_low::Expression, + vir_low::Expression, + ) -> SpannedEncodingResult, + ) -> SpannedEncodingResult { + use vir_low::macros::*; + let result = self.result()?; + let discriminant_snapshot = + self.lowerer + .obtain_enum_discriminant(result.into(), self.ty, self.position)?; + let discriminant_field = decl.discriminant_field(); + let discriminant_place = self.lowerer.encode_field_place( + self.ty, + &discriminant_field, + self.place.clone().into(), + self.position, + )?; + let discriminant_address = self.lowerer.encode_field_address( + self.ty, + &discriminant_field, + self.address.clone().into(), + self.position, + )?; + let snap_call = snap_call(self, decl, discriminant_place, discriminant_address)?; + let snap_call_int = self.lowerer.obtain_constant_value( + &decl.discriminant_type, + snap_call, + self.position, + )?; + Ok(expr! { + [discriminant_snapshot] == [snap_call_int] + }) + } + + pub(in super::super::super) fn create_variant_snapshot_equality( + &mut self, + discriminant_value: vir_mid::DiscriminantValue, + variant: &vir_mid::type_decl::Struct, + snap_call: impl FnOnce( + &mut FunctionDeclBuilder, + &vir_mid::Type, + vir_low::Expression, + vir_low::Expression, + ) -> SpannedEncodingResult, + ) -> SpannedEncodingResult<(vir_low::Expression, vir_low::Expression)> { + use vir_low::macros::*; + let result = self.result()?; + let discriminant_call = + self.lowerer + .obtain_enum_discriminant(result.clone().into(), self.ty, self.position)?; + let guard = expr! { + [ discriminant_call ] == [ discriminant_value.into() ] + }; + let variant_index = variant.name.clone().into(); + let variant_place = self.lowerer.encode_enum_variant_place( + self.ty, + &variant_index, + self.place.clone().into(), + self.position, + )?; + let variant_address = self.lowerer.encode_enum_variant_address( + self.ty, + &variant_index, + self.address.clone().into(), + self.position, + )?; + let variant_snapshot = self.lowerer.obtain_enum_variant_snapshot( + self.ty, + &variant_index, + result.into(), + self.position, + )?; + let ty = self.ty.clone(); + let variant_type = ty.variant(variant_index); + let snap_call = snap_call(self, &variant_type, variant_place, variant_address)?; + let equality = expr! { + [variant_snapshot] == [snap_call] + }; + Ok((guard, equality)) + } + + // pub(in super::super::super) fn add_snapshot_body_postcondition( + // &mut self, + // precondition_predicate: vir_low::Expression, + // body: vir_low::Expression, + // ) -> SpannedEncodingResult<()> { + // let unfolding = precondition_predicate.into_unfolding(body); + // self.add_postcondition(unfolding) + // } + + pub(in super::super::super) fn add_structural_invariant( + &mut self, + decl: &vir_mid::type_decl::Struct, + is_invariant_pure: bool, + predicate_kind: PredicateKind, + snap_call: &impl Fn( + &mut Self, + &vir_mid::FieldDecl, + vir_low::Expression, + vir_low::Expression, + ) -> SpannedEncodingResult, + ) -> SpannedEncodingResult<()> { + if let Some(invariant) = decl.structural_invariant.clone() { + let mut regular_field_arguments = Vec::new(); + for field in &decl.fields { + let field_snap_call = self.create_field_snap_call(field, snap_call)?; + regular_field_arguments.push(field_snap_call); + // regular_field_arguments.push(self.create_field_snap_call(field)?); + } + let result = self.result()?; + let (deref_fields, deref_range_fields) = self + .lowerer + .structural_invariant_to_deref_fields(&invariant)?; + for deref_owned in &deref_fields { + self.owned_snapshot_functions_to_encode + .push(deref_owned.place.get_type().clone()); + } + for deref_range_owned in &deref_range_fields { + self.owned_range_snapshot_functions_to_encode + .push(deref_range_owned.address.get_type().clone()); + } + let mut constructor_encoder = AssertionToSnapshotConstructor::for_function_body( + predicate_kind, + self.ty, + regular_field_arguments, + decl.fields.clone(), + (deref_fields, deref_range_fields), + self.position, + ); + let invariant_expression = invariant.into_iter().conjoin(); + let permission_expression = invariant_expression.convert_into_permission_expression(); + let constructor = constructor_encoder + .expression_to_snapshot_constructor(self.lowerer, &permission_expression)?; + let body = vir_low::Expression::equals(result.into(), constructor); + if !is_invariant_pure { + self.add_snapshot_body_postcondition(body)?; + } else { + self.add_snapshot_postcondition(body)?; + } + } + Ok(()) + } + + pub(in super::super) fn range_result_type(&mut self) -> SpannedEncodingResult { + let vir_mid::Type::Pointer(pointer_type) = self.ty else { + unreachable!("{} must be a pointer type", self.ty); + }; + let element_type = pointer_type.target_type.to_snapshot(self.lowerer)?; + let return_type = vir_low::Type::seq(element_type); + Ok(return_type) + } + + pub(in super::super) fn range_result( + &mut self, + ) -> SpannedEncodingResult { + Ok(vir_low::VariableDecl::result_variable( + self.range_result_type()?, + )) + } + + pub(in super::super) fn create_range_postcondition( + &mut self, + posts: &mut Vec, + address: &vir_low::VariableDecl, + start_index: &vir_low::VariableDecl, + end_index: &vir_low::VariableDecl, + snap_call_constructor: impl Fn( + &mut Lowerer, + &vir_mid::Type, + vir_low::Expression, + vir_low::Position, + ) -> SpannedEncodingResult, + ) -> SpannedEncodingResult<()> { + use vir_low::macros::*; + let size_type = self.lowerer.size_type_mid()?; + var_decls! { + index: Int + } + let vir_mid::Type::Pointer(ty) = self.ty else { + unreachable!() + }; + let initial_address = + self.lowerer + .pointer_address(self.ty, address.clone().into(), self.position)?; + let vir_mid::Type::Pointer(pointer_type) = self.ty else { + unreachable!() + }; + let size = self + .lowerer + .encode_type_size_expression2(&pointer_type.target_type, &*pointer_type.target_type)?; + let start_index = self.lowerer.obtain_constant_value( + &size_type, + start_index.clone().into(), + self.position, + )?; + let end_index = self.lowerer.obtain_constant_value( + &size_type, + end_index.clone().into(), + self.position, + )?; + let offset_index = + self.lowerer + .int_add_call(start_index.clone(), index.clone().into(), self.position)?; + let element_address = + self.lowerer + .address_offset(size, initial_address, offset_index, self.position)?; + let snap_call = snap_call_constructor( + self.lowerer, + &ty.target_type, + element_address.clone(), + self.position, + )?; + let result_type = self.range_result_type()?; + let result = self.range_result()?; + let result_len = vir_low::Expression::container_op( + vir_low::ContainerOpKind::SeqLen, + result_type.clone(), + vec![result.clone().into()], + self.position, + ); + let index_diff = vir_low::Expression::subtract(end_index, start_index); + posts.push(expr!([result_len.clone()] == [index_diff])); + let element_snap = vir_low::Expression::container_op( + vir_low::ContainerOpKind::SeqIndex, + result_type, + vec![result.into(), index.clone().into()], + self.position, + ); + let body = expr!( + (([0.into()] <= index) && (index < [result_len])) ==> + ([snap_call] == [element_snap.clone()]) + ); + let expression = vir_low::Expression::forall( + vec![index], + vec![ + vir_low::Trigger::new(vec![element_address]), + vir_low::Trigger::new(vec![element_snap]), + ], + body, + ); + expression.assert_valid_debug(); + posts.push(expression); + Ok(()) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/common/function_use.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/common/function_use.rs new file mode 100644 index 00000000000..2ddd0f84e31 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/common/function_use.rs @@ -0,0 +1,80 @@ +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::{ + builtin_methods::CallContext, + lifetimes::LifetimesInterface, + lowerer::Lowerer, + snapshots::{IntoPureSnapshot, IntoSnapshot}, + }, +}; +use vir_crate::{ + common::identifier::WithIdentifier, + low::{self as vir_low}, + middle as vir_mid, + middle::operations::{const_generics::WithConstArguments, lifetimes::WithLifetimes}, +}; + +pub(in super::super) struct FunctionCallBuilder<'l, 'p, 'v, 'tcx, G> +where + G: WithLifetimes + WithConstArguments, +{ + pub(in super::super) lowerer: &'l mut Lowerer<'p, 'v, 'tcx>, + pub(in super::super) function_name: &'l str, + pub(in super::super) context: CallContext, + pub(in super::super) ty: &'l vir_mid::Type, + pub(in super::super) generics: &'l G, + pub(in super::super) arguments: Vec, + pub(in super::super) position: vir_low::Position, +} + +impl<'l, 'p, 'v, 'tcx, G> FunctionCallBuilder<'l, 'p, 'v, 'tcx, G> +where + G: WithLifetimes + WithConstArguments, +{ + pub(in super::super) fn new( + lowerer: &'l mut Lowerer<'p, 'v, 'tcx>, + function_name: &'l str, + context: CallContext, + ty: &'l vir_mid::Type, + generics: &'l G, + arguments: Vec, + position: vir_low::Position, + ) -> SpannedEncodingResult { + Ok(Self { + lowerer, + function_name, + context, + ty, + generics, + arguments, + position, + }) + } + + pub(in super::super) fn build(self) -> SpannedEncodingResult { + let return_type = self.ty.to_snapshot(self.lowerer)?; + let call = vir_low::Expression::function_call( + format!("{}${}", self.function_name, self.ty.get_identifier()), + self.arguments, + return_type, + ); + Ok(call.set_default_position(self.position)) + } + + pub(in super::super) fn add_lifetime_arguments(&mut self) -> SpannedEncodingResult<()> { + self.arguments.extend( + self.lowerer + .create_lifetime_arguments(self.context, self.generics)?, + ); + Ok(()) + } + + pub(in super::super) fn add_const_arguments(&mut self) -> SpannedEncodingResult<()> { + // FIXME: remove code duplication with other add_const_arguments methods + for argument in self.generics.get_const_arguments() { + self.arguments + .push(argument.to_pure_snapshot(self.lowerer)?); + } + Ok(()) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/common/mod.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/common/mod.rs index ef427252419..6bcb70532ad 100644 --- a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/common/mod.rs +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/common/mod.rs @@ -1,2 +1,4 @@ +pub(super) mod function_decl; +pub(super) mod function_use; pub(super) mod predicate_decl; pub(super) mod predicate_use; diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/common/predicate_decl.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/common/predicate_decl.rs index ab23d9db1f2..b4cbb7bbd17 100644 --- a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/common/predicate_decl.rs +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/common/predicate_decl.rs @@ -1,15 +1,21 @@ use crate::encoder::{ errors::SpannedEncodingResult, middle::core_proof::{ + addresses::AddressesInterface, builtin_methods::CallContext, lifetimes::LifetimesInterface, lowerer::Lowerer, - predicates::owned::builders::{ - unique_ref::predicate_use::UniqueRefUseBuilder, FracRefUseBuilder, - }, + places::PlacesInterface, + pointers::PointersInterface, + predicates::{PredicatesMemoryBlockInterface, PredicatesOwnedInterface}, references::ReferencesInterface, - snapshots::{IntoPureSnapshot, SnapshotValidityInterface, SnapshotValuesInterface}, + snapshots::{ + IntoPureSnapshot, IntoSnapshot, IntoSnapshotLowerer, PredicateKind, + SelfFramingAssertionToSnapshot, SnapshotBytesInterface, SnapshotValidityInterface, + SnapshotValuesInterface, + }, type_layouts::TypeLayoutsInterface, + types::TypesInterface, }, }; use vir_crate::{ @@ -18,6 +24,12 @@ use vir_crate::{ middle as vir_mid, }; +pub(in super::super::super) enum ContainingPredicateKind { + Owned, + UniqueRef, + FracRef, +} + pub(in super::super::super) struct PredicateDeclBuilder<'l, 'p, 'v, 'tcx> { pub(in super::super) lowerer: &'l mut Lowerer<'p, 'v, 'tcx>, pub(in super::super) predicate_name: &'l str, @@ -26,6 +38,12 @@ pub(in super::super::super) struct PredicateDeclBuilder<'l, 'p, 'v, 'tcx> { pub(in super::super) parameters: Vec, pub(in super::super) conjuncts: Option>, pub(in super::super) position: vir_low::Position, + /// `place` is used by subtypes that cannot be aliased. + pub(in super::super) place: vir_low::VariableDecl, + // /// `address` is used by subtypes that cannot be aliased. + // pub(in super::super) address: vir_low::VariableDecl, + /// `address` is used by subtypes that can be aliased. + pub(in super::super) address: vir_low::VariableDecl, } impl<'l, 'p, 'v, 'tcx> PredicateDeclBuilder<'l, 'p, 'v, 'tcx> { @@ -37,6 +55,9 @@ impl<'l, 'p, 'v, 'tcx> PredicateDeclBuilder<'l, 'p, 'v, 'tcx> { position: vir_low::Position, ) -> SpannedEncodingResult { Ok(Self { + place: vir_low::VariableDecl::new("place", lowerer.place_option_type()?), + // address: vir_low::VariableDecl::new("address", lowerer.address_type()?), + address: vir_low::VariableDecl::new("address", lowerer.address_type()?), ty, predicate_name, type_decl, @@ -50,6 +71,7 @@ impl<'l, 'p, 'v, 'tcx> PredicateDeclBuilder<'l, 'p, 'v, 'tcx> { pub(in super::super) fn build(self) -> vir_low::PredicateDecl { vir_low::PredicateDecl { name: format!("{}${}", self.predicate_name, self.ty.get_identifier()), + kind: vir_low::PredicateKind::Owned, parameters: self.parameters, body: self .conjuncts @@ -89,84 +111,217 @@ impl<'l, 'p, 'v, 'tcx> PredicateDeclBuilder<'l, 'p, 'v, 'tcx> { self.add_conjunct(validity) } + pub(in super::super) fn add_frac_ref_pointer_predicate( + &mut self, + lifetime: &vir_mid::ty::LifetimeConst, + place: vir_low::VariableDecl, + address: vir_low::VariableDecl, + ) -> SpannedEncodingResult { + let lifetime = lifetime.to_pure_snapshot(self.lowerer)?; + let pointer_type = { + let reference_type = self.type_decl.clone().unwrap_reference(); + vir_mid::Type::pointer(reference_type.target_type) + }; + self.lowerer.ensure_type_definition(&pointer_type)?; + let expression = self.lowerer.frac_ref( + CallContext::BuiltinMethod, + &pointer_type, + &pointer_type, + place.into(), + address.into(), + lifetime.into(), + None, // FIXME + None, + self.position, + )?; + self.add_conjunct(expression)?; + Ok(pointer_type) + } + + pub(in super::super) fn add_unique_ref_pointer_predicate( + &mut self, + lifetime: &vir_mid::ty::LifetimeConst, + place: vir_low::VariableDecl, + address: vir_low::VariableDecl, + // _snapshot: &vir_low::VariableDecl, + ) -> SpannedEncodingResult { + let lifetime = lifetime.to_pure_snapshot(self.lowerer)?; + // let pointer_type = &self.lowerer.reference_address_type(self.ty)?; + let pointer_type = { + let reference_type = self.type_decl.clone().unwrap_reference(); + vir_mid::Type::pointer(reference_type.target_type) + }; + self.lowerer.ensure_type_definition(&pointer_type)?; + let expression = self.lowerer.unique_ref( + CallContext::BuiltinMethod, + &pointer_type, + &pointer_type, + place.into(), + address.into(), + lifetime.into(), + None, // FIXME + None, + self.position, + )?; + self.add_conjunct(expression)?; + Ok(pointer_type) + } + + /// `containing_predicate` – whether the predicate is used in `Owned` or `UniqueRef`. pub(in super::super) fn add_unique_ref_target_predicate( &mut self, target_type: &vir_mid::Type, lifetime: &vir_mid::ty::LifetimeConst, - place: &vir_low::VariableDecl, - snapshot: &vir_low::VariableDecl, + place: vir_low::Expression, + address: vir_low::VariableDecl, + containing_predicate: ContainingPredicateKind, ) -> SpannedEncodingResult<()> { use vir_low::macros::*; let deref_place = self .lowerer - .reference_deref_place(place.clone().into(), self.position)?; - let target_address = - self.lowerer - .reference_address(self.ty, snapshot.clone().into(), self.position)?; - let current_snapshot = self.lowerer.reference_target_current_snapshot( - self.ty, - snapshot.clone().into(), - self.position, - )?; - let final_snapshot = self.lowerer.reference_target_final_snapshot( - self.ty, - snapshot.clone().into(), - self.position, - )?; + .reference_deref_place(place.clone(), self.position)?; let lifetime_alive = self .lowerer .encode_lifetime_const_into_pure_is_alive_variable(lifetime)?; let lifetime = lifetime.to_pure_snapshot(self.lowerer)?; - let mut builder = UniqueRefUseBuilder::new( - self.lowerer, + let (target_address, target_len) = { + let pointer_type = &self.lowerer.reference_address_type(self.ty)?; + let pointer_snapshot = match containing_predicate { + ContainingPredicateKind::Owned => { + self.lowerer + .encode_snapshot_to_bytes_function(pointer_type)?; + let size_of = self + .lowerer + .encode_type_size_expression2(self.ty, self.type_decl)?; + let bytes = self + .lowerer + .encode_memory_block_bytes_expression(address.into(), size_of)?; + let from_bytes = pointer_type.to_snapshot(self.lowerer)?; + expr! { + Snap::from_bytes([bytes]) + } + } + ContainingPredicateKind::UniqueRef => self.lowerer.unique_ref_snap( + CallContext::BuiltinMethod, + pointer_type, + pointer_type, + place, + address.into(), + lifetime.clone().into(), + None, + false, + self.position, + )?, + ContainingPredicateKind::FracRef => unreachable!(), + }; + let target_address = self.lowerer.pointer_address( + pointer_type, + pointer_snapshot.clone(), + self.position, + )?; + let target_len = if pointer_type.is_pointer_to_slice() { + Some(self.lowerer.pointer_slice_len( + pointer_type, + pointer_snapshot, + self.position, + )?) + } else { + None + }; + (target_address, target_len) + }; + let expression = self.lowerer.unique_ref( CallContext::BuiltinMethod, target_type, target_type, deref_place, target_address, - current_snapshot, - final_snapshot, lifetime.into(), + target_len, + None, + self.position, )?; - builder.add_lifetime_arguments()?; - builder.add_const_arguments()?; - let expression = builder.build(); self.add_conjunct(expr! { [lifetime_alive.into()] ==> [expression] }) } + // FIXME: Code duplication with `add_unique_ref_target_predicate`. pub(in super::super) fn add_frac_ref_target_predicate( &mut self, target_type: &vir_mid::Type, lifetime: &vir_mid::ty::LifetimeConst, - place: &vir_low::VariableDecl, - snapshot: &vir_low::VariableDecl, + place: vir_low::Expression, + address: vir_low::VariableDecl, + containing_predicate: ContainingPredicateKind, ) -> SpannedEncodingResult<()> { + use vir_low::macros::*; let deref_place = self .lowerer - .reference_deref_place(place.clone().into(), self.position)?; + .reference_deref_place(place.clone(), self.position)?; + let lifetime_alive = self + .lowerer + .encode_lifetime_const_into_pure_is_alive_variable(lifetime)?; + let lifetime = lifetime.to_pure_snapshot(self.lowerer)?; + let pointer_type = &self.lowerer.reference_address_type(self.ty)?; + let pointer_snapshot = match containing_predicate { + ContainingPredicateKind::Owned => { + self.lowerer + .encode_snapshot_to_bytes_function(pointer_type)?; + let size_of = self + .lowerer + .encode_type_size_expression2(self.ty, self.type_decl)?; + let bytes = self + .lowerer + .encode_memory_block_bytes_expression(address.into(), size_of)?; + let from_bytes = pointer_type.to_snapshot(self.lowerer)?; + expr! { + Snap::from_bytes([bytes]) + } + } + ContainingPredicateKind::UniqueRef => self.lowerer.unique_ref_snap( + CallContext::BuiltinMethod, + pointer_type, + pointer_type, + place, + address.into(), + lifetime.clone().into(), + None, + false, + self.position, + )?, + ContainingPredicateKind::FracRef => self.lowerer.frac_ref_snap( + CallContext::BuiltinMethod, + pointer_type, + pointer_type, + place, + address.into(), + lifetime.clone().into(), + None, + self.position, + )?, + }; let target_address = self.lowerer - .reference_address(self.ty, snapshot.clone().into(), self.position)?; - let current_snapshot = self.lowerer.reference_target_current_snapshot( - self.ty, - snapshot.clone().into(), - self.position, - )?; - let lifetime = lifetime.to_pure_snapshot(self.lowerer)?; - let mut builder = FracRefUseBuilder::new( - self.lowerer, + .pointer_address(pointer_type, pointer_snapshot.clone(), self.position)?; + let target_len = if pointer_type.is_pointer_to_slice() { + Some( + self.lowerer + .pointer_slice_len(pointer_type, pointer_snapshot, self.position)?, + ) + } else { + None + }; + let expression = self.lowerer.frac_ref( CallContext::BuiltinMethod, target_type, target_type, deref_place, target_address, - current_snapshot, lifetime.into(), + target_len, + None, + self.position, )?; - builder.add_lifetime_arguments()?; - builder.add_const_arguments()?; - let expression = builder.build(); - self.add_conjunct(expression) + self.add_conjunct(expr! { [lifetime_alive.into()] ==> [expression] }) } pub(in super::super) fn array_length_int( @@ -194,6 +349,28 @@ impl<'l, 'p, 'v, 'tcx> PredicateDeclBuilder<'l, 'p, 'v, 'tcx> { }; self.add_conjunct(expression) } + + pub(in super::super::super) fn add_structural_invariant( + &mut self, + decl: &vir_mid::type_decl::Struct, + predicate_kind: PredicateKind, + ) -> SpannedEncodingResult> { + if let Some(invariant) = &decl.structural_invariant { + let mut encoder = SelfFramingAssertionToSnapshot::for_predicate_body( + self.place.clone(), + self.address.clone(), + predicate_kind, + ); + for assertion in invariant { + let low_assertion = + encoder.expression_to_snapshot(self.lowerer, assertion, true)?; + self.add_conjunct(low_assertion)?; + } + Ok(encoder.into_created_predicate_types()) + } else { + Ok(Vec::new()) + } + } } pub(in super::super::super) trait PredicateDeclBuilderMethods<'l, 'p, 'v, 'tcx> diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/common/predicate_use.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/common/predicate_use.rs index 6eaa9dc1e77..1cd108629ff 100644 --- a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/common/predicate_use.rs +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/common/predicate_use.rs @@ -1,8 +1,8 @@ use crate::encoder::{ errors::SpannedEncodingResult, middle::core_proof::{ - builtin_methods::CallContext, lifetimes::LifetimesInterface, lowerer::Lowerer, - snapshots::IntoPureSnapshot, + builtin_methods::CallContext, const_generics::ConstGenericsInterface, + lifetimes::LifetimesInterface, lowerer::Lowerer, }, }; use vir_crate::{ @@ -53,7 +53,7 @@ where pub(in super::super) fn build(self) -> vir_low::Expression { vir_low::Expression::predicate_access_predicate( - format!("{}${}", self.predicate_name, self.ty.get_identifier()), + self.predicate_name(), self.arguments, self.permission_amount .unwrap_or_else(vir_low::Expression::full_permission), @@ -61,6 +61,10 @@ where ) } + pub(in super::super) fn predicate_name(&self) -> String { + format!("{}${}", self.predicate_name, self.ty.get_identifier()) + } + pub(in super::super) fn add_lifetime_arguments(&mut self) -> SpannedEncodingResult<()> { self.arguments.extend( self.lowerer @@ -70,11 +74,15 @@ where } pub(in super::super) fn add_const_arguments(&mut self) -> SpannedEncodingResult<()> { - // FIXME: remove code duplication with other add_const_arguments methods - for argument in self.generics.get_const_arguments() { - self.arguments - .push(argument.to_pure_snapshot(self.lowerer)?); - } + // // FIXME: remove code duplication with other add_const_arguments methods + // for argument in self.generics.get_const_arguments() { + // self.arguments + // .push(argument.to_pure_snapshot(self.lowerer)?); + // } + self.arguments.extend( + self.lowerer + .create_const_arguments(self.context, self.generics)?, + ); Ok(()) } diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/frac_ref/function_decl.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/frac_ref/function_decl.rs new file mode 100644 index 00000000000..38de022e197 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/frac_ref/function_decl.rs @@ -0,0 +1,411 @@ +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::{ + builtin_methods::CallContext, + lifetimes::LifetimesInterface, + lowerer::Lowerer, + permissions::PermissionsInterface, + predicates::{ + owned::builders::common::function_decl::FunctionDeclBuilder, PredicatesOwnedInterface, + }, + snapshots::{IntoPureSnapshot, PredicateKind}, + type_layouts::TypeLayoutsInterface, + }, +}; +use vir_crate::{ + low::{self as vir_low}, + middle::{ + self as vir_mid, + operations::{const_generics::WithConstArguments, lifetimes::WithLifetimes}, + }, +}; + +pub(in super::super::super) struct FracRefSnapFunctionBuilder<'l, 'p, 'v, 'tcx> { + inner: FunctionDeclBuilder<'l, 'p, 'v, 'tcx>, + // place: vir_low::VariableDecl, + // root_address: vir_low::VariableDecl, + reference_lifetime: vir_low::VariableDecl, + slice_len: Option, +} + +impl<'l, 'p, 'v, 'tcx> FracRefSnapFunctionBuilder<'l, 'p, 'v, 'tcx> { + pub(in super::super::super) fn new( + lowerer: &'l mut Lowerer<'p, 'v, 'tcx>, + ty: &'l vir_mid::Type, + type_decl: &'l vir_mid::TypeDecl, + ) -> SpannedEncodingResult { + let slice_len = if ty.is_slice() { + Some(vir_mid::VariableDecl::new( + "slice_len", + lowerer.size_type_mid()?, + )) + } else { + None + }; + Ok(Self { + // place: vir_low::VariableDecl::new("place", lowerer.place_type()?), + // root_address: vir_low::VariableDecl::new("root_address", lowerer.address_type()?), + reference_lifetime: vir_low::VariableDecl::new( + "reference_lifetime", + lowerer.lifetime_type()?, + ), + slice_len, + inner: FunctionDeclBuilder::new( + lowerer, + "snap_current_frac_ref", + ty, + type_decl, + Default::default(), + )?, + }) + } + + pub(in super::super::super) fn create_parameters(&mut self) -> SpannedEncodingResult<()> { + self.inner.parameters.push(self.inner.place.clone()); + self.inner.parameters.push(self.inner.address.clone()); + self.inner.parameters.push(self.reference_lifetime.clone()); + self.inner.create_lifetime_parameters()?; + if let Some(slice_len) = self.slice_len()? { + self.inner.parameters.push(slice_len); + } + self.inner.create_const_parameters()?; + Ok(()) + } + + pub(in super::super::super) fn add_frac_ref_precondition( + &mut self, + ) -> SpannedEncodingResult<()> { + let predicate = self.precondition_predicate()?; + self.inner.add_precondition(predicate) + } + + // FIXME: Code duplication. + fn slice_len(&mut self) -> SpannedEncodingResult> { + self.slice_len + .as_ref() + .map(|slice_len_mid| slice_len_mid.to_pure_snapshot(self.inner.lowerer)) + .transpose() + } + + // FIXME: Code duplication. + fn slice_len_expression(&mut self) -> SpannedEncodingResult> { + Ok(self.slice_len()?.map(|slice_len| slice_len.into())) + } + + fn precondition_predicate(&mut self) -> SpannedEncodingResult { + self.frac_ref_predicate( + self.inner.ty, + self.inner.type_decl, + self.inner.place.clone().into(), + self.inner.address.clone().into(), + self.reference_lifetime.clone().into(), + ) + } + + fn frac_ref_predicate( + &mut self, + ty: &vir_mid::Type, + generics: &G, + place: vir_low::Expression, + address: vir_low::Expression, + reference_lifetime: vir_low::Expression, + ) -> SpannedEncodingResult + where + G: WithLifetimes + WithConstArguments, + { + let slice_len = if let Some(slice_len_mid) = &self.slice_len { + let slice_len = slice_len_mid.to_pure_snapshot(self.inner.lowerer)?; + Some(slice_len.into()) + } else { + None + }; + // let mut builder = FracRefUseBuilder::new( + // self.inner.lowerer, + // CallContext::BuiltinMethod, + // ty, + // generics, + // place, + // address, + // reference_lifetime, + // // slice_len, + // )?; + // builder.add_lifetime_arguments()?; + // builder.add_const_arguments()?; + // builder.set_maybe_permission_amount(Some(vir_low::Expression::wildcard_permission()))?; + // builder.build() + let wildcard_permission = self.inner.lowerer.wildcard_permission()?; + self.inner.lowerer.frac_ref( + CallContext::BuiltinMethod, + ty, + generics, + place, + address, + reference_lifetime, + slice_len, + Some(wildcard_permission), + self.inner.position, + ) + } + + pub(in super::super::super) fn get_snapshot_postconditions( + &self, + ) -> SpannedEncodingResult> { + self.inner.get_snapshot_postconditions() + } + + pub(in super::super::super) fn get_snapshot_body( + &self, + ) -> SpannedEncodingResult> { + self.inner.get_snapshot_body() + } + + pub(in super::super::super) fn build(self) -> SpannedEncodingResult { + self.inner.build() + } + + // // FIXME: Code duplication. + // fn create_field_snap_call( + // &mut self, + // field: &vir_mid::FieldDecl, + // ) -> SpannedEncodingResult { + // let field_place = self.inner.lowerer.encode_field_place( + // self.inner.ty, + // field, + // self.inner.place.clone().into(), + // self.inner.position, + // )?; + // let target_slice_len = self.slice_len_expression()?; + // self.inner.lowerer.frac_ref_snap( + // CallContext::BuiltinMethod, + // &field.ty, + // &field.ty, + // field_place, + // self.root_address.clone().into(), + // self.reference_lifetime.clone().into(), + // target_slice_len, + // ) + // } + + // FIXME: Code duplication. + pub(in super::super::super) fn create_field_snapshot_equality( + &mut self, + field: &vir_mid::FieldDecl, + ) -> SpannedEncodingResult { + // use vir_low::macros::*; + // let result = self.inner.result()?; + // let field_snapshot = self.inner.lowerer.obtain_struct_field_snapshot( + // self.inner.ty, + // field, + // result.into(), + // self.inner.position, + // )?; + // let snap_call = self.create_field_snap_call(&field)?; + // Ok(expr! { + // [field_snapshot] == [snap_call] + // }) + // self.inner.create_field_snap_call(field, |builder, field, field_place| { + // let target_slice_len = self.slice_len_expression()?; + // self.inner.lowerer.frac_ref_snap( + // CallContext::BuiltinMethod, + // &field.ty, + // &field.ty, + // field_place, + // self.root_address.clone().into(), + // self.reference_lifetime.clone().into(), + // target_slice_len, + // ) + // }) + let frac_ref_call = self.field_frac_ref_snap()?; + self.inner + .create_field_snapshot_equality(field, frac_ref_call) + } + + fn field_frac_ref_snap( + &mut self, + ) -> SpannedEncodingResult< + impl Fn( + &mut FunctionDeclBuilder, + &vir_mid::FieldDecl, + vir_low::Expression, + vir_low::Expression, + ) -> SpannedEncodingResult, + > { + let target_slice_len = self.slice_len_expression()?; + // let root_address: vir_low::Expression = self.root_address.clone().into(); + // let root_address = std::rc::Rc::new(root_address); + let lifetime: vir_low::Expression = self.reference_lifetime.clone().into(); + let lifetime = std::rc::Rc::new(lifetime); + Ok( + move |builder: &mut FunctionDeclBuilder, + field: &vir_mid::FieldDecl, + field_place, + field_address| { + builder.lowerer.frac_ref_snap( + CallContext::BuiltinMethod, + &field.ty, + &field.ty, + field_place, + field_address, + (*lifetime).clone(), + target_slice_len.clone(), + builder.position, + ) + }, + ) + } + + // FIXME: Code duplication. + pub(in super::super::super) fn add_snapshot_body_postcondition( + &mut self, + body: vir_low::Expression, + ) -> SpannedEncodingResult<()> { + // let predicate = self.precondition_predicate()?; + // let unfolding = predicate.into_unfolding(body); + // self.inner.add_postcondition(unfolding) + self.inner.add_snapshot_body_postcondition(body) + } + + pub(in super::super::super) fn add_validity_postcondition( + &mut self, + ) -> SpannedEncodingResult<()> { + self.inner.add_validity_postcondition() + } + + pub(in super::super::super) fn add_structural_invariant( + &mut self, + decl: &vir_mid::type_decl::Struct, + ) -> SpannedEncodingResult<()> { + // let precondition_predicate = self.precondition_predicate()?; + let predicate_kind = PredicateKind::FracRef { + lifetime: self.reference_lifetime.clone().into(), + }; + let snap_call = self.field_frac_ref_snap()?; + self.inner + .add_structural_invariant(decl, false, predicate_kind, &snap_call) + } + + pub(in super::super::super) fn create_discriminant_snapshot_equality( + &mut self, + decl: &vir_mid::type_decl::Enum, + ) -> SpannedEncodingResult { + let call = self.discriminant_frac_ref_snap()?; + self.inner.create_discriminant_snapshot_equality(decl, call) + } + + fn discriminant_frac_ref_snap( + &mut self, + ) -> SpannedEncodingResult< + impl Fn( + &mut FunctionDeclBuilder, + &vir_mid::type_decl::Enum, + vir_low::Expression, + vir_low::Expression, + ) -> SpannedEncodingResult, + > { + let target_slice_len = self.slice_len_expression()?; + let lifetime: vir_low::Expression = self.reference_lifetime.clone().into(); + let lifetime = std::rc::Rc::new(lifetime); + Ok( + move |builder: &mut FunctionDeclBuilder, + decl: &vir_mid::type_decl::Enum, + discriminant_place, + discriminant_address| { + builder.lowerer.frac_ref_snap( + CallContext::BuiltinMethod, + &decl.discriminant_type, + &decl.discriminant_type, + discriminant_place, + discriminant_address, + (*lifetime).clone(), + target_slice_len.clone(), + builder.position, + ) + }, + ) + } + + pub(in super::super::super) fn create_variant_snapshot_equality( + &mut self, + discriminant_value: vir_mid::DiscriminantValue, + variant: &vir_mid::type_decl::Struct, + ) -> SpannedEncodingResult<(vir_low::Expression, vir_low::Expression)> { + let call = self.variant_frac_ref_snap()?; + self.inner + .create_variant_snapshot_equality(discriminant_value, variant, call) + } + + fn variant_frac_ref_snap( + &mut self, + ) -> SpannedEncodingResult< + impl Fn( + &mut FunctionDeclBuilder, + &vir_mid::Type, + vir_low::Expression, + vir_low::Expression, + ) -> SpannedEncodingResult, + > { + let target_slice_len = self.slice_len_expression()?; + let lifetime: vir_low::Expression = self.reference_lifetime.clone().into(); + let lifetime = std::rc::Rc::new(lifetime); + Ok( + move |builder: &mut FunctionDeclBuilder, + variant_type: &vir_mid::Type, + variant_place, + variant_address| { + builder.lowerer.frac_ref_snap( + CallContext::BuiltinMethod, + variant_type, + // Enum variant and enum have the same set of lifetime parameters, + // so we use type_decl here. We cannot use `variant_type` because + // `ty` is normalized. + builder.type_decl, + variant_place, + variant_address, + (*lifetime).clone(), + target_slice_len.clone(), + builder.position, + ) + }, + ) + } + + // // FIXME: Code duplication. + // pub(in super::super::super) fn add_structural_invariant2( + // &mut self, + // decl: &vir_mid::type_decl::Struct, + // ) -> SpannedEncodingResult<()> { + // if let Some(invariant) = decl.structural_invariant.clone() { + // let mut regular_field_arguments = Vec::new(); + // for field in &decl.fields { + // let frac_ref_call = self.field_frac_ref_snap()?; + // let snap_call = self.inner.create_field_snap_call(field, frac_ref_call)?; + // regular_field_arguments.push(snap_call); + // // regular_field_arguments.push(self.create_field_snap_call(field)?); + // } + // let result = self.inner.result()?; + // let deref_fields = self + // .inner + // .lowerer + // .structural_invariant_to_deref_fields(&invariant)?; + // let mut constructor_encoder = AssertionToSnapshotConstructor::for_function_body( + // PredicateKind::FracRef { + // lifetime: self.reference_lifetime.clone().into(), + // }, + // self.inner.ty, + // regular_field_arguments, + // decl.fields.clone(), + // deref_fields, + // self.inner.position, + // ); + // let invariant_expression = invariant.into_iter().conjoin(); + // let permission_expression = invariant_expression.convert_into_permission_expression(); + // let constructor = constructor_encoder + // .expression_to_snapshot_constructor(self.inner.lowerer, &permission_expression)?; + // self.add_unfolding_postcondition(vir_low::Expression::equals( + // result.into(), + // constructor, + // ))?; + // } + // Ok(()) + // } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/frac_ref/function_range_decl.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/frac_ref/function_range_decl.rs new file mode 100644 index 00000000000..ed2db858d94 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/frac_ref/function_range_decl.rs @@ -0,0 +1,231 @@ +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::{ + builtin_methods::CallContext, + lifetimes::LifetimesInterface, + lowerer::Lowerer, + permissions::PermissionsInterface, + places::PlacesInterface, + predicates::{ + owned::builders::common::function_decl::FunctionDeclBuilder, PredicatesOwnedInterface, + }, + snapshots::{IntoPureSnapshot, IntoSnapshot}, + type_layouts::TypeLayoutsInterface, + }, +}; +use vir_crate::{ + common::identifier::WithIdentifier, + low::{self as vir_low}, + middle::{self as vir_mid}, +}; + +// FIXME: Code duplication with UniqueRefCurrentRangeSnapFunctionBuilder +pub(in super::super::super) struct FracRefRangeSnapFunctionBuilder<'l, 'p, 'v, 'tcx> { + inner: FunctionDeclBuilder<'l, 'p, 'v, 'tcx>, + address: vir_low::VariableDecl, + start_index: vir_low::VariableDecl, + end_index: vir_low::VariableDecl, + reference_lifetime: vir_low::VariableDecl, + slice_len: Option, + pres: Vec, + posts: Vec, +} + +impl<'l, 'p, 'v, 'tcx> FracRefRangeSnapFunctionBuilder<'l, 'p, 'v, 'tcx> { + pub(in super::super::super) fn new( + lowerer: &'l mut Lowerer<'p, 'v, 'tcx>, + ty: &'l vir_mid::Type, + type_decl: &'l vir_mid::TypeDecl, + ) -> SpannedEncodingResult { + let slice_len = if ty.is_slice() { + Some(vir_mid::VariableDecl::new( + "slice_len", + lowerer.size_type_mid()?, + )) + } else { + None + }; + Ok(Self { + address: vir_low::VariableDecl::new("address", ty.to_snapshot(lowerer)?), + start_index: vir_low::VariableDecl::new("start_index", lowerer.size_type()?), + end_index: vir_low::VariableDecl::new("end_index", lowerer.size_type()?), + reference_lifetime: vir_low::VariableDecl::new("lifetime", lowerer.lifetime_type()?), + slice_len, + inner: FunctionDeclBuilder::new( + lowerer, + "snap_frac_ref_range_aliased", + ty, + type_decl, + Default::default(), + )?, + pres: Vec::new(), + posts: Vec::new(), + }) + } + + pub(in super::super::super) fn build(mut self) -> SpannedEncodingResult { + let return_type = self.inner.range_result_type()?; + let function = vir_low::FunctionDecl { + name: format!( + "{}${}", + self.inner.function_name, + self.inner.ty.get_identifier() + ), + kind: vir_low::FunctionKind::SnapRange, + parameters: self.inner.parameters, + body: None, + pres: self.pres, + posts: self.posts, + return_type, + }; + Ok(function) + } + + // fn result_type(&mut self) -> SpannedEncodingResult { + // let vir_mid::Type::Pointer(pointer_type) = self.inner.ty else { + // unreachable!("{} must be a pointer type", self.inner.ty); + // }; + // let element_type = pointer_type.target_type.to_snapshot(self.inner.lowerer)?; + // let return_type = vir_low::Type::seq(element_type); + // Ok(return_type) + // } + + // fn result(&mut self) -> SpannedEncodingResult { + // Ok(vir_low::VariableDecl::result_variable(self.result_type()?)) + // } + + pub(in super::super::super) fn create_parameters(&mut self) -> SpannedEncodingResult<()> { + self.inner.parameters.push(self.address.clone()); + self.inner.parameters.push(self.start_index.clone()); + self.inner.parameters.push(self.end_index.clone()); + self.inner.parameters.push(self.reference_lifetime.clone()); + self.inner.create_lifetime_parameters()?; + if let Some(slice_len_mid) = &self.slice_len { + let slice_len = slice_len_mid.to_pure_snapshot(self.inner.lowerer)?; + self.inner.parameters.push(slice_len); + } + self.inner.create_const_parameters()?; + Ok(()) + } + + pub(in super::super::super) fn add_owned_precondition(&mut self) -> SpannedEncodingResult<()> { + let wildcard_permission = self.inner.lowerer.wildcard_permission()?; + let predicates = self.inner.lowerer.frac_ref_range( + CallContext::BuiltinMethod, + self.inner.ty, + self.inner.type_decl, + self.address.clone().into(), + self.start_index.clone().into(), + self.end_index.clone().into(), + self.reference_lifetime.clone().into(), + Some(wildcard_permission), + self.inner.position, + )?; + self.pres.push(predicates); + Ok(()) + } + + pub(in super::super::super) fn add_postcondition(&mut self) -> SpannedEncodingResult<()> { + // use vir_low::macros::*; + // let size_type = self.inner.lowerer.size_type_mid()?; + // var_decls! { + // index: Int + // } + // let vir_mid::Type::Pointer(ty) = self.inner.ty else { + // unreachable!() + // }; + // let initial_address = self.inner.lowerer.pointer_address( + // self.inner.ty, + // self.address.clone().into(), + // self.inner.position, + // )?; + // let vir_mid::Type::Pointer(pointer_type) = self.inner.ty else { + // unreachable!() + // }; + // let size = self + // .inner + // .lowerer + // .encode_type_size_expression2(&pointer_type.target_type, &*pointer_type.target_type)?; + // let element_place = self + // .inner + // .lowerer + // .place_option_none_constructor(self.inner.position)?; + // let element_address = self.inner.lowerer.address_offset( + // size, + // initial_address, + // index.clone().into(), + // self.inner.position, + // )?; + // let TODO_target_slice_len = None; + // let snap_call = self.inner.lowerer.frac_ref_snap( + // CallContext::BuiltinMethod, + // &ty.target_type, + // &*ty.target_type, + // element_place, + // element_address.clone(), + // self.reference_lifetime.clone().into(), + // TODO_target_slice_len, + // self.inner.position, + // )?; + // let result_type = self.result_type()?; + // let result = self.result()?; + // let start_index = self.inner.lowerer.obtain_constant_value( + // &size_type, + // self.start_index.clone().into(), + // self.inner.position, + // )?; + // let end_index = self.inner.lowerer.obtain_constant_value( + // &size_type, + // self.end_index.clone().into(), + // self.inner.position, + // )?; + // let result_len = vir_low::Expression::container_op( + // vir_low::ContainerOpKind::SeqLen, + // result_type.clone(), + // vec![result.clone().into()], + // self.inner.position, + // ); + // let index_diff = vir_low::Expression::subtract(end_index.clone(), start_index.clone()); + // self.posts.push(expr!([result_len] == [index_diff])); + // let element_snap = vir_low::Expression::container_op( + // vir_low::ContainerOpKind::SeqIndex, + // result_type, + // vec![ + // result.into(), + // vir_low::Expression::subtract(index.clone().into(), start_index.clone()), + // ], + // self.inner.position, + // ); + // let body = expr!( + // (([start_index] <= index) && (index < [end_index])) ==> + // ([snap_call] == [element_snap]) + // ); + // let expression = vir_low::Expression::forall( + // vec![index], + // vec![vir_low::Trigger::new(vec![element_address])], + // body, + // ); + // self.posts.push(expression); + self.inner.create_range_postcondition( + &mut self.posts, + &self.address, + &self.start_index, + &self.end_index, + |lowerer, ty, element_address, position| { + let element_place = lowerer.place_option_none_constructor(position)?; + let TODO_target_slice_len = None; + lowerer.frac_ref_snap( + CallContext::BuiltinMethod, + ty, + ty, + element_place, + element_address, + self.reference_lifetime.clone().into(), + TODO_target_slice_len, + position, + ) + }, + )?; + Ok(()) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/frac_ref/function_range_use.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/frac_ref/function_range_use.rs new file mode 100644 index 00000000000..6b5313bf37f --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/frac_ref/function_range_use.rs @@ -0,0 +1,93 @@ +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::{ + builtin_methods::CallContext, lowerer::Lowerer, + predicates::owned::builders::common::function_use::FunctionCallBuilder, + snapshots::IntoSnapshot, + }, +}; +use vir_crate::{ + common::identifier::WithIdentifier, + low::{self as vir_low}, + middle::{ + self as vir_mid, + operations::{const_generics::WithConstArguments, lifetimes::WithLifetimes}, + }, +}; + +// FIXME: Code identical to `UniqueRefCurrentRangeSnapCallBuilder`. +pub(in super::super::super::super::super) struct FracRefRangeSnapCallBuilder<'l, 'p, 'v, 'tcx, G> +where + G: WithLifetimes + WithConstArguments, +{ + inner: FunctionCallBuilder<'l, 'p, 'v, 'tcx, G>, +} + +impl<'l, 'p, 'v, 'tcx, G> FracRefRangeSnapCallBuilder<'l, 'p, 'v, 'tcx, G> +where + G: WithLifetimes + WithConstArguments, +{ + pub(in super::super::super::super::super) fn new( + lowerer: &'l mut Lowerer<'p, 'v, 'tcx>, + context: CallContext, + ty: &'l vir_mid::Type, + generics: &'l G, + address: vir_low::Expression, + start_index: vir_low::Expression, + end_index: vir_low::Expression, + reference_lifetime: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult { + let arguments = vec![address, start_index, end_index, reference_lifetime]; + let inner = FunctionCallBuilder::new( + lowerer, + "snap_frac_ref_range_aliased", + context, + ty, + generics, + arguments, + position, + )?; + Ok(Self { inner }) + } + + pub(in super::super::super::super::super) fn build( + self, + ) -> SpannedEncodingResult { + let vir_mid::Type::Pointer(pointer_type) = self.inner.ty else { + unreachable!("{} must be a pointer type", self.inner.ty); + }; + let element_type = pointer_type.target_type.to_snapshot(self.inner.lowerer)?; + let return_type = vir_low::Type::seq(element_type); + let call = vir_low::Expression::function_call( + format!( + "{}${}", + self.inner.function_name, + self.inner.ty.get_identifier() + ), + self.inner.arguments, + return_type, + ); + Ok(call.set_default_position(self.inner.position)) + } + + // pub(in super::super::super::super::super) fn add_custom_argument( + // &mut self, + // argument: vir_low::Expression, + // ) -> SpannedEncodingResult<()> { + // self.inner.arguments.push(argument); + // Ok(()) + // } + + pub(in super::super::super::super::super) fn add_lifetime_arguments( + &mut self, + ) -> SpannedEncodingResult<()> { + self.inner.add_lifetime_arguments() + } + + pub(in super::super::super::super::super) fn add_const_arguments( + &mut self, + ) -> SpannedEncodingResult<()> { + self.inner.add_const_arguments() + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/frac_ref/function_use.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/frac_ref/function_use.rs new file mode 100644 index 00000000000..f50e3ba941b --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/frac_ref/function_use.rs @@ -0,0 +1,65 @@ +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::{ + builtin_methods::CallContext, lowerer::Lowerer, + predicates::owned::builders::common::function_use::FunctionCallBuilder, + }, +}; +use vir_crate::{ + low::{self as vir_low}, + middle::{ + self as vir_mid, + operations::{const_generics::WithConstArguments, lifetimes::WithLifetimes}, + }, +}; + +pub(in super::super::super) struct FracRefSnapCallBuilder<'l, 'p, 'v, 'tcx, G> +where + G: WithLifetimes + WithConstArguments, +{ + inner: FunctionCallBuilder<'l, 'p, 'v, 'tcx, G>, +} + +impl<'l, 'p, 'v, 'tcx, G> FracRefSnapCallBuilder<'l, 'p, 'v, 'tcx, G> +where + G: WithLifetimes + WithConstArguments, +{ + pub(in super::super::super) fn new( + lowerer: &'l mut Lowerer<'p, 'v, 'tcx>, + context: CallContext, + ty: &'l vir_mid::Type, + generics: &'l G, + place: vir_low::Expression, + address: vir_low::Expression, + reference_lifetime: vir_low::Expression, + target_slice_len: Option, + ) -> SpannedEncodingResult { + let mut arguments = vec![place, address, reference_lifetime]; + if let Some(len) = target_slice_len { + arguments.push(len); + } + let name = "snap_current_frac_ref"; + let inner = FunctionCallBuilder::new( + lowerer, + name, + context, + ty, + generics, + arguments, + Default::default(), + )?; + Ok(Self { inner }) + } + + pub(in super::super::super) fn build(self) -> SpannedEncodingResult { + self.inner.build() + } + + pub(in super::super::super) fn add_lifetime_arguments(&mut self) -> SpannedEncodingResult<()> { + self.inner.add_lifetime_arguments() + } + + pub(in super::super::super) fn add_const_arguments(&mut self) -> SpannedEncodingResult<()> { + self.inner.add_const_arguments() + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/frac_ref/mod.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/frac_ref/mod.rs index ef427252419..0bc824c1cbb 100644 --- a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/frac_ref/mod.rs +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/frac_ref/mod.rs @@ -1,2 +1,7 @@ +pub(super) mod function_decl; +pub(super) mod function_use; +pub(super) mod function_range_decl; +pub(super) mod function_range_use; pub(super) mod predicate_decl; pub(super) mod predicate_use; +pub(super) mod predicate_range_use; diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/frac_ref/predicate_decl.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/frac_ref/predicate_decl.rs index 1a6745c0f26..b5566f4250f 100644 --- a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/frac_ref/predicate_decl.rs +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/frac_ref/predicate_decl.rs @@ -6,11 +6,15 @@ use crate::encoder::{ lifetimes::LifetimesInterface, lowerer::Lowerer, places::PlacesInterface, - predicates::owned::builders::{ - common::predicate_decl::PredicateDeclBuilder, PredicateDeclBuilderMethods, + predicates::{ + owned::builders::{ + common::predicate_decl::{ContainingPredicateKind, PredicateDeclBuilder}, + PredicateDeclBuilderMethods, + }, + PredicatesOwnedInterface, }, snapshots::{ - IntoPureSnapshot, IntoSnapshot, SnapshotValidityInterface, SnapshotValuesInterface, + IntoPureSnapshot, PredicateKind, SnapshotValidityInterface, SnapshotValuesInterface, }, type_layouts::TypeLayoutsInterface, }, @@ -21,13 +25,11 @@ use vir_crate::{ middle as vir_mid, }; -use super::predicate_use::FracRefUseBuilder; - pub(in super::super::super) struct FracRefBuilder<'l, 'p, 'v, 'tcx> { inner: PredicateDeclBuilder<'l, 'p, 'v, 'tcx>, - place: vir_low::VariableDecl, - root_address: vir_low::VariableDecl, - current_snapshot: vir_low::VariableDecl, + // place: vir_low::VariableDecl, + // address: vir_low::VariableDecl, + // current_snapshot: vir_low::VariableDecl, reference_lifetime: vir_low::VariableDecl, slice_len: Option, } @@ -55,12 +57,12 @@ impl<'l, 'p, 'v, 'tcx> FracRefBuilder<'l, 'p, 'v, 'tcx> { None }; Ok(Self { - place: vir_low::VariableDecl::new("place", lowerer.place_type()?), - root_address: vir_low::VariableDecl::new("root_address", lowerer.address_type()?), - current_snapshot: vir_low::VariableDecl::new( - "current_snapshot", - ty.to_snapshot(lowerer)?, - ), + // place: vir_low::VariableDecl::new("place", lowerer.place_type()?), + // address: vir_low::VariableDecl::new("address", lowerer.address_type()?), + // current_snapshot: vir_low::VariableDecl::new( + // "current_snapshot", + // ty.to_snapshot(lowerer)?, + // ), reference_lifetime: vir_low::VariableDecl::new( "reference_lifetime", lowerer.lifetime_type()?, @@ -68,7 +70,7 @@ impl<'l, 'p, 'v, 'tcx> FracRefBuilder<'l, 'p, 'v, 'tcx> { slice_len, inner: PredicateDeclBuilder::new( lowerer, - "FracRef2", + "FracRef", ty, type_decl, Default::default(), @@ -81,9 +83,9 @@ impl<'l, 'p, 'v, 'tcx> FracRefBuilder<'l, 'p, 'v, 'tcx> { } pub(in super::super::super) fn create_parameters(&mut self) -> SpannedEncodingResult<()> { - self.inner.parameters.push(self.place.clone()); - self.inner.parameters.push(self.root_address.clone()); - self.inner.parameters.push(self.current_snapshot.clone()); + self.inner.parameters.push(self.inner.place.clone()); + self.inner.parameters.push(self.inner.address.clone()); + // self.inner.parameters.push(self.current_snapshot.clone()); self.inner.parameters.push(self.reference_lifetime.clone()); self.inner.create_lifetime_parameters()?; if let Some(slice_len_mid) = &self.slice_len { @@ -94,9 +96,9 @@ impl<'l, 'p, 'v, 'tcx> FracRefBuilder<'l, 'p, 'v, 'tcx> { Ok(()) } - pub(in super::super::super) fn add_validity(&mut self) -> SpannedEncodingResult<()> { - self.inner.add_validity(&self.current_snapshot) - } + // pub(in super::super::super) fn add_validity(&mut self) -> SpannedEncodingResult<()> { + // self.inner.add_validity(&self.current_snapshot) + // } pub(in super::super::super) fn add_field_predicate( &mut self, @@ -105,28 +107,46 @@ impl<'l, 'p, 'v, 'tcx> FracRefBuilder<'l, 'p, 'v, 'tcx> { let field_place = self.inner.lowerer.encode_field_place( self.inner.ty, field, - self.place.clone().into(), + self.inner.place.clone().into(), self.inner.position, )?; - let current_field_snapshot = self.inner.lowerer.obtain_struct_field_snapshot( + let field_address = self.inner.lowerer.encode_field_address( self.inner.ty, field, - self.current_snapshot.clone().into(), - Default::default(), + self.inner.address.clone().into(), + self.inner.position, )?; - let mut builder = FracRefUseBuilder::new( - self.inner.lowerer, + // let current_field_snapshot = self.inner.lowerer.obtain_struct_field_snapshot( + // self.inner.ty, + // field, + // self.current_snapshot.clone().into(), + // Default::default(), + // )?; + // let mut builder = FracRefUseBuilder::new( + // self.inner.lowerer, + // CallContext::BuiltinMethod, + // &field.ty, + // &field.ty, + // field_place, + // self.inner.address.clone().into(), + // // current_field_snapshot, + // self.reference_lifetime.clone().into(), + // )?; + // builder.add_lifetime_arguments()?; + // builder.add_const_arguments()?; + // let expression = builder.build(); + let TODO_target_slice_len = None; + let expression = self.inner.lowerer.frac_ref( CallContext::BuiltinMethod, &field.ty, &field.ty, field_place, - self.root_address.clone().into(), - current_field_snapshot, + field_address, self.reference_lifetime.clone().into(), + TODO_target_slice_len, + None, + self.inner.position, )?; - builder.add_lifetime_arguments()?; - builder.add_const_arguments()?; - let expression = builder.build(); self.inner.add_conjunct(expression) } @@ -138,43 +158,73 @@ impl<'l, 'p, 'v, 'tcx> FracRefBuilder<'l, 'p, 'v, 'tcx> { let discriminant_place = self.inner.lowerer.encode_field_place( self.inner.ty, &discriminant_field, - self.place.clone().into(), + self.inner.place.clone().into(), self.inner.position, )?; - let current_discriminant_call = self.inner.lowerer.obtain_enum_discriminant( - self.current_snapshot.clone().into(), + let discriminant_address = self.inner.lowerer.encode_field_address( self.inner.ty, + &discriminant_field, + self.inner.address.clone().into(), self.inner.position, )?; - let current_discriminant_snapshot = self.inner.lowerer.construct_constant_snapshot( - &decl.discriminant_type, - current_discriminant_call, - self.inner.position, - )?; - let builder = FracRefUseBuilder::new( - self.inner.lowerer, + // let current_discriminant_call = self.inner.lowerer.obtain_enum_discriminant( + // self.current_snapshot.clone().into(), + // self.inner.ty, + // self.inner.position, + // )?; + // let current_discriminant_snapshot = self.inner.lowerer.construct_constant_snapshot( + // &decl.discriminant_type, + // current_discriminant_call, + // self.inner.position, + // )?; + // let builder = FracRefUseBuilder::new( + // self.inner.lowerer, + // CallContext::BuiltinMethod, + // &decl.discriminant_type, + // &decl.discriminant_type, + // discriminant_place, + // self.inner.address.clone().into(), + // // current_discriminant_snapshot, + // self.reference_lifetime.clone().into(), + // )?; + // let expression = builder.build(); + let expression = self.inner.lowerer.frac_ref( CallContext::BuiltinMethod, &decl.discriminant_type, &decl.discriminant_type, discriminant_place, - self.root_address.clone().into(), - current_discriminant_snapshot, + discriminant_address, self.reference_lifetime.clone().into(), + None, + None, + self.inner.position, )?; - let expression = builder.build(); self.inner.add_conjunct(expression) } + pub(in super::super::super) fn add_frac_ref_pointer_predicate( + &mut self, + lifetime: &vir_mid::ty::LifetimeConst, + ) -> SpannedEncodingResult { + let place = self.inner.place.clone(); + let address = self.inner.address.clone(); + self.inner + .add_frac_ref_pointer_predicate(lifetime, place, address) + } + pub(in super::super::super) fn add_frac_ref_target_predicate( &mut self, target_type: &vir_mid::Type, lifetime: &vir_mid::ty::LifetimeConst, ) -> SpannedEncodingResult<()> { + let place = self.inner.place.clone(); + let address = self.inner.address.clone(); self.inner.add_frac_ref_target_predicate( target_type, lifetime, - &self.place, - &self.current_snapshot, + place.into(), + address, + ContainingPredicateKind::FracRef, ) } @@ -184,14 +234,14 @@ impl<'l, 'p, 'v, 'tcx> FracRefBuilder<'l, 'p, 'v, 'tcx> { Ok(self.slice_len.as_ref().unwrap().clone()) } - pub(in super::super::super) fn add_snapshot_len_equal_to( - &mut self, - array_length_mid: &vir_mid::VariableDecl, - ) -> SpannedEncodingResult<()> { - self.inner - .add_snapshot_len_equal_to(&self.current_snapshot, array_length_mid)?; - Ok(()) - } + // pub(in super::super::super) fn add_snapshot_len_equal_to( + // &mut self, + // array_length_mid: &vir_mid::VariableDecl, + // ) -> SpannedEncodingResult<()> { + // self.inner + // .add_snapshot_len_equal_to(&self.current_snapshot, array_length_mid)?; + // Ok(()) + // } pub(in super::super::super) fn add_quantified_permission( &mut self, @@ -216,28 +266,46 @@ impl<'l, 'p, 'v, 'tcx> FracRefBuilder<'l, 'p, 'v, 'tcx> { let array_length_int = self.inner.array_length_int(array_length_mid)?; let element_place = self.inner.lowerer.encode_index_place( self.inner.ty, - self.place.clone().into(), + self.inner.place.clone().into(), index.clone().into(), self.inner.position, )?; - let current_element_snapshot = self.inner.lowerer.obtain_array_element_snapshot( - self.current_snapshot.clone().into(), - index_int.clone(), + let element_address = self.inner.lowerer.encode_index_address( + self.inner.ty, + self.inner.address.clone().into(), + index.clone().into(), self.inner.position, )?; - let mut builder = FracRefUseBuilder::new( - self.inner.lowerer, + // let current_element_snapshot = self.inner.lowerer.obtain_array_element_snapshot( + // self.current_snapshot.clone().into(), + // index_int.clone(), + // self.inner.position, + // )?; + // let mut builder = FracRefUseBuilder::new( + // self.inner.lowerer, + // CallContext::BuiltinMethod, + // element_type, + // element_type, + // element_place, + // self.inner.address.clone().into(), + // // current_element_snapshot, + // self.reference_lifetime.clone().into(), + // )?; + // builder.add_lifetime_arguments()?; + // builder.add_const_arguments()?; + // let element_predicate_acc = builder.build(); + let TODO_target_slice_len = None; + let element_predicate_acc = self.inner.lowerer.frac_ref( CallContext::BuiltinMethod, element_type, element_type, element_place, - self.root_address.clone().into(), - current_element_snapshot, + element_address, self.reference_lifetime.clone().into(), + TODO_target_slice_len, + None, + self.inner.position, )?; - builder.add_lifetime_arguments()?; - builder.add_const_arguments()?; - let element_predicate_acc = builder.build(); let elements = vir_low::Expression::forall( vec![index], vec![vir_low::Trigger::new(vec![element_predicate_acc.clone()])], @@ -251,16 +319,44 @@ impl<'l, 'p, 'v, 'tcx> FracRefBuilder<'l, 'p, 'v, 'tcx> { pub(in super::super::super) fn create_variant_predicate( &mut self, + decl: &vir_mid::type_decl::Enum, discriminant_value: vir_mid::DiscriminantValue, variant: &vir_mid::type_decl::Struct, variant_type: &vir_mid::Type, ) -> SpannedEncodingResult<(vir_low::Expression, vir_low::Expression)> { use vir_low::macros::*; - let discriminant_call = self.inner.lowerer.obtain_enum_discriminant( - self.current_snapshot.clone().into(), - self.inner.ty, - self.inner.position, - )?; + let discriminant_call = { + // FIXME: Code duplication with other create_variant_predicate methods. + let discriminant_field = decl.discriminant_field(); + let discriminant_place = self.inner.lowerer.encode_field_place( + self.inner.ty, + &discriminant_field, + self.inner.place.clone().into(), + self.inner.position, + )?; + let discriminant_address = self.inner.lowerer.encode_field_address( + self.inner.ty, + &discriminant_field, + self.inner.address.clone().into(), + self.inner.position, + )?; + let TODO_target_slice_len = None; + let discriminant_snapshot = self.inner.lowerer.frac_ref_snap( + CallContext::BuiltinMethod, + &decl.discriminant_type, + &decl.discriminant_type, + discriminant_place, + discriminant_address, + self.reference_lifetime.clone().into(), + TODO_target_slice_len, + self.inner.position, + )?; + self.inner.lowerer.obtain_constant_value( + &decl.discriminant_type, + discriminant_snapshot, + self.inner.position, + )? + }; let guard = expr! { [ discriminant_call ] == [ discriminant_value.into() ] }; @@ -268,28 +364,46 @@ impl<'l, 'p, 'v, 'tcx> FracRefBuilder<'l, 'p, 'v, 'tcx> { let variant_place = self.inner.lowerer.encode_enum_variant_place( self.inner.ty, &variant_index, - self.place.clone().into(), + self.inner.place.clone().into(), self.inner.position, )?; - let current_variant_snapshot = self.inner.lowerer.obtain_enum_variant_snapshot( + let variant_address = self.inner.lowerer.encode_enum_variant_address( self.inner.ty, &variant_index, - self.current_snapshot.clone().into(), + self.inner.address.clone().into(), self.inner.position, )?; - let mut builder = FracRefUseBuilder::new( - self.inner.lowerer, + // let current_variant_snapshot = self.inner.lowerer.obtain_enum_variant_snapshot( + // self.inner.ty, + // &variant_index, + // self.current_snapshot.clone().into(), + // self.inner.position, + // )?; + // let mut builder = FracRefUseBuilder::new( + // self.inner.lowerer, + // CallContext::BuiltinMethod, + // variant_type, + // variant_type, + // variant_place, + // self.inner.address.clone().into(), + // // current_variant_snapshot, + // self.reference_lifetime.clone().into(), + // )?; + // builder.add_lifetime_arguments()?; + // builder.add_const_arguments()?; + // let predicate = builder.build(); + let TODO_target_slice_len = None; + let predicate = self.inner.lowerer.frac_ref( CallContext::BuiltinMethod, variant_type, variant_type, variant_place, - self.root_address.clone().into(), - current_variant_snapshot, + variant_address, self.reference_lifetime.clone().into(), + TODO_target_slice_len, + None, + self.inner.position, )?; - builder.add_lifetime_arguments()?; - builder.add_const_arguments()?; - let predicate = builder.build(); Ok((guard, predicate)) } @@ -300,4 +414,39 @@ impl<'l, 'p, 'v, 'tcx> FracRefBuilder<'l, 'p, 'v, 'tcx> { self.inner .add_conjunct(variant_predicates.into_iter().create_match()) } + + pub(in super::super::super) fn add_structural_invariant( + &mut self, + decl: &vir_mid::type_decl::Struct, + ) -> SpannedEncodingResult> { + self.inner.add_structural_invariant( + decl, + PredicateKind::FracRef { + lifetime: self.reference_lifetime.clone().into(), + }, + ) + } + + // pub(in super::super::super) fn add_structural_invariant( + // &mut self, + // decl: &vir_mid::type_decl::Struct, + // ) -> SpannedEncodingResult> { + // if let Some(invariant) = &decl.structural_invariant { + // let mut encoder = SelfFramingAssertionToSnapshot::for_predicate_body( + // self.inner.place.clone(), + // self.inner.address.clone(), + // PredicateKind::FracRef { + // lifetime: self.reference_lifetime.clone().into(), + // }, + // ); + // for assertion in invariant { + // let low_assertion = + // encoder.expression_to_snapshot(self.inner.lowerer, assertion, true)?; + // self.inner.add_conjunct(low_assertion)?; + // } + // Ok(encoder.into_created_predicate_types()) + // } else { + // Ok(Vec::new()) + // } + // } } diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/frac_ref/predicate_range_use.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/frac_ref/predicate_range_use.rs new file mode 100644 index 00000000000..eb9348467cf --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/frac_ref/predicate_range_use.rs @@ -0,0 +1,152 @@ +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::{ + addresses::AddressesInterface, builtin_methods::CallContext, lowerer::Lowerer, + places::PlacesInterface, pointers::PointersInterface, predicates::PredicatesOwnedInterface, + snapshots::SnapshotValuesInterface, type_layouts::TypeLayoutsInterface, + }, +}; + +use vir_crate::{ + common::expression::QuantifierHelpers, + low::{self as vir_low}, + middle::{ + self as vir_mid, + operations::{const_generics::WithConstArguments, lifetimes::WithLifetimes}, + }, +}; + +// FIXME: Identical code with `UniqueRefRangeUseBuilder`. +pub(in super::super::super::super::super) struct FracRefRangeUseBuilder<'l, 'p, 'v, 'tcx, G> +where + G: WithLifetimes + WithConstArguments, +{ + lowerer: &'l mut Lowerer<'p, 'v, 'tcx>, + context: CallContext, + ty: &'l vir_mid::Type, + generics: &'l G, + address: vir_low::Expression, + start_index: vir_low::Expression, + end_index: vir_low::Expression, + lifetime: vir_low::Expression, + permission_amount: Option, + position: vir_low::Position, +} + +impl<'l, 'p, 'v, 'tcx, G> FracRefRangeUseBuilder<'l, 'p, 'v, 'tcx, G> +where + G: WithLifetimes + WithConstArguments, +{ + pub(in super::super::super::super::super) fn new( + lowerer: &'l mut Lowerer<'p, 'v, 'tcx>, + context: CallContext, + ty: &'l vir_mid::Type, + generics: &'l G, + address: vir_low::Expression, + start_index: vir_low::Expression, + end_index: vir_low::Expression, + lifetime: vir_low::Expression, + permission_amount: Option, + position: vir_low::Position, + ) -> SpannedEncodingResult { + Ok(Self { + lowerer, + context, + ty, + generics, + address, + start_index, + end_index, + lifetime, + permission_amount, + position, + }) + } + + pub(in super::super::super::super::super) fn build( + self, + ) -> SpannedEncodingResult { + use vir_low::macros::*; + let size_type = self.lowerer.size_type_mid()?; + // var_decls! { + // index: Int + // } + let vir_mid::Type::Pointer(ty) = self.ty else { + unreachable!() + }; + let initial_address = self + .lowerer + .pointer_address(self.ty, self.address, self.position)?; + // let vir_mid::Type::Pointer(pointer_type) = self.ty else { + // unreachable!() + // }; + let size = self + .lowerer + .encode_type_size_expression2(&ty.target_type, &*ty.target_type)?; + // let element_address = self.lowerer.address_offset( + // size, + // initial_address, + // index.clone().into(), + // self.position, + // )?; + let element_place = self.lowerer.place_option_none_constructor(self.position)?; + // let TODO_target_slice_len = None; + // let predicate = self.lowerer.frac_ref( + // self.context, + // &ty.target_type, + // self.generics, + // element_place, + // element_address.clone(), + // self.lifetime, + // TODO_target_slice_len, + // self.permission_amount, + // self.position, + // )?; + let start_index = + self.lowerer + .obtain_constant_value(&size_type, self.start_index, self.position)?; + let end_index = + self.lowerer + .obtain_constant_value(&size_type, self.end_index, self.position)?; + // let body = expr!( + // (([start_index] <= index) && (index < [end_index])) ==> [predicate] + // ); + // let expression = vir_low::Expression::forall( + // vec![index], + // vec![vir_low::Trigger::new(vec![element_address])], + // body, + // ); + // Ok(expression) + + var_decls! { + element_address: Address + } + let TODO_target_slice_len = None; + let predicate = self.lowerer.frac_ref( + self.context, + &ty.target_type, + self.generics, + element_place.clone(), + element_address.clone().into(), + self.lifetime, + TODO_target_slice_len, + self.permission_amount, + self.position, + )?; + let guard = self.lowerer.address_range_contains( + initial_address, + start_index, + end_index, + size, + element_address.clone().into(), + self.position, + )?; + let body = expr!([guard] ==> [predicate.clone()]); + let expression = vir_low::Expression::forall( + vec![element_address], + vec![vir_low::Trigger::new(vec![predicate])], + body, + ); + Ok(expression) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/frac_ref/predicate_use.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/frac_ref/predicate_use.rs index 04f8bb3dd27..94e101eb93a 100644 --- a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/frac_ref/predicate_use.rs +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/frac_ref/predicate_use.rs @@ -3,7 +3,6 @@ use crate::encoder::{ middle::core_proof::{ builtin_methods::CallContext, lowerer::Lowerer, predicates::owned::builders::common::predicate_use::PredicateUseBuilder, - snapshots::SnapshotValuesInterface, type_layouts::TypeLayoutsInterface, }, }; use vir_crate::{ @@ -19,7 +18,7 @@ where G: WithLifetimes + WithConstArguments, { inner: PredicateUseBuilder<'l, 'p, 'v, 'tcx, G>, - current_snapshot: vir_low::Expression, + target_slice_len: Option, } impl<'l, 'p, 'v, 'tcx, G> FracRefUseBuilder<'l, 'p, 'v, 'tcx, G> @@ -33,27 +32,28 @@ where ty: &'l vir_mid::Type, generics: &'l G, place: vir_low::Expression, - root_address: vir_low::Expression, - current_snapshot: vir_low::Expression, + address: vir_low::Expression, lifetime: vir_low::Expression, + target_slice_len: Option, + position: vir_low::Position, ) -> SpannedEncodingResult { + let mut arguments = vec![place, address, lifetime]; + if let Some(len) = target_slice_len.clone() { + arguments.push(len); + } let inner = PredicateUseBuilder::new( - lowerer, - "FracRef2", - context, - ty, - generics, - vec![place, root_address, current_snapshot.clone(), lifetime], - Default::default(), + lowerer, "FracRef", context, ty, generics, arguments, position, )?; Ok(Self { inner, - current_snapshot, + target_slice_len, }) } - pub(in super::super::super::super::super) fn build(self) -> vir_low::Expression { - self.inner.build() + pub(in super::super::super::super::super) fn build( + self, + ) -> SpannedEncodingResult { + Ok(self.inner.build()) } pub(in super::super::super::super::super) fn add_lifetime_arguments( @@ -66,18 +66,26 @@ where &mut self, ) -> SpannedEncodingResult<()> { if self.inner.ty.is_slice() { - let snapshot_length = self - .inner - .lowerer - .obtain_array_len_snapshot(self.current_snapshot.clone(), self.inner.position)?; - let size_type = self.inner.lowerer.size_type_mid()?; - let argument = self.inner.lowerer.construct_constant_snapshot( - &size_type, - snapshot_length, - self.inner.position, - )?; - self.inner.arguments.push(argument); + unimplemented!(); + // let snapshot_length = self + // .inner + // .lowerer + // .obtain_array_len_snapshot(self.current_snapshot.clone(), self.inner.position)?; + // let size_type = self.inner.lowerer.size_type_mid()?; + // let argument = self.inner.lowerer.construct_constant_snapshot( + // &size_type, + // snapshot_length, + // self.inner.position, + // )?; + // self.inner.arguments.push(argument); } self.inner.add_const_arguments() } + + pub(in super::super::super::super::super) fn set_maybe_permission_amount( + &mut self, + permission_amount: Option, + ) -> SpannedEncodingResult<()> { + self.inner.set_maybe_permission_amount(permission_amount) + } } diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/mod.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/mod.rs index f271de25200..03974891202 100644 --- a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/mod.rs +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/mod.rs @@ -1,15 +1,45 @@ mod common; mod frac_ref; mod owned_non_aliased; +mod owned_aliased; mod unique_ref; pub(super) use self::{ - common::predicate_decl::PredicateDeclBuilderMethods, frac_ref::predicate_decl::FracRefBuilder, - owned_non_aliased::predicate_decl::OwnedNonAliasedBuilder, - unique_ref::predicate_decl::UniqueRefBuilder, + common::predicate_decl::PredicateDeclBuilderMethods, + frac_ref::{ + function_decl::FracRefSnapFunctionBuilder, + function_range_decl::FracRefRangeSnapFunctionBuilder, function_use::FracRefSnapCallBuilder, + predicate_decl::FracRefBuilder, + }, + owned_aliased::function_range_decl::OwnedAliasedRangeSnapFunctionBuilder, + owned_non_aliased::{ + function_decl::OwnedNonAliasedSnapFunctionBuilder, predicate_decl::OwnedNonAliasedBuilder, + }, + unique_ref::{ + function_current_decl::UniqueRefCurrentSnapFunctionBuilder, + function_current_range_decl::UniqueRefCurrentRangeSnapFunctionBuilder, + function_current_use::UniqueRefCurrentSnapCallBuilder, + function_final_decl::UniqueRefFinalSnapFunctionBuilder, + function_final_range_decl::UniqueRefFinalRangeSnapFunctionBuilder, + function_final_use::UniqueRefFinalSnapCallBuilder, predicate_decl::UniqueRefBuilder, + }, }; pub(in super::super::super) use self::{ - frac_ref::predicate_use::FracRefUseBuilder, - owned_non_aliased::predicate_use::OwnedNonAliasedUseBuilder, - unique_ref::predicate_use::UniqueRefUseBuilder, + frac_ref::{ + function_range_use::FracRefRangeSnapCallBuilder, + predicate_range_use::FracRefRangeUseBuilder, predicate_use::FracRefUseBuilder, + }, + owned_aliased::{ + function_range_use::OwnedAliasedRangeSnapCallBuilder, + // function_use::OwnedAliasedSnapCallBuilder, + predicate_range_use::OwnedAliasedRangeUseBuilder, + }, + owned_non_aliased::{ + function_use::OwnedNonAliasedSnapCallBuilder, predicate_use::OwnedNonAliasedUseBuilder, + }, + unique_ref::{ + function_current_range_use::UniqueRefCurrentRangeSnapCallBuilder, + function_final_range_use::UniqueRefFinalRangeSnapCallBuilder, + predicate_range_use::UniqueRefRangeUseBuilder, predicate_use::UniqueRefUseBuilder, + }, }; diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/owned_aliased/function_decl.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/owned_aliased/function_decl.rs new file mode 100644 index 00000000000..eb14c709ada --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/owned_aliased/function_decl.rs @@ -0,0 +1,550 @@ +// use crate::encoder::{ +// errors::SpannedEncodingResult, +// middle::core_proof::{ +// addresses::AddressesInterface, +// builtin_methods::CallContext, +// lifetimes::LifetimesInterface, +// lowerer::Lowerer, +// places::PlacesInterface, +// predicates::{ +// owned::builders::common::function_decl::FunctionDeclBuilder, +// PredicatesMemoryBlockInterface, PredicatesOwnedInterface, +// }, +// references::ReferencesInterface, +// snapshots::{ +// IntoPureSnapshot, PredicateKind, SnapshotBytesInterface, SnapshotValidityInterface, +// SnapshotValuesInterface, +// }, +// type_layouts::TypeLayoutsInterface, +// }, +// }; + +// use vir_crate::{ +// common::{expression::QuantifierHelpers, position::Positioned}, +// low::{self as vir_low}, +// middle::{self as vir_mid}, +// }; + +// pub(in super::super::super) struct OwnedAliasedSnapFunctionBuilder<'l, 'p, 'v, 'tcx> { +// inner: FunctionDeclBuilder<'l, 'p, 'v, 'tcx>, +// // address: vir_low::VariableDecl, +// slice_len: Option, +// } + +// impl<'l, 'p, 'v, 'tcx> OwnedAliasedSnapFunctionBuilder<'l, 'p, 'v, 'tcx> { +// pub(in super::super::super) fn new( +// _lowerer: &'l mut Lowerer<'p, 'v, 'tcx>, +// _ty: &'l vir_mid::Type, +// _type_decl: &'l vir_mid::TypeDecl, +// ) -> SpannedEncodingResult { +// unimplemented!(); +// // let slice_len = if ty.is_slice() { +// // Some(vir_mid::VariableDecl::new( +// // "slice_len", +// // lowerer.size_type_mid()?, +// // )) +// // } else { +// // None +// // }; +// // Ok(Self { +// // // address: vir_low::VariableDecl::new("address", lowerer.address_type()?), +// // slice_len, +// // inner: FunctionDeclBuilder::new( +// // lowerer, +// // "snap_owned_aliased", +// // ty, +// // type_decl, +// // Default::default(), +// // )?, +// // }) +// } + +// pub(in super::super::super) fn get_snapshot_postconditions( +// &self, +// ) -> SpannedEncodingResult> { +// self.inner.get_snapshot_postconditions() +// } + +// pub(in super::super::super) fn get_snapshot_body( +// &self, +// ) -> SpannedEncodingResult> { +// self.inner.get_snapshot_body() +// } + +// pub(in super::super::super) fn build(self) -> SpannedEncodingResult { +// self.inner.build() +// } + +// pub(in super::super::super) fn create_parameters(&mut self) -> SpannedEncodingResult<()> { +// self.inner.parameters.push(self.inner.address.clone()); +// self.inner.create_lifetime_parameters()?; +// if let Some(slice_len_mid) = &self.slice_len { +// let slice_len = slice_len_mid.to_pure_snapshot(self.inner.lowerer)?; +// self.inner.parameters.push(slice_len); +// } +// self.inner.create_const_parameters()?; +// Ok(()) +// } + +// // FIXME: Code duplication. +// pub(in super::super::super) fn get_slice_len( +// &self, +// ) -> SpannedEncodingResult { +// Ok(self.slice_len.as_ref().unwrap().clone()) +// } + +// // fn owned_predicate( +// // &mut self, +// // ty: &vir_mid::Type, +// // generics: &G, +// // address: vir_low::Expression, +// // ) -> SpannedEncodingResult +// // where +// // G: WithLifetimes + WithConstArguments, +// // { +// // let mut builder = OwnedNonAliasedUseBuilder::new( +// // self.inner.lowerer, +// // CallContext::BuiltinMethod, +// // ty, +// // generics, +// // place, +// // root_address, +// // )?; +// // builder.add_lifetime_arguments()?; +// // builder.add_const_arguments()?; +// // builder.build() +// // } + +// // FIXME: Code duplication with add_quantified_permission. +// pub(in super::super::super) fn add_quantifiers( +// &mut self, +// array_length_mid: &vir_mid::VariableDecl, +// element_type: &vir_mid::Type, +// ) -> SpannedEncodingResult<()> { +// use vir_low::macros::*; +// let size_type_mid = self.inner.lowerer.size_type_mid()?; +// var_decls! { +// index_int: Int +// }; +// let index = self.inner.lowerer.construct_constant_snapshot( +// &size_type_mid, +// index_int.clone().into(), +// self.inner.position, +// )?; +// let index_validity = self +// .inner +// .lowerer +// .encode_snapshot_valid_call_for_type(index.clone(), &size_type_mid)?; +// let array_length_int = self.inner.array_length_int(array_length_mid)?; +// let element_address = self.inner.lowerer.encode_index_address( +// self.inner.ty, +// self.inner.address.clone().into(), +// index, +// self.inner.position, +// )?; +// let element_predicate_acc = { +// self.inner.lowerer.owned_aliased( +// CallContext::BuiltinMethod, +// element_type, +// element_type, +// element_address.clone(), +// None, +// self.inner.position, +// )? +// }; +// let result = self.inner.result()?.into(); +// let element_snapshot = self.inner.lowerer.obtain_array_element_snapshot( +// result, +// index_int.clone().into(), +// self.inner.position, +// )?; +// let element_snap_call = self.inner.lowerer.owned_aliased_snap( +// CallContext::BuiltinMethod, +// element_type, +// element_type, +// element_address, +// self.inner.position, +// )?; +// let elements = vir_low::Expression::forall( +// vec![index_int.clone()], +// vec![vir_low::Trigger::new(vec![element_predicate_acc])], +// expr! { +// ([index_validity] && (index_int < [array_length_int])) ==> +// ([element_snapshot] == [element_snap_call]) +// }, +// ); +// self.add_snapshot_body_postcondition(elements) +// } + +// pub(in super::super::super) fn add_snapshot_body_postcondition( +// &mut self, +// body: vir_low::Expression, +// ) -> SpannedEncodingResult<()> { +// // let predicate = self.precondition_predicate()?; +// // let unfolding = predicate.into_unfolding(body); +// // self.inner.add_postcondition(unfolding) +// self.inner.add_snapshot_body_postcondition(body) +// } + +// pub(in super::super::super) fn add_validity_postcondition( +// &mut self, +// ) -> SpannedEncodingResult<()> { +// self.inner.add_validity_postcondition() +// } + +// pub(in super::super::super) fn add_snapshot_len_equal_to_postcondition( +// &mut self, +// array_length_mid: &vir_mid::VariableDecl, +// ) -> SpannedEncodingResult<()> { +// self.inner +// .add_snapshot_len_equal_to_postcondition(array_length_mid) +// } + +// pub(in super::super::super) fn add_owned_precondition(&mut self) -> SpannedEncodingResult<()> { +// let predicate = self.precondition_predicate()?; +// self.inner.add_precondition(predicate) +// } + +// fn precondition_predicate(&mut self) -> SpannedEncodingResult { +// self.inner.lowerer.owned_aliased( +// CallContext::BuiltinMethod, +// self.inner.ty, +// self.inner.type_decl, +// self.inner.address.clone().into(), +// Some(vir_low::Expression::wildcard_permission()), +// self.inner.position, +// ) +// } + +// // fn compute_address(&self) -> SpannedEncodingResult { +// // use vir_low::macros::*; +// // let compute_address = ty!(Address); +// // let expression = expr! { +// // ComputeAddress::compute_address( +// // [self.place.clone().into()], +// // [self.root_address.clone().into()] +// // ) +// // }; +// // Ok(expression) +// // } + +// fn size_of(&mut self) -> SpannedEncodingResult { +// self.inner +// .lowerer +// .encode_type_size_expression2(self.inner.ty, self.inner.type_decl) +// } + +// // FIXME: Code duplication. +// fn add_bytes_snapshot_equality_with( +// &mut self, +// snap_ty: &vir_mid::Type, +// snapshot: vir_low::Expression, +// ) -> SpannedEncodingResult<()> { +// use vir_low::macros::*; +// let size_of = self.size_of()?; +// let bytes = self +// .inner +// .lowerer +// .encode_memory_block_bytes_expression(self.inner.address.clone().into(), size_of)?; +// let to_bytes = ty! { Bytes }; +// let expression = expr! { +// [bytes] == (Snap::to_bytes([snapshot])) +// }; +// self.add_snapshot_body_postcondition(expression) +// } + +// pub(in super::super::super) fn add_bytes_snapshot_equality( +// &mut self, +// ) -> SpannedEncodingResult<()> { +// let result = self.inner.result()?.into(); +// self.add_bytes_snapshot_equality_with(self.inner.ty, result) +// } + +// pub(in super::super::super) fn add_bytes_address_snapshot_equality( +// &mut self, +// ) -> SpannedEncodingResult<()> { +// let result = self.inner.result()?.into(); +// let address_type = self.inner.lowerer.reference_address_type(self.inner.ty)?; +// self.inner +// .lowerer +// .encode_snapshot_to_bytes_function(&address_type)?; +// let target_address_snapshot = self.inner.lowerer.reference_address_snapshot( +// self.inner.ty, +// result, +// self.inner.position, +// )?; +// self.add_bytes_snapshot_equality_with(&address_type, target_address_snapshot) +// } + +// // // fn create_field_snap_call( +// // // &mut self, +// // // field: &vir_mid::FieldDecl, +// // // ) -> SpannedEncodingResult { +// // // let field_place = self.inner.lowerer.encode_field_place( +// // // self.inner.ty, +// // // field, +// // // self.place.clone().into(), +// // // self.inner.position, +// // // )?; +// // // self.inner.lowerer.owned_non_aliased_snap( +// // // CallContext::BuiltinMethod, +// // // &field.ty, +// // // &field.ty, +// // // field_place, +// // // self.root_address.clone().into(), +// // // self.inner.position, +// // // ) +// // // } + +// // // pub(in super::super::super) fn create_field_snapshot_equality( +// // // &mut self, +// // // field: &vir_mid::FieldDecl, +// // // ) -> SpannedEncodingResult { +// // // use vir_low::macros::*; +// // // let result = self.inner.result()?; +// // // let field_snapshot = self.inner.lowerer.obtain_struct_field_snapshot( +// // // self.inner.ty, +// // // field, +// // // result.into(), +// // // self.inner.position, +// // // )?; +// // // let snap_call = self.create_field_snap_call(&field)?; +// // // Ok(expr! { +// // // [field_snapshot] == [snap_call] +// // // }) +// // // } + +// pub(in super::super::super) fn create_field_snapshot_equality( +// &mut self, +// field: &vir_mid::FieldDecl, +// ) -> SpannedEncodingResult { +// let owned_call = self.field_owned_snap()?; +// self.inner.create_field_snapshot_equality(field, owned_call) +// } + +// fn field_owned_snap( +// &mut self, +// ) -> SpannedEncodingResult< +// impl Fn( +// &mut FunctionDeclBuilder, +// &vir_mid::FieldDecl, +// vir_low::Expression, +// vir_low::Expression, +// ) -> SpannedEncodingResult, +// > { +// let address: vir_low::Expression = self.inner.address.clone().into(); +// let _address = std::rc::Rc::new(address); +// Ok( +// move |builder: &mut FunctionDeclBuilder, +// field: &vir_mid::FieldDecl, +// _, +// field_address| { +// // let field_address = builder.lowerer.encode_field_address( +// // builder.ty, +// // field, +// // (*address).clone(), +// // builder.position, +// // )?; +// builder.lowerer.owned_aliased_snap( +// CallContext::BuiltinMethod, +// &field.ty, +// &field.ty, +// field_address, +// builder.position, +// ) +// }, +// ) +// } + +// pub(in super::super::super) fn create_discriminant_snapshot_equality( +// &mut self, +// decl: &vir_mid::type_decl::Enum, +// ) -> SpannedEncodingResult { +// use vir_low::macros::*; +// let result = self.inner.result()?; +// let discriminant_snapshot = self.inner.lowerer.obtain_enum_discriminant( +// result.into(), +// self.inner.ty, +// self.inner.position, +// )?; +// let discriminant_field = decl.discriminant_field(); +// let discriminant_address = self.inner.lowerer.encode_field_address( +// self.inner.ty, +// &discriminant_field, +// self.inner.address.clone().into(), +// self.inner.position, +// )?; +// let snap_call = self.inner.lowerer.owned_aliased_snap( +// CallContext::BuiltinMethod, +// &decl.discriminant_type, +// &decl.discriminant_type, +// discriminant_address, +// self.inner.position, +// )?; +// let snap_call_int = self.inner.lowerer.obtain_constant_value( +// &decl.discriminant_type, +// snap_call, +// self.inner.position, +// )?; +// Ok(expr! { +// [discriminant_snapshot] == [snap_call_int] +// }) +// } + +// pub(in super::super::super) fn create_variant_snapshot_equality( +// &mut self, +// discriminant_value: vir_mid::DiscriminantValue, +// variant: &vir_mid::type_decl::Struct, +// ) -> SpannedEncodingResult<(vir_low::Expression, vir_low::Expression)> { +// use vir_low::macros::*; +// let result = self.inner.result()?; +// let discriminant_call = self.inner.lowerer.obtain_enum_discriminant( +// result.clone().into(), +// self.inner.ty, +// self.inner.position, +// )?; +// let guard = expr! { +// [ discriminant_call ] == [ discriminant_value.into() ] +// }; +// let variant_index = variant.name.clone().into(); +// let variant_address = self.inner.lowerer.encode_enum_variant_address( +// self.inner.ty, +// &variant_index, +// self.inner.address.clone().into(), +// self.inner.position, +// )?; +// let variant_snapshot = self.inner.lowerer.obtain_enum_variant_snapshot( +// self.inner.ty, +// &variant_index, +// result.into(), +// self.inner.position, +// )?; +// let ty = self.inner.ty.clone(); +// let variant_type = ty.variant(variant_index); +// let snap_call = self.inner.lowerer.owned_aliased_snap( +// CallContext::BuiltinMethod, +// &variant_type, +// // Enum variant and enum have the same set of lifetime parameters, +// // so we use type_decl here. We cannot use `variant_type` because +// // `ty` is normalized. +// self.inner.type_decl, +// variant_address, +// self.inner.position, +// )?; +// let equality = expr! { +// [variant_snapshot] == [snap_call] +// }; +// Ok((guard, equality)) +// } + +// pub(in super::super::super) fn add_reference_snapshot_equalities( +// &mut self, +// decl: &vir_mid::type_decl::Reference, +// lifetime: &vir_mid::ty::LifetimeConst, +// ) -> SpannedEncodingResult<()> { +// use vir_low::macros::*; +// let result = self.inner.result()?; +// let guard = self +// .inner +// .lowerer +// .encode_lifetime_const_into_pure_is_alive_variable(lifetime)?; +// let lifetime = lifetime.to_pure_snapshot(self.inner.lowerer)?; +// let place = self +// .inner +// .lowerer +// .encode_aliased_place_root(self.inner.position)?; +// let deref_place = self +// .inner +// .lowerer +// .reference_deref_place(place, self.inner.position)?; +// let current_snapshot = self.inner.lowerer.reference_target_current_snapshot( +// self.inner.ty, +// result.clone().into(), +// self.inner.position, +// )?; +// let final_snapshot = self.inner.lowerer.reference_target_final_snapshot( +// self.inner.ty, +// result.clone().into(), +// self.inner.position, +// )?; +// let address = self.inner.lowerer.reference_address( +// self.inner.ty, +// result.clone().into(), +// self.inner.position, +// )?; +// let slice_len = self.inner.lowerer.reference_slice_len( +// self.inner.ty, +// result.into(), +// self.inner.position, +// )?; +// let equalities = if decl.uniqueness.is_unique() { +// let current_snap_call = self.inner.lowerer.unique_ref_snap( +// CallContext::BuiltinMethod, +// &decl.target_type, +// &decl.target_type, +// deref_place.clone(), +// address.clone(), +// lifetime.clone().into(), +// slice_len.clone(), +// false, +// self.inner.position, +// )?; +// let final_snap_call = self.inner.lowerer.unique_ref_snap( +// CallContext::BuiltinMethod, +// &decl.target_type, +// &decl.target_type, +// deref_place, +// address, +// lifetime.into(), +// slice_len, +// true, +// self.inner.position, +// )?; +// expr! { +// ([current_snapshot] == [current_snap_call]) && +// ([final_snapshot] == [final_snap_call]) +// } +// } else { +// let snap_call = self.inner.lowerer.frac_ref_snap( +// CallContext::BuiltinMethod, +// &decl.target_type, +// &decl.target_type, +// deref_place, +// address, +// lifetime.into(), +// slice_len, +// self.inner.position, +// )?; +// expr! { +// [current_snapshot] == [snap_call] +// } +// }; +// let expression = expr! { +// guard ==> [equalities] +// }; +// self.add_snapshot_body_postcondition(expression) +// } + +// pub(in super::super::super) fn add_structural_invariant( +// &mut self, +// decl: &vir_mid::type_decl::Struct, +// ) -> SpannedEncodingResult<()> { +// // let precondition_predicate = self.precondition_predicate()?; +// let predicate_kind = PredicateKind::Owned; +// let snap_call = self.field_owned_snap()?; +// self.inner +// .add_structural_invariant(decl, false, predicate_kind, &snap_call) +// } + +// pub(in super::super::super) fn take_owned_snapshot_functions_to_encode( +// &mut self, +// ) -> Vec { +// std::mem::take(&mut self.inner.owned_snapshot_functions_to_encode) +// } + +// pub(in super::super::super) fn take_owned_range_snapshot_functions_to_encode( +// &mut self, +// ) -> Vec { +// std::mem::take(&mut self.inner.owned_range_snapshot_functions_to_encode) +// } +// } diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/owned_aliased/function_range_decl.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/owned_aliased/function_range_decl.rs new file mode 100644 index 00000000000..302da507685 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/owned_aliased/function_range_decl.rs @@ -0,0 +1,210 @@ +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::{ + builtin_methods::CallContext, + lowerer::{FunctionsLowererInterface, Lowerer}, + permissions::PermissionsInterface, + predicates::{ + owned::builders::common::function_decl::FunctionDeclBuilder, PredicatesOwnedInterface, + }, + snapshots::{IntoPureSnapshot, IntoSnapshot}, + type_layouts::TypeLayoutsInterface, + }, +}; + +use vir_crate::{ + low::{self as vir_low}, + middle::{self as vir_mid}, +}; + +pub(in super::super::super) struct OwnedAliasedRangeSnapFunctionBuilder<'l, 'p, 'v, 'tcx> { + inner: FunctionDeclBuilder<'l, 'p, 'v, 'tcx>, + address: vir_low::VariableDecl, + start_index: vir_low::VariableDecl, + end_index: vir_low::VariableDecl, + slice_len: Option, + pres: Vec, + posts: Vec, +} + +impl<'l, 'p, 'v, 'tcx> OwnedAliasedRangeSnapFunctionBuilder<'l, 'p, 'v, 'tcx> { + pub(in super::super::super) fn new( + lowerer: &'l mut Lowerer<'p, 'v, 'tcx>, + ty: &'l vir_mid::Type, + type_decl: &'l vir_mid::TypeDecl, + ) -> SpannedEncodingResult { + let slice_len = if ty.is_slice() { + Some(vir_mid::VariableDecl::new( + "slice_len", + lowerer.size_type_mid()?, + )) + } else { + None + }; + Ok(Self { + address: vir_low::VariableDecl::new("address", ty.to_snapshot(lowerer)?), + start_index: vir_low::VariableDecl::new("start_index", lowerer.size_type()?), + end_index: vir_low::VariableDecl::new("end_index", lowerer.size_type()?), + slice_len, + inner: FunctionDeclBuilder::new( + lowerer, + "snap_owned_range_aliased", + ty, + type_decl, + Default::default(), + )?, + pres: Vec::new(), + posts: Vec::new(), + }) + } + + pub(in super::super::super) fn build(mut self) -> SpannedEncodingResult { + let return_type = self.inner.range_result_type()?; + let function_name = self + .inner + .lowerer + .construct_function_name(self.inner.function_name, self.inner.ty)?; + let function = vir_low::FunctionDecl { + name: function_name, + kind: vir_low::FunctionKind::SnapRange, + parameters: self.inner.parameters, + body: None, + pres: self.pres, + posts: self.posts, + return_type, + }; + Ok(function) + } + + // fn result_type(&mut self) -> SpannedEncodingResult { + // let vir_mid::Type::Pointer(pointer_type) = self.inner.ty else { + // unreachable!("{} must be a pointer type", self.inner.ty); + // }; + // let element_type = pointer_type.target_type.to_snapshot(self.inner.lowerer)?; + // let return_type = vir_low::Type::seq(element_type); + // Ok(return_type) + // } + + // fn result(&mut self) -> SpannedEncodingResult { + // Ok(vir_low::VariableDecl::result_variable(self.result_type()?)) + // } + + pub(in super::super::super) fn create_parameters(&mut self) -> SpannedEncodingResult<()> { + self.inner.parameters.push(self.address.clone()); + self.inner.parameters.push(self.start_index.clone()); + self.inner.parameters.push(self.end_index.clone()); + self.inner.create_lifetime_parameters()?; + if let Some(slice_len_mid) = &self.slice_len { + let slice_len = slice_len_mid.to_pure_snapshot(self.inner.lowerer)?; + self.inner.parameters.push(slice_len); + } + self.inner.create_const_parameters()?; + Ok(()) + } + + pub(in super::super::super) fn add_owned_precondition(&mut self) -> SpannedEncodingResult<()> { + let wildcard_permission = self.inner.lowerer.wildcard_permission()?; + let predicates = self.inner.lowerer.owned_aliased_range( + CallContext::BuiltinMethod, + self.inner.ty, + self.inner.type_decl, + self.address.clone().into(), + self.start_index.clone().into(), + self.end_index.clone().into(), + Some(wildcard_permission), + self.inner.position, + )?; + self.pres.push(predicates); + Ok(()) + } + + pub(in super::super::super) fn add_postcondition(&mut self) -> SpannedEncodingResult<()> { + // use vir_low::macros::*; + // let size_type = self.inner.lowerer.size_type_mid()?; + // var_decls! { + // index: Int + // } + // let vir_mid::Type::Pointer(ty) = self.inner.ty else { + // unreachable!() + // }; + // let initial_address = self.inner.lowerer.pointer_address( + // self.inner.ty, + // self.address.clone().into(), + // self.inner.position, + // )?; + // let vir_mid::Type::Pointer(pointer_type) = self.inner.ty else { + // unreachable!() + // }; + // let size = self + // .inner + // .lowerer + // .encode_type_size_expression2(&pointer_type.target_type, &*pointer_type.target_type)?; + // let element_address = self.inner.lowerer.address_offset( + // size, + // initial_address, + // index.clone().into(), + // self.inner.position, + // )?; + // let snap_call = self.inner.lowerer.owned_aliased_snap( + // CallContext::BuiltinMethod, + // &ty.target_type, + // &*ty.target_type, + // element_address.clone(), + // self.inner.position, + // )?; + // let result_type = self.result_type()?; + // let result = self.result()?; + // let start_index = self.inner.lowerer.obtain_constant_value( + // &size_type, + // self.start_index.clone().into(), + // self.inner.position, + // )?; + // let end_index = self.inner.lowerer.obtain_constant_value( + // &size_type, + // self.end_index.clone().into(), + // self.inner.position, + // )?; + // let result_len = vir_low::Expression::container_op( + // vir_low::ContainerOpKind::SeqLen, + // result_type.clone(), + // vec![result.clone().into()], + // self.inner.position, + // ); + // let index_diff = vir_low::Expression::subtract(end_index.clone(), start_index.clone()); + // self.posts.push(expr!([result_len] == [index_diff])); + // let element_snap = vir_low::Expression::container_op( + // vir_low::ContainerOpKind::SeqIndex, + // result_type, + // vec![ + // result.into(), + // vir_low::Expression::subtract(index.clone().into(), start_index.clone()), + // ], + // self.inner.position, + // ); + // let body = expr!( + // (([start_index] <= index) && (index < [end_index])) ==> + // ([snap_call] == [element_snap]) + // ); + // let expression = vir_low::Expression::forall( + // vec![index], + // vec![vir_low::Trigger::new(vec![element_address])], + // body, + // ); + self.inner.create_range_postcondition( + &mut self.posts, + &self.address, + &self.start_index, + &self.end_index, + |lowerer, ty, element_address, position| { + lowerer.owned_aliased_snap( + CallContext::BuiltinMethod, + ty, + ty, + element_address, + position, + ) + }, + )?; + Ok(()) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/owned_aliased/function_range_use.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/owned_aliased/function_range_use.rs new file mode 100644 index 00000000000..3dbb9650d95 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/owned_aliased/function_range_use.rs @@ -0,0 +1,96 @@ +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::{ + builtin_methods::CallContext, lowerer::Lowerer, + predicates::owned::builders::common::function_use::FunctionCallBuilder, + snapshots::IntoSnapshot, + }, +}; +use vir_crate::{ + common::identifier::WithIdentifier, + low::{self as vir_low}, + middle::{ + self as vir_mid, + operations::{const_generics::WithConstArguments, lifetimes::WithLifetimes}, + }, +}; + +pub(in super::super::super::super::super) struct OwnedAliasedRangeSnapCallBuilder< + 'l, + 'p, + 'v, + 'tcx, + G, +> where + G: WithLifetimes + WithConstArguments, +{ + inner: FunctionCallBuilder<'l, 'p, 'v, 'tcx, G>, +} + +impl<'l, 'p, 'v, 'tcx, G> OwnedAliasedRangeSnapCallBuilder<'l, 'p, 'v, 'tcx, G> +where + G: WithLifetimes + WithConstArguments, +{ + pub(in super::super::super::super::super) fn new( + lowerer: &'l mut Lowerer<'p, 'v, 'tcx>, + context: CallContext, + ty: &'l vir_mid::Type, + generics: &'l G, + address: vir_low::Expression, + start_index: vir_low::Expression, + end_index: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult { + let arguments = vec![address, start_index, end_index]; + let inner = FunctionCallBuilder::new( + lowerer, + "snap_owned_range_aliased", + context, + ty, + generics, + arguments, + position, + )?; + Ok(Self { inner }) + } + + pub(in super::super::super::super::super) fn build( + self, + ) -> SpannedEncodingResult { + let vir_mid::Type::Pointer(pointer_type) = self.inner.ty else { + unreachable!("{} must be a pointer type", self.inner.ty); + }; + let element_type = pointer_type.target_type.to_snapshot(self.inner.lowerer)?; + let return_type = vir_low::Type::seq(element_type); + let call = vir_low::Expression::function_call( + format!( + "{}${}", + self.inner.function_name, + self.inner.ty.get_identifier() + ), + self.inner.arguments, + return_type, + ); + Ok(call.set_default_position(self.inner.position)) + } + + // pub(in super::super::super::super::super) fn add_custom_argument( + // &mut self, + // argument: vir_low::Expression, + // ) -> SpannedEncodingResult<()> { + // self.inner.arguments.push(argument); + // Ok(()) + // } + + // pub(in super::super::super::super::super) fn add_lifetime_arguments( + // &mut self, + // ) -> SpannedEncodingResult<()> { + // self.inner.add_lifetime_arguments() + // } + + // pub(in super::super::super::super::super) fn add_const_arguments( + // &mut self, + // ) -> SpannedEncodingResult<()> { + // self.inner.add_const_arguments() + // } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/owned_aliased/function_use.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/owned_aliased/function_use.rs new file mode 100644 index 00000000000..c1f9c392216 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/owned_aliased/function_use.rs @@ -0,0 +1,73 @@ +// use crate::encoder::{ +// errors::SpannedEncodingResult, +// middle::core_proof::{ +// builtin_methods::CallContext, lowerer::Lowerer, +// predicates::owned::builders::common::function_use::FunctionCallBuilder, +// }, +// }; +// use vir_crate::{ +// low::{self as vir_low}, +// middle::{ +// self as vir_mid, +// operations::{const_generics::WithConstArguments, lifetimes::WithLifetimes}, +// }, +// }; + +// pub(in super::super::super::super::super) struct OwnedAliasedSnapCallBuilder<'l, 'p, 'v, 'tcx, G> +// where +// G: WithLifetimes + WithConstArguments, +// { +// inner: FunctionCallBuilder<'l, 'p, 'v, 'tcx, G>, +// } + +// impl<'l, 'p, 'v, 'tcx, G> OwnedAliasedSnapCallBuilder<'l, 'p, 'v, 'tcx, G> +// where +// G: WithLifetimes + WithConstArguments, +// { +// pub(in super::super::super::super::super) fn new( +// lowerer: &'l mut Lowerer<'p, 'v, 'tcx>, +// context: CallContext, +// ty: &'l vir_mid::Type, +// generics: &'l G, +// address: vir_low::Expression, +// position: vir_low::Position, +// ) -> SpannedEncodingResult { +// let arguments = vec![address]; +// let inner = FunctionCallBuilder::new( +// lowerer, +// "snap_owned_aliased", +// context, +// ty, +// generics, +// arguments, +// position, +// )?; +// Ok(Self { inner }) +// } + +// pub(in super::super::super::super::super) fn build( +// self, +// ) -> SpannedEncodingResult { +// self.inner.build() +// } + +// // pub(in super::super::super::super::super) fn add_custom_argument( +// // &mut self, +// // argument: vir_low::Expression, +// // ) -> SpannedEncodingResult<()> { +// // self.inner.arguments.push(argument); +// // Ok(()) +// // } + +// pub(in super::super::super::super::super) fn add_lifetime_arguments( +// &mut self, +// ) -> SpannedEncodingResult<()> { +// self.inner.add_lifetime_arguments() +// } + +// pub(in super::super::super::super::super) fn add_const_arguments( +// &mut self, +// ) -> SpannedEncodingResult<()> { +// self.inner.add_const_arguments() +// } +// } diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/owned_aliased/mod.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/owned_aliased/mod.rs new file mode 100644 index 00000000000..0bc824c1cbb --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/owned_aliased/mod.rs @@ -0,0 +1,7 @@ +pub(super) mod function_decl; +pub(super) mod function_use; +pub(super) mod function_range_decl; +pub(super) mod function_range_use; +pub(super) mod predicate_decl; +pub(super) mod predicate_use; +pub(super) mod predicate_range_use; diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/owned_aliased/predicate_decl.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/owned_aliased/predicate_decl.rs new file mode 100644 index 00000000000..ff051272860 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/owned_aliased/predicate_decl.rs @@ -0,0 +1,309 @@ +// use crate::encoder::{ +// errors::SpannedEncodingResult, +// middle::core_proof::{ +// addresses::AddressesInterface, +// builtin_methods::CallContext, +// lowerer::Lowerer, +// places::PlacesInterface, +// predicates::{ +// owned::builders::{ +// common::predicate_decl::PredicateDeclBuilder, PredicateDeclBuilderMethods, +// }, +// PredicatesOwnedInterface, +// }, +// snapshots::{IntoPureSnapshot, PredicateKind, SnapshotValuesInterface}, +// type_layouts::TypeLayoutsInterface, +// }, +// }; + +// use vir_crate::{ +// common::{expression::GuardedExpressionIterator, position::Positioned}, +// low::{self as vir_low}, +// middle::{self as vir_mid}, +// }; + +// pub(in super::super::super) struct OwnedAliasedBuilder<'l, 'p, 'v, 'tcx> { +// inner: PredicateDeclBuilder<'l, 'p, 'v, 'tcx>, +// slice_len: Option, +// } + +// impl<'l, 'p, 'v, 'tcx> PredicateDeclBuilderMethods<'l, 'p, 'v, 'tcx> +// for OwnedAliasedBuilder<'l, 'p, 'v, 'tcx> +// { +// fn inner(&mut self) -> &mut PredicateDeclBuilder<'l, 'p, 'v, 'tcx> { +// &mut self.inner +// } +// } + +// impl<'l, 'p, 'v, 'tcx> OwnedAliasedBuilder<'l, 'p, 'v, 'tcx> { +// pub(in super::super::super) fn new( +// _lowerer: &'l mut Lowerer<'p, 'v, 'tcx>, +// _ty: &'l vir_mid::Type, +// _type_decl: &'l vir_mid::TypeDecl, +// ) -> SpannedEncodingResult { +// unimplemented!() +// // let slice_len = if ty.is_slice() { +// // Some(vir_mid::VariableDecl::new( +// // "slice_len", +// // lowerer.size_type_mid()?, +// // )) +// // } else { +// // None +// // }; +// // let position = type_decl.position(); +// // Ok(Self { +// // slice_len, +// // inner: PredicateDeclBuilder::new(lowerer, "OwnedAliased", ty, type_decl, position)?, +// // }) +// } + +// pub(in super::super::super) fn build(self) -> vir_low::PredicateDecl { +// self.inner.build() +// } + +// pub(in super::super::super) fn create_parameters(&mut self) -> SpannedEncodingResult<()> { +// self.inner.parameters.push(self.inner.address.clone()); +// self.inner.create_lifetime_parameters()?; +// if let Some(slice_len_mid) = &self.slice_len { +// let slice_len = slice_len_mid.to_pure_snapshot(self.inner.lowerer)?; +// self.inner.parameters.push(slice_len); +// } +// self.inner.create_const_parameters()?; +// Ok(()) +// } + +// fn size_of(&mut self) -> SpannedEncodingResult { +// self.inner +// .lowerer +// .encode_type_size_expression2(self.inner.ty, self.inner.type_decl) +// } + +// fn padding_size(&mut self) -> SpannedEncodingResult { +// self.inner +// .lowerer +// .encode_type_padding_size_expression(self.inner.ty) +// } + +// pub(in super::super::super) fn add_base_memory_block(&mut self) -> SpannedEncodingResult<()> { +// use vir_low::macros::*; +// let size_of = self.size_of()?; +// let address = &self.inner.address; +// let expression = expr! { +// acc(MemoryBlock(address, [size_of])) +// }; +// self.inner.add_conjunct(expression) +// } + +// pub(in super::super::super) fn add_padding_memory_block( +// &mut self, +// ) -> SpannedEncodingResult<()> { +// use vir_low::macros::*; +// let padding_size = self.padding_size()?; +// let address = &self.inner.address; +// let expression = expr! { +// acc(MemoryBlock(address, [padding_size])) +// }; +// self.inner.add_conjunct(expression) +// } + +// pub(in super::super::super) fn add_field_predicate( +// &mut self, +// field: &vir_mid::FieldDecl, +// ) -> SpannedEncodingResult<()> { +// let field_address = self.inner.lowerer.encode_field_address( +// self.inner.ty, +// field, +// self.inner.address.clone().into(), +// self.inner.position, +// )?; +// let expression = self.inner.lowerer.owned_aliased( +// CallContext::BuiltinMethod, +// &field.ty, +// &field.ty, +// field_address, +// None, +// self.inner.position, +// )?; +// self.inner.add_conjunct(expression) +// } + +// pub(in super::super::super) fn add_discriminant_predicate( +// &mut self, +// decl: &vir_mid::type_decl::Enum, +// ) -> SpannedEncodingResult<()> { +// let discriminant_field = decl.discriminant_field(); +// let discriminant_address = self.inner.lowerer.encode_field_address( +// self.inner.ty, +// &discriminant_field, +// self.inner.address.clone().into(), +// self.inner.position, +// )?; +// let expression = self.inner.lowerer.owned_aliased( +// CallContext::BuiltinMethod, +// &decl.discriminant_type, +// &decl.discriminant_type, +// discriminant_address, +// None, +// self.inner.position, +// )?; +// self.inner.add_conjunct(expression) +// } + +// pub(in super::super::super) fn add_unique_ref_target_predicate( +// &mut self, +// target_type: &vir_mid::Type, +// lifetime: &vir_mid::ty::LifetimeConst, +// ) -> SpannedEncodingResult<()> { +// let place = self +// .inner +// .lowerer +// .encode_aliased_place_root(self.inner.position)?; +// let root_address = self.inner.address.clone(); +// self.inner.add_unique_ref_target_predicate( +// target_type, +// lifetime, +// place, +// root_address, +// false, +// ) +// } + +// pub(in super::super::super) fn add_frac_ref_target_predicate( +// &mut self, +// target_type: &vir_mid::Type, +// lifetime: &vir_mid::ty::LifetimeConst, +// ) -> SpannedEncodingResult<()> { +// let place = self +// .inner +// .lowerer +// .encode_aliased_place_root(self.inner.position)?; +// let root_address = self.inner.address.clone(); +// self.inner +// .add_frac_ref_target_predicate(target_type, lifetime, place, root_address) +// } + +// // FIXME: Code duplication. +// pub(in super::super::super) fn get_slice_len( +// &self, +// ) -> SpannedEncodingResult { +// Ok(self.slice_len.as_ref().unwrap().clone()) +// } + +// // pub(in super::super::super) fn add_quantified_permission( +// // &mut self, +// // array_length_mid: &vir_mid::VariableDecl, +// // element_type: &vir_mid::Type, +// // ) -> SpannedEncodingResult<()> { +// // use vir_low::macros::*; +// // let size_type = self.inner.lowerer.size_type()?; +// // let size_type_mid = self.inner.lowerer.size_type_mid()?; +// // var_decls! { +// // index: {size_type} +// // }; +// // let index_validity = self +// // .inner +// // .lowerer +// // .encode_snapshot_valid_call_for_type(index.clone().into(), &size_type_mid)?; +// // let index_int = self.inner.lowerer.obtain_constant_value( +// // &size_type_mid, +// // index.clone().into(), +// // self.inner.position, +// // )?; +// // let array_length_int = self.inner.array_length_int(array_length_mid)?; +// // let element_place = self.inner.lowerer.encode_index_place( +// // self.inner.ty, +// // self.inner.place.clone().into(), +// // index.clone().into(), +// // self.inner.position, +// // )?; +// // let element_snapshot = self.inner.lowerer.obtain_array_element_snapshot( +// // self.snapshot.clone().into(), +// // index_int.clone(), +// // self.inner.position, +// // )?; +// // let element_predicate_acc = self.inner.lowerer.owned_non_aliased( +// // CallContext::BuiltinMethod, +// // element_type, +// // element_type, +// // element_place, +// // self.inner.root_address.clone().into(), +// // element_snapshot, +// // None, +// // )?; +// // let elements = vir_low::Expression::forall( +// // vec![index], +// // vec![vir_low::Trigger::new(vec![element_predicate_acc.clone()])], +// // expr! { +// // ([index_validity] && ([index_int] < [array_length_int])) ==> +// // [element_predicate_acc] +// // }, +// // ); +// // self.inner.add_conjunct(elements) +// // } + +// pub(in super::super::super) fn create_variant_predicate( +// &mut self, +// decl: &vir_mid::type_decl::Enum, +// discriminant_value: vir_mid::DiscriminantValue, +// variant: &vir_mid::type_decl::Struct, +// variant_type: &vir_mid::Type, +// ) -> SpannedEncodingResult<(vir_low::Expression, vir_low::Expression)> { +// use vir_low::macros::*; +// let discriminant_call = { +// let discriminant_field = decl.discriminant_field(); +// let discriminant_address = self.inner.lowerer.encode_field_address( +// self.inner.ty, +// &discriminant_field, +// self.inner.place.clone().into(), +// self.inner.position, +// )?; +// let discriminant_snapshot = self.inner.lowerer.owned_aliased_snap( +// CallContext::BuiltinMethod, +// &decl.discriminant_type, +// &decl.discriminant_type, +// discriminant_address, +// self.inner.position, +// )?; +// self.inner.lowerer.obtain_constant_value( +// &decl.discriminant_type, +// discriminant_snapshot, +// self.inner.position, +// )? +// }; +// let guard = expr! { +// [ discriminant_call ] == [ discriminant_value.into() ] +// }; +// let variant_index = variant.name.clone().into(); +// let variant_address = self.inner.lowerer.encode_enum_variant_address( +// self.inner.ty, +// &variant_index, +// self.inner.place.clone().into(), +// self.inner.position, +// )?; +// let predicate = self.inner.lowerer.owned_aliased( +// CallContext::BuiltinMethod, +// variant_type, +// variant_type, +// variant_address, +// None, +// self.inner.position, +// )?; +// Ok((guard, predicate)) +// } + +// pub(in super::super::super) fn add_variant_predicates( +// &mut self, +// variant_predicates: Vec<(vir_low::Expression, vir_low::Expression)>, +// ) -> SpannedEncodingResult<()> { +// self.inner +// .add_conjunct(variant_predicates.into_iter().create_match()) +// } + +// pub(in super::super::super) fn add_structural_invariant( +// &mut self, +// decl: &vir_mid::type_decl::Struct, +// ) -> SpannedEncodingResult> { +// self.inner +// .add_structural_invariant(decl, PredicateKind::Owned) +// } +// } diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/owned_aliased/predicate_range_use.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/owned_aliased/predicate_range_use.rs new file mode 100644 index 00000000000..25f36187565 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/owned_aliased/predicate_range_use.rs @@ -0,0 +1,138 @@ +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::{ + addresses::AddressesInterface, builtin_methods::CallContext, lowerer::Lowerer, + pointers::PointersInterface, predicates::PredicatesOwnedInterface, + snapshots::SnapshotValuesInterface, type_layouts::TypeLayoutsInterface, + }, +}; + +use vir_crate::{ + common::expression::QuantifierHelpers, + low::{self as vir_low}, + middle::{ + self as vir_mid, + operations::{const_generics::WithConstArguments, lifetimes::WithLifetimes}, + }, +}; + +pub(in super::super::super::super::super) struct OwnedAliasedRangeUseBuilder<'l, 'p, 'v, 'tcx, G> +where + G: WithLifetimes + WithConstArguments, +{ + lowerer: &'l mut Lowerer<'p, 'v, 'tcx>, + context: CallContext, + ty: &'l vir_mid::Type, + generics: &'l G, + address: vir_low::Expression, + start_index: vir_low::Expression, + end_index: vir_low::Expression, + permission_amount: Option, + position: vir_low::Position, +} + +impl<'l, 'p, 'v, 'tcx, G> OwnedAliasedRangeUseBuilder<'l, 'p, 'v, 'tcx, G> +where + G: WithLifetimes + WithConstArguments, +{ + pub(in super::super::super::super::super) fn new( + lowerer: &'l mut Lowerer<'p, 'v, 'tcx>, + context: CallContext, + ty: &'l vir_mid::Type, + generics: &'l G, + address: vir_low::Expression, + start_index: vir_low::Expression, + end_index: vir_low::Expression, + permission_amount: Option, + position: vir_low::Position, + ) -> SpannedEncodingResult { + Ok(Self { + lowerer, + context, + ty, + generics, + address, + start_index, + end_index, + permission_amount, + position, + }) + } + + pub(in super::super::super::super::super) fn build( + self, + ) -> SpannedEncodingResult { + use vir_low::macros::*; + let size_type = self.lowerer.size_type_mid()?; + // var_decls! { + // index: Int + // } + let vir_mid::Type::Pointer(ty) = self.ty else { + unreachable!() + }; + let initial_address = self + .lowerer + .pointer_address(self.ty, self.address, self.position)?; + let vir_mid::Type::Pointer(pointer_type) = self.ty else { + unreachable!() + }; + let size = self + .lowerer + .encode_type_size_expression2(&pointer_type.target_type, &*pointer_type.target_type)?; + // let element_address = self.lowerer.address_offset( + // size, + // initial_address, + // index.clone().into(), + // self.position, + // )?; + // let predicate = self.lowerer.owned_aliased( + // self.context, + // &ty.target_type, + // self.generics, + // element_address.clone(), + // self.permission_amount, + // self.position, + // )?; + let start_index = + self.lowerer + .obtain_constant_value(&size_type, self.start_index, self.position)?; + let end_index = + self.lowerer + .obtain_constant_value(&size_type, self.end_index, self.position)?; + // let body = expr!( + // (([start_index] <= index) && (index < [end_index])) ==> [predicate] + // ); + // let expression = vir_low::Expression::forall( + // vec![index], + // vec![vir_low::Trigger::new(vec![element_address])], + // body, + // ); + + var_decls! { + element_address: Address + } + let predicate = self.lowerer.owned_aliased( + self.context, + &ty.target_type, + self.generics, + element_address.clone().into(), + self.permission_amount, + self.position, + )?; + let guard = self.lowerer.address_range_contains( + initial_address, + start_index, + end_index, + size, + element_address.clone().into(), + self.position, + )?; + let body = expr!([guard] ==> [predicate.clone()]); + let expression = vir_low::Expression::forall( + vec![element_address], + vec![vir_low::Trigger::new(vec![predicate])], + body, + ); + Ok(expression) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/owned_aliased/predicate_use.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/owned_aliased/predicate_use.rs new file mode 100644 index 00000000000..3d856cc79fc --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/owned_aliased/predicate_use.rs @@ -0,0 +1,72 @@ +// use crate::encoder::{ +// errors::SpannedEncodingResult, +// middle::core_proof::{ +// builtin_methods::CallContext, lowerer::Lowerer, +// predicates::owned::builders::common::predicate_use::PredicateUseBuilder, +// }, +// }; + +// use vir_crate::{ +// low::{self as vir_low}, +// middle::{ +// self as vir_mid, +// operations::{const_generics::WithConstArguments, lifetimes::WithLifetimes}, +// }, +// }; + +// pub(in super::super::super::super::super) struct OwnedAliasedUseBuilder<'l, 'p, 'v, 'tcx, G> +// where +// G: WithLifetimes + WithConstArguments, +// { +// inner: PredicateUseBuilder<'l, 'p, 'v, 'tcx, G>, +// } + +// impl<'l, 'p, 'v, 'tcx, G> OwnedAliasedUseBuilder<'l, 'p, 'v, 'tcx, G> +// where +// G: WithLifetimes + WithConstArguments, +// { +// pub(in super::super::super::super::super) fn new( +// lowerer: &'l mut Lowerer<'p, 'v, 'tcx>, +// context: CallContext, +// ty: &'l vir_mid::Type, +// generics: &'l G, +// address: vir_low::Expression, +// ) -> SpannedEncodingResult { +// let arguments = vec![address]; +// let inner = PredicateUseBuilder::new( +// lowerer, +// "OwnedAliased", +// context, +// ty, +// generics, +// arguments, +// Default::default(), +// )?; +// Ok(Self { inner }) +// } + +// pub(in super::super::super::super::super) fn build( +// self, +// ) -> SpannedEncodingResult { +// Ok(self.inner.build()) +// } + +// pub(in super::super::super::super::super) fn add_lifetime_arguments( +// &mut self, +// ) -> SpannedEncodingResult<()> { +// self.inner.add_lifetime_arguments() +// } + +// pub(in super::super::super::super::super) fn add_const_arguments( +// &mut self, +// ) -> SpannedEncodingResult<()> { +// self.inner.add_const_arguments() +// } + +// pub(in super::super::super::super::super) fn set_maybe_permission_amount( +// &mut self, +// permission_amount: Option, +// ) -> SpannedEncodingResult<()> { +// self.inner.set_maybe_permission_amount(permission_amount) +// } +// } diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/owned_non_aliased/function_decl.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/owned_non_aliased/function_decl.rs new file mode 100644 index 00000000000..81ff64a0a03 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/owned_non_aliased/function_decl.rs @@ -0,0 +1,739 @@ +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::{ + addresses::AddressesInterface, + builtin_methods::CallContext, + lifetimes::LifetimesInterface, + lowerer::Lowerer, + permissions::PermissionsInterface, + places::PlacesInterface, + predicates::{ + owned::builders::common::function_decl::FunctionDeclBuilder, OwnedNonAliasedUseBuilder, + PredicatesMemoryBlockInterface, PredicatesOwnedInterface, + }, + references::ReferencesInterface, + snapshots::{ + IntoPureSnapshot, IntoSnapshotLowerer, PredicateKind, SnapshotBytesInterface, + SnapshotValidityInterface, SnapshotValuesInterface, + }, + type_layouts::TypeLayoutsInterface, + }, +}; + +use vir_crate::{ + common::{expression::QuantifierHelpers, position::Positioned}, + low::{self as vir_low}, + middle::{ + self as vir_mid, + operations::{const_generics::WithConstArguments, lifetimes::WithLifetimes}, + }, +}; + +pub(in super::super::super) struct OwnedNonAliasedSnapFunctionBuilder<'l, 'p, 'v, 'tcx> { + inner: FunctionDeclBuilder<'l, 'p, 'v, 'tcx>, + place: vir_low::VariableDecl, + address: vir_low::VariableDecl, + slice_len: Option, +} + +impl<'l, 'p, 'v, 'tcx> OwnedNonAliasedSnapFunctionBuilder<'l, 'p, 'v, 'tcx> { + pub(in super::super::super) fn new( + lowerer: &'l mut Lowerer<'p, 'v, 'tcx>, + ty: &'l vir_mid::Type, + type_decl: &'l vir_mid::TypeDecl, + ) -> SpannedEncodingResult { + let slice_len = if ty.is_slice() { + Some(vir_mid::VariableDecl::new( + "slice_len", + lowerer.size_type_mid()?, + )) + } else { + None + }; + Ok(Self { + place: vir_low::VariableDecl::new("place", lowerer.place_option_type()?), + address: vir_low::VariableDecl::new("address", lowerer.address_type()?), + slice_len, + inner: FunctionDeclBuilder::new( + lowerer, + "snap_owned_non_aliased", + ty, + type_decl, + Default::default(), + )?, + }) + } + + pub(in super::super::super) fn get_snapshot_postconditions( + &self, + ) -> SpannedEncodingResult> { + self.inner.get_snapshot_postconditions() + } + + pub(in super::super::super) fn get_snapshot_body( + &self, + ) -> SpannedEncodingResult> { + self.inner.get_snapshot_body() + } + + pub(in super::super::super) fn build(self) -> SpannedEncodingResult { + self.inner.build() + } + + pub(in super::super::super) fn create_parameters(&mut self) -> SpannedEncodingResult<()> { + self.inner.parameters.push(self.place.clone()); + self.inner.parameters.push(self.address.clone()); + self.inner.create_lifetime_parameters()?; + if let Some(slice_len_mid) = &self.slice_len { + let slice_len = slice_len_mid.to_pure_snapshot(self.inner.lowerer)?; + self.inner.parameters.push(slice_len); + } + self.inner.create_const_parameters()?; + Ok(()) + } + + // FIXME: Code duplication. + pub(in super::super::super) fn get_slice_len( + &self, + ) -> SpannedEncodingResult { + Ok(self.slice_len.as_ref().unwrap().clone()) + } + + fn owned_predicate( + &mut self, + ty: &vir_mid::Type, + generics: &G, + place: vir_low::Expression, + address: vir_low::Expression, + ) -> SpannedEncodingResult + where + G: WithLifetimes + WithConstArguments, + { + let wildcard_permission = self.inner.lowerer.wildcard_permission()?; + let mut builder = OwnedNonAliasedUseBuilder::new( + self.inner.lowerer, + CallContext::BuiltinMethod, + ty, + generics, + place, + address, + self.inner.position, + )?; + builder.add_lifetime_arguments()?; + builder.add_const_arguments()?; + builder.set_maybe_permission_amount(Some(wildcard_permission))?; + builder.build() + } + + // FIXME: Code duplication with add_quantified_permission. + pub(in super::super::super) fn add_quantifiers( + &mut self, + array_length_mid: &vir_mid::VariableDecl, + element_type: &vir_mid::Type, + ) -> SpannedEncodingResult<()> { + use vir_low::macros::*; + let size_type_mid = self.inner.lowerer.size_type_mid()?; + var_decls! { + index_int: Int + }; + let index = self.inner.lowerer.construct_constant_snapshot( + &size_type_mid, + index_int.clone().into(), + self.inner.position, + )?; + let index_validity = self + .inner + .lowerer + .encode_snapshot_valid_call_for_type(index.clone(), &size_type_mid)?; + let array_length_int = self.inner.array_length_int(array_length_mid)?; + let element_place = self.inner.lowerer.encode_index_place( + self.inner.ty, + self.place.clone().into(), + index.clone(), + self.inner.position, + )?; + let element_address = self.inner.lowerer.encode_index_address( + self.inner.ty, + self.address.clone().into(), + index, + self.inner.position, + )?; + let element_predicate_acc = { + self.owned_predicate( + element_type, + element_type, + element_place.clone(), + element_address.clone(), + )? + }; + let result = self.inner.result()?.into(); + let element_snapshot = self.inner.lowerer.obtain_array_element_snapshot( + result, + index_int.clone().into(), + self.inner.position, + )?; + let element_snap_call = self.inner.lowerer.owned_non_aliased_snap( + CallContext::BuiltinMethod, + element_type, + element_type, + element_place, + element_address, + self.inner.position, + )?; + let elements = vir_low::Expression::forall( + vec![index_int.clone()], + vec![vir_low::Trigger::new(vec![element_predicate_acc])], + expr! { + ([index_validity] && (index_int < [array_length_int])) ==> + ([element_snapshot] == [element_snap_call]) + }, + ); + self.add_snapshot_body_postcondition(elements) + } + + pub(in super::super::super) fn add_snapshot_body_postcondition( + &mut self, + body: vir_low::Expression, + ) -> SpannedEncodingResult<()> { + // let predicate = self.precondition_predicate()?; + // let unfolding = predicate.into_unfolding(body); + // self.inner.add_postcondition(unfolding) + self.inner.add_snapshot_body_postcondition(body) + } + + pub(in super::super::super) fn add_validity_postcondition( + &mut self, + ) -> SpannedEncodingResult<()> { + self.inner.add_validity_postcondition() + } + + pub(in super::super::super) fn add_snapshot_len_equal_to_postcondition( + &mut self, + array_length_mid: &vir_mid::VariableDecl, + ) -> SpannedEncodingResult<()> { + self.inner + .add_snapshot_len_equal_to_postcondition(array_length_mid) + } + + pub(in super::super::super) fn add_owned_precondition(&mut self) -> SpannedEncodingResult<()> { + let predicate = self.precondition_predicate()?; + self.inner.add_precondition(predicate) + } + + fn precondition_predicate(&mut self) -> SpannedEncodingResult { + self.owned_predicate( + self.inner.ty, + self.inner.type_decl, + self.place.clone().into(), + self.address.clone().into(), + ) + } + + // fn compute_address(&self) -> SpannedEncodingResult { + // use vir_low::macros::*; + // let compute_address = ty!(Address); + // let expression = expr! { + // ComputeAddress::compute_address( + // [self.place.clone().into()], + // [self.address.clone().into()] + // ) + // }; + // Ok(expression) + // } + + fn size_of(&mut self) -> SpannedEncodingResult { + self.inner + .lowerer + .encode_type_size_expression2(self.inner.ty, self.inner.type_decl) + } + + fn add_bytes_snapshot_equality_with( + &mut self, + snap_ty: &vir_mid::Type, + snapshot: vir_low::Expression, + ) -> SpannedEncodingResult<()> { + use vir_low::macros::*; + let size_of = self.size_of()?; + let bytes = self + .inner + .lowerer + .encode_memory_block_bytes_expression(self.address.clone().into(), size_of)?; + let to_bytes = ty! { Bytes }; + let expression = expr! { + [bytes] == (Snap::to_bytes([snapshot])) + }; + self.add_snapshot_body_postcondition(expression) + } + + pub(in super::super::super) fn add_bytes_snapshot_equality( + &mut self, + ) -> SpannedEncodingResult<()> { + let result = self.inner.result()?.into(); + self.add_bytes_snapshot_equality_with(self.inner.ty, result) + } + + pub(in super::super::super) fn add_bytes_address_snapshot_equality( + &mut self, + ) -> SpannedEncodingResult<()> { + let result = self.inner.result()?.into(); + let address_type = self.inner.lowerer.reference_address_type(self.inner.ty)?; + self.inner + .lowerer + .encode_snapshot_to_bytes_function(&address_type)?; + let target_address_snapshot = self.inner.lowerer.reference_address_snapshot( + self.inner.ty, + result, + self.inner.position, + )?; + self.add_bytes_snapshot_equality_with(&address_type, target_address_snapshot) + } + + // fn create_field_snap_call( + // &mut self, + // field: &vir_mid::FieldDecl, + // ) -> SpannedEncodingResult { + // let field_place = self.inner.lowerer.encode_field_place( + // self.inner.ty, + // field, + // self.place.clone().into(), + // self.inner.position, + // )?; + // self.inner.lowerer.owned_non_aliased_snap( + // CallContext::BuiltinMethod, + // &field.ty, + // &field.ty, + // field_place, + // self.address.clone().into(), + // self.inner.position, + // ) + // } + + // pub(in super::super::super) fn create_field_snapshot_equality( + // &mut self, + // field: &vir_mid::FieldDecl, + // ) -> SpannedEncodingResult { + // use vir_low::macros::*; + // let result = self.inner.result()?; + // let field_snapshot = self.inner.lowerer.obtain_struct_field_snapshot( + // self.inner.ty, + // field, + // result.into(), + // self.inner.position, + // )?; + // let snap_call = self.create_field_snap_call(&field)?; + // Ok(expr! { + // [field_snapshot] == [snap_call] + // }) + // } + + pub(in super::super::super) fn create_field_snapshot_equality( + &mut self, + field: &vir_mid::FieldDecl, + ) -> SpannedEncodingResult { + let owned_call = self.field_owned_snap()?; + self.inner.create_field_snapshot_equality(field, owned_call) + } + + fn field_owned_snap( + &mut self, + ) -> SpannedEncodingResult< + impl Fn( + &mut FunctionDeclBuilder, + &vir_mid::FieldDecl, + vir_low::Expression, + vir_low::Expression, + ) -> SpannedEncodingResult, + > { + Ok( + move |builder: &mut FunctionDeclBuilder, + field: &vir_mid::FieldDecl, + field_place, + field_address| { + builder.lowerer.owned_non_aliased_snap( + CallContext::BuiltinMethod, + &field.ty, + &field.ty, + field_place, + field_address, + builder.position, + ) + }, + ) + } + + pub(in super::super::super) fn create_discriminant_snapshot_equality( + &mut self, + decl: &vir_mid::type_decl::Enum, + ) -> SpannedEncodingResult { + let call = self.discriminant_owned_snap()?; + self.inner.create_discriminant_snapshot_equality(decl, call) + } + + fn discriminant_owned_snap( + &mut self, + ) -> SpannedEncodingResult< + impl Fn( + &mut FunctionDeclBuilder, + &vir_mid::type_decl::Enum, + vir_low::Expression, + vir_low::Expression, + ) -> SpannedEncodingResult, + > { + Ok( + move |builder: &mut FunctionDeclBuilder, + decl: &vir_mid::type_decl::Enum, + discriminant_place, + discriminant_address| { + builder.lowerer.owned_non_aliased_snap( + CallContext::BuiltinMethod, + &decl.discriminant_type, + &decl.discriminant_type, + discriminant_place, + discriminant_address, + builder.position, + ) + }, + ) + } + + pub(in super::super::super) fn create_variant_snapshot_equality( + &mut self, + discriminant_value: vir_mid::DiscriminantValue, + variant: &vir_mid::type_decl::Struct, + ) -> SpannedEncodingResult<(vir_low::Expression, vir_low::Expression)> { + let call = self.variant_owned_snap()?; + self.inner + .create_variant_snapshot_equality(discriminant_value, variant, call) + } + + fn variant_owned_snap( + &mut self, + ) -> SpannedEncodingResult< + impl Fn( + &mut FunctionDeclBuilder, + &vir_mid::Type, + vir_low::Expression, + vir_low::Expression, + ) -> SpannedEncodingResult, + > { + Ok( + move |builder: &mut FunctionDeclBuilder, + variant_type: &vir_mid::Type, + variant_place, + variant_address| { + builder.lowerer.owned_non_aliased_snap( + CallContext::BuiltinMethod, + variant_type, + // Enum variant and enum have the same set of lifetime parameters, + // so we use type_decl here. We cannot use `variant_type` because + // `ty` is normalized. + builder.type_decl, + variant_place, + variant_address, + builder.position, + ) + }, + ) + } + + // pub(in super::super::super) fn create_discriminant_snapshot_equality( + // &mut self, + // decl: &vir_mid::type_decl::Enum, + // ) -> SpannedEncodingResult { + // use vir_low::macros::*; + // let result = self.inner.result()?; + // let discriminant_snapshot = self.inner.lowerer.obtain_enum_discriminant( + // result.into(), + // self.inner.ty, + // self.inner.position, + // )?; + // let discriminant_field = decl.discriminant_field(); + // let discriminant_place = self.inner.lowerer.encode_field_place( + // self.inner.ty, + // &discriminant_field, + // self.place.clone().into(), + // self.inner.position, + // )?; + // let discriminant_address = self.inner.lowerer.encode_field_address( + // self.inner.ty, + // &discriminant_field, + // self.address.clone().into(), + // self.inner.position, + // )?; + // let snap_call = self.inner.lowerer.owned_non_aliased_snap( + // CallContext::BuiltinMethod, + // &decl.discriminant_type, + // &decl.discriminant_type, + // discriminant_place, + // discriminant_address, + // self.inner.position, + // )?; + // let snap_call_int = self.inner.lowerer.obtain_constant_value( + // &decl.discriminant_type, + // snap_call, + // self.inner.position, + // )?; + // Ok(expr! { + // [discriminant_snapshot] == [snap_call_int] + // }) + // } + + // pub(in super::super::super) fn create_variant_snapshot_equality( + // &mut self, + // discriminant_value: vir_mid::DiscriminantValue, + // variant: &vir_mid::type_decl::Struct, + // ) -> SpannedEncodingResult<(vir_low::Expression, vir_low::Expression)> { + // use vir_low::macros::*; + // let result = self.inner.result()?; + // let discriminant_call = self.inner.lowerer.obtain_enum_discriminant( + // result.clone().into(), + // self.inner.ty, + // self.inner.position, + // )?; + // let guard = expr! { + // [ discriminant_call ] == [ discriminant_value.into() ] + // }; + // let variant_index = variant.name.clone().into(); + // let variant_place = self.inner.lowerer.encode_enum_variant_place( + // self.inner.ty, + // &variant_index, + // self.place.clone().into(), + // self.inner.position, + // )?; + // let variant_address = self.inner.lowerer.encode_enum_variant_address( + // self.inner.ty, + // &variant_index, + // self.address.clone().into(), + // self.inner.position, + // )?; + // let variant_snapshot = self.inner.lowerer.obtain_enum_variant_snapshot( + // self.inner.ty, + // &variant_index, + // result.into(), + // self.inner.position, + // )?; + // let ty = self.inner.ty.clone(); + // // let mut enum_ty = ty.unwrap_enum(); + // // enum_ty.lifetimes = variant.lifetimes.clone(); + // // enum_ty.variant = Some(variant_index); + // // let variant_type = vir_mid::Type::Enum(enum_ty); + // let variant_type = ty.variant(variant_index); + // let snap_call = self.inner.lowerer.owned_non_aliased_snap( + // CallContext::BuiltinMethod, + // &variant_type, + // // Enum variant and enum have the same set of lifetime parameters, + // // so we use type_decl here. We cannot use `variant_type` because + // // `ty` is normalized. + // self.inner.type_decl, + // variant_place, + // variant_address, + // self.inner.position, + // )?; + // let equality = expr! { + // [variant_snapshot] == [snap_call] + // }; + // Ok((guard, equality)) + // } + + pub(in super::super::super) fn add_reference_snapshot_equalities( + &mut self, + decl: &vir_mid::type_decl::Reference, + lifetime: &vir_mid::ty::LifetimeConst, + ) -> SpannedEncodingResult<()> { + use vir_low::macros::*; + let result = self.inner.result()?; + let guard = self + .inner + .lowerer + .encode_lifetime_const_into_pure_is_alive_variable(lifetime)?; + let lifetime = lifetime.to_pure_snapshot(self.inner.lowerer)?; + let deref_place = self + .inner + .lowerer + .reference_deref_place(self.place.clone().into(), self.inner.position)?; + let current_snapshot = self.inner.lowerer.reference_target_current_snapshot( + self.inner.ty, + result.clone().into(), + self.inner.position, + )?; + let address = self.inner.lowerer.reference_address( + self.inner.ty, + result.clone().into(), + self.inner.position, + )?; + let slice_len = self.inner.lowerer.reference_slice_len( + self.inner.ty, + result.clone().into(), + self.inner.position, + )?; + let equalities = if decl.uniqueness.is_unique() { + let final_snapshot = self.inner.lowerer.reference_target_final_snapshot( + self.inner.ty, + result.into(), + self.inner.position, + )?; + let current_snap_call = self.inner.lowerer.unique_ref_snap( + CallContext::BuiltinMethod, + &decl.target_type, + &decl.target_type, + deref_place.clone(), + address.clone(), + lifetime.clone().into(), + slice_len.clone(), + false, + self.inner.position, + )?; + let final_snap_call = self.inner.lowerer.unique_ref_snap( + CallContext::BuiltinMethod, + &decl.target_type, + &decl.target_type, + deref_place, + address, + lifetime.into(), + slice_len, + true, + self.inner.position, + )?; + expr! { + ([current_snapshot] == [current_snap_call]) && + ([final_snapshot] == [final_snap_call]) + } + } else { + let snap_call = self.inner.lowerer.frac_ref_snap( + CallContext::BuiltinMethod, + &decl.target_type, + &decl.target_type, + deref_place, + address, + lifetime.into(), + slice_len, + self.inner.position, + )?; + expr! { + [current_snapshot] == [snap_call] + } + }; + let expression = expr! { + guard ==> [equalities] + }; + self.add_snapshot_body_postcondition(expression) + } + + pub(in super::super::super) fn add_structural_invariant( + &mut self, + decl: &vir_mid::type_decl::Struct, + ) -> SpannedEncodingResult<()> { + // let precondition_predicate = self.precondition_predicate()?; + let predicate_kind = PredicateKind::Owned; + let snap_call = self.field_owned_snap()?; + self.inner + .add_structural_invariant(decl, false, predicate_kind, &snap_call) + } + + pub(in super::super::super) fn take_owned_snapshot_functions_to_encode( + &mut self, + ) -> Vec { + std::mem::take(&mut self.inner.owned_snapshot_functions_to_encode) + } + + pub(in super::super::super) fn take_owned_range_snapshot_functions_to_encode( + &mut self, + ) -> Vec { + std::mem::take(&mut self.inner.owned_range_snapshot_functions_to_encode) + } + + // // FIXME: Code duplication. + // pub(in super::super::super) fn add_structural_invariant( + // &mut self, + // decl: &vir_mid::type_decl::Struct, + // ) -> SpannedEncodingResult<()> { + // if let Some(invariant) = decl.structural_invariant.clone() { + // let mut regular_field_arguments = Vec::new(); + // for field in &decl.fields { + // let owned_call = self.field_owned_snap()?; + // let snap_call = self.inner.create_field_snap_call(field, owned_call)?; + // regular_field_arguments.push(snap_call); + // // regular_field_arguments.push(self.create_field_snap_call(field)?); + // } + // let result = self.inner.result()?; + // let deref_fields = self + // .inner + // .lowerer + // .structural_invariant_to_deref_fields(&invariant)?; + // let mut constructor_encoder = AssertionToSnapshotConstructor::for_function_body( + // PredicateKind::Owned, + // self.inner.ty, + // regular_field_arguments, + // decl.fields.clone(), + // deref_fields, + // self.inner.position, + // ); + // let invariant_expression = invariant.into_iter().conjoin(); + // let permission_expression = invariant_expression.convert_into_permission_expression(); + // let constructor = constructor_encoder + // .expression_to_snapshot_constructor(self.inner.lowerer, &permission_expression)?; + // self.add_snapshot_body_postcondition(vir_low::Expression::equals( + // result.into(), + // constructor, + // ))?; + // // let mut equalities = Vec::new(); + // // for assertion in invariant { + // // for (guard, place) in assertion.collect_guarded_owned_places() { + // // let parameter = self.inner.lowerer.compute_deref_parameter(&place)?; + // // let deref_result_snapshot = self.inner.lowerer.obtain_parameter_snapshot( + // // self.inner.ty, + // // ¶meter.name, + // // parameter.ty, + // // result.clone().into(), + // // self.inner.position, + // // )?; + // // let ty = place.get_type(); + // // let place_low = self.inner.lowerer.encode_expression_as_place(&place)?; + // // let root_address_low = { + // // // Code duplication with pointer_deref_into_address + // // let deref_place = place.get_last_dereferenced_pointer().unwrap(); + // // // TODO: replace self in deref_place with result. + // // let base_snapshot = deref_place.to_pure_snapshot(self.inner.lowerer)?; + // // let ty = deref_place.get_type(); + // // self.inner + // // .lowerer + // // .pointer_address(ty, base_snapshot, place.position())? + // // }; + // // let snap_call = self.inner.lowerer.owned_non_aliased_snap( + // // CallContext::BuiltinMethod, + // // ty, + // // ty, + // // place_low, + // // root_address_low, + // // self.inner.position, + // // )?; + // // equalities.push(expr! { + // // [deref_result_snapshot] == [snap_call] + // // }); + // // } + // // } + // // self.add_snapshot_body_postcondition(equalities.into_iter().conjoin())?; + // } + + // // // FIXME: Code duplication with encode_assign_method_rvalue + // // if let Some(invariant) = &decl.structural_invariant { + // // let mut assertion_encoder = + // // crate::encoder::middle::core_proof::builtin_methods::AssertionEncoder::new( + // // &decl, + // // Vec::new(), + // // &None, + // // ); + // // assertion_encoder.set_result_value(self.inner.result()?.clone()); + // // assertion_encoder.set_in_function(); + // // for assertion in invariant { + // // let low_assertion = assertion_encoder.expression_to_snapshot( + // // self.inner.lowerer, + // // assertion, + // // true, + // // )?; + // // self.add_snapshot_body_postcondition(low_assertion)?; + // // } + // // } + // Ok(()) + // } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/owned_non_aliased/function_use.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/owned_non_aliased/function_use.rs new file mode 100644 index 00000000000..93ef015593d --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/owned_non_aliased/function_use.rs @@ -0,0 +1,74 @@ +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::{ + builtin_methods::CallContext, lowerer::Lowerer, + predicates::owned::builders::common::function_use::FunctionCallBuilder, + }, +}; +use vir_crate::{ + low::{self as vir_low}, + middle::{ + self as vir_mid, + operations::{const_generics::WithConstArguments, lifetimes::WithLifetimes}, + }, +}; + +pub(in super::super::super::super::super) struct OwnedNonAliasedSnapCallBuilder<'l, 'p, 'v, 'tcx, G> +where + G: WithLifetimes + WithConstArguments, +{ + inner: FunctionCallBuilder<'l, 'p, 'v, 'tcx, G>, +} + +impl<'l, 'p, 'v, 'tcx, G> OwnedNonAliasedSnapCallBuilder<'l, 'p, 'v, 'tcx, G> +where + G: WithLifetimes + WithConstArguments, +{ + pub(in super::super::super::super::super) fn new( + lowerer: &'l mut Lowerer<'p, 'v, 'tcx>, + context: CallContext, + ty: &'l vir_mid::Type, + generics: &'l G, + place: vir_low::Expression, + address: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult { + let arguments = vec![place, address]; + let inner = FunctionCallBuilder::new( + lowerer, + "snap_owned_non_aliased", + context, + ty, + generics, + arguments, + position, + )?; + Ok(Self { inner }) + } + + pub(in super::super::super::super::super) fn build( + self, + ) -> SpannedEncodingResult { + self.inner.build() + } + + pub(in super::super::super::super::super) fn add_custom_argument( + &mut self, + argument: vir_low::Expression, + ) -> SpannedEncodingResult<()> { + self.inner.arguments.push(argument); + Ok(()) + } + + pub(in super::super::super::super::super) fn add_lifetime_arguments( + &mut self, + ) -> SpannedEncodingResult<()> { + self.inner.add_lifetime_arguments() + } + + pub(in super::super::super::super::super) fn add_const_arguments( + &mut self, + ) -> SpannedEncodingResult<()> { + self.inner.add_const_arguments() + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/owned_non_aliased/mod.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/owned_non_aliased/mod.rs index ef427252419..6bcb70532ad 100644 --- a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/owned_non_aliased/mod.rs +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/owned_non_aliased/mod.rs @@ -1,2 +1,4 @@ +pub(super) mod function_decl; +pub(super) mod function_use; pub(super) mod predicate_decl; pub(super) mod predicate_use; diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/owned_non_aliased/predicate_decl.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/owned_non_aliased/predicate_decl.rs index 768d0b4dd55..973d94f06e9 100644 --- a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/owned_non_aliased/predicate_decl.rs +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/owned_non_aliased/predicate_decl.rs @@ -1,4 +1,3 @@ -use super::predicate_use::OwnedNonAliasedUseBuilder; use crate::encoder::{ errors::SpannedEncodingResult, middle::core_proof::{ @@ -8,28 +7,30 @@ use crate::encoder::{ places::PlacesInterface, predicates::{ owned::builders::{ - common::predicate_decl::PredicateDeclBuilder, PredicateDeclBuilderMethods, + common::predicate_decl::{ContainingPredicateKind, PredicateDeclBuilder}, + PredicateDeclBuilderMethods, }, - PredicatesMemoryBlockInterface, + PredicatesMemoryBlockInterface, PredicatesOwnedInterface, }, - references::ReferencesInterface, snapshots::{ - IntoPureSnapshot, IntoSnapshot, SnapshotBytesInterface, SnapshotValidityInterface, - SnapshotValuesInterface, + IntoPureSnapshot, IntoSnapshot, IntoSnapshotLowerer, PredicateKind, + SnapshotValidityInterface, SnapshotValuesInterface, }, type_layouts::TypeLayoutsInterface, }, }; +use prusti_common::config; use vir_crate::{ - common::expression::{GuardedExpressionIterator, QuantifierHelpers}, + common::{ + expression::{GuardedExpressionIterator, QuantifierHelpers}, + position::Positioned, + }, low::{self as vir_low}, - middle as vir_mid, + middle::{self as vir_mid}, }; pub(in super::super::super) struct OwnedNonAliasedBuilder<'l, 'p, 'v, 'tcx> { inner: PredicateDeclBuilder<'l, 'p, 'v, 'tcx>, - place: vir_low::VariableDecl, - root_address: vir_low::VariableDecl, snapshot: vir_low::VariableDecl, slice_len: Option, } @@ -56,18 +57,11 @@ impl<'l, 'p, 'v, 'tcx> OwnedNonAliasedBuilder<'l, 'p, 'v, 'tcx> { } else { None }; + let position = type_decl.position(); Ok(Self { - place: vir_low::VariableDecl::new("place", lowerer.place_type()?), - root_address: vir_low::VariableDecl::new("root_address", lowerer.address_type()?), snapshot: vir_low::VariableDecl::new("snapshot", ty.to_snapshot(lowerer)?), slice_len, - inner: PredicateDeclBuilder::new( - lowerer, - "OwnedNonAliased", - ty, - type_decl, - Default::default(), - )?, + inner: PredicateDeclBuilder::new(lowerer, "OwnedNonAliased", ty, type_decl, position)?, }) } @@ -76,9 +70,11 @@ impl<'l, 'p, 'v, 'tcx> OwnedNonAliasedBuilder<'l, 'p, 'v, 'tcx> { } pub(in super::super::super) fn create_parameters(&mut self) -> SpannedEncodingResult<()> { - self.inner.parameters.push(self.place.clone()); - self.inner.parameters.push(self.root_address.clone()); - self.inner.parameters.push(self.snapshot.clone()); + self.inner.parameters.push(self.inner.place.clone()); + self.inner.parameters.push(self.inner.address.clone()); + if config::use_snapshot_parameters_in_predicates() { + self.inner.parameters.push(self.snapshot.clone()); + } self.inner.create_lifetime_parameters()?; if let Some(slice_len_mid) = &self.slice_len { let slice_len = slice_len_mid.to_pure_snapshot(self.inner.lowerer)?; @@ -92,17 +88,17 @@ impl<'l, 'p, 'v, 'tcx> OwnedNonAliasedBuilder<'l, 'p, 'v, 'tcx> { self.inner.add_validity(&self.snapshot) } - fn compute_address(&self) -> SpannedEncodingResult { - use vir_low::macros::*; - let compute_address = ty!(Address); - let expression = expr! { - ComputeAddress::compute_address( - [self.place.clone().into()], - [self.root_address.clone().into()] - ) - }; - Ok(expression) - } + // fn compute_address(&self) -> SpannedEncodingResult { + // use vir_low::macros::*; + // let compute_address = ty!(Address); + // let expression = expr! { + // ComputeAddress::compute_address( + // [self.inner.place.clone().into()], + // [self.inner.address.clone().into()] + // ) + // }; + // Ok(expression) + // } fn size_of(&mut self) -> SpannedEncodingResult { self.inner @@ -118,10 +114,11 @@ impl<'l, 'p, 'v, 'tcx> OwnedNonAliasedBuilder<'l, 'p, 'v, 'tcx> { pub(in super::super::super) fn add_base_memory_block(&mut self) -> SpannedEncodingResult<()> { use vir_low::macros::*; - let compute_address = self.compute_address()?; + // let compute_address = self.compute_address()?; let size_of = self.size_of()?; + let address = &self.inner.address; let expression = expr! { - acc(MemoryBlock([compute_address], [size_of])) + acc(MemoryBlock(address, [size_of])) }; self.inner.add_conjunct(expression) } @@ -130,10 +127,12 @@ impl<'l, 'p, 'v, 'tcx> OwnedNonAliasedBuilder<'l, 'p, 'v, 'tcx> { &mut self, ) -> SpannedEncodingResult<()> { use vir_low::macros::*; - let compute_address = self.compute_address()?; + // let compute_address = self.compute_address()?; let padding_size = self.padding_size()?; + let address = &self.inner.address; let expression = expr! { - acc(MemoryBlock([compute_address], [padding_size])) + // acc(MemoryBlock([compute_address], [padding_size])) + acc(MemoryBlock(address, [padding_size])) }; self.inner.add_conjunct(expression) } @@ -148,7 +147,8 @@ impl<'l, 'p, 'v, 'tcx> OwnedNonAliasedBuilder<'l, 'p, 'v, 'tcx> { let bytes = self .inner .lowerer - .encode_memory_block_bytes_expression(self.compute_address()?, size_of)?; + // .encode_memory_block_bytes_expression(self.compute_address()?, size_of)?; + .encode_memory_block_bytes_expression(self.inner.address.clone().into(), size_of)?; let to_bytes = ty! { Bytes }; let expression = expr! { [bytes] == (Snap::to_bytes([snapshot])) @@ -162,20 +162,20 @@ impl<'l, 'p, 'v, 'tcx> OwnedNonAliasedBuilder<'l, 'p, 'v, 'tcx> { self.add_bytes_snapshot_equality_with(self.inner.ty, self.snapshot.clone().into()) } - pub(in super::super::super) fn add_bytes_address_snapshot_equality( - &mut self, - ) -> SpannedEncodingResult<()> { - let address_type = self.inner.lowerer.reference_address_type(self.inner.ty)?; - self.inner - .lowerer - .encode_snapshot_to_bytes_function(&address_type)?; - let target_address_snapshot = self.inner.lowerer.reference_address_snapshot( - self.inner.ty, - self.snapshot.clone().into(), - self.inner.position, - )?; - self.add_bytes_snapshot_equality_with(&address_type, target_address_snapshot) - } + // pub(in super::super::super) fn add_bytes_address_snapshot_equality( + // &mut self, + // ) -> SpannedEncodingResult<()> { + // let address_type = self.inner.lowerer.reference_address_type(self.inner.ty)?; + // self.inner + // .lowerer + // .encode_snapshot_to_bytes_function(&address_type)?; + // let target_address_snapshot = self.inner.lowerer.reference_address_snapshot( + // self.inner.ty, + // self.snapshot.clone().into(), + // self.inner.position, + // )?; + // self.add_bytes_snapshot_equality_with(&address_type, target_address_snapshot) + // } pub(in super::super::super) fn add_field_predicate( &mut self, @@ -184,27 +184,30 @@ impl<'l, 'p, 'v, 'tcx> OwnedNonAliasedBuilder<'l, 'p, 'v, 'tcx> { let field_place = self.inner.lowerer.encode_field_place( self.inner.ty, field, - self.place.clone().into(), + self.inner.place.clone().into(), self.inner.position, )?; - let field_snapshot = self.inner.lowerer.obtain_struct_field_snapshot( + let field_address = self.inner.lowerer.encode_field_address( self.inner.ty, field, - self.snapshot.clone().into(), - Default::default(), + self.inner.address.clone().into(), + self.inner.position, )?; - let mut builder = OwnedNonAliasedUseBuilder::new( - self.inner.lowerer, + // let field_snapshot = self.inner.lowerer.obtain_struct_field_snapshot( + // self.inner.ty, + // field, + // self.snapshot.clone().into(), + // Default::default(), + // )?; + let expression = self.inner.lowerer.owned_non_aliased( CallContext::BuiltinMethod, &field.ty, &field.ty, field_place, - self.root_address.clone().into(), - field_snapshot, + field_address, + None, + self.inner.position, )?; - builder.add_lifetime_arguments()?; - builder.add_const_arguments()?; - let expression = builder.build(); self.inner.add_conjunct(expression) } @@ -216,29 +219,34 @@ impl<'l, 'p, 'v, 'tcx> OwnedNonAliasedBuilder<'l, 'p, 'v, 'tcx> { let discriminant_place = self.inner.lowerer.encode_field_place( self.inner.ty, &discriminant_field, - self.place.clone().into(), + self.inner.place.clone().into(), self.inner.position, )?; - let discriminant_call = self.inner.lowerer.obtain_enum_discriminant( - self.snapshot.clone().into(), + let discriminant_address = self.inner.lowerer.encode_field_address( self.inner.ty, + &discriminant_field, + self.inner.address.clone().into(), self.inner.position, )?; - let discriminant_snapshot = self.inner.lowerer.construct_constant_snapshot( - &decl.discriminant_type, - discriminant_call, + let _discriminant_call = self.inner.lowerer.obtain_enum_discriminant( + self.snapshot.clone().into(), + self.inner.ty, self.inner.position, )?; - let builder = OwnedNonAliasedUseBuilder::new( - self.inner.lowerer, + // let discriminant_snapshot = self.inner.lowerer.construct_constant_snapshot( + // &decl.discriminant_type, + // discriminant_call, + // self.inner.position, + // )?; + let expression = self.inner.lowerer.owned_non_aliased( CallContext::BuiltinMethod, &decl.discriminant_type, &decl.discriminant_type, discriminant_place, - self.root_address.clone().into(), - discriminant_snapshot, + discriminant_address, + None, + self.inner.position, )?; - let expression = builder.build(); self.inner.add_conjunct(expression) } @@ -247,11 +255,14 @@ impl<'l, 'p, 'v, 'tcx> OwnedNonAliasedBuilder<'l, 'p, 'v, 'tcx> { target_type: &vir_mid::Type, lifetime: &vir_mid::ty::LifetimeConst, ) -> SpannedEncodingResult<()> { + let place = self.inner.place.clone(); + let address = self.inner.address.clone(); self.inner.add_unique_ref_target_predicate( target_type, lifetime, - &self.place, - &self.snapshot, + place.into(), + address, + ContainingPredicateKind::Owned, ) } @@ -260,10 +271,18 @@ impl<'l, 'p, 'v, 'tcx> OwnedNonAliasedBuilder<'l, 'p, 'v, 'tcx> { target_type: &vir_mid::Type, lifetime: &vir_mid::ty::LifetimeConst, ) -> SpannedEncodingResult<()> { - self.inner - .add_frac_ref_target_predicate(target_type, lifetime, &self.place, &self.snapshot) + let place = self.inner.place.clone(); + let address = self.inner.address.clone(); + self.inner.add_frac_ref_target_predicate( + target_type, + lifetime, + place.into(), + address, + ContainingPredicateKind::Owned, + ) } + // FIXME: Code duplication. pub(in super::super::super) fn get_slice_len( &self, ) -> SpannedEncodingResult { @@ -301,27 +320,30 @@ impl<'l, 'p, 'v, 'tcx> OwnedNonAliasedBuilder<'l, 'p, 'v, 'tcx> { let array_length_int = self.inner.array_length_int(array_length_mid)?; let element_place = self.inner.lowerer.encode_index_place( self.inner.ty, - self.place.clone().into(), + self.inner.place.clone().into(), index.clone().into(), self.inner.position, )?; - let element_snapshot = self.inner.lowerer.obtain_array_element_snapshot( - self.snapshot.clone().into(), - index_int.clone(), + let element_address = self.inner.lowerer.encode_index_address( + self.inner.ty, + self.inner.address.clone().into(), + index.clone().into(), self.inner.position, )?; - let mut builder = OwnedNonAliasedUseBuilder::new( - self.inner.lowerer, + // let element_snapshot = self.inner.lowerer.obtain_array_element_snapshot( + // self.snapshot.clone().into(), + // index_int.clone(), + // self.inner.position, + // )?; + let element_predicate_acc = self.inner.lowerer.owned_non_aliased( CallContext::BuiltinMethod, element_type, element_type, element_place, - self.root_address.clone().into(), - element_snapshot, + element_address, + None, + self.inner.position, )?; - builder.add_lifetime_arguments()?; - builder.add_const_arguments()?; - let element_predicate_acc = builder.build(); let elements = vir_low::Expression::forall( vec![index], vec![vir_low::Trigger::new(vec![element_predicate_acc.clone()])], @@ -335,16 +357,47 @@ impl<'l, 'p, 'v, 'tcx> OwnedNonAliasedBuilder<'l, 'p, 'v, 'tcx> { pub(in super::super::super) fn create_variant_predicate( &mut self, + decl: &vir_mid::type_decl::Enum, discriminant_value: vir_mid::DiscriminantValue, variant: &vir_mid::type_decl::Struct, variant_type: &vir_mid::Type, ) -> SpannedEncodingResult<(vir_low::Expression, vir_low::Expression)> { use vir_low::macros::*; - let discriminant_call = self.inner.lowerer.obtain_enum_discriminant( - self.snapshot.clone().into(), - self.inner.ty, - self.inner.position, - )?; + let discriminant_call = if config::use_snapshot_parameters_in_predicates() { + self.inner.lowerer.obtain_enum_discriminant( + self.snapshot.clone().into(), + self.inner.ty, + self.inner.position, + )? + } else { + // FIXME: Code duplication with other create_variant_predicate methods. + let discriminant_field = decl.discriminant_field(); + let discriminant_place = self.inner.lowerer.encode_field_place( + self.inner.ty, + &discriminant_field, + self.inner.place.clone().into(), + self.inner.position, + )?; + let discriminant_address = self.inner.lowerer.encode_field_address( + self.inner.ty, + &discriminant_field, + self.inner.address.clone().into(), + self.inner.position, + )?; + let discriminant_snapshot = self.inner.lowerer.owned_non_aliased_snap( + CallContext::BuiltinMethod, + &decl.discriminant_type, + &decl.discriminant_type, + discriminant_place, + discriminant_address, + self.inner.position, + )?; + self.inner.lowerer.obtain_constant_value( + &decl.discriminant_type, + discriminant_snapshot, + self.inner.position, + )? + }; let guard = expr! { [ discriminant_call ] == [ discriminant_value.into() ] }; @@ -352,27 +405,30 @@ impl<'l, 'p, 'v, 'tcx> OwnedNonAliasedBuilder<'l, 'p, 'v, 'tcx> { let variant_place = self.inner.lowerer.encode_enum_variant_place( self.inner.ty, &variant_index, - self.place.clone().into(), + self.inner.place.clone().into(), self.inner.position, )?; - let variant_snapshot = self.inner.lowerer.obtain_enum_variant_snapshot( + let variant_address = self.inner.lowerer.encode_enum_variant_address( self.inner.ty, &variant_index, - self.snapshot.clone().into(), + self.inner.address.clone().into(), self.inner.position, )?; - let mut builder = OwnedNonAliasedUseBuilder::new( - self.inner.lowerer, + // let variant_snapshot = self.inner.lowerer.obtain_enum_variant_snapshot( + // self.inner.ty, + // &variant_index, + // self.snapshot.clone().into(), + // self.inner.position, + // )?; + let predicate = self.inner.lowerer.owned_non_aliased( CallContext::BuiltinMethod, variant_type, variant_type, variant_place, - self.root_address.clone().into(), - variant_snapshot, + variant_address, + None, + self.inner.position, )?; - builder.add_lifetime_arguments()?; - builder.add_const_arguments()?; - let predicate = builder.build(); Ok((guard, predicate)) } @@ -383,4 +439,313 @@ impl<'l, 'p, 'v, 'tcx> OwnedNonAliasedBuilder<'l, 'p, 'v, 'tcx> { self.inner .add_conjunct(variant_predicates.into_iter().create_match()) } + + pub(in super::super::super) fn add_structural_invariant( + &mut self, + decl: &vir_mid::type_decl::Struct, + ) -> SpannedEncodingResult> { + self.inner + .add_structural_invariant(decl, PredicateKind::Owned) + } + + // pub(in super::super::super) fn add_structural_invariant( + // &mut self, + // decl: &vir_mid::type_decl::Struct, + // ) -> SpannedEncodingResult<()> { + // if let Some(invariant) = &decl.structural_invariant { + // let mut encoder = SelfFramingAssertionToSnapshot::for_predicate_body( + // self.inner.place.clone(), + // self.inner.address.clone(), + // PredicateKind::Owned, + // ); + // // let mut encoder = PredicateAssertionEncoder { + // // place: &self.inner.place, + // // address: &self.inner.address, + // // snap_calls: Default::default(), + // // }; + // for assertion in invariant { + // let low_assertion = + // encoder.expression_to_snapshot(self.inner.lowerer, assertion, true)?; + // self.inner.add_conjunct(low_assertion)?; + // } + // } + // Ok(()) + // } } + +// // FIXME: Move this to its own module. +// FIXME: This should be replaced by prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/assertions/self_framing.rs +// struct PredicateAssertionEncoder<'a> { +// place: &'a vir_low::VariableDecl, +// address: &'a vir_low::VariableDecl, +// /// Mapping from place to snapshot. We use a vector because we need to know +// /// the insertion order. +// snap_calls: Vec<(vir_mid::Expression, vir_low::Expression)>, +// } + +// impl<'a> PredicateAssertionEncoder<'a> { +// // FIXME: Code duplication. +// fn pointer_deref_into_address<'p, 'v, 'tcx>( +// &mut self, +// lowerer: &mut Lowerer<'p, 'v, 'tcx>, +// place: &vir_mid::Expression, +// ) -> SpannedEncodingResult { +// if let Some(deref_place) = place.get_last_dereferenced_pointer() { +// let base_snapshot = self.expression_to_snapshot(lowerer, deref_place, true)?; +// let ty = deref_place.get_type(); +// lowerer.pointer_address(ty, base_snapshot, place.position()) +// } else { +// unreachable!() +// } +// // match place { +// // vir_mid::Expression::Deref(deref) => { +// // let base_snapshot = self.expression_to_snapshot(lowerer, &deref.base, true)?; +// // let ty = deref.base.get_type(); +// // assert!(ty.is_pointer()); +// // lowerer.pointer_address(ty, base_snapshot, place.position()) +// // } +// // _ => unreachable!(), +// // } +// } +// } + +// impl<'a, 'p, 'v: 'p, 'tcx: 'v> IntoSnapshotLowerer<'p, 'v, 'tcx> for PredicateAssertionEncoder<'a> { +// fn expression_to_snapshot( +// &mut self, +// lowerer: &mut Lowerer<'p, 'v, 'tcx>, +// expression: &vir_mid::Expression, +// expect_math_bool: bool, +// ) -> SpannedEncodingResult { +// for (place, call) in &self.snap_calls { +// if place == expression { +// return Ok(call.clone()); +// } +// } +// self.expression_to_snapshot_impl(lowerer, expression, expect_math_bool) +// } + +// fn variable_to_snapshot( +// &mut self, +// lowerer: &mut Lowerer<'p, 'v, 'tcx>, +// variable: &vir_mid::VariableDecl, +// ) -> SpannedEncodingResult { +// assert!(variable.is_self_variable(), "{} must be self", variable); +// Ok(vir_low::VariableDecl { +// name: variable.name.clone(), +// ty: self.type_to_snapshot(lowerer, &variable.ty)?, +// }) +// } + +// fn labelled_old_to_snapshot( +// &mut self, +// _lowerer: &mut Lowerer<'p, 'v, 'tcx>, +// _old: &vir_mid::LabelledOld, +// _expect_math_bool: bool, +// ) -> SpannedEncodingResult { +// unreachable!("Old expression are not allowed in predicates"); +// } + +// fn func_app_to_snapshot( +// &mut self, +// lowerer: &mut Lowerer<'p, 'v, 'tcx>, +// app: &vir_mid::FuncApp, +// expect_math_bool: bool, +// ) -> SpannedEncodingResult { +// todo!() +// } + +// fn binary_op_to_snapshot( +// &mut self, +// lowerer: &mut Lowerer<'p, 'v, 'tcx>, +// op: &vir_mid::BinaryOp, +// expect_math_bool: bool, +// ) -> SpannedEncodingResult { +// // TODO: Create impl versions of each method so that I can override +// // without copying. +// let mut introduced_snap = false; +// if op.op_kind == vir_mid::BinaryOpKind::And { +// if let box vir_mid::Expression::AccPredicate(expression) = &op.left { +// if expression.predicate.is_owned_non_aliased() { +// introduced_snap = true; +// } +// } +// } +// let expression = self.binary_op_to_snapshot_impl(lowerer, op, expect_math_bool)?; +// if introduced_snap { +// // TODO: Use the snap calls from this vector instead of generating +// // on demand. This must always succeed because we require +// // expressions to be framed. +// self.snap_calls.pop(); +// } +// Ok(expression) +// } + +// fn acc_predicate_to_snapshot( +// &mut self, +// lowerer: &mut Lowerer<'p, 'v, 'tcx>, +// acc_predicate: &vir_mid::AccPredicate, +// expect_math_bool: bool, +// ) -> SpannedEncodingResult { +// assert!(expect_math_bool); +// let expression = match &*acc_predicate.predicate { +// vir_mid::Predicate::OwnedNonAliased(predicate) => { +// let ty = predicate.place.get_type(); +// let place = lowerer.encode_expression_as_place(&predicate.place)?; +// let address = self.pointer_deref_into_address(lowerer, &predicate.place)?; +// let snapshot = true.into(); +// let acc = lowerer.owned_non_aliased( +// CallContext::Procedure, +// ty, +// ty, +// place.clone(), +// address.clone(), +// snapshot, +// None, +// )?; +// let snap_call = lowerer.owned_non_aliased_snap( +// CallContext::BuiltinMethod, +// ty, +// ty, +// place, +// address, +// predicate.place.position(), +// )?; +// self.snap_calls.push((predicate.place.clone(), snap_call)); +// acc +// } +// vir_mid::Predicate::MemoryBlockHeap(predicate) => { +// let place = lowerer.encode_expression_as_place(&predicate.address)?; +// let address = self.pointer_deref_into_address(lowerer, &predicate.address)?; +// use vir_low::macros::*; +// let compute_address = ty!(Address); +// let address = expr! { +// ComputeAddress::compute_address([place], [address]) +// }; +// let size = +// self.expression_to_snapshot(lowerer, &predicate.size, expect_math_bool)?; +// lowerer.encode_memory_block_acc(address, size, acc_predicate.position)? +// } +// vir_mid::Predicate::MemoryBlockHeapDrop(predicate) => { +// let place = self.pointer_deref_into_address(lowerer, &predicate.address)?; +// let size = +// self.expression_to_snapshot(lowerer, &predicate.size, expect_math_bool)?; +// lowerer.encode_memory_block_heap_drop_acc(place, size, acc_predicate.position)? +// } +// _ => unimplemented!("{acc_predicate}"), +// }; +// Ok(expression) +// } + +// fn field_to_snapshot( +// &mut self, +// lowerer: &mut Lowerer<'p, 'v, 'tcx>, +// field: &vir_mid::Field, +// expect_math_bool: bool, +// ) -> SpannedEncodingResult { +// match &*field.base { +// vir_mid::Expression::Local(local) => { +// assert!(local.variable.is_self_variable()); +// let field_place = lowerer.encode_field_place( +// &local.variable.ty, +// &field.field, +// self.inner.place.clone().into(), +// field.position, +// )?; +// lowerer.owned_non_aliased_snap( +// CallContext::BuiltinMethod, +// &field.field.ty, +// &field.field.ty, +// field_place, +// self.inner.address.clone().into(), +// local.position, +// ) +// } +// _ => { +// // FIXME: Code duplication because Rust does not have syntax for calling +// // overriden methods. +// let base_snapshot = +// self.expression_to_snapshot(lowerer, &field.base, expect_math_bool)?; +// let result = if field.field.is_discriminant() { +// let ty = field.base.get_type(); +// // FIXME: Create a method for obtainging the discriminant type. +// let type_decl = lowerer.encoder.get_type_decl_mid(ty)?; +// let enum_decl = type_decl.unwrap_enum(); +// let discriminant_call = +// lowerer.obtain_enum_discriminant(base_snapshot, ty, field.position)?; +// lowerer.construct_constant_snapshot( +// &enum_decl.discriminant_type, +// discriminant_call, +// field.position, +// )? +// } else { +// lowerer.obtain_struct_field_snapshot( +// field.base.get_type(), +// &field.field, +// base_snapshot, +// field.position, +// )? +// }; +// self.ensure_bool_expression(lowerer, field.get_type(), result, expect_math_bool) +// } +// } +// } + +// // FIXME: Code duplication. +// fn deref_to_snapshot( +// &mut self, +// lowerer: &mut Lowerer<'p, 'v, 'tcx>, +// deref: &vir_mid::Deref, +// expect_math_bool: bool, +// ) -> SpannedEncodingResult { +// let base_snapshot = self.expression_to_snapshot(lowerer, &deref.base, expect_math_bool)?; +// let ty = deref.base.get_type(); +// let result = if ty.is_reference() { +// lowerer.reference_target_current_snapshot(ty, base_snapshot, Default::default())? +// } else { +// let aliased_root_place = lowerer.encode_aliased_place_root(deref.position)?; +// let address = lowerer.pointer_address(ty, base_snapshot, deref.position)?; +// lowerer.owned_non_aliased_snap( +// CallContext::BuiltinMethod, +// &deref.ty, +// &deref.ty, +// aliased_root_place, +// address, +// deref.position, +// )? +// // snap_owned_non_aliased$I32(aliased_place_root(), destructor$Snap$ptr$I32$$address(snap_owned_non_aliased$ptr$I32(field_place$$struct$m_T5$$$f$p2(place), address))) + +// // FIXME: This should be unreachable. Most likely, in predicates we should use snap +// // functions. +// // let heap = vir_low::VariableDecl::new("predicate_heap$", lowerer.heap_type()?); +// // lowerer.pointer_target_snapshot_in_heap( +// // deref.base.get_type(), +// // heap, +// // base_snapshot, +// // deref.position, +// // )? +// }; +// self.ensure_bool_expression(lowerer, deref.get_type(), result, expect_math_bool) +// } + +// fn owned_non_aliased_snap( +// &mut self, +// lowerer: &mut Lowerer<'p, 'v, 'tcx>, +// ty: &vir_mid::Type, +// pointer_snapshot: &vir_mid::Expression, +// ) -> SpannedEncodingResult { +// unimplemented!() +// } + +// fn call_context(&self) -> CallContext { +// CallContext::BuiltinMethod +// } + +// // fn unfolding_to_snapshot( +// // &mut self, +// // lowerer: &mut Lowerer<'p, 'v, 'tcx>, +// // unfolding: &vir_mid::Unfolding, +// // expect_math_bool: bool, +// // ) -> SpannedEncodingResult { +// // todo!() +// // } +// } diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/owned_non_aliased/predicate_use.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/owned_non_aliased/predicate_use.rs index f40e59eee36..bc1a826b325 100644 --- a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/owned_non_aliased/predicate_use.rs +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/owned_non_aliased/predicate_use.rs @@ -1,11 +1,16 @@ use crate::encoder::{ errors::SpannedEncodingResult, middle::core_proof::{ - builtin_methods::CallContext, lowerer::Lowerer, - predicates::owned::builders::common::predicate_use::PredicateUseBuilder, + builtin_methods::CallContext, + lowerer::Lowerer, + predicates::{ + owned::builders::common::predicate_use::PredicateUseBuilder, PredicatesOwnedInterface, + }, }, }; +use prusti_common::config; use vir_crate::{ + common::expression::BinaryOperationHelpers, low::{self as vir_low}, middle::{ self as vir_mid, @@ -18,6 +23,7 @@ where G: WithLifetimes + WithConstArguments, { inner: PredicateUseBuilder<'l, 'p, 'v, 'tcx, G>, + snapshot: Option, } impl<'l, 'p, 'v, 'tcx, G> OwnedNonAliasedUseBuilder<'l, 'p, 'v, 'tcx, G> @@ -30,23 +36,57 @@ where ty: &'l vir_mid::Type, generics: &'l G, place: vir_low::Expression, - root_address: vir_low::Expression, - snapshot: vir_low::Expression, + address: vir_low::Expression, + position: vir_mid::Position, ) -> SpannedEncodingResult { + let arguments = vec![place, address]; let inner = PredicateUseBuilder::new( lowerer, "OwnedNonAliased", context, ty, generics, - vec![place, root_address, snapshot], - Default::default(), + arguments, + position, )?; - Ok(Self { inner }) + Ok(Self { + inner, + snapshot: None, + }) + } + + pub(in super::super::super::super::super) fn build( + mut self, + ) -> SpannedEncodingResult { + let expression = if let Some(snapshot) = self.snapshot.take() { + let snap_call = self.inner.lowerer.owned_non_aliased_snap( + self.inner.context, + self.inner.ty, + self.inner.generics, + self.inner.arguments[0].clone(), + self.inner.arguments[1].clone(), + self.inner.position, + )?; + vir_low::Expression::and( + self.inner.build(), + vir_low::Expression::equals(snapshot, snap_call), + ) + } else { + self.inner.build() + }; + Ok(expression) } - pub(in super::super::super::super::super) fn build(self) -> vir_low::Expression { - self.inner.build() + pub(in super::super::super::super::super) fn add_snapshot_argument( + &mut self, + snapshot: vir_low::Expression, + ) -> SpannedEncodingResult<()> { + if config::use_snapshot_parameters_in_predicates() { + self.inner.arguments.push(snapshot); + } else { + self.snapshot = Some(snapshot); + } + Ok(()) } pub(in super::super::super::super::super) fn add_lifetime_arguments( diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/unique_ref/function_current_decl.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/unique_ref/function_current_decl.rs new file mode 100644 index 00000000000..3c74996bec9 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/unique_ref/function_current_decl.rs @@ -0,0 +1,577 @@ +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::{ + addresses::AddressesInterface, + builtin_methods::CallContext, + lifetimes::LifetimesInterface, + lowerer::Lowerer, + permissions::PermissionsInterface, + predicates::{ + owned::builders::common::function_decl::FunctionDeclBuilder, PredicatesOwnedInterface, + }, + snapshots::{IntoPureSnapshot, PredicateKind}, + type_layouts::TypeLayoutsInterface, + }, +}; +use vir_crate::{ + low::{self as vir_low}, + middle::{ + self as vir_mid, + operations::{const_generics::WithConstArguments, lifetimes::WithLifetimes}, + }, +}; + +pub(in super::super::super) struct UniqueRefCurrentSnapFunctionBuilder<'l, 'p, 'v, 'tcx> { + inner: FunctionDeclBuilder<'l, 'p, 'v, 'tcx>, + // place: vir_low::VariableDecl, + address: vir_low::VariableDecl, + reference_lifetime: vir_low::VariableDecl, + slice_len: Option, +} + +impl<'l, 'p, 'v, 'tcx> UniqueRefCurrentSnapFunctionBuilder<'l, 'p, 'v, 'tcx> { + pub(in super::super::super) fn new( + lowerer: &'l mut Lowerer<'p, 'v, 'tcx>, + ty: &'l vir_mid::Type, + type_decl: &'l vir_mid::TypeDecl, + ) -> SpannedEncodingResult { + let slice_len = if ty.is_slice() { + Some(vir_mid::VariableDecl::new( + "slice_len", + lowerer.size_type_mid()?, + )) + } else { + None + }; + let function_name = "snap_current_unique_ref"; + Ok(Self { + address: vir_low::VariableDecl::new("address", lowerer.address_type()?), + reference_lifetime: vir_low::VariableDecl::new( + "reference_lifetime", + lowerer.lifetime_type()?, + ), + slice_len, + inner: FunctionDeclBuilder::new( + lowerer, + function_name, + ty, + type_decl, + Default::default(), + )?, + }) + } + + pub(in super::super::super) fn get_snapshot_postconditions( + &self, + ) -> SpannedEncodingResult> { + self.inner.get_snapshot_postconditions() + } + + pub(in super::super::super) fn get_snapshot_body( + &self, + ) -> SpannedEncodingResult> { + self.inner.get_snapshot_body() + } + + pub(in super::super::super) fn build(self) -> SpannedEncodingResult { + self.inner.build() + } + + pub(in super::super::super) fn create_parameters(&mut self) -> SpannedEncodingResult<()> { + self.inner.parameters.push(self.inner.place.clone()); + self.inner.parameters.push(self.address.clone()); + self.inner.parameters.push(self.reference_lifetime.clone()); + self.inner.create_lifetime_parameters()?; + if let Some(slice_len_mid) = &self.slice_len { + let slice_len = slice_len_mid.to_pure_snapshot(self.inner.lowerer)?; + self.inner.parameters.push(slice_len); + } + self.inner.create_const_parameters()?; + Ok(()) + } + + // // FIXME: Code duplication. + // pub(in super::super::super) fn get_slice_len( + // &self, + // ) -> SpannedEncodingResult { + // Ok(self.slice_len.as_ref().unwrap().clone()) + // } + + fn unique_ref_predicate( + &mut self, + ty: &vir_mid::Type, + generics: &G, + place: vir_low::Expression, + address: vir_low::Expression, + reference_lifetime: vir_low::Expression, + ) -> SpannedEncodingResult + where + G: WithLifetimes + WithConstArguments, + { + let slice_len = if let Some(slice_len_mid) = &self.slice_len { + let slice_len = slice_len_mid.to_pure_snapshot(self.inner.lowerer)?; + Some(slice_len.into()) + } else { + None + }; + let wildcard_permission = self.inner.lowerer.wildcard_permission()?; + self.inner.lowerer.unique_ref( + CallContext::BuiltinMethod, + ty, + generics, + place, + address, + reference_lifetime, + slice_len, + Some(wildcard_permission), + self.inner.position, + ) + } + + // // FIXME: Code duplication with add_quantified_permission. + // pub(in super::super::super) fn add_quantifiers( + // &mut self, + // array_length_mid: &vir_mid::VariableDecl, + // element_type: &vir_mid::Type, + // ) -> SpannedEncodingResult<()> { + // use vir_low::macros::*; + // let size_type_mid = self.inner.lowerer.size_type_mid()?; + // var_decls! { + // index_int: Int + // }; + // let index = self.inner.lowerer.construct_constant_snapshot( + // &size_type_mid, + // index_int.clone().into(), + // self.inner.position, + // )?; + // let index_validity = self + // .inner + // .lowerer + // .encode_snapshot_valid_call_for_type(index.clone(), &size_type_mid)?; + // let array_length_int = self.inner.array_length_int(array_length_mid)?; + // let element_place = self.inner.lowerer.encode_index_place( + // self.inner.ty, + // self.place.clone().into(), + // index, + // self.inner.position, + // )?; + // let element_predicate_acc = { + // self.owned_predicate( + // element_type, + // element_type, + // element_place.clone(), + // self.address.clone().into(), + // )? + // }; + // let result = self.inner.result()?.into(); + // let element_snapshot = self.inner.lowerer.obtain_array_element_snapshot( + // result, + // index_int.clone().into(), + // self.inner.position, + // )?; + // let element_snap_call = self.inner.lowerer.owned_non_aliased_snap( + // CallContext::BuiltinMethod, + // element_type, + // element_type, + // element_place, + // self.address.clone().into(), + // )?; + // let elements = vir_low::Expression::forall( + // vec![index_int.clone()], + // vec![vir_low::Trigger::new(vec![element_predicate_acc])], + // expr! { + // ([index_validity] && (index_int < [array_length_int])) ==> + // ([element_snapshot] == [element_snap_call]) + // }, + // ); + // self.add_unfolding_postcondition(elements) + // } + + pub(in super::super::super) fn add_snapshot_body_postcondition( + &mut self, + body: vir_low::Expression, + ) -> SpannedEncodingResult<()> { + // let predicate = self.precondition_predicate()?; + // let unfolding = predicate.into_unfolding(body); + self.inner.add_snapshot_body_postcondition(body) + } + + // pub(in super::super::super) fn add_snapshot_postcondition( + // &mut self, + // expression: vir_low::Expression, + // ) -> SpannedEncodingResult<()> { + // self.inner.add_snapshot_postcondition(expression) + // } + + pub(in super::super::super) fn add_validity_postcondition( + &mut self, + ) -> SpannedEncodingResult<()> { + self.inner.add_validity_postcondition() + } + + // pub(in super::super::super) fn add_validity_postcondition( + // &mut self, + // ) -> SpannedEncodingResult<()> { + // self.inner.add_validity_postcondition() + // } + + // pub(in super::super::super) fn add_snapshot_len_equal_to_postcondition( + // &mut self, + // array_length_mid: &vir_mid::VariableDecl, + // ) -> SpannedEncodingResult<()> { + // self.inner + // .add_snapshot_len_equal_to_postcondition(array_length_mid) + // } + + pub(in super::super::super) fn add_unique_ref_precondition( + &mut self, + ) -> SpannedEncodingResult<()> { + let predicate = self.precondition_predicate()?; + self.inner.add_precondition(predicate) + } + + fn precondition_predicate(&mut self) -> SpannedEncodingResult { + self.unique_ref_predicate( + self.inner.ty, + self.inner.type_decl, + self.inner.place.clone().into(), + self.address.clone().into(), + self.reference_lifetime.clone().into(), + ) + } + + pub(in super::super::super) fn add_structural_invariant( + &mut self, + decl: &vir_mid::type_decl::Struct, + ) -> SpannedEncodingResult<()> { + // let precondition_predicate = if self.is_final { + // None + // } else { + // Some(self.precondition_predicate()?) + // }; + let predicate_kind = PredicateKind::UniqueRef { + lifetime: self.reference_lifetime.clone().into(), + is_final: false, + }; + let snap_call = self.field_unique_ref_snap()?; + self.inner + .add_structural_invariant(decl, false, predicate_kind, &snap_call) + } + + // fn compute_address(&self) -> SpannedEncodingResult { + // use vir_low::macros::*; + // let compute_address = ty!(Address); + // let expression = expr! { + // ComputeAddress::compute_address( + // [self.place.clone().into()], + // [self.address.clone().into()] + // ) + // }; + // Ok(expression) + // } + + // fn size_of(&mut self) -> SpannedEncodingResult { + // self.inner + // .lowerer + // .encode_type_size_expression2(self.inner.ty, self.inner.type_decl) + // } + + // fn add_bytes_snapshot_equality_with( + // &mut self, + // snap_ty: &vir_mid::Type, + // snapshot: vir_low::Expression, + // ) -> SpannedEncodingResult<()> { + // use vir_low::macros::*; + // let size_of = self.size_of()?; + // let bytes = self + // .inner + // .lowerer + // .encode_memory_block_bytes_expression(self.compute_address()?, size_of)?; + // let to_bytes = ty! { Bytes }; + // let expression = expr! { + // [bytes] == (Snap::to_bytes([snapshot])) + // }; + // self.add_unfolding_postcondition(expression) + // } + + // pub(in super::super::super) fn add_bytes_snapshot_equality( + // &mut self, + // ) -> SpannedEncodingResult<()> { + // let result = self.inner.result()?.into(); + // self.add_bytes_snapshot_equality_with(self.inner.ty, result) + // } + + // pub(in super::super::super) fn add_bytes_address_snapshot_equality( + // &mut self, + // ) -> SpannedEncodingResult<()> { + // let result = self.inner.result()?.into(); + // let address_type = self.inner.lowerer.reference_address_type(self.inner.ty)?; + // self.inner + // .lowerer + // .encode_snapshot_to_bytes_function(&address_type)?; + // let target_address_snapshot = self.inner.lowerer.reference_address_snapshot( + // self.inner.ty, + // result, + // self.inner.position, + // )?; + // self.add_bytes_snapshot_equality_with(&address_type, target_address_snapshot) + // } + + // pub(in super::super::super) fn create_field_snapshot_equality( + // &mut self, + // field: &vir_mid::FieldDecl, + // ) -> SpannedEncodingResult { + // use vir_low::macros::*; + // let result = self.inner.result()?; + // let field_place = self.inner.lowerer.encode_field_place( + // self.inner.ty, + // field, + // self.place.clone().into(), + // self.inner.position, + // )?; + // let field_snapshot = self.inner.lowerer.obtain_struct_field_snapshot( + // self.inner.ty, + // field, + // result.into(), + // self.inner.position, + // )?; + // let snap_call = self.inner.lowerer.owned_non_aliased_snap( + // CallContext::BuiltinMethod, + // &field.ty, + // &field.ty, + // field_place, + // self.address.clone().into(), + // )?; + // Ok(expr! { + // [field_snapshot] == [snap_call] + // }) + // } + + pub(in super::super::super) fn create_discriminant_snapshot_equality( + &mut self, + decl: &vir_mid::type_decl::Enum, + ) -> SpannedEncodingResult { + let call = self.discriminant_unique_ref_snap()?; + self.inner.create_discriminant_snapshot_equality(decl, call) + } + + fn discriminant_unique_ref_snap( + &mut self, + ) -> SpannedEncodingResult< + impl Fn( + &mut FunctionDeclBuilder, + &vir_mid::type_decl::Enum, + vir_low::Expression, + vir_low::Expression, + ) -> SpannedEncodingResult, + > { + let target_slice_len = self.slice_len_expression()?; + let lifetime: vir_low::Expression = self.reference_lifetime.clone().into(); + let lifetime = std::rc::Rc::new(lifetime); + Ok( + move |builder: &mut FunctionDeclBuilder, + decl: &vir_mid::type_decl::Enum, + discriminant_place, + discriminant_address| { + builder.lowerer.unique_ref_snap( + CallContext::BuiltinMethod, + &decl.discriminant_type, + &decl.discriminant_type, + discriminant_place, + discriminant_address, + (*lifetime).clone(), + target_slice_len.clone(), + false, + builder.position, + ) + }, + ) + } + + // pub(in super::super::super) fn create_discriminant_snapshot_equality( + // &mut self, + // decl: &vir_mid::type_decl::Enum, + // ) -> SpannedEncodingResult { + // use vir_low::macros::*; + // let result = self.inner.result()?; + // let discriminant_snapshot = self.inner.lowerer.obtain_enum_discriminant( + // result.into(), + // self.inner.ty, + // self.inner.position, + // )?; + // let discriminant_field = decl.discriminant_field(); + // let discriminant_place = self.inner.lowerer.encode_field_place( + // self.inner.ty, + // &discriminant_field, + // self.inner.place.clone().into(), + // self.inner.position, + // )?; + // let snap_call = self.inner.lowerer.unique_ref_snap( + // CallContext::BuiltinMethod, + // &decl.discriminant_type, + // &decl.discriminant_type, + // discriminant_place, + // self.address.clone().into(), + // self.reference_lifetime.clone().into(), + // None, // FIXME + // false, + // self.inner.position, + // )?; + // let snap_call_int = self.inner.lowerer.obtain_constant_value( + // &decl.discriminant_type, + // snap_call, + // self.inner.position, + // )?; + // Ok(expr! { + // [discriminant_snapshot] == [snap_call_int] + // }) + // } + + pub(in super::super::super) fn create_variant_snapshot_equality( + &mut self, + discriminant_value: vir_mid::DiscriminantValue, + variant: &vir_mid::type_decl::Struct, + ) -> SpannedEncodingResult<(vir_low::Expression, vir_low::Expression)> { + let call = self.variant_unique_ref_snap()?; + self.inner + .create_variant_snapshot_equality(discriminant_value, variant, call) + } + + fn variant_unique_ref_snap( + &mut self, + ) -> SpannedEncodingResult< + impl Fn( + &mut FunctionDeclBuilder, + &vir_mid::Type, + vir_low::Expression, + vir_low::Expression, + ) -> SpannedEncodingResult, + > { + let target_slice_len = self.slice_len_expression()?; + let lifetime: vir_low::Expression = self.reference_lifetime.clone().into(); + let lifetime = std::rc::Rc::new(lifetime); + Ok( + move |builder: &mut FunctionDeclBuilder, + variant_type: &vir_mid::Type, + variant_place, + variant_address| { + builder.lowerer.unique_ref_snap( + CallContext::BuiltinMethod, + variant_type, + // Enum variant and enum have the same set of lifetime parameters, + // so we use type_decl here. We cannot use `variant_type` because + // `ty` is normalized. + builder.type_decl, + variant_place, + variant_address, + (*lifetime).clone(), + target_slice_len.clone(), + false, + builder.position, + ) + }, + ) + } + + // pub(in super::super::super) fn create_variant_snapshot_equality( + // &mut self, + // discriminant_value: vir_mid::DiscriminantValue, + // variant: &vir_mid::type_decl::Struct, + // ) -> SpannedEncodingResult<(vir_low::Expression, vir_low::Expression)> { + // use vir_low::macros::*; + // let result = self.inner.result()?; + // let discriminant_call = self.inner.lowerer.obtain_enum_discriminant( + // result.clone().into(), + // self.inner.ty, + // self.inner.position, + // )?; + // let guard = expr! { + // [ discriminant_call ] == [ discriminant_value.into() ] + // }; + // let variant_index = variant.name.clone().into(); + // let variant_place = self.inner.lowerer.encode_enum_variant_place( + // self.inner.ty, + // &variant_index, + // self.inner.place.clone().into(), + // self.inner.position, + // )?; + // let variant_snapshot = self.inner.lowerer.obtain_enum_variant_snapshot( + // self.inner.ty, + // &variant_index, + // result.into(), + // self.inner.position, + // )?; + // let variant_type = self.inner.ty.clone().variant(variant_index); + // let snap_call = self.inner.lowerer.unique_ref_snap( + // CallContext::BuiltinMethod, + // &variant_type, + // &variant_type, + // variant_place, + // self.address.clone().into(), + // self.reference_lifetime.clone().into(), + // None, // FIXME + // false, + // self.inner.position, + // )?; + // let equality = expr! { + // [variant_snapshot] == [snap_call] + // }; + // Ok((guard, equality)) + // } + + // FIXME: Code duplication. + fn slice_len(&mut self) -> SpannedEncodingResult> { + self.slice_len + .as_ref() + .map(|slice_len_mid| slice_len_mid.to_pure_snapshot(self.inner.lowerer)) + .transpose() + } + + // FIXME: Code duplication. + fn slice_len_expression(&mut self) -> SpannedEncodingResult> { + Ok(self.slice_len()?.map(|slice_len| slice_len.into())) + } + + pub(in super::super::super) fn create_field_snapshot_equality( + &mut self, + field: &vir_mid::FieldDecl, + ) -> SpannedEncodingResult { + let unique_ref_call = self.field_unique_ref_snap()?; + self.inner + .create_field_snapshot_equality(field, unique_ref_call) + } + + fn field_unique_ref_snap( + &mut self, + ) -> SpannedEncodingResult< + impl Fn( + &mut FunctionDeclBuilder, + &vir_mid::FieldDecl, + vir_low::Expression, + vir_low::Expression, + ) -> SpannedEncodingResult, + > { + let target_slice_len = self.slice_len_expression()?; + let lifetime: vir_low::Expression = self.reference_lifetime.clone().into(); + let lifetime = std::rc::Rc::new(lifetime); + Ok( + move |builder: &mut FunctionDeclBuilder, + field: &vir_mid::FieldDecl, + field_place, + field_address| { + builder.lowerer.unique_ref_snap( + CallContext::BuiltinMethod, + &field.ty, + &field.ty, + field_place, + // (*address).clone(), + field_address, + (*lifetime).clone(), + target_slice_len.clone(), + false, + builder.position, + ) + }, + ) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/unique_ref/function_current_range_decl.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/unique_ref/function_current_range_decl.rs new file mode 100644 index 00000000000..473111110fe --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/unique_ref/function_current_range_decl.rs @@ -0,0 +1,139 @@ +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::{ + builtin_methods::CallContext, + lifetimes::LifetimesInterface, + lowerer::Lowerer, + permissions::PermissionsInterface, + places::PlacesInterface, + predicates::{ + owned::builders::common::function_decl::FunctionDeclBuilder, PredicatesOwnedInterface, + }, + snapshots::{IntoPureSnapshot, IntoSnapshot}, + type_layouts::TypeLayoutsInterface, + }, +}; +use vir_crate::{ + common::identifier::WithIdentifier, + low::{self as vir_low}, + middle::{self as vir_mid}, +}; + +// FIXME: Code duplication with FracRefRangeSnapFunctionBuilder +pub(in super::super::super) struct UniqueRefCurrentRangeSnapFunctionBuilder<'l, 'p, 'v, 'tcx> { + inner: FunctionDeclBuilder<'l, 'p, 'v, 'tcx>, + address: vir_low::VariableDecl, + start_index: vir_low::VariableDecl, + end_index: vir_low::VariableDecl, + reference_lifetime: vir_low::VariableDecl, + slice_len: Option, + pres: Vec, + posts: Vec, +} + +impl<'l, 'p, 'v, 'tcx> UniqueRefCurrentRangeSnapFunctionBuilder<'l, 'p, 'v, 'tcx> { + pub(in super::super::super) fn new( + lowerer: &'l mut Lowerer<'p, 'v, 'tcx>, + ty: &'l vir_mid::Type, + type_decl: &'l vir_mid::TypeDecl, + ) -> SpannedEncodingResult { + let slice_len = if ty.is_slice() { + Some(vir_mid::VariableDecl::new( + "slice_len", + lowerer.size_type_mid()?, + )) + } else { + None + }; + Ok(Self { + address: vir_low::VariableDecl::new("address", ty.to_snapshot(lowerer)?), + start_index: vir_low::VariableDecl::new("start_index", lowerer.size_type()?), + end_index: vir_low::VariableDecl::new("end_index", lowerer.size_type()?), + reference_lifetime: vir_low::VariableDecl::new("lifetime", lowerer.lifetime_type()?), + slice_len, + inner: FunctionDeclBuilder::new( + lowerer, + "snap_unique_ref_current_range_aliased", + ty, + type_decl, + Default::default(), + )?, + pres: Vec::new(), + posts: Vec::new(), + }) + } + + pub(in super::super::super) fn build(mut self) -> SpannedEncodingResult { + let return_type = self.inner.range_result_type()?; + let function = vir_low::FunctionDecl { + name: format!( + "{}${}", + self.inner.function_name, + self.inner.ty.get_identifier() + ), + kind: vir_low::FunctionKind::SnapRange, + parameters: self.inner.parameters, + body: None, + pres: self.pres, + posts: self.posts, + return_type, + }; + Ok(function) + } + + pub(in super::super::super) fn create_parameters(&mut self) -> SpannedEncodingResult<()> { + self.inner.parameters.push(self.address.clone()); + self.inner.parameters.push(self.start_index.clone()); + self.inner.parameters.push(self.end_index.clone()); + self.inner.parameters.push(self.reference_lifetime.clone()); + self.inner.create_lifetime_parameters()?; + if let Some(slice_len_mid) = &self.slice_len { + let slice_len = slice_len_mid.to_pure_snapshot(self.inner.lowerer)?; + self.inner.parameters.push(slice_len); + } + self.inner.create_const_parameters()?; + Ok(()) + } + + pub(in super::super::super) fn add_owned_precondition(&mut self) -> SpannedEncodingResult<()> { + let wildcard_permission = self.inner.lowerer.wildcard_permission()?; + let predicates = self.inner.lowerer.unique_ref_range( + CallContext::BuiltinMethod, + self.inner.ty, + self.inner.type_decl, + self.address.clone().into(), + self.start_index.clone().into(), + self.end_index.clone().into(), + self.reference_lifetime.clone().into(), + Some(wildcard_permission), + self.inner.position, + )?; + self.pres.push(predicates); + Ok(()) + } + + pub(in super::super::super) fn add_postcondition(&mut self) -> SpannedEncodingResult<()> { + self.inner.create_range_postcondition( + &mut self.posts, + &self.address, + &self.start_index, + &self.end_index, + |lowerer, ty, element_address, position| { + let element_place = lowerer.place_option_none_constructor(position)?; + let TODO_target_slice_len = None; + lowerer.unique_ref_snap( + CallContext::BuiltinMethod, + ty, + ty, + element_place, + element_address, + self.reference_lifetime.clone().into(), + TODO_target_slice_len, + false, + position, + ) + }, + )?; + Ok(()) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/unique_ref/function_current_range_use.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/unique_ref/function_current_range_use.rs new file mode 100644 index 00000000000..d85dabd9b7b --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/unique_ref/function_current_range_use.rs @@ -0,0 +1,98 @@ +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::{ + builtin_methods::CallContext, lowerer::Lowerer, + predicates::owned::builders::common::function_use::FunctionCallBuilder, + snapshots::IntoSnapshot, + }, +}; +use vir_crate::{ + common::identifier::WithIdentifier, + low::{self as vir_low}, + middle::{ + self as vir_mid, + operations::{const_generics::WithConstArguments, lifetimes::WithLifetimes}, + }, +}; + +// FIXME: Code identical to `FracRefRangeSnapCallBuilder`. +pub(in super::super::super::super::super) struct UniqueRefCurrentRangeSnapCallBuilder< + 'l, + 'p, + 'v, + 'tcx, + G, +> where + G: WithLifetimes + WithConstArguments, +{ + inner: FunctionCallBuilder<'l, 'p, 'v, 'tcx, G>, +} + +impl<'l, 'p, 'v, 'tcx, G> UniqueRefCurrentRangeSnapCallBuilder<'l, 'p, 'v, 'tcx, G> +where + G: WithLifetimes + WithConstArguments, +{ + pub(in super::super::super::super::super) fn new( + lowerer: &'l mut Lowerer<'p, 'v, 'tcx>, + context: CallContext, + ty: &'l vir_mid::Type, + generics: &'l G, + address: vir_low::Expression, + start_index: vir_low::Expression, + end_index: vir_low::Expression, + reference_lifetime: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult { + let arguments = vec![address, start_index, end_index, reference_lifetime]; + let inner = FunctionCallBuilder::new( + lowerer, + "snap_unique_ref_current_range_aliased", + context, + ty, + generics, + arguments, + position, + )?; + Ok(Self { inner }) + } + + pub(in super::super::super::super::super) fn build( + self, + ) -> SpannedEncodingResult { + let vir_mid::Type::Pointer(pointer_type) = self.inner.ty else { + unreachable!("{} must be a pointer type", self.inner.ty); + }; + let element_type = pointer_type.target_type.to_snapshot(self.inner.lowerer)?; + let return_type = vir_low::Type::seq(element_type); + let call = vir_low::Expression::function_call( + format!( + "{}${}", + self.inner.function_name, + self.inner.ty.get_identifier() + ), + self.inner.arguments, + return_type, + ); + Ok(call.set_default_position(self.inner.position)) + } + + // pub(in super::super::super::super::super) fn add_custom_argument( + // &mut self, + // argument: vir_low::Expression, + // ) -> SpannedEncodingResult<()> { + // self.inner.arguments.push(argument); + // Ok(()) + // } + + pub(in super::super::super::super::super) fn add_lifetime_arguments( + &mut self, + ) -> SpannedEncodingResult<()> { + self.inner.add_lifetime_arguments() + } + + pub(in super::super::super::super::super) fn add_const_arguments( + &mut self, + ) -> SpannedEncodingResult<()> { + self.inner.add_const_arguments() + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/unique_ref/function_current_use.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/unique_ref/function_current_use.rs new file mode 100644 index 00000000000..b5525a953ec --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/unique_ref/function_current_use.rs @@ -0,0 +1,59 @@ +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::{ + builtin_methods::CallContext, lowerer::Lowerer, + predicates::owned::builders::common::function_use::FunctionCallBuilder, + }, +}; +use vir_crate::{ + low::{self as vir_low}, + middle::{ + self as vir_mid, + operations::{const_generics::WithConstArguments, lifetimes::WithLifetimes}, + }, +}; + +pub(in super::super::super) struct UniqueRefCurrentSnapCallBuilder<'l, 'p, 'v, 'tcx, G> +where + G: WithLifetimes + WithConstArguments, +{ + inner: FunctionCallBuilder<'l, 'p, 'v, 'tcx, G>, +} + +impl<'l, 'p, 'v, 'tcx, G> UniqueRefCurrentSnapCallBuilder<'l, 'p, 'v, 'tcx, G> +where + G: WithLifetimes + WithConstArguments, +{ + pub(in super::super::super) fn new( + lowerer: &'l mut Lowerer<'p, 'v, 'tcx>, + context: CallContext, + ty: &'l vir_mid::Type, + generics: &'l G, + place: vir_low::Expression, + root_address: vir_low::Expression, + reference_lifetime: vir_low::Expression, + target_slice_len: Option, + position: vir_low::Position, + ) -> SpannedEncodingResult { + let mut arguments = vec![place, root_address, reference_lifetime]; + if let Some(len) = target_slice_len { + arguments.push(len); + } + let name = "snap_current_unique_ref"; + let inner = + FunctionCallBuilder::new(lowerer, name, context, ty, generics, arguments, position)?; + Ok(Self { inner }) + } + + pub(in super::super::super) fn build(self) -> SpannedEncodingResult { + self.inner.build() + } + + pub(in super::super::super) fn add_lifetime_arguments(&mut self) -> SpannedEncodingResult<()> { + self.inner.add_lifetime_arguments() + } + + pub(in super::super::super) fn add_const_arguments(&mut self) -> SpannedEncodingResult<()> { + self.inner.add_const_arguments() + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/unique_ref/function_final_decl.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/unique_ref/function_final_decl.rs new file mode 100644 index 00000000000..9a0a48c1700 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/unique_ref/function_final_decl.rs @@ -0,0 +1,320 @@ +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::{ + addresses::AddressesInterface, + builtin_methods::CallContext, + function_gas::FunctionGasInterface, + lifetimes::LifetimesInterface, + lowerer::{DomainsLowererInterface, Lowerer}, + predicates::{ + owned::builders::common::function_decl::FunctionDeclBuilder, PredicatesOwnedInterface, + }, + snapshots::{IntoPureSnapshot, IntoSnapshot, PredicateKind}, + type_layouts::TypeLayoutsInterface, + }, +}; +use vir_crate::{ + common::{ + expression::{ExpressionIterator, QuantifierHelpers}, + identifier::WithIdentifier, + }, + low::{self as vir_low}, + middle::{self as vir_mid}, +}; + +pub(in super::super::super) struct UniqueRefFinalSnapFunctionBuilder<'l, 'p, 'v, 'tcx> { + inner: FunctionDeclBuilder<'l, 'p, 'v, 'tcx>, + // place: vir_low::VariableDecl, + address: vir_low::VariableDecl, + reference_lifetime: vir_low::VariableDecl, + slice_len: Option, +} + +impl<'l, 'p, 'v, 'tcx> UniqueRefFinalSnapFunctionBuilder<'l, 'p, 'v, 'tcx> { + pub(in super::super::super) fn new( + lowerer: &'l mut Lowerer<'p, 'v, 'tcx>, + ty: &'l vir_mid::Type, + type_decl: &'l vir_mid::TypeDecl, + ) -> SpannedEncodingResult { + let slice_len = if ty.is_slice() { + Some(vir_mid::VariableDecl::new( + "slice_len", + lowerer.size_type_mid()?, + )) + } else { + None + }; + let function_name = "snap_final_unique_ref"; + Ok(Self { + address: vir_low::VariableDecl::new("address", lowerer.address_type()?), + reference_lifetime: vir_low::VariableDecl::new( + "reference_lifetime", + lowerer.lifetime_type()?, + ), + slice_len, + inner: FunctionDeclBuilder::new( + lowerer, + function_name, + ty, + type_decl, + Default::default(), + )?, + }) + } + + pub(in super::super::super) fn get_snapshot_postconditions( + &self, + ) -> SpannedEncodingResult> { + self.inner.get_snapshot_postconditions() + } + + pub(in super::super::super) fn get_snapshot_body( + &self, + ) -> SpannedEncodingResult> { + let body = self.inner.get_snapshot_body()?; + assert_eq!(body.len(), 0); + Ok(body) + } + + pub(in super::super::super) fn build(self) -> SpannedEncodingResult<(String, vir_low::Type)> { + use vir_low::macros::*; + let return_type = self.inner.ty.to_snapshot(self.inner.lowerer)?; + let function_name = format!( + "{}${}", + self.inner.function_name, + self.inner.ty.get_identifier() + ); + let gas = self.inner.lowerer.function_gas_parameter()?; + let parameters = { + let mut parameters = self.inner.parameters.clone(); + parameters.push(gas.clone()); + parameters + }; + let mut arguments_succ_gas: Vec<_> = self + .inner + .parameters + .into_iter() + .map(|parameter| parameter.into()) + .collect(); + let mut arguments_gas = arguments_succ_gas.clone(); + arguments_succ_gas.push( + self.inner + .lowerer + .add_function_gas_level(gas.clone().into())?, + ); + arguments_gas.push(gas.into()); + let call_succ_gas = self.inner.lowerer.create_domain_func_app( + "UniqueRefSnapFunctions", + function_name.clone(), + arguments_succ_gas, + return_type.clone(), + Default::default(), + )?; + let call_gas = vir_low::Expression::domain_function_call( + "UniqueRefSnapFunctions", + function_name.clone(), + arguments_gas, + return_type.clone(), + ); + assert_eq!(self.inner.snapshot_body_posts.len(), 0); + let result: vir_low::Expression = var! { __result: {return_type.clone()} }.into(); + let posts_expression = self + .inner + .snapshot_posts + .into_iter() + .conjoin() + .replace_place(&result, &call_succ_gas); + let axiom_body = vir_low::Expression::forall( + parameters, + vec![vir_low::Trigger::new(vec![call_succ_gas.clone()])], + expr! { + [posts_expression] && ([call_succ_gas] == [call_gas]) + }, + ); + let axiom = vir_low::DomainAxiomDecl { + comment: None, + name: format!("{function_name}$definitional_axiom"), + body: axiom_body, + }; + self.inner + .lowerer + .declare_axiom("UniqueRefSnapFunctions", axiom)?; + Ok((function_name, return_type)) + } + + pub(in super::super::super) fn create_parameters(&mut self) -> SpannedEncodingResult<()> { + self.inner.parameters.push(self.inner.place.clone()); + self.inner.parameters.push(self.address.clone()); + self.inner.parameters.push(self.reference_lifetime.clone()); + self.inner.create_lifetime_parameters()?; + if let Some(slice_len_mid) = &self.slice_len { + let slice_len = slice_len_mid.to_pure_snapshot(self.inner.lowerer)?; + self.inner.parameters.push(slice_len); + } + self.inner.create_const_parameters()?; + Ok(()) + } + + pub(in super::super::super) fn add_snapshot_postcondition( + &mut self, + expression: vir_low::Expression, + ) -> SpannedEncodingResult<()> { + self.inner.add_snapshot_postcondition(expression) + } + + pub(in super::super::super) fn add_structural_invariant( + &mut self, + decl: &vir_mid::type_decl::Struct, + ) -> SpannedEncodingResult<()> { + let predicate_kind = PredicateKind::UniqueRef { + lifetime: self.reference_lifetime.clone().into(), + is_final: true, + }; + let snap_call = self.field_unique_ref_snap()?; + self.inner + .add_structural_invariant(decl, true, predicate_kind, &snap_call) + } + + // FIXME: Code duplication. + fn slice_len(&mut self) -> SpannedEncodingResult> { + self.slice_len + .as_ref() + .map(|slice_len_mid| slice_len_mid.to_pure_snapshot(self.inner.lowerer)) + .transpose() + } + + // FIXME: Code duplication. + fn slice_len_expression(&mut self) -> SpannedEncodingResult> { + Ok(self.slice_len()?.map(|slice_len| slice_len.into())) + } + + pub(in super::super::super) fn create_field_snapshot_equality( + &mut self, + field: &vir_mid::FieldDecl, + ) -> SpannedEncodingResult { + let unique_ref_call = self.field_unique_ref_snap()?; + self.inner + .create_field_snapshot_equality(field, unique_ref_call) + } + + fn field_unique_ref_snap( + &mut self, + ) -> SpannedEncodingResult< + impl Fn( + &mut FunctionDeclBuilder, + &vir_mid::FieldDecl, + vir_low::Expression, + vir_low::Expression, + ) -> SpannedEncodingResult, + > { + let target_slice_len = self.slice_len_expression()?; + let lifetime: vir_low::Expression = self.reference_lifetime.clone().into(); + let lifetime = std::rc::Rc::new(lifetime); + let is_final = true; // FIXME: Unused field. + Ok( + move |builder: &mut FunctionDeclBuilder, + field: &vir_mid::FieldDecl, + field_place, + field_address| { + builder.lowerer.unique_ref_snap( + CallContext::BuiltinMethod, + &field.ty, + &field.ty, + field_place, + field_address, + (*lifetime).clone(), + target_slice_len.clone(), + is_final, + builder.position, + ) + }, + ) + } + + pub(in super::super::super) fn create_discriminant_snapshot_equality( + &mut self, + decl: &vir_mid::type_decl::Enum, + ) -> SpannedEncodingResult { + let call = self.discriminant_unique_ref_snap()?; + self.inner.create_discriminant_snapshot_equality(decl, call) + } + + fn discriminant_unique_ref_snap( + &mut self, + ) -> SpannedEncodingResult< + impl Fn( + &mut FunctionDeclBuilder, + &vir_mid::type_decl::Enum, + vir_low::Expression, + vir_low::Expression, + ) -> SpannedEncodingResult, + > { + let target_slice_len = self.slice_len_expression()?; + let lifetime: vir_low::Expression = self.reference_lifetime.clone().into(); + let lifetime = std::rc::Rc::new(lifetime); + Ok( + move |builder: &mut FunctionDeclBuilder, + decl: &vir_mid::type_decl::Enum, + discriminant_place, + discriminant_address| { + builder.lowerer.unique_ref_snap( + CallContext::BuiltinMethod, + &decl.discriminant_type, + &decl.discriminant_type, + discriminant_place, + discriminant_address, + (*lifetime).clone(), + target_slice_len.clone(), + true, + builder.position, + ) + }, + ) + } + + pub(in super::super::super) fn create_variant_snapshot_equality( + &mut self, + discriminant_value: vir_mid::DiscriminantValue, + variant: &vir_mid::type_decl::Struct, + ) -> SpannedEncodingResult<(vir_low::Expression, vir_low::Expression)> { + let call = self.variant_unique_ref_snap()?; + self.inner + .create_variant_snapshot_equality(discriminant_value, variant, call) + } + + fn variant_unique_ref_snap( + &mut self, + ) -> SpannedEncodingResult< + impl Fn( + &mut FunctionDeclBuilder, + &vir_mid::Type, + vir_low::Expression, + vir_low::Expression, + ) -> SpannedEncodingResult, + > { + let target_slice_len = self.slice_len_expression()?; + let lifetime: vir_low::Expression = self.reference_lifetime.clone().into(); + let lifetime = std::rc::Rc::new(lifetime); + Ok( + move |builder: &mut FunctionDeclBuilder, + variant_type: &vir_mid::Type, + variant_place, + variant_address| { + builder.lowerer.unique_ref_snap( + CallContext::BuiltinMethod, + variant_type, + // Enum variant and enum have the same set of lifetime parameters, + // so we use type_decl here. We cannot use `variant_type` because + // `ty` is normalized. + builder.type_decl, + variant_place, + variant_address, + (*lifetime).clone(), + target_slice_len.clone(), + true, + builder.position, + ) + }, + ) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/unique_ref/function_final_range_decl.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/unique_ref/function_final_range_decl.rs new file mode 100644 index 00000000000..181e5f96747 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/unique_ref/function_final_range_decl.rs @@ -0,0 +1,208 @@ +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::{ + builtin_methods::CallContext, + function_gas::FunctionGasInterface, + lifetimes::LifetimesInterface, + lowerer::{DomainsLowererInterface, Lowerer}, + places::PlacesInterface, + predicates::owned::builders::common::function_decl::FunctionDeclBuilder, + snapshots::{IntoPureSnapshot, IntoSnapshot}, + type_layouts::TypeLayoutsInterface, + }, +}; +use vir_crate::{ + common::{ + expression::{ExpressionIterator, QuantifierHelpers}, + identifier::WithIdentifier, + }, + low::{self as vir_low}, + middle::{self as vir_mid}, +}; + +use super::function_final_use::UniqueRefFinalSnapCallBuilder; + +// FIXME: Code duplication with FracRefRangeSnapFunctionBuilder +pub(in super::super::super) struct UniqueRefFinalRangeSnapFunctionBuilder<'l, 'p, 'v, 'tcx> { + inner: FunctionDeclBuilder<'l, 'p, 'v, 'tcx>, + address: vir_low::VariableDecl, + start_index: vir_low::VariableDecl, + end_index: vir_low::VariableDecl, + reference_lifetime: vir_low::VariableDecl, + slice_len: Option, + posts: Vec, +} + +impl<'l, 'p, 'v, 'tcx> UniqueRefFinalRangeSnapFunctionBuilder<'l, 'p, 'v, 'tcx> { + pub(in super::super::super) fn new( + lowerer: &'l mut Lowerer<'p, 'v, 'tcx>, + ty: &'l vir_mid::Type, + type_decl: &'l vir_mid::TypeDecl, + ) -> SpannedEncodingResult { + let slice_len = if ty.is_slice() { + Some(vir_mid::VariableDecl::new( + "slice_len", + lowerer.size_type_mid()?, + )) + } else { + None + }; + Ok(Self { + address: vir_low::VariableDecl::new("address", ty.to_snapshot(lowerer)?), + start_index: vir_low::VariableDecl::new("start_index", lowerer.size_type()?), + end_index: vir_low::VariableDecl::new("end_index", lowerer.size_type()?), + reference_lifetime: vir_low::VariableDecl::new("lifetime", lowerer.lifetime_type()?), + slice_len, + inner: FunctionDeclBuilder::new( + lowerer, + "snap_unique_ref_final_range_aliased", + ty, + type_decl, + Default::default(), + )?, + posts: Default::default(), + }) + } + + fn gas_amount(&mut self) -> SpannedEncodingResult { + self.inner.lowerer.function_gas_parameter() + } + + pub(in super::super::super) fn build(mut self) -> SpannedEncodingResult<()> { + use vir_low::macros::*; + let return_type = self.inner.range_result_type()?; + let function_name = format!( + "{}${}", + self.inner.function_name, + self.inner.ty.get_identifier() + ); + let gas = self.gas_amount()?; + let parameters = { + let mut parameters = self.inner.parameters.clone(); + parameters.push(gas.clone()); + parameters + }; + let mut arguments_succ_gas: Vec<_> = self + .inner + .parameters + .into_iter() + .map(|parameter| parameter.into()) + .collect(); + let mut arguments_gas = arguments_succ_gas.clone(); + arguments_succ_gas.push( + self.inner + .lowerer + .add_function_gas_level(gas.clone().into())?, + ); + arguments_gas.push(gas.into()); + let call_succ_gas = self.inner.lowerer.create_domain_func_app( + "UniqueRefSnapFunctions", + function_name.clone(), + arguments_succ_gas, + return_type.clone(), + Default::default(), + )?; + let call_gas = vir_low::Expression::domain_function_call( + "UniqueRefSnapFunctions", + function_name.clone(), + arguments_gas, + return_type.clone(), + ); + assert_eq!(self.inner.snapshot_body_posts.len(), 0); + let result: vir_low::Expression = var! { __result: {return_type} }.into(); + let posts_expression = self + .posts + .into_iter() + .conjoin() + .replace_place(&result, &call_succ_gas); + let axiom_body = vir_low::Expression::forall( + parameters, + vec![vir_low::Trigger::new(vec![call_succ_gas.clone()])], + expr! { + [posts_expression] && ([call_succ_gas] == [call_gas]) + }, + ); + let axiom = vir_low::DomainAxiomDecl { + comment: None, + name: format!("{function_name}$definitional_axiom"), + body: axiom_body, + }; + self.inner + .lowerer + .declare_axiom("UniqueRefSnapFunctions", axiom)?; + Ok(()) + } + + // pub(in super::super::super) fn build(mut self) -> SpannedEncodingResult { + // let return_type = self.inner.range_result_type()?; + // let function = vir_low::FunctionDecl { + // name: format!( + // "{}${}", + // self.inner.function_name, + // self.inner.ty.get_identifier() + // ), + // kind: vir_low::FunctionKind::SnapRange, + // parameters: self.inner.parameters, + // body: None, + // pres: self.pres, + // posts: self.posts, + // return_type, + // }; + // Ok(function) + // } + + pub(in super::super::super) fn create_parameters(&mut self) -> SpannedEncodingResult<()> { + self.inner.parameters.push(self.address.clone()); + self.inner.parameters.push(self.start_index.clone()); + self.inner.parameters.push(self.end_index.clone()); + self.inner.parameters.push(self.reference_lifetime.clone()); + self.inner.create_lifetime_parameters()?; + if let Some(slice_len_mid) = &self.slice_len { + let slice_len = slice_len_mid.to_pure_snapshot(self.inner.lowerer)?; + self.inner.parameters.push(slice_len); + } + self.inner.create_const_parameters()?; + Ok(()) + } + + pub(in super::super::super) fn add_postcondition(&mut self) -> SpannedEncodingResult<()> { + let gas = self.gas_amount()?; + self.inner.create_range_postcondition( + &mut self.posts, + &self.address, + &self.start_index, + &self.end_index, + |lowerer, ty, element_address, position| { + let element_place = lowerer.place_option_none_constructor(position)?; + let TODO_target_slice_len = None; + // lowerer.unique_ref_snap( + // CallContext::BuiltinMethod, + // ty, + // ty, + // element_place, + // element_address.clone(), + // self.reference_lifetime.clone().into(), + // TODO_target_slice_len, + // true, + // position, + // ) + let mut builder = UniqueRefFinalSnapCallBuilder::new( + lowerer, + CallContext::BuiltinMethod, + ty, + ty, + element_place, + element_address, + self.reference_lifetime.clone().into(), + TODO_target_slice_len, + position, + )?; + builder.add_lifetime_arguments()?; + builder.add_const_arguments()?; + builder.set_gas_amount(gas.clone().into())?; + builder.build() + }, + )?; + Ok(()) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/unique_ref/function_final_range_use.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/unique_ref/function_final_range_use.rs new file mode 100644 index 00000000000..bcea916a4c9 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/unique_ref/function_final_range_use.rs @@ -0,0 +1,105 @@ +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::{ + builtin_methods::CallContext, function_gas::FunctionGasInterface, lowerer::Lowerer, + predicates::owned::builders::common::function_use::FunctionCallBuilder, + snapshots::IntoSnapshot, + }, +}; +use prusti_common::config; +use vir_crate::{ + common::identifier::WithIdentifier, + low::{self as vir_low}, + middle::{ + self as vir_mid, + operations::{const_generics::WithConstArguments, lifetimes::WithLifetimes}, + }, +}; + +pub(in super::super::super::super::super) struct UniqueRefFinalRangeSnapCallBuilder< + 'l, + 'p, + 'v, + 'tcx, + G, +> where + G: WithLifetimes + WithConstArguments, +{ + inner: FunctionCallBuilder<'l, 'p, 'v, 'tcx, G>, +} + +impl<'l, 'p, 'v, 'tcx, G> UniqueRefFinalRangeSnapCallBuilder<'l, 'p, 'v, 'tcx, G> +where + G: WithLifetimes + WithConstArguments, +{ + pub(in super::super::super::super::super) fn new( + lowerer: &'l mut Lowerer<'p, 'v, 'tcx>, + context: CallContext, + ty: &'l vir_mid::Type, + generics: &'l G, + address: vir_low::Expression, + start_index: vir_low::Expression, + end_index: vir_low::Expression, + reference_lifetime: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult { + let arguments = vec![address, start_index, end_index, reference_lifetime]; + let inner = FunctionCallBuilder::new( + lowerer, + "snap_unique_ref_final_range_aliased", + context, + ty, + generics, + arguments, + position, + )?; + Ok(Self { inner }) + } + + pub(in super::super::super::super::super) fn build( + self, + ) -> SpannedEncodingResult { + let vir_mid::Type::Pointer(pointer_type) = self.inner.ty else { + unreachable!("{} must be a pointer type", self.inner.ty); + }; + let element_type = pointer_type.target_type.to_snapshot(self.inner.lowerer)?; + let return_type = vir_low::Type::seq(element_type); + let gas_amount = self + .inner + .lowerer + .function_gas_constant(config::function_gas_amount())?; + let mut arguments = self.inner.arguments; + arguments.push(gas_amount); + let call = vir_low::Expression::domain_function_call( + "UniqueRefSnapFunctions", + format!( + "{}${}", + self.inner.function_name, + self.inner.ty.get_identifier() + ), + arguments, + return_type, + ); + Ok(call.set_default_position(self.inner.position)) + } + + // pub(in super::super::super::super::super) fn add_custom_argument( + // &mut self, + // argument: vir_low::Expression, + // ) -> SpannedEncodingResult<()> { + // self.inner.arguments.push(argument); + // Ok(()) + // } + + pub(in super::super::super::super::super) fn add_lifetime_arguments( + &mut self, + ) -> SpannedEncodingResult<()> { + self.inner.add_lifetime_arguments() + } + + pub(in super::super::super::super::super) fn add_const_arguments( + &mut self, + ) -> SpannedEncodingResult<()> { + self.inner.add_const_arguments() + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/unique_ref/function_final_use.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/unique_ref/function_final_use.rs new file mode 100644 index 00000000000..3b23e5ac0fd --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/unique_ref/function_final_use.rs @@ -0,0 +1,85 @@ +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::{ + builtin_methods::CallContext, function_gas::FunctionGasInterface, lowerer::Lowerer, + predicates::owned::builders::common::function_use::FunctionCallBuilder, + snapshots::IntoSnapshot, + }, +}; +use prusti_common::config; +use vir_crate::{ + common::identifier::WithIdentifier, + low::{self as vir_low}, + middle::{ + self as vir_mid, + operations::{const_generics::WithConstArguments, lifetimes::WithLifetimes}, + }, +}; + +pub(in super::super::super) struct UniqueRefFinalSnapCallBuilder<'l, 'p, 'v, 'tcx, G> +where + G: WithLifetimes + WithConstArguments, +{ + inner: FunctionCallBuilder<'l, 'p, 'v, 'tcx, G>, + gas_amount: vir_low::Expression, +} + +impl<'l, 'p, 'v, 'tcx, G> UniqueRefFinalSnapCallBuilder<'l, 'p, 'v, 'tcx, G> +where + G: WithLifetimes + WithConstArguments, +{ + pub(in super::super::super) fn new( + lowerer: &'l mut Lowerer<'p, 'v, 'tcx>, + context: CallContext, + ty: &'l vir_mid::Type, + generics: &'l G, + place: vir_low::Expression, + address: vir_low::Expression, + reference_lifetime: vir_low::Expression, + target_slice_len: Option, + position: vir_low::Position, + ) -> SpannedEncodingResult { + let mut arguments = vec![place, address, reference_lifetime]; + if let Some(len) = target_slice_len { + arguments.push(len); + } + let name = "snap_final_unique_ref"; + let gas_amount = lowerer.function_gas_constant(config::function_gas_amount())?; + let inner = + FunctionCallBuilder::new(lowerer, name, context, ty, generics, arguments, position)?; + Ok(Self { inner, gas_amount }) + } + + pub(in super::super::super) fn build(self) -> SpannedEncodingResult { + let return_type = self.inner.ty.to_snapshot(self.inner.lowerer)?; + let mut arguments = self.inner.arguments; + arguments.push(self.gas_amount); + let call = vir_low::Expression::domain_function_call( + "UniqueRefSnapFunctions", + format!( + "{}${}", + self.inner.function_name, + self.inner.ty.get_identifier() + ), + arguments, + return_type, + ); + Ok(call.set_default_position(self.inner.position)) + } + + pub(in super::super::super) fn add_lifetime_arguments(&mut self) -> SpannedEncodingResult<()> { + self.inner.add_lifetime_arguments() + } + + pub(in super::super::super) fn add_const_arguments(&mut self) -> SpannedEncodingResult<()> { + self.inner.add_const_arguments() + } + + pub(in super::super::super) fn set_gas_amount( + &mut self, + new_gas_amount: vir_low::Expression, + ) -> SpannedEncodingResult<()> { + self.gas_amount = new_gas_amount; + Ok(()) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/unique_ref/mod.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/unique_ref/mod.rs index ef427252419..e9530889037 100644 --- a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/unique_ref/mod.rs +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/unique_ref/mod.rs @@ -1,2 +1,11 @@ +pub(super) mod function_current_decl; +pub(super) mod function_final_decl; +pub(super) mod function_current_use; +pub(super) mod function_final_use; +pub(super) mod function_current_range_decl; +pub(super) mod function_final_range_decl; +pub(super) mod function_current_range_use; +pub(super) mod function_final_range_use; pub(super) mod predicate_decl; pub(super) mod predicate_use; +pub(super) mod predicate_range_use; diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/unique_ref/predicate_decl.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/unique_ref/predicate_decl.rs index 280520840fa..b20f8aaaf0b 100644 --- a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/unique_ref/predicate_decl.rs +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/unique_ref/predicate_decl.rs @@ -1,4 +1,3 @@ -use super::predicate_use::UniqueRefUseBuilder; use crate::encoder::{ errors::SpannedEncodingResult, middle::core_proof::{ @@ -7,15 +6,20 @@ use crate::encoder::{ lifetimes::LifetimesInterface, lowerer::Lowerer, places::PlacesInterface, - predicates::owned::builders::{ - common::predicate_decl::PredicateDeclBuilder, PredicateDeclBuilderMethods, + predicates::{ + owned::builders::{ + common::predicate_decl::{ContainingPredicateKind, PredicateDeclBuilder}, + PredicateDeclBuilderMethods, + }, + PredicatesOwnedInterface, }, snapshots::{ - IntoPureSnapshot, IntoSnapshot, SnapshotValidityInterface, SnapshotValuesInterface, + IntoPureSnapshot, PredicateKind, SnapshotValidityInterface, SnapshotValuesInterface, }, type_layouts::TypeLayoutsInterface, }, }; + use vir_crate::{ common::expression::{GuardedExpressionIterator, QuantifierHelpers}, low::{self as vir_low}, @@ -24,10 +28,8 @@ use vir_crate::{ pub(in super::super::super) struct UniqueRefBuilder<'l, 'p, 'v, 'tcx> { inner: PredicateDeclBuilder<'l, 'p, 'v, 'tcx>, - place: vir_low::VariableDecl, - root_address: vir_low::VariableDecl, - current_snapshot: vir_low::VariableDecl, - final_snapshot: vir_low::VariableDecl, + // current_snapshot: vir_low::VariableDecl, + // final_snapshot: vir_low::VariableDecl, reference_lifetime: vir_low::VariableDecl, slice_len: Option, } @@ -55,13 +57,11 @@ impl<'l, 'p, 'v, 'tcx> UniqueRefBuilder<'l, 'p, 'v, 'tcx> { None }; Ok(Self { - place: vir_low::VariableDecl::new("place", lowerer.place_type()?), - root_address: vir_low::VariableDecl::new("root_address", lowerer.address_type()?), - current_snapshot: vir_low::VariableDecl::new( - "current_snapshot", - ty.to_snapshot(lowerer)?, - ), - final_snapshot: vir_low::VariableDecl::new("final_snapshot", ty.to_snapshot(lowerer)?), + // current_snapshot: vir_low::VariableDecl::new( + // "current_snapshot", + // ty.to_snapshot(lowerer)?, + // ), + // final_snapshot: vir_low::VariableDecl::new("final_snapshot", ty.to_snapshot(lowerer)?), reference_lifetime: vir_low::VariableDecl::new( "reference_lifetime", lowerer.lifetime_type()?, @@ -69,7 +69,7 @@ impl<'l, 'p, 'v, 'tcx> UniqueRefBuilder<'l, 'p, 'v, 'tcx> { slice_len, inner: PredicateDeclBuilder::new( lowerer, - "UniqueRef2", + "UniqueRef", ty, type_decl, Default::default(), @@ -82,11 +82,13 @@ impl<'l, 'p, 'v, 'tcx> UniqueRefBuilder<'l, 'p, 'v, 'tcx> { } pub(in super::super::super) fn create_parameters(&mut self) -> SpannedEncodingResult<()> { - self.inner.parameters.push(self.place.clone()); - self.inner.parameters.push(self.root_address.clone()); - self.inner.parameters.push(self.current_snapshot.clone()); - self.inner.parameters.push(self.final_snapshot.clone()); + self.inner.parameters.push(self.inner.place.clone()); + self.inner.parameters.push(self.inner.address.clone()); self.inner.parameters.push(self.reference_lifetime.clone()); + // if config::use_snapshot_parameters_in_predicates() { + // self.inner.parameters.push(self.current_snapshot.clone()); + // self.inner.parameters.push(self.final_snapshot.clone()); + // } self.inner.create_lifetime_parameters()?; if let Some(slice_len_mid) = &self.slice_len { let slice_len = slice_len_mid.to_pure_snapshot(self.inner.lowerer)?; @@ -96,9 +98,9 @@ impl<'l, 'p, 'v, 'tcx> UniqueRefBuilder<'l, 'p, 'v, 'tcx> { Ok(()) } - pub(in super::super::super) fn add_validity(&mut self) -> SpannedEncodingResult<()> { - self.inner.add_validity(&self.current_snapshot) - } + // pub(in super::super::super) fn add_validity(&mut self) -> SpannedEncodingResult<()> { + // self.inner.add_validity(&self.current_snapshot) + // } pub(in super::super::super) fn add_field_predicate( &mut self, @@ -107,35 +109,38 @@ impl<'l, 'p, 'v, 'tcx> UniqueRefBuilder<'l, 'p, 'v, 'tcx> { let field_place = self.inner.lowerer.encode_field_place( self.inner.ty, field, - self.place.clone().into(), + self.inner.place.clone().into(), self.inner.position, )?; - let current_field_snapshot = self.inner.lowerer.obtain_struct_field_snapshot( + let field_address = self.inner.lowerer.encode_field_address( self.inner.ty, field, - self.current_snapshot.clone().into(), + self.inner.address.clone().into(), self.inner.position, )?; - let final_field_snapshot = self.inner.lowerer.obtain_struct_field_snapshot( - self.inner.ty, - field, - self.final_snapshot.clone().into(), - self.inner.position, - )?; - let mut builder = UniqueRefUseBuilder::new( - self.inner.lowerer, + // let current_field_snapshot = self.inner.lowerer.obtain_struct_field_snapshot( + // self.inner.ty, + // field, + // self.current_snapshot.clone().into(), + // self.inner.position, + // )?; + // let final_field_snapshot = self.inner.lowerer.obtain_struct_field_snapshot( + // self.inner.ty, + // field, + // self.final_snapshot.clone().into(), + // self.inner.position, + // )?; + let expression = self.inner.lowerer.unique_ref( CallContext::BuiltinMethod, &field.ty, &field.ty, field_place, - self.root_address.clone().into(), - current_field_snapshot, - final_field_snapshot, + field_address, self.reference_lifetime.clone().into(), + None, // FIXME: This should be a proper value + None, + self.inner.position, )?; - builder.add_lifetime_arguments()?; - builder.add_const_arguments()?; - let expression = builder.build(); self.inner.add_conjunct(expression) } @@ -147,54 +152,86 @@ impl<'l, 'p, 'v, 'tcx> UniqueRefBuilder<'l, 'p, 'v, 'tcx> { let discriminant_place = self.inner.lowerer.encode_field_place( self.inner.ty, &discriminant_field, - self.place.clone().into(), + self.inner.place.clone().into(), self.inner.position, )?; - let current_discriminant_call = self.inner.lowerer.obtain_enum_discriminant( - self.current_snapshot.clone().into(), + let discriminant_address = self.inner.lowerer.encode_field_address( self.inner.ty, + &discriminant_field, + self.inner.address.clone().into(), self.inner.position, )?; - let current_discriminant_snapshot = self.inner.lowerer.construct_constant_snapshot( - &decl.discriminant_type, - current_discriminant_call, - self.inner.position, - )?; - let final_discriminant_call = self.inner.lowerer.obtain_enum_discriminant( - self.final_snapshot.clone().into(), - self.inner.ty, - self.inner.position, - )?; - let final_discriminant_snapshot = self.inner.lowerer.construct_constant_snapshot( - &decl.discriminant_type, - final_discriminant_call, - self.inner.position, - )?; - let builder = UniqueRefUseBuilder::new( - self.inner.lowerer, + // let current_discriminant_call = self.inner.lowerer.obtain_enum_discriminant( + // self.current_snapshot.clone().into(), + // self.inner.ty, + // self.inner.position, + // )?; + // let current_discriminant_snapshot = self.inner.lowerer.construct_constant_snapshot( + // &decl.discriminant_type, + // current_discriminant_call, + // self.inner.position, + // )?; + // let final_discriminant_call = self.inner.lowerer.obtain_enum_discriminant( + // self.final_snapshot.clone().into(), + // self.inner.ty, + // self.inner.position, + // )?; + // let final_discriminant_snapshot = self.inner.lowerer.construct_constant_snapshot( + // &decl.discriminant_type, + // final_discriminant_call, + // self.inner.position, + // )?; + // let builder = UniqueRefUseBuilder::new( + // self.inner.lowerer, + // CallContext::BuiltinMethod, + // &decl.discriminant_type, + // &decl.discriminant_type, + // discriminant_place, + // self.inner.address.clone().into(), + // current_discriminant_snapshot, + // final_discriminant_snapshot, + // self.reference_lifetime.clone().into(), + // )?; + // let expression = builder.build(); + let expression = self.inner.lowerer.unique_ref( CallContext::BuiltinMethod, &decl.discriminant_type, &decl.discriminant_type, discriminant_place, - self.root_address.clone().into(), - current_discriminant_snapshot, - final_discriminant_snapshot, + discriminant_address, self.reference_lifetime.clone().into(), + None, // FIXME: This should be a proper value + None, + self.inner.position, )?; - let expression = builder.build(); self.inner.add_conjunct(expression) } + pub(in super::super::super) fn add_unique_ref_pointer_predicate( + &mut self, + lifetime: &vir_mid::ty::LifetimeConst, + ) -> SpannedEncodingResult { + let place = self.inner.place.clone(); + let address = self.inner.address.clone(); + self.inner.add_unique_ref_pointer_predicate( + lifetime, place, address, + // &self.current_snapshot, + ) + } + pub(in super::super::super) fn add_unique_ref_target_predicate( &mut self, target_type: &vir_mid::Type, lifetime: &vir_mid::ty::LifetimeConst, ) -> SpannedEncodingResult<()> { + let place = self.inner.place.clone(); + let address = self.inner.address.clone(); self.inner.add_unique_ref_target_predicate( target_type, lifetime, - &self.place, - &self.current_snapshot, + place.into(), + address, + ContainingPredicateKind::UniqueRef, ) } @@ -203,11 +240,14 @@ impl<'l, 'p, 'v, 'tcx> UniqueRefBuilder<'l, 'p, 'v, 'tcx> { target_type: &vir_mid::Type, lifetime: &vir_mid::ty::LifetimeConst, ) -> SpannedEncodingResult<()> { + let place = self.inner.place.clone(); + let address = self.inner.address.clone(); self.inner.add_frac_ref_target_predicate( target_type, lifetime, - &self.place, - &self.current_snapshot, + place.into(), + address, + ContainingPredicateKind::UniqueRef, ) } @@ -219,12 +259,13 @@ impl<'l, 'p, 'v, 'tcx> UniqueRefBuilder<'l, 'p, 'v, 'tcx> { pub(in super::super::super) fn add_snapshot_len_equal_to( &mut self, - array_length_mid: &vir_mid::VariableDecl, + _array_length_mid: &vir_mid::VariableDecl, ) -> SpannedEncodingResult<()> { - self.inner - .add_snapshot_len_equal_to(&self.current_snapshot, array_length_mid)?; - self.inner - .add_snapshot_len_equal_to(&self.final_snapshot, array_length_mid)?; + unimplemented!(); + // self.inner + // .add_snapshot_len_equal_to(&self.current_snapshot, array_length_mid)?; + // self.inner + // .add_snapshot_len_equal_to(&self.final_snapshot, array_length_mid)?; Ok(()) } @@ -251,34 +292,52 @@ impl<'l, 'p, 'v, 'tcx> UniqueRefBuilder<'l, 'p, 'v, 'tcx> { let array_length_int = self.inner.array_length_int(array_length_mid)?; let element_place = self.inner.lowerer.encode_index_place( self.inner.ty, - self.place.clone().into(), + self.inner.place.clone().into(), index.clone().into(), self.inner.position, )?; - let current_element_snapshot = self.inner.lowerer.obtain_array_element_snapshot( - self.current_snapshot.clone().into(), - index_int.clone(), - self.inner.position, - )?; - let final_element_snapshot = self.inner.lowerer.obtain_array_element_snapshot( - self.final_snapshot.clone().into(), - index_int.clone(), + let element_address = self.inner.lowerer.encode_index_address( + self.inner.ty, + self.inner.address.clone().into(), + index.clone().into(), self.inner.position, )?; - let mut builder = UniqueRefUseBuilder::new( - self.inner.lowerer, + // let current_element_snapshot = self.inner.lowerer.obtain_array_element_snapshot( + // self.current_snapshot.clone().into(), + // index_int.clone(), + // self.inner.position, + // )?; + // let final_element_snapshot = self.inner.lowerer.obtain_array_element_snapshot( + // self.final_snapshot.clone().into(), + // index_int.clone(), + // self.inner.position, + // )?; + + // let mut builder = UniqueRefUseBuilder::new( + // self.inner.lowerer, + // CallContext::BuiltinMethod, + // element_type, + // element_type, + // element_place, + // self.inner.address.clone().into(), + // current_element_snapshot, + // final_element_snapshot, + // self.reference_lifetime.clone().into(), + // )?; + // builder.add_lifetime_arguments()?; + // builder.add_const_arguments()?; + // let element_predicate_acc = builder.build(); + let element_predicate_acc = self.inner.lowerer.unique_ref( CallContext::BuiltinMethod, element_type, element_type, element_place, - self.root_address.clone().into(), - current_element_snapshot, - final_element_snapshot, + element_address, self.reference_lifetime.clone().into(), + None, // FIXME: This should be a proper value + None, + self.inner.position, )?; - builder.add_lifetime_arguments()?; - builder.add_const_arguments()?; - let element_predicate_acc = builder.build(); let elements = vir_low::Expression::forall( vec![index], vec![vir_low::Trigger::new(vec![element_predicate_acc.clone()])], @@ -292,16 +351,45 @@ impl<'l, 'p, 'v, 'tcx> UniqueRefBuilder<'l, 'p, 'v, 'tcx> { pub(in super::super::super) fn create_variant_predicate( &mut self, + decl: &vir_mid::type_decl::Enum, discriminant_value: vir_mid::DiscriminantValue, variant: &vir_mid::type_decl::Struct, variant_type: &vir_mid::Type, ) -> SpannedEncodingResult<(vir_low::Expression, vir_low::Expression)> { use vir_low::macros::*; - let discriminant_call = self.inner.lowerer.obtain_enum_discriminant( - self.current_snapshot.clone().into(), - self.inner.ty, - self.inner.position, - )?; + let discriminant_call = { + // FIXME: Code duplication with other create_variant_predicate methods. + let discriminant_field = decl.discriminant_field(); + let discriminant_place = self.inner.lowerer.encode_field_place( + self.inner.ty, + &discriminant_field, + self.inner.place.clone().into(), + self.inner.position, + )?; + let discriminant_address = self.inner.lowerer.encode_field_address( + self.inner.ty, + &discriminant_field, + self.inner.address.clone().into(), + self.inner.position, + )?; + let TODO_target_slice_len = None; + let discriminant_snapshot = self.inner.lowerer.unique_ref_snap( + CallContext::BuiltinMethod, + &decl.discriminant_type, + &decl.discriminant_type, + discriminant_place, + discriminant_address, + self.reference_lifetime.clone().into(), + TODO_target_slice_len, + false, + self.inner.position, + )?; + self.inner.lowerer.obtain_constant_value( + &decl.discriminant_type, + discriminant_snapshot, + self.inner.position, + )? + }; let guard = expr! { [ discriminant_call ] == [ discriminant_value.into() ] }; @@ -309,36 +397,82 @@ impl<'l, 'p, 'v, 'tcx> UniqueRefBuilder<'l, 'p, 'v, 'tcx> { let variant_place = self.inner.lowerer.encode_enum_variant_place( self.inner.ty, &variant_index, - self.place.clone().into(), - self.inner.position, - )?; - let current_variant_snapshot = self.inner.lowerer.obtain_enum_variant_snapshot( - self.inner.ty, - &variant_index, - self.current_snapshot.clone().into(), + self.inner.place.clone().into(), self.inner.position, )?; - let final_variant_snapshot = self.inner.lowerer.obtain_enum_variant_snapshot( + let variant_address = self.inner.lowerer.encode_enum_variant_address( self.inner.ty, &variant_index, - self.final_snapshot.clone().into(), + self.inner.address.clone().into(), self.inner.position, )?; - let mut builder = UniqueRefUseBuilder::new( - self.inner.lowerer, + let TODO_target_slice_len = None; + let predicate = self.inner.lowerer.unique_ref( CallContext::BuiltinMethod, variant_type, variant_type, variant_place, - self.root_address.clone().into(), - current_variant_snapshot, - final_variant_snapshot, + variant_address, self.reference_lifetime.clone().into(), + TODO_target_slice_len, + None, + self.inner.position, )?; - builder.add_lifetime_arguments()?; - builder.add_const_arguments()?; - let predicate = builder.build(); Ok((guard, predicate)) + // use vir_low::macros::*; + // let discriminant_call = self.inner.lowerer.obtain_enum_discriminant( + // self.current_snapshot.clone().into(), + // self.inner.ty, + // self.inner.position, + // )?; + // let guard = expr! { + // [ discriminant_call ] == [ discriminant_value.into() ] + // }; + // let variant_index = variant.name.clone().into(); + // let variant_place = self.inner.lowerer.encode_enum_variant_place( + // self.inner.ty, + // &variant_index, + // self.inner.place.clone().into(), + // self.inner.position, + // )?; + // let current_variant_snapshot = self.inner.lowerer.obtain_enum_variant_snapshot( + // self.inner.ty, + // &variant_index, + // self.current_snapshot.clone().into(), + // self.inner.position, + // )?; + // let final_variant_snapshot = self.inner.lowerer.obtain_enum_variant_snapshot( + // self.inner.ty, + // &variant_index, + // self.final_snapshot.clone().into(), + // self.inner.position, + // )?; + // // let mut builder = UniqueRefUseBuilder::new( + // // self.inner.lowerer, + // // CallContext::BuiltinMethod, + // // variant_type, + // // variant_type, + // // variant_place, + // // self.inner.address.clone().into(), + // // current_variant_snapshot, + // // final_variant_snapshot, + // // self.reference_lifetime.clone().into(), + // // )?; + // // builder.add_lifetime_arguments()?; + // // builder.add_const_arguments()?; + // // let predicate = builder.build(); + // let predicate = self.inner.lowerer.unique_ref( + // CallContext::BuiltinMethod, + // variant_type, + // variant_type, + // variant_place, + // self.inner.address.clone().into(), + // current_variant_snapshot, + // final_variant_snapshot, + // self.reference_lifetime.clone().into(), + // None, // FIXME: This should be a proper value + // )?; + // Ok((guard, predicate)) } pub(in super::super::super) fn add_variant_predicates( @@ -348,4 +482,41 @@ impl<'l, 'p, 'v, 'tcx> UniqueRefBuilder<'l, 'p, 'v, 'tcx> { self.inner .add_conjunct(variant_predicates.into_iter().create_match()) } + + pub(in super::super::super) fn add_structural_invariant( + &mut self, + decl: &vir_mid::type_decl::Struct, + ) -> SpannedEncodingResult> { + self.inner.add_structural_invariant( + decl, + PredicateKind::UniqueRef { + lifetime: self.reference_lifetime.clone().into(), + is_final: false, + }, + ) + } + + // /// FIXME: Code duplication. + // pub(in super::super::super) fn add_structural_invariant( + // &mut self, + // decl: &vir_mid::type_decl::Struct, + // ) -> SpannedEncodingResult> { + // if let Some(invariant) = &decl.structural_invariant { + // let mut encoder = SelfFramingAssertionToSnapshot::for_predicate_body( + // self.inner.place.clone(), + // self.inner.address.clone(), + // PredicateKind::UniqueRef { + // lifetime: self.reference_lifetime.clone().into(), + // }, + // ); + // for assertion in invariant { + // let low_assertion = + // encoder.expression_to_snapshot(self.inner.lowerer, assertion, true)?; + // self.inner.add_conjunct(low_assertion)?; + // } + // Ok(encoder.into_created_predicate_types()) + // } else { + // Ok(Vec::new()) + // } + // } } diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/unique_ref/predicate_range_use.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/unique_ref/predicate_range_use.rs new file mode 100644 index 00000000000..9e457c1945b --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/unique_ref/predicate_range_use.rs @@ -0,0 +1,152 @@ +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::{ + addresses::AddressesInterface, builtin_methods::CallContext, lowerer::Lowerer, + places::PlacesInterface, pointers::PointersInterface, predicates::PredicatesOwnedInterface, + snapshots::SnapshotValuesInterface, type_layouts::TypeLayoutsInterface, + }, +}; + +use vir_crate::{ + common::expression::QuantifierHelpers, + low::{self as vir_low}, + middle::{ + self as vir_mid, + operations::{const_generics::WithConstArguments, lifetimes::WithLifetimes}, + }, +}; + +// FIXME: Identical code with `FracRefRangeUseBuilder`. +pub(in super::super::super::super::super) struct UniqueRefRangeUseBuilder<'l, 'p, 'v, 'tcx, G> +where + G: WithLifetimes + WithConstArguments, +{ + lowerer: &'l mut Lowerer<'p, 'v, 'tcx>, + context: CallContext, + ty: &'l vir_mid::Type, + generics: &'l G, + address: vir_low::Expression, + start_index: vir_low::Expression, + end_index: vir_low::Expression, + lifetime: vir_low::Expression, + permission_amount: Option, + position: vir_low::Position, +} + +impl<'l, 'p, 'v, 'tcx, G> UniqueRefRangeUseBuilder<'l, 'p, 'v, 'tcx, G> +where + G: WithLifetimes + WithConstArguments, +{ + pub(in super::super::super::super::super) fn new( + lowerer: &'l mut Lowerer<'p, 'v, 'tcx>, + context: CallContext, + ty: &'l vir_mid::Type, + generics: &'l G, + address: vir_low::Expression, + start_index: vir_low::Expression, + end_index: vir_low::Expression, + lifetime: vir_low::Expression, + permission_amount: Option, + position: vir_low::Position, + ) -> SpannedEncodingResult { + Ok(Self { + lowerer, + context, + ty, + generics, + address, + start_index, + end_index, + lifetime, + permission_amount, + position, + }) + } + + pub(in super::super::super::super::super) fn build( + self, + ) -> SpannedEncodingResult { + use vir_low::macros::*; + let size_type = self.lowerer.size_type_mid()?; + // var_decls! { + // index: Int + // } + let vir_mid::Type::Pointer(ty) = self.ty else { + unreachable!() + }; + let initial_address = self + .lowerer + .pointer_address(self.ty, self.address, self.position)?; + // let vir_mid::Type::Pointer(pointer_type) = self.ty else { + // unreachable!() + // }; + let size = self + .lowerer + .encode_type_size_expression2(&ty.target_type, &*ty.target_type)?; + // let element_address = self.lowerer.address_offset( + // size, + // initial_address, + // index.clone().into(), + // self.position, + // )?; + let element_place = self.lowerer.place_option_none_constructor(self.position)?; + // let TODO_target_slice_len = None; + // let predicate = self.lowerer.unique_ref( + // self.context, + // &ty.target_type, + // self.generics, + // element_place, + // element_address.clone(), + // self.lifetime, + // TODO_target_slice_len, + // self.permission_amount, + // self.position, + // )?; + let start_index = + self.lowerer + .obtain_constant_value(&size_type, self.start_index, self.position)?; + let end_index = + self.lowerer + .obtain_constant_value(&size_type, self.end_index, self.position)?; + // let body = expr!( + // (([start_index] <= index) && (index < [end_index])) ==> [predicate] + // ); + // let expression = vir_low::Expression::forall( + // vec![index], + // vec![vir_low::Trigger::new(vec![element_address])], + // body, + // ); + // Ok(expression) + + var_decls! { + element_address: Address + } + let TODO_target_slice_len = None; + let predicate = self.lowerer.unique_ref( + self.context, + &ty.target_type, + self.generics, + element_place, + element_address.clone().into(), + self.lifetime, + TODO_target_slice_len, + self.permission_amount, + self.position, + )?; + let guard = self.lowerer.address_range_contains( + initial_address, + start_index, + end_index, + size, + element_address.clone().into(), + self.position, + )?; + let body = expr!([guard] ==> [predicate.clone()]); + let expression = vir_low::Expression::forall( + vec![element_address], + vec![vir_low::Trigger::new(vec![predicate])], + body, + ); + Ok(expression) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/unique_ref/predicate_use.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/unique_ref/predicate_use.rs index 799f81227d1..07be1142361 100644 --- a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/unique_ref/predicate_use.rs +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/unique_ref/predicate_use.rs @@ -3,9 +3,9 @@ use crate::encoder::{ middle::core_proof::{ builtin_methods::CallContext, lowerer::Lowerer, predicates::owned::builders::common::predicate_use::PredicateUseBuilder, - snapshots::SnapshotValuesInterface, type_layouts::TypeLayoutsInterface, }, }; + use vir_crate::{ low::{self as vir_low}, middle::{ @@ -19,7 +19,7 @@ where G: WithLifetimes + WithConstArguments, { inner: PredicateUseBuilder<'l, 'p, 'v, 'tcx, G>, - current_snapshot: vir_low::Expression, + target_slice_len: Option, } impl<'l, 'p, 'v, 'tcx, G> UniqueRefUseBuilder<'l, 'p, 'v, 'tcx, G> @@ -33,34 +33,34 @@ where ty: &'l vir_mid::Type, generics: &'l G, place: vir_low::Expression, - root_address: vir_low::Expression, - current_snapshot: vir_low::Expression, - final_snapshot: vir_low::Expression, + address: vir_low::Expression, lifetime: vir_low::Expression, + target_slice_len: Option, + position: vir_low::Position, ) -> SpannedEncodingResult { + let mut arguments = vec![place, address, lifetime]; + if let Some(len) = target_slice_len.clone() { + arguments.push(len); + } let inner = PredicateUseBuilder::new( lowerer, - "UniqueRef2", + "UniqueRef", context, ty, generics, - vec![ - place, - root_address, - current_snapshot.clone(), - final_snapshot, - lifetime, - ], - Default::default(), + arguments, + position, )?; Ok(Self { inner, - current_snapshot, + target_slice_len, }) } - pub(in super::super::super::super::super) fn build(self) -> vir_low::Expression { - self.inner.build() + pub(in super::super::super::super::super) fn build( + self, + ) -> SpannedEncodingResult { + Ok(self.inner.build()) } pub(in super::super::super::super::super) fn add_lifetime_arguments( @@ -73,18 +73,27 @@ where &mut self, ) -> SpannedEncodingResult<()> { if self.inner.ty.is_slice() { - let snapshot_length = self - .inner - .lowerer - .obtain_array_len_snapshot(self.current_snapshot.clone(), self.inner.position)?; - let size_type = self.inner.lowerer.size_type_mid()?; - let argument = self.inner.lowerer.construct_constant_snapshot( - &size_type, - snapshot_length, - self.inner.position, - )?; - self.inner.arguments.push(argument); + // FIXME + eprintln!("FIXME!!!"); + // let snapshot_length = self + // .inner + // .lowerer + // .obtain_array_len_snapshot(self.current_snapshot.clone(), self.inner.position)?; + // let size_type = self.inner.lowerer.size_type_mid()?; + // let argument = self.inner.lowerer.construct_constant_snapshot( + // &size_type, + // snapshot_length, + // self.inner.position, + // )?; + // self.inner.arguments.push(argument); } self.inner.add_const_arguments() } + + pub(in super::super::super::super::super) fn set_maybe_permission_amount( + &mut self, + permission_amount: Option, + ) -> SpannedEncodingResult<()> { + self.inner.set_maybe_permission_amount(permission_amount) + } } diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/encoder/function.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/encoder/function.rs new file mode 100644 index 00000000000..3bed7b2e727 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/encoder/function.rs @@ -0,0 +1,323 @@ +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::{ + lowerer::{FunctionsLowererInterface, Lowerer}, + predicates::{ + owned::builders::{ + FracRefSnapFunctionBuilder, OwnedNonAliasedSnapFunctionBuilder, + UniqueRefCurrentSnapFunctionBuilder, UniqueRefFinalSnapFunctionBuilder, + }, + OwnedPredicateInfo, SnapshotFunctionInfo, + }, + }, +}; + +use vir_crate::{ + common::expression::{ExpressionIterator, GuardedExpressionIterator}, + middle::{self as vir_mid}, +}; + +impl<'p, 'v: 'p, 'tcx: 'v> Lowerer<'p, 'v, 'tcx> { + pub(super) fn encode_owned_predicate_snapshot( + &mut self, + normalized_type: &vir_mid::Type, + type_decl: &vir_mid::TypeDecl, + ) -> SpannedEncodingResult { + super::guard!(assert self, encoded_owned_predicate_snapshot_functions, normalized_type); + + let mut builder = + OwnedNonAliasedSnapFunctionBuilder::new(self, normalized_type, type_decl)?; + builder.create_parameters()?; + builder.add_owned_precondition()?; + builder.add_validity_postcondition()?; + match type_decl { + vir_mid::TypeDecl::Bool + | vir_mid::TypeDecl::Int(_) + | vir_mid::TypeDecl::Float(_) + | vir_mid::TypeDecl::Pointer(_) + | vir_mid::TypeDecl::Sequence(_) + | vir_mid::TypeDecl::Map(_) => { + builder.add_bytes_snapshot_equality()?; + } + vir_mid::TypeDecl::Trusted(_) | vir_mid::TypeDecl::TypeVar(_) => {} + vir_mid::TypeDecl::Struct(decl) => { + let mut equalities = Vec::new(); + for field in &decl.fields { + equalities.push(builder.create_field_snapshot_equality(field)?); + } + builder.add_snapshot_body_postcondition(equalities.into_iter().conjoin())?; + builder.add_structural_invariant(decl)?; + } + vir_mid::TypeDecl::Enum(decl) => { + let mut equalities = Vec::new(); + if decl.safety.is_enum() { + let discriminant_equality = + builder.create_discriminant_snapshot_equality(decl)?; + builder.add_snapshot_body_postcondition(discriminant_equality)?; + } + for (discriminant, variant) in decl.iter_discriminant_variants() { + equalities + .push(builder.create_variant_snapshot_equality(discriminant, variant)?); + } + builder.add_snapshot_body_postcondition(equalities.into_iter().create_match())?; + } + vir_mid::TypeDecl::Reference(decl) => { + builder.add_bytes_address_snapshot_equality()?; + // FIXME: Have a getter for the first lifetime. + let lifetime = &decl.lifetimes[0]; + builder.add_reference_snapshot_equalities(decl, lifetime)?; + } + vir_mid::TypeDecl::Array(decl) => { + let length = if normalized_type.is_slice() { + builder.get_slice_len()? + } else { + decl.const_parameters[0].clone() + }; + builder.add_snapshot_len_equal_to_postcondition(&length)?; + builder.add_quantifiers(&length, &decl.element_type)?; + } + _ => { + unimplemented!("{}", type_decl); + } + } + let owned_snapshots_to_encode = builder.take_owned_snapshot_functions_to_encode(); + let owned_range_snapshots_to_encode = + builder.take_owned_range_snapshot_functions_to_encode(); + let snapshot_postconditions = builder.get_snapshot_postconditions()?; + let snapshot_body = builder.get_snapshot_body()?; + let function = builder.build()?; + let function_name = function.name.clone(); + let snapshot_type = function.return_type.clone(); + self.declare_function(function)?; + for ty in owned_snapshots_to_encode { + self.encode_owned_predicate(&ty)?; + } + for ty in owned_range_snapshots_to_encode { + self.encode_owned_predicate_range_snapshot(&ty)?; + } + let snapshot_range_function = + self.construct_function_name("snap_owned_range_aliased", normalized_type)?; + Ok(OwnedPredicateInfo { + current_snapshot_function: SnapshotFunctionInfo { + function_name, + postconditions: snapshot_postconditions, + body: snapshot_body, + }, + final_snapshot_function: None, + snapshot_range_function, + snapshot_type, + }) + } + + pub(super) fn encode_unique_ref_predicate_current_snapshot( + &mut self, + normalized_type: &vir_mid::Type, + type_decl: &vir_mid::TypeDecl, + ) -> SpannedEncodingResult { + super::guard!(assert self, encoded_unique_ref_predicate_current_snapshot_functions, normalized_type); + + let mut builder = + UniqueRefCurrentSnapFunctionBuilder::new(self, normalized_type, type_decl)?; + builder.create_parameters()?; + builder.add_unique_ref_precondition()?; + builder.add_validity_postcondition()?; + match &type_decl { + vir_mid::TypeDecl::Bool + | vir_mid::TypeDecl::Int(_) + | vir_mid::TypeDecl::Float(_) + | vir_mid::TypeDecl::Pointer(_) + | vir_mid::TypeDecl::Sequence(_) + | vir_mid::TypeDecl::Map(_) => { + // For these types the unique ref predicate is abstract. + } + vir_mid::TypeDecl::Trusted(_) | vir_mid::TypeDecl::TypeVar(_) => {} + vir_mid::TypeDecl::Struct(decl) => { + let mut equalities = Vec::new(); + for field in &decl.fields { + equalities.push(builder.create_field_snapshot_equality(field)?); + } + builder.add_snapshot_body_postcondition(equalities.into_iter().conjoin())?; + builder.add_structural_invariant(decl)?; + } + vir_mid::TypeDecl::Enum(decl) => { + let mut equalities = Vec::new(); + if decl.safety.is_enum() { + let discriminant_equality = + builder.create_discriminant_snapshot_equality(decl)?; + builder.add_snapshot_body_postcondition(discriminant_equality)?; + } + for (discriminant, variant) in decl.iter_discriminant_variants() { + equalities + .push(builder.create_variant_snapshot_equality(discriminant, variant)?); + } + builder.add_snapshot_body_postcondition(equalities.into_iter().create_match())?; + } + vir_mid::TypeDecl::Reference(_decl) => { + // FIXME: Implement. + } + vir_mid::TypeDecl::Array(_decl) => { + unimplemented!(); + } + _ => { + unimplemented!("{}", type_decl); + } + } + let snapshot_postconditions = builder.get_snapshot_postconditions()?; + let snapshot_body = builder.get_snapshot_body()?; + let function = builder.build()?; + let function_name = function.name.clone(); + let snapshot_type = function.return_type.clone(); + self.declare_function(function)?; + let snapshot_range_function = + self.construct_function_name("snap_unique_ref_current_range_aliased", normalized_type)?; + Ok(OwnedPredicateInfo { + current_snapshot_function: SnapshotFunctionInfo { + function_name, + postconditions: snapshot_postconditions, + body: snapshot_body, + }, + final_snapshot_function: None, + snapshot_range_function: (snapshot_range_function), + snapshot_type, + }) + } + + pub(super) fn encode_unique_ref_predicate_final_snapshot( + &mut self, + normalized_type: &vir_mid::Type, + type_decl: &vir_mid::TypeDecl, + ) -> SpannedEncodingResult { + super::guard!(assert self, encoded_unique_ref_predicate_final_snapshot_functions, normalized_type); + + let mut builder = UniqueRefFinalSnapFunctionBuilder::new(self, normalized_type, type_decl)?; + builder.create_parameters()?; + match &type_decl { + vir_mid::TypeDecl::Bool + | vir_mid::TypeDecl::Int(_) + | vir_mid::TypeDecl::Float(_) + | vir_mid::TypeDecl::Pointer(_) + | vir_mid::TypeDecl::Sequence(_) + | vir_mid::TypeDecl::Map(_) => { + // For these types the unique ref predicate is abstract. + } + vir_mid::TypeDecl::Trusted(_) | vir_mid::TypeDecl::TypeVar(_) => {} + vir_mid::TypeDecl::Struct(decl) => { + let mut equalities = Vec::new(); + for field in &decl.fields { + equalities.push(builder.create_field_snapshot_equality(field)?); + } + builder.add_snapshot_postcondition(equalities.into_iter().conjoin())?; + builder.add_structural_invariant(decl)?; + } + vir_mid::TypeDecl::Enum(decl) => { + let mut equalities = Vec::new(); + if decl.safety.is_enum() { + let discriminant_equality = + builder.create_discriminant_snapshot_equality(decl)?; + builder.add_snapshot_postcondition(discriminant_equality)?; + } + for (discriminant, variant) in decl.iter_discriminant_variants() { + equalities + .push(builder.create_variant_snapshot_equality(discriminant, variant)?); + } + builder.add_snapshot_postcondition(equalities.into_iter().create_match())?; + } + vir_mid::TypeDecl::Reference(_decl) => { + // For references, the final snapshot is abstract. + } + vir_mid::TypeDecl::Array(_decl) => { + unimplemented!(); + } + _ => { + unimplemented!("{}", type_decl); + } + } + let snapshot_postconditions = builder.get_snapshot_postconditions()?; + let snapshot_body = builder.get_snapshot_body()?; + let (function_name, snapshot_type) = builder.build()?; + let snapshot_range_function = + self.construct_function_name("snap_unique_ref_final_range_aliased", normalized_type)?; + Ok(OwnedPredicateInfo { + current_snapshot_function: SnapshotFunctionInfo { + function_name, + postconditions: snapshot_postconditions, + body: snapshot_body, + }, + final_snapshot_function: None, + snapshot_range_function: (snapshot_range_function), + snapshot_type, + }) + } + + pub(super) fn encode_frac_ref_predicate_snapshot( + &mut self, + normalized_type: &vir_mid::Type, + type_decl: &vir_mid::TypeDecl, + ) -> SpannedEncodingResult { + super::guard!(assert self, encoded_frac_ref_predicate_snapshot_functions, normalized_type); + + let mut builder = FracRefSnapFunctionBuilder::new(self, normalized_type, type_decl)?; + builder.create_parameters()?; + builder.add_frac_ref_precondition()?; + builder.add_validity_postcondition()?; + match &type_decl { + vir_mid::TypeDecl::Bool + | vir_mid::TypeDecl::Int(_) + | vir_mid::TypeDecl::Float(_) + | vir_mid::TypeDecl::Pointer(_) + | vir_mid::TypeDecl::Sequence(_) + | vir_mid::TypeDecl::Map(_) => { + // For these types the unique ref predicate is abstract. + } + vir_mid::TypeDecl::Trusted(_) | vir_mid::TypeDecl::TypeVar(_) => {} + vir_mid::TypeDecl::Struct(decl) => { + let mut equalities = Vec::new(); + for field in &decl.fields { + equalities.push(builder.create_field_snapshot_equality(field)?); + } + builder.add_snapshot_body_postcondition(equalities.into_iter().conjoin())?; + builder.add_structural_invariant(decl)?; + } + vir_mid::TypeDecl::Enum(decl) => { + let mut equalities = Vec::new(); + if decl.safety.is_enum() { + let discriminant_equality = + builder.create_discriminant_snapshot_equality(decl)?; + builder.add_snapshot_body_postcondition(discriminant_equality)?; + } + for (discriminant, variant) in decl.iter_discriminant_variants() { + equalities + .push(builder.create_variant_snapshot_equality(discriminant, variant)?); + } + builder.add_snapshot_body_postcondition(equalities.into_iter().create_match())?; + } + vir_mid::TypeDecl::Reference(_decl) => { + // FIXME: Implement. + } + vir_mid::TypeDecl::Array(_decl) => { + unimplemented!(); + } + _ => { + unimplemented!("{}", type_decl); + } + } + let snapshot_postconditions = builder.get_snapshot_postconditions()?; + let snapshot_body = builder.get_snapshot_body()?; + let function = builder.build()?; + let function_name = function.name.clone(); + let snapshot_type = function.return_type.clone(); + self.declare_function(function)?; + let snapshot_range_function = + self.construct_function_name("snap_frac_ref_range_aliased", normalized_type)?; + Ok(OwnedPredicateInfo { + current_snapshot_function: SnapshotFunctionInfo { + function_name, + postconditions: snapshot_postconditions, + body: snapshot_body, + }, + final_snapshot_function: None, + snapshot_range_function: (snapshot_range_function), + snapshot_type, + }) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/encoder/function_range.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/encoder/function_range.rs new file mode 100644 index 00000000000..53453ecd9cd --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/encoder/function_range.rs @@ -0,0 +1,108 @@ +use crate::encoder::{ + errors::SpannedEncodingResult, + high::types::HighTypeEncoderInterface, + middle::core_proof::{ + lowerer::{FunctionsLowererInterface, Lowerer}, + predicates::owned::builders::{ + FracRefRangeSnapFunctionBuilder, OwnedAliasedRangeSnapFunctionBuilder, + UniqueRefCurrentRangeSnapFunctionBuilder, UniqueRefFinalRangeSnapFunctionBuilder, + }, + }, +}; + +use vir_crate::middle::{self as vir_mid}; + +impl<'p, 'v: 'p, 'tcx: 'v> Lowerer<'p, 'v, 'tcx> { + pub(in super::super) fn encode_owned_predicate_range_snapshot( + &mut self, + ty: &vir_mid::Type, + ) -> SpannedEncodingResult<()> { + super::guard!(self, encoded_owned_predicate_range_snapshot_functions, ty); + // let ty_identifier = ty.get_identifier(); + // if self + // .state() + // .encoded_owned_predicate_range_snapshot_functions + // .contains(&ty_identifier) + // { + // return Ok(()); + // } + // self + // .state() + // .encoded_owned_predicate_range_snapshot_functions + // .insert(ty_identifier); + + let type_decl = self.encoder.get_type_decl_mid(ty)?; + let normalized_type = ty.normalize_type(); + let mut builder = + OwnedAliasedRangeSnapFunctionBuilder::new(self, &normalized_type, &type_decl)?; + builder.create_parameters()?; + builder.add_owned_precondition()?; + builder.add_postcondition()?; + let function = builder.build()?; + self.declare_function(function)?; + Ok(()) + } + + pub(in super::super) fn encode_unique_ref_predicate_current_range_snapshot( + &mut self, + ty: &vir_mid::Type, + ) -> SpannedEncodingResult<()> { + super::guard!( + self, + encoded_unique_ref_predicate_current_range_snapshot_functions, + ty + ); + let type_decl = self.encoder.get_type_decl_mid(ty)?; + let normalized_type = ty.normalize_type(); + let mut builder = + UniqueRefCurrentRangeSnapFunctionBuilder::new(self, &normalized_type, &type_decl)?; + builder.create_parameters()?; + builder.add_owned_precondition()?; + builder.add_postcondition()?; + let function = builder.build()?; + self.declare_function(function)?; + Ok(()) + } + + pub(in super::super) fn encode_unique_ref_predicate_final_range_snapshot( + &mut self, + ty: &vir_mid::Type, + ) -> SpannedEncodingResult<()> { + super::guard!( + self, + encoded_unique_ref_predicate_final_range_snapshot_functions, + ty + ); + let type_decl = self.encoder.get_type_decl_mid(ty)?; + let normalized_type = ty.normalize_type(); + let mut builder = + UniqueRefFinalRangeSnapFunctionBuilder::new(self, &normalized_type, &type_decl)?; + builder.create_parameters()?; + // builder.add_owned_precondition()?; + builder.add_postcondition()?; + // let function = builder.build()?; + // self.declare_function(function)?; + builder.build()?; + Ok(()) + } + + pub(in super::super) fn encode_frac_ref_predicate_range_snapshot( + &mut self, + ty: &vir_mid::Type, + ) -> SpannedEncodingResult<()> { + super::guard!( + self, + encoded_frac_ref_predicate_range_snapshot_functions, + ty + ); + let type_decl = self.encoder.get_type_decl_mid(ty)?; + let normalized_type = ty.normalize_type(); + let mut builder = FracRefRangeSnapFunctionBuilder::new(self, &normalized_type, &type_decl)?; + builder.create_parameters()?; + builder.add_owned_precondition()?; + builder.add_postcondition()?; + let function = builder.build()?; + self.declare_function(function)?; + Ok(()) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/encoder/mod.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/encoder/mod.rs new file mode 100644 index 00000000000..14d8967f83a --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/encoder/mod.rs @@ -0,0 +1,27 @@ +use super::PredicatesOwnedState; +use crate::encoder::middle::core_proof::lowerer::Lowerer; + +mod predicate; +mod function; +mod function_range; + +macro guard { + ($self:ident, $set:ident, $ty:ident) => { + let ty_identifier = vir_crate::common::identifier::WithIdentifier::get_identifier($ty); + if $self.state().$set.contains(&ty_identifier) { + return Ok(()); + } + $self.state().$set.insert(ty_identifier); + }, + (assert $self:ident, $set:ident, $ty:ident) => { + let ty_identifier = vir_crate::common::identifier::WithIdentifier::get_identifier($ty); + assert!(!$self.state().$set.contains(&ty_identifier)); + $self.state().$set.insert(ty_identifier); + }, +} + +impl<'p, 'v: 'p, 'tcx: 'v> Lowerer<'p, 'v, 'tcx> { + fn state(&mut self) -> &mut PredicatesOwnedState { + &mut self.predicates_encoding_state.owned + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/encoder.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/encoder/predicate.rs similarity index 69% rename from prusti-viper/src/encoder/middle/core_proof/predicates/owned/encoder.rs rename to prusti-viper/src/encoder/middle/core_proof/predicates/owned/encoder/predicate.rs index 2d9e138997f..c5fd41a9771 100644 --- a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/encoder.rs +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/encoder/predicate.rs @@ -16,66 +16,41 @@ use crate::encoder::{ types::TypesInterface, }, }; -use rustc_hash::FxHashSet; -use vir_crate::{ - common::identifier::WithIdentifier, - low::{self as vir_low}, - middle as vir_mid, -}; - -pub(super) struct PredicateEncoder<'l, 'p, 'v, 'tcx> { - lowerer: &'l mut Lowerer<'p, 'v, 'tcx>, - unfolded_owned_non_aliased_predicates: &'l FxHashSet, - encoded_owned_predicates: FxHashSet, - encoded_mut_borrow_predicates: FxHashSet, - encoded_frac_borrow_predicates: FxHashSet, - predicates: Vec, -} - -impl<'l, 'p, 'v, 'tcx> PredicateEncoder<'l, 'p, 'v, 'tcx> { - pub(super) fn new( - lowerer: &'l mut Lowerer<'p, 'v, 'tcx>, - unfolded_owned_non_aliased_predicates: &'l FxHashSet, - ) -> Self { - Self { - lowerer, - unfolded_owned_non_aliased_predicates, - encoded_owned_predicates: Default::default(), - encoded_mut_borrow_predicates: Default::default(), - encoded_frac_borrow_predicates: Default::default(), - predicates: Default::default(), - } - } - pub(super) fn into_predicates(self) -> Vec { - self.predicates - } +use vir_crate::middle::{self as vir_mid}; - pub(super) fn encode_owned_non_aliased( +impl<'p, 'v: 'p, 'tcx: 'v> Lowerer<'p, 'v, 'tcx> { + pub(in super::super) fn encode_owned_predicate( &mut self, ty: &vir_mid::Type, ) -> SpannedEncodingResult<()> { - let ty_identifier = ty.get_identifier(); - if self.encoded_owned_predicates.contains(&ty_identifier) { - return Ok(()); - } + super::guard!(self, encoded_owned_predicates, ty); + // let ty_identifier = ty.get_identifier(); + // if self + // .state() + // .encoded_owned_non_aliased_predicates + // .contains(&ty_identifier) + // { + // return Ok(()); + // } - self.encoded_owned_predicates.insert(ty_identifier); - self.lowerer.encode_compute_address(ty)?; - let type_decl = self.lowerer.encoder.get_type_decl_mid(ty)?; + // self.state() + // .encoded_owned_non_aliased_predicates + // .insert(ty_identifier); + self.encode_compute_address(ty)?; + let type_decl = self.encoder.get_type_decl_mid(ty)?; let normalized_type = ty.normalize_type(); - self.lowerer - .encode_snapshot_to_bytes_function(&normalized_type)?; + self.encode_snapshot_to_bytes_function(&normalized_type)?; + let predicate_info = self.encode_owned_predicate_snapshot(&normalized_type, &type_decl)?; let mut owned_predicates_to_encode = Vec::new(); let mut unique_ref_predicates_to_encode = Vec::new(); let mut frac_ref_predicates_to_encode = Vec::new(); - self.lowerer.encode_memory_block_predicate()?; - let mut builder = OwnedNonAliasedBuilder::new(self.lowerer, &normalized_type, &type_decl)?; + self.encode_memory_block_predicate()?; + let mut builder = OwnedNonAliasedBuilder::new(self, &normalized_type, &type_decl)?; builder.create_parameters()?; if !(type_decl.is_type_var() || type_decl.is_trusted()) { builder.create_body(); - builder.add_validity()?; } // Build the body. match &type_decl { @@ -86,20 +61,18 @@ impl<'l, 'p, 'v, 'tcx> PredicateEncoder<'l, 'p, 'v, 'tcx> { | vir_mid::TypeDecl::Sequence(_) | vir_mid::TypeDecl::Map(_) => { builder.add_base_memory_block()?; - builder.add_bytes_snapshot_equality()?; + if let vir_mid::TypeDecl::Pointer(decl) = &type_decl { + owned_predicates_to_encode.push(decl.target_type.clone()); + } } vir_mid::TypeDecl::Trusted(_) | vir_mid::TypeDecl::TypeVar(_) => {} vir_mid::TypeDecl::Struct(decl) => { builder.add_padding_memory_block()?; for field in &decl.fields { builder.add_field_predicate(field)?; - if !self - .unfolded_owned_non_aliased_predicates - .contains(&field.ty) - { - owned_predicates_to_encode.push(field.ty.clone()); - } + owned_predicates_to_encode.push(field.ty.clone()); } + owned_predicates_to_encode.extend(builder.add_structural_invariant(decl)?); } vir_mid::TypeDecl::Enum(decl) => { builder.add_padding_memory_block()?; @@ -114,24 +87,15 @@ impl<'l, 'p, 'v, 'tcx> PredicateEncoder<'l, 'p, 'v, 'tcx> { decl.lifetimes.clone(), ); variant_predicates.push(builder.create_variant_predicate( + decl, discriminant, variant, &variant_type, )?); let variant_type = ty.clone().variant(variant_index); - if !self - .unfolded_owned_non_aliased_predicates - .contains(&variant_type) - { - owned_predicates_to_encode.push(variant_type); - } - } - if !self - .unfolded_owned_non_aliased_predicates - .contains(&decl.discriminant_type) - { - owned_predicates_to_encode.push(decl.discriminant_type.clone()); + owned_predicates_to_encode.push(variant_type); } + owned_predicates_to_encode.push(decl.discriminant_type.clone()); if decl.safety.is_enum() { builder.add_discriminant_predicate(decl)?; } @@ -139,7 +103,6 @@ impl<'l, 'p, 'v, 'tcx> PredicateEncoder<'l, 'p, 'v, 'tcx> { } vir_mid::TypeDecl::Reference(decl) => { builder.add_base_memory_block()?; - builder.add_bytes_address_snapshot_equality()?; // FIXME: Have a getter for the first lifetime. let lifetime = &decl.lifetimes[0]; if decl.uniqueness.is_unique() { @@ -163,7 +126,6 @@ impl<'l, 'p, 'v, 'tcx> PredicateEncoder<'l, 'p, 'v, 'tcx> { } else { decl.const_parameters[0].clone() }; - builder.add_snapshot_len_equal_to(&length)?; builder.add_quantified_permission(&length, &decl.element_type)?; } _ => { @@ -172,38 +134,60 @@ impl<'l, 'p, 'v, 'tcx> PredicateEncoder<'l, 'p, 'v, 'tcx> { unimplemented!("{}", type_decl); } } - self.predicates.push(builder.build()); + let predicate = builder.build(); + self.state() + .predicate_info + .insert(predicate.name.clone(), predicate_info); + self.state().predicates.push(predicate); for ty in owned_predicates_to_encode { // TODO: Optimization: This variant is never unfolded, // encode it as abstract predicate. - self.encode_owned_non_aliased(&ty)?; + self.encode_owned_predicate(&ty)?; } for ty in unique_ref_predicates_to_encode { // TODO: Optimization: This variant is never unfolded, // encode it as abstract predicate. - self.encode_unique_ref(&ty)?; + self.encode_unique_ref_predicate(&ty)?; } for ty in frac_ref_predicates_to_encode { // TODO: Optimization: This variant is never unfolded, // encode it as abstract predicate. - self.encode_frac_ref(&ty)?; + self.encode_frac_ref_predicate(&ty)?; } Ok(()) } - fn encode_frac_ref(&mut self, ty: &vir_mid::Type) -> SpannedEncodingResult<()> { - let ty_identifier = ty.get_identifier(); - if self.encoded_frac_borrow_predicates.contains(&ty_identifier) { - return Ok(()); - } - self.encoded_frac_borrow_predicates.insert(ty_identifier); - self.lowerer.encode_compute_address(ty)?; - - let type_decl = self.lowerer.encoder.get_type_decl_mid(ty)?; + pub(in super::super) fn encode_unique_ref_predicate( + &mut self, + ty: &vir_mid::Type, + ) -> SpannedEncodingResult<()> { + super::guard!(self, encoded_unique_ref_predicates, ty); + // let ty_identifier = ty.get_identifier(); + // if self + // .state() + // .encoded_mut_borrow_predicates + // .contains(&ty_identifier) + // { + // return Ok(()); + // } + // self.state() + // .encoded_mut_borrow_predicates + // .insert(ty_identifier); + self.encode_compute_address(ty)?; + let type_decl = self.encoder.get_type_decl_mid(ty)?; + // FIXME: Make get_type_decl_mid to return the erased ty for which it + // returned type_decl. let normalized_type = ty.normalize_type(); - let mut predicates_to_encode = Vec::new(); - let mut builder = FracRefBuilder::new(self.lowerer, &normalized_type, &type_decl)?; + // if !config::use_snapshot_parameters_in_predicates() { + let current_predicate_info = + self.encode_unique_ref_predicate_current_snapshot(&normalized_type, &type_decl)?; + let final_predicate_info = + self.encode_unique_ref_predicate_final_snapshot(&normalized_type, &type_decl)?; + // } + let mut unique_ref_predicates_to_encode = Vec::new(); + let mut frac_ref_predicates_to_encode = Vec::new(); + let mut builder = UniqueRefBuilder::new(self, &normalized_type, &type_decl)?; builder.create_parameters()?; if !matches!( type_decl, @@ -217,7 +201,9 @@ impl<'l, 'p, 'v, 'tcx> PredicateEncoder<'l, 'p, 'v, 'tcx> { | vir_mid::TypeDecl::TypeVar(_) ) { builder.create_body(); - builder.add_validity()?; + // if config::use_snapshot_parameters_in_predicates() { + // builder.add_validity()?; + // } } // Build the body. match &type_decl { @@ -232,8 +218,9 @@ impl<'l, 'p, 'v, 'tcx> PredicateEncoder<'l, 'p, 'v, 'tcx> { vir_mid::TypeDecl::Struct(decl) => { for field in &decl.fields { builder.add_field_predicate(field)?; - predicates_to_encode.push(field.ty.clone()); + unique_ref_predicates_to_encode.push(field.ty.clone()); } + unique_ref_predicates_to_encode.extend(builder.add_structural_invariant(decl)?); } vir_mid::TypeDecl::Enum(decl) => { let mut variant_predicates = Vec::new(); @@ -241,13 +228,14 @@ impl<'l, 'p, 'v, 'tcx> PredicateEncoder<'l, 'p, 'v, 'tcx> { let variant_index = variant.name.clone().into(); let variant_type = ty.clone().variant(variant_index); variant_predicates.push(builder.create_variant_predicate( + decl, discriminant, variant, &variant_type, )?); - predicates_to_encode.push(variant_type); + unique_ref_predicates_to_encode.push(variant_type); } - predicates_to_encode.push(decl.discriminant_type.clone()); + unique_ref_predicates_to_encode.push(decl.discriminant_type.clone()); if decl.safety.is_enum() { builder.add_discriminant_predicate(decl)?; } @@ -256,15 +244,23 @@ impl<'l, 'p, 'v, 'tcx> PredicateEncoder<'l, 'p, 'v, 'tcx> { vir_mid::TypeDecl::Reference(decl) => { // FIXME: Have a getter for the first lifetime. let lifetime = &decl.lifetimes[0]; - builder.add_frac_ref_target_predicate(&decl.target_type, lifetime)?; - predicates_to_encode.push(decl.target_type.clone()); + let pointer_type = builder.add_unique_ref_pointer_predicate(lifetime)?; + if decl.uniqueness.is_unique() { + builder.add_unique_ref_target_predicate(&decl.target_type, lifetime)?; + unique_ref_predicates_to_encode.push(decl.target_type.clone()); + unique_ref_predicates_to_encode.push(pointer_type); + } else { + builder.add_frac_ref_target_predicate(&decl.target_type, lifetime)?; + frac_ref_predicates_to_encode.push(decl.target_type.clone()); + frac_ref_predicates_to_encode.push(pointer_type); + } } vir_mid::TypeDecl::Array(decl) => { builder.lowerer().encode_place_array_index_axioms(ty)?; builder .lowerer() .ensure_type_definition(&decl.element_type)?; - predicates_to_encode.push(decl.element_type.clone()); + unique_ref_predicates_to_encode.push(decl.element_type.clone()); builder.add_const_parameters_validity()?; // FIXME: Have a getter for the first const parameter. let length = if normalized_type.is_slice() { @@ -272,37 +268,59 @@ impl<'l, 'p, 'v, 'tcx> PredicateEncoder<'l, 'p, 'v, 'tcx> { } else { decl.const_parameters[0].clone() }; - builder.add_snapshot_len_equal_to(&length)?; builder.add_quantified_permission(&length, &decl.element_type)?; } _ => { unimplemented!("{:?}", type_decl); } } - self.predicates.push(builder.build()); - for ty in predicates_to_encode { + let predicate = builder.build(); + assert_eq!( + current_predicate_info.snapshot_type, + final_predicate_info.snapshot_type + ); + let mut predicate_info = current_predicate_info; + predicate_info.final_snapshot_function = + Some(final_predicate_info.current_snapshot_function); + self.state() + .predicate_info + .insert(predicate.name.clone(), predicate_info); + self.state().predicates.push(predicate); + for ty in unique_ref_predicates_to_encode { // TODO: Optimization: This variant is never unfolded, // encode it as abstract predicate. - self.encode_frac_ref(&ty)?; + self.encode_unique_ref_predicate(&ty)?; + } + for ty in frac_ref_predicates_to_encode { + self.encode_frac_ref_predicate(&ty)?; } Ok(()) } - pub(super) fn encode_unique_ref(&mut self, ty: &vir_mid::Type) -> SpannedEncodingResult<()> { - let ty_identifier = ty.get_identifier(); - if self.encoded_mut_borrow_predicates.contains(&ty_identifier) { - return Ok(()); - } - self.encoded_mut_borrow_predicates.insert(ty_identifier); - self.lowerer.encode_compute_address(ty)?; - let type_decl = self.lowerer.encoder.get_type_decl_mid(ty)?; + pub(in super::super) fn encode_frac_ref_predicate( + &mut self, + ty: &vir_mid::Type, + ) -> SpannedEncodingResult<()> { + super::guard!(self, encoded_frac_ref_predicates, ty); + // let ty_identifier = ty.get_identifier(); + // if self + // .state() + // .encoded_frac_borrow_predicates + // .contains(&ty_identifier) + // { + // return Ok(()); + // } + // self.state() + // .encoded_frac_borrow_predicates + // .insert(ty_identifier); + self.encode_compute_address(ty)?; + let type_decl = self.encoder.get_type_decl_mid(ty)?; - // FIXME: Make get_type_decl_mid to return the erased ty for which it - // returned type_decl. let normalized_type = ty.normalize_type(); - let mut unique_ref_predicates_to_encode = Vec::new(); - let mut frac_ref_predicates_to_encode = Vec::new(); - let mut builder = UniqueRefBuilder::new(self.lowerer, &normalized_type, &type_decl)?; + let predicate_info = + self.encode_frac_ref_predicate_snapshot(&normalized_type, &type_decl)?; + let mut predicates_to_encode = Vec::new(); + let mut builder = FracRefBuilder::new(self, &normalized_type, &type_decl)?; builder.create_parameters()?; if !matches!( type_decl, @@ -316,7 +334,7 @@ impl<'l, 'p, 'v, 'tcx> PredicateEncoder<'l, 'p, 'v, 'tcx> { | vir_mid::TypeDecl::TypeVar(_) ) { builder.create_body(); - builder.add_validity()?; + // builder.add_validity()?; } // Build the body. match &type_decl { @@ -331,8 +349,9 @@ impl<'l, 'p, 'v, 'tcx> PredicateEncoder<'l, 'p, 'v, 'tcx> { vir_mid::TypeDecl::Struct(decl) => { for field in &decl.fields { builder.add_field_predicate(field)?; - unique_ref_predicates_to_encode.push(field.ty.clone()); + predicates_to_encode.push(field.ty.clone()); } + predicates_to_encode.extend(builder.add_structural_invariant(decl)?); } vir_mid::TypeDecl::Enum(decl) => { let mut variant_predicates = Vec::new(); @@ -340,13 +359,14 @@ impl<'l, 'p, 'v, 'tcx> PredicateEncoder<'l, 'p, 'v, 'tcx> { let variant_index = variant.name.clone().into(); let variant_type = ty.clone().variant(variant_index); variant_predicates.push(builder.create_variant_predicate( + decl, discriminant, variant, &variant_type, )?); - unique_ref_predicates_to_encode.push(variant_type); + predicates_to_encode.push(variant_type); } - unique_ref_predicates_to_encode.push(decl.discriminant_type.clone()); + predicates_to_encode.push(decl.discriminant_type.clone()); if decl.safety.is_enum() { builder.add_discriminant_predicate(decl)?; } @@ -355,42 +375,41 @@ impl<'l, 'p, 'v, 'tcx> PredicateEncoder<'l, 'p, 'v, 'tcx> { vir_mid::TypeDecl::Reference(decl) => { // FIXME: Have a getter for the first lifetime. let lifetime = &decl.lifetimes[0]; - if decl.uniqueness.is_unique() { - builder.add_unique_ref_target_predicate(&decl.target_type, lifetime)?; - unique_ref_predicates_to_encode.push(decl.target_type.clone()); - } else { - builder.add_frac_ref_target_predicate(&decl.target_type, lifetime)?; - frac_ref_predicates_to_encode.push(decl.target_type.clone()); - } + let pointer_type = builder.add_frac_ref_pointer_predicate(lifetime)?; + builder.add_frac_ref_target_predicate(&decl.target_type, lifetime)?; + predicates_to_encode.push(decl.target_type.clone()); + predicates_to_encode.push(pointer_type); } vir_mid::TypeDecl::Array(decl) => { builder.lowerer().encode_place_array_index_axioms(ty)?; builder .lowerer() .ensure_type_definition(&decl.element_type)?; - unique_ref_predicates_to_encode.push(decl.element_type.clone()); + predicates_to_encode.push(decl.element_type.clone()); builder.add_const_parameters_validity()?; // FIXME: Have a getter for the first const parameter. - let length = if normalized_type.is_slice() { + let _length = if normalized_type.is_slice() { builder.get_slice_len()? } else { decl.const_parameters[0].clone() }; - builder.add_snapshot_len_equal_to(&length)?; - builder.add_quantified_permission(&length, &decl.element_type)?; + unimplemented!(); + // builder.add_snapshot_len_equal_to(&length)?; + // builder.add_quantified_permission(&length, &decl.element_type)?; } _ => { unimplemented!("{:?}", type_decl); } } - self.predicates.push(builder.build()); - for ty in unique_ref_predicates_to_encode { + let predicate = builder.build(); + self.state() + .predicate_info + .insert(predicate.name.clone(), predicate_info); + self.state().predicates.push(predicate); + for ty in predicates_to_encode { // TODO: Optimization: This variant is never unfolded, // encode it as abstract predicate. - self.encode_unique_ref(&ty)?; - } - for ty in frac_ref_predicates_to_encode { - self.encode_frac_ref(&ty)?; + self.encode_frac_ref_predicate(&ty)?; } Ok(()) } diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/interface.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/interface.rs index 16dc6b0981b..420f286550f 100644 --- a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/interface.rs +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/interface.rs @@ -1,46 +1,119 @@ use super::{ - encoder::PredicateEncoder, FracRefUseBuilder, OwnedNonAliasedUseBuilder, UniqueRefUseBuilder, + builders::{ + FracRefRangeSnapCallBuilder, FracRefRangeUseBuilder, FracRefSnapCallBuilder, + OwnedAliasedRangeSnapCallBuilder, OwnedAliasedRangeUseBuilder, + OwnedNonAliasedSnapCallBuilder, UniqueRefCurrentRangeSnapCallBuilder, + UniqueRefCurrentSnapCallBuilder, UniqueRefFinalRangeSnapCallBuilder, + UniqueRefFinalSnapCallBuilder, UniqueRefRangeUseBuilder, + }, + FracRefUseBuilder, OwnedNonAliasedUseBuilder, UniqueRefUseBuilder, }; use crate::encoder::{ errors::SpannedEncodingResult, - middle::core_proof::{builtin_methods::CallContext, lowerer::Lowerer, types::TypesInterface}, + middle::core_proof::{ + builtin_methods::CallContext, lowerer::Lowerer, places::PlacesInterface, + types::TypesInterface, + }, }; -use rustc_hash::FxHashSet; +use std::collections::BTreeMap; use vir_crate::{ - low::{self as vir_low}, + common::expression::BinaryOperationHelpers, + low::{self as vir_low, operations::ty::Typed}, middle::{ self as vir_mid, operations::{const_generics::WithConstArguments, lifetimes::WithLifetimes}, }, }; -#[derive(Default)] -pub(in super::super) struct PredicatesOwnedState { - unfolded_owned_non_aliased_predicates: FxHashSet, - used_unique_ref_predicates: FxHashSet, +// #[derive(Default)] +// pub(in super::super) struct PredicatesOwnedState { +// unfolded_owned_predicates: FxHashSet, +// used_unique_ref_predicates: FxHashSet, +// used_frac_ref_predicates: FxHashSet, +// used_owned_range_snapshot_functions: FxHashSet, +// used_unique_ref_range_snapshot_functions: FxHashSet, +// used_frac_ref_range_snapshot_functions: FxHashSet, +// } + +/// Addidional information about the predicate used by purification +/// optimizations. +#[derive(Clone, Debug)] +pub(in super::super::super) struct OwnedPredicateInfo { + /// Snapshot function of the current state. + pub(in super::super::super) current_snapshot_function: SnapshotFunctionInfo, + /// Snapshot function of the final state. + pub(in super::super::super) final_snapshot_function: Option, + /// The snapshot type. + pub(in super::super::super) snapshot_type: vir_low::Type, + /// The snapshot range function. + pub(in super::super::super) snapshot_range_function: String, +} + +/// Addidional information about the snapshot function used by purification +/// optimizations. +#[derive(Clone, Debug)] +pub(in super::super::super) struct SnapshotFunctionInfo { + /// The name of the snapshot function. + pub(in super::super::super) function_name: String, + /// The properties that we know to hold when we have a predicate instance. + pub(in super::super::super) postconditions: Vec, + /// The assertions that link the snapshot of the predicate with the + /// snapshots of inner predicates. + pub(in super::super::super) body: Vec, } pub(in super::super::super) trait PredicatesOwnedInterface { - /// Marks that `OwnedNonAliased` was unfolded in the program and we need - /// to provide its body. - fn mark_owned_non_aliased_as_unfolded( - &mut self, - ty: &vir_mid::Type, - ) -> SpannedEncodingResult<()>; + /// Marks that `Owned` was unfolded in the program and we need to + /// provide its body. + fn mark_owned_predicate_as_unfolded(&mut self, ty: &vir_mid::Type) + -> SpannedEncodingResult<()>; /// Marks that `UniqueRef` was used in the program. fn mark_unique_ref_as_used(&mut self, ty: &vir_mid::Type) -> SpannedEncodingResult<()>; + fn mark_frac_ref_as_used(&mut self, ty: &vir_mid::Type) -> SpannedEncodingResult<()>; + // FIXME: Make this method to be defined on the state and take `self`. fn collect_owned_predicate_decls( &mut self, - ) -> SpannedEncodingResult>; + ) -> SpannedEncodingResult<( + Vec, + BTreeMap, + )>; + /// Owned predicate that can be either aliased or non-aliased depending on + /// the value of `place`. + fn owned_predicate( + &mut self, + context: CallContext, + ty: &vir_mid::Type, + generics: &G, + place: vir_low::Expression, + address: vir_low::Expression, + permission_amount: Option, + position: vir_low::Position, + ) -> SpannedEncodingResult + where + G: WithLifetimes + WithConstArguments; /// A version of `owned_non_aliased` for the most common case. + #[allow(clippy::too_many_arguments)] fn owned_non_aliased_full_vars( &mut self, context: CallContext, ty: &vir_mid::Type, generics: &G, place: &vir_low::VariableDecl, - root_address: &vir_low::VariableDecl, + address: &vir_low::VariableDecl, + position: vir_low::Position, + ) -> SpannedEncodingResult + where + G: WithLifetimes + WithConstArguments; + #[allow(clippy::too_many_arguments)] + fn owned_non_aliased_full_vars_with_snapshot( + &mut self, + context: CallContext, + ty: &vir_mid::Type, + generics: &G, + place: &vir_low::VariableDecl, + address: &vir_low::VariableDecl, snapshot: &vir_low::VariableDecl, + position: vir_low::Position, ) -> SpannedEncodingResult where G: WithLifetimes + WithConstArguments; @@ -51,9 +124,95 @@ pub(in super::super::super) trait PredicatesOwnedInterface { ty: &vir_mid::Type, generics: &G, place: vir_low::Expression, - root_address: vir_low::Expression, + address: vir_low::Expression, + permission_amount: Option, + position: vir_low::Position, + ) -> SpannedEncodingResult + where + G: WithLifetimes + WithConstArguments; + #[allow(clippy::too_many_arguments)] + fn owned_non_aliased_with_snapshot( + &mut self, + context: CallContext, + ty: &vir_mid::Type, + generics: &G, + place: vir_low::Expression, + address: vir_low::Expression, snapshot: vir_low::Expression, permission_amount: Option, + position: vir_low::Position, + ) -> SpannedEncodingResult + where + G: WithLifetimes + WithConstArguments; + #[allow(clippy::too_many_arguments)] + fn owned_aliased( + &mut self, + context: CallContext, + ty: &vir_mid::Type, + generics: &G, + address: vir_low::Expression, + permission_amount: Option, + position: vir_low::Position, + ) -> SpannedEncodingResult + where + G: WithLifetimes + WithConstArguments; + fn owned_aliased_range( + &mut self, + context: CallContext, + ty: &vir_mid::Type, + generics: &G, + address: vir_low::Expression, + start_index: vir_low::Expression, + end_index: vir_low::Expression, + permission_amount: Option, + position: vir_low::Position, + ) -> SpannedEncodingResult + where + G: WithLifetimes + WithConstArguments; + #[allow(clippy::too_many_arguments)] + fn owned_predicate_snap( + &mut self, + context: CallContext, + ty: &vir_mid::Type, + generics: &G, + place: vir_low::Expression, + address: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult + where + G: WithLifetimes + WithConstArguments; + #[allow(clippy::too_many_arguments)] + fn owned_non_aliased_snap( + &mut self, + context: CallContext, + ty: &vir_mid::Type, + generics: &G, + place: vir_low::Expression, + address: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult + where + G: WithLifetimes + WithConstArguments; + #[allow(clippy::too_many_arguments)] + fn owned_aliased_snap( + &mut self, + context: CallContext, + ty: &vir_mid::Type, + generics: &G, + address: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult + where + G: WithLifetimes + WithConstArguments; + fn owned_aliased_range_snap( + &mut self, + context: CallContext, + ty: &vir_mid::Type, + generics: &G, + address: vir_low::Expression, + start_index: vir_low::Expression, + end_index: vir_low::Expression, + position: vir_low::Position, ) -> SpannedEncodingResult where G: WithLifetimes + WithConstArguments; @@ -64,10 +223,25 @@ pub(in super::super::super) trait PredicatesOwnedInterface { ty: &vir_mid::Type, generics: &G, place: &vir_low::VariableDecl, - root_address: &vir_low::VariableDecl, + address: &vir_low::VariableDecl, + lifetime: &vir_low::VariableDecl, + target_slice_len: Option, + position: vir_low::Position, + ) -> SpannedEncodingResult + where + G: WithLifetimes + WithConstArguments; + #[allow(clippy::too_many_arguments)] + fn unique_ref_full_vars_with_current_snapshot( + &mut self, + context: CallContext, + ty: &vir_mid::Type, + generics: &G, + place: &vir_low::VariableDecl, + address: &vir_low::VariableDecl, current_snapshot: &vir_low::VariableDecl, - final_snapshot: &vir_low::VariableDecl, lifetime: &vir_low::VariableDecl, + target_slice_len: Option, + position: vir_low::Position, ) -> SpannedEncodingResult where G: WithLifetimes + WithConstArguments; @@ -78,10 +252,71 @@ pub(in super::super::super) trait PredicatesOwnedInterface { ty: &vir_mid::Type, generics: &G, place: vir_low::Expression, - root_address: vir_low::Expression, + address: vir_low::Expression, + lifetime: vir_low::Expression, + target_slice_len: Option, + permission_amount: Option, + position: vir_low::Position, + ) -> SpannedEncodingResult + where + G: WithLifetimes + WithConstArguments; + fn unique_ref_range( + &mut self, + context: CallContext, + ty: &vir_mid::Type, + generics: &G, + address: vir_low::Expression, + start_index: vir_low::Expression, + end_index: vir_low::Expression, + lifetime: vir_low::Expression, + permission_amount: Option, + position: vir_low::Position, + ) -> SpannedEncodingResult + where + G: WithLifetimes + WithConstArguments; + #[allow(clippy::too_many_arguments)] + fn unique_ref_with_current_snapshot( + &mut self, + context: CallContext, + ty: &vir_mid::Type, + generics: &G, + place: vir_low::Expression, + address: vir_low::Expression, current_snapshot: vir_low::Expression, - final_snapshot: vir_low::Expression, lifetime: vir_low::Expression, + target_slice_len: Option, + permission_amount: Option, + position: vir_low::Position, + ) -> SpannedEncodingResult + where + G: WithLifetimes + WithConstArguments; + #[allow(clippy::too_many_arguments)] + fn unique_ref_snap( + &mut self, + context: CallContext, + ty: &vir_mid::Type, + generics: &G, + place: vir_low::Expression, + address: vir_low::Expression, + lifetime: vir_low::Expression, + target_slice_len: Option, + is_final: bool, + position: vir_low::Position, + ) -> SpannedEncodingResult + where + G: WithLifetimes + WithConstArguments; + #[allow(clippy::too_many_arguments)] + fn unique_ref_range_snap( + &mut self, + context: CallContext, + ty: &vir_mid::Type, + generics: &G, + address: vir_low::Expression, + start_index: vir_low::Expression, + end_index: vir_low::Expression, + lifetime: vir_low::Expression, + is_final: bool, + position: vir_low::Position, ) -> SpannedEncodingResult where G: WithLifetimes + WithConstArguments; @@ -92,9 +327,25 @@ pub(in super::super::super) trait PredicatesOwnedInterface { ty: &vir_mid::Type, generics: &G, place: &vir_low::VariableDecl, - root_address: &vir_low::VariableDecl, + address: &vir_low::VariableDecl, + lifetime: &vir_low::VariableDecl, + target_slice_len: Option, + position: vir_low::Position, + ) -> SpannedEncodingResult + where + G: WithLifetimes + WithConstArguments; + #[allow(clippy::too_many_arguments)] + fn frac_ref_full_vars_with_current_snapshot( + &mut self, + context: CallContext, + ty: &vir_mid::Type, + generics: &G, + place: &vir_low::VariableDecl, + address: &vir_low::VariableDecl, current_snapshot: &vir_low::VariableDecl, lifetime: &vir_low::VariableDecl, + target_slice_len: Option, + position: vir_low::Position, ) -> SpannedEncodingResult where G: WithLifetimes + WithConstArguments; @@ -105,72 +356,218 @@ pub(in super::super::super) trait PredicatesOwnedInterface { ty: &vir_mid::Type, generics: &G, place: vir_low::Expression, - root_address: vir_low::Expression, + address: vir_low::Expression, + lifetime: vir_low::Expression, + target_slice_len: Option, + permission_amount: Option, + position: vir_low::Position, + ) -> SpannedEncodingResult + where + G: WithLifetimes + WithConstArguments; + fn frac_ref_range( + &mut self, + context: CallContext, + ty: &vir_mid::Type, + generics: &G, + address: vir_low::Expression, + start_index: vir_low::Expression, + end_index: vir_low::Expression, + lifetime: vir_low::Expression, + permission_amount: Option, + position: vir_low::Position, + ) -> SpannedEncodingResult + where + G: WithLifetimes + WithConstArguments; + #[allow(clippy::too_many_arguments)] + fn frac_ref_with_current_snapshot( + &mut self, + context: CallContext, + ty: &vir_mid::Type, + generics: &G, + place: vir_low::Expression, + address: vir_low::Expression, current_snapshot: vir_low::Expression, lifetime: vir_low::Expression, + target_slice_len: Option, + permission_amount: Option, + position: vir_low::Position, + ) -> SpannedEncodingResult + where + G: WithLifetimes + WithConstArguments; + #[allow(clippy::too_many_arguments)] + fn frac_ref_snap( + &mut self, + context: CallContext, + ty: &vir_mid::Type, + generics: &G, + place: vir_low::Expression, + address: vir_low::Expression, + lifetime: vir_low::Expression, + target_slice_len: Option, + position: vir_low::Position, + ) -> SpannedEncodingResult + where + G: WithLifetimes + WithConstArguments; + #[allow(clippy::too_many_arguments)] + fn frac_ref_range_snap( + &mut self, + context: CallContext, + ty: &vir_mid::Type, + generics: &G, + address: vir_low::Expression, + start_index: vir_low::Expression, + end_index: vir_low::Expression, + lifetime: vir_low::Expression, + position: vir_low::Position, ) -> SpannedEncodingResult where G: WithLifetimes + WithConstArguments; } impl<'p, 'v: 'p, 'tcx: 'v> PredicatesOwnedInterface for Lowerer<'p, 'v, 'tcx> { - fn mark_owned_non_aliased_as_unfolded( + fn mark_owned_predicate_as_unfolded( &mut self, ty: &vir_mid::Type, ) -> SpannedEncodingResult<()> { - if !self - .predicates_encoding_state - .owned - .unfolded_owned_non_aliased_predicates - .contains(ty) - { - self.ensure_type_definition(ty)?; - self.predicates_encoding_state - .owned - .unfolded_owned_non_aliased_predicates - .insert(ty.clone()); - } + self.ensure_type_definition(ty)?; + self.encode_owned_predicate(ty)?; + // if !self + // .predicates_encoding_state + // .owned + // .unfolded_owned_predicates + // .contains(ty) + // { + // self.ensure_type_definition(ty)?; + // self.predicates_encoding_state + // .owned + // .unfolded_owned_predicates + // .insert(ty.clone()); + // } Ok(()) } fn mark_unique_ref_as_used(&mut self, ty: &vir_mid::Type) -> SpannedEncodingResult<()> { - if !self - .predicates_encoding_state - .owned - .used_unique_ref_predicates - .contains(ty) - { - self.predicates_encoding_state - .owned - .used_unique_ref_predicates - .insert(ty.clone()); - } + self.encode_unique_ref_predicate(ty)?; + // if !self + // .predicates_encoding_state + // .owned + // .used_unique_ref_predicates + // .contains(ty) + // { + // self.predicates_encoding_state + // .owned + // .used_unique_ref_predicates + // .insert(ty.clone()); + // } + Ok(()) + } + + fn mark_frac_ref_as_used(&mut self, ty: &vir_mid::Type) -> SpannedEncodingResult<()> { + self.encode_frac_ref_predicate(ty)?; + // if !self + // .predicates_encoding_state + // .owned + // .used_frac_ref_predicates + // .contains(ty) + // { + // self.predicates_encoding_state + // .owned + // .used_frac_ref_predicates + // .insert(ty.clone()); + // } Ok(()) } fn collect_owned_predicate_decls( &mut self, - ) -> SpannedEncodingResult> { - let unfolded_predicates = std::mem::take( - &mut self - .predicates_encoding_state - .owned - .unfolded_owned_non_aliased_predicates, - ); - let used_unique_ref_predicates = std::mem::take( - &mut self - .predicates_encoding_state - .owned - .used_unique_ref_predicates, - ); - let mut predicate_encoder = PredicateEncoder::new(self, &unfolded_predicates); - for ty in &unfolded_predicates { - predicate_encoder.encode_owned_non_aliased(ty)?; - } - for ty in &used_unique_ref_predicates { - predicate_encoder.encode_unique_ref(ty)?; - } - Ok(predicate_encoder.into_predicates()) + ) -> SpannedEncodingResult<( + Vec, + BTreeMap, + )> { + // // let unfolded_owned_predicates = std::mem::take( + // // &mut self + // // .predicates_encoding_state + // // .owned + // // .unfolded_owned_predicates, + // // ); + // let unfolded_owned_predicates = std::mem::take( + // &mut self + // .predicates_encoding_state + // .owned + // .unfolded_owned_predicates, + // ); + // let used_unique_ref_predicates = std::mem::take( + // &mut self + // .predicates_encoding_state + // .owned + // .used_unique_ref_predicates, + // ); + // let used_owned_range_snapshot_functions = std::mem::take( + // &mut self + // .predicates_encoding_state + // .owned + // .used_owned_range_snapshot_functions, + // ); + // let used_unique_ref_range_snapshot_functions = std::mem::take( + // &mut self + // .predicates_encoding_state + // .owned + // .used_unique_ref_range_snapshot_functions, + // ); + // let used_frac_ref_range_snapshot_functions = std::mem::take( + // &mut self + // .predicates_encoding_state + // .owned + // .used_frac_ref_range_snapshot_functions, + // ); + // let mut predicate_encoder = PredicateEncoder::new(self); + // for ty in &unfolded_owned_predicates { + // predicate_encoder.encode_owned_non_aliased(ty)?; + // } + // // for ty in &unfolded_owned_predicates { + // // unimplemented!(); + // // // predicate_encoder.encode_owned_aliased(ty)?; + // // } + // for ty in &used_unique_ref_predicates { + // predicate_encoder.encode_unique_ref(ty)?; + // } + // for ty in &used_owned_range_snapshot_functions { + // predicate_encoder.encode_owned_range_snapshot(ty)?; + // } + // for ty in &used_unique_ref_range_snapshot_functions { + // predicate_encoder.encode_unique_ref_range_snapshot(ty)?; + // } + // for ty in &used_frac_ref_range_snapshot_functions { + // predicate_encoder.encode_frac_ref_range_snapshot(ty)?; + // } + // let predicate_info = predicate_encoder.take_predicate_info(); + // Ok((predicate_encoder.into_predicates(), predicate_info)) + let predicates = std::mem::take(&mut self.predicates_encoding_state.owned.predicates); + let predicate_info = + std::mem::take(&mut self.predicates_encoding_state.owned.predicate_info); + Ok((predicates, predicate_info)) + } + + fn owned_predicate( + &mut self, + context: CallContext, + ty: &vir_mid::Type, + generics: &G, + place: vir_low::Expression, + address: vir_low::Expression, + permission_amount: Option, + position: vir_low::Position, + ) -> SpannedEncodingResult + where + G: WithLifetimes + WithConstArguments, + { + self.mark_owned_predicate_as_unfolded(ty)?; + let mut builder = + OwnedNonAliasedUseBuilder::new(self, context, ty, generics, place, address, position)?; + builder.add_lifetime_arguments()?; + builder.add_const_arguments()?; + builder.set_maybe_permission_amount(permission_amount)?; + builder.build() } fn owned_non_aliased_full_vars( @@ -179,8 +576,8 @@ impl<'p, 'v: 'p, 'tcx: 'v> PredicatesOwnedInterface for Lowerer<'p, 'v, 'tcx> { ty: &vir_mid::Type, generics: &G, place: &vir_low::VariableDecl, - root_address: &vir_low::VariableDecl, - snapshot: &vir_low::VariableDecl, + address: &vir_low::VariableDecl, + position: vir_low::Position, ) -> SpannedEncodingResult where G: WithLifetimes + WithConstArguments, @@ -190,129 +587,578 @@ impl<'p, 'v: 'p, 'tcx: 'v> PredicatesOwnedInterface for Lowerer<'p, 'v, 'tcx> { ty, generics, place.clone().into(), - root_address.clone().into(), - snapshot.clone().into(), + address.clone().into(), None, + position, ) } - fn owned_non_aliased( + fn owned_non_aliased_full_vars_with_snapshot( &mut self, context: CallContext, ty: &vir_mid::Type, generics: &G, - place: vir_low::Expression, - root_address: vir_low::Expression, - snapshot: vir_low::Expression, - permission_amount: Option, + place: &vir_low::VariableDecl, + address: &vir_low::VariableDecl, + snapshot: &vir_low::VariableDecl, + position: vir_low::Position, ) -> SpannedEncodingResult where G: WithLifetimes + WithConstArguments, { - let mut builder = OwnedNonAliasedUseBuilder::new( - self, + self.owned_non_aliased_with_snapshot( context, ty, generics, - place, - root_address, - snapshot, - )?; - builder.add_lifetime_arguments()?; - builder.add_const_arguments()?; - builder.set_maybe_permission_amount(permission_amount)?; - Ok(builder.build()) + place.clone().into(), + address.clone().into(), + snapshot.clone().into(), + None, + position, + ) } - fn unique_ref_full_vars( + fn owned_non_aliased( &mut self, context: CallContext, ty: &vir_mid::Type, generics: &G, - place: &vir_low::VariableDecl, - root_address: &vir_low::VariableDecl, - current_snapshot: &vir_low::VariableDecl, - final_snapshot: &vir_low::VariableDecl, - lifetime: &vir_low::VariableDecl, + place: vir_low::Expression, + address: vir_low::Expression, + permission_amount: Option, + position: vir_low::Position, ) -> SpannedEncodingResult where G: WithLifetimes + WithConstArguments, { - self.unique_ref( + self.owned_predicate( context, ty, generics, - place.clone().into(), - root_address.clone().into(), - current_snapshot.clone().into(), - final_snapshot.clone().into(), - lifetime.clone().into(), + place, + address, + permission_amount, + position, ) } - fn unique_ref( + fn owned_non_aliased_with_snapshot( &mut self, context: CallContext, ty: &vir_mid::Type, generics: &G, place: vir_low::Expression, - root_address: vir_low::Expression, - current_snapshot: vir_low::Expression, - final_snapshot: vir_low::Expression, - lifetime: vir_low::Expression, + address: vir_low::Expression, + snapshot: vir_low::Expression, + permission_amount: Option, + position: vir_low::Position, ) -> SpannedEncodingResult where G: WithLifetimes + WithConstArguments, { - let mut builder = UniqueRefUseBuilder::new( - self, + let predicate = self.owned_non_aliased( context, ty, generics, - place, - root_address, - current_snapshot, - final_snapshot, - lifetime, + place.clone(), + address.clone(), + permission_amount, + position, )?; - builder.add_lifetime_arguments()?; - builder.add_const_arguments()?; - Ok(builder.build()) + let snap_call = + self.owned_non_aliased_snap(context, ty, generics, place, address, position)?; + Ok(vir_low::Expression::and( + predicate, + vir_low::Expression::equals(snapshot, snap_call), + )) } - fn frac_ref_full_vars( + fn owned_aliased( &mut self, context: CallContext, ty: &vir_mid::Type, generics: &G, - place: &vir_low::VariableDecl, - root_address: &vir_low::VariableDecl, - current_snapshot: &vir_low::VariableDecl, - lifetime: &vir_low::VariableDecl, + address: vir_low::Expression, + permission_amount: Option, + position: vir_low::Position, ) -> SpannedEncodingResult where G: WithLifetimes + WithConstArguments, { - self.frac_ref( + let place = self.place_option_none_constructor(position)?; + self.owned_non_aliased( + context, + ty, + generics, + place, + address, + permission_amount, + position, + ) + } + + fn owned_aliased_range( + &mut self, + context: CallContext, + ty: &vir_mid::Type, + generics: &G, + address: vir_low::Expression, + start_index: vir_low::Expression, + end_index: vir_low::Expression, + permission_amount: Option, + position: vir_low::Position, + ) -> SpannedEncodingResult + where + G: WithLifetimes + WithConstArguments, + { + let builder = OwnedAliasedRangeUseBuilder::new( + self, + context, + ty, + generics, + address, + start_index, + end_index, + permission_amount, + position, + )?; + builder.build() + } + + fn owned_predicate_snap( + &mut self, + context: CallContext, + ty: &vir_mid::Type, + generics: &G, + place: vir_low::Expression, + address: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult + where + G: WithLifetimes + WithConstArguments, + { + self.mark_owned_predicate_as_unfolded(ty)?; + let mut builder = OwnedNonAliasedSnapCallBuilder::new( + self, context, ty, generics, place, address, position, + )?; + builder.add_lifetime_arguments()?; + builder.add_const_arguments()?; + builder.build() + } + + fn owned_non_aliased_snap( + &mut self, + context: CallContext, + ty: &vir_mid::Type, + generics: &G, + place: vir_low::Expression, + address: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult + where + G: WithLifetimes + WithConstArguments, + { + self.owned_predicate_snap(context, ty, generics, place, address, position) + } + + fn owned_aliased_snap( + &mut self, + context: CallContext, + ty: &vir_mid::Type, + generics: &G, + address: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult + where + G: WithLifetimes + WithConstArguments, + { + let place = self.place_option_none_constructor(position)?; + self.owned_predicate_snap(context, ty, generics, place, address, position) + } + + fn owned_aliased_range_snap( + &mut self, + context: CallContext, + ty: &vir_mid::Type, + generics: &G, + address: vir_low::Expression, + start_index: vir_low::Expression, + end_index: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult + where + G: WithLifetimes + WithConstArguments, + { + self.ensure_type_definition(ty)?; + self.encode_owned_predicate_range_snapshot(ty)?; + // if !self + // .predicates_encoding_state + // .owned + // .used_owned_range_snapshot_functions + // .contains(ty) + // { + // self.ensure_type_definition(ty)?; + // self.predicates_encoding_state + // .owned + // .used_owned_range_snapshot_functions + // .insert(ty.clone()); + // } + let builder = OwnedAliasedRangeSnapCallBuilder::new( + self, + context, + ty, + generics, + address, + start_index, + end_index, + position, + )?; + builder.build() + } + + fn unique_ref_full_vars( + &mut self, + context: CallContext, + ty: &vir_mid::Type, + generics: &G, + place: &vir_low::VariableDecl, + address: &vir_low::VariableDecl, + lifetime: &vir_low::VariableDecl, + target_slice_len: Option, + position: vir_low::Position, + ) -> SpannedEncodingResult + where + G: WithLifetimes + WithConstArguments, + { + self.unique_ref( context, ty, generics, place.clone().into(), - root_address.clone().into(), + address.clone().into(), + lifetime.clone().into(), + target_slice_len, + None, + position, + ) + } + + fn unique_ref_full_vars_with_current_snapshot( + &mut self, + context: CallContext, + ty: &vir_mid::Type, + generics: &G, + place: &vir_low::VariableDecl, + address: &vir_low::VariableDecl, + current_snapshot: &vir_low::VariableDecl, + lifetime: &vir_low::VariableDecl, + target_slice_len: Option, + position: vir_low::Position, + ) -> SpannedEncodingResult + where + G: WithLifetimes + WithConstArguments, + { + self.unique_ref_with_current_snapshot( + context, + ty, + generics, + place.clone().into(), + address.clone().into(), current_snapshot.clone().into(), lifetime.clone().into(), + target_slice_len, + None, + position, ) } - fn frac_ref( + fn unique_ref( &mut self, context: CallContext, ty: &vir_mid::Type, generics: &G, place: vir_low::Expression, - root_address: vir_low::Expression, + address: vir_low::Expression, + lifetime: vir_low::Expression, + target_slice_len: Option, + permission_amount: Option, + position: vir_low::Position, + ) -> SpannedEncodingResult + where + G: WithLifetimes + WithConstArguments, + { + self.mark_unique_ref_as_used(ty)?; + let mut builder = UniqueRefUseBuilder::new( + self, + context, + ty, + generics, + place, + address, + lifetime, + target_slice_len, + position, + )?; + builder.add_lifetime_arguments()?; + builder.add_const_arguments()?; + builder.set_maybe_permission_amount(permission_amount)?; + builder.build() + } + + fn unique_ref_range( + &mut self, + context: CallContext, + ty: &vir_mid::Type, + generics: &G, + address: vir_low::Expression, + start_index: vir_low::Expression, + end_index: vir_low::Expression, + lifetime: vir_low::Expression, + permission_amount: Option, + position: vir_low::Position, + ) -> SpannedEncodingResult + where + G: WithLifetimes + WithConstArguments, + { + let builder = UniqueRefRangeUseBuilder::new( + self, + context, + ty, + generics, + address, + start_index, + end_index, + lifetime, + permission_amount, + position, + )?; + builder.build() + } + + fn unique_ref_with_current_snapshot( + &mut self, + context: CallContext, + ty: &vir_mid::Type, + generics: &G, + place: vir_low::Expression, + address: vir_low::Expression, current_snapshot: vir_low::Expression, lifetime: vir_low::Expression, + target_slice_len: Option, + permission_amount: Option, + position: vir_low::Position, + ) -> SpannedEncodingResult + where + G: WithLifetimes + WithConstArguments, + { + let predicate = self.unique_ref( + context, + ty, + generics, + place.clone(), + address.clone(), + lifetime.clone(), + target_slice_len.clone(), + permission_amount, + position, + )?; + let snap_call = self.unique_ref_snap( + context, + ty, + generics, + place, + address, + lifetime, + target_slice_len, + false, + position, + )?; + debug_assert_eq!(current_snapshot.get_type(), snap_call.get_type()); + Ok(vir_low::Expression::and( + predicate, + vir_low::Expression::equals(current_snapshot, snap_call), + )) + } + + fn unique_ref_snap( + &mut self, + context: CallContext, + ty: &vir_mid::Type, + generics: &G, + place: vir_low::Expression, + address: vir_low::Expression, + lifetime: vir_low::Expression, + target_slice_len: Option, + is_final: bool, + position: vir_low::Position, + ) -> SpannedEncodingResult + where + G: WithLifetimes + WithConstArguments, + { + self.mark_unique_ref_as_used(ty)?; + if is_final { + let mut builder = UniqueRefFinalSnapCallBuilder::new( + self, + context, + ty, + generics, + place, + address, + lifetime, + target_slice_len, + position, + )?; + builder.add_lifetime_arguments()?; + builder.add_const_arguments()?; + builder.build() + } else { + let mut builder = UniqueRefCurrentSnapCallBuilder::new( + self, + context, + ty, + generics, + place, + address, + lifetime, + target_slice_len, + position, + )?; + builder.add_lifetime_arguments()?; + builder.add_const_arguments()?; + builder.build() + } + } + + fn unique_ref_range_snap( + &mut self, + context: CallContext, + ty: &vir_mid::Type, + generics: &G, + address: vir_low::Expression, + start_index: vir_low::Expression, + end_index: vir_low::Expression, + lifetime: vir_low::Expression, + is_final: bool, + position: vir_low::Position, + ) -> SpannedEncodingResult + where + G: WithLifetimes + WithConstArguments, + { + self.ensure_type_definition(ty)?; + // if !self + // .predicates_encoding_state + // .owned + // .used_unique_ref_range_snapshot_functions + // .contains(ty) + // { + // self.ensure_type_definition(ty)?; + // self.predicates_encoding_state + // .owned + // .used_unique_ref_range_snapshot_functions + // .insert(ty.clone()); + // } + if is_final { + self.encode_unique_ref_predicate_final_range_snapshot(ty)?; + let mut builder = UniqueRefFinalRangeSnapCallBuilder::new( + self, + context, + ty, + generics, + address, + start_index, + end_index, + lifetime, + position, + )?; + builder.add_lifetime_arguments()?; + builder.add_const_arguments()?; + builder.build() + } else { + self.encode_unique_ref_predicate_current_range_snapshot(ty)?; + let mut builder = UniqueRefCurrentRangeSnapCallBuilder::new( + self, + context, + ty, + generics, + address, + start_index, + end_index, + lifetime, + position, + )?; + builder.add_lifetime_arguments()?; + builder.add_const_arguments()?; + builder.build() + } + } + + fn frac_ref_full_vars( + &mut self, + context: CallContext, + ty: &vir_mid::Type, + generics: &G, + place: &vir_low::VariableDecl, + address: &vir_low::VariableDecl, + lifetime: &vir_low::VariableDecl, + target_slice_len: Option, + position: vir_low::Position, + ) -> SpannedEncodingResult + where + G: WithLifetimes + WithConstArguments, + { + self.frac_ref( + context, + ty, + generics, + place.clone().into(), + address.clone().into(), + lifetime.clone().into(), + target_slice_len, + None, + position, + ) + } + + fn frac_ref_full_vars_with_current_snapshot( + &mut self, + context: CallContext, + ty: &vir_mid::Type, + generics: &G, + place: &vir_low::VariableDecl, + address: &vir_low::VariableDecl, + current_snapshot: &vir_low::VariableDecl, + lifetime: &vir_low::VariableDecl, + target_slice_len: Option, + position: vir_low::Position, + ) -> SpannedEncodingResult + where + G: WithLifetimes + WithConstArguments, + { + self.frac_ref_with_current_snapshot( + context, + ty, + generics, + place.clone().into(), + address.clone().into(), + current_snapshot.clone().into(), + lifetime.clone().into(), + target_slice_len, + None, + position, + ) + } + + fn frac_ref( + &mut self, + context: CallContext, + ty: &vir_mid::Type, + generics: &G, + place: vir_low::Expression, + address: vir_low::Expression, + lifetime: vir_low::Expression, + target_slice_len: Option, + permission_amount: Option, + position: vir_low::Position, ) -> SpannedEncodingResult where G: WithLifetimes + WithConstArguments, @@ -323,12 +1169,161 @@ impl<'p, 'v: 'p, 'tcx: 'v> PredicatesOwnedInterface for Lowerer<'p, 'v, 'tcx> { ty, generics, place, - root_address, - current_snapshot, + address, + lifetime, + target_slice_len, + position, + )?; + builder.add_lifetime_arguments()?; + builder.add_const_arguments()?; + builder.set_maybe_permission_amount(permission_amount)?; + builder.build() + } + + fn frac_ref_range( + &mut self, + context: CallContext, + ty: &vir_mid::Type, + generics: &G, + address: vir_low::Expression, + start_index: vir_low::Expression, + end_index: vir_low::Expression, + lifetime: vir_low::Expression, + permission_amount: Option, + position: vir_low::Position, + ) -> SpannedEncodingResult + where + G: WithLifetimes + WithConstArguments, + { + let builder = FracRefRangeUseBuilder::new( + self, + context, + ty, + generics, + address, + start_index, + end_index, + lifetime, + permission_amount, + position, + )?; + builder.build() + } + + fn frac_ref_with_current_snapshot( + &mut self, + context: CallContext, + ty: &vir_mid::Type, + generics: &G, + place: vir_low::Expression, + address: vir_low::Expression, + current_snapshot: vir_low::Expression, + lifetime: vir_low::Expression, + target_slice_len: Option, + permission_amount: Option, + position: vir_low::Position, + ) -> SpannedEncodingResult + where + G: WithLifetimes + WithConstArguments, + { + let predicate = self.frac_ref( + context, + ty, + generics, + place.clone(), + address.clone(), + lifetime.clone(), + target_slice_len.clone(), + permission_amount, + position, + )?; + let snap_call = self.frac_ref_snap( + context, + ty, + generics, + place, + address, + lifetime, + target_slice_len, + position, + )?; + Ok(vir_low::Expression::and( + predicate, + vir_low::Expression::equals(current_snapshot, snap_call), + )) + } + + fn frac_ref_snap( + &mut self, + context: CallContext, + ty: &vir_mid::Type, + generics: &G, + place: vir_low::Expression, + address: vir_low::Expression, + lifetime: vir_low::Expression, + target_slice_len: Option, + _position: vir_low::Position, + ) -> SpannedEncodingResult + where + G: WithLifetimes + WithConstArguments, + { + self.mark_frac_ref_as_used(ty)?; + let mut builder = FracRefSnapCallBuilder::new( + self, + context, + ty, + generics, + place, + address, + lifetime, + target_slice_len, + )?; + builder.add_lifetime_arguments()?; + builder.add_const_arguments()?; + builder.build() + } + + fn frac_ref_range_snap( + &mut self, + context: CallContext, + ty: &vir_mid::Type, + generics: &G, + address: vir_low::Expression, + start_index: vir_low::Expression, + end_index: vir_low::Expression, + lifetime: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult + where + G: WithLifetimes + WithConstArguments, + { + // if !self + // .predicates_encoding_state + // .owned + // .used_frac_ref_range_snapshot_functions + // .contains(ty) + // { + // self.ensure_type_definition(ty)?; + // self.predicates_encoding_state + // .owned + // .used_frac_ref_range_snapshot_functions + // .insert(ty.clone()); + // } + self.ensure_type_definition(ty)?; + self.encode_frac_ref_predicate_range_snapshot(ty)?; + let mut builder = FracRefRangeSnapCallBuilder::new( + self, + context, + ty, + generics, + address, + start_index, + end_index, lifetime, + position, )?; builder.add_lifetime_arguments()?; builder.add_const_arguments()?; - Ok(builder.build()) + builder.build() } } diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/mod.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/mod.rs index 75979d536e4..ac954d7612f 100644 --- a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/mod.rs +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/mod.rs @@ -2,10 +2,14 @@ mod builders; mod encoder; +mod state; mod interface; -pub(super) use self::interface::PredicatesOwnedState; +pub(super) use self::state::PredicatesOwnedState; pub(in super::super) use self::{ - builders::{FracRefUseBuilder, OwnedNonAliasedUseBuilder, UniqueRefUseBuilder}, - interface::PredicatesOwnedInterface, + builders::{ + FracRefUseBuilder, OwnedNonAliasedSnapCallBuilder, OwnedNonAliasedUseBuilder, + UniqueRefUseBuilder, + }, + interface::{OwnedPredicateInfo, PredicatesOwnedInterface, SnapshotFunctionInfo}, }; diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/owned/state.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/state.rs new file mode 100644 index 00000000000..8b50f4ce71d --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/owned/state.rs @@ -0,0 +1,31 @@ +use super::OwnedPredicateInfo; +use rustc_hash::FxHashSet; +use std::collections::BTreeMap; +use vir_crate::low::{self as vir_low}; + +#[derive(Default)] +pub(in super::super) struct PredicatesOwnedState { + // pub(super) unfolded_owned_predicates: FxHashSet, + // pub(super) used_unique_ref_predicates: FxHashSet, + // pub(super) used_frac_ref_predicates: FxHashSet, + // pub(super) used_owned_range_snapshot_functions: FxHashSet, + // pub(super) used_unique_ref_range_snapshot_functions: FxHashSet, + // pub(super) used_frac_ref_range_snapshot_functions: FxHashSet, + pub(super) encoded_owned_predicates: FxHashSet, + pub(super) encoded_unique_ref_predicates: FxHashSet, + pub(super) encoded_frac_ref_predicates: FxHashSet, + + pub(super) encoded_owned_predicate_snapshot_functions: FxHashSet, + pub(super) encoded_unique_ref_predicate_current_snapshot_functions: FxHashSet, + pub(super) encoded_unique_ref_predicate_final_snapshot_functions: FxHashSet, + pub(super) encoded_frac_ref_predicate_snapshot_functions: FxHashSet, + + pub(super) encoded_owned_predicate_range_snapshot_functions: FxHashSet, + pub(super) encoded_unique_ref_predicate_current_range_snapshot_functions: FxHashSet, + pub(super) encoded_unique_ref_predicate_final_range_snapshot_functions: FxHashSet, + pub(super) encoded_frac_ref_predicate_range_snapshot_functions: FxHashSet, + + pub(super) predicates: Vec, + /// A map from predicate names to snapshot function names and snapshot types. + pub(super) predicate_info: BTreeMap, +} diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/restoration/interface.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/restoration/interface.rs new file mode 100644 index 00000000000..fc13478036b --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/restoration/interface.rs @@ -0,0 +1,70 @@ +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::lowerer::{Lowerer, PredicatesLowererInterface}, +}; +use rustc_hash::FxHashSet; +use vir_crate::{common::identifier::WithIdentifier, low as vir_low, middle as vir_mid}; + +#[derive(Default)] +pub(in super::super) struct RestorationState { + encoded_restore_raw_borrowed_transition_predicate: FxHashSet, +} + +pub(in super::super::super) trait RestorationInterface { + fn encode_restore_raw_borrowed_transition_predicate( + &mut self, + ty: &vir_mid::Type, + ) -> SpannedEncodingResult<()>; + fn restore_raw_borrowed( + &mut self, + ty: &vir_mid::Type, + place: vir_low::Expression, + address: vir_low::Expression, + ) -> SpannedEncodingResult; +} + +impl<'p, 'v: 'p, 'tcx: 'v> RestorationInterface for Lowerer<'p, 'v, 'tcx> { + fn encode_restore_raw_borrowed_transition_predicate( + &mut self, + ty: &vir_mid::Type, + ) -> SpannedEncodingResult<()> { + let ty_identifier = ty.get_identifier(); + if !self + .predicates_encoding_state + .restoration + .encoded_restore_raw_borrowed_transition_predicate + .contains(&ty_identifier) + { + self.predicates_encoding_state + .restoration + .encoded_restore_raw_borrowed_transition_predicate + .insert(ty_identifier); + + use vir_low::macros::*; + let predicate = vir_low::PredicateDecl::new( + predicate_name! { RestoreRawBorrowed }, + vir_low::PredicateKind::WithoutSnapshotWhole, + vars!(place: PlaceOption, address: Address), + None, + ); + self.declare_predicate(predicate)?; + } + Ok(()) + } + fn restore_raw_borrowed( + &mut self, + ty: &vir_mid::Type, + place: vir_low::Expression, + address: vir_low::Expression, + ) -> SpannedEncodingResult { + self.encode_restore_raw_borrowed_transition_predicate(ty)?; + use vir_low::macros::*; + let predicate = expr! { + acc(RestoreRawBorrowed( + [place], + [address] + )) + }; + Ok(predicate) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/restoration/mod.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/restoration/mod.rs new file mode 100644 index 00000000000..58ddd243565 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/restoration/mod.rs @@ -0,0 +1,6 @@ +//! Encoder of predicates that guard restoration of permissions. + +mod interface; + +pub(in super::super) use self::interface::RestorationInterface; +pub(super) use self::interface::RestorationState; diff --git a/prusti-viper/src/encoder/middle/core_proof/predicates/state.rs b/prusti-viper/src/encoder/middle/core_proof/predicates/state.rs index 6b5f047885e..f04d3f7e72b 100644 --- a/prusti-viper/src/encoder/middle/core_proof/predicates/state.rs +++ b/prusti-viper/src/encoder/middle/core_proof/predicates/state.rs @@ -1,7 +1,12 @@ -use super::{memory_block::PredicatesMemoryBlockState, owned::PredicatesOwnedState}; +use super::{ + aliasing::PredicatesAliasingState, memory_block::PredicatesMemoryBlockState, + owned::PredicatesOwnedState, restoration::RestorationState, +}; #[derive(Default)] pub(in super::super) struct PredicatesState { pub(super) owned: PredicatesOwnedState, pub(super) memory_block: PredicatesMemoryBlockState, + pub(super) restoration: RestorationState, + pub(super) aliasing: PredicatesAliasingState, } diff --git a/prusti-viper/src/encoder/middle/core_proof/references/interface.rs b/prusti-viper/src/encoder/middle/core_proof/references/interface.rs index 20ec1f726b2..384987e6006 100644 --- a/prusti-viper/src/encoder/middle/core_proof/references/interface.rs +++ b/prusti-viper/src/encoder/middle/core_proof/references/interface.rs @@ -7,6 +7,7 @@ use crate::encoder::{ snapshots::{ IntoSnapshot, SnapshotAdtsInterface, SnapshotDomainsInterface, SnapshotValuesInterface, }, + type_layouts::TypeLayoutsInterface, types::TypesInterface, }, }; @@ -15,17 +16,17 @@ use vir_crate::{ middle::{self as vir_mid}, }; -trait Private { - fn reference_target_snapshot( - &mut self, - ty: &vir_mid::Type, - snapshot: vir_low::Expression, - position: vir_low::Position, - version: &str, - ) -> SpannedEncodingResult; -} +// trait Private { +// fn reference_target_snapshot( +// &mut self, +// ty: &vir_mid::Type, +// snapshot: vir_low::Expression, +// position: vir_low::Position, +// version: &str, +// ) -> SpannedEncodingResult; +// } -impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { +impl<'p, 'v: 'p, 'tcx: 'v> Lowerer<'p, 'v, 'tcx> { fn reference_target_snapshot( &mut self, ty: &vir_mid::Type, @@ -49,6 +50,14 @@ pub(in super::super) trait ReferencesInterface { current_snapshot: vir_low::Expression, position: vir_low::Position, ) -> SpannedEncodingResult; + fn unique_reference_snapshot_constructor( + &mut self, + ty: &vir_mid::Type, + address: vir_low::Expression, + current_snapshot: vir_low::Expression, + final_snapshot: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult; fn reference_deref_place( &mut self, place: vir_low::Expression, @@ -72,6 +81,12 @@ pub(in super::super) trait ReferencesInterface { snapshot: vir_low::Expression, position: vir_low::Position, ) -> SpannedEncodingResult; + fn reference_slice_len( + &mut self, + reference_type: &vir_mid::Type, + snapshot: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult>; fn reference_address_snapshot( &mut self, reference_type: &vir_mid::Type, @@ -103,6 +118,24 @@ impl<'p, 'v: 'p, 'tcx: 'v> ReferencesInterface for Lowerer<'p, 'v, 'tcx> { )? .set_default_position(position)) } + fn unique_reference_snapshot_constructor( + &mut self, + ty: &vir_mid::Type, + address: vir_low::Expression, + current_snapshot: vir_low::Expression, + final_snapshot: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult { + self.ensure_type_definition(ty)?; + let domain_name = self.encode_snapshot_domain_name(ty)?; + Ok(self + .snapshot_constructor_constant_call( + // FIXME: Why is the function called “constant”? + &domain_name, + vec![address, current_snapshot, final_snapshot], + )? + .set_default_position(position)) + } fn reference_deref_place( &mut self, place: vir_low::Expression, @@ -124,6 +157,10 @@ impl<'p, 'v: 'p, 'tcx: 'v> ReferencesInterface for Lowerer<'p, 'v, 'tcx> { snapshot: vir_low::Expression, position: vir_low::Position, ) -> SpannedEncodingResult { + assert!( + ty.is_unique_reference(), + "Expected unique reference, got {ty}" + ); self.reference_target_snapshot(ty, snapshot, position, "target_final") } fn reference_address( @@ -133,11 +170,33 @@ impl<'p, 'v: 'p, 'tcx: 'v> ReferencesInterface for Lowerer<'p, 'v, 'tcx> { position: vir_low::Position, ) -> SpannedEncodingResult { assert!(reference_type.is_reference()); - let domain_name = self.encode_snapshot_domain_name(reference_type)?; + // let domain_name = self.encode_snapshot_domain_name(reference_type)?; let return_type = self.address_type()?; - Ok(self - .snapshot_destructor_struct_call(&domain_name, "address", return_type, snapshot)? - .set_default_position(position)) + self.obtain_parameter_snapshot(reference_type, "address", return_type, snapshot, position) + // Ok(self + // .snapshot_destructor_struct_call(&domain_name, "address", return_type, snapshot)? + // .set_default_position(position)) + } + fn reference_slice_len( + &mut self, + reference_type: &vir_mid::Type, + snapshot: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult> { + assert!(reference_type.is_reference()); + let len = if reference_type.is_reference_to_slice() { + let return_type = self.size_type()?; + Some(self.obtain_parameter_snapshot( + reference_type, + "len", + return_type, + snapshot, + position, + )?) + } else { + None + }; + Ok(len) } fn reference_address_snapshot( &mut self, @@ -145,9 +204,13 @@ impl<'p, 'v: 'p, 'tcx: 'v> ReferencesInterface for Lowerer<'p, 'v, 'tcx> { snapshot: vir_low::Expression, position: vir_low::Position, ) -> SpannedEncodingResult { - let address = self.reference_address(reference_type, snapshot, position)?; + let address = self.reference_address(reference_type, snapshot.clone(), position)?; + let mut arguments = vec![address]; let address_type = self.reference_address_type(reference_type)?; - self.construct_struct_snapshot(&address_type, vec![address], position) + if let Some(len) = self.reference_slice_len(reference_type, snapshot, position)? { + arguments.push(len); + }; + self.construct_struct_snapshot(&address_type, arguments, position) } fn reference_address_type( &mut self, diff --git a/prusti-viper/src/encoder/middle/core_proof/snapshots/adts/interface.rs b/prusti-viper/src/encoder/middle/core_proof/snapshots/adts/interface.rs index ec84dad71db..2ca4d567bb1 100644 --- a/prusti-viper/src/encoder/middle/core_proof/snapshots/adts/interface.rs +++ b/prusti-viper/src/encoder/middle/core_proof/snapshots/adts/interface.rs @@ -60,6 +60,16 @@ pub(in super::super::super) trait SnapshotAdtsInterface { argument: vir_low::Expression, ) -> SpannedEncodingResult; + // Equality calls. + fn snapshot_equality_call( + &mut self, + domain_name: &str, + variant_name: &str, + left: vir_low::Expression, + right: vir_low::Expression, + gas: vir_low::Expression, + ) -> SpannedEncodingResult; + // Registration. fn register_constant_constructor( @@ -78,6 +88,8 @@ pub(in super::super::super) trait SnapshotAdtsInterface { &mut self, domain_name: &str, variant_name: &str, + unary_operation: Option, + binary_operation: Option, use_main_constructor_destructors: bool, parameters: Vec, ) -> SpannedEncodingResult<()>; @@ -102,13 +114,21 @@ impl<'p, 'v: 'p, 'tcx: 'v> SnapshotAdtsInterface for Lowerer<'p, 'v, 'tcx> { &mut self, domain_name: &str, ) -> SpannedEncodingResult { - self.adt_destructor_main_name(domain_name, "value") + let name = self.adt_destructor_main_name(domain_name, "value")?; + self.snapshots_state + .snapshot_domains_info + .register_constant_destructor(domain_name, &name)?; + Ok(name) } fn snapshot_constructor_struct_name( &mut self, domain_name: &str, ) -> SpannedEncodingResult { - self.adt_constructor_main_name(domain_name) + let name = self.adt_constructor_main_name(domain_name)?; + self.snapshots_state + .snapshot_domains_info + .register_constant_constructor(domain_name, &name)?; + Ok(name) } fn snapshot_constructor_struct_alternative_name( &mut self, @@ -129,6 +149,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> SnapshotAdtsInterface for Lowerer<'p, 'v, 'tcx> { domain_name: &str, arguments: Vec, ) -> SpannedEncodingResult { + let _ = self.snapshot_constructor_struct_name(domain_name)?; // FIXME: this is a hack to trigger registration. self.adt_constructor_main_call(domain_name, arguments) } fn snapshot_alternative_constructor_struct_call( @@ -157,6 +178,16 @@ impl<'p, 'v: 'p, 'tcx: 'v> SnapshotAdtsInterface for Lowerer<'p, 'v, 'tcx> { ) -> SpannedEncodingResult { self.adt_destructor_variant_call(domain_name, variant_name, "value", variant_type, argument) } + fn snapshot_equality_call( + &mut self, + domain_name: &str, + variant_name: &str, + left: vir_low::Expression, + right: vir_low::Expression, + gas: vir_low::Expression, + ) -> SpannedEncodingResult { + self.adt_snapshot_equality_variant_call(domain_name, variant_name, left, right, gas) + } fn register_constant_constructor( &mut self, domain_name: &str, @@ -181,12 +212,27 @@ impl<'p, 'v: 'p, 'tcx: 'v> SnapshotAdtsInterface for Lowerer<'p, 'v, 'tcx> { &mut self, domain_name: &str, variant_name: &str, + unary_operation: Option, + binary_operation: Option, use_main_constructor_destructors: bool, parameters: Vec, ) -> SpannedEncodingResult<()> { + if let Some(op) = unary_operation { + let constructor_name = self.adt_constructor_variant_name(domain_name, variant_name)?; + self.snapshots_state + .snapshot_domains_info + .register_unary_operation(domain_name, op, constructor_name)?; + } + if let Some(op) = binary_operation { + let constructor_name = self.adt_constructor_variant_name(domain_name, variant_name)?; + self.snapshots_state + .snapshot_domains_info + .register_binary_operation(domain_name, op, constructor_name)?; + } self.adt_register_variant_constructor( domain_name, variant_name, + // operation, use_main_constructor_destructors, parameters, false, @@ -203,6 +249,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> SnapshotAdtsInterface for Lowerer<'p, 'v, 'tcx> { self.adt_register_variant_constructor( domain_name, variant_name, + // None, use_main_constructor_destructors, parameters, true, @@ -235,6 +282,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> SnapshotAdtsInterface for Lowerer<'p, 'v, 'tcx> { self.adt_register_variant_constructor( domain_name, variant_name, + // None, false, vars! { value: {parameter_type}}, true, diff --git a/prusti-viper/src/encoder/middle/core_proof/snapshots/adts/mod.rs b/prusti-viper/src/encoder/middle/core_proof/snapshots/adts/mod.rs index 199dec471a2..68f9ca25709 100644 --- a/prusti-viper/src/encoder/middle/core_proof/snapshots/adts/mod.rs +++ b/prusti-viper/src/encoder/middle/core_proof/snapshots/adts/mod.rs @@ -6,5 +6,9 @@ //! `prusti-viper/src/encoder/middle/core_proof/adts/interface.rs`. mod interface; +mod state; -pub(in super::super) use self::interface::SnapshotAdtsInterface; +pub(in super::super) use self::{ + interface::SnapshotAdtsInterface, + state::{SnapshotDomainInfo, SnapshotDomainsInfo}, +}; diff --git a/prusti-viper/src/encoder/middle/core_proof/snapshots/adts/state.rs b/prusti-viper/src/encoder/middle/core_proof/snapshots/adts/state.rs new file mode 100644 index 00000000000..8fc60fe7be7 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/snapshots/adts/state.rs @@ -0,0 +1,108 @@ +use crate::encoder::errors::SpannedEncodingResult; +use rustc_hash::FxHashMap; +use std::collections::BTreeMap; +use vir_crate::low::{self as vir_low}; + +#[derive(Default, Clone)] +pub(in super::super::super) struct SnapshotDomainsInfo { + /// A map from a snapshot domain name to information about the snapshot domain. + pub(in super::super::super) snapshot_domains: BTreeMap, + /// A map from a type to the name of the snapshot domain that represents the type. + pub(in super::super::super) type_domains: FxHashMap, + pub(in super::super::super) bool_type: Option, +} + +#[derive(Default, Clone)] +pub(in super::super::super) struct SnapshotDomainInfo { + /// The name of the domain function used to create constant values. + pub(in super::super::super) constant_constructor_name: Option, + /// The name of the domain function used to destruct constant values. + pub(in super::super::super) constant_destructor_name: Option, + /// The binary operators that correspond to the given domain functions. + pub(in super::super::super) binary_operators: BTreeMap, + /// The unary operators that correspond to the given domain functions. + pub(in super::super::super) unary_operators: BTreeMap, + /// The snapshot extensionality triggering functions. + pub(in super::super::super) snapshot_equality: Option, +} + +impl SnapshotDomainsInfo { + pub(in super::super) fn register_constant_constructor( + &mut self, + domain_name: &str, + function_name: &str, + ) -> SpannedEncodingResult<()> { + let snapshot_domain = self.get_snapshot_domain(domain_name)?; + if snapshot_domain.constant_constructor_name.is_none() { + snapshot_domain.constant_constructor_name = Some(function_name.to_string()); + } + Ok(()) + } + + pub(in super::super) fn register_constant_destructor( + &mut self, + domain_name: &str, + function_name: &str, + ) -> SpannedEncodingResult<()> { + let snapshot_domain = self.get_snapshot_domain(domain_name)?; + if snapshot_domain.constant_destructor_name.is_none() { + snapshot_domain.constant_destructor_name = Some(function_name.to_string()); + } + Ok(()) + } + + pub(in super::super) fn register_unary_operation( + &mut self, + domain_name: &str, + op: vir_low::UnaryOpKind, + function_name: String, + ) -> SpannedEncodingResult<()> { + let snapshot_domain = self.get_snapshot_domain(domain_name)?; + assert!(snapshot_domain + .unary_operators + .insert(function_name, op) + .is_none()); + Ok(()) + } + + pub(in super::super) fn register_binary_operation( + &mut self, + domain_name: &str, + op: vir_low::BinaryOpKind, + function_name: String, + ) -> SpannedEncodingResult<()> { + let snapshot_domain = self.get_snapshot_domain(domain_name)?; + assert!(snapshot_domain + .binary_operators + .insert(function_name, op) + .is_none()); + Ok(()) + } + + // FIXME: The visibility should be `pub(in super::super)`. + pub(in super::super::super) fn register_snapshot_equality( + &mut self, + domain_name: &str, + function_name: &str, + ) -> SpannedEncodingResult<()> { + let snapshot_domain = self.get_snapshot_domain(domain_name)?; + if snapshot_domain.snapshot_equality.is_none() { + snapshot_domain.snapshot_equality = Some(function_name.to_string()); + self.type_domains.insert( + vir_low::Type::domain(domain_name.to_string()), + domain_name.to_string(), + ); + } + Ok(()) + } + + fn get_snapshot_domain( + &mut self, + domain_name: &str, + ) -> SpannedEncodingResult<&mut SnapshotDomainInfo> { + Ok(self + .snapshot_domains + .entry(domain_name.to_string()) + .or_default()) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/snapshots/bytes/interface.rs b/prusti-viper/src/encoder/middle/core_proof/snapshots/bytes/interface.rs index cd6f7741c4c..13d3b2752f8 100644 --- a/prusti-viper/src/encoder/middle/core_proof/snapshots/bytes/interface.rs +++ b/prusti-viper/src/encoder/middle/core_proof/snapshots/bytes/interface.rs @@ -6,6 +6,7 @@ use crate::encoder::{ snapshots::SnapshotDomainsInterface, }, }; +use prusti_common::config; use vir_crate::{ common::identifier::WithIdentifier, low::{self as vir_low}, @@ -38,13 +39,70 @@ impl<'p, 'v: 'p, 'tcx: 'v> SnapshotBytesInterface for Lowerer<'p, 'v, 'tcx> { let domain_name = self.encode_snapshot_domain_name(ty)?; let domain_type = self.encode_snapshot_domain_type(ty)?; let return_type = self.bytes_type()?; + let to_bytes = format!("to_bytes${}", ty.get_identifier()); + let snapshot = vir_low::VariableDecl::new("snapshot", domain_type.clone()); self.declare_domain_function( &domain_name, - std::borrow::Cow::Owned(format!("to_bytes${}", ty.get_identifier())), + std::borrow::Cow::Owned(to_bytes.clone()), false, - std::borrow::Cow::Owned(vec![vir_low::VariableDecl::new("snapshot", domain_type)]), - std::borrow::Cow::Owned(return_type), + std::borrow::Cow::Owned(vec![snapshot.clone()]), + std::borrow::Cow::Owned(return_type.clone()), )?; + if !config::use_snapshot_parameters_in_predicates() + && matches!( + ty, + vir_mid::Type::Bool + | vir_mid::Type::Int(_) + | vir_mid::Type::Float(_) + | vir_mid::Type::Pointer(_) + | vir_mid::Type::Sequence(_) + | vir_mid::Type::Map(_) + ) + { + // This is sound only for primitive types. + let from_bytes = format!("from_bytes${}", ty.get_identifier()); + self.declare_domain_function( + &domain_name, + std::borrow::Cow::Owned(from_bytes.clone()), + false, + std::borrow::Cow::Owned(vec![vir_low::VariableDecl::new( + "bytes", + return_type.clone(), + )]), + std::borrow::Cow::Owned(domain_type.clone()), + )?; + + let to_bytes_call = vir_low::Expression::domain_function_call( + domain_name.clone(), + to_bytes, + vec![snapshot.clone().into()], + return_type, + ); + let from_bytes_call = vir_low::Expression::domain_function_call( + domain_name.clone(), + from_bytes, + vec![to_bytes_call.clone()], + domain_type, + ); + // let body = vir_low::Expression::forall( + // vec![snapshot.clone()], + // vec![vir_low::Trigger::new(vec![to_bytes_call])], + // expr! { + // snapshot == [ from_bytes_call ] + // }, + // ); + let axiom = vir_low::DomainRewriteRuleDecl { + // We use ty identifier to distinguish sequences from arrays. + name: format!("{}${}$to_bytes_injective", domain_name, ty.get_identifier()), + comment: None, + egg_only: false, + variables: vec![snapshot.clone()], + triggers: Some(vec![vir_low::Trigger::new(vec![to_bytes_call])]), + source: snapshot.into(), + target: from_bytes_call, + }; + self.declare_rewrite_rule(&domain_name, axiom)?; + } } Ok(()) } diff --git a/prusti-viper/src/encoder/middle/core_proof/snapshots/domains/interface.rs b/prusti-viper/src/encoder/middle/core_proof/snapshots/domains/interface.rs index 6e3c004141f..b20076183ab 100644 --- a/prusti-viper/src/encoder/middle/core_proof/snapshots/domains/interface.rs +++ b/prusti-viper/src/encoder/middle/core_proof/snapshots/domains/interface.rs @@ -3,6 +3,7 @@ use crate::encoder::{ middle::core_proof::{ lifetimes::LifetimesInterface, lowerer::{DomainsLowererInterface, Lowerer}, + predicates::PredicatesMemoryBlockInterface, }, }; use std::collections::hash_map::Entry; @@ -95,6 +96,8 @@ impl<'p, 'v: 'p, 'tcx: 'v> SnapshotDomainsInterface for Lowerer<'p, 'v, 'tcx> { vir_mid::Type::MBool => Ok(vir_low::Type::Bool), vir_mid::Type::MInt => Ok(vir_low::Type::Int), vir_mid::Type::MPerm => Ok(vir_low::Type::Perm), + vir_mid::Type::MByte => self.byte_type(), + vir_mid::Type::MBytes => self.bytes_type(), vir_mid::Type::Sequence(seq) => { let enc_elem = self.encode_snapshot_domain_type(&seq.element_type)?; let low_ty = vir_low::Type::seq(enc_elem); diff --git a/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/assertions/constructor.rs b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/assertions/constructor.rs new file mode 100644 index 00000000000..c970ab5491e --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/assertions/constructor.rs @@ -0,0 +1,605 @@ +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::{ + builtin_methods::CallContext, + footprint::{DerefFields, DerefOwned, DerefOwnedRange}, + lowerer::{DomainsLowererInterface, Lowerer}, + places::PlacesInterface, + pointers::PointersInterface, + predicates::PredicatesOwnedInterface, + snapshots::{ + IntoSnapshot, IntoSnapshotLowerer, SnapshotDomainsInterface, SnapshotValuesInterface, + }, + }, +}; +use rustc_hash::FxHashMap; +use std::collections::BTreeMap; +use vir_crate::{ + common::{expression::SyntacticEvaluation, position::Positioned}, + low::{self as vir_low}, + middle::{self as vir_mid, operations::ty::Typed}, +}; + +use super::PredicateKind; + +pub(in super::super::super::super) struct AssertionToSnapshotConstructor<'a> { + predicate_kind: PredicateKind, + ty: &'a vir_mid::Type, + /// Arguments for the regular struct fields. + regular_field_arguments: Vec, + /// A map for replacing `self.field` with a matching argument. Used in + /// assign postcondition. + field_replacement_map: FxHashMap, + /// Mapping from deref fields to their positions in the arguments' list. + deref_fields: BTreeMap, + /// Mapping from deref range fields to their positions in the arguments' list. + deref_range_fields: BTreeMap, + /// Which places are framed on the path being explored. + framed_places: Vec, + /// Which addresses are framed on the path being explored. + /// + /// The tuple is `(address, start_index, end_index)`. + framed_range_addresses: Vec<( + vir_mid::Expression, + vir_mid::Expression, + vir_mid::Expression, + )>, + /// Whether should wrap all snap calls into old. + is_in_old_state: bool, + /// A flag used to check whether a conditional has nested conditionals. + found_conditional: bool, + position: vir_low::Position, +} + +fn deref_fields_into_maps( + regular_field_arguments_len: usize, + (deref_fields, deref_range_fields): DerefFields, +) -> ( + BTreeMap, + BTreeMap, +) { + let deref_fields = deref_fields + .into_iter() + .enumerate() + .map(|(i, DerefOwned { place, .. })| (i + regular_field_arguments_len, place)) + .collect::>(); + let deref_range_fields = deref_range_fields + .into_iter() + .enumerate() + .map(|(i, DerefOwnedRange { address, .. })| { + ( + i + deref_fields.len() + regular_field_arguments_len, + address, + ) + }) + .collect(); + (deref_fields, deref_range_fields) +} + +#[derive(Clone, Debug, PartialEq, Eq)] +enum FramingPredicate { + Owned(vir_mid::ast::predicate::OwnedNonAliased), + UniqueRef(vir_mid::ast::predicate::UniqueRef), +} + +impl<'a> AssertionToSnapshotConstructor<'a> { + pub(in super::super::super::super) fn for_assign_aggregate_postcondition( + ty: &'a vir_mid::Type, + regular_field_arguments: Vec, + fields: Vec, + all_deref_fields: DerefFields, + position: vir_low::Position, + ) -> Self { + let field_replacement_map = fields + .into_iter() + .zip(regular_field_arguments.iter().cloned()) + .collect(); + let (deref_fields, deref_range_fields) = + deref_fields_into_maps(regular_field_arguments.len(), all_deref_fields); + Self { + predicate_kind: PredicateKind::Owned, + ty, + regular_field_arguments, + field_replacement_map, + deref_fields, + deref_range_fields, + framed_places: Vec::new(), + framed_range_addresses: Vec::new(), + is_in_old_state: true, + found_conditional: false, + position, + } + } + + pub(in super::super::super::super) fn for_function_body( + predicate_kind: PredicateKind, + ty: &'a vir_mid::Type, + regular_field_arguments: Vec, + fields: Vec, + all_deref_fields: DerefFields, + position: vir_low::Position, + ) -> Self { + let field_replacement_map = fields + .into_iter() + .zip(regular_field_arguments.iter().cloned()) + .collect(); + let (deref_fields, deref_range_fields) = + deref_fields_into_maps(regular_field_arguments.len(), all_deref_fields); + Self { + predicate_kind, + ty, + regular_field_arguments, + field_replacement_map, + deref_fields, + deref_range_fields, + framed_places: Vec::new(), + framed_range_addresses: Vec::new(), + is_in_old_state: false, + found_conditional: false, + position, + } + } + + pub(in super::super::super::super) fn expression_to_snapshot_constructor<'p, 'v, 'tcx>( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + expression: &vir_mid::Expression, + ) -> SpannedEncodingResult { + let constructor_expression = self.expression_to_snapshot(lowerer, expression, false)?; + if self.found_conditional { + Ok(constructor_expression) + } else { + self.generate_snapshot_constructor(lowerer) + } + } + + fn framed_place_contains(&self, place: &vir_mid::Expression) -> Option<&FramingPredicate> { + self.framed_places.iter().find(|predicate| match predicate { + FramingPredicate::Owned(owned) => owned.place == *place, + FramingPredicate::UniqueRef(unique_ref) => unique_ref.place == *place, + }) + } + + // FIXME: Code duplication. + fn snap_call<'p, 'v, 'tcx>( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + ty: &vir_mid::Type, + place: vir_low::Expression, + address: vir_low::Expression, + framing_predicate: &FramingPredicate, + position: vir_low::Position, + ) -> SpannedEncodingResult { + match framing_predicate { + FramingPredicate::Owned(_) => match &self.predicate_kind { + PredicateKind::Owned => lowerer.owned_non_aliased_snap( + CallContext::BuiltinMethod, + ty, + ty, + place, + address, + position, + ), + PredicateKind::FracRef { lifetime } => { + let TODO_target_slice_len = None; + lowerer.frac_ref_snap( + CallContext::BuiltinMethod, + ty, + ty, + place, + address, + lifetime.clone(), + TODO_target_slice_len, + position, + ) + } + PredicateKind::UniqueRef { lifetime, is_final } => { + let TODO_target_slice_len = None; + lowerer.unique_ref_snap( + CallContext::BuiltinMethod, + ty, + ty, + place, + address, + lifetime.clone(), + TODO_target_slice_len, + *is_final, + position, + ) + } + }, + FramingPredicate::UniqueRef(predicate) => match &self.predicate_kind { + PredicateKind::Owned | PredicateKind::UniqueRef { .. } => { + let TODO_target_slice_len = None; + let lifetime = + self.encode_lifetime_in_self_context(lowerer, predicate.lifetime.clone())?; + lowerer.unique_ref_snap( + CallContext::BuiltinMethod, + ty, + ty, + place, + address, + lifetime, + TODO_target_slice_len, + false, + position, + ) + } + PredicateKind::FracRef { lifetime } => { + let TODO_target_slice_len = None; + lowerer.frac_ref_snap( + CallContext::BuiltinMethod, + ty, + ty, + place, + address, + lifetime.clone(), + TODO_target_slice_len, + position, + ) + } + }, + } + } + + fn snap_range_call<'p, 'v, 'tcx>( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + ty: &vir_mid::Type, + address: vir_low::Expression, + start_index: vir_low::Expression, + end_index: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult { + match &self.predicate_kind { + PredicateKind::Owned => lowerer.owned_aliased_range_snap( + CallContext::BuiltinMethod, + ty, + ty, + address, + start_index, + end_index, + position, + ), + PredicateKind::FracRef { lifetime } => lowerer.frac_ref_range_snap( + CallContext::BuiltinMethod, + ty, + ty, + address, + start_index, + end_index, + lifetime.clone(), + position, + ), + PredicateKind::UniqueRef { lifetime, is_final } => lowerer.unique_ref_range_snap( + CallContext::BuiltinMethod, + ty, + ty, + address, + start_index, + end_index, + lifetime.clone(), + *is_final, + position, + ), + } + } + + fn generate_dangling_snapshot<'p, 'v, 'tcx>( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + ty: &vir_mid::Type, + ) -> SpannedEncodingResult { + let domain_name = lowerer.encode_snapshot_domain_name(ty)?; + let function_name = format!("{domain_name}$dangling"); + let return_type = ty.to_snapshot(lowerer)?; + lowerer.create_unique_domain_func_app( + domain_name, + function_name, + Vec::new(), + return_type, + self.position, + ) + } + + fn compute_deref_address<'p, 'v, 'tcx>( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + place: &vir_mid::Expression, + ) -> SpannedEncodingResult { + let address = match place { + vir_mid::Expression::Local(_) => unreachable!("{place}"), + vir_mid::Expression::Field(_) => todo!(), + vir_mid::Expression::Deref(deref) => { + let base_snapshot = self.expression_to_snapshot(lowerer, &deref.base, true)?; + let ty = deref.base.get_type(); + lowerer.pointer_address(ty, base_snapshot, place.position())? + } + _ => unimplemented!("{place}"), + }; + Ok(address) + } + + fn generate_snapshot_constructor<'p, 'v, 'tcx>( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + ) -> SpannedEncodingResult { + let mut arguments = self.regular_field_arguments.clone(); + for deref_field in self.deref_fields.clone().values() { + let ty = deref_field.get_type(); + let deref_field_snapshot = + if let Some(framing_predicate) = self.framed_place_contains(deref_field) { + let framing_predicate = (*framing_predicate).clone(); + // The place is framed, generate the snap call. + let place = lowerer.encode_expression_as_place(deref_field)?; + // Note: we cannot use `encode_expression_as_place_address` here + // because that method can be used only inside procedure with + // SSA addresses. Therefore, we need to compute the address + // ourselves. + let address = self.compute_deref_address(lowerer, deref_field)?; + let snap_call = self.snap_call( + lowerer, + ty, + place, + address, + &framing_predicate, + self.position, + )?; + if self.is_in_old_state { + vir_low::Expression::labelled_old(None, snap_call, self.position) + } else { + snap_call + } + } else { + // The place is not framed. Create a dangling (null) snapshot. + self.generate_dangling_snapshot(lowerer, ty)? + }; + arguments.push(deref_field_snapshot); + } + for deref_range_field in self.deref_range_fields.clone().values() { + let ty = deref_range_field.get_type(); + let deref_range_field_snapshot = + if let Some((pointer_value_mid, start_index, end_index)) = self + .framed_range_addresses + .iter() + .find(|(address, _, _)| deref_range_field == address) + .cloned() + { + // The address is framed, generate the snap call. + let pointer_value = + self.expression_to_snapshot(lowerer, &pointer_value_mid, true)?; + let start_index = self.expression_to_snapshot(lowerer, &start_index, true)?; + let end_index = self.expression_to_snapshot(lowerer, &end_index, true)?; + let snap_call = self.snap_range_call( + lowerer, + ty, + pointer_value, + start_index, + end_index, + self.position, + )?; + if self.is_in_old_state { + vir_low::Expression::labelled_old(None, snap_call, self.position) + } else { + snap_call + } + } else { + // The place is not framed. Create a dangling (null) snapshot. + self.generate_dangling_snapshot(lowerer, ty)? + }; + arguments.push(deref_range_field_snapshot); + } + lowerer.construct_struct_snapshot(self.ty, arguments, self.position) + } + + // // FIXME: Code duplication. + // fn pointer_deref_into_address<'p, 'v, 'tcx>( + // &mut self, + // lowerer: &mut Lowerer<'p, 'v, 'tcx>, + // place: &vir_mid::Expression, + // ) -> SpannedEncodingResult { + // if let Some(deref_place) = place.get_last_dereferenced_pointer() { + // let base_snapshot = self.expression_to_snapshot(lowerer, deref_place, true)?; + // let ty = deref_place.get_type(); + // lowerer.pointer_address(ty, base_snapshot, place.position()) + // } else { + // unreachable!() + // } + // } + + fn conditional_branch_to_snapshot<'p, 'v, 'tcx>( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + branch: &vir_mid::Expression, + expect_math_bool: bool, + ) -> SpannedEncodingResult { + self.found_conditional = false; + let old_framed_places_count = self.framed_places.len(); + let branch_snapshot = self.expression_to_snapshot(lowerer, branch, expect_math_bool)?; + let expression = if !self.found_conditional { + // We reached the lowest level, generate the snapshot constructor. + self.generate_snapshot_constructor(lowerer)? + } else { + branch_snapshot + }; + self.framed_places.truncate(old_framed_places_count); + Ok(expression) + } +} + +impl<'a, 'p, 'v: 'p, 'tcx: 'v> IntoSnapshotLowerer<'p, 'v, 'tcx> + for AssertionToSnapshotConstructor<'a> +{ + fn binary_op_to_snapshot( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + op: &vir_mid::BinaryOp, + expect_math_bool: bool, + ) -> SpannedEncodingResult { + if op.op_kind == vir_mid::BinaryOpKind::And { + if op.left.is_true() { + return self.expression_to_snapshot(lowerer, &op.right, expect_math_bool); + } else if op.right.is_true() { + return self.expression_to_snapshot(lowerer, &op.left, expect_math_bool); + } + } + self.binary_op_to_snapshot_impl(lowerer, op, expect_math_bool) + } + + fn conditional_to_snapshot( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + conditional: &vir_mid::Conditional, + expect_math_bool: bool, + ) -> SpannedEncodingResult { + let guard_snapshot = self.expression_to_snapshot(lowerer, &conditional.guard, true)?; + + let then_expr_snapshot = + self.conditional_branch_to_snapshot(lowerer, &conditional.then_expr, expect_math_bool)?; + let else_expr_snapshot = + self.conditional_branch_to_snapshot(lowerer, &conditional.else_expr, expect_math_bool)?; + + self.found_conditional = true; + Ok(vir_low::Expression::conditional( + guard_snapshot, + then_expr_snapshot, + else_expr_snapshot, + conditional.position, + )) + } + + fn field_to_snapshot( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + field: &vir_mid::Field, + expect_math_bool: bool, + ) -> SpannedEncodingResult { + match &*field.base { + vir_mid::Expression::Local(local) + if local.variable.is_self_variable() + && self.field_replacement_map.contains_key(&field.field) => + { + Ok(self.field_replacement_map[&field.field].clone()) + } + _ => self.field_to_snapshot_impl(lowerer, field, expect_math_bool), + } + } + + fn variable_to_snapshot( + &mut self, + _lowerer: &mut Lowerer<'p, 'v, 'tcx>, + _variable: &vir_mid::VariableDecl, + ) -> SpannedEncodingResult { + todo!() + } + + fn labelled_old_to_snapshot( + &mut self, + _lowerer: &mut Lowerer<'p, 'v, 'tcx>, + _old: &vir_mid::LabelledOld, + _expect_math_bool: bool, + ) -> SpannedEncodingResult { + todo!() + } + + fn func_app_to_snapshot( + &mut self, + _lowerer: &mut Lowerer<'p, 'v, 'tcx>, + _app: &vir_mid::FuncApp, + _expect_math_bool: bool, + ) -> SpannedEncodingResult { + todo!() + } + + fn acc_predicate_to_snapshot( + &mut self, + _lowerer: &mut Lowerer<'p, 'v, 'tcx>, + acc_predicate: &vir_mid::AccPredicate, + _expect_math_bool: bool, + ) -> SpannedEncodingResult { + match &*acc_predicate.predicate { + vir_mid::Predicate::LifetimeToken(_) + | vir_mid::Predicate::MemoryBlockStack(_) + | vir_mid::Predicate::MemoryBlockStackDrop(_) => { + unreachable!(); + } + vir_mid::Predicate::MemoryBlockHeap(_) + | vir_mid::Predicate::MemoryBlockHeapRange(_) + | vir_mid::Predicate::MemoryBlockHeapRangeGuarded(_) + | vir_mid::Predicate::MemoryBlockHeapDrop(_) => { + // Do nothing. + } + vir_mid::Predicate::OwnedNonAliased(predicate) => { + self.framed_places + .push(FramingPredicate::Owned(predicate.clone())); + } + vir_mid::Predicate::OwnedRange(predicate) => { + self.framed_range_addresses.push(( + predicate.address.clone(), + predicate.start_index.clone(), + predicate.end_index.clone(), + )); + } + vir_mid::Predicate::OwnedSet(_) => todo!(), + vir_mid::Predicate::UniqueRef(predicate) => { + self.framed_places + .push(FramingPredicate::UniqueRef(predicate.clone())); + } + vir_mid::Predicate::UniqueRefRange(predicate) => { + self.framed_range_addresses.push(( + predicate.address.clone(), + predicate.start_index.clone(), + predicate.end_index.clone(), + )); + } + vir_mid::Predicate::FracRef(_) => todo!(), + vir_mid::Predicate::FracRefRange(_) => todo!(), + } + Ok(true.into()) + } + + // FIXME: Code duplication. + fn pointer_deref_to_snapshot( + &mut self, + _lowerer: &mut Lowerer<'p, 'v, 'tcx>, + _deref: &vir_mid::Deref, + _base_snapshot: vir_low::Expression, + _expect_math_bool: bool, + ) -> SpannedEncodingResult { + unimplemented!("outdated code"); + // let heap = self + // .heap + // .clone() + // .expect("This function should be reachable only when heap is Some"); + // lowerer.pointer_target_snapshot_in_heap( + // deref.base.get_type(), + // heap, + // base_snapshot, + // deref.position, + // ) + } + + fn call_context(&self) -> CallContext { + CallContext::BuiltinMethod + } + + fn owned_non_aliased_snap( + &mut self, + _lowerer: &mut Lowerer<'p, 'v, 'tcx>, + _ty: &vir_mid::Type, + _pointer_place: &vir_mid::Expression, + ) -> SpannedEncodingResult { + todo!() + } + + fn push_bound_variables( + &mut self, + _variables: &[vir_mid::VariableDecl], + ) -> SpannedEncodingResult<()> { + todo!() + } + + fn pop_bound_variables(&mut self) -> SpannedEncodingResult<()> { + todo!() + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/assertions/mod.rs b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/assertions/mod.rs new file mode 100644 index 00000000000..664b6ffc290 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/assertions/mod.rs @@ -0,0 +1,32 @@ +use vir_crate::low::{self as vir_low}; + +/// Assertions that are self-framing: each dereference of a pointer needs to be +/// behind `own`. +mod self_framing; +/// Assertions where the places (leaves) are translated to `snap` calls. +mod snap; +/// Assertions where the places are translated by using `heap$` pure variable. +mod pure_heap; +/// The snapshot validity assertion. +mod validity; +/// Structural invariant that needs to be translated into a snapshot +/// constructor. +mod constructor; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub(in super::super::super::super) enum PredicateKind { + Owned, + FracRef { + lifetime: vir_low::Expression, + }, + UniqueRef { + lifetime: vir_low::Expression, + is_final: bool, + }, +} + +pub(in super::super::super) use self::{ + constructor::AssertionToSnapshotConstructor, + self_framing::{SelfFramingAssertionEncoderState, SelfFramingAssertionToSnapshot}, + validity::ValidityAssertionToSnapshot, +}; diff --git a/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/assertions/pure_heap.rs b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/assertions/pure_heap.rs new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/assertions/pure_heap.rs @@ -0,0 +1 @@ + diff --git a/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/assertions/self_framing.rs b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/assertions/self_framing.rs new file mode 100644 index 00000000000..039b518d1af --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/assertions/self_framing.rs @@ -0,0 +1,1210 @@ +use super::PredicateKind; +use crate::encoder::{ + errors::{SpannedEncodingError, SpannedEncodingResult}, + high::types::HighTypeEncoderInterface, + middle::core_proof::{ + addresses::AddressesInterface, + builtin_methods::CallContext, + lifetimes::LifetimesInterface, + lowerer::{FunctionsLowererInterface, Lowerer}, + places::PlacesInterface, + pointers::PointersInterface, + predicates::{PredicatesMemoryBlockInterface, PredicatesOwnedInterface}, + snapshots::{IntoSnapshotLowerer, SnapshotValuesInterface, SnapshotVariablesInterface}, + type_layouts::TypeLayoutsInterface, + }, +}; +use rustc_hash::FxHashMap; +use std::collections::BTreeMap; +use vir_crate::{ + common::{identifier::WithIdentifier, position::Positioned}, + low::{self as vir_low}, + middle::{ + self as vir_mid, + operations::{quantifiers::BoundVariableStack, ty::Typed}, + }, +}; + +#[derive(Clone, Debug, PartialEq, Eq, derive_more::IsVariant)] +enum CallerKind { + PredicateBody { + /// The `place` parameter of the predicate. + place: vir_low::VariableDecl, + /// The `address` parameter of the predicate. + address: vir_low::VariableDecl, + }, + AssignPrecondition { + /// A map for replacing `self.field` with a matching argument. + field_replacement_map: FxHashMap, + }, + InhaleExhale, + // PlaceExpression, +} + +#[derive(Default)] +pub(in super::super::super::super::super) struct SelfFramingAssertionEncoderState { + states: BTreeMap, +} + +#[derive(Default)] +struct State { + snap_calls: Vec<(vir_mid::Expression, vir_low::Expression)>, + range_snap_calls: Vec<(vir_low::Expression, (vir_mid::Type, vir_low::Position))>, +} + +// Based on +// prusti-viper/src/encoder/middle/core_proof/predicates/owned/builders/owned_non_aliased/predicate_decl.rs, +// whch should be deleted. +pub(in super::super::super::super::super) struct SelfFramingAssertionToSnapshot { + /// Indicates which constructor was used. This is used only for assertions + /// to ensure that certain branches are unreachable. + caller_kind: CallerKind, + /// Do we need to use SSA when encoding variables? + use_ssa: bool, + /// Which kind of predicate is being encoded? + predicate_kind: PredicateKind, + /// Keeps track of types for which we need to encode predicates. + created_predicate_types: Vec, + /// Mapping from (label, place) to snapshot. We use a vector because we need to know + /// the insertion order. + snap_calls: Vec<(Option, vir_mid::Expression, vir_low::Expression)>, + /// Mapping from the start of owned range to information needed to compute + /// the snapshot of an element. We use a vector because we need to know the + /// insertion order. + range_snap_calls: Vec<(vir_low::Expression, (vir_mid::Type, vir_low::Position))>, + /// If true, removes the accessibility predicates from the result. + is_target_pure: bool, + /// The old label in the currently converted subexpression. + old_label: Option, + /// Variables introduced by quantifiers. + bound_variable_stack: BoundVariableStack, + /// The label of the current state in which information about `snap_calls` + /// and `range_snap_calls` should be stored. + /// + /// This is used only for inhale and exhale statements. + current_state_label: Option, +} + +impl SelfFramingAssertionToSnapshot { + /// Used for encoding structural invariant as a predicate body. + pub(in super::super::super::super::super) fn for_predicate_body( + place: vir_low::VariableDecl, + address: vir_low::VariableDecl, + predicate_kind: PredicateKind, + ) -> Self { + Self { + caller_kind: CallerKind::PredicateBody { place, address }, + use_ssa: false, + predicate_kind, + created_predicate_types: Vec::new(), + snap_calls: Vec::new(), + range_snap_calls: Vec::new(), + is_target_pure: false, + old_label: None, + bound_variable_stack: Default::default(), + current_state_label: None, + } + } + + /// Used for encoding structural invariant as a assign helper method + /// postcondition. + pub(in super::super::super::super::super) fn for_assign_precondition( + regular_field_arguments: Vec, + fields: Vec, + ) -> Self { + let field_replacement_map = fields + .into_iter() + .zip(regular_field_arguments.iter().cloned()) + .collect(); + Self { + caller_kind: CallerKind::AssignPrecondition { + field_replacement_map, + }, + use_ssa: false, + predicate_kind: PredicateKind::Owned, + created_predicate_types: Vec::new(), + snap_calls: Vec::new(), + range_snap_calls: Vec::new(), + is_target_pure: false, + old_label: None, + bound_variable_stack: Default::default(), + current_state_label: None, + } + } + + /// Used for encoding inhale and exhale statements. + pub(in super::super::super::super::super) fn for_inhale_exhale_expression( + current_state_label: Option, + ) -> Self { + Self { + caller_kind: CallerKind::InhaleExhale, + use_ssa: true, + predicate_kind: PredicateKind::Owned, + created_predicate_types: Vec::new(), + snap_calls: Vec::new(), + range_snap_calls: Vec::new(), + is_target_pure: false, + old_label: None, + bound_variable_stack: Default::default(), + current_state_label, + } + } + + // Use PlaceToSnapshot::for_place instead. + // /// Used for encoding place expressions in procedures. + // pub(in super::super::super::super::super) fn for_place_expression() -> Self { + // Self { + // caller_kind: CallerKind::PlaceExpression, + // use_ssa: true, + // predicate_kind: PredicateKind::Owned, + // created_predicate_types: Vec::new(), + // snap_calls: Vec::new(), + // range_snap_calls: Vec::new(), + // is_target_pure: false, + // old_label: None, + // bound_variable_stack: Default::default(), + // current_state_label: None, + // } + // } + + pub(in super::super::super::super::super) fn into_created_predicate_types( + self, + ) -> Vec { + self.created_predicate_types + } + + fn predicate_place(&self) -> vir_low::Expression { + let CallerKind::PredicateBody { ref place, .. } = self.caller_kind else { + unreachable!() + }; + place.clone().into() + } + + fn predicate_address(&self) -> vir_low::Expression { + let CallerKind::PredicateBody { ref address, .. } = self.caller_kind else { + unreachable!() + }; + address.clone().into() + } + + // // FIXME: Code duplication. + // fn pointer_deref_into_address<'p, 'v, 'tcx>( + // &mut self, + // lowerer: &mut Lowerer<'p, 'v, 'tcx>, + // place: &vir_mid::Expression, + // ) -> SpannedEncodingResult { + // if let Some(deref_place) = place.get_last_dereferenced_pointer() { + // let base_snapshot = self.expression_to_snapshot(lowerer, deref_place, true)?; + // let ty = deref_place.get_type(); + // lowerer.pointer_address(ty, base_snapshot, place.position()) + // } else { + // unreachable!() + // } + // } + + // FIXME: Code duplication. + fn snap_call<'p, 'v, 'tcx>( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + ty: &vir_mid::Type, + place: vir_low::Expression, + address: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult { + self.snap_call_with_predicate_kind( + lowerer, + ty, + self.predicate_kind.clone(), + place, + address, + position, + ) + } + + // FIXME: Code duplication. + fn snap_call_with_predicate_kind<'p, 'v, 'tcx>( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + ty: &vir_mid::Type, + predicate_kind: PredicateKind, + place: vir_low::Expression, + address: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult { + match predicate_kind { + PredicateKind::Owned => lowerer.owned_non_aliased_snap( + self.call_context(), + ty, + ty, + place, + address, + position, + ), + PredicateKind::FracRef { lifetime } => { + let TODO_target_slice_len = None; + lowerer.frac_ref_snap( + self.call_context(), + ty, + ty, + place, + address, + lifetime, + TODO_target_slice_len, + position, + ) + } + PredicateKind::UniqueRef { lifetime, is_final } => { + assert!(!is_final); + let TODO_target_slice_len = None; + lowerer.unique_ref_snap( + self.call_context(), + ty, + ty, + place, + address, + lifetime, + TODO_target_slice_len, + false, + position, + ) + } + } + } + + fn predicate<'p, 'v, 'tcx>( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + ty: &vir_mid::Type, + place: vir_low::Expression, + address: vir_low::Expression, + used_predicate_kind: PredicateKind, + position: vir_low::Position, + ) -> SpannedEncodingResult { + self.created_predicate_types.push(ty.clone()); + match used_predicate_kind { + PredicateKind::Owned => match &self.predicate_kind { + PredicateKind::Owned => lowerer.owned_non_aliased( + self.call_context(), + ty, + ty, + place, + address, + None, + position, + ), + PredicateKind::FracRef { lifetime } => { + let TODO_target_slice_len = None; + lowerer.frac_ref( + self.call_context(), + ty, + ty, + place, + address, + lifetime.clone(), + TODO_target_slice_len, + None, + position, + ) + } + PredicateKind::UniqueRef { lifetime, is_final } => { + assert!(!is_final); + let TODO_target_slice_len = None; + lowerer.unique_ref( + self.call_context(), + ty, + ty, + place, + address, + lifetime.clone(), + TODO_target_slice_len, + None, + position, + ) + } + }, + PredicateKind::FracRef { lifetime: _ } => todo!(), + PredicateKind::UniqueRef { lifetime, is_final } => match &self.predicate_kind { + PredicateKind::UniqueRef { .. } | PredicateKind::Owned => { + assert!(!is_final); + let TODO_target_slice_len = None; + lowerer.unique_ref( + self.call_context(), + ty, + ty, + place, + address, + lifetime, + TODO_target_slice_len, + None, + position, + ) + } + PredicateKind::FracRef { lifetime } => { + let TODO_target_slice_len = None; + lowerer.frac_ref( + self.call_context(), + ty, + ty, + place, + address, + lifetime.clone(), + TODO_target_slice_len, + None, + position, + ) + } + }, + } + } + + fn predicate_range<'p, 'v, 'tcx>( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + ty: &vir_mid::Type, + address: vir_low::Expression, + start_index: vir_low::Expression, + end_index: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult { + self.created_predicate_types.push(ty.clone()); + match &self.predicate_kind { + PredicateKind::Owned => lowerer.owned_aliased_range( + self.call_context(), + ty, + ty, + address, + start_index, + end_index, + None, + position, + ), + PredicateKind::UniqueRef { lifetime, is_final } => { + assert!(!is_final); + lowerer.unique_ref_range( + self.call_context(), + ty, + ty, + address, + start_index, + end_index, + lifetime.clone(), + None, + position, + ) + } + PredicateKind::FracRef { lifetime } => lowerer.frac_ref_range( + self.call_context(), + ty, + ty, + address, + start_index, + end_index, + lifetime.clone(), + None, + position, + ), + } + } + + fn maybe_store_range_snap_call<'p, 'v, 'tcx>( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + address: &vir_low::Expression, + ty: &vir_mid::Type, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + if let Some(current_state_label) = &self.current_state_label { + let entry = lowerer + .snapshots_state + .self_framing_assertion_encoder_state + .states + .entry(current_state_label.clone()) + .or_default(); + entry + .range_snap_calls + .push((address.clone(), (ty.clone(), position))); + } + Ok(()) + } + + fn get_range_snap_calls<'a, 'p, 'v, 'tcx>( + &'a self, + lowerer: &'a mut Lowerer<'p, 'v, 'tcx>, + ) -> SpannedEncodingResult<&'a [(vir_low::Expression, (vir_mid::Type, vir_low::Position))]> + { + if let Some(old_label) = &self.old_label { + let entry = lowerer + .snapshots_state + .self_framing_assertion_encoder_state + .states + .get(old_label) + .unwrap(); + Ok(&entry.range_snap_calls) + } else { + Ok(&self.range_snap_calls) + } + } + + fn maybe_store_snap_call<'p, 'v, 'tcx>( + &self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + place: &vir_mid::Expression, + snap_call: &vir_low::Expression, + ) -> SpannedEncodingResult<()> { + if let Some(current_state_label) = &self.current_state_label { + let entry = lowerer + .snapshots_state + .self_framing_assertion_encoder_state + .states + .entry(current_state_label.clone()) + .or_default(); + entry.snap_calls.push((place.clone(), snap_call.clone())); + } + Ok(()) + } +} + +impl<'p, 'v: 'p, 'tcx: 'v> IntoSnapshotLowerer<'p, 'v, 'tcx> for SelfFramingAssertionToSnapshot { + fn expression_to_snapshot( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + expression: &vir_mid::Expression, + expect_math_bool: bool, + ) -> SpannedEncodingResult { + for (label, place, call) in &self.snap_calls { + if label == &self.old_label && place == expression { + return self.ensure_bool_expression( + lowerer, + expression.get_type(), + call.clone(), + expect_math_bool, + ); + // return Ok(call.clone()); + } + } + self.expression_to_snapshot_impl(lowerer, expression, expect_math_bool) + } + + fn local_to_snapshot( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + local: &vir_mid::Local, + expect_math_bool: bool, + ) -> SpannedEncodingResult { + if let Some(label) = &self.old_label { + for (_snap_label, place, call) in &self.snap_calls { + // FIXME: snap_label should probably used here somehow. + if let vir_mid::Expression::LabelledOld(vir_mid::LabelledOld { + label: old_label, + base: box vir_mid::Expression::Local(predicate_local), + .. + }) = place + { + if old_label == label && predicate_local == local { + return self.ensure_bool_expression( + lowerer, + local.get_type(), + call.clone(), + expect_math_bool, + ); + } + } + } + } + self.local_to_snapshot_impl(lowerer, local, expect_math_bool) + } + + fn binary_op_to_snapshot( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + op: &vir_mid::BinaryOp, + expect_math_bool: bool, + ) -> SpannedEncodingResult { + // let mut introduced_snap = false; + // let mut introduced_range_snap = false; + // if op.op_kind == vir_mid::BinaryOpKind::And { + // if let box vir_mid::Expression::AccPredicate(expression) = &op.left { + // if expression.predicate.is_owned_non_aliased() { + // // The recursive call to `acc_predicate_to_snapshot` will + // // add a snap call to `self.snap_calls`. + // introduced_snap = true; + // } + // if expression.predicate.is_owned_range() { + // // The recursive call to `acc_predicate_to_snapshot` will + // // add a snap call to `self.range_snap_calls`. + // introduced_range_snap = true; + // } + // } + // } + let snap_call_count = self.snap_calls.len(); + let snap_range_call_count = self.range_snap_calls.len(); + let expression = self.binary_op_to_snapshot_impl(lowerer, op, expect_math_bool)?; + if op.op_kind == vir_mid::BinaryOpKind::Implies { + // The predicates were introduced conditionally and, therefore, + // frame only the right hand side of the implication. + while self.snap_calls.len() > snap_call_count { + self.snap_calls.pop(); + } + while self.range_snap_calls.len() > snap_range_call_count { + self.range_snap_calls.pop(); + } + } + // if introduced_snap { + // self.snap_calls.pop(); + // } + // if introduced_range_snap { + // let predicate = self.range_snap_calls.pop(); + // eprintln!("pop: {:?}", predicate); + // } + Ok(expression) + } + + fn field_to_snapshot( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + field: &vir_mid::Field, + expect_math_bool: bool, + ) -> SpannedEncodingResult { + match &*field.base { + vir_mid::Expression::Local(local) if self.caller_kind.is_predicate_body() => { + assert!(local.variable.is_self_variable()); + let field_place = lowerer.encode_field_place( + &local.variable.ty, + &field.field, + self.predicate_place(), + field.position, + )?; + let field_address = lowerer.encode_field_address( + &local.variable.ty, + &field.field, + self.predicate_address(), + field.position, + )?; + self.snap_call( + lowerer, + &field.field.ty, + field_place, + field_address, + local.position, + ) + } + vir_mid::Expression::Local(local) if self.caller_kind.is_assign_precondition() => { + // FIXME: these assertions may be wrong. + assert!(local.variable.is_self_variable()); + let CallerKind::AssignPrecondition { ref field_replacement_map, .. } = self.caller_kind else { + unreachable!() + }; + assert!(field_replacement_map.contains_key(&field.field)); + Ok(field_replacement_map[&field.field].clone()) + } + _ => self.field_to_snapshot_impl(lowerer, field, expect_math_bool), + } + } + + fn variable_to_snapshot( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + variable: &vir_mid::VariableDecl, + ) -> SpannedEncodingResult { + assert!( + !self.caller_kind.is_assign_precondition(), + "all variables should be replaced by arguments; got: {variable}" + ); + assert!( + !self.caller_kind.is_predicate_body() || variable.is_self_variable(), + "{variable} must be self" + ); + if self.use_ssa && !self.bound_variable_stack.contains(variable) { + if matches!( + variable.ty, + vir_mid::Type::MBool + | vir_mid::Type::MInt + | vir_mid::Type::MFloat32 + | vir_mid::Type::MFloat64 + | vir_mid::Type::MPerm + | vir_mid::Type::MByte + | vir_mid::Type::MBytes + | vir_mid::Type::Lifetime + | vir_mid::Type::Int(vir_mid::ty::Int::Unbounded) + ) { + if let Some(label) = &self.old_label { + lowerer.snapshot_variable_version_at_label(variable, label) + } else { + lowerer.current_snapshot_variable_version(variable) + } + } else if let Some(label) = &self.old_label { + unreachable!("Should be covered by eval_in: {variable} in {label}"); + } else { + unreachable!("Should be covered by eval_in: {variable}"); + } + } else { + Ok(vir_low::VariableDecl { + name: variable.name.clone(), + ty: self.type_to_snapshot(lowerer, &variable.ty)?, + }) + } + } + + fn labelled_old_to_snapshot( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + old: &vir_mid::LabelledOld, + expect_math_bool: bool, + ) -> SpannedEncodingResult { + let parent_old_label = std::mem::replace(&mut self.old_label, Some(old.label.clone())); + let result = self.expression_to_snapshot(lowerer, &old.base, expect_math_bool)?; + self.old_label = parent_old_label; + Ok(vir_low::Expression::labelled_old( + Some(old.label.clone()), + result, + old.position, + )) + } + + fn func_app_to_snapshot( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + app: &vir_mid::FuncApp, + expect_math_bool: bool, + ) -> SpannedEncodingResult { + let arguments = + self.expression_vec_to_snapshot(lowerer, &app.arguments, expect_math_bool)?; + let return_type = self.type_to_snapshot(lowerer, &app.return_type)?; + let func_app = lowerer.call_pure_function_in_procedure_context( + app.get_identifier(), + arguments, + return_type, + app.position, + )?; + let result = vir_low::Expression::FuncApp(func_app); + self.ensure_bool_expression(lowerer, &app.return_type, result, expect_math_bool) + } + + fn acc_predicate_to_snapshot( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + acc_predicate: &vir_mid::AccPredicate, + expect_math_bool: bool, + ) -> SpannedEncodingResult { + assert!(expect_math_bool); + assert!( + lowerer + .check_mode + .unwrap() + .supports_accessibility_predicates_in_assertions() + || matches!(self.caller_kind, CallerKind::PredicateBody { .. }) + ); + // assert_ne!(self.caller_kind, CallerKind::PlaceExpression, "unimplemented: report a proper error message"); + let expression = match &*acc_predicate.predicate { + vir_mid::Predicate::OwnedNonAliased(predicate) => { + let ty = predicate.place.get_type(); + let place = lowerer.encode_expression_as_place(&predicate.place)?; + let address = self.pointer_deref_into_address(lowerer, &predicate.place)?; + let acc = self.predicate( + lowerer, + ty, + place.clone(), + address.clone(), + PredicateKind::Owned, + predicate.position, + )?; + let snap_call = + self.snap_call(lowerer, ty, place, address, predicate.place.position())?; + self.maybe_store_snap_call(lowerer, &predicate.place, &snap_call)?; + self.snap_calls + .push((None, predicate.place.clone(), snap_call)); + acc + } + vir_mid::Predicate::OwnedRange(predicate) => { + let ty = predicate.address.get_type(); + let address = self.expression_to_snapshot(lowerer, &predicate.address, true)?; + let start_index = + self.expression_to_snapshot(lowerer, &predicate.start_index, true)?; + let end_index = self.expression_to_snapshot(lowerer, &predicate.end_index, true)?; + self.range_snap_calls + .push((address.clone(), (ty.clone(), predicate.position))); + self.maybe_store_range_snap_call(lowerer, &address, ty, predicate.position)?; + let vir_mid::Type::Pointer(pointer_type) = ty else { + unreachable!(); + }; + self.created_predicate_types + .push((*pointer_type.target_type).clone()); + self.predicate_range( + lowerer, + ty, + address, + start_index, + end_index, + predicate.position, + )? + // lowerer.owned_aliased_range( + // self.call_context(), + // ty, + // ty, + // address, + // start_index, + // end_index, + // None, + // predicate.position, + // )? + } + vir_mid::Predicate::UniqueRef(predicate) => { + let lifetime = + self.encode_lifetime_in_self_context(lowerer, predicate.lifetime.clone())?; + let ty = predicate.place.get_type(); + let place = lowerer.encode_expression_as_place(&predicate.place)?; + let address = self.pointer_deref_into_address(lowerer, &predicate.place)?; + let acc = self.predicate( + lowerer, + ty, + place.clone(), + address.clone(), + PredicateKind::UniqueRef { + lifetime, + is_final: false, + }, + predicate.position, + )?; + // self.predicate_kind = old_predicate_kind; + let snap_call = + self.snap_call(lowerer, ty, place, address, predicate.place.position())?; + self.maybe_store_snap_call(lowerer, &predicate.place, &snap_call)?; + self.snap_calls + .push((None, predicate.place.clone(), snap_call)); + acc + } + vir_mid::Predicate::UniqueRefRange(predicate) => { + let ty = predicate.address.get_type(); + let _lifetime = + self.encode_lifetime_in_self_context(lowerer, predicate.lifetime.clone())?; + let address = self.expression_to_snapshot(lowerer, &predicate.address, true)?; + let _start_index = + self.expression_to_snapshot(lowerer, &predicate.start_index, true)?; + let _end_index = + self.expression_to_snapshot(lowerer, &predicate.end_index, true)?; + self.range_snap_calls + .push((address.clone(), (ty.clone(), predicate.position))); + self.maybe_store_range_snap_call(lowerer, &address, ty, predicate.position)?; + let vir_mid::Type::Pointer(pointer_type) = ty else { + unreachable!(); + }; + self.created_predicate_types + .push((*pointer_type.target_type).clone()); + unimplemented!(); + + // self.predicate_range( + // lowerer, + // ty, + // address, + // start_index, + // end_index, + // predicate.position, + // )? + } + vir_mid::Predicate::MemoryBlockHeap(predicate) => { + match self.predicate_kind { + PredicateKind::Owned => { + let address = + self.pointer_deref_into_address(lowerer, &predicate.address)?; + // let place = lowerer.encode_expression_as_place(&predicate.address)?; + // let address = + // self.pointer_deref_into_address(lowerer, &predicate.address)?; + // use vir_low::macros::*; + // let compute_address = ty!(Address); + // let address = expr! { + // ComputeAddress::compute_address([place], [address]) + // }; + let size = self.expression_to_snapshot( + lowerer, + &predicate.size, + expect_math_bool, + )?; + lowerer.encode_memory_block_acc(address, size, acc_predicate.position)? + } + PredicateKind::FracRef { .. } | PredicateKind::UniqueRef { .. } => { + // Memory blocks are not accessible in frac/unique ref predicates. + true.into() + } + } + } + vir_mid::Predicate::MemoryBlockHeapDrop(predicate) => { + match self.predicate_kind { + PredicateKind::Owned => { + // FIXME: Why this does not match the encoding of MemoryBlockHeap? + let address = + self.pointer_deref_into_address(lowerer, &predicate.address)?; + let size = self.expression_to_snapshot( + lowerer, + &predicate.size, + expect_math_bool, + )?; + lowerer.encode_memory_block_heap_drop_acc( + address, + size, + acc_predicate.position, + )? + } + PredicateKind::FracRef { .. } | PredicateKind::UniqueRef { .. } => { + // Memory blocks are not accessible in frac/unique ref predicates. + true.into() + } + } + } + vir_mid::Predicate::MemoryBlockHeapRange(predicate) => { + let pointer_value = + self.expression_to_snapshot(lowerer, &predicate.address, true)?; + let address = lowerer.pointer_address( + predicate.address.get_type(), + pointer_value, + predicate.position, + )?; + let size = self.expression_to_snapshot(lowerer, &predicate.size, true)?; + let start_index = + self.expression_to_snapshot(lowerer, &predicate.start_index, true)?; + let end_index = self.expression_to_snapshot(lowerer, &predicate.end_index, true)?; + lowerer.encode_memory_block_range_acc( + address, + size, + start_index, + end_index, + acc_predicate.position, + )? + } + vir_mid::Predicate::MemoryBlockHeapRangeGuarded(predicate) => { + let pointer_value = + self.expression_to_snapshot(lowerer, &predicate.address, true)?; + let address = lowerer.pointer_address( + predicate.address.get_type(), + pointer_value, + predicate.position, + )?; + let size = self.expression_to_snapshot(lowerer, &predicate.size, true)?; + self.bound_variable_stack + .push_single(&predicate.index_variable); + let index_variable = + self.variable_to_snapshot(lowerer, &predicate.index_variable)?; + let guard = self.expression_to_snapshot(lowerer, &predicate.guard, true)?; + assert_eq!( + predicate.triggers.len(), + 0, + "Triggers are currently not supported" + ); + let expression = lowerer.encode_memory_block_range_guarded_acc( + address, + size, + index_variable, + guard, + acc_predicate.position, + )?; + self.bound_variable_stack.pop(); + expression + } + _ => unimplemented!("{acc_predicate}"), + }; + if self.is_target_pure { + Ok(true.into()) + } else { + Ok(expression) + } + } + + fn deref_own( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + pointer_type: &vir_mid::Type, + pointer: vir_low::Expression, + index: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult { + // FIXME: improve error reporting by showing which permission was used + // by linking to predicate_position. + let pointer = pointer.remove_unnecessary_old(); + let comparison_pointer = if let Some(old_label) = &self.old_label { + pointer.clone().remove_old_label(old_label) + } else { + pointer.clone() + }; + let Some((_, (_todo_remove_ty, _predicate_position))) = self.get_range_snap_calls(lowerer)?.iter().find(|(range_pointer, _)| { + range_pointer == &comparison_pointer + }) else { + unimplemented!("Report a proper error message about not syntactically framed deref_own: {pointer}"); + }; + // let address = lowerer.pointer_address( + // pointer_type, + // pointer, + // position, + // )?; + let vir_mid::Type::Pointer(ty) = pointer_type else { + unreachable!() + }; + let size_type = lowerer.size_type_mid()?; + let index_int = lowerer.obtain_constant_value(&size_type, index, position)?; + let element_address = + lowerer.encode_index_address(pointer_type, pointer, index_int, position)?; + let result = lowerer.owned_aliased_snap( + self.call_context(), + &ty.target_type, + &*ty.target_type, + element_address, + position, + )?; + Ok(result) + } + + // FIXME: Code duplication. + fn pointer_deref_to_snapshot( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + deref: &vir_mid::Deref, + _base_snapshot: vir_low::Expression, + _expect_math_bool: bool, + ) -> SpannedEncodingResult { + let span = lowerer + .encoder + .error_manager() + .position_manager() + .get_span(deref.position.into()) + .cloned() + .unwrap(); + Err(SpannedEncodingError::incorrect( + "the place must be syntactically framed by permissions", + span, + )) + // TODO: outdated code, delete. Return true for now because we expect + // the result to not be used. + // unimplemented!("pointer_deref_to_snapshot: {deref} {base_snapshot}"); + // Ok(true.into()) + // let heap = self + // .unsafe_cell_values + // .clone() + // .expect("This function should be reachable only when heap is Some"); + // lowerer.pointer_target_snapshot_in_heap( + // deref.base.get_type(), + // heap, + // base_snapshot, + // deref.position, + // ) + } + + fn unfolding_to_snapshot( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + unfolding: &vir_mid::Unfolding, + expect_math_bool: bool, + ) -> SpannedEncodingResult { + // FIXME: Replace all unfolding expressions with snap function calls. + // Currently, we just ignore all unfolding expressions. + self.expression_to_snapshot(lowerer, &unfolding.body, expect_math_bool) + } + + fn eval_in_to_snapshot( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + eval_in: &vir_mid::EvalIn, + expect_math_bool: bool, + body_to_snapshot: F, + ) -> SpannedEncodingResult + where + F: FnOnce( + &mut Self, + &mut Lowerer<'p, 'v, 'tcx>, + &vir_mid::Expression, + bool, + ) -> SpannedEncodingResult, + { + if eval_in.context_kind == vir_mid::EvalInContextKind::SafeConstructor { + let ty = eval_in.context.get_type(); + let type_decl = lowerer.encoder.get_type_decl_mid(ty)?; + let place = &*eval_in.context; + let position = eval_in.position; + let constructor_call = match &type_decl { + vir_mid::TypeDecl::Reference(decl) => { + match decl.uniqueness { + vir_mid::ty::Uniqueness::Unique => { + // FIXME: This is currently not implemented since we + // just hope that the specification needs only the + // deref of the reference than the actual reference + // itself. + true.into() + } + vir_mid::ty::Uniqueness::Shared => todo!(), + } + } + vir_mid::TypeDecl::Struct(decl) => { + assert!(decl.structural_invariant.is_none(), "report a proper error message that structs with invariants cannot be automatically folded"); + let mut arguments = Vec::new(); + for field in &decl.fields { + let field_place = vir_mid::Expression::field( + place.clone(), + field.clone(), + eval_in.position, + ); + arguments.push(field_place); + } + let constructor_call = + vir_mid::Expression::constructor(ty.clone(), arguments, position); + self.expression_to_snapshot(lowerer, &constructor_call, false)? + } + _ => unimplemented!("{type_decl}"), + }; + self.snap_calls + .push((None, place.clone(), constructor_call)); + let result = body_to_snapshot(self, lowerer, &eval_in.body, expect_math_bool)?; + self.snap_calls.pop(); + return Ok(result); + } + // let box vir_mid::Expression::AccPredicate(predicate) = &eval_in.context else { + // unimplemented!("A proper error message that this must be a predicate: {}", eval_in.context); + // }; + let (predicate, old_label) = match &*eval_in.context { + vir_mid::Expression::AccPredicate(predicate) => (predicate, self.old_label.clone()), + vir_mid::Expression::LabelledOld(vir_mid::LabelledOld { + label, + base: box vir_mid::Expression::AccPredicate(predicate), + .. + }) => (predicate, Some(label.clone())), + _ => unimplemented!( + "A proper error message that this must be a predicate: {}", + eval_in.context + ), + }; + let result = match &*predicate.predicate { + vir_mid::Predicate::OwnedNonAliased(predicate) => { + let predicate_place = &predicate.place; + // let (predicate_place, old_label) = + // if let vir_mid::Expression::LabelledOld(vir_mid::LabelledOld { + // label, + // base, + // .. + // }) = &predicate.place + // { + // // assert!(matches!( + // // eval_in.context_kind, + // // vir_mid::EvalInContextKind::Old + // // | vir_mid::EvalInContextKind::OldOpenedRefPredicate + // // )); + // (&**base, Some(label)) + // } else { + // // assert!(matches!( + // // eval_in.context_kind, + // // vir_mid::EvalInContextKind::Predicate + // // | vir_mid::EvalInContextKind::QuantifiedPredicate + // // | vir_mid::EvalInContextKind::OpenedRefPredicate + // // )); + // (&predicate.place, None) + // }; + let ty = predicate.place.get_type(); + let place = lowerer.encode_expression_as_place(predicate_place)?; + let address = if predicate_place.is_behind_pointer_dereference() { + // assert!(old_label.is_none(), "unimplemented: {predicate}"); + self.pointer_deref_into_address(lowerer, predicate_place)? + } else { + lowerer.encode_expression_as_place_address(predicate_place)? + }; + let mut predicate_kind = if predicate_place.is_place() { + // FIXME: We currently incorrectly assume that if a + // predicate place is not a place, when it is a raw + // pointer to owned and not a referenced location. + if let Some((lifetime, uniqueness)) = predicate_place.get_dereference_kind() { + let lifetime = lowerer + .encode_lifetime_const_into_procedure_variable(lifetime)? + .into(); + match uniqueness { + vir_mid::ty::Uniqueness::Unique => PredicateKind::UniqueRef { + lifetime, + is_final: false, + }, + vir_mid::ty::Uniqueness::Shared => PredicateKind::FracRef { lifetime }, + } + } else { + PredicateKind::Owned + } + } else { + PredicateKind::Owned + }; + if matches!( + eval_in.context_kind, + vir_mid::EvalInContextKind::OpenedRefPredicate // | vir_mid::EvalInContextKind::OldOpenedRefPredicate + ) { + predicate_kind = PredicateKind::Owned; + } + let mut snap_call = self.snap_call_with_predicate_kind( + lowerer, + ty, + predicate_kind, + place, + address, + predicate.place.position(), + )?; + if matches!( + eval_in.context_kind, + vir_mid::EvalInContextKind::QuantifiedPredicate + ) { + let vir_low::Expression::FuncApp(func_app) = &mut snap_call else { + unreachable!("snap_call must be a FuncApp: {snap_call}"); + }; + // Mark that this snapshot function call should not be + // purified. + func_app.context = vir_low::FuncAppContext::QuantifiedPermission; + } + if let Some(old_label) = &old_label { + snap_call = vir_low::Expression::labelled_old( + Some(old_label.to_string()), + snap_call, + predicate.place.position(), + ) + } + self.snap_calls + .push((old_label, predicate.place.clone(), snap_call)); + let result = body_to_snapshot(self, lowerer, &eval_in.body, expect_math_bool)?; + self.snap_calls.pop(); + result + } + vir_mid::Predicate::OwnedRange(predicate) => { + assert!(old_label.is_none(), "unimplemented: {predicate}"); + let ty = predicate.address.get_type(); + let address = self.expression_to_snapshot(lowerer, &predicate.address, true)?; + self.range_snap_calls + .push((address, (ty.clone(), predicate.position))); + let result = body_to_snapshot(self, lowerer, &eval_in.body, expect_math_bool)?; + self.range_snap_calls.pop(); + result + } + _ => unimplemented!( + "A proper error message that this must be an owned predicate: {predicate}" + ), + }; + Ok(result) + } + + fn call_context(&self) -> CallContext { + match self.caller_kind { + CallerKind::PredicateBody { .. } | CallerKind::AssignPrecondition { .. } => { + CallContext::BuiltinMethod + } + CallerKind::InhaleExhale => CallContext::Procedure, + } + } + + fn owned_non_aliased_snap( + &mut self, + _lowerer: &mut Lowerer<'p, 'v, 'tcx>, + _ty: &vir_mid::Type, + _pointer_place: &vir_mid::Expression, + ) -> SpannedEncodingResult { + todo!() + } + + fn push_bound_variables( + &mut self, + variables: &[vir_mid::VariableDecl], + ) -> SpannedEncodingResult<()> { + self.bound_variable_stack.push(variables); + Ok(()) + } + + fn pop_bound_variables(&mut self) -> SpannedEncodingResult<()> { + self.bound_variable_stack.pop(); + Ok(()) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/assertions/snap.rs b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/assertions/snap.rs new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/assertions/snap.rs @@ -0,0 +1 @@ + diff --git a/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/assertions/validity.rs b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/assertions/validity.rs new file mode 100644 index 00000000000..3aa5f961d92 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/assertions/validity.rs @@ -0,0 +1,196 @@ +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::{ + builtin_methods::CallContext, + footprint::{DerefFields, DerefOwned, DerefOwnedRange}, + lowerer::Lowerer, + snapshots::{IntoSnapshotLowerer, SnapshotValidityInterface}, + }, +}; +use std::collections::BTreeMap; +use vir_crate::{ + low::{self as vir_low}, + middle::{self as vir_mid, operations::ty::Typed}, +}; + +pub(in super::super::super::super::super) struct ValidityAssertionToSnapshot { + framed_places: Vec, + deref_fields: BTreeMap, + framed_range_addresses: Vec, + deref_range_fields: BTreeMap, +} + +impl ValidityAssertionToSnapshot { + pub(in super::super::super::super) fn new( + (deref_fields, deref_range_fields): DerefFields, + ) -> Self { + Self { + framed_places: Vec::new(), + deref_fields: deref_fields + .into_iter() + .map( + |DerefOwned { + place, + field_name, + field_type, + }| { + (place, vir_low::VariableDecl::new(field_name, field_type)) + }, + ) + .collect(), + framed_range_addresses: Vec::new(), + deref_range_fields: deref_range_fields + .into_iter() + .map( + |DerefOwnedRange { + address, + field_name, + field_type, + }| { + (address, vir_low::VariableDecl::new(field_name, field_type)) + }, + ) + .collect(), + } + } +} + +impl<'p, 'v: 'p, 'tcx: 'v> IntoSnapshotLowerer<'p, 'v, 'tcx> for ValidityAssertionToSnapshot { + fn expression_to_snapshot( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + expression: &vir_mid::Expression, + expect_math_bool: bool, + ) -> SpannedEncodingResult { + if let Some(field) = self.deref_fields.get(expression) { + // for framed_place in &self.framed_places { + // eprintln!("Framed: {framed_place}"); + // } + assert!( + self.framed_places.contains(expression), + "The place {expression} must be framed" + ); + Ok(field.clone().into()) + } else { + self.expression_to_snapshot_impl(lowerer, expression, expect_math_bool) + } + } + + fn variable_to_snapshot( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + variable: &vir_mid::VariableDecl, + ) -> SpannedEncodingResult { + assert!(variable.is_self_variable(), "{variable} must be self"); + Ok(vir_low::VariableDecl { + name: variable.name.clone(), + ty: self.type_to_snapshot(lowerer, &variable.ty)?, + }) + } + + fn labelled_old_to_snapshot( + &mut self, + _lowerer: &mut Lowerer<'p, 'v, 'tcx>, + _old: &vir_mid::LabelledOld, + _expect_math_bool: bool, + ) -> SpannedEncodingResult { + todo!() + } + + fn func_app_to_snapshot( + &mut self, + _lowerer: &mut Lowerer<'p, 'v, 'tcx>, + _app: &vir_mid::FuncApp, + _expect_math_bool: bool, + ) -> SpannedEncodingResult { + todo!() + } + + fn binary_op_to_snapshot( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + op: &vir_mid::BinaryOp, + expect_math_bool: bool, + ) -> SpannedEncodingResult { + let mut introduced_snap = false; + if op.op_kind == vir_mid::BinaryOpKind::And { + if let box vir_mid::Expression::AccPredicate(expression) = &op.left { + if let vir_mid::Predicate::OwnedNonAliased(predicate) = &*expression.predicate { + self.framed_places.push(predicate.place.clone()); + introduced_snap = true; + } + } + } + let expression = self.binary_op_to_snapshot_impl(lowerer, op, expect_math_bool)?; + if introduced_snap { + self.framed_places.pop(); + } + Ok(expression) + } + + fn acc_predicate_to_snapshot( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + acc_predicate: &vir_mid::AccPredicate, + expect_math_bool: bool, + ) -> SpannedEncodingResult { + assert!(expect_math_bool); + let expression = match &*acc_predicate.predicate { + vir_mid::Predicate::OwnedNonAliased(predicate) => { + self.framed_places.push(predicate.place.clone()); + let place = self.expression_to_snapshot(lowerer, &predicate.place, false)?; + self.framed_places.pop(); + lowerer.encode_snapshot_valid_call_for_type(place, predicate.place.get_type())? + } + vir_mid::Predicate::OwnedRange(predicate) => { + self.framed_range_addresses.push(predicate.address.clone()); + let address = self.expression_to_snapshot(lowerer, &predicate.address, false)?; + self.framed_range_addresses.pop(); + lowerer + .encode_snapshot_valid_call_for_type(address, predicate.address.get_type())? + } + vir_mid::Predicate::UniqueRef(predicate) => { + self.framed_places.push(predicate.place.clone()); + let place = self.expression_to_snapshot(lowerer, &predicate.place, false)?; + self.framed_places.pop(); + lowerer.encode_snapshot_valid_call_for_type(place, predicate.place.get_type())? + } + vir_mid::Predicate::UniqueRefRange(predicate) => { + self.framed_range_addresses.push(predicate.address.clone()); + let address = self.expression_to_snapshot(lowerer, &predicate.address, false)?; + self.framed_range_addresses.pop(); + lowerer + .encode_snapshot_valid_call_for_type(address, predicate.address.get_type())? + } + vir_mid::Predicate::MemoryBlockHeap(_) + | vir_mid::Predicate::MemoryBlockHeapRange(_) + | vir_mid::Predicate::MemoryBlockHeapDrop(_) => true.into(), + _ => unimplemented!("{acc_predicate}"), + }; + Ok(expression) + } + + fn call_context(&self) -> CallContext { + todo!() + } + + fn owned_non_aliased_snap( + &mut self, + _lowerer: &mut Lowerer<'p, 'v, 'tcx>, + _ty: &vir_mid::Type, + _pointer_place: &vir_mid::Expression, + ) -> SpannedEncodingResult { + todo!() + } + + fn push_bound_variables( + &mut self, + _variables: &[vir_mid::VariableDecl], + ) -> SpannedEncodingResult<()> { + todo!() + } + + fn pop_bound_variables(&mut self) -> SpannedEncodingResult<()> { + todo!() + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/builtin_methods/mod.rs b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/builtin_methods/mod.rs index 21ff7d3ab21..e7a4513f16c 100644 --- a/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/builtin_methods/mod.rs +++ b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/builtin_methods/mod.rs @@ -1,7 +1,10 @@ use super::common::IntoSnapshotLowerer; use crate::encoder::{ errors::SpannedEncodingResult, - middle::core_proof::lowerer::{FunctionsLowererInterface, Lowerer}, + middle::core_proof::{ + builtin_methods::CallContext, + lowerer::{FunctionsLowererInterface, Lowerer}, + }, }; use vir_crate::{ common::identifier::WithIdentifier, @@ -56,4 +59,46 @@ impl<'p, 'v: 'p, 'tcx: 'v> IntoSnapshotLowerer<'p, 'v, 'tcx> for BuiltinMethodSn // In pure contexts values cannot be mutated, so `old` has no effect. self.expression_to_snapshot(lowerer, &old.base, expect_math_bool) } + + fn acc_predicate_to_snapshot( + &mut self, + _lowerer: &mut Lowerer<'p, 'v, 'tcx>, + _predicate: &vir_mid::AccPredicate, + _expect_math_bool: bool, + ) -> SpannedEncodingResult { + unreachable!() + } + + fn owned_non_aliased_snap( + &mut self, + _lowerer: &mut Lowerer<'p, 'v, 'tcx>, + _ty: &vir_mid::Type, + _pointer_snapshot: &vir_mid::Expression, + ) -> SpannedEncodingResult { + unimplemented!() + } + + fn call_context(&self) -> CallContext { + CallContext::BuiltinMethod + } + + fn push_bound_variables( + &mut self, + _variables: &[vir_mid::VariableDecl], + ) -> SpannedEncodingResult<()> { + todo!() + } + + fn pop_bound_variables(&mut self) -> SpannedEncodingResult<()> { + todo!() + } + + // fn unfolding_to_snapshot( + // &mut self, + // lowerer: &mut Lowerer<'p, 'v, 'tcx>, + // unfolding: &vir_mid::Unfolding, + // expect_math_bool: bool, + // ) -> SpannedEncodingResult { + // todo!() + // } } diff --git a/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/common/mod.rs b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/common/mod.rs index f88ff2b7ac6..ef1fe2e62e3 100644 --- a/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/common/mod.rs +++ b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/common/mod.rs @@ -3,20 +3,40 @@ use crate::encoder::{ errors::SpannedEncodingResult, high::types::HighTypeEncoderInterface, middle::core_proof::{ + addresses::AddressesInterface, + arithmetic_wrappers::ArithmeticWrappersInterface, + builtin_methods::CallContext, + casts::CastsInterface, lifetimes::*, lowerer::DomainsLowererInterface, + places::PlacesInterface, + pointers::PointersInterface, + predicates::{PredicatesMemoryBlockInterface, PredicatesOwnedInterface}, references::ReferencesInterface, - snapshots::{IntoSnapshot, SnapshotDomainsInterface, SnapshotValuesInterface}, + snapshots::{ + IntoSnapshot, SnapshotDomainsInterface, SnapshotValidityInterface, + SnapshotValuesInterface, + }, + type_layouts::TypeLayoutsInterface, types::TypesInterface, }, }; +use prusti_common::config; use vir_crate::{ - common::{identifier::WithIdentifier, position::Positioned}, + common::{ + builtin_constants::LIFETIME_DOMAIN_NAME, + expression::{BinaryOperationHelpers, ExpressionIterator}, + identifier::WithIdentifier, + position::Positioned, + validator::Validator, + }, low::{self as vir_low}, middle::{self as vir_mid, operations::ty::Typed}, }; -pub(super) trait IntoSnapshotLowerer<'p, 'v: 'p, 'tcx: 'v> { +pub(in super::super::super) trait IntoSnapshotLowerer<'p, 'v: 'p, 'tcx: 'v>: + Sized +{ fn expression_vec_to_snapshot( &mut self, lowerer: &mut Lowerer<'p, 'v, 'tcx>, @@ -38,6 +58,15 @@ pub(super) trait IntoSnapshotLowerer<'p, 'v: 'p, 'tcx: 'v> { lowerer: &mut Lowerer<'p, 'v, 'tcx>, expression: &vir_mid::Expression, expect_math_bool: bool, + ) -> SpannedEncodingResult { + self.expression_to_snapshot_impl(lowerer, expression, expect_math_bool) + } + + fn expression_to_snapshot_impl( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + expression: &vir_mid::Expression, + expect_math_bool: bool, ) -> SpannedEncodingResult { match expression { vir_mid::Expression::Local(expression) => { @@ -55,6 +84,9 @@ pub(super) trait IntoSnapshotLowerer<'p, 'v: 'p, 'tcx: 'v> { vir_mid::Expression::Deref(expression) => { self.deref_to_snapshot(lowerer, expression, expect_math_bool) } + vir_mid::Expression::Final(expression) => { + self.final_to_snapshot(lowerer, expression, expect_math_bool) + } vir_mid::Expression::AddrOf(expression) => { self.addr_of_to_snapshot(lowerer, expression, expect_math_bool) } @@ -75,7 +107,9 @@ pub(super) trait IntoSnapshotLowerer<'p, 'v: 'p, 'tcx: 'v> { vir_mid::Expression::Conditional(expression) => { self.conditional_to_snapshot(lowerer, expression, expect_math_bool) } - // vir_mid::Expression::Quantifier(expression) => self.quantifier_to_snapshot(lowerer, expression, expect_math_bool), + vir_mid::Expression::Quantifier(expression) => { + self.quantifier_to_snapshot(lowerer, expression, expect_math_bool) + } // vir_mid::Expression::LetExpr(expression) => self.letexpr_to_snapshot(lowerer, expression, expect_math_bool), vir_mid::Expression::FuncApp(expression) => { self.func_app_to_snapshot(lowerer, expression, expect_math_bool) @@ -84,6 +118,18 @@ pub(super) trait IntoSnapshotLowerer<'p, 'v: 'p, 'tcx: 'v> { self.builtin_func_app_to_snapshot(lowerer, expression, expect_math_bool) } // vir_mid::Expression::Downcast(expression) => self.downcast_to_snapshot(lowerer, expression, expect_math_bool), + vir_mid::Expression::AccPredicate(expression) => { + self.acc_predicate_to_snapshot(lowerer, expression, expect_math_bool) + } + vir_mid::Expression::Unfolding(expression) => { + self.unfolding_to_snapshot(lowerer, expression, expect_math_bool) + } + vir_mid::Expression::EvalIn(expression) => self.eval_in_to_snapshot( + lowerer, + expression, + expect_math_bool, + Self::expression_to_snapshot, + ), x => unimplemented!("{:?}", x), } } @@ -106,7 +152,7 @@ pub(super) trait IntoSnapshotLowerer<'p, 'v: 'p, 'tcx: 'v> { fn variable_to_snapshot( &mut self, lowerer: &mut Lowerer<'p, 'v, 'tcx>, - local: &vir_mid::VariableDecl, + variable: &vir_mid::VariableDecl, ) -> SpannedEncodingResult; fn local_to_snapshot( @@ -114,6 +160,15 @@ pub(super) trait IntoSnapshotLowerer<'p, 'v: 'p, 'tcx: 'v> { lowerer: &mut Lowerer<'p, 'v, 'tcx>, local: &vir_mid::Local, expect_math_bool: bool, + ) -> SpannedEncodingResult { + self.local_to_snapshot_impl(lowerer, local, expect_math_bool) + } + + fn local_to_snapshot_impl( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + local: &vir_mid::Local, + expect_math_bool: bool, ) -> SpannedEncodingResult { let snapshot_variable = self.variable_to_snapshot(lowerer, &local.variable)?; let result = vir_low::Expression::local(snapshot_variable, local.position); @@ -131,7 +186,21 @@ pub(super) trait IntoSnapshotLowerer<'p, 'v: 'p, 'tcx: 'v> { for argument in &constructor.arguments { arguments.push(self.expression_to_snapshot(lowerer, argument, false)?); } - lowerer.construct_struct_snapshot(&constructor.ty, arguments, constructor.position) + let struct_snapshot = + lowerer.construct_struct_snapshot(&constructor.ty, arguments, constructor.position)?; + if let vir_mid::Type::Enum(vir_mid::ty::Enum { + variant: Some(_), .. + }) = &constructor.ty + { + let enum_snapshot = lowerer.construct_enum_snapshot( + &constructor.ty, + struct_snapshot, + constructor.position, + )?; + Ok(enum_snapshot) + } else { + Ok(struct_snapshot) + } } fn variant_to_snapshot( @@ -155,6 +224,15 @@ pub(super) trait IntoSnapshotLowerer<'p, 'v: 'p, 'tcx: 'v> { lowerer: &mut Lowerer<'p, 'v, 'tcx>, field: &vir_mid::Field, expect_math_bool: bool, + ) -> SpannedEncodingResult { + self.field_to_snapshot_impl(lowerer, field, expect_math_bool) + } + + fn field_to_snapshot_impl( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + field: &vir_mid::Field, + expect_math_bool: bool, ) -> SpannedEncodingResult { let base_snapshot = self.expression_to_snapshot(lowerer, &field.base, expect_math_bool)?; let result = if field.field.is_discriminant() { @@ -169,6 +247,8 @@ pub(super) trait IntoSnapshotLowerer<'p, 'v: 'p, 'tcx: 'v> { discriminant_call, field.position, )? + } else if field.field.is_address() { + lowerer.pointer_address(&field.field.ty, base_snapshot, field.position)? } else { lowerer.obtain_struct_field_snapshot( field.base.get_type(), @@ -187,14 +267,41 @@ pub(super) trait IntoSnapshotLowerer<'p, 'v: 'p, 'tcx: 'v> { expect_math_bool: bool, ) -> SpannedEncodingResult { let base_snapshot = self.expression_to_snapshot(lowerer, &deref.base, expect_math_bool)?; - let result = lowerer.reference_target_current_snapshot( - deref.base.get_type(), - base_snapshot, - Default::default(), - )?; + let ty = deref.base.get_type(); + let result = if ty.is_reference() { + lowerer.reference_target_current_snapshot(ty, base_snapshot, deref.position)? + } else { + self.pointer_deref_to_snapshot(lowerer, deref, base_snapshot, expect_math_bool)? + }; self.ensure_bool_expression(lowerer, deref.get_type(), result, expect_math_bool) } + fn final_to_snapshot( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + deref: &vir_mid::Final, + expect_math_bool: bool, + ) -> SpannedEncodingResult { + let base_snapshot = self.expression_to_snapshot(lowerer, &deref.base, expect_math_bool)?; + let ty = deref.base.get_type(); + let result = if ty.is_reference() { + lowerer.reference_target_final_snapshot(ty, base_snapshot, deref.position)? + } else { + unreachable!("Final deref is not supported for non-reference types.") + }; + self.ensure_bool_expression(lowerer, deref.get_type(), result, expect_math_bool) + } + + fn pointer_deref_to_snapshot( + &mut self, + _lowerer: &mut Lowerer<'p, 'v, 'tcx>, + _deref: &vir_mid::Deref, + _base_snapshot: vir_low::Expression, + _expect_math_bool: bool, + ) -> SpannedEncodingResult { + unreachable!("Should be overriden."); + } + fn addr_of_to_snapshot( &mut self, lowerer: &mut Lowerer<'p, 'v, 'tcx>, @@ -203,15 +310,14 @@ pub(super) trait IntoSnapshotLowerer<'p, 'v: 'p, 'tcx: 'v> { ) -> SpannedEncodingResult { let result = match &addr_of.ty { vir_mid::Type::Reference(reference) if reference.uniqueness.is_shared() => { - let base_snapshot = - self.expression_to_snapshot(lowerer, &addr_of.base, expect_math_bool)?; + let base_snapshot = self.expression_to_snapshot(lowerer, &addr_of.base, false)?; lowerer.shared_non_alloc_reference_snapshot_constructor( &addr_of.ty, base_snapshot, Default::default(), )? } - _ => unimplemented!("ty: {}", addr_of.ty), + _ => unimplemented!("addr_of: {addr_of}\n{addr_of:?}\n ty: {}", addr_of.ty), }; self.ensure_bool_expression(lowerer, &addr_of.ty, result, expect_math_bool) } @@ -274,6 +380,9 @@ pub(super) trait IntoSnapshotLowerer<'p, 'v: 'p, 'tcx: 'v> { vir_mid::expression::ConstantValue::BigInt(value) => { vir_low::expression::ConstantValue::BigInt(value.clone()) } + vir_mid::expression::ConstantValue::String(_value) => { + unimplemented!(); + } vir_mid::expression::ConstantValue::Float(_value) => { unimplemented!(); } @@ -316,6 +425,15 @@ pub(super) trait IntoSnapshotLowerer<'p, 'v: 'p, 'tcx: 'v> { lowerer: &mut Lowerer<'p, 'v, 'tcx>, op: &vir_mid::BinaryOp, expect_math_bool: bool, + ) -> SpannedEncodingResult { + self.binary_op_to_snapshot_impl(lowerer, op, expect_math_bool) + } + + fn binary_op_to_snapshot_impl( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + op: &vir_mid::BinaryOp, + expect_math_bool: bool, ) -> SpannedEncodingResult { // FIXME: Binary Operations with MPerm should not be handled manually as special cases // They are difficult because binary operations with MPerm and Integer values are allowed. @@ -376,15 +494,26 @@ pub(super) trait IntoSnapshotLowerer<'p, 'v: 'p, 'tcx: 'v> { self.expression_to_snapshot(lowerer, &op.right, expect_math_bool_args)?; let arg_type = op.left.get_type().clone().erase_lifetimes(); assert_eq!(arg_type, op.right.get_type().clone().erase_lifetimes()); - let result = lowerer.construct_binary_op_snapshot( - op.op_kind, - ty, - &arg_type, - left_snapshot, - right_snapshot, - op.position, - )?; - self.ensure_bool_expression(lowerer, ty, result, expect_math_bool) + if expect_math_bool && op.op_kind == vir_mid::BinaryOpKind::EqCmp { + // FIXME: Instead of this ad-hoc optimization, have a proper + // optimization pass. + Ok(vir_low::Expression::binary_op( + vir_low::BinaryOpKind::EqCmp, + left_snapshot, + right_snapshot, + op.position, + )) + } else { + let result = lowerer.construct_binary_op_snapshot( + op.op_kind, + ty, + &arg_type, + left_snapshot, + right_snapshot, + op.position, + )?; + self.ensure_bool_expression(lowerer, ty, result, expect_math_bool) + } } fn binary_op_kind_to_snapshot( @@ -422,8 +551,11 @@ pub(super) trait IntoSnapshotLowerer<'p, 'v: 'p, 'tcx: 'v> { self.expression_to_snapshot(lowerer, &conditional.then_expr, expect_math_bool)?; let else_expr_snapshot = self.expression_to_snapshot(lowerer, &conditional.else_expr, expect_math_bool)?; - let arg_type = conditional.then_expr.get_type(); - assert_eq!(arg_type, conditional.else_expr.get_type()); + let arg_type = vir_low::operations::ty::Typed::get_type(&then_expr_snapshot); + assert_eq!( + arg_type, + vir_low::operations::ty::Typed::get_type(&else_expr_snapshot) + ); // We do not need to ensure expect_math_bool because we pushed this // responsibility to the arguments. Ok(vir_low::Expression::conditional( @@ -434,6 +566,76 @@ pub(super) trait IntoSnapshotLowerer<'p, 'v: 'p, 'tcx: 'v> { )) } + fn quantifier_to_snapshot( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + quantifier: &vir_mid::Quantifier, + expect_math_bool: bool, + ) -> SpannedEncodingResult { + assert!(expect_math_bool); + let quantifier_kind = match quantifier.kind { + vir_mid::expression::QuantifierKind::ForAll => { + vir_low::expression::QuantifierKind::ForAll + } + vir_mid::expression::QuantifierKind::Exists => { + vir_low::expression::QuantifierKind::Exists + } + }; + self.push_bound_variables(&quantifier.variables)?; + let body_snapshot = self.expression_to_snapshot(lowerer, &quantifier.body, true)?; + let mut variables = Vec::new(); + let mut variable_validity = Vec::new(); + for variable_mid in &quantifier.variables { + let variable = vir_low::VariableDecl::new( + variable_mid.name.clone(), + self.type_to_snapshot(lowerer, &variable_mid.ty)?, + ); + variables.push(variable.clone()); + let validity = + lowerer.encode_snapshot_valid_call_for_type(variable.into(), &variable_mid.ty)?; + variable_validity.push(validity); + } + let variable_validity = variable_validity.into_iter().conjoin(); + let body = match quantifier.kind { + vir_mid::expression::QuantifierKind::ForAll => { + vir_low::Expression::implies(variable_validity, body_snapshot) + } + vir_mid::expression::QuantifierKind::Exists => { + vir_low::Expression::and(variable_validity, body_snapshot) + } + }; + let triggers = quantifier + .triggers + .iter() + .map(|trigger| { + trigger + .terms + .iter() + .map(|expr| self.expression_to_snapshot(lowerer, expr, true)) + .collect::>>() + .map(vir_low::Trigger::new) + }) + .collect::>>()?; + let result = vir_low::Expression::quantifier( + None, + quantifier_kind, + variables, + triggers, + body, + quantifier.position, + ); + self.pop_bound_variables()?; + // self.ensure_bool_expression(lowerer, &vir_mid::Type::Bool, result, expect_math_bool) + Ok(result) + } + + fn push_bound_variables( + &mut self, + variables: &[vir_mid::VariableDecl], + ) -> SpannedEncodingResult<()>; + + fn pop_bound_variables(&mut self) -> SpannedEncodingResult<()>; + fn func_app_to_snapshot( &mut self, lowerer: &mut Lowerer<'p, 'v, 'tcx>, @@ -444,7 +646,7 @@ pub(super) trait IntoSnapshotLowerer<'p, 'v: 'p, 'tcx: 'v> { fn builtin_func_app_to_snapshot( &mut self, lowerer: &mut Lowerer<'p, 'v, 'tcx>, - app: &vir_crate::middle::expression::BuiltinFuncApp, + app: &vir_mid::BuiltinFuncApp, expect_math_bool: bool, ) -> SpannedEncodingResult { use vir_low::expression::ContainerOpKind; @@ -455,26 +657,29 @@ pub(super) trait IntoSnapshotLowerer<'p, 'v: 'p, 'tcx: 'v> { .iter() .map(|ty| self.type_to_snapshot(lowerer, ty)) .collect::, _>>()?; - let mut args = - self.expression_vec_to_snapshot(lowerer, &app.arguments, expect_math_bool)?; - if !app.arguments.is_empty() { - let first_arg_type = app.arguments[0].get_type(); - if first_arg_type.is_reference() - && app.function != vir_mid::BuiltinFunc::SnapshotEquality - { - // The first argument is a reference, dereference it. - args[0] = lowerer.reference_target_current_snapshot( - first_arg_type, - args[0].clone(), - app.position, - )?; + let construct_args = |this: &mut Self, lowerer: &mut _| -> Result<_, _> { + let mut args = + this.expression_vec_to_snapshot(lowerer, &app.arguments, expect_math_bool)?; + if !app.arguments.is_empty() { + let first_arg_type = app.arguments[0].get_type(); + if first_arg_type.is_reference() + && app.function != vir_mid::BuiltinFunc::SnapshotEquality + { + // The first argument is a reference, dereference it. + args[0] = lowerer.reference_target_current_snapshot( + first_arg_type, + args[0].clone(), + app.position, + )?; + } } - } + Ok(args) + }; lowerer.ensure_type_definition(&app.return_type)?; - let map = |low_kind| { + let map = |this, lowerer, low_kind| { let map_ty = vir_low::Type::map(ty_args[0].clone(), ty_args[1].clone()); - let args = args.clone(); + let args = construct_args(this, lowerer)?; Ok(vir_low::Expression::container_op( low_kind, map_ty, @@ -483,38 +688,176 @@ pub(super) trait IntoSnapshotLowerer<'p, 'v: 'p, 'tcx: 'v> { )) }; - let seq = |low_kind| { + let seq = |this, lowerer, low_kind| { + let args = construct_args(this, lowerer)?; Ok(vir_low::Expression::container_op( low_kind, vir_low::Type::seq(ty_args[0].clone()), - args.clone(), + args, app.position, )) }; match app.function { BuiltinFunc::Size => { - let return_type = self.type_to_snapshot(lowerer, &app.return_type)?; - lowerer.create_domain_func_app( - "Size", - app.get_identifier(), - args, - return_type, - app.position, - ) + let _return_type = self.type_to_snapshot(lowerer, &app.return_type)?; + let args = construct_args(self, lowerer)?; + assert_eq!(app.type_arguments.len(), 1); + match &app.type_arguments[0] { + vir_mid::Type::Int(ty) + if !matches!( + ty, + vir_mid::ty::Int::Isize + | vir_mid::ty::Int::Usize + | vir_mid::ty::Int::Char + | vir_mid::ty::Int::Unbounded + ) => + { + let size = match ty { + vir_mid::ty::Int::I8 => 1, + vir_mid::ty::Int::I16 => 2, + vir_mid::ty::Int::I32 => 4, + vir_mid::ty::Int::I64 => 8, + vir_mid::ty::Int::I128 => 16, + vir_mid::ty::Int::U8 => 1, + vir_mid::ty::Int::U16 => 2, + vir_mid::ty::Int::U32 => 4, + vir_mid::ty::Int::U64 => 8, + vir_mid::ty::Int::U128 => 16, + vir_mid::ty::Int::Isize => unreachable!(), + vir_mid::ty::Int::Usize => unreachable!(), + vir_mid::ty::Int::Char => unreachable!(), + vir_mid::ty::Int::Unbounded => unreachable!(), + }; + let constant = + vir_low::Expression::constant_no_pos(size.into(), vir_low::Type::Int); + lowerer.construct_constant_snapshot( + &app.return_type, + constant, + app.position, + ) + } + vir_mid::Type::Bool => { + let size = 1; + let constant = + vir_low::Expression::constant_no_pos(size.into(), vir_low::Type::Int); + lowerer.construct_constant_snapshot( + &app.return_type, + constant, + app.position, + ) + } + vir_mid::Type::Struct(_ty) => { + let type_decl = lowerer + .encoder + .get_type_decl_mid(&app.type_arguments[0])? + .unwrap_struct(); + if let Some(size) = type_decl.size { + let constant = vir_low::Expression::constant_no_pos( + size.into(), + vir_low::Type::Int, + ); + lowerer.construct_constant_snapshot( + &app.return_type, + constant, + app.position, + ) + } else { + lowerer.encode_size_function_call_with_axioms( + app.get_identifier(), + args, + app.position, + ) + // lowerer.create_domain_func_app( + // "Size", + // app.get_identifier(), + // args, + // return_type, + // app.position, + // ) + } + } + // _ => lowerer.create_domain_func_app( + // "Size", + // app.get_identifier(), + // args, + // return_type, + // app.position, + // ), + _ => lowerer.encode_size_function_call_with_axioms( + app.get_identifier(), + args, + app.position, + ), + } } BuiltinFunc::PaddingSize => { + let args = construct_args(self, lowerer)?; assert_eq!(args.len(), 0); - let return_type = self.type_to_snapshot(lowerer, &app.return_type)?; - lowerer.create_domain_func_app( - "Size", + // let return_type = self.type_to_snapshot(lowerer, &app.return_type)?; + // lowerer.create_domain_func_app( + // "Size", + // app.get_identifier(), + // args, + // return_type, + // app.position, + // ) + lowerer.encode_size_function_call_with_axioms( app.get_identifier(), args, - return_type, app.position, ) } + BuiltinFunc::Align => { + let args = construct_args(self, lowerer)?; + assert_eq!(args.len(), 0); + let return_type = self.type_to_snapshot(lowerer, &app.return_type)?; + assert_eq!(app.type_arguments.len(), 1); + match app.type_arguments[0] { + vir_mid::Type::Int(ty) + if !matches!( + ty, + vir_mid::ty::Int::Isize + | vir_mid::ty::Int::Usize + | vir_mid::ty::Int::Char + | vir_mid::ty::Int::Unbounded + ) => + { + let size = match ty { + vir_mid::ty::Int::I8 => 1, + vir_mid::ty::Int::I16 => 2, + vir_mid::ty::Int::I32 => 4, + vir_mid::ty::Int::I64 => 8, + vir_mid::ty::Int::I128 => 8, + vir_mid::ty::Int::U8 => 1, + vir_mid::ty::Int::U16 => 2, + vir_mid::ty::Int::U32 => 4, + vir_mid::ty::Int::U64 => 8, + vir_mid::ty::Int::U128 => 8, + vir_mid::ty::Int::Isize => unreachable!(), + vir_mid::ty::Int::Usize => unreachable!(), + vir_mid::ty::Int::Char => unreachable!(), + vir_mid::ty::Int::Unbounded => unreachable!(), + }; + let constant = + vir_low::Expression::constant_no_pos(size.into(), vir_low::Type::Int); + lowerer.construct_constant_snapshot( + &app.return_type, + constant, + app.position, + ) + } + _ => lowerer.create_domain_func_app( + "Align", + app.get_identifier(), + args, + return_type, + app.position, + ), + } + } BuiltinFunc::Discriminant => { + let mut args = construct_args(self, lowerer)?; assert_eq!(args.len(), 1); let discriminant_call = lowerer.obtain_enum_discriminant( args.pop().unwrap(), @@ -527,10 +870,10 @@ pub(super) trait IntoSnapshotLowerer<'p, 'v: 'p, 'tcx: 'v> { app.position, ) } - BuiltinFunc::EmptyMap => map(ContainerOpKind::MapEmpty), - BuiltinFunc::UpdateMap => map(ContainerOpKind::MapUpdate), + BuiltinFunc::EmptyMap => map(self, lowerer, ContainerOpKind::MapEmpty), + BuiltinFunc::UpdateMap => map(self, lowerer, ContainerOpKind::MapUpdate), BuiltinFunc::LookupMap => { - let value = map(ContainerOpKind::MapLookup)?; + let value = map(self, lowerer, ContainerOpKind::MapLookup)?; if app.return_type.is_reference() { lowerer.shared_non_alloc_reference_snapshot_constructor( &app.return_type, @@ -542,16 +885,17 @@ pub(super) trait IntoSnapshotLowerer<'p, 'v: 'p, 'tcx: 'v> { } } BuiltinFunc::MapLen => { - let value = map(ContainerOpKind::MapLen)?; + let value = map(self, lowerer, ContainerOpKind::MapLen)?; lowerer.construct_constant_snapshot(app.get_type(), value, app.position) } BuiltinFunc::MapContains => { - let m = map(ContainerOpKind::MapContains)?; + let m = map(self, lowerer, ContainerOpKind::MapContains)?; let m = lowerer.construct_constant_snapshot(app.get_type(), m, app.position)?; self.ensure_bool_expression(lowerer, app.get_type(), m, expect_math_bool) } BuiltinFunc::LookupSeq => { use vir_low::operations::ty::Typed; + let args = construct_args(self, lowerer)?; assert!( args[0].get_type().is_seq(), "Expected Sequence type, got {:?}", @@ -580,22 +924,24 @@ pub(super) trait IntoSnapshotLowerer<'p, 'v: 'p, 'tcx: 'v> { Ok(value) } } - BuiltinFunc::ConcatSeq => seq(ContainerOpKind::SeqConcat), + BuiltinFunc::ConcatSeq => seq(self, lowerer, ContainerOpKind::SeqConcat), BuiltinFunc::SeqLen => { - let value = seq(ContainerOpKind::SeqLen)?; + let value = seq(self, lowerer, ContainerOpKind::SeqLen)?; lowerer.construct_constant_snapshot(app.get_type(), value, app.position) } BuiltinFunc::LifetimeIncluded => { + let args = construct_args(self, lowerer)?; assert_eq!(args.len(), 2); lowerer.encode_lifetime_included()?; Ok(vir_low::Expression::domain_function_call( - "Lifetime", + LIFETIME_DOMAIN_NAME, "included", args, vir_low::ty::Type::Bool, )) } BuiltinFunc::LifetimeIntersect => { + let args = construct_args(self, lowerer)?; assert!(!args.is_empty()); // FIXME: Fix code duplication. let lifetime_set_type = lowerer.lifetime_set_type()?; @@ -606,7 +952,7 @@ pub(super) trait IntoSnapshotLowerer<'p, 'v: 'p, 'tcx: 'v> { args, ); let intersect = lowerer.create_domain_func_app( - "Lifetime", + LIFETIME_DOMAIN_NAME, "intersect", vec![lifetime_set], lifetime_type, @@ -615,6 +961,7 @@ pub(super) trait IntoSnapshotLowerer<'p, 'v: 'p, 'tcx: 'v> { Ok(intersect) } BuiltinFunc::EmptySeq | BuiltinFunc::SingleSeq => { + let args = construct_args(self, lowerer)?; Ok(vir_low::Expression::container_op( vir_low::ContainerOpKind::SeqConstructor, vir_low::Type::seq(ty_args[0].clone()), @@ -623,6 +970,7 @@ pub(super) trait IntoSnapshotLowerer<'p, 'v: 'p, 'tcx: 'v> { )) } BuiltinFunc::NewInt => { + let mut args = construct_args(self, lowerer)?; assert_eq!(args.len(), 1); let arg = args.pop().unwrap(); let value = lowerer.obtain_constant_value( @@ -633,6 +981,7 @@ pub(super) trait IntoSnapshotLowerer<'p, 'v: 'p, 'tcx: 'v> { lowerer.construct_constant_snapshot(app.get_type(), value, app.position) } BuiltinFunc::Index => { + let args = construct_args(self, lowerer)?; assert_eq!(args.len(), 2); // FIXME: Remove duplication with LookupSeq. let index = lowerer.obtain_constant_value( @@ -640,21 +989,24 @@ pub(super) trait IntoSnapshotLowerer<'p, 'v: 'p, 'tcx: 'v> { args[1].clone(), app.position, )?; - Ok(vir_low::Expression::container_op( + let expression = vir_low::Expression::container_op( vir_low::ContainerOpKind::SeqIndex, vir_low::Type::seq(ty_args[0].clone()), vec![args[0].clone(), index], app.position, - )) + ); + expression.assert_valid_debug(); + Ok(expression) } BuiltinFunc::Len => { - assert_eq!(args.len(), 1); + assert_eq!(app.arguments.len(), 1); // FIXME: Remove duplication with SeqLen. - let value = seq(ContainerOpKind::SeqLen)?; + let value = seq(self, lowerer, ContainerOpKind::SeqLen)?; lowerer.construct_constant_snapshot(app.get_type(), value, app.position) } BuiltinFunc::SnapshotEquality => { assert_eq!(app.arguments[0].get_type(), app.arguments[1].get_type()); + let args = construct_args(self, lowerer)?; let value = vir_low::Expression::binary_op( vir_low::BinaryOpKind::EqCmp, args[0].clone(), @@ -667,9 +1019,509 @@ pub(super) trait IntoSnapshotLowerer<'p, 'v: 'p, 'tcx: 'v> { lowerer.construct_constant_snapshot(&vir_mid::Type::Bool, value, app.position) } } + BuiltinFunc::PtrIsNull => { + let args = construct_args(self, lowerer)?; + assert_eq!(args.len(), 1); + let ty = app.arguments[0].get_type(); + let address = lowerer.pointer_address(ty, args[0].clone(), app.position)?; + let null_address = lowerer.address_null(app.position)?; + let equals = vir_low::Expression::equals(address, null_address); + let equals = + lowerer.construct_constant_snapshot(app.get_type(), equals, app.position)?; + self.ensure_bool_expression(lowerer, app.get_type(), equals, expect_math_bool) + } + BuiltinFunc::PtrOffset + | BuiltinFunc::PtrWrappingOffset + | BuiltinFunc::PtrAdd + | BuiltinFunc::PtrAddressOffset => { + let args = construct_args(self, lowerer)?; + assert_eq!(args.len(), 2); + let ty = app.arguments[0].get_type(); + let address = lowerer.pointer_address(ty, args[0].clone(), app.position)?; + let vir_mid::Type::Pointer(pointer_type) = ty else { + unreachable!() + }; + let size = lowerer.encode_type_size_expression2( + &pointer_type.target_type, + &*pointer_type.target_type, + )?; + let offset = lowerer.obtain_constant_value( + app.arguments[1].get_type(), + args[1].clone(), + app.position, + )?; + let new_address = lowerer.address_offset(size, address, offset, app.position)?; + lowerer.address_to_pointer(ty, new_address, app.position) + } + BuiltinFunc::PtrAddressOffsetFrom => { + let args = construct_args(self, lowerer)?; + assert_eq!(args.len(), 2); + let ty = app.arguments[0].get_type(); + let address_to = lowerer.pointer_address(ty, args[0].clone(), app.position)?; + let address_from = lowerer.pointer_address(ty, args[1].clone(), app.position)?; + let vir_mid::Type::Pointer(pointer_type) = ty else { + unreachable!() + }; + let size = lowerer.encode_type_size_expression2( + &pointer_type.target_type, + &*pointer_type.target_type, + )?; + let offset = + lowerer.offset_from_address(size, address_to, address_from, app.position)?; + lowerer.construct_constant_snapshot( + &vir_mid::Type::Int(vir_mid::ty::Int::Unbounded), + offset, + app.position, + ) + } + BuiltinFunc::PtrSameAllocation => { + let args = construct_args(self, lowerer)?; + let ty = app.arguments[0].get_type(); + assert_eq!(args.len(), 2); + let address1 = lowerer.pointer_address(ty, args[0].clone(), app.position)?; + let address2 = lowerer.pointer_address(ty, args[1].clone(), app.position)?; + let allocation1 = lowerer.address_allocation(address1, app.position)?; + let allocation2 = lowerer.address_allocation(address2, app.position)?; + let equals = vir_low::Expression::equals(allocation1, allocation2); + let equals = + lowerer.construct_constant_snapshot(app.get_type(), equals, app.position)?; + self.ensure_bool_expression(lowerer, app.get_type(), equals, expect_math_bool) + } + BuiltinFunc::PtrFreshAllocation => { + let args = construct_args(self, lowerer)?; + let ty = app.arguments[0].get_type(); + assert_eq!(args.len(), 1); + let address = lowerer.pointer_address(ty, args[0].clone(), app.position)?; + let allocation = lowerer.address_allocation(address, app.position)?; + let fresh_allocation = lowerer.fresh_allocation(app.position)?; + let equals = vir_low::Expression::equals(allocation, fresh_allocation); + let equals = + lowerer.construct_constant_snapshot(app.get_type(), equals, app.position)?; + self.ensure_bool_expression(lowerer, app.get_type(), equals, expect_math_bool) + } + BuiltinFunc::PtrRangeContains => { + let args = construct_args(self, lowerer)?; + assert_eq!(args.len(), 3); + let ty = app.arguments[0].get_type(); + let start_address = lowerer.pointer_address(ty, args[0].clone(), app.position)?; + let vir_mid::Type::Pointer(pointer_type) = ty else { + unreachable!() + }; + let type_size = lowerer.encode_type_size_expression2( + &pointer_type.target_type, + &*pointer_type.target_type, + )?; + let range_length = lowerer.obtain_constant_value( + app.arguments[1].get_type(), + args[1].clone(), + app.position, + )?; + let checked_address = lowerer.pointer_address(ty, args[2].clone(), app.position)?; + lowerer.pointer_range_contains( + start_address, + type_size, + range_length, + checked_address, + app.position, + ) + } + BuiltinFunc::IsValid => { + let mut args = construct_args(self, lowerer)?; + assert_eq!(app.arguments.len(), 1); + let argument = args.pop().unwrap(); + let ty = app.arguments[0].get_type(); + lowerer.encode_snapshot_valid_call_for_type(argument, ty) + } + BuiltinFunc::EnsureOwnedPredicate => { + assert_eq!(app.arguments.len(), 1); + fn peel_unfolding<'p, 'v: 'p, 'tcx: 'v>( + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + into_snap_lowerer: &mut impl IntoSnapshotLowerer<'p, 'v, 'tcx>, + place: &vir_mid::Expression, + ) -> SpannedEncodingResult { + match place { + vir_mid::Expression::Unfolding(unfolding) => { + let body = peel_unfolding(lowerer, into_snap_lowerer, &unfolding.body)?; + into_snap_lowerer.unfolding_to_snapshot_with_body( + lowerer, + &unfolding.predicate, + body, + unfolding.position, + true, + ) + } + _ => { + let ty = place.get_type(); + let snap_call = + into_snap_lowerer.owned_non_aliased_snap(lowerer, ty, place)?; + let snapshot = + into_snap_lowerer.expression_to_snapshot(lowerer, place, true)?; + let position = place.position(); + Ok(vir_low::Expression::binary_op( + vir_low::BinaryOpKind::EqCmp, + snap_call, + snapshot, + position, + )) + } + } + } + peel_unfolding(lowerer, self, &app.arguments[0]) + // let argument = &app.arguments[0]; + // let ty = argument.get_type(); + // let snap_call = self.owned_non_aliased_snap(lowerer, ty, argument)?; + // lowerer.wrap_snap_into_bool(ty, snap_call.set_default_position(app.position)) + } + BuiltinFunc::TakeLifetime => { + unimplemented!("TODO: Delete"); + } + BuiltinFunc::ReadByte => { + let mut args = construct_args(self, lowerer)?; + let index = args.pop().unwrap(); + let bytes = args.pop().unwrap(); + lowerer.encode_read_byte_expression_usize(bytes, index, app.position) + } + BuiltinFunc::MemoryBlockBytes => { + assert_eq!(app.arguments.len(), 2); + // let mut args = construct_args(self, lowerer)?; + // let size = args.pop().unwrap(); + let size = + self.expression_to_snapshot(lowerer, &app.arguments[1], expect_math_bool)?; + // let pointer_value = args.pop().unwrap(); + // let address = lowerer.pointer_address( + // app.arguments[0].get_type(), + // pointer_value, + // app.position, + // )?; + let address = self.pointer_deref_into_address(lowerer, &app.arguments[0])?; + lowerer.encode_memory_block_bytes_expression(address, size) + } + BuiltinFunc::MemoryBlockBytesPtr => { + // TODO: Should have been desugared into MemoryBlockBytes. + let pointer = &app.arguments[0]; + let vir_mid::Type::Pointer(pointer_type) = pointer.get_type() else { + unreachable!("pointer.get_type() should be Pointer, got: {}", pointer.get_type()); + }; + let pointer_deref = pointer + .clone() + .deref((*pointer_type.target_type).clone(), app.position); + let size = + self.expression_to_snapshot(lowerer, &app.arguments[1], expect_math_bool)?; + let address = self.pointer_deref_into_address(lowerer, &pointer_deref)?; + lowerer.encode_memory_block_bytes_expression(address, size) + } + BuiltinFunc::DerefOwn => { + let mut args = construct_args(self, lowerer)?; + let pointer_type = app.arguments[0].get_type(); + // let address = lowerer.pointer_address( + // pointer_type, + // args[0].clone(), + // app.position, + // )?; + let index = args.pop().unwrap(); + let pointer = args.pop().unwrap(); + self.deref_own(lowerer, pointer_type, pointer, index, app.position) + } + BuiltinFunc::CastMutToConstPointer => { + let mut args = construct_args(self, lowerer)?; + // We currently do not distinguish between mutable and immutable + // pointers so this is a no-op. + Ok(args.pop().unwrap()) + // let address = lowerer.pointer_address( + // app.arguments[0].get_type(), + // args[0].clone(), + // app.position, + // )?; + // lowerer.address_to_pointer(&app.return_type, address, app.position) + } + BuiltinFunc::CastPtrToPtr => { + let mut args = construct_args(self, lowerer)?; + // FIXME: This encoding is probably wrong because we are not doing any casting. + Ok(args.pop().unwrap()) + } + BuiltinFunc::CastIntToInt => { + let mut args = construct_args(self, lowerer)?; + assert_eq!(args.len(), 1); + let arg = args.pop().unwrap(); + assert_eq!(app.type_arguments.len(), 2); + let source_type = &app.type_arguments[0]; + let destination_type = &app.type_arguments[1]; + lowerer.cast_int_to_int(source_type, destination_type, arg, app.position) + } + BuiltinFunc::BeforeExpiry => { + unreachable!("BeforeExpiry should be desugard before"); + } + BuiltinFunc::AfterExpiry => { + unreachable!("AfterExpiry should be desugard before"); + } + BuiltinFunc::BuildingUniqueRefPredicate => { + unreachable!("UniqueRef should have been already built.") + } + BuiltinFunc::BuildingUniqueRefPredicateWithRealLifetime + | BuiltinFunc::BuildingUniqueRefPredicateRangeWithRealLifetime => { + unreachable!("UniqueRef should have been already built.") + } + BuiltinFunc::BuildingFracRefPredicate => { + unreachable!("FracRef should have been already built.") + } + BuiltinFunc::AllocationNeverFails => { + let return_type = self.type_to_snapshot(lowerer, &app.return_type)?; + let call = lowerer.create_domain_func_app( + "AllocationNeverFails", + "allocation_never_fails", + Vec::new(), + return_type, + app.position, + )?; + self.ensure_bool_expression(lowerer, app.get_type(), call, expect_math_bool) + } + BuiltinFunc::Multiply => { + let mut args = construct_args(self, lowerer)?; + assert_eq!(args.len(), 2); + let arg1 = args.pop().unwrap(); + let arg2 = args.pop().unwrap(); + let arg1 = lowerer.obtain_constant_value( + app.arguments[0].get_type(), + arg1, + app.position, + )?; + let arg2 = lowerer.obtain_constant_value( + app.arguments[1].get_type(), + arg2, + app.position, + )?; + let expression = if config::smt_use_nonlinear_arithmetic_solver() { + vir_low::Expression::multiply(arg1, arg2) + } else { + // let multiply_call = lowerer.create_domain_func_app( + // "ArithmeticWrappers", + // "multiply_wrapper", + // vec![arg1, arg2], + // vir_low::ty::Type::Int, + // app.position, + // )?; + let multiply_call = lowerer.int_mul_call(arg1, arg2, app.position)?; + lowerer.construct_constant_snapshot( + &app.return_type, + multiply_call, + app.position, + )? + // let unbounded_ty = vir_mid::Type::Int(vir_mid::ty::Int::Unbounded); + // let return_type = self.type_to_snapshot(lowerer, &unbounded_ty)?; + // let is_not_unbounded = !matches!(app.return_type, vir_mid::Type::Int(vir_mid::ty::Int::Unbounded)); + // if is_not_unbounded { + // // arg1 = lowerer.cast_int_to_int(&app.return_type, &unbounded_ty, arg1, app.position)?; + // // arg2 = lowerer.cast_int_to_int(&app.return_type, &unbounded_ty, arg2, app.position)?; + // arg1 = lowerer.obtain_constant_value(&app.return_type, arg1, app.position)?; + // } + // let mut call = lowerer.create_domain_func_app( + // "ArithmeticWrappers", + // "multiply_wrapper", + // vec![arg1, arg2], + // return_type, + // app.position, + // )?; + // if is_not_unbounded { + // call = lowerer.cast_int_to_int(&unbounded_ty, &app.return_type, call, app.position)?; + // } + // call + }; + Ok(expression) + } } } + /// Deref a raw pointer with the specified offset. + fn deref_own( + &mut self, + _lowerer: &mut Lowerer<'p, 'v, 'tcx>, + _pointer_type: &vir_mid::Type, + _pointer: vir_low::Expression, + _index: vir_low::Expression, + _position: vir_low::Position, + ) -> SpannedEncodingResult { + unimplemented!(); + } + + // FIXME: Code duplication. + fn pointer_deref_into_address( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + place: &vir_mid::Expression, + ) -> SpannedEncodingResult { + if let vir_mid::Expression::EvalIn(eval_in) = place { + let result = + self.eval_in_to_snapshot(lowerer, eval_in, false, |this, lowerer, place, _| { + this.pointer_deref_into_address(lowerer, place) + }); + return result; + } + if let Some(parent) = place.get_parent_ref_of_place_like() { + let parent_type = parent.get_type(); + if place.is_deref() && parent_type.is_pointer() { + let base_snapshot = self.expression_to_snapshot(lowerer, parent, true)?; + let ty = parent.get_type(); + lowerer.pointer_address(ty, base_snapshot, place.position()) + } else { + let base_address = self.pointer_deref_into_address(lowerer, parent)?; + let position = place.position(); + match place { + vir_mid::Expression::Field(place) => lowerer.encode_field_address( + parent_type, + &place.field, + base_address, + position, + ), + vir_mid::Expression::Variant(place) => lowerer.encode_enum_variant_address( + parent_type, + &place.variant_index, + base_address, + position, + ), + _ => unreachable!("place: {place}"), + } + } + } else { + unreachable!("place: {place}"); + } + // if let Some(deref_place) = place.get_last_dereferenced_pointer() { + // let base_snapshot = self.expression_to_snapshot(lowerer, deref_place, true)?; + // let ty = deref_place.get_type(); + // lowerer.pointer_address(ty, base_snapshot, place.position()) + // } else { + // unreachable!("place: {place}"); + // } + } + + fn acc_predicate_to_snapshot( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + acc_predicate: &vir_mid::AccPredicate, + expect_math_bool: bool, + ) -> SpannedEncodingResult; + + // fn unfolding_to_snapshot( + // &mut self, + // lowerer: &mut Lowerer<'p, 'v, 'tcx>, + // unfolding: &vir_mid::Unfolding, + // expect_math_bool: bool, + // ) -> SpannedEncodingResult; + + fn call_context(&self) -> CallContext; + + fn encode_lifetime_in_self_context( + &self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + lifetime: vir_mid::ty::LifetimeConst, + ) -> SpannedEncodingResult { + let encoded_lifetime = match self.call_context() { + CallContext::BuiltinMethod => { + crate::encoder::middle::core_proof::snapshots::IntoPureSnapshot::to_pure_snapshot( + &lifetime, lowerer, + )? + } + CallContext::Procedure => { + lowerer.encode_lifetime_const_into_procedure_variable(lifetime)? + } + }; + Ok(encoded_lifetime.into()) + } + + fn unfolding_to_snapshot_with_body( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + predicate: &vir_mid::Predicate, + body: vir_low::Expression, + position: vir_low::Position, + expect_math_bool: bool, + ) -> SpannedEncodingResult { + assert!(expect_math_bool, "not implemented"); + let predicate = match predicate { + vir_mid::Predicate::OwnedNonAliased(predicate) => { + let ty = predicate.place.get_type(); + lowerer.mark_owned_predicate_as_unfolded(ty)?; + let place = lowerer.encode_expression_as_place(&predicate.place)?; + let address = lowerer.encode_expression_as_place_address(&predicate.place)?; + // let root_address = lowerer.extract_root_address(&predicate.place)?; + // let snapshot = + // self.expression_to_snapshot(lowerer, &predicate.place, expect_math_bool)?; + // predicate.place.to_procedure_snapshot(lowerer)?; // FIXME: This is probably wrong. It should take into account the current old. + lowerer + .owned_non_aliased(self.call_context(), ty, ty, place, address, None, position)? + .unwrap_predicate_access_predicate() + } + _ => unimplemented!("{predicate}"), + }; + let expression = vir_low::Expression::unfolding(predicate, body, position); + Ok(expression) + } + + fn unfolding_to_snapshot( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + unfolding: &vir_mid::Unfolding, + expect_math_bool: bool, + ) -> SpannedEncodingResult { + let body = self.expression_to_snapshot(lowerer, &unfolding.body, expect_math_bool)?; + self.unfolding_to_snapshot_with_body( + lowerer, + &unfolding.predicate, + body, + unfolding.position, + expect_math_bool, + ) + // let predicate = match &*unfolding.predicate { + // vir_mid::Predicate::OwnedNonAliased(predicate) => { + // let ty = predicate.place.get_type(); + // lowerer.mark_owned_predicate_as_unfolded(ty)?; + // let place = lowerer.encode_expression_as_place(&predicate.place)?; + // let root_address = lowerer.extract_root_address(&predicate.place)?; + // let snapshot = self.expression_to_snapshot(lowerer, &predicate.place, expect_math_bool)?; + // // predicate.place.to_procedure_snapshot(lowerer)?; // FIXME: This is probably wrong. It should take into account the current old. + // lowerer + // .owned_non_aliased( + // self.call_context(), + // ty, + // ty, + // place, + // root_address, + // snapshot, + // None, + // )? + // .unwrap_predicate_access_predicate() + // } + // _ => unimplemented!("{unfolding}"), + // }; + // let body = self.expression_to_snapshot(lowerer, &unfolding.body, expect_math_bool)?; + // let expression = vir_low::Expression::unfolding(predicate, body, unfolding.position); + // Ok(expression) + } + + fn eval_in_to_snapshot( + &mut self, + _lowerer: &mut Lowerer<'p, 'v, 'tcx>, + _eval_in: &vir_mid::EvalIn, + _expect_math_bool: bool, + _body_to_snapshot: F, + ) -> SpannedEncodingResult + where + F: FnOnce( + &mut Self, + &mut Lowerer<'p, 'v, 'tcx>, + &vir_mid::Expression, + bool, + ) -> SpannedEncodingResult, + { + unimplemented!("FIXME: Make this abstract."); + } + + fn owned_non_aliased_snap( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + ty: &vir_mid::Type, + pointer_place: &vir_mid::Expression, + ) -> SpannedEncodingResult; + fn type_to_snapshot( &mut self, lowerer: &mut Lowerer<'p, 'v, 'tcx>, diff --git a/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/context_independent/mod.rs b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/context_independent/mod.rs index 4b17e40976f..c0781310274 100644 --- a/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/context_independent/mod.rs +++ b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/context_independent/mod.rs @@ -2,7 +2,10 @@ //! the context. Currently, the only example is types. use super::common::IntoSnapshotLowerer; -use crate::encoder::{errors::SpannedEncodingResult, middle::core_proof::lowerer::Lowerer}; +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::{builtin_methods::CallContext, lowerer::Lowerer}, +}; use vir_crate::{ low::{self as vir_low}, middle::{self as vir_mid}, @@ -41,4 +44,46 @@ impl<'p, 'v: 'p, 'tcx: 'v> IntoSnapshotLowerer<'p, 'v, 'tcx> for ContextIndepend ) -> SpannedEncodingResult { unreachable!("requested context dependent encoding"); } + + fn acc_predicate_to_snapshot( + &mut self, + _lowerer: &mut Lowerer<'p, 'v, 'tcx>, + _predicate: &vir_mid::AccPredicate, + _expect_math_bool: bool, + ) -> SpannedEncodingResult { + unreachable!("requested context dependent encoding"); + } + + fn owned_non_aliased_snap( + &mut self, + _lowerer: &mut Lowerer<'p, 'v, 'tcx>, + _ty: &vir_mid::Type, + _pointer_snapshot: &vir_mid::Expression, + ) -> SpannedEncodingResult { + unimplemented!() + } + + // fn unfolding_to_snapshot( + // &mut self, + // lowerer: &mut Lowerer<'p, 'v, 'tcx>, + // unfolding: &vir_mid::Unfolding, + // expect_math_bool: bool, + // ) -> SpannedEncodingResult { + // todo!() + // } + + fn call_context(&self) -> CallContext { + todo!() + } + + fn push_bound_variables( + &mut self, + _variables: &[vir_mid::VariableDecl], + ) -> SpannedEncodingResult<()> { + todo!() + } + + fn pop_bound_variables(&mut self) -> SpannedEncodingResult<()> { + todo!() + } } diff --git a/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/expressions/mod.rs b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/expressions/mod.rs new file mode 100644 index 00000000000..fa659df8eb2 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/expressions/mod.rs @@ -0,0 +1,10 @@ +/// Expressions that are framed and used in pure contexts. For example, pure +/// function bodies. +mod pure_framed; +/// Expressions to be used in procedure bodies. For example, arguments of +/// builtin methods. +mod procedure_bodies; + +pub(in super::super::super) use self::{ + procedure_bodies::PlaceToSnapshot, pure_framed::FramedExpressionToSnapshot, +}; diff --git a/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/expressions/procedure_bodies.rs b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/expressions/procedure_bodies.rs new file mode 100644 index 00000000000..e2bb56af111 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/expressions/procedure_bodies.rs @@ -0,0 +1,167 @@ +// FIXME: Rename the module. + +use super::super::PredicateKind; +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::{ + addresses::AddressesInterface, + builtin_methods::CallContext, + lowerer::Lowerer, + places::PlacesInterface, + predicates::PredicatesOwnedInterface, + snapshots::{IntoSnapshotLowerer, SnapshotVariablesInterface}, + }, +}; + +use vir_crate::{ + common::position::Positioned, + low::{self as vir_low}, + middle::{self as vir_mid, operations::ty::Typed}, +}; + +pub(in super::super::super::super::super) struct PlaceToSnapshot { + old_label: Option, + predicate_kind: PredicateKind, +} + +impl PlaceToSnapshot { + pub(in super::super::super::super) fn for_place(predicate_kind: PredicateKind) -> Self { + Self { + old_label: None, + predicate_kind, + } + } + + fn snap_call<'p, 'v, 'tcx>( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + ty: &vir_mid::Type, + pointer_place: &vir_mid::Expression, + ) -> SpannedEncodingResult { + let place = lowerer.encode_expression_as_place(pointer_place)?; + let address = lowerer.encode_expression_as_place_address(pointer_place)?; + match &self.predicate_kind { + PredicateKind::Owned => lowerer.owned_non_aliased_snap( + CallContext::Procedure, + ty, + ty, + place, + address, + pointer_place.position(), + ), + PredicateKind::FracRef { lifetime } => { + let TODO_target_slice_len = None; + lowerer.frac_ref_snap( + CallContext::Procedure, + ty, + ty, + place, + address, + lifetime.clone(), + TODO_target_slice_len, + pointer_place.position(), + ) + } + PredicateKind::UniqueRef { lifetime, is_final } => { + let TODO_target_slice_len = None; + lowerer.unique_ref_snap( + CallContext::Procedure, + ty, + ty, + place, + address, + lifetime.clone(), + TODO_target_slice_len, + *is_final, + pointer_place.position(), + ) + } + } + } +} + +impl<'p, 'v: 'p, 'tcx: 'v> IntoSnapshotLowerer<'p, 'v, 'tcx> for PlaceToSnapshot { + fn expression_to_snapshot( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + expression: &vir_mid::Expression, + _expect_math_bool: bool, + ) -> SpannedEncodingResult { + assert!(expression.is_place(),); + let ty = expression.get_type(); + self.snap_call(lowerer, ty, expression) + } + + fn variable_to_snapshot( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + variable: &vir_mid::VariableDecl, + ) -> SpannedEncodingResult { + if let Some(label) = &self.old_label { + lowerer.snapshot_variable_version_at_label(variable, label) + } else { + lowerer.current_snapshot_variable_version(variable) + } + } + + fn labelled_old_to_snapshot( + &mut self, + _lowerer: &mut Lowerer<'p, 'v, 'tcx>, + _old: &vir_mid::LabelledOld, + _expect_math_bool: bool, + ) -> SpannedEncodingResult { + todo!() + } + + fn func_app_to_snapshot( + &mut self, + _lowerer: &mut Lowerer<'p, 'v, 'tcx>, + _app: &vir_mid::FuncApp, + _expect_math_bool: bool, + ) -> SpannedEncodingResult { + todo!() + } + + fn acc_predicate_to_snapshot( + &mut self, + _lowerer: &mut Lowerer<'p, 'v, 'tcx>, + _acc_predicate: &vir_mid::AccPredicate, + _expect_math_bool: bool, + ) -> SpannedEncodingResult { + todo!() + } + + fn call_context(&self) -> CallContext { + todo!() + } + + fn owned_non_aliased_snap( + &mut self, + _lowerer: &mut Lowerer<'p, 'v, 'tcx>, + _ty: &vir_mid::Type, + _pointer_place: &vir_mid::Expression, + ) -> SpannedEncodingResult { + todo!() + } + + fn pointer_deref_to_snapshot( + &mut self, + _lowerer: &mut Lowerer<'p, 'v, 'tcx>, + _deref: &vir_mid::Deref, + _base_snapshot: vir_low::Expression, + _expect_math_bool: bool, + ) -> SpannedEncodingResult { + unreachable!("Should be overriden."); + } + + fn push_bound_variables( + &mut self, + _variables: &[vir_mid::VariableDecl], + ) -> SpannedEncodingResult<()> { + todo!() + } + + fn pop_bound_variables(&mut self) -> SpannedEncodingResult<()> { + todo!() + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/expressions/pure_framed.rs b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/expressions/pure_framed.rs new file mode 100644 index 00000000000..33d12dbd12d --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/expressions/pure_framed.rs @@ -0,0 +1,286 @@ +use crate::encoder::{ + errors::SpannedEncodingResult, + high::types::HighTypeEncoderInterface, + middle::core_proof::{ + builtin_methods::CallContext, + footprint::FootprintInterface, + lowerer::{FunctionsLowererInterface, Lowerer}, + pointers::PointersInterface, + snapshots::{IntoSnapshotLowerer, SnapshotValuesInterface}, + }, +}; + +use vir_crate::{ + common::{identifier::WithIdentifier, position::Positioned, validator::Validator}, + low::{self as vir_low}, + middle::{self as vir_mid, operations::ty::Typed}, +}; + +pub(in super::super::super::super::super) struct FramedExpressionToSnapshot<'a> { + framing_variables: &'a [vir_mid::VariableDecl], +} + +/// Information needed to convert a pointer dereference into a snapshot. +enum PointerFramingInfo<'e> { + /// Dereference a single pointer. + Single { + /// The place that holds the permission. + base_place: &'e vir_mid::Expression, + /// The place framed by the permission in invariant. + /// + /// Note: it is rooted at `self` to be able to search for it in the invariant. + framed_place: vir_mid::Expression, + /// The invariant of the struct. + invariant: Vec, + }, + /// Dereference of an element of a range. + RangeElement { encoded_deref: vir_low::Expression }, +} + +impl<'a> FramedExpressionToSnapshot<'a> { + pub(in super::super::super::super::super) fn for_function_body( + framing_variables: &'a [vir_mid::VariableDecl], + ) -> Self { + Self { framing_variables } + } + + /// Find a base of type struct that has an invariant. + fn obtain_invariant<'e>( + &mut self, + lowerer: &mut Lowerer, + expression: &'e vir_mid::Expression, + ) -> SpannedEncodingResult> { + let ty = expression.get_type(); + if ty.is_struct() { + let type_decl = lowerer.encoder.get_type_decl_mid(ty)?; + if let vir_mid::TypeDecl::Struct(vir_mid::type_decl::Struct { + structural_invariant: Some(invariant), + .. + }) = type_decl + { + let self_place = vir_mid::VariableDecl::self_variable(ty.clone()); + Ok(PointerFramingInfo::Single { + base_place: expression, + framed_place: self_place.into(), + invariant, + }) + } else { + unimplemented!("TODO: A proper error message that only permissions from non-nested structs are supported."); + } + } else if let vir_mid::Expression::BuiltinFuncApp(vir_mid::BuiltinFuncApp { + function: + vir_mid::BuiltinFunc::PtrOffset + | vir_mid::BuiltinFunc::PtrWrappingOffset + | vir_mid::BuiltinFunc::PtrAdd + | vir_mid::BuiltinFunc::PtrAddressOffset, + type_arguments: _, + arguments, + return_type: _, + position, + }) = expression + { + let PointerFramingInfo::Single {base_place, framed_place, invariant} = self.obtain_invariant( + lowerer, + &arguments[0], + )? else { + unimplemented!("expression: {expression}"); + }; + let (_, deref_range_fields) = + lowerer.structural_invariant_to_deref_fields(&invariant)?; + let deref_range_field = deref_range_fields + .into_iter() + .find(|deref_range_field| deref_range_field.address == framed_place) + .unwrap(); + // for deref_range_field in deref_range_fields { + // unimplemented!("TODO: {}", deref_range_field.address); + // } + let base_snapshot = self.expression_to_snapshot(lowerer, base_place, false)?; + let index_snapshot = self.expression_to_snapshot(lowerer, &arguments[1], false)?; + let index_int = lowerer.obtain_constant_value( + arguments[1].get_type(), + index_snapshot, + *position, + )?; + // let seq_type = vir_low::Type::seq(deref_range_field.field_type); + let seq_type = deref_range_field.field_type; + let pointer_deref = lowerer.pointer_target_as_snapshot_field( + base_place.get_type(), + &deref_range_field.field_name, + seq_type.clone(), + base_snapshot, + *position, + )?; + let element = vir_low::Expression::container_op( + vir_low::ContainerOpKind::SeqIndex, + seq_type, + vec![pointer_deref, index_int], + *position, + ); + element.assert_valid_debug(); + Ok(PointerFramingInfo::RangeElement { + encoded_deref: element, + }) + // let expression_with_new_parent = vir_mid::Expression::builtin_func_app( + // vir_mid::BuiltinFunc::PtrWrappingOffset, + // type_arguments.clone(), + // vec![parent.into(), arguments[1].clone()], + // return_type.clone(), + // *position, + // ); + // Ok((base_place, expression_with_new_parent, invariant)) + } else { + let PointerFramingInfo::Single {base_place, framed_place, invariant} = self.obtain_invariant( + lowerer, + expression + .get_parent_ref() + .expect("TODO: A proper error message that the permission has to be framed."), + )? else { + unimplemented!("expression: {expression}"); + }; + Ok(PointerFramingInfo::Single { + base_place, + framed_place: expression.with_new_parent(framed_place), + invariant, + }) + } + } +} + +impl<'a, 'p, 'v: 'p, 'tcx: 'v> IntoSnapshotLowerer<'p, 'v, 'tcx> + for FramedExpressionToSnapshot<'a> +{ + fn variable_to_snapshot( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + variable: &vir_mid::VariableDecl, + ) -> SpannedEncodingResult { + Ok(vir_low::VariableDecl { + name: variable.name.clone(), + ty: self.type_to_snapshot(lowerer, &variable.ty)?, + }) + } + + // FIXME: Code duplication with + // prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/pure/mod.rs + fn labelled_old_to_snapshot( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + old: &vir_mid::LabelledOld, + expect_math_bool: bool, + ) -> SpannedEncodingResult { + // In pure contexts values cannot be mutated, so `old` has no effect. + self.expression_to_snapshot(lowerer, &old.base, expect_math_bool) + } + + // FIXME: Code duplication with + // prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/pure/mod.rs + fn func_app_to_snapshot( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + app: &vir_mid::FuncApp, + expect_math_bool: bool, + ) -> SpannedEncodingResult { + let arguments = + self.expression_vec_to_snapshot(lowerer, &app.arguments, expect_math_bool)?; + let return_type = self.type_to_snapshot(lowerer, &app.return_type)?; + let func_app = lowerer.call_pure_function_in_pure_context( + app.get_identifier(), + arguments, + return_type, + app.position, + )?; + let result = vir_low::Expression::DomainFuncApp(func_app); + self.ensure_bool_expression(lowerer, &app.return_type, result, expect_math_bool) + } + + fn acc_predicate_to_snapshot( + &mut self, + _lowerer: &mut Lowerer<'p, 'v, 'tcx>, + _acc_predicate: &vir_mid::AccPredicate, + _expect_math_bool: bool, + ) -> SpannedEncodingResult { + todo!() + } + + fn call_context(&self) -> CallContext { + todo!() + } + + fn owned_non_aliased_snap( + &mut self, + _lowerer: &mut Lowerer<'p, 'v, 'tcx>, + _ty: &vir_mid::Type, + _pointer_place: &vir_mid::Expression, + ) -> SpannedEncodingResult { + todo!() + } + + fn pointer_deref_to_snapshot( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + deref: &vir_mid::Deref, + _base_snapshot: vir_low::Expression, + expect_math_bool: bool, + ) -> SpannedEncodingResult { + // let (base_place, framed_place, invariant) = self.obtain_invariant(lowerer, &deref.base)?; + match self.obtain_invariant(lowerer, &deref.base)? { + PointerFramingInfo::Single { + base_place, + framed_place, + invariant, + } => { + let framed_place = + vir_mid::Expression::deref_no_pos(framed_place, deref.ty.clone()); + let (deref_fields, _) = lowerer.structural_invariant_to_deref_fields(&invariant)?; + let base_snapshot = + self.expression_to_snapshot(lowerer, base_place, expect_math_bool)?; + // for (deref_place, name, ty) in deref_fields { + for deref_field in deref_fields { + if deref_field.place == framed_place { + return lowerer.pointer_target_as_snapshot_field( + base_place.get_type(), + &deref_field.field_name, + deref_field.field_type, + base_snapshot, + deref.position, + ); + } + } + } + PointerFramingInfo::RangeElement { encoded_deref } => { + return Ok(encoded_deref); + } + } + // let framed_place = vir_mid::Expression::deref_no_pos(framed_place, deref.ty.clone()); + // let (deref_fields, deref_range_fields) = + // lowerer.structural_invariant_to_deref_fields(&invariant)?; + // let base_snapshot = self.expression_to_snapshot(lowerer, base_place, expect_math_bool)?; + // // for (deref_place, name, ty) in deref_fields { + // for deref_field in deref_fields { + // if deref_field.place == framed_place { + // return lowerer.pointer_target_as_snapshot_field( + // base_place.get_type(), + // &deref_field.field_name, + // deref_field.field_type, + // base_snapshot, + // deref.position, + // ); + // } + // } + // for deref_range_field in deref_range_fields { + // unimplemented!("TODO: {}", deref_range_field.address); + // } + unimplemented!("TODO: A proper error message that failed to find a framing place.") + } + + fn push_bound_variables( + &mut self, + _variables: &[vir_mid::VariableDecl], + ) -> SpannedEncodingResult<()> { + todo!() + } + + fn pop_bound_variables(&mut self) -> SpannedEncodingResult<()> { + todo!() + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/mod.rs b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/mod.rs index 282b2ffede8..bc278e0b118 100644 --- a/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/mod.rs +++ b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/mod.rs @@ -1,22 +1,43 @@ -//! The traits for converting expressions into snapshots: -//! -//! + `procedure` contains the traits for converting in procedure contexts where -//! we need to use SSA form and `caller_for` for calling pure functions. -//! + `pure` contains the traits for converting in pure contexts such as axioms -//! and pure function definitions where we do not use neither SSA nor -//! `caller_for`. -//! + `builtin_methods` contains the traits for converting in builtin-method -//! contexts where we do not use SSA, but use `caller_for`. +//! The traits for converting expressions into snapshots. +/// Contains the traits for converting in builtin-method contexts where we do +/// not use SSA, but use `caller_for`. +/// +/// FIXME: This probably should be removed. mod builtin_methods; +/// The trait that provides the general skeleton for converting expressions into +/// snapshots. mod common; +/// Contains the traits for converting elements into the snapshots where the +/// context does not matter. Currently, the only example is types. mod context_independent; +/// Contains the traits for converting in procedure contexts where we need to +/// use SSA form and `caller_for` for calling pure functions. +/// +/// FIXME: This probably should be removed. mod procedure; +/// Contains the traits for converting in pure contexts such as axioms and pure +/// function definitions where we do not use neither SSA nor `caller_for`. +/// +/// FIXME: This probably should be removed. mod pure; +/// Contains structs for converting assertions (potentially containing +/// accessibility predicates) to snapshots. Both SSA and non-SSA forms are +/// supported. +mod assertions; +/// Contains structs for converting expressions to snapshots. +mod expressions; +mod utils; pub(in super::super) use self::{ + assertions::{ + AssertionToSnapshotConstructor, PredicateKind, SelfFramingAssertionEncoderState, + SelfFramingAssertionToSnapshot, ValidityAssertionToSnapshot, + }, builtin_methods::IntoBuiltinMethodSnapshot, + common::IntoSnapshotLowerer, context_independent::IntoSnapshot, - procedure::{IntoProcedureBoolExpression, IntoProcedureFinalSnapshot, IntoProcedureSnapshot}, + expressions::{FramedExpressionToSnapshot, PlaceToSnapshot}, + procedure::{IntoProcedureAssertion, IntoProcedureBoolExpression, IntoProcedureSnapshot}, pure::{IntoPureBoolExpression, IntoPureSnapshot}, }; diff --git a/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/procedure/mod.rs b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/procedure/mod.rs index 776b1e8e7c5..52bfae736f2 100644 --- a/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/procedure/mod.rs +++ b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/procedure/mod.rs @@ -2,11 +2,16 @@ //! procedure bodies. Most important difference from `pure` is that this //! encoding uses SSA. -use super::common::IntoSnapshotLowerer; +use super::{common::IntoSnapshotLowerer, PredicateKind}; use crate::encoder::{ errors::SpannedEncodingResult, middle::core_proof::{ + addresses::AddressesInterface, + builtin_methods::CallContext, lowerer::{FunctionsLowererInterface, Lowerer}, + places::PlacesInterface, + pointers::PointersInterface, + predicates::{PredicatesMemoryBlockInterface, PredicatesOwnedInterface}, references::ReferencesInterface, snapshots::SnapshotVariablesInterface, }, @@ -14,27 +19,72 @@ use crate::encoder::{ use vir_crate::{ common::identifier::WithIdentifier, low::{self as vir_low}, - middle::{self as vir_mid, operations::ty::Typed}, + middle::{ + self as vir_mid, + operations::{quantifiers::BoundVariableStack, ty::Typed}, + }, }; mod traits; pub(in super::super::super) use self::traits::{ - IntoProcedureBoolExpression, IntoProcedureFinalSnapshot, IntoProcedureSnapshot, + IntoProcedureAssertion, IntoProcedureBoolExpression, IntoProcedureSnapshot, }; -#[derive(Default)] -struct ProcedureSnapshot { +pub(in super::super::super::super) struct ProcedureSnapshot { old_label: Option, deref_to_final: bool, + is_assertion: bool, + in_heap_assertions: Vec, + predicate_kind: PredicateKind, + bound_variable_stack: BoundVariableStack, +} + +impl ProcedureSnapshot { + pub(in super::super) fn new_for_owned() -> Self { + Self { + old_label: None, + deref_to_final: false, + is_assertion: false, + in_heap_assertions: Vec::new(), + predicate_kind: PredicateKind::Owned, + bound_variable_stack: Default::default(), + } + } } impl<'p, 'v: 'p, 'tcx: 'v> IntoSnapshotLowerer<'p, 'v, 'tcx> for ProcedureSnapshot { + fn expression_to_snapshot( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + expression: &vir_mid::Expression, + expect_math_bool: bool, + ) -> SpannedEncodingResult { + if !lowerer.use_heap_variable()? + && expression.is_place() + && expression.get_last_dereferenced_pointer().is_some() + { + // let address = lowerer.encode_expression_as_place_address(expression)?; + // let place = lowerer.encode_expression_as_place(expression)?; + // let root_address = lowerer.extract_root_address(expression)?; + let ty = expression.get_type(); + // return lowerer.owned_non_aliased_snap(CallContext::Procedure, ty, ty, place, root_address); + return self.owned_non_aliased_snap(lowerer, ty, expression); + } + self.expression_to_snapshot_impl(lowerer, expression, expect_math_bool) + } + fn variable_to_snapshot( &mut self, lowerer: &mut Lowerer<'p, 'v, 'tcx>, variable: &vir_mid::VariableDecl, ) -> SpannedEncodingResult { + if self.bound_variable_stack.contains(variable) { + return Ok(vir_low::VariableDecl::new( + variable.name.clone(), + self.type_to_snapshot(lowerer, &variable.ty)?, + )); + } if let Some(label) = &self.old_label { lowerer.snapshot_variable_version_at_label(variable, label) } else { @@ -87,17 +137,266 @@ impl<'p, 'v: 'p, 'tcx: 'v> IntoSnapshotLowerer<'p, 'v, 'tcx> for ProcedureSnapsh lowerer.reference_target_final_snapshot( deref.base.get_type(), base_snapshot, - Default::default(), + deref.position, )? } else { let base_snapshot = self.expression_to_snapshot(lowerer, &deref.base, expect_math_bool)?; - lowerer.reference_target_current_snapshot( - deref.base.get_type(), - base_snapshot, - Default::default(), - )? + if deref.base.get_type().is_reference() { + lowerer.reference_target_current_snapshot( + deref.base.get_type(), + base_snapshot, + deref.position, + )? + } else { + lowerer.pointer_target_snapshot( + deref.base.get_type(), + &self.old_label, + base_snapshot, + deref.position, + )? + } }; self.ensure_bool_expression(lowerer, deref.get_type(), result, expect_math_bool) } + + fn acc_predicate_to_snapshot( + &mut self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + acc_predicate: &vir_mid::AccPredicate, + _expect_math_bool: bool, + ) -> SpannedEncodingResult { + assert!(self.is_assertion); + // fn in_heap<'p, 'v, 'tcx>( + // old_label: &Option, + // place: &vir_mid::Expression, + // lowerer: &mut Lowerer<'p, 'v, 'tcx>, + // ) -> SpannedEncodingResult { + // let in_heap = if let Some(pointer_place) = place.get_last_dereferenced_pointer() { + // let pointer = pointer_place.to_procedure_snapshot(lowerer)?; + // let address = + // lowerer.pointer_address(pointer_place.get_type(), pointer, place.position())?; + // let heap = lowerer.heap_variable_version_at_label(old_label)?; + // vir_low::Expression::container_op_no_pos( + // vir_low::ContainerOpKind::MapContains, + // heap.ty.clone(), + // vec![heap.into(), address], + // ) + // } else { + // unimplemented!("TODO: Proper error message: {:?}", place); + // }; + // Ok(in_heap) + // } + let expression = match &*acc_predicate.predicate { + vir_mid::Predicate::OwnedNonAliased(predicate) => { + let _ty = predicate.place.get_type(); + let _place = lowerer.encode_expression_as_place(&predicate.place)?; + let _root_address = lowerer.extract_root_address(&predicate.place)?; + let _snapshot = predicate.place.to_procedure_snapshot(lowerer)?; // FIXME: This is probably wrong. It should take into account the current old. + // if lowerer.use_heap_variable()? { + // let in_heap = in_heap(&self.old_label, &predicate.place, lowerer)?; + // self.in_heap_assertions.push(in_heap); + // } + // let acc = + unimplemented!(); + // lowerer.owned_aliased( + // CallContext::Procedure, + // ty, + // ty, + // place, + // root_address, + // snapshot, + // None, + // )? + // ; + // vir_low::Expression::and(in_heap, acc) + } + vir_mid::Predicate::OwnedRange(_predicate) => { + unimplemented!(); + // let ty = predicate.address.get_type(); + // let address = predicate.address.to_procedure_snapshot(lowerer)?; + // let start_index = predicate.start_index.to_procedure_snapshot(lowerer)?; + // let end_index = predicate.end_index.to_procedure_snapshot(lowerer)?; + // lowerer.owned_aliased_range( + // CallContext::Procedure, + // ty, + // ty, + // address, + // start_index, + // end_index, + // None, + // )? + } + vir_mid::Predicate::MemoryBlockHeap(predicate) => { + let place = lowerer.encode_expression_as_place_address(&predicate.address)?; + let size = predicate.size.to_procedure_snapshot(lowerer)?; + // if lowerer.use_heap_variable()? { + // let in_heap = in_heap(&self.old_label, &predicate.address, lowerer)?; + // self.in_heap_assertions.push(in_heap); + // } + // let acc = + lowerer.encode_memory_block_acc(place, size, acc_predicate.position)? + //; + // vir_low::Expression::and(in_heap, acc) + } + vir_mid::Predicate::MemoryBlockHeapRange(_predicate) => { + unimplemented!(); + // let pointer_value = predicate.address.to_procedure_snapshot(lowerer)?; + // let address = lowerer.pointer_address( + // predicate.address.get_type(), + // pointer_value, + // predicate.position, + // )?; + // let size = predicate.size.to_procedure_snapshot(lowerer)?; + // let start_index = predicate.start_index.to_procedure_snapshot(lowerer)?; + // let end_index = predicate.end_index.to_procedure_snapshot(lowerer)?; + // lowerer.encode_memory_block_range_acc( + // address, + // size, + // start_index, + // end_index, + // acc_predicate.position, + // )? + } + vir_mid::Predicate::MemoryBlockHeapDrop(predicate) => { + let place = lowerer.encode_expression_as_place_address(&predicate.address)?; // FIXME: This looks very wrong. + let size = predicate.size.to_procedure_snapshot(lowerer)?; + // if lowerer.use_heap_variable()? { + // let in_heap = in_heap(&self.old_label, &predicate.address, lowerer)?; + // self.in_heap_assertions.push(in_heap); + // } + // let acc = + lowerer.encode_memory_block_heap_drop_acc(place, size, acc_predicate.position)? + // ; + // vir_low::Expression::and(in_heap, acc) + } + _ => unimplemented!("{acc_predicate}"), + }; + Ok(expression) + } + + fn owned_non_aliased_snap( + &mut self, + _lowerer: &mut Lowerer<'p, 'v, 'tcx>, + _ty: &vir_mid::Type, + _pointer_place: &vir_mid::Expression, + ) -> SpannedEncodingResult { + unimplemented!(); + // let place = lowerer.encode_expression_as_place(pointer_place)?; + // let root_address = lowerer.extract_root_address(pointer_place)?; + // match &self.predicate_kind { + // PredicateKind::Owned => lowerer.owned_non_aliased_snap( + // CallContext::Procedure, + // ty, + // ty, + // place, + // root_address, + // pointer_place.position(), + // ), + // PredicateKind::FracRef { lifetime } => todo!(), + // PredicateKind::UniqueRef { lifetime, is_final } => { + // let TODO_target_slice_len = None; + // lowerer.unique_ref_snap( + // CallContext::Procedure, + // ty, + // ty, + // place, + // root_address, + // lifetime.clone(), + // TODO_target_slice_len, + // *is_final, + // ) + // } + // } + // if let Some(reference_place) = pointer_place.get_first_dereferenced_reference() { + // let vir_mid::Type::Reference(reference_type) = reference_place.get_type() else { + // unreachable!() + // }; + // let TODO_target_slice_len = None; + // let lifetime = lowerer + // .encode_lifetime_const_into_procedure_variable(reference_type.lifetime.clone())?; + // match reference_type.uniqueness { + // vir_mid::ty::Uniqueness::Unique => lowerer.unique_ref_snap( + // CallContext::Procedure, + // ty, + // ty, + // place, + // root_address, + // lifetime.into(), + // TODO_target_slice_len, + // self.deref_to_final, + // ), + // vir_mid::ty::Uniqueness::Shared => lowerer.frac_ref_snap( + // CallContext::Procedure, + // ty, + // ty, + // place, + // root_address, + // lifetime.into(), + // TODO_target_slice_len, + // ), + // } + // } else { + // lowerer.owned_non_aliased_snap( + // CallContext::Procedure, + // ty, + // ty, + // place, + // root_address, + // pointer_place.position(), + // ) + // } + // // TODO: Check whether the place is behind a shared/mutable reference and use the appropriate function + // eprintln!("pointer_place: {}", pointer_place); + // eprintln!("pointer_place: {:?}", pointer_place); + } + + fn call_context(&self) -> CallContext { + CallContext::Procedure + } + + fn push_bound_variables( + &mut self, + variables: &[vir_mid::VariableDecl], + ) -> SpannedEncodingResult<()> { + self.bound_variable_stack.push(variables); + Ok(()) + } + + fn pop_bound_variables(&mut self) -> SpannedEncodingResult<()> { + self.bound_variable_stack.pop(); + Ok(()) + } + + // fn unfolding_to_snapshot( + // &mut self, + // lowerer: &mut Lowerer<'p, 'v, 'tcx>, + // unfolding: &vir_mid::Unfolding, + // expect_math_bool: bool, + // ) -> SpannedEncodingResult { + // let predicate = match &*unfolding.predicate { + // vir_mid::Predicate::OwnedNonAliased(predicate) => { + // let ty = predicate.place.get_type(); + // lowerer.mark_owned_predicate_as_unfolded(ty)?; + // let place = lowerer.encode_expression_as_place(&predicate.place)?; + // let root_address = lowerer.extract_root_address(&predicate.place)?; + // let snapshot = predicate.place.to_procedure_snapshot(lowerer)?; // FIXME: This is probably wrong. It should take into account the current old. + // lowerer + // .owned_non_aliased( + // CallContext::Procedure, + // ty, + // ty, + // place, + // root_address, + // snapshot, + // None, + // )? + // .unwrap_predicate_access_predicate() + // } + // _ => unimplemented!("{unfolding}"), + // }; + // let body = self.expression_to_snapshot(lowerer, &unfolding.body, expect_math_bool)?; + // let expression = vir_low::Expression::unfolding(predicate, body, unfolding.position); + // Ok(expression) + // } } diff --git a/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/procedure/traits.rs b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/procedure/traits.rs index 30a50fbb9a1..76cd89c4145 100644 --- a/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/procedure/traits.rs +++ b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/procedure/traits.rs @@ -6,6 +6,7 @@ use crate::encoder::{ middle::core_proof::{lowerer::Lowerer, snapshots::into_snapshot::common::IntoSnapshotLowerer}, }; use vir_crate::{ + common::expression::ExpressionIterator, low::{self as vir_low}, middle::{self as vir_mid}, }; @@ -25,7 +26,35 @@ impl IntoProcedureBoolExpression for vir_mid::Expression { &self, lowerer: &mut Lowerer<'p, 'v, 'tcx>, ) -> SpannedEncodingResult { - ProcedureSnapshot::default().expression_to_snapshot(lowerer, self, true) + ProcedureSnapshot::new_for_owned().expression_to_snapshot(lowerer, self, true) + } +} + +/// Converts `self` into assertion that evaluates to a Viper Bool. +pub(in super::super::super::super) trait IntoProcedureAssertion { + type Target; + fn to_procedure_assertion<'p, 'v: 'p, 'tcx: 'v>( + &self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + ) -> SpannedEncodingResult; +} + +impl IntoProcedureAssertion for vir_mid::Expression { + type Target = vir_low::Expression; + fn to_procedure_assertion<'p, 'v: 'p, 'tcx: 'v>( + &self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + ) -> SpannedEncodingResult { + let mut snapshot_encoder = ProcedureSnapshot { + is_assertion: true, + ..ProcedureSnapshot::new_for_owned() + }; + let expression = snapshot_encoder.expression_to_snapshot(lowerer, self, true)?; + Ok(snapshot_encoder + .in_heap_assertions + .into_iter() + .chain(std::iter::once(expression)) + .conjoin()) } } @@ -43,7 +72,7 @@ impl IntoProcedureSnapshot for vir_mid::VariableDecl { &self, lowerer: &mut Lowerer<'p, 'v, 'tcx>, ) -> SpannedEncodingResult { - ProcedureSnapshot::default().variable_to_snapshot(lowerer, self) + ProcedureSnapshot::new_for_owned().variable_to_snapshot(lowerer, self) } } @@ -53,7 +82,7 @@ impl IntoProcedureSnapshot for vir_mid::Expression { &self, lowerer: &mut Lowerer<'p, 'v, 'tcx>, ) -> SpannedEncodingResult { - ProcedureSnapshot::default().expression_to_snapshot(lowerer, self, false) + ProcedureSnapshot::new_for_owned().expression_to_snapshot(lowerer, self, false) } } @@ -87,7 +116,7 @@ impl IntoProcedureFinalSnapshot for vir_mid::Expression { ) -> SpannedEncodingResult { let mut snapshot_encoder = ProcedureSnapshot { deref_to_final: true, - ..ProcedureSnapshot::default() + ..ProcedureSnapshot::new_for_owned() }; snapshot_encoder.expression_to_snapshot(lowerer, self, false) } diff --git a/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/pure/mod.rs b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/pure/mod.rs index aa9561a9468..b712d2964e5 100644 --- a/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/pure/mod.rs +++ b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/pure/mod.rs @@ -4,7 +4,10 @@ use super::common::IntoSnapshotLowerer; use crate::encoder::{ errors::SpannedEncodingResult, - middle::core_proof::lowerer::{FunctionsLowererInterface, Lowerer}, + middle::core_proof::{ + builtin_methods::CallContext, + lowerer::{FunctionsLowererInterface, Lowerer}, + }, }; use vir_crate::{ common::identifier::WithIdentifier, @@ -16,7 +19,11 @@ mod traits; pub(in super::super::super) use self::traits::{IntoPureBoolExpression, IntoPureSnapshot}; -struct PureSnapshot; +#[derive(Default)] +struct PureSnapshot { + /// Assume that all pointer accesses are safe. + assume_pointers_to_be_framed: bool, +} impl<'p, 'v: 'p, 'tcx: 'v> IntoSnapshotLowerer<'p, 'v, 'tcx> for PureSnapshot { fn variable_to_snapshot( @@ -58,4 +65,131 @@ impl<'p, 'v: 'p, 'tcx: 'v> IntoSnapshotLowerer<'p, 'v, 'tcx> for PureSnapshot { // In pure contexts values cannot be mutated, so `old` has no effect. self.expression_to_snapshot(lowerer, &old.base, expect_math_bool) } + + fn pointer_deref_to_snapshot( + &mut self, + _lowerer: &mut Lowerer<'p, 'v, 'tcx>, + deref: &vir_mid::Deref, + base_snapshot: vir_low::Expression, + _expect_math_bool: bool, + ) -> SpannedEncodingResult { + // FIXME: Delete. + assert!(self.assume_pointers_to_be_framed); + eprintln!("deref: {deref}"); + eprintln!("base_snapshot: {base_snapshot}"); + unimplemented!(); + } + + // fn deref_to_snapshot( + // &mut self, + // lowerer: &mut Lowerer<'p, 'v, 'tcx>, + // deref: &vir_mid::Deref, + // expect_math_bool: bool, + // ) -> SpannedEncodingResult { + // let base_snapshot = self.expression_to_snapshot(lowerer, &deref.base, expect_math_bool)?; + // let ty = deref.base.get_type(); + // let result = if ty.is_reference() { + // lowerer.reference_target_current_snapshot(ty, base_snapshot, deref.position)? + // } else { + // unreachable!(); + // // unimplemented!("TODO: to double-check that this is actually used (and in a correct way)"); + // // This most likely should be unreachable. In axioms we should use snapshot variables + // // instead. + // // let heap = vir_low::VariableDecl::new("pure_heap$", lowerer.heap_type()?); + // // lowerer.pointer_target_snapshot_in_heap( + // // deref.base.get_type(), + // // heap, + // // base_snapshot, + // // deref.position, + // // )? + // // lowerer.pointer_target_snapshot( + // // deref.base.get_type(), + // // &None, + // // base_snapshot, + // // deref.position, + // // )? + // }; + // self.ensure_bool_expression(lowerer, deref.get_type(), result, expect_math_bool) + // } + + // FIXME: Mark as unreachable. + fn acc_predicate_to_snapshot( + &mut self, + _lowerer: &mut Lowerer<'p, 'v, 'tcx>, + _acc_predicate: &vir_mid::AccPredicate, + _expect_math_bool: bool, + ) -> SpannedEncodingResult { + unimplemented!("FIXME: Delete"); + // assert!(self.is_assertion); + // let expression = match &*acc_predicate.predicate { + // vir_mid::Predicate::OwnedNonAliased(predicate) => { + // eprintln!("pure predicate: {}", predicate); + // let ty = predicate.place.get_type(); + // let place = lowerer.encode_expression_as_place(&predicate.place)?; + // // let root_address = lowerer.extract_root_address(&predicate.place)?; + // let root_address = true.into(); + // // let snapshot = predicate.place.to_pure_snapshot(lowerer)?; + // let snapshot = true.into(); + // let acc = lowerer.owned_aliased( + // CallContext::Procedure, + // ty, + // ty, + // place, + // root_address, + // snapshot, + // None, + // )?; + // eprintln!(" → {}", acc); + // acc + // } + // vir_mid::Predicate::MemoryBlockHeap(predicate) => { + // // let place = lowerer.encode_expression_as_place_address(&predicate.address)?; + // let place = true.into(); + // let size = predicate.size.to_pure_snapshot(lowerer)?; + // lowerer.encode_memory_block_acc(place, size, acc_predicate.position)? + // } + // vir_mid::Predicate::MemoryBlockHeapDrop(predicate) => { + // // let place = lowerer.encode_expression_as_place_address(&predicate.address)?; + // let place = true.into(); + // // let size = predicate.size.to_pure_snapshot(lowerer)?; + // let size = true.into(); + // lowerer.encode_memory_block_heap_drop_acc(place, size, acc_predicate.position)? + // } + // _ => unimplemented!("{acc_predicate}"), + // }; + // Ok(expression) + } + + fn owned_non_aliased_snap( + &mut self, + _lowerer: &mut Lowerer<'p, 'v, 'tcx>, + _ty: &vir_mid::Type, + _pointer_snapshot: &vir_mid::Expression, + ) -> SpannedEncodingResult { + unimplemented!() + } + + // fn unfolding_to_snapshot( + // &mut self, + // lowerer: &mut Lowerer<'p, 'v, 'tcx>, + // unfolding: &vir_mid::Unfolding, + // expect_math_bool: bool, + // ) -> SpannedEncodingResult { + // todo!() + // } + + fn call_context(&self) -> CallContext { + todo!() + } + + fn push_bound_variables( + &mut self, + _variables: &[vir_mid::VariableDecl], + ) -> SpannedEncodingResult<()> { + todo!() + } + + fn pop_bound_variables(&mut self) -> SpannedEncodingResult<()> { + todo!() + } } diff --git a/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/pure/traits.rs b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/pure/traits.rs index 3cda221b325..c06e5b9c6e1 100644 --- a/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/pure/traits.rs +++ b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/pure/traits.rs @@ -28,7 +28,7 @@ impl IntoPureBoolExpression for vir_mid::Expression { &self, lowerer: &mut Lowerer<'p, 'v, 'tcx>, ) -> SpannedEncodingResult { - PureSnapshot.expression_to_snapshot(lowerer, self, true) + PureSnapshot::default().expression_to_snapshot(lowerer, self, true) } } @@ -38,7 +38,31 @@ impl IntoPureBoolExpression for Vec { &self, lowerer: &mut Lowerer<'p, 'v, 'tcx>, ) -> SpannedEncodingResult { - PureSnapshot.expression_vec_to_snapshot(lowerer, self, true) + PureSnapshot::default().expression_vec_to_snapshot(lowerer, self, true) + } +} + +/// Converts `self` into expression that evaluates to a snapshot. It assumes +/// that all pointers can be safely dereferenced. +pub(in super::super::super::super) trait IntoFramedPureSnapshot { + type Target; + fn to_framed_pure_snapshot<'p, 'v: 'p, 'tcx: 'v>( + &self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + ) -> SpannedEncodingResult; +} + +impl IntoFramedPureSnapshot for vir_mid::Expression { + type Target = vir_low::Expression; + fn to_framed_pure_snapshot<'p, 'v: 'p, 'tcx: 'v>( + &self, + lowerer: &mut Lowerer<'p, 'v, 'tcx>, + ) -> SpannedEncodingResult { + let mut snapshot_encoder = PureSnapshot { + assume_pointers_to_be_framed: true, + ..PureSnapshot::default() + }; + snapshot_encoder.expression_to_snapshot(lowerer, self, true) } } @@ -57,7 +81,7 @@ impl IntoPureSnapshot for vir_mid::Expression { &self, lowerer: &mut Lowerer<'p, 'v, 'tcx>, ) -> SpannedEncodingResult { - PureSnapshot.expression_to_snapshot(lowerer, self, false) + PureSnapshot::default().expression_to_snapshot(lowerer, self, false) } } @@ -69,7 +93,7 @@ impl IntoPureSnapshot for Vec { ) -> SpannedEncodingResult { let mut variables = Vec::new(); for variable in self { - variables.push(PureSnapshot.variable_to_snapshot(lowerer, variable)?); + variables.push(PureSnapshot::default().variable_to_snapshot(lowerer, variable)?); } Ok(variables) } @@ -81,7 +105,7 @@ impl IntoPureSnapshot for vir_mid::VariableDecl { &self, lowerer: &mut Lowerer<'p, 'v, 'tcx>, ) -> SpannedEncodingResult { - PureSnapshot.variable_to_snapshot(lowerer, self) + PureSnapshot::default().variable_to_snapshot(lowerer, self) } } diff --git a/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/utils/mod.rs b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/utils/mod.rs new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/snapshots/into_snapshot/utils/mod.rs @@ -0,0 +1 @@ + diff --git a/prusti-viper/src/encoder/middle/core_proof/snapshots/mod.rs b/prusti-viper/src/encoder/middle/core_proof/snapshots/mod.rs index 51806b98828..141c1fab421 100644 --- a/prusti-viper/src/encoder/middle/core_proof/snapshots/mod.rs +++ b/prusti-viper/src/encoder/middle/core_proof/snapshots/mod.rs @@ -9,13 +9,16 @@ mod values; mod variables; pub(super) use self::{ - adts::SnapshotAdtsInterface, + adts::{SnapshotAdtsInterface, SnapshotDomainInfo, SnapshotDomainsInfo}, builtin_functions::BuiltinFunctionsInterface, bytes::SnapshotBytesInterface, domains::SnapshotDomainsInterface, into_snapshot::{ - IntoBuiltinMethodSnapshot, IntoProcedureBoolExpression, IntoProcedureFinalSnapshot, - IntoProcedureSnapshot, IntoPureBoolExpression, IntoPureSnapshot, IntoSnapshot, + AssertionToSnapshotConstructor, FramedExpressionToSnapshot, IntoBuiltinMethodSnapshot, + IntoProcedureAssertion, IntoProcedureBoolExpression, IntoProcedureSnapshot, + IntoPureBoolExpression, IntoPureSnapshot, IntoSnapshot, IntoSnapshotLowerer, + PlaceToSnapshot, PredicateKind, SelfFramingAssertionToSnapshot, + ValidityAssertionToSnapshot, }, state::SnapshotsState, validity::{valid_call, valid_call2, SnapshotValidityInterface}, diff --git a/prusti-viper/src/encoder/middle/core_proof/snapshots/state.rs b/prusti-viper/src/encoder/middle/core_proof/snapshots/state.rs index 3d7591b02b9..8b85dd85fce 100644 --- a/prusti-viper/src/encoder/middle/core_proof/snapshots/state.rs +++ b/prusti-viper/src/encoder/middle/core_proof/snapshots/state.rs @@ -1,5 +1,4 @@ -use super::variables::{AllVariablesMap, VariableVersionMap}; - +use super::{adts::SnapshotDomainsInfo, into_snapshot::SelfFramingAssertionEncoderState}; use rustc_hash::{FxHashMap, FxHashSet}; use std::collections::BTreeMap; use vir_crate::{ @@ -9,16 +8,25 @@ use vir_crate::{ #[derive(Default)] pub(in super::super) struct SnapshotsState { + /// FIXME: The visibility should be `pub(super)`. + pub(in super::super) snapshot_domains_info: SnapshotDomainsInfo, /// Used for decoding domain names into original types. pub(super) domain_types: BTreeMap, /// The list of types for which `to_bytes` was encoded. pub(super) encoded_to_bytes: FxHashSet, /// The list of types for which sequence_repeat_constructor was encoded. pub(super) encoded_sequence_repeat_constructor: FxHashSet, - pub(super) all_variables: AllVariablesMap, - pub(super) variables: BTreeMap, - pub(super) variables_at_label: BTreeMap, - pub(super) current_variables: Option, + pub(super) ssa_state: vir_low::ssa::SSAState, + // pub(super) all_variables: AllVariablesMap, + // pub(super) variables: BTreeMap, + // pub(super) variables_at_label: BTreeMap, + // pub(super) current_variables: Option, /// Mapping from low types to their domain names. pub(super) type_domains: FxHashMap, + pub(super) self_framing_assertion_encoder_state: SelfFramingAssertionEncoderState, +} +impl SnapshotsState { + pub(in super::super) fn destruct(self) -> SnapshotDomainsInfo { + self.snapshot_domains_info + } } diff --git a/prusti-viper/src/encoder/middle/core_proof/snapshots/validity/interface.rs b/prusti-viper/src/encoder/middle/core_proof/snapshots/validity/interface.rs index 70cc436c562..6ab29932790 100644 --- a/prusti-viper/src/encoder/middle/core_proof/snapshots/validity/interface.rs +++ b/prusti-viper/src/encoder/middle/core_proof/snapshots/validity/interface.rs @@ -54,6 +54,12 @@ pub(in super::super::super) trait SnapshotValidityInterface { &mut self, domain_name: &str, parameters: Vec, + ) -> SpannedEncodingResult<()>; + fn encode_validity_axioms_struct_with_invariant( + &mut self, + domain_name: &str, + parameters: Vec, + parameters_with_validity: usize, invariant: vir_low::Expression, ) -> SpannedEncodingResult<()>; fn encode_validity_axioms_struct_alternative_constructor( @@ -61,6 +67,7 @@ pub(in super::super::super) trait SnapshotValidityInterface { domain_name: &str, variant_name: &str, parameters: Vec, + parameters_with_validity: usize, invariant: vir_low::Expression, ) -> SpannedEncodingResult<()>; /// `variants` is `(variant_name, variant_domain, discriminant)`. @@ -120,31 +127,56 @@ impl<'p, 'v: 'p, 'tcx: 'v> SnapshotValidityInterface for Lowerer<'p, 'v, 'tcx> { ) -> SpannedEncodingResult<()> { use vir_low::macros::*; let parameters = vars! { value: {parameter_type}}; - self.encode_validity_axioms_struct(domain_name, parameters, invariant) + let parameters_with_validity = parameters.len(); + self.encode_validity_axioms_struct_with_invariant( + domain_name, + parameters, + parameters_with_validity, + invariant, + ) } fn encode_validity_axioms_struct( &mut self, domain_name: &str, parameters: Vec, + ) -> SpannedEncodingResult<()> { + let parameters_with_validity = parameters.len(); + self.encode_validity_axioms_struct_with_invariant( + domain_name, + parameters, + parameters_with_validity, + true.into(), + ) + } + fn encode_validity_axioms_struct_with_invariant( + &mut self, + domain_name: &str, + parameters: Vec, + parameters_with_validity: usize, invariant: vir_low::Expression, ) -> SpannedEncodingResult<()> { self.encode_validity_axioms_struct_alternative_constructor( domain_name, "", parameters, + parameters_with_validity, invariant, ) } + /// `parameters_with_validity` – how many of `parameters` should have a + /// conjoined validity call. For all Rust types without permissions in their + /// structural invariants, `parameters_with_validity == parameters.len()`. fn encode_validity_axioms_struct_alternative_constructor( &mut self, domain_name: &str, variant_name: &str, parameters: Vec, + parameters_with_validity: usize, invariant: vir_low::Expression, ) -> SpannedEncodingResult<()> { use vir_low::macros::*; let mut valid_parameters = Vec::new(); - for parameter in ¶meters { + for parameter in parameters.iter().take(parameters_with_validity) { if let Some(domain_name) = self.get_non_primitive_domain(¶meter.ty) { let domain_name = domain_name.to_string(); valid_parameters @@ -159,7 +191,8 @@ impl<'p, 'v: 'p, 'tcx: 'v> SnapshotValidityInterface for Lowerer<'p, 'v, 'tcx> { .map(|parameter| parameter.clone().into()) .collect(), )?; - let valid_constructor = self.encode_snapshot_valid_call(domain_name, constructor_call)?; + let valid_constructor = + self.encode_snapshot_valid_call(domain_name, constructor_call.clone())?; if parameters.is_empty() { let axiom = vir_low::DomainAxiomDecl { comment: None, @@ -181,8 +214,11 @@ impl<'p, 'v: 'p, 'tcx: 'v> SnapshotValidityInterface for Lowerer<'p, 'v, 'tcx> { // parameters, the bottom-up and top-down axioms are equivalent. let mut top_down_validity_expression = validity_expression.clone(); var_decls! { snapshot: {vir_low::Type::domain(domain_name.to_string())}}; + let snapshot_expression = snapshot.clone().into(); + top_down_validity_expression = + top_down_validity_expression.replace_self(&snapshot_expression); let valid_constructor = - self.encode_snapshot_valid_call(domain_name, snapshot.clone().into())?; + self.encode_snapshot_valid_call(domain_name, snapshot_expression)?; let mut triggers = Vec::new(); for parameter in ¶meters { if self.get_non_primitive_domain(¶meter.ty).is_some() { @@ -214,6 +250,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> SnapshotValidityInterface for Lowerer<'p, 'v, 'tcx> { }; self.declare_axiom(domain_name, axiom_top_down)?; } + let bottom_up_validity_expression = validity_expression.replace_self(&constructor_call); let axiom_bottom_up_body = { let mut trigger = vec![valid_constructor.clone()]; trigger.extend(valid_parameters.clone()); @@ -221,7 +258,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> SnapshotValidityInterface for Lowerer<'p, 'v, 'tcx> { parameters, vec![vir_low::Trigger::new(trigger)], expr! { - [ valid_constructor ] == [ validity_expression ] + [ valid_constructor ] == [ bottom_up_validity_expression ] }, ) }; diff --git a/prusti-viper/src/encoder/middle/core_proof/snapshots/values/interface.rs b/prusti-viper/src/encoder/middle/core_proof/snapshots/values/interface.rs index 98af319413c..14abba10169 100644 --- a/prusti-viper/src/encoder/middle/core_proof/snapshots/values/interface.rs +++ b/prusti-viper/src/encoder/middle/core_proof/snapshots/values/interface.rs @@ -2,13 +2,14 @@ use crate::encoder::{ errors::SpannedEncodingResult, middle::core_proof::{ addresses::AddressesInterface, + function_gas::FunctionGasInterface, lowerer::{DomainsLowererInterface, Lowerer}, snapshots::{IntoSnapshot, SnapshotAdtsInterface, SnapshotDomainsInterface}, types::TypesInterface, }, }; use vir_crate::{ - common::expression::UnaryOperationHelpers, + common::{expression::UnaryOperationHelpers, validator::Validator}, low::{self as vir_low, operations::ty::Typed}, middle::{self as vir_mid}, }; @@ -23,6 +24,14 @@ pub(in super::super::super) trait SnapshotValuesInterface { argument: vir_low::Expression, position: vir_mid::Position, ) -> SpannedEncodingResult; + fn obtain_parameter_snapshot( + &mut self, + base_type: &vir_mid::Type, + parameter_name: &str, + parameter_type: vir_low::Type, + base_snapshot: vir_low::Expression, + position: vir_mid::Position, + ) -> SpannedEncodingResult; fn obtain_struct_field_snapshot( &mut self, base_type: &vir_mid::Type, @@ -93,6 +102,16 @@ pub(in super::super::super) trait SnapshotValuesInterface { argument: vir_low::Expression, position: vir_mid::Position, ) -> SpannedEncodingResult; + + // Extensionality: trigger the knowledge that two snapshots are equal. + + fn snapshots_extensionality_equal_call( + &mut self, + ty: &vir_mid::Type, + left: vir_low::Expression, + right: vir_low::Expression, + position: vir_mid::Position, + ) -> SpannedEncodingResult; } impl<'p, 'v: 'p, 'tcx: 'v> SnapshotValuesInterface for Lowerer<'p, 'v, 'tcx> { @@ -118,19 +137,46 @@ impl<'p, 'v: 'p, 'tcx: 'v> SnapshotValuesInterface for Lowerer<'p, 'v, 'tcx> { position, ) } - fn obtain_struct_field_snapshot( + fn obtain_parameter_snapshot( &mut self, base_type: &vir_mid::Type, - field: &vir_mid::FieldDecl, + parameter_name: &str, + parameter_type: vir_low::Type, base_snapshot: vir_low::Expression, position: vir_mid::Position, ) -> SpannedEncodingResult { let domain_name = self.encode_snapshot_domain_name(base_type)?; - let return_type = field.ty.to_snapshot(self)?; + let return_type = parameter_type; Ok(self - .snapshot_destructor_struct_call(&domain_name, &field.name, return_type, base_snapshot)? + .snapshot_destructor_struct_call( + &domain_name, + parameter_name, + return_type, + base_snapshot, + )? .set_default_position(position)) } + fn obtain_struct_field_snapshot( + &mut self, + base_type: &vir_mid::Type, + field: &vir_mid::FieldDecl, + base_snapshot: vir_low::Expression, + position: vir_mid::Position, + ) -> SpannedEncodingResult { + let parameter_type = field.ty.to_snapshot(self)?; + self.obtain_parameter_snapshot( + base_type, + &field.name, + parameter_type, + base_snapshot, + position, + ) + // let domain_name = self.encode_snapshot_domain_name(base_type)?; + // let return_type = field.ty.to_snapshot(self)?; + // Ok(self + // .snapshot_destructor_struct_call(&domain_name, &field.name, return_type, base_snapshot)? + // .set_default_position(position)) + } fn obtain_enum_variant_snapshot( &mut self, base_type: &vir_mid::Type, @@ -187,12 +233,14 @@ impl<'p, 'v: 'p, 'tcx: 'v> SnapshotValuesInterface for Lowerer<'p, 'v, 'tcx> { index: vir_low::Expression, position: vir_mid::Position, ) -> SpannedEncodingResult { - Ok(vir_low::Expression::container_op( + let expression = vir_low::Expression::container_op( vir_low::expression::ContainerOpKind::SeqIndex, base_snapshot.get_type().clone(), vec![base_snapshot, index], position, - )) + ); + expression.assert_valid_debug(); + Ok(expression) } fn construct_constant_snapshot( &mut self, @@ -209,7 +257,9 @@ impl<'p, 'v: 'p, 'tcx: 'v> SnapshotValuesInterface for Lowerer<'p, 'v, 'tcx> { vir_mid::Type::Reference(_) => self.address_type()?, x => unimplemented!("{:?}", x), }; - vir_low::operations::ty::Typed::set_type(&mut argument, low_type); + if !ty.is_bool() { + vir_low::operations::ty::Typed::set_type(&mut argument, low_type); + } Ok(self .snapshot_constructor_constant_call(&domain_name, vec![argument])? .set_default_position(position)) @@ -307,4 +357,22 @@ impl<'p, 'v: 'p, 'tcx: 'v> SnapshotValuesInterface for Lowerer<'p, 'v, 'tcx> { position, ) } + fn snapshots_extensionality_equal_call( + &mut self, + ty: &vir_mid::Type, + left: vir_low::Expression, + right: vir_low::Expression, + position: vir_mid::Position, + ) -> SpannedEncodingResult { + let variant_name = match ty { + vir_mid::Type::Enum(ty) => ty.variant.as_ref().unwrap().as_ref(), + _ => unreachable!("expected enum or union, got: {}", ty), + }; + let enum_ty = ty.forget_variant().unwrap(); + let domain_name = self.encode_snapshot_domain_name(&enum_ty)?; + let gas = self.function_gas_constant(3)?; + let expression = + self.snapshot_equality_call(&domain_name, variant_name, left, right, gas)?; + Ok(expression.set_default_position(position)) + } } diff --git a/prusti-viper/src/encoder/middle/core_proof/snapshots/variables/interface.rs b/prusti-viper/src/encoder/middle/core_proof/snapshots/variables/interface.rs index 8f63e6eec50..5da4c4e4398 100644 --- a/prusti-viper/src/encoder/middle/core_proof/snapshots/variables/interface.rs +++ b/prusti-viper/src/encoder/middle/core_proof/snapshots/variables/interface.rs @@ -1,8 +1,11 @@ use crate::encoder::{ - errors::{ErrorCtxt, SpannedEncodingResult}, + errors::SpannedEncodingResult, high::types::HighTypeEncoderInterface, middle::core_proof::{ + addresses::AddressesInterface, + heap::HeapInterface, lowerer::{Lowerer, VariablesLowererInterface}, + pointers::PointersInterface, references::ReferencesInterface, snapshots::{ IntoProcedureSnapshot, IntoSnapshot, SnapshotValidityInterface, SnapshotValuesInterface, @@ -10,7 +13,6 @@ use crate::encoder::{ type_layouts::TypeLayoutsInterface, types::TypesInterface, }, - mir::errors::ErrorInterface, }; use std::collections::BTreeMap; @@ -19,36 +21,38 @@ use vir_crate::{ middle::{self as vir_mid, operations::ty::Typed}, }; -use super::VariableVersionMap; +// trait Private { +// fn create_snapshot_variable( +// &mut self, +// name: &str, +// ty: &vir_mid::Type, +// version: u64, +// ) -> SpannedEncodingResult; +// #[allow(clippy::ptr_arg)] // Clippy false positive. +// /// Note: if `new_snapshot_root` is `Some`, the current encoding assumes +// /// that the `place` is not behind a raw pointer. +// fn snapshot_copy_except( +// &mut self, +// statements: &mut Vec, +// base: vir_mid::VariableDecl, +// // old_snapshot_root: vir_low::Expression, +// // new_snapshot_root: vir_low::Expression, +// place: &vir_mid::Expression, +// position: vir_low::Position, +// ) -> SpannedEncodingResult<(vir_low::Expression, vir_low::Expression)>; +// } -trait Private { - fn create_snapshot_variable( - &mut self, - name: &str, - ty: &vir_mid::Type, - version: u64, - ) -> SpannedEncodingResult; - #[allow(clippy::ptr_arg)] // Clippy false positive. - fn snapshot_copy_except( - &mut self, - statements: &mut Vec, - old_snapshot_root: vir_low::VariableDecl, - new_snapshot_root: vir_low::VariableDecl, - place: &vir_mid::Expression, - position: vir_low::Position, - ) -> SpannedEncodingResult<(vir_low::Expression, vir_low::Expression)>; -} - -impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { +impl<'p, 'v: 'p, 'tcx: 'v> Lowerer<'p, 'v, 'tcx> { fn create_snapshot_variable( &mut self, name: &str, ty: &vir_mid::Type, version: u64, ) -> SpannedEncodingResult { - let name = format!("{name}$snapshot${version}"); + // let name = format!("{}$snapshot${}", name, version); let ty = ty.to_snapshot(self)?; - self.create_variable(name, ty) + // self.create_variable(name, ty) + self.create_snapshot_variable_low(name, ty, version) } /// Copy all values of the old snapshot into the new snapshot, except the /// ones that belong to `place`. @@ -57,27 +61,72 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { fn snapshot_copy_except( &mut self, statements: &mut Vec, - old_snapshot_root: vir_low::VariableDecl, - new_snapshot_root: vir_low::VariableDecl, + base: vir_mid::VariableDecl, + // old_snapshot_root: vir_low::Expression, + // new_snapshot_root: vir_low::Expression, place: &vir_mid::Expression, position: vir_low::Position, ) -> SpannedEncodingResult<(vir_low::Expression, vir_low::Expression)> { use vir_low::macros::*; if let Some(parent) = place.get_parent_ref() { - let (old_snapshot, new_snapshot) = self.snapshot_copy_except( - statements, - old_snapshot_root, - new_snapshot_root, - parent, - position, - )?; let parent_type = parent.get_type(); + let (old_snapshot, new_snapshot) = + if let vir_mid::Type::Pointer(pointer_type) = parent_type { + let fresh_heap_chunk = self.fresh_heap_chunk(position)?; + let heap_chunk = self.heap_chunk_to_snapshot( + &pointer_type.target_type, + fresh_heap_chunk.clone().into(), + position, + )?; + if self.use_heap_variable()? { + let old_snapshot = parent.to_procedure_snapshot(self)?; // FIXME: This is most likely wrong. + let old_target_snapshot = self.pointer_target_snapshot( + parent.get_type(), + &None, + old_snapshot.clone(), + position, + )?; + let old_heap = self.heap_variable_version_at_label(&None)?; + + // Note: All `old_*` need to be computed before the heap version + // is incremented. + let new_heap = self.new_heap_variable_version(position)?; + let address = + self.pointer_address(parent.get_type(), old_snapshot, position)?; + statements.push(vir_low::Statement::assign( + new_heap, + self.heap_update( + old_heap.into(), + address, + fresh_heap_chunk.into(), + position, + )?, + // vir_low::Expression::container_op( + // vir_low::ContainerOpKind::MapUpdate, + // self.heap_type()?, + // vec![old_heap.into(), address, fresh_heap_chunk.into()], + // position, + // ), + position, + )); + return Ok((old_target_snapshot, heap_chunk)); + } else { + return Ok((heap_chunk.clone(), heap_chunk)); + } + } else { + self.snapshot_copy_except( + statements, base, + // old_snapshot_root, + // new_snapshot_root, + parent, position, + )? + }; + let type_decl = self.encoder.get_type_decl_mid(parent_type)?; match &type_decl { vir_mid::TypeDecl::Bool | vir_mid::TypeDecl::Int(_) - | vir_mid::TypeDecl::Float(_) - | vir_mid::TypeDecl::Pointer(_) => { + | vir_mid::TypeDecl::Float(_) => { unreachable!("place: {}", place); } vir_mid::TypeDecl::Trusted(_) | vir_mid::TypeDecl::TypeVar(_) => { @@ -231,13 +280,46 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { unimplemented!("Place: {}", place); } } + vir_mid::TypeDecl::Pointer(_decl) => { + unreachable!("Should be handled by the caller."); + // let fresh_heap_chunk = self.fresh_heap_chunk()?; + // let heap_chunk = self.heap_chunk_to_snapshot( + // &decl.target_type, + // fresh_heap_chunk.clone().into(), + // position, + // )?; + // let old_heap = self.heap_variable_version_at_label(&None)?; + // let new_heap = self.new_heap_variable_version(position)?; + // let address = + // self.pointer_address(parent_type, old_snapshot.clone(), position)?; + // statements.push(vir_low::Statement::assign( + // new_heap, + // vir_low::Expression::container_op( + // vir_low::ContainerOpKind::MapUpdate, + // self.heap_type()?, + // vec![old_heap.into(), address, fresh_heap_chunk.into()], + // position, + // ), + // position, + // )); + // // statements.push(vir_low::Statement::assume( + // // vir_low::Expression::equals( + // // heap_chunk.clone(), + + // // ) + // // )); + // let old_target_snapshot = + // self.pointer_target_snapshot(parent_type, &None, old_snapshot, position)?; + // Ok((old_target_snapshot, heap_chunk)) + } vir_mid::TypeDecl::Sequence(_) => unimplemented!("ty: {}", type_decl), vir_mid::TypeDecl::Map(_) => unimplemented!("ty: {}", type_decl), - vir_mid::TypeDecl::Never => unimplemented!("ty: {}", type_decl), vir_mid::TypeDecl::Closure(_) => unimplemented!("ty: {}", type_decl), vir_mid::TypeDecl::Unsupported(_) => unimplemented!("ty: {}", type_decl), } } else { + let old_snapshot_root = base.to_procedure_snapshot(self)?; + let new_snapshot_root = self.new_snapshot_variable_version(&base, position)?; // We reached the root. Nothing to do here. Ok((old_snapshot_root.into(), new_snapshot_root.into())) } @@ -245,6 +327,12 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { } pub(in super::super::super) trait SnapshotVariablesInterface { + fn create_snapshot_variable_low( + &mut self, + name: &str, + ty: vir_low::Type, + version: u64, + ) -> SpannedEncodingResult; fn new_snapshot_variable_version( &mut self, variable: &vir_mid::VariableDecl, @@ -263,21 +351,40 @@ pub(in super::super::super) trait SnapshotVariablesInterface { variable: &vir_mid::VariableDecl, label: &str, ) -> SpannedEncodingResult; + fn use_heap_variable(&self) -> SpannedEncodingResult; + fn heap_variable_name(&self) -> SpannedEncodingResult<&'static str>; + fn new_heap_variable_version( + &mut self, + position: vir_low::Position, + ) -> SpannedEncodingResult; + fn heap_variable_version_at_label( + &mut self, + old_label: &Option, + ) -> SpannedEncodingResult; + fn address_variable_version_at_label( + &mut self, + variable_name: &str, + old_label: &Option, + ) -> SpannedEncodingResult; + fn fresh_heap_chunk( + &mut self, + position: vir_low::Position, + ) -> SpannedEncodingResult; fn encode_snapshot_havoc( &mut self, statements: &mut Vec, target: &vir_mid::Expression, position: vir_low::Position, - new_snapshot: Option, - ) -> SpannedEncodingResult<()>; + // new_snapshot: Option, + ) -> SpannedEncodingResult; fn encode_snapshot_update_with_new_snapshot( &mut self, statements: &mut Vec, target: &vir_mid::Expression, value: vir_low::Expression, position: vir_low::Position, - new_snapshot: Option, - ) -> SpannedEncodingResult<()>; + // new_snapshot: Option, + ) -> SpannedEncodingResult; #[allow(clippy::ptr_arg)] // Clippy false positive. fn encode_snapshot_update( &mut self, @@ -292,8 +399,15 @@ pub(in super::super::super) trait SnapshotVariablesInterface { predecessors: &BTreeMap>, basic_block_edges: &mut BTreeMap< vir_mid::BasicBlockId, - BTreeMap>, + BTreeMap< + vir_mid::BasicBlockId, + Vec<(String, vir_low::Type, vir_low::Position, u64, u64)>, + >, >, + // basic_block_edges: &mut BTreeMap< + // vir_mid::BasicBlockId, + // BTreeMap>, + // >, ) -> SpannedEncodingResult<()>; fn unset_current_block_for_snapshots( &mut self, @@ -303,32 +417,51 @@ pub(in super::super::super) trait SnapshotVariablesInterface { } impl<'p, 'v: 'p, 'tcx: 'v> SnapshotVariablesInterface for Lowerer<'p, 'v, 'tcx> { + fn create_snapshot_variable_low( + &mut self, + name: &str, + ty: vir_low::Type, + version: u64, + ) -> SpannedEncodingResult { + let name = format!("{name}$snapshot${version}"); + self.create_variable(name, ty) + } fn new_snapshot_variable_version( &mut self, variable: &vir_mid::VariableDecl, position: vir_low::Position, ) -> SpannedEncodingResult { - let new_version = self - .snapshots_state - .all_variables - .new_version_or_default(variable, position); - self.snapshots_state - .current_variables - .as_mut() - .unwrap() - .set(variable.name.clone(), new_version); - self.create_snapshot_variable(&variable.name, &variable.ty, new_version) + let ty = variable.ty.to_snapshot(self)?; + // let new_version = self.snapshots_state.all_variables.new_version_or_default( + // &variable.name, + // &ty, + // position, + // ); + // self.snapshots_state + // .current_variables + // .as_mut() + // .unwrap() + // .set(variable.name.clone(), new_version); + let new_version = + self.snapshots_state + .ssa_state + .new_variable_version(&variable.name, &ty, position); + self.create_snapshot_variable_low(&variable.name, ty, new_version) } fn current_snapshot_variable_version( &mut self, variable: &vir_mid::VariableDecl, ) -> SpannedEncodingResult { + // let version = self + // .snapshots_state + // .current_variables + // .as_ref() + // .unwrap() + // .get_or_default(&variable.name); let version = self .snapshots_state - .current_variables - .as_ref() - .unwrap() - .get_or_default(&variable.name); + .ssa_state + .current_variable_version(&variable.name); self.create_snapshot_variable(&variable.name, &variable.ty, version) } fn initial_snapshot_variable_version( @@ -342,45 +475,203 @@ impl<'p, 'v: 'p, 'tcx: 'v> SnapshotVariablesInterface for Lowerer<'p, 'v, 'tcx> variable: &vir_mid::VariableDecl, label: &str, ) -> SpannedEncodingResult { + // let version = self + // .snapshots_state + // .variables_at_label + // .get(label) + // .unwrap_or_else(|| panic!("not found label {}", label)) + // .get_or_default(&variable.name); let version = self .snapshots_state - .variables_at_label - .get(label) - .unwrap_or_else(|| panic!("not found label {label}")) - .get_or_default(&variable.name); + .ssa_state + .variable_version_at_label(&variable.name, label); self.create_snapshot_variable(&variable.name, &variable.ty, version) } + fn use_heap_variable(&self) -> SpannedEncodingResult { + // Ok(self.check_mode.unwrap().is_purification_group()) + // FIXME: Rename to unsafe_cell_values. + Ok(false) // FIXME: For now use only the heap-dependent proofs. + } + fn heap_variable_name(&self) -> SpannedEncodingResult<&'static str> { + assert!( + self.use_heap_variable()?, + "The heap variable is not used when the check mode is Both" + ); + Ok("heap$") + } + fn new_heap_variable_version( + &mut self, + position: vir_low::Position, + ) -> SpannedEncodingResult { + // let name = "heap$"; + let name = self.heap_variable_name()?; + let ty = self.heap_type()?; + let new_version = self + .snapshots_state + .ssa_state + .new_variable_version(name, &ty, position); + // let new_version = self + // .snapshots_state + // .all_variables + // .new_version_or_default(name, &ty, position); + // self.snapshots_state + // .current_variables + // .as_mut() + // .unwrap() + // .set(name.to_string(), new_version); + self.create_snapshot_variable_low(name, ty, new_version) + } + fn heap_variable_version_at_label( + &mut self, + old_label: &Option, + ) -> SpannedEncodingResult { + // let name = "heap$"; + let name = self.heap_variable_name()?; + let version = self + .snapshots_state + .ssa_state + .variable_version_at_maybe_label(name, old_label); + // let version = if let Some(label) = old_label { + // self.snapshots_state + // .variables_at_label + // .get(label) + // .unwrap_or_else(|| panic!("not found label {}", label)) + // .get_or_default(name) + // } else { + // self.snapshots_state + // .current_variables + // .as_ref() + // .unwrap() + // .get_or_default(name) + // }; + let ty = self.heap_type()?; + // let name = format!("{}${}", name, version); + // self.create_variable(name, ty) + self.create_snapshot_variable_low(name, ty, version) + } + fn address_variable_version_at_label( + &mut self, + variable_name: &str, + old_label: &Option, + ) -> SpannedEncodingResult { + let name = format!("{variable_name}$address"); + let version = self + .snapshots_state + .ssa_state + .variable_version_at_maybe_label(&name, old_label); + // let version = if let Some(label) = old_label { + // self.snapshots_state + // .variables_at_label + // .get(label) + // .unwrap_or_else(|| panic!("not found label {}", label)) + // .get_or_default(&name) + // } else { + // self.snapshots_state + // .current_variables + // .as_ref() + // .unwrap() + // .get_or_default(&name) + // }; + let ty = self.address_type()?; + self.create_snapshot_variable_low(&name, ty, version) + } + fn fresh_heap_chunk( + &mut self, + position: vir_low::Position, + ) -> SpannedEncodingResult { + let name = "heap_chunk$"; + let ty = self.heap_chunk_type()?; + let new_version = self + .snapshots_state + .ssa_state + .new_variable_version(name, &ty, position); + // let new_version = self + // .snapshots_state + // .all_variables + // .new_version_or_default(name, &ty, position); + // self.snapshots_state + // .current_variables + // .as_mut() + // .unwrap() + // .set(name.to_string(), new_version); + // let name = format!("{}${}", name, new_version); + // self.create_variable(name, ty) + self.create_snapshot_variable_low(name, ty, new_version) + } fn encode_snapshot_havoc( &mut self, statements: &mut Vec, target: &vir_mid::Expression, position: vir_low::Position, - new_snapshot: Option, - ) -> SpannedEncodingResult<()> { + // new_snapshot_root: Option, + ) -> SpannedEncodingResult { + // let base = target.get_base(); + // self.ensure_type_definition(&base.ty)?; + // let old_snapshot = base.to_procedure_snapshot(self)?; + // let new_snapshot = if let Some(new_snapshot) = new_snapshot { + // new_snapshot + // } else { + // self.new_snapshot_variable_version(&base, position)? + // }; + // self.snapshot_copy_except(statements, old_snapshot, new_snapshot, target, position)?; + // Ok(()) let base = target.get_base(); self.ensure_type_definition(&base.ty)?; - let old_snapshot = base.to_procedure_snapshot(self)?; - let new_snapshot = if let Some(new_snapshot) = new_snapshot { - new_snapshot - } else { - self.new_snapshot_variable_version(&base, position)? - }; - self.snapshot_copy_except(statements, old_snapshot, new_snapshot, target, position)?; - Ok(()) + + // if let Some(pointer_place) = target.get_last_dereferenced_pointer() { + // let pointer_type = pointer_place.get_type().clone().unwrap_pointer(); + // let fresh_heap_chunk = self.fresh_heap_chunk()?; + // let heap_chunk = self.heap_chunk_to_snapshot( + // &pointer_type.target_type, + // fresh_heap_chunk.clone().into(), + // position, + // )?; + // let old_heap = self.heap_variable_version_at_label(&None)?; + // let new_heap = self.new_heap_variable_version(position)?; + // let address = + // self.pointer_address(pointer_place.get_type(), old_snapshot.clone().into(), position)?; + // statements.push(vir_low::Statement::assign( + // new_heap, + // vir_low::Expression::container_op( + // vir_low::ContainerOpKind::MapUpdate, + // self.heap_type()?, + // vec![old_heap.into(), address, fresh_heap_chunk.into()], + // position, + // ), + // position, + // )); + // let old_target_snapshot = + // self.pointer_target_snapshot(pointer_place.get_type(), &None, old_snapshot.into(), position)?; + // // Ok((old_target_snapshot, heap_chunk) + // let (_old_snapshot, new_snapshot) = + // self.snapshot_copy_except(statements, old_target_snapshot, heap_chunk, pointer_place, position)?; + // Ok(new_snapshot) + // } else { + + // let (_old_snapshot, new_snapshot) = + // self.snapshot_copy_except(statements, old_snapshot, new_snapshot, target, position)?; + let (_old_snapshot, new_snapshot) = + self.snapshot_copy_except(statements, base, target, position)?; + Ok(new_snapshot) + // } } + /// `new_snapshot_root` is used when we want to use a specific variable + /// version as the root of the new snapshot. fn encode_snapshot_update_with_new_snapshot( &mut self, statements: &mut Vec, target: &vir_mid::Expression, value: vir_low::Expression, position: vir_low::Position, - new_snapshot: Option, - ) -> SpannedEncodingResult<()> { + // new_snapshot_root: Option, + ) -> SpannedEncodingResult { use vir_low::macros::*; - self.encode_snapshot_havoc(statements, target, position, new_snapshot)?; - statements - .push(stmtp! { position => assume ([target.to_procedure_snapshot(self)?] == [value]) }); - Ok(()) + // self.encode_snapshot_havoc(statements, target, position, new_snapshot)?; + // statements + // .push(stmtp! { position => assume ([target.to_procedure_snapshot(self)?] == [value]) }); + let new_snapshot = self.encode_snapshot_havoc(statements, target, position)?; + statements.push(stmtp! { position => assume ([new_snapshot.clone()] == [value]) }); + Ok(new_snapshot) } fn encode_snapshot_update( &mut self, @@ -389,7 +680,8 @@ impl<'p, 'v: 'p, 'tcx: 'v> SnapshotVariablesInterface for Lowerer<'p, 'v, 'tcx> value: vir_low::Expression, position: vir_low::Position, ) -> SpannedEncodingResult<()> { - self.encode_snapshot_update_with_new_snapshot(statements, target, value, position, None) + self.encode_snapshot_update_with_new_snapshot(statements, target, value, position)?; + Ok(()) } /// `basic_block_edges` are statements to be executed then going from one /// block to another. @@ -399,73 +691,88 @@ impl<'p, 'v: 'p, 'tcx: 'v> SnapshotVariablesInterface for Lowerer<'p, 'v, 'tcx> predecessors: &BTreeMap>, basic_block_edges: &mut BTreeMap< vir_mid::BasicBlockId, - BTreeMap>, + BTreeMap< + vir_mid::BasicBlockId, + Vec<(String, vir_low::Type, vir_low::Position, u64, u64)>, + >, >, + // basic_block_edges: &mut BTreeMap< + // vir_mid::BasicBlockId, + // BTreeMap>, + // >, ) -> SpannedEncodingResult<()> { - let predecessor_labels = &predecessors[label]; - let mut new_map = VariableVersionMap::default(); - for variable in self.snapshots_state.all_variables.names_clone() { - let predecessor_maps = predecessor_labels - .iter() - .map(|label| &self.snapshots_state.variables[label]) - .collect::>(); - let first_version = predecessor_maps[0].get_or_default(&variable); - let different = predecessor_maps - .iter() - .any(|map| map.get_or_default(&variable) != first_version); - if different { - let new_version = self.snapshots_state.all_variables.new_version(&variable); - let ty = self - .snapshots_state - .all_variables - .get_type(&variable) - .clone(); - let new_variable = self.create_snapshot_variable(&variable, &ty, new_version)?; - for predecessor_label in predecessor_labels { - let old_version = - self.snapshots_state.variables[predecessor_label].get_or_default(&variable); - let statements = basic_block_edges - .entry(predecessor_label.clone()) - .or_default() - .entry(label.clone()) - .or_default(); - let old_variable = - self.create_snapshot_variable(&variable, &ty, old_version)?; - let position = self.encoder.change_error_context( - // FIXME: Get a more precise span. - self.snapshots_state.all_variables.get_position(&variable), - ErrorCtxt::Unexpected, - ); - let statement = vir_low::macros::stmtp! { position => assume (new_variable == old_variable) }; - statements.push(statement); - } - new_map.set(variable, new_version); - } else { - new_map.set(variable, first_version); - } - } - self.snapshots_state.current_variables = Some(new_map); + self.snapshots_state.ssa_state.prepare_new_current_block( + label, + predecessors, + basic_block_edges, + ); + // let predecessor_labels = &predecessors[label]; + // let mut new_map = VariableVersionMap::default(); + // for variable in self.snapshots_state.all_variables.names_clone() { + // let predecessor_maps = predecessor_labels + // .iter() + // .map(|label| &self.snapshots_state.variables[label]) + // .collect::>(); + // let first_version = predecessor_maps[0].get_or_default(&variable); + // let different = predecessor_maps + // .iter() + // .any(|map| map.get_or_default(&variable) != first_version); + // if different { + // let new_version = self.snapshots_state.all_variables.new_version(&variable); + // let ty = self + // .snapshots_state + // .all_variables + // .get_type(&variable) + // .clone(); + // let new_variable = + // self.create_snapshot_variable_low(&variable, ty.clone(), new_version)?; + // for predecessor_label in predecessor_labels { + // let old_version = + // self.snapshots_state.variables[predecessor_label].get_or_default(&variable); + // let statements = basic_block_edges + // .entry(predecessor_label.clone()) + // .or_default() + // .entry(label.clone()) + // .or_default(); + // let old_variable = + // self.create_snapshot_variable_low(&variable, ty.clone(), old_version)?; + // let position = self.encoder.change_error_context( + // // FIXME: Get a more precise span. + // self.snapshots_state.all_variables.get_position(&variable), + // ErrorCtxt::Unexpected, + // ); + // let statement = vir_low::macros::stmtp! { position => assume (new_variable == old_variable) }; + // statements.push(statement); + // } + // new_map.set(variable, new_version); + // } else { + // new_map.set(variable, first_version); + // } + // } + // self.snapshots_state.current_variables = Some(new_map); Ok(()) } fn unset_current_block_for_snapshots( &mut self, label: vir_mid::BasicBlockId, ) -> SpannedEncodingResult<()> { - let current_variables = self.snapshots_state.current_variables.take().unwrap(); - assert!(self - .snapshots_state - .variables - .insert(label, current_variables) - .is_none()); + self.snapshots_state.ssa_state.finish_current_block(label); + // let current_variables = self.snapshots_state.current_variables.take().unwrap(); + // assert!(self + // .snapshots_state + // .variables + // .insert(label, current_variables) + // .is_none()); Ok(()) } fn save_old_label(&mut self, label: String) -> SpannedEncodingResult<()> { - let current_variables = self.snapshots_state.current_variables.clone().unwrap(); - assert!(self - .snapshots_state - .variables_at_label - .insert(label, current_variables) - .is_none()); + self.snapshots_state.ssa_state.save_state_at_label(label); + // let current_variables = self.snapshots_state.current_variables.clone().unwrap(); + // assert!(self + // .snapshots_state + // .variables_at_label + // .insert(label, current_variables) + // .is_none()); Ok(()) } } diff --git a/prusti-viper/src/encoder/middle/core_proof/snapshots/variables/mod.rs b/prusti-viper/src/encoder/middle/core_proof/snapshots/variables/mod.rs index eb61a1f4135..32086242768 100644 --- a/prusti-viper/src/encoder/middle/core_proof/snapshots/variables/mod.rs +++ b/prusti-viper/src/encoder/middle/core_proof/snapshots/variables/mod.rs @@ -4,4 +4,3 @@ mod interface; mod state; pub(in super::super) use self::interface::SnapshotVariablesInterface; -pub(super) use self::state::{AllVariablesMap, VariableVersionMap}; diff --git a/prusti-viper/src/encoder/middle/core_proof/snapshots/variables/state.rs b/prusti-viper/src/encoder/middle/core_proof/snapshots/variables/state.rs index f60795708a3..7b757ae4c3e 100644 --- a/prusti-viper/src/encoder/middle/core_proof/snapshots/variables/state.rs +++ b/prusti-viper/src/encoder/middle/core_proof/snapshots/variables/state.rs @@ -1,11 +1,8 @@ use std::collections::BTreeMap; -use vir_crate::{ - low::{self as vir_low}, - middle::{self as vir_mid}, -}; +use vir_crate::low::{self as vir_low}; #[derive(Default, Clone)] -pub(in super::super) struct VariableVersionMap { +pub(in super::super::super) struct VariableVersionMap { /// Mapping from variable names to their versions. variable_versions: BTreeMap, } @@ -31,9 +28,9 @@ impl VariableVersionMap { } #[derive(Default)] -pub(in super::super) struct AllVariablesMap { +pub(in super::super::super) struct AllVariablesMap { versions: BTreeMap, - types: BTreeMap, + types: BTreeMap, positions: BTreeMap, } @@ -41,7 +38,7 @@ impl AllVariablesMap { pub(super) fn names_clone(&self) -> Vec { self.versions.keys().cloned().collect() } - pub(super) fn get_type(&self, variable: &str) -> &vir_mid::Type { + pub(super) fn get_type(&self, variable: &str) -> &vir_low::Type { &self.types[variable] } pub(super) fn get_position(&self, variable: &str) -> vir_low::Position { @@ -54,18 +51,18 @@ impl AllVariablesMap { } pub(super) fn new_version_or_default( &mut self, - variable: &vir_mid::VariableDecl, + variable: &str, + ty: &vir_low::Type, position: vir_low::Position, ) -> u64 { - if self.versions.contains_key(&variable.name) { - let version = self.versions.get_mut(&variable.name).unwrap(); + if self.versions.contains_key(variable) { + let version = self.versions.get_mut(variable).unwrap(); *version += 1; *version } else { - self.versions.insert(variable.name.clone(), 1); - self.types - .insert(variable.name.clone(), variable.ty.clone()); - self.positions.insert(variable.name.clone(), position); + self.versions.insert(variable.to_string(), 1); + self.types.insert(variable.to_string(), ty.clone()); + self.positions.insert(variable.to_string(), position); 1 } } diff --git a/prusti-viper/src/encoder/middle/core_proof/svirpti/errors.rs b/prusti-viper/src/encoder/middle/core_proof/svirpti/errors.rs new file mode 100644 index 00000000000..44e326dbe15 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/svirpti/errors.rs @@ -0,0 +1,50 @@ +use super::{super::transformations::encoder_context::EncoderContext, ProcedureExecutor}; +use crate::encoder::errors::SpannedEncodingResult; +use vir_crate::low as vir_low; + +#[derive(Debug)] +pub(crate) struct VerificationError { + full_id: String, + position: vir_low::Position, + message: String, +} + +impl VerificationError { + pub(crate) fn as_viper_verification_error(&self) -> viper::VerificationError { + viper::VerificationError { + full_id: self.full_id.clone(), + pos_id: None, + offending_pos_id: Some(self.position.id.to_string()), + reason_pos_id: None, + message: self.message.clone(), + counterexample: None, + } + } +} + +impl<'a, 'c, EC: EncoderContext> ProcedureExecutor<'a, 'c, EC> { + pub(super) fn create_verification_error_for_expression( + &self, + full_id: &str, + position: vir_low::Position, + expression: &vir_low::Expression, + ) -> SpannedEncodingResult { + let frame = self.current_frame(); + let trace = self.current_execution_trace()?; + let message = format!( + "Expression `{}` in program {} procedure {} basic block {} \ + statement {} failed to verify. Trace: {:?}.", + expression, + self.source_filename(), + self.procedure_name(), + frame.label().name, + frame.statement_index(), + trace, + ); + Ok(VerificationError { + full_id: full_id.to_string(), + position, + message, + }) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/svirpti/mod.rs b/prusti-viper/src/encoder/middle/core_proof/svirpti/mod.rs new file mode 100644 index 00000000000..05f013abbbb --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/svirpti/mod.rs @@ -0,0 +1,131 @@ +use self::procedure_verifier::ProcedureExecutor; +use super::transformations::{ + encoder_context::EncoderContext, predicate_domains::PredicateDomainsInfo, + symbolic_execution_new::ProgramContext, +}; +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::{predicates::OwnedPredicateInfo, snapshots::SnapshotDomainsInfo}, + Encoder, +}; +use log::debug; +use prusti_common::config; +use rustc_hash::FxHashSet; +use std::collections::BTreeMap; +use vir_crate::low as vir_low; + +mod smt; +mod procedure_verifier; +mod errors; + +pub(crate) use self::errors::VerificationError; + +#[derive(Debug)] +pub(crate) enum VerificationResult { + Success, + Failure { errors: Vec }, +} + +impl VerificationResult { + pub(crate) fn is_success(&self) -> bool { + matches!(self, Self::Success) + } + + pub(crate) fn get_errors(&self) -> &[VerificationError] { + match self { + Self::Success => &[], + Self::Failure { errors } => errors, + } + } +} + +pub(super) fn verify_program( + encoder: &mut Encoder, + source_filename: &str, + program: vir_low::Program, + predicate_domains_info: PredicateDomainsInfo, + non_aliased_memory_block_addresses: FxHashSet, + snapshot_domains_info: &SnapshotDomainsInfo, + owned_predicates_info: BTreeMap, + extensionality_gas_constant: &vir_low::Expression, +) -> SpannedEncodingResult { + debug!( + "purify_with_symbolic_execution {} {}", + source_filename, program.name + ); + let mut verifier = Verifier::new(program.name.clone()); + verifier.execute( + source_filename, + program, + predicate_domains_info, + non_aliased_memory_block_addresses, + snapshot_domains_info, + owned_predicates_info, + extensionality_gas_constant, + encoder, + )?; + let result = if verifier.errors.is_empty() { + VerificationResult::Success + } else { + VerificationResult::Failure { + errors: verifier.errors, + } + }; + Ok(result) +} + +struct Verifier { + program_name: String, + errors: Vec, +} + +impl Verifier { + pub(crate) fn new(program_name: String) -> Self { + Self { + program_name, + errors: Vec::new(), + } + } + + pub(crate) fn execute( + &mut self, + source_filename: &str, + program: vir_low::Program, + predicate_domains_info: PredicateDomainsInfo, + non_aliased_memory_block_addresses: FxHashSet, + snapshot_domains_info: &SnapshotDomainsInfo, + owned_predicates_info: BTreeMap, + extensionality_gas_constant: &vir_low::Expression, + encoder: &mut impl EncoderContext, + ) -> SpannedEncodingResult<()> { + let mut program_context = ProgramContext::new( + &program.domains, + &program.functions, + &program.predicates, + snapshot_domains_info, + owned_predicates_info, + &non_aliased_memory_block_addresses, + extensionality_gas_constant, + encoder, + ); + for procedure in program.procedures { + let mut procedure_executor = ProcedureExecutor::new( + self, + source_filename, + procedure.name.clone(), + &mut program_context, + &predicate_domains_info, + )?; + procedure_executor.load_domains(&program.domains)?; + procedure_executor.execute_procedure(&procedure, &program.predicates)?; + } + Ok(()) + } + + pub(crate) fn report_error(&mut self, error: VerificationError) { + if config::svirpti_stop_on_first_error() { + panic!("A verification error: {:?}", error); + } + self.errors.push(error); + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/svirpti/procedure_verifier/heap/boolean_mask_log_with_heap.rs b/prusti-viper/src/encoder/middle/core_proof/svirpti/procedure_verifier/heap/boolean_mask_log_with_heap.rs new file mode 100644 index 00000000000..37429b37442 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/svirpti/procedure_verifier/heap/boolean_mask_log_with_heap.rs @@ -0,0 +1,863 @@ +use super::super::{ + super::super::transformations::encoder_context::EncoderContext, ProcedureExecutor, +}; +use crate::encoder::errors::SpannedEncodingResult; +use prusti_common::config; +use rustc_hash::FxHashMap; +use vir_crate::{ + common::expression::{BinaryOperationHelpers, ExpressionIterator, UnaryOperationHelpers}, + low::{self as vir_low, operations::ty::Typed}, +}; + +#[derive(Default, Clone, Debug)] +pub(in super::super::super::super) struct BooleanMaskLogWithHeap { + /// A map from predicate names to the current log entries. + permission_log_entry: FxHashMap, + heap_versions: FxHashMap, +} + +#[derive(Debug, Clone)] +pub(super) enum LogEntryKind { + InhaleFull, + ExhaleFull, +} + +#[derive(Debug, Clone)] +pub(super) enum LogEntry { + InhaleFull(LogEntryFull), + ExhaleFull(LogEntryFull), + InhaleQuantified(LogEntryQuantifiedFull), + ExhaleQuantified(LogEntryQuantifiedFull), +} + +#[derive(Debug, Clone)] +pub(super) struct LogEntryFull { + pub(super) arguments: Vec, +} + +#[derive(Debug, Clone)] +pub(super) struct LogEntryQuantifiedFull { + pub(super) quantifier_name: Option, + pub(super) variables: Vec, + pub(super) guard: vir_low::Expression, + pub(super) arguments: Vec, +} + +#[derive(Default, Debug)] +pub(in super::super::super::super) struct BooleanMaskLog { + pub(super) entries: FxHashMap>, +} + +fn heap_variable_name(predicate_name: &str, id: usize) -> String { + format!("{}$heap${}", predicate_name, id) +} + +impl<'a, 'c, EC: EncoderContext> ProcedureExecutor<'a, 'c, EC> { + pub(super) fn initialise_boolean_mask_log_with_heap( + &mut self, + predicate_name: &str, + ) -> SpannedEncodingResult<()> { + let id = self.generate_fresh_id(); + let heap = self.current_frame_mut().heap_mut(); + assert!(heap + .boolean_mask_log_with_heap + .permission_log_entry + .insert(predicate_name.to_string(), 0usize) + .is_none()); + assert!(heap + .boolean_mask_log_with_heap + .heap_versions + .insert(predicate_name.to_string(), id) + .is_none()); + assert!(self + .global_heap + .boolean_mask_log + .entries + .insert(predicate_name.to_string(), vec![]) + .is_none()); + let heap_name = heap_variable_name(predicate_name, id); + let predicate_info = self + .predicate_domains_info + .get_with_heap(predicate_name) + .unwrap(); + let heap = predicate_info.create_heap_variable(heap_name); + self.declare_variable(&heap)?; + Ok(()) + } + + pub(super) fn execute_inhale_boolean_mask_log_with_heap_full( + &mut self, + predicate: vir_low::PredicateAccessPredicate, + _position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + assert!(predicate.permission.is_full_permission()); + + // Update local records. + let frame: &mut crate::encoder::middle::core_proof::svirpti::procedure_verifier::solver_stack::StackFrame = self.current_frame_mut(); + let state = &mut frame.heap_mut().boolean_mask_log_with_heap; + let log_entry = state.permission_log_entry.get_mut(&predicate.name).unwrap(); + let old_log_entry = *log_entry; + *log_entry += 1; + let new_log_entry = *log_entry; + + // Update Z3 state. + if !config::svirpti_use_pseudo_boolean_heap() && old_log_entry > 0 { + let (guard_definitions, check) = + self.create_permission_check(&predicate.name, &predicate.arguments, old_log_entry)?; + for definition in guard_definitions { + self.comment("non-aliasing-assumptions")?; + self.assume(&definition)?; + } + let negated_check = vir_low::Expression::not(check.clone()); + self.assume(&negated_check)?; + } + + // Update the global heap. + let entries = self + .global_heap + .boolean_mask_log + .entries + .get_mut(&predicate.name) + .unwrap(); + if entries.len() > old_log_entry { + entries.truncate(old_log_entry); + } + assert_eq!(entries.len(), old_log_entry); + let entry = LogEntry::InhaleFull(LogEntryFull { + arguments: predicate.arguments.clone(), + }); + entries.push(entry); + assert_eq!(entries.len(), new_log_entry); + + Ok(()) + } + + pub(super) fn execute_inhale_quantified_boolean_mask_log_with_heap_full( + &mut self, + quantifier_name: Option, + variables: Vec, + guard: vir_low::Expression, + predicate: vir_low::PredicateAccessPredicate, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + assert!(predicate.permission.is_full_permission()); + + // Update local records. + let frame: &mut crate::encoder::middle::core_proof::svirpti::procedure_verifier::solver_stack::StackFrame = self.current_frame_mut(); + let state = &mut frame.heap_mut().boolean_mask_log_with_heap; + let log_entry = state.permission_log_entry.get_mut(&predicate.name).unwrap(); + let old_log_entry = *log_entry; + *log_entry += 1; + let new_log_entry = *log_entry; + + // The corresponding updating Z3 state step is done in + // check_quantified_permissions_with_heap_bools because for quantified + // permissions we need to do it on each instantiation. + + // Update the global heap. + let entries = self + .global_heap + .boolean_mask_log + .entries + .get_mut(&predicate.name) + .unwrap(); + if entries.len() > old_log_entry { + entries.truncate(old_log_entry); + } + assert_eq!(entries.len(), old_log_entry); + let entry = LogEntry::InhaleQuantified(LogEntryQuantifiedFull { + quantifier_name: quantifier_name.clone(), + variables: variables.to_vec(), + guard: guard.clone(), + arguments: predicate.arguments.clone(), + }); + entries.push(entry); + assert_eq!(entries.len(), new_log_entry); + + Ok(()) + } + + fn check_permissions_with_heap_pbge( + &mut self, + predicate_name: &str, + predicate_arguments: &[vir_low::Expression], + guard: Option, + entry_id: usize, + full_error_id: &str, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + use vir_low::macros::*; + assert!( + entry_id > 0, + "TODO: A proper error message that we are exhaling for an empty heap." + ); + let entries = self + .global_heap + .boolean_mask_log + .entries + .get_mut(predicate_name) + .unwrap(); + let mut pbge_arguments = Vec::with_capacity(entry_id); + let plus_one: vir_low::Expression = 1.into(); + let minus_one: vir_low::Expression = (-1).into(); + for entry in entries.iter().take(entry_id) { + match entry { + LogEntry::InhaleFull(_) | LogEntry::InhaleQuantified(_) => { + pbge_arguments.push(plus_one.clone()); + } + LogEntry::ExhaleFull(_) | LogEntry::ExhaleQuantified(_) => { + pbge_arguments.push(minus_one.clone()); + } + } + } + for entry in entries.iter().take(entry_id) { + match entry { + LogEntry::InhaleFull(entry) | LogEntry::ExhaleFull(entry) => { + let arguments_equal = predicate_arguments + .iter() + .zip(entry.arguments.iter()) + .map(|(predicate_argument, entry_argument)| { + expr! { [predicate_argument.clone()] == [entry_argument.clone()] } + }) + .conjoin(); + pbge_arguments.push(arguments_equal); + } + LogEntry::InhaleQuantified(entry) | LogEntry::ExhaleQuantified(entry) => { + let entry_replacements = if entry.variables.len() == 1 + && entry.variables[0].name == "element_address" + { + assert_eq!(&entry.variables[0].ty, predicate_arguments[0].get_type()); + let mut entry_replacements = FxHashMap::default(); + entry_replacements.insert(&entry.variables[0], &predicate_arguments[0]); + entry_replacements + } else { + unimplemented!(); + }; + let arguments_equal = predicate_arguments + .iter() + .zip(entry.arguments.iter()) + .map(|(predicate_argument, entry_argument)| { + let entry_argument = entry_argument + .clone() + .substitute_variables(&entry_replacements); + expr! { [predicate_argument.clone()] == [entry_argument] } + }) + .conjoin(); + let entry_guard = entry + .guard + .clone() + .substitute_variables(&entry_replacements); + pbge_arguments.push(expr! { [entry_guard] && [arguments_equal] }); + } + } + } + let mut check_permissions = vir_low::Expression::smt_operation_no_pos( + vir_low::SmtOperationKind::PbQe, + pbge_arguments, + vir_low::Type::Bool, + ); + if let Some(guard) = guard { + check_permissions = vir_low::Expression::implies(guard, check_permissions); + } + let error = self.create_verification_error_for_expression( + full_error_id, + position, + &check_permissions, + )?; + self.assert(check_permissions, error)?; + Ok(()) + } + + pub(super) fn create_permission_check( + &mut self, + predicate_name: &str, + predicate_arguments: &[vir_low::Expression], + entry_id: usize, + ) -> SpannedEncodingResult<(Vec, vir_low::Expression)> { + use vir_low::macros::*; + assert!( + entry_id > 0, + "TODO: A proper error message that we are exhaling for an empty heap." + ); + let mut guards = Vec::with_capacity(entry_id); + for _ in 0..entry_id { + let guard_id = self.generate_fresh_id(); + let guard_name = format!("guard${}", guard_id); + let guard = vir_low::VariableDecl::new(guard_name, vir_low::Type::Bool); + guards.push(guard); + } + fn arguments_equal( + predicate_arguments: &[vir_low::Expression], + entry_arguments: &[vir_low::Expression], + ) -> vir_low::Expression { + use vir_low::macros::*; + predicate_arguments + .iter() + .zip(entry_arguments.iter()) + .map(|(predicate_argument, entry_argument)| { + expr! { [predicate_argument.clone()] == [entry_argument.clone()] } + }) + .conjoin() + } + fn arguments_equal_quantified( + predicate_arguments: &[vir_low::Expression], + entry: &LogEntryQuantifiedFull, + ) -> vir_low::Expression { + use vir_low::macros::*; + let entry_replacements = + if entry.variables.len() == 1 && entry.variables[0].name == "element_address" { + assert_eq!(&entry.variables[0].ty, predicate_arguments[0].get_type()); + let mut entry_replacements = FxHashMap::default(); + entry_replacements.insert(&entry.variables[0], &predicate_arguments[0]); + entry_replacements + } else { + unimplemented!(); + }; + let arguments_equal = predicate_arguments + .iter() + .zip(entry.arguments.iter()) + .map(|(predicate_argument, entry_argument)| { + let entry_argument = entry_argument + .clone() + .substitute_variables(&entry_replacements); + expr! { [predicate_argument.clone()] == [entry_argument] } + }) + .conjoin(); + let entry_guard = entry + .guard + .clone() + .substitute_variables(&entry_replacements); + expr! { [entry_guard] && [arguments_equal] } + } + let entries = self + .global_heap + .boolean_mask_log + .entries + .get_mut(predicate_name) + .unwrap(); + let mut entry_iterator = entries.iter().take(entry_id).zip(guards.into_iter()); + // let mut guard_definitions = Vec::with_capacity(entry_id); + let mut guard_definitions = FxHashMap::default(); + let (first_entry, first_guard) = entry_iterator.next().unwrap(); + let mut check_permissions: vir_low::Expression = match first_entry { + LogEntry::InhaleFull(entry) => { + let arguments_equal = arguments_equal(&predicate_arguments, &entry.arguments); + // guard_definitions.push(expr! { first_guard == [arguments_equal] }); + guard_definitions.insert(arguments_equal, first_guard.clone()); + first_guard.into() + } + LogEntry::InhaleQuantified(entry) => { + let guard_definition = arguments_equal_quantified(&predicate_arguments, entry); + guard_definitions.insert(guard_definition, first_guard.clone()); + first_guard.into() + } + LogEntry::ExhaleFull(_) | LogEntry::ExhaleQuantified(_) => unreachable!(), + }; + for (entry, guard) in entry_iterator { + match entry { + LogEntry::InhaleFull(entry) => { + let arguments_equal = arguments_equal(&predicate_arguments, &entry.arguments); + let guard_variable = guard_definitions.entry(arguments_equal).or_insert(guard); + check_permissions = + vir_low::Expression::or(check_permissions, guard_variable.clone().into()); + } + LogEntry::ExhaleFull(entry) => { + let arguments_equal = arguments_equal(&predicate_arguments, &entry.arguments); + let guard_variable = guard_definitions.entry(arguments_equal).or_insert(guard); + check_permissions = vir_low::Expression::and( + check_permissions, + vir_low::Expression::not(guard_variable.clone().into()), + ); + } + LogEntry::InhaleQuantified(entry) => { + let guard_definition = arguments_equal_quantified(&predicate_arguments, entry); + let guard_variable = guard_definitions.entry(guard_definition).or_insert(guard); + check_permissions = + vir_low::Expression::or(check_permissions, guard_variable.clone().into()); + } + LogEntry::ExhaleQuantified(entry) => { + let guard_definition = arguments_equal_quantified(&predicate_arguments, entry); + let guard_variable = guard_definitions.entry(guard_definition).or_insert(guard); + check_permissions = vir_low::Expression::and( + check_permissions, + vir_low::Expression::not(guard_variable.clone().into()), + ); + } + } + } + let mut guard_definitions_vec = Vec::new(); + for (definition, guard) in guard_definitions { + self.declare_variable(&guard)?; + guard_definitions_vec.push(expr! { guard == [definition] }); + } + Ok((guard_definitions_vec, check_permissions)) + } + + fn check_permissions_with_heap_bools( + &mut self, + predicate_name: &str, + predicate_arguments: &[vir_low::Expression], + guard: Option, + entry_id: usize, + full_error_id: &str, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + // Teach Z3 about the non-aliasing assumptions coming from quantified + // inhales. + let quantified_inhale_ids = self + .global_heap + .boolean_mask_log + .entries + .get(predicate_name) + .unwrap() + .iter() + .enumerate() + .filter_map(|(id, entry)| match entry { + LogEntry::InhaleQuantified(_) => Some(id), + _ => None, + }) + .collect::>(); + for inhale_entry_id in quantified_inhale_ids { + if inhale_entry_id == 0 { + continue; + } + let LogEntry::InhaleQuantified(entry) = self.global_heap.boolean_mask_log.entries.get(predicate_name).unwrap()[inhale_entry_id].clone() else { + unreachable!(); + }; + let replacement_map = + if entry.variables.len() == 1 && entry.variables[0].name == "element_address" { + assert_eq!(&entry.variables[0].ty, predicate_arguments[0].get_type()); + let mut entry_replacements = FxHashMap::default(); + entry_replacements.insert(&entry.variables[0], &predicate_arguments[0]); + entry_replacements + } else { + unimplemented!(); + }; + let arguments: Vec<_> = entry + .arguments + .into_iter() + .map(|argument| argument.substitute_variables(&replacement_map)) + .collect(); + let (guard_definitions, check) = + self.create_permission_check(predicate_name, &arguments, inhale_entry_id)?; + for definition in guard_definitions { + self.assume(&definition)?; + } + let inhale_guard = entry.guard.substitute_variables(&replacement_map); + let negated_check = vir_low::Expression::not(check.clone()); + let guarded_negated_check = vir_low::Expression::implies(inhale_guard, negated_check); + self.comment("non-aliasing-assumptions")?; + self.assume(&guarded_negated_check)?; + } + + // Construct the check. + let (guard_definitions, mut check_permissions) = + self.create_permission_check(predicate_name, predicate_arguments, entry_id)?; + if let Some(guard) = guard { + check_permissions = vir_low::Expression::implies(guard, check_permissions); + } + let error = self.create_verification_error_for_expression( + full_error_id, + position, + &check_permissions, + )?; + self.assert_with_assumptions(&guard_definitions, check_permissions, error)?; + Ok(()) + } + + fn check_permissions_with_heap( + &mut self, + predicate_name: &str, + predicate_arguments: &[vir_low::Expression], + guard: Option, + entry_id: usize, + full_error_id: &str, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + if config::svirpti_use_pseudo_boolean_heap() { + self.check_permissions_with_heap_pbge( + predicate_name, + predicate_arguments, + guard, + entry_id, + full_error_id, + position, + ) + } else { + self.check_permissions_with_heap_bools( + predicate_name, + predicate_arguments, + guard, + entry_id, + full_error_id, + position, + ) + } + } + + fn check_quantified_permissions_with_heap_pbge( + &mut self, + predicate_name: &str, + predicate_arguments: &[vir_low::Expression], + variables: &[vir_low::VariableDecl], + guard: vir_low::Expression, + entry_id: usize, + full_error_id: &str, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + use vir_low::macros::*; + assert!( + entry_id > 0, + "TODO: A proper error message that we are exhaling for an empty heap." + ); + let mut fresh_variables: Vec = Vec::with_capacity(variables.len()); + for variable in variables { + let fresh_variable_name = format!("{}${}", variable.name, self.generate_fresh_id()); + let fresh_variable = + vir_low::VariableDecl::new(fresh_variable_name, variable.ty.clone()); + self.declare_variable(&fresh_variable)?; + fresh_variables.push(fresh_variable.into()); + } + + let replacements = variables.iter().zip(fresh_variables.iter()).collect(); + let guard = guard.substitute_variables(&replacements); + let predicate_arguments: Vec<_> = predicate_arguments + .iter() + .map(|argument| argument.clone().substitute_variables(&replacements)) + .collect(); + + let entries = self + .global_heap + .boolean_mask_log + .entries + .get_mut(predicate_name) + .unwrap(); + let mut pbge_arguments = Vec::with_capacity(entry_id); + let plus_one: vir_low::Expression = 1.into(); + let minus_one: vir_low::Expression = (-1).into(); + for entry in entries.iter().take(entry_id) { + match entry { + LogEntry::InhaleFull(_) | LogEntry::InhaleQuantified(_) => { + pbge_arguments.push(plus_one.clone()); + } + LogEntry::ExhaleFull(_) | LogEntry::ExhaleQuantified(_) => { + pbge_arguments.push(minus_one.clone()); + } + } + } + for entry in entries.iter().take(entry_id) { + match entry { + LogEntry::InhaleFull(entry) | LogEntry::ExhaleFull(entry) => { + let arguments_equal = predicate_arguments + .iter() + .zip(entry.arguments.iter()) + .map(|(predicate_argument, entry_argument)| { + expr! { [predicate_argument.clone()] == [entry_argument.clone()] } + }) + .conjoin(); + pbge_arguments.push(arguments_equal); + } + LogEntry::InhaleQuantified(entry) | LogEntry::ExhaleQuantified(entry) => { + let entry_replacements = + entry.variables.iter().zip(fresh_variables.iter()).collect(); + let arguments_equal = predicate_arguments + .iter() + .zip(entry.arguments.iter()) + .map(|(predicate_argument, entry_argument)| { + let entry_argument = entry_argument + .clone() + .substitute_variables(&entry_replacements); + expr! { [predicate_argument.clone()] == [entry_argument] } + }) + .conjoin(); + let entry_guard = entry + .guard + .clone() + .substitute_variables(&entry_replacements); + pbge_arguments.push(expr! { [entry_guard] && [arguments_equal] }); + } + } + } + let mut check_permissions = vir_low::Expression::smt_operation_no_pos( + vir_low::SmtOperationKind::PbQe, + pbge_arguments, + vir_low::Type::Bool, + ); + check_permissions = vir_low::Expression::implies(guard, check_permissions); + let error = self.create_verification_error_for_expression( + full_error_id, + position, + &check_permissions, + )?; + self.assert(check_permissions, error)?; + Ok(()) + } + + fn check_quantified_permissions_with_heap_bools( + &mut self, + predicate_name: &str, + predicate_arguments: &[vir_low::Expression], + variables: &[vir_low::VariableDecl], + guard: vir_low::Expression, + entry_id: usize, + full_error_id: &str, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + assert!( + entry_id > 0, + "TODO: A proper error message that we are exhaling for an empty heap." + ); + let mut replacements: Vec<(_, vir_low::Expression)> = Vec::new(); + for variable in variables { + let fresh_variable_name = format!("{}${}", variable.name, self.generate_fresh_id()); + let fresh_variable = + vir_low::VariableDecl::new(fresh_variable_name, variable.ty.clone()); + self.declare_variable(&fresh_variable)?; + replacements.push((variable, fresh_variable.into())); + } + let replacement_map: FxHashMap<_, _> = replacements + .iter() + .map(|(variable, replacement)| (*variable, replacement)) + .collect(); + let predicate_arguments = predicate_arguments + .iter() + .map(|argument| argument.clone().substitute_variables(&replacement_map)) + .collect::>(); + + // Teach Z3 about the non-aliasing assumptions coming from quantified + // inhales. + let quantified_inhale_ids = self + .global_heap + .boolean_mask_log + .entries + .get(predicate_name) + .unwrap() + .iter() + .enumerate() + .filter_map(|(id, entry)| match entry { + LogEntry::InhaleQuantified(_) => Some(id), + _ => None, + }) + .collect::>(); + for inhale_entry_id in quantified_inhale_ids { + if inhale_entry_id == 0 { + continue; + } + let LogEntry::InhaleQuantified(entry) = self.global_heap.boolean_mask_log.entries.get(predicate_name).unwrap()[inhale_entry_id].clone() else { + unreachable!(); + }; + assert_eq!(&entry.variables, variables, "unimplemented!"); + let arguments: Vec<_> = entry + .arguments + .into_iter() + .map(|argument| argument.substitute_variables(&replacement_map)) + .collect(); + let (guard_definitions, check) = + self.create_permission_check(predicate_name, &arguments, inhale_entry_id)?; + for definition in guard_definitions { + self.assume(&definition)?; + } + let inhale_guard = entry.guard.substitute_variables(&replacement_map); + let negated_check = vir_low::Expression::not(check.clone()); + let guarded_negated_check = vir_low::Expression::implies(inhale_guard, negated_check); + self.comment("non-aliasing-assumptions")?; + self.assume(&guarded_negated_check)?; + } + + // Construct the check. + let guard = guard.substitute_variables(&replacement_map); + let (guard_definitions, mut check_permissions) = + self.create_permission_check(predicate_name, &predicate_arguments, entry_id)?; + check_permissions = vir_low::Expression::implies(guard, check_permissions); + let error = self.create_verification_error_for_expression( + full_error_id, + position, + &check_permissions, + )?; + + self.assert_with_assumptions(&guard_definitions, check_permissions, error)?; + Ok(()) + } + + fn check_quantified_permissions_with_heap( + &mut self, + predicate_name: &str, + predicate_arguments: &[vir_low::Expression], + variables: &[vir_low::VariableDecl], + guard: vir_low::Expression, + entry_id: usize, + full_error_id: &str, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + if config::svirpti_use_pseudo_boolean_heap() { + self.check_quantified_permissions_with_heap_pbge( + predicate_name, + predicate_arguments, + variables, + guard, + entry_id, + full_error_id, + position, + ) + } else { + self.check_quantified_permissions_with_heap_bools( + predicate_name, + predicate_arguments, + variables, + guard, + entry_id, + full_error_id, + position, + ) + } + } + + pub(super) fn execute_exhale_boolean_mask_log_with_heap_full( + &mut self, + predicate: &vir_low::PredicateAccessPredicate, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + assert!(predicate.permission.is_full_permission()); + + // Update local records. + let frame = self.current_frame_mut(); + let state = &mut frame.heap_mut().boolean_mask_log_with_heap; + let log_entry = state.permission_log_entry.get_mut(&predicate.name).unwrap(); + let old_log_entry = *log_entry; + *log_entry += 1; + let new_log_entry = *log_entry; + + // Update the global heap. + let entries = self + .global_heap + .boolean_mask_log + .entries + .get_mut(&predicate.name) + .unwrap(); + if entries.len() > old_log_entry { + entries.truncate(old_log_entry); + } + assert_eq!(entries.len(), old_log_entry); + self.check_permissions_with_heap( + &predicate.name, + &predicate.arguments, + None, + old_log_entry, + "exhale.failed:insufficient.permission", + position, + )?; + let entries = self + .global_heap + .boolean_mask_log + .entries + .get_mut(&predicate.name) + .unwrap(); + let entry = LogEntry::ExhaleFull(LogEntryFull { + arguments: predicate.arguments.clone(), + }); + entries.push(entry); + assert_eq!(entries.len(), new_log_entry); + + Ok(()) + } + + pub(super) fn execute_exhale_quantified_boolean_mask_log_with_heap_full( + &mut self, + quantifier_name: Option, + variables: Vec, + guard: vir_low::Expression, + predicate: vir_low::PredicateAccessPredicate, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + assert!(predicate.permission.is_full_permission()); + + // Update local records. + let frame = self.current_frame_mut(); + let state = &mut frame.heap_mut().boolean_mask_log_with_heap; + let log_entry = state.permission_log_entry.get_mut(&predicate.name).unwrap(); + let old_log_entry = *log_entry; + *log_entry += 1; + let new_log_entry = *log_entry; + + // Update the global heap. + let entries = self + .global_heap + .boolean_mask_log + .entries + .get_mut(&predicate.name) + .unwrap(); + if entries.len() > old_log_entry { + entries.truncate(old_log_entry); + } + assert_eq!(entries.len(), old_log_entry); + self.check_quantified_permissions_with_heap( + &predicate.name, + &predicate.arguments, + &variables, + guard.clone(), + old_log_entry, + "exhale.failed:insufficient.permission", + position, + )?; + let entries = self + .global_heap + .boolean_mask_log + .entries + .get_mut(&predicate.name) + .unwrap(); + let entry = LogEntry::ExhaleQuantified(LogEntryQuantifiedFull { + quantifier_name: quantifier_name.clone(), + variables: variables, + guard: guard.clone(), + arguments: predicate.arguments.clone(), + }); + entries.push(entry); + assert_eq!(entries.len(), new_log_entry); + + Ok(()) + } + + pub(super) fn resolve_snapshot_with_check_boolean_mask_log_with_heap( + &mut self, + path_condition: &[vir_low::Expression], + label: &Option, + predicate_name: &str, + arguments: &[vir_low::Expression], + position: vir_low::Position, + ) -> SpannedEncodingResult { + let heap = self.heap_at_label(label); + let current_log_entry = *heap + .boolean_mask_log_with_heap + .permission_log_entry + .get(predicate_name) + .unwrap(); + let current_heap_id = *heap + .boolean_mask_log_with_heap + .heap_versions + .get(predicate_name) + .unwrap(); + + let current_heap_name = heap_variable_name(predicate_name, current_heap_id); + let predicate_info = self + .predicate_domains_info + .get_with_heap(predicate_name) + .unwrap(); + let current_heap = predicate_info.create_heap_variable(current_heap_name); + + // Check for sufficient permissions. + let guard = path_condition.iter().cloned().conjoin(); + self.check_permissions_with_heap( + predicate_name, + arguments, + Some(guard), + current_log_entry, + "application.precondition:insufficient.permission", + position, + )?; + + // Generate heap snapshot lookup. + let snapshot = predicate_info.lookup_snapshot(¤t_heap, arguments); + + Ok(snapshot) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/svirpti/procedure_verifier/heap/boolean_mask_log_without_heap.rs b/prusti-viper/src/encoder/middle/core_proof/svirpti/procedure_verifier/heap/boolean_mask_log_without_heap.rs new file mode 100644 index 00000000000..dc4739338fe --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/svirpti/procedure_verifier/heap/boolean_mask_log_without_heap.rs @@ -0,0 +1,258 @@ +use super::super::{ + super::super::transformations::encoder_context::EncoderContext, ProcedureExecutor, +}; +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::svirpti::procedure_verifier::heap::boolean_mask_log_with_heap::{ + LogEntry, LogEntryFull, LogEntryKind, + }, +}; +use prusti_common::config; +use rustc_hash::FxHashMap; +use vir_crate::{ + common::expression::{BinaryOperationHelpers, ExpressionIterator, UnaryOperationHelpers}, + low as vir_low, +}; + +#[derive(Default, Clone, Debug)] +pub(in super::super::super::super) struct BooleanMaskLogWithoutHeap { + /// A map from predicate names to the current log entries. + permission_log_entry: FxHashMap, +} + +impl<'a, 'c, EC: EncoderContext> ProcedureExecutor<'a, 'c, EC> { + pub(super) fn initialise_boolean_mask_log_without_heap( + &mut self, + predicate_name: &str, + ) -> SpannedEncodingResult<()> { + let heap = self.current_frame_mut().heap_mut(); + assert!(heap + .boolean_mask_log_without_heap + .permission_log_entry + .insert(predicate_name.to_string(), 0usize) + .is_none()); + assert!(self + .global_heap + .boolean_mask_log + .entries + .insert(predicate_name.to_string(), vec![]) + .is_none()); + Ok(()) + } + + pub(super) fn execute_inhale_boolean_mask_log_without_heap_full( + &mut self, + predicate: vir_low::PredicateAccessPredicate, + _position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + assert!(predicate.permission.is_full_permission()); + + // Update local records. + let frame: &mut crate::encoder::middle::core_proof::svirpti::procedure_verifier::solver_stack::StackFrame = self.current_frame_mut(); + let state = &mut frame.heap_mut().boolean_mask_log_without_heap; + let log_entry = state.permission_log_entry.get_mut(&predicate.name).unwrap(); + let old_log_entry = *log_entry; + *log_entry += 1; + let new_log_entry = *log_entry; + + // Update Z3 state. + if !config::svirpti_use_pseudo_boolean_heap() && old_log_entry > 0 { + let (guard_definitions, check) = + self.create_permission_check(&predicate.name, &predicate.arguments, old_log_entry)?; + for definition in guard_definitions { + self.assume(&definition)?; + } + let negated_check = vir_low::Expression::not(check.clone()); + self.assume(&negated_check)?; + } + + // Update the global heap. + let entries = self + .global_heap + .boolean_mask_log + .entries + .get_mut(&predicate.name) + .unwrap(); + if entries.len() > old_log_entry { + entries.truncate(old_log_entry); + } + assert_eq!(entries.len(), old_log_entry); + let entry = LogEntry::InhaleFull(LogEntryFull { + arguments: predicate.arguments.clone(), + }); + entries.push(entry); + assert_eq!(entries.len(), new_log_entry); + + Ok(()) + } + + fn check_permissions_without_heap_pbge( + &mut self, + predicate_name: &str, + predicate_arguments: &[vir_low::Expression], + guard: Option, + entry_id: usize, + full_error_id: &str, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + use vir_low::macros::*; + assert!( + entry_id > 0, + "TODO: A proper error message that we are exhaling for an empty heap." + ); + let entries = self + .global_heap + .boolean_mask_log + .entries + .get_mut(predicate_name) + .unwrap(); + let mut pbge_arguments = Vec::with_capacity(entry_id); + let plus_one: vir_low::Expression = 1.into(); + let minus_one: vir_low::Expression = (-1).into(); + for entry in entries.iter().take(entry_id) { + match entry { + LogEntry::InhaleFull(_) => { + pbge_arguments.push(plus_one.clone()); + } + LogEntry::ExhaleFull(_) => { + pbge_arguments.push(minus_one.clone()); + } + LogEntry::InhaleQuantified(_) => { + unimplemented!(); + } + LogEntry::ExhaleQuantified(_) => { + unimplemented!(); + } + } + } + for entry in entries.iter().take(entry_id) { + unimplemented!(); + // let arguments_equal = predicate_arguments + // .iter() + // .zip(entry.arguments.iter()) + // .map(|(predicate_argument, entry_argument)| { + // expr! { [predicate_argument.clone()] == [entry_argument.clone()] } + // }) + // .conjoin(); + // pbge_arguments.push(arguments_equal); + } + let mut check_permissions = vir_low::Expression::smt_operation_no_pos( + vir_low::SmtOperationKind::PbQe, + pbge_arguments, + vir_low::Type::Bool, + ); + if let Some(guard) = guard { + check_permissions = vir_low::Expression::implies(guard, check_permissions); + } + let error = self.create_verification_error_for_expression( + full_error_id, + position, + &check_permissions, + )?; + self.assert(check_permissions, error)?; + Ok(()) + } + + fn check_permissions_without_heap_bools( + &mut self, + predicate_name: &str, + predicate_arguments: &[vir_low::Expression], + guard: Option, + entry_id: usize, + full_error_id: &str, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + let (guard_definitions, mut check_permissions) = + self.create_permission_check(predicate_name, predicate_arguments, entry_id)?; + if let Some(guard) = guard { + check_permissions = vir_low::Expression::implies(guard, check_permissions); + } + let error = self.create_verification_error_for_expression( + full_error_id, + position, + &check_permissions, + )?; + self.assert_with_assumptions(&guard_definitions, check_permissions, error)?; + Ok(()) + } + + fn check_permissions_without_heap( + &mut self, + predicate_name: &str, + predicate_arguments: &[vir_low::Expression], + guard: Option, + entry_id: usize, + full_error_id: &str, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + // FIXME: Avoid code duplication between heap and non-heap versions. + if config::svirpti_use_pseudo_boolean_heap() { + self.check_permissions_without_heap_pbge( + predicate_name, + predicate_arguments, + guard, + entry_id, + full_error_id, + position, + ) + } else { + self.check_permissions_without_heap_bools( + predicate_name, + predicate_arguments, + guard, + entry_id, + full_error_id, + position, + ) + } + } + + pub(super) fn execute_exhale_boolean_mask_log_without_heap_full( + &mut self, + predicate: &vir_low::PredicateAccessPredicate, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + assert!(predicate.permission.is_full_permission()); + + // Update local records. + let frame = self.current_frame_mut(); + let state = &mut frame.heap_mut().boolean_mask_log_without_heap; + let log_entry = state.permission_log_entry.get_mut(&predicate.name).unwrap(); + let old_log_entry = *log_entry; + *log_entry += 1; + let new_log_entry = *log_entry; + + // Update the global heap. + let entries = self + .global_heap + .boolean_mask_log + .entries + .get_mut(&predicate.name) + .unwrap(); + if entries.len() > old_log_entry { + entries.truncate(old_log_entry); + } + assert_eq!(entries.len(), old_log_entry); + self.check_permissions_without_heap( + &predicate.name, + &predicate.arguments, + None, + old_log_entry, + "exhale.failed:insufficient.permission", + position, + )?; + let entries = self + .global_heap + .boolean_mask_log + .entries + .get_mut(&predicate.name) + .unwrap(); + let entry = LogEntry::ExhaleFull(LogEntryFull { + arguments: predicate.arguments.clone(), + }); + entries.push(entry); + assert_eq!(entries.len(), new_log_entry); + + Ok(()) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/svirpti/procedure_verifier/heap/boolean_mask_with_heap.rs b/prusti-viper/src/encoder/middle/core_proof/svirpti/procedure_verifier/heap/boolean_mask_with_heap.rs new file mode 100644 index 00000000000..cd4a1bc01bb --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/svirpti/procedure_verifier/heap/boolean_mask_with_heap.rs @@ -0,0 +1,238 @@ +use super::super::{ + super::super::transformations::encoder_context::EncoderContext, ProcedureExecutor, +}; +use crate::encoder::errors::SpannedEncodingResult; +use rustc_hash::FxHashMap; +use vir_crate::{ + common::expression::{BinaryOperationHelpers, ExpressionIterator}, + low as vir_low, +}; + +#[derive(Default, Clone, Debug)] +pub(in super::super::super::super) struct BooleanMaskWithHeap { + // TODO: Rename to BooleanMaskHeap. + /// A map from predicate names to their permission mask versions. + permission_mask_versions: FxHashMap, + heap_versions: FxHashMap, +} + +fn permission_mask_variable_name(predicate_name: &str, id: usize) -> String { + format!("{}$mask${}", predicate_name, id) +} + +fn heap_variable_name(predicate_name: &str, id: usize) -> String { + format!("{}$heap${}", predicate_name, id) +} + +impl<'a, 'c, EC: EncoderContext> ProcedureExecutor<'a, 'c, EC> { + pub(super) fn initialise_boolean_mask_with_heap( + &mut self, + predicate_name: &str, + ) -> SpannedEncodingResult<()> { + let id = self.generate_fresh_id(); + let heap = self.current_frame_mut().heap_mut(); + assert!(heap + .boolean_mask_with_heap + .permission_mask_versions + .insert(predicate_name.to_string(), id) + .is_none()); + assert!(heap + .boolean_mask_with_heap + .heap_versions + .insert(predicate_name.to_string(), id) + .is_none()); + let permission_mask_name = permission_mask_variable_name(predicate_name, id); + let heap_name = heap_variable_name(predicate_name, id); + let predicate_info = self + .predicate_domains_info + .get_with_heap(predicate_name) + .unwrap(); + let permission_mask = predicate_info.create_permission_mask_variable(permission_mask_name); + let heap = predicate_info.create_heap_variable(heap_name); + self.declare_variable(&permission_mask)?; + self.declare_variable(&heap)?; + Ok(()) + } + + pub(super) fn execute_inhale_boolean_mask_with_heap_full( + &mut self, + predicate: &vir_low::PredicateAccessPredicate, + _position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + assert!(predicate.permission.is_full_permission()); + + // Update local records. + let new_permission_mask_id = self.generate_fresh_id(); + let frame = self.current_frame_mut(); + let memory_block = &mut frame.heap_mut().boolean_mask_with_heap; + let permission_mask_version = memory_block + .permission_mask_versions + .get_mut(&predicate.name) + .unwrap(); + let current_permission_mask_id = *permission_mask_version; + *permission_mask_version = new_permission_mask_id; + + // Update the SMT solver state. + let current_permission_mask_name = + permission_mask_variable_name(&predicate.name, current_permission_mask_id); + let new_permission_mask_name = + permission_mask_variable_name(&predicate.name, new_permission_mask_id); + + let predicate_info = self + .predicate_domains_info + .get_with_heap(&predicate.name) + .unwrap(); + let current_permission_mask = + predicate_info.create_permission_mask_variable(current_permission_mask_name); + let new_permission_mask = + predicate_info.create_permission_mask_variable(new_permission_mask_name); + + self.declare_variable(&new_permission_mask)?; + + let update_permissions = predicate_info.set_permissions_to_full( + ¤t_permission_mask, + &new_permission_mask, + &predicate.arguments, + ); + // Note: We are keeping the old version of the heap because we are not + // removing anything. + self.assume(&update_permissions)?; + + // // // Assume that the old permission is none. + // // let mut current_arguments = vec![current_permission_mask.clone().into()]; + // // current_arguments.extend(predicate.arguments.clone()); + // // let old_permission = vir_low::Expression::domain_function_call( + // // MEMORY_BLOCK_PERMISSION_MASK_DOMAIN, + // // "perm", + // // current_arguments, + // // permission_mask_type()); + // // let old_permission_is_none = vir_low::Expression::not( + // // old_permission, + // // ); + // // self.assume(&old_permission_is_none)?; + // // Update the permission mask. This also assumes that the old permission is none. + // let mut new_arguments = vec![ + // current_permission_mask.clone().into(), + // new_permission_mask.clone().into(), + // ]; + // new_arguments.extend(predicate.arguments.clone()); + // let update_mask = vir_low::Expression::domain_function_call( + // MEMORY_BLOCK_PERMISSION_MASK_DOMAIN, + // "set_full_permission", + // new_arguments, + // permission_mask_type(), + // ); + // self.assume(&update_mask)?; + + Ok(()) + } + + pub(super) fn execute_exhale_boolean_mask_with_heap_full( + &mut self, + predicate: &vir_low::PredicateAccessPredicate, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + assert!(predicate.permission.is_full_permission()); + + // TODO: Avoid code duplication with execute_inhale_boolean_mask_full. BEGIN + + // Update local records. + let new_permission_mask_id = self.generate_fresh_id(); + let frame = self.current_frame_mut(); + let memory_block = &mut frame.heap_mut().boolean_mask_with_heap; + let permission_mask_version = memory_block + .permission_mask_versions + .get_mut(&predicate.name) + .unwrap(); + let current_permission_mask_id = *permission_mask_version; + *permission_mask_version = new_permission_mask_id; + + // Update the SMT solver state. + let current_permission_mask_name = + permission_mask_variable_name(&predicate.name, current_permission_mask_id); + let new_permission_mask_name = + permission_mask_variable_name(&predicate.name, new_permission_mask_id); + + let predicate_info = self + .predicate_domains_info + .get_with_heap(&predicate.name) + .unwrap(); + let current_permission_mask = + predicate_info.create_permission_mask_variable(current_permission_mask_name); + let new_permission_mask = + predicate_info.create_permission_mask_variable(new_permission_mask_name); + + self.declare_variable(&new_permission_mask)?; + // TODO: END + + let check_permissions = + predicate_info.check_permissions_full(¤t_permission_mask, &predicate.arguments); + let error = self.create_verification_error_for_expression( + "exhale.failed:insufficient.permission", + position, + &check_permissions, + )?; + self.assert(check_permissions, error)?; + + let update_permissions = predicate_info.set_permissions_to_none( + ¤t_permission_mask, + &new_permission_mask, + &predicate.arguments, + ); + self.assume(&update_permissions)?; + + // TODO: Havoc heap. + + Ok(()) + } + + pub(super) fn resolve_snapshot_with_check_boolean_mask_with_heap( + &mut self, + path_condition: &[vir_low::Expression], + label: &Option, + predicate_name: &str, + arguments: &[vir_low::Expression], + position: vir_low::Position, + ) -> SpannedEncodingResult { + let heap = self.heap_at_label(label); + let current_permission_mask_id = *heap + .boolean_mask_with_heap + .permission_mask_versions + .get(predicate_name) + .unwrap(); + let current_heap_id = *heap + .boolean_mask_with_heap + .heap_versions + .get(predicate_name) + .unwrap(); + + let current_permission_mask_name = + permission_mask_variable_name(predicate_name, current_permission_mask_id); + let current_heap_name = heap_variable_name(predicate_name, current_heap_id); + let predicate_info = self + .predicate_domains_info + .get_with_heap(predicate_name) + .unwrap(); + let current_permission_mask = + predicate_info.create_permission_mask_variable(current_permission_mask_name); + let current_heap = predicate_info.create_heap_variable(current_heap_name); + + // Check for sufficient permissions. + let check_permissions = + predicate_info.check_permissions_full(¤t_permission_mask, arguments); + + // Generate heap snapshot lookup. + let snapshot = predicate_info.lookup_snapshot(¤t_heap, arguments); + + let guard = path_condition.iter().cloned().conjoin(); + let check = vir_low::Expression::implies(guard, check_permissions); + let error = self.create_verification_error_for_expression( + "application.precondition:insufficient.permission", + position, + &check, + )?; + self.assert(check, error)?; + + Ok(snapshot) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/svirpti/procedure_verifier/heap/boolean_mask_without_heap.rs b/prusti-viper/src/encoder/middle/core_proof/svirpti/procedure_verifier/heap/boolean_mask_without_heap.rs new file mode 100644 index 00000000000..fc9fb8e8dde --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/svirpti/procedure_verifier/heap/boolean_mask_without_heap.rs @@ -0,0 +1,143 @@ +use super::super::{ + super::super::transformations::encoder_context::EncoderContext, ProcedureExecutor, +}; +use crate::encoder::errors::SpannedEncodingResult; +use rustc_hash::FxHashMap; +use vir_crate::low as vir_low; + +#[derive(Default, Clone, Debug)] +pub(in super::super::super::super) struct BooleanMaskWithoutHeap { + /// A map from predicate names to their permission mask versions. + permission_mask_versions: FxHashMap, +} + +fn permission_mask_variable_name(predicate_name: &str, id: usize) -> String { + format!("{}$mask${}", predicate_name, id) +} + +impl<'a, 'c, EC: EncoderContext> ProcedureExecutor<'a, 'c, EC> { + pub(super) fn initialise_boolean_mask_without_heap( + &mut self, + predicate_name: &str, + ) -> SpannedEncodingResult<()> { + let id = self.generate_fresh_id(); + let heap = self.current_frame_mut().heap_mut(); + assert!(heap + .boolean_mask_without_heap + .permission_mask_versions + .insert(predicate_name.to_string(), id) + .is_none()); + let permission_mask_name = permission_mask_variable_name(predicate_name, id); + let predicate_info = self + .predicate_domains_info + .get_permissions_info(predicate_name) + .unwrap(); + let permission_mask = predicate_info.create_permission_mask_variable(permission_mask_name); + self.declare_variable(&permission_mask)?; + Ok(()) + } + + pub(super) fn execute_inhale_boolean_mask_without_heap_full( + &mut self, + predicate: &vir_low::PredicateAccessPredicate, + _position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + assert!(predicate.permission.is_full_permission()); + + // Update local records. + let new_permission_mask_id = self.generate_fresh_id(); + let frame = self.current_frame_mut(); + let memory_block = &mut frame.heap_mut().boolean_mask_without_heap; + let permission_mask_version = memory_block + .permission_mask_versions + .get_mut(&predicate.name) + .unwrap(); + let current_permission_mask_id = *permission_mask_version; + *permission_mask_version = new_permission_mask_id; + + // Update the SMT solver state. + let current_permission_mask_name = + permission_mask_variable_name(&predicate.name, current_permission_mask_id); + let new_permission_mask_name = + permission_mask_variable_name(&predicate.name, new_permission_mask_id); + + let predicate_info = self + .predicate_domains_info + .get_permissions_info(&predicate.name) + .unwrap(); + let current_permission_mask = + predicate_info.create_permission_mask_variable(current_permission_mask_name); + let new_permission_mask = + predicate_info.create_permission_mask_variable(new_permission_mask_name); + + self.declare_variable(&new_permission_mask)?; + + let update_permissions = predicate_info.set_permissions_to_full( + ¤t_permission_mask, + &new_permission_mask, + &predicate.arguments, + ); + // Note: We are keeping the old version of the heap because we are not + // removing anything. + self.assume(&update_permissions)?; + + Ok(()) + } + + pub(super) fn execute_exhale_boolean_mask_without_heap_full( + &mut self, + predicate: &vir_low::PredicateAccessPredicate, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + assert!(predicate.permission.is_full_permission()); + + // TODO: Avoid code duplication with execute_inhale_boolean_mask_full. BEGIN + + // Update local records. + let new_permission_mask_id = self.generate_fresh_id(); + let frame = self.current_frame_mut(); + let memory_block = &mut frame.heap_mut().boolean_mask_without_heap; + let permission_mask_version = memory_block + .permission_mask_versions + .get_mut(&predicate.name) + .unwrap(); + let current_permission_mask_id = *permission_mask_version; + *permission_mask_version = new_permission_mask_id; + + // Update the SMT solver state. + let current_permission_mask_name = + permission_mask_variable_name(&predicate.name, current_permission_mask_id); + let new_permission_mask_name = + permission_mask_variable_name(&predicate.name, new_permission_mask_id); + + let predicate_info = self + .predicate_domains_info + .get_permissions_info(&predicate.name) + .unwrap(); + let current_permission_mask = + predicate_info.create_permission_mask_variable(current_permission_mask_name); + let new_permission_mask = + predicate_info.create_permission_mask_variable(new_permission_mask_name); + + self.declare_variable(&new_permission_mask)?; + // TODO: END + + let check_permissions = + predicate_info.check_permissions_full(¤t_permission_mask, &predicate.arguments); + let error = self.create_verification_error_for_expression( + "exhale.failed:insufficient.permission", + position, + &check_permissions, + )?; + self.assert(check_permissions, error)?; + + let update_permissions = predicate_info.set_permissions_to_none( + ¤t_permission_mask, + &new_permission_mask, + &predicate.arguments, + ); + self.assume(&update_permissions)?; + + Ok(()) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/svirpti/procedure_verifier/heap/expression.rs b/prusti-viper/src/encoder/middle/core_proof/svirpti/procedure_verifier/heap/expression.rs new file mode 100644 index 00000000000..9a301cea69a --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/svirpti/procedure_verifier/heap/expression.rs @@ -0,0 +1,173 @@ +use super::super::{ + super::super::transformations::encoder_context::EncoderContext, ProcedureExecutor, +}; +use crate::encoder::errors::SpannedEncodingError; +use vir_crate::{ + common::{expression::UnaryOperationHelpers, position::Positioned}, + low::{ + self as vir_low, expression::visitors::ExpressionFallibleFolder, + operations::quantifiers::BoundVariableStack, + }, +}; + +pub(super) struct ExpressionPurifier<'a, 'b, 'c, EC: EncoderContext> { + executor: &'a mut ProcedureExecutor<'b, 'c, EC>, + bound_variables: BoundVariableStack, + label: Option, + path_condition: Vec, +} + +impl<'a, 'b, 'c, EC: EncoderContext> ExpressionPurifier<'a, 'b, 'c, EC> { + pub(super) fn new(executor: &'a mut ProcedureExecutor<'b, 'c, EC>) -> Self { + Self { + executor, + bound_variables: Default::default(), + label: None, + path_condition: Vec::new(), + } + } +} + +impl<'a, 'b, 'c, EC: EncoderContext> ExpressionFallibleFolder + for ExpressionPurifier<'a, 'b, 'c, EC> +{ + type Error = SpannedEncodingError; + + fn fallible_fold_trigger( + &mut self, + mut trigger: vir_low::Trigger, + ) -> Result { + for term in std::mem::take(&mut trigger.terms) { + let new_term = self.fallible_fold_expression(term)?; + trigger.terms.push(new_term); + } + Ok(trigger) + } + + fn fallible_fold_func_app_enum( + &mut self, + func_app: vir_low::expression::FuncApp, + ) -> Result { + let func_app = self.fallible_fold_func_app(func_app)?; + let function = self + .executor + .program_context + .get_function(&func_app.function_name); + assert_eq!(function.parameters.len(), func_app.arguments.len()); + let snapshot = match function.kind { + vir_low::FunctionKind::MemoryBlockBytes | vir_low::FunctionKind::Snap => { + let predicate_name = self + .executor + .program_context + .get_snapshot_predicate(&func_app.function_name) + .unwrap() + .to_string(); + self.executor.resolve_snapshot_with_check_predicate( + &self.path_condition, + &self.label, + &predicate_name, + &func_app.arguments, + func_app.position, + )? + } + vir_low::FunctionKind::CallerFor => todo!(), + vir_low::FunctionKind::SnapRange => todo!(), + }; + + // if func_app.context == vir_low::FuncAppContext::QuantifiedPermission { + // debug_assert!(matches!( + // function.kind, + // vir_low::FunctionKind::MemoryBlockBytes | vir_low::FunctionKind::Snap + // )); + // // This function application is dependent on the quantified resource + // // and should not be purified out. + // return Ok(vir_low::Expression::FuncApp(func_app)); + // } + // match function.kind { + // vir_low::FunctionKind::CallerFor | vir_low::FunctionKind::SnapRange => { + // Ok(vir_low::Expression::FuncApp(func_app)) + // } + // vir_low::FunctionKind::MemoryBlockBytes | vir_low::FunctionKind::Snap => { + // match self.resolve_snapshot(&func_app.function_name, &func_app.arguments)? { + // FindSnapshotResult::NotFound => Ok(vir_low::Expression::FuncApp(func_app)), + // FindSnapshotResult::FoundGuarded { + // snapshot, + // precondition, + // } => { + // if let Some(assertion) = precondition { + // let guarded_assertion = vir_low::Expression::implies( + // self.path_condition.clone().into_iter().conjoin(), + // assertion, + // ); + // self.guarded_assertions.push(guarded_assertion); + // } + // Ok(vir_low::Expression::local(snapshot, func_app.position)) + // } + // FindSnapshotResult::FoundConditional { + // binding, + // guarded_candidates, + // } => { + // assert!(!guarded_candidates.is_empty()); + // self.bindings.push(SnapshotBinding { + // guard: self.path_condition.clone().into_iter().conjoin(), + // variable: binding.clone(), + // guarded_candidates, + // }); + // Ok(vir_low::Expression::local(binding, func_app.position)) + // } + // } + // } + // } + Ok(snapshot) + } + + fn fallible_fold_labelled_old_enum( + &mut self, + mut labelled_old: vir_low::LabelledOld, + ) -> Result { + std::mem::swap(&mut labelled_old.label, &mut self.label); + let body = self.fallible_fold_expression(*labelled_old.base)?; + std::mem::swap(&mut labelled_old.label, &mut self.label); + Ok(body) + } + + fn fallible_fold_quantifier_enum( + &mut self, + quantifier: vir_low::Quantifier, + ) -> Result { + self.bound_variables.push(&quantifier.variables); + let quantifier = self.fallible_fold_quantifier(quantifier)?; + self.bound_variables.pop(); + Ok(vir_low::Expression::Quantifier(quantifier)) + } + + fn fallible_fold_binary_op( + &mut self, + mut binary_op: vir_low::expression::BinaryOp, + ) -> Result { + binary_op.left = self.fallible_fold_expression_boxed(binary_op.left)?; + if binary_op.op_kind == vir_low::BinaryOpKind::Implies { + self.path_condition.push((*binary_op.left).clone()); + } + binary_op.right = self.fallible_fold_expression_boxed(binary_op.right)?; + if binary_op.op_kind == vir_low::BinaryOpKind::Implies { + self.path_condition.pop(); + } + Ok(binary_op) + } + + fn fallible_fold_conditional( + &mut self, + mut conditional: vir_low::expression::Conditional, + ) -> Result { + conditional.guard = self.fallible_fold_expression_boxed(conditional.guard)?; + self.path_condition.push((*conditional.guard).clone()); + conditional.then_expr = self.fallible_fold_expression_boxed(conditional.then_expr)?; + self.path_condition.pop(); + self.path_condition + .push(vir_low::Expression::not((*conditional.guard).clone())); + conditional.else_expr = self.fallible_fold_expression_boxed(conditional.else_expr)?; + self.path_condition.pop(); + Ok(conditional) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/svirpti/procedure_verifier/heap/lifetimes.rs b/prusti-viper/src/encoder/middle/core_proof/svirpti/procedure_verifier/heap/lifetimes.rs new file mode 100644 index 00000000000..c84b7ca9ca4 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/svirpti/procedure_verifier/heap/lifetimes.rs @@ -0,0 +1,138 @@ +use super::super::{ + super::super::transformations::encoder_context::EncoderContext, ProcedureExecutor, +}; +use crate::encoder::errors::SpannedEncodingResult; + +use std::collections::BTreeMap; +use vir_crate::{ + common::expression::BinaryOperationHelpers, + low::{self as vir_low}, +}; + +#[derive(Default, Clone, Debug)] +pub(in super::super::super::super) struct LifetimeTokens { + /// Map from variables identifying tokens to variables tracking permission + /// amounts. + tokens: BTreeMap, +} + +fn permission_variable(id: usize) -> SpannedEncodingResult { + let name = format!("lifetime_token_permission${}", id); + let variable = vir_low::VariableDecl::new(name, vir_low::Type::Perm); + Ok(variable) +} + +impl<'a, 'c, EC: EncoderContext> ProcedureExecutor<'a, 'c, EC> { + pub(super) fn execute_inhale_lifetime_token( + &mut self, + predicate: &vir_low::PredicateAccessPredicate, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + assert_eq!(predicate.arguments.len(), 1); + let Some(vir_low::Expression::Local(local)) = predicate.arguments.get(0) else { + unimplemented!("TODO: A proper error message."); + }; + let permission_amount_is_non_negative = vir_low::Expression::greater_equals( + (*predicate.permission).clone(), + vir_low::Expression::no_permission(), + ); + let error = self.create_verification_error_for_expression( + "inhale.failed:negative.permission", + position, + &permission_amount_is_non_negative, + )?; + self.assert(permission_amount_is_non_negative, error)?; + let new_permission_id = self.generate_fresh_id(); + let new_permission_variable = permission_variable(new_permission_id)?; + self.declare_variable(&new_permission_variable)?; + let frame = self.current_frame_mut(); + if let Some(current_permission_id) = frame + .heap_mut() + .lifetime_tokens + .tokens + .get_mut(&local.variable.name) + { + let current_permission_variable = permission_variable(*current_permission_id)?; + *current_permission_id = new_permission_id; + let set_new_value = vir_low::Expression::equals( + new_permission_variable.into(), + vir_low::Expression::perm_binary_op( + vir_low::PermBinaryOpKind::Add, + current_permission_variable.into(), + (*predicate.permission).clone(), + position, + ), + ); + self.assume(&set_new_value)?; + } else { + frame + .heap_mut() + .lifetime_tokens + .tokens + .insert(local.variable.name.clone(), new_permission_id); + let set_new_value = vir_low::Expression::equals( + new_permission_variable.into(), + (*predicate.permission).clone(), + ); + self.assume(&set_new_value)?; + } + Ok(()) + } + + pub(super) fn execute_exhale_lifetime_token( + &mut self, + predicate: &vir_low::PredicateAccessPredicate, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + assert_eq!(predicate.arguments.len(), 1); + let Some(vir_low::Expression::Local(local)) = predicate.arguments.get(0) else { + unimplemented!("TODO: A proper error message."); + }; + let permission_amount_is_non_negative = vir_low::Expression::greater_equals( + (*predicate.permission).clone(), + vir_low::Expression::no_permission(), + ); + let error = self.create_verification_error_for_expression( + "exhale.failed:negative.permission", + position, + &permission_amount_is_non_negative, + )?; + self.assert(permission_amount_is_non_negative, error)?; + let new_permission_id = self.generate_fresh_id(); + let new_permission_variable = permission_variable(new_permission_id)?; + self.declare_variable(&new_permission_variable)?; + let frame = self.current_frame_mut(); + if let Some(current_permission_id) = frame + .heap_mut() + .lifetime_tokens + .tokens + .get_mut(&local.variable.name) + { + let current_permission_variable = permission_variable(*current_permission_id)?; + *current_permission_id = new_permission_id; + let set_new_value = vir_low::Expression::equals( + new_permission_variable.clone().into(), + vir_low::Expression::perm_binary_op( + vir_low::PermBinaryOpKind::Sub, + current_permission_variable.into(), + (*predicate.permission).clone(), + position, + ), + ); + self.assume(&set_new_value)?; + let new_permission_amount_is_non_negative = vir_low::Expression::greater_equals( + new_permission_variable.into(), + vir_low::Expression::no_permission(), + ); + let error = self.create_verification_error_for_expression( + "exhale.failed:insufficient.permission", + position, + &new_permission_amount_is_non_negative, + )?; + self.assert(new_permission_amount_is_non_negative, error)?; + } else { + unimplemented!("TODO: Report a verification error."); + } + Ok(()) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/svirpti/procedure_verifier/heap/mod.rs b/prusti-viper/src/encoder/middle/core_proof/svirpti/procedure_verifier/heap/mod.rs new file mode 100644 index 00000000000..50efe36db89 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/svirpti/procedure_verifier/heap/mod.rs @@ -0,0 +1,305 @@ +use self::expression::ExpressionPurifier; +use super::{super::super::transformations::encoder_context::EncoderContext, ProcedureExecutor}; +use crate::encoder::errors::SpannedEncodingResult; +use vir_crate::low::{self as vir_low, expression::visitors::ExpressionFallibleFolder}; + +mod lifetimes; +mod boolean_mask_log_with_heap; +mod boolean_mask_log_without_heap; +mod boolean_mask_with_heap; +mod boolean_mask_without_heap; +mod expression; + +#[derive(Default, Clone, Debug)] +pub(super) struct Heap { + lifetime_tokens: lifetimes::LifetimeTokens, + boolean_mask_with_heap: boolean_mask_with_heap::BooleanMaskWithHeap, + boolean_mask_without_heap: boolean_mask_without_heap::BooleanMaskWithoutHeap, + boolean_mask_log_with_heap: boolean_mask_log_with_heap::BooleanMaskLogWithHeap, + boolean_mask_log_without_heap: boolean_mask_log_without_heap::BooleanMaskLogWithoutHeap, +} + +#[derive(Default, Debug)] +pub(super) struct GlobalHeap { + boolean_mask_log: boolean_mask_log_with_heap::BooleanMaskLog, +} + +impl<'a, 'c, EC: EncoderContext> ProcedureExecutor<'a, 'c, EC> { + pub(super) fn initialise_heap( + &mut self, + predicates: &[vir_low::PredicateDecl], + ) -> SpannedEncodingResult<()> { + for predicate in predicates { + match predicate.kind { + vir_low::PredicateKind::Owned | vir_low::PredicateKind::MemoryBlock => { + self.initialise_boolean_mask_log_with_heap(&predicate.name)?; + // self.initialise_boolean_mask_with_heap(&predicate.name)?; + } + vir_low::PredicateKind::LifetimeToken => { + // Nothing to do. + } + vir_low::PredicateKind::CloseFracRef => todo!(), + vir_low::PredicateKind::WithoutSnapshotWhole => todo!(), + vir_low::PredicateKind::WithoutSnapshotWholeNonAliased => { + self.initialise_boolean_mask_log_without_heap(&predicate.name)?; + // self.initialise_boolean_mask_without_heap(&predicate.name)?; + } + vir_low::PredicateKind::DeadLifetimeToken => { + // Nothing to do. + } + vir_low::PredicateKind::EndBorrowViewShift => todo!(), + } + } + Ok(()) + } + + pub(super) fn execute_inhale_predicate( + &mut self, + predicate: vir_low::PredicateAccessPredicate, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + let predicate_kind = self.program_context.get_predicate_kind(&predicate.name); + match predicate_kind { + vir_low::PredicateKind::Owned | vir_low::PredicateKind::MemoryBlock => { + if predicate.permission.is_full_permission() { + // self.execute_inhale_boolean_mask_with_heap_full(&predicate, position)?; + self.execute_inhale_boolean_mask_log_with_heap_full(predicate, position)?; + } else { + // self.execute_inhale_memory_block_fractional(&predicate, position)?; + unimplemented!("inhale_predicate: {predicate}"); + } + } + vir_low::PredicateKind::LifetimeToken => { + self.execute_inhale_lifetime_token(&predicate, position)?; + } + vir_low::PredicateKind::DeadLifetimeToken => { + unimplemented!("inhale_predicate: {predicate}"); + } + vir_low::PredicateKind::CloseFracRef => { + unimplemented!("inhale_predicate: {predicate}"); + } + vir_low::PredicateKind::WithoutSnapshotWhole => { + unimplemented!("inhale_predicate: {predicate}"); + } + vir_low::PredicateKind::WithoutSnapshotWholeNonAliased => { + if predicate.permission.is_full_permission() { + self.execute_inhale_boolean_mask_log_without_heap_full(predicate, position)?; + // self.execute_inhale_boolean_mask_without_heap_full(&predicate, position)?; + } else { + // self.execute_inhale_memory_block_fractional(&predicate, position)?; + unimplemented!("inhale_predicate: {predicate}"); + } + } + vir_low::PredicateKind::EndBorrowViewShift => { + unimplemented!("inhale_predicate: {predicate}"); + } + }; + Ok(()) + } + + pub(super) fn execute_inhale_quantified_predicate( + &mut self, + quantifier_name: Option, + variables: Vec, + guard: vir_low::Expression, + predicate: vir_low::PredicateAccessPredicate, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + let predicate_kind = self.program_context.get_predicate_kind(&predicate.name); + match predicate_kind { + vir_low::PredicateKind::Owned | vir_low::PredicateKind::MemoryBlock => { + if predicate.permission.is_full_permission() { + self.execute_inhale_quantified_boolean_mask_log_with_heap_full( + quantifier_name, + variables, + guard, + predicate, + position, + )?; + } else { + unimplemented!("inhale_predicate: {predicate}"); + } + } + vir_low::PredicateKind::LifetimeToken => { + unimplemented!("inhale_predicate: {predicate}"); + } + vir_low::PredicateKind::DeadLifetimeToken => { + unimplemented!("inhale_predicate: {predicate}"); + } + vir_low::PredicateKind::CloseFracRef => { + unimplemented!("inhale_predicate: {predicate}"); + } + vir_low::PredicateKind::WithoutSnapshotWhole => { + unimplemented!("inhale_predicate: {predicate}"); + } + vir_low::PredicateKind::WithoutSnapshotWholeNonAliased => { + unimplemented!("inhale_predicate: {predicate}"); + } + vir_low::PredicateKind::EndBorrowViewShift => { + unimplemented!("inhale_predicate: {predicate}"); + } + }; + Ok(()) + } + + pub(super) fn execute_exhale_predicate( + &mut self, + predicate: &vir_low::PredicateAccessPredicate, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + let predicate_kind = self.program_context.get_predicate_kind(&predicate.name); + match predicate_kind { + vir_low::PredicateKind::Owned | vir_low::PredicateKind::MemoryBlock => { + if predicate.permission.is_full_permission() { + // self.execute_exhale_boolean_mask_with_heap_full(&predicate, position)?; + self.execute_exhale_boolean_mask_log_with_heap_full(predicate, position)?; + } else { + // self.execute_exhale_memory_block_fractional(&predicate, position)?; + unimplemented!("exhale_predicate: {predicate}"); + } + } + vir_low::PredicateKind::LifetimeToken => { + self.execute_exhale_lifetime_token(predicate, position)?; + } + vir_low::PredicateKind::DeadLifetimeToken => { + unimplemented!("exhale_predicate: {predicate}"); + } + vir_low::PredicateKind::CloseFracRef => { + unimplemented!("exhale_predicate: {predicate}"); + } + vir_low::PredicateKind::WithoutSnapshotWhole => { + unimplemented!("exhale_predicate: {predicate}"); + } + vir_low::PredicateKind::WithoutSnapshotWholeNonAliased => { + if predicate.permission.is_full_permission() { + self.execute_exhale_boolean_mask_log_without_heap_full(predicate, position)?; + // self.execute_exhale_boolean_mask_without_heap_full(&predicate, position)?; + } else { + // self.execute_exhale_memory_block_fractional(&predicate, position)?; + unimplemented!("exhale_predicate: {predicate}"); + } + } + vir_low::PredicateKind::EndBorrowViewShift => { + unimplemented!("exhale_predicate: {predicate}"); + } + }; + Ok(()) + } + + pub(super) fn execute_exhale_quantified_predicate( + &mut self, + quantifier_name: Option, + variables: Vec, + guard: vir_low::Expression, + predicate: vir_low::PredicateAccessPredicate, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + let predicate_kind = self.program_context.get_predicate_kind(&predicate.name); + match predicate_kind { + vir_low::PredicateKind::Owned | vir_low::PredicateKind::MemoryBlock => { + if predicate.permission.is_full_permission() { + self.execute_exhale_quantified_boolean_mask_log_with_heap_full( + quantifier_name, + variables, + guard, + predicate, + position, + )?; + } else { + unimplemented!("exhale_predicate: {predicate}"); + } + } + vir_low::PredicateKind::LifetimeToken => { + unimplemented!("exhale_predicate: {predicate}"); + } + vir_low::PredicateKind::DeadLifetimeToken => { + unimplemented!("exhale_predicate: {predicate}"); + } + vir_low::PredicateKind::CloseFracRef => { + unimplemented!("exhale_predicate: {predicate}"); + } + vir_low::PredicateKind::WithoutSnapshotWhole => { + unimplemented!("exhale_predicate: {predicate}"); + } + vir_low::PredicateKind::WithoutSnapshotWholeNonAliased => { + unimplemented!("exhale_predicate: {predicate}"); + } + vir_low::PredicateKind::EndBorrowViewShift => { + unimplemented!("exhale_predicate: {predicate}"); + } + }; + Ok(()) + } + + pub(super) fn desugar_heap_expression( + &mut self, + expression: vir_low::Expression, + ) -> SpannedEncodingResult { + let mut purifier = ExpressionPurifier::new(self); + purifier.fallible_fold_expression(expression) + } + + pub(super) fn desugar_heap_expressions( + &mut self, + expressions: Vec, + ) -> SpannedEncodingResult> { + let mut purified_expressions = Vec::with_capacity(expressions.len()); + for expression in expressions { + let purified_expression = self.desugar_heap_expression(expression)?; + purified_expressions.push(purified_expression); + } + Ok(purified_expressions) + } + + fn resolve_snapshot_with_check_predicate( + &mut self, + path_condition: &[vir_low::Expression], + label: &Option, + predicate_name: &str, + arguments: &[vir_low::Expression], + position: vir_low::Position, + ) -> SpannedEncodingResult { + let predicate_kind = self.program_context.get_predicate_kind(predicate_name); + let snapshot = match predicate_kind { + vir_low::PredicateKind::MemoryBlock | vir_low::PredicateKind::Owned => { + // self + // .resolve_snapshot_with_check_boolean_mask_with_heap( + // path_condition, + // label, + // predicate_name, + // arguments, + // position, + // )?; + self.resolve_snapshot_with_check_boolean_mask_log_with_heap( + path_condition, + label, + predicate_name, + arguments, + position, + )? + } + vir_low::PredicateKind::LifetimeToken => todo!(), + vir_low::PredicateKind::CloseFracRef => todo!(), + vir_low::PredicateKind::WithoutSnapshotWhole => todo!(), + vir_low::PredicateKind::WithoutSnapshotWholeNonAliased => todo!(), + vir_low::PredicateKind::DeadLifetimeToken => todo!(), + vir_low::PredicateKind::EndBorrowViewShift => todo!(), + }; + Ok(snapshot) + } + + pub(super) fn save_state(&mut self, label: String) -> SpannedEncodingResult<()> { + let frame = self.current_frame_mut(); + let heap = frame.heap().clone(); + frame.log_saved_state_label(label.clone())?; + assert!(self.saved_heaps.insert(label, heap).is_none()); + Ok(()) + } + + pub(super) fn heap_at_label(&self, label: &Option) -> &Heap { + match label { + Some(label) => self.saved_heaps.get(label).unwrap(), + None => self.current_frame().heap(), + } + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/svirpti/procedure_verifier/manual_triggering.rs b/prusti-viper/src/encoder/middle/core_proof/svirpti/procedure_verifier/manual_triggering.rs new file mode 100644 index 00000000000..4e493e52a5a --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/svirpti/procedure_verifier/manual_triggering.rs @@ -0,0 +1,225 @@ +//! Ensure that certain axioms are triggered even if the triggering term is +//! originally under a conditional. This property is ensured by pulling the +//! triggering terms to the top level. + +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::{ + svirpti::smt::{Info, SmtSolver, Sort2SmtWrap}, + transformations::{ + encoder_context::EncoderContext, symbolic_execution_new::ProgramContext, + }, + }, +}; +use rustc_hash::FxHashMap; +use vir_crate::{ + common::expression::UnaryOperationHelpers, + low::{self as vir_low, expression::visitors::ExpressionWalker}, +}; + +#[derive(Default)] +pub(super) struct TriggerWrappers { + emitted_bool_wrapper: bool, +} +impl TriggerWrappers { + pub(super) fn emit_wrappers(&self, smt_solver: &mut SmtSolver) -> SpannedEncodingResult<()> { + smt_solver + .declare_function( + "Wrappers", + "wrap_bool_call", + &vec![vir_low::Type::Bool.wrap()], + &vir_low::Type::Bool.wrap(), + ) + .unwrap(); // TODO: handle error + Ok(()) + } +} + +pub(super) struct TriggerTermCollector<'a, 'c, EC: EncoderContext> { + terms: Vec<(&'static str, vir_low::Expression)>, + need_bool_wrapper: bool, + program_context: &'a ProgramContext<'c, EC>, + address_range_contains_definitional_axiom: &'a vir_low::DomainAxiomDecl, + disable_offset_address: bool, + offset_address_definitional_axiom: &'a vir_low::DomainAxiomDecl, + address_constructor_injectivity_axiom2: &'a vir_low::DomainAxiomDecl, + usize_validity_bottom_up_axiom: &'a vir_low::DomainAxiomDecl, +} + +impl<'a, 'c, EC: EncoderContext> TriggerTermCollector<'a, 'c, EC> { + pub(super) fn new(program_context: &'a ProgramContext<'c, EC>) -> Self { + let domains = program_context.get_domains(); + let address_domain = domains + .iter() + .find(|domain| domain.name == "Address") + .unwrap(); + let address_range_contains_definitional_axiom = address_domain + .axioms + .iter() + .find(|axiom| axiom.name == "address_range_contains$definition") + .unwrap(); + let offset_address_definitional_axiom = address_domain + .axioms + .iter() + .find(|axiom| axiom.name == "offset_address$definition") + .unwrap(); + let address_constructor_injectivity_axiom2 = address_domain + .axioms + .iter() + .find(|axiom| axiom.name == "address_constructor$injectivity2") + .unwrap(); + let snap_usize_domain = domains + .iter() + .find(|domain| domain.name == "Snap$Usize") + .unwrap(); + let usize_validity_bottom_up_axiom = snap_usize_domain + .axioms + .iter() + .find(|axiom| axiom.name == "Snap$Usize$$validity_axiom_bottom_up_alternative") + .unwrap(); + Self { + terms: Vec::new(), + need_bool_wrapper: false, + program_context, + address_range_contains_definitional_axiom, + disable_offset_address: false, + offset_address_definitional_axiom, + address_constructor_injectivity_axiom2, + usize_validity_bottom_up_axiom, + } + } + + pub(super) fn analyse_expression(&mut self, expression: &vir_low::Expression) { + self.walk_expression(expression); + } + + pub(super) fn emit_triggering_terms( + &self, + smt_solver: &mut SmtSolver, + trigger_wrappers: &mut TriggerWrappers, + ) -> SpannedEncodingResult<()> { + let info = Info { + program_context: self.program_context, + }; + // if self.need_bool_wrapper && !trigger_wrappers.emitted_bool_wrapper { + // // trigger_wrappers.emitted_bool_wrapper = true; + // smt_solver + // .declare_function( + // "Wrappers", + // "wrap_bool_call", + // &vec![vir_low::Type::Bool.wrap()], + // &vir_low::Type::Bool.wrap(), + // ) + // .unwrap(); // TODO: handle error + // } + for (comment, term) in &self.terms { + smt_solver.comment(comment).unwrap(); + smt_solver.assert(term, info).unwrap() // TODO: handle error + } + Ok(()) + } +} + +impl<'a, 'c, EC: EncoderContext> ExpressionWalker for TriggerTermCollector<'a, 'c, EC> { + fn walk_domain_func_app_enum(&mut self, domain_func_app: &vir_low::DomainFuncApp) { + if domain_func_app.domain_name == "Address" { + if domain_func_app.function_name == "address_range_contains$" { + // self.need_bool_wrapper = true; + // let wrapper = vir_low::Expression::domain_function_call( + // "Wrappers", + // "wrap_bool_call", + // vec![vir_low::Expression::DomainFuncApp(domain_func_app.clone())], + // vir_low::Type::Bool, + // ); + // self.terms.push(wrapper); + + let axiom_body = &self.address_range_contains_definitional_axiom.body; + let vir_low::Expression::Quantifier(quantifier) = axiom_body else { + unreachable!() + }; + let replacements = quantifier + .variables + .iter() + .zip(domain_func_app.arguments.iter()) + .collect(); + let term = quantifier.body.clone().substitute_variables(&replacements); + self.terms.push(( + "Manually triggering: address_range_contains$definition", + term, + )); + } else if domain_func_app.function_name == "offset_address$" + && !self.disable_offset_address + { + let axiom_body = &self.offset_address_definitional_axiom.body; + let vir_low::Expression::Quantifier(quantifier) = axiom_body else { + unreachable!() + }; + let replacements = quantifier + .variables + .iter() + .zip(domain_func_app.arguments.iter()) + .collect(); + let term = quantifier.body.clone().substitute_variables(&replacements); + self.disable_offset_address = true; + self.walk_expression(&term); + self.disable_offset_address = false; + self.terms + .push(("Manually triggering: offset_address$definition", term)); + } else if domain_func_app.function_name == "address_constructor$" { + match &domain_func_app.arguments[0] { + vir_low::Expression::DomainFuncApp(vir_low::DomainFuncApp { + domain_name, + function_name, + arguments, + .. + }) if domain_name == "Address" && function_name == "get_allocation$" => { + let axiom_body = &self.address_constructor_injectivity_axiom2.body; + let vir_low::Expression::Quantifier(quantifier) = axiom_body else { + unreachable!() + }; + assert_eq!(arguments.len(), 1); + assert_eq!(quantifier.variables.len(), 3); + let address = &arguments[0]; + let mut replacements = FxHashMap::default(); + replacements.insert(&quantifier.variables[0], address); + replacements + .insert(&quantifier.variables[1], &domain_func_app.arguments[1]); + replacements + .insert(&quantifier.variables[2], &domain_func_app.arguments[2]); + let term = quantifier.body.clone().substitute_variables(&replacements); + self.terms.push(( + "Manually triggering: address_constructor$injectivity2", + term, + )); + } + _ => {} + } + } + } else if domain_func_app.domain_name == "Snap$Usize" { + if domain_func_app.function_name == "constructor$Snap$Usize$" { + assert_eq!(domain_func_app.arguments.len(), 1); + if matches!( + domain_func_app.arguments[0], + vir_low::Expression::Constant(_) + ) { + let axiom_body = &self.usize_validity_bottom_up_axiom.body; + let vir_low::Expression::Quantifier(quantifier) = axiom_body else { + unreachable!() + }; + assert_eq!(quantifier.variables.len(), 1); + let replacements = quantifier + .variables + .iter() + .zip(domain_func_app.arguments.iter()) + .collect(); + let term = quantifier.body.clone().substitute_variables(&replacements); + self.terms.push(( + "Manually triggering: Snap$Usize$$validity_axiom_bottom_up_alternative", + term, + )); + } + } + } + self.walk_domain_func_app(domain_func_app) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/svirpti/procedure_verifier/mod.rs b/prusti-viper/src/encoder/middle/core_proof/svirpti/procedure_verifier/mod.rs new file mode 100644 index 00000000000..22f0232ad1d --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/svirpti/procedure_verifier/mod.rs @@ -0,0 +1,344 @@ +use self::solver_stack::StackFrame; +use super::{ + super::transformations::{ + encoder_context::EncoderContext, symbolic_execution_new::ProgramContext, + }, + smt::{SmtSolver, Sort2SmtWrap}, + Verifier, +}; +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::transformations::predicate_domains::PredicateDomainsInfo, +}; +use log::info; +use manual_triggering::TriggerWrappers; +use prusti_common::config; +use rustc_hash::FxHashMap; +use vir_crate::{ + common::{cfg::Cfg, graphviz::ToGraphviz}, + low::{self as vir_low}, +}; + +mod solver_stack; +mod statements; +mod solver; +mod heap; +mod manual_triggering; + +pub(super) struct ProcedureExecutor<'a, 'c, EC: EncoderContext> { + verifier: &'a mut Verifier, + source_filename: &'a str, + procedure_name: String, + program_context: &'a mut ProgramContext<'c, EC>, + predicate_domains_info: &'a PredicateDomainsInfo, + stack: Vec, + reached_contradiction: bool, + smt_solver: SmtSolver, + unique_id_generator: usize, + saved_heaps: FxHashMap, + global_heap: heap::GlobalHeap, + trigger_wrappers: TriggerWrappers, +} + +impl<'a, 'c, EC: EncoderContext> Drop for ProcedureExecutor<'a, 'c, EC> { + fn drop(&mut self) { + if prusti_common::config::dump_debug_info() && std::thread::panicking() { + // prusti_common::report::log::report_with_writer( + // "graphviz_method_vir_low_crashing_symbolic_execution", + // format!("{}.{}.crash.dot", self.source_filename, self.procedure.name,), + // |writer| self.to_graphviz(writer).unwrap(), + // ); + } + if !std::thread::panicking() { + assert_eq!(self.stack.len(), 0); + } + } +} + +impl<'a, 'c, EC: EncoderContext> ProcedureExecutor<'a, 'c, EC> { + pub(super) fn new( + verifier: &'a mut Verifier, + source_filename: &'a str, + procedure_name: String, + program_context: &'a mut ProgramContext<'c, EC>, + predicate_domains_info: &'a PredicateDomainsInfo, + ) -> SpannedEncodingResult { + let smt_solver = SmtSolver::default().unwrap(); + Ok(Self { + verifier, + source_filename, + procedure_name, + program_context, + predicate_domains_info, + stack: Vec::new(), + reached_contradiction: false, + smt_solver, + unique_id_generator: 0, + saved_heaps: FxHashMap::default(), + global_heap: heap::GlobalHeap::default(), + trigger_wrappers: TriggerWrappers::default(), + }) + } + + pub(super) fn source_filename(&self) -> &str { + self.source_filename + } + + pub(super) fn procedure_name(&self) -> &str { + &self.procedure_name + } + + pub(super) fn execute_procedure( + mut self, + procedure: &'a vir_low::ProcedureDecl, + predicates: &[vir_low::PredicateDecl], + ) -> SpannedEncodingResult<()> { + info!("Executing procedure: {}", procedure.name); + if prusti_common::config::dump_debug_info() { + prusti_common::report::log::report_with_writer( + "graphviz_method_before_symbolic_execution", + format!("{}.{}.dot", self.source_filename, procedure.name,), + |writer| procedure.to_graphviz(writer).unwrap(), + ); + } + self.smt_solver.push().unwrap(); // FIXME: Handle errors + self.smt_solver + .comment(&format!("Executing procedure: {}", procedure.name)) + .unwrap(); // FIXME: Handle errors + self.declare_local_variables(procedure)?; + self.stack_push(None, procedure.entry.clone())?; + self.initialise_heap(predicates)?; + while !self.stack.is_empty() { + self.mark_current_frame_as_being_executed()?; + self.log_current_stack_status()?; + let block = procedure + .basic_blocks + .get(self.current_frame().label()) + .unwrap(); + self.execute_block(block)?; + // Executing the terminator changes the stack, so we need to mark + // the frame as executed now. + self.mark_current_frame_as_executed()?; + if self.reached_contradiction { + self.reached_contradiction = false; + } else { + self.execute_terminator(block)?; + } + self.pop_executed_frames()?; + } + info!("Finished executing procedure: {}", procedure.name); + self.smt_solver + .comment(&format!("Finished executing procedure: {}", procedure.name)) + .unwrap(); // FIXME: Handle errors + self.smt_solver.pop().unwrap(); // FIXME: Handle errors + Ok(()) + } + + fn execute_block(&mut self, block: &vir_low::BasicBlock) -> SpannedEncodingResult<()> { + eprintln!("Executing block: {}", self.current_frame().label()); + self.smt_solver + .comment(&format!( + "Executing block: {}", + self.current_frame().label() + )) + .unwrap(); // FIXME: Handle errors + for statement in &block.statements { + self.execute_statement(statement)?; + if self.reached_contradiction { + return Ok(()); + } + self.inc_statement_index()?; + } + Ok(()) + } + + fn execute_terminator(&mut self, block: &vir_low::BasicBlock) -> SpannedEncodingResult<()> { + let current_label = self.current_frame().label().clone(); + match &block.successor { + vir_low::Successor::Return => { + info!("Executing return terminator"); + self.stack_pop()?; + } + vir_low::Successor::Goto(label) => { + info!("Executing goto terminator"); + self.stack_push(Some(current_label), label.clone())?; + } + vir_low::Successor::GotoSwitch(cases) => { + info!("Executing switch terminator"); + for (_, label) in cases.iter().rev() { + self.stack_push(Some(current_label.clone()), label.clone())?; + } + } + } + Ok(()) + } + + pub(super) fn load_domains( + &mut self, + domains: &[vir_low::DomainDecl], + ) -> SpannedEncodingResult<()> { + // self.create_builtin_types()?; + self.create_domain_types(domains)?; + self.create_domain_functions(domains)?; + self.define_domain_axioms(domains)?; + assert!(self.smt_solver.check_sat().unwrap().is_sat_or_unknown()); + Ok(()) + } + + fn declare_local_variables( + &mut self, + procedure: &vir_low::ProcedureDecl, + ) -> SpannedEncodingResult<()> { + for variable in &procedure.locals { + self.declare_variable(variable).unwrap(); // FIXME: Handle errors + } + Ok(()) + } + + fn create_domain_types( + &mut self, + domains: &[vir_low::DomainDecl], + ) -> SpannedEncodingResult<()> { + for domain in domains { + let domain_name = &domain.name; + self.smt_solver.declare_sort(domain_name).unwrap(); // FIXME: Handle errors + } + Ok(()) + } + + fn create_domain_functions( + &mut self, + domains: &[vir_low::DomainDecl], + ) -> SpannedEncodingResult<()> { + for domain in domains { + self.smt_solver + .comment(&format!("Functions for domain: {}", domain.name)) + .unwrap(); // FIXME: Handle errors + for function in &domain.functions { + let parameter_types = function + .parameters + .iter() + .map(|parameter| parameter.ty.wrap()) + .collect::>(); + self.smt_solver + .declare_function( + &domain.name, + &function.name, + parameter_types, + function.return_type.wrap(), + ) + .unwrap(); // FIXME: Handle errors + } + } + self.trigger_wrappers.emit_wrappers(&mut self.smt_solver)?; + + Ok(()) + } + + fn define_domain_axioms( + &mut self, + domains: &[vir_low::DomainDecl], + ) -> SpannedEncodingResult<()> { + for domain in domains { + self.smt_solver + .comment(&format!("Axioms for domain: {}", domain.name)) + .unwrap(); // FIXME: Handle errors + for axiom in &domain.axioms { + let not_supported = matches!( + axiom.name.as_str(), + "mul_wrapper$zero" + | "Snap$Bool$$validity_axiom_bottom_up_alternative" + | "LeCmp_Unbounded$simplification_axiom" + | "Snap$Unbounded$$validity_axiom_bottom_up_alternative" + ); + let suitable_for_manual = + if config::svirpti_enable_manual_triggering() && !not_supported { + self.smt_solver.add_axiom(axiom.clone()).unwrap() + } else { + false + }; + if !(suitable_for_manual && config::svirpti_remove_unnecessary_axioms()) { + if let Some(comment) = &axiom.comment { + self.comment(comment)?; + } + self.comment(&format!("axiom: {}", axiom.name))?; + // if matches!( + // axiom.name.as_str(), + // "address_constructor$injectivity2" + // | "address_range_contains$definition" + // | "address_constructor$injectivity1" + // | "mul_wrapper$commutativity" + // // | "mul_wrapper$zero" + // | "mul_wrapper$non_negative_range" + // | "mul_wrapper$positive_increases" + // | "mul_wrapper$definition" + // | "offset_address$definition" + // | "m_std$$ptr$$mut_ptr$$$openang$impl$space$$star$mut$space$T$closeang$$$add$struct$m_T$$$definitional_axiom" + // | "intersect_singleton$" + // | "constructor$Snap$Bool$$top_down_injectivity_axiom" + // | "LeCmp_Unbounded$eval_axiom" + // | "GtCmp_Usize$simplification_axiom" + // | "GtCmp_Usize$eval_axiom" + // // | "Snap$Bool$$validity_axiom_bottom_up_alternative" + // // | "LeCmp_Unbounded$simplification_axiom" + // | "SetConstructor1ArgumentsContained" + // | "SetConstructor2ArgumentsContained" + // | "Snap$ptr$struct$m_T$$$validity_axiom_bottom_up_alternative" + // | "constructor$Snap$ptr$struct$m_T$$$top_down_injectivity_axiom" + // | "constructor$Snap$struct$m_T$$$top_down_injectivity_axiom" + // | "Snap$Usize$$validity_axiom_bottom_up_alternative" + // | "constructor$Snap$Usize$$top_down_injectivity_axiom" + // | "constructor$Snap$Tuple$$$top_down_injectivity_axiom" + // // | "Snap$Unbounded$$validity_axiom_bottom_up_alternative" + // | "constructor$Snap$Unbounded$$top_down_injectivity_axiom" + + // ) { + // self.comment("optimised away")?; + // } else { + if matches!(axiom.name.as_str(), "address_constructor$injectivity2") { + self.comment("FIXME: This axiom is only manually instantiated because I could not find proper triggers.")?; + } else { + self.assume_axiom(&axiom.body)?; + } + // } + } + } + for rewrite_rule in &domain.rewrite_rules { + if rewrite_rule.egg_only { + continue; + } + let axiom = rewrite_rule.convert_into_axiom(); + let suitable_for_manual = if config::svirpti_enable_manual_triggering() { + self.smt_solver.add_axiom(axiom.clone()).unwrap() + } else { + false + }; + if !(suitable_for_manual && config::svirpti_remove_unnecessary_axioms()) { + if let Some(comment) = &axiom.comment { + self.comment(comment)?; + } + self.comment(&format!("axiom: {}", axiom.name))?; + self.assume(&axiom.body)?; + } + } + } + self.assume(&vir_low::Expression::domain_function_call( + "Snap$Usize", + "valid$Snap$Usize", + vec![vir_low::Expression::domain_function_call( + "Snap$Usize", + "constructor$Snap$Usize$", + vec![1.into()], + vir_low::Type::domain("Snap$Usize".into()), + )], + vir_low::Type::Bool, + ))?; + Ok(()) + } + + fn generate_fresh_id(&mut self) -> usize { + let new_value = self.unique_id_generator.checked_add(1).unwrap(); + self.unique_id_generator = new_value; + new_value + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/svirpti/procedure_verifier/solver.rs b/prusti-viper/src/encoder/middle/core_proof/svirpti/procedure_verifier/solver.rs new file mode 100644 index 00000000000..da36ed9908b --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/svirpti/procedure_verifier/solver.rs @@ -0,0 +1,105 @@ +use super::{ + super::{ + super::transformations::encoder_context::EncoderContext, + smt::{Info, Sort2SmtWrap}, + VerificationError, + }, + manual_triggering::TriggerTermCollector, + ProcedureExecutor, +}; +use crate::encoder::errors::SpannedEncodingResult; +use vir_crate::{common::expression::UnaryOperationHelpers, low as vir_low}; + +impl<'a, 'c, EC: EncoderContext> ProcedureExecutor<'a, 'c, EC> { + pub(super) fn comment(&mut self, comment: &str) -> SpannedEncodingResult<()> { + self.smt_solver.comment(comment).unwrap(); // TODO: handle error + Ok(()) + } + + pub(super) fn assume_axiom( + &mut self, + expression: &vir_low::Expression, + ) -> SpannedEncodingResult<()> { + let info = Info { + program_context: self.program_context, + }; + self.smt_solver.assert(expression, info).unwrap(); // TODO: handle error + Ok(()) + } + + pub(super) fn assume(&mut self, expression: &vir_low::Expression) -> SpannedEncodingResult<()> { + let info = Info { + program_context: self.program_context, + }; + let mut collector = TriggerTermCollector::new(self.program_context); + collector.analyse_expression(&expression); + collector.emit_triggering_terms(&mut self.smt_solver, &mut self.trigger_wrappers)?; + self.smt_solver.assert(expression, info).unwrap(); // TODO: handle error + Ok(()) + } + + pub(super) fn smoke_check(&mut self) -> SpannedEncodingResult<()> { + let result = self.smt_solver.check_sat().unwrap(); // TODO: handle error + assert!(!result.is_unsat(), "Smoke check failed"); + Ok(()) + } + + pub(super) fn assert( + &mut self, + expression: vir_low::Expression, + error: VerificationError, + ) -> SpannedEncodingResult<()> { + self.assert_with_assumptions(&[], expression, error) + // self.smt_solver.push().unwrap(); // TODO: handle error + // let negated_expression = vir_low::Expression::not(expression); + // let info = Info { + // program_context: self.program_context, + // }; + // self.smt_solver.assert(&negated_expression, info).unwrap(); // TODO: handle error + // let result = self.smt_solver.check_sat().unwrap(); // TODO: handle error + // if result.is_sat_or_unknown() { + // self.verifier.report_error(error); + // } + // self.smt_solver.pop().unwrap(); // TODO: handle error + // Ok(()) + } + + pub(super) fn assert_with_assumptions( + &mut self, + assumptions: &[vir_low::Expression], + expression: vir_low::Expression, + error: VerificationError, + ) -> SpannedEncodingResult<()> { + self.smt_solver.push().unwrap(); // TODO: handle error + let info = Info { + program_context: self.program_context, + }; + let mut collector = TriggerTermCollector::new(self.program_context); + collector.analyse_expression(&expression); + for assumption in assumptions { + collector.analyse_expression(assumption); + } + collector.emit_triggering_terms(&mut self.smt_solver, &mut self.trigger_wrappers)?; + let negated_expression = vir_low::Expression::not(expression); + for assumption in assumptions { + self.smt_solver.assert(assumption, info).unwrap(); // TODO: handle error + } + self.smt_solver.assert(&negated_expression, info).unwrap(); // TODO: handle error + let result = self.smt_solver.check_sat().unwrap(); // TODO: handle error + if result.is_sat_or_unknown() { + self.verifier.report_error(error); + } + self.smt_solver.pop().unwrap(); // TODO: handle error + Ok(()) + } + + pub(super) fn declare_variable( + &mut self, + variable: &vir_low::VariableDecl, + ) -> SpannedEncodingResult<()> { + self.smt_solver + .declare_variable(&variable.name, variable.ty.wrap()) + .unwrap(); // TODO: handle error + Ok(()) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/svirpti/procedure_verifier/solver_stack.rs b/prusti-viper/src/encoder/middle/core_proof/svirpti/procedure_verifier/solver_stack.rs new file mode 100644 index 00000000000..f8ba5a8634f --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/svirpti/procedure_verifier/solver_stack.rs @@ -0,0 +1,159 @@ +use super::{ + super::super::transformations::encoder_context::EncoderContext, heap::Heap, ProcedureExecutor, +}; +use crate::encoder::errors::SpannedEncodingResult; +use vir_crate::low::{self as vir_low}; + +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +enum StackFrameStatus { + /// The frame has been created but no statement has been executed yet. + Created, + /// The frame is currently being executed. + BeingExecuted, + /// The frame has been fully executed. However, its children may currently + /// be executed. + Executed, +} + +#[derive(Debug)] +pub struct StackFrame { + parent: Option, + label: vir_low::Label, + /// The index of the statement in the block that is currently being + /// executed. + statement_index: usize, + status: StackFrameStatus, + heap: Heap, + saved_state_labels: Vec, +} + +impl StackFrame { + pub(super) fn parent(&self) -> &Option { + &self.parent + } + + pub(in super::super) fn label(&self) -> &vir_low::Label { + &self.label + } + + pub(in super::super) fn statement_index(&self) -> usize { + self.statement_index + } + + pub(super) fn heap_mut(&mut self) -> &mut Heap { + &mut self.heap + } + + pub(super) fn heap(&self) -> &Heap { + &self.heap + } + + pub(super) fn log_saved_state_label(&mut self, label: String) -> SpannedEncodingResult<()> { + self.saved_state_labels.push(label); + Ok(()) + } +} + +impl<'a, 'c, EC: EncoderContext> ProcedureExecutor<'a, 'c, EC> { + pub(in super::super) fn current_frame(&self) -> &StackFrame { + self.stack.last().unwrap() + } + + pub(super) fn current_frame_mut(&mut self) -> &mut StackFrame { + self.stack.last_mut().unwrap() + } + + pub(super) fn inc_statement_index(&mut self) -> SpannedEncodingResult<()> { + let frame = self.current_frame_mut(); + frame.statement_index += 1; + Ok(()) + } + + pub(super) fn stack_push( + &mut self, + parent: Option, + label: vir_low::Label, + ) -> SpannedEncodingResult<()> { + let heap = if let Some(parent) = &parent { + let frame = self + .stack + .iter() + .find(|frame| frame.label() == parent) + .unwrap(); + assert_eq!(frame.status, StackFrameStatus::Executed); + frame.heap.clone() + } else { + Heap::default() + }; + let frame = StackFrame { + parent, + label, + statement_index: 0, + status: StackFrameStatus::Created, + heap, + saved_state_labels: Vec::new(), + }; + self.stack.push(frame); + self.smt_solver.push().unwrap(); // FIXME: Handle errors + Ok(()) + } + + pub(super) fn stack_pop(&mut self) -> SpannedEncodingResult<()> { + let frame = self.stack.pop().unwrap(); + assert_eq!(frame.status, StackFrameStatus::Executed); + for label in frame.saved_state_labels { + assert!(self.saved_heaps.remove(&label).is_some()); + } + self.smt_solver.pop().unwrap(); // FIXME: Handle errors + Ok(()) + } + + pub(super) fn pop_executed_frames(&mut self) -> SpannedEncodingResult<()> { + while let Some(frame) = self.stack.last() { + if frame.status == StackFrameStatus::Executed { + self.stack_pop()?; + } else { + break; + } + } + Ok(()) + } + + pub(super) fn mark_current_frame_as_being_executed(&mut self) -> SpannedEncodingResult<()> { + let frame = self.current_frame_mut(); + assert_eq!(frame.status, StackFrameStatus::Created); + frame.status = StackFrameStatus::BeingExecuted; + Ok(()) + } + + pub(super) fn mark_current_frame_as_executed(&mut self) -> SpannedEncodingResult<()> { + let frame = self.current_frame_mut(); + assert_eq!(frame.status, StackFrameStatus::BeingExecuted); + frame.status = StackFrameStatus::Executed; + Ok(()) + } + + pub(super) fn log_current_stack_status(&mut self) -> SpannedEncodingResult<()> { + for frame in &self.stack { + self.smt_solver + .comment(&format!( + "Frame: {} status={:?} parent={:?}", + frame.label(), + frame.status, + frame.parent() + )) + .unwrap(); // FIXME: Handle errors + } + Ok(()) + } + + pub(in super::super) fn current_execution_trace(&self) -> SpannedEncodingResult> { + let mut trace = Vec::new(); + for frame in &self.stack { + if matches!(frame.status, StackFrameStatus::Executed) { + trace.push(&*frame.label.name); + } + } + Ok(trace) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/svirpti/procedure_verifier/statements/exhale.rs b/prusti-viper/src/encoder/middle/core_proof/svirpti/procedure_verifier/statements/exhale.rs new file mode 100644 index 00000000000..2c5c5221724 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/svirpti/procedure_verifier/statements/exhale.rs @@ -0,0 +1,64 @@ +use super::super::{ + super::super::transformations::encoder_context::EncoderContext, ProcedureExecutor, +}; +use crate::encoder::errors::SpannedEncodingResult; +use vir_crate::low::{self as vir_low}; + +impl<'a, 'c, EC: EncoderContext> ProcedureExecutor<'a, 'c, EC> { + pub(super) fn execute_exhale( + &mut self, + expression: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + match expression { + vir_low::Expression::BinaryOp(expression) + if expression.op_kind == vir_low::BinaryOpKind::And => + { + self.execute_exhale(*expression.left, position)?; + self.execute_exhale(*expression.right, position)?; + return Ok(()); + } + _ => (), + } + if let vir_low::Expression::PredicateAccessPredicate(predicate) = &expression { + self.execute_exhale_predicate(predicate, position)?; + return Ok(()); + } + if expression.is_pure() { + let expression = self.desugar_heap_expression(expression)?; + let error = self.create_verification_error_for_expression( + "exhale.failed:assertion.false", + position, + &expression, + )?; + self.assert(expression, error)?; + } else { + match expression { + vir_low::Expression::Quantifier(vir_low::Quantifier { + name, + kind: vir_low::QuantifierKind::ForAll, + variables, + triggers: _, + body: + box vir_low::Expression::BinaryOp(vir_low::BinaryOp { + op_kind: vir_low::BinaryOpKind::Implies, + left: box guard, + right: box vir_low::Expression::PredicateAccessPredicate(mut predicate), + position: _, + }), + position, + }) => { + predicate.arguments = self.desugar_heap_expressions(predicate.arguments)?; + let guard = self.desugar_heap_expression(guard)?; + self.execute_exhale_quantified_predicate( + name, variables, guard, predicate, position, + )?; + } + _ => { + unimplemented!("exhale: {expression}"); + } + } + } + Ok(()) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/svirpti/procedure_verifier/statements/inhale.rs b/prusti-viper/src/encoder/middle/core_proof/svirpti/procedure_verifier/statements/inhale.rs new file mode 100644 index 00000000000..81ef42f76cd --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/svirpti/procedure_verifier/statements/inhale.rs @@ -0,0 +1,56 @@ +use super::super::{ + super::super::transformations::encoder_context::EncoderContext, ProcedureExecutor, +}; +use crate::encoder::errors::SpannedEncodingResult; +use vir_crate::low::{self as vir_low}; + +impl<'a, 'c, EC: EncoderContext> ProcedureExecutor<'a, 'c, EC> { + pub(super) fn execute_inhale( + &mut self, + expression: &vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + if let vir_low::Expression::BinaryOp(expression) = expression { + if expression.op_kind == vir_low::BinaryOpKind::And { + self.execute_inhale(&expression.left, position)?; + self.execute_inhale(&expression.right, position)?; + return Ok(()); + } + } + let expression = expression.clone(); + if expression.is_pure() { + let expression = self.desugar_heap_expression(expression)?; + self.assume(&expression)?; + } else if let vir_low::Expression::PredicateAccessPredicate(mut predicate) = expression { + predicate.arguments = self.desugar_heap_expressions(predicate.arguments)?; + self.execute_inhale_predicate(predicate, position)?; + } else { + match expression { + vir_low::Expression::Quantifier(vir_low::Quantifier { + name, + kind: vir_low::QuantifierKind::ForAll, + variables, + triggers: _, + body: + box vir_low::Expression::BinaryOp(vir_low::BinaryOp { + op_kind: vir_low::BinaryOpKind::Implies, + left: box guard, + right: box vir_low::Expression::PredicateAccessPredicate(mut predicate), + position: _, + }), + position, + }) => { + predicate.arguments = self.desugar_heap_expressions(predicate.arguments)?; + let guard = self.desugar_heap_expression(guard)?; + self.execute_inhale_quantified_predicate( + name, variables, guard, predicate, position, + )?; + } + _ => { + unimplemented!("inhale: {expression}"); + } + } + } + Ok(()) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/svirpti/procedure_verifier/statements/mod.rs b/prusti-viper/src/encoder/middle/core_proof/svirpti/procedure_verifier/statements/mod.rs new file mode 100644 index 00000000000..6891e4d1380 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/svirpti/procedure_verifier/statements/mod.rs @@ -0,0 +1,160 @@ +use super::{super::super::transformations::encoder_context::EncoderContext, ProcedureExecutor}; +use crate::encoder::errors::SpannedEncodingResult; +use prusti_common::config; +use vir_crate::{common::expression::SyntacticEvaluation, low as vir_low}; + +mod exhale; +mod inhale; + +impl<'a, 'c, EC: EncoderContext> ProcedureExecutor<'a, 'c, EC> { + pub(super) fn execute_statement( + &mut self, + statement: &vir_low::Statement, + ) -> SpannedEncodingResult<()> { + eprintln!("Executing statement: {}", statement); + match statement { + vir_low::Statement::Label(statement) => { + self.execute_statement_label(statement)?; + } + vir_low::Statement::Assign(statement) => { + self.execute_statement_assign(statement)?; + } + vir_low::Statement::Assume(statement) => { + self.execute_statement_assume(statement)?; + } + vir_low::Statement::Assert(statement) => { + self.execute_statement_assert(statement)?; + } + vir_low::Statement::Inhale(statement) => { + self.execute_statement_inhale(statement)?; + } + vir_low::Statement::Exhale(statement) => { + self.execute_statement_exhale(statement)?; + } + vir_low::Statement::Comment(statement) => { + self.smt_solver.comment(&statement.to_string()).unwrap(); // TODO: handle error + } + vir_low::Statement::LogEvent(statement) => { + self.smt_solver.comment(&statement.to_string()).unwrap(); // TODO: handle error + } + vir_low::Statement::Fold(_) + | vir_low::Statement::Unfold(_) + | vir_low::Statement::ApplyMagicWand(_) + | vir_low::Statement::MethodCall(_) + | vir_low::Statement::Conditional(_) => { + unreachable!(); + } + vir_low::Statement::MaterializePredicate(statement) => { + self.execute_materialize_predicate(statement)?; + } + vir_low::Statement::CaseSplit(statement) => { + self.execute_case_split(statement)?; + } + } + if config::svirpti_enable_smoke_check() { + self.smoke_check()?; + } + Ok(()) + } + + fn execute_statement_label( + &mut self, + statement: &vir_low::ast::statement::Label, + ) -> SpannedEncodingResult<()> { + self.save_state(statement.label.clone())?; + Ok(()) + } + + fn execute_statement_assign( + &mut self, + statement: &vir_low::ast::statement::Assign, + ) -> SpannedEncodingResult<()> { + assert!(statement.value.is_constant()); + unimplemented!(); + // let target_variable = self.create_new_bool_variable_version(&statement.target.name)?; + // let expression = + // vir_low::Expression::equals(target_variable.into(), statement.value.clone()); + // self.try_assume_heap_independent_conjuncts(&expression)?; + // self.add_assume(expression, statement.position)?; + Ok(()) + } + + fn execute_statement_assume( + &mut self, + statement: &vir_low::ast::statement::Assume, + ) -> SpannedEncodingResult<()> { + if statement.expression.is_false() { + self.reached_contradiction = true; + return Ok(()); + } + let expression = self.desugar_heap_expression(statement.expression.clone())?; + self.assume(&expression)?; + Ok(()) + } + + fn execute_statement_assert( + &mut self, + statement: &vir_low::ast::statement::Assert, + ) -> SpannedEncodingResult<()> { + let expression = self.desugar_heap_expression(statement.expression.clone())?; + let error = self.create_verification_error_for_expression( + "assert.failed:assertion.false", + statement.position, + &expression, + )?; + self.assert(expression, error)?; + if statement.expression.is_false() { + self.reached_contradiction = true; + } + Ok(()) + } + + fn execute_statement_inhale( + &mut self, + statement: &vir_low::ast::statement::Inhale, + ) -> SpannedEncodingResult<()> { + if statement.expression.is_false() { + self.reached_contradiction = true; + return Ok(()); + } + self.execute_inhale(&statement.expression, statement.position)?; + Ok(()) + } + + fn execute_statement_exhale( + &mut self, + statement: &vir_low::ast::statement::Exhale, + ) -> SpannedEncodingResult<()> { + let exhale_label = format!("exhale_label${}", self.generate_fresh_id()); + let expression = statement.expression.clone().wrap_in_old(&exhale_label); + let label = vir_low::ast::statement::Label::new(exhale_label); + self.execute_statement_label(&label)?; + self.execute_exhale(expression, statement.position)?; + Ok(()) + } + + fn execute_materialize_predicate( + &mut self, + _statement: &vir_low::ast::statement::MaterializePredicate, + ) -> SpannedEncodingResult<()> { + unimplemented!(); + // let vir_low::Expression::PredicateAccessPredicate(predicate) = self.simplify_expression(&statement.predicate, statement.position)? else { + // unreachable!(); + // }; + // self.materialize_predicate(predicate, statement.check_that_exists, statement.position)?; + Ok(()) + } + + fn execute_case_split( + &mut self, + _statement: &vir_low::ast::statement::CaseSplit, + ) -> SpannedEncodingResult<()> { + unimplemented!(); + // let expression = self.simplify_expression(&statement.expression, statement.position)?; + // self.add_statement(vir_low::Statement::case_split( + // expression, + // statement.position, + // ))?; + Ok(()) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/svirpti/smt/configuration.rs b/prusti-viper/src/encoder/middle/core_proof/svirpti/smt/configuration.rs new file mode 100644 index 00000000000..6d9b4cdd319 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/svirpti/smt/configuration.rs @@ -0,0 +1,57 @@ +use prusti_common::config; +use rsmt2::SmtConf; + +pub struct Configuration { + pub(super) smt_conf: SmtConf, + /// Attributes fed into solver's `set_info` method. + pub(super) attributes: Vec, + /// Options fed into solver's `set_option` method. + pub(super) options: Vec<(String, String)>, + pub(super) tee_path: Option, +} + +impl Default for Configuration { + fn default() -> Self { + let mut smt_conf = SmtConf::z3(config::svirpti_smt_solver()); + smt_conf.check_success(); + let attributes = vec![(":smt-lib-version 2.0")] + .into_iter() + .map(|attribute| (attribute.into())) + .collect(); + let options = vec![ + // Silicon. + (":global-decls".to_string(), "true".to_string()), + (":auto_config".to_string(), "false".to_string()), + (":smt.case_split".to_string(), "3".to_string()), + (":smt.delay_units".to_string(), "true".to_string()), + (":type_check".to_string(), "true".to_string()), + (":smt.mbqi".to_string(), "false".to_string()), + (":pp.bv_literals".to_string(), "false".to_string()), + (":smt.arith.solver".to_string(), "2".to_string()), + (":model.v2".to_string(), "true".to_string()), + (":smt.qi.max_multi_patterns".to_string(), "1000".to_string()), + // (":timeout".to_string(), "5000".to_string()), + // Prusti. + ( + ":smt.qi.eager_threshold".to_string(), + config::smt_qi_eager_threshold().to_string(), + ), + ( + ":model.partial".to_string(), + config::counterexample().to_string(), + ), + ( + ":smt.arith.nl".to_string(), + config::smt_use_nonlinear_arithmetic_solver().to_string(), + ), + // (":smt.arith.nl.gb".to_string(), config::smt_use_nonlinear_arithmetic_solver().to_string()), + ]; + let tee_path = config::svirpti_smt_solver_log(); + Self { + smt_conf, + options, + attributes, + tee_path, + } + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/svirpti/smt/errors.rs b/prusti-viper/src/encoder/middle/core_proof/svirpti/smt/errors.rs new file mode 100644 index 00000000000..d2d0cad1095 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/svirpti/smt/errors.rs @@ -0,0 +1,12 @@ +pub type SmtSolverResult = Result; + +#[derive(Debug)] +pub enum SmtSolverError { + Rsmt2Error(rsmt2::errors::Error), +} + +impl From for SmtSolverError { + fn from(e: rsmt2::errors::Error) -> Self { + SmtSolverError::Rsmt2Error(e) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/svirpti/smt/expressions.rs b/prusti-viper/src/encoder/middle/core_proof/svirpti/smt/expressions.rs new file mode 100644 index 00000000000..23ed799a127 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/svirpti/smt/expressions.rs @@ -0,0 +1,485 @@ +use super::{ + super::super::transformations::encoder_context::EncoderContext, solver::Info, types::Type2Smt, +}; +use rsmt2::{print::Expr2Smt, SmtRes}; +use std::io::Write; +use vir_crate::low::{self as vir_low, operations::ty::Typed}; + +trait Expression2Smt<'a, Info> { + fn expression_to_smt2(&'a self, writer: &mut Writer, info: Info) -> SmtRes<()> + where + Writer: Write; +} + +impl<'a, 'c, EC: EncoderContext, T> Expression2Smt<'a, Info<'a, 'c, EC>> for T +where + Expr2SmtWrapper<'a, T>: Expr2Smt> + 'a, +{ + fn expression_to_smt2( + &'a self, + writer: &mut Writer, + info: Info<'a, 'c, EC>, + ) -> SmtRes<()> + where + Writer: Write, + { + Expr2SmtWrapper::new(self).expr_to_smt2(writer, info) + } +} + +pub(super) struct Expr2SmtWrapper<'a, T> { + expr: &'a T, +} + +impl<'a, T> Expr2SmtWrapper<'a, T> { + pub(super) fn new(expr: &'a T) -> Self { + Self { expr } + } +} + +pub(super) trait Expr2SmtWrap { + fn wrap(&self) -> Expr2SmtWrapper; +} + +impl<'a> Expr2SmtWrap for vir_low::Expression { + fn wrap(&self) -> Expr2SmtWrapper { + Expr2SmtWrapper { expr: self } + } +} + +impl<'a, 'c, EC: EncoderContext> Expr2Smt> + for Expr2SmtWrapper<'a, vir_low::Expression> +{ + fn expr_to_smt2(&self, writer: &mut Writer, info: Info<'a, 'c, EC>) -> SmtRes<()> + where + Writer: Write, + { + match self.expr { + vir_low::Expression::Local(expression) => expression.expression_to_smt2(writer, info), + vir_low::Expression::Field(expression) => expression.expression_to_smt2(writer, info), + vir_low::Expression::LabelledOld(expression) => { + expression.expression_to_smt2(writer, info) + } + vir_low::Expression::Constant(expression) => { + expression.expression_to_smt2(writer, info) + } + vir_low::Expression::MagicWand(expression) => { + expression.expression_to_smt2(writer, info) + } + vir_low::Expression::PredicateAccessPredicate(expression) => { + expression.expression_to_smt2(writer, info) + } + vir_low::Expression::FieldAccessPredicate(expression) => { + expression.expression_to_smt2(writer, info) + } + vir_low::Expression::Unfolding(expression) => { + expression.expression_to_smt2(writer, info) + } + vir_low::Expression::UnaryOp(expression) => expression.expression_to_smt2(writer, info), + vir_low::Expression::BinaryOp(expression) => { + expression.expression_to_smt2(writer, info) + } + vir_low::Expression::PermBinaryOp(expression) => { + expression.expression_to_smt2(writer, info) + } + vir_low::Expression::ContainerOp(expression) => { + expression.expression_to_smt2(writer, info) + } + vir_low::Expression::Conditional(expression) => { + expression.expression_to_smt2(writer, info) + } + vir_low::Expression::Quantifier(expression) => { + expression.expression_to_smt2(writer, info) + } + vir_low::Expression::LetExpr(expression) => expression.expression_to_smt2(writer, info), + vir_low::Expression::FuncApp(expression) => expression.expression_to_smt2(writer, info), + vir_low::Expression::DomainFuncApp(expression) => { + expression.expression_to_smt2(writer, info) + } + vir_low::Expression::InhaleExhale(expression) => { + expression.expression_to_smt2(writer, info) + } + vir_low::Expression::SmtOperation(expression) => { + expression.expression_to_smt2(writer, info) + } + } + } +} + +impl<'a, 'c, EC: EncoderContext> Expr2Smt> + for Expr2SmtWrapper<'a, vir_low::VariableDecl> +{ + fn expr_to_smt2(&self, writer: &mut Writer, _info: Info<'a, 'c, EC>) -> SmtRes<()> + where + Writer: Write, + { + write!(writer, "(")?; + write!(writer, "{}", self.expr.name)?; + write!(writer, " ")?; + self.expr.ty.type_to_smt2(writer)?; + write!(writer, ")")?; + Ok(()) + } +} + +impl<'a, 'c, EC: EncoderContext> Expr2Smt> + for Expr2SmtWrapper<'a, vir_low::Local> +{ + fn expr_to_smt2(&self, writer: &mut Writer, _info: Info<'a, 'c, EC>) -> SmtRes<()> + where + Writer: Write, + { + write!(writer, "{}", self.expr.variable.name)?; + Ok(()) + } +} + +impl<'a, 'c, EC: EncoderContext> Expr2Smt> + for Expr2SmtWrapper<'a, vir_low::Field> +{ + fn expr_to_smt2(&self, _writer: &mut Writer, _info: Info<'a, 'c, EC>) -> SmtRes<()> + where + Writer: Write, + { + unimplemented!() + } +} + +impl<'a, 'c, EC: EncoderContext> Expr2Smt> + for Expr2SmtWrapper<'a, vir_low::LabelledOld> +{ + fn expr_to_smt2(&self, _writer: &mut Writer, _info: Info<'a, 'c, EC>) -> SmtRes<()> + where + Writer: Write, + { + unreachable!("Should be desugared by the caller: {}", self.expr); + } +} + +impl<'a, 'c, EC: EncoderContext> Expr2Smt> + for Expr2SmtWrapper<'a, vir_low::Constant> +{ + fn expr_to_smt2(&self, writer: &mut Writer, _info: Info<'a, 'c, EC>) -> SmtRes<()> + where + Writer: Write, + { + match &self.expr.value { + vir_low::ConstantValue::Bool(true) => write!(writer, "true")?, + vir_low::ConstantValue::Bool(false) => write!(writer, "false")?, + vir_low::ConstantValue::Int(value) => write!(writer, "{}", value)?, + vir_low::ConstantValue::BigInt(value) => write!(writer, "{}", value)?, + } + Ok(()) + } +} + +impl<'a, 'c, EC: EncoderContext> Expr2Smt> + for Expr2SmtWrapper<'a, vir_low::MagicWand> +{ + fn expr_to_smt2(&self, _writer: &mut Writer, _info: Info<'a, 'c, EC>) -> SmtRes<()> + where + Writer: Write, + { + unimplemented!() + } +} + +impl<'a, 'c, EC: EncoderContext> Expr2Smt> + for Expr2SmtWrapper<'a, vir_low::PredicateAccessPredicate> +{ + fn expr_to_smt2(&self, _writer: &mut Writer, _info: Info<'a, 'c, EC>) -> SmtRes<()> + where + Writer: Write, + { + unimplemented!() + } +} + +impl<'a, 'c, EC: EncoderContext> Expr2Smt> + for Expr2SmtWrapper<'a, vir_low::FieldAccessPredicate> +{ + fn expr_to_smt2(&self, _writer: &mut Writer, _info: Info<'a, 'c, EC>) -> SmtRes<()> + where + Writer: Write, + { + unimplemented!() + } +} + +impl<'a, 'c, EC: EncoderContext> Expr2Smt> + for Expr2SmtWrapper<'a, vir_low::Unfolding> +{ + fn expr_to_smt2(&self, _writer: &mut Writer, _info: Info<'a, 'c, EC>) -> SmtRes<()> + where + Writer: Write, + { + unimplemented!() + } +} + +impl<'a, 'c, EC: EncoderContext> Expr2Smt> + for Expr2SmtWrapper<'a, vir_low::UnaryOp> +{ + fn expr_to_smt2(&self, writer: &mut Writer, info: Info<'a, 'c, EC>) -> SmtRes<()> + where + Writer: Write, + { + match self.expr.op_kind { + vir_low::UnaryOpKind::Not => { + write!(writer, "(not ")?; + } + vir_low::UnaryOpKind::Minus => { + write!(writer, "(- ")?; + } + } + self.expr.argument.expression_to_smt2(writer, info)?; + write!(writer, " )")?; + Ok(()) + } +} + +impl<'a, 'c, EC: EncoderContext> Expr2Smt> + for Expr2SmtWrapper<'a, vir_low::BinaryOp> +{ + fn expr_to_smt2(&self, writer: &mut Writer, info: Info<'a, 'c, EC>) -> SmtRes<()> + where + Writer: Write, + { + write!(writer, "(")?; + match self.expr.op_kind { + vir_low::BinaryOpKind::EqCmp => write!(writer, "=")?, + vir_low::BinaryOpKind::NeCmp => { + write!(writer, "not (= ")?; + } + vir_low::BinaryOpKind::GtCmp => write!(writer, ">")?, + vir_low::BinaryOpKind::GeCmp => write!(writer, ">=")?, + vir_low::BinaryOpKind::LtCmp => write!(writer, "<")?, + vir_low::BinaryOpKind::LeCmp => write!(writer, "<=")?, + vir_low::BinaryOpKind::Add => write!(writer, "+")?, + vir_low::BinaryOpKind::Sub => write!(writer, "-")?, + vir_low::BinaryOpKind::Mul => write!(writer, "*")?, + vir_low::BinaryOpKind::Div => { + if matches!(self.expr.left.get_type(), vir_low::Type::Int) { + write!(writer, "div")? + } else { + write!(writer, "/")? + } + } + vir_low::BinaryOpKind::Mod => write!(writer, "mod")?, + vir_low::BinaryOpKind::And => write!(writer, "and")?, + vir_low::BinaryOpKind::Or => write!(writer, "or")?, + vir_low::BinaryOpKind::Implies => write!(writer, "=>")?, + } + write!(writer, " ")?; + self.expr.left.expression_to_smt2(writer, info)?; + write!(writer, " ")?; + self.expr.right.expression_to_smt2(writer, info)?; + write!(writer, " )")?; + if self.expr.op_kind == vir_low::BinaryOpKind::NeCmp { + write!(writer, " )")?; + } + Ok(()) + } +} + +impl<'a, 'c, EC: EncoderContext> Expr2Smt> + for Expr2SmtWrapper<'a, vir_low::PermBinaryOp> +{ + fn expr_to_smt2(&self, writer: &mut Writer, info: Info<'a, 'c, EC>) -> SmtRes<()> + where + Writer: Write, + { + write!(writer, "(")?; + match self.expr.op_kind { + vir_low::PermBinaryOpKind::Add => write!(writer, "+")?, + vir_low::PermBinaryOpKind::Sub => write!(writer, "-")?, + vir_low::PermBinaryOpKind::Mul => write!(writer, "*")?, + vir_low::PermBinaryOpKind::Div => write!(writer, "/")?, + } + write!(writer, " ")?; + self.expr.left.expression_to_smt2(writer, info)?; + write!(writer, " ")?; + self.expr.right.expression_to_smt2(writer, info)?; + write!(writer, " )")?; + Ok(()) + } +} + +impl<'a, 'c, EC: EncoderContext> Expr2Smt> + for Expr2SmtWrapper<'a, vir_low::ContainerOp> +{ + fn expr_to_smt2(&self, _writer: &mut Writer, _info: Info<'a, 'c, EC>) -> SmtRes<()> + where + Writer: Write, + { + unreachable!( + "ContainerOp should be desugared before this point: {}", + self.expr + ); + // write!(writer, "(")?; + // self.expr.container_type.type_to_smt2(writer, info)?; + // write!(writer, "@{}", self.expr.kind)?; + // for arg in &self.expr.operands { + // write!(writer, " ")?; + // arg.expression_to_smt2(writer, info)?; + // } + // write!(writer, ")")?; + // Ok(()) + } +} + +impl<'a, 'c, EC: EncoderContext> Expr2Smt> + for Expr2SmtWrapper<'a, vir_low::ConditionalExpression> +{ + fn expr_to_smt2(&self, writer: &mut Writer, info: Info<'a, 'c, EC>) -> SmtRes<()> + where + Writer: Write, + { + write!(writer, "(ite ")?; + self.expr.guard.expression_to_smt2(writer, info)?; + write!(writer, " ")?; + self.expr.then_expr.expression_to_smt2(writer, info)?; + write!(writer, " ")?; + self.expr.else_expr.expression_to_smt2(writer, info)?; + write!(writer, ")")?; + Ok(()) + } +} + +impl<'a, 'c, EC: EncoderContext> Expr2Smt> + for Expr2SmtWrapper<'a, vir_low::Quantifier> +{ + fn expr_to_smt2(&self, writer: &mut Writer, info: Info<'a, 'c, EC>) -> SmtRes<()> + where + Writer: Write, + { + let expr = self.expr; + match expr.kind { + vir_low::QuantifierKind::ForAll => write!(writer, "(forall (")?, + vir_low::QuantifierKind::Exists => write!(writer, "(exists (")?, + } + for variable in &expr.variables { + variable.expression_to_smt2(writer, info)?; + } + write!(writer, ") (! ")?; + Expr2SmtWrapper::new(&*expr.body).expr_to_smt2(writer, info)?; + if let Some(name) = &self.expr.name { + write!(writer, " :qid |{}|", name)?; + } + for trigger in &expr.triggers { + trigger.expression_to_smt2(writer, info)?; + } + write!(writer, " ))")?; + Ok(()) + } +} + +impl<'a, 'c, EC: EncoderContext> Expr2Smt> + for Expr2SmtWrapper<'a, vir_low::Trigger> +{ + fn expr_to_smt2(&self, writer: &mut Writer, info: Info<'a, 'c, EC>) -> SmtRes<()> + where + Writer: Write, + { + write!(writer, " :pattern (")?; + for part in &self.expr.terms { + part.expression_to_smt2(writer, info)?; + } + write!(writer, ")")?; + Ok(()) + } +} + +impl<'a, 'c, EC: EncoderContext> Expr2Smt> + for Expr2SmtWrapper<'a, vir_low::LetExpr> +{ + fn expr_to_smt2(&self, _writer: &mut Writer, _info: Info<'a, 'c, EC>) -> SmtRes<()> + where + Writer: Write, + { + unimplemented!() + } +} + +impl<'a, 'c, EC: EncoderContext> Expr2Smt> + for Expr2SmtWrapper<'a, vir_low::FuncApp> +{ + fn expr_to_smt2(&self, _writer: &mut Writer, _info: Info<'a, 'c, EC>) -> SmtRes<()> + where + Writer: Write, + { + unreachable!("FuncApp: {}. Should be desugared by the caller.", self.expr); + } +} + +impl<'a, 'c, EC: EncoderContext> Expr2Smt> + for Expr2SmtWrapper<'a, vir_low::DomainFuncApp> +{ + fn expr_to_smt2(&self, writer: &mut Writer, info: Info<'a, 'c, EC>) -> SmtRes<()> + where + Writer: Write, + { + if self.expr.arguments.is_empty() { + write!( + writer, + "{}@{}", + self.expr.domain_name, self.expr.function_name + )?; + } else { + write!(writer, "(")?; + write!( + writer, + "{}@{}", + self.expr.domain_name, self.expr.function_name + )?; + for arg in &self.expr.arguments { + write!(writer, " ")?; + arg.expression_to_smt2(writer, info)?; + } + write!(writer, ")")?; + } + Ok(()) + } +} + +impl<'a, 'c, EC: EncoderContext> Expr2Smt> + for Expr2SmtWrapper<'a, vir_low::InhaleExhale> +{ + fn expr_to_smt2(&self, _writer: &mut Writer, _info: Info<'a, 'c, EC>) -> SmtRes<()> + where + Writer: Write, + { + unimplemented!() + } +} + +impl<'a, 'c, EC: EncoderContext> Expr2Smt> + for Expr2SmtWrapper<'a, vir_low::SmtOperation> +{ + fn expr_to_smt2(&self, writer: &mut Writer, info: Info<'a, 'c, EC>) -> SmtRes<()> + where + Writer: Write, + { + match self.expr.operation_kind { + vir_low::SmtOperationKind::PbQe => { + let arguments = &*self.expr.arguments; + assert!(arguments.len() % 2 == 0); + let weights = &arguments[..arguments.len() / 2]; + let guards = &arguments[arguments.len() / 2..]; + assert_eq!(weights.len(), guards.len()); + write!(writer, "((_ pbge 1")?; + for weight in weights { + write!(writer, " ")?; + weight.expression_to_smt2(writer, info)?; + } + write!(writer, ")")?; + for guard in guards { + guard.expression_to_smt2(writer, info)?; + write!(writer, " ")?; + } + write!(writer, ")")?; + } + } + Ok(()) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/svirpti/smt/mod.rs b/prusti-viper/src/encoder/middle/core_proof/svirpti/smt/mod.rs new file mode 100644 index 00000000000..1adfcb3e2b8 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/svirpti/smt/mod.rs @@ -0,0 +1,14 @@ +mod configuration; +mod parser; +mod solver; +mod errors; +mod expressions; +mod types; + +pub(super) use self::solver::Info; +pub use self::{ + configuration::Configuration, + errors::{SmtSolverError, SmtSolverResult}, + solver::SmtSolver, + types::Sort2SmtWrap, +}; diff --git a/prusti-viper/src/encoder/middle/core_proof/svirpti/smt/parser.rs b/prusti-viper/src/encoder/middle/core_proof/svirpti/smt/parser.rs new file mode 100644 index 00000000000..a510545a05f --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/svirpti/smt/parser.rs @@ -0,0 +1 @@ +pub struct SmtParser {} diff --git a/prusti-viper/src/encoder/middle/core_proof/svirpti/smt/solver.rs b/prusti-viper/src/encoder/middle/core_proof/svirpti/smt/solver.rs new file mode 100644 index 00000000000..87a2d0d5197 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/svirpti/smt/solver.rs @@ -0,0 +1,318 @@ +use crate::encoder::middle::core_proof::svirpti::smt::SmtSolverError; + +use super::{ + super::super::transformations::{ + encoder_context::EncoderContext, symbolic_execution_new::ProgramContext, + }, + configuration::Configuration, + errors::SmtSolverResult, + expressions::Expr2SmtWrap, + parser::SmtParser, +}; +use prusti_common::Stopwatch; +use rsmt2::{print::Sort2Smt, Solver}; +use rustc_hash::{FxHashMap, FxHashSet}; +use vir_crate::{ + common::expression::BinaryOperationHelpers, + low::{ + self as vir_low, + expression::visitors::{default_fallible_walk_expression, ExpressionFallibleWalker}, + }, +}; + +#[derive(Debug, PartialEq, Eq)] +pub enum SatResult { + Unsat, + Unknown, + Sat, +} + +impl SatResult { + pub fn is_sat(&self) -> bool { + matches!(self, SatResult::Sat) + } + pub fn is_unsat(&self) -> bool { + matches!(self, SatResult::Unsat) + } + pub fn is_sat_or_unknown(&self) -> bool { + matches!(self, SatResult::Sat | SatResult::Unknown) + } +} + +pub struct Info<'a, 'c, EC: EncoderContext> { + pub(in super::super) program_context: &'a ProgramContext<'c, EC>, +} + +impl<'a, 'c, EC: EncoderContext> Clone for Info<'a, 'c, EC> { + fn clone(&self) -> Self { + Self { + program_context: self.program_context, + } + } +} + +impl<'a, 'c, EC: EncoderContext> Copy for Info<'a, 'c, EC> {} + +pub struct SmtSolver { + check_sat_counter: u64, + solver: Solver, + /// Triggerring function name → Vec<(trigger, quantifier)> + axiom_quantifiers: FxHashMap>, + matched_terms: FxHashSet, +} + +impl SmtSolver { + pub fn new(conf: Configuration) -> SmtSolverResult { + let parser = SmtParser {}; + let mut solver = Solver::new(conf.smt_conf, parser)?; + if let Some(tee_path) = conf.tee_path { + solver.path_tee(tee_path).unwrap(); + } + for attribute in &conf.attributes { + solver.set_info(attribute)?; + } + for (option, value) in &conf.options { + solver.set_option(option, value)?; + } + Ok(Self { + solver, + check_sat_counter: 0, + axiom_quantifiers: Default::default(), + matched_terms: Default::default(), + }) + } + /// Add an axiom to be automatically instantiated. + pub fn add_axiom(&mut self, axiom: vir_low::DomainAxiomDecl) -> SmtSolverResult { + eprintln!("axiom: {axiom}"); + match axiom.body { + vir_low::Expression::Quantifier(mut quantifier) => { + for mut trigger in std::mem::take(&mut quantifier.triggers) { + if trigger.terms.len() != 1 { + return Ok(false); + } + match &trigger.terms[0] { + vir_low::Expression::DomainFuncApp(vir_low::DomainFuncApp { + function_name, + .. + }) => { + let entry = self + .axiom_quantifiers + .entry(function_name.clone()) + .or_default(); + entry.push((trigger.terms.pop().unwrap(), quantifier.clone())); + } + _ => unimplemented!(), + } + } + return Ok(true); + } + vir_low::Expression::DomainFuncApp(_) => { + return Ok(false); + } + vir_low::Expression::BinaryOp(vir_low::BinaryOp { + op_kind: vir_low::BinaryOpKind::EqCmp, + .. + }) => { + return Ok(false); + } + _ => unimplemented!(), + } + unreachable!(); + } + /// We cannot use the `Default` trait because this is potentially failing + /// operation. + pub fn default() -> SmtSolverResult { + Self::new(Default::default()) + } + pub fn check_sat(&mut self) -> SmtSolverResult { + self.solver + .comment(&format!("Check-sat id: {}", self.check_sat_counter)) + .unwrap(); + self.check_sat_counter += 1; + let stopwatch = Stopwatch::start("svirpti", "check-sat"); + let result = match self.solver.check_sat_or_unk()? { + Some(true) => SatResult::Sat, + Some(false) => SatResult::Unsat, + None => SatResult::Unknown, + }; + let duration = stopwatch.finish(); + self.solver + .comment(&format!("Check-sat duration: {:?}", duration))?; + Ok(result) + } + pub fn push(&mut self) -> SmtSolverResult<()> { + self.solver.push(1)?; + Ok(()) + } + pub fn pop(&mut self) -> SmtSolverResult<()> { + self.solver.pop(1)?; + Ok(()) + } + pub fn declare_sort(&mut self, sort: &str) -> SmtSolverResult<()> { + self.solver.declare_sort(sort, 0)?; + Ok(()) + } + pub fn declare_variable(&mut self, name: &str, sort: Sort) -> SmtSolverResult<()> + where + Sort: Sort2Smt, + { + self.solver.declare_const(name, sort)?; + Ok(()) + } + pub fn declare_function( + &mut self, + domain_name: &str, + function_name: &str, + parameter_types: ParameterSorts, + result_type: ResultSort, + ) -> SmtSolverResult<()> + where + ParameterSorts: IntoIterator, + ParameterSorts::Item: Sort2Smt, + ResultSort: Sort2Smt, + { + let full_function_name = format!("{domain_name}@{function_name}"); + self.solver + .declare_fun(full_function_name, parameter_types, result_type)?; + Ok(()) + } + pub fn comment(&mut self, comment: &str) -> SmtSolverResult<()> { + self.solver.comment(comment)?; + Ok(()) + } + pub fn assert<'a, 'c, EC: EncoderContext>( + &mut self, + assertion: &vir_low::Expression, + info: Info<'a, 'c, EC>, + ) -> SmtSolverResult<()> { + self.trigger_axioms(assertion, info)?; + self.solver.assert_with(assertion.wrap(), info)?; + Ok(()) + } + fn trigger_axioms<'a, 'c, EC: EncoderContext>( + &mut self, + assertion: &vir_low::Expression, + info: Info<'a, 'c, EC>, + ) -> SmtSolverResult<()> { + struct Instantiator<'a, 'c, EC: EncoderContext> { + solver: &'a mut SmtSolver, + info: Info<'a, 'c, EC>, + } + fn try_match<'a>( + expression: &'a vir_low::Expression, + trigger: &'a vir_low::Expression, + replacements: &mut FxHashMap<&'a vir_low::VariableDecl, &'a vir_low::Expression>, + ) -> bool { + match (expression, trigger) { + (_, vir_low::Expression::Local(local)) => { + assert!(replacements.insert(&local.variable, expression).is_none()); + true + } + ( + vir_low::Expression::DomainFuncApp(app1), + vir_low::Expression::DomainFuncApp(app2), + ) if app1.domain_name == app2.domain_name + && app1.function_name == app2.function_name => + { + for (arg1, arg2) in app1.arguments.iter().zip(app2.arguments.iter()) { + if !try_match(arg1, arg2, replacements) { + return false; + } + } + true + } + _ => false, + } + } + impl<'a, 'c, EC: EncoderContext> ExpressionFallibleWalker for Instantiator<'a, 'c, EC> { + type Error = SmtSolverError; + fn fallible_walk_expression( + &mut self, + expression: &vir_low::Expression, + ) -> Result<(), Self::Error> { + match expression { + vir_low::Expression::DomainFuncApp(domain_func_app) => { + if self.solver.matched_terms.contains(expression) { + return Ok(()); + } + let mut assertions = Vec::new(); + if let Some(quantifiers) = self + .solver + .axiom_quantifiers + .get(&domain_func_app.function_name) + { + for (trigger, quantifier) in quantifiers { + let mut replacements = FxHashMap::default(); + if try_match(expression, trigger, &mut replacements) { + eprintln!( + "matched:\nexpression: {expression}\ntrigger: {trigger}" + ); + eprintln!("quantifier: {quantifier}"); + assert_eq!(quantifier.variables.len(), replacements.len()); + for variable in &quantifier.variables { + assert!( + replacements.contains_key(variable), + "Missing variable: {variable}" + ); + } + let assertion = + quantifier.body.clone().substitute_variables(&replacements); + assertions.push(assertion); + } else { + eprintln!( + "unmatched:\nexpression: {expression}\ntrigger: {trigger}" + ); + eprintln!("quantifier: {quantifier}"); + if domain_func_app.function_name == "valid$Snap$Usize" { + assert_eq!(domain_func_app.arguments.len(), 1); + let argument = &domain_func_app.arguments[0]; + let value = vir_low::Expression::domain_function_call( + "Snap$Usize", + "destructor$Snap$Usize$$value", + vec![argument.clone()], + vir_low::Type::Int, + ); + let assertion = vir_low::Expression::and( + vir_low::Expression::less_equals( + 0.into(), + value.clone(), + ), + vir_low::Expression::less_equals( + value.clone(), + 18446744073709551615u64.into(), + ), + ); + eprintln!("assertion: {assertion}"); + assertions.push(assertion); + } + // assert_ne!( + // &domain_func_app.function_name, + // "mul_wrapper$" + // ); + // unimplemented!(); + } + } + } + if !assertions.is_empty() { + self.solver.matched_terms.insert(expression.clone()); + } + for assertion in assertions { + self.solver.comment("quantifier trigger")?; + self.solver.assert(&assertion, self.info)?; + } + } + vir_low::Expression::Quantifier(_) => { + // FIXME: In such cases, we should emit axioms to Z3 so + // that it can instantiate them itself. + return Ok(()); + } + _ => {} + } + default_fallible_walk_expression(self, expression) + } + } + let mut instantiator = Instantiator { solver: self, info }; + instantiator.fallible_walk_expression(assertion)?; + Ok(()) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/svirpti/smt/types.rs b/prusti-viper/src/encoder/middle/core_proof/svirpti/smt/types.rs new file mode 100644 index 00000000000..2c294161501 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/svirpti/smt/types.rs @@ -0,0 +1,67 @@ +use rsmt2::{print::Sort2Smt, SmtRes}; +use std::io::Write; +use vir_crate::low::{self as vir_low}; + +pub(super) trait Type2Smt<'a> { + fn type_to_smt2(&'a self, writer: &mut Writer) -> SmtRes<()> + where + Writer: Write; +} + +impl<'a, T> Type2Smt<'a> for T +where + Sort2SmtWrapper<'a, T>: Sort2Smt + 'a, +{ + fn type_to_smt2(&'a self, writer: &mut Writer) -> SmtRes<()> + where + Writer: Write, + { + Sort2SmtWrapper::new(self).sort_to_smt2(writer) + } +} + +pub struct Sort2SmtWrapper<'a, T> { + ty: &'a T, +} + +impl<'a, T> Sort2SmtWrapper<'a, T> { + pub(super) fn new(ty: &'a T) -> Self { + Self { ty } + } +} + +pub trait Sort2SmtWrap { + fn wrap(&self) -> Sort2SmtWrapper; +} + +impl<'a> Sort2SmtWrap for vir_low::Type { + fn wrap(&self) -> Sort2SmtWrapper { + Sort2SmtWrapper::new(self) + } +} + +impl<'a> Sort2Smt for Sort2SmtWrapper<'a, vir_low::Type> { + fn sort_to_smt2(&self, writer: &mut Writer) -> SmtRes<()> + where + Writer: Write, + { + match self.ty { + vir_low::Type::Int => write!(writer, "Int")?, + vir_low::Type::Bool => write!(writer, "Bool")?, + vir_low::Type::Perm => write!(writer, "Real")?, + vir_low::Type::Float(_) => todo!(), + vir_low::Type::BitVector(_) => todo!(), + vir_low::Type::Seq(_) => todo!(), + vir_low::Type::Set(ty) => { + write!(writer, "Set<")?; + ty.element_type.type_to_smt2(writer)?; + write!(writer, ">")?; + } + vir_low::Type::MultiSet(_) => todo!(), + vir_low::Type::Map(_) => todo!(), + vir_low::Type::Ref => todo!(), + vir_low::Type::Domain(ty) => write!(writer, "{}", ty.name)?, + } + Ok(()) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/case_splits.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/case_splits.rs new file mode 100644 index 00000000000..7a119b5b32f --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/case_splits.rs @@ -0,0 +1,101 @@ +use crate::encoder::errors::SpannedEncodingResult; +use vir_crate::{ + common::{expression::UnaryOperationHelpers, graphviz::ToGraphviz, position::Positioned}, + low::{self as vir_low}, +}; + +pub(in super::super) fn desugar_case_splits( + source_filename: &str, + mut program: vir_low::Program, +) -> SpannedEncodingResult { + for procedure in std::mem::take(&mut program.procedures) { + if prusti_common::config::dump_debug_info() { + prusti_common::report::log::report_with_writer( + "graphviz_method_vir_low_before_case_split", + format!("{}.{}.dot", source_filename, procedure.name), + |writer| procedure.to_graphviz(writer).unwrap(), + ); + } + desugar_case_splits_in_procedure(procedure, &mut program.procedures)?; + } + for procedure in &program.procedures { + if prusti_common::config::dump_debug_info() { + prusti_common::report::log::report_with_writer( + "graphviz_method_vir_low_after_case_split", + format!("{}.{}.dot", source_filename, procedure.name), + |writer| procedure.to_graphviz(writer).unwrap(), + ); + } + } + Ok(program) +} + +fn desugar_case_splits_in_procedure( + procedure: vir_low::ProcedureDecl, + procedures: &mut Vec, +) -> SpannedEncodingResult<()> { + let case_splits = collect_case_splits(&procedure)?; + if case_splits.is_empty() { + procedures.push(procedure); + return Ok(()); + } else { + expand_case_splits(&procedure, &case_splits[..], procedures)?; + } + Ok(()) +} + +fn collect_case_splits( + procedure: &vir_low::ProcedureDecl, +) -> SpannedEncodingResult> { + let mut case_splits = Vec::new(); + for (label, block) in &procedure.basic_blocks { + for (index, statement) in block.statements.iter().enumerate() { + if let vir_low::Statement::CaseSplit { .. } = statement { + case_splits.push((label.clone(), index)); + } + } + } + Ok(case_splits) +} + +fn expand_case_splits( + procedure: &vir_low::ProcedureDecl, + case_splits: &[(vir_low::Label, usize)], + procedures: &mut Vec, +) -> SpannedEncodingResult<()> { + assert!(case_splits.len() < 64); + let number_of_choices = 1u64 << case_splits.len(); + for choices in 0..number_of_choices { + let mut new_procedure = procedure.clone(); + new_procedure.name = format!("{}_case_split_{}", procedure.name, choices); + let mut choice_statements = + vec![vir_low::Statement::comment("Start case splits".to_string())]; + for (choice_location, (label, statement_index)) in case_splits.iter().enumerate() { + let choice = (choices >> choice_location) & 1; + let choice = choice == 1; + let block = new_procedure.basic_blocks.get_mut(label).unwrap(); + let statement = &mut block.statements[*statement_index]; + let vir_low::Statement::CaseSplit(case_split) = statement else { + unreachable!(); + }; + if choice { + *statement = + vir_low::Statement::assume(case_split.expression.clone(), case_split.position); + } else { + *statement = vir_low::Statement::assume( + vir_low::Expression::not(case_split.expression.clone()), + case_split.position, + ); + } + choice_statements.push(statement.clone()); + } + choice_statements.push(vir_low::Statement::comment("End case splits".to_string())); + let entry_block = new_procedure + .basic_blocks + .get_mut(&new_procedure.entry) + .unwrap(); + entry_block.statements.splice(0..0, choice_statements); + procedures.push(new_procedure); + } + Ok(()) +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/clean_labels.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/clean_labels.rs new file mode 100644 index 00000000000..6f180b848e7 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/clean_labels.rs @@ -0,0 +1,95 @@ +use rustc_hash::FxHashSet; +use vir_crate::{ + common::graphviz::ToGraphviz, + low::{ + self as vir_low, ast::statement::visitors::StatementWalker, + expression::visitors::ExpressionWalker, + }, +}; + +/// The transformations performed: +/// +/// 1. Remove all unused labels. +pub(in super::super) fn clean_labels( + source_filename: &str, + mut program: vir_low::Program, +) -> vir_low::Program { + for procedure in std::mem::take(&mut program.procedures) { + if prusti_common::config::dump_debug_info() { + prusti_common::report::log::report_with_writer( + "graphviz_method_vir_low_before_clean_labels", + format!("{}.{}.dot", source_filename, procedure.name), + |writer| procedure.to_graphviz(writer).unwrap(), + ); + } + let new_procedure = clean_labels_in_procedure(procedure); + if prusti_common::config::dump_debug_info() { + prusti_common::report::log::report_with_writer( + "graphviz_method_vir_low_after_clean_labels", + format!("{}.{}.dot", source_filename, new_procedure.name), + |writer| new_procedure.to_graphviz(writer).unwrap(), + ); + } + program.procedures.push(new_procedure); + } + program +} + +fn clean_labels_in_procedure(mut procedure: vir_low::ProcedureDecl) -> vir_low::ProcedureDecl { + let mut collector = UsedLabelCollector { + used_labels: Default::default(), + }; + for block in procedure.basic_blocks.values() { + for statement in &block.statements { + collector.walk_statement(statement); + } + match &block.successor { + vir_low::Successor::Return | vir_low::Successor::Goto(_) => {} + vir_low::Successor::GotoSwitch(expressions) => { + for (expression, _) in expressions { + ExpressionWalker::walk_expression(&mut collector, expression); + } + } + } + } + for block in procedure.basic_blocks.values_mut() { + for statement in std::mem::take(&mut block.statements) { + match statement { + vir_low::Statement::Label(vir_low::LabelStatement { label, .. }) + if !collector.used_labels.contains(&label) => + { + // This label is removed. + } + _ => block.statements.push(statement), + } + } + } + procedure + .custom_labels + .retain(|label| collector.used_labels.contains(&label.name)); + procedure +} + +struct UsedLabelCollector { + used_labels: FxHashSet, +} + +impl StatementWalker for UsedLabelCollector { + fn walk_expression(&mut self, expression: &vir_low::Expression) { + ExpressionWalker::walk_expression(self, expression); + } +} + +impl ExpressionWalker for UsedLabelCollector { + fn walk_labelled_old_enum(&mut self, labelled_old: &vir_low::LabelledOld) { + if let Some(label) = &labelled_old.label { + self.used_labels.insert(label.clone()); + } + self.walk_labelled_old(labelled_old); + } + fn walk_trigger(&mut self, trigger: &vir_low::Trigger) { + for expression in &trigger.terms { + ExpressionWalker::walk_expression(self, expression); + } + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/clean_old.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/clean_old.rs new file mode 100644 index 00000000000..cf3347c5b8e --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/clean_old.rs @@ -0,0 +1,96 @@ +use vir_crate::{ + common::graphviz::ToGraphviz, + low::{ + self as vir_low, ast::statement::visitors::StatementFolder, + expression::visitors::ExpressionFolder, + }, +}; + +/// The transformations performed: +/// +/// 1. Remove all redundant nested old expressions like +/// `old[label1](old[label1](...))`. +/// 2. Remove all unnecessary old expressions that wrap heap independent +/// expressions. +pub(in super::super) fn clean_old( + source_filename: &str, + mut program: vir_low::Program, +) -> vir_low::Program { + for procedure in std::mem::take(&mut program.procedures) { + if prusti_common::config::dump_debug_info() { + prusti_common::report::log::report_with_writer( + "graphviz_method_vir_low_before_clean_old", + format!("{}.{}.dot", source_filename, procedure.name), + |writer| procedure.to_graphviz(writer).unwrap(), + ); + } + let new_procedure = clean_old_in_procedure(procedure); + if prusti_common::config::dump_debug_info() { + prusti_common::report::log::report_with_writer( + "graphviz_method_vir_low_after_clean_old", + format!("{}.{}.dot", source_filename, new_procedure.name), + |writer| new_procedure.to_graphviz(writer).unwrap(), + ); + } + program.procedures.push(new_procedure); + } + program +} + +fn clean_old_in_procedure(mut procedure: vir_low::ProcedureDecl) -> vir_low::ProcedureDecl { + let mut folder = CleanOldFolder { + current_label: None, + }; + for block in procedure.basic_blocks.values_mut() { + for statement in std::mem::take(&mut block.statements) { + let new_statement = StatementFolder::fold_statement(&mut folder, statement); + block.statements.push(new_statement); + } + } + procedure +} + +struct CleanOldFolder { + current_label: Option, +} + +impl StatementFolder for CleanOldFolder { + fn fold_expression(&mut self, expression: vir_low::Expression) -> vir_low::Expression { + ExpressionFolder::fold_expression(self, expression) + } +} + +impl ExpressionFolder for CleanOldFolder { + fn fold_trigger(&mut self, mut trigger: vir_low::Trigger) -> vir_low::Trigger { + for term in std::mem::take(&mut trigger.terms) { + let new_term = ExpressionFolder::fold_expression(self, term); + trigger.terms.push(new_term); + } + trigger + } + + fn fold_labelled_old_enum( + &mut self, + labelled_old: vir_low::LabelledOld, + ) -> vir_low::Expression { + if labelled_old.base.is_heap_independent() { + return ExpressionFolder::fold_expression(self, *labelled_old.base); + } + let label = labelled_old.label.as_ref().expect("all labelled old expressions should have a label since we do not use regular preconditions"); + if let Some(current_label) = &self.current_label { + if label == current_label { + return ExpressionFolder::fold_expression(self, *labelled_old.base); + } + } + let old_label = self.current_label.take(); + self.current_label = labelled_old.label; + let expression = ExpressionFolder::fold_expression(self, *labelled_old.base); + let result = vir_low::Expression::labelled_old( + self.current_label.take(), + expression, + labelled_old.position, + ); + self.current_label = old_label; + result + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/clean_variables.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/clean_variables.rs new file mode 100644 index 00000000000..50d33627cba --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/clean_variables.rs @@ -0,0 +1,83 @@ +use rustc_hash::FxHashSet; +use vir_crate::{ + common::graphviz::ToGraphviz, + low::{ + self as vir_low, ast::statement::visitors::StatementWalker, + expression::visitors::ExpressionWalker, + }, +}; + +/// The transformations performed: +/// +/// 1. Remove unused variables. +pub(in super::super) fn clean_variables( + source_filename: &str, + mut program: vir_low::Program, +) -> vir_low::Program { + for procedure in std::mem::take(&mut program.procedures) { + if prusti_common::config::dump_debug_info() { + prusti_common::report::log::report_with_writer( + "graphviz_method_vir_low_before_clean_variables", + format!("{}.{}.dot", source_filename, procedure.name), + |writer| procedure.to_graphviz(writer).unwrap(), + ); + } + let new_procedure = clean_variables_in_procedure(procedure); + if prusti_common::config::dump_debug_info() { + prusti_common::report::log::report_with_writer( + "graphviz_method_vir_low_after_clean_variables", + format!("{}.{}.dot", source_filename, new_procedure.name), + |writer| new_procedure.to_graphviz(writer).unwrap(), + ); + } + program.procedures.push(new_procedure); + } + program +} + +fn clean_variables_in_procedure(mut procedure: vir_low::ProcedureDecl) -> vir_low::ProcedureDecl { + let mut collector = UsedVariableCollector { + used_variables: Default::default(), + }; + for block in procedure.basic_blocks.values() { + for statement in &block.statements { + collector.walk_statement(statement); + } + match &block.successor { + vir_low::Successor::Return | vir_low::Successor::Goto(_) => {} + vir_low::Successor::GotoSwitch(expressions) => { + for (expression, _) in expressions { + ExpressionWalker::walk_expression(&mut collector, expression); + } + } + } + } + procedure + .locals + .retain(|local| collector.used_variables.contains(&local.name)); + procedure +} + +struct UsedVariableCollector { + used_variables: FxHashSet, +} + +impl StatementWalker for UsedVariableCollector { + fn walk_expression(&mut self, expression: &vir_low::Expression) { + ExpressionWalker::walk_expression(self, expression); + } + fn walk_variable_decl(&mut self, variable_decl: &vir_low::VariableDecl) { + self.used_variables.insert(variable_decl.name.clone()); + } +} + +impl ExpressionWalker for UsedVariableCollector { + fn walk_variable_decl(&mut self, variable_decl: &vir_low::VariableDecl) { + self.used_variables.insert(variable_decl.name.clone()); + } + fn walk_trigger(&mut self, trigger: &vir_low::Trigger) { + for expression in &trigger.terms { + ExpressionWalker::walk_expression(self, expression); + } + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/custom_heap_encoding/heap_encoder/bound_variable_stack.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/custom_heap_encoding/heap_encoder/bound_variable_stack.rs new file mode 100644 index 00000000000..2c0da018d71 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/custom_heap_encoding/heap_encoder/bound_variable_stack.rs @@ -0,0 +1,47 @@ +use super::HeapEncoder; +use crate::encoder::errors::SpannedEncodingResult; +use rustc_hash::FxHashMap; +use vir_crate::low::{self as vir_low}; + +impl<'p, 'v: 'p, 'tcx: 'v> HeapEncoder<'p, 'v, 'tcx> { + pub(super) fn create_quantifier_variables_remap( + &mut self, + bound_variables: &[vir_low::VariableDecl], + ) -> SpannedEncodingResult<()> { + let mut remaps = FxHashMap::default(); + for bound_variable in bound_variables { + let remap = self.fresh_variable(bound_variable)?; + remaps.insert(bound_variable.clone(), remap); + } + self.bound_variable_remap_stack.push(remaps); + Ok(()) + } +} + +#[derive(Default)] +pub(super) struct BoundVariableRemapStack { + stack: Vec>, +} + +impl BoundVariableRemapStack { + pub(super) fn push(&mut self, remaps: FxHashMap) { + self.stack.push(remaps); + } + + pub(super) fn pop(&mut self) { + self.stack.pop(); + } + + pub(super) fn get(&self, variable: &vir_low::VariableDecl) -> Option<&vir_low::VariableDecl> { + for remaps in self.stack.iter().rev() { + if let Some(remap) = remaps.get(variable) { + return Some(remap); + } + } + None + } + + pub(super) fn is_empty(&self) -> bool { + self.stack.is_empty() + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/custom_heap_encoding/heap_encoder/effects/mod.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/custom_heap_encoding/heap_encoder/effects/mod.rs new file mode 100644 index 00000000000..e512b359dd0 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/custom_heap_encoding/heap_encoder/effects/mod.rs @@ -0,0 +1,1615 @@ +use super::{ + permission_mask::{ + PermissionMaskKind, PermissionMaskKindAliasedBool, PermissionMaskKindAliasedDuplicableBool, + PermissionMaskKindAliasedFractionalBoundedPerm, PermissionMaskOperations, + PredicatePermissionMaskKind, QuantifiedPermissionMaskOperations, TPermissionMaskOperations, + TQuantifiedPermissionMaskOperations, + }, + HeapEncoder, +}; +use crate::encoder::errors::SpannedEncodingResult; +use prusti_common::config; +use vir_crate::{ + common::expression::{ + BinaryOperationHelpers, ExpressionIterator, QuantifierHelpers, SyntacticEvaluation, + }, + low::{self as vir_low, operations::ty::Typed}, +}; + +impl<'p, 'v: 'p, 'tcx: 'v> HeapEncoder<'p, 'v, 'tcx> { + /// Note: this method assumes that it is called only as a top level assert + /// because it performs creating a new permission mask and rolling it back. + /// + /// Note: this method also evaluates accessibility predicates in + /// `expression_evaluation_state_label`. + pub(super) fn encode_expression_assert( + &mut self, + statements: &mut Vec, + expression: vir_low::Expression, + position: vir_low::Position, + expression_evaluation_state_label: Option, + ) -> SpannedEncodingResult<()> { + assert!(!position.is_default(), "expression: {expression}"); + if expression.is_pure() { + let expression = self.purify_snap_function_calls_in_expression( + statements, + expression, + expression_evaluation_state_label, + Vec::new(), + position, + false, + )?; + statements.push(vir_low::Statement::assert(expression, position)); + } else { + let check_point = self.fresh_label(); + self.ssa_state.save_state_at_label(check_point.clone()); + let evaluation_state = if let Some(label) = &expression_evaluation_state_label { + // This call is needed because we want to evaluate the + // accessibility predicates in the specified state. + self.ssa_state.change_state_to_label(label); + label + } else { + &check_point + }; + self.encode_expression_exhale(statements, expression, position, evaluation_state)?; + self.ssa_state.change_state_to_label(&check_point); + } + Ok(()) + } + + /// This method is similar to `encode_expression_assert` but it is intended + /// for asserting function preconditions. The key difference between + /// asserting function preconditions and regular assert statements is that + /// in function preconditions we ignore the permission amounts used in the + /// accessibility predicates: we only check that the permission amounts are + /// positive. + pub(super) fn encode_function_precondition_assert( + &mut self, + statements: &mut Vec, + expression: vir_low::Expression, + position: vir_low::Position, + expression_evaluation_state_label: Option, + ) -> SpannedEncodingResult<()> { + assert!(!position.is_default(), "expression: {expression}"); + if expression.is_pure() { + let expression = self.purify_snap_function_calls_in_expression( + statements, + expression, + expression_evaluation_state_label, + Vec::new(), + position, + true, + )?; + statements.push(vir_low::Statement::assert(expression, position)); + } else { + let check_point = self.fresh_label(); + self.ssa_state.save_state_at_label(check_point.clone()); + let evaluation_state = if let Some(label) = &expression_evaluation_state_label { + // This call is needed because we want to evaluate the + // accessibility predicates in the specified state. + self.ssa_state.change_state_to_label(label); + label + } else { + &check_point + }; + let expression = self.purify_snap_function_calls_in_expression( + statements, + expression, + Some(evaluation_state.to_string()), + Vec::new(), + position, + true, + )?; + self.encode_function_precondition_assert_rec( + statements, + expression, + position, + evaluation_state, + )?; + self.ssa_state.change_state_to_label(&check_point); + } + Ok(()) + } + + pub(super) fn encode_function_precondition_assert_rec( + &mut self, + statements: &mut Vec, + expression: vir_low::Expression, + position: vir_low::Position, + expression_evaluation_state_label: &str, + ) -> SpannedEncodingResult<()> { + assert!(!position.is_default(), "expression: {expression}"); + if expression.is_pure() { + // let expression = self.purify_snap_function_calls_in_expression( + // statements, + // expression, + // Some(expression_evaluation_state_label.to_string()), + // position, + // true, + // )?; + statements.push(vir_low::Statement::assert(expression, position)); + } else { + match expression { + vir_low::Expression::PredicateAccessPredicate(expression) => { + // expression.arguments = self.purify_snap_function_calls_in_expressions( + // statements, + // expression.arguments, + // Some(expression_evaluation_state_label.to_string()), + // position, + // true, + // )?; + // FIXME: evaluate predicate arguments in expression_evaluation_state_label + match self.get_predicate_permission_mask_kind(&expression.name)? { + PredicatePermissionMaskKind::AliasedWholeBool + | PredicatePermissionMaskKind::AliasedFractionalBool => { + let operations = + PermissionMaskOperations::::new( + self, + statements, + &expression, + Some(expression_evaluation_state_label.to_string()), + position, + )?; + self.encode_function_precondition_assert_rec_predicate( + statements, + &expression, + position, + operations, + )? + } + PredicatePermissionMaskKind::AliasedFractionalBoundedPerm => { + let operations = PermissionMaskOperations::< + PermissionMaskKindAliasedFractionalBoundedPerm, + >::new( + self, + statements, + &expression, + Some(expression_evaluation_state_label.to_string()), + position, + )?; + self.encode_function_precondition_assert_rec_predicate( + statements, + &expression, + position, + operations, + )? + } + PredicatePermissionMaskKind::AliasedWholeDuplicable => unimplemented!(), + } + } + vir_low::Expression::Unfolding(_) => todo!(), + vir_low::Expression::LabelledOld(_) => todo!(), + vir_low::Expression::BinaryOp(expression) => match expression.op_kind { + vir_low::BinaryOpKind::And => { + self.encode_function_precondition_assert_rec( + statements, + *expression.left, + position, + expression_evaluation_state_label, + )?; + self.encode_function_precondition_assert_rec( + statements, + *expression.right, + position, + expression_evaluation_state_label, + )?; + } + vir_low::BinaryOpKind::Implies if expression.left.is_true() => { + self.encode_function_precondition_assert_rec( + statements, + *expression.right, + position, + expression_evaluation_state_label, + )?; + } + vir_low::BinaryOpKind::Implies => { + let mut then_branch = Vec::new(); + self.encode_function_precondition_assert_rec( + &mut then_branch, + *expression.right, + position, + expression_evaluation_state_label, + )?; + // let guard = self.purify_snap_function_calls_in_expression( + // statements, + // *expression.left, + // Some(expression_evaluation_state_label.to_string()), + // position, + // true, + // )?; + let guard = *expression.left; + statements.push(vir_low::Statement::conditional( + guard, + then_branch, + Vec::new(), + position, + )); + } + _ => unreachable!("expression: {}", expression), + }, + vir_low::Expression::Quantifier(expression) => { + if let vir_low::Expression::BinaryOp(vir_low::BinaryOp { + op_kind: vir_low::BinaryOpKind::Implies, + left: box guard, + right: box vir_low::Expression::PredicateAccessPredicate(predicate), + .. + }) = *expression.body + { + self.create_quantifier_variables_remap(&expression.variables)?; + eprintln!("-----------------"); + // let guard = self.purify_snap_function_calls_in_expression( + // statements, + // guard, + // Some(expression_evaluation_state_label.to_string()), + // position, + // false, + // )?; + // FIXME: evaluate predicate arguments in expression_evaluation_state_label + match self.get_predicate_permission_mask_kind(&predicate.name)? { + PredicatePermissionMaskKind::AliasedWholeBool + | PredicatePermissionMaskKind::AliasedFractionalBool => { + let operations = QuantifiedPermissionMaskOperations::< + PermissionMaskKindAliasedBool, + >::new( + self, + statements, + &predicate, + Some(expression_evaluation_state_label.to_string()), + position, + )?; + self.encode_function_precondition_assert_rec_quantified_predicate( + statements, + expression.variables, + guard, + &predicate, + expression.triggers, + position, + operations, + )? + } + PredicatePermissionMaskKind::AliasedFractionalBoundedPerm => { + let operations = QuantifiedPermissionMaskOperations::< + PermissionMaskKindAliasedFractionalBoundedPerm, + >::new( + self, + statements, + &predicate, + Some(expression_evaluation_state_label.to_string()), + position, + )?; + self.encode_function_precondition_assert_rec_quantified_predicate( + statements, + expression.variables, + guard, + &predicate, + expression.triggers, + position, + operations, + )? + } + PredicatePermissionMaskKind::AliasedWholeDuplicable => unimplemented!(), + } + self.bound_variable_remap_stack.pop(); + } else { + unimplemented!("expression: {:?}", expression); + } + } + vir_low::Expression::Conditional(_) => todo!(), + vir_low::Expression::FuncApp(_) => todo!(), + vir_low::Expression::DomainFuncApp(_) => todo!(), + _ => { + unimplemented!("expression: {:?}", expression); + } + } + } + Ok(()) + } + + // Note: This function does not check that permissions are disjoint, only + // that we have enough. This is fine, because function preconditions only + // need to be checked that we have some permission amount. + fn encode_function_precondition_assert_rec_predicate( + &mut self, + statements: &mut Vec, + predicate: &vir_low::ast::expression::PredicateAccessPredicate, + position: vir_low::Position, + operations: PermissionMaskOperations, + ) -> SpannedEncodingResult<()> + where + PermissionMaskOperations: TPermissionMaskOperations, + { + statements.push(vir_low::Statement::comment(format!( + "assert-function-precondition-predicate {predicate}" + ))); + // assert perm

(r1, r2, v_old) >= p + statements.push(vir_low::Statement::assert( + operations.perm_old_positive(), + position, // FIXME: use position of expression.permission with proper ErrorCtxt. + )); + Ok(()) + } + + // Note: This function does not check that permissions are disjoint, only + // that we have enough. This is fine, because function preconditions only + // need to be checked that we have some permission amount. + fn encode_function_precondition_assert_rec_quantified_predicate<'a, Kind: PermissionMaskKind>( + &mut self, + statements: &mut Vec, + _variables: Vec, + guard: vir_low::Expression, + predicate: &vir_low::ast::expression::PredicateAccessPredicate, + _triggers: Vec, + position: vir_low::Position, + operations: QuantifiedPermissionMaskOperations<'a, Kind>, + ) -> SpannedEncodingResult<()> + where + QuantifiedPermissionMaskOperations<'a, Kind>: TQuantifiedPermissionMaskOperations, + { + // FIXME: Code duplication with encode_expression_inhale_quantified_predicate + + // FIXME: See whether I can skolemize out the quantifier into a single + // assert statement. + statements.push(vir_low::Statement::comment(format!( + "assert-function-precondition-quantified-predicate {guard} ==> {predicate}" + ))); + // // Generate inverse functions for the variables. + // // ``` + // // forall index: Int :: + // // 0 <= index && index < size ==> + // // inverse$qp$P$index(offset(addr, index), Size$Pair()) == index + // // ``` + // // Also construct the necessary parts for the permission mask update quantifier. + // let parameters: Vec<_> = predicate + // .arguments + // .iter() + // .enumerate() + // .map(|(index, argument)| { + // vir_low::VariableDecl::new(format!("_{index}"), argument.get_type().clone()) + // }) + // .collect(); + // let parameters_as_arguments: Vec<_> = parameters + // .clone() + // .into_iter() + // .map(|parameter| parameter.into()) + // .collect(); + // let mut permission_mask_assert_trigger_terms = Vec::new(); + // let mut permission_mask_assert_guard = guard.clone(); + // let mut equalities: Vec = Vec::new(); + // for variable in &variables { + // let inverse_function_name = format!( + // "inverse$qp${}${}${}", + // predicate.name, + // variable.name, + // self.inverse_function_domain.functions.len() + // ); + // { + // // Declare the inverse function. + // let inverse_function = vir_low::DomainFunctionDecl::new( + // inverse_function_name.clone(), + // false, + // parameters.clone(), + // variable.ty.clone(), + // ); + // let permission_mask_assert_inverse_function_call = + // vir_low::Expression::domain_function_call( + // self.inverse_function_domain.name.clone(), + // inverse_function_name.clone(), + // parameters_as_arguments.clone(), + // variable.ty.clone(), + // ); + // permission_mask_assert_guard = permission_mask_assert_guard.replace_place( + // &variable.clone().into(), + // &permission_mask_assert_inverse_function_call, + // ); + // permission_mask_assert_trigger_terms + // .push(permission_mask_assert_inverse_function_call); + // self.inverse_function_domain + // .functions + // .push(inverse_function); + // } + // { + // // Declare the inverse function definitional equality. + // let inverse_function_call = vir_low::Expression::domain_function_call( + // self.inverse_function_domain.name.clone(), + // inverse_function_name, + // predicate.arguments.clone(), + // variable.ty.clone(), + // ); + // eprintln!("inverse_function: {}", inverse_function_call); + // equalities.push(vir_low::Expression::equals( + // variable.clone().into(), + // inverse_function_call, + // )); + // } + // } + // We are using skolemized variables, so inverse functions are not needed. + // let axiom_body = vir_low::Expression::forall( + // variables.clone(), + // triggers.clone(), + // vir_low::Expression::implies(guard.clone(), equalities.into_iter().conjoin()), + // ); + // // let axiom = vir_low::DomainAxiomDecl::new( + // // None, + // // format!( + // // "inverse_function_definitional_axiom${}", + // // self.inverse_function_domain.axioms.len() + // // ), + // // axiom_body, + // // ); + // // eprintln!("axiom: {axiom}"); + // // self.inverse_function_domain.axioms.push(axiom); + // statements.push(vir_low::Statement::assume(axiom_body, position)); + + // ``` + // assert forall parameters :: + // {inverse$qp$P$index(parameters)} + // guard_with_variables_replaced_with_inverse_functions ==> + // perm

(parameters, v_old) >= p : + // ``` + // let permission_mask_assert_statement = vir_low::Statement::assert( + // vir_low::Expression::forall( + // parameters, + // vec![vir_low::Trigger::new(permission_mask_assert_trigger_terms)], + // vir_low::Expression::implies( + // permission_mask_assert_guard, + // operations.perm_old_positive(self, parameters_as_arguments)?, + // ), + // ), + // position, // FIXME: use position of expression.permission with proper ErrorCtxt. + // ); + let permission_mask_assert_statement = vir_low::Statement::assert( + vir_low::Expression::implies( + guard, + operations.perm_old_positive(self, predicate.arguments.clone())?, + ), + position, // FIXME: use position of expression.permission with proper ErrorCtxt. + ); + // eprintln!( + // "permission_mask_assert_statement: {}", + // permission_mask_assert_statement + // ); + statements.push(permission_mask_assert_statement); + Ok(()) + } + + pub(super) fn encode_expression_exhale( + &mut self, + statements: &mut Vec, + expression: vir_low::Expression, + position: vir_low::Position, + expression_evaluation_state_label: &str, + ) -> SpannedEncodingResult<()> { + assert!(!position.is_default(), "expression: {expression}"); + if expression.is_pure() { + let expression = self.purify_snap_function_calls_in_expression( + statements, + expression, + Some(expression_evaluation_state_label.to_string()), + Vec::new(), + position, + false, + )?; + statements.push(vir_low::Statement::assert(expression, position)); + } else { + match expression { + vir_low::Expression::PredicateAccessPredicate(expression) => { + // FIXME: evaluate predicate arguments in expression_evaluation_state_label + match self.get_predicate_permission_mask_kind(&expression.name)? { + PredicatePermissionMaskKind::AliasedWholeBool + | PredicatePermissionMaskKind::AliasedFractionalBool => { + let operations = + PermissionMaskOperations::::new( + self, + statements, + &expression, + Some(expression_evaluation_state_label.to_string()), + position, + )?; + self.encode_expression_exhale_predicate( + statements, + &expression, + position, + Some(expression_evaluation_state_label.to_string()), + operations, + )? + } + PredicatePermissionMaskKind::AliasedFractionalBoundedPerm => { + let operations = PermissionMaskOperations::< + PermissionMaskKindAliasedFractionalBoundedPerm, + >::new( + self, + statements, + &expression, + Some(expression_evaluation_state_label.to_string()), + position, + )?; + self.encode_expression_exhale_predicate( + statements, + &expression, + position, + Some(expression_evaluation_state_label.to_string()), + operations, + )? + } + PredicatePermissionMaskKind::AliasedWholeDuplicable => { + let operations = PermissionMaskOperations::< + PermissionMaskKindAliasedDuplicableBool, + >::new( + self, + statements, + &expression, + Some(expression_evaluation_state_label.to_string()), + position, + )?; + self.encode_expression_exhale_predicate( + statements, + &expression, + position, + Some(expression_evaluation_state_label.to_string()), + operations, + )? + } + } + } + vir_low::Expression::Unfolding(_) => todo!(), + vir_low::Expression::LabelledOld(_) => todo!(), + vir_low::Expression::BinaryOp(expression) => match expression.op_kind { + vir_low::BinaryOpKind::And => { + self.encode_expression_exhale( + statements, + *expression.left, + position, + expression_evaluation_state_label, + )?; + self.encode_expression_exhale( + statements, + *expression.right, + position, + expression_evaluation_state_label, + )?; + } + vir_low::BinaryOpKind::Implies if expression.left.is_true() => { + self.encode_expression_exhale( + statements, + *expression.right, + position, + expression_evaluation_state_label, + )?; + } + vir_low::BinaryOpKind::Implies => { + unimplemented!("Merge the heap versions in the commented out code below."); + // let guard = self.purify_snap_function_calls_in_expression( + // statements, + // *expression.left, + // Some(expression_evaluation_state_label.to_string()), + // position, + // )?; + // let mut body = Vec::new(); + // self.encode_expression_exhale( + // &mut body, + // *expression.right, + // position, + // expression_evaluation_state_label, + // )?; + // // FIXME: Permission mask and heap versions need to be + // // unified after the branch merge. + // statements.push(vir_low::Statement::conditional( + // guard, + // body, + // Vec::new(), + // position, + // )) + } + _ => unreachable!("expression: {}", expression), + }, + vir_low::Expression::Quantifier(expression) => { + if let vir_low::Expression::BinaryOp(vir_low::BinaryOp { + op_kind: vir_low::BinaryOpKind::Implies, + left: box guard, + right: box vir_low::Expression::PredicateAccessPredicate(mut predicate), + .. + }) = *expression.body + { + self.create_quantifier_variables_remap(&expression.variables)?; + let guard = self.purify_snap_function_calls_in_expression( + statements, + guard, + Some(expression_evaluation_state_label.to_string()), + Vec::new(), + position, + false, + )?; + predicate.arguments = self.purify_snap_function_calls_in_expressions( + statements, + predicate.arguments, + Some(expression_evaluation_state_label.to_string()), + vec![guard.clone()], + position, + false, + )?; + eprintln!("guard: {guard}"); + eprintln!("body: {predicate}"); + match self.get_predicate_permission_mask_kind(&predicate.name)? { + PredicatePermissionMaskKind::AliasedWholeBool + | PredicatePermissionMaskKind::AliasedFractionalBool => { + let operations = QuantifiedPermissionMaskOperations::< + PermissionMaskKindAliasedBool, + >::new( + self, + statements, + &predicate, + Some(expression_evaluation_state_label.to_string()), + position, + )?; + self.encode_expression_exhale_quantified_predicate( + statements, + expression.variables, + guard, + &predicate, + expression.triggers, + position, + Some(expression_evaluation_state_label.to_string()), + operations, + )? + } + PredicatePermissionMaskKind::AliasedFractionalBoundedPerm => { + let operations = QuantifiedPermissionMaskOperations::< + PermissionMaskKindAliasedFractionalBoundedPerm, + >::new( + self, + statements, + &predicate, + Some(expression_evaluation_state_label.to_string()), + position, + )?; + self.encode_expression_exhale_quantified_predicate( + statements, + expression.variables, + guard, + &predicate, + expression.triggers, + position, + Some(expression_evaluation_state_label.to_string()), + operations, + )? + } + PredicatePermissionMaskKind::AliasedWholeDuplicable => { + let operations = QuantifiedPermissionMaskOperations::< + PermissionMaskKindAliasedDuplicableBool, + >::new( + self, + statements, + &predicate, + Some(expression_evaluation_state_label.to_string()), + position, + )?; + self.encode_expression_exhale_quantified_predicate( + statements, + expression.variables, + guard, + &predicate, + expression.triggers, + position, + Some(expression_evaluation_state_label.to_string()), + operations, + )? + } + } + self.bound_variable_remap_stack.pop(); + } else { + unimplemented!("expression: {:?}", expression); + } + } + vir_low::Expression::Conditional(_) => todo!(), + vir_low::Expression::FuncApp(_) => todo!(), + vir_low::Expression::DomainFuncApp(_) => todo!(), + _ => { + unimplemented!("expression: {:?}", expression); + } + } + } + Ok(()) + } + + fn encode_expression_exhale_predicate( + &mut self, + statements: &mut Vec, + predicate: &vir_low::ast::expression::PredicateAccessPredicate, + position: vir_low::Position, + expression_evaluation_state_label: Option, + operations: PermissionMaskOperations, + ) -> SpannedEncodingResult<()> + where + PermissionMaskOperations: TPermissionMaskOperations, + { + statements.push(vir_low::Statement::comment(format!( + "exhale-predicate {predicate}" + ))); + // assert perm

(r1, r2, v_old) >= p + statements.push(vir_low::Statement::assert( + operations.perm_old_greater_equals(&predicate.permission), + position, // FIXME: use position of expression.permission with proper ErrorCtxt. + )); + let perm_new_value = operations.perm_old_sub(&predicate.permission); + // assume perm

(r1, r2, v_new) == perm

(r1, r2, v_old) - p + statements.push(vir_low::Statement::assume( + vir_low::Expression::equals(operations.perm_new(), perm_new_value.clone()), + position, // FIXME: use position of expression.permission with proper ErrorCtxt. + )); + // assume forall arg1: Ref, arg2: Ref :: + // {perm

(arg1, arg2, v_new)} + // !(r1 == arg1 && r2 == arg2) ==> + // perm

(arg1, arg2, v_new) == perm

(arg1, arg2, v_old) + self.encode_perm_unchanged_quantifier( + statements, + predicate, + operations.old_permission_mask_version(), + operations.new_permission_mask_version(), + position, + expression_evaluation_state_label, + perm_new_value, + )?; + // assume forall arg1: Ref, arg2: Ref :: + // {heap

(arg1, arg2, vh_new)} + // perm

(arg1, arg2, v_new) > 0 ==> + // heap

(arg1, arg2, vh_new) == heap

(arg1, arg2, vh_old) + self.encode_heap_unchanged_quantifier( + statements, + predicate, + operations.new_permission_mask_version(), + position, + )?; + Ok(()) + } + + fn encode_expression_exhale_quantified_predicate<'a, Kind: PermissionMaskKind>( + &mut self, + statements: &mut Vec, + variables: Vec, + guard: vir_low::Expression, + predicate: &vir_low::ast::expression::PredicateAccessPredicate, + mut triggers: Vec, + position: vir_low::Position, + // FIXME: The use of `expression_evaluation_state_label` is probably + // wrong in both QP and non-QP inhale. Shouldn't arguments be always + // purified? + expression_evaluation_state_label: Option, + operations: QuantifiedPermissionMaskOperations<'a, Kind>, + ) -> SpannedEncodingResult<()> + where + QuantifiedPermissionMaskOperations<'a, Kind>: TQuantifiedPermissionMaskOperations, + { + // FIXME: Code duplication with encode_function_precondition_assert_rec_quantified_predicate + statements.push(vir_low::Statement::comment(format!( + "exhale-qp-predicate {guard} ==> {predicate}" + ))); + eprintln!( + "variables: {}", + vir_crate::common::display::cjoin(&variables) + ); + eprintln!("guard: {}", guard); + eprintln!("predicate: {}", predicate); + if self.is_predicate_already_injective(&variables, &predicate.arguments)? + && config::custom_heap_encoding_omit_injective() + { + unimplemented!("injective predicate"); + } + // Generate inverse functions for the variables. + // ``` + // forall index: Int :: + // 0 <= index && index < size ==> + // inverse$qp$P$index(offset(addr, index), Size$Pair()) == index + // ``` + // Also construct the necessary parts for the permission mask update quantifier. + let parameters: Vec<_> = predicate + .arguments + .iter() + .enumerate() + .map(|(index, argument)| { + vir_low::VariableDecl::new(format!("_{index}"), argument.get_type().clone()) + }) + .collect(); + let parameters_as_arguments: Vec<_> = parameters + .clone() + .into_iter() + .map(|parameter| parameter.into()) + .collect(); + let mut permission_mask_trigger_terms = Vec::new(); + let mut permission_mask_guard = guard.clone(); + let mut inverse_function_trigger_function_calls = Vec::new(); + let mut variable_inverse_equalities: Vec = Vec::new(); + let mut parameter_inverse_equalities: Vec = Vec::new(); + for variable in &variables { + let inverse_function_name = format!( + "inverse$qp${}${}${}", + predicate.name, + variable.name, + self.inverse_function_domain.functions.len() + ); + { + // Declare the inverse function. + let inverse_function = vir_low::DomainFunctionDecl::new( + inverse_function_name.clone(), + false, + parameters.clone(), + variable.ty.clone(), + ); + let permission_mask_inverse_function_call = + vir_low::Expression::domain_function_call( + self.inverse_function_domain.name.clone(), + inverse_function_name.clone(), + parameters_as_arguments.clone(), + variable.ty.clone(), + ); + permission_mask_guard = permission_mask_guard.replace_place( + &variable.clone().into(), + &permission_mask_inverse_function_call, + ); + permission_mask_trigger_terms.push(permission_mask_inverse_function_call); + self.inverse_function_domain + .functions + .push(inverse_function); + // Declare the inverse function definitional equality. + let inverse_function_call = vir_low::Expression::domain_function_call( + self.inverse_function_domain.name.clone(), + inverse_function_name.clone(), + parameters_as_arguments.clone(), + variable.ty.clone(), + ); + for (parameter, argument) in parameters_as_arguments + .iter() + .zip(predicate.arguments.iter()) + { + let argument = argument + .clone() + .replace_place(&variable.clone().into(), &inverse_function_call); + parameter_inverse_equalities + .push(vir_low::Expression::equals(parameter.clone(), argument)); + } + } + { + // Declare the inverse function trigger function. + let inverse_function_trigger_function = vir_low::DomainFunctionDecl::new( + format!("{}$trigger", inverse_function_name), + false, + vec![vir_low::VariableDecl::new("_0", variable.ty.clone())], + vir_low::Type::Bool, + ); + // Declare the inverse function definitional equality. + let inverse_function_call = vir_low::Expression::domain_function_call( + self.inverse_function_domain.name.clone(), + inverse_function_name, + predicate.arguments.clone(), + variable.ty.clone(), + ); + let inverse_function_trigger_function_call = + vir_low::Expression::domain_function_call( + self.inverse_function_domain.name.clone(), + inverse_function_trigger_function.name.clone(), + vec![inverse_function_call.clone()], + vir_low::Type::Bool, + ); + eprintln!("inverse_function: {}", inverse_function_call); + inverse_function_trigger_function_calls + .push(inverse_function_trigger_function_call); + variable_inverse_equalities.push(vir_low::Expression::equals( + variable.clone().into(), + inverse_function_call, + )); + self.inverse_function_domain + .functions + .push(inverse_function_trigger_function); + } + } + // Desugar predicates in the trigger. + for trigger in &mut triggers { + for term in &mut trigger.terms { + if !term.is_heap_independent() { + let vir_low::Expression::PredicateAccessPredicate(trigger_predicate) = term else { + unreachable!("expected a predicate as a trigger") + }; + assert_eq!(trigger_predicate.name, predicate.name, "unimplemented"); + let trigger_perm = operations + .perm_new(self, std::mem::take(&mut trigger_predicate.arguments))?; + *term = trigger_perm; + } + } + } + // `variable == inverse(e(variable))` + let injectivity_axiom_body1 = vir_low::Expression::forall( + variables, + triggers, + vir_low::Expression::and( + inverse_function_trigger_function_calls + .into_iter() + .conjoin(), + vir_low::Expression::implies( + guard.clone(), + variable_inverse_equalities.into_iter().conjoin(), + ), + ), + ); + // let axiom = vir_low::DomainAxiomDecl::new( + // None, + // format!( + // "inverse_function_definitional_axiom${}", + // self.inverse_function_domain.axioms.len() + // ), + // injectivity_axiom_body, + // ); + // eprintln!("axiom: {axiom}"); + // self.inverse_function_domain.axioms.push(axiom); + statements.push(vir_low::Statement::assume( + injectivity_axiom_body1, + position, + )); + // `location == e(inverse(location))` + let injectivity_axiom_body2 = vir_low::Expression::forall( + parameters.clone(), + vec![vir_low::Trigger::new(permission_mask_trigger_terms.clone())], + vir_low::Expression::implies( + permission_mask_guard.clone(), + parameter_inverse_equalities.into_iter().conjoin(), + ), + ); + statements.push(vir_low::Statement::assume( + injectivity_axiom_body2, + position, + )); + + // ``` + // assert forall parameters :: + // {inverse$qp$P$index(parameters)} + // guard_with_variables_replaced_with_inverse_functions ==> + // perm

(parameters, v_old) >= p : + // ``` + // let permission_mask_assert_statement = vir_low::Statement::assert( + // vir_low::Expression::forall( + // parameters.clone(), + // vec![vir_low::Trigger::new(permission_mask_trigger_terms.clone())], + // vir_low::Expression::implies( + // permission_mask_guard.clone(), + // operations.perm_old_greater_equals( + // self, + // parameters_as_arguments.clone(), + // &predicate.permission, + // )?, + // ), + // ), + // position, // FIXME: use position of expression.permission with proper ErrorCtxt. + // ); + let purified_guard = self.purify_snap_function_calls_in_expression( + statements, + guard, + expression_evaluation_state_label.clone(), + Vec::new(), + position, + true, + )?; + let permission_mask_assert_arguments = self.purify_snap_function_calls_in_expressions( + statements, + predicate.arguments.clone(), + expression_evaluation_state_label, + vec![purified_guard.clone()], + position, + true, + )?; + let permission_mask_assert_statement = vir_low::Statement::assert( + vir_low::Expression::implies( + purified_guard, + operations.perm_old_greater_equals( + self, + permission_mask_assert_arguments, + &predicate.permission, + )?, + ), + position, // FIXME: use position of expression.permission with proper ErrorCtxt. + ); + statements.push(permission_mask_assert_statement); + + let perm_new_value = operations.perm_old_sub( + self, + parameters_as_arguments.clone(), + &predicate.permission, + )?; + eprintln!("perm_new_value: {}", perm_new_value); + let perm_new = operations.perm_new(self, parameters_as_arguments.clone())?; + let perm_old = operations.perm_old(self, parameters_as_arguments)?; + // Compared to `encode_expression_inhale_predicate`, the setting of the new value and + // transfering the old values is merged into a single quantifer: + // ``` + // assume forall parameters :: + // {inverse$qp$P$index(parameters)} + // perm

(parameters, v_new) == ( + // guard_with_variables_replaced_with_inverse_functions ? + // perm

(parameters, v_old) + p : + // perm

(parameters, v_old) + // ) + // ``` + let permission_mask_update_statement = vir_low::Statement::assume( + vir_low::Expression::forall( + parameters, + vec![vir_low::Trigger::new(permission_mask_trigger_terms)], + vir_low::Expression::equals( + perm_new, + vir_low::Expression::conditional( + permission_mask_guard, + perm_new_value, + perm_old, + position, + ), + ), + ), + position, // FIXME: use position of expression.permission with proper ErrorCtxt. + ); + eprintln!( + "permission_mask_update_statement: {}", + permission_mask_update_statement + ); + statements.push(permission_mask_update_statement); + Ok(()) + } + + pub(super) fn encode_expression_inhale( + &mut self, + statements: &mut Vec, + expression: vir_low::Expression, + position: vir_low::Position, + expression_evaluation_state_label: Option, + ) -> SpannedEncodingResult<()> { + if expression.is_pure() { + let expression = self.purify_snap_function_calls_in_expression( + statements, + expression, + expression_evaluation_state_label, + Vec::new(), + position, + false, + )?; + statements.push(vir_low::Statement::assume(expression, position)); + } else { + match expression { + vir_low::Expression::PredicateAccessPredicate(expression) => { + match self.get_predicate_permission_mask_kind(&expression.name)? { + PredicatePermissionMaskKind::AliasedWholeBool + | PredicatePermissionMaskKind::AliasedFractionalBool => { + let operations = + PermissionMaskOperations::::new( + self, + statements, + &expression, + expression_evaluation_state_label.clone(), + position, + )?; + self.encode_expression_inhale_predicate( + statements, + &expression, + position, + expression_evaluation_state_label, + operations, + )? + } + PredicatePermissionMaskKind::AliasedFractionalBoundedPerm => { + let operations = PermissionMaskOperations::< + PermissionMaskKindAliasedFractionalBoundedPerm, + >::new( + self, + statements, + &expression, + expression_evaluation_state_label.clone(), + position, + )?; + self.encode_expression_inhale_predicate( + statements, + &expression, + position, + expression_evaluation_state_label, + operations, + )? + } + PredicatePermissionMaskKind::AliasedWholeDuplicable => { + let operations = PermissionMaskOperations::< + PermissionMaskKindAliasedDuplicableBool, + >::new( + self, + statements, + &expression, + expression_evaluation_state_label.clone(), + position, + )?; + self.encode_expression_inhale_predicate( + statements, + &expression, + position, + expression_evaluation_state_label, + operations, + )? + } + } + } + vir_low::Expression::Unfolding(_) => todo!(), + vir_low::Expression::LabelledOld(_) => todo!(), + vir_low::Expression::BinaryOp(expression) => match expression.op_kind { + vir_low::BinaryOpKind::And => { + self.encode_expression_inhale( + statements, + *expression.left, + position, + expression_evaluation_state_label.clone(), + )?; + self.encode_expression_inhale( + statements, + *expression.right, + position, + expression_evaluation_state_label, + )?; + } + vir_low::BinaryOpKind::Implies => { + let guard = self.purify_snap_function_calls_in_expression( + statements, + *expression.left, + expression_evaluation_state_label.clone(), + Vec::new(), + position, + false, + )?; + let mut body = Vec::new(); + self.encode_expression_inhale( + &mut body, + *expression.right, + position, + expression_evaluation_state_label, + )?; + statements.push(vir_low::Statement::conditional( + guard, + body, + Vec::new(), + position, + )) + } + _ => unreachable!("expression: {}", expression), + }, + vir_low::Expression::Quantifier(expression) => { + if let vir_low::Expression::BinaryOp(vir_low::BinaryOp { + op_kind: vir_low::BinaryOpKind::Implies, + left: box guard, + right: box vir_low::Expression::PredicateAccessPredicate(mut predicate), + .. + }) = *expression.body + { + self.create_quantifier_variables_remap(&expression.variables)?; + let guard = self.purify_snap_function_calls_in_expression( + statements, + guard, + expression_evaluation_state_label.clone(), + Vec::new(), + position, + false, + )?; + predicate.arguments = self.purify_snap_function_calls_in_expressions( + statements, + predicate.arguments, + expression_evaluation_state_label.clone(), + vec![guard.clone()], + position, + false, + )?; + eprintln!("guard: {guard}"); + eprintln!("body: {predicate}"); + match self.get_predicate_permission_mask_kind(&predicate.name)? { + PredicatePermissionMaskKind::AliasedWholeBool + | PredicatePermissionMaskKind::AliasedFractionalBool => { + let operations = QuantifiedPermissionMaskOperations::< + PermissionMaskKindAliasedBool, + >::new( + self, + statements, + &predicate, + expression_evaluation_state_label.clone(), + position, + )?; + self.encode_expression_inhale_quantified_predicate( + statements, + expression.variables, + guard, + &predicate, + expression.triggers, + position, + expression_evaluation_state_label, + operations, + )? + } + PredicatePermissionMaskKind::AliasedFractionalBoundedPerm => { + let operations = QuantifiedPermissionMaskOperations::< + PermissionMaskKindAliasedFractionalBoundedPerm, + >::new( + self, + statements, + &predicate, + expression_evaluation_state_label.clone(), + position, + )?; + self.encode_expression_inhale_quantified_predicate( + statements, + expression.variables, + guard, + &predicate, + expression.triggers, + position, + expression_evaluation_state_label, + operations, + )? + } + PredicatePermissionMaskKind::AliasedWholeDuplicable => { + let operations = QuantifiedPermissionMaskOperations::< + PermissionMaskKindAliasedDuplicableBool, + >::new( + self, + statements, + &predicate, + expression_evaluation_state_label.clone(), + position, + )?; + self.encode_expression_inhale_quantified_predicate( + statements, + expression.variables, + guard, + &predicate, + expression.triggers, + position, + expression_evaluation_state_label, + operations, + )? + } + } + self.bound_variable_remap_stack.pop(); + } else { + unimplemented!("expression: {:?}", expression); + } + } + vir_low::Expression::Conditional(_) => todo!(), + vir_low::Expression::FuncApp(_) => todo!(), + vir_low::Expression::DomainFuncApp(_) => todo!(), + _ => { + unimplemented!("expression: {:?}", expression); + } + } + } + Ok(()) + } + + fn encode_expression_inhale_predicate( + &mut self, + statements: &mut Vec, + predicate: &vir_low::ast::expression::PredicateAccessPredicate, + position: vir_low::Position, + // FIXME: The use of `expression_evaluation_state_label` is probably + // wrong in both QP and non-QP inhale. Shouldn't arguments be always + // purified? + expression_evaluation_state_label: Option, + operations: PermissionMaskOperations, + ) -> SpannedEncodingResult<()> + where + PermissionMaskOperations: TPermissionMaskOperations, + { + statements.push(vir_low::Statement::comment(format!( + "inhale-predicate {predicate}" + ))); + if operations.can_assume_old_permission_is_none(&predicate.permission) { + statements.push(vir_low::Statement::assume( + operations.perm_old_equal_none(), + position, // FIXME: use position of expression.permission with proper ErrorCtxt. + )); + } + let perm_new_value = operations.perm_old_add(&predicate.permission); + // assume perm

(r1, r2, v_new) == perm

(r1, r2, v_old) + p + statements.push(vir_low::Statement::assume( + vir_low::Expression::equals(operations.perm_new(), perm_new_value.clone()), + position, // FIXME: use position of expression.permission with proper ErrorCtxt. + )); + // assume forall arg1: Ref, arg2: Ref :: + // {perm

(arg1, arg2, v_new)} + // !(r1 == arg1 && r2 == arg2) ==> + // perm

(arg1, arg2, v_new) == perm

(arg1, arg2, v_old) + self.encode_perm_unchanged_quantifier( + statements, + predicate, + operations.old_permission_mask_version(), + operations.new_permission_mask_version(), + position, + expression_evaluation_state_label, + perm_new_value, + )?; + Ok(()) + } + + fn is_predicate_already_injective( + &self, + quantified_variables: &[vir_low::VariableDecl], + predicate_arguments: &[vir_low::Expression], + ) -> SpannedEncodingResult { + assert_eq!( + quantified_variables.len(), + 1, + "unimplemented: {}", + vir_crate::common::display::cjoin(quantified_variables) + ); + let variable = quantified_variables.first().unwrap(); + for argument in predicate_arguments { + match argument { + vir_low::Expression::Local(local) if &local.variable == variable => { + // The quantified variable is used directly. This means + // that the expression is already bijective, so we do + // not need to generate the inverse function. + } + _ => { + if argument.contains_variable(variable) { + // The argument contains the quantified variable, so + // we need to generate the inverse function. + return Ok(false); + } + } + } + } + Ok(true) + } + + fn encode_expression_inhale_quantified_predicate<'a, Kind: PermissionMaskKind>( + &mut self, + statements: &mut Vec, + variables: Vec, + guard: vir_low::Expression, + predicate: &vir_low::ast::expression::PredicateAccessPredicate, + mut triggers: Vec, + position: vir_low::Position, + // FIXME: The use of `expression_evaluation_state_label` is probably + // wrong in both QP and non-QP inhale. Shouldn't arguments be always + // purified? + _expression_evaluation_state_label: Option, + operations: QuantifiedPermissionMaskOperations<'a, Kind>, + ) -> SpannedEncodingResult<()> + where + QuantifiedPermissionMaskOperations<'a, Kind>: TQuantifiedPermissionMaskOperations, + { + // FIXME: Code duplication with encode_function_precondition_assert_rec_quantified_predicate + statements.push(vir_low::Statement::comment(format!( + "inhale-qp-predicate {guard} ==> {predicate}" + ))); + eprintln!( + "variables: {}", + vir_crate::common::display::cjoin(&variables) + ); + eprintln!("triggers: {}", vir_crate::common::display::cjoin(&triggers)); + eprintln!("guard: {}", guard); + eprintln!("predicate: {}", predicate); + // let guard = self.purify_snap_function_calls_in_expression( + // statements, + // guard, + // expression_evaluation_state_label, + // position, + // true, + // )?; + // Desugar predicates in the trigger. + for trigger in &mut triggers { + for term in &mut trigger.terms { + if !term.is_heap_independent() { + let vir_low::Expression::PredicateAccessPredicate(trigger_predicate) = term else { + unreachable!("expected a predicate as a trigger") + }; + assert_eq!(trigger_predicate.name, predicate.name, "unimplemented"); + let trigger_perm = operations + .perm_new(self, std::mem::take(&mut trigger_predicate.arguments))?; + *term = trigger_perm; + } + } + } + if operations.can_assume_old_permission_is_none(&predicate.permission) { + statements.push(vir_low::Statement::assume( + vir_low::Expression::forall( + variables.clone(), + triggers.clone(), + vir_low::Expression::implies( + guard.clone(), + operations.perm_old_equal_none(self, predicate.arguments.clone())?, + ), + ), + position, // FIXME: use position of expression.permission with proper ErrorCtxt. + )); + } + if self.is_predicate_already_injective(&variables, &predicate.arguments)? + && config::custom_heap_encoding_omit_injective() + { + unimplemented!("injective predicate"); + } + // Generate inverse functions for the variables. + // ``` + // forall index: Int :: + // 0 <= index && index < size ==> + // inverse$qp$P$index(offset(addr, index), Size$Pair()) == index + // ``` + // Also construct the necessary parts for the permission mask update quantifier. + let parameters: Vec<_> = predicate + .arguments + .iter() + .enumerate() + .map(|(index, argument)| { + vir_low::VariableDecl::new(format!("_{index}"), argument.get_type().clone()) + }) + .collect(); + let parameters_as_arguments: Vec<_> = parameters + .clone() + .into_iter() + .map(|parameter| parameter.into()) + .collect(); + let mut permission_mask_trigger_terms = Vec::new(); + let mut permission_mask_guard = guard.clone(); + let mut inverse_function_trigger_function_calls = Vec::new(); + let mut variable_inverse_equalities: Vec = Vec::new(); + let mut parameter_inverse_equalities: Vec = Vec::new(); + for variable in &variables { + let inverse_function_name = format!( + "inverse$qp${}${}${}", + predicate.name, + variable.name, + self.inverse_function_domain.functions.len() + ); + { + // Declare the inverse function. + let inverse_function = vir_low::DomainFunctionDecl::new( + inverse_function_name.clone(), + false, + parameters.clone(), + variable.ty.clone(), + ); + let permission_mask_inverse_function_call = + vir_low::Expression::domain_function_call( + self.inverse_function_domain.name.clone(), + inverse_function_name.clone(), + parameters_as_arguments.clone(), + variable.ty.clone(), + ); + permission_mask_guard = permission_mask_guard.replace_place( + &variable.clone().into(), + &permission_mask_inverse_function_call, + ); + permission_mask_trigger_terms.push(permission_mask_inverse_function_call); + self.inverse_function_domain + .functions + .push(inverse_function); + // Declare the inverse function definitional equality. + let inverse_function_call = vir_low::Expression::domain_function_call( + self.inverse_function_domain.name.clone(), + inverse_function_name.clone(), + parameters_as_arguments.clone(), + variable.ty.clone(), + ); + for (parameter, argument) in parameters_as_arguments + .iter() + .zip(predicate.arguments.iter()) + { + let argument = argument + .clone() + .replace_place(&variable.clone().into(), &inverse_function_call); + parameter_inverse_equalities + .push(vir_low::Expression::equals(parameter.clone(), argument)); + } + } + { + // Declare the inverse function trigger function. + let inverse_function_trigger_function = vir_low::DomainFunctionDecl::new( + format!("{}$trigger", inverse_function_name), + false, + vec![vir_low::VariableDecl::new("_0", variable.ty.clone())], + vir_low::Type::Bool, + ); + // Declare the inverse function definitional equality. + let inverse_function_call = vir_low::Expression::domain_function_call( + self.inverse_function_domain.name.clone(), + inverse_function_name, + predicate.arguments.clone(), + variable.ty.clone(), + ); + let inverse_function_trigger_function_call = + vir_low::Expression::domain_function_call( + self.inverse_function_domain.name.clone(), + inverse_function_trigger_function.name.clone(), + vec![inverse_function_call.clone()], + vir_low::Type::Bool, + ); + eprintln!("inverse_function: {}", inverse_function_call); + inverse_function_trigger_function_calls + .push(inverse_function_trigger_function_call); + variable_inverse_equalities.push(vir_low::Expression::equals( + variable.clone().into(), + inverse_function_call, + )); + self.inverse_function_domain + .functions + .push(inverse_function_trigger_function); + } + } + // `variable == inverse(e(variable))` + let injectivity_axiom_body1 = vir_low::Expression::forall( + variables, + triggers.clone(), + vir_low::Expression::and( + inverse_function_trigger_function_calls + .into_iter() + .conjoin(), + vir_low::Expression::implies( + guard, + variable_inverse_equalities.into_iter().conjoin(), + ), + ), + ); + // let axiom = vir_low::DomainAxiomDecl::new( + // None, + // format!( + // "inverse_function_definitional_axiom${}", + // self.inverse_function_domain.axioms.len() + // ), + // axiom_body, + // ); + // eprintln!("axiom: {axiom}"); + // self.inverse_function_domain.axioms.push(axiom); + statements.push(vir_low::Statement::assume( + injectivity_axiom_body1, + position, + )); + // `location == e(inverse(location))` + let injectivity_axiom_body2 = vir_low::Expression::forall( + parameters.clone(), + vec![vir_low::Trigger::new(permission_mask_trigger_terms.clone())], + vir_low::Expression::implies( + permission_mask_guard.clone(), + parameter_inverse_equalities.into_iter().conjoin(), + ), + ); + statements.push(vir_low::Statement::assume( + injectivity_axiom_body2, + position, + )); + + let perm_new_value = operations.perm_old_add( + self, + parameters_as_arguments.clone(), + &predicate.permission, + )?; + eprintln!("perm_new_value: {}", perm_new_value); + let perm_new = operations.perm_new(self, parameters_as_arguments.clone())?; + let perm_old = operations.perm_old(self, parameters_as_arguments)?; + // Compared to `encode_expression_inhale_predicate`, the setting of the new value and + // transfering the old values is merged into a single quantifer: + // ``` + // assume forall parameters :: + // {inverse$qp$P$index(parameters)} + // perm

(parameters, v_new) == ( + // guard_with_variables_replaced_with_inverse_functions ? + // perm

(parameters, v_old) + p : + // perm

(parameters, v_old) + // ) + // ``` + let permission_mask_update_statement = vir_low::Statement::assume( + vir_low::Expression::forall( + parameters, + vec![vir_low::Trigger::new(permission_mask_trigger_terms)], + vir_low::Expression::equals( + perm_new, + vir_low::Expression::conditional( + permission_mask_guard, + perm_new_value, + perm_old, + position, + ), + ), + ), + position, // FIXME: use position of expression.permission with proper ErrorCtxt. + ); + eprintln!( + "permission_mask_update_statement: {}", + permission_mask_update_statement + ); + statements.push(permission_mask_update_statement); + Ok(()) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/custom_heap_encoding/heap_encoder/heap/mod.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/custom_heap_encoding/heap_encoder/heap/mod.rs new file mode 100644 index 00000000000..94d4635a62e --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/custom_heap_encoding/heap_encoder/heap/mod.rs @@ -0,0 +1,310 @@ +use super::HeapEncoder; +use crate::encoder::errors::{SpannedEncodingError, SpannedEncodingResult}; +use rustc_hash::FxHashSet; +use vir_crate::{ + common::expression::{BinaryOperationHelpers, ExpressionIterator, QuantifierHelpers}, + low::{self as vir_low, expression::visitors::ExpressionFallibleFolder}, +}; + +impl<'p, 'v: 'p, 'tcx: 'v> HeapEncoder<'p, 'v, 'tcx> { + fn heap_version_type(&self) -> vir_low::Type { + vir_low::Type::domain("HeapVersion".to_string()) + } + + pub(super) fn heap_function_name(&self, predicate_name: &str) -> String { + format!("heap${predicate_name}") + } + + pub(super) fn heap_range_function_name(&self, predicate_name: &str) -> String { + format!("heap_range${predicate_name}") + } + + pub(super) fn heap_call( + &mut self, + predicate: &vir_low::ast::expression::PredicateAccessPredicate, + mut arguments: Vec, + heap_version: vir_low::Expression, + ) -> SpannedEncodingResult> { + let call = + if let Some(snapshot_type) = self.get_snapshot_type_for_predicate(&predicate.name) { + let heap_function_name = self.heap_function_name(&predicate.name); + arguments.push(heap_version); + Some(vir_low::Expression::domain_function_call( + "HeapFunctions", + heap_function_name, + arguments, + snapshot_type, + )) + } else { + None + }; + Ok(call) + } + + pub(super) fn heap_call_for_predicate_def( + &mut self, + predicate: &vir_low::ast::expression::PredicateAccessPredicate, + heap_version: vir_low::Expression, + ) -> SpannedEncodingResult> { + let arguments = self.get_predicate_parameters_as_arguments(&predicate.name)?; + self.heap_call(predicate, arguments, heap_version) + } + + pub(super) fn encode_heap_unchanged_quantifier( + &mut self, + statements: &mut Vec, + predicate: &vir_low::ast::expression::PredicateAccessPredicate, + new_permission_mask: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + let heap_version_old = self.get_current_heap_version_for(&predicate.name)?; + if let Some(heap_old) = self.heap_call_for_predicate_def(predicate, heap_version_old)? { + let heap_version_new = self.get_new_heap_version_for(&predicate.name, position)?; + let heap_new = self + .heap_call_for_predicate_def(predicate, heap_version_new)? + .unwrap(); + let predicate_parameters = self.get_predicate_parameters(&predicate.name).to_owned(); + let triggers = vec![vir_low::Trigger::new(vec![heap_new.clone()])]; + let guard = self + .positive_permission_mask_call_for_predicate_def(predicate, new_permission_mask)?; + let body = vir_low::Expression::implies( + guard, + vir_low::Expression::equals(heap_old, heap_new), + ); + statements.push(vir_low::Statement::assume( + vir_low::Expression::forall(predicate_parameters, triggers, body), + position, + )); + } + Ok(()) + } + + pub(super) fn get_current_heap_version_for( + &mut self, + predicate_name: &str, + ) -> SpannedEncodingResult { + let variable_name = self.heap_names.get(predicate_name).unwrap(); + let version = self.ssa_state.current_variable_version(variable_name); + let ty = self.heap_version_type(); + Ok(self + .new_variables + .create_variable(variable_name, ty, version)? + .into()) + } + + fn get_new_heap_version_for( + &mut self, + predicate_name: &str, + position: vir_low::Position, + ) -> SpannedEncodingResult { + let variable_name = self.heap_names.get(predicate_name).unwrap(); + let ty = self.heap_version_type(); + let version = self + .ssa_state + .new_variable_version(variable_name, &ty, position); + Ok(self + .new_variables + .create_variable(variable_name, ty, version)? + .into()) + } + + pub(super) fn get_heap_version_at_label( + &mut self, + predicate_name: &str, + label: &str, + ) -> SpannedEncodingResult { + let variable_name = self.heap_names.get(predicate_name).unwrap(); + let version = self + .ssa_state + .variable_version_at_label(variable_name, label); + let ty = self.heap_version_type(); + Ok(self + .new_variables + .create_variable(variable_name, ty, version)? + .into()) + } + + pub(super) fn generate_heap_domains( + &self, + domains: &mut Vec, + ) -> SpannedEncodingResult<()> { + let heap_version_domain = + vir_low::DomainDecl::new("HeapVersion", Vec::new(), Vec::new(), Vec::new()); + domains.push(heap_version_domain); + let mut functions = Vec::new(); + let mut axioms = Vec::new(); + let mut already_encoded_ensures_validity_functions = FxHashSet::default(); + for predicate in self.predicates.iter_decls() { + if let Some(snapshot_type) = self.get_snapshot_type_for_predicate(&predicate.name) { + let mut parameters = predicate.parameters.clone(); + parameters.push(vir_low::VariableDecl::new( + "version", + self.heap_version_type(), + )); + functions.push(vir_low::DomainFunctionDecl::new( + self.heap_function_name(&predicate.name), + false, + parameters, + snapshot_type.clone(), + )); + if predicate.kind == vir_low::PredicateKind::Owned + && !already_encoded_ensures_validity_functions.contains(&snapshot_type) + { + already_encoded_ensures_validity_functions.insert(snapshot_type.clone()); + // Ensures validity function definition. + let vir_low::Type::Domain(snapshot_domain) = &snapshot_type else { + unreachable!("snapshot_type: {snapshot_type}") + }; + // FIXME: Do not rely on strings. Use OwnedPredicateInfo instead. + let validity_function_name = format!("valid${}", snapshot_domain.name); + let ensures_validity_function_name = + format!("ensures${}", validity_function_name); + let parameter = vir_low::VariableDecl::new("snapshot", snapshot_type.clone()); + functions.push(vir_low::DomainFunctionDecl::new( + ensures_validity_function_name.clone(), + false, + vec![parameter.clone()], + snapshot_type.clone(), + )); + let function_call = vir_low::Expression::domain_function_call( + "HeapFunctions", + ensures_validity_function_name.clone(), + vec![parameter.clone().into()], + snapshot_type.clone(), + ); + let validity_function_call = vir_low::Expression::domain_function_call( + snapshot_domain.name.clone(), + validity_function_name.clone(), + vec![parameter.clone().into()], + vir_low::Type::Bool, + ); + let axiom_body = vir_low::Expression::forall( + vec![parameter.clone()], + vec![vir_low::Trigger::new(vec![function_call.clone()])], + vir_low::Expression::and( + vir_low::Expression::equals(function_call, parameter.clone().into()), + validity_function_call, + ), + ); + let definitional_axiom = vir_low::DomainAxiomDecl::new( + None, + format!("{}$definitional_axiom", ensures_validity_function_name), + axiom_body, + ); + axioms.push(definitional_axiom); + } + } + } + for (range_function_name, predicate) in self.predicates.iter_range_functions() { + if let Some(snapshot_type) = self.get_snapshot_type_for_predicate(&predicate.name) { + if let Some(function_decl) = self.functions.get(range_function_name) { + eprintln!("range_function_name: {}", range_function_name); + eprintln!("predicate: {}", predicate); + eprintln!("snapshot_type: {}", snapshot_type); + eprintln!("function_decl: {}", function_decl); + let function_name = self.heap_range_function_name(&predicate.name); + eprintln!("function_name: {}", function_name); + let mut parameters = function_decl.parameters.clone(); + let heap_version = + vir_low::VariableDecl::new("version", self.heap_version_type()); + parameters.push(heap_version.clone()); + functions.push(vir_low::DomainFunctionDecl::new( + function_name.clone(), + false, + parameters.clone(), + function_decl.return_type.clone(), + )); + let arguments = parameters + .iter() + .map(|parameter| parameter.clone().into()) + .collect(); + let function_call = vir_low::Expression::domain_function_call( + "HeapFunctions", + function_name.clone(), + arguments, + function_decl.return_type.clone(), + ); + struct Rewriter<'a, 'p, 'v, 'tcx> { + function_call: &'a vir_low::Expression, + heap_encoder: &'a HeapEncoder<'p, 'v, 'tcx>, + heap_version: &'a vir_low::VariableDecl, + } + impl<'a, 'p, 'v, 'tcx> ExpressionFallibleFolder for Rewriter<'a, 'p, 'v, 'tcx> { + type Error = SpannedEncodingError; + fn fallible_fold_local_enum( + &mut self, + local: vir_low::Local, + ) -> SpannedEncodingResult { + let local = if local.variable.is_result_variable() { + self.function_call.clone() + } else { + vir_low::Expression::Local(local) + }; + Ok(local) + } + fn fallible_fold_func_app_enum( + &mut self, + func_app: vir_low::FuncApp, + ) -> SpannedEncodingResult { + let func_app = self.fallible_fold_func_app(func_app)?; + let function = self.heap_encoder.functions[&func_app.function_name]; + match function.kind { + vir_low::FunctionKind::Snap => { + let predicate_name = self + .heap_encoder + .get_predicate_name_for_function(&func_app.function_name)?; + let mut arguments = func_app.arguments; + arguments.push(self.heap_version.clone().into()); + let heap_function_name = + self.heap_encoder.heap_function_name(&predicate_name); + Ok(vir_low::Expression::domain_function_call( + "HeapFunctions", + heap_function_name, + arguments, + func_app.return_type, + )) + } + _ => unreachable!("unexpected kind: {}", function.kind), + } + } + fn fallible_fold_trigger( + &mut self, + mut trigger: vir_low::Trigger, + ) -> SpannedEncodingResult { + for term in std::mem::take(&mut trigger.terms) { + let term = self.fallible_fold_expression(term)?; + trigger.terms.push(term); + } + Ok(trigger) + } + } + let mut rewriter = Rewriter { + function_call: &function_call, + heap_encoder: self, + heap_version: &heap_version, + }; + let mut conjuncts = Vec::new(); + for postcondition in &function_decl.posts { + let postcondition = + rewriter.fallible_fold_expression(postcondition.clone())?; + conjuncts.push(postcondition); + } + let axiom_body = vir_low::Expression::forall( + parameters, + vec![vir_low::Trigger::new(vec![function_call])], + conjuncts.into_iter().conjoin(), + ); + let definitional_axiom = vir_low::DomainAxiomDecl::new( + None, + format!("{}$definitional_axiom", function_name), + axiom_body, + ); + axioms.push(definitional_axiom); + } + } + } + let heap_domain = vir_low::DomainDecl::new("HeapFunctions", functions, axioms, Vec::new()); + domains.push(heap_domain); + Ok(()) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/custom_heap_encoding/heap_encoder/mod.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/custom_heap_encoding/heap_encoder/mod.rs new file mode 100644 index 00000000000..85996620f28 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/custom_heap_encoding/heap_encoder/mod.rs @@ -0,0 +1,156 @@ +mod statements; +mod pure_expressions; +mod heap; +mod effects; +mod predicates; +mod permission_mask; +mod bound_variable_stack; + +use self::{bound_variable_stack::BoundVariableRemapStack, predicates::Predicates}; +use super::variable_declarations::VariableDeclarations; +use crate::encoder::{ + errors::SpannedEncodingResult, middle::core_proof::predicates::OwnedPredicateInfo, Encoder, +}; +use rustc_hash::{FxHashMap, FxHashSet}; +use std::collections::BTreeMap; +use vir_crate::low::{self as vir_low}; + +pub(super) struct HeapEncoder<'p, 'v: 'p, 'tcx: 'v> { + encoder: &'p mut Encoder<'v, 'tcx>, + new_variables: VariableDeclarations, + predicates: Predicates<'p>, + functions: FxHashMap, + ssa_state: vir_low::ssa::SSAState, + permission_mask_names: FxHashMap, + heap_names: FxHashMap, + /// A counter used for generating fresh labels. + fresh_label_counter: u64, + bound_variable_remap_stack: BoundVariableRemapStack, + inverse_function_domain: vir_low::DomainDecl, +} + +impl<'p, 'v: 'p, 'tcx: 'v> HeapEncoder<'p, 'v, 'tcx> { + pub(super) fn new( + encoder: &'p mut Encoder<'v, 'tcx>, + predicates: &'p [vir_low::PredicateDecl], + predicate_info: BTreeMap, + functions: &'p [vir_low::FunctionDecl], + ) -> Self { + Self { + encoder, + new_variables: Default::default(), + permission_mask_names: predicates + .iter() + .map(|predicate| { + let mask_name = format!("perm${}", predicate.name); + (predicate.name.clone(), mask_name) + }) + .collect(), + heap_names: predicates + .iter() + .map(|predicate| { + let heap_name = format!("heap${}", predicate.name); + (predicate.name.clone(), heap_name) + }) + .collect(), + predicates: Predicates::new(predicates, predicate_info), + functions: functions + .iter() + .map(|function| (function.name.clone(), function)) + .collect(), + ssa_state: Default::default(), + fresh_label_counter: 0, + bound_variable_remap_stack: Default::default(), + inverse_function_domain: vir_low::DomainDecl::new( + "QPInverseFunctions", + Vec::new(), + Vec::new(), + Vec::new(), + ), + } + } + + pub(super) fn reset(&mut self) { + self.new_variables = Default::default(); + self.ssa_state = Default::default(); + self.fresh_label_counter = 0; + } + + pub(super) fn encode_statement( + &mut self, + statements: &mut Vec, + statement: vir_low::Statement, + ) -> SpannedEncodingResult<()> { + self.encode_statement_internal(statements, statement) + } + + pub(super) fn prepare_new_current_block( + &mut self, + label: &vir_low::Label, + predecessors: &BTreeMap>, + basic_block_edges: &mut BTreeMap< + vir_low::Label, + BTreeMap>, + >, + ) -> SpannedEncodingResult<()> { + self.ssa_state + .prepare_new_current_block(label, predecessors, basic_block_edges); + Ok(()) + } + + pub(super) fn finish_current_block( + &mut self, + label: vir_low::Label, + ) -> SpannedEncodingResult<()> { + self.ssa_state.finish_current_block(label); + Ok(()) + } + + pub(super) fn generate_init_permissions_to_zero( + &mut self, + position: vir_low::Position, + ) -> SpannedEncodingResult> { + self.generate_init_permissions_to_zero_internal(position) + } + + pub(super) fn generate_necessary_domains( + &self, + ) -> SpannedEncodingResult> { + let mut domains = Vec::new(); + self.generate_permission_mask_domains(&mut domains)?; + self.generate_heap_domains(&mut domains)?; + domains.push(self.inverse_function_domain.clone()); + Ok(domains) + } + + pub(super) fn create_variable( + &mut self, + variable_name: &str, + ty: vir_low::Type, + version: u64, + ) -> SpannedEncodingResult { + self.new_variables + .create_variable(variable_name, ty, version) + } + + pub(super) fn fresh_variable( + &mut self, + variable: &vir_low::VariableDecl, + ) -> SpannedEncodingResult { + self.new_variables + .fresh_variable(&variable.name, &variable.ty) + } + + pub(super) fn take_variables(&mut self) -> FxHashSet { + self.new_variables.take_variables() + } + + pub(super) fn encoder(&mut self) -> &mut Encoder<'v, 'tcx> { + self.encoder + } + + fn fresh_label(&mut self) -> String { + self.fresh_label_counter += 1; + format!("heap_label${}", self.fresh_label_counter) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/custom_heap_encoding/heap_encoder/permission_mask/mod.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/custom_heap_encoding/heap_encoder/permission_mask/mod.rs new file mode 100644 index 00000000000..0ca871127ba --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/custom_heap_encoding/heap_encoder/permission_mask/mod.rs @@ -0,0 +1,325 @@ +mod operations; + +use super::HeapEncoder; +use crate::encoder::errors::SpannedEncodingResult; +use vir_crate::{ + common::expression::{BinaryOperationHelpers, ExpressionIterator, QuantifierHelpers}, + low::{self as vir_low}, +}; + +pub(super) use self::operations::{ + PermissionMaskKind, PermissionMaskKindAliasedBool, PermissionMaskKindAliasedDuplicableBool, + PermissionMaskKindAliasedFractionalBoundedPerm, PermissionMaskOperations, + QuantifiedPermissionMaskOperations, TPermissionMaskOperations, + TQuantifiedPermissionMaskOperations, +}; + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub(super) enum PredicatePermissionMaskKind { + /// The permission amounts can be either full or none. + AliasedWholeBool, + /// The permission amounts can be fractional, but we are always guaranteed + /// to operate on the same amount. Therefore, we do not need to perform + /// arithmetic operations on permissions and can use a boolean permission + /// mask with a third parameter that specifies the permission amount that we + /// are currently tracking. + AliasedFractionalBool, + /// The permission amounts can be fractional and we need to perform + /// arithmetic operations on them. However, the permission amount is bounded + /// by `write` and, therefore, when inhaling `write` we can assume that the + /// current amount is `none`. + AliasedFractionalBoundedPerm, + /// The permission is duplicable. + AliasedWholeDuplicable, +} + +impl<'p, 'v: 'p, 'tcx: 'v> HeapEncoder<'p, 'v, 'tcx> { + fn perm_version_type(&self) -> vir_low::Type { + vir_low::Type::domain("PermVersion".to_string()) + } + + fn mask_function_return_type(&self, kind: PredicatePermissionMaskKind) -> vir_low::Type { + match kind { + PredicatePermissionMaskKind::AliasedWholeBool + | PredicatePermissionMaskKind::AliasedFractionalBool + | PredicatePermissionMaskKind::AliasedWholeDuplicable => vir_low::Type::Bool, + PredicatePermissionMaskKind::AliasedFractionalBoundedPerm => vir_low::Type::Perm, + } + } + + fn no_permission(&self, kind: PredicatePermissionMaskKind) -> vir_low::Expression { + match kind { + PredicatePermissionMaskKind::AliasedWholeBool + | PredicatePermissionMaskKind::AliasedFractionalBool + | PredicatePermissionMaskKind::AliasedWholeDuplicable => false.into(), + PredicatePermissionMaskKind::AliasedFractionalBoundedPerm => { + vir_low::Expression::no_permission() + } + } + } + + fn permission_amount_parameter( + &self, + kind: PredicatePermissionMaskKind, + ) -> Option { + match kind { + PredicatePermissionMaskKind::AliasedFractionalBool => Some(vir_low::VariableDecl::new( + "permission_amount", + vir_low::Type::Perm, + )), + PredicatePermissionMaskKind::AliasedWholeBool + | PredicatePermissionMaskKind::AliasedWholeDuplicable + | PredicatePermissionMaskKind::AliasedFractionalBoundedPerm => None, + } + } + + fn permission_mask_function_name(&self, predicate_name: &str) -> String { + format!("perm${predicate_name}") + } + + pub(super) fn get_current_permission_mask_for( + &mut self, + predicate_name: &str, + ) -> SpannedEncodingResult { + let variable_name = self.permission_mask_names.get(predicate_name).unwrap(); + let version = self.ssa_state.current_variable_version(variable_name); + let ty = self.perm_version_type(); + Ok(self + .new_variables + .create_variable(variable_name, ty, version)? + .into()) + } + + pub(super) fn get_new_permission_mask_for( + &mut self, + predicate_name: &str, + position: vir_low::Position, + ) -> SpannedEncodingResult { + let variable_name = self.permission_mask_names.get(predicate_name).unwrap(); + let ty = self.perm_version_type(); + let version = self + .ssa_state + .new_variable_version(variable_name, &ty, position); + Ok(self + .new_variables + .create_variable(variable_name, ty, version)? + .into()) + } + + pub(super) fn permission_mask_call( + &mut self, + predicate: &vir_low::ast::expression::PredicateAccessPredicate, + mut arguments: Vec, + permission_mask_version: vir_low::Expression, + ) -> SpannedEncodingResult { + let perm_function_name = self.permission_mask_function_name(&predicate.name); + arguments.push(permission_mask_version); + let kind = self.get_predicate_permission_mask_kind(&predicate.name)?; + if kind == PredicatePermissionMaskKind::AliasedFractionalBool { + arguments.push((*predicate.permission).clone()); + } + let return_type = self.mask_function_return_type(kind); + Ok(vir_low::Expression::domain_function_call( + "PermFunctions", + perm_function_name, + arguments, + return_type, + )) + } + + pub(super) fn permission_mask_call_for_predicate_use( + &mut self, + statements: &mut Vec, + predicate: &vir_low::ast::expression::PredicateAccessPredicate, + permission_mask: vir_low::Expression, + expression_evaluation_state_label: Option, + position: vir_low::Position, + ) -> SpannedEncodingResult { + let arguments = self.purify_predicate_arguments( + statements, + predicate, + expression_evaluation_state_label, + position, + )?; + self.permission_mask_call(predicate, arguments, permission_mask) + } + + pub(super) fn permission_mask_call_for_predicate_def( + &mut self, + predicate: &vir_low::ast::expression::PredicateAccessPredicate, + permission_mask: vir_low::Expression, + ) -> SpannedEncodingResult { + let arguments = self.get_predicate_parameters_as_arguments(&predicate.name)?; + self.permission_mask_call(predicate, arguments, permission_mask) + } + + pub(super) fn positive_permission_mask_call_for_predicate_def( + &mut self, + predicate: &vir_low::ast::expression::PredicateAccessPredicate, + permission_mask: vir_low::Expression, + ) -> SpannedEncodingResult { + let perm_call = self.permission_mask_call_for_predicate_def(predicate, permission_mask)?; + let kind = self.get_predicate_permission_mask_kind(&predicate.name)?; + let positivity_check = match kind { + PredicatePermissionMaskKind::AliasedWholeBool + | PredicatePermissionMaskKind::AliasedFractionalBool => perm_call, + PredicatePermissionMaskKind::AliasedFractionalBoundedPerm => { + vir_low::Expression::greater_than(perm_call, vir_low::Expression::no_permission()) + } + PredicatePermissionMaskKind::AliasedWholeDuplicable => unimplemented!(), + }; + Ok(positivity_check) + } + + pub(super) fn encode_perm_unchanged_quantifier( + &mut self, + statements: &mut Vec, + predicate: &vir_low::ast::expression::PredicateAccessPredicate, + old_permission_mask_version: vir_low::Expression, + new_permission_mask_version: vir_low::Expression, + position: vir_low::Position, + expression_evaluation_state_label: Option, + perm_new_value: vir_low::Expression, + ) -> SpannedEncodingResult<()> { + let perm_new = + self.permission_mask_call_for_predicate_def(predicate, new_permission_mask_version)?; + let perm_old = + self.permission_mask_call_for_predicate_def(predicate, old_permission_mask_version)?; + let predicate_parameters = self.get_predicate_parameters(&predicate.name).to_owned(); + let predicate_arguments = self.get_predicate_parameters_as_arguments(&predicate.name)?; + let arguments = self.purify_predicate_arguments( + statements, + predicate, + expression_evaluation_state_label, + position, + )?; + let triggers = vec![vir_low::Trigger::new(vec![perm_new.clone()])]; + let guard = predicate_arguments + .into_iter() + .zip(arguments) + .map(|(parameter, argument)| vir_low::Expression::equals(parameter, argument)) + .conjoin(); + let body = vir_low::Expression::equals( + perm_new, + vir_low::Expression::conditional_no_pos(guard, perm_new_value, perm_old), + ); + statements.push(vir_low::Statement::assume( + vir_low::Expression::forall(predicate_parameters, triggers, body), + position, + )); + Ok(()) + } + + pub(super) fn generate_permission_mask_domains( + &self, + domains: &mut Vec, + ) -> SpannedEncodingResult<()> { + let perm_version_domain = + vir_low::DomainDecl::new("PermVersion", Vec::new(), Vec::new(), Vec::new()); + domains.push(perm_version_domain); + let mut functions = Vec::new(); + let mut axioms = Vec::new(); + for predicate in self.predicates.iter_decls() { + let mut parameters = predicate.parameters.clone(); + parameters.push(vir_low::VariableDecl::new( + "version", + self.perm_version_type(), + )); + let function_name = self.permission_mask_function_name(&predicate.name); + let kind = self.get_predicate_permission_mask_kind(&predicate.name)?; + parameters.extend(self.permission_amount_parameter(kind)); + let return_type = self.mask_function_return_type(kind); + let function = vir_low::DomainFunctionDecl::new( + function_name.clone(), + false, + parameters.clone(), + return_type, + ); + functions.push(function); + if matches!( + kind, + PredicatePermissionMaskKind::AliasedFractionalBoundedPerm + ) { + let function_call = vir_low::Expression::domain_func_app_no_pos( + "PermFunctions".to_string(), + function_name.clone(), + parameters + .clone() + .into_iter() + .map(|parameter| parameter.into()) + .collect(), + parameters.clone(), + vir_low::Type::Perm, + ); + use vir_low::macros::*; + let body = vir_low::Expression::forall( + parameters, + vec![vir_low::Trigger::new(vec![function_call.clone()])], + expr! { + ([vir_low::Expression::no_permission()] <= [function_call.clone()]) && + ([function_call] <= [vir_low::Expression::full_permission()]) + }, + ); + let axiom = + vir_low::DomainAxiomDecl::new(None, format!("{function_name}$bounds"), body); + axioms.push(axiom); + } + } + let perm_domain = vir_low::DomainDecl::new("PermFunctions", functions, axioms, Vec::new()); + domains.push(perm_domain); + Ok(()) + } + + pub(super) fn generate_init_permissions_to_zero_internal( + &mut self, + position: vir_low::Position, + ) -> SpannedEncodingResult> { + assert!(!position.is_default()); + let mut statements = Vec::new(); + for predicate in self.predicates.iter_decls() { + let initial_permission_mask_name = + self.permission_mask_names.get(&predicate.name).unwrap(); + let initial_permission_mask_version = self + .ssa_state + .initial_variable_version(initial_permission_mask_name); + let initial_permission_mask_ty = self.perm_version_type(); + let initial_permission_mask: vir_low::Expression = self + .new_variables + .create_variable( + initial_permission_mask_name, + initial_permission_mask_ty, + initial_permission_mask_version, + )? + .into(); + let kind = self.get_predicate_permission_mask_kind(&predicate.name)?; + let mut arguments: Vec<_> = predicate + .parameters + .iter() + .map(|parameter| parameter.clone().into()) + .collect(); + arguments.push(initial_permission_mask.clone()); + arguments.extend( + self.permission_amount_parameter(kind) + .map(|parameter| parameter.into()), + ); + let perm_function_name = self.permission_mask_function_name(&predicate.name); + let return_type = self.mask_function_return_type(kind); + let perm = vir_low::Expression::domain_function_call( + "PermFunctions", + perm_function_name.clone(), + arguments, + return_type, + ); + let no_permission = self.no_permission(kind); + let triggers = vec![vir_low::Trigger::new(vec![perm.clone()])]; + let body = vir_low::Expression::equals(perm, no_permission); + let mut parameters = predicate.parameters.clone(); + parameters.extend(self.permission_amount_parameter(kind)); + statements.push(vir_low::Statement::assume( + vir_low::Expression::forall(parameters, triggers, body), + position, + )); + } + Ok(statements) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/custom_heap_encoding/heap_encoder/permission_mask/operations.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/custom_heap_encoding/heap_encoder/permission_mask/operations.rs new file mode 100644 index 00000000000..8aa0e9fcb0a --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/custom_heap_encoding/heap_encoder/permission_mask/operations.rs @@ -0,0 +1,500 @@ +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::transformations::custom_heap_encoding::heap_encoder::HeapEncoder, +}; +use vir_crate::{ + common::expression::BinaryOperationHelpers, + low::{self as vir_low}, +}; + +pub(in super::super) trait PermissionMaskKind {} +pub(in super::super) struct PermissionMaskKindAliasedFractionalBoundedPerm {} +impl PermissionMaskKind for PermissionMaskKindAliasedFractionalBoundedPerm {} +pub(in super::super) struct PermissionMaskKindAliasedBool {} +impl PermissionMaskKind for PermissionMaskKindAliasedBool {} +pub(in super::super) struct PermissionMaskKindAliasedDuplicableBool {} +impl PermissionMaskKind for PermissionMaskKindAliasedDuplicableBool {} + +pub(in super::super) struct PermissionMaskOperations { + _kind: std::marker::PhantomData, + old_permission_mask_version: vir_low::Expression, + new_permission_mask_version: vir_low::Expression, + perm_old: vir_low::Expression, + perm_new: vir_low::Expression, +} + +pub(in super::super) struct QuantifiedPermissionMaskOperations<'a, Kind: PermissionMaskKind> { + _kind: std::marker::PhantomData, + old_permission_mask_version: vir_low::Expression, + new_permission_mask_version: vir_low::Expression, + predicate: &'a vir_low::ast::expression::PredicateAccessPredicate, +} + +impl PermissionMaskOperations { + pub(in super::super) fn new<'p, 'v: 'p, 'tcx: 'v>( + heap_encoder: &mut HeapEncoder<'p, 'v, 'tcx>, + statements: &mut Vec, + predicate: &vir_low::ast::expression::PredicateAccessPredicate, + expression_evaluation_state_label: Option, + position: vir_low::Position, + ) -> SpannedEncodingResult { + let old_permission_mask_version = + heap_encoder.get_current_permission_mask_for(&predicate.name)?; + let new_permission_mask_version = + heap_encoder.get_new_permission_mask_for(&predicate.name, position)?; + let perm_old = heap_encoder.permission_mask_call_for_predicate_use( + statements, + predicate, + old_permission_mask_version.clone(), + expression_evaluation_state_label.clone(), + position, + )?; + let perm_new = heap_encoder.permission_mask_call_for_predicate_use( + statements, + predicate, + new_permission_mask_version.clone(), + expression_evaluation_state_label, + position, + )?; + Ok(Self { + _kind: std::marker::PhantomData, + old_permission_mask_version, + new_permission_mask_version, + perm_old, + perm_new, + }) + } + + pub(in super::super) fn perm_new(&self) -> vir_low::Expression { + self.perm_new.clone() + } + + pub(in super::super) fn old_permission_mask_version(&self) -> vir_low::Expression { + self.old_permission_mask_version.clone() + } + + pub(in super::super) fn new_permission_mask_version(&self) -> vir_low::Expression { + self.new_permission_mask_version.clone() + } +} + +impl<'a, K: PermissionMaskKind> QuantifiedPermissionMaskOperations<'a, K> { + pub(in super::super) fn new<'p, 'v: 'p, 'tcx: 'v>( + heap_encoder: &mut HeapEncoder<'p, 'v, 'tcx>, + _statements: &mut Vec, + predicate: &'a vir_low::ast::expression::PredicateAccessPredicate, + _expression_evaluation_state_label: Option, + position: vir_low::Position, + ) -> SpannedEncodingResult { + let old_permission_mask_version = + heap_encoder.get_current_permission_mask_for(&predicate.name)?; + let new_permission_mask_version = + heap_encoder.get_new_permission_mask_for(&predicate.name, position)?; + Ok(Self { + _kind: std::marker::PhantomData, + old_permission_mask_version, + new_permission_mask_version, + predicate, + }) + } + + pub(in super::super) fn perm_old( + &self, + heap_encoder: &mut HeapEncoder, + predicate_location: Vec, + ) -> SpannedEncodingResult { + heap_encoder.permission_mask_call( + self.predicate, + predicate_location, + self.old_permission_mask_version.clone(), + ) + } + + pub(in super::super) fn perm_new( + &self, + heap_encoder: &mut HeapEncoder, + predicate_location: Vec, + ) -> SpannedEncodingResult { + heap_encoder.permission_mask_call( + self.predicate, + predicate_location, + self.new_permission_mask_version.clone(), + ) + } + + pub(in super::super) fn old_permission_mask_version(&self) -> vir_low::Expression { + self.old_permission_mask_version.clone() + } + + pub(in super::super) fn new_permission_mask_version(&self) -> vir_low::Expression { + self.new_permission_mask_version.clone() + } +} + +pub(in super::super) trait TPermissionMaskOperations { + fn perm_old_greater_equals( + &self, + permission_amount: &vir_low::Expression, + ) -> vir_low::Expression; + + fn perm_old_positive(&self) -> vir_low::Expression; + + fn perm_old_sub(&self, permission_amount: &vir_low::Expression) -> vir_low::Expression; + + fn perm_old_add(&self, permission_amount: &vir_low::Expression) -> vir_low::Expression; + + fn perm_old_equal_none(&self) -> vir_low::Expression; + + fn can_assume_old_permission_is_none(&self, permission_amount: &vir_low::Expression) -> bool; +} + +impl TPermissionMaskOperations + for PermissionMaskOperations +{ + fn perm_old_greater_equals( + &self, + permission_amount: &vir_low::Expression, + ) -> vir_low::Expression { + vir_low::Expression::greater_equals(self.perm_old.clone(), permission_amount.clone()) + } + + fn perm_old_positive(&self) -> vir_low::Expression { + vir_low::Expression::greater_than( + self.perm_old.clone(), + vir_low::Expression::no_permission(), + ) + } + + fn perm_old_sub(&self, permission_amount: &vir_low::Expression) -> vir_low::Expression { + if permission_amount.is_full_permission() { + vir_low::Expression::no_permission() + } else { + vir_low::Expression::perm_binary_op_no_pos( + vir_low::ast::expression::PermBinaryOpKind::Sub, + self.perm_old.clone(), + permission_amount.clone(), + ) + } + } + + fn perm_old_add(&self, permission_amount: &vir_low::Expression) -> vir_low::Expression { + if permission_amount.is_full_permission() { + vir_low::Expression::full_permission() + } else { + vir_low::Expression::perm_binary_op_no_pos( + vir_low::ast::expression::PermBinaryOpKind::Add, + self.perm_old.clone(), + permission_amount.clone(), + ) + } + } + + fn perm_old_equal_none(&self) -> vir_low::Expression { + vir_low::Expression::equals(self.perm_old.clone(), vir_low::Expression::no_permission()) + } + + fn can_assume_old_permission_is_none(&self, permission_amount: &vir_low::Expression) -> bool { + permission_amount.is_full_permission() + } +} + +impl TPermissionMaskOperations for PermissionMaskOperations { + fn perm_old_greater_equals( + &self, + permission_amount: &vir_low::Expression, + ) -> vir_low::Expression { + assert!(permission_amount.is_full_permission()); + self.perm_old.clone() + } + + fn perm_old_positive(&self) -> vir_low::Expression { + self.perm_old.clone() + } + + fn perm_old_sub(&self, permission_amount: &vir_low::Expression) -> vir_low::Expression { + assert!(permission_amount.is_full_permission()); + false.into() + } + + fn perm_old_add(&self, permission_amount: &vir_low::Expression) -> vir_low::Expression { + assert!(permission_amount.is_full_permission()); + true.into() + } + + fn perm_old_equal_none(&self) -> vir_low::Expression { + vir_low::Expression::equals(self.perm_old.clone(), false.into()) + } + + fn can_assume_old_permission_is_none(&self, _: &vir_low::Expression) -> bool { + true + } +} + +impl TPermissionMaskOperations + for PermissionMaskOperations +{ + fn perm_old_greater_equals( + &self, + permission_amount: &vir_low::Expression, + ) -> vir_low::Expression { + assert!(permission_amount.is_full_permission()); + self.perm_old.clone() + } + + fn perm_old_positive(&self) -> vir_low::Expression { + self.perm_old.clone() + } + + fn perm_old_sub(&self, permission_amount: &vir_low::Expression) -> vir_low::Expression { + assert!(permission_amount.is_full_permission()); + // The permission is duplicable, so exhale does nothing. + true.into() + } + + fn perm_old_add(&self, permission_amount: &vir_low::Expression) -> vir_low::Expression { + assert!(permission_amount.is_full_permission()); + true.into() + } + + fn perm_old_equal_none(&self) -> vir_low::Expression { + vir_low::Expression::equals(self.perm_old.clone(), false.into()) + } + + fn can_assume_old_permission_is_none(&self, _: &vir_low::Expression) -> bool { + // We may inhale the same permission multiple times, so we cannot assume that the old + // permission is none. + false + } +} + +pub(in super::super) trait TQuantifiedPermissionMaskOperations { + fn perm_old_greater_equals( + &self, + heap_encoder: &mut HeapEncoder, + predicate_location: Vec, + permission_amount: &vir_low::Expression, + ) -> SpannedEncodingResult; + + fn perm_old_positive( + &self, + heap_encoder: &mut HeapEncoder, + predicate_location: Vec, + ) -> SpannedEncodingResult; + + fn perm_old_sub( + &self, + heap_encoder: &mut HeapEncoder, + predicate_location: Vec, + permission_amount: &vir_low::Expression, + ) -> SpannedEncodingResult; + + fn perm_old_add( + &self, + heap_encoder: &mut HeapEncoder, + predicate_location: Vec, + permission_amount: &vir_low::Expression, + ) -> SpannedEncodingResult; + + fn perm_old_equal_none( + &self, + heap_encoder: &mut HeapEncoder, + predicate_location: Vec, + ) -> SpannedEncodingResult; + + fn can_assume_old_permission_is_none(&self, permission_amount: &vir_low::Expression) -> bool; +} + +impl<'a> TQuantifiedPermissionMaskOperations + for QuantifiedPermissionMaskOperations<'a, PermissionMaskKindAliasedFractionalBoundedPerm> +{ + fn perm_old_greater_equals( + &self, + heap_encoder: &mut HeapEncoder, + predicate_location: Vec, + permission_amount: &vir_low::Expression, + ) -> SpannedEncodingResult { + Ok(vir_low::Expression::greater_equals( + self.perm_old(heap_encoder, predicate_location)?, + permission_amount.clone(), + )) + } + + fn perm_old_positive( + &self, + heap_encoder: &mut HeapEncoder, + predicate_location: Vec, + ) -> SpannedEncodingResult { + Ok(vir_low::Expression::greater_than( + self.perm_old(heap_encoder, predicate_location)?, + vir_low::Expression::no_permission(), + )) + } + + fn perm_old_sub( + &self, + heap_encoder: &mut HeapEncoder, + predicate_location: Vec, + permission_amount: &vir_low::Expression, + ) -> SpannedEncodingResult { + let result = if permission_amount.is_full_permission() { + vir_low::Expression::no_permission() + } else { + vir_low::Expression::perm_binary_op_no_pos( + vir_low::ast::expression::PermBinaryOpKind::Sub, + self.perm_old(heap_encoder, predicate_location)?, + permission_amount.clone(), + ) + }; + Ok(result) + } + + fn perm_old_add( + &self, + heap_encoder: &mut HeapEncoder, + predicate_location: Vec, + permission_amount: &vir_low::Expression, + ) -> SpannedEncodingResult { + let result = if permission_amount.is_full_permission() { + vir_low::Expression::full_permission() + } else { + vir_low::Expression::perm_binary_op_no_pos( + vir_low::ast::expression::PermBinaryOpKind::Add, + self.perm_old(heap_encoder, predicate_location)?, + permission_amount.clone(), + ) + }; + Ok(result) + } + + fn perm_old_equal_none( + &self, + heap_encoder: &mut HeapEncoder, + predicate_location: Vec, + ) -> SpannedEncodingResult { + Ok(vir_low::Expression::equals( + self.perm_old(heap_encoder, predicate_location)?, + vir_low::Expression::no_permission(), + )) + } + + fn can_assume_old_permission_is_none(&self, permission_amount: &vir_low::Expression) -> bool { + permission_amount.is_full_permission() + } +} + +impl<'a> TQuantifiedPermissionMaskOperations + for QuantifiedPermissionMaskOperations<'a, PermissionMaskKindAliasedBool> +{ + fn perm_old_greater_equals( + &self, + heap_encoder: &mut HeapEncoder, + predicate_location: Vec, + permission_amount: &vir_low::Expression, + ) -> SpannedEncodingResult { + assert!(permission_amount.is_full_permission()); + self.perm_old(heap_encoder, predicate_location) + } + + fn perm_old_positive( + &self, + heap_encoder: &mut HeapEncoder, + predicate_location: Vec, + ) -> SpannedEncodingResult { + self.perm_old(heap_encoder, predicate_location) + } + + fn perm_old_sub( + &self, + _heap_encoder: &mut HeapEncoder, + _predicate_location: Vec, + permission_amount: &vir_low::Expression, + ) -> SpannedEncodingResult { + assert!(permission_amount.is_full_permission()); + Ok(false.into()) + } + + fn perm_old_add( + &self, + _: &mut HeapEncoder, + _: Vec, + permission_amount: &vir_low::Expression, + ) -> SpannedEncodingResult { + assert!(permission_amount.is_full_permission()); + Ok(true.into()) + } + + fn perm_old_equal_none( + &self, + heap_encoder: &mut HeapEncoder, + predicate_location: Vec, + ) -> SpannedEncodingResult { + Ok(vir_low::Expression::equals( + self.perm_old(heap_encoder, predicate_location)?, + false.into(), + )) + } + + fn can_assume_old_permission_is_none(&self, _: &vir_low::Expression) -> bool { + true + } +} + +impl<'a> TQuantifiedPermissionMaskOperations + for QuantifiedPermissionMaskOperations<'a, PermissionMaskKindAliasedDuplicableBool> +{ + fn perm_old_greater_equals( + &self, + heap_encoder: &mut HeapEncoder, + predicate_location: Vec, + permission_amount: &vir_low::Expression, + ) -> SpannedEncodingResult { + assert!(permission_amount.is_full_permission()); + self.perm_old(heap_encoder, predicate_location) + } + + fn perm_old_positive( + &self, + heap_encoder: &mut HeapEncoder, + predicate_location: Vec, + ) -> SpannedEncodingResult { + self.perm_old(heap_encoder, predicate_location) + } + + fn perm_old_sub( + &self, + _: &mut HeapEncoder, + _: Vec, + permission_amount: &vir_low::Expression, + ) -> SpannedEncodingResult { + assert!(permission_amount.is_full_permission()); + // The permission is duplicable, so exhale does nothing. + Ok(true.into()) + } + + fn perm_old_add( + &self, + _: &mut HeapEncoder, + _: Vec, + permission_amount: &vir_low::Expression, + ) -> SpannedEncodingResult { + assert!(permission_amount.is_full_permission()); + Ok(true.into()) + } + + fn perm_old_equal_none( + &self, + heap_encoder: &mut HeapEncoder, + predicate_location: Vec, + ) -> SpannedEncodingResult { + Ok(vir_low::Expression::equals( + self.perm_old(heap_encoder, predicate_location)?, + false.into(), + )) + } + + fn can_assume_old_permission_is_none(&self, _: &vir_low::Expression) -> bool { + // We may inhale the same permission multiple times, so we cannot assume that the old + // permission is none. + false + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/custom_heap_encoding/heap_encoder/predicates.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/custom_heap_encoding/heap_encoder/predicates.rs new file mode 100644 index 00000000000..5de6bfccddd --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/custom_heap_encoding/heap_encoder/predicates.rs @@ -0,0 +1,200 @@ +use super::{permission_mask::PredicatePermissionMaskKind, HeapEncoder}; +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::predicates::{OwnedPredicateInfo, SnapshotFunctionInfo}, +}; +use rustc_hash::FxHashMap; +use std::collections::BTreeMap; +use vir_crate::low::{self as vir_low}; + +pub(super) struct Predicates<'p> { + predicate_decls: FxHashMap, + snapshot_functions_to_predicates: BTreeMap, + snapshot_range_functions_to_predicates: BTreeMap, + predicates_to_snapshot_types: BTreeMap, +} + +impl<'p> Predicates<'p> { + pub(super) fn new( + predicate_decls: &'p [vir_low::PredicateDecl], + predicate_info: BTreeMap, + ) -> Self { + let mut snapshot_functions_to_predicates = BTreeMap::new(); + let mut snapshot_range_functions_to_predicates = BTreeMap::new(); + let mut predicates_to_snapshot_types = BTreeMap::new(); + for ( + predicate_name, + OwnedPredicateInfo { + current_snapshot_function: SnapshotFunctionInfo { function_name, .. }, + snapshot_type, + snapshot_range_function, + .. + }, + ) in predicate_info + { + snapshot_functions_to_predicates.insert(function_name, predicate_name.clone()); + snapshot_range_functions_to_predicates + .insert(snapshot_range_function, predicate_name.clone()); + predicates_to_snapshot_types.insert(predicate_name, snapshot_type); + } + Self { + predicate_decls: predicate_decls + .iter() + .map(|predicate| (predicate.name.clone(), predicate)) + .collect(), + snapshot_functions_to_predicates, + snapshot_range_functions_to_predicates, + predicates_to_snapshot_types, + } + } + + pub(super) fn iter_decls(&self) -> impl Iterator { + self.predicate_decls.values().cloned() + } + + pub(super) fn iter_range_functions( + &self, + ) -> impl Iterator { + self.snapshot_range_functions_to_predicates + .iter() + .map(|(function_name, predicate_name)| { + ( + function_name.as_str(), + self.predicate_decls[predicate_name.as_str()], + ) + }) + } +} + +impl<'p, 'v: 'p, 'tcx: 'v> HeapEncoder<'p, 'v, 'tcx> { + pub(super) fn get_predicate_decl( + &self, + predicate_name: &str, + ) -> SpannedEncodingResult<&'p vir_low::PredicateDecl> { + let decl = self + .predicates + .predicate_decls + .get(predicate_name) + .cloned() + .unwrap(); + Ok(decl) + } + + pub(super) fn get_predicate_parameters( + &self, + predicate_name: &str, + ) -> &[vir_low::VariableDecl] { + self.predicates + .predicate_decls + .get(predicate_name) + .unwrap() + .parameters + .as_slice() + } + + pub(super) fn get_predicate_parameters_as_arguments( + &mut self, + predicate_name: &str, + ) -> SpannedEncodingResult> { + let predicate_parameters = self.get_predicate_parameters(predicate_name).to_owned(); + Ok(predicate_parameters + .iter() + .map(|parameter| parameter.clone().into()) + .collect()) + } + + pub(super) fn get_predicate_name_for_function<'a>( + &'a self, + function_name: &str, + ) -> SpannedEncodingResult { + let function = self.functions[function_name]; + let predicate_name = match function.kind { + vir_low::FunctionKind::MemoryBlockBytes => todo!(), + vir_low::FunctionKind::CallerFor => todo!(), + vir_low::FunctionKind::SnapRange => self + .predicates + .snapshot_range_functions_to_predicates + .get(function_name) + .unwrap_or_else(|| panic!("not found {function_name}")) + .clone(), + vir_low::FunctionKind::Snap => { + self.predicates.snapshot_functions_to_predicates[function_name].clone() + } + }; + Ok(predicate_name) + } + + pub(super) fn get_snapshot_type_for_predicate( + &self, + predicate_name: &str, + ) -> Option { + let predicate = self.predicates.predicate_decls[predicate_name]; + match predicate.kind { + vir_low::PredicateKind::MemoryBlock => { + use vir_low::macros::*; + Some(ty!(Bytes)) + } + vir_low::PredicateKind::Owned => Some( + self.predicates + .predicates_to_snapshot_types + .get(predicate_name) + .unwrap_or_else(|| unreachable!("predicate not found: {}", predicate_name)) + .clone(), + ), + vir_low::PredicateKind::LifetimeToken + | vir_low::PredicateKind::CloseFracRef + | vir_low::PredicateKind::WithoutSnapshotWhole + | vir_low::PredicateKind::WithoutSnapshotWholeNonAliased + // | vir_low::PredicateKind::WithoutSnapshotFrac + | vir_low::PredicateKind::DeadLifetimeToken + | vir_low::PredicateKind::EndBorrowViewShift => None, + } + } + + pub(super) fn purify_predicate_arguments( + &mut self, + statements: &mut Vec, + predicate: &vir_low::ast::expression::PredicateAccessPredicate, + expression_evaluation_state_label: Option, + position: vir_low::Position, + ) -> SpannedEncodingResult> { + let mut arguments = Vec::new(); + for argument in &predicate.arguments { + arguments.push(self.purify_snap_function_calls_in_expression( + statements, + argument.clone(), + expression_evaluation_state_label.clone(), + Vec::new(), // FIXME: This is probably wrong. + position, + false, + )?); + } + Ok(arguments) + } + + pub(super) fn get_predicate_permission_mask_kind( + &self, + predicate_name: &str, + ) -> SpannedEncodingResult { + let predicate_decl = self.get_predicate_decl(predicate_name)?; + let mask_kind = match predicate_decl.kind { + vir_low::PredicateKind::MemoryBlock | vir_low::PredicateKind::Owned => { + PredicatePermissionMaskKind::AliasedFractionalBoundedPerm + } + // vir_low::PredicateKind::WithoutSnapshotFrac | + vir_low::PredicateKind::LifetimeToken => { + PredicatePermissionMaskKind::AliasedFractionalBoundedPerm + } + vir_low::PredicateKind::CloseFracRef + | vir_low::PredicateKind::WithoutSnapshotWhole + | vir_low::PredicateKind::WithoutSnapshotWholeNonAliased + | vir_low::PredicateKind::EndBorrowViewShift => { + PredicatePermissionMaskKind::AliasedWholeBool + } + vir_low::PredicateKind::DeadLifetimeToken => { + PredicatePermissionMaskKind::AliasedWholeDuplicable + } + }; + Ok(mask_kind) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/custom_heap_encoding/heap_encoder/pure_expressions.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/custom_heap_encoding/heap_encoder/pure_expressions.rs new file mode 100644 index 00000000000..4a4588ee4e9 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/custom_heap_encoding/heap_encoder/pure_expressions.rs @@ -0,0 +1,297 @@ +use super::HeapEncoder; +use crate::encoder::errors::{SpannedEncodingError, SpannedEncodingResult}; +use vir_crate::{ + common::{ + builtin_constants::MEMORY_BLOCK_PREDICATE_NAME, + expression::{BinaryOperationHelpers, ExpressionIterator, UnaryOperationHelpers}, + }, + low::{self as vir_low, expression::visitors::ExpressionFallibleFolder}, +}; + +impl<'p, 'v: 'p, 'tcx: 'v> HeapEncoder<'p, 'v, 'tcx> { + pub(super) fn purify_snap_function_calls_in_expressions( + &mut self, + statements: &mut Vec, + expressions: Vec, + expression_evaluation_state_label: Option, + initial_path_condition: Vec, + position: vir_low::Position, + is_in_frame_check: bool, + ) -> SpannedEncodingResult> { + let mut purified_expressions = Vec::new(); + for expression in expressions { + let purified_expression = self.purify_snap_function_calls_in_expression( + statements, + expression, + expression_evaluation_state_label.clone(), + initial_path_condition.clone(), + position, + is_in_frame_check, + )?; + purified_expressions.push(purified_expression); + } + Ok(purified_expressions) + } + + /// If `is_in_frame_check` is true, then variables bound by quantifiers are skolemized out. + pub(super) fn purify_snap_function_calls_in_expression( + &mut self, + statements: &mut Vec, + expression: vir_low::Expression, + expression_evaluation_state_label: Option, + initial_path_condition: Vec, + position: vir_low::Position, + is_in_frame_check: bool, + ) -> SpannedEncodingResult { + let mut purifier = Purifier { + expression_evaluation_state_label, + heap_encoder: self, + statements, + path_condition: initial_path_condition, + position, + is_in_frame_check, + inside_trigger: false, + }; + purifier.fallible_fold_expression(expression) + } +} + +struct Purifier<'e, 'p, 'v: 'p, 'tcx: 'v> { + /// The state in which we should evaluate the heap expressions. If `None`, + /// takes the latest heap._ + expression_evaluation_state_label: Option, + heap_encoder: &'e mut HeapEncoder<'p, 'v, 'tcx>, + statements: &'e mut Vec, + path_condition: Vec, + position: vir_low::Position, + is_in_frame_check: bool, + inside_trigger: bool, +} + +impl<'e, 'p, 'v: 'p, 'tcx: 'v> Purifier<'e, 'p, 'v, 'tcx> { + fn current_heap_version( + &mut self, + predicate_name: &str, + ) -> SpannedEncodingResult { + let heap_version = if let Some(expression_evaluation_state_label) = + &self.expression_evaluation_state_label + { + self.heap_encoder + .get_heap_version_at_label(predicate_name, expression_evaluation_state_label)? + } else { + self.heap_encoder + .get_current_heap_version_for(predicate_name)? + }; + Ok(heap_version) + } + + fn snap_function_range_call( + &mut self, + function_name: &str, + predicate_name: &str, + mut arguments: Vec, + ) -> SpannedEncodingResult { + let heap_version = self.current_heap_version(predicate_name)?; + arguments.push(heap_version); + // FIXME: Generate the definitional axiom. + let heap_function_name = self.heap_encoder.heap_range_function_name(predicate_name); + let Some(function_decl) = self.heap_encoder.functions.get(function_name) else { + unreachable!(); + }; + let return_type = function_decl.return_type.clone(); + Ok(vir_low::Expression::domain_function_call( + "HeapFunctions", + heap_function_name, + arguments, + return_type, + )) + } + + fn snap_function_call( + &mut self, + predicate_name: &str, + mut arguments: Vec, + ) -> SpannedEncodingResult { + let heap_version = self.current_heap_version(predicate_name)?; + arguments.push(heap_version); + let heap_function_name = self.heap_encoder.heap_function_name(predicate_name); + let return_type = self + .heap_encoder + .get_snapshot_type_for_predicate(predicate_name) + .unwrap(); + Ok(vir_low::Expression::domain_function_call( + "HeapFunctions", + heap_function_name, + arguments, + return_type, + )) + } +} + +impl<'e, 'p, 'v: 'p, 'tcx: 'v> ExpressionFallibleFolder for Purifier<'e, 'p, 'v, 'tcx> { + type Error = SpannedEncodingError; + + fn fallible_fold_func_app_enum( + &mut self, + func_app: vir_low::expression::FuncApp, + ) -> Result { + let function = self.heap_encoder.functions[&func_app.function_name]; + assert_eq!(function.parameters.len(), func_app.arguments.len()); + let arguments = func_app + .arguments + .clone() + .into_iter() + .map(|argument| self.fallible_fold_expression(argument)) + .collect::, _>>()?; + let path_condition = self.path_condition.iter().cloned().conjoin(); + let replacements = function.parameters.iter().zip(arguments.iter()).collect(); + let pres = function + .pres + .iter() + .cloned() + .conjoin() + .substitute_variables(&replacements); + let pres = self.fallible_fold_expression(pres)?; + let assert_precondition = vir_low::Expression::implies(path_condition, pres); + eprintln!("assert_precondition: {}", assert_precondition); + if !self.inside_trigger { + // Do not assert preconditions inside triggers. + self.heap_encoder.encode_function_precondition_assert( + self.statements, + assert_precondition, + self.position, + self.expression_evaluation_state_label.clone(), + )?; + } + match function.kind { + vir_low::FunctionKind::MemoryBlockBytes => { + self.snap_function_call(MEMORY_BLOCK_PREDICATE_NAME, arguments) + } + vir_low::FunctionKind::CallerFor => { + let inlined_function = function + .body + .clone() + .unwrap() + .substitute_variables(&replacements); + Ok(inlined_function) + } + vir_low::FunctionKind::SnapRange => { + let predicate_name = self + .heap_encoder + .get_predicate_name_for_function(&func_app.function_name)?; + self.snap_function_range_call(&func_app.function_name, &predicate_name, arguments) + } + vir_low::FunctionKind::Snap => { + let predicate_name = self + .heap_encoder + .get_predicate_name_for_function(&func_app.function_name)?; + let validity_function = { + // FIXME: This is a hack, put it into OwnedPredicateInfo instead. + match function.posts[0].clone() { + vir_low::Expression::DomainFuncApp(domain_func_app) => { + assert!(domain_func_app.function_name.starts_with("valid$Snap$")); + assert_eq!(domain_func_app.arguments.len(), 1); + domain_func_app + } + _ => unreachable!(), + } + }; + let call = self.snap_function_call(&predicate_name, arguments)?; + let ensures_validity = vir_low::Expression::domain_function_call( + "HeapFunctions", + format!("ensures${}", validity_function.function_name), + vec![call], + func_app.return_type, + ); + Ok(ensures_validity) + } + } + } + + fn fallible_fold_binary_op( + &mut self, + mut binary_op: vir_low::expression::BinaryOp, + ) -> Result { + binary_op.left = self.fallible_fold_expression_boxed(binary_op.left)?; + if binary_op.op_kind == vir_low::BinaryOpKind::Implies { + self.path_condition.push((*binary_op.left).clone()); + } + binary_op.right = self.fallible_fold_expression_boxed(binary_op.right)?; + if binary_op.op_kind == vir_low::BinaryOpKind::Implies { + self.path_condition.pop(); + } + Ok(binary_op) + } + + fn fallible_fold_conditional( + &mut self, + mut conditional: vir_low::expression::Conditional, + ) -> Result { + conditional.guard = self.fallible_fold_expression_boxed(conditional.guard)?; + self.path_condition.push((*conditional.guard).clone()); + conditional.then_expr = self.fallible_fold_expression_boxed(conditional.then_expr)?; + self.path_condition.pop(); + self.path_condition + .push(vir_low::Expression::not((*conditional.guard).clone())); + conditional.else_expr = self.fallible_fold_expression_boxed(conditional.else_expr)?; + self.path_condition.pop(); + Ok(conditional) + } + + fn fallible_fold_labelled_old_enum( + &mut self, + mut labelled_old: vir_low::expression::LabelledOld, + ) -> Result { + std::mem::swap( + &mut labelled_old.label, + &mut self.expression_evaluation_state_label, + ); + let mut labelled_old = self.fallible_fold_labelled_old(labelled_old)?; + std::mem::swap( + &mut labelled_old.label, + &mut self.expression_evaluation_state_label, + ); + Ok(vir_low::Expression::LabelledOld(labelled_old)) + } + + fn fallible_fold_quantifier_enum( + &mut self, + quantifier: vir_low::Quantifier, + ) -> Result { + self.heap_encoder + .create_quantifier_variables_remap(&quantifier.variables)?; + let quantifier = self.fallible_fold_quantifier(quantifier)?; + self.heap_encoder.bound_variable_remap_stack.pop(); + Ok(vir_low::Expression::Quantifier(quantifier)) + } + + fn fallible_fold_variable_decl( + &mut self, + variable_decl: vir_low::VariableDecl, + ) -> Result { + if self.is_in_frame_check { + if let Some(remap) = self + .heap_encoder + .bound_variable_remap_stack + .get(&variable_decl) + { + return Ok(remap.clone()); + } + } + Ok(variable_decl) + } + + fn fallible_fold_trigger( + &mut self, + mut trigger: vir_low::Trigger, + ) -> Result { + assert!(!self.inside_trigger); + self.inside_trigger = true; + for term in std::mem::take(&mut trigger.terms) { + let term = self.fallible_fold_expression(term)?; + trigger.terms.push(term); + } + self.inside_trigger = false; + Ok(trigger) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/custom_heap_encoding/heap_encoder/statements.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/custom_heap_encoding/heap_encoder/statements.rs new file mode 100644 index 00000000000..d04168623f6 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/custom_heap_encoding/heap_encoder/statements.rs @@ -0,0 +1,94 @@ +use super::HeapEncoder; +use crate::encoder::errors::SpannedEncodingResult; +use vir_crate::low::{self as vir_low}; + +impl<'p, 'v: 'p, 'tcx: 'v> HeapEncoder<'p, 'v, 'tcx> { + pub(super) fn encode_statement_internal( + &mut self, + statements: &mut Vec, + statement: vir_low::Statement, + ) -> SpannedEncodingResult<()> { + assert!(self.bound_variable_remap_stack.is_empty()); + match statement { + vir_low::Statement::Comment(_) + | vir_low::Statement::LogEvent(_) + | vir_low::Statement::Assign(_) => { + statements.push(statement); + } + vir_low::Statement::Label(statement) => { + self.ssa_state.save_state_at_label(statement.label.clone()); + statements.push(vir_low::Statement::Label(statement)); + } + vir_low::Statement::Assume(statement) => { + assert!(statement.expression.is_pure()); + let expression = self.purify_snap_function_calls_in_expression( + statements, + statement.expression, + None, + Vec::new(), + statement.position, + false, + )?; + statements.push(vir_low::Statement::assume(expression, statement.position)); + } + vir_low::Statement::Assert(statement) => { + self.encode_expression_assert( + statements, + statement.expression, + statement.position, + None, + )?; + } + vir_low::Statement::Inhale(statement) => { + statements.push(vir_low::Statement::comment(format!("{statement}"))); + self.encode_expression_inhale( + statements, + statement.expression, + statement.position, + None, + )?; + } + vir_low::Statement::Exhale(statement) => { + statements.push(vir_low::Statement::comment(format!("{statement}"))); + let evaluation_state = self.fresh_label(); + self.ssa_state.save_state_at_label(evaluation_state.clone()); + self.encode_expression_exhale( + statements, + statement.expression, + statement.position, + &evaluation_state, + )?; + } + vir_low::Statement::Fold(_) => todo!(), + vir_low::Statement::Unfold(_) => todo!(), + vir_low::Statement::ApplyMagicWand(_) => { + unimplemented!("magic wands are not supported yet"); + } + vir_low::Statement::MethodCall(statement) => { + unreachable!("method call: {}", statement); + } + vir_low::Statement::Conditional(conditional) => { + unreachable!("conditional: {}", conditional); + } + vir_low::Statement::MaterializePredicate(statement) => { + unreachable!("materialize predicate: {statement}"); + } + vir_low::Statement::CaseSplit(statement) => { + assert!(statement.expression.is_pure()); + let expression = self.purify_snap_function_calls_in_expression( + statements, + statement.expression, + None, + Vec::new(), + statement.position, + false, + )?; + statements.push(vir_low::Statement::case_split( + expression, + statement.position, + )); + } + } + Ok(()) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/custom_heap_encoding/mod.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/custom_heap_encoding/mod.rs new file mode 100644 index 00000000000..ac5660dbc77 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/custom_heap_encoding/mod.rs @@ -0,0 +1,111 @@ +//! This module contains a custom heap encoding that can be used instead of the +//! Viper builtin heap encoding. This module depends on `ErrorManager` and, +//! therefore, has to be in the `prusti-viper` crate. + +mod heap_encoder; +mod variable_declarations; + +use self::heap_encoder::HeapEncoder; +use crate::encoder::{ + errors::{ErrorCtxt, SpannedEncodingResult}, + middle::core_proof::predicates::OwnedPredicateInfo, + mir::errors::ErrorInterface, + Encoder, +}; +use std::collections::BTreeMap; +use vir_crate::{ + common::cfg::Cfg, + low::{self as vir_low}, +}; + +pub(in super::super) fn custom_heap_encoding<'p, 'v: 'p, 'tcx: 'v>( + encoder: &'p mut Encoder<'v, 'tcx>, + program: &mut vir_low::Program, + predicate_info: BTreeMap, +) -> SpannedEncodingResult<()> { + let mut procedures = Vec::new(); + let mut heap_encoder = HeapEncoder::new( + encoder, + &program.predicates, + predicate_info, + &program.functions, + ); + for procedure in std::mem::take(&mut program.procedures) { + heap_encoder.reset(); + let procedure = custom_heap_encoding_for_procedure(&mut heap_encoder, procedure)?; + procedures.push(procedure); + } + program.procedures = procedures; + program + .domains + .extend(heap_encoder.generate_necessary_domains()?); + Ok(()) +} + +fn custom_heap_encoding_for_procedure<'p, 'v: 'p, 'tcx: 'v>( + heap_encoder: &mut HeapEncoder<'p, 'v, 'tcx>, + mut procedure: vir_low::ProcedureDecl, +) -> SpannedEncodingResult { + assert!(!procedure.position.is_default()); + let predecessors = procedure.predecessors_owned(); + let traversal_order = procedure.get_topological_sort(); + let mut basic_block_edges = BTreeMap::new(); + for label in &traversal_order { + heap_encoder.prepare_new_current_block(label, &predecessors, &mut basic_block_edges)?; + let mut statements = Vec::new(); + let block = procedure.basic_blocks.get_mut(label).unwrap(); + for statement in std::mem::take(&mut block.statements) { + heap_encoder.encode_statement(&mut statements, statement)?; + } + block.statements = statements; + heap_encoder.finish_current_block(label.clone())?; + } + for label in traversal_order { + if let Some(intermediate_blocks) = basic_block_edges.remove(&label) { + let mut block = procedure.basic_blocks.remove(&label).unwrap(); + for (successor_label, equalities) in intermediate_blocks { + let intermediate_block_label = vir_low::Label::new(format!( + "label__from__{}__to__{}", + label.name, successor_label.name + )); + block + .successor + .replace_label(&successor_label, intermediate_block_label.clone()); + let mut successor_statements = Vec::new(); + for (variable_name, ty, position, old_version, new_version) in equalities { + let new_variable = + heap_encoder.create_variable(&variable_name, ty.clone(), new_version)?; + let old_variable = + heap_encoder.create_variable(&variable_name, ty.clone(), old_version)?; + let position = heap_encoder.encoder().change_error_context( + // FIXME: Get a more precise span. + position, + ErrorCtxt::Unexpected, + ); + let statement = vir_low::macros::stmtp! { + position => assume (new_variable == old_variable) + }; + successor_statements.push(statement); + } + procedure.basic_blocks.insert( + intermediate_block_label, + vir_low::BasicBlock { + statements: successor_statements, + successor: vir_low::Successor::Goto(successor_label), + }, + ); + } + procedure.basic_blocks.insert(label, block); + } + } + let init_permissions_to_zero = + heap_encoder.generate_init_permissions_to_zero(procedure.position)?; + procedure.locals.extend(heap_encoder.take_variables()); + procedure + .basic_blocks + .get_mut(&procedure.entry) + .unwrap() + .statements + .splice(0..0, init_permissions_to_zero); + Ok(procedure) +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/custom_heap_encoding/variable_declarations.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/custom_heap_encoding/variable_declarations.rs new file mode 100644 index 00000000000..0d21fcd3ac1 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/custom_heap_encoding/variable_declarations.rs @@ -0,0 +1,40 @@ +use crate::encoder::errors::SpannedEncodingResult; +use rustc_hash::FxHashSet; + +use vir_crate::low::{self as vir_low}; + +#[derive(Default)] +pub(super) struct VariableDeclarations { + variables: FxHashSet, + fresh_variable_counter: u64, +} + +impl VariableDeclarations { + pub(super) fn create_variable( + &mut self, + variable_name: &str, + ty: vir_low::Type, + version: u64, + ) -> SpannedEncodingResult { + let variable = vir_low::VariableDecl::new(format!("{variable_name}_{version}"), ty); + self.variables.insert(variable.clone()); + Ok(variable) + } + + pub(super) fn fresh_variable( + &mut self, + variable_name: &str, + ty: &vir_low::Type, + ) -> SpannedEncodingResult { + let count = self.fresh_variable_counter; + self.fresh_variable_counter += 1; + let variable = + vir_low::VariableDecl::new(format!("{variable_name}$fresh${count}"), ty.clone()); + self.variables.insert(variable.clone()); + Ok(variable) + } + + pub(super) fn take_variables(&mut self) -> FxHashSet { + std::mem::take(&mut self.variables) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/desugar_conditionals.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/desugar_conditionals.rs new file mode 100644 index 00000000000..851244b0eae --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/desugar_conditionals.rs @@ -0,0 +1,92 @@ +use vir_crate::{ + common::{expression::UnaryOperationHelpers, graphviz::ToGraphviz}, + low::{self as vir_low}, +}; + +pub(crate) fn desugar_conditionals( + source_filename: &str, + mut program: vir_low::Program, +) -> vir_low::Program { + let mut procedures = Vec::new(); + for procedure in std::mem::take(&mut program.procedures) { + let procedure = desugar_conditionals_in_procedure(procedure); + if prusti_common::config::dump_debug_info() { + prusti_common::report::log::report_with_writer( + "graphviz_method_vir_low_after_desugar_conditionals", + format!("{}.{}.dot", source_filename, procedure.name), + |writer| procedure.to_graphviz(writer).unwrap(), + ); + } + procedures.push(procedure); + } + program.procedures = procedures; + program +} + +fn new_label(prefix: &str, label_counter: &mut usize) -> vir_low::Label { + let label = format!("{prefix}__{label_counter}"); + *label_counter += 1; + vir_low::Label::new(label) +} + +fn desugar_conditionals_in_procedure( + mut procedure: vir_low::ProcedureDecl, +) -> vir_low::ProcedureDecl { + let mut work_queue: Vec<_> = procedure.basic_blocks.keys().cloned().collect(); + let mut label_counter = 0; + while let Some(current_label) = work_queue.pop() { + let block = procedure.basic_blocks.get_mut(¤t_label).unwrap(); + if let Some(conditional_position) = block + .statements + .iter() + .position(|statement| matches!(statement, vir_low::Statement::Conditional(_))) + { + let remaining_statements = block.statements.split_off(conditional_position + 1); + let vir_low::Statement::Conditional(conditional) = block.statements.pop().unwrap() else { + unreachable!(); + }; + let remaining_block_label = new_label("remaining_block_label", &mut label_counter); + let then_block_label = new_label("then_block_label", &mut label_counter); + let else_block_label = new_label("else_block_label", &mut label_counter); + let then_block = vir_low::BasicBlock { + statements: conditional.then_branch, + successor: vir_low::Successor::Goto(remaining_block_label.clone()), + }; + + let mut targets = vec![(conditional.guard.clone(), then_block_label.clone())]; + let negated_guard = vir_low::Expression::not(conditional.guard.clone()); + let else_block = if conditional.else_branch.is_empty() { + targets.push((negated_guard, remaining_block_label.clone())); + None + } else { + let else_block = vir_low::BasicBlock { + statements: conditional.else_branch, + successor: vir_low::Successor::Goto(remaining_block_label.clone()), + }; + targets.push((negated_guard, else_block_label.clone())); + Some(else_block) + }; + let new_successor = vir_low::Successor::GotoSwitch(targets); + let original_successor = std::mem::replace(&mut block.successor, new_successor); + let remaining_block = vir_low::BasicBlock { + statements: remaining_statements, + successor: original_successor, + }; + work_queue.push(remaining_block_label.clone()); + work_queue.push(then_block_label.clone()); + procedure + .basic_blocks + .insert(then_block_label.clone(), then_block); + if let Some(else_block) = else_block { + work_queue.push(else_block_label.clone()); + procedure + .basic_blocks + .insert(else_block_label.clone(), else_block); + } + procedure + .basic_blocks + .insert(remaining_block_label.clone(), remaining_block); + } + } + procedure +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/desugar_containers.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/desugar_containers.rs new file mode 100644 index 00000000000..40229e5c666 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/desugar_containers.rs @@ -0,0 +1,326 @@ +use rustc_hash::FxHashSet; +use vir_crate::{ + common::{ + expression::{BinaryOperationHelpers, ExpressionIterator, QuantifierHelpers}, + graphviz::ToGraphviz, + }, + low::{ + self as vir_low, + ast::statement::visitors::StatementFolder, + expression::visitors::{default_fold_container_op, ExpressionFolder}, + operations::ty::Typed, + ty::visitors::TypeFolder, + }, +}; + +pub(crate) fn desugar_containers( + source_filename: &str, + mut program: vir_low::Program, +) -> vir_low::Program { + let mut rewriter = Rewriter { + used_set_types: FxHashSet::default(), + used_set_constructors: FxHashSet::default(), + }; + for procedure in &mut program.procedures { + desugar_containers_in_procedure(&mut rewriter, procedure); + if prusti_common::config::dump_debug_info() { + prusti_common::report::log::report_with_writer( + "graphviz_method_vir_low_after_desugar_containers", + format!("{}.{}.dot", source_filename, procedure.name), + |writer| procedure.to_graphviz(writer).unwrap(), + ); + } + } + for domain in &mut program.domains { + desugar_containers_in_domain(&mut rewriter, domain); + } + for ty in rewriter.used_set_types { + let domain_name = set_domain_name(&ty); + let mut functions = Vec::new(); + let mut axioms = Vec::new(); + let set_contains_function_name = vir_low::ContainerOpKind::SetContains.to_string(); + let set_subset_function_name = vir_low::ContainerOpKind::SetSubset.to_string(); + for (container_type, arity) in &rewriter.used_set_constructors { + let vir_low::Type::Set(set_type) = container_type else { + unreachable!(); + }; + if &ty != set_type { + continue; + } + let function_name = format!("SetConstructor{}", arity); + let mut parameters = Vec::new(); + let mut contained_conjuncts = Vec::new(); + for i in 0..*arity { + let parameter = vir_low::VariableDecl { + name: format!("_{}", i), + ty: (*ty.element_type).clone(), + }; + parameters.push(parameter); + } + let return_type = vir_low::Type::Set(ty.clone()); + let constructor_call = vir_low::Expression::domain_function_call( + domain_name.clone(), + function_name.clone(), + parameters + .clone() + .into_iter() + .map(vir_low::Expression::local_no_pos) + .collect(), + return_type.clone(), + ); + for parameter in ¶meters { + contained_conjuncts.push(vir_low::Expression::domain_function_call( + domain_name.clone(), + set_contains_function_name.clone(), + vec![ + vir_low::Expression::local_no_pos(parameter.clone()), + constructor_call.clone(), + ], + vir_low::Type::Bool, + )); + } + let function = vir_low::DomainFunctionDecl { + name: function_name.clone(), + is_unique: false, + parameters: parameters.clone(), + return_type, + }; + let arguments_contained_axiom = vir_low::DomainAxiomDecl { + comment: None, + name: format!("{}ArgumentsContained", function_name), + body: vir_low::Expression::forall( + parameters.clone(), + vec![vir_low::Trigger::new(vec![constructor_call.clone()])], + contained_conjuncts.into_iter().conjoin(), + ), + }; + functions.push(function); + axioms.push(arguments_contained_axiom); + } + let set_subset_function = vir_low::DomainFunctionDecl { + name: set_subset_function_name.clone(), + is_unique: false, + parameters: vec![ + vir_low::VariableDecl { + name: "set1".to_string(), + ty: vir_low::Type::Set(ty.clone()), + }, + vir_low::VariableDecl { + name: "set2".to_string(), + ty: vir_low::Type::Set(ty.clone()), + }, + ], + return_type: vir_low::Type::Bool, + }; + functions.push(set_subset_function); + { + let set_a = vir_low::VariableDecl { + name: "set1".to_string(), + ty: vir_low::Type::Set(ty.clone()), + }; + let set_b = vir_low::VariableDecl { + name: "set2".to_string(), + ty: vir_low::Type::Set(ty.clone()), + }; + let element = vir_low::VariableDecl { + name: "element".to_string(), + ty: (*ty.element_type).clone(), + }; + let set_a_contains_element = vir_low::Expression::domain_function_call( + domain_name.clone(), + set_contains_function_name.clone(), + vec![ + vir_low::Expression::local_no_pos(element.clone()), + vir_low::Expression::local_no_pos(set_a.clone()), + ], + vir_low::Type::Bool, + ); + let set_b_contains_element = vir_low::Expression::domain_function_call( + domain_name.clone(), + set_contains_function_name.clone(), + vec![ + vir_low::Expression::local_no_pos(element.clone()), + vir_low::Expression::local_no_pos(set_b.clone()), + ], + vir_low::Type::Bool, + ); + let elements_contained = vir_low::Expression::forall( + vec![element.clone()], + vec![ + vir_low::Trigger::new(vec![set_a_contains_element.clone()]), + vir_low::Trigger::new(vec![set_b_contains_element.clone()]), + ], + vir_low::Expression::implies( + set_a_contains_element.clone(), + set_b_contains_element.clone(), + ), + ); + let set_a_b_subset = vir_low::Expression::domain_function_call( + domain_name.clone(), + set_subset_function_name.clone(), + vec![ + vir_low::Expression::local_no_pos(set_a.clone()), + vir_low::Expression::local_no_pos(set_b.clone()), + ], + vir_low::Type::Bool, + ); + let set_subset = vir_low::Expression::forall( + vec![set_a.clone(), set_b.clone()], + vec![vir_low::Trigger::new(vec![set_a_b_subset.clone()])], + vir_low::Expression::equals(set_a_b_subset.clone(), elements_contained.clone()), + ); + let set_subset_axiom = vir_low::DomainAxiomDecl { + comment: None, + name: "SetSubset".to_string(), + body: set_subset, + }; + axioms.push(set_subset_axiom); + } + let set_contains_function = vir_low::DomainFunctionDecl { + name: set_contains_function_name.clone(), + is_unique: false, + parameters: vec![ + vir_low::VariableDecl { + name: "element".to_string(), + ty: (*ty.element_type).clone(), + }, + vir_low::VariableDecl { + name: "set".to_string(), + ty: vir_low::Type::Set(ty.clone()), + }, + ], + return_type: vir_low::Type::Bool, + }; + functions.push(set_contains_function); + let domain = vir_low::DomainDecl { + name: domain_name.clone(), + functions, + axioms, + rewrite_rules: Vec::new(), + }; + program.domains.push(domain); + } + program +} + +fn desugar_containers_in_procedure( + rewriter: &mut Rewriter, + procedure: &mut vir_low::ProcedureDecl, +) { + for basic_block in procedure.basic_blocks.values_mut() { + for statement in std::mem::take(&mut basic_block.statements) { + let new_statement = rewriter.fold_statement(statement); + basic_block.statements.push(new_statement); + } + } +} + +fn desugar_containers_in_domain(rewriter: &mut Rewriter, domain: &mut vir_low::DomainDecl) { + for mut function in std::mem::take(&mut domain.functions) { + function.return_type = TypeFolder::fold_type(rewriter, function.return_type); + for mut parameter in std::mem::take(&mut function.parameters) { + parameter.ty = TypeFolder::fold_type(rewriter, parameter.ty); + function.parameters.push(parameter); + } + domain.functions.push(function); + } + for mut axiom in std::mem::take(&mut domain.axioms) { + axiom.body = ExpressionFolder::fold_expression(rewriter, axiom.body); + domain.axioms.push(axiom); + } +} + +struct Rewriter { + used_set_types: FxHashSet, + used_set_constructors: FxHashSet<(vir_low::Type, usize)>, +} + +impl StatementFolder for Rewriter { + fn fold_expression(&mut self, expression: vir_low::Expression) -> vir_low::Expression { + ExpressionFolder::fold_expression(self, expression) + } +} + +impl ExpressionFolder for Rewriter { + fn fold_type(&mut self, ty: vir_low::Type) -> vir_low::Type { + TypeFolder::fold_type(self, ty) + } + fn fold_trigger(&mut self, mut trigger: vir_low::Trigger) -> vir_low::Trigger { + for term in std::mem::take(&mut trigger.terms) { + let new_term = ExpressionFolder::fold_expression(self, term); + trigger.terms.push(new_term); + } + trigger + } + fn fold_container_op_enum( + &mut self, + container_op: vir_low::ContainerOp, + ) -> vir_low::Expression { + let function_name = match container_op.kind { + vir_low::ContainerOpKind::SeqConstructor => todo!(), + vir_low::ContainerOpKind::SetConstructor => { + self.used_set_constructors.insert(( + container_op.container_type.clone(), + container_op.operands.len(), + )); + format!("SetConstructor{}", container_op.operands.len()) + } + vir_low::ContainerOpKind::MultiSetConstructor => todo!(), + _ => container_op.kind.to_string(), + }; + let container_op = default_fold_container_op(self, container_op); + // This is already the converterd type. + let return_type = container_op.get_type().clone(); + let vir_low::Type::Domain(domain) = &container_op.container_type else { + unreachable!(); + }; + let domain_name = domain.name.clone(); + vir_low::Expression::domain_function_call( + domain_name, + function_name, + container_op.operands, + return_type, + ) + } +} + +impl TypeFolder for Rewriter { + fn fold_type(&mut self, ty: vir_low::Type) -> vir_low::Type { + match ty { + vir_low::Type::Seq(_container) => unimplemented!(), + vir_low::Type::Set(mut container) => { + self.used_set_types.insert(container.clone()); + container.element_type = TypeFolder::fold_type_boxed(self, container.element_type); + + set_domain(&container) + } + vir_low::Type::MultiSet(_container) => unimplemented!(), + vir_low::Type::Map(_container) => unimplemented!(), + _ => ty, + } + } +} + +fn domain_name(container: &vir_low::Type) -> String { + match container { + // vir_low::Type::Seq(container) => seq_domain_name(container), + vir_low::Type::Set(container) => set_domain_name(container), + // vir_low::Type::MultiSet(container) => multi_set_domain_name(container), + // vir_low::Type::Map(container) => map_domain_name(container), + _ => unimplemented!("domain_name for {}", container), + } +} + +fn set_domain_name(container: &vir_low::ty::Set) -> String { + let vir_low::Type::Domain(element_domain) = &*container.element_type else { + unreachable!("Set element type is not a domain type. It is: {}", container.element_type); + }; + let domain_name = format!("Set<{}>", element_domain.name); + debug_assert!(!domain_name.contains(' ')); + domain_name +} + +fn set_domain(container: &vir_low::ty::Set) -> vir_low::Type { + let domain_name = set_domain_name(container); + vir_low::Type::domain(domain_name) +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/desugar_fold_unfold.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/desugar_fold_unfold.rs new file mode 100644 index 00000000000..dd3ac866473 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/desugar_fold_unfold.rs @@ -0,0 +1,310 @@ +use crate::encoder::middle::core_proof::predicates::OwnedPredicateInfo; +use rustc_hash::FxHashMap; +use std::collections::BTreeMap; +use vir_crate::{ + common::graphviz::ToGraphviz, + low::{self as vir_low, expression::visitors::ExpressionFolder}, +}; + +pub(in super::super) fn desugar_fold_unfold( + source_filename: &str, + mut program: vir_low::Program, + predicates_info: &BTreeMap, +) -> vir_low::Program { + let predicate_decls = program + .predicates + .iter() + .map(|predicate| (predicate.name.clone(), predicate)) + .collect(); + let function_decls = program + .functions + .iter() + .map(|function| (function.name.clone(), function)) + .collect(); + for procedure in std::mem::take(&mut program.procedures) { + if prusti_common::config::dump_debug_info() { + prusti_common::report::log::report_with_writer( + "graphviz_method_vir_low_before_desugar_fold_unfold", + format!("{}.{}.dot", source_filename, procedure.name), + |writer| procedure.to_graphviz(writer).unwrap(), + ); + } + let new_procedure = desugar_fold_unfold_in_procedure( + procedure, + &predicate_decls, + &function_decls, + predicates_info, + ); + if prusti_common::config::dump_debug_info() { + prusti_common::report::log::report_with_writer( + "graphviz_method_vir_low_after_desugar_fold_unfold", + format!("{}.{}.dot", source_filename, new_procedure.name), + |writer| new_procedure.to_graphviz(writer).unwrap(), + ); + } + program.procedures.push(new_procedure); + } + program +} + +fn desugar_fold_unfold_in_procedure( + mut procedure: vir_low::ProcedureDecl, + predicate_decls: &BTreeMap, + function_decls: &BTreeMap, + predicates_info: &BTreeMap, +) -> vir_low::ProcedureDecl { + let mut label_counter = 0; + for block in procedure.basic_blocks.values_mut() { + desugar_fold_unfold_in_statements( + std::mem::take(&mut block.statements), + &mut block.statements, + &mut procedure.custom_labels, + &mut label_counter, + predicate_decls, + function_decls, + predicates_info, + ); + } + procedure +} + +fn desugar_fold_unfold_in_statements( + original_statements: Vec, + new_statements: &mut Vec, + custom_labels: &mut Vec, + label_counter: &mut u32, + predicate_decls: &BTreeMap, + function_decls: &BTreeMap, + predicates_info: &BTreeMap, +) { + for statement in original_statements { + match statement { + vir_low::Statement::Fold(statement) => { + new_statements.push(vir_low::Statement::comment(format!("{statement}"))); + let old_label = new_label( + new_statements, + custom_labels, + label_counter, + statement.position, + ); + let predicate = extract_predicate(&statement.expression); + // assert!( + // predicate.permission.is_full_permission(), + // "unimplemented: {predicate}" + // ); + let predicate_info = predicates_info.get(&predicate.name).unwrap(); + let predicate_decl = predicate_decls.get(&predicate.name).unwrap(); + let body = get_predicate_body(&statement.expression, predicate_decls); + new_statements.push(vir_low::Statement::exhale(body, statement.position)); + new_statements.push(vir_low::Statement::inhale( + statement.expression.clone(), + statement.position, + )); + let (result, snapshot_call) = construct_snapshot_call_replacement( + &statement.expression, + predicates_info, + function_decls, + ); + let new_state_label = new_label( + new_statements, + custom_labels, + label_counter, + statement.position, + ); + let snapshot_call = vir_low::Expression::labelled_old( + Some(new_state_label), + snapshot_call, + statement.position, + ); + let mut replacements = + create_parameter_replacements(&predicate_decl.parameters, &predicate.arguments); + replacements.insert(&result, &snapshot_call); + let mut snapshot_postcondition = Vec::new(); + for assertion in &predicate_info.current_snapshot_function.postconditions { + snapshot_postcondition + .push(assertion.clone().substitute_variables(&replacements)); + } + for assertion in &predicate_info.current_snapshot_function.body { + let assertion = wrap_heap_dependent_calls_in_old( + assertion.clone(), + &old_label, + statement.position, + ); + snapshot_postcondition + .push(assertion.clone().substitute_variables(&replacements)); + } + + for assertion in snapshot_postcondition { + new_statements.push(vir_low::Statement::inhale(assertion, statement.position)); + } + } + vir_low::Statement::Unfold(statement) => { + new_statements.push(vir_low::Statement::comment(format!("{statement}"))); + let old_label = new_label( + new_statements, + custom_labels, + label_counter, + statement.position, + ); + let body = get_predicate_body(&statement.expression, predicate_decls); + let predicate = extract_predicate(&statement.expression); + // assert!( + // predicate.permission.is_full_permission(), + // "unimplemented: {predicate}" + // ); + let predicate_info = predicates_info.get(&predicate.name).unwrap(); + let (result, snapshot_call) = construct_snapshot_call_replacement( + &statement.expression, + predicates_info, + function_decls, + ); + let snapshot_call = vir_low::Expression::labelled_old( + Some(old_label), + snapshot_call, + statement.position, + ); + let predicate_decl = predicate_decls.get(&predicate.name).unwrap(); + let mut replacements = + create_parameter_replacements(&predicate_decl.parameters, &predicate.arguments); + replacements.insert(&result, &snapshot_call); + let mut snapshot_postcondition = Vec::new(); + for assertion in &predicate_info.current_snapshot_function.postconditions { + snapshot_postcondition + .push(assertion.clone().substitute_variables(&replacements)); + } + for assertion in &predicate_info.current_snapshot_function.body { + snapshot_postcondition + .push(assertion.clone().substitute_variables(&replacements)); + } + new_statements.push(vir_low::Statement::exhale( + statement.expression, + statement.position, + )); + new_statements.push(vir_low::Statement::inhale(body, statement.position)); + for assertion in snapshot_postcondition { + new_statements.push(vir_low::Statement::inhale(assertion, statement.position)); + } + } + vir_low::Statement::Conditional(mut statement) => { + desugar_fold_unfold_in_statements( + std::mem::take(&mut statement.then_branch), + &mut statement.then_branch, + custom_labels, + label_counter, + predicate_decls, + function_decls, + predicates_info, + ); + desugar_fold_unfold_in_statements( + std::mem::take(&mut statement.else_branch), + &mut statement.else_branch, + custom_labels, + label_counter, + predicate_decls, + function_decls, + predicates_info, + ); + new_statements.push(vir_low::Statement::Conditional(statement)); + } + _ => { + new_statements.push(statement); + } + }; + } +} + +fn wrap_heap_dependent_calls_in_old( + expression: vir_low::Expression, + old_label: &str, + position: vir_low::Position, +) -> vir_low::Expression { + struct Wrapper<'a> { + old_label: &'a str, + position: vir_low::Position, + } + impl<'a> ExpressionFolder for Wrapper<'a> { + fn fold_func_app_enum( + &mut self, + func_app: vir_low::expression::FuncApp, + ) -> vir_low::Expression { + let func_app = self.fold_func_app(func_app); + let expression = vir_low::Expression::FuncApp(func_app); + vir_low::Expression::labelled_old( + Some(self.old_label.to_string()), + expression, + self.position, + ) + } + } + let mut wrapper = Wrapper { + old_label, + position, + }; + wrapper.fold_expression(expression) +} + +fn new_label( + statements: &mut Vec, + custom_labels: &mut Vec, + label_counter: &mut u32, + position: vir_low::Position, +) -> String { + let old_label = format!("fold_unfold_label${label_counter}"); + custom_labels.push(vir_low::Label::new(old_label.clone())); + *label_counter += 1; + statements.push(vir_low::Statement::label(old_label.clone(), position)); + old_label +} + +fn extract_predicate( + expression: &vir_low::Expression, +) -> &vir_low::ast::expression::PredicateAccessPredicate { + let vir_low::Expression::PredicateAccessPredicate(predicate) = &expression else { + unreachable!("{expression}") + }; + predicate +} + +fn create_parameter_replacements<'a>( + parameters: &'a [vir_low::VariableDecl], + arguments: &'a [vir_low::Expression], +) -> FxHashMap<&'a vir_low::VariableDecl, &'a vir_low::Expression> { + assert_eq!(arguments.len(), parameters.len()); + parameters.iter().zip(arguments.iter()).collect() +} + +fn get_predicate_body( + expression: &vir_low::Expression, + predicate_decls: &BTreeMap, +) -> vir_low::Expression { + let predicate = extract_predicate(expression); + let predicate_permission = &predicate.permission; + let predicate_decl = predicate_decls.get(&predicate.name).unwrap(); + let body = predicate_decl.body.as_ref().unwrap().clone(); + let replacements = + create_parameter_replacements(&predicate_decl.parameters, &predicate.arguments); + body.substitute_variables(&replacements) + .replace_predicate_permissions(predicate_permission) +} + +fn construct_snapshot_call_replacement( + expression: &vir_low::Expression, + predicates_info: &BTreeMap, + function_decls: &BTreeMap, +) -> (vir_low::VariableDecl, vir_low::Expression) { + let predicate = extract_predicate(expression); + let predicate_info = predicates_info.get(&predicate.name).unwrap(); + let snapshot_function_name = &predicate_info.current_snapshot_function.function_name; + let function_decl = function_decls.get(snapshot_function_name).unwrap(); + assert!( + function_decl.body.is_none(), + "Snapshot functions are expected to be bodyless?" + ); + let call = vir_low::Expression::function_call( + snapshot_function_name.clone(), + predicate.arguments.clone(), + function_decl.return_type.clone(), + ); + let result = function_decl.result_variable(); + (result, call) +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/desugar_implications.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/desugar_implications.rs new file mode 100644 index 00000000000..b4489a3441f --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/desugar_implications.rs @@ -0,0 +1,186 @@ +use vir_crate::{ + common::graphviz::ToGraphviz, + low::{self as vir_low, operations::ty::Typed}, +}; + +pub(crate) fn desugar_implications( + source_filename: &str, + mut program: vir_low::Program, +) -> vir_low::Program { + let mut procedures = Vec::new(); + for procedure in std::mem::take(&mut program.procedures) { + let procedure = desugar_implications_in_procedure(procedure); + if prusti_common::config::dump_debug_info() { + prusti_common::report::log::report_with_writer( + "graphviz_method_vir_low_after_desugar_implications", + format!("{}.{}.dot", source_filename, procedure.name), + |writer| procedure.to_graphviz(writer).unwrap(), + ); + } + procedures.push(procedure); + } + program.procedures = procedures; + program +} + +fn desugar_implications_in_procedure( + mut procedure: vir_low::ProcedureDecl, +) -> vir_low::ProcedureDecl { + let mut label_counter: u64 = 0; + for block in procedure.basic_blocks.values_mut() { + desugar_statements( + std::mem::take(&mut block.statements), + &mut block.statements, + &mut procedure.custom_labels, + &mut label_counter, + ); + } + procedure +} + +fn desugar_statements( + old_statements: Vec, + new_statements: &mut Vec, + custom_labels: &mut Vec, + label_counter: &mut u64, +) { + for statement in old_statements { + match statement { + vir_low::Statement::Assume(statement) => { + desugar_expression( + new_statements, + statement.expression, + statement.position, + &mut vir_low::Statement::assume, + ); + } + vir_low::Statement::Assert(statement) => { + desugar_expression( + new_statements, + statement.expression, + statement.position, + &mut vir_low::Statement::assert, + ); + } + vir_low::Statement::Inhale(statement) => { + desugar_expression( + new_statements, + statement.expression, + statement.position, + &mut vir_low::Statement::inhale, + ); + } + vir_low::Statement::Exhale(statement) => { + let label = format!("desugar_impls_label${label_counter}"); + *label_counter += 1; + let expression = statement.expression.wrap_in_old(&label); + new_statements.push(vir_low::Statement::label(label.clone(), statement.position)); + custom_labels.push(vir_low::Label::new(label)); + desugar_expression( + new_statements, + expression, + statement.position, + &mut vir_low::Statement::exhale, + ); + } + vir_low::Statement::Conditional(mut statement) => { + desugar_statements( + std::mem::take(&mut statement.then_branch), + &mut statement.then_branch, + custom_labels, + label_counter, + ); + desugar_statements( + std::mem::take(&mut statement.else_branch), + &mut statement.else_branch, + custom_labels, + label_counter, + ); + new_statements.push(vir_low::Statement::Conditional(statement)); + } + _ => { + new_statements.push(statement); + } + } + } +} + +fn desugar_expression( + statements: &mut Vec, + expression: vir_low::Expression, + position: vir_low::Position, + statement_constructor: &mut impl FnMut(vir_low::Expression, vir_low::Position) -> vir_low::Statement, +) { + match expression { + // _ if expression.is_pure() => { + // statements.push(statement_constructor(expression, position)); + // } + vir_low::Expression::BinaryOp(binary_op_expression) + if matches!( + binary_op_expression.op_kind, + vir_low::BinaryOpKind::And | vir_low::BinaryOpKind::Implies + ) => + { + match binary_op_expression.op_kind { + vir_low::BinaryOpKind::And => { + desugar_expression( + statements, + *binary_op_expression.left, + position, + statement_constructor, + ); + desugar_expression( + statements, + *binary_op_expression.right, + position, + statement_constructor, + ); + } + vir_low::BinaryOpKind::Implies => { + let mut then_statements = Vec::new(); + desugar_expression( + &mut then_statements, + *binary_op_expression.right, + position, + statement_constructor, + ); + statements.push(vir_low::Statement::conditional( + *binary_op_expression.left, + then_statements, + Vec::new(), + position, + )); + } + _ => { + unreachable!(); + } + } + } + vir_low::Expression::Conditional(conditional_expression) => { + assert_eq!(conditional_expression.get_type(), &vir_low::Type::Bool); + let mut then_statements = Vec::new(); + desugar_expression( + &mut then_statements, + *conditional_expression.then_expr, + position, + statement_constructor, + ); + let mut else_statements = Vec::new(); + desugar_expression( + &mut else_statements, + *conditional_expression.else_expr, + position, + statement_constructor, + ); + statements.push(vir_low::Statement::conditional( + *conditional_expression.guard, + then_statements, + else_statements, + position, + )); + } + _ => { + statements.push(statement_constructor(expression, position)); + } + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/desugar_method_calls.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/desugar_method_calls.rs new file mode 100644 index 00000000000..82d95267c8f --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/desugar_method_calls.rs @@ -0,0 +1,132 @@ +use rustc_hash::FxHashMap; +use vir_crate::{ + common::{expression::ExpressionIterator, graphviz::ToGraphviz}, + low::{self as vir_low}, +}; + +pub(in super::super) fn desugar_method_calls( + source_filename: &str, + mut program: vir_low::Program, +) -> vir_low::Program { + let mut procedures = Vec::new(); + let methods: FxHashMap<_, _> = program + .methods + .iter() + .map(|procedure| (&procedure.name, procedure)) + .collect(); + for procedure in std::mem::take(&mut program.procedures) { + let procedure = desugar_method_calls_for_procedure(source_filename, &methods, procedure); + procedures.push(procedure); + } + program.procedures = procedures; + program +} + +pub(in super::super) fn desugar_method_calls_for_procedure( + source_filename: &str, + methods: &FxHashMap<&String, &vir_low::MethodDecl>, + mut procedure: vir_low::ProcedureDecl, +) -> vir_low::ProcedureDecl { + let mut label_counter = 0; + for block in procedure.basic_blocks.values_mut() { + block.statements = desugar_method_calls_for_statements( + methods, + &mut label_counter, + &mut procedure.custom_labels, + std::mem::take(&mut block.statements), + ); + } + if prusti_common::config::dump_debug_info() { + prusti_common::report::log::report_with_writer( + "graphviz_method_vir_low_after_desugar_method_calls", + format!("{}.{}.dot", source_filename, procedure.name), + |writer| procedure.to_graphviz(writer).unwrap(), + ); + } + procedure +} + +pub(in super::super) fn desugar_method_calls_for_statements( + methods: &FxHashMap<&String, &vir_low::MethodDecl>, + label_counter: &mut usize, + custom_labels: &mut Vec, + original_statements: Vec, +) -> Vec { + let mut statements = Vec::new(); + for statement in original_statements { + match statement { + vir_low::Statement::MethodCall(statement) => { + statements.push(vir_low::Statement::comment(format!("{statement}"))); + let old_label = format!("method_call_label${label_counter}"); + custom_labels.push(vir_low::Label::new(old_label.clone())); + *label_counter += 1; + statements.push(vir_low::Statement::label( + old_label.clone(), + statement.position, + )); + let method = methods.get(&statement.method_name).unwrap_or_else(|| { + panic!( + "Method `{}` not found in the list of methods: {:?}", + statement.method_name, + methods.keys() + ) + }); + let arguments: Vec<_> = statement + .arguments + .iter() + .map(|argument| { + vir_low::Expression::labelled_old( + Some(old_label.clone()), + argument.clone(), + statement.position, + ) + }) + .collect(); + let mut replacements = method.parameters.iter().zip(arguments.iter()).collect(); + let assertion = method + .pres + .clone() + .into_iter() + .conjoin() + .substitute_variables(&replacements) + .remove_unnecessary_old(); + statements.push( + vir_low::Statement::exhale_no_pos(assertion) + .set_default_position(statement.position), + ); + replacements.extend(method.targets.iter().zip(statement.targets.iter())); + let assertion = method + .posts + .clone() + .into_iter() + .conjoin() + .substitute_variables(&replacements) + .set_old_label(&old_label) + .remove_unnecessary_old(); + statements.push( + vir_low::Statement::inhale_no_pos(assertion) + .set_default_position(statement.position), + ); + } + vir_low::Statement::Conditional(mut statement) => { + statement.then_branch = desugar_method_calls_for_statements( + methods, + label_counter, + custom_labels, + std::mem::take(&mut statement.then_branch), + ); + statement.else_branch = desugar_method_calls_for_statements( + methods, + label_counter, + custom_labels, + std::mem::take(&mut statement.else_branch), + ); + statements.push(vir_low::Statement::Conditional(statement)); + } + _ => { + statements.push(statement); + } + } + } + statements +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/encoder_context.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/encoder_context.rs new file mode 100644 index 00000000000..6f6e1196635 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/encoder_context.rs @@ -0,0 +1,32 @@ +use crate::encoder::{errors::ErrorCtxt, mir::errors::ErrorInterface, Encoder}; +use prusti_rustc_interface::errors::MultiSpan; +use vir_crate::low::{self as vir_low}; + +pub trait EncoderContext { + fn get_span(&mut self, position: vir_low::Position) -> Option; + fn change_error_context( + &mut self, + position: vir_low::Position, + error_ctxt: ErrorCtxt, + ) -> vir_low::Position; + fn get_error_context(&mut self, position: vir_low::Position) -> ErrorCtxt; +} + +impl<'v, 'tcx: 'v> EncoderContext for Encoder<'v, 'tcx> { + fn get_span(&mut self, position: vir_low::Position) -> Option { + self.error_manager() + .position_manager() + .get_span(position.into()) + .cloned() + } + fn change_error_context( + &mut self, + position: vir_low::Position, + error_ctxt: ErrorCtxt, + ) -> vir_low::Position { + ErrorInterface::change_error_context(self, position, error_ctxt) + } + fn get_error_context(&mut self, position: vir_low::Position) -> ErrorCtxt { + ErrorInterface::get_error_context(self, position) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/expand_quantifiers.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/expand_quantifiers.rs new file mode 100644 index 00000000000..540dbabfedc --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/expand_quantifiers.rs @@ -0,0 +1,114 @@ +use vir_crate::{ + common::graphviz::ToGraphviz, + low::{self as vir_low}, +}; + +pub(crate) fn expand_quantifiers( + source_filename: &str, + mut program: vir_low::Program, +) -> vir_low::Program { + let mut procedures = Vec::new(); + for procedure in std::mem::take(&mut program.procedures) { + let procedure = expand_quantifiers_in_procedure(procedure); + if prusti_common::config::dump_debug_info() { + prusti_common::report::log::report_with_writer( + "graphviz_method_vir_low_after_expand_quantifiers", + format!("{}.{}.dot", source_filename, procedure.name), + |writer| procedure.to_graphviz(writer).unwrap(), + ); + } + procedures.push(procedure); + } + program.procedures = procedures; + program +} + +fn expand_quantifiers_in_procedure( + mut procedure: vir_low::ProcedureDecl, +) -> vir_low::ProcedureDecl { + let mut counter = 0; + for block in procedure.basic_blocks.values_mut() { + for statement in std::mem::take(&mut block.statements) { + match statement { + vir_low::Statement::Assert(statement) => { + desugar_expression( + &mut counter, + &mut procedure.locals, + &mut block.statements, + statement.expression, + statement.position, + &mut vir_low::Statement::assert, + ); + } + vir_low::Statement::Exhale(statement) if statement.expression.is_pure() => { + desugar_expression( + &mut counter, + &mut procedure.locals, + &mut block.statements, + statement.expression, + statement.position, + &mut vir_low::Statement::exhale, + ); + } + _ => { + block.statements.push(statement); + } + } + } + } + procedure +} + +fn desugar_expression( + variable_counter: &mut usize, + locals: &mut Vec, + statements: &mut Vec, + expression: vir_low::Expression, + position: vir_low::Position, + statement_constructor: &mut impl FnMut(vir_low::Expression, vir_low::Position) -> vir_low::Statement, +) { + match expression { + vir_low::Expression::Quantifier(quantifier) + if quantifier.kind == vir_low::QuantifierKind::ForAll => + { + statements.push(vir_low::Statement::comment(format!( + "desugaring forall {quantifier}" + ))); + let mut variable_expressions = Vec::new(); + for bound_variable in &quantifier.variables { + let variable = vir_low::VariableDecl::new( + format!("{}$quantifier${}", bound_variable.name, variable_counter), + bound_variable.ty.clone(), + ); + *variable_counter += 1; + statements.push(vir_low::Statement::comment(format!( + " {bound_variable} → {variable}" + ))); + locals.push(variable.clone()); + variable_expressions.push(variable.clone().into()); + } + let replacements = quantifier + .variables + .iter() + .zip(variable_expressions.iter()) + .collect(); + let body = quantifier.body.substitute_variables(&replacements); + let assertion = match body { + vir_low::Expression::BinaryOp(binary_expression) + if binary_expression.op_kind == vir_low::BinaryOpKind::Implies => + { + statements.push(vir_low::Statement::assume( + *binary_expression.left, + position, + )); + *binary_expression.right + } + body => body, + }; + statements.push(vir_low::Statement::assert(assertion, position)); + } + _ => { + statements.push(statement_constructor(expression, position)); + } + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/inline_functions.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/inline_functions.rs index f7878f44769..aafd353fdd5 100644 --- a/prusti-viper/src/encoder/middle/core_proof/transformations/inline_functions.rs +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/inline_functions.rs @@ -1,63 +1,105 @@ +use std::collections::BTreeSet; + use rustc_hash::FxHashMap; use vir_crate::{ - common::expression::{ExpressionIterator, UnaryOperationHelpers}, - low::{self as vir_low}, + common::{ + expression::{ExpressionIterator, UnaryOperationHelpers}, + graphviz::ToGraphviz, + }, + low::{self as vir_low, operations::quantifiers::BoundVariableStack}, }; use vir_low::expression::visitors::ExpressionFolder; -pub(crate) fn inline_caller_for(program: &mut vir_low::Program) { - let caller_for_functions = program +pub(crate) fn inline_caller_for(source_filename: &str, program: &mut vir_low::Program) { + let mut caller_for_functions = program .functions .drain_filter(|function| function.kind == vir_low::FunctionKind::CallerFor) .map(|function| (function.name.clone(), function)) .collect(); + let mut failed_to_inline_functions = Default::default(); for procedure in &mut program.procedures { - for block in &mut procedure.basic_blocks { - inline_in_statements(&mut block.statements, &caller_for_functions); + let mut inliner = Inliner { + caller_for_functions: &caller_for_functions, + statements: Vec::new(), + path_condition: Vec::new(), + inside_trigger: false, + bound_variable_stack: Default::default(), + failed_to_inline_functions: &mut failed_to_inline_functions, + }; + for block in procedure.basic_blocks.values_mut() { + inline_in_statements(&mut inliner, std::mem::take(&mut block.statements)); + match &mut block.successor { + vir_low::Successor::Return => {} + vir_low::Successor::Goto(_) => {} + vir_low::Successor::GotoSwitch(targets) => { + let mut new_targets = Vec::new(); + for (guard, target) in std::mem::take(targets) { + let guard = inliner.fold_expression(guard); + new_targets.push((guard, target)); + } + block.successor = vir_low::Successor::GotoSwitch(new_targets); + } + } + block.statements = std::mem::take(&mut inliner.statements); } + if prusti_common::config::dump_debug_info() { + prusti_common::report::log::report_with_writer( + "graphviz_method_vir_low_after_inline_caller_for", + format!("{}.{}.dot", source_filename, procedure.name), + |writer| procedure.to_graphviz(writer).unwrap(), + ); + } + } + for function_name in failed_to_inline_functions { + program + .functions + .push(caller_for_functions.remove(&function_name).unwrap()); } } -fn inline_in_statements( - statements: &mut Vec, - caller_for_functions: &FxHashMap, -) { - let old_statements = std::mem::take(statements); - let mut inliner = Inliner { - caller_for_functions, - statements, - path_condition: Vec::new(), - }; - let mut sentinel = true.into(); +fn inline_in_statements(inliner: &mut Inliner, old_statements: Vec) { for statement in old_statements { assert!(inliner.path_condition.is_empty()); match statement { - vir_low::Statement::Assume(mut statement) => { - sentinel = - inliner.fold_expression(std::mem::replace(&mut statement.expression, sentinel)); - std::mem::swap(&mut statement.expression, &mut sentinel); - inliner - .statements - .push(vir_low::Statement::Assume(statement)); + vir_low::Statement::Assume(statement) => { + inliner.inline_statement( + statement.expression, + statement.position, + vir_low::Statement::assume, + ); } - vir_low::Statement::Assert(mut statement) => { - sentinel = - inliner.fold_expression(std::mem::replace(&mut statement.expression, sentinel)); - std::mem::swap(&mut statement.expression, &mut sentinel); - inliner - .statements - .push(vir_low::Statement::Assert(statement)); + vir_low::Statement::Assert(statement) => { + inliner.inline_statement( + statement.expression, + statement.position, + vir_low::Statement::assert, + ); + } + vir_low::Statement::Inhale(statement) => { + inliner.inline_statement( + statement.expression, + statement.position, + vir_low::Statement::inhale, + ); + } + vir_low::Statement::Exhale(statement) => { + inliner.inline_statement( + statement.expression, + statement.position, + vir_low::Statement::exhale, + ); } vir_low::Statement::Comment(_) + | vir_low::Statement::Label(_) | vir_low::Statement::LogEvent(_) - | vir_low::Statement::Inhale(_) - | vir_low::Statement::Exhale(_) | vir_low::Statement::Fold(_) | vir_low::Statement::Unfold(_) | vir_low::Statement::ApplyMagicWand(_) | vir_low::Statement::MethodCall(_) | vir_low::Statement::Assign(_) - | vir_low::Statement::Conditional(_) => { + | vir_low::Statement::Conditional(_) + | vir_low::Statement::MaterializePredicate(_) + | vir_low::Statement::CaseSplit(_) => { inliner.statements.push(statement); } } @@ -66,11 +108,35 @@ fn inline_in_statements( struct Inliner<'a> { caller_for_functions: &'a FxHashMap, - statements: &'a mut Vec, + statements: Vec, path_condition: Vec, + inside_trigger: bool, + bound_variable_stack: BoundVariableStack, + failed_to_inline_functions: &'a mut BTreeSet, +} + +impl<'a> Inliner<'a> { + fn inline_statement( + &mut self, + expression: vir_low::Expression, + position: vir_low::Position, + constructor: fn(vir_low::Expression, vir_low::Position) -> vir_low::Statement, + ) { + let expression = self.fold_expression(expression); + let statement = constructor(expression, position); + self.statements.push(statement); + } } impl<'a> ExpressionFolder for Inliner<'a> { + fn fold_quantifier_enum(&mut self, quantifier: vir_low::Quantifier) -> vir_low::Expression { + let mut quantifier = quantifier; + self.bound_variable_stack.push(&quantifier.variables); + quantifier = self.fold_quantifier(quantifier); + self.bound_variable_stack.pop(); + quantifier.into() + } + fn fold_binary_op( &mut self, mut binary_op: vir_low::expression::BinaryOp, @@ -101,36 +167,61 @@ impl<'a> ExpressionFolder for Inliner<'a> { conditional } + fn fold_trigger(&mut self, mut trigger: vir_low::Trigger) -> vir_low::Trigger { + let old_inside_trigger = self.inside_trigger; + self.inside_trigger = true; + for term in std::mem::take(&mut trigger.terms) { + let term = self.fold_expression(term); + trigger.terms.push(term); + } + self.inside_trigger = old_inside_trigger; + trigger + } + fn fold_func_app_enum( &mut self, func_app: vir_low::expression::FuncApp, ) -> vir_low::Expression { if let Some(function) = self.caller_for_functions.get(&func_app.function_name) { - let path_condition = self.path_condition.iter().cloned().conjoin(); - assert_eq!(function.parameters.len(), func_app.arguments.len()); - let arguments: Vec<_> = func_app - .arguments - .into_iter() - .map(|argument| self.fold_expression(argument)) - .collect(); - let replacements = function.parameters.iter().zip(arguments.iter()).collect(); - let pres = function - .pres - .iter() - .cloned() - .conjoin() - .substitute_variables(&replacements); - use vir_low::macros::*; - self.statements.push(stmtp! { func_app.position => - assert ([path_condition] ==> [pres]) - }); - function - .body - .clone() - .unwrap() - .substitute_variables(&replacements) - } else { - vir_low::Expression::FuncApp(self.fold_func_app(func_app)) + if (!self + .bound_variable_stack + .expressions_contains_bound_variables(&func_app.arguments) + && !self + .bound_variable_stack + .expressions_contains_bound_variables(&self.path_condition)) + || self.inside_trigger + { + assert_eq!(function.parameters.len(), func_app.arguments.len()); + let arguments: Vec<_> = func_app + .arguments + .into_iter() + .map(|argument| self.fold_expression(argument)) + .collect(); + let replacements = function.parameters.iter().zip(arguments.iter()).collect(); + if !self.inside_trigger { + let path_condition = self.path_condition.iter().cloned().conjoin(); + let pres = function + .pres + .iter() + .cloned() + .conjoin() + .substitute_variables(&replacements); + use vir_low::macros::*; + self.statements.push(stmtp! { func_app.position => + assert ([path_condition] ==> [pres]) + }); + } + let new_function = function + .body + .clone() + .unwrap() + .substitute_variables(&replacements); + return new_function; + } else { + self.failed_to_inline_functions + .insert(func_app.function_name.clone()); + } } + vir_low::Expression::FuncApp(self.fold_func_app(func_app)) } } diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/make_all_jumps_nondeterministic.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/make_all_jumps_nondeterministic.rs new file mode 100644 index 00000000000..d4466d133e2 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/make_all_jumps_nondeterministic.rs @@ -0,0 +1,97 @@ +use vir_crate::{ + common::{ + cfg::Cfg, + expression::{BinaryOperationHelpers, ExpressionIterator, UnaryOperationHelpers}, + graphviz::ToGraphviz, + }, + low::{self as vir_low}, +}; + +/// Move jump condition to from the guard to the assumption statement at the +/// beginning of the block. +pub(in super::super) fn make_all_jumps_nondeterministic( + source_filename: &str, + mut program: vir_low::Program, +) -> vir_low::Program { + for procedure in std::mem::take(&mut program.procedures) { + if prusti_common::config::dump_debug_info() { + prusti_common::report::log::report_with_writer( + "graphviz_method_vir_low_before_make_all_jumps_nondeterministic", + format!("{}.{}.dot", source_filename, procedure.name), + |writer| procedure.to_graphviz(writer).unwrap(), + ); + } + let new_procedure = make_all_jumps_nondeterministic_in_procedure(procedure); + if prusti_common::config::dump_debug_info() { + prusti_common::report::log::report_with_writer( + "graphviz_method_vir_low_after_make_all_jumps_nondeterministic", + format!("{}.{}.dot", source_filename, new_procedure.name), + |writer| new_procedure.to_graphviz(writer).unwrap(), + ); + } + program.procedures.push(new_procedure); + } + program +} + +fn make_all_jumps_nondeterministic_in_procedure( + mut procedure: vir_low::ProcedureDecl, +) -> vir_low::ProcedureDecl { + let predecessors = procedure.predecessors_owned(); + let mut counter = 0; + let mut non_deterministic_choice_counter = 0; + let mut intermediate_blocks = Vec::new(); + let mut insert_at_start = Vec::new(); + for (source, block) in &mut procedure.basic_blocks { + match &mut block.successor { + vir_low::Successor::Return | vir_low::Successor::Goto(_) => {} + vir_low::Successor::GotoSwitch(expressions) => { + let mut negated_conditions = Vec::new(); + let variable = vir_low::VariableDecl::new( + format!("non_det_branch_choice${}", non_deterministic_choice_counter), + vir_low::Type::Int, + ); + non_deterministic_choice_counter += 1; + for (i, (condition, target)) in expressions.iter_mut().enumerate() { + let condition = std::mem::replace( + condition, + vir_low::Expression::equals(variable.clone().into(), i.into()), + ); + let assume_condition = vir_low::Statement::assume( + vir_low::Expression::and( + negated_conditions.clone().into_iter().conjoin(), + condition.clone(), + ), + procedure.position, + ); + if predecessors[target].len() > 1 { + let intermediate_label = vir_low::Label::new(format!( + "{}__{}__{}", + source.name, target.name, counter + )); + counter += 1; + let target = std::mem::replace(target, intermediate_label.clone()); + let intermediate_block = vir_low::BasicBlock { + statements: vec![assume_condition], + successor: vir_low::Successor::Goto(target), + }; + intermediate_blocks.push((intermediate_label, intermediate_block)); + } else { + insert_at_start.push((target.clone(), assume_condition)); + } + negated_conditions.push(UnaryOperationHelpers::not(condition)); + } + } + } + } + procedure.basic_blocks.extend(intermediate_blocks); + for (target, assume_condition) in insert_at_start { + procedure + .basic_blocks + .get_mut(&target) + .unwrap() + .statements + .insert(0, assume_condition); + } + procedure +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/merge_consequent_blocks.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/merge_consequent_blocks.rs new file mode 100644 index 00000000000..6234e92b26e --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/merge_consequent_blocks.rs @@ -0,0 +1,96 @@ +use vir_crate::{ + common::{cfg::Cfg, graphviz::ToGraphviz}, + low::{self as vir_low}, +}; + +/// Merges consequent basic blocks into one. +pub(in super::super) fn merge_consequent_blocks( + source_filename: &str, + mut program: vir_low::Program, +) -> vir_low::Program { + for procedure in std::mem::take(&mut program.procedures) { + if prusti_common::config::dump_debug_info() { + prusti_common::report::log::report_with_writer( + "graphviz_method_vir_low_before_merge_consequent_blocks", + format!("{}.{}.dot", source_filename, procedure.name), + |writer| procedure.to_graphviz(writer).unwrap(), + ); + } + let new_procedure = merge_consequent_blocks_in_procedure(procedure); + if prusti_common::config::dump_debug_info() { + prusti_common::report::log::report_with_writer( + "graphviz_method_vir_low_after_merge_consequent_blocks", + format!("{}.{}.dot", source_filename, new_procedure.name), + |writer| new_procedure.to_graphviz(writer).unwrap(), + ); + } + program.procedures.push(new_procedure); + } + program +} + +fn merge_consequent_blocks_in_procedure( + mut procedure: vir_low::ProcedureDecl, +) -> vir_low::ProcedureDecl { + let traversal_order = procedure.get_topological_sort(); + let predecessors = procedure.predecessors_owned(); + for label in traversal_order { + if let Some(mut block) = procedure.basic_blocks.get(&label) { + while let vir_low::Successor::Goto(target) = &block.successor { + if predecessors[target].len() == 1 { + let target = target.clone(); + let mut target_block = procedure.basic_blocks.remove(&target).unwrap(); + let mut source_block = procedure.basic_blocks.get_mut(&label).unwrap(); + source_block + .statements + .push(vir_low::Statement::comment(format!( + "Merged in block: {}", + target + ))); + source_block + .statements + .push(vir_low::Statement::label_no_pos(target.name.clone())); + source_block.statements.append(&mut target_block.statements); + source_block.successor = target_block.successor; + if let Some(next_block) = procedure.basic_blocks.get(&label) { + block = next_block; + } else { + break; + } + } else { + break; + } + } + } + } + + // let mut edges_to_remove = Vec::new(); + // for (source, block) in &procedure.basic_blocks { + // match &block.successor { + // vir_low::Successor::Goto(target) => { + // if predecessors[target].len() == 1 { + // edges_to_remove.push((source.clone(), target.clone())); + // } + // } + // vir_low::Successor::Return | vir_low::Successor::GotoSwitch(_) => {} + // } + // } + // let mut groups: Vec = (0..edges_to_remove.len()).into_iter().collect(); + // for (i, (source1, target1)) in edges_to_remove.iter().enumerate() { + // for (j, (source2, target2)) in edges_to_remove[i + 1..].iter().enumerate() { + // assert!(!(source1 == source2 && target1 == target2)); + // if target1 == source2 { + // let j = j + i + 1; + // groups[j] = groups[i]; + // } + // } + // } + // for i in 0..edges_to_remove.len() { + // let (source, target) = &edges_to_remove[i]; + // let (main_source, _) = &edges_to_remove[groups[i]]; + // // Merge target block into main_source block. + // let main_source_block = procedure.basic_blocks.get_mut(main_source).unwrap(); + + // } + procedure +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/merge_statements.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/merge_statements.rs new file mode 100644 index 00000000000..d9905d72f37 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/merge_statements.rs @@ -0,0 +1,176 @@ +use prusti_common::config; +use vir_crate::{ + common::{expression::ExpressionIterator, graphviz::ToGraphviz, position::Positioned}, + low::{self as vir_low}, +}; + +/// The transformations performed: +/// +/// 1. Remove all unused labels. +pub(in super::super) fn merge_statements( + source_filename: &str, + mut program: vir_low::Program, +) -> vir_low::Program { + for procedure in std::mem::take(&mut program.procedures) { + if prusti_common::config::dump_debug_info() { + prusti_common::report::log::report_with_writer( + "graphviz_method_vir_low_before_merge_statements", + format!("{}.{}.dot", source_filename, procedure.name), + |writer| procedure.to_graphviz(writer).unwrap(), + ); + } + let new_procedure = merge_statements_in_procedure(procedure); + if prusti_common::config::dump_debug_info() { + prusti_common::report::log::report_with_writer( + "graphviz_method_vir_low_after_merge_statements", + format!("{}.{}.dot", source_filename, new_procedure.name), + |writer| new_procedure.to_graphviz(writer).unwrap(), + ); + } + program.procedures.push(new_procedure); + } + program +} + +fn merge_statements_in_procedure(mut procedure: vir_low::ProcedureDecl) -> vir_low::ProcedureDecl { + for block in procedure.basic_blocks.values_mut() { + let statements = std::mem::take(&mut block.statements); + merge_statements_in_block(&mut block.statements, statements); + } + procedure +} + +#[derive(PartialEq, Eq, Hash)] +enum ExpressionKind { + None, + Inhale, + Exhale, + Assert, +} + +fn merge_statements_in_block( + new_statements: &mut Vec, + statements: Vec, +) { + let mut conjuncts = Vec::new(); + let mut expression_kind = ExpressionKind::None; + let mut last_position = None; + for statement in statements { + if let Some(current_last_position) = last_position { + if config::merge_consecutive_statements_same_pos() + && !conjuncts.is_empty() + && statement.position() != current_last_position + && expression_kind != ExpressionKind::Inhale + { + new_statements.push(create_statement( + &mut expression_kind, + &mut conjuncts, + &mut last_position, + )); + } + } + match statement { + vir_low::Statement::Comment(_) => {} + vir_low::Statement::Assume(statement) => { + if expression_kind != ExpressionKind::Inhale + && expression_kind != ExpressionKind::None + { + new_statements.push(create_statement( + &mut expression_kind, + &mut conjuncts, + &mut last_position, + )); + } + expression_kind = ExpressionKind::Inhale; + conjuncts.push(statement.expression); + last_position = Some(statement.position); + } + vir_low::Statement::Inhale(statement) => { + if expression_kind != ExpressionKind::Inhale + && expression_kind != ExpressionKind::None + { + new_statements.push(create_statement( + &mut expression_kind, + &mut conjuncts, + &mut last_position, + )); + } + expression_kind = ExpressionKind::Inhale; + conjuncts.push(statement.expression); + last_position = Some(statement.position); + } + vir_low::Statement::Assert(statement) + if !config::merge_consecutive_statements_only_inhale() => + { + if expression_kind != ExpressionKind::Assert + && expression_kind != ExpressionKind::None + { + new_statements.push(create_statement( + &mut expression_kind, + &mut conjuncts, + &mut last_position, + )); + } + expression_kind = ExpressionKind::Assert; + conjuncts.push(statement.expression); + if let Some(last_position) = last_position { + assert_eq!(last_position, statement.position); + } + last_position = Some(statement.position); + } + vir_low::Statement::Exhale(statement) + if !config::merge_consecutive_statements_only_inhale() => + { + if expression_kind != ExpressionKind::Exhale + && expression_kind != ExpressionKind::None + { + new_statements.push(create_statement( + &mut expression_kind, + &mut conjuncts, + &mut last_position, + )); + } + expression_kind = ExpressionKind::Exhale; + conjuncts.push(statement.expression); + if let Some(last_position) = last_position { + assert_eq!(last_position, statement.position); + } + last_position = Some(statement.position); + } + _ => { + if !conjuncts.is_empty() { + new_statements.push(create_statement( + &mut expression_kind, + &mut conjuncts, + &mut last_position, + )); + } + new_statements.push(statement); + } + } + } + if !conjuncts.is_empty() { + new_statements.push(create_statement( + &mut expression_kind, + &mut conjuncts, + &mut last_position, + )); + } +} + +fn create_statement( + expression_kind: &mut ExpressionKind, + conjuncts: &mut Vec, + position: &mut Option, +) -> vir_low::Statement { + let position = position.take().unwrap(); + let expression = std::mem::take(conjuncts).into_iter().conjoin(); + let statement = match expression_kind { + ExpressionKind::Assert => vir_low::Statement::assert(expression, position), + ExpressionKind::Inhale => vir_low::Statement::inhale(expression, position), + ExpressionKind::Exhale => vir_low::Statement::exhale(expression, position), + ExpressionKind::None => unreachable!(), + }; + *expression_kind = ExpressionKind::None; + statement +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/mod.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/mod.rs index b678daa9e5f..de10041f996 100644 --- a/prusti-viper/src/encoder/middle/core_proof/transformations/mod.rs +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/mod.rs @@ -1,3 +1,22 @@ pub(super) mod inline_functions; pub(super) mod remove_predicates; pub(super) mod remove_unvisited_blocks; +pub(super) mod custom_heap_encoding; +pub(super) mod desugar_fold_unfold; +pub(super) mod desugar_method_calls; +pub(super) mod desugar_conditionals; +pub(super) mod desugar_containers; +pub(super) mod predicate_domains; +pub(super) mod symbolic_execution; +pub(super) mod symbolic_execution_new; +pub(super) mod clean_old; +pub(super) mod clean_labels; +pub(super) mod clean_variables; +pub(super) mod merge_statements; +pub(super) mod desugar_implications; +pub(super) mod expand_quantifiers; +pub(super) mod name_quantifiers; +pub(super) mod encoder_context; +pub(super) mod make_all_jumps_nondeterministic; +pub(super) mod merge_consequent_blocks; +pub(super) mod case_splits; diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/name_quantifiers.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/name_quantifiers.rs new file mode 100644 index 00000000000..b1ca6c84bf2 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/name_quantifiers.rs @@ -0,0 +1,56 @@ +use vir_crate::low::{ + self as vir_low, + ast::statement::visitors::StatementFolder, + expression::visitors::{default_fold_quantifier, ExpressionFolder}, +}; + +pub(crate) fn name_quantifiers( + _source_filename: &str, + mut program: vir_low::Program, +) -> vir_low::Program { + for domain in &mut program.domains { + for axiom in std::mem::take(&mut domain.axioms) { + let axiom = name_quantifiers_in_axiom(&domain.name, axiom); + domain.axioms.push(axiom); + } + } + // for procedure in &mut program.procedures{ + // let procedure = expand_quantifiers_in_procedure(procedure); + // program.procedures.push(procedure); + // } + program +} + +fn name_quantifiers_in_axiom( + domain_name: &str, + mut axiom: vir_low::DomainAxiomDecl, +) -> vir_low::DomainAxiomDecl { + let mut namer = QuantifierNamer { + base_name: format!("{}${}", domain_name, axiom.name), + counter: 0, + }; + axiom.body = ExpressionFolder::fold_expression(&mut namer, axiom.body); + axiom +} + +struct QuantifierNamer { + base_name: String, + counter: usize, +} + +impl ExpressionFolder for QuantifierNamer { + fn fold_quantifier(&mut self, quantifier: vir_low::Quantifier) -> vir_low::Quantifier { + let mut quantifier = default_fold_quantifier(self, quantifier); + if quantifier.name.is_none() { + quantifier.name = Some(format!("{}${}", self.base_name, self.counter)); + self.counter += 1; + } + quantifier + } +} + +impl StatementFolder for QuantifierNamer { + fn fold_expression(&mut self, expression: vir_low::Expression) -> vir_low::Expression { + ExpressionFolder::fold_expression(self, expression) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/predicate_domains.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/predicate_domains.rs new file mode 100644 index 00000000000..9668f92fe8b --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/predicate_domains.rs @@ -0,0 +1,506 @@ +use rustc_hash::FxHashMap; +use std::collections::BTreeMap; +use vir_crate::{ + common::expression::{BinaryOperationHelpers, ExpressionIterator, QuantifierHelpers}, + low::{self as vir_low}, +}; + +use crate::encoder::middle::core_proof::predicates::OwnedPredicateInfo; + +pub(crate) struct PredicateDomainsInfo { + permission: FxHashMap, + heap: FxHashMap, +} + +impl PredicateDomainsInfo { + pub(crate) fn get_with_heap<'a>( + &'a self, + predicate_name: &str, + ) -> Option> { + let permission = self.permission.get(predicate_name)?; + let heap = self.heap.get(predicate_name)?; + Some(PredicateWithHeapDomainInfo { permission, heap }) + } + + pub(crate) fn get_permissions_info( + &self, + predicate_name: &str, + ) -> Option<&PredicatePermissionDomainInfo> { + self.permission.get(predicate_name) + } +} + +pub(crate) struct PredicatePermissionDomainInfo { + pub(crate) domain_name: String, + pub(crate) amount_type: vir_low::Type, + pub(crate) lookup_function_name: String, + pub(crate) set_full_function_name: String, + pub(crate) set_none_function_name: String, +} + +impl PredicatePermissionDomainInfo { + pub(crate) fn permission_mask_type(&self) -> vir_low::Type { + vir_low::Type::domain(self.domain_name.clone()) + } + + pub(crate) fn create_permission_mask_variable(&self, name: String) -> vir_low::VariableDecl { + vir_low::VariableDecl::new(name, self.permission_mask_type()) + } + + fn set_permissions( + &self, + setter: &str, + old_permission_mask: &vir_low::VariableDecl, + new_permission_mask: &vir_low::VariableDecl, + predicate_arguments: &[vir_low::Expression], + ) -> vir_low::Expression { + let mut arguments = Vec::with_capacity(2 + predicate_arguments.len()); + arguments.push(old_permission_mask.clone().into()); + arguments.push(new_permission_mask.clone().into()); + arguments.extend(predicate_arguments.iter().cloned()); + vir_low::Expression::domain_function_call( + self.domain_name.clone(), + setter.to_string(), + arguments, + vir_low::Type::Bool, + ) + } + + pub(crate) fn set_permissions_to_full( + &self, + old_permission_mask: &vir_low::VariableDecl, + new_permission_mask: &vir_low::VariableDecl, + predicate_arguments: &[vir_low::Expression], + ) -> vir_low::Expression { + self.set_permissions( + &self.set_full_function_name, + old_permission_mask, + new_permission_mask, + predicate_arguments, + ) + } + + pub(crate) fn set_permissions_to_none( + &self, + old_permission_mask: &vir_low::VariableDecl, + new_permission_mask: &vir_low::VariableDecl, + predicate_arguments: &[vir_low::Expression], + ) -> vir_low::Expression { + self.set_permissions( + &self.set_none_function_name, + old_permission_mask, + new_permission_mask, + predicate_arguments, + ) + } + + fn lookup_permission( + &self, + permission_mask: &vir_low::VariableDecl, + predicate_arguments: &[vir_low::VariableDecl], + ) -> vir_low::Expression { + let mut arguments = Vec::with_capacity(1 + predicate_arguments.len()); + arguments.push(permission_mask.clone().into()); + arguments.extend( + predicate_arguments + .iter() + .cloned() + .map(|parameter| parameter.into()), + ); + vir_low::Expression::domain_function_call( + self.domain_name.clone(), + self.lookup_function_name.clone(), + arguments, + self.amount_type.clone(), + ) + } + + pub(crate) fn check_permissions_full( + &self, + permission_mask: &vir_low::VariableDecl, + predicate_arguments: &[vir_low::Expression], + ) -> vir_low::Expression { + let mut arguments = Vec::with_capacity(1 + predicate_arguments.len()); + arguments.push(permission_mask.clone().into()); + arguments.extend(predicate_arguments.iter().cloned()); + + vir_low::Expression::domain_function_call( + self.domain_name.clone(), + self.lookup_function_name.clone(), + arguments, + self.amount_type.clone(), + ) + } +} + +pub(crate) struct PredicateHeapDomainInfo { + pub(crate) domain_name: String, + pub(crate) snapshot_type: vir_low::Type, + pub(crate) lookup_function_name: String, +} + +impl PredicateHeapDomainInfo { + pub(crate) fn heap_type(&self) -> vir_low::Type { + vir_low::Type::domain(self.domain_name.clone()) + } + + pub(crate) fn create_heap_variable(&self, name: String) -> vir_low::VariableDecl { + vir_low::VariableDecl::new(name, self.heap_type()) + } + + pub(crate) fn lookup_snapshot( + &self, + heap: &vir_low::VariableDecl, + predicate_arguments: &[vir_low::Expression], + ) -> vir_low::Expression { + let mut arguments = Vec::with_capacity(1 + predicate_arguments.len()); + arguments.push(heap.clone().into()); + arguments.extend(predicate_arguments.iter().cloned()); + vir_low::Expression::domain_function_call( + self.domain_name.clone(), + self.lookup_function_name.clone(), + arguments, + self.snapshot_type.clone(), + ) + } +} + +pub(crate) struct PredicateWithHeapDomainInfo<'a> { + pub(crate) permission: &'a PredicatePermissionDomainInfo, + pub(crate) heap: &'a PredicateHeapDomainInfo, +} + +impl<'a> PredicateWithHeapDomainInfo<'a> { + pub(crate) fn create_permission_mask_variable(&self, name: String) -> vir_low::VariableDecl { + self.permission.create_permission_mask_variable(name) + } + + pub(crate) fn set_permissions_to_full( + &self, + old_permission_mask: &vir_low::VariableDecl, + new_permission_mask: &vir_low::VariableDecl, + predicate_arguments: &[vir_low::Expression], + ) -> vir_low::Expression { + self.permission.set_permissions_to_full( + old_permission_mask, + new_permission_mask, + predicate_arguments, + ) + } + + pub(crate) fn set_permissions_to_none( + &self, + old_permission_mask: &vir_low::VariableDecl, + new_permission_mask: &vir_low::VariableDecl, + predicate_arguments: &[vir_low::Expression], + ) -> vir_low::Expression { + self.permission.set_permissions_to_none( + old_permission_mask, + new_permission_mask, + predicate_arguments, + ) + } + + pub(crate) fn check_permissions_full( + &self, + permission_mask: &vir_low::VariableDecl, + predicate_arguments: &[vir_low::Expression], + ) -> vir_low::Expression { + self.permission + .check_permissions_full(permission_mask, predicate_arguments) + } + + pub(crate) fn create_heap_variable(&self, name: String) -> vir_low::VariableDecl { + self.heap.create_heap_variable(name) + } + + pub(crate) fn lookup_snapshot( + &self, + heap: &vir_low::VariableDecl, + predicate_arguments: &[vir_low::Expression], + ) -> vir_low::Expression { + self.heap.lookup_snapshot(heap, predicate_arguments) + } +} + +pub(in super::super) fn define_predicate_domains( + _source_filename: &str, + mut program: vir_low::Program, + owned_predicate_info: &BTreeMap, +) -> (vir_low::Program, PredicateDomainsInfo) { + let mut domains_info = PredicateDomainsInfo { + permission: FxHashMap::default(), + heap: FxHashMap::default(), + }; + for predicate in &program.predicates { + match predicate.kind { + vir_low::PredicateKind::MemoryBlock => { + define_predicate_domain_for_boolean_mask( + &mut program.domains, + &mut domains_info, + predicate, + vir_low::macros::ty!(Bytes), + ); + } + vir_low::PredicateKind::Owned => { + define_predicate_domain_for_boolean_mask( + &mut program.domains, + &mut domains_info, + predicate, + owned_predicate_info + .get(&predicate.name) + .unwrap() + .snapshot_type + .clone(), + ); + } + vir_low::PredicateKind::LifetimeToken => { + // Lifetime tokens require no additional axioms. + } + vir_low::PredicateKind::CloseFracRef => todo!("predicate: {predicate}"), + vir_low::PredicateKind::WithoutSnapshotWhole => todo!("predicate: {predicate}"), + vir_low::PredicateKind::WithoutSnapshotWholeNonAliased => { + let permission_info = PredicatePermissionDomainInfo { + domain_name: format!("{}$Perm", predicate.name), + amount_type: vir_low::Type::Bool, + lookup_function_name: format!("{}$perm", predicate.name), + set_full_function_name: format!("{}$set_write", predicate.name), + set_none_function_name: format!("{}$set_none", predicate.name), + }; + let permission_domain = + create_permission_domain_for_boolean_mask(predicate, &permission_info); + program.domains.push(permission_domain); + assert!(domains_info + .permission + .insert(predicate.name.clone(), permission_info) + .is_none()); + } + vir_low::PredicateKind::DeadLifetimeToken => { + // Dead lifetime tokens require no additional axioms. + } + vir_low::PredicateKind::EndBorrowViewShift => todo!("predicate: {predicate}"), + } + } + (program, domains_info) +} + +fn define_predicate_domain_for_boolean_mask( + domains: &mut Vec, + domains_info: &mut PredicateDomainsInfo, + predicate: &vir_low::PredicateDecl, + snapshot_type: vir_low::Type, +) { + let permission_info = PredicatePermissionDomainInfo { + domain_name: format!("{}$Perm", predicate.name), + amount_type: vir_low::Type::Bool, + lookup_function_name: format!("{}$perm", predicate.name), + set_full_function_name: format!("{}$set_write", predicate.name), + set_none_function_name: format!("{}$set_none", predicate.name), + }; + let heap_info = PredicateHeapDomainInfo { + domain_name: format!("{}$Heap", predicate.name), + snapshot_type, + lookup_function_name: format!("{}$lookup", predicate.name), + }; + + let permission_domain = create_permission_domain_for_boolean_mask(predicate, &permission_info); + let heap_domain = create_heap_domain_for_boolean_mask(predicate, &heap_info); + + domains.push(permission_domain); + domains.push(heap_domain); + assert!(domains_info + .permission + .insert(predicate.name.clone(), permission_info) + .is_none()); + assert!(domains_info + .heap + .insert(predicate.name.clone(), heap_info) + .is_none()); +} + +fn create_permission_domain_for_boolean_mask( + predicate: &vir_low::PredicateDecl, + predicate_info: &PredicatePermissionDomainInfo, +) -> vir_low::DomainDecl { + use vir_low::macros::*; + + let mask = predicate_info.create_permission_mask_variable("mask".to_string()); + let mut lookup_parameters = Vec::with_capacity(1 + predicate.parameters.len()); + lookup_parameters.push(mask); + lookup_parameters.extend(predicate.parameters.iter().cloned()); + let lookup = vir_low::DomainFunctionDecl::new( + predicate_info.lookup_function_name.clone(), + false, + lookup_parameters, + predicate_info.amount_type.clone(), + ); + + let old_mask = predicate_info.create_permission_mask_variable("old_mask".to_string()); + let new_mask = predicate_info.create_permission_mask_variable("new_mask".to_string()); + let mut set_full_parameters = Vec::with_capacity(2 + predicate.parameters.len()); + set_full_parameters.push(old_mask.clone()); + set_full_parameters.push(new_mask.clone()); + set_full_parameters.extend(predicate.parameters.iter().cloned()); + let set_full = vir_low::DomainFunctionDecl::new( + predicate_info.set_full_function_name.clone(), + false, + set_full_parameters.clone(), + vir_low::Type::Bool, + ); + + let set_full_axiom = { + let old_lookup = predicate_info.lookup_permission(&old_mask, &predicate.parameters); + let new_lookup = predicate_info.lookup_permission(&new_mask, &predicate.parameters); + + let set_full_arguments = set_full_parameters + .iter() + .map(|parameter| parameter.clone().into()) + .collect(); + let set_full_call = vir_low::Expression::domain_function_call( + predicate_info.domain_name.clone(), + predicate_info.set_full_function_name.clone(), + set_full_arguments, + vir_low::Type::Bool, + ); + + let other_preserved = { + let parameters_nested: Vec<_> = predicate + .parameters + .iter() + .map(|parameter| { + vir_low::VariableDecl::new( + format!("{}$nested", parameter.name), + parameter.ty.clone(), + ) + }) + .collect(); + let old_lookup_nested = predicate_info.lookup_permission(&old_mask, ¶meters_nested); + let new_lookup_nested = predicate_info.lookup_permission(&new_mask, ¶meters_nested); + + vir_low::Expression::forall( + parameters_nested, + vec![vir_low::Trigger::new(vec![new_lookup_nested.clone()])], + expr! { [old_lookup_nested] ==> [new_lookup_nested] }, + ) + }; + + let set_full_definition = expr! { + (![old_lookup]) && + [new_lookup] && + [other_preserved] + }; + + let axiom_body = vir_low::Expression::forall( + set_full_parameters, + vec![vir_low::Trigger::new(vec![set_full_call.clone()])], + expr! { [set_full_call] == [set_full_definition]}, + ); + + vir_low::DomainAxiomDecl::new( + None, + format!("{}$definitional_axiom", set_full.name), + axiom_body, + ) + }; + + let mut set_none_parameters = Vec::with_capacity(2 + predicate.parameters.len()); + set_none_parameters.push(old_mask.clone()); + set_none_parameters.push(new_mask.clone()); + set_none_parameters.extend(predicate.parameters.iter().cloned()); + let set_none = vir_low::DomainFunctionDecl::new( + predicate_info.set_none_function_name.clone(), + false, + set_none_parameters.clone(), + vir_low::Type::Bool, + ); + + let set_none_axiom = { + let set_none_arguments = set_none_parameters + .iter() + .map(|parameter| parameter.clone().into()) + .collect(); + let set_none_call = vir_low::Expression::domain_function_call( + predicate_info.domain_name.clone(), + predicate_info.set_none_function_name.clone(), + set_none_arguments, + vir_low::Type::Bool, + ); + + let set_none_definition = { + let parameters_nested: Vec<_> = predicate + .parameters + .iter() + .map(|parameter| { + vir_low::VariableDecl::new( + format!("{}$nested", parameter.name), + parameter.ty.clone(), + ) + }) + .collect(); + let old_lookup_nested = predicate_info.lookup_permission(&old_mask, ¶meters_nested); + let new_lookup_nested = predicate_info.lookup_permission(&new_mask, ¶meters_nested); + let arguments_equal = predicate + .parameters + .iter() + .zip(parameters_nested.iter()) + .map(|(parameter, parameter_nested)| { + expr! { parameter == parameter_nested } + }) + .conjoin(); + + vir_low::Expression::forall( + parameters_nested, + vec![vir_low::Trigger::new(vec![new_lookup_nested.clone()])], + vir_low::Expression::implies( + expr! { ![arguments_equal] }, + expr! { [old_lookup_nested] == [new_lookup_nested] }, + ), + ) + }; + + let axiom_body = vir_low::Expression::forall( + set_none_parameters, + vec![vir_low::Trigger::new(vec![set_none_call.clone()])], + expr! { [set_none_call] == [set_none_definition]}, + ); + + vir_low::DomainAxiomDecl::new( + None, + format!("{}$definitional_axiom", set_none.name), + axiom_body, + ) + }; + + let functions = vec![lookup, set_full, set_none]; + let axioms = vec![set_full_axiom, set_none_axiom]; + vir_low::DomainDecl::new( + predicate_info.domain_name.clone(), + functions, + axioms, + Vec::new(), + ) +} + +fn create_heap_domain_for_boolean_mask( + predicate: &vir_low::PredicateDecl, + predicate_info: &PredicateHeapDomainInfo, +) -> vir_low::DomainDecl { + let heap = predicate_info.create_heap_variable("heap".to_string()); + let mut lookup_parameters = Vec::with_capacity(1 + predicate.parameters.len()); + lookup_parameters.push(heap); + lookup_parameters.extend(predicate.parameters.iter().cloned()); + let lookup = vir_low::DomainFunctionDecl::new( + predicate_info.lookup_function_name.clone(), + false, + lookup_parameters, + predicate_info.snapshot_type.clone(), + ); + + let functions = vec![lookup]; + vir_low::DomainDecl::new( + predicate_info.domain_name.clone(), + functions, + Vec::new(), + Vec::new(), + ) +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/remove_predicates.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/remove_predicates.rs index b53badeb770..ed65fe853e2 100644 --- a/prusti-viper/src/encoder/middle/core_proof/transformations/remove_predicates.rs +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/remove_predicates.rs @@ -1,5 +1,5 @@ use rustc_hash::{FxHashMap, FxHashSet}; -use vir_crate::low as vir_low; +use vir_crate::low::{self as vir_low, expression::visitors::default_fold_func_app}; use vir_low::expression::visitors::ExpressionFolder; pub(in super::super) fn remove_predicates( @@ -22,7 +22,7 @@ fn from_procedure( removed_functions: &FxHashSet, predicates: &FxHashMap, ) { - for block in &mut procedure.basic_blocks { + for block in procedure.basic_blocks.values_mut() { from_statements( &mut block.statements, removed_methods, @@ -63,10 +63,12 @@ fn from_statements( for statement in std::mem::take(statements) { match statement { vir_low::Statement::Comment(_) + | vir_low::Statement::Label(_) | vir_low::Statement::LogEvent(_) | vir_low::Statement::Assume(_) | vir_low::Statement::Assert(_) - | vir_low::Statement::Assign(_) => { + | vir_low::Statement::Assign(_) + | vir_low::Statement::CaseSplit(_) => { statements.push(statement); } vir_low::Statement::MethodCall(method) => { @@ -107,6 +109,7 @@ fn from_statements( ); statements.push(vir_low::Statement::Conditional(conditional)); } + vir_low::Statement::MaterializePredicate(_) => todo!(), } } } @@ -149,7 +152,7 @@ impl<'a> ExpressionFolder for PredicateRemover<'a> { if self.removed_functions.contains(&func_app.function_name) { self.drop_parent_binary_op = true; } - func_app + default_fold_func_app(self, func_app) } fn fold_binary_op_enum( &mut self, @@ -163,6 +166,12 @@ impl<'a> ExpressionFolder for PredicateRemover<'a> { vir_low::Expression::BinaryOp(binary_op) } } + fn fold_unfolding_enum( + &mut self, + unfolding: vir_low::expression::Unfolding, + ) -> vir_low::Expression { + self.fold_expression(*unfolding.base) + } } struct PredicateInliner<'a> { diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/remove_unvisited_blocks.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/remove_unvisited_blocks.rs index 90964cf6507..1ce12e1dbcc 100644 --- a/prusti-viper/src/encoder/middle/core_proof/transformations/remove_unvisited_blocks.rs +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/remove_unvisited_blocks.rs @@ -10,8 +10,8 @@ pub(in super::super) fn remove_unvisited_blocks( label_markers: &FxHashMap, ) -> SpannedEncodingResult<()> { for procedure in procedures { - for block in &mut procedure.basic_blocks { - if !label_markers.get(&block.label.name).unwrap_or(&true) { + for (label, block) in &mut procedure.basic_blocks { + if !label_markers.get(&label.name).unwrap_or(&true) { // The block was not visited. Replace with assume false. let mut position = None; for statement in &block.statements { diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/consistency_tracker.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/consistency_tracker.rs new file mode 100644 index 00000000000..7a2d90bf9da --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/consistency_tracker.rs @@ -0,0 +1,197 @@ +//! Tracks the values of boolean variables to catch when we enter an +//! inconsistent state. + +use crate::encoder::{ + errors::SpannedEncodingResult, middle::core_proof::snapshots::SnapshotDomainInfo, +}; +use std::collections::BTreeMap; +use vir_crate::{ + common::expression::SyntacticEvaluation, + low::{self as vir_low, operations::ty::Typed}, +}; + +#[derive(Clone)] +pub(super) struct ConsistencyTracker { + /// The current values of the boolean variables. + variables: BTreeMap, + is_inconsistent: bool, + bool_type: vir_low::Type, + bool_domain_info: SnapshotDomainInfo, +} + +impl ConsistencyTracker { + pub(super) fn new(bool_type: vir_low::Type, bool_domain_info: SnapshotDomainInfo) -> Self { + Self { + variables: BTreeMap::new(), + is_inconsistent: false, + bool_type, + bool_domain_info, + } + } + + fn is_bool_constructor_name(&self, function_name: &str) -> bool { + if let Some(constant_constructor_name) = &self.bool_domain_info.constant_constructor_name { + function_name == constant_constructor_name + } else { + false + } + } + + fn is_bool_destructor_name(&self, function_name: &str) -> bool { + if let Some(constant_destructor_name) = &self.bool_domain_info.constant_destructor_name { + function_name == constant_destructor_name + } else { + false + } + } + + pub(super) fn is_inconsistent(&self) -> SpannedEncodingResult { + Ok(self.is_inconsistent) + } + + pub(super) fn try_assume(&mut self, term: &vir_low::Expression) -> SpannedEncodingResult<()> { + assert!(term.get_type().is_bool(), "term: {term} {term:?}"); + if term.is_false() { + self.is_inconsistent = true; + } else if Some(false) == self.try_eval(term)? { + self.is_inconsistent = true; + } else { + self.try_assume_value(term, true)?; + } + Ok(()) + } + + fn try_assume_value( + &mut self, + term: &vir_low::Expression, + value: bool, + ) -> SpannedEncodingResult<()> { + match term { + vir_low::Expression::Local(local) => { + self.set_variable_bool(&local.variable.name, value)?; + } + vir_low::Expression::UnaryOp(vir_low::UnaryOp { + op_kind: vir_low::UnaryOpKind::Not, + argument, + .. + }) => { + self.try_assume_value(argument, !value)?; + } + vir_low::Expression::BinaryOp(vir_low::BinaryOp { + op_kind: vir_low::BinaryOpKind::EqCmp, + left, + right, + .. + }) => { + self.try_assume_equal(left, right)?; + } + vir_low::Expression::DomainFuncApp(domain_function_app) + if self.is_bool_destructor_name(&domain_function_app.function_name) => + { + assert!(domain_function_app.arguments.len() == 1); + self.try_assume_value(&domain_function_app.arguments[0], value)?; + } + _ => (), + } + Ok(()) + } + + pub(super) fn try_assume_equal( + &mut self, + left: &vir_low::Expression, + right: &vir_low::Expression, + ) -> SpannedEncodingResult<()> { + if !(left.get_type().is_bool() || left.get_type() == &self.bool_type) { + return Ok(()); + } + match (left, right) { + (vir_low::Expression::Local(local), vir_low::Expression::Constant(constant)) + | (vir_low::Expression::Constant(constant), vir_low::Expression::Local(local)) => { + self.set_variable(local, constant)?; + } + (vir_low::Expression::Local(left_local), vir_low::Expression::Local(right_local)) => { + if let Some(left_value) = self.variables.get(&left_local.variable.name) { + self.set_variable_bool(&right_local.variable.name, *left_value)?; + } + if let Some(right_value) = self.variables.get(&right_local.variable.name) { + self.set_variable_bool(&left_local.variable.name, *right_value)?; + } + } + ( + vir_low::Expression::Local(local), + vir_low::Expression::DomainFuncApp(domain_function_app), + ) + | ( + vir_low::Expression::DomainFuncApp(domain_function_app), + vir_low::Expression::Local(local), + ) if self.is_bool_constructor_name(&domain_function_app.function_name) => { + assert!(domain_function_app.arguments.len() == 1); + if let vir_low::Expression::Constant(constant) = &domain_function_app.arguments[0] { + self.set_variable(local, constant)?; + } + } + _ => {} + } + Ok(()) + } + + fn set_variable( + &mut self, + local: &vir_low::Local, + constant: &vir_low::Constant, + ) -> SpannedEncodingResult<()> { + let vir_low::ConstantValue::Bool(value) = &constant.value else { + unreachable!("local: {local:?} constant: {constant:?}"); + }; + self.set_variable_bool(&local.variable.name, *value)?; + Ok(()) + } + + fn set_variable_bool(&mut self, variable_name: &str, value: bool) -> SpannedEncodingResult<()> { + if let Some(current_value) = self.variables.get(variable_name) { + if value != *current_value { + self.is_inconsistent = true; + } + } else { + self.variables.insert(variable_name.to_string(), value); + } + Ok(()) + } + + fn try_eval(&self, term: &vir_low::Expression) -> SpannedEncodingResult> { + let result = match term { + vir_low::Expression::Local(local) => self.variables.get(&local.variable.name).cloned(), + vir_low::Expression::Constant(constant) => match constant.value { + vir_low::ConstantValue::Bool(value) => Some(value), + _ => None, + }, + vir_low::Expression::UnaryOp(vir_low::UnaryOp { + op_kind: vir_low::UnaryOpKind::Not, + argument, + .. + }) => self.try_eval(argument)?.map(|value| !value), + vir_low::Expression::BinaryOp(vir_low::BinaryOp { + op_kind: vir_low::BinaryOpKind::And, + left, + right, + .. + }) => match (self.try_eval(left)?, self.try_eval(right)?) { + (Some(left_value), Some(right_value)) => Some(left_value && right_value), + (Some(false), _) | (_, Some(false)) => Some(false), + _ => None, + }, + vir_low::Expression::BinaryOp(vir_low::BinaryOp { + op_kind: vir_low::BinaryOpKind::Or, + left, + right, + .. + }) => match (self.try_eval(left)?, self.try_eval(right)?) { + (Some(left_value), Some(right_value)) => Some(left_value || right_value), + (Some(true), _) | (_, Some(true)) => Some(true), + _ => None, + }, + _ => None, + }; + Ok(result) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/egg/graphviz.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/egg/graphviz.rs new file mode 100644 index 00000000000..4d87fc4f876 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/egg/graphviz.rs @@ -0,0 +1,94 @@ +use super::{language::ExpressionLanguage, EGraphState}; +use crate::encoder::errors::SpannedEncodingResult; +use egg::{Id, Language}; +use rustc_hash::FxHashSet; +use std::{fmt::Write, path::Path}; + +impl EGraphState { + pub(in super::super) fn eclass_to_dot_file( + &self, + id: Id, + filename: impl AsRef, + ) -> SpannedEncodingResult<()> { + use std::io::Write; + let mut file = std::fs::File::create(filename).unwrap(); + let mut buffer = String::new(); + self.eclass_to_dot(id, &mut buffer).unwrap(); + writeln!(file, "{buffer}").unwrap(); + Ok(()) + } + + pub(in super::super) fn eclass_to_dot( + &self, + id: Id, + writer: &mut dyn Write, + ) -> std::fmt::Result { + writeln!(writer, "digraph {{")?; + + writeln!(writer, " compound=true")?; + writeln!(writer, " clusterrank=local")?; + + let mut printed_classes = FxHashSet::default(); + let mut classes_to_print = vec![id]; + while let Some(id) = classes_to_print.pop() { + self.print_eclass(id, writer, &mut printed_classes, &mut classes_to_print)?; + } + + writeln!(writer, "}}")?; + Ok(()) + } + + fn print_eclass( + &self, + id: Id, + writer: &mut dyn Write, + printed_classes: &mut FxHashSet, + classes_to_print: &mut Vec, + ) -> std::fmt::Result { + if !printed_classes.contains(&id) { + printed_classes.insert(id); + let class = &self.egraph[id]; + writeln!(writer, " subgraph cluster_{id} {{")?; + writeln!(writer, " style=dotted")?; + for (i, node) in class.iter().enumerate() { + match node { + ExpressionLanguage::Variable(symbol) + if symbol.as_str().starts_with("snapshot$") => + { + // ignore snapshot variables + writeln!(writer, " {id}.{i}[label = \"\"]")?; + } + _ => { + writeln!(writer, " {id}.{i}[label = \"{id} {node}\"]")?; + } + } + } + writeln!(writer, " }}")?; + + for (i_in_class, node) in class.iter().enumerate() { + let mut arg_i = 0; + node.try_for_each(|child| { + let child_class_id = self.egraph.find(child); + classes_to_print.push(child_class_id); + if child_class_id == class.id { + // We have a self-loop. + writeln!( + writer, + " {}.{} -> {}.{}:n [lhead = cluster_{}, label=\"{}:{}\"]", + class.id, i_in_class, class.id, i_in_class, class.id, arg_i, child + )?; + } else { + writeln!( + writer, + " {}.{} -> {}.0 [lhead = cluster_{}, label=\"{}:{}\"]", + class.id, i_in_class, child, child_class_id, arg_i, child + )?; + } + arg_i += 1; + Ok::<_, std::fmt::Error>(()) + })?; + } + } + Ok(()) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/egg/language.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/egg/language.rs new file mode 100644 index 00000000000..295b3402f91 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/egg/language.rs @@ -0,0 +1,29 @@ +use egg::{define_language, Id, Symbol}; + +define_language! { + pub(super) enum ExpressionLanguage { + "true" = True, + "false" = False, + "==" = EqCmp([Id; 2]), + "!=" = NeCmp([Id; 2]), + ">" = GtCmp([Id; 2]), + ">=" = GeCmp([Id; 2]), + "<=" = LtCmp([Id; 2]), + "<" = LeCmp([Id; 2]), + "+" = Add([Id; 2]), + "-" = Sub([Id; 2]), + "*" = Mul([Id; 2]), + "/" = Div([Id; 2]), + "%" = Mod([Id; 2]), + "&&" = And([Id; 2]), + "||" = Or([Id; 2]), + "==>" = Implies([Id; 2]), + "!" = Not(Id), + "neg" = Minus(Id), + Int(i64), + BigInt(Symbol), + Variable(Symbol), + FuncApp(Symbol, Vec), + BuiltinFuncApp(Symbol, Vec), + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/egg/mod.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/egg/mod.rs new file mode 100644 index 00000000000..62f71340d41 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/egg/mod.rs @@ -0,0 +1,508 @@ +use self::language::ExpressionLanguage; +use super::consistency_tracker::ConsistencyTracker; +use crate::encoder::{ + errors::{SpannedEncodingError, SpannedEncodingResult}, + middle::core_proof::{ + snapshots::SnapshotDomainInfo, + transformations::symbolic_execution::egg::{ + rule_applier::RuleApplier, term_interner::TermInterner, + }, + }, +}; +use egg::{EGraph, Id, Language}; +use rustc_hash::{FxHashMap, FxHashSet}; +use std::{collections::BTreeMap, io::Write}; +use vir_crate::low::{ + self as vir_low, + expression::visitors::{default_fallible_walk_expression, ExpressionFallibleWalker}, +}; + +mod language; +mod term_interner; +mod rule_applier; +mod graphviz; + +// impl<'a> ProcedureExecutor<'a> { +// /// Returns true if all arguments are valid terms; that is they are heap +// /// independent. +// pub(super) fn check_and_register_terms( +// &mut self, +// arguments: &[vir_low::Expression], +// ) -> SpannedEncodingResult { +// let mut all_arguments_heap_independent = true; +// for argument in arguments { +// if argument.is_heap_independent() { +// self.execution_trace_builder +// .current_egraph_state() +// .intern_term(argument)?; +// } else { +// all_arguments_heap_independent = false; +// } +// } +// Ok(all_arguments_heap_independent) +// } +// } + +#[derive(Clone)] +pub(super) struct EGraphState { + egraph: EGraph, + simplification_rules: Vec>, + false_id: Id, + true_id: Id, + interned_terms: FxHashMap, + consistency_tracker: ConsistencyTracker, +} + +impl EGraphState { + pub(super) fn new( + domains: &[vir_low::DomainDecl], + bool_type: vir_low::Type, + bool_domain_info: SnapshotDomainInfo, + ) -> SpannedEncodingResult { + let mut egraph = EGraph::default(); + let true_id = egraph.add(ExpressionLanguage::True); + let false_id = egraph.add(ExpressionLanguage::False); + let mut simplification_rules = Vec::new(); + for domain in domains { + for rule in &domain.rewrite_rules { + let mut variables = BTreeMap::new(); + let mut source_pattern_ast: egg::RecExpr> = + egg::RecExpr::default(); + let true_id = + source_pattern_ast.add(egg::ENodeOrVar::ENode(ExpressionLanguage::True)); + let false_id = + source_pattern_ast.add(egg::ENodeOrVar::ENode(ExpressionLanguage::False)); + for variable in &rule.variables { + let egg_variable: egg::Var = format!("?{}", variable.name).parse().unwrap(); + let variable_id = source_pattern_ast.add(egg::ENodeOrVar::Var(egg_variable)); + variables.insert(variable.name.clone(), variable_id); + } + let mut target_pattern_ast = source_pattern_ast.clone(); + let mut trigger_pattern = source_pattern_ast.clone(); + source_pattern_ast.intern_pattern(&rule.source, true_id, false_id, &variables)?; + target_pattern_ast.intern_pattern(&rule.target, true_id, false_id, &variables)?; + let egg_rule = if let Some(triggers) = &rule.triggers { + assert_eq!( + triggers.len(), + 1, + "Currently only single term triggers are implemented." + ); + assert_eq!( + triggers[0].terms.len(), + 1, + "Currently only single term triggers are implemented." + ); + trigger_pattern.intern_pattern( + &triggers[0].terms[0], + true_id, + false_id, + &variables, + )?; + let trigger_pattern = egg::Pattern::new(trigger_pattern); + egg::rewrite!(&rule.name; trigger_pattern => { + RuleApplier::new(source_pattern_ast, target_pattern_ast) + }) + } else { + let source_pattern = egg::Pattern::new(source_pattern_ast); + let target_pattern = egg::Pattern::new(target_pattern_ast); + egg::rewrite!(&rule.name; source_pattern => target_pattern) + }; + simplification_rules.push(egg_rule); + } + } + // let rule = { + // let place_var: egg::Var = "?place".parse().unwrap(); + // let address_var: egg::Var = "?address".parse().unwrap(); + // let mut pattern: egg::RecExpr> = + // egg::RecExpr::default(); + // let place = pattern.add(egg::ENodeOrVar::Var(place_var)); + // let address = pattern.add(egg::ENodeOrVar::Var(address_var)); + // pattern.add(egg::ENodeOrVar::ENode(ExpressionLanguage::FuncApp( + // Symbol::from("compute_address"), + // vec![place, address], + // ))); + // let match_pattern = egg::Pattern::new(pattern); + // let mut pattern: egg::RecExpr> = + // egg::RecExpr::default(); + // pattern.add(egg::ENodeOrVar::Var(address_var)); + // let target_pattern = egg::Pattern::new(pattern); + // egg::rewrite!("evaluate_compute_address"; match_pattern => target_pattern) + // }; + // let simplification_rules = vec![rule]; + Ok(Self { + egraph, + simplification_rules, + true_id, + false_id, + interned_terms: Default::default(), + consistency_tracker: ConsistencyTracker::new(bool_type, bool_domain_info), + }) + } + + /// Assume all internable conjuncts. Conjuncts are internable if they are + /// heap independent and do not contain quantifiers, conditionals, and let + /// expressions. + pub(super) fn try_assume_heap_independent_conjuncts( + &mut self, + expression: &vir_low::Expression, + ) -> SpannedEncodingResult<()> { + if let vir_low::Expression::BinaryOp(binary_expression) = expression { + match binary_expression.op_kind { + vir_low::BinaryOpKind::EqCmp => { + if expression.is_heap_independent() { + self.assume_equal(&binary_expression.left, &binary_expression.right)?; + return Ok(()); + } + } + vir_low::BinaryOpKind::And => { + self.try_assume_heap_independent_conjuncts(&binary_expression.left)?; + self.try_assume_heap_independent_conjuncts(&binary_expression.right)?; + return Ok(()); + } + _ => {} + } + } + if expression.is_heap_independent() { + self.try_assume(expression)?; + } + Ok(()) + } + + pub(super) fn try_intern_heap_independent_conjuncts( + &mut self, + expression: &vir_low::Expression, + ) -> SpannedEncodingResult<()> { + if let vir_low::Expression::BinaryOp(binary_expression) = expression { + if let vir_low::BinaryOpKind::And = binary_expression.op_kind { + self.try_intern_heap_independent_conjuncts(&binary_expression.left)?; + self.try_intern_heap_independent_conjuncts(&binary_expression.right)?; + return Ok(()); + } + } + if expression.is_heap_independent() { + self.try_intern_term(expression)?; + } + Ok(()) + } + + /// Returns true if any new terms were interned. + pub(super) fn intern_heap_independent_terms( + &mut self, + terms: &[vir_low::Expression], + ) -> SpannedEncodingResult { + let mut newly_interned = false; + for term in terms { + if term.is_heap_independent() && self.try_lookup_term(term)?.is_none() { + self.intern_term(term)?; + newly_interned = true; + } + } + Ok(newly_interned) + } + + pub(super) fn intern_heap_independent_subexpressions( + &mut self, + expression: &vir_low::Expression, + ) -> SpannedEncodingResult<()> { + // eprintln!("intern_heap_independent_subexpressions: {expression}"); + struct Walker<'a> { + egraph: &'a mut EGraphState, + } + impl<'a> ExpressionFallibleWalker for Walker<'a> { + type Error = SpannedEncodingError; + fn fallible_walk_trigger( + &mut self, + trigger: &vir_low::Trigger, + ) -> Result<(), Self::Error> { + for term in &trigger.terms { + self.fallible_walk_expression(term)?; + } + Ok(()) + } + fn fallible_walk_expression( + &mut self, + expression: &vir_low::Expression, + ) -> Result<(), Self::Error> { + if expression.is_heap_independent() { + // eprintln!("Try interning: {expression}"); + self.egraph.try_intern_term(expression)?; + // return Ok(()); – FIXME: We cannot return early here + // because `try_intern_term` stores only the id of the + // whole expression, not the ids of its subexpressions. + } + default_fallible_walk_expression(self, expression) + } + } + let mut walker = Walker { egraph: self }; + walker.fallible_walk_expression(expression)?; + + // struct Walker2<'a> { + // egraph: &'a mut EGraphState, + // } + // impl<'a> ExpressionFallibleWalker for Walker2<'a> { + // type Error = SpannedEncodingError; + // fn fallible_walk_domain_func_app( + // &mut self, + // domain_func_app: &vir_low::DomainFuncApp, + // ) -> Result<(), Self::Error> { + // // assert_ne!(domain_func_app.function_name, "constructor$Snap$Usize$Mul_Usize"); + // // assert_ne!(domain_func_app.function_name, "Size$I32$"); + // if domain_func_app.function_name == "destructor$Snap$Usize$$value" { + // if let vir_low::Expression::DomainFuncApp(domain_func_app2) = &domain_func_app.arguments[0] { + // assert_ne!(domain_func_app2.function_name, "Size$I32$", "{domain_func_app}"); + // } + // } + // // assert_ne!(domain_func_app.function_name, "destructor$Snap$Usize$$value", "{domain_func_app}"); + // vir_low::expression::visitors::default_fallible_walk_domain_func_app(self, domain_func_app) + // } + // } + // let mut walker = Walker2 { + // egraph: self.execution_trace_builder.current_egraph_state(), + // }; + // walker.fallible_walk_expression(expression)?; + + Ok(()) + } + + pub(super) fn assume(&mut self, term: &vir_low::Expression) -> SpannedEncodingResult<()> { + self.consistency_tracker.try_assume(term)?; + let term_id = self.intern_term(term)?; + self.egraph.union(term_id, self.true_id); + Ok(()) + } + + fn try_assume(&mut self, term: &vir_low::Expression) -> SpannedEncodingResult<()> { + self.consistency_tracker.try_assume(term)?; + if let Some(term_id) = self.try_intern_term(term)? { + self.egraph.union(term_id, self.true_id); + } + Ok(()) + } + + pub(super) fn assume_equal( + &mut self, + left: &vir_low::Expression, + right: &vir_low::Expression, + ) -> SpannedEncodingResult<()> { + self.consistency_tracker.try_assume_equal(left, right)?; + let left_id = self.intern_term(left)?; + let right_id = self.intern_term(right)?; + self.egraph.union(left_id, right_id); + Ok(()) + } + + /// If the graph was modified, `saturate` must be called before `is_equal` can + /// be used. + pub(super) fn is_equal( + &self, + left: &vir_low::Expression, + right: &vir_low::Expression, + ) -> SpannedEncodingResult { + let left_id = self.lookup_term(left)?; + let right_id = self.lookup_term(right)?; + Ok(self.egraph.find(left_id) == self.egraph.find(right_id)) + } + + pub(super) fn is_true(&self, term: &vir_low::Expression) -> SpannedEncodingResult { + let term_id = self.lookup_term(term)?; + Ok(self.egraph.find(term_id) == self.egraph.find(self.true_id)) + } + + pub(super) fn try_is_true( + &self, + term: &vir_low::Expression, + ) -> SpannedEncodingResult> { + if let Some(term_id) = self.try_lookup_term(term)? { + Ok(Some( + self.egraph.find(term_id) == self.egraph.find(self.true_id), + )) + } else { + Ok(None) + } + } + + /// Check whether the term is known to be a constant. + /// + /// Returns: + /// + /// * `Some((Some(constructor_name), constant))` if the term is equivalent + /// to a given constantat wrapped in the specified constructor. + /// * `Some(None, constant)` if the term is directly equivalent to a constant. + /// * `None` if the term is not equivalent to a constant. + pub(super) fn resolve_constant( + &self, + term: &vir_low::Expression, + constant_constructors: &FxHashSet, + ) -> SpannedEncodingResult, vir_low::Expression)>> { + let Some(id) = self.try_lookup_term(term)? else { + // eprintln!("not interned: {term}"); + return Ok(None); + }; + struct PreferConstantsCostFunction<'a> { + constant_constructors: &'a FxHashSet, + } + impl<'a> egg::CostFunction for PreferConstantsCostFunction<'a> { + type Cost = f64; + fn cost(&mut self, enode: &ExpressionLanguage, mut costs: C) -> Self::Cost + where + C: FnMut(Id) -> Self::Cost, + { + let op_cost = match enode { + ExpressionLanguage::True + | ExpressionLanguage::False + | ExpressionLanguage::Int(_) + | ExpressionLanguage::BigInt(_) => 1.0, + ExpressionLanguage::FuncApp(symbol, _) + | ExpressionLanguage::BuiltinFuncApp(symbol, _) + if self.constant_constructors.contains(symbol.as_str()) => + { + 2.0 + } + _ => 10.0, + }; + enode + .children() + .iter() + .fold(op_cost, |sum, id| sum + costs(*id)) + } + } + let cost_func = PreferConstantsCostFunction { + constant_constructors, + }; + let extractor = egg::Extractor::new(&self.egraph, cost_func); + let (_best_cost, node) = extractor.find_best(id); + let last: Id = (node.as_ref().len() - 1).into(); + match &node[last] { + ExpressionLanguage::FuncApp(name, arguments) if arguments.len() == 1 => { + match node[arguments[0]] { + ExpressionLanguage::Int(constant) => { + return Ok(Some((Some(name.to_string()), constant.into()))); + } + ExpressionLanguage::BigInt(_) => todo!(), + _ => {} + } + } + ExpressionLanguage::Int(constant) => { + return Ok(Some((None, (*constant).into()))); + } + ExpressionLanguage::BigInt(constant) => { + let constant_value = vir_low::ConstantValue::BigInt(constant.to_string()); + let expression = + vir_low::Expression::constant_no_pos(constant_value, vir_low::Type::Int); + return Ok(Some((None, expression))); + } + _ => {} + } + Ok(None) + } + + pub(super) fn saturate(&mut self) -> SpannedEncodingResult<()> { + self.egraph.rebuild(); + let runner: egg::Runner<_, _, ()> = egg::Runner::new(()) + .with_egraph(std::mem::take(&mut self.egraph)) + // .with_node_limit(200) + .run(&self.simplification_rules); + if !(matches!(runner.stop_reason.unwrap(), egg::StopReason::Saturated)) { + runner + .egraph + .dot() + .to_dot("/tmp/egraph-unsaturated.dot") + .unwrap(); + panic!("simplification rules did not saturate; see /tmp/egraph-unsaturated.dot"); + } + self.egraph = runner.egraph; + Ok(()) + } + + pub(super) fn is_inconsistent(&mut self) -> SpannedEncodingResult { + self.consistency_tracker.is_inconsistent() + // if self.consistency_tracker.is_inconsistent()? { + // Ok(true) + // } else { + // // self.egraph.rebuild(); + // Ok(self.egraph.find(self.true_id) == self.egraph.find(self.false_id)) + // } + } + + /// Lookup the id of a previously interned term. + fn lookup_term(&self, term: &vir_low::Expression) -> SpannedEncodingResult { + Ok(self.try_lookup_term(term)?.unwrap_or_else(|| { + panic!("term {term} is not interned"); + })) + } + + /// Lookup the id of a previously interned term. + fn try_lookup_term(&self, term: &vir_low::Expression) -> SpannedEncodingResult> { + Ok(self.interned_terms.get(term).cloned()) + } + + pub(super) fn intern_term(&mut self, term: &vir_low::Expression) -> SpannedEncodingResult { + let id = self.try_intern_term(term)?.unwrap_or_else(|| { + panic!("term {term} cannot be interned"); + }); + Ok(id) + // if let Some(id) = self.interned_terms.get(term) { + // Ok(*id) + // } else { + // assert!(term.is_heap_independent(), "{term} is heap independent"); + // let id = self.egraph.intern_term(term, self.true_id, self.false_id)?; + // self.interned_terms.insert(term.clone(), id); + // Ok(id) + // } + } + + pub(super) fn try_intern_term( + &mut self, + term: &vir_low::Expression, + ) -> SpannedEncodingResult> { + if let Some(id) = self.interned_terms.get(term) { + Ok(Some(*id)) + } else { + assert!(term.is_heap_independent(), "{term} is heap dependent"); + if let Some(id) = self + .egraph + .try_intern_term(term, self.true_id, self.false_id)? + { + self.interned_terms.insert(term.clone(), id); + Ok(Some(id)) + } else { + Ok(None) + } + } + } + + pub(super) fn to_graphviz(&self, writer: &mut dyn Write) -> std::io::Result<()> { + write!(writer, "{}", self.egraph.dot()) + } + + #[allow(unused)] + pub(super) fn dump_dot(&self, path: &str) -> SpannedEncodingResult<()> { + self.egraph.dot().to_dot(path).unwrap(); + Ok(()) + } + + // pub(super) fn get_dump_eclass_of( + // &self, + // term: &vir_low::Expression, + // ) -> SpannedEncodingResult { + // use std::fmt::Write; + // let id = self.lookup_term(term)?; + // // if id == 337.into() { + // // println!("eclass of {term}: {id}"); + // // for node in &self.egraph[id].nodes { + // // println!(" {node}"); + // // } + // // // self.dump_dot("/tmp/egraph-337.dot").unwrap(); + // // self.eclass_to_dot_file(id, "/tmp/egraph-337.dot")?; + // // self.eclass_to_dot_file(322.into(), "/tmp/egraph-322.dot")?; + // // self.eclass_to_dot_file(134.into(), "/tmp/egraph-134.dot")?; + // // panic!(); + // // } + // let mut buf = String::new(); + // writeln!(buf, "// eclass of {term}: {id}").unwrap(); + // for node in &self.egraph[id].nodes { + // writeln!(buf, "// {node}").unwrap(); + // } + // Ok(buf) + // } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/egg/rule_applier.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/egg/rule_applier.rs new file mode 100644 index 00000000000..445aade957b --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/egg/rule_applier.rs @@ -0,0 +1,49 @@ +use super::language::ExpressionLanguage; + +pub(super) struct RuleApplier { + // source: egg::Pattern, target: egg::Pattern, + source: egg::PatternAst, + target: egg::PatternAst, +} + +impl RuleApplier { + // pub(super) fn new(source: egg::Pattern, target: egg::Pattern) -> Self { + pub(super) fn new( + source: egg::PatternAst, + target: egg::PatternAst, + ) -> Self { + Self { source, target } + } +} + +impl egg::Applier for RuleApplier { + fn apply_one( + &self, + egraph: &mut egg::EGraph, + _eclass: egg::Id, + subst: &egg::Subst, + _searcher_ast: Option<&egg::PatternAst>, + rule_name: egg::Symbol, + ) -> Vec { + let (new_id, unified) = + egraph.union_instantiations(&self.source, &self.target, subst, rule_name); + if unified { + vec![new_id] + } else { + Vec::new() + } + // let source = self.source.apply_one(egraph, eclass, subst, searcher_ast, rule_name); + // let target = self.target.apply_one(egraph, eclass, subst, searcher_ast, rule_name); + // assert_eq!(source.len(), 1); + // assert_eq!(target.len(), 1); + // let source = source[0]; + // let target = target[0]; + // let source = egraph.find(source); + // let target = egraph.find(target); + // if source == target { + // Vec::new() + // } else { + // vec![source, target] + // } + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/egg/term_interner.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/egg/term_interner.rs new file mode 100644 index 00000000000..47c04f074a5 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/egg/term_interner.rs @@ -0,0 +1,371 @@ +use super::language::ExpressionLanguage; +use crate::encoder::errors::SpannedEncodingResult; +use egg::{EGraph, Id, RecExpr, Symbol}; +use rustc_hash::FxHashSet; +use std::collections::BTreeMap; +use vir_crate::low::{self as vir_low}; + +pub(super) trait TermInterner { + fn try_intern_term( + &mut self, + term: &vir_low::Expression, + true_id: Id, + false_id: Id, + ) -> SpannedEncodingResult>; + + fn intern_term( + &mut self, + term: &vir_low::Expression, + true_id: Id, + false_id: Id, + ) -> SpannedEncodingResult; + + fn intern_pattern( + &mut self, + term: &vir_low::Expression, + true_id: Id, + false_id: Id, + variables: &BTreeMap, + ) -> SpannedEncodingResult; + + fn add(&mut self, term: ExpressionLanguage) -> Id; +} + +impl TermInterner for EGraph { + fn try_intern_term( + &mut self, + term: &vir_low::Expression, + true_id: Id, + false_id: Id, + ) -> SpannedEncodingResult> { + Ok(try_intern_term_rec( + self, + true_id, + false_id, + &BTreeMap::new(), + &mut Vec::new(), + term, + )) + } + + fn intern_term( + &mut self, + term: &vir_low::Expression, + true_id: Id, + false_id: Id, + ) -> SpannedEncodingResult { + Ok(try_intern_term_rec( + self, + true_id, + false_id, + &BTreeMap::new(), + &mut Vec::new(), + term, + ) + .unwrap_or_else(|| panic!("Failed to intern term: {term}"))) + } + + fn intern_pattern( + &mut self, + term: &vir_low::Expression, + true_id: Id, + false_id: Id, + variables: &BTreeMap, + ) -> SpannedEncodingResult { + Ok( + try_intern_term_rec(self, true_id, false_id, variables, &mut Vec::new(), term) + .unwrap_or_else(|| panic!("Failed to intern term: {term}")), + ) + } + + fn add(&mut self, term: ExpressionLanguage) -> Id { + self.add(term) + } +} + +impl TermInterner for RecExpr> { + fn try_intern_term( + &mut self, + term: &vir_low::Expression, + true_id: Id, + false_id: Id, + ) -> SpannedEncodingResult> { + Ok(try_intern_term_rec( + self, + true_id, + false_id, + &BTreeMap::new(), + &mut Vec::new(), + term, + )) + } + + fn intern_term( + &mut self, + term: &vir_low::Expression, + true_id: Id, + false_id: Id, + ) -> SpannedEncodingResult { + Ok(try_intern_term_rec( + self, + true_id, + false_id, + &BTreeMap::new(), + &mut Vec::new(), + term, + ) + .unwrap_or_else(|| panic!("Failed to intern term: {term}"))) + } + + fn intern_pattern( + &mut self, + term: &vir_low::Expression, + true_id: Id, + false_id: Id, + variables: &BTreeMap, + ) -> SpannedEncodingResult { + Ok( + try_intern_term_rec(self, true_id, false_id, variables, &mut Vec::new(), term) + .unwrap_or_else(|| panic!("Failed to intern term: {term}")), + ) + } + + fn add(&mut self, term: ExpressionLanguage) -> Id { + self.add(egg::ENodeOrVar::ENode(term)) + } +} + +/// This method must be called only through `intern_term` that checks its +/// precondition. That is the reason why this method is private and not part of +/// `TermInterner`. +fn try_intern_term_rec( + egraph: &mut impl TermInterner, + true_id: Id, + false_id: Id, + variables: &BTreeMap, + bound_variables: &mut Vec>, + term: &vir_low::Expression, +) -> Option { + let id = match term { + vir_low::Expression::Local(expression) => { + for frame in bound_variables { + if frame.contains(&expression.variable.name) { + return None; + } + } + if let Some(variable_id) = variables.get(&expression.variable.name) { + *variable_id + } else { + let symbol = Symbol::from(&expression.variable.name); + egraph.add(ExpressionLanguage::Variable(symbol)) + } + } + vir_low::Expression::Constant(expression) => match &expression.value { + vir_low::ConstantValue::Bool(true) => true_id, + vir_low::ConstantValue::Bool(false) => false_id, + vir_low::ConstantValue::Int(value) => egraph.add(ExpressionLanguage::Int(*value)), + vir_low::ConstantValue::BigInt(value) => { + if let Ok(value_int) = std::str::FromStr::from_str(value) { + egraph.add(ExpressionLanguage::Int(value_int)) + } else { + egraph.add(ExpressionLanguage::BigInt(Symbol::from(value))) + } + } + }, + vir_low::Expression::UnaryOp(expression) => { + let operand_id = try_intern_term_rec( + egraph, + true_id, + false_id, + variables, + bound_variables, + &expression.argument, + )?; + match expression.op_kind { + vir_low::UnaryOpKind::Not => egraph.add(ExpressionLanguage::Not(operand_id)), + vir_low::UnaryOpKind::Minus => egraph.add(ExpressionLanguage::Minus(operand_id)), + } + } + vir_low::Expression::BinaryOp(expression) => { + let left_id = try_intern_term_rec( + egraph, + true_id, + false_id, + variables, + bound_variables, + &expression.left, + )?; + let right_id = try_intern_term_rec( + egraph, + true_id, + false_id, + variables, + bound_variables, + &expression.right, + )?; + match expression.op_kind { + vir_low::BinaryOpKind::EqCmp => { + egraph.add(ExpressionLanguage::EqCmp([left_id, right_id])) + } + vir_low::BinaryOpKind::NeCmp => { + egraph.add(ExpressionLanguage::NeCmp([left_id, right_id])) + } + vir_low::BinaryOpKind::GtCmp => { + egraph.add(ExpressionLanguage::GtCmp([left_id, right_id])) + } + vir_low::BinaryOpKind::GeCmp => { + egraph.add(ExpressionLanguage::GeCmp([left_id, right_id])) + } + vir_low::BinaryOpKind::LtCmp => { + egraph.add(ExpressionLanguage::LtCmp([left_id, right_id])) + } + vir_low::BinaryOpKind::LeCmp => { + egraph.add(ExpressionLanguage::LeCmp([left_id, right_id])) + } + vir_low::BinaryOpKind::Add => { + egraph.add(ExpressionLanguage::Add([left_id, right_id])) + } + vir_low::BinaryOpKind::Sub => { + egraph.add(ExpressionLanguage::Sub([left_id, right_id])) + } + vir_low::BinaryOpKind::Mul => { + egraph.add(ExpressionLanguage::Mul([left_id, right_id])) + } + vir_low::BinaryOpKind::Div => { + egraph.add(ExpressionLanguage::Div([left_id, right_id])) + } + vir_low::BinaryOpKind::Mod => { + egraph.add(ExpressionLanguage::Mod([left_id, right_id])) + } + vir_low::BinaryOpKind::And => { + egraph.add(ExpressionLanguage::And([left_id, right_id])) + } + vir_low::BinaryOpKind::Or => { + egraph.add(ExpressionLanguage::Or([left_id, right_id])) + } + vir_low::BinaryOpKind::Implies => { + egraph.add(ExpressionLanguage::Implies([left_id, right_id])) + } + } + } + vir_low::Expression::PermBinaryOp(expression) => { + let left_id = try_intern_term_rec( + egraph, + true_id, + false_id, + variables, + bound_variables, + &expression.left, + )?; + let right_id = try_intern_term_rec( + egraph, + true_id, + false_id, + variables, + bound_variables, + &expression.right, + )?; + match expression.op_kind { + vir_low::expression::PermBinaryOpKind::Add => { + egraph.add(ExpressionLanguage::Add([left_id, right_id])) + } + vir_low::expression::PermBinaryOpKind::Sub => { + egraph.add(ExpressionLanguage::Sub([left_id, right_id])) + } + vir_low::expression::PermBinaryOpKind::Mul => { + egraph.add(ExpressionLanguage::Mul([left_id, right_id])) + } + vir_low::expression::PermBinaryOpKind::Div => { + egraph.add(ExpressionLanguage::Div([left_id, right_id])) + } + } + } + vir_low::Expression::ContainerOp(expression) => { + let mut operands = Vec::new(); + for operand in &expression.operands { + let operand_id = try_intern_term_rec( + egraph, + true_id, + false_id, + variables, + bound_variables, + operand, + )?; + operands.push(operand_id); + } + egraph.add(ExpressionLanguage::BuiltinFuncApp( + Symbol::from(format!("{:?}", expression.kind)), + operands, + )) + } + vir_low::Expression::DomainFuncApp(expression) => { + let symbol = Symbol::from(&expression.function_name); + let arguments = expression + .arguments + .iter() + .map(|argument| { + try_intern_term_rec( + egraph, + true_id, + false_id, + variables, + bound_variables, + argument, + ) + }) + .collect::>>()?; + egraph.add(ExpressionLanguage::FuncApp(symbol, arguments)) + } + vir_low::Expression::LabelledOld(expression) => try_intern_term_rec( + egraph, + true_id, + false_id, + variables, + bound_variables, + &expression.base, + )?, + // FIXME: It does not make sense to intern the contents of these + // expressions because in the interning table we store only the id of + // the root. + vir_low::Expression::Conditional(_) + | vir_low::Expression::Quantifier(_) + | vir_low::Expression::LetExpr(_) => { + return None; + } + // vir_low::Expression::Conditional(expression) => { + // try_intern_term_rec(egraph, true_id, false_id, variables, bound_variables,&expression.guard)?; + // try_intern_term_rec(egraph, true_id, false_id, variables, bound_variables,&expression.then_expr)?; + // try_intern_term_rec(egraph, true_id, false_id, variables, bound_variables,&expression.else_expr)?; + // return None; + // } + // vir_low::Expression::Quantifier(expression) => { + // bound_variables.push(expression.variables.iter().map(|variable| variable.name.clone()).collect()); + // try_intern_term_rec(egraph, true_id, false_id, variables, bound_variables, &expression.body)?; + // for trigger in &expression.triggers { + // for term in &trigger.terms { + // try_intern_term_rec(egraph, true_id, false_id, variables, bound_variables, term)?; + // } + // } + // bound_variables.pop(); + // return None; + // } + // vir_low::Expression::LetExpr(expression) => { + // try_intern_term_rec(egraph, true_id, false_id, variables, bound_variables, &expression.def)?; + // bound_variables.push(vec![expression.variable.name.clone()].into_iter().collect()); + // try_intern_term_rec(egraph, true_id, false_id, variables, bound_variables, &expression.body)?; + // bound_variables.pop(); + // return None; + // } + vir_low::Expression::MagicWand(_) + | vir_low::Expression::PredicateAccessPredicate(_) + | vir_low::Expression::FieldAccessPredicate(_) + | vir_low::Expression::Unfolding(_) + | vir_low::Expression::FuncApp(_) + | vir_low::Expression::InhaleExhale(_) + | vir_low::Expression::Field(_) => { + unreachable!("term: {}", term); + } + vir_low::Expression::SmtOperation(_) => todo!(), + }; + Some(id) +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/heap/entry.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/heap/entry.rs new file mode 100644 index 00000000000..2ab62014c85 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/heap/entry.rs @@ -0,0 +1,41 @@ +use vir_crate::low::{self as vir_low}; + +pub(in super::super) enum HeapEntry { + Comment(vir_low::ast::statement::Comment), + Label(vir_low::ast::statement::Label), + /// An inhale entry that can be purified. + InhalePredicate( + vir_low::ast::expression::PredicateAccessPredicate, + vir_low::Position, + ), + /// An exhale entry that can be purified. + ExhalePredicate( + vir_low::ast::expression::PredicateAccessPredicate, + vir_low::Position, + ), + /// A generic inhale entry that cannot be purified. + InhaleGeneric(vir_low::ast::statement::Inhale), + /// A generic exhale entry that cannot be purified. + ExhaleGeneric(vir_low::ast::statement::Exhale), + Assume(vir_low::ast::statement::Assume), + Assert(vir_low::ast::statement::Assert), +} + +impl std::fmt::Display for HeapEntry { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + HeapEntry::Comment(statement) => write!(f, "{statement}"), + HeapEntry::Label(statement) => write!(f, "{statement}"), + HeapEntry::InhalePredicate(predicate, _position) => { + write!(f, "inhale-predicate {predicate}") + } + HeapEntry::ExhalePredicate(predicate, _position) => { + write!(f, "exhale-predicate {predicate}") + } + HeapEntry::InhaleGeneric(statement) => write!(f, "{statement}"), + HeapEntry::ExhaleGeneric(statement) => write!(f, "{statement}"), + HeapEntry::Assume(statement) => write!(f, "{statement}"), + HeapEntry::Assert(statement) => write!(f, "{statement}"), + } + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/heap/finalizer.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/heap/finalizer.rs new file mode 100644 index 00000000000..be1d1aa83ed --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/heap/finalizer.rs @@ -0,0 +1,429 @@ +use super::{ + lifetime_tokens::LifetimeTokens, + predicate_snapshots::{ + check_non_aliased_snap_calls_purified, purify_snap_calls, purify_snap_calls_vec_with_retry, + PredicateSnapshots, + }, + state::{PredicateInstance, PredicateInstanceState}, + HeapEntry, HeapState, Location, +}; +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::transformations::{ + encoder_context::EncoderContext, + symbolic_execution::{egg::EGraphState, program_context::ProgramContext, trace::Trace}, + }, +}; +use prusti_common::config; +use std::collections::BTreeMap; +use vir_crate::low::{self as vir_low}; + +pub(super) struct TraceFinalizer<'a, EC: EncoderContext> { + source_filename: &'a str, + procedure_name: &'a str, + purification_failure_count: usize, + final_state: &'a HeapState, + trace: Vec, + new_variables: Vec, + new_labels: Vec, + predicate_snapshots: PredicateSnapshots, + predicate_snapshots_at_label: BTreeMap, + lifetime_tokens: LifetimeTokens, + solver: &'a mut EGraphState, + program: &'a ProgramContext<'a, EC>, +} + +impl<'a, EC: EncoderContext> TraceFinalizer<'a, EC> { + pub(super) fn new( + source_filename: &'a str, + procedure_name: &'a str, + final_state: &'a HeapState, + solver: &'a mut EGraphState, + program: &'a ProgramContext<'a, EC>, + ) -> Self { + Self { + source_filename, + procedure_name, + purification_failure_count: 0, + final_state, + trace: Vec::new(), + new_variables: Vec::new(), + new_labels: Vec::new(), + predicate_snapshots: Default::default(), + predicate_snapshots_at_label: Default::default(), + lifetime_tokens: Default::default(), + solver, + program, + } + } + + pub(super) fn into_trace(self) -> Trace { + let mut variables = self.new_variables; + variables.extend(self.predicate_snapshots.into_variables()); + variables.extend(self.lifetime_tokens.into_variables()); + Trace { + statements: self.trace, + variables, + labels: self.new_labels, + } + } + + pub(super) fn trace_len(&self) -> usize { + self.trace.len() + } + + pub(super) fn trace_suffix(&self, checkpoint: usize) -> &[vir_low::Statement] { + self.trace[checkpoint..].as_ref() + } + + pub(super) fn add_variables( + &mut self, + new_variables: &[vir_low::VariableDecl], + ) -> SpannedEncodingResult<()> { + self.new_variables.extend_from_slice(new_variables); + Ok(()) + } + + pub(super) fn add_labels( + &mut self, + new_labels: &[vir_low::Label], + ) -> SpannedEncodingResult<()> { + self.new_labels.extend_from_slice(new_labels); + Ok(()) + } + + pub(super) fn add_entry( + &mut self, + location: Location, + entry: &HeapEntry, + ) -> SpannedEncodingResult<()> { + match entry { + HeapEntry::Comment(statement) => { + self.trace + .push(vir_low::Statement::Comment(statement.clone())); + } + HeapEntry::Label(statement) => { + self.save_state(statement.label.clone()); + self.trace + .push(vir_low::Statement::Label(statement.clone())); + } + HeapEntry::InhalePredicate(predicate, position) => { + let predicate_kind = self.program.get_predicate_kind(&predicate.name); + let arguments = purify_snap_calls_vec_with_retry( + &self.predicate_snapshots, + &self.predicate_snapshots_at_label, + self.solver, + self.program, + predicate.arguments.clone(), + )?; + if predicate_kind == vir_low::PredicateKind::LifetimeToken { + self.purify_lifetime_token_inhale(predicate, *position)?; + } else if let Some(predicate_instance) = + self.is_purified_inhale(location, predicate) + { + let snapshot = predicate_instance.snapshot.clone(); + if config::report_symbolic_execution_purification() { + self.trace.push(vir_low::Statement::comment(format!( + "purified out: {entry}" + ))); + } + if let Some(snapshot_variable_name) = snapshot { + self.predicate_snapshots.register_predicate_snapshot( + self.program, + &predicate.name, + arguments, + snapshot_variable_name, + ); + } else { + self.predicate_snapshots.create_predicate_snapshot( + self.program, + &predicate.name, + arguments, + ); + } + } else { + self.report_purification_failure(*position)?; + self.trace.push(vir_low::Statement::inhale( + vir_low::Expression::predicate_access_predicate( + predicate.name.clone(), + arguments, + *(predicate.permission).clone(), + predicate.position, + ), + *position, + )); + } + } + HeapEntry::ExhalePredicate(predicate, position) => { + let predicate_kind = self.program.get_predicate_kind(&predicate.name); + let arguments = purify_snap_calls_vec_with_retry( + &self.predicate_snapshots, + &self.predicate_snapshots_at_label, + self.solver, + self.program, + predicate.arguments.clone(), + )?; + if predicate_kind == vir_low::PredicateKind::LifetimeToken { + self.purify_lifetime_token_exhale(predicate, *position)?; + } else if self.is_purified_exhale(location, predicate) { + if config::report_symbolic_execution_purification() { + self.trace.push(vir_low::Statement::comment(format!( + "purified out: {entry}" + ))); + } + self.predicate_snapshots.destroy_predicate_snapshot( + &predicate.name, + &arguments, + self.solver, + )?; + } else { + if predicate_kind == vir_low::PredicateKind::CloseFracRef { + self.try_assert_frac_ref_snapshot_equality(predicate, *position)?; + } + self.report_purification_failure(*position)?; + self.trace.push(vir_low::Statement::exhale( + vir_low::Expression::predicate_access_predicate( + predicate.name.clone(), + arguments, + *(predicate.permission).clone(), + predicate.position, + ), + *position, + )); + } + } + HeapEntry::InhaleGeneric(statement) => { + // eprintln!("InhaleGeneric: {}", statement.expression); + self.add_expression_entry( + &statement.expression, + statement.position, + vir_low::Statement::inhale, + )?; + } + HeapEntry::ExhaleGeneric(statement) => { + self.add_expression_entry( + &statement.expression, + statement.position, + vir_low::Statement::exhale, + )?; + } + HeapEntry::Assume(statement) => { + self.add_expression_entry( + &statement.expression, + statement.position, + vir_low::Statement::assume, + )?; + } + HeapEntry::Assert(statement) => { + self.add_expression_entry( + &statement.expression, + statement.position, + vir_low::Statement::assert, + )?; + } + } + Ok(()) + } + + fn add_expression_entry( + &mut self, + expression: &vir_low::Expression, + position: vir_low::Position, + constructor: fn(vir_low::Expression, vir_low::Position) -> vir_low::Statement, + ) -> SpannedEncodingResult<()> { + // let simplified_expression = simplify_expression(expression.clone(), self.program, self.solver)?; + // if &simplified_expression != expression { + // self.solver.intern_heap_independent_subexpressions(&simplified_expression)?; + // } + let simplified_expression = expression.clone(); + let expression = self.purify_snap_calls(simplified_expression)?; + if !check_non_aliased_snap_calls_purified(&expression, self.program) { + // Purification failed, this should be unreachable. + self.trace.push(vir_low::Statement::comment(format!( + "Failed to purify: {}", + expression + ))); + self.trace + .push(vir_low::Statement::assert(false.into(), position)); + } + // assert!(check_non_aliased_snap_calls_purified( + // &expression, + // self.program + // )); + self.trace.push(constructor(expression, position)); + Ok(()) + } + + fn report_purification_failure( + &mut self, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + if config::report_symbolic_execution_failures() { + prusti_common::report::log::report_with_writer( + "symbex_purification_failures", + format!( + "{}.{}.{}.dot", + self.source_filename, self.procedure_name, self.purification_failure_count + ), + |writer| self.solver.to_graphviz(writer).unwrap(), + ); + self.trace.push(vir_low::Statement::comment(format!( + "Failed to purify. Failure id: {}", + self.purification_failure_count + ))); + self.trace + .push(vir_low::Statement::assert(false.into(), position)); + self.purification_failure_count += 1; + } + Ok(()) + } + + fn is_purified_inhale( + &self, + location: Location, + predicate: &vir_low::expression::PredicateAccessPredicate, + ) -> Option<&PredicateInstance> { + if let Some(predicate_state) = self.final_state.get_predicate(&predicate.name) { + for predicate_instance in predicate_state.get_instances() { + if predicate_instance.inhale_location == location { + // We can purify out exhaled predicates. + // + // FIXME: We also can purify + // `PredicateInstanceState::FreshNonAliased`, but purifying + // it too early may miss an exhale of the predicate and lead + // to an usoudness. (The unsoudness can be replaced with a + // verification error by uncommenting the asserts in + // `try_removing_predicate_instance`.) + if matches!( + predicate_instance.state, + PredicateInstanceState::Exhaled(_) + | PredicateInstanceState::FreshNonAliased + ) { + return Some(predicate_instance); + } + } + } + } + None + } + + fn is_purified_exhale( + &self, + location: Location, + predicate: &vir_low::expression::PredicateAccessPredicate, + ) -> bool { + if let Some(predicate_state) = self.final_state.get_predicate(&predicate.name) { + for predicate_instance in predicate_state.get_instances() { + if let PredicateInstanceState::Exhaled(exhale_location) = predicate_instance.state { + if exhale_location == location { + assert_eq!(*predicate.permission, predicate_instance.permission_amount); + return true; + } + } + } + } + false + } + + fn save_state(&mut self, label: String) { + assert!(self + .predicate_snapshots_at_label + .insert(label, self.predicate_snapshots.clone()) + .is_none()); + } + + fn purify_snap_calls( + &mut self, + expression: vir_low::Expression, + ) -> SpannedEncodingResult { + let result = purify_snap_calls( + &self.predicate_snapshots, + &self.predicate_snapshots_at_label, + self.solver, + self.program, + expression, + )?; + // assert!(check_non_aliased_snap_calls_purified(&result, self.program)); + Ok(result) + } + + pub(super) fn purify_lifetime_token_inhale( + &mut self, + predicate: &vir_low::PredicateAccessPredicate, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + self.lifetime_tokens + .inhale_predicate(&mut self.trace, self.solver, predicate, position) + } + + pub(super) fn purify_lifetime_token_exhale( + &mut self, + predicate: &vir_low::PredicateAccessPredicate, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + self.lifetime_tokens + .exhale_predicate(&mut self.trace, self.solver, predicate, position) + } + + pub(super) fn try_assert_frac_ref_snapshot_equality( + &mut self, + predicate: &vir_low::PredicateAccessPredicate, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + let predicate_lifetime = &predicate.arguments[0]; + let predicate_snapshot = &predicate.arguments[4]; + let mut snapshot_candidate = None; + if let Some(predicate_state) = self.final_state.get_predicate(&predicate.name) { + for predicate_instance in predicate_state.get_instances() { + let instance_lifetime = &predicate_instance.arguments[0]; + if self + .solver + .is_equal(predicate_lifetime, instance_lifetime)? + { + if snapshot_candidate.is_some() { + // There are multiple snapshots for the same lifetime. + // We cannot assert anything. + return Ok(()); + } + snapshot_candidate = Some(&predicate_instance.arguments[4]); + } + } + } + if let Some(instance_snapshot) = snapshot_candidate { + self.trace.push(vir_low::Statement::comment(format!( + "Asserting that the snapshot of {} is equal to the snapshot of the predicate instance", + predicate.name + ))); + // This does not work because we do not have accees to the lowerer + // anymore ☹. + // + // ```rust + // let extensionality_trigger = + // self.lowerer.snapshots_extensionality_equal_call( + // predicate_snapshot.clone(), instance_snapshot.clone(), + // position, )?; + // ``` + // + // Instead, we use the following hack. + let extensionality_trigger = self.program.predicate_snapshots_extensionality_call( + predicate_snapshot.clone(), + instance_snapshot.clone(), + position, + ); + let extensionality_trigger = purify_snap_calls( + &self.predicate_snapshots, + &self.predicate_snapshots_at_label, + self.solver, + self.program, + extensionality_trigger, + )?; + assert!(check_non_aliased_snap_calls_purified( + &extensionality_trigger, + self.program + )); + self.trace + .push(vir_low::Statement::assert(extensionality_trigger, position)); + } + Ok(()) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/heap/graphviz.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/heap/graphviz.rs new file mode 100644 index 00000000000..252c4aa4728 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/heap/graphviz.rs @@ -0,0 +1,29 @@ +use super::HeapEntry; +use crate::encoder::middle::core_proof::transformations::symbolic_execution::trace_builder::ExecutionTraceHeapView; +use vir_crate::common::graphviz::{escape_html_wrap, Graph, ToGraphviz}; + +impl<'a> ToGraphviz for ExecutionTraceHeapView<'a> { + fn to_graph(&self) -> Graph { + let mut graph = Graph::with_columns(&["statement"]); + for (block_id, block) in self.iter_blocks().enumerate() { + let mut node_builder = graph.create_node(format!("block{block_id}")); + for statement in block.iter_entries() { + let statement_string = match statement { + HeapEntry::Comment(statement) => { + format!( + "{}", + escape_html_wrap(statement) + ) + } + _ => escape_html_wrap(statement.to_string()), + }; + node_builder.add_row_sequence(vec![statement_string]); + } + node_builder.build(); + if let Some(parent) = block.parent() { + graph.add_regular_edge(format!("block{parent}"), format!("block{block_id}")); + } + } + graph + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/heap/lifetime_tokens.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/heap/lifetime_tokens.rs new file mode 100644 index 00000000000..ff2f058291b --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/heap/lifetime_tokens.rs @@ -0,0 +1,157 @@ +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::transformations::symbolic_execution::egg::EGraphState, +}; +use vir_crate::{ + common::expression::BinaryOperationHelpers, + low::{self as vir_low}, +}; + +#[derive(Default)] +pub(super) struct LifetimeTokens { + permission_variables: Vec, +} + +impl LifetimeTokens { + pub(super) fn into_variables(self) -> Vec { + let mut variables = Vec::new(); + for permission_variable in self.permission_variables { + for version in 0..permission_variable.permission_variable_version + 1 { + variables.push(permission_variable.create_variable(version)); + } + } + variables + } + + fn find_permission_variable( + &mut self, + solver: &EGraphState, + lifetime: &vir_low::Expression, + ) -> SpannedEncodingResult> { + for permission_variable in &mut self.permission_variables { + if solver.is_equal(&permission_variable.lifetime, lifetime)? { + return Ok(Some(permission_variable)); + } + } + Ok(None) + } + + pub(super) fn inhale_predicate( + &mut self, + statements: &mut Vec, + solver: &EGraphState, + predicate: &vir_low::PredicateAccessPredicate, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + assert_eq!(predicate.arguments.len(), 1); + if let Some(permission_variable) = + self.find_permission_variable(solver, &predicate.arguments[0])? + { + let current_permission_amount_variable = + permission_variable.current_permission_amount_variable(); + let new_permission_amount_variable = + permission_variable.new_permission_amount_variable(); + statements.push(vir_low::Statement::assume( + vir_low::Expression::equals( + new_permission_amount_variable.into(), + vir_low::Expression::perm_binary_op_no_pos( + vir_low::PermBinaryOpKind::Add, + current_permission_amount_variable.into(), + (*predicate.permission).clone(), + ), + ) + .set_default_position(position), + position, + )); + } else { + let permission_variable = PermissionVariable { + lifetime: predicate.arguments[0].clone(), + permission_variable_name: format!( + "lifetime_token${}", + self.permission_variables.len() + ), + permission_variable_version: 0, + }; + let new_permission_amount_variable = + permission_variable.current_permission_amount_variable(); + self.permission_variables.push(permission_variable); + statements.push(vir_low::Statement::assume( + vir_low::Expression::equals( + new_permission_amount_variable.into(), + (*predicate.permission).clone(), + ) + .set_default_position(position), + position, + )); + } + Ok(()) + } + + pub(super) fn exhale_predicate( + &mut self, + statements: &mut Vec, + solver: &EGraphState, + predicate: &vir_low::PredicateAccessPredicate, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + assert_eq!(predicate.arguments.len(), 1); + if let Some(permission_variable) = + self.find_permission_variable(solver, &predicate.arguments[0])? + { + let current_permission_amount_variable = + permission_variable.current_permission_amount_variable(); + let new_permission_amount_variable = + permission_variable.new_permission_amount_variable(); + statements.push(vir_low::Statement::assert( + vir_low::Expression::greater_equals( + current_permission_amount_variable.clone().into(), + (*predicate.permission).clone(), + ) + .set_default_position(position), + position, + )); + statements.push(vir_low::Statement::assume( + vir_low::Expression::equals( + new_permission_amount_variable.into(), + vir_low::Expression::perm_binary_op_no_pos( + vir_low::PermBinaryOpKind::Sub, + current_permission_amount_variable.into(), + (*predicate.permission).clone(), + ), + ) + .set_default_position(position), + position, + )); + } else { + unreachable!("Exhaling a predicate that was not inhaled before: {predicate}"); + } + Ok(()) + } +} + +struct PermissionVariable { + /// An expression that indicates the lifetime that is mapped to this + /// variable. + lifetime: vir_low::Expression, + /// The name of the variable used to track the permission amount of this + /// lifetime. + permission_variable_name: String, + /// The SSA version of the permission variable. + permission_variable_version: u32, +} + +impl PermissionVariable { + fn create_variable(&self, version: u32) -> vir_low::VariableDecl { + let variable_name = format!("{}${}", self.permission_variable_name, version); + vir_low::VariableDecl::new(variable_name, vir_low::Type::Perm) + } + + fn current_permission_amount_variable(&self) -> vir_low::VariableDecl { + self.create_variable(self.permission_variable_version) + } + + fn new_permission_amount_variable(&mut self) -> vir_low::VariableDecl { + self.permission_variable_version += 1; + self.create_variable(self.permission_variable_version) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/heap/mod.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/heap/mod.rs new file mode 100644 index 00000000000..c4f95d0660c --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/heap/mod.rs @@ -0,0 +1,223 @@ +mod graphviz; +mod entry; +mod state; +mod finalizer; +mod predicate_snapshots; +mod lifetime_tokens; + +use super::{ + program_context::ProgramContext, + trace::Trace, + trace_builder::{ExecutionTraceBuilder, ExecutionTraceHeapView}, +}; +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::transformations::encoder_context::EncoderContext, +}; +use log::debug; +use prusti_common::config; +use vir_crate::low::{self as vir_low}; + +use self::finalizer::TraceFinalizer; +pub(super) use self::{entry::HeapEntry, state::HeapState}; + +impl<'a> ExecutionTraceBuilder<'a> { + pub(super) fn heap_comment( + &mut self, + statement: vir_low::ast::statement::Comment, + ) -> SpannedEncodingResult<()> { + self.add_heap_entry(HeapEntry::Comment(statement))?; + Ok(()) + } + + pub(super) fn heap_label( + &mut self, + statement: vir_low::ast::statement::Label, + ) -> SpannedEncodingResult<()> { + let state = self.current_heap_state_mut(); + state.save_state(statement.label.clone()); + self.add_heap_entry(HeapEntry::Label(statement))?; + Ok(()) + } + + pub(super) fn heap_assume( + &mut self, + expression: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + assert!( + !position.is_default(), + "assume {expression} with default position" + ); + self.add_heap_entry(HeapEntry::Assume(vir_low::ast::statement::Assume { + expression, + position, + }))?; + Ok(()) + } + + pub(super) fn heap_assert( + &mut self, + expression: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + self.add_heap_entry(HeapEntry::Assert(vir_low::ast::statement::Assert { + expression, + position, + }))?; + Ok(()) + } + + fn next_location(&self) -> Location { + let view = self.heap_view(); + Location { + block_id: view.block_count() - 1, + entry_id: view.last_block_entry_count(), + } + } + + pub(super) fn heap_inhale_predicate( + &mut self, + predicate: vir_low::ast::expression::PredicateAccessPredicate, + program: &ProgramContext, + // is_instance_non_aliased: bool, + // non_aliased_predicate_instances: &'a FxHashSet, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + let next_location = self.next_location(); + let (state, solver) = self.current_heap_and_egraph_state_mut(); + // let (state, solver) = self.current_heap_and_egraph_state_mut(); + // solver.saturate()?; + // let mut is_instance_non_aliased = false; + // for non_aliased_predicate in non_aliased_predicate_instances { + // if non_aliased_predicate.name == predicate.name { + // if arguments_match( + // &non_aliased_predicate.arguments, + // &predicate.arguments, + // solver, + // )? { + // is_instance_non_aliased = true; + // break; + // } + // } + // } + state.add_predicate_instance( + solver, + program, + &predicate, + // is_instance_non_aliased, + next_location, + )?; + self.add_heap_entry(HeapEntry::InhalePredicate(predicate, position)) + } + + pub(super) fn heap_inhale_generic( + &mut self, + expression: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + let state = self.current_heap_state_mut(); + for predicate_name in expression.collect_access_predicate_names() { + state.mark_predicate_instances_seen_qp_inhale(&predicate_name); + } + self.add_heap_entry(HeapEntry::InhaleGeneric(vir_low::ast::statement::Inhale { + expression, + position, + })) + } + + pub(super) fn heap_exhale_predicate( + &mut self, + predicate: vir_low::ast::expression::PredicateAccessPredicate, + program: &mut ProgramContext, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + let next_location = self.next_location(); + let (state, solver) = self.current_heap_and_egraph_state_mut(); + let result = state.try_removing_predicate_instance( + solver, + program, + &predicate, + next_location, + position, + )?; + if let state::PurificationResult::Error(error_position) = result { + self.add_heap_entry(HeapEntry::Comment(vir_low::Comment { + comment: format!("Failed to exhale non-aliased predicate: {}", predicate), + }))?; + self.add_heap_entry(HeapEntry::Assert(vir_low::Assert { + expression: false.into(), + position: error_position, + }))?; + } + self.add_heap_entry(HeapEntry::ExhalePredicate(predicate, position))?; + Ok(()) + } + + pub(super) fn heap_exhale_generic( + &mut self, + expression: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + let state = self.current_heap_state_mut(); + for predicate_name in expression.collect_access_predicate_names() { + state.mark_predicate_instances_seen_qp_exhale(&predicate_name); + } + self.add_heap_entry(HeapEntry::ExhaleGeneric(vir_low::ast::statement::Exhale { + expression, + position, + })) + } + + pub(super) fn heap_finalize_trace( + &mut self, + program: &ProgramContext, + block_id: usize, + ) -> SpannedEncodingResult { + debug!("Finalizing trace"); + // let (state, solver) = self.current_heap_and_egraph_state(); + let mut solver = self.steal_egraph_solver(block_id); + let state = self.heap_state(block_id); + let view = self.heap_view(); + // let last_block_id = view.last_block_id(); + let last_block_id = block_id; + let mut trace_finalizer = TraceFinalizer::new( + self.source_filename, + self.procedure_name, + state, + &mut solver, + program, + ); + self.finalize_trace_for_block(&mut trace_finalizer, view, last_block_id)?; + Ok(trace_finalizer.into_trace()) + } + + fn finalize_trace_for_block( + &self, + trace_finalizer: &mut TraceFinalizer, + view: ExecutionTraceHeapView, + block_id: usize, + ) -> SpannedEncodingResult<()> { + let block = view.get_block(block_id); + if let Some(parent_id) = block.parent() { + self.finalize_trace_for_block(trace_finalizer, view, parent_id)?; + } + trace_finalizer.add_variables(block.get_new_variables())?; + trace_finalizer.add_labels(block.get_new_labels())?; + let trace_checkpoint = trace_finalizer.trace_len(); + for (entry_id, entry) in block.iter_entries().enumerate() { + trace_finalizer.add_entry(Location { block_id, entry_id }, entry)?; + } + let finalized_statements = trace_finalizer.trace_suffix(trace_checkpoint); + if config::symbolic_execution_single_method() { + block.set_finalized_statements(finalized_statements); + } + Ok(()) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +struct Location { + block_id: usize, + entry_id: usize, +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/heap/predicate_snapshots.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/heap/predicate_snapshots.rs new file mode 100644 index 00000000000..88ebe10a903 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/heap/predicate_snapshots.rs @@ -0,0 +1,453 @@ +use crate::encoder::{ + errors::{SpannedEncodingError, SpannedEncodingResult}, + middle::core_proof::transformations::{ + encoder_context::EncoderContext, + symbolic_execution::{ + egg::EGraphState, + program_context::ProgramContext, + utils::{all_heap_independent, arguments_match, is_place_non_aliased}, + }, + }, +}; +use log::debug; +use std::collections::BTreeMap; +use vir_crate::{ + common::display, + low::{ + self as vir_low, + expression::visitors::{ExpressionFallibleFolder, ExpressionWalker}, + }, +}; + +pub(super) fn check_non_aliased_snap_calls_purified<'a>( + expression: &vir_low::Expression, + program: &'a ProgramContext<'a, impl EncoderContext>, +) -> bool { + struct Walker<'a, EC: EncoderContext> { + program: &'a ProgramContext<'a, EC>, + found_violation: bool, + } + impl<'a, EC: EncoderContext> ExpressionWalker for Walker<'a, EC> { + fn walk_trigger(&mut self, trigger: &vir_low::Trigger) { + for term in &trigger.terms { + self.walk_expression(term); + } + } + fn walk_func_app_enum(&mut self, func_app: &vir_low::expression::FuncApp) { + self.walk_func_app(func_app); + let function = self.program.get_function(&func_app.function_name); + assert_eq!(function.parameters.len(), func_app.arguments.len()); + match function.kind { + vir_low::FunctionKind::CallerFor => {} + vir_low::FunctionKind::SnapRange => {} + vir_low::FunctionKind::MemoryBlockBytes => {} + vir_low::FunctionKind::Snap => { + if is_place_non_aliased(&func_app.arguments[0]) { + self.found_violation = true; + } + } + } + } + fn walk_quantifier_enum(&mut self, quantifier: &vir_low::Quantifier) { + self.walk_quantifier(quantifier); + if quantifier.body.is_heap_independent() { + for trigger in &quantifier.triggers { + for term in &trigger.terms { + assert!(term.is_heap_independent(), "heap dependent trigger: {term}\nin indepedentent quantifier: {quantifier}"); + } + } + } + } + } + let mut purifier = Walker { + program, + found_violation: false, + }; + purifier.walk_expression(expression); + !purifier.found_violation +} + +pub(super) fn purify_snap_calls_vec_with_retry<'a>( + predicate_snapshots: &'a PredicateSnapshots, + predicate_snapshots_at_label: &'a BTreeMap, + solver: &'a mut EGraphState, + program: &'a ProgramContext<'a, impl EncoderContext>, + expressions: Vec, +) -> SpannedEncodingResult> { + let mut expressions = purify_snap_calls_vec( + predicate_snapshots, + predicate_snapshots_at_label, + solver, + program, + expressions, + )?; + if !all_heap_independent(&expressions) { + solver.saturate()?; + expressions = purify_snap_calls_vec( + predicate_snapshots, + predicate_snapshots_at_label, + solver, + program, + expressions, + )?; + } + solver.intern_heap_independent_terms(&expressions)?; + for expression in &expressions { + assert!(check_non_aliased_snap_calls_purified(expression, program)); + } + Ok(expressions) +} + +pub(super) fn purify_snap_calls_vec<'a>( + predicate_snapshots: &'a PredicateSnapshots, + predicate_snapshots_at_label: &'a BTreeMap, + solver: &'a mut EGraphState, + program: &'a ProgramContext<'a, impl EncoderContext>, + original_expressions: Vec, +) -> SpannedEncodingResult> { + let mut expressions = Vec::new(); + for expression in original_expressions { + expressions.push(purify_snap_calls( + predicate_snapshots, + predicate_snapshots_at_label, + solver, + program, + expression, + )?); + } + Ok(expressions) +} + +pub(super) fn purify_snap_calls<'a>( + predicate_snapshots: &'a PredicateSnapshots, + predicate_snapshots_at_label: &'a BTreeMap, + solver: &'a mut EGraphState, + program: &'a ProgramContext<'a, impl EncoderContext>, + expression: vir_low::Expression, +) -> SpannedEncodingResult { + struct Purifier<'a, EC: EncoderContext> { + predicate_snapshots: &'a PredicateSnapshots, + predicate_snapshots_at_label: &'a BTreeMap, + solver: &'a mut EGraphState, + program: &'a ProgramContext<'a, EC>, + label: Option, + argument_purified: bool, + } + impl<'a, EC: EncoderContext> ExpressionFallibleFolder for Purifier<'a, EC> { + type Error = SpannedEncodingError; + + fn fallible_fold_trigger( + &mut self, + mut trigger: vir_low::Trigger, + ) -> Result { + for term in std::mem::take(&mut trigger.terms) { + let new_term = self.fallible_fold_expression(term)?; + trigger.terms.push(new_term); + } + Ok(trigger) + } + + fn fallible_fold_func_app_enum( + &mut self, + func_app: vir_low::expression::FuncApp, + ) -> Result { + let old_argument_purified = self.argument_purified; + self.argument_purified = false; + let func_app = self.fallible_fold_func_app(func_app)?; + if self.argument_purified + && all_heap_independent(&func_app.arguments) + && self + .solver + .intern_heap_independent_terms(&func_app.arguments)? + { + self.solver.saturate()?; + } + self.argument_purified = old_argument_purified; + let function = self.program.get_function(&func_app.function_name); + assert_eq!(function.parameters.len(), func_app.arguments.len()); + match function.kind { + vir_low::FunctionKind::CallerFor | vir_low::FunctionKind::SnapRange => { + Ok(vir_low::Expression::FuncApp(func_app)) + } + vir_low::FunctionKind::MemoryBlockBytes | vir_low::FunctionKind::Snap => { + if let Some(snapshot_variable) = + self.resolve_snapshot(&func_app.function_name, &func_app.arguments)? + { + self.argument_purified = true; + Ok(vir_low::Expression::local( + snapshot_variable, + func_app.position, + )) + } else { + Ok(vir_low::Expression::FuncApp(func_app)) + } + } + } + } + + fn fallible_fold_labelled_old( + &mut self, + mut labelled_old: vir_low::expression::LabelledOld, + ) -> Result { + std::mem::swap(&mut labelled_old.label, &mut self.label); + labelled_old.base = self.fallible_fold_expression_boxed(labelled_old.base)?; + std::mem::swap(&mut labelled_old.label, &mut self.label); + Ok(labelled_old) + } + } + impl<'a, EC: EncoderContext> Purifier<'a, EC> { + fn resolve_snapshot( + &mut self, + function_name: &str, + arguments: &[vir_low::Expression], + ) -> SpannedEncodingResult> { + let predicate_snapshots = if let Some(label) = &self.label { + self.predicate_snapshots_at_label.get(label).unwrap() + } else { + self.predicate_snapshots + }; + let Some(predicate_name) = self.program.get_snapshot_predicate(function_name) else { + // The final snapshot function is already pure and, + // therefore, is not mapped to a predicate. + return Ok(None); + }; + predicate_snapshots.find_snapshot(predicate_name, arguments, self.solver) + } + } + // eprintln!("predicate_snapshots: {predicate_snapshots}"); + let mut purifier = Purifier { + predicate_snapshots, + predicate_snapshots_at_label, + solver, + program, + label: None, + argument_purified: false, + }; + // eprintln!("expression: {expression}"); + purifier.fallible_fold_expression(expression) +} + +#[derive(Default, Clone)] +pub(super) struct PredicateSnapshots { + snapshots: BTreeMap>, + variables: Vec, +} + +impl std::fmt::Display for PredicateSnapshots { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for (predicate_name, snapshots) in &self.snapshots { + writeln!(f, "Predicate {}", predicate_name)?; + for snapshot in snapshots { + writeln!(f, " {}", snapshot)?; + } + } + Ok(()) + } +} + +impl PredicateSnapshots { + /// Method to be used during the initial symbolic execution. + pub(super) fn create_non_aliased_predicate_snapshot( + &mut self, + program_context: &ProgramContext, + predicate_name: &str, + arguments: Vec, + ) -> SpannedEncodingResult { + let predicate_snapshots = self + .snapshots + .entry(predicate_name.to_string()) + .or_default(); + let snapshot_variable_name = format!( + "snapshot_non_aliased${}$round{}${}", + predicate_name, + program_context.get_purification_round(), + predicate_snapshots.len() + ); + let snapshot = if let Some(ty) = program_context.get_snapshot_type(predicate_name) { + let snapshot = vir_low::VariableDecl::new(snapshot_variable_name.clone(), ty); + self.variables.push(snapshot.clone()); + PredicateSnapshotState::Inhaled(snapshot) + } else { + PredicateSnapshotState::NoSnapshot + }; + assert!( + all_heap_independent(&arguments), + "arguments: {}", + display::cjoin(&arguments) + ); + predicate_snapshots.push(PredicateSnapshot { + arguments, + snapshot, + }); + Ok(snapshot_variable_name) + } + + /// Method to be used by the finalizer. + pub(super) fn register_predicate_snapshot( + &mut self, + program_context: &ProgramContext, + predicate_name: &str, + arguments: Vec, + snapshot_variable_name: String, + ) { + let predicate_snapshots = self + .snapshots + .entry(predicate_name.to_string()) + .or_default(); + let snapshot = if let Some(ty) = program_context.get_snapshot_type(predicate_name) { + let snapshot = vir_low::VariableDecl::new(snapshot_variable_name, ty); + self.variables.push(snapshot.clone()); + PredicateSnapshotState::Inhaled(snapshot) + } else { + PredicateSnapshotState::NoSnapshot + }; + // assert!(all_heap_independent(&predicate.arguments), "arguments: {}", display::cjoin(&predicate.arguments)); + predicate_snapshots.push(PredicateSnapshot { + arguments, + snapshot, + }); + } + + /// Method to be used by the finalizer. + pub(super) fn create_predicate_snapshot( + &mut self, + program_context: &ProgramContext, + predicate_name: &str, + arguments: Vec, + ) { + let predicate_snapshots = self + .snapshots + .entry(predicate_name.to_string()) + .or_default(); + let snapshot_variable_name = format!( + "snapshot${}$round{}${}", + predicate_name, + program_context.get_purification_round(), + predicate_snapshots.len() + ); + let snapshot = if let Some(ty) = program_context.get_snapshot_type(predicate_name) { + let snapshot = vir_low::VariableDecl::new(snapshot_variable_name, ty); + self.variables.push(snapshot.clone()); + PredicateSnapshotState::Inhaled(snapshot) + } else { + PredicateSnapshotState::NoSnapshot + }; + // assert!(all_heap_independent(&predicate.arguments), "arguments: {}", display::cjoin(&predicate.arguments)); + predicate_snapshots.push(PredicateSnapshot { + arguments, + snapshot, + }); + } + + pub(super) fn destroy_predicate_snapshot( + &mut self, + // predicate: &vir_low::expression::PredicateAccessPredicate, + predicate_name: &str, + arguments: &[vir_low::Expression], + solver: &mut EGraphState, + ) -> SpannedEncodingResult<()> { + let predicate_snapshots = self + .snapshots + .get_mut(predicate_name) + .unwrap_or_else(|| panic!("no key: {predicate_name} {}", display::cjoin(arguments))); + for predicate_snapshot in predicate_snapshots.iter_mut() { + if predicate_snapshot.snapshot.is_not_exhaled() + && predicate_snapshot.matches_arguments(arguments, solver)? + { + predicate_snapshot.snapshot = PredicateSnapshotState::Exhaled; + return Ok(()); + } + } + solver.saturate()?; + for predicate_snapshot in predicate_snapshots { + if predicate_snapshot.snapshot.is_not_exhaled() + && predicate_snapshot.matches_arguments(arguments, solver)? + { + predicate_snapshot.snapshot = PredicateSnapshotState::Exhaled; + return Ok(()); + } + } + unreachable!( + "snapshot not found: {predicate_name} {}", + display::cjoin(arguments) + ); + } + + pub(super) fn find_snapshot( + &self, + predicate_name: &str, + arguments: &[vir_low::Expression], + solver: &EGraphState, + ) -> SpannedEncodingResult> { + if let Some(predicate_snapshots) = self.snapshots.get(predicate_name) { + for predicate_snapshot in predicate_snapshots { + if let PredicateSnapshotState::Inhaled(snapshot) = &predicate_snapshot.snapshot { + if predicate_snapshot.matches_arguments(arguments, solver)? { + return Ok(Some(snapshot.clone())); + } + } + } + } + Ok(None) + } + + pub(super) fn into_variables(self) -> Vec { + self.variables + } +} + +#[derive(Clone, derive_more::Display)] +enum PredicateSnapshotState { + /// The snapshot is valid. + Inhaled(vir_low::VariableDecl), + /// The snapshot was exhaled and no longer valid. + Exhaled, + /// The predicate does not have a snapshot. + NoSnapshot, +} + +impl PredicateSnapshotState { + pub(super) fn is_not_exhaled(&self) -> bool { + matches!( + self, + PredicateSnapshotState::Inhaled(_) | PredicateSnapshotState::NoSnapshot + ) + } +} + +#[derive(Clone)] +pub(super) struct PredicateSnapshot { + /// Predicate arguments. + arguments: Vec, + /// None means that the corresponding predicate was exhaled. + snapshot: PredicateSnapshotState, +} + +impl std::fmt::Display for PredicateSnapshot { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}: {}", display::cjoin(&self.arguments), self.snapshot) + } +} + +impl PredicateSnapshot { + pub(super) fn matches( + &self, + predicate: &vir_low::ast::expression::PredicateAccessPredicate, + solver: &EGraphState, + ) -> SpannedEncodingResult { + arguments_match(&self.arguments, &predicate.arguments, solver) + } + + pub(super) fn matches_arguments( + &self, + arguments: &[vir_low::Expression], + solver: &EGraphState, + ) -> SpannedEncodingResult { + debug!( + "matches_arguments:\n self: {}\n other: {}", + display::cjoin(&self.arguments), + display::cjoin(arguments) + ); + arguments_match(&self.arguments, arguments, solver) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/heap/state.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/heap/state.rs new file mode 100644 index 00000000000..4610875f34d --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/heap/state.rs @@ -0,0 +1,393 @@ +use super::{ + predicate_snapshots::{ + purify_snap_calls, purify_snap_calls_vec_with_retry, PredicateSnapshots, + }, + Location, +}; +use crate::encoder::{ + errors::{ErrorCtxt, SpannedEncodingResult}, + middle::core_proof::transformations::{ + encoder_context::EncoderContext, + symbolic_execution::{ + egg::EGraphState, + program_context::ProgramContext, + utils::{all_heap_independent, arguments_match, is_place_non_aliased}, + }, + }, +}; +use std::collections::BTreeMap; +use vir_crate::{ + common::display, + low::{self as vir_low}, +}; + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub(super) enum PurificationResult { + Success, + Error(vir_low::Position), +} + +#[derive(Default, Clone)] +pub(in super::super) struct HeapState { + /// A map from predicate names to their state. + predicates: BTreeMap, + predicate_snapshots: PredicateSnapshots, + predicate_snapshots_at_label: BTreeMap, +} + +impl std::fmt::Display for HeapState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for (predicate_name, predicate_state) in &self.predicates { + writeln!(f, "{predicate_name}:")?; + for predicate_instance in &predicate_state.instances { + writeln!( + f, + " {} @ {:?}: {}, {:?}", + display::cjoin(&predicate_instance.arguments), + predicate_instance.inhale_location, + predicate_instance.permission_amount, + predicate_instance.state + )?; + } + } + writeln!( + f, + "current predicate snapshots:\n{}", + self.predicate_snapshots + )?; + Ok(()) + } +} + +impl HeapState { + fn is_predicate_instance_non_aliased( + &mut self, + solver: &mut EGraphState, + program: &ProgramContext, + predicate_name: &str, + arguments: &[vir_low::Expression], + ) -> SpannedEncodingResult { + if program.is_predicate_kind_non_aliased(predicate_name) { + return Ok(true); + } + fn construct_predicate_address_non_aliased_call( + predicate_address: &vir_low::Expression, + ) -> vir_low::Expression { + use vir_low::macros::*; + let address_is_non_aliased = ty!(Bool); + expr! { + (ComputeAddress::address_is_non_aliased([predicate_address.clone()])) + } + } + match program.get_predicate_kind(predicate_name) { + vir_low::PredicateKind::MemoryBlock => { + let predicate_address = &arguments[0]; + let predicate_address_non_aliased_call = + construct_predicate_address_non_aliased_call(predicate_address); + solver.intern_term(&predicate_address_non_aliased_call)?; + if solver.is_true(&predicate_address_non_aliased_call)? { + return Ok(true); + } else { + solver.saturate()?; + if solver.is_true(&predicate_address_non_aliased_call)? { + return Ok(true); + } + } + } + vir_low::PredicateKind::Owned => { + let predicate_place = &arguments[0]; + if is_place_non_aliased(predicate_place) { + return Ok(true); + } + } + _ => {} + } + Ok(false) + } + + pub(in super::super) fn purify_expression_with_retry( + &mut self, + solver: &mut EGraphState, + program: &ProgramContext, + expression: vir_low::Expression, + ) -> SpannedEncodingResult { + let mut expression = purify_snap_calls( + &self.predicate_snapshots, + &self.predicate_snapshots_at_label, + solver, + program, + expression, + )?; + if !expression.is_heap_independent() { + solver.saturate()?; + expression = purify_snap_calls( + &self.predicate_snapshots, + &self.predicate_snapshots_at_label, + solver, + program, + expression, + )?; + } + Ok(expression) + } + + fn purify_predicate_arguments_with_retry( + &mut self, + solver: &mut EGraphState, + program: &ProgramContext, + arguments: Vec, + ) -> SpannedEncodingResult> { + purify_snap_calls_vec_with_retry( + &self.predicate_snapshots, + &self.predicate_snapshots_at_label, + solver, + program, + arguments, + ) + // eprintln!("purify_predicate_arguments_with_retry"); + // eprintln!(" 1: {}", display::cjoin(&arguments)); + // let mut arguments = purify_snap_calls_vec( + // &self.predicate_snapshots, + // &self.predicate_snapshots_at_label, + // solver, + // program, + // arguments, + // )?; + // eprintln!(" 2: {}", display::cjoin(&arguments)); + // if !all_heap_independent(&arguments) { + // solver.saturate()?; + // arguments = purify_snap_calls_vec( + // &self.predicate_snapshots, + // &self.predicate_snapshots_at_label, + // solver, + // program, + // arguments, + // )?; + // eprintln!(" 3: {}", display::cjoin(&arguments)); + // } + // eprintln!(" 4: {}", display::cjoin(&arguments)); + // solver.intern_heap_independent_terms(&arguments)?; + // Ok(arguments) + } + + pub(super) fn save_state(&mut self, label: String) { + assert!(self + .predicate_snapshots_at_label + .insert(label, self.predicate_snapshots.clone()) + .is_none()); + } + + pub(super) fn add_predicate_instance( + &mut self, + solver: &mut EGraphState, + program: &ProgramContext, + predicate: &vir_low::ast::expression::PredicateAccessPredicate, + inhale_location: Location, + ) -> SpannedEncodingResult<()> { + let arguments = self.purify_predicate_arguments_with_retry( + solver, + program, + predicate.arguments.clone(), + )?; + let (state, snapshot) = if all_heap_independent(&arguments) { + if self.is_predicate_instance_non_aliased( + solver, + program, + &predicate.name, + &arguments, + )? { + let predicate_snapshot = self + .predicate_snapshots + .create_non_aliased_predicate_snapshot( + program, + &predicate.name, + arguments.clone(), + )?; + ( + PredicateInstanceState::FreshNonAliased, + Some(predicate_snapshot), + ) + } else { + (PredicateInstanceState::FreshAliased, None) + } + } else { + self.mark_predicate_instances_seen_qp_inhale(&predicate.name); + (PredicateInstanceState::FreshHeapDependent, None) + }; + let predicate_name = predicate.name.clone(); + let predicate_state = + self.predicates + .entry(predicate_name) + .or_insert_with(|| PredicateState { + instances: Vec::new(), + }); + let predicate_instance = PredicateInstance { + arguments, + permission_amount: (*predicate.permission).clone(), + inhale_location, + state, + snapshot, + }; + predicate_state.instances.push(predicate_instance); + Ok(()) + } + + pub(super) fn try_removing_predicate_instance( + &mut self, + solver: &mut EGraphState, + program: &mut ProgramContext, + predicate: &vir_low::ast::expression::PredicateAccessPredicate, + exhale_location: Location, + position: vir_low::Position, + ) -> SpannedEncodingResult { + let arguments = self.purify_predicate_arguments_with_retry( + solver, + program, + predicate.arguments.clone(), + )?; + if all_heap_independent(&arguments) { + let is_instance_non_aliased = self.is_predicate_instance_non_aliased( + solver, + program, + &predicate.name, + &arguments, + )?; + if let Some(predicate_state) = self.predicates.get_mut(&predicate.name) { + for predicate_instance in &mut predicate_state.instances { + // eprintln!("predicate_instance {} state: {:?}", display::cjoin(&predicate_instance.arguments), predicate_instance.state); + if matches!( + predicate_instance.state, + PredicateInstanceState::FreshAliased + | PredicateInstanceState::FreshNonAliased + | PredicateInstanceState::SeenQPExhale + ) && predicate_instance.matches(&arguments, &predicate.permission, solver)? + { + predicate_instance.state = PredicateInstanceState::Exhaled(exhale_location); + assert_eq!(predicate_instance.permission_amount, *predicate.permission); + if predicate_instance.snapshot.is_some() { + self.predicate_snapshots.destroy_predicate_snapshot( + &predicate.name, + &arguments, + solver, + )?; + } + return Ok(PurificationResult::Success); + } + } + if is_instance_non_aliased { + // Report the failure to the caller so that they mark the + // state as unreachable. + let new_position = program + .env() + .change_error_context(position, ErrorCtxt::ExhaleNonAliasedPredicate); + return Ok(PurificationResult::Error(new_position)); + // let span = program.env().get_span(position).unwrap(); + // error_incorrect!(span => + // "there might be insufficient permission to a place" + // ) + } + assert!( + !is_instance_non_aliased, + "Failed to exhale non-aliased predicate: {predicate}" + ); + // Failed to exhale, mark all instances as failed exhale. + for predicate_instance in &mut predicate_state.instances { + if matches!( + predicate_instance.state, + PredicateInstanceState::FreshAliased | PredicateInstanceState::SeenQPExhale + ) { + predicate_instance.state = PredicateInstanceState::SeenFailedExhale; + } + } + } else if is_instance_non_aliased { + // Report the failure to the caller so that they mark the + // state as unreachable. + let new_position = program + .env() + .change_error_context(position, ErrorCtxt::UnexpectedUnreachable); + return Ok(PurificationResult::Error(new_position)); + } + } else { + self.mark_predicate_instances_seen_qp_exhale(&predicate.name); + } + Ok(PurificationResult::Success) + } + + pub(super) fn mark_predicate_instances_seen_qp_inhale(&mut self, predicate_name: &str) { + if let Some(predicate_state) = self.predicates.get_mut(predicate_name) { + for predicate_instance in &mut predicate_state.instances { + if predicate_instance.state == PredicateInstanceState::SeenQPExhale { + predicate_instance.state = PredicateInstanceState::SeenQPInhale; + } + } + } + } + + pub(super) fn mark_predicate_instances_seen_qp_exhale(&mut self, predicate_name: &str) { + if let Some(predicate_state) = self.predicates.get_mut(predicate_name) { + for predicate_instance in &mut predicate_state.instances { + if predicate_instance.state == PredicateInstanceState::FreshAliased { + predicate_instance.state = PredicateInstanceState::SeenQPExhale; + } + } + } + } + + pub(super) fn get_predicate(&self, predicate_name: &str) -> Option<&PredicateState> { + self.predicates.get(predicate_name) + } +} + +#[derive(Clone)] +pub(super) struct PredicateState { + instances: Vec, +} + +impl PredicateState { + pub(super) fn get_instances(&self) -> &[PredicateInstance] { + &self.instances + } +} + +#[derive(Clone)] +pub(super) struct PredicateInstance { + /// The arguments of the predicate instance. + pub(super) arguments: Vec, + pub(super) permission_amount: vir_low::Expression, + /// The location of the inhale statement that inhaled this predicate instance. + pub(super) inhale_location: Location, + /// The state of the predicate. + pub(super) state: PredicateInstanceState, + /// The snapshot variable name given when purifying a non-aliased predicate. + pub(super) snapshot: Option, +} + +impl PredicateInstance { + fn matches( + &self, + predicate_arguments: &[vir_low::Expression], + permission: &vir_low::Expression, + solver: &EGraphState, + ) -> SpannedEncodingResult { + assert_eq!(self.arguments.len(), predicate_arguments.len()); + if !arguments_match(&self.arguments, predicate_arguments, solver)? { + return Ok(false); + } + Ok(self.permission_amount == *permission) + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub(super) enum PredicateInstanceState { + /// The predicate was inhaled and has not seen QP exhale yet. The predicate + /// instance can be aliased by QPs, etc. + FreshAliased, + /// The predicate was inhaled. The predicate instance cannot be aliased by + /// QPs. + FreshNonAliased, + SeenQPExhale, + SeenQPInhale, + SeenFailedExhale, + Exhaled(Location), + FreshHeapDependent, +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/mod.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/mod.rs new file mode 100644 index 00000000000..ec3100179b5 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/mod.rs @@ -0,0 +1,420 @@ +//! This module contains the symbolic execution engine that is used to purify +//! predicates in the Viper program. This module depends on `ErrorManager` and, +//! therefore, has to be in the `prusti-viper` crate. + +mod trace_builder; +mod egg; +mod statements; +mod heap; +mod trace; +mod program_context; +pub(super) mod utils; +mod simplifier; +mod consistency_tracker; + +use self::program_context::ProgramContext; +use super::encoder_context::EncoderContext; +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::{predicates::OwnedPredicateInfo, snapshots::SnapshotDomainsInfo}, +}; +use log::debug; +use prusti_common::config; +use rustc_hash::FxHashSet; +use std::collections::BTreeMap; +use vir_crate::{ + common::{ + expression::{BinaryOperationHelpers, ExpressionIterator, UnaryOperationHelpers}, + graphviz::ToGraphviz, + }, + low::{self as vir_low}, +}; + +pub(in super::super) fn purify_with_symbolic_execution( + encoder: &mut impl EncoderContext, + source_filename: &str, + program: vir_low::Program, + non_aliased_memory_block_addresses: FxHashSet, + snapshot_domains_info: &SnapshotDomainsInfo, + owned_predicates_info: BTreeMap, + extensionality_gas_constant: &vir_low::Expression, + purification_round: u32, +) -> SpannedEncodingResult { + debug!( + "purify_with_symbolic_execution {} {} {}", + source_filename, program.name, purification_round + ); + let mut executor = Executor::new(purification_round); + let program = executor.execute( + source_filename, + program, + non_aliased_memory_block_addresses, + snapshot_domains_info, + owned_predicates_info, + extensionality_gas_constant, + encoder, + )?; + Ok(program) +} + +struct Executor { + /// Which iteration of purification with symbolic execution we are currently + /// executing? + /// + /// 1. The first iteration should purify all stack-allocated variables that + /// are non-aliased by default. + /// 2. The second iteration should purify heap resources that are dependent + /// only on stack-allocated variables. + purification_round: u32, +} + +struct ProcedureExecutor<'a, 'c, EC: EncoderContext> { + executor: &'a mut Executor, + source_filename: &'a str, + program_context: &'a mut ProgramContext<'c, EC>, + continuations: Vec, + exhale_label_generator_counter: u64, + /// The execution trace showing in what order the statements were executed. + execution_trace_builder: trace_builder::ExecutionTraceBuilder<'a>, + /// The original execution traces. + original_traces: Vec, + /// Traces in which purifiable predicates were purified. + final_traces: Vec, + trace_counter: u64, +} + +#[derive(Debug)] +pub struct Continuation { + next_block_label: vir_low::Label, + parent_block_label: vir_low::Label, + condition: vir_low::Expression, +} + +impl Executor { + pub(crate) fn new(purification_round: u32) -> Self { + Self { purification_round } + } + + pub(crate) fn execute( + &mut self, + source_filename: &str, + mut program: vir_low::Program, + non_aliased_memory_block_addresses: FxHashSet, + snapshot_domains_info: &SnapshotDomainsInfo, + owned_predicates_info: BTreeMap, + extensionality_gas_constant: &vir_low::Expression, + encoder: &mut impl EncoderContext, + ) -> SpannedEncodingResult { + let mut program_context = ProgramContext::new( + &program.domains, + &program.functions, + &program.predicates, + snapshot_domains_info, + owned_predicates_info, + &non_aliased_memory_block_addresses, + extensionality_gas_constant, + self.purification_round, + encoder, + ); + let mut new_procedures = Vec::new(); + for procedure in program.procedures { + let procedure_name = procedure.name.clone(); + let procedure_executor = ProcedureExecutor::new( + self, + source_filename, + &procedure_name, + &mut program_context, + )?; + procedure_executor.execute_procedure(procedure, &mut new_procedures)?; + } + program.procedures = new_procedures; + Ok(program) + } +} + +impl<'a, 'c, EC: EncoderContext> ProcedureExecutor<'a, 'c, EC> { + fn new( + executor: &'a mut Executor, + source_filename: &'a str, + procedure_name: &'a str, + program_context: &'a mut ProgramContext<'c, EC>, + ) -> SpannedEncodingResult { + let (bool_type, bool_domain_info) = program_context.get_bool_domain_info(); + Ok(Self { + executor, + source_filename, + continuations: Vec::new(), + exhale_label_generator_counter: 0, + execution_trace_builder: trace_builder::ExecutionTraceBuilder::new( + source_filename, + procedure_name, + program_context.get_domains(), + bool_type, + bool_domain_info, + )?, + program_context, + original_traces: Vec::new(), + final_traces: Vec::new(), + trace_counter: 0, + }) + } + + fn execute_procedure( + mut self, + procedure: vir_low::ProcedureDecl, + new_procedures: &mut Vec, + ) -> SpannedEncodingResult<()> { + debug!( + "Executing procedure: {} round: {}", + procedure.name, self.executor.purification_round + ); + // Intern all non-aliased predicates. + for address in self + .program_context + .get_non_aliased_memory_block_addresses() + { + assert!(address.is_heap_independent()); + use vir_low::macros::*; + let address_is_non_aliased = ty!(Bool); + let address_non_aliased_call = expr! { + (ComputeAddress::address_is_non_aliased([address.clone()])) + }; + self.execution_trace_builder + .current_egraph_state() + .assume(&address_non_aliased_call)?; + } + let mut current_block = procedure.entry.clone(); + loop { + if self + .execution_trace_builder + .current_egraph_state() + .is_inconsistent()? + { + self.finalize_trace()?; + if let Some(new_current_block) = self.next_continuation(procedure.position)? { + current_block = new_current_block; + continue; + } else { + break; + } + } + let block = procedure.basic_blocks.get(¤t_block).unwrap(); + self.execute_block(¤t_block, block)?; + if self + .execution_trace_builder + .current_egraph_state() + .is_inconsistent()? + { + self.finalize_trace()?; + if let Some(new_current_block) = self.next_continuation(procedure.position)? { + current_block = new_current_block; + continue; + } else { + break; + } + } + match &block.successor { + vir_low::Successor::Return => { + self.finalize_trace()?; + if let Some(new_current_block) = self.next_continuation(procedure.position)? { + current_block = new_current_block; + } else { + break; + } + } + vir_low::Successor::Goto(label) => current_block = label.clone(), + vir_low::Successor::GotoSwitch(targets) => { + let parent_block_label = current_block.clone(); + self.execution_trace_builder + .add_split_point(parent_block_label.clone())?; + // Since the jumps are evaluated one after another, we need + // to negate all the previous conditions when considering + // the new one. + let mut negated_conditions = Vec::new(); + let mut targets = targets.iter(); + let (condition, label) = targets.next().unwrap(); + self.assume_condition(condition.clone(), procedure.position)?; + current_block = label.clone(); + negated_conditions.push(UnaryOperationHelpers::not(condition.clone())); + for (condition, label) in targets { + let continuation = Continuation { + next_block_label: label.clone(), + parent_block_label: parent_block_label.clone(), + condition: vir_low::Expression::and( + negated_conditions.clone().into_iter().conjoin(), + condition.clone(), + ), + }; + self.continuations.push(continuation); + negated_conditions.push(UnaryOperationHelpers::not(condition.clone())); + } + } + } + } + self.finalize_traces()?; + if prusti_common::config::dump_debug_info() { + prusti_common::report::log::report_with_writer( + "graphviz_method_vir_symbex_original", + format!( + "{}.{}.round-{}.dot", + self.source_filename, procedure.name, self.executor.purification_round, + ), + |writer| { + self.execution_trace_builder + .original_view() + .to_graphviz(writer) + .unwrap() + }, + ); + for (i, trace) in self.original_traces.iter().enumerate() { + prusti_common::report::log::report_with_writer( + "vir_symbex_original_traces", + format!( + "{}.{}.round-{}.{}.vpr", + self.source_filename, procedure.name, self.executor.purification_round, i + ), + |writer| trace.write(writer).unwrap(), + ); + } + prusti_common::report::log::report_with_writer( + "graphviz_method_vir_symbex_optimized", + format!( + "{}.{}.round-{}.dot", + self.source_filename, procedure.name, self.executor.purification_round, + ), + |writer| { + self.execution_trace_builder + .heap_view() + .to_graphviz(writer) + .unwrap() + }, + ); + for (i, trace) in self.final_traces.iter().enumerate() { + prusti_common::report::log::report_with_writer( + "vir_symbex_optimized_traces", + format!( + "{}.{}.round-{}.{}.vpr", + self.source_filename, procedure.name, self.executor.purification_round, i + ), + |writer| trace.write(writer).unwrap(), + ); + } + } + if config::purify_with_symbolic_execution() { + if config::symbolic_execution_single_method() { + let new_procedure = self.execution_trace_builder.into_procedure(&procedure); + prusti_common::report::log::report_with_writer( + "graphviz_method_vir_low_symbex_single_method", + format!("{}.{}.dot", self.source_filename, new_procedure.name), + |writer| new_procedure.to_graphviz(writer).unwrap(), + ); + new_procedures.push(new_procedure); + } else { + for (i, trace) in self.final_traces.into_iter().enumerate() { + let new_procedure = trace.into_procedure(i, &procedure); + new_procedures.push(new_procedure); + } + } + } else { + for (i, trace) in self.original_traces.into_iter().enumerate() { + let new_procedure = trace.into_procedure(i, &procedure); + new_procedures.push(new_procedure); + } + } + Ok(()) + } + + fn next_continuation( + &mut self, + default_position: vir_low::Position, + ) -> SpannedEncodingResult> { + while let Some(continuation) = self.continuations.pop() { + debug!("Rolling back to {}", continuation.parent_block_label); + self.execution_trace_builder + .rollback_to_split_point(continuation.parent_block_label)?; + self.assume_condition(continuation.condition, default_position)?; + if self + .execution_trace_builder + .current_egraph_state() + .is_inconsistent()? + { + debug!("Inconsistent state after rollback"); + self.execution_trace_builder.remove_last_block()?; + } else { + return Ok(Some(continuation.next_block_label)); + } + } + Ok(None) + } + + fn assume_condition( + &mut self, + condition: vir_low::Expression, + default_position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + self.execution_trace_builder + .current_egraph_state() + .try_assume_heap_independent_conjuncts(&condition)?; + self.execution_trace_builder + .heap_assume(condition.clone(), default_position)?; + self.execution_trace_builder + .add_original_statement(vir_low::Statement::assume(condition, default_position))?; + Ok(()) + } + + fn execute_block( + &mut self, + current_block: &vir_low::Label, + block: &vir_low::BasicBlock, + ) -> SpannedEncodingResult<()> { + debug!("Executing block {}", current_block); + let comment = format!("Executing block: {current_block}"); + self.execution_trace_builder + .add_original_statement(vir_low::Statement::comment(comment.clone()))?; + self.execution_trace_builder + .heap_comment(vir_low::Comment::new(comment))?; + for statement in &block.statements { + self.execute_statement(current_block, statement)?; + if self + .execution_trace_builder + .current_egraph_state() + .is_inconsistent()? + { + break; + } + } + Ok(()) + } + + fn finalize_trace(&mut self) -> SpannedEncodingResult<()> { + // // TODO: Instead of finalizing eagerly, collect all leaves and finalize + // // traces ending at them. + // self.execution_trace_builder + // .current_egraph_state() + // .saturate()?; + // let (original_trace, final_trace) = self + // .execution_trace_builder + // .finalize_last_trace(self.program_context)?; + // self.original_traces.push(original_trace); + // self.final_traces.push(final_trace); + // This assert safe guards us from crashing the machine by consuming too + // much memory. + // self.trace_counter += 1; + // assert!(self.trace_counter <= 100, "Traces budget exceeded"); + Ok(()) + } + + fn finalize_traces(&mut self) -> SpannedEncodingResult<()> { + for leaf in self.execution_trace_builder.get_leaves() { + self.execution_trace_builder + .get_egraph_state(leaf) + .saturate()?; + let (original_trace, final_trace) = self + .execution_trace_builder + .finalize_trace(self.program_context, leaf)?; + self.original_traces.push(original_trace); + self.final_traces.push(final_trace); + } + Ok(()) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/program_context.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/program_context.rs new file mode 100644 index 00000000000..1f5414f3c63 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/program_context.rs @@ -0,0 +1,261 @@ +use crate::encoder::middle::core_proof::{ + predicates::{OwnedPredicateInfo, SnapshotFunctionInfo}, + snapshots::{SnapshotDomainInfo, SnapshotDomainsInfo}, + transformations::encoder_context::EncoderContext, +}; +use prusti_common::config; +use rustc_hash::{FxHashMap, FxHashSet}; +use std::collections::BTreeMap; +use vir_crate::{ + common::builtin_constants::MEMORY_BLOCK_PREDICATE_NAME, + low::{self as vir_low, operations::ty::Typed}, +}; + +pub(super) struct ProgramContext<'a, EC: EncoderContext> { + domains: &'a [vir_low::DomainDecl], + domain_functions: FxHashMap, + functions: FxHashMap, + predicate_decls: FxHashMap, + snapshot_functions_to_predicates: BTreeMap, + predicates_to_snapshot_types: BTreeMap, + non_aliased_memory_block_addresses: &'a FxHashSet, + snapshot_domains_info: &'a SnapshotDomainsInfo, + constant_constructor_names: FxHashSet, + extensionality_gas_constant: &'a vir_low::Expression, + purification_round: u32, + encoder: &'a mut EC, +} + +impl<'a, EC: EncoderContext> ProgramContext<'a, EC> { + pub(super) fn new( + domains: &'a [vir_low::DomainDecl], + functions: &'a [vir_low::FunctionDecl], + predicate_decls: &'a [vir_low::PredicateDecl], + snapshot_domains_info: &'a SnapshotDomainsInfo, + predicate_info: BTreeMap, + non_aliased_memory_block_addresses: &'a FxHashSet, + extensionality_gas_constant: &'a vir_low::Expression, + purification_round: u32, + encoder: &'a mut EC, + ) -> Self { + let mut snapshot_functions_to_predicates = BTreeMap::new(); + let mut predicates_to_snapshot_types = BTreeMap::new(); + for ( + predicate_name, + OwnedPredicateInfo { + current_snapshot_function: SnapshotFunctionInfo { function_name, .. }, + // We are not purifying the final snapshot function because it + // is already pure. + final_snapshot_function: _, + snapshot_type, + snapshot_range_function: _, + }, + ) in predicate_info + { + snapshot_functions_to_predicates.insert(function_name, predicate_name.clone()); + predicates_to_snapshot_types.insert(predicate_name, snapshot_type); + } + Self { + constant_constructor_names: snapshot_domains_info + .snapshot_domains + .values() + .flat_map(|domain| domain.constant_constructor_name.clone()) + .collect(), + domain_functions: domains + .iter() + .flat_map(|domain| { + domain + .functions + .iter() + .map(move |function| (function.name.clone(), function)) + }) + .collect(), + domains, + snapshot_functions_to_predicates, + predicates_to_snapshot_types, + functions: functions + .iter() + .map(|function| (function.name.clone(), function)) + .collect(), + predicate_decls: predicate_decls + .iter() + .map(|predicate| (predicate.name.clone(), predicate)) + .collect(), + non_aliased_memory_block_addresses, + snapshot_domains_info, + extensionality_gas_constant, + purification_round, + encoder, + } + } + + pub(super) fn get_domains(&self) -> &'a [vir_low::DomainDecl] { + self.domains + } + + pub(super) fn get_function(&self, name: &str) -> &'a vir_low::FunctionDecl { + self.functions.get(name).unwrap_or_else(|| { + panic!( + "Function not found: {} (purification round: {})", + name, self.purification_round + ) + }) + } + + pub(super) fn get_snapshot_type(&self, predicate_name: &str) -> Option { + // FIXME: Code duplication with + // prusti-viper/src/encoder/middle/core_proof/transformations/custom_heap_encoding/heap_encoder/predicates.rs + let predicate = self.predicate_decls[predicate_name]; + match predicate.kind { + vir_low::PredicateKind::MemoryBlock => { + use vir_low::macros::*; + Some(ty!(Bytes)) + } + vir_low::PredicateKind::Owned => Some( + self.predicates_to_snapshot_types + .get(predicate_name) + .unwrap_or_else(|| unreachable!("predicate not found: {}", predicate_name)) + .clone(), + ), + vir_low::PredicateKind::CloseFracRef + | vir_low::PredicateKind::LifetimeToken + | vir_low::PredicateKind::WithoutSnapshotWhole + | vir_low::PredicateKind::WithoutSnapshotWholeNonAliased + // | vir_low::PredicateKind::WithoutSnapshotFrac + | vir_low::PredicateKind::DeadLifetimeToken + | vir_low::PredicateKind::EndBorrowViewShift => None, + } + } + + pub(super) fn get_snapshot_predicate(&self, function_name: &str) -> Option<&str> { + let function = self.get_function(function_name); + match function.kind { + vir_low::FunctionKind::MemoryBlockBytes => Some(MEMORY_BLOCK_PREDICATE_NAME), + vir_low::FunctionKind::CallerFor => todo!(), + vir_low::FunctionKind::SnapRange => todo!(), + vir_low::FunctionKind::Snap => self + .snapshot_functions_to_predicates + .get(function_name) + .map(|s| s.as_str()), + } + } + + pub(super) fn get_non_aliased_memory_block_addresses( + &self, + ) -> &'a FxHashSet { + self.non_aliased_memory_block_addresses + } + + pub(super) fn get_predicate_kind(&self, predicate_name: &str) -> vir_low::PredicateKind { + self.predicate_decls[predicate_name].kind + } + + pub(super) fn is_predicate_kind_non_aliased(&self, predicate_name: &str) -> bool { + let kind = self + .predicate_decls + .get(predicate_name) + .unwrap_or_else(|| panic!("{predicate_name}")) + .kind; + if kind.is_non_aliased() { + true + } else { + config::end_borrow_view_shift_non_aliased() + && matches!(kind, vir_low::PredicateKind::EndBorrowViewShift) + } + } + + pub(super) fn get_purification_round(&self) -> u32 { + self.purification_round + } + + pub(super) fn get_binary_operator( + &self, + snapshot_domain_name: &str, + function_name: &str, + ) -> Option { + self.snapshot_domains_info + .snapshot_domains + .get(snapshot_domain_name) + .and_then(|snapshot_domain| { + snapshot_domain.binary_operators.get(function_name).cloned() + }) + } + + pub(super) fn get_constant_constructor( + &self, + snapshot_domain_name: &str, + ) -> &'a vir_low::DomainFunctionDecl { + let constructor_name = self + .snapshot_domains_info + .snapshot_domains + .get(snapshot_domain_name) + .unwrap() + .constant_constructor_name + .as_ref() + .unwrap_or_else(|| panic!("not found: {snapshot_domain_name}")); + self.domain_functions[constructor_name] + } + + pub(super) fn get_constant_destructor( + &self, + snapshot_domain_name: &str, + ) -> &vir_low::DomainFunctionDecl { + let destructor_name = self + .snapshot_domains_info + .snapshot_domains + .get(snapshot_domain_name) + .unwrap() + .constant_destructor_name + .as_ref() + .unwrap_or_else(|| panic!("not found: {snapshot_domain_name}")); + self.domain_functions[destructor_name] + } + + pub(super) fn get_constant_constructor_names(&self) -> &FxHashSet { + &self.constant_constructor_names + } + + pub(super) fn predicate_snapshots_extensionality_call( + &self, + left: vir_low::Expression, + right: vir_low::Expression, + position: vir_low::Position, + ) -> vir_low::Expression { + let domain_name = self + .snapshot_domains_info + .type_domains + .get(left.get_type()) + .unwrap_or_else(|| panic!("not found: {}", left.get_type())); + let function_name = self + .snapshot_domains_info + .snapshot_domains + .get(domain_name) + .unwrap_or_else(|| panic!("not found: {}", domain_name)) + .snapshot_equality + .as_ref() + .unwrap_or_else(|| panic!("not found: {}", domain_name)); + vir_low::Expression::domain_function_call( + domain_name, + function_name, + vec![left, right, self.extensionality_gas_constant.clone()], + vir_low::Type::Bool, + ) + .set_default_position(position) + } + + pub(super) fn get_bool_domain_info(&self) -> (vir_low::Type, SnapshotDomainInfo) { + let bool_type = self + .snapshot_domains_info + .bool_type + .as_ref() + .unwrap() + .clone(); + let bool_domain = &self.snapshot_domains_info.type_domains[&bool_type]; + let domain_info = self.snapshot_domains_info.snapshot_domains[bool_domain].clone(); + (bool_type, domain_info) + } + + pub(super) fn env(&mut self) -> &mut impl EncoderContext { + self.encoder + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/simplifier.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/simplifier.rs new file mode 100644 index 00000000000..fd4a8d9fbcc --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/simplifier.rs @@ -0,0 +1,194 @@ +use super::{egg::EGraphState, program_context::ProgramContext}; +use crate::{ + encoder::{ + errors::{SpannedEncodingError, SpannedEncodingResult}, + middle::core_proof::transformations::encoder_context::EncoderContext, + }, + error_internal, +}; +use prusti_common::config; +use vir_crate::{ + common::expression::BinaryOperationHelpers, + low::{self as vir_low, expression::visitors::ExpressionFallibleFolder}, +}; + +// impl<'a> ProcedureExecutor<'a> { +// pub(super) fn simplify_expression( +// &mut self, +// expression: vir_low::Expression, +// ) -> SpannedEncodingResult { +// let mut simplifier = Simplifier { +// program_context: self.program_context, +// solver: self.execution_trace_builder.current_egraph_state(), +// }; +// simplifier.fallible_fold_expression(expression) +// } +// } + +pub(super) fn simplify_expression<'a, 'c>( + expression: vir_low::Expression, + program_context: &'a mut ProgramContext<'c, impl EncoderContext>, + solver: &'a mut EGraphState, +) -> SpannedEncodingResult { + let mut simplifier = Simplifier { + program_context, + solver, + }; + simplifier.fallible_fold_expression(expression) +} + +struct Simplifier<'a, 'c: 'a, EC: EncoderContext> { + program_context: &'a mut ProgramContext<'c, EC>, + solver: &'a mut EGraphState, +} + +impl<'a, 'c: 'a, EC: EncoderContext> Simplifier<'a, 'c, EC> { + // fn intern_arguments_and_saturate( + // &mut self, + // arguments: &[vir_low::Expression], + // ) -> SpannedEncodingResult<()> { + // for argument in arguments { + // self.solver + // .try_intern_heap_independent_conjuncts(argument)?; + // } + // self.solver.saturate()?; + // Ok(()) + // } + + fn try_resolve_constants( + &mut self, + arguments: &[vir_low::Expression], + ) -> SpannedEncodingResult<(bool, Vec, vir_low::Expression)>>)> { + let mut maybe_constants = Vec::new(); + let mut found_constant = false; + for argument in arguments { + let maybe_constant = self.solver.resolve_constant( + argument, + self.program_context.get_constant_constructor_names(), + )?; + if maybe_constant.is_some() { + found_constant = true; + } + maybe_constants.push(maybe_constant); + } + Ok((found_constant, maybe_constants)) + } +} + +impl<'a, 'c: 'a, EC: EncoderContext> ExpressionFallibleFolder for Simplifier<'a, 'c, EC> { + type Error = SpannedEncodingError; + + fn fallible_fold_domain_func_app_enum( + &mut self, + mut domain_func_app: vir_low::DomainFuncApp, + ) -> Result { + if let Some(op) = self + .program_context + .get_binary_operator(&domain_func_app.domain_name, &domain_func_app.function_name) + { + if matches!(op, vir_low::BinaryOpKind::Mul) { + let domain_func_app_original = + vir_low::Expression::DomainFuncApp(domain_func_app.clone()); + // eprintln!("simplify: {domain_func_app}"); + // self.intern_arguments_and_saturate(&domain_func_app.arguments)?; + let (found_constant, maybe_constants) = + self.try_resolve_constants(&domain_func_app.arguments)?; + if found_constant { + let constructor = self + .program_context + .get_constant_constructor(&domain_func_app.domain_name); + let destructor = self + .program_context + .get_constant_destructor(&domain_func_app.domain_name); + let mut constructor_arguments = Vec::new(); + for (maybe_constant, argument) in maybe_constants + .into_iter() + .zip(std::mem::take(&mut domain_func_app.arguments).into_iter()) + { + if let Some((constructor_name, constant)) = maybe_constant { + assert_eq!(constructor_name.unwrap(), constructor.name); + constructor_arguments.push(constant); + } else { + let destructor = vir_low::Expression::domain_function_call( + &domain_func_app.domain_name, + destructor.name.clone(), + vec![argument], + vir_low::Type::Int, + ); + constructor_arguments.push(destructor); + } + } + let right = constructor_arguments.pop().unwrap(); + let left = constructor_arguments.pop().unwrap(); + assert!(constructor_arguments.is_empty()); + let result = vir_low::Expression::domain_function_call( + domain_func_app.domain_name, + constructor.name.clone(), + vec![vir_low::Expression::multiply(left, right)], + domain_func_app.return_type, + ) + .set_default_position(domain_func_app.position); + + if result.is_heap_independent() + && domain_func_app_original.is_heap_independent() + { + self.solver + .assume_equal(&result, &domain_func_app_original)?; + } + return Ok(result); + } else if config::error_non_linear_arithmetic_simp() { + let span = self + .program_context + .env() + .get_span(domain_func_app.position) + .unwrap(); + error_internal!(span => "failed to rewrite multiplication: {}", domain_func_app); + // unimplemented!("failed to rewrite multiplication: {domain_func_app}"); + } + } + } + self.fallible_fold_domain_func_app(domain_func_app) + .map(vir_low::Expression::DomainFuncApp) + } + + fn fallible_fold_binary_op_enum( + &mut self, + mut binary_op: vir_low::BinaryOp, + ) -> Result { + if matches!(binary_op.op_kind, vir_low::BinaryOpKind::Mul) + && !binary_op.left.is_constant() + && !binary_op.right.is_constant() + { + let arguments = vec![(*binary_op.left).clone(), (*binary_op.right).clone()]; + // self.intern_arguments_and_saturate(&arguments)?; + let (found_constant, maybe_constants) = self.try_resolve_constants(&arguments)?; + if found_constant { + let mut binary_op_arguments = Vec::new(); + for (maybe_constant, argument) in + maybe_constants.into_iter().zip(arguments.into_iter()) + { + if let Some((constructor_name, constant)) = maybe_constant { + assert!(constructor_name.is_none()); + binary_op_arguments.push(constant); + } else { + binary_op_arguments.push(argument); + } + } + let right = binary_op_arguments.pop().unwrap(); + let left = binary_op_arguments.pop().unwrap(); + assert!(binary_op_arguments.is_empty()); + binary_op.left = Box::new(left); + binary_op.right = Box::new(right); + return Ok(vir_low::Expression::BinaryOp(binary_op)); + } else if config::error_non_linear_arithmetic_simp() { + unimplemented!( + "failed to rewrite multiplication: {} * {}", + arguments[0], + arguments[1] + ); + } + } + self.fallible_fold_binary_op(binary_op) + .map(vir_low::Expression::BinaryOp) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/statements.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/statements.rs new file mode 100644 index 00000000000..3a84405b420 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/statements.rs @@ -0,0 +1,379 @@ +use super::{utils::calculate_hash, ProcedureExecutor}; +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::transformations::encoder_context::EncoderContext, +}; +use prusti_common::config; +use vir_crate::{ + common::expression::BinaryOperationHelpers, + low::{self as vir_low}, +}; + +impl<'a, 'c: 'a, EC: EncoderContext> ProcedureExecutor<'a, 'c, EC> { + pub(super) fn execute_statement( + &mut self, + _current_block: &vir_low::Label, + statement: &vir_low::Statement, + ) -> SpannedEncodingResult<()> { + self.execution_trace_builder + .add_original_statement(statement.clone())?; + match statement { + vir_low::Statement::Comment(statement) => { + self.execute_statement_comment(statement)?; + } + vir_low::Statement::Label(statement) => { + self.execute_statement_label(statement)?; + } + vir_low::Statement::LogEvent(statement) => { + self.execute_statement_log_event(statement)?; + } + vir_low::Statement::Assume(statement) => { + self.execute_statement_assume(statement)?; + } + vir_low::Statement::Assert(statement) => { + self.execute_statement_assert(statement)?; + } + vir_low::Statement::Inhale(statement) => { + self.execute_statement_inhale(statement)?; + } + vir_low::Statement::Exhale(statement) => { + self.execute_statement_exhale(statement)?; + } + vir_low::Statement::Fold(_) => unreachable!("{statement}"), + vir_low::Statement::Unfold(_) => unreachable!("{statement}"), + vir_low::Statement::ApplyMagicWand(magic_wand) => { + unreachable!("magic_wand: {magic_wand}"); + } + vir_low::Statement::MethodCall(method_call) => { + unreachable!("method_call: {method_call}"); + } + vir_low::Statement::Assign(statement) => { + self.execute_statement_assign(statement)?; + } + vir_low::Statement::Conditional(_) => { + unreachable!(); + } + vir_low::Statement::MaterializePredicate(_) => todo!(), + vir_low::Statement::CaseSplit(_) => todo!(), + } + Ok(()) + } + + fn execute_statement_comment( + &mut self, + statement: &vir_low::ast::statement::Comment, + ) -> SpannedEncodingResult<()> { + self.execution_trace_builder + .heap_comment(statement.clone())?; + Ok(()) + } + + fn execute_statement_label( + &mut self, + statement: &vir_low::ast::statement::Label, + ) -> SpannedEncodingResult<()> { + self.execution_trace_builder.heap_label(statement.clone())?; + Ok(()) + } + + fn execute_statement_log_event( + &mut self, + statement: &vir_low::ast::statement::LogEvent, + ) -> SpannedEncodingResult<()> { + self.execution_trace_builder + .current_egraph_state() + .try_assume_heap_independent_conjuncts(&statement.expression)?; + self.execution_trace_builder + .heap_assume(statement.expression.clone(), statement.position)?; + Ok(()) + } + + fn execute_statement_assume( + &mut self, + statement: &vir_low::ast::statement::Assume, + ) -> SpannedEncodingResult<()> { + let expression = self.simplify_expression(&statement.expression)?; + // let expression = statement.expression.clone(); + // self.execution_trace_builder.current_egraph_state().intern_heap_independent_subexpressions(&expression)?; + self.execution_trace_builder + .current_egraph_state() + .try_assume_heap_independent_conjuncts(&expression)?; + self.execution_trace_builder + .heap_assume(expression, statement.position)?; + Ok(()) + } + + fn execute_statement_assert( + &mut self, + statement: &vir_low::ast::statement::Assert, + ) -> SpannedEncodingResult<()> { + let expression = self.simplify_expression(&statement.expression)?; + // let expression = statement.expression.clone(); + // self.execution_trace_builder.current_egraph_state().intern_heap_independent_subexpressions(&expression)?; + self.execution_trace_builder + .heap_assert(expression, statement.position)?; + // TODO: Try this: + // self.execution_trace_builder + // .current_egraph_state() + // .assume_heap_independent_conjuncts(&statement.expression)?; + Ok(()) + } + + fn execute_statement_inhale( + &mut self, + statement: &vir_low::ast::statement::Inhale, + ) -> SpannedEncodingResult<()> { + // We cannot do `let expression = + // self.simplify_expression(&statement.expression)?;` here because we + // need to take into account the predicates contained in this expression + // when simplifying it. Therefore, we simplify each conjunct separately. + + // let expression = self.simplify_expression(&statement.expression)?; + // self.execution_trace_builder.current_egraph_state().intern_heap_independent_subexpressions(&statement.expression)?; + self.execute_inhale(&statement.expression, statement.position)?; + // self.execute_inhale(&expression, statement.position)?; + Ok(()) + } + + fn execute_inhale( + &mut self, + expression: &vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + if let vir_low::Expression::BinaryOp(expression) = expression { + if expression.op_kind == vir_low::BinaryOpKind::And { + self.execute_inhale(&expression.left, position)?; + self.execute_inhale(&expression.right, position)?; + return Ok(()); + } + } + if let vir_low::Expression::PredicateAccessPredicate(predicate) = expression { + let mut arguments = Vec::new(); + for argument in &predicate.arguments { + arguments.push(self.simplify_expression(argument)?); + } + self.execution_trace_builder.heap_inhale_predicate( + vir_low::PredicateAccessPredicate::new( + predicate.name.clone(), + arguments, + (*predicate.permission).clone(), + ), + self.program_context, + position, + )?; + return Ok(()); + } + let expression = self.simplify_expression(expression)?; + // self.execution_trace_builder + // .current_egraph_state() + // .intern_heap_independent_subexpressions(&expression)?; + self.execution_trace_builder + .current_egraph_state() + .try_assume_heap_independent_conjuncts(&expression)?; + self.execution_trace_builder + .heap_inhale_generic(expression, position)?; + Ok(()) + } + + // fn is_predicate_instance_non_aliased( + // &mut self, + // predicate: &vir_low::PredicateAccessPredicate, + // ) -> SpannedEncodingResult { + // if self + // .program_context + // .is_predicate_kind_non_aliased(&predicate.name) + // { + // return Ok(true); + // } + // fn construct_predicate_address_non_aliased_call( + // predicate_address: &vir_low::Expression, + // ) -> vir_low::Expression { + // use vir_low::macros::*; + // let address_is_non_aliased = ty!(Bool); + // expr! { + // (ComputeAddress::address_is_non_aliased([predicate_address.clone()])) + // } + // } + // match self.program_context.get_predicate_kind(&predicate.name) { + // vir_low::PredicateKind::MemoryBlock => { + // let solver = self.execution_trace_builder.current_egraph_state(); + // let predicate_address = &predicate.arguments[0]; + // let predicate_address_non_aliased_call = + // construct_predicate_address_non_aliased_call(predicate_address); + // solver.intern_term(&predicate_address_non_aliased_call)?; + // solver.saturate()?; + // if solver.is_true(&predicate_address_non_aliased_call)? { + // return Ok(true); + // } + // } + // vir_low::PredicateKind::Owned => { + // let solver = self.execution_trace_builder.current_egraph_state(); + // let predicate_address = &predicate.arguments[1]; + // debug_assert_eq!(predicate_address.get_type(), &vir_low::macros::ty!(Address)); + // let predicate_address_non_aliased_call = + // construct_predicate_address_non_aliased_call(predicate_address); + // solver.intern_term(&predicate_address_non_aliased_call)?; + // solver.saturate()?; + // if solver.is_true(&predicate_address_non_aliased_call)? { + // return Ok(true); + // } + // } + // _ => {} + // } + // Ok(false) + // } + + fn execute_statement_exhale( + &mut self, + statement: &vir_low::ast::statement::Exhale, + ) -> SpannedEncodingResult<()> { + // let expression = statement.expression.clone(); + let exhale_label = format!( + "exhale_label$round{}${}", + self.executor.purification_round, self.exhale_label_generator_counter + ); + self.exhale_label_generator_counter += 1; + self.execution_trace_builder + .heap_label(vir_low::ast::statement::Label { + label: exhale_label.clone(), + position: statement.position, + })?; + self.execution_trace_builder + .register_label(vir_low::Label::new(&exhale_label))?; + self.execute_exhale(&statement.expression, statement.position, &exhale_label)?; + Ok(()) + } + + fn execute_exhale( + &mut self, + expression: &vir_low::Expression, + position: vir_low::Position, + exhale_label: &str, + ) -> SpannedEncodingResult<()> { + if let vir_low::Expression::BinaryOp(expression) = expression { + if expression.op_kind == vir_low::BinaryOpKind::And { + self.execute_exhale(&expression.left, position, exhale_label)?; + self.execute_exhale(&expression.right, position, exhale_label)?; + return Ok(()); + } + } + if let vir_low::Expression::PredicateAccessPredicate(predicate) = expression { + let mut arguments = Vec::new(); + for argument in &predicate.arguments { + let simplified_argument = self.simplify_expression(argument)?; + arguments.push(simplified_argument.wrap_in_old(exhale_label)); + } + self.execution_trace_builder.heap_exhale_predicate( + vir_low::PredicateAccessPredicate::new( + predicate.name.clone(), + arguments, + (*predicate.permission).clone(), + ), + self.program_context, + position, + )?; + return Ok(()); + } + // self.execution_trace_builder.current_egraph_state().intern_heap_independent_subexpressions(&expression)?; + let expression = self.simplify_expression(expression)?; + let expression = expression.wrap_in_old(exhale_label); + self.execution_trace_builder + .current_egraph_state() + .try_intern_heap_independent_conjuncts(&expression)?; + // self.execution_trace_builder + // .current_egraph_state() + // .saturate()?; + if expression.is_heap_independent() + && self + .execution_trace_builder + .current_egraph_state() + .try_is_true(&expression)? + == Some(true) + { + if config::report_symbolic_execution_purification() { + self.execution_trace_builder + .heap_comment(vir_low::Comment::new(format!("purified out: {expression}")))?; + } + } else { + if config::report_symbolic_execution_purification() { + if self + .execution_trace_builder + .current_egraph_state() + .try_is_true(&expression)? + .is_none() + { + self.execution_trace_builder + .heap_comment(vir_low::Comment::new(format!( + "not interned: {expression}" + )))?; + } else { + self.execution_trace_builder + .heap_comment(vir_low::Comment::new(format!( + "interned, but false: {expression}" + )))?; + // let debug_info = self + // .execution_trace_builder + // .current_egraph_state() + // .get_dump_eclass_of(&expression)?; + // self.execution_trace_builder + // .heap_comment(vir_low::Comment::new(debug_info))?; + } + } + self.execution_trace_builder + .heap_exhale_generic(expression, position)?; + } + Ok(()) + } + + fn execute_statement_assign( + &mut self, + statement: &vir_low::ast::statement::Assign, + ) -> SpannedEncodingResult<()> { + assert!( + !statement.position.is_default(), + "{statement} has no position" + ); + assert!(statement.value.is_constant()); + let target_variable = self + .execution_trace_builder + .create_new_bool_variable_version(&statement.target.name)?; + let expression = + vir_low::Expression::equals(target_variable.into(), statement.value.clone()); + self.execution_trace_builder + .current_egraph_state() + .try_assume_heap_independent_conjuncts(&expression)?; + self.execution_trace_builder + .heap_assume(expression, statement.position)?; + Ok(()) + } + + fn simplify_expression( + &mut self, + expression: &vir_low::Expression, + ) -> SpannedEncodingResult { + self.execution_trace_builder + .current_egraph_state() + .intern_heap_independent_subexpressions(expression)?; + let (heap_state, solver) = self + .execution_trace_builder + .current_heap_and_egraph_state_mut(); + let purified_expression = heap_state.purify_expression_with_retry( + solver, + self.program_context, + expression.clone(), + )?; + let purified_expression_hash = calculate_hash(&purified_expression); + // let simplified_expression = purified_expression; + let simplified_expression = super::simplifier::simplify_expression( + purified_expression, + self.program_context, + solver, + )?; + if calculate_hash(&simplified_expression) != purified_expression_hash { + self.execution_trace_builder + .current_egraph_state() + .intern_heap_independent_subexpressions(&simplified_expression)?; + } + Ok(simplified_expression) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/trace.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/trace.rs new file mode 100644 index 00000000000..9f4e16e0d3f --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/trace.rs @@ -0,0 +1,45 @@ +use std::collections::BTreeMap; +use vir_crate::low::{self as vir_low}; + +pub(super) struct Trace { + pub statements: Vec, + pub variables: Vec, + pub labels: Vec, +} + +impl Trace { + pub(super) fn write(&self, writer: &mut dyn std::io::Write) -> std::io::Result<()> { + for statement in &self.statements { + writeln!(writer, "{statement}")?; + } + Ok(()) + } + + pub(super) fn into_procedure( + self, + trace_index: usize, + original_procedure: &vir_low::ProcedureDecl, + ) -> vir_low::ProcedureDecl { + let entry = vir_low::Label::new("trace_start"); + let exit = vir_low::Label::new("trace_end"); + let entry_block = + vir_low::BasicBlock::new(self.statements, vir_low::Successor::Goto(exit.clone())); + let exit_block = vir_low::BasicBlock::new(Vec::new(), vir_low::Successor::Return); + let mut basic_blocks = BTreeMap::new(); + basic_blocks.insert(entry.clone(), entry_block); + basic_blocks.insert(exit.clone(), exit_block); + let mut locals = original_procedure.locals.clone(); + locals.extend(self.variables); + let mut custom_labels = original_procedure.custom_labels.clone(); + custom_labels.extend(self.labels); + vir_low::ProcedureDecl::new_with_pos( + format!("{}$trace_{}", original_procedure.name, trace_index), + locals, + custom_labels, + entry, + exit, + basic_blocks, + original_procedure.position, + ) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/trace_builder/heap_view.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/trace_builder/heap_view.rs new file mode 100644 index 00000000000..26d79bbbdc4 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/trace_builder/heap_view.rs @@ -0,0 +1,68 @@ +use super::{ExecutionTraceBlock, ExecutionTraceBuilder}; +use crate::encoder::middle::core_proof::transformations::symbolic_execution::heap::HeapEntry; +use vir_crate::low::{self as vir_low}; + +pub(in super::super) struct ExecutionTraceHeapView<'a> { + pub(super) trace: &'a ExecutionTraceBuilder<'a>, +} + +pub(in super::super) struct BlockView<'a> { + block: &'a ExecutionTraceBlock, +} + +impl<'a> ExecutionTraceHeapView<'a> { + pub(in super::super) fn iter_blocks(&self) -> impl Iterator> { + self.trace.blocks.iter().map(|block| BlockView { block }) + } + + pub(in super::super) fn block_count(&self) -> usize { + self.trace.blocks.len() + } + + pub(in super::super) fn last_block_id(&self) -> usize { + self.trace.blocks.len() - 1 + } + + pub(in super::super) fn last_block_entry_count(&self) -> usize { + self.trace.blocks.last().unwrap().heap_statements.len() + } + + pub(in super::super) fn get_block(&self, id: usize) -> BlockView<'a> { + BlockView { + block: &self.trace.blocks[id], + } + } +} + +impl<'a> BlockView<'a> { + pub(in super::super) fn iter_entries(&self) -> impl Iterator { + self.block.heap_statements.iter() + } + + pub(in super::super) fn parent(&self) -> Option { + self.block.parent + } + + pub(in super::super) fn get_new_variables(&self) -> &[vir_low::VariableDecl] { + &self.block.new_variables + } + + pub(in super::super) fn get_new_labels(&self) -> &[vir_low::Label] { + &self.block.new_labels + } + + pub(crate) fn set_finalized_statements(&self, new_statements: &[vir_low::Statement]) { + let mut borrow = self.block.finalized_statements.borrow_mut(); + if let Some(statements) = borrow.as_ref() { + // FIXME: This does not work because whether an inhale is purified + // out depends on whether a matching exhale is found, which depends + // on the executed trace. + for (old, new) in statements.iter().zip(new_statements.iter()) { + assert_eq!(old, new); + } + assert_eq!(statements.len(), new_statements.len()); + } else { + *borrow = Some(new_statements.to_vec()); + } + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/trace_builder/mod.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/trace_builder/mod.rs new file mode 100644 index 00000000000..9761d9e9487 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/trace_builder/mod.rs @@ -0,0 +1,403 @@ +mod original_view; +mod heap_view; + +use super::{ + egg::EGraphState, + heap::{HeapEntry, HeapState}, + program_context::ProgramContext, + trace::Trace, +}; +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::{ + snapshots::SnapshotDomainInfo, transformations::encoder_context::EncoderContext, + }, +}; +use rustc_hash::FxHashMap; +use std::collections::BTreeMap; +use vir_crate::{ + common::{expression::BinaryOperationHelpers, graphviz::ToGraphviz}, + low::{self as vir_low}, +}; + +pub(super) use self::{ + heap_view::ExecutionTraceHeapView, original_view::ExecutionTraceOriginalView, +}; + +pub(super) struct ExecutionTraceBuilder<'a> { + pub(super) source_filename: &'a str, + pub(super) procedure_name: &'a str, + blocks: Vec, + split_point_parents: BTreeMap, + variable_versions: FxHashMap, +} + +impl<'a> Drop for ExecutionTraceBuilder<'a> { + fn drop(&mut self) { + if prusti_common::config::dump_debug_info() && std::thread::panicking() { + prusti_common::report::log::report_with_writer( + "graphviz_method_vir_symbex_original", + format!("{}.{}.crash.dot", self.source_filename, self.procedure_name,), + |writer| self.original_view().to_graphviz(writer).unwrap(), + ); + prusti_common::report::log::report_with_writer( + "graphviz_method_vir_symbex_optimized", + format!("{}.{}.crash.dot", self.source_filename, self.procedure_name,), + |writer| self.heap_view().to_graphviz(writer).unwrap(), + ); + let mut original_trace = Trace { + statements: Vec::new(), + variables: Vec::new(), + labels: Vec::new(), + }; + self.finalize_original_trace_for_block(&mut original_trace, self.blocks.len() - 1) + .unwrap(); + prusti_common::report::log::report_with_writer( + "vir_symbex_original_traces", + format!("{}.{}.crash.vpr", self.source_filename, self.procedure_name), + |writer| original_trace.write(writer).unwrap(), + ); + } + } +} + +impl<'a> ExecutionTraceBuilder<'a> { + pub(super) fn new( + source_filename: &'a str, + procedure_name: &'a str, + domains: &[vir_low::DomainDecl], + bool_type: vir_low::Type, + bool_domain_info: SnapshotDomainInfo, + ) -> SpannedEncodingResult { + let initial_block = ExecutionTraceBlock::root(domains, bool_type, bool_domain_info)?; + Ok(Self { + source_filename, + procedure_name, + blocks: vec![initial_block], + split_point_parents: Default::default(), + variable_versions: Default::default(), + }) + } + + fn current_block(&self) -> &ExecutionTraceBlock { + self.blocks.last().unwrap() + } + + fn current_block_mut(&mut self) -> &mut ExecutionTraceBlock { + self.blocks.last_mut().unwrap() + } + + pub(super) fn current_egraph_state(&mut self) -> &mut EGraphState { + self.current_block_mut().egraph_state.as_mut().unwrap() + } + + pub(super) fn get_egraph_state(&mut self, block_id: usize) -> &mut EGraphState { + self.blocks[block_id].egraph_state.as_mut().unwrap() + } + + pub(super) fn current_heap_state_mut(&mut self) -> &mut HeapState { + &mut self.current_block_mut().heap_state + } + + pub(super) fn current_heap_state(&self) -> &HeapState { + &self.current_block().heap_state + } + + pub(super) fn heap_state(&self, block_id: usize) -> &HeapState { + &self.blocks[block_id].heap_state + } + + pub(super) fn steal_current_egraph_solver(&mut self) -> EGraphState { + std::mem::take(&mut self.current_block_mut().egraph_state).unwrap() + } + + pub(super) fn steal_egraph_solver(&mut self, block_id: usize) -> EGraphState { + std::mem::take(&mut self.blocks[block_id].egraph_state).unwrap() + } + + pub(super) fn current_heap_and_egraph_state(&self) -> (&HeapState, &EGraphState) { + let current_block = self.current_block(); + ( + ¤t_block.heap_state, + (current_block.egraph_state.as_ref().unwrap()), + ) + } + + pub(super) fn current_heap_and_egraph_state_mut( + &mut self, + ) -> (&mut HeapState, &mut EGraphState) { + let current_block = self.current_block_mut(); + ( + &mut current_block.heap_state, + current_block.egraph_state.as_mut().unwrap(), + ) + } + + pub(super) fn add_original_statement( + &mut self, + statement: vir_low::Statement, + ) -> SpannedEncodingResult<()> { + let current_block = self.current_block_mut(); + current_block.original_statements.push(statement); + Ok(()) + } + + pub(super) fn add_heap_entry(&mut self, entry: HeapEntry) -> SpannedEncodingResult<()> { + let current_block = self.current_block_mut(); + current_block.heap_statements.push(entry); + Ok(()) + } + + pub(super) fn add_split_point( + &mut self, + parent_block_label: vir_low::Label, + ) -> SpannedEncodingResult<()> { + let parent_id = self.blocks.len() - 1; + let new_block = ExecutionTraceBlock::from_parent(parent_id, self.current_block()); + self.blocks.push(new_block); + self.split_point_parents + .insert(parent_block_label, parent_id); + Ok(()) + } + + pub(super) fn rollback_to_split_point( + &mut self, + split_point_label: vir_low::Label, + ) -> SpannedEncodingResult<()> { + let parent_id = self.split_point_parents[&split_point_label]; + let parent = &self.blocks[parent_id]; + let new_block = ExecutionTraceBlock::from_parent(parent_id, parent); + self.blocks.push(new_block); + Ok(()) + } + + pub(super) fn original_view(&self) -> ExecutionTraceOriginalView { + ExecutionTraceOriginalView { trace: self } + } + + pub(super) fn heap_view(&self) -> ExecutionTraceHeapView { + ExecutionTraceHeapView { trace: self } + } + + pub(super) fn create_new_bool_variable_version( + &mut self, + variable_name: &str, + ) -> SpannedEncodingResult { + let version = self + .variable_versions + .entry(variable_name.to_string()) + .or_default(); + *version += 1; + let version = *version; + let variable = + vir_low::VariableDecl::new(format!("{variable_name}${version}"), vir_low::Type::Bool); + self.current_block_mut() + .new_variables + .push(variable.clone()); + Ok(variable) + } + + pub(super) fn register_label(&mut self, label: vir_low::Label) -> SpannedEncodingResult<()> { + self.current_block_mut().new_labels.push(label); + Ok(()) + } + + pub(super) fn finalize_last_trace( + &mut self, + program: &ProgramContext, + ) -> SpannedEncodingResult<(Trace, Trace)> { + self.finalize_trace(program, self.blocks.len() - 1) + } + + pub(super) fn finalize_trace( + &mut self, + program: &ProgramContext, + block_id: usize, + ) -> SpannedEncodingResult<(Trace, Trace)> { + let mut original_trace = Trace { + statements: Vec::new(), + variables: Vec::new(), + labels: Vec::new(), + }; + self.finalize_original_trace_for_block(&mut original_trace, block_id)?; + let final_trace = self.heap_finalize_trace(program, block_id)?; + Ok((original_trace, final_trace)) + } + + fn finalize_original_trace_for_block( + &self, + trace: &mut Trace, + block_id: usize, + ) -> SpannedEncodingResult<()> { + let block = &self.blocks[block_id]; + if let Some(parent_id) = block.parent { + self.finalize_original_trace_for_block(trace, parent_id)?; + } + for statement in &block.original_statements { + trace.statements.push(statement.clone()); + } + Ok(()) + } + + /// Removes the last block from the trace. This method should be used only + /// when the last method is a freshly added unreachable branch. + pub(super) fn remove_last_block(&mut self) -> SpannedEncodingResult<()> { + let last_block = self.blocks.pop().unwrap(); + assert_eq!(last_block.original_statements.len(), 1); + assert_eq!(last_block.heap_statements.len(), 1); + Ok(()) + } + + pub(super) fn into_procedure( + mut self, + original_procedure: &vir_low::ProcedureDecl, + ) -> vir_low::ProcedureDecl { + let entry = vir_low::Label::new("trace_start"); + let exit = vir_low::Label::new("trace_end"); + let mut jump_targets = vec![Vec::new(); self.blocks.len()]; + for (i, block) in self.blocks.iter().enumerate() { + if let Some(parent) = block.parent { + jump_targets[parent].push(i); + } + } + let locals = original_procedure.locals.clone(); + let custom_labels = original_procedure.custom_labels.clone(); + let mut basic_blocks = BTreeMap::new(); + for (i, block) in std::mem::take(&mut self.blocks).into_iter().enumerate() { + let mut statements = block.finalized_statements.borrow_mut().take().unwrap(); + let successor = match jump_targets[i].len() { + 0 => vir_low::Successor::Goto(exit.clone()), + 1 => vir_low::Successor::Goto(vir_low::Label::new(format!( + "trace_block_{}", + jump_targets[i][0] + ))), + _ => { + let mut targets = Vec::new(); + let variable = vir_low::VariableDecl::new( + format!("trace_block_{}_guard", i), + vir_low::Type::Int, + ); + for (j, target) in jump_targets[i].iter().enumerate() { + let guard = vir_low::Expression::equals(variable.clone().into(), j.into()); + let label = vir_low::Label::new(format!("trace_block_{}", target)); + targets.push((guard, label)); + } + statements.push(vir_low::Statement::assume( + vir_low::Expression::and( + vir_low::Expression::greater_equals(0.into(), variable.clone().into()), + vir_low::Expression::less_than( + variable.into(), + jump_targets[i].len().into(), + ), + ), + original_procedure.position, + )); + vir_low::Successor::GotoSwitch(targets) + } + }; + let basic_block = vir_low::BasicBlock::new(statements, successor); + basic_blocks.insert( + vir_low::Label::new(format!("trace_block_{}", i)), + basic_block, + ); + } + vir_low::ProcedureDecl::new_with_pos( + format!("{}$trace", original_procedure.name), + locals, + custom_labels, + entry, + exit, + basic_blocks, + original_procedure.position, + ) + } + + pub(super) fn get_leaves(&self) -> Vec { + let mut has_parent = vec![false; self.blocks.len()]; + let mut is_empty = vec![false; self.blocks.len()]; + for (i, block) in self.blocks.iter().enumerate() { + if let Some(parent) = block.parent { + has_parent[parent] = true; + } + if block.original_statements.is_empty() { + is_empty[i] = true; + } else if block.original_statements.len() == 1 { + if let vir_low::Statement::Assume(statement) = &block.original_statements[0] { + if statement.expression.is_heap_independent() { + is_empty[i] = true; + } + } + } + } + let mut is_leaf = vec![true; self.blocks.len()]; + for (i, block) in self.blocks.iter().enumerate() { + if is_empty[i] && !has_parent[i] { + // We do not count as a child unless we also have a child. + continue; + } + if let Some(parent) = block.parent { + is_leaf[parent] = false; + } + } + let mut leaves = Vec::new(); + for (i, is_leaf) in is_leaf.into_iter().enumerate() { + if is_leaf { + leaves.push(i); + } + } + leaves + } +} + +struct ExecutionTraceBlock { + /// The parent of this block. The root does not have a parent. + parent: Option, + /// New variables declared while executing the trace. + new_variables: Vec, + /// New labels declared while executing the trace. + new_labels: Vec, + /// Original statements that were executed in the trace. + original_statements: Vec, + /// Statements that make the heap operations more explicit. + heap_statements: Vec, + /// Statements after all the transformations. + finalized_statements: std::cell::RefCell>>, + /// The last heap state. If the block is fully executed, it is the state + /// after the last statement. + heap_state: HeapState, + /// The last e-graph state. If the block is fully executed, it is the state + /// after the last statement. + egraph_state: Option, +} + +impl ExecutionTraceBlock { + fn root( + domains: &[vir_low::DomainDecl], + bool_type: vir_low::Type, + bool_domain_info: SnapshotDomainInfo, + ) -> SpannedEncodingResult { + Ok(Self { + parent: None, + new_variables: Vec::new(), + new_labels: Vec::new(), + original_statements: Vec::new(), + heap_statements: Vec::new(), + finalized_statements: std::cell::RefCell::new(None), + heap_state: HeapState::default(), + egraph_state: Some(EGraphState::new(domains, bool_type, bool_domain_info)?), + }) + } + + fn from_parent(parent_id: usize, parent: &Self) -> Self { + Self { + parent: Some(parent_id), + new_variables: Vec::new(), + new_labels: Vec::new(), + original_statements: Vec::new(), + heap_statements: Vec::new(), + finalized_statements: std::cell::RefCell::new(None), + heap_state: parent.heap_state.clone(), + egraph_state: parent.egraph_state.clone(), + } + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/trace_builder/original_view.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/trace_builder/original_view.rs new file mode 100644 index 00000000000..79f7614c594 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/trace_builder/original_view.rs @@ -0,0 +1,35 @@ +use super::ExecutionTraceBuilder; +use vir_crate::{ + common::graphviz::{escape_html_wrap, Graph, ToGraphviz}, + low::{self as vir_low}, +}; + +pub(in super::super) struct ExecutionTraceOriginalView<'a> { + pub(super) trace: &'a ExecutionTraceBuilder<'a>, +} + +impl<'a> ToGraphviz for ExecutionTraceOriginalView<'a> { + fn to_graph(&self) -> Graph { + let mut graph = Graph::with_columns(&["statement"]); + for (block_id, block) in self.trace.blocks.iter().enumerate() { + let mut node_builder = graph.create_node(format!("block{block_id}")); + for statement in &block.original_statements { + let statement_string = match statement { + vir_low::Statement::Comment(statement) => { + format!( + "{}", + escape_html_wrap(statement) + ) + } + _ => escape_html_wrap(statement.to_string()), + }; + node_builder.add_row_sequence(vec![statement_string]); + } + node_builder.build(); + if let Some(parent) = block.parent { + graph.add_regular_edge(format!("block{parent}"), format!("block{block_id}")); + } + } + graph + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/utils.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/utils.rs new file mode 100644 index 00000000000..c71cb159914 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution/utils.rs @@ -0,0 +1,53 @@ +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::transformations::symbolic_execution::egg::EGraphState, +}; +use std::hash::{Hash, Hasher}; +use vir_crate::low::{self as vir_low, operations::ty::Typed}; + +pub(super) fn arguments_match( + args1: &[vir_low::Expression], + args2: &[vir_low::Expression], + solver: &EGraphState, +) -> SpannedEncodingResult { + for (arg1, arg2) in args1.iter().zip(args2) { + if !solver.is_equal(arg1, arg2)? { + return Ok(false); + } + } + Ok(true) +} + +pub(in super::super) fn all_heap_independent(arguments: &[vir_low::Expression]) -> bool { + arguments + .iter() + .all(|argument| argument.is_heap_independent()) +} + +pub(super) fn calculate_hash(t: &T) -> u64 { + let mut s = std::collections::hash_map::DefaultHasher::new(); + t.hash(&mut s); + s.finish() +} + +pub(super) fn is_place_non_aliased(place: &vir_low::Expression) -> bool { + assert_eq!(place.get_type(), &vir_low::macros::ty!(PlaceOption)); + match place { + vir_low::Expression::DomainFuncApp(domain_func_app) + if domain_func_app.arguments.len() == 1 => + { + let argument = &domain_func_app.arguments[0]; + if domain_func_app.function_name == "place_option_some" { + true + } else { + is_place_non_aliased(argument) + } + } + vir_low::Expression::DomainFuncApp(domain_func_app) => { + assert_eq!(domain_func_app.function_name, "place_option_none"); + false + } + vir_low::Expression::LabelledOld(labelled_old) => is_place_non_aliased(&labelled_old.base), + _ => unreachable!("place: {place}"), + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/block_builder.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/block_builder.rs new file mode 100644 index 00000000000..f3dc84a8d60 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/block_builder.rs @@ -0,0 +1,68 @@ +use crate::encoder::errors::SpannedEncodingResult; +use vir_crate::low::{self as vir_low}; + +pub(super) struct BlockBuilder { + pub(super) statements: Vec, + pub(super) successors: Vec, + pub(super) current_materialization_point: usize, +} + +impl BlockBuilder { + pub(super) fn new(successors: Vec) -> SpannedEncodingResult { + let builder = Self { + statements: Vec::new(), + successors, + current_materialization_point: 0, + }; + Ok(builder) + } + + pub(super) fn add_statement( + &mut self, + statement: vir_low::Statement, + ) -> SpannedEncodingResult<()> { + self.statements.push(statement); + Ok(()) + } + + pub(super) fn add_statements( + &mut self, + statements: Vec, + ) -> SpannedEncodingResult<()> { + self.statements.extend(statements); + Ok(()) + } + + pub(super) fn set_materialization_point(&mut self) -> SpannedEncodingResult<()> { + self.current_materialization_point = self.statements.len(); + Ok(()) + } + + pub(super) fn add_statements_at_materialization_point( + &mut self, + statements: Vec, + ) -> SpannedEncodingResult<()> { + self.statements.splice( + self.current_materialization_point..self.current_materialization_point, + statements, + ); + Ok(()) + } +} + +pub trait StatementsBuilder { + fn add_statement(&mut self, statement: vir_low::Statement) -> SpannedEncodingResult<()>; +} + +impl StatementsBuilder for BlockBuilder { + fn add_statement(&mut self, statement: vir_low::Statement) -> SpannedEncodingResult<()> { + BlockBuilder::add_statement(self, statement) + } +} + +impl StatementsBuilder for Vec { + fn add_statement(&mut self, statement: vir_low::Statement) -> SpannedEncodingResult<()> { + self.push(statement); + Ok(()) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/egg/graphviz.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/egg/graphviz.rs new file mode 100644 index 00000000000..3e7f1741cf2 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/egg/graphviz.rs @@ -0,0 +1,86 @@ +use super::state::EGraphState; +use crate::encoder::errors::SpannedEncodingResult; +use egg::{Id, Language}; +use rustc_hash::FxHashSet; +use std::{fmt::Write, path::Path}; + +impl EGraphState { + pub(in super::super) fn eclass_to_dot_file( + &self, + id: Id, + filename: impl AsRef, + ) -> SpannedEncodingResult<()> { + use std::io::Write; + let mut file = std::fs::File::create(filename).unwrap(); + let mut buffer = String::new(); + self.eclass_to_dot(id, &mut buffer).unwrap(); + writeln!(file, "{buffer}").unwrap(); + Ok(()) + } + + pub(in super::super) fn eclass_to_dot( + &self, + id: Id, + writer: &mut dyn Write, + ) -> std::fmt::Result { + writeln!(writer, "digraph {{")?; + + writeln!(writer, " compound=true")?; + writeln!(writer, " clusterrank=local")?; + + let mut printed_classes = FxHashSet::default(); + let mut classes_to_print = vec![id]; + while let Some(id) = classes_to_print.pop() { + self.print_eclass(id, writer, &mut printed_classes, &mut classes_to_print)?; + } + + writeln!(writer, "}}")?; + Ok(()) + } + + fn print_eclass( + &self, + id: Id, + writer: &mut dyn Write, + printed_classes: &mut FxHashSet, + classes_to_print: &mut Vec, + ) -> std::fmt::Result { + if !printed_classes.contains(&id) { + printed_classes.insert(id); + let class = &self.egraph[id]; + writeln!(writer, " subgraph cluster_{id} {{")?; + writeln!(writer, " style=dotted")?; + for (i, node) in class.iter().enumerate() { + { + writeln!(writer, " {id}.{i}[label = \"{id} {node}\"]")?; + } + } + writeln!(writer, " }}")?; + + for (i_in_class, node) in class.iter().enumerate() { + let mut arg_i = 0; + node.try_for_each(|child| { + let child_class_id = self.egraph.find(child); + classes_to_print.push(child_class_id); + if child_class_id == class.id { + // We have a self-loop. + writeln!( + writer, + " {}.{} -> {}.{}:n [lhead = cluster_{}, label=\"{}:{}\"]", + class.id, i_in_class, class.id, i_in_class, class.id, arg_i, child + )?; + } else { + writeln!( + writer, + " {}.{} -> {}.0 [lhead = cluster_{}, label=\"{}:{}\"]", + class.id, i_in_class, child, child_class_id, arg_i, child + )?; + } + arg_i += 1; + Ok::<_, std::fmt::Error>(()) + })?; + } + } + Ok(()) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/egg/interface.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/egg/interface.rs new file mode 100644 index 00000000000..8406b82e833 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/egg/interface.rs @@ -0,0 +1,208 @@ +use super::EGraphState; +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::transformations::{ + encoder_context::EncoderContext, symbolic_execution_new::program_context::ProgramContext, + }, +}; +use rustc_hash::FxHashSet; +use vir_crate::low::{self as vir_low}; + +/// Provides an interface over an EGraph that operates on `vir_low::Expression`. +#[derive(Clone)] +pub(in super::super) struct ExpressionEGraph { + /// The EGraph. + egraph: EGraphState, + /// The variables that were interned into the Egraph. + interned_variables: FxHashSet, + /// The equalities that were assumed to hold between variables. + assumed_variable_equalities: FxHashSet<(vir_low::VariableDecl, vir_low::VariableDecl)>, +} + +pub(in super::super) struct IntersectingReport { + pub(in super::super) self_dropped_variables: Vec, + pub(in super::super) other_dropped_variables: Vec, + /// The equalities between non-dropped variables that were dropped. + pub(in super::super) self_dropped_equalities: + Vec<(vir_low::VariableDecl, vir_low::VariableDecl)>, + /// The equalities between non-dropped variables that were dropped. + pub(in super::super) other_dropped_equalities: + Vec<(vir_low::VariableDecl, vir_low::VariableDecl)>, +} + +/// State management. +impl ExpressionEGraph { + pub(in super::super) fn new( + program_context: &ProgramContext, + ) -> SpannedEncodingResult { + let (bool_type, bool_domain_info) = program_context.get_bool_domain_info(); + let mut egraph = + EGraphState::new(program_context.get_domains(), bool_type, bool_domain_info)?; + // Assume which addresses are non-aliased. + // FIXME: This could be a separate global EGraph because non-aliasing + // information is static. + for address in program_context.get_non_aliased_memory_block_addresses() { + assert!(address.is_heap_independent()); + use vir_low::macros::*; + let address_is_non_aliased = ty!(Bool); + let address_non_aliased_call = expr! { + (ComputeAddress::address_is_non_aliased([address.clone()])) + }; + let address_id = egraph.try_intern_term(&address_non_aliased_call)?.unwrap(); + egraph.assume_equal(address_id, egraph.non_aliased_address_id)?; + } + Ok(Self { + egraph, + interned_variables: Default::default(), + assumed_variable_equalities: Default::default(), + }) + } + + /// Intersect the equalities of the egraph with the given egraph. Used by + /// state merging. + pub(in super::super) fn intersect_with( + &mut self, + other: &ExpressionEGraph, + ) -> SpannedEncodingResult { + let self_dropped_variables = self + .interned_variables + .drain_filter(|variable| !other.interned_variables.contains(variable)) + .collect(); + let other_dropped_variables = other + .interned_variables + .iter() + .filter(|expression| !self.interned_variables.contains(expression)) + .cloned() + .collect(); + let self_dropped_equalities = self + .assumed_variable_equalities + .drain_filter(|equality| !other.assumed_variable_equalities.contains(equality)) + .filter(|(left, right)| { + self.interned_variables.contains(left) && self.interned_variables.contains(right) + }) + .collect(); + let other_dropped_equalities = other + .assumed_variable_equalities + .iter() + .filter(|equality| !self.assumed_variable_equalities.contains(equality)) + .filter(|(left, right)| { + self.interned_variables.contains(left) && self.interned_variables.contains(right) + }) + .cloned() + .collect(); + self.egraph.intersect_with(&other.egraph)?; + let report = IntersectingReport { + self_dropped_variables, + other_dropped_variables, + self_dropped_equalities, + other_dropped_equalities, + }; + Ok(report) + } + + pub(in super::super) fn contains(&self, variable: &vir_low::VariableDecl) -> bool { + self.interned_variables.contains(variable) + } +} + +/// Interning expressions. +impl ExpressionEGraph { + fn intern(&mut self, expression: &vir_low::Expression) -> SpannedEncodingResult { + assert!( + expression.is_heap_independent(), + "expression: {}", + expression + ); + let id = self.egraph.try_intern_term(expression)?.unwrap_or_else(|| { + panic!("expression {expression} cannot be interned"); + }); + if let vir_low::Expression::Local(local) = expression { + if !self.interned_variables.contains(&local.variable) { + self.interned_variables.insert(local.variable.clone()); + } + } + Ok(id) + } +} + +/// Equalities. +impl ExpressionEGraph { + pub(in super::super) fn assume_equal( + &mut self, + left: &vir_low::Expression, + right: &vir_low::Expression, + ) -> SpannedEncodingResult<()> { + match (left, right) { + (vir_low::Expression::Local(left_local), vir_low::Expression::Local(right_local)) => { + self.assumed_variable_equalities + .insert((left_local.variable.clone(), right_local.variable.clone())); + self.assumed_variable_equalities + .insert((right_local.variable.clone(), left_local.variable.clone())); + } + _ => {} + } + let left_id = self.intern(left)?; + let right_id = self.intern(right)?; + self.egraph.assume_equal(left_id, right_id)?; + Ok(()) + } + + pub(in super::super) fn assume_expression_valid( + &mut self, + expression: &vir_low::Expression, + ) -> SpannedEncodingResult<()> { + let expression_id = self.intern(expression)?; + self.egraph.assume_expression_valid(expression_id)?; + Ok(()) + } + + pub(in super::super) fn is_expression_valid( + &mut self, + expression: &vir_low::Expression, + ) -> SpannedEncodingResult { + let expression_id = self.intern(expression)?; + self.egraph.is_expression_valid(expression_id) + } + + pub(in super::super) fn is_equal( + &mut self, + left: &vir_low::Expression, + right: &vir_low::Expression, + ) -> SpannedEncodingResult { + let left_id = self.intern(left)?; + let right_id = self.intern(right)?; + self.saturate_solver()?; + self.egraph.is_equal(left_id, right_id) + } + + // pub(in super::super) fn is_non_aliased_address( + // &mut self, + // address: &vir_low::Expression, + // ) -> SpannedEncodingResult { + // let address_id = self.intern(address)?; + // if self + // .egraph + // .is_equal(address_id, self.egraph.non_aliased_address_id)? + // { + // return Ok(true); + // } else { + // self.saturate_solver()?; + // self.egraph + // .is_equal(address_id, self.egraph.non_aliased_address_id) + // } + // } + + pub(in super::super) fn resolve_constant( + &mut self, + expression: &vir_low::Expression, + constant_constructors: &FxHashSet, + ) -> SpannedEncodingResult, vir_low::Expression)>> { + let id = self.intern(expression)?; + self.saturate_solver()?; + self.egraph.resolve_constant(id, constant_constructors) + } + + pub(in super::super) fn saturate_solver(&mut self) -> SpannedEncodingResult<()> { + self.egraph.saturate() + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/egg/language.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/egg/language.rs new file mode 100644 index 00000000000..1c27fe0f66b --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/egg/language.rs @@ -0,0 +1,31 @@ +use egg::{define_language, Id, Symbol}; + +define_language! { + pub(super) enum ExpressionLanguage { + "true" = True, + "false" = False, + "NON_ALIASED_ADDRESS" = NonAliasedAddress, + "VALID_EXPRESSION" = ValidExpression, + "==" = EqCmp([Id; 2]), + "!=" = NeCmp([Id; 2]), + ">" = GtCmp([Id; 2]), + ">=" = GeCmp([Id; 2]), + "<=" = LtCmp([Id; 2]), + "<" = LeCmp([Id; 2]), + "+" = Add([Id; 2]), + "-" = Sub([Id; 2]), + "*" = Mul([Id; 2]), + "/" = Div([Id; 2]), + "%" = Mod([Id; 2]), + "&&" = And([Id; 2]), + "||" = Or([Id; 2]), + "==>" = Implies([Id; 2]), + "!" = Not(Id), + "neg" = Minus(Id), + Int(i64), + BigInt(Symbol), + Variable(Symbol), + FuncApp(Symbol, Vec), + BuiltinFuncApp(Symbol, Vec), + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/egg/mod.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/egg/mod.rs new file mode 100644 index 00000000000..091ac29a050 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/egg/mod.rs @@ -0,0 +1,11 @@ +mod language; +mod term_interner; +mod rule_applier; +mod graphviz; +mod state; +mod interface; + +pub(super) use self::{ + interface::{ExpressionEGraph, IntersectingReport}, + state::EGraphState, +}; diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/egg/rule_applier.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/egg/rule_applier.rs new file mode 100644 index 00000000000..fb89b29d6c0 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/egg/rule_applier.rs @@ -0,0 +1,34 @@ +use super::language::ExpressionLanguage; + +pub(super) struct RuleApplier { + source: egg::PatternAst, + target: egg::PatternAst, +} + +impl RuleApplier { + pub(super) fn new( + source: egg::PatternAst, + target: egg::PatternAst, + ) -> Self { + Self { source, target } + } +} + +impl egg::Applier for RuleApplier { + fn apply_one( + &self, + egraph: &mut egg::EGraph, + _eclass: egg::Id, + subst: &egg::Subst, + _searcher_ast: Option<&egg::PatternAst>, + rule_name: egg::Symbol, + ) -> Vec { + let (new_id, unified) = + egraph.union_instantiations(&self.source, &self.target, subst, rule_name); + if unified { + vec![new_id] + } else { + Vec::new() + } + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/egg/state.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/egg/state.rs new file mode 100644 index 00000000000..afb9505e3f2 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/egg/state.rs @@ -0,0 +1,247 @@ +use super::{language::ExpressionLanguage, rule_applier::RuleApplier, term_interner::TermInterner}; +use crate::encoder::{ + errors::SpannedEncodingResult, middle::core_proof::snapshots::SnapshotDomainInfo, +}; +use egg::{EGraph, Id, Language}; +use rustc_hash::FxHashSet; +use std::collections::BTreeMap; +use vir_crate::low::{self as vir_low}; + +#[derive(Clone)] +pub(in super::super) struct EGraphState { + pub(super) egraph: EGraph, + pub(super) simplification_rules: Vec>, + pub(super) false_id: Id, + pub(super) true_id: Id, + pub(super) non_aliased_address_id: Id, + // pub(super) valid_expression_id: Id, +} + +impl EGraphState { + pub(in super::super) fn new( + domains: &[vir_low::DomainDecl], + _bool_type: vir_low::Type, + _bool_domain_info: SnapshotDomainInfo, + ) -> SpannedEncodingResult { + let mut egraph = EGraph::default().with_explanations_enabled(); + let true_id = egraph.add(ExpressionLanguage::True); + let false_id = egraph.add(ExpressionLanguage::False); + let non_aliased_address_id = egraph.add(ExpressionLanguage::NonAliasedAddress); + // let valid_expression_id = egraph.add(ExpressionLanguage::ValidExpression); + let mut simplification_rules = Vec::new(); + for domain in domains { + for rule in &domain.rewrite_rules { + let mut variables = BTreeMap::new(); + let mut source_pattern_ast: egg::RecExpr> = + egg::RecExpr::default(); + let true_id = + source_pattern_ast.add(egg::ENodeOrVar::ENode(ExpressionLanguage::True)); + let false_id = + source_pattern_ast.add(egg::ENodeOrVar::ENode(ExpressionLanguage::False)); + for variable in &rule.variables { + let egg_variable: egg::Var = format!("?{}", variable.name).parse().unwrap(); + let variable_id = source_pattern_ast.add(egg::ENodeOrVar::Var(egg_variable)); + variables.insert(variable.name.clone(), variable_id); + } + let mut target_pattern_ast = source_pattern_ast.clone(); + let mut trigger_pattern = source_pattern_ast.clone(); + source_pattern_ast.intern_pattern(&rule.source, true_id, false_id, &variables)?; + target_pattern_ast.intern_pattern(&rule.target, true_id, false_id, &variables)?; + let egg_rule = if let Some(triggers) = &rule.triggers { + assert_eq!( + triggers.len(), + 1, + "Currently only single term triggers are implemented." + ); + assert_eq!( + triggers[0].terms.len(), + 1, + "Currently only single term triggers are implemented." + ); + trigger_pattern.intern_pattern( + &triggers[0].terms[0], + true_id, + false_id, + &variables, + )?; + let trigger_pattern = egg::Pattern::new(trigger_pattern); + egg::rewrite!(&rule.name; trigger_pattern => { + RuleApplier::new(source_pattern_ast, target_pattern_ast) + }) + } else { + let source_pattern = egg::Pattern::new(source_pattern_ast); + let target_pattern = egg::Pattern::new(target_pattern_ast); + egg::rewrite!(&rule.name; source_pattern => target_pattern) + }; + simplification_rules.push(egg_rule); + } + } + Ok(Self { + egraph, + simplification_rules, + true_id, + false_id, + non_aliased_address_id, + // valid_expression_id, + }) + } + + // pub(in super::super) fn merge_in(&mut self, other: &EGraphState) -> SpannedEncodingResult<()> { + // self.egraph.egraph_union(&other.egraph); + // Ok(()) + // } + + pub(in super::super) fn intersect_with( + &mut self, + other: &EGraphState, + ) -> SpannedEncodingResult<()> { + let new_egraph = { + self.egraph.analysis; + self.egraph.egraph_intersect(&other.egraph, ()) + }; + let _old_egraph = std::mem::replace(&mut self.egraph, new_egraph); + Ok(()) + } + + pub(in super::super) fn assume_equal( + &mut self, + left_id: Id, + right_id: Id, + ) -> SpannedEncodingResult<()> { + self.egraph.union(left_id, right_id); + Ok(()) + } + + /// If the graph was modified, `saturate` must be called before `is_equal` can + /// be used. + pub(in super::super) fn is_equal( + &self, + left_id: Id, + right_id: Id, + ) -> SpannedEncodingResult { + Ok(self.egraph.find(left_id) == self.egraph.find(right_id)) + } + + /// Check whether the term is known to be a constant. + /// + /// Returns: + /// + /// * `Some((Some(constructor_name), constant))` if the term is equivalent + /// to a given constantat wrapped in the specified constructor. + /// * `Some(None, constant)` if the term is directly equivalent to a constant. + /// * `None` if the term is not equivalent to a constant. + pub(in super::super) fn resolve_constant( + &self, + expression_id: Id, + constant_constructors: &FxHashSet, + ) -> SpannedEncodingResult, vir_low::Expression)>> { + struct PreferConstantsCostFunction<'a> { + constant_constructors: &'a FxHashSet, + } + impl<'a> egg::CostFunction for PreferConstantsCostFunction<'a> { + type Cost = f64; + fn cost(&mut self, enode: &ExpressionLanguage, mut costs: C) -> Self::Cost + where + C: FnMut(Id) -> Self::Cost, + { + let op_cost = match enode { + ExpressionLanguage::True + | ExpressionLanguage::False + | ExpressionLanguage::Int(_) + | ExpressionLanguage::BigInt(_) => 1.0, + ExpressionLanguage::FuncApp(symbol, _) + | ExpressionLanguage::BuiltinFuncApp(symbol, _) + if self.constant_constructors.contains(symbol.as_str()) => + { + 2.0 + } + _ => 10.0, + }; + enode + .children() + .iter() + .fold(op_cost, |sum, id| sum + costs(*id)) + } + } + let cost_func = PreferConstantsCostFunction { + constant_constructors, + }; + let extractor = egg::Extractor::new(&self.egraph, cost_func); + let (_best_cost, node) = extractor.find_best(expression_id); + let last: Id = (node.as_ref().len() - 1).into(); + match &node[last] { + ExpressionLanguage::FuncApp(name, arguments) if arguments.len() == 1 => { + match node[arguments[0]] { + ExpressionLanguage::Int(constant) => { + return Ok(Some((Some(name.to_string()), constant.into()))); + } + ExpressionLanguage::BigInt(constant) => { + return Ok(Some(( + Some(name.to_string()), + vir_low::Expression::constant_no_pos( + vir_low::ConstantValue::BigInt(constant.to_string()), + vir_low::Type::Int, + ), + ))); + } + _ => {} + } + } + ExpressionLanguage::Int(constant) => { + return Ok(Some((None, (*constant).into()))); + } + ExpressionLanguage::BigInt(constant) => { + let constant_value = vir_low::ConstantValue::BigInt(constant.to_string()); + let expression = + vir_low::Expression::constant_no_pos(constant_value, vir_low::Type::Int); + return Ok(Some((None, expression))); + } + _ => {} + } + Ok(None) + } + + pub(in super::super) fn saturate(&mut self) -> SpannedEncodingResult<()> { + self.egraph.rebuild(); + let runner: egg::Runner<_, _, ()> = egg::Runner::new(()) + .with_egraph(std::mem::take(&mut self.egraph)) + // .with_node_limit(200) + .run(&self.simplification_rules); + if !(matches!(runner.stop_reason.unwrap(), egg::StopReason::Saturated)) { + runner + .egraph + .dot() + .to_dot("/tmp/egraph-unsaturated.dot") + .unwrap(); + panic!("simplification rules did not saturate; see /tmp/egraph-unsaturated.dot"); + } + self.egraph = runner.egraph; + Ok(()) + } + + pub(in super::super) fn try_intern_term( + &mut self, + term: &vir_low::Expression, + ) -> SpannedEncodingResult> { + self.egraph + .try_intern_term(term, self.true_id, self.false_id) + } + + pub(in super::super) fn assume_expression_valid( + &mut self, + _expression_id: Id, + ) -> SpannedEncodingResult<()> { + // FIXME: This is completely wrong. We should equate not the expression + // directly, but a validity function applied to the expression. + // self.egraph.union(expression_id, self.valid_expression_id); + Ok(()) + } + + pub(in super::super) fn is_expression_valid( + &mut self, + _expression_id: Id, + ) -> SpannedEncodingResult { + unimplemented!(); + // Ok(self.egraph.find(expression_id) == self.egraph.find(self.valid_expression_id)) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/egg/term_interner.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/egg/term_interner.rs new file mode 100644 index 00000000000..c81d216830b --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/egg/term_interner.rs @@ -0,0 +1,281 @@ +use super::language::ExpressionLanguage; +use crate::encoder::errors::SpannedEncodingResult; +use egg::{EGraph, Id, RecExpr, Symbol}; +use std::collections::BTreeMap; +use vir_crate::low::{self as vir_low}; + +pub(super) trait TermInterner { + fn try_intern_term( + &mut self, + term: &vir_low::Expression, + true_id: Id, + false_id: Id, + ) -> SpannedEncodingResult>; + + fn intern_term( + &mut self, + term: &vir_low::Expression, + true_id: Id, + false_id: Id, + ) -> SpannedEncodingResult; + + fn intern_pattern( + &mut self, + term: &vir_low::Expression, + true_id: Id, + false_id: Id, + variables: &BTreeMap, + ) -> SpannedEncodingResult; + + fn add(&mut self, term: ExpressionLanguage) -> Id; +} + +impl TermInterner for EGraph { + fn try_intern_term( + &mut self, + term: &vir_low::Expression, + true_id: Id, + false_id: Id, + ) -> SpannedEncodingResult> { + Ok(try_intern_term_rec( + self, + true_id, + false_id, + &BTreeMap::new(), + term, + )) + } + + fn intern_term( + &mut self, + term: &vir_low::Expression, + true_id: Id, + false_id: Id, + ) -> SpannedEncodingResult { + Ok( + try_intern_term_rec(self, true_id, false_id, &BTreeMap::new(), term) + .unwrap_or_else(|| panic!("Failed to intern term: {term}")), + ) + } + + fn intern_pattern( + &mut self, + term: &vir_low::Expression, + true_id: Id, + false_id: Id, + variables: &BTreeMap, + ) -> SpannedEncodingResult { + Ok( + try_intern_term_rec(self, true_id, false_id, variables, term) + .unwrap_or_else(|| panic!("Failed to intern term: {term}")), + ) + } + + fn add(&mut self, term: ExpressionLanguage) -> Id { + assert!(self.are_explanations_enabled()); + + self.add(term) + } +} + +impl TermInterner for RecExpr> { + fn try_intern_term( + &mut self, + term: &vir_low::Expression, + true_id: Id, + false_id: Id, + ) -> SpannedEncodingResult> { + Ok(try_intern_term_rec( + self, + true_id, + false_id, + &BTreeMap::new(), + term, + )) + } + + fn intern_term( + &mut self, + term: &vir_low::Expression, + true_id: Id, + false_id: Id, + ) -> SpannedEncodingResult { + Ok( + try_intern_term_rec(self, true_id, false_id, &BTreeMap::new(), term) + .unwrap_or_else(|| panic!("Failed to intern term: {term}")), + ) + } + + fn intern_pattern( + &mut self, + term: &vir_low::Expression, + true_id: Id, + false_id: Id, + variables: &BTreeMap, + ) -> SpannedEncodingResult { + Ok( + try_intern_term_rec(self, true_id, false_id, variables, term) + .unwrap_or_else(|| panic!("Failed to intern term: {term}")), + ) + } + + fn add(&mut self, term: ExpressionLanguage) -> Id { + self.add(egg::ENodeOrVar::ENode(term)) + } +} + +/// This method must be called only through `intern_term` that checks its +/// precondition. That is the reason why this method is private and not part of +/// `TermInterner`. +fn try_intern_term_rec( + egraph: &mut impl TermInterner, + true_id: Id, + false_id: Id, + variables: &BTreeMap, + term: &vir_low::Expression, +) -> Option { + let id = match term { + vir_low::Expression::Local(expression) => { + if let Some(variable_id) = variables.get(&expression.variable.name) { + *variable_id + } else { + let symbol = Symbol::from(&expression.variable.name); + + egraph.add(ExpressionLanguage::Variable(symbol)) + } + } + vir_low::Expression::Constant(expression) => match &expression.value { + vir_low::ConstantValue::Bool(true) => true_id, + vir_low::ConstantValue::Bool(false) => false_id, + vir_low::ConstantValue::Int(value) => egraph.add(ExpressionLanguage::Int(*value)), + vir_low::ConstantValue::BigInt(value) => { + if let Ok(value_int) = std::str::FromStr::from_str(value) { + egraph.add(ExpressionLanguage::Int(value_int)) + } else { + egraph.add(ExpressionLanguage::BigInt(Symbol::from(value))) + } + } + }, + vir_low::Expression::UnaryOp(expression) => { + let operand_id = + try_intern_term_rec(egraph, true_id, false_id, variables, &expression.argument)?; + match expression.op_kind { + vir_low::UnaryOpKind::Not => egraph.add(ExpressionLanguage::Not(operand_id)), + vir_low::UnaryOpKind::Minus => egraph.add(ExpressionLanguage::Minus(operand_id)), + } + } + vir_low::Expression::BinaryOp(expression) => { + let left_id = + try_intern_term_rec(egraph, true_id, false_id, variables, &expression.left)?; + let right_id = + try_intern_term_rec(egraph, true_id, false_id, variables, &expression.right)?; + match expression.op_kind { + vir_low::BinaryOpKind::EqCmp => { + egraph.add(ExpressionLanguage::EqCmp([left_id, right_id])) + } + vir_low::BinaryOpKind::NeCmp => { + egraph.add(ExpressionLanguage::NeCmp([left_id, right_id])) + } + vir_low::BinaryOpKind::GtCmp => { + egraph.add(ExpressionLanguage::GtCmp([left_id, right_id])) + } + vir_low::BinaryOpKind::GeCmp => { + egraph.add(ExpressionLanguage::GeCmp([left_id, right_id])) + } + vir_low::BinaryOpKind::LtCmp => { + egraph.add(ExpressionLanguage::LtCmp([left_id, right_id])) + } + vir_low::BinaryOpKind::LeCmp => { + egraph.add(ExpressionLanguage::LeCmp([left_id, right_id])) + } + vir_low::BinaryOpKind::Add => { + egraph.add(ExpressionLanguage::Add([left_id, right_id])) + } + vir_low::BinaryOpKind::Sub => { + egraph.add(ExpressionLanguage::Sub([left_id, right_id])) + } + vir_low::BinaryOpKind::Mul => { + egraph.add(ExpressionLanguage::Mul([left_id, right_id])) + } + vir_low::BinaryOpKind::Div => { + egraph.add(ExpressionLanguage::Div([left_id, right_id])) + } + vir_low::BinaryOpKind::Mod => { + egraph.add(ExpressionLanguage::Mod([left_id, right_id])) + } + vir_low::BinaryOpKind::And => { + egraph.add(ExpressionLanguage::And([left_id, right_id])) + } + vir_low::BinaryOpKind::Or => { + egraph.add(ExpressionLanguage::Or([left_id, right_id])) + } + vir_low::BinaryOpKind::Implies => { + egraph.add(ExpressionLanguage::Implies([left_id, right_id])) + } + } + } + vir_low::Expression::PermBinaryOp(expression) => { + let left_id = + try_intern_term_rec(egraph, true_id, false_id, variables, &expression.left)?; + let right_id = + try_intern_term_rec(egraph, true_id, false_id, variables, &expression.right)?; + match expression.op_kind { + vir_low::expression::PermBinaryOpKind::Add => { + egraph.add(ExpressionLanguage::Add([left_id, right_id])) + } + vir_low::expression::PermBinaryOpKind::Sub => { + egraph.add(ExpressionLanguage::Sub([left_id, right_id])) + } + vir_low::expression::PermBinaryOpKind::Mul => { + egraph.add(ExpressionLanguage::Mul([left_id, right_id])) + } + vir_low::expression::PermBinaryOpKind::Div => { + egraph.add(ExpressionLanguage::Div([left_id, right_id])) + } + } + } + vir_low::Expression::ContainerOp(expression) => { + let mut operands = Vec::new(); + for operand in &expression.operands { + let operand_id = + try_intern_term_rec(egraph, true_id, false_id, variables, operand)?; + operands.push(operand_id); + } + egraph.add(ExpressionLanguage::BuiltinFuncApp( + Symbol::from(format!("{:?}", expression.kind)), + operands, + )) + } + vir_low::Expression::DomainFuncApp(expression) => { + let symbol = Symbol::from(&expression.function_name); + let arguments = expression + .arguments + .iter() + .map(|argument| try_intern_term_rec(egraph, true_id, false_id, variables, argument)) + .collect::>>()?; + egraph.add(ExpressionLanguage::FuncApp(symbol, arguments)) + } + vir_low::Expression::LabelledOld(expression) => { + try_intern_term_rec(egraph, true_id, false_id, variables, &expression.base)? + } + // FIXME: It does not make sense to intern the contents of these + // expressions because in the interning table we store only the id of + // the root. + vir_low::Expression::Conditional(_) + | vir_low::Expression::Quantifier(_) + | vir_low::Expression::LetExpr(_) => { + unreachable!("term: {}", term); + } + vir_low::Expression::MagicWand(_) + | vir_low::Expression::PredicateAccessPredicate(_) + | vir_low::Expression::FieldAccessPredicate(_) + | vir_low::Expression::Unfolding(_) + | vir_low::Expression::FuncApp(_) + | vir_low::Expression::InhaleExhale(_) + | vir_low::Expression::Field(_) => { + unreachable!("term: {}", term); + } + vir_low::Expression::SmtOperation(_) => todo!(), + }; + Some(id) +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/expression_interner.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/expression_interner.rs new file mode 100644 index 00000000000..b0a7b0114ac --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/expression_interner.rs @@ -0,0 +1,70 @@ +use crate::encoder::errors::SpannedEncodingResult; +use rustc_hash::FxHashMap; +use vir_crate::low::{self as vir_low}; + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub(super) struct ExpressionId(u64); + +/// FIXME: Rename this to Equality Manager or something like that. +#[derive(Default)] +pub(super) struct ExpressionInterner { + bool_expression_ids: FxHashMap, + snapshot_expression_ids: FxHashMap, +} + +/// Interning boolean expressions for consistency checker. +impl ExpressionInterner { + pub(super) fn intern_bool_expression( + &mut self, + expression: &vir_low::Expression, + ) -> SpannedEncodingResult { + assert!( + expression.is_heap_independent(), + "expression: {}", + expression + ); + if let Some(expression_id) = self.bool_expression_ids.get(expression) { + Ok(*expression_id) + } else { + let id = self.bool_expression_ids.len() as u64; + let expression_id = ExpressionId(id); + self.bool_expression_ids + .insert(expression.clone(), expression_id); + Ok(expression_id) + } + } + + pub(super) fn intern_snapshot_expression( + &mut self, + expression: &vir_low::Expression, + ) -> SpannedEncodingResult { + assert!( + expression.is_heap_independent(), + "expression: {}", + expression + ); + // FIXME: Avoid unnecessary clone. + let expression = expression.clone().remove_unnecessary_old(); + if let Some(expression_id) = self.snapshot_expression_ids.get(&expression) { + Ok(*expression_id) + } else { + let id = self.snapshot_expression_ids.len() as u64; + let expression_id = ExpressionId(id); + self.snapshot_expression_ids + .insert(expression, expression_id); + Ok(expression_id) + } + } + + pub(super) fn lookup_snapshot_expression_id( + &self, + expression: &vir_low::Expression, + ) -> SpannedEncodingResult> { + if !expression.is_heap_independent() { + return Ok(None); + } + let expression = expression.clone().remove_unnecessary_old(); + let id = self.snapshot_expression_ids.get(&expression).copied(); + Ok(id) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/mod.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/mod.rs new file mode 100644 index 00000000000..21937106b36 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/mod.rs @@ -0,0 +1,87 @@ +//! This module contains the symbolic execution engine that is used to purify +//! predicates in the Viper program. This module depends on `ErrorManager` and, +//! therefore, has to be in the `prusti-viper` crate. + +use self::procedure_executor::ProcedureExecutor; +use super::encoder_context::EncoderContext; +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::{predicates::OwnedPredicateInfo, snapshots::SnapshotDomainsInfo}, +}; +use log::debug; +use rustc_hash::FxHashSet; +use std::collections::BTreeMap; +use vir_crate::low::{self as vir_low}; + +mod program_context; +mod procedure_executor; +mod block_builder; +mod trace_builder; +mod expression_interner; +mod egg; + +pub(in super::super) use self::program_context::ProgramContext; + +pub(in super::super) fn purify_with_symbolic_execution( + encoder: &mut impl EncoderContext, + source_filename: &str, + program: vir_low::Program, + non_aliased_memory_block_addresses: FxHashSet, + snapshot_domains_info: &SnapshotDomainsInfo, + owned_predicates_info: BTreeMap, + extensionality_gas_constant: &vir_low::Expression, +) -> SpannedEncodingResult { + debug!( + "purify_with_symbolic_execution {} {}", + source_filename, program.name + ); + let mut executor = Executor::new(); + let program = executor.execute( + source_filename, + program, + non_aliased_memory_block_addresses, + snapshot_domains_info, + owned_predicates_info, + extensionality_gas_constant, + encoder, + )?; + Ok(program) +} + +struct Executor {} + +impl Executor { + pub(crate) fn new() -> Self { + Self {} + } + + pub(crate) fn execute( + &mut self, + source_filename: &str, + mut program: vir_low::Program, + non_aliased_memory_block_addresses: FxHashSet, + snapshot_domains_info: &SnapshotDomainsInfo, + owned_predicates_info: BTreeMap, + extensionality_gas_constant: &vir_low::Expression, + encoder: &mut impl EncoderContext, + ) -> SpannedEncodingResult { + let mut program_context = ProgramContext::new( + &program.domains, + &program.functions, + &program.predicates, + snapshot_domains_info, + owned_predicates_info, + &non_aliased_memory_block_addresses, + extensionality_gas_constant, + encoder, + ); + let mut new_procedures = Vec::new(); + for procedure in program.procedures { + let procedure_executor = + ProcedureExecutor::new(self, source_filename, &mut program_context, &procedure)?; + procedure_executor.execute_procedure(&mut new_procedures)?; + } + program.procedures = new_procedures; + Ok(program) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/block_marker_conditions.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/block_marker_conditions.rs new file mode 100644 index 00000000000..79e8221fd7a --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/block_marker_conditions.rs @@ -0,0 +1,47 @@ +use vir_crate::{ + common::display, + low::{self as vir_low}, +}; + +#[derive(derive_more::Display, PartialEq, Eq, PartialOrd, Ord, Clone, Debug)] +#[display(fmt = "{}{}", "display::condition!(*visited, \"\", \"!\")", label)] +pub struct BlockMarkerConditionElement { + pub label: vir_low::Label, + pub visited: bool, +} + +#[derive(derive_more::Display, PartialEq, Eq, PartialOrd, Ord, Clone, Debug)] +#[display(fmt = "{}", "display::cjoin(elements)")] +pub struct BlockMarkerCondition { + pub elements: Vec, +} + +impl BlockMarkerCondition { + pub fn visited_with_disambiguator( + visited_label: vir_low::Label, + disambiguator: Vec, + ) -> Self { + let mut this = Self { + elements: Vec::new(), + }; + this.extend_with_visited_with_disambiguator(visited_label, disambiguator); + this + } + + pub fn extend_with_visited_with_disambiguator( + &mut self, + visited_label: vir_low::Label, + disambiguator: Vec, + ) { + self.elements.push(BlockMarkerConditionElement { + label: visited_label, + visited: true, + }); + for label in disambiguator { + self.elements.push(BlockMarkerConditionElement { + visited: false, + label, + }); + } + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/constraints/block.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/constraints/block.rs new file mode 100644 index 00000000000..96c025ee482 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/constraints/block.rs @@ -0,0 +1,582 @@ +use std::collections::{BTreeMap, BTreeSet}; + +use super::{ + consistency_tracker::ConsistencyTracker, + equality_manager::{EqualityState, EqualityStateMergeReport}, + merge_report::ConstraintsMergeReport, + validity_tracker::ValidityTracker, +}; +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::transformations::{ + encoder_context::EncoderContext, + symbolic_execution_new::{ + expression_interner::{ExpressionId, ExpressionInterner}, + program_context::ProgramContext, + }, + }, +}; +use rustc_hash::FxHashSet; +use vir_crate::low::{self as vir_low, operations::ty::Typed}; + +pub(in super::super) struct BlockConstraints { + visited_blocks: BTreeSet, + /// Consistency tracker for the path up to this point. + pub(super) consistency_tracker: ConsistencyTracker, + /// Consistency tracker only for this block. The difference is achieved by + /// overriding the `clone` method. + pub(super) block_consistency_tracker: ConsistencyTracker, + pub(super) validity_tracker: ValidityTracker, + /// The lifetime equalities. + pub(super) lifetime_equality_classes: BTreeMap, + /// The lifetime equalities based on the intersect function applications. + /// The map is from the equality class to the set of lifetimes that are part + /// of the equality class. + pub(super) derived_lifetime_equality_classes: BTreeMap, BTreeSet>, + /// To which version an old lifetime SSA version was mapped. + pub(super) lifetime_version_updates: BTreeMap<(String, u32), u32>, + /// The equalities of everything that are not lifetimes. + pub(super) equality_classes: EqualityState, +} + +impl Clone for BlockConstraints { + fn clone(&self) -> Self { + Self { + visited_blocks: self.visited_blocks.clone(), + block_consistency_tracker: Default::default(), + consistency_tracker: self.consistency_tracker.clone(), + validity_tracker: self.validity_tracker.clone(), + lifetime_equality_classes: self.lifetime_equality_classes.clone(), + derived_lifetime_equality_classes: self.derived_lifetime_equality_classes.clone(), + lifetime_version_updates: self.lifetime_version_updates.clone(), + equality_classes: self.equality_classes.clone(), + } + } +} + +impl BlockConstraints { + pub(in super::super) fn new( + program_context: &ProgramContext, + ) -> SpannedEncodingResult { + let equality_classes = EqualityState::new(program_context)?; + Ok(Self { + visited_blocks: Default::default(), + block_consistency_tracker: Default::default(), + consistency_tracker: Default::default(), + validity_tracker: Default::default(), + lifetime_equality_classes: Default::default(), + derived_lifetime_equality_classes: Default::default(), + lifetime_version_updates: Default::default(), + equality_classes, + }) + } + + pub(super) fn is_inconsistent(&self) -> SpannedEncodingResult { + self.consistency_tracker.is_inconsistent() + } + + pub(super) fn is_definitely_true( + &self, + expression: &vir_low::Expression, + expression_id: ExpressionId, + ) -> SpannedEncodingResult { + self.consistency_tracker + .is_definitely_true(expression, expression_id) + } + + pub(super) fn is_definitely_false( + &self, + expression: &vir_low::Expression, + expression_id: ExpressionId, + ) -> SpannedEncodingResult { + self.consistency_tracker + .is_definitely_false(expression, expression_id) + } + + pub(in super::super) fn assume_false(&mut self) -> SpannedEncodingResult<()> { + self.block_consistency_tracker.assume_false()?; + self.consistency_tracker.assume_false() + } + + pub(super) fn assume( + &mut self, + expression_interner: &mut ExpressionInterner, + expression: &vir_low::Expression, + expression_id: ExpressionId, + value: bool, + ) -> SpannedEncodingResult<()> { + self.block_consistency_tracker + .assume(expression, expression_id, value)?; + self.consistency_tracker + .assume(expression, expression_id, value)?; + self.try_assume_valid(expression_interner, expression, value)?; + Ok(()) + } + + pub(super) fn assuming_makes_block_inconsistent( + &self, + expression_id: ExpressionId, + value: bool, + ) -> SpannedEncodingResult { + self.consistency_tracker + .assuming_makes_inconsistent(expression_id, value) + } + + /// Extracts validity expressions and assumes them to be valid. + pub(super) fn try_assume_valid( + &mut self, + expression_interner: &mut ExpressionInterner, + expression: &vir_low::Expression, + value: bool, + ) -> SpannedEncodingResult<()> { + if !expression.is_heap_independent() { + return Ok(()); + } + match expression { + // FIXME: Do not rely on string comparisons. Use program_context + // instead. + vir_low::Expression::DomainFuncApp(domain_func_app) + if domain_func_app.function_name.starts_with("valid$") => + { + assert_eq!(domain_func_app.arguments.len(), 1); + assert!(value, "Not valid?: {expression}"); + self.validity_tracker + .assume_expression_valid(expression_interner, &domain_func_app.arguments[0])?; + self.equality_classes + .assume_expression_valid(expression_interner, &domain_func_app.arguments[0])?; + } + _ => {} + } + // self.validity_tracker + // .assume(expression_interner, expression, value)?; + Ok(()) + } + + fn try_propagate_validity( + &mut self, + expression_interner: &mut ExpressionInterner, + left: &vir_low::Expression, + right: &vir_low::Expression, + ) -> SpannedEncodingResult<()> { + if !left.is_heap_independent() || right.is_heap_independent() { + return Ok(()); + } + if self + .validity_tracker + .is_valid_expression(expression_interner, left)? + { + self.validity_tracker + .assume_expression_valid(expression_interner, right)?; + } + if self + .validity_tracker + .is_valid_expression(expression_interner, right)? + { + self.validity_tracker + .assume_expression_valid(expression_interner, left)?; + } + Ok(()) + } + + /// Assumes that the given expression is valid. + pub(in super::super) fn assume_expression_valid( + &mut self, + expression_interner: &mut ExpressionInterner, + expression: &vir_low::Expression, + ) -> SpannedEncodingResult<()> { + self.validity_tracker + .assume_expression_valid(expression_interner, expression)?; + Ok(()) + } + + pub(in super::super) fn is_expression_valid( + &mut self, + expression_interner: &mut ExpressionInterner, + expression: &vir_low::Expression, + ) -> SpannedEncodingResult { + if let Some(expression_id) = + expression_interner.lookup_snapshot_expression_id(expression)? + { + if self.validity_tracker.is_valid(expression_id)? { + return Ok(true); + } + } + self.equality_classes.is_expression_valid(expression) + } + + pub(in super::super) fn resolve_cannonical_lifetime_name( + &self, + lifetime_name: &str, + ) -> SpannedEncodingResult> { + if let Some(cannonical_name) = self.lifetime_equality_classes.get(lifetime_name) { + Ok(Some(&**cannonical_name)) + } else { + Ok(None) + } + } + + pub(in super::super) fn get_equal_lifetimes( + &self, + lifetime_name: &str, + ) -> SpannedEncodingResult> { + let mut equality_class = BTreeSet::new(); + for (name, cannonical_name) in &self.lifetime_equality_classes { + if cannonical_name == lifetime_name { + equality_class.insert(name.clone()); + } + if name == lifetime_name { + equality_class.insert(cannonical_name.clone()); + } + } + for lifetimes in self.derived_lifetime_equality_classes.values() { + if lifetimes.contains(lifetime_name) { + equality_class.extend(lifetimes.iter().cloned()); + } + } + equality_class.insert(lifetime_name.to_string()); + Ok(equality_class) + } + + pub(in super::super) fn get_dependent_lifetimes_for( + &self, + lifetime_name: &str, + ) -> SpannedEncodingResult> { + let mut dependent_lifetimes = BTreeSet::new(); + for (equality_class, lifetimes) in &self.derived_lifetime_equality_classes { + if equality_class.contains(lifetime_name) { + dependent_lifetimes.extend(lifetimes.iter().cloned()); + } + } + Ok(dependent_lifetimes) + } + + pub(in super::super) fn get_latest_lifetime_version( + &self, + lifetime_name: &str, + mut current_version: u32, + ) -> SpannedEncodingResult { + while let Some(version) = self + .lifetime_version_updates + .get(&(lifetime_name.to_string(), current_version)) + { + current_version = *version; + } + Ok(current_version) + } + + pub(in super::super) fn merge( + &mut self, + other: &Self, + ) -> SpannedEncodingResult { + self.visited_blocks + .extend(other.visited_blocks.iter().cloned()); + self.consistency_tracker.merge(&other.consistency_tracker)?; + self.validity_tracker.merge(&other.validity_tracker)?; + // Keep only the lifetime equalities that are present in both states. + self.lifetime_equality_classes + .retain(|name, cannonical_name| { + other + .lifetime_equality_classes + .get(name) + .map(|other_cannonical_name| other_cannonical_name == cannonical_name) + .unwrap_or(false) + }); + for (equality_class, lifetimes) in &mut self.derived_lifetime_equality_classes { + if let Some(other_lifetimes) = + other.derived_lifetime_equality_classes.get(equality_class) + { + lifetimes.retain(|lifetime| other_lifetimes.contains(lifetime)); + } else { + lifetimes.clear(); + } + } + // Keep only the lifetime version updates that are present in both states. + let self_lifetime_version_updates = self.lifetime_version_updates.clone(); + self.lifetime_version_updates + .retain(|name_with_version, version| { + other + .lifetime_version_updates + .get(name_with_version) + .map(|other_version| other_version == version) + .unwrap_or(false) + }); + // // Keep only the lifetime equalities that are present in both states. + // let dropped_self_lifetime_equalities = self + // .lifetime_equality_classes + // .drain_filter(|name, cannonical_name| { + // !other + // .lifetime_equality_classes + // .get(name) + // .map(|other_cannonical_name| other_cannonical_name == cannonical_name) + // .unwrap_or(false) + // }) + // .map(|(name, cannonical_name)| (cannonical_name, name)) + // .collect(); + // let dropped_other_lifetime_equalities = other + // .lifetime_equality_classes + // .iter() + // .filter(|(name, cannonical_name)| { + // !self + // .lifetime_equality_classes + // .get(*name) + // .map(|self_cannonical_name| self_cannonical_name == *cannonical_name) + // .unwrap_or(false) + // }) + // .map(|(name, cannonical_name)| (cannonical_name.clone(), name.clone())) + // .collect(); + // Merge equality graphs. + let EqualityStateMergeReport { + self_remaps, + other_remaps, + dropped_self_equalities, + dropped_other_equalities, + } = self.equality_classes.merge(&other.equality_classes)?; + Ok(ConstraintsMergeReport { + // dropped_self_lifetime_equalities, + // dropped_other_lifetime_equalities, + self_lifetime_version_updates, + other_lifetime_version_updates: other.lifetime_version_updates.clone(), + self_remaps, + other_remaps, + dropped_self_equalities, + dropped_other_equalities, + }) + } + + pub(in super::super) fn saturate_solver(&mut self) -> SpannedEncodingResult<()> { + self.equality_classes.saturate_solver() + } + + fn assume_lifetimes_equal(&mut self, left: &vir_low::Expression, right: &vir_low::Expression) { + fn parse_variable_version(name_with_version: &str) -> (&str, u32) { + // FIXME: This is a hack. We should use proper versioned variables. The + // version is the number after the last `$`. + let mut split = name_with_version.rsplitn(2, '$'); + let version = split.next().unwrap().parse::().unwrap(); + let name = split.next().unwrap(); + (name, version) + } + match (left, right) { + (vir_low::Expression::Local(left), vir_low::Expression::Local(right)) => { + let (left_name, left_version) = parse_variable_version(&left.variable.name); + let (right_name, right_version) = parse_variable_version(&right.variable.name); + if !left_name.starts_with("old_") { + // FIXME: This is a hack, we should not rely on string comparisons. + assert_eq!(left_name, right_name); + if left_version < right_version { + if let Some(old_right_version) = self + .lifetime_version_updates + .insert((left_name.to_string(), left_version), right_version) + { + assert_eq!( + old_right_version, right_version, + "{left_name}:{left_version} → {right_version}" + ); + } + } else if let Some(old_left_version) = self + .lifetime_version_updates + .insert((right_name.to_string(), right_version), left_version) + { + assert_eq!( + old_left_version, left_version, + "{right_name}:{right_version} → {left_version}" + ); + } + } + let mut cannonical_name = &right.variable.name; + while let Some(base) = self.lifetime_equality_classes.get(cannonical_name) { + cannonical_name = base; + } + self.lifetime_equality_classes + .insert(left.variable.name.clone(), cannonical_name.to_string()); + } + ( + vir_low::Expression::Local(left), + vir_low::Expression::DomainFuncApp(vir_low::DomainFuncApp { + domain_name, + function_name, + arguments, + .. + }), + ) if domain_name == "Lifetime" && function_name == "intersect" => { + // FIXME: Do not rely on string comparisons. + assert_eq!(arguments.len(), 1); + let intersected_lifetimes: BTreeSet<_> = + if let vir_low::Expression::ContainerOp(set_constructor) = &arguments[0] { + assert_eq!( + set_constructor.kind, + vir_low::ContainerOpKind::SetConstructor + ); + set_constructor + .operands + .iter() + .map(|element| { + if let vir_low::Expression::Local(local) = element { + local.variable.name.clone() + } else { + unreachable!(); + } + }) + .collect() + } else { + unreachable!(); + }; + let entry = self + .derived_lifetime_equality_classes + .entry(intersected_lifetimes) + .or_default(); + entry.insert(left.variable.name.clone()); + } + _ => { + unimplemented!("{left:?}\n{right:?}") + } + } + } + + pub(super) fn get_equalities( + &self, + ) -> SpannedEncodingResult> { + self.equality_classes.get_equalities() + } + + // pub(in super::super) fn is_non_aliased_address( + // &mut self, + // address: &vir_low::Expression, + // ) -> SpannedEncodingResult { + // self.equality_classes.is_non_aliased_address(address) + // } + + pub(in super::super) fn assume_equal( + &mut self, + expression_interner: &mut ExpressionInterner, + left: &vir_low::Expression, + right: &vir_low::Expression, + ) -> SpannedEncodingResult<()> { + self.equality_classes + .assume_equal(expression_interner, left, right)?; + if left.get_type().is_lifetime() { + self.assume_lifetimes_equal(left, right); + } + if left.get_type().is_bool() { + let left_id = expression_interner.intern_bool_expression(left)?; + let right_id = expression_interner.intern_bool_expression(right)?; + if self.consistency_tracker.is_definitely_true(left, left_id)? { + self.consistency_tracker.assume(right, right_id, true)?; + } + if self + .consistency_tracker + .is_definitely_false(left, left_id)? + { + self.consistency_tracker.assume(right, right_id, false)?; + } + if self + .consistency_tracker + .is_definitely_true(right, right_id)? + { + self.consistency_tracker.assume(left, left_id, true)?; + } + if self + .consistency_tracker + .is_definitely_false(right, right_id)? + { + self.consistency_tracker.assume(left, left_id, false)?; + } + } + self.try_propagate_validity(expression_interner, left, right)?; + Ok(()) + } + + pub(in super::super) fn is_equal( + &mut self, + expression_interner: &mut ExpressionInterner, + program_context: &ProgramContext, + arg1: &vir_low::Expression, + arg2: &vir_low::Expression, + ) -> SpannedEncodingResult { + let equal = if arg1 == arg2 { + true + } else { + assert_eq!(arg1.get_type(), arg2.get_type()); + let ty = arg1.get_type(); + match ty { + vir_low::Type::Int => todo!("{ty}"), + vir_low::Type::Bool => { + let arg1_id = expression_interner.intern_bool_expression(arg1)?; + let arg2_id = expression_interner.intern_bool_expression(arg2)?; + let both_true = self.is_definitely_true(arg1, arg1_id)? + && self.is_definitely_true(arg2, arg2_id)?; + let both_false = self.is_definitely_false(arg1, arg1_id)? + && self.is_definitely_false(arg2, arg2_id)?; + both_true || both_false + } + vir_low::Type::Perm => todo!("{ty}"), + vir_low::Type::Float(_) => todo!("{ty}"), + vir_low::Type::BitVector(_) => todo!("{ty}"), + vir_low::Type::Seq(_) => todo!("{ty}"), + vir_low::Type::Set(_) => todo!("{ty}"), + vir_low::Type::MultiSet(_) => todo!("{ty}"), + vir_low::Type::Map(_) => todo!("{ty}"), + vir_low::Type::Ref => todo!("{ty}"), + vir_low::Type::Domain(_) if program_context.is_place_option_type(ty) => { + // Places have to be syntactically equal, except when they are both aliased (none). + !program_context.is_place_non_aliased(arg1) + && !program_context.is_place_non_aliased(arg2) + } + // vir_low::Type::Domain(_) if program_context.is_address_type(ty) => self + // .equality_classes + // .is_equal(expression_interner, arg1, arg2)?, + // vir_low::Type::Domain(_) if program_context.is_lifetime_type(ty) => { + // let vir_low::Expression::Local(local1) = arg1 else { + // unreachable!("arg1: {arg1}"); + // }; + // let vir_low::Expression::Local(local2) = arg2 else { + // unreachable!("arg2: {arg2}"); + // }; + // let cannonical_arg1 = + // self.resolve_cannonical_lifetime_name(&local1.variable.name)?; + // let cannonical_arg2 = + // self.resolve_cannonical_lifetime_name(&local2.variable.name)?; + // match (cannonical_arg1, cannonical_arg2) { + // (Some(cannonical_arg1), Some(cannonical_arg2)) => { + // eprintln!(" {cannonical_arg1} == {cannonical_arg2}"); + // cannonical_arg1 == cannonical_arg2 + // } + // _ => self + // .equality_classes + // .is_equal(expression_interner, arg1, arg2)?, + // } + // } + vir_low::Type::Domain(_) => { + self.equality_classes + .is_equal(expression_interner, arg1, arg2)? + } + } + }; + Ok(equal) + } + + pub(in super::super) fn resolve_constant( + &mut self, + expression: &vir_low::Expression, + constant_constructors: &FxHashSet, + ) -> SpannedEncodingResult, vir_low::Expression)>> { + self.equality_classes + .resolve_constant(expression, constant_constructors) + } + + pub(in super::super) fn set_visited_block(&mut self, block: vir_low::Label) { + assert!(self.visited_blocks.insert(block)); + } + + pub(in super::super) fn get_visited_blocks(&self) -> &BTreeSet { + &self.visited_blocks + } + + pub(in super::super) fn set_visited_blocks( + &mut self, + new_visited_blocks: BTreeSet, + ) { + assert!(new_visited_blocks.is_subset(&self.visited_blocks)); + self.visited_blocks = new_visited_blocks; + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/constraints/consistency_tracker.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/constraints/consistency_tracker.rs new file mode 100644 index 00000000000..fe40de3143b --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/constraints/consistency_tracker.rs @@ -0,0 +1,106 @@ +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::transformations::symbolic_execution_new::expression_interner::ExpressionId, +}; +use rustc_hash::FxHashSet; +use vir_crate::{ + common::expression::SyntacticEvaluation, + low::{self as vir_low}, +}; + +#[derive(Clone, Default, Debug)] +pub(super) struct ConsistencyTracker { + is_inconsistent: bool, + /// The set of expressions that are known to be definitely true at this + /// point. + definitely_true: FxHashSet, + /// The set of expressions that are known to be definitely false at this + /// point. + definitely_false: FxHashSet, +} + +impl std::fmt::Display for ConsistencyTracker { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "is_inconsistent: {}", self.is_inconsistent)?; + writeln!(f, "definitely true",)?; + for id in &self.definitely_true { + writeln!(f, " {id:?}",)?; + } + writeln!(f, "definitely false",)?; + for id in &self.definitely_false { + writeln!(f, " {id:?}",)?; + } + Ok(()) + } +} + +impl ConsistencyTracker { + pub(super) fn is_inconsistent(&self) -> SpannedEncodingResult { + Ok(self.is_inconsistent) + } + + pub(super) fn is_definitely_true( + &self, + expression: &vir_low::Expression, + expression_id: ExpressionId, + ) -> SpannedEncodingResult { + Ok(self.definitely_true.contains(&expression_id) || expression.is_true()) + } + + pub(super) fn is_definitely_false( + &self, + expression: &vir_low::Expression, + expression_id: ExpressionId, + ) -> SpannedEncodingResult { + Ok(self.definitely_false.contains(&expression_id) || expression.is_false()) + } + + pub(super) fn merge(&mut self, other: &Self) -> SpannedEncodingResult<()> { + // Something is definitely true or false only if it is true or false on + // both states. Therefore, we intersect. + self.definitely_true + .retain(|id| other.definitely_true.contains(id)); + self.definitely_false + .retain(|id| other.definitely_false.contains(id)); + Ok(()) + } + + pub(super) fn assume_false(&mut self) -> SpannedEncodingResult<()> { + self.is_inconsistent = true; + Ok(()) + } + + pub(super) fn assume( + &mut self, + expression: &vir_low::Expression, + expression_id: ExpressionId, + value: bool, + ) -> SpannedEncodingResult<()> { + debug_assert!(expression.is_heap_independent()); + if value { + if self.is_definitely_false(expression, expression_id)? { + self.is_inconsistent = true; + } else { + self.definitely_true.insert(expression_id); + } + } else if self.is_definitely_true(expression, expression_id)? { + self.is_inconsistent = true; + } else { + self.definitely_false.insert(expression_id); + } + Ok(()) + } + + pub(super) fn assuming_makes_inconsistent( + &self, + expression_id: ExpressionId, + value: bool, + ) -> SpannedEncodingResult { + let is_inconsistent = if value { + self.definitely_false.contains(&expression_id) + } else { + self.definitely_true.contains(&expression_id) + }; + Ok(is_inconsistent) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/constraints/equality_manager.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/constraints/equality_manager.rs new file mode 100644 index 00000000000..a1289967a51 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/constraints/equality_manager.rs @@ -0,0 +1,247 @@ +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::transformations::{ + encoder_context::EncoderContext, + symbolic_execution_new::{ + egg::{ExpressionEGraph, IntersectingReport}, + expression_interner::ExpressionInterner, + program_context::ProgramContext, + }, + }, +}; +use log::debug; +use rustc_hash::{FxHashMap, FxHashSet}; +use vir_crate::low::{self as vir_low}; + +/// A data structure for tracking equalities between expressions of the same +/// type. At a specific block, the equalities are tracked by using two data +/// structures: +/// +/// 1. A log of equalities that have been seen so far. +/// 2. egg +/// +/// Merging is done by intersecting incoming egraphs. +/// // TODO: I cannot use my interning tables!!! Need to reconstruct expression from the EGraph representation!!! +// TODO: Do in the same way as with lifetimes: keep a set of interened expressions and from that compute +// which were dropped. Also keep a sequence of SSA variable reassignments seen in the last block. +// For all dropped expressions construct a new expression with SSA reassignments and add it to the +// map of changed expressions. +// #[derive(Clone)] +pub(super) struct EqualityState { + egraph: ExpressionEGraph, + /// Equalities between variables assumed in the current block. + variable_equalities_in_block: Vec<(vir_low::VariableDecl, vir_low::VariableDecl)>, + equalities_in_block: Vec<(vir_low::Expression, vir_low::Expression)>, +} + +impl Clone for EqualityState { + fn clone(&self) -> Self { + Self { + egraph: self.egraph.clone(), + variable_equalities_in_block: self.variable_equalities_in_block.clone(), + equalities_in_block: Default::default(), + } + } +} + +pub(super) struct EqualityStateMergeReport { + pub(super) self_remaps: FxHashMap, + pub(super) other_remaps: FxHashMap, + pub(super) dropped_self_equalities: FxHashMap, + pub(super) dropped_other_equalities: FxHashMap, +} + +impl EqualityState { + pub(super) fn new( + program_context: &ProgramContext, + ) -> SpannedEncodingResult { + let egraph = ExpressionEGraph::new(program_context)?; + Ok(Self { + egraph, + variable_equalities_in_block: Vec::new(), + equalities_in_block: Vec::new(), + }) + } + // pub(super) fn set_final_egraph(&mut self, egraph: EGraphState) -> SpannedEncodingResult<()> { + // assert!(self.final_egraph.is_none()); + // self.final_egraph = Some(egraph); + // Ok(()) + // } + + // pub(super) fn get_final_egraph(&self) -> SpannedEncodingResult<&EGraphState> { + // Ok(self.final_egraph.as_ref().unwrap()) + // } + + pub(super) fn saturate_solver(&mut self) -> SpannedEncodingResult<()> { + self.egraph.saturate_solver() + } + + pub(super) fn get_equalities( + &self, + ) -> SpannedEncodingResult> { + // let equalities = self + // .variable_equalities_in_block + // .iter() + // .map(|(left, right)| (left.clone().into(), right.clone().into())) + // .collect(); + // Ok(equalities) + Ok(self.equalities_in_block.clone()) + } + + // pub(super) fn is_non_aliased_address( + // &mut self, + // address: &vir_low::Expression, + // ) -> SpannedEncodingResult { + // self.egraph.is_non_aliased_address(address) + // } + + pub(super) fn assume_equal( + &mut self, + _expression_interner: &mut ExpressionInterner, + left: &vir_low::Expression, + right: &vir_low::Expression, + ) -> SpannedEncodingResult<()> { + self.equalities_in_block.push((left.clone(), right.clone())); + self.egraph.assume_equal(left, right)?; + match (left, right) { + (vir_low::Expression::Local(left), vir_low::Expression::Local(right)) => { + self.variable_equalities_in_block + .push((left.variable.clone(), right.variable.clone())); + } + _ => {} + } + Ok(()) + } + + pub(super) fn is_equal( + &mut self, + _expression_interner: &mut ExpressionInterner, + left: &vir_low::Expression, + right: &vir_low::Expression, + ) -> SpannedEncodingResult { + self.egraph.is_equal(left, right) + } + + pub(super) fn assume_expression_valid( + &mut self, + _expression_interner: &mut ExpressionInterner, + expression: &vir_low::Expression, + ) -> SpannedEncodingResult<()> { + self.egraph.assume_expression_valid(expression) + } + + pub(super) fn is_expression_valid( + &mut self, + expression: &vir_low::Expression, + ) -> SpannedEncodingResult { + self.egraph.is_expression_valid(expression) + } + + pub(super) fn resolve_constant( + &mut self, + expression: &vir_low::Expression, + constant_constructors: &FxHashSet, + ) -> SpannedEncodingResult, vir_low::Expression)>> { + self.egraph + .resolve_constant(expression, constant_constructors) + } + + pub(super) fn merge( + &mut self, + other: &Self, + ) -> SpannedEncodingResult { + let IntersectingReport { + self_dropped_variables, + other_dropped_variables, + self_dropped_equalities, + other_dropped_equalities, + } = self.egraph.intersect_with(&other.egraph)?; + fn create_remap( + dropped_variables: Vec, + variable_equalities_in_block: &[(vir_low::VariableDecl, vir_low::VariableDecl)], + egraph: &ExpressionEGraph, + ) -> FxHashMap { + let mut remap = FxHashMap::default(); + let mut unmapped_variables = Vec::new(); + 'outer: for variable in dropped_variables { + for (left, right) in variable_equalities_in_block { + if &variable == right && egraph.contains(left) { + debug!("remapping {} to {}", variable, left); + remap.insert(variable, left.clone()); + continue 'outer; + } + if &variable == left && egraph.contains(right) { + debug!("remapping {} to {}", variable, right); + remap.insert(variable, right.clone()); + continue 'outer; + } + } + unmapped_variables.push(variable); + } + let mut remaps_added = true; + while remaps_added { + remaps_added = false; + 'outer: for variable in std::mem::take(&mut unmapped_variables) { + for (left, right) in variable_equalities_in_block { + if &variable == right { + if let Some(left_remap) = remap.get(left) { + debug!("remapping {} to {}", variable, left_remap); + remap.insert(variable, left_remap.clone()); + remaps_added = true; + continue 'outer; + } + } + if &variable == left { + if let Some(right_remap) = remap.get(right) { + debug!("remapping {} to {}", variable, right_remap); + remap.insert(variable, right_remap.clone()); + remaps_added = true; + continue 'outer; + } + } + } + unmapped_variables.push(variable); + } + } + remap + } + let intersected_egraph = &self.egraph; + let self_remaps = create_remap( + self_dropped_variables, + &self.variable_equalities_in_block, + intersected_egraph, + ); + let other_remaps = create_remap( + other_dropped_variables, + &other.variable_equalities_in_block, + intersected_egraph, + ); + let report = EqualityStateMergeReport { + self_remaps, + other_remaps, + dropped_self_equalities: self_dropped_equalities.into_iter().collect(), + dropped_other_equalities: other_dropped_equalities.into_iter().collect(), + }; + Ok(report) + } + + // pub(super) fn merge(&mut self, other: &Self) { + // // Intersect all the `equality_log` sets in the states. + // self.equality_log + // .retain(|equality| other.equality_log.contains(equality)); + // // Reconstruct the union-find. + // self.union_find = InPlaceUnificationTable::new(); + // self.expression_to_key = BTreeMap::new(); + // for (left_expression_id, right_expression_id) in &self.equality_log { + // let left_key = *self + // .expression_to_key + // .entry(*left_expression_id) + // .or_insert_with(|| self.union_find.new_key(())); + // let right_key = *self + // .expression_to_key + // .entry(*right_expression_id) + // .or_insert_with(|| self.union_find.new_key(())); + // self.union_find.union(left_key, right_key); + // } + // } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/constraints/merge_report.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/constraints/merge_report.rs new file mode 100644 index 00000000000..7fec47d48a1 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/constraints/merge_report.rs @@ -0,0 +1,104 @@ +use rustc_hash::FxHashMap; +use std::collections::BTreeMap; +use vir_crate::low::{self as vir_low}; + +#[derive(Debug, Clone)] +pub(in super::super) struct ConstraintsMergeReport { + // pub(super) dropped_self_lifetime_equalities: BTreeMap, + // pub(super) dropped_other_lifetime_equalities: BTreeMap, + pub(super) self_lifetime_version_updates: BTreeMap<(String, u32), u32>, + pub(super) other_lifetime_version_updates: BTreeMap<(String, u32), u32>, + pub(super) self_remaps: FxHashMap, + pub(super) other_remaps: FxHashMap, + pub(super) dropped_self_equalities: FxHashMap, + pub(super) dropped_other_equalities: FxHashMap, +} + +impl ConstraintsMergeReport { + // pub(in super::super) fn resolve_new_self_cannonical_lifetime_name( + // &self, + // lifetime_name: &str, + // ) -> Option<&String> { + // self.dropped_self_lifetime_equalities.get(lifetime_name) + // } + + // pub(in super::super) fn resolve_new_other_cannonical_lifetime_name( + // &self, + // lifetime_name: &str, + // ) -> Option<&String> { + // self.dropped_other_lifetime_equalities.get(lifetime_name) + // } + + // pub(in super::super) fn resolve_old_other_cannonical_lifetime_name( + // &self, + // lifetime_name: &str, + // ) -> Option<&String> { + // self.dropped_other_lifetime_equalities + // .iter() + // .find_map(|(old_name, new_name)| { + // if new_name == lifetime_name { + // Some(old_name) + // } else { + // None + // } + // }) + // } + + pub(in super::super) fn resolve_self_latest_lifetime_variable_version( + &self, + lifetime_variable: &str, + mut current_version: u32, + ) -> u32 { + while let Some(new_version) = self + .self_lifetime_version_updates + .get(&(lifetime_variable.to_string(), current_version)) + .copied() + { + current_version = new_version; + } + current_version + } + + pub(in super::super) fn resolve_other_latest_lifetime_variable_version( + &self, + lifetime_variable: &str, + mut current_version: u32, + ) -> u32 { + while let Some(new_version) = self + .other_lifetime_version_updates + .get(&(lifetime_variable.to_string(), current_version)) + .copied() + { + current_version = new_version; + } + current_version + // self.other_lifetime_version_updates + // .get(&(lifetime_variable.to_string(), current_version)) + // .copied() + // .unwrap_or(current_version) + } + + pub(in super::super) fn get_self_remaps( + &self, + ) -> &FxHashMap { + &self.self_remaps + } + + pub(in super::super) fn get_other_remaps( + &self, + ) -> &FxHashMap { + &self.other_remaps + } + + pub(in super::super) fn get_dropped_self_equalities( + &self, + ) -> &FxHashMap { + &self.dropped_self_equalities + } + + pub(in super::super) fn get_dropped_other_equalities( + &self, + ) -> &FxHashMap { + &self.dropped_other_equalities + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/constraints/mod.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/constraints/mod.rs new file mode 100644 index 00000000000..bd720d082b0 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/constraints/mod.rs @@ -0,0 +1,201 @@ +use std::collections::{BTreeMap, BTreeSet}; + +use super::{super::super::encoder_context::EncoderContext, ProcedureExecutor}; +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::transformations::symbolic_execution_new::expression_interner::ExpressionId, +}; +use vir_crate::{ + common::{cfg::Cfg, expression::SyntacticEvaluation}, + low::{self as vir_low, operations::ty::Typed}, +}; + +mod block; +mod merge_report; +mod equality_manager; +mod consistency_tracker; +mod validity_tracker; +mod visited_blocks; + +pub(super) use self::{block::BlockConstraints, merge_report::ConstraintsMergeReport}; + +impl<'a, 'c, EC: EncoderContext> ProcedureExecutor<'a, 'c, EC> { + pub(super) fn path_constraints_inconsistent(&self) -> SpannedEncodingResult { + self.current_block_constraints().is_inconsistent() + } + + pub(super) fn try_assume_heap_independent_conjuncts( + &mut self, + expression: &vir_low::Expression, + ) -> SpannedEncodingResult<()> { + match expression { + vir_low::Expression::BinaryOp(binary_expression) => match binary_expression.op_kind { + vir_low::BinaryOpKind::EqCmp => { + self.try_assume_equal(&binary_expression.left, &binary_expression.right)?; + return Ok(()); + } + vir_low::BinaryOpKind::And => { + self.try_assume_heap_independent_conjuncts(&binary_expression.left)?; + self.try_assume_heap_independent_conjuncts(&binary_expression.right)?; + return Ok(()); + } + _ => {} + }, + + _ => {} + } + self.try_assume(expression, true)?; + Ok(()) + } + + fn try_assume_equal( + &mut self, + left: &vir_low::Expression, + right: &vir_low::Expression, + ) -> SpannedEncodingResult<()> { + if left.is_term() && right.is_term() { + debug_assert_eq!(left.get_type(), right.get_type()); + let current_block = self.current_block.as_ref().unwrap(); + let current_constraints = + &mut self.state_keeper.get_state_mut(current_block).constraints; + current_constraints.assume_equal(&mut self.expression_interner, left, right)?; + } + Ok(()) + } + + fn try_assume( + &mut self, + expression: &vir_low::Expression, + value: bool, + ) -> SpannedEncodingResult<()> { + if expression.is_term() { + match expression { + vir_low::Expression::UnaryOp(unary_expression) => match unary_expression.op_kind { + vir_low::UnaryOpKind::Not => { + self.try_assume(&unary_expression.argument, !value)?; + return Ok(()); + } + _ => {} + }, + vir_low::Expression::Local(_) => { + self.assume(expression, value)?; + } + _ if expression.is_false() && value => { + self.assume_false()?; + } + _ => { + let current_block = self.current_block.as_ref().unwrap(); + let current_constraints = + &mut self.state_keeper.get_state_mut(current_block).constraints; + current_constraints.try_assume_valid( + &mut self.expression_interner, + expression, + value, + )?; + } + } + } + Ok(()) + } + + fn assume_false(&mut self) -> SpannedEncodingResult<()> { + let current_constraints = self.current_block_constraints_mut(); + current_constraints.assume_false()?; + Ok(()) + } + + fn assume( + &mut self, + expression: &vir_low::Expression, + value: bool, + ) -> SpannedEncodingResult<()> { + debug_assert!(expression.is_heap_independent()); + let expression_id = self + .expression_interner + .intern_bool_expression(expression)?; + let current_block = self.current_block.as_ref().unwrap(); + let current_constraints = &mut self.state_keeper.get_state_mut(current_block).constraints; + // let current_constraints = self.current_block_constraints_mut(); + current_constraints.assume( + &mut self.expression_interner, + expression, + expression_id, + value, + )?; + if !current_constraints.is_inconsistent()? { + let current_constraints = self.current_block_constraints(); + let result: Option<(BTreeSet, BTreeSet)> = self + .check_inconsistencies_with_visited_blocks( + current_constraints.get_visited_blocks(), + expression_id, + value, + )?; + if let Some((new_visited_blocks, new_dominators)) = result { + let mut equalities = BTreeMap::new(); + for new_dominator in new_dominators { + let dominator_equalities = self + .state_keeper + .get_state(&new_dominator) + .constraints + .get_equalities()?; + equalities.insert(new_dominator, dominator_equalities); + } + let current_block = self.current_block.as_ref().unwrap(); + let current_constraints = + &mut self.state_keeper.get_state_mut(current_block).constraints; + current_constraints.set_visited_blocks(new_visited_blocks); + for (_dominator, dominator_equalities) in &equalities { + for (left, right) in dominator_equalities { + current_constraints.assume_equal( + &mut self.expression_interner, + left, + right, + )?; + } + } + } + } + Ok(()) + } + + fn check_inconsistencies_with_visited_blocks( + &self, + visited_blocks: &BTreeSet, + expression_id: ExpressionId, + value: bool, + ) -> SpannedEncodingResult, BTreeSet)>> { + let mut inconsistent_blocks = BTreeSet::new(); + for visited_block in visited_blocks { + let visited_block_constraints = &self.state_keeper.get_state(visited_block).constraints; + if visited_block_constraints.assuming_makes_block_inconsistent(expression_id, value)? { + inconsistent_blocks.insert(visited_block); + } + } + if !inconsistent_blocks.is_empty() { + let current_label = self.current_block.as_ref().unwrap(); + let mut new_visited_blocks = self + .procedure + .find_reaching(current_label, &inconsistent_blocks); + assert!(new_visited_blocks.remove(current_label)); + new_visited_blocks.retain(|label| visited_blocks.contains(label)); + let old_dominators = self + .procedure + .compute_dominators_considering(current_label, visited_blocks); + let mut new_dominators = self + .procedure + .compute_dominators_considering(current_label, &new_visited_blocks); + new_dominators.retain(|dominator| !old_dominators.contains(dominator)); + Ok(Some((new_visited_blocks, new_dominators))) + } else { + Ok(None) + } + } + + fn current_block_constraints_mut(&mut self) -> &mut BlockConstraints { + &mut self.current_state_mut().constraints + } + + fn current_block_constraints(&self) -> &BlockConstraints { + &self.current_state().constraints + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/constraints/validity_tracker.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/constraints/validity_tracker.rs new file mode 100644 index 00000000000..bfc586eb8d8 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/constraints/validity_tracker.rs @@ -0,0 +1,86 @@ +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::transformations::symbolic_execution_new::expression_interner::{ + ExpressionId, ExpressionInterner, + }, +}; +use rustc_hash::FxHashSet; +use vir_crate::low::{self as vir_low}; + +#[derive(Clone, Default, Debug)] +pub(super) struct ValidityTracker { + /// The set of values that are known to be valid at this point. + valid_expressions: FxHashSet, +} + +impl std::fmt::Display for ValidityTracker { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "valid expressions")?; + for id in &self.valid_expressions { + writeln!(f, " {id:?}",)?; + } + Ok(()) + } +} + +impl ValidityTracker { + pub(super) fn merge(&mut self, other: &Self) -> SpannedEncodingResult<()> { + // Something is valid only if it is valid in both states. Therefore, we + // intersect. + self.valid_expressions + .retain(|id| other.valid_expressions.contains(id)); + Ok(()) + } + + // pub(super) fn assume( + // &mut self, + // expression_interner: &mut ExpressionInterner, + // expression: &vir_low::Expression, + // value: bool, + // ) -> SpannedEncodingResult<()> { + // if !expression.is_heap_independent() { + // return Ok(()); + // } + // match expression { + // // FIXME: Do not rely on string comparisons. Use program_context + // // instead. + // vir_low::Expression::DomainFuncApp(domain_func_app) + // if domain_func_app.function_name.starts_with("valid$") => + // { + // assert_eq!(domain_func_app.arguments.len(), 1); + // assert!(value, "Not valid?: {expression}"); + // let expression_id = expression_interner + // .intern_snapshot_expression(&domain_func_app.arguments[0])?; + // eprintln!("Assuming {expression_id:?} is valid: {expression}"); + // self.valid_expressions.insert(expression_id); + // } + // _ => {} + // } + // Ok(()) + // } + + pub(super) fn assume_expression_valid( + &mut self, + expression_interner: &mut ExpressionInterner, + expression: &vir_low::Expression, + ) -> SpannedEncodingResult<()> { + debug_assert!(expression.is_heap_independent()); + let expression_id = expression_interner.intern_snapshot_expression(expression)?; + self.valid_expressions.insert(expression_id); + Ok(()) + } + + pub(super) fn is_valid(&self, expression_id: ExpressionId) -> SpannedEncodingResult { + Ok(self.valid_expressions.contains(&expression_id)) + } + + pub(super) fn is_valid_expression( + &self, + expression_interner: &mut ExpressionInterner, + expression: &vir_low::Expression, + ) -> SpannedEncodingResult { + debug_assert!(expression.is_heap_independent()); + let expression_id = expression_interner.intern_snapshot_expression(expression)?; + self.is_valid(expression_id) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/constraints/visited_blocks.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/constraints/visited_blocks.rs new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/constraints/visited_blocks.rs @@ -0,0 +1 @@ + diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/expressions.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/expressions.rs new file mode 100644 index 00000000000..9d5822da83f --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/expressions.rs @@ -0,0 +1,353 @@ +use super::{ + constraints::BlockConstraints, + heap::{PurificationResult, SnapshotBinding}, + ProcedureExecutor, +}; +use crate::{ + encoder::{ + errors::{SpannedEncodingError, SpannedEncodingResult}, + middle::core_proof::transformations::{ + encoder_context::EncoderContext, + symbolic_execution_new::{ + expression_interner::ExpressionInterner, program_context::ProgramContext, + }, + }, + }, + error_internal, +}; +use prusti_common::config; +use vir_crate::{ + common::expression::BinaryOperationHelpers, + low::{self as vir_low, expression::visitors::ExpressionFallibleFolder, operations::ty::Typed}, +}; + +impl<'a, 'c, EC: EncoderContext> ProcedureExecutor<'a, 'c, EC> { + pub(super) fn simplify_expression( + &mut self, + expression: &vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult { + // self.add_statement(vir_low::Statement::comment(format!( + // "simplify expression: {expression}" + // )))?; + let PurificationResult { + expression, + guarded_assertions, + bindings, + } = self.purify_snap_function_calls(expression)?; + let current_block = self.current_block.as_ref().unwrap(); + let current_constraints = &mut self.state_keeper.get_state_mut(current_block).constraints; + let mut simplifier = Simplifier { + program_context: self.program_context, + constraints: current_constraints, + expression_interner: &mut self.expression_interner, + }; + let mut expression = simplifier.fallible_fold_expression(expression)?; + if config::symbolic_execution_simp_valid_expr() { + let mut validity_simplifier = ValiditySimplifier { + program_context: self.program_context, + constraints: current_constraints, + expression_interner: &mut self.expression_interner, + }; + expression = validity_simplifier.fallible_fold_expression(expression)?; + } + if !bindings.is_empty() { + self.add_statement(vir_low::Statement::comment( + "Let bindings for conditional snapshots".to_string(), + ))?; + for SnapshotBinding { + guard: _binding_guard, + variable, + guarded_candidates, + } in bindings + { + assert!(!guarded_candidates.is_empty()); + for (candidate_guard, candidate) in guarded_candidates { + let equality = + vir_low::Expression::equals(variable.clone().into(), candidate.into()); + let guarded_assume = vir_low::Statement::assume_no_pos( + vir_low::Expression::implies(candidate_guard, equality), + ) + .set_default_position(position); + self.add_statement(guarded_assume)?; + } + // let mut statement = + // vir_low::Statement::assert_no_pos(false.into()).set_default_position(position); + // for (candidate_guard, candidate) in guarded_candidates { + // statement = vir_low::Statement::conditional_no_pos( + // candidate_guard, + // vec![ + // vir_low::Statement::assume_no_pos(vir_low::Expression::equals( + // variable.clone().into(), + // candidate.into(), + // )) + // .set_default_position(position), + // ], + // vec![statement], + // ) + // .set_default_position(position); + // } + // // Putting this under binding_guard is not easy because it may + // // contain quantified variables, which need to be dealt with. + // // Omitting binding_guard is sound because the snapshot can have + // // the values only from the existing heap chunks. However, it + // // may be incomplete because the assert false branch may become + // // reachable. + // self.add_statement(statement)?; + // // self.add_statement( + // // vir_low::Statement::conditional_no_pos(binding_guard, vec![statement], vec![]) + // // .set_default_position(position), + // // )?; + } + } + if !guarded_assertions.is_empty() { + self.add_statement(vir_low::Statement::comment( + "Guarded assertions for snap function preconditions".to_string(), + ))?; + } + for assertion in guarded_assertions { + self.add_statement( + vir_low::Statement::assert_no_pos(assertion).set_default_position(position), + )?; + } + Ok(expression) + } +} + +struct Simplifier<'a, 'c: 'a, EC: EncoderContext> { + program_context: &'a mut ProgramContext<'c, EC>, + constraints: &'a mut BlockConstraints, + expression_interner: &'a mut ExpressionInterner, +} + +impl<'a, 'c: 'a, EC: EncoderContext> Simplifier<'a, 'c, EC> { + fn try_resolve_constants( + &mut self, + arguments: &[vir_low::Expression], + ) -> SpannedEncodingResult<(bool, Vec, vir_low::Expression)>>)> { + let mut maybe_constants = Vec::new(); + let mut found_constant = false; + for argument in arguments { + let maybe_constant = self.constraints.resolve_constant( + argument, + self.program_context.get_constant_constructor_names(), + )?; + if maybe_constant.is_some() { + found_constant = true; + } + maybe_constants.push(maybe_constant); + } + Ok((found_constant, maybe_constants)) + } +} + +impl<'a, 'c: 'a, EC: EncoderContext> ExpressionFallibleFolder for Simplifier<'a, 'c, EC> { + type Error = SpannedEncodingError; + + fn fallible_fold_domain_func_app_enum( + &mut self, + mut domain_func_app: vir_low::DomainFuncApp, + ) -> Result { + if let Some(op) = self + .program_context + .get_binary_operator(&domain_func_app.domain_name, &domain_func_app.function_name) + { + if matches!(op, vir_low::BinaryOpKind::Mul) { + let domain_func_app_original = + vir_low::Expression::DomainFuncApp(domain_func_app.clone()); + // eprintln!("simplify: {domain_func_app}"); + // self.intern_arguments_and_saturate(&domain_func_app.arguments)?; + let (found_constant, maybe_constants) = + self.try_resolve_constants(&domain_func_app.arguments)?; + if found_constant { + let constructor = self + .program_context + .get_constant_constructor(&domain_func_app.domain_name); + let destructor = self + .program_context + .get_constant_destructor(&domain_func_app.domain_name); + let mut constructor_arguments = Vec::new(); + for (maybe_constant, argument) in maybe_constants + .into_iter() + .zip(std::mem::take(&mut domain_func_app.arguments).into_iter()) + { + if let Some((constructor_name, constant)) = maybe_constant { + assert_eq!(constructor_name.unwrap(), constructor.name); + constructor_arguments.push(constant); + } else { + let destructor = vir_low::Expression::domain_function_call( + &domain_func_app.domain_name, + destructor.name.clone(), + vec![argument], + vir_low::Type::Int, + ); + constructor_arguments.push(destructor); + } + } + let right = constructor_arguments.pop().unwrap(); + let left = constructor_arguments.pop().unwrap(); + assert!(constructor_arguments.is_empty()); + let result = vir_low::Expression::domain_function_call( + domain_func_app.domain_name, + constructor.name.clone(), + vec![vir_low::Expression::multiply(left, right)], + domain_func_app.return_type, + ) + .set_default_position(domain_func_app.position); + + if result.is_term() && domain_func_app_original.is_term() { + self.constraints.assume_equal( + self.expression_interner, + &result, + &domain_func_app_original, + )?; + } + return Ok(result); + } else if config::error_non_linear_arithmetic_simp() { + let span = self + .program_context + .env() + .get_span(domain_func_app.position) + .unwrap(); + error_internal!(span => "failed to rewrite multiplication: {}", domain_func_app); + // unimplemented!("failed to rewrite multiplication: {domain_func_app}"); + } + } + } + self.fallible_fold_domain_func_app(domain_func_app) + .map(vir_low::Expression::DomainFuncApp) + } + + fn fallible_fold_binary_op_enum( + &mut self, + mut binary_op: vir_low::BinaryOp, + ) -> Result { + if matches!(binary_op.op_kind, vir_low::BinaryOpKind::Mul) + && !binary_op.left.is_constant() + && !binary_op.right.is_constant() + { + let arguments = vec![(*binary_op.left).clone(), (*binary_op.right).clone()]; + // self.intern_arguments_and_saturate(&arguments)?; + let (found_constant, maybe_constants) = self.try_resolve_constants(&arguments)?; + if found_constant { + let mut binary_op_arguments = Vec::new(); + for (maybe_constant, argument) in + maybe_constants.into_iter().zip(arguments.into_iter()) + { + if let Some((constructor_name, constant)) = maybe_constant { + assert!(constructor_name.is_none()); + binary_op_arguments.push(constant); + } else { + binary_op_arguments.push(argument); + } + } + let right = binary_op_arguments.pop().unwrap(); + let left = binary_op_arguments.pop().unwrap(); + assert!(binary_op_arguments.is_empty()); + binary_op.left = Box::new(left); + binary_op.right = Box::new(right); + return Ok(vir_low::Expression::BinaryOp(binary_op)); + } else if config::error_non_linear_arithmetic_simp() { + unimplemented!( + "failed to rewrite multiplication: {} * {}", + arguments[0], + arguments[1] + ); + } + } + self.fallible_fold_binary_op(binary_op) + .map(vir_low::Expression::BinaryOp) + } +} + +struct ValiditySimplifier<'a, 'c: 'a, EC: EncoderContext> { + program_context: &'a mut ProgramContext<'c, EC>, + constraints: &'a mut BlockConstraints, + expression_interner: &'a mut ExpressionInterner, +} + +impl<'a, 'c: 'a, EC: EncoderContext> ValiditySimplifier<'a, 'c, EC> { + fn apply_destructor( + &self, + argument: &vir_low::Expression, + ) -> SpannedEncodingResult { + match argument { + vir_low::Expression::DomainFuncApp(domain_func_app) + if self + .program_context + .is_constant_constructor(&domain_func_app.function_name) => + { + assert_eq!(domain_func_app.arguments.len(), 1); + Ok(domain_func_app.arguments[0].clone()) + } + _ => { + let vir_low::Type::Domain(domain) = argument.get_type() else { + unreachable!("expected domain type: {argument}: {}", argument.get_type()); + }; + let destructor_decl = self.program_context.get_constant_destructor(&domain.name); + let destructor_call = vir_low::Expression::domain_function_call( + &domain.name, + destructor_decl.name.clone(), + vec![argument.clone()], + destructor_decl.return_type.clone(), + ); + Ok(destructor_call) + } + } + } + + fn apply_constructor( + &self, + domain_name: &str, + argument: vir_low::Expression, + ) -> SpannedEncodingResult { + let constructor_decl = self.program_context.get_constant_constructor(domain_name); + let constructor_call = vir_low::Expression::domain_function_call( + domain_name, + constructor_decl.name.clone(), + vec![argument], + constructor_decl.return_type.clone(), + ); + Ok(constructor_call) + } +} + +impl<'a, 'c: 'a, EC: EncoderContext> ExpressionFallibleFolder for ValiditySimplifier<'a, 'c, EC> { + type Error = SpannedEncodingError; + + fn fallible_fold_domain_func_app_enum( + &mut self, + domain_func_app: vir_low::DomainFuncApp, + ) -> Result { + let domain_func_app = self.fallible_fold_domain_func_app(domain_func_app)?; + if let Some(op) = self + .program_context + .get_binary_operator(&domain_func_app.domain_name, &domain_func_app.function_name) + { + assert_eq!(domain_func_app.arguments.len(), 2); + let left = &domain_func_app.arguments[0]; + let right = &domain_func_app.arguments[1]; + if !left.is_heap_independent() || !right.is_heap_independent() { + return Ok(vir_low::Expression::DomainFuncApp(domain_func_app)); + } + if !self + .constraints + .is_expression_valid(self.expression_interner, left)? + || !self + .constraints + .is_expression_valid(self.expression_interner, right)? + { + return Ok(vir_low::Expression::DomainFuncApp(domain_func_app)); + } + let left = self.apply_destructor(left)?; + let right = self.apply_destructor(right)?; + let operation = + vir_low::Expression::binary_op(op, left, right, domain_func_app.position); + let constructor = self.apply_constructor(&domain_func_app.domain_name, operation)?; + self.constraints + .assume_expression_valid(self.expression_interner, &constructor)?; + return Ok(constructor); + } + Ok(vir_low::Expression::DomainFuncApp(domain_func_app)) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/graphviz.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/graphviz.rs new file mode 100644 index 00000000000..1618cfa8701 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/graphviz.rs @@ -0,0 +1,67 @@ +use super::ProcedureExecutor; +use crate::encoder::middle::core_proof::transformations::encoder_context::EncoderContext; +use vir_crate::{ + common::graphviz::{escape_html_wrap, Graph, NodeBuilder, ToGraphviz}, + low::{self as vir_low}, +}; + +fn label_to_string(label: &vir_low::Label) -> String { + label.name.clone() +} + +fn build_block(mut node_builder: NodeBuilder, statements: &[vir_low::Statement]) { + for statement in statements { + let statement_string = match statement { + vir_low::Statement::Comment(statement) => { + format!( + "{}", + escape_html_wrap(statement) + ) + } + _ => escape_html_wrap(statement.to_string()), + }; + node_builder.add_row_sequence(vec![statement_string]); + } + node_builder.build(); +} + +impl<'a, 'c, EC: EncoderContext> ToGraphviz for ProcedureExecutor<'a, 'c, EC> { + fn to_graph(&self) -> Graph { + let mut graph = Graph::with_columns(&["statement"]); + for (label, block) in &self.trace_builder.blocks { + let node_builder = graph.create_node(label_to_string(label)); + build_block(node_builder, &block.statements); + for successor in &block.successors { + if let Some(edge_block) = self + .trace_builder + .edge_blocks + .get(&(label.clone(), successor.clone())) + { + let edge_label = format!( + "{}__{}__edge", + label_to_string(label), + label_to_string(successor) + ); + let node_builder = graph.create_node(edge_label.clone()); + build_block(node_builder, edge_block); + graph.add_regular_edge(label_to_string(label), edge_label.clone()); + graph.add_regular_edge(edge_label, label_to_string(successor)); + } else { + graph.add_regular_edge(label_to_string(label), label_to_string(successor)); + } + } + } + if let Some(label) = &self.current_block { + let block = self.current_block_builder.as_ref().unwrap(); + let node_builder = graph.create_node_with_custom_style( + label_to_string(label), + "bgcolor=\"red\"".to_string(), + ); + build_block(node_builder, &block.statements); + for successor in &block.successors { + graph.add_regular_edge(label_to_string(label), label_to_string(successor)); + } + } + graph + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/close_frac_ref.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/close_frac_ref.rs new file mode 100644 index 00000000000..1a6cec137f4 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/close_frac_ref.rs @@ -0,0 +1,202 @@ +use super::{ + common::{AliasedWholeBool, NamedPredicateInstances, NoSnapshot}, + merge_report::HeapMergeReport, + GlobalHeapState, +}; +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::transformations::{ + encoder_context::EncoderContext, + symbolic_execution_new::{ + block_builder::BlockBuilder, + expression_interner::ExpressionInterner, + procedure_executor::constraints::{BlockConstraints, ConstraintsMergeReport}, + program_context::ProgramContext, + }, + }, +}; +use vir_crate::low::{self as vir_low}; + +#[derive(Default, Clone)] +pub(super) struct ClosedFracRef { + predicates: NamedPredicateInstances, +} + +impl std::fmt::Display for ClosedFracRef { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.predicates) + } +} + +impl ClosedFracRef { + pub(super) fn inhale( + &mut self, + program_context: &ProgramContext, + expression_interner: &mut ExpressionInterner, + global_state: &mut GlobalHeapState, + predicate: vir_low::PredicateAccessPredicate, + position: vir_low::Position, + constraints: &mut BlockConstraints, + block_builder: &mut BlockBuilder, + ) -> SpannedEncodingResult<()> { + self.predicates.inhale( + program_context, + expression_interner, + global_state, + predicate, + position, + constraints, + block_builder, + ) + } + + pub(super) fn exhale( + &mut self, + program_context: &mut ProgramContext, + expression_interner: &mut ExpressionInterner, + global_state: &mut GlobalHeapState, + predicate: vir_low::PredicateAccessPredicate, + position: vir_low::Position, + constraints: &mut BlockConstraints, + block_builder: &mut BlockBuilder, + ) -> SpannedEncodingResult<()> { + self.try_assert_frac_ref_snapshot_equality( + program_context, + expression_interner, + &predicate, + position, + constraints, + block_builder, + )?; + self.predicates.exhale( + program_context, + expression_interner, + global_state, + predicate, + position, + constraints, + block_builder, + ) + } + + pub(super) fn materialize( + &mut self, + program_context: &mut ProgramContext, + expression_interner: &mut ExpressionInterner, + global_state: &mut GlobalHeapState, + predicate: vir_low::PredicateAccessPredicate, + position: vir_low::Position, + constraints: &mut BlockConstraints, + block_builder: &mut BlockBuilder, + check_that_exists: bool, + ) -> SpannedEncodingResult<()> { + self.predicates.materialize( + program_context, + expression_interner, + global_state, + predicate, + position, + constraints, + block_builder, + check_that_exists, + ) + } + + fn try_assert_frac_ref_snapshot_equality( + &mut self, + program_context: &mut ProgramContext, + expression_interner: &mut ExpressionInterner, + predicate: &vir_low::PredicateAccessPredicate, + position: vir_low::Position, + constraints: &mut BlockConstraints, + block_builder: &mut BlockBuilder, + ) -> SpannedEncodingResult<()> { + if let Some(predicate_instances) = self.predicates.get_instances(&predicate.name) { + let predicate_lifetime = &predicate.arguments[0]; + let predicate_snapshot = &predicate.arguments[4]; + let mut snapshot_candidate = None; + for predicate_instance in predicate_instances.get_aliased_predicate_instances() { + let instance_lifetime = predicate_instance.get_argument(0); + if constraints.is_equal( + expression_interner, + program_context, + predicate_lifetime, + instance_lifetime, + )? { + if snapshot_candidate.is_some() { + // There are multiple snapshots for the same lifetime. + // We cannot assert anything. + return Ok(()); + } + snapshot_candidate = Some(predicate_instance.get_argument(4)); + } + } + if let Some(instance_snapshot) = snapshot_candidate { + block_builder.add_statement(vir_low::Statement::comment(format!( + "Asserting that the snapshot of {} is equal to the snapshot of the predicate instance", + predicate.name + )))?; + // This does not work because we do not have accees to the lowerer + // anymore ☹. + // + // ```rust + // let extensionality_trigger = + // self.lowerer.snapshots_extensionality_equal_call( + // predicate_snapshot.clone(), instance_snapshot.clone(), + // position, )?; + // ``` + // + // Instead, we use the following hack. + if let Some(extensionality_trigger) = program_context + .predicate_snapshots_extensionality_call( + predicate_snapshot.clone(), + instance_snapshot.clone(), + position, + ) + { + block_builder.add_statement(vir_low::Statement::assert( + extensionality_trigger, + position, + ))?; + } + } + } + Ok(()) + } + + pub(super) fn merge_deleted_permission_variables( + &mut self, + other: &Self, + ) -> SpannedEncodingResult<()> { + self.predicates + .merge_deleted_permission_variables(&other.predicates)?; + Ok(()) + } + + pub(super) fn merge( + &mut self, + other: &Self, + self_edge_block: &mut Vec, + other_edge_block: &mut Vec, + position: vir_low::Position, + heap_merge_report: &mut HeapMergeReport, + constraints: &mut BlockConstraints, + constraints_merge_report: &ConstraintsMergeReport, + expression_interner: &mut ExpressionInterner, + program_context: &ProgramContext, + global_state: &mut GlobalHeapState, + ) -> SpannedEncodingResult<()> { + self.predicates.merge( + &other.predicates, + self_edge_block, + other_edge_block, + position, + heap_merge_report, + constraints, + constraints_merge_report, + expression_interner, + program_context, + global_state, + ) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/common/mod.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/common/mod.rs new file mode 100644 index 00000000000..1b2b0ceb272 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/common/mod.rs @@ -0,0 +1,18 @@ +mod named_predicate_instances; +mod predicate_instances; +mod predicate_instance; +mod snapshot; +mod utils; + +pub(super) use self::{ + named_predicate_instances::NamedPredicateInstances, + predicate_instance::NoSnapshot, + predicate_instances::{ + AliasedFractionalBool, + // AliasedFractionalBoundedPerm, + AliasedWholeBool, + FindSnapshotResult, + // AliasedWholeNat, + PredicateInstances, + }, +}; diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/common/named_predicate_instances.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/common/named_predicate_instances.rs new file mode 100644 index 00000000000..a6d4fab6a78 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/common/named_predicate_instances.rs @@ -0,0 +1,288 @@ +use super::{ + predicate_instance::SnapshotType, + predicate_instances::{FindSnapshotResult, PermissionType}, + PredicateInstances, +}; +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::transformations::{ + encoder_context::EncoderContext, + symbolic_execution_new::{ + block_builder::BlockBuilder, + expression_interner::ExpressionInterner, + procedure_executor::{ + constraints::{BlockConstraints, ConstraintsMergeReport}, + heap::{global_heap_state::HeapVariables, GlobalHeapState, HeapMergeReport}, + }, + program_context::ProgramContext, + }, + }, +}; +use log::trace; +use prusti_common::config; +use std::collections::BTreeMap; +use vir_crate::low::{self as vir_low}; + +#[derive(Clone)] +pub(in super::super) struct NamedPredicateInstances { + predicates: BTreeMap>, +} + +impl Default for NamedPredicateInstances { + fn default() -> Self { + Self { + predicates: BTreeMap::new(), + } + } +} + +impl std::fmt::Display + for NamedPredicateInstances +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for (predicate_name, instances) in &self.predicates { + write!(f, "{}:\n{}", predicate_name, instances)?; + } + Ok(()) + } +} + +impl NamedPredicateInstances { + pub(in super::super) fn find_snapshot( + &self, + predicate_name: &str, + arguments: &[vir_low::Expression], + heap_variables: &mut HeapVariables, + constraints: &mut BlockConstraints, + expression_interner: &mut ExpressionInterner, + program_context: &ProgramContext, + ) -> SpannedEncodingResult { + trace!("find_snapshot: {}", predicate_name); + if let Some(predicate_instances) = self.predicates.get(predicate_name) { + predicate_instances.find_snapshot( + predicate_name, + arguments, + heap_variables, + constraints, + expression_interner, + program_context, + ) + } else { + Ok(FindSnapshotResult::NotFound) + } + } +} + +impl NamedPredicateInstances { + pub(in super::super) fn get_instances( + &self, + predicate_name: &str, + ) -> Option<&PredicateInstances> { + self.predicates.get(predicate_name) + } + + pub(in super::super) fn inhale( + &mut self, + program_context: &ProgramContext, + expression_interner: &mut ExpressionInterner, + global_state: &mut GlobalHeapState, + predicate: vir_low::PredicateAccessPredicate, + position: vir_low::Position, + constraints: &mut BlockConstraints, + block_builder: &mut BlockBuilder, + ) -> SpannedEncodingResult<()> { + trace!("inhale: {}", predicate.name); + let predicate_instances = self.predicates.entry(predicate.name.clone()).or_default(); + predicate_instances.inhale( + program_context, + expression_interner, + global_state, + predicate, + position, + constraints, + block_builder, + )?; + Ok(()) + } + + pub(in super::super) fn exhale( + &mut self, + program_context: &mut ProgramContext, + expression_interner: &mut ExpressionInterner, + global_state: &mut GlobalHeapState, + predicate: vir_low::PredicateAccessPredicate, + position: vir_low::Position, + constraints: &mut BlockConstraints, + block_builder: &mut BlockBuilder, + ) -> SpannedEncodingResult<()> { + trace!("exhale: {}", predicate.name); + if let Some(predicate_instances) = self.predicates.get_mut(&predicate.name) { + predicate_instances.exhale( + program_context, + expression_interner, + global_state, + predicate, + position, + constraints, + block_builder, + )?; + } else { + // Check if non-aliased. If aliased, then emit materialized exhale. + let is_non_aliased = super::utils::is_non_aliased( + &predicate.name, + &predicate.arguments, + program_context, + constraints, + )?; + block_builder.add_statement(vir_low::Statement::comment(format!( + "failed to exhale (nothing inhaled): {predicate}" + )))?; + if is_non_aliased { + if config::panic_on_failed_exhale() { + panic!("failed to exhale: {predicate}\n{self}"); + } + block_builder.add_statement( + vir_low::Statement::assert_no_pos(false.into()).set_default_position(position), + )?; + } else { + if config::panic_on_failed_exhale() + || config::panic_on_failed_exhale_materialization() + { + panic!("failed to exhale: {predicate}\n{self}"); + } + block_builder.add_statement(vir_low::Statement::exhale( + vir_low::Expression::PredicateAccessPredicate(predicate), + position, + ))?; + } + } + Ok(()) + } + + pub(in super::super) fn materialize( + &mut self, + program_context: &mut ProgramContext, + expression_interner: &mut ExpressionInterner, + global_state: &mut GlobalHeapState, + predicate: vir_low::PredicateAccessPredicate, + position: vir_low::Position, + constraints: &mut BlockConstraints, + block_builder: &mut BlockBuilder, + check_that_exists: bool, + ) -> SpannedEncodingResult<()> { + trace!("materialize: {}", predicate.name); + let predicate_instances = self.predicates.entry(predicate.name.clone()).or_default(); + predicate_instances.materialize( + program_context, + expression_interner, + global_state, + predicate, + position, + constraints, + block_builder, + check_that_exists, + )?; + Ok(()) + } + + pub(in super::super) fn prepare_for_unhandled_exhale( + &mut self, + program_context: &mut ProgramContext, + expression_interner: &mut ExpressionInterner, + global_state: &mut GlobalHeapState, + predicate_name: &str, + position: vir_low::Position, + constraints: &mut BlockConstraints, + block_builder: &mut BlockBuilder, + ) -> SpannedEncodingResult<()> { + trace!("prepare_for_unhandled_exhale: {}", predicate_name); + if let Some(predicate_instances) = self.predicates.get_mut(predicate_name) { + predicate_instances.prepare_for_unhandled_exhale( + program_context, + expression_interner, + global_state, + predicate_name, + position, + constraints, + block_builder, + )?; + } + Ok(()) + } + + pub(in super::super) fn merge_deleted_permission_variables( + &mut self, + other: &Self, + ) -> SpannedEncodingResult<()> { + for (predicate_name, self_instances) in &mut self.predicates { + if let Some(other_instances) = other.predicates.get(predicate_name) { + self_instances.merge_deleted_permission_variables(other_instances)?; + } + } + for predicate_name in other.predicates.keys() { + if !self.predicates.contains_key(predicate_name) { + let mut fresh_predicate_instances = PredicateInstances::default(); + fresh_predicate_instances + .merge_deleted_permission_variables(&other.predicates[predicate_name])?; + self.predicates + .insert(predicate_name.clone(), fresh_predicate_instances); + } + } + Ok(()) + } + + pub(in super::super) fn merge( + &mut self, + other: &Self, + self_edge_block: &mut Vec, + other_edge_block: &mut Vec, + position: vir_low::Position, + heap_merge_report: &mut HeapMergeReport, + constraints: &mut BlockConstraints, + constraints_merge_report: &ConstraintsMergeReport, + expression_interner: &mut ExpressionInterner, + program_context: &ProgramContext, + global_state: &mut GlobalHeapState, + ) -> SpannedEncodingResult<()> { + for (predicate_name, self_instances) in &mut self.predicates { + if let Some(other_instances) = other.predicates.get(predicate_name) { + self_instances.merge( + other_instances, + self_edge_block, + other_edge_block, + predicate_name, + position, + heap_merge_report, + constraints, + constraints_merge_report, + expression_interner, + program_context, + global_state, + )?; + } else { + // Nothing to do because we already have the information we need. + // FIXME: Check whether we need to `PredicateInstance::remap_arguments` here. + } + } + for predicate_name in other.predicates.keys() { + if !self.predicates.contains_key(predicate_name) { + unreachable!("merge_deleted_permission_variables should have already created"); + // // let mut self_predicate_instances = Vec::new(); + // // for other_instance in &other.predicates[predicate_name].predicate_instances { + // // let instance = other_instance.clone(); + // // self_predicate_instances.push(instance); + // // } + // // self.predicates.insert( + // // predicate_name.clone(), + // // PredicateInstances::new(self_predicate_instances), + // // ); + // self.predicates.insert( + // predicate_name.clone(), + // other.predicates[predicate_name].clone(), + // ); + // // FIXME: Check whether we need to `PredicateInstance::remap_arguments` here. + } + } + Ok(()) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/common/predicate_instance.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/common/predicate_instance.rs new file mode 100644 index 00000000000..22868d0c20d --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/common/predicate_instance.rs @@ -0,0 +1,341 @@ +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::transformations::{ + encoder_context::EncoderContext, + symbolic_execution_new::{ + expression_interner::ExpressionInterner, + procedure_executor::{ + constraints::BlockConstraints, + heap::{global_heap_state::HeapVariables, GlobalHeapState, HeapMergeReport}, + }, + program_context::ProgramContext, + }, + }, +}; +use rustc_hash::FxHashMap; +use std::collections::BTreeSet; +use vir_crate::{ + common::{ + display, + expression::{BinaryOperationHelpers, ExpressionIterator}, + }, + low::{self as vir_low}, +}; + +pub(in super::super) trait SnapshotType: Clone + std::fmt::Display { + fn merge_snapshots( + &mut self, + other: &Self, + predicate_name: &str, + heap_merge_report: &mut HeapMergeReport, + global_state: &mut GlobalHeapState, + ) -> SpannedEncodingResult<()>; + fn create_snapshot_variable( + predicate_name: &str, + program_context: &ProgramContext, + heap_variables: &mut HeapVariables, + ) -> SpannedEncodingResult; + fn as_expression(&self) -> Option; +} + +#[derive(Clone, Debug, Default)] +pub(in super::super) struct NoSnapshot; + +impl std::fmt::Display for NoSnapshot { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "NoSnapshot") + } +} + +#[derive(Clone)] +pub(in super::super) struct PredicateInstance { + /// Arguments of the predicate instance. + pub(super) arguments: Vec, + /// Snapshot of the predicate instance. + pub(super) snapshot_variable: S, + /// The permission amount of the predicate instance. + pub(super) permission_amount: vir_low::Expression, + /// The variable that holds the permission amount. The value of the variable + /// should be equal to `permission_amount` unless we are in a trace in which + /// the predciate was not inhaled. + pub(super) permission_variable: vir_low::VariableDecl, + /// Whether the predicate inhale was emitted as a Viper statement or is the + /// state tracked only in the symbolic execution. + /// + /// We use this flag instead of just forgetting the chunk because of the + /// following example: + /// + /// ```viper + /// inhale R + /// if cond { + /// exhale R2 // an exhale that triggers materialization + /// } + /// exhale R + /// ``` + /// + /// If we forgot about `R` at the materialization point, we would not be + /// able to verify `exhale R`: since we would still have a chunk with `R` + /// from the else branch, we would try to use its permission, but that would + /// inevitably fail. + pub(super) is_materialized: bool, + /// Is this predicate instance present on all incoming traces or just some + /// of them? + pub(super) is_unconditional: bool, +} + +impl SnapshotType for vir_low::VariableDecl { + fn merge_snapshots( + &mut self, + other: &Self, + predicate_name: &str, + heap_merge_report: &mut HeapMergeReport, + global_state: &mut GlobalHeapState, + ) -> SpannedEncodingResult<()> { + if self != other { + *self = heap_merge_report.remap_snapshot_variable( + predicate_name, + self, + other, + global_state, + ); + } + Ok(()) + } + + fn create_snapshot_variable( + predicate_name: &str, + program_context: &ProgramContext, + global_state: &mut HeapVariables, + ) -> SpannedEncodingResult { + let Some(ty) = program_context.get_snapshot_type(predicate_name) else { + unreachable!(); + }; + Ok(global_state.create_snapshot_variable(predicate_name, ty)) + } + + fn as_expression(&self) -> Option { + Some(self.clone().into()) + } +} + +impl SnapshotType for NoSnapshot { + fn merge_snapshots( + &mut self, + _other: &Self, + _predicate_name: &str, + _heap_merge_report: &mut HeapMergeReport, + _global_state: &mut GlobalHeapState, + ) -> SpannedEncodingResult<()> { + Ok(()) + } + + fn create_snapshot_variable( + _predicate_name: &str, + _program_context: &ProgramContext, + _global_state: &mut HeapVariables, + ) -> SpannedEncodingResult { + Ok(NoSnapshot) + } + + fn as_expression(&self) -> Option { + None + } +} + +impl PredicateInstance { + pub(in super::super) fn get_argument(&self, index: usize) -> &vir_low::Expression { + &self.arguments[index] + } + + pub(super) fn remap_arguments( + &mut self, + remaps: &FxHashMap, + ) -> SpannedEncodingResult<()> { + for argument in std::mem::take(&mut self.arguments) { + let remapped_argument = argument.map_variables(|variable| { + if let Some(remap) = remaps.get(&variable) { + remap.clone() + } else { + variable + } + }); + self.arguments.push(remapped_argument); + } + Ok(()) + } + + pub(super) fn merge( + &mut self, + other: &Self, + self_edge_block: &mut Vec, + other_edge_block: &mut Vec, + deleted_permission_variables: &BTreeSet, + predicate_name: &str, + position: vir_low::Position, + heap_merge_report: &mut HeapMergeReport, + _constraints: &BlockConstraints, + _expression_interner: &mut ExpressionInterner, + program_context: &ProgramContext, + global_state: &mut GlobalHeapState, + ) -> SpannedEncodingResult<()> { + assert_eq!(self.arguments.len(), other.arguments.len()); + if self.is_materialized != other.is_materialized { + if !self.is_materialized { + self_edge_block.push(self.create_materialization_statement( + predicate_name, + position, + program_context, + )?); + } + if !other.is_materialized { + other_edge_block.push(other.create_materialization_statement( + predicate_name, + position, + program_context, + )?); + } + self.is_materialized = true; + } + if self.is_unconditional != other.is_unconditional { + self.is_unconditional = false; + } + assert_eq!(self.permission_amount, other.permission_amount); + self.snapshot_variable.merge_snapshots( + &other.snapshot_variable, + predicate_name, + heap_merge_report, + global_state, + )?; + if self.permission_variable != other.permission_variable + || deleted_permission_variables.contains(&self.permission_variable.name) + || deleted_permission_variables.contains(&other.permission_variable.name) + { + self.permission_variable = heap_merge_report.remap_permission_variable( + predicate_name, + &self.permission_variable, + &other.permission_variable, + global_state, + ); + } + Ok(()) + } + + pub(super) fn bump_self_permission_variable_version( + &mut self, + predicate_name: &str, + heap_merge_report: &mut HeapMergeReport, + global_state: &mut GlobalHeapState, + ) -> SpannedEncodingResult<()> { + self.permission_variable = heap_merge_report.bump_self_permission_variable_version( + predicate_name, + &self.permission_variable, + global_state, + ); + Ok(()) + } + + pub(super) fn bump_other_permission_variable_version( + &mut self, + predicate_name: &str, + heap_merge_report: &mut HeapMergeReport, + global_state: &mut GlobalHeapState, + ) -> SpannedEncodingResult<()> { + self.permission_variable = heap_merge_report.bump_other_permission_variable_version( + predicate_name, + &self.permission_variable, + global_state, + ); + Ok(()) + } + + pub(super) fn create_materialization_statement( + &self, + predicate_name: &str, + position: vir_low::Position, + program_context: &ProgramContext, + ) -> SpannedEncodingResult { + use vir_low::macros::*; + let permission = vir_low::Expression::predicate_access_predicate_no_pos( + predicate_name.to_string(), + self.arguments.clone(), + self.permission_variable.clone().into(), + ); + let snapshot = if let Some(snapshot) = self.snapshot_variable.as_expression() { + let function_name = program_context.get_predicate_snapshot_function(predicate_name); + let snapshot_type = program_context.get_snapshot_type(predicate_name).unwrap(); + let snapshot_equality = vir_low::Expression::equals( + snapshot, + vir_low::Expression::function_call( + function_name, + self.arguments.clone(), + snapshot_type, + ), + ); + expr! { + ([self.permission_variable.clone().into()] != + [vir_low::Expression::no_permission()]) ==> + [snapshot_equality] + } + } else { + true.into() + }; + let statement = + vir_low::Statement::inhale_no_pos(vir_low::Expression::and(permission, snapshot)) + .set_default_position(position); + Ok(statement) + } + + /// When this method is called, `self.permission_variable` is typically + /// already updated, so we need pass the old version as `self_permission_variable`. + pub(super) fn create_matches_check( + &self, + predicate_arguments: &[vir_low::Expression], + self_permission_variable: &vir_low::VariableDecl, + predicate_permission: &vir_low::Expression, + ) -> SpannedEncodingResult { + let guard = self + .arguments + .iter() + .zip(predicate_arguments.iter()) + .map(|(instance_argument, predicate_argument)| { + vir_low::Expression::equals(instance_argument.clone(), predicate_argument.clone()) + }) + .chain(std::iter::once(vir_low::Expression::equals( + self_permission_variable.clone().into(), + (*predicate_permission).clone(), + ))) + .conjoin(); + Ok(guard) + } + + pub(super) fn new_permission_variable( + &mut self, + global_state: &mut GlobalHeapState, + predicate_name: &str, + ) -> SpannedEncodingResult { + let new_permission_variable = global_state.create_permission_variable(predicate_name); + let old_permission_variable = + std::mem::replace(&mut self.permission_variable, new_permission_variable); + Ok(old_permission_variable) + } +} + +impl std::fmt::Display for PredicateInstance { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}; {}: {} {}", + display::cjoin(&self.arguments), + self.permission_amount, + self.snapshot_variable, + self.permission_variable, + )?; + if self.is_materialized { + write!(f, " materialized")?; + } + if self.is_unconditional { + write!(f, " unconditional")?; + } + Ok(()) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/common/predicate_instances.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/common/predicate_instances.rs new file mode 100644 index 00000000000..07ab0996c82 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/common/predicate_instances.rs @@ -0,0 +1,1416 @@ +use super::predicate_instance::{PredicateInstance, SnapshotType}; +use crate::encoder::{ + errors::{ErrorCtxt, SpannedEncodingResult}, + middle::core_proof::transformations::{ + encoder_context::EncoderContext, + symbolic_execution::utils::all_heap_independent, + symbolic_execution_new::{ + block_builder::{BlockBuilder, StatementsBuilder}, + expression_interner::ExpressionInterner, + procedure_executor::{ + constraints::{BlockConstraints, ConstraintsMergeReport}, + heap::{ + common::utils::MatchesResult, + global_heap_state::HeapVariables, + utils::{matches_arguments, matches_arguments_with_remaps}, + GlobalHeapState, HeapMergeReport, + }, + }, + program_context::ProgramContext, + }, + }, +}; +use prusti_common::config; +use std::collections::BTreeSet; +use vir_crate::{ + common::{display, expression::BinaryOperationHelpers}, + low::{self as vir_low, operations::ty::Typed}, +}; + +pub(in super::super) trait PermissionType: Default + Clone { + fn inhale( + &self, + old_permission_variable: vir_low::VariableDecl, + new_permission_variable: &vir_low::VariableDecl, + permission_amount: &vir_low::Expression, + position: vir_low::Position, + block_builder: &mut BlockBuilder, + ) -> SpannedEncodingResult<()>; + fn inhale_fresh( + &self, + new_permission_variable: &vir_low::VariableDecl, + permission_amount: &vir_low::Expression, + position: vir_low::Position, + block_builder: &mut BlockBuilder, + ) -> SpannedEncodingResult<()>; + fn exhale( + &self, + old_permission_variable: vir_low::VariableDecl, + new_permission_variable: &vir_low::VariableDecl, + permission_amount: &vir_low::Expression, + position: vir_low::Position, + block_builder: &mut impl StatementsBuilder, + ) -> SpannedEncodingResult<()>; + /// Does the exhale may need to sum the permissions? + fn exhale_needs_to_add(&self) -> bool; +} + +/// The permission amounts can be either full or none. +#[derive(Default, Clone, Copy)] +pub(in super::super) struct AliasedWholeBool; + +/// The permission amounts can be fractional, but we are always guaranteed +/// to operate on the same amount. Therefore, we do not need to perform +/// arithmetic operations on permissions and can use a boolean permission +/// mask with a third parameter that specifies the permission amount that we +/// are currently tracking. +#[derive(Default, Clone, Copy)] +pub(in super::super) struct AliasedFractionalBool; + +// /// The permission amounts can be fractional and we need to perform +// /// arithmetic operations on them. However, the permission amount is bounded +// /// by `write` and, therefore, when inhaling `write` we can assume that the +// /// current amount is `none`. +// #[derive(Default, Clone, Copy)] +// pub(in super::super) struct AliasedFractionalBoundedPerm; + +// /// The permission amounts are natural numbers. +// #[derive(Default, Clone, Copy)] +// pub(in super::super) struct AliasedWholeNat; + +impl PermissionType for AliasedWholeBool { + fn inhale( + &self, + _old_permission_variable: vir_low::VariableDecl, + new_permission_variable: &vir_low::VariableDecl, + permission_amount: &vir_low::Expression, + position: vir_low::Position, + block_builder: &mut BlockBuilder, + ) -> SpannedEncodingResult<()> { + // FIXME: This is currently commented out because we do know what is the + // first version of the SSA permission variable. + // block_builder.add_statement( + // vir_low::Statement::assert_no_pos(vir_low::Expression::equals( + // old_permission_variable.clone().into(), + // vir_low::Expression::no_permission(), + // )) + // .set_default_position(position), + // )?; + self.inhale_fresh( + new_permission_variable, + permission_amount, + position, + block_builder, + ) + } + + fn inhale_fresh( + &self, + new_permission_variable: &vir_low::VariableDecl, + permission_amount: &vir_low::Expression, + position: vir_low::Position, + block_builder: &mut BlockBuilder, + ) -> SpannedEncodingResult<()> { + block_builder.add_statement( + vir_low::Statement::assume_no_pos(vir_low::Expression::equals( + new_permission_variable.clone().into(), + permission_amount.clone(), + )) + .set_default_position(position), + )?; + Ok(()) + } + + fn exhale( + &self, + old_permission_variable: vir_low::VariableDecl, + new_permission_variable: &vir_low::VariableDecl, + permission_amount: &vir_low::Expression, + position: vir_low::Position, + block_builder: &mut impl StatementsBuilder, + ) -> SpannedEncodingResult<()> { + block_builder.add_statement( + vir_low::Statement::assert_no_pos(vir_low::Expression::equals( + old_permission_variable.into(), + permission_amount.clone(), + )) + .set_default_position(position), + )?; + block_builder.add_statement( + vir_low::Statement::assume_no_pos(vir_low::Expression::equals( + new_permission_variable.clone().into(), + vir_low::Expression::no_permission(), + )) + .set_default_position(position), + )?; + Ok(()) + } + + fn exhale_needs_to_add(&self) -> bool { + false + } +} + +impl PermissionType for AliasedFractionalBool { + fn inhale( + &self, + _old_permission_variable: vir_low::VariableDecl, + new_permission_variable: &vir_low::VariableDecl, + permission_amount: &vir_low::Expression, + position: vir_low::Position, + block_builder: &mut BlockBuilder, + ) -> SpannedEncodingResult<()> { + // FIXME: This is currently commented out because we do know what is the + // first version of the SSA permission variable. + // block_builder.add_statement( + // vir_low::Statement::assert_no_pos(vir_low::Expression::equals( + // old_permission_variable.clone().into(), + // vir_low::Expression::no_permission(), + // )) + // .set_default_position(position), + // )?; + self.inhale_fresh( + new_permission_variable, + permission_amount, + position, + block_builder, + ) + } + + fn inhale_fresh( + &self, + new_permission_variable: &vir_low::VariableDecl, + permission_amount: &vir_low::Expression, + position: vir_low::Position, + block_builder: &mut BlockBuilder, + ) -> SpannedEncodingResult<()> { + block_builder.add_statement( + vir_low::Statement::assume_no_pos(vir_low::Expression::equals( + new_permission_variable.clone().into(), + permission_amount.clone(), + )) + .set_default_position(position), + )?; + Ok(()) + } + + fn exhale( + &self, + old_permission_variable: vir_low::VariableDecl, + new_permission_variable: &vir_low::VariableDecl, + permission_amount: &vir_low::Expression, + position: vir_low::Position, + block_builder: &mut impl StatementsBuilder, + ) -> SpannedEncodingResult<()> { + block_builder.add_statement( + vir_low::Statement::assert_no_pos(vir_low::Expression::equals( + old_permission_variable.into(), + permission_amount.clone(), + )) + .set_default_position(position), + )?; + block_builder.add_statement( + vir_low::Statement::assume_no_pos(vir_low::Expression::equals( + new_permission_variable.clone().into(), + vir_low::Expression::no_permission(), + )) + .set_default_position(position), + )?; + Ok(()) + } + + fn exhale_needs_to_add(&self) -> bool { + false + } +} + +// impl PermissionType for AliasedFractionalBoundedPerm { +// fn inhale( +// &self, +// permission_variable: &vir_low::VariableDecl, +// permission_amount: &vir_low::Expression, +// position: vir_low::Position, +// block_builder: &mut BlockBuilder, +// ) -> SpannedEncodingResult<()> { +// // FIXME: This is most likely wrong. +// block_builder.add_statement( +// vir_low::Statement::assert_no_pos(vir_low::Expression::equals( +// permission_variable.clone().into(), +// vir_low::Expression::no_permission(), +// )) +// .set_default_position(position), +// )?; +// self.inhale_fresh( +// permission_variable, +// permission_amount, +// position, +// block_builder, +// ) +// } + +// fn inhale_fresh( +// &self, +// permission_variable: &vir_low::VariableDecl, +// permission_amount: &vir_low::Expression, +// position: vir_low::Position, +// block_builder: &mut BlockBuilder, +// ) -> SpannedEncodingResult<()> { +// // FIXME: This is most likely wrong. +// block_builder.add_statement( +// vir_low::Statement::assign_no_pos( +// permission_variable.clone(), +// permission_amount.clone(), +// ) +// .set_default_position(position), +// )?; +// Ok(()) +// } + +// fn exhale( +// &self, +// permission_variable: &vir_low::VariableDecl, +// permission_amount: &vir_low::Expression, +// position: vir_low::Position, +// block_builder: &mut BlockBuilder, +// ) -> SpannedEncodingResult<()> { +// // FIXME: This is most likely wrong. +// block_builder.add_statement( +// vir_low::Statement::assert_no_pos(vir_low::Expression::equals( +// permission_variable.clone().into(), +// permission_amount.clone(), +// )) +// .set_default_position(position), +// )?; +// block_builder.add_statement( +// vir_low::Statement::assign_no_pos( +// permission_variable.clone(), +// vir_low::Expression::no_permission(), +// ) +// .set_default_position(position), +// )?; +// Ok(()) +// } +// } + +// impl PermissionType for AliasedWholeNat { +// fn inhale( +// &self, +// permission_variable: &vir_low::VariableDecl, +// permission_amount: &vir_low::Expression, +// position: vir_low::Position, +// block_builder: &mut BlockBuilder, +// ) -> SpannedEncodingResult<()> { +// block_builder.add_statement( +// vir_low::Statement::assign_no_pos( +// permission_variable.clone(), +// vir_low::Expression::perm_binary_op_no_pos( +// vir_low::PermBinaryOpKind::Add, +// permission_variable.clone().into(), +// permission_amount.clone(), +// ), +// ) +// .set_default_position(position), +// )?; +// Ok(()) +// } + +// fn inhale_fresh( +// &self, +// permission_variable: &vir_low::VariableDecl, +// permission_amount: &vir_low::Expression, +// position: vir_low::Position, +// block_builder: &mut BlockBuilder, +// ) -> SpannedEncodingResult<()> { +// block_builder.add_statement( +// vir_low::Statement::assign_no_pos( +// permission_variable.clone(), +// permission_amount.clone(), +// ) +// .set_default_position(position), +// )?; +// Ok(()) +// } + +// fn exhale( +// &self, +// permission_variable: &vir_low::VariableDecl, +// permission_amount: &vir_low::Expression, +// position: vir_low::Position, +// block_builder: &mut BlockBuilder, +// ) -> SpannedEncodingResult<()> { +// block_builder.add_statement( +// vir_low::Statement::assert_no_pos(vir_low::Expression::greater_equals( +// permission_variable.clone().into(), +// permission_amount.clone(), +// )) +// .set_default_position(position), +// )?; +// block_builder.add_statement( +// vir_low::Statement::assign_no_pos( +// permission_variable.clone(), +// vir_low::Expression::perm_binary_op_no_pos( +// vir_low::PermBinaryOpKind::Sub, +// permission_variable.clone().into(), +// permission_amount.clone(), +// ), +// ) +// .set_default_position(position), +// )?; +// Ok(()) +// } +// } + +#[derive(Clone)] +pub(in super::super) struct PredicateInstances { + permission_type: P, + pub(super) aliased_predicate_instances: Vec>, + pub(super) non_aliased_predicate_instances: Vec>, + deleted_permission_variables: BTreeSet, +} + +impl Default for PredicateInstances { + fn default() -> Self { + Self { + permission_type: Default::default(), + aliased_predicate_instances: Vec::new(), + non_aliased_predicate_instances: Vec::new(), + deleted_permission_variables: BTreeSet::new(), + } + } +} + +impl std::fmt::Display + for PredicateInstances +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, " aliased:")?; + for instance in &self.aliased_predicate_instances { + writeln!(f, " {}", instance)?; + } + writeln!(f, " non-aliased:")?; + for instance in &self.non_aliased_predicate_instances { + writeln!(f, " {}", instance)?; + } + Ok(()) + } +} + +pub(in super::super) enum FindSnapshotResult { + NotFound, + FoundGuarded { + snapshot: vir_low::VariableDecl, + precondition: Option, + }, + FoundConditional { + binding: vir_low::VariableDecl, + guarded_candidates: Vec<(vir_low::Expression, vir_low::VariableDecl)>, + }, +} + +impl PredicateInstances { + pub(in super::super) fn find_snapshot( + &self, + predicate_name: &str, + arguments: &[vir_low::Expression], + heap_variables: &mut HeapVariables, + constraints: &mut BlockConstraints, + expression_interner: &mut ExpressionInterner, + program_context: &ProgramContext, + ) -> SpannedEncodingResult { + assert!( + all_heap_independent(arguments), + "arguments: {}", + display::cjoin(arguments) + ); + let is_non_aliased = + super::utils::is_non_aliased(predicate_name, arguments, program_context, constraints)?; + if is_non_aliased { + for predicate_instance in &self.non_aliased_predicate_instances { + match super::utils::matches_non_aliased( + predicate_name, + arguments, + &predicate_instance.arguments, + expression_interner, + program_context, + constraints, + )? { + MatchesResult::MatchesConditionally { assert } => { + return Ok(FindSnapshotResult::FoundGuarded { + snapshot: predicate_instance.snapshot_variable.clone(), + precondition: Some(assert), + }); + } + MatchesResult::MatchesUnonditionally => { + return Ok(FindSnapshotResult::FoundGuarded { + snapshot: predicate_instance.snapshot_variable.clone(), + precondition: None, + }); + } + MatchesResult::DoesNotMatch => {} + } + } + // We did not find a match, this must be an unreachable state. + let fresh_variable = ::create_snapshot_variable( + predicate_name, + program_context, + heap_variables, + )?; + return Ok(FindSnapshotResult::FoundGuarded { + snapshot: fresh_variable, + precondition: Some(false.into()), + }); + } + + for predicate_instance in &self.aliased_predicate_instances { + if matches_arguments( + &predicate_instance.arguments, + arguments, + constraints, + expression_interner, + program_context, + )? { + if predicate_instance.is_unconditional { + // We know for sure that we have a heap chunk and, therefore, this snapshot + // is unique and valid. + return Ok(FindSnapshotResult::FoundGuarded { + snapshot: predicate_instance.snapshot_variable.clone(), + precondition: None, + }); + } + if predicate_instance.is_materialized { + // The chunk is potentially aliased by a QP chunk. We cannot purify it out. + return Ok(FindSnapshotResult::NotFound); + } + } + } + // We do not know which of the heap chunks is the one we need. Therefore, we return + // a conditional. + let mut guarded_candidates = Vec::new(); + let binding: vir_low::VariableDecl = + ::create_snapshot_variable( + predicate_name, + program_context, + heap_variables, + )?; + let mut predicate_instances = self + .aliased_predicate_instances + .iter() + .filter(|predicate_instance| !predicate_instance.is_materialized); + for predicate_instance in predicate_instances { + let guard = predicate_instance.create_matches_check( + arguments, + &predicate_instance.permission_variable, + &predicate_instance.permission_amount, + )?; + guarded_candidates.push((guard, predicate_instance.snapshot_variable.clone())); + } + assert!(!guarded_candidates.is_empty(), "TODO: A proper error messages that did not find any candidate for purifying \ + snapshot. Suggest to either use quantified_predicate! or check whether the expression is evaluated \ + in the right state. predicate_name={}, arguments={}", predicate_name, display::cjoin(arguments)); + Ok(FindSnapshotResult::FoundConditional { + binding, + guarded_candidates, + }) + } +} + +impl PredicateInstances { + pub(in super::super) fn get_aliased_predicate_instances(&self) -> &[PredicateInstance] { + &self.aliased_predicate_instances + } + + fn is_non_aliased_predicate( + &self, + predicate: &vir_low::PredicateAccessPredicate, + program_context: &ProgramContext, + constraints: &mut BlockConstraints, + ) -> SpannedEncodingResult { + super::utils::is_non_aliased( + &predicate.name, + &predicate.arguments, + program_context, + constraints, + ) + } + + pub(in super::super) fn inhale( + &mut self, + program_context: &ProgramContext, + expression_interner: &mut ExpressionInterner, + global_state: &mut GlobalHeapState, + predicate: vir_low::PredicateAccessPredicate, + position: vir_low::Position, + constraints: &mut BlockConstraints, + block_builder: &mut BlockBuilder, + ) -> SpannedEncodingResult<()> { + assert!( + all_heap_independent(&predicate.arguments), + "arguments: {}", + display::cjoin(&predicate.arguments) + ); + let is_non_aliased = + self.is_non_aliased_predicate(&predicate, program_context, constraints)?; + if is_non_aliased { + for predicate_instance in &mut self.non_aliased_predicate_instances { + let result = super::utils::predicate_matches_non_aliased( + &predicate, + &predicate_instance.arguments, + expression_interner, + program_context, + constraints, + )?; + match &result { + MatchesResult::MatchesConditionally { .. } + | MatchesResult::MatchesUnonditionally => { + if let MatchesResult::MatchesConditionally { assert } = result { + block_builder.add_statement( + vir_low::Statement::assert_no_pos(assert) + .set_default_position(position), + )?; + } + // Predicate instance already exists, but should be with no permission. + assert_eq!(predicate_instance.permission_amount, *predicate.permission); + assert!( + !predicate_instance.is_materialized, + "non-aliased predicates should never be materialized" + ); + let old_permission_variable = predicate_instance + .new_permission_variable(global_state, &predicate.name)?; + block_builder.add_statement( + vir_low::Statement::comment(format!( + "inhaling {} to existing non-aliased predicate: snapshot={} old_permission={}", + predicate, predicate_instance.snapshot_variable, old_permission_variable + )) + )?; + self.permission_type.inhale( + old_permission_variable, + &predicate_instance.permission_variable, + &predicate.permission, + position, + block_builder, + )?; + return Ok(()); + } + MatchesResult::DoesNotMatch => {} + } + } + } else { + for predicate_instance in &mut self.aliased_predicate_instances { + if matches_arguments( + &predicate_instance.arguments, + &predicate.arguments, + constraints, + expression_interner, + program_context, + )? { + // Predicate instance already exists, but should be with no permission. + assert_eq!(predicate_instance.permission_amount, *predicate.permission); + if predicate_instance.is_materialized { + // Predicate instance is materialized, so we should keep the + // inhale. + block_builder.add_statement( + vir_low::Statement::comment(format!( + "inhaling {} to existing aliased materialized predicate: snapshot={} old_permission={}", + predicate, predicate_instance.snapshot_variable, predicate_instance.permission_variable + )) + )?; + block_builder.add_statement( + vir_low::Statement::inhale_no_pos( + vir_low::Expression::PredicateAccessPredicate(predicate), + ) + .set_default_position(position), + )?; + } else { + let old_permission_variable = predicate_instance + .new_permission_variable(global_state, &predicate.name)?; + block_builder.add_statement( + vir_low::Statement::comment(format!( + "inhaling {} to existing aliased predicate: snapshot={} old_permission={}", + predicate, predicate_instance.snapshot_variable, old_permission_variable + )) + )?; + self.permission_type.inhale( + old_permission_variable, + &predicate_instance.permission_variable, + &predicate.permission, + position, + block_builder, + )?; + } + return Ok(()); + } + } + } + // Predicate instance does not exist, create a new one. + let snapshot_variable = ::create_snapshot_variable( + &predicate.name, + program_context, + &mut global_state.heap_variables, + )?; + let permission_variable = global_state.create_permission_variable(&predicate.name); + self.permission_type.inhale_fresh( + &permission_variable, + &predicate.permission, + position, + block_builder, + )?; + let predicate_instance = PredicateInstance { + arguments: predicate.arguments, + snapshot_variable, + permission_amount: *predicate.permission, + permission_variable, + is_materialized: false, + is_unconditional: true, + }; + if is_non_aliased { + block_builder.add_statement(vir_low::Statement::comment(format!( + "inhaling fresh non-aliased predicate instance: snapshot={} old_permission={}", + predicate_instance.snapshot_variable, predicate_instance.permission_variable + )))?; + self.non_aliased_predicate_instances + .push(predicate_instance); + } else { + block_builder.add_statement(vir_low::Statement::comment(format!( + "inhaling fresh aliased predicate instance: snapshot={} old_permission={}", + predicate_instance.snapshot_variable, predicate_instance.permission_variable + )))?; + self.aliased_predicate_instances.push(predicate_instance); + } + Ok(()) + } + + pub(in super::super) fn exhale( + &mut self, + program_context: &mut ProgramContext, + expression_interner: &mut ExpressionInterner, + global_state: &mut GlobalHeapState, + predicate: vir_low::PredicateAccessPredicate, + position: vir_low::Position, + constraints: &mut BlockConstraints, + block_builder: &mut BlockBuilder, + ) -> SpannedEncodingResult<()> { + assert!( + all_heap_independent(&predicate.arguments), + "arguments: {}", + display::cjoin(&predicate.arguments) + ); + let is_non_aliased = + self.is_non_aliased_predicate(&predicate, program_context, constraints)?; + let position = { + let current_error_context = program_context.env().get_error_context(position); + let new_error_context = match current_error_context { + ErrorCtxt::ProcedureCall => ErrorCtxt::ProcedureCallPermissionExhale, + ErrorCtxt::DropCall => ErrorCtxt::DropCallPermissionExhale, + ErrorCtxt::ExhaleMethodPrecondition => { + ErrorCtxt::ExhaleMethodPreconditionPermissionExhale + } + ErrorCtxt::ExhaleMethodPostcondition => { + ErrorCtxt::ExhaleMethodPostconditionPermissionExhale + } + ErrorCtxt::AssertMethodPostcondition => { + ErrorCtxt::AssertMethodPostconditionPermissionExhale + } + _ => current_error_context, + }; + program_context + .env() + .change_error_context(position, new_error_context) + }; + if is_non_aliased { + for (i, predicate_instance) in + self.non_aliased_predicate_instances.iter_mut().enumerate() + { + let result = super::utils::predicate_matches_non_aliased( + &predicate, + &predicate_instance.arguments, + expression_interner, + program_context, + constraints, + )?; + match &result { + MatchesResult::MatchesConditionally { .. } + | MatchesResult::MatchesUnonditionally => { + if let MatchesResult::MatchesConditionally { assert } = result { + block_builder.add_statement( + vir_low::Statement::assert_no_pos(assert) + .set_default_position(position), + )?; + } + let mut predicate_instance = self.non_aliased_predicate_instances.remove(i); + assert_eq!(predicate_instance.permission_amount, *predicate.permission); + assert!( + !predicate_instance.is_materialized, + "non-aliased predicates should never be materialized" + ); + let old_permission_variable = predicate_instance + .new_permission_variable(global_state, &predicate.name)?; + let old_permission_variable_name = old_permission_variable.name.clone(); + self.permission_type.exhale( + old_permission_variable, + &predicate_instance.permission_variable, + &predicate.permission, + position, + block_builder, + )?; + self.deleted_permission_variables + .insert(old_permission_variable_name); + return Ok(()); + } + MatchesResult::DoesNotMatch => {} + } + } + if config::panic_on_failed_exhale() { + panic!("failed to exhale: {predicate}\n{self}"); + } else { + block_builder.add_statement(vir_low::Statement::comment(format!( + "failed to exhale (non-aliased): {predicate}" + )))?; + block_builder.add_statement( + vir_low::Statement::assert_no_pos(false.into()).set_default_position(position), + )?; + constraints.assume_false()?; + } + } else { + constraints.saturate_solver()?; + for (i, predicate_instance) in self.aliased_predicate_instances.iter().enumerate() { + if matches_arguments( + &predicate_instance.arguments, + &predicate.arguments, + constraints, + expression_interner, + program_context, + )? { + if (predicate_instance.is_unconditional + || config::ignore_whether_exhale_is_unconditional()) + || predicate_instance.is_materialized + || self.aliased_predicate_instances.len() == 1 + { + let mut predicate_instance = self.aliased_predicate_instances.remove(i); + assert_eq!(predicate_instance.permission_amount, *predicate.permission); + if predicate_instance.is_materialized { + // The predicate instance is materialized, so we need to + // produce a materialized exhale. + block_builder.add_statement( + vir_low::Statement::exhale_no_pos( + vir_low::Expression::PredicateAccessPredicate(predicate), + ) + .set_default_position(position), + )?; + return Ok(()); + } else { + let old_permission_variable = predicate_instance + .new_permission_variable(global_state, &predicate.name)?; + let old_permission_variable_name = old_permission_variable.name.clone(); + self.permission_type.exhale( + old_permission_variable, + &predicate_instance.permission_variable, + &predicate.permission, + position, + block_builder, + )?; + self.deleted_permission_variables + .insert(old_permission_variable_name); + } + return Ok(()); + } else { + // The predicate instance is conditional, so we need to + // materialize the exhale. + break; + } + } + } + if config::panic_on_failed_exhale() || config::panic_on_failed_exhale_materialization() + { + panic!("failed to exhale: {predicate}\n{self}"); + } else if config::materialize_on_failed_exhale() { + block_builder.add_statement(vir_low::Statement::comment(format!( + "failed to exhale (materializing): {predicate}" + )))?; + self.materialize_aliased_instances( + &predicate.name, + position, + constraints, + block_builder, + program_context, + )?; + block_builder.add_statement(vir_low::Statement::exhale( + vir_low::Expression::PredicateAccessPredicate(predicate), + position, + ))?; + } else { + block_builder.add_statement(vir_low::Statement::comment(format!( + "failed to exhale (conditional exhale): {predicate}" + )))?; + self.emit_conditional_exhale( + predicate, + position, + global_state, + constraints, + block_builder, + program_context, + )?; + } + } + Ok(()) + } + + pub(in super::super) fn materialize( + &mut self, + program_context: &mut ProgramContext, + expression_interner: &mut ExpressionInterner, + global_state: &mut GlobalHeapState, + predicate: vir_low::PredicateAccessPredicate, + position: vir_low::Position, + constraints: &mut BlockConstraints, + block_builder: &mut BlockBuilder, + check_that_exists: bool, + ) -> SpannedEncodingResult<()> { + let mut found = false; + for predicate_instance in &mut self.aliased_predicate_instances { + if matches_arguments( + &predicate_instance.arguments, + &predicate.arguments, + constraints, + expression_interner, + program_context, + )? { + found = true; + assert!( + !predicate_instance.is_materialized, + "TODO: a proper error message {predicate}" + ); + predicate_instance.is_materialized = true; + block_builder.add_statement(vir_low::Statement::comment(format!( + "materializing found predicate with snapshot={} permission={}", + predicate_instance.snapshot_variable, predicate_instance.permission_variable + )))?; + let statement = predicate_instance.create_materialization_statement( + &predicate.name, + position, + program_context, + )?; + block_builder.add_statement(statement)?; + } + } + if !found { + assert!( + !check_that_exists, + "TODO: a proper error message {predicate} {check_that_exists}" + ); + // Assert that the predicate exists and assume that its snapshot is + // the same as a freshly generated variable. + let snapshot_variable = ::create_snapshot_variable( + &predicate.name, + program_context, + &mut global_state.heap_variables, + )?; + let permission_variable = global_state.create_permission_variable(&predicate.name); + block_builder.add_statement(vir_low::Statement::comment(format!( + "materializing not-found predicate with snapshot={} permission={}", + snapshot_variable, permission_variable + )))?; + let predicate_instance = PredicateInstance { + arguments: predicate.arguments.clone(), + snapshot_variable, + permission_amount: (*predicate.permission).clone(), + permission_variable, + is_unconditional: false, + is_materialized: true, + }; + let predicate_name = predicate.name.clone(); + block_builder.add_statement( + vir_low::Statement::assert_no_pos(vir_low::Expression::PredicateAccessPredicate( + predicate, + )) + .set_default_position(position), + )?; + if let Some(snapshot) = predicate_instance.snapshot_variable.as_expression() { + let function_name = + program_context.get_predicate_snapshot_function(&predicate_name); + let snapshot_type = program_context.get_snapshot_type(&predicate_name).unwrap(); + let snapshot_equality = vir_low::Expression::equals( + snapshot, + vir_low::Expression::function_call( + function_name, + predicate_instance.arguments.clone(), + snapshot_type, + ), + ); + block_builder.add_statement( + vir_low::Statement::assume_no_pos(snapshot_equality) + .set_default_position(position), + )?; + } + self.aliased_predicate_instances.push(predicate_instance); + } + Ok(()) + } + + pub(in super::super) fn prepare_for_unhandled_exhale( + &mut self, + program_context: &mut ProgramContext, + _expression_interner: &mut ExpressionInterner, + _global_state: &mut GlobalHeapState, + predicate_name: &str, + position: vir_low::Position, + constraints: &mut BlockConstraints, + block_builder: &mut BlockBuilder, + ) -> SpannedEncodingResult<()> { + if config::materialize_on_failed_exhale() { + self.materialize_aliased_instances( + predicate_name, + position, + constraints, + block_builder, + program_context, + )?; + } + Ok(()) + } + + pub(in super::super) fn merge_deleted_permission_variables( + &mut self, + other: &Self, + ) -> SpannedEncodingResult<()> { + self.deleted_permission_variables + .extend(other.deleted_permission_variables.iter().cloned()); + Ok(()) + } + + pub(in super::super) fn merge( + &mut self, + other: &Self, + self_edge_block: &mut Vec, + other_edge_block: &mut Vec, + predicate_name: &str, + position: vir_low::Position, + heap_merge_report: &mut HeapMergeReport, + constraints: &mut BlockConstraints, + constraints_merge_report: &ConstraintsMergeReport, + expression_interner: &mut ExpressionInterner, + program_context: &ProgramContext, + global_state: &mut GlobalHeapState, + ) -> SpannedEncodingResult<()> { + self.merge_non_aliased( + other, + self_edge_block, + other_edge_block, + predicate_name, + position, + heap_merge_report, + constraints, + constraints_merge_report, + expression_interner, + program_context, + global_state, + )?; + self.merge_aliased( + other, + self_edge_block, + other_edge_block, + predicate_name, + position, + heap_merge_report, + constraints, + constraints_merge_report, + expression_interner, + program_context, + global_state, + )?; + for predicate_instance in &self.aliased_predicate_instances { + assert!(!self + .deleted_permission_variables + .contains(&predicate_instance.permission_variable.name)); + } + for predicate_instance in &self.non_aliased_predicate_instances { + assert!( + !self + .deleted_permission_variables + .contains(&predicate_instance.permission_variable.name), + "{}", + predicate_instance.permission_variable + ); + } + Ok(()) + } + + fn merge_non_aliased( + &mut self, + other: &Self, + self_edge_block: &mut Vec, + other_edge_block: &mut Vec, + predicate_name: &str, + position: vir_low::Position, + heap_merge_report: &mut HeapMergeReport, + constraints: &mut BlockConstraints, + constraints_merge_report: &ConstraintsMergeReport, + expression_interner: &mut ExpressionInterner, + program_context: &ProgramContext, + global_state: &mut GlobalHeapState, + ) -> SpannedEncodingResult<()> { + let mut other_used = vec![false; other.non_aliased_predicate_instances.len()]; + for self_instance in &mut self.non_aliased_predicate_instances { + self_instance.remap_arguments(constraints_merge_report.get_self_remaps())?; + let mut found = false; + for (i, other_instance) in other.non_aliased_predicate_instances.iter().enumerate() { + let (are_equal, disequalities) = super::utils::matches_non_aliased_diff( + predicate_name, + &self_instance.arguments, + &other_instance.arguments, + expression_interner, + program_context, + constraints, + )?; + if are_equal { + for (self_arg, other_arg) in disequalities { + let variable = + global_state.create_merge_variable(self_arg.get_type().clone()); + self_edge_block.push( + vir_low::Statement::assume_no_pos(vir_low::Expression::equals( + variable.clone().into(), + self_arg, + )) + .set_default_position(position), + ); + other_edge_block.push( + vir_low::Statement::assume_no_pos(vir_low::Expression::equals( + variable.clone().into(), + other_arg, + )) + .set_default_position(position), + ); + } + assert!(!other_used[i]); + other_used[i] = true; + self_instance.merge( + other_instance, + self_edge_block, + other_edge_block, + &self.deleted_permission_variables, + predicate_name, + position, + heap_merge_report, + constraints, + expression_interner, + program_context, + global_state, + )?; + found = true; + break; + } + } + if !found { + // The permission amount is tracked by the verifier, so we do + // not need to do anything. + if self + .deleted_permission_variables + .contains(&self_instance.permission_variable.name) + { + self_instance.bump_self_permission_variable_version( + predicate_name, + heap_merge_report, + global_state, + )?; + } + } + } + for (i, used) in other_used.iter().enumerate() { + if !*used { + let mut instance = other.non_aliased_predicate_instances[i].clone(); + if self + .deleted_permission_variables + .contains(&instance.permission_variable.name) + { + instance.bump_other_permission_variable_version( + predicate_name, + heap_merge_report, + global_state, + )?; + } + self.non_aliased_predicate_instances.push(instance); + } + } + Ok(()) + } + + fn merge_aliased( + &mut self, + other: &Self, + self_edge_block: &mut Vec, + other_edge_block: &mut Vec, + predicate_name: &str, + position: vir_low::Position, + heap_merge_report: &mut HeapMergeReport, + constraints: &mut BlockConstraints, + constraints_merge_report: &ConstraintsMergeReport, + expression_interner: &mut ExpressionInterner, + program_context: &ProgramContext, + global_state: &mut GlobalHeapState, + ) -> SpannedEncodingResult<()> { + let mut other_used = vec![false; other.aliased_predicate_instances.len()]; + let mut needs_state_consolidation = false; + for self_instance in &mut self.aliased_predicate_instances { + self_instance.remap_arguments(constraints_merge_report.get_self_remaps())?; + let mut found = false; + for (i, other_instance) in other.aliased_predicate_instances.iter().enumerate() { + if matches_arguments( + &self_instance.arguments, + &other_instance.arguments, + constraints, + expression_interner, + program_context, + )? || matches_arguments_with_remaps( + &self_instance.arguments, + &other_instance.arguments, + constraints_merge_report, + constraints, + expression_interner, + program_context, + )? { + if other_used[i] { + // We have two elements in self that are equal to `i`th + // in other. This means that they are equal to each + // other and we can merge them. + needs_state_consolidation = true; + } + other_used[i] = true; + self_instance.merge( + other_instance, + self_edge_block, + other_edge_block, + &self.deleted_permission_variables, + predicate_name, + position, + heap_merge_report, + constraints, + expression_interner, + program_context, + global_state, + )?; + found = true; + break; + } + } + if !found { + // The permission amount is tracked by the verifier, so we only + // need to mark that the instance is conditional. + self_instance.is_unconditional = false; + if self + .deleted_permission_variables + .contains(&self_instance.permission_variable.name) + { + self_instance.bump_self_permission_variable_version( + predicate_name, + heap_merge_report, + global_state, + )?; + } + } + } + for (i, used) in other_used.iter().enumerate() { + if !*used { + let mut instance = other.aliased_predicate_instances[i].clone(); + instance.is_unconditional = false; + if self + .deleted_permission_variables + .contains(&instance.permission_variable.name) + { + instance.bump_other_permission_variable_version( + predicate_name, + heap_merge_report, + global_state, + )?; + } + self.aliased_predicate_instances.push(instance); + // This could mean that we have two elements in other that are + // equal to each other and, therefore, we may need to merge + // them. + needs_state_consolidation = true; + } + } + if needs_state_consolidation { + self.consolidate_state( + self_edge_block, + predicate_name, + position, + heap_merge_report, + constraints, + expression_interner, + program_context, + global_state, + )?; + } + Ok(()) + } + + fn consolidate_state( + &mut self, + self_edge_block: &mut Vec, + predicate_name: &str, + position: vir_low::Position, + heap_merge_report: &mut HeapMergeReport, + constraints: &mut BlockConstraints, + expression_interner: &mut ExpressionInterner, + program_context: &ProgramContext, + global_state: &mut GlobalHeapState, + ) -> SpannedEncodingResult<()> { + let mut matching_instances = Vec::new(); + for (i, first_instance) in self.aliased_predicate_instances.iter().enumerate() { + for (j, second_instance) in self + .aliased_predicate_instances + .iter() + .enumerate() + .skip(i + 1) + { + if matches_arguments( + &first_instance.arguments, + &second_instance.arguments, + constraints, + expression_interner, + program_context, + )? { + matching_instances.push((i, j)); + } + } + } + let mut other_edge_block = Vec::new(); + let mut first_invalid_index = self.aliased_predicate_instances.len(); + for (i, j) in matching_instances.into_iter().rev() { + assert!(i < j); + assert!(j < first_invalid_index); + let second_instance = self.aliased_predicate_instances.remove(j); + first_invalid_index = j; + let first_instance = self.aliased_predicate_instances.get_mut(i).unwrap(); + first_instance.merge( + &second_instance, + self_edge_block, + &mut other_edge_block, + &self.deleted_permission_variables, + predicate_name, + position, + heap_merge_report, + constraints, + expression_interner, + program_context, + global_state, + )?; + } + self_edge_block.extend(other_edge_block); + Ok(()) + } + + fn materialize_aliased_instances( + &mut self, + predicate_name: &str, + position: vir_low::Position, + constraints: &mut BlockConstraints, + block_builder: &mut BlockBuilder, + program_context: &ProgramContext, + ) -> SpannedEncodingResult<()> { + let mut statements = vec![vir_low::Statement::comment( + "Materializing predicates".to_string(), + )]; + for instance in &mut self.aliased_predicate_instances { + if !super::utils::is_non_aliased( + predicate_name, + &instance.arguments, + program_context, + constraints, + )? && !instance.is_materialized + { + instance.is_materialized = true; + let statement = instance.create_materialization_statement( + predicate_name, + position, + program_context, + )?; + statements.push(statement); + } + } + block_builder.add_statements_at_materialization_point(statements)?; + // self.predicate_instances + // .retain(|instance| !instance.is_materialized); + Ok(()) + } + + fn emit_conditional_exhale( + &mut self, + predicate: vir_low::PredicateAccessPredicate, + position: vir_low::Position, + global_state: &mut GlobalHeapState, + _constraints: &mut BlockConstraints, + block_builder: &mut BlockBuilder, + _program_context: &ProgramContext, + ) -> SpannedEncodingResult<()> { + block_builder.add_statement(vir_low::Statement::comment( + "Conditional exhale".to_string(), + ))?; + // Assert that we need to exhale exactly one heap chunk. This allows + // making the encoding more performant (achieve the Silicon-like grouping + // of summands). + assert!(!self.permission_type.exhale_needs_to_add()); + // We use this for cases when the permission amount did not change. + let mut new_old_permission_variables = Vec::new(); + for predicate_instance in self + .aliased_predicate_instances + .iter_mut() + .filter(|predicate_instance| !predicate_instance.is_materialized) + { + let old_permission_variable = + predicate_instance.new_permission_variable(global_state, &predicate.name)?; + self.deleted_permission_variables + .insert(old_permission_variable.name.clone()); + new_old_permission_variables.push(( + old_permission_variable, + predicate_instance.permission_variable.clone(), + )); + } + // We consider only instances that are not materialized and conditional: + // 1. Materialized instances should be exhaled only by QPs. + // 2. For now, we just assume that unconditional instances would be + // always successfully matched. – this seems to be wrong. + let mut predicate_instances = self + .aliased_predicate_instances + .iter_mut() + .filter(|predicate_instance| !predicate_instance.is_materialized) + .enumerate(); + let mut statement = + vir_low::Statement::assert_no_pos(false.into()).set_default_position(position); + for (index, predicate_instance) in predicate_instances { + let old_permission_variable = new_old_permission_variables[index].0.clone(); + let guard = predicate_instance.create_matches_check( + &predicate.arguments, + &old_permission_variable, + &predicate.permission, + )?; + let mut then_statements = Vec::new(); + // Perform the exhale updating the permission variable. + self.permission_type.exhale( + old_permission_variable, + &predicate_instance.permission_variable, + &predicate.permission, + position, + &mut then_statements, + )?; + // All other permission variables preserve their values. + for (index2, (old_permission_variable, new_permission_variable)) in + new_old_permission_variables.iter().enumerate() + { + if index2 != index { + then_statements.push( + vir_low::Statement::assume_no_pos(vir_low::Expression::equals( + new_permission_variable.clone().into(), + old_permission_variable.clone().into(), + )) + .set_default_position(position), + ); + } + } + statement = + vir_low::Statement::conditional_no_pos(guard, then_statements, vec![statement]) + .set_default_position(position); + } + block_builder.add_statement(statement)?; + Ok(()) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/common/snapshot.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/common/snapshot.rs new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/common/snapshot.rs @@ -0,0 +1 @@ + diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/common/utils.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/common/utils.rs new file mode 100644 index 00000000000..02115843cf8 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/common/utils.rs @@ -0,0 +1,269 @@ +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::transformations::{ + encoder_context::EncoderContext, + symbolic_execution_new::{ + expression_interner::ExpressionInterner, + procedure_executor::constraints::BlockConstraints, program_context::ProgramContext, + }, + }, +}; +use vir_crate::{ + common::expression::{BinaryOperationHelpers, ExpressionIterator}, + low::{self as vir_low}, +}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub(super) enum MatchesResult { + /// The two instances match and we managed to discharge all proof obligations. + MatchesUnonditionally, + /// The two instances match, but we could not discharge all proof + /// obligations. + MatchesConditionally { + /// The assertion to be checked by the verified because we could not prove it syntactically. + assert: vir_low::Expression, + }, + /// The two instances do not match. + DoesNotMatch, +} + +pub(super) fn is_non_aliased( + predicate_name: &str, + arguments: &[vir_low::Expression], + program_context: &ProgramContext, + _constraints: &mut BlockConstraints, +) -> SpannedEncodingResult { + if program_context.is_predicate_kind_non_aliased(predicate_name) { + return Ok(true); + } + // fn construct_predicate_address_non_aliased_call( + // predicate_address: &vir_low::Expression, + // ) -> vir_low::Expression { + // use vir_low::macros::*; + // let address_is_non_aliased = ty!(Bool); + // expr! { + // (ComputeAddress::address_is_non_aliased([predicate_address.clone()])) + // } + // } + match program_context.get_predicate_kind(predicate_name) { + vir_low::PredicateKind::MemoryBlock => { + let predicate_address = &arguments[0]; + if program_context.is_address_non_aliased(predicate_address) { + return Ok(true); + } + // let predicate_address_non_aliased_call = + // construct_predicate_address_non_aliased_call(predicate_address); + // if constraints.is_non_aliased_address(&predicate_address_non_aliased_call)? { + // eprintln!( + // "is_non_aliased_address: {}", + // predicate_address_non_aliased_call + // ); + // return Ok(true); + // } + // eprintln!("aliased_address: {}", predicate_address_non_aliased_call); + // if solver.is_true(&predicate_address_non_aliased_call)? { + // return Ok(true); + // } else { + // solver.saturate()?; + // if solver.is_true(&predicate_address_non_aliased_call)? { + // return Ok(true); + // } + // } + } + vir_low::PredicateKind::Owned => { + let predicate_place = &arguments[0]; + if program_context.is_place_non_aliased(predicate_place) { + return Ok(true); + } + } + _ => {} + } + Ok(false) +} + +pub(super) fn matches_non_aliased_diff( + predicate_name: &str, + predicate_arguments: &[vir_low::Expression], + predicate_instance_arguments: &[vir_low::Expression], + expression_interner: &mut ExpressionInterner, + program_context: &ProgramContext, + constraints: &mut BlockConstraints, +) -> SpannedEncodingResult<(bool, Vec<(vir_low::Expression, vir_low::Expression)>)> { + fn construct_result( + predicate_arguments: &[vir_low::Expression], + predicate_instance_arguments: &[vir_low::Expression], + are_equal: bool, + expression_interner: &mut ExpressionInterner, + program_context: &ProgramContext, + constraints: &mut BlockConstraints, + ) -> SpannedEncodingResult<(bool, Vec<(vir_low::Expression, vir_low::Expression)>)> { + if are_equal { + assert_eq!( + predicate_arguments.len(), + predicate_instance_arguments.len() + ); + let mut disequalities = Vec::new(); + for (left, right) in predicate_arguments + .iter() + .zip(predicate_instance_arguments.iter()) + { + if !constraints.is_equal(expression_interner, program_context, left, right)? { + disequalities.push((left.clone(), right.clone())); + } + } + Ok((true, disequalities)) + } else { + Ok((false, Vec::new())) + } + } + match program_context.get_predicate_kind(predicate_name) { + vir_low::PredicateKind::Owned => construct_result( + predicate_arguments, + predicate_instance_arguments, + predicate_arguments[0] == predicate_instance_arguments[0], + expression_interner, + program_context, + constraints, + ), + vir_low::PredicateKind::MemoryBlock => { + // We need to include the size because after splitting the memory + // block we sometimes get a smaller memory block at the same + // location. + let are_equal = (predicate_arguments[0] == predicate_instance_arguments[0]) + && (predicate_arguments[1] == predicate_instance_arguments[1]); + construct_result( + predicate_arguments, + predicate_instance_arguments, + are_equal, + expression_interner, + program_context, + constraints, + ) + } + vir_low::PredicateKind::LifetimeToken => todo!(), + vir_low::PredicateKind::CloseFracRef => todo!(), + // vir_low::PredicateKind::WithoutSnapshotFrac => todo!(), + vir_low::PredicateKind::WithoutSnapshotWhole => todo!(), + vir_low::PredicateKind::WithoutSnapshotWholeNonAliased => { + assert_eq!(predicate_name, "MemoryBlockStackDrop"); + construct_result( + predicate_arguments, + predicate_instance_arguments, + predicate_arguments[0] == predicate_instance_arguments[0], + expression_interner, + program_context, + constraints, + ) + } + vir_low::PredicateKind::DeadLifetimeToken => todo!(), + vir_low::PredicateKind::EndBorrowViewShift => { + let vir_low::Expression::Local(local1) = &predicate_arguments[0] else { + unreachable!() + }; + let vir_low::Expression::Local(local2) = &predicate_instance_arguments[0] else { + unreachable!() + }; + // FIXME: Do not rely on strings. + let lifetime1 = &local1.variable.name.split('$').nth(0).unwrap(); + let lifetime2 = &local2.variable.name.split('$').nth(0).unwrap(); + construct_result( + predicate_arguments, + predicate_instance_arguments, + lifetime1 == lifetime2, + expression_interner, + program_context, + constraints, + ) + } + } +} + +pub(super) fn matches_non_aliased( + predicate_name: &str, + predicate_arguments: &[vir_low::Expression], + predicate_instance_arguments: &[vir_low::Expression], + expression_interner: &mut ExpressionInterner, + program_context: &ProgramContext, + constraints: &mut BlockConstraints, +) -> SpannedEncodingResult { + let (are_equal, disequalities) = matches_non_aliased_diff( + predicate_name, + predicate_arguments, + predicate_instance_arguments, + expression_interner, + program_context, + constraints, + )?; + if are_equal { + if disequalities.is_empty() { + Ok(MatchesResult::MatchesUnonditionally) + } else { + let assert = disequalities + .into_iter() + .map(|(left, right)| vir_low::Expression::equals(left, right)) + .conjoin(); + Ok(MatchesResult::MatchesConditionally { assert }) + } + } else { + Ok(MatchesResult::DoesNotMatch) + } +} + +pub(super) fn predicate_matches_non_aliased( + predicate: &vir_low::PredicateAccessPredicate, + predicate_instance_arguments: &[vir_low::Expression], + expression_interner: &mut ExpressionInterner, + program_context: &ProgramContext, + constraints: &mut BlockConstraints, +) -> SpannedEncodingResult { + matches_non_aliased( + &predicate.name, + &predicate.arguments, + predicate_instance_arguments, + expression_interner, + program_context, + constraints, + ) +} + +// pub(super) fn assert_arguments_equal_non_aliased( +// arguments1: &[vir_low::Expression], +// arguments2: &[vir_low::Expression], +// expression_interner: &mut ExpressionInterner, +// program_context: &ProgramContext, +// constraints: &mut BlockConstraints, +// ) -> SpannedEncodingResult { +// assert_eq!(arguments1.len(), arguments2.len()); +// let mut disequalities = Vec::new(); +// for (left, right) in arguments1.iter().zip(arguments2.iter()) { +// if !constraints.is_equal(expression_interner, program_context, left, right)? { +// disequalities.push(vir_low::Expression::equals(left.clone(), right.clone())); +// } +// } +// if disequalities.is_empty() { +// Ok(MatchesResult::MatchesUnonditionally) +// } else { +// Ok(MatchesResult::MatchesConditionally { +// assert: disequalities.into_iter().conjoin(), +// }) +// } +// } + +// fn assert_arguments_equal_non_aliased( +// arguments1: &[vir_low::Expression], +// arguments2: &[vir_low::Expression], +// block_builder: &mut BlockBuilder, +// position: vir_low::Position, +// ) -> SpannedEncodingResult<()> { +// assert_eq!(arguments1.len(), arguments2.len()); +// for (left, right) in arguments1.iter().zip(arguments2.iter()) { +// block_builder.add_statement( +// vir_low::Statement::assert_no_pos(vir_low::Expression::equals( +// left.clone(), +// right.clone(), +// )) +// .set_default_position(position), +// )?; +// } +// Ok(()) +// } diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/dead_lifetimes.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/dead_lifetimes.rs new file mode 100644 index 00000000000..606148415f8 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/dead_lifetimes.rs @@ -0,0 +1,253 @@ +use super::{GlobalHeapState, HeapMergeReport}; +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::transformations::symbolic_execution_new::{ + block_builder::BlockBuilder, procedure_executor::constraints::BlockConstraints, + }, +}; +use log::error; +use std::collections::{BTreeMap, BTreeSet}; +use vir_crate::low::{self as vir_low}; + +#[derive(Default, Clone)] +pub(super) struct DeadLifetimeTokens { + /// A set of lifetimes for which we for sure have dead lifetime tokens. + dead_lifetime_tokens: BTreeSet, + /// A map from lifetimes to which we potentially have a dead lifetime token + /// to variables used to track the actual permission amount. + potentially_dead_lifetime_token_permissions: BTreeMap, +} + +impl std::fmt::Display for DeadLifetimeTokens { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "dead lifetime tokens:")?; + for lifetime in &self.dead_lifetime_tokens { + writeln!(f, " {lifetime}")?; + } + writeln!(f, "potentially dead lifetime tokens:")?; + for (lifetime, permission) in &self.potentially_dead_lifetime_token_permissions { + writeln!(f, " {lifetime}: {permission}")?; + } + Ok(()) + } +} + +impl DeadLifetimeTokens { + pub(super) fn inhale( + &mut self, + _global_state: &mut GlobalHeapState, + mut predicate: vir_low::PredicateAccessPredicate, + _position: vir_low::Position, + constraints: &mut BlockConstraints, + _block_builder: &mut BlockBuilder, + ) -> SpannedEncodingResult<()> { + assert_eq!(predicate.arguments.len(), 1); + let Some(vir_low::Expression::Local(local)) = predicate.arguments.pop() else { + unimplemented!("TODO: A proper error message."); + }; + let lifetime = local.variable.name; + // Spread over equality class. + let equality_class = constraints.get_equal_lifetimes(&lifetime)?; + self.dead_lifetime_tokens.extend(equality_class); + // Insert the lifetime itself. + self.dead_lifetime_tokens.insert(lifetime); + Ok(()) + } + + pub(super) fn exhale_attempt( + &mut self, + lifetime: &str, + position: vir_low::Position, + constraints: &mut BlockConstraints, + block_builder: &mut BlockBuilder, + ) -> SpannedEncodingResult { + // Since DeadLifetimeToken is duplicable, we only need to assert that we + // have the permission. + if self.dead_lifetime_tokens.contains(lifetime) { + // We certainly have the permission, nothing to do. + return Ok(true); + } + let cannonical_lifetime = constraints.resolve_cannonical_lifetime_name(lifetime)?; + if let Some(cannonical_lifetime) = &cannonical_lifetime { + if self.dead_lifetime_tokens.contains(*cannonical_lifetime) { + // We certainly have the permission, nothing to do. + return Ok(true); + } + } + if let Some(permission) = self + .potentially_dead_lifetime_token_permissions + .get(lifetime) + { + // We potentially have the permission, but the verifier needs to check. + block_builder.add_statement( + vir_low::Statement::assign_no_pos( + permission.clone(), + vir_low::Expression::full_permission(), + ) + .set_default_position(position), + )?; + return Ok(true); + } + if let Some(cannonical_lifetime) = &cannonical_lifetime { + if let Some(permission) = self + .potentially_dead_lifetime_token_permissions + .get(*cannonical_lifetime) + { + // We potentially have the permission, but the verifier needs to check. + block_builder.add_statement( + vir_low::Statement::assign_no_pos( + permission.clone(), + vir_low::Expression::full_permission(), + ) + .set_default_position(position), + )?; + return Ok(true); + } + } + Ok(false) + } + + pub(super) fn exhale( + &mut self, + mut predicate: vir_low::PredicateAccessPredicate, + position: vir_low::Position, + constraints: &mut BlockConstraints, + block_builder: &mut BlockBuilder, + ) -> SpannedEncodingResult<()> { + assert_eq!(predicate.arguments.len(), 1); + let Some(vir_low::Expression::Local(local)) = predicate.arguments.pop() else { + unimplemented!("TODO: A proper error message."); + }; + let lifetime = local.variable.name; + if !self.exhale_attempt(&lifetime, position, constraints, block_builder)? { + self.spread_permission_over_eclasses(constraints)?; + if !self.exhale_attempt(&lifetime, position, constraints, block_builder)? { + for eq in constraints.get_equal_lifetimes(&lifetime)? { + error!(" {eq} == {lifetime}"); + } + for dead_lifetime in &self.dead_lifetime_tokens { + for dependent_lifetime in + constraints.get_dependent_lifetimes_for(dead_lifetime)? + { + error!(" dead lifetime {dead_lifetime}: {dependent_lifetime}"); + } + } + for (potentially_dead_lifetime, _) in + &self.potentially_dead_lifetime_token_permissions + { + for dependent_lifetime in constraints.get_dependent_lifetimes_for(&lifetime)? { + error!( + " potentially dead lifetime {potentially_dead_lifetime}: {dependent_lifetime}" + ); + } + } + unimplemented!("TODO: this should be unreachable: {lifetime}\n{self}"); + } + } + Ok(()) + } + + /// This function spreads the permission over known e-classes of lifetimes + /// for which we have permission + pub(super) fn spread_permission_over_eclasses( + &mut self, + constraints: &mut BlockConstraints, + ) -> SpannedEncodingResult<()> { + for lifetime in std::mem::take(&mut self.dead_lifetime_tokens) { + let equality_class = constraints.get_equal_lifetimes(&lifetime)?; + self.dead_lifetime_tokens.extend(equality_class); + self.dead_lifetime_tokens + .extend(constraints.get_dependent_lifetimes_for(&lifetime)?); + self.dead_lifetime_tokens.insert(lifetime); + } + for (lifetime, permission) in + std::mem::take(&mut self.potentially_dead_lifetime_token_permissions) + { + for equal_lifetime in constraints.get_equal_lifetimes(&lifetime)? { + self.potentially_dead_lifetime_token_permissions + .entry(equal_lifetime) + .or_insert_with(|| permission.clone()); + } + for dependent_lifetime in constraints.get_dependent_lifetimes_for(&lifetime)? { + self.potentially_dead_lifetime_token_permissions + .entry(dependent_lifetime) + .or_insert_with(|| permission.clone()); + } + self.potentially_dead_lifetime_token_permissions + .insert(lifetime, permission); + } + Ok(()) + } + + pub(super) fn merge( + &mut self, + other: &Self, + heap_merge_report: &mut HeapMergeReport, + global_state: &mut GlobalHeapState, + ) -> SpannedEncodingResult<()> { + let predicate_name = "DeadLifetimeToken"; + // First, intersect the guaranteed sets and obtain the new potential + // sets. + let new_self_potentially_dead_lifetimes: Vec<_> = self + .dead_lifetime_tokens + .drain_filter(|lifetime| other.dead_lifetime_tokens.contains(lifetime)) + .collect(); + let new_other_potentially_dead_lifetimes = other + .dead_lifetime_tokens + .iter() + .filter(|lifetime| !self.dead_lifetime_tokens.contains(*lifetime)); + // Generate fresh permission variables for all potentially dead + // lifetimes. + for (lifetime, old_self_permission) in &mut self.potentially_dead_lifetime_token_permissions + { + if let Some(old_other_permission) = other + .potentially_dead_lifetime_token_permissions + .get(lifetime) + { + let new_permission_variable = heap_merge_report.remap_permission_variable( + predicate_name, + old_self_permission, + old_other_permission, + global_state, + ); + *old_self_permission = new_permission_variable; + } + } + for (lifetime, old_other_permission) in &other.potentially_dead_lifetime_token_permissions { + if !self + .potentially_dead_lifetime_token_permissions + .contains_key(lifetime) + { + self.potentially_dead_lifetime_token_permissions + .insert(lifetime.clone(), old_other_permission.clone()); + } + } + for lifetime in new_self_potentially_dead_lifetimes { + if let Some(permission_variable) = self + .potentially_dead_lifetime_token_permissions + .get(&lifetime) + { + heap_merge_report.set_write_in_all_predecessors_except_last(permission_variable); + } else { + let permission_variable = global_state.create_permission_variable(predicate_name); + heap_merge_report.set_write_in_all_predecessors_except_last(&permission_variable); + self.potentially_dead_lifetime_token_permissions + .insert(lifetime, permission_variable); + } + } + for lifetime in new_other_potentially_dead_lifetimes { + if let Some(permission_variable) = self + .potentially_dead_lifetime_token_permissions + .get(lifetime) + { + heap_merge_report.set_write_in_last_predecessor(permission_variable.clone()); + } else { + let permission_variable = global_state.create_permission_variable(predicate_name); + heap_merge_report.set_write_in_last_predecessor(permission_variable.clone()); + self.potentially_dead_lifetime_token_permissions + .insert(lifetime.clone(), permission_variable); + } + } + Ok(()) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/global_heap_state.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/global_heap_state.rs new file mode 100644 index 00000000000..ae8cb8975af --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/global_heap_state.rs @@ -0,0 +1,134 @@ +use super::{BlockHeap, HeapAtLabel}; +use crate::encoder::errors::SpannedEncodingResult; +use std::collections::BTreeMap; +use vir_crate::low::{self as vir_low}; + +#[derive(Default)] +pub(in super::super::super) struct GlobalHeapState { + pub(super) snapshots_at_label: BTreeMap, + pub(super) heap_variables: HeapVariables, +} + +#[derive(Default)] +pub(in super::super::super) struct HeapVariables { + variables: Vec, + /// NOTE: Permission variables are **NOT** SSA. + permission_variables: Vec, + permission_map_variables: Vec, +} + +impl HeapVariables { + pub(super) fn create_snapshot_variable( + &mut self, + predicate_name: &str, + ty: vir_low::Type, + ) -> vir_low::VariableDecl { + let snapshot_variable_name = + format!("snapshot${}${}", predicate_name, self.variables.len()); + let variable = vir_low::VariableDecl::new(snapshot_variable_name, ty); + self.variables.push(variable.clone()); + variable + } + + pub(super) fn create_merge_variable(&mut self, ty: vir_low::Type) -> vir_low::VariableDecl { + let variable_name = format!("merge_variable${}", self.variables.len()); + let variable = vir_low::VariableDecl::new(variable_name, ty); + self.variables.push(variable.clone()); + variable + } + + /// Note: Permission variables are **NOT** SSA. + pub(super) fn create_permission_variable( + &mut self, + predicate_name: &str, + ) -> vir_low::VariableDecl { + let permission_variable_name = format!( + "permission${}${}", + predicate_name, + self.permission_variables.len() + ); + let variable = vir_low::VariableDecl::new(permission_variable_name, vir_low::Type::Perm); + self.permission_variables.push(variable.clone()); + variable + } + + // pub(in super::super::super) fn initialize_permission_variables( + // &self, + // position: vir_low::Position, + // ) -> impl Iterator + '_ { + // self.permission_variables.iter().map(move |variable| { + // vir_low::Statement::assign_no_pos( + // variable.clone(), + // vir_low::Expression::no_permission(), + // ) + // .set_default_position(position) + // }) + // } + + pub(in super::super::super) fn clone_variables(&self) -> Vec { + let mut variables = self.variables.clone(); + variables.extend(self.permission_variables.clone()); + variables.extend(self.permission_map_variables.clone()); + variables + } +} + +impl GlobalHeapState { + pub(super) fn insert( + &mut self, + label: String, + snapshots: &BlockHeap, + ) -> SpannedEncodingResult<()> { + let predicate_snapshots = HeapAtLabel { + owned: snapshots.owned.clone(), + memory_block: snapshots.memory_block.clone(), + }; + assert!(self + .snapshots_at_label + .insert(label, predicate_snapshots) + .is_none()); + Ok(()) + } + + pub(super) fn create_snapshot_variable( + &mut self, + predicate_name: &str, + ty: vir_low::Type, + ) -> vir_low::VariableDecl { + self.heap_variables + .create_snapshot_variable(predicate_name, ty) + } + + pub(super) fn create_merge_variable(&mut self, ty: vir_low::Type) -> vir_low::VariableDecl { + self.heap_variables.create_merge_variable(ty) + } + + /// Note: Permission variables are **NOT** SSA. + pub(super) fn create_permission_variable( + &mut self, + predicate_name: &str, + ) -> vir_low::VariableDecl { + self.heap_variables + .create_permission_variable(predicate_name) + } + + // pub(super) fn create_permission_map(&mut self, ty: vir_low::Type) -> vir_low::VariableDecl { + // let permission_variable_name = + // format!("permission_map${}", self.permission_map_variables.len()); + // let variable = vir_low::VariableDecl::new(permission_variable_name, ty); + // self.permission_map_variables.push(variable.clone()); + // variable + // } + + // pub(in super::super::super) fn initialize_permission_variables( + // &self, + // position: vir_low::Position, + // ) -> impl Iterator + '_ { + // self.heap_variables + // .initialize_permission_variables(position) + // } + + pub(in super::super::super) fn clone_variables(&self) -> Vec { + self.heap_variables.clone_variables() + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/lifetimes.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/lifetimes.rs new file mode 100644 index 00000000000..4276950c3e8 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/lifetimes.rs @@ -0,0 +1,476 @@ +use super::super::constraints::BlockConstraints; +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::transformations::symbolic_execution_new::{ + block_builder::BlockBuilder, procedure_executor::constraints::ConstraintsMergeReport, + }, +}; +use log::{debug, error}; +use prusti_common::config; +use std::collections::BTreeMap; +use vir_crate::{ + common::expression::BinaryOperationHelpers, + low::{self as vir_low}, +}; + +#[derive(Default, Clone)] +pub(in super::super::super::super) struct LifetimeTokens { + tokens: BTreeMap, +} + +#[derive(Clone, Debug)] +struct LifetimeVariable { + name: String, + version: u32, +} + +impl From for LifetimeVariable { + fn from(name_with_version: String) -> Self { + // FIXME: This is a hack. We should use proper versioned variables. The + // version is the number after the last `$`. + let mut split = name_with_version.rsplitn(2, '$'); + let part = split.next().unwrap(); + let version = part.parse::().unwrap(); + let part = split.next().unwrap(); + let name = part.to_string(); + Self { name, version } + } +} + +impl std::fmt::Display for LifetimeVariable { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{name}${version}", + name = self.name, + version = self.version + ) + } +} + +#[derive(Clone, Debug)] +struct LifetimeToken { + latest_variable_version: u32, + permission_amount: vir_low::Expression, +} + +impl std::fmt::Display for LifetimeToken { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{{version={version} amount={permission_amount}}}", + version = self.latest_variable_version, + permission_amount = self.permission_amount + ) + } +} + +impl std::fmt::Display for LifetimeTokens { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for (lifetime, token) in &self.tokens { + writeln!(f, "{}: {}", lifetime, token)?; + } + Ok(()) + } +} + +impl LifetimeTokens { + pub(super) fn inhale( + &mut self, + mut predicate: vir_low::PredicateAccessPredicate, + position: vir_low::Position, + _constraints: &BlockConstraints, + block_builder: &mut BlockBuilder, + ) -> SpannedEncodingResult<()> { + assert_eq!(predicate.arguments.len(), 1); + let Some(vir_low::Expression::Local(local)) = predicate.arguments.pop() else { + unimplemented!("TODO: A proper error message."); + }; + let lifetime: LifetimeVariable = local.variable.name.into(); + let permission_amount_is_non_negative = vir_low::Statement::assert( + vir_low::Expression::greater_equals( + (*predicate.permission).clone(), + vir_low::Expression::no_permission(), + ), + position, + ); + if let Some(token) = self.tokens.get_mut(&lifetime.name) { + assert_eq!( + token.latest_variable_version, lifetime.version, + "lifetime: {}", + lifetime.name + ); + token.permission_amount = vir_low::Expression::perm_binary_op( + vir_low::PermBinaryOpKind::Add, + token.permission_amount.clone(), + *predicate.permission, + position, + ) + .simplify_perm(); + } else { + self.tokens.insert( + lifetime.name.clone(), + LifetimeToken { + latest_variable_version: lifetime.version, + permission_amount: *predicate.permission, + }, + ); + } + block_builder.add_statement(permission_amount_is_non_negative)?; + Ok(()) + } + + pub(super) fn exhale( + &mut self, + mut predicate: vir_low::PredicateAccessPredicate, + position: vir_low::Position, + constraints: &BlockConstraints, + block_builder: &mut BlockBuilder, + ) -> SpannedEncodingResult<()> { + debug!(" Exhaling predicate: {predicate}"); + assert_eq!(predicate.arguments.len(), 1); + let Some(vir_low::Expression::Local(local)) = predicate.arguments.pop() else { + unimplemented!("TODO: A proper error message."); + }; + let lifetime: LifetimeVariable = local.variable.name.into(); + if let Some(mut token) = self.tokens.remove(&lifetime.name) { + token.latest_variable_version = constraints + .get_latest_lifetime_version(&lifetime.name, token.latest_variable_version)?; + assert_eq!( + token.latest_variable_version, lifetime.version, + "lifetime: {}", + lifetime.name + ); + token.permission_amount = vir_low::Expression::perm_binary_op( + vir_low::PermBinaryOpKind::Sub, + token.permission_amount, + *predicate.permission, + position, + ); + token.permission_amount = token.permission_amount.simplify_perm(); + if !token.permission_amount.is_no_permission() { + let permission_amount_is_non_negative = vir_low::Statement::assert( + vir_low::Expression::greater_equals( + token.permission_amount.clone(), + vir_low::Expression::no_permission(), + ), + position, + ); + block_builder.add_statement(permission_amount_is_non_negative)?; + self.tokens.insert(lifetime.name.clone(), token); + } + } else if config::panic_on_failed_exhale() { + panic!("failed to exhale: {predicate}\n{self}"); + } else { + // This can happen if code panics when a reference is not + // closed. Either this is a bug or this trace is unreachable. + // Emit assert false. + block_builder.add_statement(vir_low::Statement::comment(format!( + "Did not find the predicate instance: {predicate} {lifetime} at {position}" + )))?; + block_builder.add_statement( + vir_low::Statement::assert_no_pos(false.into()).set_default_position(position), + )?; + } + // let latest_version = self + // .latest_variable_versions + // .get(&lifetime_with_version.name) + // .unwrap(); + // assert_eq!( + // latest_version, &lifetime_with_version.version, + // "lifetime: {}", + // lifetime_with_version.name + // ); + // let mut lifetime = local.variable.name.clone(); + // if let Some(cannonical_lifetime) = + // constraints.resolve_cannonical_lifetime_name(&lifetime)? + // { + // lifetime = cannonical_lifetime.to_string(); + // } + // if let Some(mut amount) = self.token_permission_amounts.remove(&lifetime) { + // if IS_DEAD { + // // Since DeadLifetimeToken is duplicable, the exhale only + // // asserts that we have the permission. + // } else { + // amount = vir_low::Expression::perm_binary_op( + // vir_low::PermBinaryOpKind::Sub, + // amount, + // *predicate.permission, + // position, + // ); + // amount = amount.simplify_perm(); + // } + // if !amount.is_no_permission() { + // self.token_permission_amounts + // .insert(lifetime, amount.clone()); + // let permission_amount_is_non_negative = vir_low::Statement::assert( + // vir_low::Expression::greater_equals( + // amount, + // vir_low::Expression::no_permission(), + // ), + // position, + // ); + // block_builder.add_statement(permission_amount_is_non_negative)?; + // } else { + // self.latest_variable_versions + // .remove(&lifetime_with_version.name); + // } + // } else if config::panic_on_failed_exhale() { + // panic!("failed to exhale: {predicate}\n{self}"); + // } else { + // // It could be that the permission tracking is not precise enough. Emit a conditional exhale. + // let mut sum = vir_low::Expression::no_permission(); + // for (chunk_lifetime, amount) in &self.token_permission_amounts { + // sum = vir_low::Expression::perm_binary_op_no_pos( + // vir_low::PermBinaryOpKind::Add, + // sum, + // vir_low::Expression::conditional_no_pos( + // vir_low::Expression::equals( + // local.variable.clone().into(), + // vir_low::VariableDecl::new( + // chunk_lifetime.clone(), + // local.variable.ty.clone(), + // ) + // .into(), + // ), + // amount.clone(), + // vir_low::Expression::no_permission(), + // ), + // ); + // } + // block_builder.add_statement(vir_low::Statement::comment(format!( + // "Failed to syntactically exhale: {predicate} {lifetime} at {position}" + // )))?; + // block_builder.add_statement( + // vir_low::Statement::assert_no_pos(vir_low::Expression::greater_equals( + // sum, + // *predicate.permission, + // )) + // .set_default_position(position), + // )?; + // } + Ok(()) + } + + pub(super) fn merge( + &mut self, + other: &Self, + self_edge_block: &mut Vec, + other_edge_block: &mut Vec, + position: vir_low::Position, + constraints_merge_report: &ConstraintsMergeReport, + ) -> SpannedEncodingResult<()> { + for (lifetime, token) in &mut self.tokens { + token.latest_variable_version = constraints_merge_report + .resolve_self_latest_lifetime_variable_version( + lifetime, + token.latest_variable_version, + ); + if let Some(other_token) = other.tokens.get(lifetime) { + let latest_other_version = constraints_merge_report + .resolve_other_latest_lifetime_variable_version( + lifetime, + other_token.latest_variable_version, + ); + assert_eq!( + token.latest_variable_version, latest_other_version, + "lifetime: {}", + lifetime + ); + assert_eq!( + token.permission_amount, other_token.permission_amount, + "lifetime: {}\nself: {}\nother: {}", + lifetime, token.permission_amount, other_token.permission_amount + ); + } else { + // Did not find the lifetime token in the other block, mark that + // edge as unreachable. This can happen if the reference was not + // closed on some (potentially unreachable) path. + other_edge_block.push( + vir_low::Statement::comment(format!( + "marking as unreachable because not found in other: {lifetime}" + )) + .set_default_position(position), + ); + other_edge_block.push( + vir_low::Statement::assert_no_pos(false.into()).set_default_position(position), + ); + } + } + for (lifetime, token) in &other.tokens { + if !self.tokens.contains_key(lifetime) { + // Did not find the lifetime token in the self block, mark that + // edge as unreachable. This can happen if the reference was not + // closed on some (potentially unreachable) path. + self_edge_block.push( + vir_low::Statement::comment(format!( + "marking as unreachable because not found in other: {lifetime}" + )) + .set_default_position(position), + ); + self_edge_block.push( + vir_low::Statement::assert_no_pos(false.into()).set_default_position(position), + ); + let latest_other_version = constraints_merge_report + .resolve_other_latest_lifetime_variable_version( + lifetime, + token.latest_variable_version, + ); + let token = LifetimeToken { + latest_variable_version: latest_other_version, + permission_amount: token.permission_amount.clone(), + }; + self.tokens.insert(lifetime.clone(), token); + } + } + // for (mut lifetime, amount) in std::mem::take(&mut self.token_permission_amounts) { + // let other_amount = if let Some(other_amount) = + // other.token_permission_amounts.get(&lifetime) + // { + // other_amount + // } else { + // if let Some(cannonical_self) = + // constraints_merge_report.resolve_new_self_cannonical_lifetime_name(&lifetime) + // { + // lifetime = cannonical_self.clone(); + // } + // if let Some(other_lifetime) = + // constraints_merge_report.resolve_old_other_cannonical_lifetime_name(&lifetime) + // { + // if let Some(other_amount) = other.token_permission_amounts.get(other_lifetime) { + // other_amount + // } else { + // // Did not find the lifetime in the other block, mark that + // // edge as unreachable. This can happen if the reference was + // // not closed on some (potentially unreachable) trace. + // other_edge_block.push( + // vir_low::Statement::comment(format!( + // "marking as unreachable because not found in other: {lifetime}" + // )) + // .set_default_position(position), + // ); + // other_edge_block.push( + // vir_low::Statement::assert_no_pos(false.into()) + // .set_default_position(position), + // ); + // self.token_permission_amounts + // .insert(lifetime.clone(), amount.clone()); + // continue; + // } + // } else { + // // Did not find the lifetime in the other block, mark that + // // edge as unreachable. This can happen if the reference was + // // not closed on some (potentially unreachable) trace. + // other_edge_block.push( + // vir_low::Statement::comment(format!( + // "marking as unreachable because not found in other: {lifetime}" + // )) + // .set_default_position(position), + // ); + // other_edge_block.push( + // vir_low::Statement::assert_no_pos(false.into()) + // .set_default_position(position), + // ); + // self.token_permission_amounts + // .insert(lifetime.clone(), amount.clone()); + // continue; + // } + // }; + // assert_eq!( + // &amount, other_amount, + // "{lifetime}: {amount} != {other_amount}" + // ); + // self.token_permission_amounts.insert(lifetime, amount); + // } + // for (lifetime, amount) in &other.token_permission_amounts { + // let self_amount = if let Some(self_amount) = self.token_permission_amounts.get(lifetime) + // { + // self_amount + // } else { + // if let Some(self_lifetime) = + // constraints_merge_report.resolve_new_other_cannonical_lifetime_name(lifetime) + // { + // if let Some(self_amount) = self.token_permission_amounts.get(self_lifetime) { + // self_amount + // } else { + // // Did not find the lifetime in the other block, mark that + // // edge as unreachable. This can happen if the reference was + // // not closed on some (potentially unreachable) trace. + // self_edge_block.push( + // vir_low::Statement::comment(format!( + // "marking as unreachable because not found in self: {lifetime}" + // )) + // .set_default_position(position), + // ); + // self_edge_block.push( + // vir_low::Statement::assert_no_pos(false.into()) + // .set_default_position(position), + // ); + // self.token_permission_amounts + // .insert(lifetime.clone(), amount.clone()); + // continue; + // } + // } else { + // // Did not find the lifetime in the other block, mark that + // // edge as unreachable. This can happen if the reference was + // // not closed on some (potentially unreachable) trace. + // self_edge_block.push( + // vir_low::Statement::comment(format!( + // "marking as unreachable because not found in self: {lifetime}" + // )) + // .set_default_position(position), + // ); + // self_edge_block.push( + // vir_low::Statement::assert_no_pos(false.into()) + // .set_default_position(position), + // ); + // self.token_permission_amounts + // .insert(lifetime.clone(), amount.clone()); + // continue; + // } + // }; + // assert_eq!(self_amount, amount); + // } + + // TODO: Problem: Lifetime tokens can be aliased and in that case I need to sum their permissions, which I do not do. As a result, + // when some lifetime token is exhaled, it may also exhale aliased lifetime tokens. + + Ok(()) + } + + pub(super) fn leak_check(&self) -> SpannedEncodingResult<()> { + for (lifetime, token) in &self.tokens { + error!( + "ERROR: Lifetime token {} was not exhaled. Its amount is {}.", + lifetime, token + ); + } + assert!(self.tokens.is_empty()); + Ok(()) + } + + // pub(super) fn get_dead_lifetime_equality_classes( + // &self, + // constraints: &BlockConstraints, + // ) -> SpannedEncodingResult>> { + // let mut map = BTreeMap::new(); + // for lifetime in self.token_permission_amounts.keys() { + // map.insert(lifetime.clone(), constraints.get_equal_lifetimes(lifetime)?); + // } + // Ok(map) + // } + + // pub(super) fn remap_lifetimes( + // &mut self, + // mut remaps: BTreeMap, + // ) -> SpannedEncodingResult<()> { + // for (lifetime, amount) in std::mem::take(&mut self.token_permission_amounts) { + // let new_lifetime = remaps.remove(&lifetime).unwrap(); + // assert!(self + // .token_permission_amounts + // .insert(new_lifetime, amount) + // .is_none()); + // } + // Ok(()) + // } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/memory_block.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/memory_block.rs new file mode 100644 index 00000000000..f63f900ec73 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/memory_block.rs @@ -0,0 +1,176 @@ +use super::{ + common::{AliasedFractionalBool, FindSnapshotResult, PredicateInstances}, + global_heap_state::HeapVariables, + merge_report::HeapMergeReport, + GlobalHeapState, +}; +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::transformations::{ + encoder_context::EncoderContext, + symbolic_execution_new::{ + block_builder::BlockBuilder, + expression_interner::ExpressionInterner, + procedure_executor::constraints::{BlockConstraints, ConstraintsMergeReport}, + program_context::ProgramContext, + }, + }, +}; +use vir_crate::{ + common::builtin_constants::MEMORY_BLOCK_PREDICATE_NAME, + low::{self as vir_low}, +}; + +#[derive(Default, Clone)] +pub(super) struct MemoryBlock { + predicates: PredicateInstances, +} + +impl std::fmt::Display for MemoryBlock { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.predicates) + } +} + +impl MemoryBlock { + pub(super) fn inhale( + &mut self, + program_context: &ProgramContext, + expression_interner: &mut ExpressionInterner, + global_state: &mut GlobalHeapState, + predicate: vir_low::PredicateAccessPredicate, + position: vir_low::Position, + constraints: &mut BlockConstraints, + block_builder: &mut BlockBuilder, + ) -> SpannedEncodingResult<()> { + self.predicates.inhale( + program_context, + expression_interner, + global_state, + predicate, + position, + constraints, + block_builder, + ) + } + + pub(super) fn exhale( + &mut self, + program_context: &mut ProgramContext, + expression_interner: &mut ExpressionInterner, + global_state: &mut GlobalHeapState, + predicate: vir_low::PredicateAccessPredicate, + position: vir_low::Position, + constraints: &mut BlockConstraints, + block_builder: &mut BlockBuilder, + ) -> SpannedEncodingResult<()> { + self.predicates.exhale( + program_context, + expression_interner, + global_state, + predicate, + position, + constraints, + block_builder, + ) + } + + pub(super) fn materialize( + &mut self, + program_context: &mut ProgramContext, + expression_interner: &mut ExpressionInterner, + global_state: &mut GlobalHeapState, + predicate: vir_low::PredicateAccessPredicate, + position: vir_low::Position, + constraints: &mut BlockConstraints, + block_builder: &mut BlockBuilder, + check_that_exists: bool, + ) -> SpannedEncodingResult<()> { + self.predicates.materialize( + program_context, + expression_interner, + global_state, + predicate, + position, + constraints, + block_builder, + check_that_exists, + ) + } + + pub(super) fn prepare_for_unhandled_exhale( + &mut self, + program_context: &mut ProgramContext, + expression_interner: &mut ExpressionInterner, + global_state: &mut GlobalHeapState, + predicate_name: &str, + position: vir_low::Position, + constraints: &mut BlockConstraints, + block_builder: &mut BlockBuilder, + ) -> SpannedEncodingResult<()> { + self.predicates.prepare_for_unhandled_exhale( + program_context, + expression_interner, + global_state, + predicate_name, + position, + constraints, + block_builder, + ) + } + + pub(super) fn find_snapshot( + &self, + arguments: &[vir_low::Expression], + global_state: &mut HeapVariables, + constraints: &mut BlockConstraints, + expression_interner: &mut ExpressionInterner, + program_context: &ProgramContext, + ) -> SpannedEncodingResult { + self.predicates.find_snapshot( + MEMORY_BLOCK_PREDICATE_NAME, + arguments, + global_state, + constraints, + expression_interner, + program_context, + ) + } + + pub(super) fn merge_deleted_permission_variables( + &mut self, + other: &Self, + ) -> SpannedEncodingResult<()> { + self.predicates + .merge_deleted_permission_variables(&other.predicates)?; + Ok(()) + } + + pub(super) fn merge( + &mut self, + other: &Self, + self_edge_block: &mut Vec, + other_edge_block: &mut Vec, + position: vir_low::Position, + heap_merge_report: &mut HeapMergeReport, + constraints: &mut BlockConstraints, + constraints_merge_report: &ConstraintsMergeReport, + expression_interner: &mut ExpressionInterner, + program_context: &ProgramContext, + global_state: &mut GlobalHeapState, + ) -> SpannedEncodingResult<()> { + self.predicates.merge( + &other.predicates, + self_edge_block, + other_edge_block, + MEMORY_BLOCK_PREDICATE_NAME, + position, + heap_merge_report, + constraints, + constraints_merge_report, + expression_interner, + program_context, + global_state, + ) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/merge_report.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/merge_report.rs new file mode 100644 index 00000000000..30382d90e23 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/merge_report.rs @@ -0,0 +1,473 @@ +use super::GlobalHeapState; +use std::collections::BTreeSet; +use vir_crate::{ + common::expression::BinaryOperationHelpers, + low::{self as vir_low}, +}; + +// FIXME: Rename to `HeapMergeState`. +#[derive(Debug, Clone)] +pub(in super::super) struct HeapMergeReport { + snapshot_report: Report, + permission_report: Report, + // new_dead_lifetime_token_permission_map: Option, + // old_permission_maps: Vec, + /// For each predecessor, a list of permission variables that got `write` + /// assigned to them. It is used for tracking `DeadLifetimeToken` + /// permissions. + write_written_in_predecessors: Vec>, +} + +#[derive(Debug, Clone, Default)] +struct Report { + predecessors: Vec>, +} + +#[derive(Debug, Clone)] +struct HeapMergePredecessorReport { + remaps: Vec, +} + +impl Default for HeapMergePredecessorReport { + fn default() -> Self { + Self { remaps: Vec::new() } + } +} + +#[derive(Debug, Clone)] +pub(in super::super) struct SnapshotRemap { + old_snapshot: String, + new_snapshot: String, + ty: vir_low::Type, +} + +#[derive(Debug, Clone)] +pub(in super::super) struct PermissionRemap { + old_snapshot: String, + new_snapshot: String, + /// Whether `new_snapshot` is assigned `none` permission or the value of + /// `old_snapshot`. We keep the `old_snapshot` even when remapping to `none` + /// so that we know to what to remap elements from other incoming branches. + map_to_none: bool, +} + +trait Remap { + fn create(old: &vir_low::VariableDecl, new: String) -> Self; + fn create_map_to_none(old: &vir_low::VariableDecl, new: String) -> Self; + fn old(&self) -> &str; + fn new(&self) -> &str; + fn create_variable( + global_stae: &mut GlobalHeapState, + predicate_name: &str, + ty: &vir_low::Type, + ) -> vir_low::VariableDecl; +} + +impl Remap for SnapshotRemap { + fn create(old: &vir_low::VariableDecl, new: String) -> Self { + Self { + old_snapshot: old.name.clone(), + new_snapshot: new, + ty: old.ty.clone(), + } + } + + fn create_map_to_none(_old: &vir_low::VariableDecl, _new: String) -> Self { + unreachable!("This should not be called for snapshots"); + } + + fn old(&self) -> &str { + &self.old_snapshot + } + + fn new(&self) -> &str { + &self.new_snapshot + } + + fn create_variable( + global_state: &mut GlobalHeapState, + predicate_name: &str, + ty: &vir_low::Type, + ) -> vir_low::VariableDecl { + global_state.create_snapshot_variable(predicate_name, ty.clone()) + } +} + +impl Remap for PermissionRemap { + fn create(old: &vir_low::VariableDecl, new: String) -> Self { + Self { + old_snapshot: old.name.clone(), + new_snapshot: new, + map_to_none: false, + } + } + + fn create_map_to_none(old: &vir_low::VariableDecl, new: String) -> Self { + Self { + old_snapshot: old.name.clone(), + new_snapshot: new, + map_to_none: true, + } + } + + fn old(&self) -> &str { + &self.old_snapshot + } + + fn new(&self) -> &str { + &self.new_snapshot + } + + fn create_variable( + global_state: &mut GlobalHeapState, + predicate_name: &str, + _: &vir_low::Type, + ) -> vir_low::VariableDecl { + global_state.create_permission_variable(predicate_name) + } +} + +impl Report { + fn new() -> Self { + Self { + predecessors: vec![HeapMergePredecessorReport::default()], + } + } + + fn create_predecessor(&mut self) { + self.predecessors + .push(HeapMergePredecessorReport::default()); + } + + fn remap( + &mut self, + predicate_name: &str, + self_variable: &vir_low::VariableDecl, + other_variable: &vir_low::VariableDecl, + global_state: &mut GlobalHeapState, + ) -> vir_low::VariableDecl { + assert!(self.predecessors.len() >= 2); + let new_variable = if let Some(new_variable_name) = self.has_remap(self_variable) { + self.remap_last_predecessor(other_variable, new_variable_name.to_string()) + } else { + self.create_remap(predicate_name, self_variable, other_variable, global_state) + }; + new_variable + } + + fn has_remap(&self, first_predecessor_variable: &vir_low::VariableDecl) -> Option<&str> { + for remap in &self.predecessors[0].remaps { + if remap.new() == first_predecessor_variable.name { + return Some(remap.new()); + } + } + None + } + + fn remap_last_predecessor( + &mut self, + last_predecessor_variable: &vir_low::VariableDecl, + new_variable: String, + ) -> vir_low::VariableDecl { + let last_predecessor = self.predecessors.last_mut().unwrap(); + last_predecessor.remaps.push(Remap::create( + last_predecessor_variable, + new_variable.clone(), + )); + vir_low::VariableDecl { + name: new_variable, + ty: last_predecessor_variable.ty.clone(), + } + } + + fn create_remap( + &mut self, + predicate_name: &str, + first_predecessor_variable: &vir_low::VariableDecl, + last_predecessor_variable: &vir_low::VariableDecl, + global_state: &mut GlobalHeapState, + ) -> vir_low::VariableDecl { + let variable = + R::create_variable(global_state, predicate_name, &first_predecessor_variable.ty); + for i in 0..self.predecessors.len() - 1 { + self.predecessors[i].remaps.push(Remap::create( + first_predecessor_variable, + variable.name.clone(), + )); + } + let last_index = self.predecessors.len() - 1; + self.predecessors[last_index].remaps.push(Remap::create( + last_predecessor_variable, + variable.name.clone(), + )); + variable + } + + fn bump_self_version( + &mut self, + predicate_name: &str, + first_predecessor_variable: &vir_low::VariableDecl, + global_state: &mut GlobalHeapState, + ) -> vir_low::VariableDecl { + let variable = + R::create_variable(global_state, predicate_name, &first_predecessor_variable.ty); + for i in 0..self.predecessors.len() - 1 { + self.predecessors[i].remaps.push(Remap::create( + first_predecessor_variable, + variable.name.clone(), + )); + } + let last_index = self.predecessors.len() - 1; + self.predecessors[last_index] + .remaps + .push(Remap::create_map_to_none( + first_predecessor_variable, + variable.name.clone(), + )); + variable + } + + fn bump_other_version( + &mut self, + predicate_name: &str, + last_predecessor_variable: &vir_low::VariableDecl, + global_state: &mut GlobalHeapState, + ) -> vir_low::VariableDecl { + let variable = + R::create_variable(global_state, predicate_name, &last_predecessor_variable.ty); + for i in 0..self.predecessors.len() - 1 { + self.predecessors[i].remaps.push(Remap::create_map_to_none( + last_predecessor_variable, + variable.name.clone(), + )); + } + let last_index = self.predecessors.len() - 1; + self.predecessors[last_index].remaps.push(Remap::create( + last_predecessor_variable, + variable.name.clone(), + )); + variable + } + + fn validate(&self) { + let mut new_variables = BTreeSet::new(); + for remap in &self.predecessors[0].remaps { + assert!(new_variables.insert(remap.new()), "{}", remap.new()); + } + for (_i, predecessor) in self.predecessors.iter().enumerate() { + // This does not hold because some of the incoming paths may + // completely miss the resources. + // assert_eq!(predecessor.remaps.len(), new_variables.len(), "{}", i); + for remap in &predecessor.remaps { + assert!(new_variables.contains(&remap.new())); + } + } + } + + pub(in super::super) fn into_iter_remap(self) -> impl Iterator> { + self.predecessors + .into_iter() + .map(|predecessor| predecessor.remaps) + } +} + +impl HeapMergeReport { + pub(in super::super) fn new() -> Self { + Self { + snapshot_report: Report::new(), + permission_report: Report::new(), + // new_dead_lifetime_token_permission_map: None, + // old_permission_maps: Vec::new(), + write_written_in_predecessors: vec![Vec::new()], + } + } + + pub(in super::super) fn create_predecessor(&mut self) { + self.snapshot_report.create_predecessor(); + self.permission_report.create_predecessor(); + self.write_written_in_predecessors.push(Vec::new()); + } + + // pub(in super::super) fn is_new_dead_lifetime_token_permission_map_set(&self) -> bool { + // self.new_dead_lifetime_token_permission_map.is_some() + // } + + // pub(in super::super) fn set_new_dead_lifetime_token_permission_map( + // &mut self, + // new_map: vir_low::Expression, + // ) { + // assert!(self.new_dead_lifetime_token_permission_map.is_none()); + // self.new_dead_lifetime_token_permission_map = Some(new_map); + // } + + // pub(in super::super) fn add_old_permission_map(&mut self, old_map: vir_low::Expression) { + // self.old_permission_maps.push(old_map); + // } + + pub(in super::super) fn remap_snapshot_variable( + &mut self, + predicate_name: &str, + self_snapshot_variable: &vir_low::VariableDecl, + other_snapshot_variable: &vir_low::VariableDecl, + global_state: &mut GlobalHeapState, + ) -> vir_low::VariableDecl { + self.snapshot_report.remap( + predicate_name, + self_snapshot_variable, + other_snapshot_variable, + global_state, + ) + } + + pub(in super::super) fn remap_permission_variable( + &mut self, + predicate_name: &str, + self_permission_variable: &vir_low::VariableDecl, + other_permission_variable: &vir_low::VariableDecl, + global_state: &mut GlobalHeapState, + ) -> vir_low::VariableDecl { + self.permission_report.remap( + predicate_name, + self_permission_variable, + other_permission_variable, + global_state, + ) + } + + pub(in super::super) fn bump_self_permission_variable_version( + &mut self, + predicate_name: &str, + self_permission_variable: &vir_low::VariableDecl, + global_state: &mut GlobalHeapState, + ) -> vir_low::VariableDecl { + self.permission_report.bump_self_version( + predicate_name, + self_permission_variable, + global_state, + ) + } + + pub(in super::super) fn bump_other_permission_variable_version( + &mut self, + predicate_name: &str, + other_permission_variable: &vir_low::VariableDecl, + global_state: &mut GlobalHeapState, + ) -> vir_low::VariableDecl { + self.permission_report.bump_other_version( + predicate_name, + other_permission_variable, + global_state, + ) + } + + pub(in super::super) fn set_write_in_all_predecessors_except_last( + &mut self, + variable: &vir_low::VariableDecl, + ) { + let len = self.write_written_in_predecessors.len() - 1; + for predecessor in &mut self.write_written_in_predecessors[..len] { + predecessor.push(variable.clone()); + } + } + + pub(in super::super) fn set_write_in_last_predecessor( + &mut self, + variable: vir_low::VariableDecl, + ) { + let len = self.write_written_in_predecessors.len(); + self.write_written_in_predecessors[len - 1].push(variable); + } + + pub(in super::super) fn validate(&self) { + self.snapshot_report.validate(); + self.permission_report.validate(); + } + + pub(in super::super) fn into_remap_statements( + self, + position: vir_low::Position, + ) -> Vec> { + assert_eq!( + self.snapshot_report.predecessors.len(), + self.permission_report.predecessors.len() + ); + assert_eq!( + self.snapshot_report.predecessors.len(), + self.write_written_in_predecessors.len() + ); + let mut predecessor_statements = Vec::new(); + // let new_dead_lifetime_token_permission_map = + // self.new_dead_lifetime_token_permission_map.unwrap(); + // for ((snapshot_remaps, permission_remaps), old_permission_map) in self + // for (snapshot_remaps, permission_remaps) in self + for ((snapshot_remaps, permission_remaps), written_write) in self + .snapshot_report + .into_iter_remap() + .zip(self.permission_report.into_iter_remap()) + .zip(self.write_written_in_predecessors.into_iter()) + // .zip(self.old_permission_maps) + { + let mut statements_for_predecessor = Vec::new(); + for snapshot_remap in snapshot_remaps { + statements_for_predecessor.push(snapshot_remap.into_assume_statement(position)); + } + for permission_remap in permission_remaps { + statements_for_predecessor.push(permission_remap.into_assign_statement(position)); + } + for variable in written_write { + statements_for_predecessor.push( + vir_low::Statement::assign_no_pos( + variable, + vir_low::Expression::full_permission(), + ) + .set_default_position(position), + ); + } + // statements_for_predecessor.push( + // vir_low::Statement::assume_no_pos(vir_low::Expression::equals( + // new_dead_lifetime_token_permission_map.clone(), + // old_permission_map, + // )) + // .set_default_position(position), + // ); + predecessor_statements.push(statements_for_predecessor); + } + predecessor_statements + } +} + +impl SnapshotRemap { + /// Snapshots are in SSA, so we can use assume statements to remap them. + pub(in super::super) fn into_assume_statement( + self, + position: vir_low::Position, + ) -> vir_low::Statement { + vir_low::Statement::assume_no_pos(vir_low::Expression::equals( + vir_low::VariableDecl::new(self.old_snapshot, self.ty.clone()).into(), + vir_low::VariableDecl::new(self.new_snapshot, self.ty).into(), + )) + .set_default_position(position) + } +} + +impl PermissionRemap { + /// Permissions are not in SSA, so we need to use assign to remap them. + pub(in super::super) fn into_assign_statement( + self, + position: vir_low::Position, + ) -> vir_low::Statement { + if self.map_to_none { + vir_low::Statement::assume_no_pos(vir_low::Expression::equals( + vir_low::VariableDecl::new(self.new_snapshot, vir_low::Type::Perm).into(), + vir_low::Expression::no_permission(), + )) + .set_default_position(position) + } else { + vir_low::Statement::assume_no_pos(vir_low::Expression::equals( + vir_low::VariableDecl::new(self.new_snapshot, vir_low::Type::Perm).into(), + vir_low::VariableDecl::new(self.old_snapshot, vir_low::Type::Perm).into(), + )) + .set_default_position(position) + } + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/mod.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/mod.rs new file mode 100644 index 00000000000..e70607ce834 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/mod.rs @@ -0,0 +1,833 @@ +use self::{ + close_frac_ref::ClosedFracRef, + common::{AliasedWholeBool, FindSnapshotResult, NamedPredicateInstances, NoSnapshot}, + dead_lifetimes::DeadLifetimeTokens, + global_heap_state::HeapVariables, + lifetimes::LifetimeTokens, + memory_block::MemoryBlock, + owned::Owned, +}; +use super::{ + super::super::encoder_context::EncoderContext, + constraints::{BlockConstraints, ConstraintsMergeReport}, + ProcedureExecutor, +}; +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::transformations::symbolic_execution_new::{ + expression_interner::ExpressionInterner, program_context::ProgramContext, + }, +}; +use log::debug; +use vir_crate::{ + common::builtin_constants::MEMORY_BLOCK_PREDICATE_NAME, + low::{self as vir_low}, +}; + +mod common; +mod lifetimes; +mod dead_lifetimes; +mod owned; +mod memory_block; +mod close_frac_ref; +// mod snapshots; +mod utils; +mod purification; +mod merge_report; +mod global_heap_state; + +pub(super) use self::{ + global_heap_state::GlobalHeapState, + merge_report::HeapMergeReport, + purification::{PurificationResult, SnapshotBinding}, +}; + +impl<'a, 'c, EC: EncoderContext> ProcedureExecutor<'a, 'c, EC> { + pub(super) fn save_state(&mut self, label: String) -> SpannedEncodingResult<()> { + let current_block = self.current_block.as_ref().unwrap(); + let current_state = self.state_keeper.get_state_mut(current_block); + self.global_heap_state.insert(label, ¤t_state.heap)?; + Ok(()) + } + + /// Should be called only by simplify_expression. + pub(super) fn purify_snap_function_calls( + &mut self, + expression: &vir_low::Expression, + ) -> SpannedEncodingResult { + let current_block = self.current_block.as_ref().unwrap(); + let current_state = self.state_keeper.get_state_mut(current_block); + self::purification::purify_snap_function_calls( + ¤t_state.heap, + &mut self.global_heap_state, + self.program_context, + &mut current_state.constraints, + &mut self.expression_interner, + expression.clone(), + ) + } + + /// FIXME: Since the code is incomplete, we temporarily return a boolean to + /// indicate whether the predicate was handled. + pub(super) fn inhale_predicate( + &mut self, + predicate: vir_low::ast::expression::PredicateAccessPredicate, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + let current_block = self.current_block.as_ref().unwrap(); + let current_state = self.state_keeper.get_state_mut(current_block); + let block_builder = self.current_block_builder.as_mut().unwrap(); + block_builder.add_statement(vir_low::Statement::comment(format!("inhale {predicate}")))?; + match self.program_context.get_predicate_kind(&predicate.name) { + vir_low::PredicateKind::MemoryBlock => { + current_state.heap.memory_block.inhale( + self.program_context, + &mut self.expression_interner, + &mut self.global_heap_state, + predicate, + position, + &mut current_state.constraints, + block_builder, + )?; + } + vir_low::PredicateKind::Owned => { + current_state.heap.owned.inhale( + self.program_context, + &mut self.expression_interner, + &mut self.global_heap_state, + predicate, + position, + &mut current_state.constraints, + block_builder, + )?; + } + vir_low::PredicateKind::LifetimeToken => { + current_state.heap.lifetimes.inhale( + predicate, + position, + ¤t_state.constraints, + block_builder, + )?; + } + vir_low::PredicateKind::DeadLifetimeToken => { + // current_state.heap.dead_lifetimes.inhale( + // predicate, + // position, + // &mut current_state.constraints, + // block_builder, + // )?; + current_state.heap.dead_lifetimes.inhale( + &mut self.global_heap_state, + predicate, + position, + &mut current_state.constraints, + block_builder, + )?; + // current_state.heap.dead_lifetimes.inhale( + // self.program_context, + // &mut self.expression_interner, + // &mut self.global_heap_state, + // predicate, + // position, + // &mut current_state.constraints, + // block_builder, + // )?; + } + vir_low::PredicateKind::CloseFracRef => { + current_state.heap.close_frac_ref.inhale( + self.program_context, + &mut self.expression_interner, + &mut self.global_heap_state, + predicate, + position, + &mut current_state.constraints, + block_builder, + )?; + } + // vir_low::PredicateKind::WithoutSnapshotFrac => { + // current_state.heap.without_snapshot_frac.inhale( + // self.program_context, + // &mut self.expression_interner, + // &mut self.global_heap_state, + // predicate, + // position, + // &mut current_state.constraints, + // block_builder, + // )?; + // } + vir_low::PredicateKind::WithoutSnapshotWhole => { + current_state.heap.without_snapshot_whole.inhale( + self.program_context, + &mut self.expression_interner, + &mut self.global_heap_state, + predicate, + position, + &mut current_state.constraints, + block_builder, + )?; + } + vir_low::PredicateKind::WithoutSnapshotWholeNonAliased => { + current_state + .heap + .without_snapshot_whole_non_aliased + .inhale( + self.program_context, + &mut self.expression_interner, + &mut self.global_heap_state, + predicate, + position, + &mut current_state.constraints, + block_builder, + )?; + } + vir_low::PredicateKind::EndBorrowViewShift => { + current_state + .heap + .without_snapshot_whole_non_aliased + .inhale( + self.program_context, + &mut self.expression_interner, + &mut self.global_heap_state, + predicate, + position, + &mut current_state.constraints, + block_builder, + )?; + } + }; + Ok(()) + } + + /// FIXME: Since the code is incomplete, we temporarily return a boolean to + /// indicate whether the predicate was handled. + pub(super) fn exhale_predicate( + &mut self, + predicate: vir_low::ast::expression::PredicateAccessPredicate, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + let current_block = self.current_block.as_ref().unwrap(); + let current_state = self.state_keeper.get_state_mut(current_block); + let block_builder = self.current_block_builder.as_mut().unwrap(); + block_builder.add_statement(vir_low::Statement::comment(format!("exhale {predicate}")))?; + match self.program_context.get_predicate_kind(&predicate.name) { + vir_low::PredicateKind::MemoryBlock => { + current_state.heap.memory_block.exhale( + self.program_context, + &mut self.expression_interner, + &mut self.global_heap_state, + predicate, + position, + &mut current_state.constraints, + block_builder, + )?; + } + vir_low::PredicateKind::Owned => { + current_state.heap.owned.exhale( + self.program_context, + &mut self.expression_interner, + &mut self.global_heap_state, + predicate, + position, + &mut current_state.constraints, + block_builder, + )?; + } + vir_low::PredicateKind::LifetimeToken => { + current_state.heap.lifetimes.exhale( + predicate, + position, + ¤t_state.constraints, + block_builder, + )?; + } + vir_low::PredicateKind::DeadLifetimeToken => { + // current_state.heap.dead_lifetimes.exhale( + // predicate, + // position, + // ¤t_state.constraints, + // block_builder, + // )?; + current_state.heap.dead_lifetimes.exhale( + predicate, + position, + &mut current_state.constraints, + block_builder, + )?; + // current_state.heap.dead_lifetimes.exhale( + // self.program_context, + // &mut self.expression_interner, + // &mut self.global_heap_state, + // predicate, + // position, + // &mut current_state.constraints, + // block_builder, + // )?; + } + vir_low::PredicateKind::CloseFracRef => { + current_state.heap.close_frac_ref.exhale( + self.program_context, + &mut self.expression_interner, + &mut self.global_heap_state, + predicate, + position, + &mut current_state.constraints, + block_builder, + )?; + } + // vir_low::PredicateKind::WithoutSnapshotFrac => { + // current_state.heap.without_snapshot_frac.exhale( + // self.program_context, + // &mut self.expression_interner, + // &mut self.global_heap_state, + // predicate, + // position, + // &mut current_state.constraints, + // block_builder, + // )?; + // } + vir_low::PredicateKind::WithoutSnapshotWhole => { + current_state.heap.without_snapshot_whole.exhale( + self.program_context, + &mut self.expression_interner, + &mut self.global_heap_state, + predicate, + position, + &mut current_state.constraints, + block_builder, + )?; + } + vir_low::PredicateKind::WithoutSnapshotWholeNonAliased => { + current_state + .heap + .without_snapshot_whole_non_aliased + .exhale( + self.program_context, + &mut self.expression_interner, + &mut self.global_heap_state, + predicate, + position, + &mut current_state.constraints, + block_builder, + )?; + } + vir_low::PredicateKind::EndBorrowViewShift => { + current_state + .heap + .without_snapshot_whole_non_aliased + .exhale( + self.program_context, + &mut self.expression_interner, + &mut self.global_heap_state, + predicate, + position, + &mut current_state.constraints, + block_builder, + )?; + } + }; + Ok(()) + } + + pub(super) fn materialize_predicate( + &mut self, + predicate: vir_low::ast::expression::PredicateAccessPredicate, + check_that_exists: bool, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + let current_block = self.current_block.as_ref().unwrap(); + let current_state = self.state_keeper.get_state_mut(current_block); + let block_builder = self.current_block_builder.as_mut().unwrap(); + block_builder.add_statement(vir_low::Statement::comment(format!( + "materialize {predicate}" + )))?; + match self.program_context.get_predicate_kind(&predicate.name) { + vir_low::PredicateKind::MemoryBlock => { + current_state.heap.memory_block.materialize( + self.program_context, + &mut self.expression_interner, + &mut self.global_heap_state, + predicate, + position, + &mut current_state.constraints, + block_builder, + check_that_exists, + )?; + } + vir_low::PredicateKind::Owned => { + current_state.heap.owned.materialize( + self.program_context, + &mut self.expression_interner, + &mut self.global_heap_state, + predicate, + position, + &mut current_state.constraints, + block_builder, + check_that_exists, + )?; + } + vir_low::PredicateKind::LifetimeToken => { + unreachable!(); + } + vir_low::PredicateKind::DeadLifetimeToken => { + unreachable!(); + } + vir_low::PredicateKind::CloseFracRef => { + current_state.heap.close_frac_ref.materialize( + self.program_context, + &mut self.expression_interner, + &mut self.global_heap_state, + predicate, + position, + &mut current_state.constraints, + block_builder, + check_that_exists, + )?; + } + // vir_low::PredicateKind::WithoutSnapshotFrac => { + // current_state.heap.without_snapshot_frac.materialize( + // self.program_context, + // &mut self.expression_interner, + // &mut self.global_heap_state, + // predicate, + // position, + // &mut current_state.constraints, + // block_builder, + // )?; + // } + vir_low::PredicateKind::WithoutSnapshotWhole => { + current_state.heap.without_snapshot_whole.materialize( + self.program_context, + &mut self.expression_interner, + &mut self.global_heap_state, + predicate, + position, + &mut current_state.constraints, + block_builder, + check_that_exists, + )?; + } + vir_low::PredicateKind::WithoutSnapshotWholeNonAliased => { + current_state + .heap + .without_snapshot_whole_non_aliased + .materialize( + self.program_context, + &mut self.expression_interner, + &mut self.global_heap_state, + predicate, + position, + &mut current_state.constraints, + block_builder, + check_that_exists, + )?; + } + vir_low::PredicateKind::EndBorrowViewShift => { + current_state + .heap + .without_snapshot_whole_non_aliased + .materialize( + self.program_context, + &mut self.expression_interner, + &mut self.global_heap_state, + predicate, + position, + &mut current_state.constraints, + block_builder, + check_that_exists, + )?; + } + }; + Ok(()) + } + + // pub(super) fn mark_predicate_instances_seen_qp_inhale( + // &mut self, + // _predicate_name: &str, + // ) -> SpannedEncodingResult<()> { + // // FIXME: Implement + // Ok(()) + // } + + /// We have an untracked exhale, materialize all affected predicates. + pub(super) fn prepare_for_unhandled_exhale( + &mut self, + predicate_name: &str, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + let current_block = self.current_block.as_ref().unwrap(); + let current_state = self.state_keeper.get_state_mut(current_block); + let block_builder = self.current_block_builder.as_mut().unwrap(); + match self.program_context.get_predicate_kind(predicate_name) { + vir_low::PredicateKind::MemoryBlock => { + current_state + .heap + .memory_block + .prepare_for_unhandled_exhale( + self.program_context, + &mut self.expression_interner, + &mut self.global_heap_state, + predicate_name, + position, + &mut current_state.constraints, + block_builder, + )?; + } + vir_low::PredicateKind::Owned => { + current_state.heap.owned.prepare_for_unhandled_exhale( + self.program_context, + &mut self.expression_interner, + &mut self.global_heap_state, + predicate_name, + position, + &mut current_state.constraints, + block_builder, + )?; + } + vir_low::PredicateKind::LifetimeToken => { + unreachable!(); + } + vir_low::PredicateKind::DeadLifetimeToken => { + unreachable!(); + } + vir_low::PredicateKind::CloseFracRef => { + unreachable!(); + } + // vir_low::PredicateKind::WithoutSnapshotFrac => { + // current_state + // .heap + // .without_snapshot_frac + // .prepare_for_unhandled_exhale( + // self.program_context, + // &mut self.expression_interner, + // &mut self.global_heap_state, + // predicate_name, + // position, + // &mut current_state.constraints, + // block_builder, + // )?; + // } + vir_low::PredicateKind::WithoutSnapshotWhole => { + current_state + .heap + .without_snapshot_whole + .prepare_for_unhandled_exhale( + self.program_context, + &mut self.expression_interner, + &mut self.global_heap_state, + predicate_name, + position, + &mut current_state.constraints, + block_builder, + )?; + } + vir_low::PredicateKind::WithoutSnapshotWholeNonAliased => { + current_state + .heap + .without_snapshot_whole_non_aliased + .prepare_for_unhandled_exhale( + self.program_context, + &mut self.expression_interner, + &mut self.global_heap_state, + predicate_name, + position, + &mut current_state.constraints, + block_builder, + )?; + } + vir_low::PredicateKind::EndBorrowViewShift => { + current_state + .heap + .without_snapshot_whole_non_aliased + .prepare_for_unhandled_exhale( + self.program_context, + &mut self.expression_interner, + &mut self.global_heap_state, + predicate_name, + position, + &mut current_state.constraints, + block_builder, + )?; + } + }; + Ok(()) + } +} + +#[derive(Default, Clone)] +pub(super) struct BlockHeap { + lifetimes: LifetimeTokens, + // dead_lifetimes: LifetimeTokens, + dead_lifetimes: DeadLifetimeTokens, + owned: Owned, + memory_block: MemoryBlock, + close_frac_ref: ClosedFracRef, + // without_snapshot_frac: NamedPredicateInstances, + without_snapshot_whole: NamedPredicateInstances, + without_snapshot_whole_non_aliased: NamedPredicateInstances, + // dead_lifetimes: NamedPredicateInstances, +} + +impl std::fmt::Display for BlockHeap { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "lifetimes: {}", self.lifetimes)?; + writeln!(f, "owned: {}", self.owned)?; + writeln!(f, "memory_block: {}", self.memory_block)?; + writeln!(f, "close_frac_ref: {}", self.close_frac_ref)?; + // writeln!(f, "without_snapshot_frac: {}", self.without_snapshot_frac)?; + writeln!(f, "without_snapshot_whole: {}", self.without_snapshot_whole)?; + writeln!( + f, + "without_snapshot_whole_non_aliased: {}", + self.without_snapshot_whole_non_aliased + )?; + writeln!(f, "dead_lifetimes: {}", self.dead_lifetimes)?; + Ok(()) + } +} + +impl BlockHeap { + pub(super) fn pre_merge(&mut self, other: &Self) -> SpannedEncodingResult<()> { + // self.lifetimes + // .merge_deleted_permission_variables(&other.lifetimes)?; + // self.dead_lifetimes + // .merge_deleted_permission_variables(&other.dead_lifetimes)?; + self.owned + .merge_deleted_permission_variables(&other.owned)?; + self.memory_block + .merge_deleted_permission_variables(&other.memory_block)?; + self.close_frac_ref + .merge_deleted_permission_variables(&other.close_frac_ref)?; + self.without_snapshot_whole + .merge_deleted_permission_variables(&other.without_snapshot_whole)?; + self.without_snapshot_whole_non_aliased + .merge_deleted_permission_variables(&other.without_snapshot_whole_non_aliased)?; + Ok(()) + } + + pub(super) fn merge( + &mut self, + other: &Self, + self_edge_block: &mut Vec, + other_edge_block: &mut Vec, + position: vir_low::Position, + constraints_merge_report: ConstraintsMergeReport, + heap_merge_report: &mut HeapMergeReport, + constraints: &mut BlockConstraints, + expression_interner: &mut ExpressionInterner, + program_context: &ProgramContext, + global_state: &mut GlobalHeapState, + ) -> SpannedEncodingResult<()> { + self.lifetimes.merge( + &other.lifetimes, + self_edge_block, + other_edge_block, + position, + &constraints_merge_report, + )?; + // self.dead_lifetimes + // .merge(&other.dead_lifetimes, &constraints_merge_report)?; + self.dead_lifetimes + .merge(&other.dead_lifetimes, heap_merge_report, global_state)?; + self.owned.merge( + &other.owned, + self_edge_block, + other_edge_block, + position, + heap_merge_report, + constraints, + &constraints_merge_report, + expression_interner, + program_context, + global_state, + )?; + self.memory_block.merge( + &other.memory_block, + self_edge_block, + other_edge_block, + position, + heap_merge_report, + constraints, + &constraints_merge_report, + expression_interner, + program_context, + global_state, + )?; + self.close_frac_ref.merge( + &other.close_frac_ref, + self_edge_block, + other_edge_block, + position, + heap_merge_report, + constraints, + &constraints_merge_report, + expression_interner, + program_context, + global_state, + )?; + // self.without_snapshot_frac.merge( + // &other.without_snapshot_frac, + // self_edge_block, + // other_edge_block, + // position, + // heap_merge_report, + // constraints, + // &constraints_merge_report, + // expression_interner, + // program_context, + // global_state, + // )?; + self.without_snapshot_whole.merge( + &other.without_snapshot_whole, + self_edge_block, + other_edge_block, + position, + heap_merge_report, + constraints, + &constraints_merge_report, + expression_interner, + program_context, + global_state, + )?; + self.without_snapshot_whole_non_aliased.merge( + &other.without_snapshot_whole_non_aliased, + self_edge_block, + other_edge_block, + position, + heap_merge_report, + constraints, + &constraints_merge_report, + expression_interner, + program_context, + global_state, + )?; + // self.dead_lifetimes.merge( + // &other.dead_lifetimes, + // heap_merge_report, + // constraints, + // &constraints_merge_report, + // expression_interner, + // program_context, + // global_state, + // )?; + Ok(()) + } + + pub(super) fn leak_check(&self) -> SpannedEncodingResult<()> { + self.lifetimes.leak_check()?; + Ok(()) + } + + // pub(super) fn get_dead_lifetime_equality_classes( + // &self, + // constraints: &BlockConstraints, + // ) -> SpannedEncodingResult>> { + // self.dead_lifetimes + // .get_dead_lifetime_equality_classes(constraints) + // } + + // pub(super) fn remap_lifetimes( + // &mut self, + // remaps: BTreeMap, + // ) -> SpannedEncodingResult<()> { + // self.dead_lifetimes.remap_lifetimes(remaps) + // } + + pub(super) fn finalize_block( + &mut self, + constraints: &mut BlockConstraints, + ) -> SpannedEncodingResult<()> { + self.dead_lifetimes + .spread_permission_over_eclasses(constraints) + } + + pub(super) fn debug_print_memory_block(&self) { + debug!("Memory block state:\n{}", self.memory_block); + } +} + +#[derive(Default, Clone)] +pub(in super::super) struct HeapAtLabel { + owned: Owned, + memory_block: MemoryBlock, +} + +impl std::fmt::Display for HeapAtLabel { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "{}", self.owned)?; + Ok(()) + } +} + +enum HeapRef<'a> { + Current(&'a BlockHeap), + AtLabel(&'a HeapAtLabel), +} + +impl<'a> HeapRef<'a> { + pub(super) fn find_snapshot( + &self, + predicate_name: &str, + arguments: &[vir_low::Expression], + heap_variables: &mut HeapVariables, + constraints: &mut BlockConstraints, + expression_interner: &mut ExpressionInterner, + program_context: &ProgramContext, + ) -> SpannedEncodingResult { + match self { + HeapRef::Current(heap) => match program_context.get_predicate_kind(predicate_name) { + vir_low::PredicateKind::MemoryBlock => { + debug_assert_eq!(predicate_name, MEMORY_BLOCK_PREDICATE_NAME); + heap.memory_block.find_snapshot( + arguments, + heap_variables, + constraints, + expression_interner, + program_context, + ) + } + vir_low::PredicateKind::Owned => heap.owned.find_snapshot( + predicate_name, + arguments, + heap_variables, + constraints, + expression_interner, + program_context, + ), + vir_low::PredicateKind::LifetimeToken => todo!(), + vir_low::PredicateKind::CloseFracRef => todo!(), + // vir_low::PredicateKind::WithoutSnapshotFrac => todo!(), + vir_low::PredicateKind::WithoutSnapshotWhole => todo!(), + vir_low::PredicateKind::WithoutSnapshotWholeNonAliased => todo!(), + vir_low::PredicateKind::DeadLifetimeToken => todo!(), + vir_low::PredicateKind::EndBorrowViewShift => todo!(), + }, + HeapRef::AtLabel(heap) => match program_context.get_predicate_kind(predicate_name) { + vir_low::PredicateKind::MemoryBlock => { + debug_assert_eq!(predicate_name, MEMORY_BLOCK_PREDICATE_NAME); + heap.memory_block.find_snapshot( + arguments, + heap_variables, + constraints, + expression_interner, + program_context, + ) + } + vir_low::PredicateKind::Owned => heap.owned.find_snapshot( + predicate_name, + arguments, + heap_variables, + constraints, + expression_interner, + program_context, + ), + vir_low::PredicateKind::LifetimeToken => todo!(), + vir_low::PredicateKind::CloseFracRef => todo!(), + // vir_low::PredicateKind::WithoutSnapshotFrac => todo!(), + vir_low::PredicateKind::WithoutSnapshotWhole => todo!(), + vir_low::PredicateKind::WithoutSnapshotWholeNonAliased => todo!(), + vir_low::PredicateKind::DeadLifetimeToken => todo!(), + vir_low::PredicateKind::EndBorrowViewShift => todo!(), + }, + } + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/owned.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/owned.rs new file mode 100644 index 00000000000..c53b849a44e --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/owned.rs @@ -0,0 +1,173 @@ +use super::{ + common::{AliasedFractionalBool, FindSnapshotResult, NamedPredicateInstances}, + global_heap_state::HeapVariables, + merge_report::HeapMergeReport, + GlobalHeapState, +}; +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::transformations::{ + encoder_context::EncoderContext, + symbolic_execution_new::{ + block_builder::BlockBuilder, + expression_interner::ExpressionInterner, + procedure_executor::constraints::{BlockConstraints, ConstraintsMergeReport}, + program_context::ProgramContext, + }, + }, +}; +use vir_crate::low::{self as vir_low}; + +#[derive(Default, Clone)] +pub(super) struct Owned { + predicates: NamedPredicateInstances, +} + +impl std::fmt::Display for Owned { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.predicates) + } +} + +impl Owned { + pub(super) fn inhale( + &mut self, + program_context: &ProgramContext, + expression_interner: &mut ExpressionInterner, + global_state: &mut GlobalHeapState, + predicate: vir_low::PredicateAccessPredicate, + position: vir_low::Position, + constraints: &mut BlockConstraints, + block_builder: &mut BlockBuilder, + ) -> SpannedEncodingResult<()> { + self.predicates.inhale( + program_context, + expression_interner, + global_state, + predicate, + position, + constraints, + block_builder, + ) + } + + pub(super) fn exhale( + &mut self, + program_context: &mut ProgramContext, + expression_interner: &mut ExpressionInterner, + global_state: &mut GlobalHeapState, + predicate: vir_low::PredicateAccessPredicate, + position: vir_low::Position, + constraints: &mut BlockConstraints, + block_builder: &mut BlockBuilder, + ) -> SpannedEncodingResult<()> { + self.predicates.exhale( + program_context, + expression_interner, + global_state, + predicate, + position, + constraints, + block_builder, + ) + } + + pub(super) fn materialize( + &mut self, + program_context: &mut ProgramContext, + expression_interner: &mut ExpressionInterner, + global_state: &mut GlobalHeapState, + predicate: vir_low::PredicateAccessPredicate, + position: vir_low::Position, + constraints: &mut BlockConstraints, + block_builder: &mut BlockBuilder, + check_that_exists: bool, + ) -> SpannedEncodingResult<()> { + self.predicates.materialize( + program_context, + expression_interner, + global_state, + predicate, + position, + constraints, + block_builder, + check_that_exists, + ) + } + + pub(super) fn prepare_for_unhandled_exhale( + &mut self, + program_context: &mut ProgramContext, + expression_interner: &mut ExpressionInterner, + global_state: &mut GlobalHeapState, + predicate_name: &str, + position: vir_low::Position, + constraints: &mut BlockConstraints, + block_builder: &mut BlockBuilder, + ) -> SpannedEncodingResult<()> { + self.predicates.prepare_for_unhandled_exhale( + program_context, + expression_interner, + global_state, + predicate_name, + position, + constraints, + block_builder, + ) + } + + pub(super) fn find_snapshot( + &self, + predicate_name: &str, + arguments: &[vir_low::Expression], + global_state: &mut HeapVariables, + constraints: &mut BlockConstraints, + expression_interner: &mut ExpressionInterner, + program_context: &ProgramContext, + ) -> SpannedEncodingResult { + self.predicates.find_snapshot( + predicate_name, + arguments, + global_state, + constraints, + expression_interner, + program_context, + ) + } + + pub(super) fn merge_deleted_permission_variables( + &mut self, + other: &Self, + ) -> SpannedEncodingResult<()> { + self.predicates + .merge_deleted_permission_variables(&other.predicates)?; + Ok(()) + } + + pub(super) fn merge( + &mut self, + other: &Self, + self_edge_block: &mut Vec, + other_edge_block: &mut Vec, + position: vir_low::Position, + heap_merge_report: &mut HeapMergeReport, + constraints: &mut BlockConstraints, + constraints_merge_report: &ConstraintsMergeReport, + expression_interner: &mut ExpressionInterner, + program_context: &ProgramContext, + global_state: &mut GlobalHeapState, + ) -> SpannedEncodingResult<()> { + self.predicates.merge( + &other.predicates, + self_edge_block, + other_edge_block, + position, + heap_merge_report, + constraints, + constraints_merge_report, + expression_interner, + program_context, + global_state, + ) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/purification.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/purification.rs new file mode 100644 index 00000000000..aaed0297ee8 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/purification.rs @@ -0,0 +1,240 @@ +use super::{ + common::FindSnapshotResult, global_heap_state::HeapVariables, BlockHeap, GlobalHeapState, + HeapAtLabel, HeapRef, +}; +use crate::encoder::{ + errors::{SpannedEncodingError, SpannedEncodingResult}, + middle::core_proof::transformations::{ + encoder_context::EncoderContext, + symbolic_execution_new::{ + expression_interner::ExpressionInterner, + procedure_executor::constraints::BlockConstraints, program_context::ProgramContext, + }, + }, +}; +use std::collections::BTreeMap; +use vir_crate::{ + common::{ + expression::{BinaryOperationHelpers, ExpressionIterator, UnaryOperationHelpers}, + position::Positioned, + }, + low::{ + self as vir_low, expression::visitors::ExpressionFallibleFolder, + operations::quantifiers::BoundVariableStack, + }, +}; + +pub(in super::super) struct SnapshotBinding { + /// Under which condition this binding gets activated. + pub(in super::super) guard: vir_low::Expression, + /// A fresh variable to which the snapshot is bound. + pub(in super::super) variable: vir_low::VariableDecl, + pub(in super::super) guarded_candidates: Vec<(vir_low::Expression, vir_low::VariableDecl)>, +} + +pub(in super::super) struct PurificationResult { + pub(in super::super) expression: vir_low::Expression, + pub(in super::super) guarded_assertions: Vec, + pub(in super::super) bindings: Vec, +} + +pub(in super::super) fn purify_snap_function_calls( + heap: &BlockHeap, + global_heap_state: &mut GlobalHeapState, + program_context: &ProgramContext, + constraints: &mut BlockConstraints, + expression_interner: &mut ExpressionInterner, + expression: vir_low::Expression, +) -> SpannedEncodingResult { + let mut purifier = Purifier { + predicate_snapshots: heap, + predicate_snapshots_at_label: &global_heap_state.snapshots_at_label, + heap_variables: &mut global_heap_state.heap_variables, + constraints, + expression_interner, + program_context, + path_condition: Vec::new(), + guarded_assertions: Vec::new(), + bindings: Vec::new(), + bound_variables: Default::default(), + label: None, + }; + let mut expression = purifier.fallible_fold_expression(expression)?; + assert!(purifier.path_condition.is_empty()); + if !expression.is_heap_independent() { + purifier.constraints.saturate_solver()?; + expression = purifier.fallible_fold_expression(expression)?; + } + assert!(purifier.path_condition.is_empty()); + Ok(PurificationResult { + expression, + guarded_assertions: purifier.guarded_assertions, + bindings: purifier.bindings, + }) +} + +struct Purifier<'a, EC: EncoderContext> { + predicate_snapshots: &'a BlockHeap, + predicate_snapshots_at_label: &'a BTreeMap, + heap_variables: &'a mut HeapVariables, + constraints: &'a mut BlockConstraints, + expression_interner: &'a mut ExpressionInterner, + program_context: &'a ProgramContext<'a, EC>, + path_condition: Vec, + guarded_assertions: Vec, + bindings: Vec, + bound_variables: BoundVariableStack, + label: Option, +} + +impl<'a, EC: EncoderContext> ExpressionFallibleFolder for Purifier<'a, EC> { + type Error = SpannedEncodingError; + + fn fallible_fold_trigger( + &mut self, + mut trigger: vir_low::Trigger, + ) -> Result { + for term in std::mem::take(&mut trigger.terms) { + let new_term = self.fallible_fold_expression(term)?; + trigger.terms.push(new_term); + } + Ok(trigger) + } + + fn fallible_fold_func_app_enum( + &mut self, + func_app: vir_low::expression::FuncApp, + ) -> Result { + let func_app = self.fallible_fold_func_app(func_app)?; + let function = self.program_context.get_function(&func_app.function_name); + assert_eq!(function.parameters.len(), func_app.arguments.len()); + if func_app.context == vir_low::FuncAppContext::QuantifiedPermission { + debug_assert!(matches!( + function.kind, + vir_low::FunctionKind::MemoryBlockBytes | vir_low::FunctionKind::Snap + )); + // This function application is dependent on the quantified resource + // and should not be purified out. + return Ok(vir_low::Expression::FuncApp(func_app)); + } + match function.kind { + vir_low::FunctionKind::CallerFor | vir_low::FunctionKind::SnapRange => { + Ok(vir_low::Expression::FuncApp(func_app)) + } + vir_low::FunctionKind::MemoryBlockBytes | vir_low::FunctionKind::Snap => { + match self.resolve_snapshot(&func_app.function_name, &func_app.arguments)? { + FindSnapshotResult::NotFound => Ok(vir_low::Expression::FuncApp(func_app)), + FindSnapshotResult::FoundGuarded { + snapshot, + precondition, + } => { + if let Some(assertion) = precondition { + let guarded_assertion = vir_low::Expression::implies( + self.path_condition.clone().into_iter().conjoin(), + assertion, + ); + self.guarded_assertions.push(guarded_assertion); + } + Ok(vir_low::Expression::local(snapshot, func_app.position)) + } + FindSnapshotResult::FoundConditional { + binding, + guarded_candidates, + } => { + assert!(!guarded_candidates.is_empty()); + self.bindings.push(SnapshotBinding { + guard: self.path_condition.clone().into_iter().conjoin(), + variable: binding.clone(), + guarded_candidates, + }); + Ok(vir_low::Expression::local(binding, func_app.position)) + } + } + } + } + } + + fn fallible_fold_labelled_old( + &mut self, + mut labelled_old: vir_low::expression::LabelledOld, + ) -> Result { + std::mem::swap(&mut labelled_old.label, &mut self.label); + labelled_old.base = self.fallible_fold_expression_boxed(labelled_old.base)?; + std::mem::swap(&mut labelled_old.label, &mut self.label); + Ok(labelled_old) + } + + fn fallible_fold_quantifier_enum( + &mut self, + quantifier: vir_low::Quantifier, + ) -> Result { + self.bound_variables.push(&quantifier.variables); + let quantifier = self.fallible_fold_quantifier(quantifier)?; + self.bound_variables.pop(); + Ok(vir_low::Expression::Quantifier(quantifier)) + } + + fn fallible_fold_binary_op( + &mut self, + mut binary_op: vir_low::expression::BinaryOp, + ) -> Result { + binary_op.left = self.fallible_fold_expression_boxed(binary_op.left)?; + if binary_op.op_kind == vir_low::BinaryOpKind::Implies { + self.path_condition.push((*binary_op.left).clone()); + } + binary_op.right = self.fallible_fold_expression_boxed(binary_op.right)?; + if binary_op.op_kind == vir_low::BinaryOpKind::Implies { + self.path_condition.pop(); + } + Ok(binary_op) + } + + fn fallible_fold_conditional( + &mut self, + mut conditional: vir_low::expression::Conditional, + ) -> Result { + conditional.guard = self.fallible_fold_expression_boxed(conditional.guard)?; + self.path_condition.push((*conditional.guard).clone()); + conditional.then_expr = self.fallible_fold_expression_boxed(conditional.then_expr)?; + self.path_condition.pop(); + self.path_condition + .push(vir_low::Expression::not((*conditional.guard).clone())); + conditional.else_expr = self.fallible_fold_expression_boxed(conditional.else_expr)?; + self.path_condition.pop(); + Ok(conditional) + } +} + +impl<'a, EC: EncoderContext> Purifier<'a, EC> { + fn resolve_snapshot( + &mut self, + function_name: &str, + arguments: &[vir_low::Expression], + ) -> SpannedEncodingResult { + if self + .bound_variables + .expressions_contains_bound_variables(arguments) + { + return Ok(FindSnapshotResult::NotFound); + } + let predicate_snapshots = if let Some(label) = &self.label { + HeapRef::AtLabel(self.predicate_snapshots_at_label.get(label).unwrap()) + } else { + HeapRef::Current(self.predicate_snapshots) + }; + let Some(predicate_name) = self.program_context.get_snapshot_predicate(function_name) else { + // The final snapshot function is already pure and, therefore, is + // not mapped to a predicate. This is the case for unique_ref final + // snapshot. + return Ok(FindSnapshotResult::NotFound); + }; + predicate_snapshots.find_snapshot( + predicate_name, + arguments, + self.heap_variables, + self.constraints, + self.expression_interner, + self.program_context, + ) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/snapshots/mod.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/snapshots/mod.rs new file mode 100644 index 00000000000..260d7289d94 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/snapshots/mod.rs @@ -0,0 +1,5 @@ +// mod state; +// mod purification; + +// pub(in super::super) use self::state::GlobalPredicateSnapshotState; +// pub(super) use self::{purification::purify_snap_function_calls, state::PredicateSnapshots}; diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/snapshots/state.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/snapshots/state.rs new file mode 100644 index 00000000000..1a2f5ce6ece --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/snapshots/state.rs @@ -0,0 +1,186 @@ +// use super::super::utils::matches_arguments; +// use crate::encoder::{ +// errors::{SpannedEncodingError, SpannedEncodingResult}, +// middle::core_proof::transformations::{ +// encoder_context::EncoderContext, +// symbolic_execution::utils::all_heap_independent, +// symbolic_execution_new::{ +// expression_interner::ExpressionInterner, +// procedure_executor::constraints::{BlockConstraints, MergeReport}, +// program_context::ProgramContext, +// }, +// }, +// }; +// use log::debug; +// use std::collections::BTreeMap; +// use vir_crate::{ +// common::display, +// low::{ +// self as vir_low, +// expression::visitors::{ExpressionFallibleFolder, ExpressionWalker}, +// }, +// }; + +// #[derive(Default, Clone)] +// pub(in super::super) struct PredicateSnapshots { +// /// Maps predicate name to a list of predicate instances. +// snapshots: BTreeMap>, +// } + +// impl std::fmt::Display for PredicateSnapshots { +// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +// for (predicate_name, snapshots) in &self.snapshots { +// writeln!(f, "{}:", predicate_name)?; +// for snapshot in snapshots { +// writeln!(f, " {}", snapshot)?; +// } +// } +// Ok(()) +// } +// } + +// #[derive(Default)] +// pub(in super::super::super) struct GlobalPredicateSnapshotState { +// pub(super) snapshots_at_label: BTreeMap, +// variables: Vec, +// } + +// #[derive(Clone)] +// struct PredicateSnapshot { +// /// Predicate arguments. +// arguments: Vec, +// /// The current snapshot of the predicate. +// snapshot: vir_low::VariableDecl, +// } + +// impl std::fmt::Display for PredicateSnapshot { +// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +// write!(f, "{}: {}", display::cjoin(&self.arguments), self.snapshot)?; +// Ok(()) +// } +// } + +// impl GlobalPredicateSnapshotState { +// pub(in super::super) fn insert( +// &mut self, +// label: String, +// snapshots: &PredicateSnapshots, +// ) -> SpannedEncodingResult<()> { +// assert!(self +// .snapshots_at_label +// .insert(label, snapshots.clone()) +// .is_none()); +// Ok(()) +// } + +// pub(in super::super::super) fn take_variables(&mut self) -> Vec { +// std::mem::take(&mut self.variables) +// } +// } + +// impl PredicateSnapshots { +// pub(in super::super) fn create_predicate_snapshot( +// &mut self, +// program_context: &ProgramContext, +// state: &mut GlobalPredicateSnapshotState, +// predicate_name: &str, +// arguments: Vec, +// ) -> SpannedEncodingResult> { +// let predicate_snapshots = self +// .snapshots +// .entry(predicate_name.to_string()) +// .or_default(); +// let snapshot_variable_name = format!( +// "snapshot_non_aliased${}${}", +// predicate_name, +// state.variables.len() +// ); +// if let Some(ty) = program_context.get_snapshot_type(predicate_name) { +// assert!( +// all_heap_independent(&arguments), +// "arguments: {}", +// display::cjoin(&arguments) +// ); +// let snapshot = vir_low::VariableDecl::new(snapshot_variable_name.clone(), ty); +// predicate_snapshots.push(PredicateSnapshot { +// arguments, +// snapshot: snapshot.clone(), +// }); +// state.variables.push(snapshot.clone()); +// Ok(Some(snapshot)) +// } else { +// Ok(None) +// } +// } + +// pub(in super::super) fn remove_predicate_snapshot( +// &mut self, +// predicate_name: &str, +// arguments: &[vir_low::Expression], +// ) -> SpannedEncodingResult<()> { +// let predicate_snapshots = self.snapshots.get_mut(predicate_name).unwrap(); +// for (i, predicate_snapshot) in predicate_snapshots.iter().enumerate() { +// if arguments == &predicate_snapshot.arguments { +// predicate_snapshots.remove(i); +// return Ok(()); +// } +// } +// unreachable!("{predicate_name}({})", display::cjoin(arguments)) +// } + +// pub(super) fn find_snapshot( +// &self, +// predicate_name: &str, +// arguments: &[vir_low::Expression], +// constraints: &BlockConstraints, +// expression_interner: &mut ExpressionInterner, +// program_context: &ProgramContext, +// ) -> SpannedEncodingResult> { +// if let Some(predicate_snapshots) = self.snapshots.get(predicate_name) { +// for predicate_snapshot in predicate_snapshots { +// if predicate_snapshot.matches_arguments( +// arguments, +// constraints, +// expression_interner, +// program_context, +// )? { +// return Ok(Some(predicate_snapshot.snapshot.clone())); +// } +// } +// } +// Ok(None) +// } + +// pub(in super::super) fn merge( +// &mut self, +// other: &Self, +// constraints_merge_report: &MergeReport, +// ) -> SpannedEncodingResult<()> { +// unimplemented!(); +// } +// } + +// impl PredicateSnapshot { +// fn matches_arguments( +// &self, +// arguments: &[vir_low::Expression], +// constraints: &BlockConstraints, +// expression_interner: &mut ExpressionInterner, +// program_context: &ProgramContext, +// ) -> SpannedEncodingResult { +// matches_arguments( +// &self.arguments, +// arguments, +// constraints, +// expression_interner, +// program_context, +// ) +// // debug_assert_eq!(self.arguments.len(), arguments.len()); +// // for (arg1, arg2) in self.arguments.iter().zip(arguments) { +// // if !constraints.is_equal(expression_interner, program_context, arg1, arg2)? { +// // return Ok(false); +// // } +// // } +// // Ok(true) +// } +// } diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/utils.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/utils.rs new file mode 100644 index 00000000000..4663e929aa6 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/heap/utils.rs @@ -0,0 +1,117 @@ +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::transformations::{ + encoder_context::EncoderContext, + symbolic_execution_new::{ + expression_interner::ExpressionInterner, + procedure_executor::constraints::{BlockConstraints, ConstraintsMergeReport}, + program_context::ProgramContext, + }, + }, +}; +use log::debug; +use rustc_hash::FxHashSet; +use vir_crate::low::{self as vir_low}; + +pub(super) fn matches_arguments( + arguments1: &[vir_low::Expression], + arguments2: &[vir_low::Expression], + constraints: &mut BlockConstraints, + expression_interner: &mut ExpressionInterner, + program_context: &ProgramContext, +) -> SpannedEncodingResult { + debug_assert_eq!(arguments1.len(), arguments2.len()); + for (arg1, arg2) in arguments1.iter().zip(arguments2) { + if !constraints.is_equal(expression_interner, program_context, arg1, arg2)? { + debug!("arguments do not match: {} != {}", arg1, arg2); + return Ok(false); + } + } + Ok(true) +} + +pub(super) fn matches_arguments_with_remaps( + self_arguments: &[vir_low::Expression], + other_arguments: &[vir_low::Expression], + constraints_merge_report: &ConstraintsMergeReport, + constraints: &mut BlockConstraints, + expression_interner: &mut ExpressionInterner, + program_context: &ProgramContext, +) -> SpannedEncodingResult { + debug_assert_eq!(self_arguments.len(), other_arguments.len()); + let other_remaps = constraints_merge_report.get_other_remaps(); + let dropped_self_equalities = constraints_merge_report.get_dropped_self_equalities(); + let dropped_other_equalities = constraints_merge_report.get_dropped_other_equalities(); + // We use `remap_targets` to ensure that we are not remapping back and + // forth. + let mut remap_targets = FxHashSet::default(); + for (self_arg, other_arg) in self_arguments.iter().zip(other_arguments) { + // `self_arg` was already remaped in the caller. + let remap_self = self_arg.clone(); + let remap_other = other_arg.clone().map_variables(|variable| { + if let Some(remap) = other_remaps.get(&variable) { + remap_targets.insert(remap.name.clone()); + remap.clone() + } else { + variable + } + }); + let remap_self = remap_self.map_variables(|variable| { + if let Some(remap) = dropped_self_equalities.get(&variable) { + if remap_targets.contains(&variable.name) { + // We are remapping back and forth. + variable + } else { + remap_targets.insert(remap.name.clone()); + remap.clone() + } + } else { + variable + } + }); + let remap_other = remap_other.map_variables(|variable| { + if let Some(remap) = dropped_other_equalities.get(&variable) { + if remap_targets.contains(&variable.name) { + // We are remapping back and forth. + variable + } else { + remap_targets.insert(remap.name.clone()); + remap.clone() + } + } else { + variable + } + }); + if !constraints.is_equal( + expression_interner, + program_context, + &remap_self, + &remap_other, + )? { + return Ok(false); + } + } + Ok(true) +} + +// pub(super) fn is_place_non_aliased(place: &vir_low::Expression) -> bool { +// assert_eq!(place.get_type(), &vir_low::macros::ty!(PlaceOption)); +// match place { +// vir_low::Expression::DomainFuncApp(domain_func_app) +// if domain_func_app.arguments.len() == 1 => +// { +// let argument = &domain_func_app.arguments[0]; +// if domain_func_app.function_name == "place_option_some" { +// true +// } else { +// is_place_non_aliased(argument) +// } +// } +// vir_low::Expression::DomainFuncApp(domain_func_app) => { +// assert_eq!(domain_func_app.function_name, "place_option_none"); +// false +// } +// vir_low::Expression::LabelledOld(labelled_old) => is_place_non_aliased(&labelled_old.base), +// _ => unreachable!("place: {place}"), +// } +// } diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/mod.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/mod.rs new file mode 100644 index 00000000000..d021a5deacb --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/mod.rs @@ -0,0 +1,411 @@ +use self::{heap::GlobalHeapState, state::StateKeeper, variables::VariableVersions}; +use super::{ + super::encoder_context::EncoderContext, block_builder::BlockBuilder, + expression_interner::ExpressionInterner, program_context::ProgramContext, + trace_builder::TraceBuilder, Executor, +}; +use crate::encoder::errors::SpannedEncodingResult; +use log::debug; +use prusti_common::config; +use std::collections::BTreeMap; +use vir_crate::{ + common::{ + cfg::Cfg, + expression::{BinaryOperationHelpers, ExpressionIterator, SyntacticEvaluation}, + graphviz::ToGraphviz, + }, + low::{self as vir_low}, +}; + +mod statements; +mod constraints; +mod variables; +mod heap; +mod graphviz; +mod state; +mod block_marker_conditions; +mod expressions; + +pub(super) struct ProcedureExecutor<'a, 'c, EC: EncoderContext> { + executor: &'a mut Executor, + source_filename: &'a str, + program_context: &'a mut ProgramContext<'c, EC>, + procedure: &'a vir_low::ProcedureDecl, + reaching_predecessors: BTreeMap>, + current_block: Option, + current_block_builder: Option, + trace_builder: TraceBuilder, + expression_interner: ExpressionInterner, + // path_constraints: PathConstraints, + // heap: Heap, + state_keeper: StateKeeper, + variable_versions: VariableVersions, + exhale_label_generator_counter: u64, + global_heap_state: GlobalHeapState, + custom_labels: Vec, + return_blocks: Vec, +} + +impl<'a, 'c, EC: EncoderContext> Drop for ProcedureExecutor<'a, 'c, EC> { + fn drop(&mut self) { + if prusti_common::config::dump_debug_info() && std::thread::panicking() { + prusti_common::report::log::report_with_writer( + "graphviz_method_vir_low_crashing_symbolic_execution", + format!("{}.{}.crash.dot", self.source_filename, self.procedure.name,), + |writer| self.to_graphviz(writer).unwrap(), + ); + } + } +} + +impl<'a, 'c, EC: EncoderContext> ProcedureExecutor<'a, 'c, EC> { + pub(super) fn new( + executor: &'a mut Executor, + source_filename: &'a str, + program_context: &'a mut ProgramContext<'c, EC>, + procedure: &'a vir_low::ProcedureDecl, + ) -> SpannedEncodingResult { + Ok(Self { + executor, + source_filename, + program_context, + procedure, + reaching_predecessors: Default::default(), + current_block: None, + current_block_builder: None, + trace_builder: TraceBuilder::new()?, + expression_interner: Default::default(), + state_keeper: Default::default(), + // path_constraints: PathConstraints::new(), + // heap: Heap::new(), + variable_versions: Default::default(), + exhale_label_generator_counter: 0, + global_heap_state: Default::default(), + custom_labels: Vec::new(), + return_blocks: Vec::new(), + }) + } + + pub(super) fn execute_procedure( + mut self, + new_procedures: &'a mut Vec, + ) -> SpannedEncodingResult<()> { + debug!("Executing procedure: {}", self.procedure.name); + if prusti_common::config::dump_debug_info() { + prusti_common::report::log::report_with_writer( + "graphviz_method_vir_low_before_symbolic_execution", + format!("{}.{}.dot", self.source_filename, self.procedure.name), + |writer| self.procedure.to_graphviz(writer).unwrap(), + ); + } + self.reaching_predecessors + .insert(self.procedure.entry.clone(), Vec::new()); + let traversal_order = self.procedure.get_topological_sort(); + for current_block in traversal_order { + if !self.reaching_predecessors.contains_key(¤t_block) { + // The block is unreachable. + continue; + } + let block = self.procedure.basic_blocks.get(¤t_block).unwrap(); + if self.execute_block(¤t_block, block)? { + match &block.successor { + vir_low::Successor::Return => { + self.return_blocks.push(current_block); + } + vir_low::Successor::Goto(target) => { + self.mark_predecessor_as_analyzed(current_block, target); + } + vir_low::Successor::GotoSwitch(targets) => { + for (_, target) in targets { + self.mark_predecessor_as_analyzed(current_block.clone(), target); + } + } + } + } + } + let source_filename = self.source_filename; + if config::symbolic_execution_single_method() { + let new_procedure = self.into_procedure()?; + if prusti_common::config::dump_debug_info() { + prusti_common::report::log::report_with_writer( + "graphviz_method_vir_low_after_symbolic_execution", + format!("{}.{}.dot", source_filename, new_procedure.name), + |writer| new_procedure.to_graphviz(writer).unwrap(), + ); + } + new_procedures.push(new_procedure); + } else { + let traces = self.into_procedure_per_trace()?; + if prusti_common::config::dump_debug_info() { + for (i, trace) in traces.iter().enumerate() { + prusti_common::report::log::report_with_writer( + "graphviz_method_vir_low_after_symbolic_execution", + format!("{}.{}.{}.dot", i, source_filename, trace.name), + |writer| trace.to_graphviz(writer).unwrap(), + ); + } + } + new_procedures.extend(traces); + } + Ok(()) + } + + fn mark_predecessor_as_analyzed( + &mut self, + current_block: vir_low::Label, + target: &vir_low::Label, + ) { + let predecessors = self + .reaching_predecessors + .entry(target.clone()) + .or_default(); + predecessors.push(current_block); + } + + fn clear_successors(&mut self) -> SpannedEncodingResult<()> { + self.current_builder_mut().successors.clear(); + Ok(()) + } + + fn execute_block( + &mut self, + current_block: &vir_low::Label, + block: &vir_low::BasicBlock, + ) -> SpannedEncodingResult { + debug!("Executing block {}", current_block); + self.create_new_current_block(current_block)?; + let comment = format!("Executing block: {current_block}"); + self.add_comment(comment)?; + let mut reached_successor = true; + for statement in &block.statements { + self.execute_statement(statement)?; + if self.path_constraints_inconsistent()? { + self.add_assume(false.into(), self.procedure.position)?; + self.clear_successors()?; + reached_successor = false; + break; + } + } + self.add_current_block_to_trace()?; + Ok(reached_successor) + } + + fn create_new_current_block( + &mut self, + current_block: &vir_low::Label, + ) -> SpannedEncodingResult<()> { + assert!(self.current_block.is_none()); + assert!(self.current_block_builder.is_none()); + let successors = self + .procedure + .successors(current_block) + .into_iter() + .cloned() + .collect(); + let builder = BlockBuilder::new(successors)?; + self.current_block_builder = Some(builder); + self.current_block = Some(current_block.clone()); + self.initialize_state_for(current_block)?; + Ok(()) + } + + fn register_label(&mut self, label: vir_low::Label) -> SpannedEncodingResult<()> { + self.custom_labels.push(label); + Ok(()) + } + + fn add_current_block_to_trace(&mut self) -> SpannedEncodingResult<()> { + self.finalize_current_block()?; + let label = self.current_block.take().unwrap(); + let builder = self.current_block_builder.take().unwrap(); + self.trace_builder.add_block(label, builder) + } + + fn current_builder_mut(&mut self) -> &mut BlockBuilder { + self.current_block_builder.as_mut().unwrap() + } + + fn construct_edge_block( + source: &vir_low::Label, + target: &vir_low::Label, + edge_blocks: &mut BTreeMap<(vir_low::Label, vir_low::Label), Vec>, + basic_blocks: &mut BTreeMap, + ) -> vir_low::Label { + if let Some(edge_block) = edge_blocks.remove(&(source.clone(), target.clone())) { + if !edge_block.is_empty() { + let edge_block_label = + vir_low::Label::new(format!("edge_block_{}__{}", source.name, target.name)); + let edge_block = + vir_low::BasicBlock::new(edge_block, vir_low::Successor::Goto(target.clone())); + basic_blocks.insert(edge_block_label.clone(), edge_block); + return edge_block_label; + } + } + target.clone() + } + + fn into_procedure(mut self) -> SpannedEncodingResult { + self.uninitialize()?; + let mut counter = 0; + let mut basic_blocks = BTreeMap::new(); + let mut locals = self.procedure.locals.clone(); + for (label, mut block) in std::mem::take(&mut self.trace_builder.blocks) { + let successor = match block.successors.len() { + 0 => vir_low::Successor::Return, + 1 => vir_low::Successor::Goto(Self::construct_edge_block( + &label, + &block.successors[0], + &mut self.trace_builder.edge_blocks, + &mut basic_blocks, + )), + _ => { + let non_det_variable = vir_low::VariableDecl::new( + format!("non_deterministic_jump${counter}"), + vir_low::Type::Int, + ); + counter += 1; + let mut targets = Vec::new(); + let mut disjuncts = Vec::new(); + for (i, successor) in block.successors.into_iter().enumerate() { + let condition = + vir_low::Expression::equals(non_det_variable.clone().into(), i.into()); + disjuncts.push(condition.clone()); + let actual_successor = Self::construct_edge_block( + &label, + &successor, + &mut self.trace_builder.edge_blocks, + &mut basic_blocks, + ); + targets.push((condition, actual_successor)); + } + locals.push(non_det_variable); + block.statements.push(vir_low::Statement::assume( + disjuncts.into_iter().disjoin(), + self.procedure.position, + )); + vir_low::Successor::GotoSwitch(targets) + } + }; + let basic_block = vir_low::BasicBlock::new(block.statements, successor); + basic_blocks.insert(label, basic_block); + } + locals.extend(self.variable_versions.create_variable_decls()); + locals.extend(self.global_heap_state.clone_variables()); + let mut custom_labels = self.procedure.custom_labels.clone(); + custom_labels.extend(std::mem::take(&mut self.custom_labels)); + // let entry_block = basic_blocks.get_mut(&self.procedure.entry).unwrap(); + // let permission_variable_initialization = self + // .global_heap_state + // .initialize_permission_variables(self.procedure.position); + // entry_block + // .statements + // .splice(0..0, permission_variable_initialization); + let procedure = vir_low::ProcedureDecl::new_with_pos( + self.procedure.name.clone(), + locals, + custom_labels, + self.procedure.entry.clone(), + self.procedure.exit.clone(), + basic_blocks, + self.procedure.position, + ); + Ok(procedure) + } + + fn into_procedure_per_trace(mut self) -> SpannedEncodingResult> { + self.uninitialize()?; + fn dfs( + builder: &TraceBuilder, + current_block: &vir_low::Label, + trace: &mut Vec, + traces: &mut Vec>, + ) { + assert!( + traces.len() < config::symbolic_execution_multiple_methods_max() as usize, + "Too many traces" + ); + trace.push(current_block.clone()); + // Check if we have assume false and terminate if we do. + for statement in &builder.blocks[current_block].statements { + if let vir_low::Statement::Assume(statement) = statement { + if statement.expression.is_false() { + traces.push(trace.clone()); + trace.pop(); + return; + } + } + } + let successors = &builder.blocks[current_block].successors; + if successors.is_empty() { + traces.push(trace.clone()); + } else { + for successor in successors { + dfs(builder, successor, trace, traces); + } + } + trace.pop(); + } + let mut traces = Vec::new(); + let mut trace = vec![]; + dfs( + &self.trace_builder, + &self.procedure.entry, + &mut trace, + &mut traces, + ); + let mut procedures = Vec::new(); + for (i, trace) in traces.into_iter().enumerate() { + let mut basic_blocks = BTreeMap::new(); + let mut statements = Vec::new(); + for (i, label) in trace.iter().enumerate() { + let block = &self.trace_builder.blocks[label]; + statements.extend(block.statements.clone()); + if let Some(next_label) = trace.get(i + 1) { + if let Some(edge_block) = self + .trace_builder + .edge_blocks + .get(&(label.clone(), next_label.clone())) + { + statements.extend(edge_block.clone()); + } + } + } + basic_blocks.insert( + self.procedure.entry.clone(), + vir_low::BasicBlock::new(statements, vir_low::Successor::Return), + ); + let mut locals = self.procedure.locals.clone(); + locals.extend(self.variable_versions.create_variable_decls()); + locals.extend(self.global_heap_state.clone_variables()); + let mut custom_labels = self.procedure.custom_labels.clone(); + custom_labels.extend(self.custom_labels.clone()); + // let entry_block = basic_blocks.get_mut(&self.procedure.entry).unwrap(); + // let permission_variable_initialization = self + // .global_heap_state + // .initialize_permission_variables(self.procedure.position); + // entry_block + // .statements + // .splice(0..0, permission_variable_initialization); + let procedure = vir_low::ProcedureDecl::new_with_pos( + format!("{}$trace{}", self.procedure.name, i), + locals, + custom_labels, + self.procedure.entry.clone(), + self.procedure.exit.clone(), + basic_blocks, + self.procedure.position, + ); + procedures.push(procedure); + } + Ok(procedures) + } + + fn uninitialize(&mut self) -> SpannedEncodingResult<()> { + assert!(self.current_block.is_none()); + assert!(self.current_block_builder.is_none()); + // self.heap.uninitialize(&self.return_blocks)?; + // self.path_constraints.uninitialize()?; + self.state_keeper.uninitialize(&self.return_blocks)?; + Ok(()) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/state/block.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/state/block.rs new file mode 100644 index 00000000000..f11ba42de31 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/state/block.rs @@ -0,0 +1,82 @@ +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::transformations::{ + encoder_context::EncoderContext, + symbolic_execution_new::{ + expression_interner::ExpressionInterner, + procedure_executor::{ + constraints::BlockConstraints, + heap::{BlockHeap, GlobalHeapState, HeapMergeReport}, + }, + program_context::ProgramContext, + }, + }, +}; +use vir_crate::low::{self as vir_low}; + +#[derive(Clone)] +pub(in super::super) struct State { + pub(in super::super) heap: BlockHeap, + pub(in super::super) constraints: BlockConstraints, +} + +impl State { + pub(super) fn new( + program_context: &ProgramContext, + ) -> SpannedEncodingResult { + Ok(Self { + heap: Default::default(), + constraints: BlockConstraints::new(program_context)?, + }) + } + + pub(super) fn pre_merge(&mut self, other: &Self) -> SpannedEncodingResult<()> { + self.heap.pre_merge(&other.heap)?; + Ok(()) + } + + pub(super) fn merge( + &mut self, + other: &Self, + self_edge_block: &mut Vec, + other_edge_block: &mut Vec, + position: vir_low::Position, + heap_merge_report: &mut HeapMergeReport, + expression_interner: &mut ExpressionInterner, + program_context: &ProgramContext, + global_state: &mut GlobalHeapState, + ) -> SpannedEncodingResult<()> { + let merge_report = self.constraints.merge(&other.constraints)?; + self.heap.merge( + &other.heap, + self_edge_block, + other_edge_block, + position, + merge_report, + heap_merge_report, + &mut self.constraints, + expression_interner, + program_context, + global_state, + )?; + Ok(()) + } + + pub(super) fn debug_print_memory_block(&self) { + self.heap.debug_print_memory_block(); + } + + // pub(super) fn get_dead_lifetime_equality_classes( + // &self, + // ) -> SpannedEncodingResult>> { + // self.heap + // .get_dead_lifetime_equality_classes(&self.constraints) + // } + + // pub(super) fn remap_lifetimes( + // &mut self, + // remaps: BTreeMap, + // ) -> SpannedEncodingResult<()> { + // self.heap.remap_lifetimes(remaps) + // } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/state/keeper.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/state/keeper.rs new file mode 100644 index 00000000000..1234c526e11 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/state/keeper.rs @@ -0,0 +1,188 @@ +use super::block::State; +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::transformations::{ + encoder_context::EncoderContext, + symbolic_execution_new::{ + expression_interner::ExpressionInterner, + procedure_executor::heap::{GlobalHeapState, HeapMergeReport}, + program_context::ProgramContext, + trace_builder::TraceBuilder, + }, + }, +}; +use log::trace; +use prusti_common::config; +use std::collections::{BTreeMap, VecDeque}; +use vir_crate::low::{self as vir_low}; + +#[derive(Default)] +pub(in super::super) struct StateKeeper { + pub(super) states: BTreeMap, +} + +impl StateKeeper { + pub(super) fn create_state_for_block( + &mut self, + new_block: &vir_low::Label, + predecessors: &[vir_low::Label], + expression_interner: &mut ExpressionInterner, + program_context: &ProgramContext, + global_state: &mut GlobalHeapState, + trace_builder: &mut TraceBuilder, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + trace!("create_state_for_block: {}", new_block); + match predecessors.len() { + 0 => { + self.states + .insert(new_block.clone(), State::new(program_context)?); + } + 1 => { + let predecessor = &predecessors[0]; + let predecessor_state = self.states.get(predecessor).unwrap(); + let state = predecessor_state.clone(); + self.states.insert(new_block.clone(), state); + } + _ => { + // { + // let mut lifetime_equality_classes = Vec::new(); + // for predecessor in predecessors { + // let predecessor_state = self.states.get(predecessor).unwrap(); + // lifetime_equality_classes + // .push(predecessor_state.get_dead_lifetime_equality_classes()?); + // } + // // Iterate over mutable slices that start at ith element and + // // go till the end. + // let mut lifetime_equality_slice = &mut lifetime_equality_classes[..]; + // while let Some((first_predecessor, tail)) = + // lifetime_equality_slice.split_first_mut() + // { + // for second_predecessor in &mut *tail { + // // Intersect matching equality classes. + // for first_class in first_predecessor.values_mut() { + // assert!(!first_class.is_empty()); + // for second_class in second_predecessor.values_mut() { + // assert!(!second_class.is_empty()); + // if !first_class.is_disjoint(second_class) { + // first_class + // .retain(|element| second_class.contains(element)); + // assert!(!first_class.is_empty()); + // second_class + // .retain(|element| first_class.contains(element)); + // assert!(!second_class.is_empty()); + // } + // } + // } + // } + // lifetime_equality_slice = tail; + // } + + // // Pick a representative element from each set as the chosen remap target. + // let mut lifetime_predecessor_remaps = Vec::new(); + // for equality_classes in lifetime_equality_classes { + // let mut lifetime_remaps = BTreeMap::new(); + // for (lifetime, mut equality_class) in equality_classes { + // lifetime_remaps.insert(lifetime, equality_class.pop_first().unwrap()); + // } + // lifetime_predecessor_remaps.push(lifetime_remaps); + // } + + // // Remap all lifetime resources. + // assert_eq!(predecessors.len(), lifetime_predecessor_remaps.len()); + // for (predecessor, remaps) in + // predecessors.iter().zip(lifetime_predecessor_remaps) + // { + // let predecessor_state = self.states.get_mut(predecessor).unwrap(); + // predecessor_state.remap_lifetimes(remaps)?; + // } + // }; + let mut predecessors_iter = predecessors.iter(); + let first_predecessor = predecessors_iter.next().unwrap(); + let mut first_predecessor_edge_block = Vec::new(); + let first_predecessor_state = self.states.get(first_predecessor).unwrap(); + let mut state = first_predecessor_state.clone(); + for predecessor in predecessors { + let predecessor_state = self.states.get(predecessor).unwrap(); + state.pre_merge(predecessor_state)?; + } + let mut heap_merge_report = HeapMergeReport::new(); + let mut predecessor_edge_blocks = VecDeque::new(); + for predecessor in predecessors_iter { + heap_merge_report.create_predecessor(); + let predecessor_state = self.states.get(predecessor).unwrap(); + let mut predecessor_edge_block = Vec::new(); + state.merge( + predecessor_state, + &mut first_predecessor_edge_block, + &mut predecessor_edge_block, + position, + &mut heap_merge_report, + expression_interner, + program_context, + global_state, + )?; + predecessor_edge_blocks.push_back(predecessor_edge_block); + } + predecessor_edge_blocks.push_front(first_predecessor_edge_block); + heap_merge_report.validate(); + let predecessor_statements = heap_merge_report.into_remap_statements(position); + assert_eq!(predecessor_statements.len(), predecessors.len()); + assert_eq!(predecessor_edge_blocks.len(), predecessors.len()); + for (predecessor, (mut predecessor_edge_block, statements)) in + predecessors.iter().zip( + predecessor_edge_blocks + .into_iter() + .zip(predecessor_statements.into_iter()), + ) + { + predecessor_edge_block.extend(statements); + trace_builder.add_edge_block( + predecessor.clone(), + new_block.clone(), + predecessor_edge_block, + )?; + } + self.states.insert(new_block.clone(), state); + } + } + self.states + .get(new_block) + .unwrap() + .debug_print_memory_block(); + Ok(()) + } + + pub(super) fn finalize_block( + &mut self, + block: &vir_low::Label, + _expression_interner: &mut ExpressionInterner, + ) -> SpannedEncodingResult<()> { + let state = self.get_state_mut(block); + state.constraints.set_visited_block(block.clone()); + state.heap.finalize_block(&mut state.constraints)?; + Ok(()) + } + + pub(in super::super) fn get_state_mut(&mut self, block: &vir_low::Label) -> &mut State { + self.states.get_mut(block).unwrap() + } + + pub(in super::super) fn get_state(&self, block: &vir_low::Label) -> &State { + self.states.get(block).unwrap() + } + + pub(in super::super) fn uninitialize( + &mut self, + return_blocks: &[vir_low::Label], + ) -> SpannedEncodingResult<()> { + for return_block in return_blocks { + let heap_block = self.get_state(return_block); + if config::symbolic_execution_leak_check() { + heap_block.heap.leak_check()?; + } + } + std::mem::take(&mut self.states); + Ok(()) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/state/mod.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/state/mod.rs new file mode 100644 index 00000000000..67968f66e49 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/state/mod.rs @@ -0,0 +1,50 @@ +use self::block::State; +use super::{super::super::encoder_context::EncoderContext, ProcedureExecutor}; +use crate::encoder::errors::SpannedEncodingResult; + +use vir_crate::low::{self as vir_low}; + +mod block; +mod keeper; + +pub(super) use self::keeper::StateKeeper; + +impl<'a, 'c, EC: EncoderContext> ProcedureExecutor<'a, 'c, EC> { + pub(super) fn initialize_state_for( + &mut self, + new_block: &vir_low::Label, + ) -> SpannedEncodingResult<()> { + let predecessors = if let Some(predecessors) = self.reaching_predecessors.get(new_block) { + &**predecessors + } else { + &[] + }; + self.state_keeper.create_state_for_block( + new_block, + predecessors, + &mut self.expression_interner, + self.program_context, + &mut self.global_heap_state, + &mut self.trace_builder, + self.procedure.position, + )?; + Ok(()) + } + + pub(super) fn finalize_current_block(&mut self) -> SpannedEncodingResult<()> { + let current_block = self.current_block.as_ref().unwrap(); + self.state_keeper + .finalize_block(current_block, &mut self.expression_interner)?; + Ok(()) + } + + pub(super) fn current_state_mut(&mut self) -> &mut State { + let current_block = self.current_block.as_ref().unwrap(); + self.state_keeper.states.get_mut(current_block).unwrap() + } + + pub(super) fn current_state(&self) -> &State { + let current_block = self.current_block.as_ref().unwrap(); + self.state_keeper.states.get(current_block).unwrap() + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/statements/exhale.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/statements/exhale.rs new file mode 100644 index 00000000000..48443531970 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/statements/exhale.rs @@ -0,0 +1,32 @@ +use super::{super::super::super::encoder_context::EncoderContext, ProcedureExecutor}; +use crate::encoder::errors::SpannedEncodingResult; +use vir_crate::low::{self as vir_low}; + +impl<'a, 'c, EC: EncoderContext> ProcedureExecutor<'a, 'c, EC> { + pub(super) fn execute_exhale( + &mut self, + expression: &vir_low::Expression, + position: vir_low::Position, + exhale_label: &str, + ) -> SpannedEncodingResult<()> { + if let vir_low::Expression::BinaryOp(expression) = expression { + if expression.op_kind == vir_low::BinaryOpKind::And { + self.execute_exhale(&expression.left, position, exhale_label)?; + self.execute_exhale(&expression.right, position, exhale_label)?; + return Ok(()); + } + } + let expression = self.simplify_expression(expression, position)?; + if let vir_low::Expression::PredicateAccessPredicate(predicate) = &expression { + self.exhale_predicate(predicate.clone(), position)?; + return Ok(()); + } + let expression = expression.wrap_in_old(exhale_label); + for predicate_name in expression.collect_access_predicate_names() { + self.prepare_for_unhandled_exhale(&predicate_name, position)?; + } + self.try_assume_heap_independent_conjuncts(&expression)?; + self.add_statement(vir_low::Statement::exhale(expression, position))?; + Ok(()) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/statements/inhale.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/statements/inhale.rs new file mode 100644 index 00000000000..f81f062378c --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/statements/inhale.rs @@ -0,0 +1,31 @@ +use super::{super::super::super::encoder_context::EncoderContext, ProcedureExecutor}; +use crate::encoder::errors::SpannedEncodingResult; + +use vir_crate::low::{self as vir_low}; + +impl<'a, 'c, EC: EncoderContext> ProcedureExecutor<'a, 'c, EC> { + pub(super) fn execute_inhale( + &mut self, + expression: &vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + if let vir_low::Expression::BinaryOp(expression) = expression { + if expression.op_kind == vir_low::BinaryOpKind::And { + self.execute_inhale(&expression.left, position)?; + self.execute_inhale(&expression.right, position)?; + return Ok(()); + } + } + let expression = self.simplify_expression(expression, position)?; + if let vir_low::Expression::PredicateAccessPredicate(predicate) = &expression { + self.inhale_predicate(predicate.clone(), position)?; + return Ok(()); + } + // for predicate_name in expression.collect_access_predicate_names() { + // self.mark_predicate_instances_seen_qp_inhale(&predicate_name)?; + // } + self.try_assume_heap_independent_conjuncts(&expression)?; + self.add_statement(vir_low::Statement::inhale(expression, position))?; + Ok(()) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/statements/mod.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/statements/mod.rs new file mode 100644 index 00000000000..a1310b980ad --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/statements/mod.rs @@ -0,0 +1,170 @@ +use super::{super::super::encoder_context::EncoderContext, ProcedureExecutor}; +use crate::encoder::errors::SpannedEncodingResult; +use vir_crate::{ + common::expression::BinaryOperationHelpers, + low::{self as vir_low}, +}; + +mod inhale; +mod exhale; + +impl<'a, 'c, EC: EncoderContext> ProcedureExecutor<'a, 'c, EC> { + pub(super) fn add_statement( + &mut self, + statement: vir_low::Statement, + ) -> SpannedEncodingResult<()> { + let builder = self.current_builder_mut(); + builder.add_statement(statement) + } + + pub(super) fn add_statements( + &mut self, + statements: Vec, + ) -> SpannedEncodingResult<()> { + let builder = self.current_builder_mut(); + builder.add_statements(statements) + } + + pub(super) fn add_comment(&mut self, comment: String) -> SpannedEncodingResult<()> { + self.add_statement(vir_low::Statement::comment(comment)) + } + + pub(super) fn add_assume( + &mut self, + expression: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + self.add_statement(vir_low::Statement::assume(expression, position)) + } + + pub(super) fn execute_statement( + &mut self, + statement: &vir_low::Statement, + ) -> SpannedEncodingResult<()> { + match statement { + vir_low::Statement::Label(statement) => { + self.execute_statement_label(statement)?; + } + vir_low::Statement::Assign(statement) => { + self.execute_statement_assign(statement)?; + } + vir_low::Statement::Assume(statement) => { + self.execute_statement_assume(statement)?; + } + vir_low::Statement::Assert(statement) => { + self.execute_statement_assert(statement)?; + } + vir_low::Statement::Inhale(statement) => { + self.execute_statement_inhale(statement)?; + } + vir_low::Statement::Exhale(statement) => { + self.execute_statement_exhale(statement)?; + } + vir_low::Statement::Comment(_) | vir_low::Statement::LogEvent(_) => { + self.add_statement(statement.clone())?; + } + vir_low::Statement::Fold(_) + | vir_low::Statement::Unfold(_) + | vir_low::Statement::ApplyMagicWand(_) + | vir_low::Statement::MethodCall(_) + | vir_low::Statement::Conditional(_) => { + unreachable!(); + } + vir_low::Statement::MaterializePredicate(statement) => { + self.execute_materialize_predicate(statement)?; + } + vir_low::Statement::CaseSplit(statement) => { + self.execute_case_split(statement)?; + } + } + Ok(()) + } + + fn execute_statement_label( + &mut self, + statement: &vir_low::ast::statement::Label, + ) -> SpannedEncodingResult<()> { + self.save_state(statement.label.clone())?; + self.add_statement(vir_low::Statement::Label(statement.clone()))?; + Ok(()) + } + + fn execute_statement_assign( + &mut self, + statement: &vir_low::ast::statement::Assign, + ) -> SpannedEncodingResult<()> { + assert!(statement.value.is_constant()); + let target_variable = self.create_new_bool_variable_version(&statement.target.name)?; + let expression = + vir_low::Expression::equals(target_variable.into(), statement.value.clone()); + self.try_assume_heap_independent_conjuncts(&expression)?; + self.add_assume(expression, statement.position)?; + Ok(()) + } + + fn execute_statement_assume( + &mut self, + statement: &vir_low::ast::statement::Assume, + ) -> SpannedEncodingResult<()> { + let expression = self.simplify_expression(&statement.expression, statement.position)?; + self.try_assume_heap_independent_conjuncts(&expression)?; + self.add_statement(vir_low::Statement::assume(expression, statement.position))?; + Ok(()) + } + + fn execute_statement_assert( + &mut self, + statement: &vir_low::ast::statement::Assert, + ) -> SpannedEncodingResult<()> { + let expression = self.simplify_expression(&statement.expression, statement.position)?; + self.try_assume_heap_independent_conjuncts(&expression)?; + self.add_statement(vir_low::Statement::assert(expression, statement.position))?; + Ok(()) + } + + fn execute_statement_inhale( + &mut self, + statement: &vir_low::ast::statement::Inhale, + ) -> SpannedEncodingResult<()> { + self.execute_inhale(&statement.expression, statement.position)?; + self.current_builder_mut().set_materialization_point()?; + Ok(()) + } + + fn execute_statement_exhale( + &mut self, + statement: &vir_low::ast::statement::Exhale, + ) -> SpannedEncodingResult<()> { + let exhale_label = format!("exhale_label${}", self.exhale_label_generator_counter); + self.exhale_label_generator_counter += 1; + self.register_label(vir_low::Label::new(exhale_label.clone()))?; + let label = vir_low::ast::statement::Label::new(exhale_label.clone()); + self.execute_statement_label(&label)?; + self.execute_exhale(&statement.expression, statement.position, &exhale_label)?; + // self.current_builder_mut().set_materialization_point()?; + Ok(()) + } + + fn execute_materialize_predicate( + &mut self, + statement: &vir_low::ast::statement::MaterializePredicate, + ) -> SpannedEncodingResult<()> { + let vir_low::Expression::PredicateAccessPredicate(predicate) = self.simplify_expression(&statement.predicate, statement.position)? else { + unreachable!(); + }; + self.materialize_predicate(predicate, statement.check_that_exists, statement.position)?; + Ok(()) + } + + fn execute_case_split( + &mut self, + statement: &vir_low::ast::statement::CaseSplit, + ) -> SpannedEncodingResult<()> { + let expression = self.simplify_expression(&statement.expression, statement.position)?; + self.add_statement(vir_low::Statement::case_split( + expression, + statement.position, + ))?; + Ok(()) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/variables.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/variables.rs new file mode 100644 index 00000000000..7fa2ab599a7 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/procedure_executor/variables.rs @@ -0,0 +1,43 @@ +use super::{super::super::encoder_context::EncoderContext, ProcedureExecutor}; +use crate::encoder::errors::SpannedEncodingResult; + +use std::collections::BTreeMap; +use vir_crate::low::{self as vir_low}; + +impl<'a, 'c, EC: EncoderContext> ProcedureExecutor<'a, 'c, EC> { + pub(super) fn create_new_bool_variable_version( + &mut self, + variable_name: &str, + ) -> SpannedEncodingResult { + let version = self + .variable_versions + .versions + .entry(variable_name.to_string()) + .or_default(); + *version += 1; + let version = *version; + let variable = + vir_low::VariableDecl::new(format!("{variable_name}${version}"), vir_low::Type::Bool); + Ok(variable) + } +} + +#[derive(Debug, Clone, Default)] +pub(super) struct VariableVersions { + pub(super) versions: BTreeMap, +} + +impl VariableVersions { + pub(super) fn create_variable_decls(&self) -> Vec { + let mut variables = Vec::new(); + for (name, last_version) in &self.versions { + for version in 0..=*last_version { + variables.push(vir_low::VariableDecl::new( + format!("{name}${version}"), + vir_low::Type::Bool, + )); + } + } + variables + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/program_context.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/program_context.rs new file mode 100644 index 00000000000..6c6ccc352aa --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/program_context.rs @@ -0,0 +1,402 @@ +use crate::encoder::middle::core_proof::{ + predicates::{OwnedPredicateInfo, SnapshotFunctionInfo}, + snapshots::{SnapshotDomainInfo, SnapshotDomainsInfo}, + transformations::encoder_context::EncoderContext, +}; +use prusti_common::config; +use rustc_hash::{FxHashMap, FxHashSet}; +use std::collections::BTreeMap; +use vir_crate::{ + common::builtin_constants::MEMORY_BLOCK_PREDICATE_NAME, + low::{self as vir_low, operations::ty::Typed}, +}; + +pub(in super::super::super) struct ProgramContext<'a, EC: EncoderContext> { + domains: &'a [vir_low::DomainDecl], + domain_functions: FxHashMap, + functions: FxHashMap, + predicate_decls: FxHashMap, + snapshot_functions_to_predicates: BTreeMap, + predicates_to_snapshot_functions: BTreeMap, + predicates_to_snapshot_types: BTreeMap, + non_aliased_memory_block_addresses: &'a FxHashSet, + snapshot_domains_info: &'a SnapshotDomainsInfo, + constant_constructor_names: FxHashSet, + extensionality_gas_constant: &'a vir_low::Expression, + encoder: &'a mut EC, +} + +impl<'a, EC: EncoderContext> ProgramContext<'a, EC> { + pub(in super::super::super) fn new( + domains: &'a [vir_low::DomainDecl], + functions: &'a [vir_low::FunctionDecl], + predicate_decls: &'a [vir_low::PredicateDecl], + snapshot_domains_info: &'a SnapshotDomainsInfo, + predicate_info: BTreeMap, + non_aliased_memory_block_addresses: &'a FxHashSet, + extensionality_gas_constant: &'a vir_low::Expression, + encoder: &'a mut EC, + ) -> Self { + let mut snapshot_functions_to_predicates = BTreeMap::new(); + let mut predicates_to_snapshot_functions = BTreeMap::new(); + let mut predicates_to_snapshot_types = BTreeMap::new(); + for ( + predicate_name, + OwnedPredicateInfo { + current_snapshot_function: SnapshotFunctionInfo { function_name, .. }, + // We are not purifying the final snapshot function because it + // is already pure. + final_snapshot_function: _, + snapshot_type, + snapshot_range_function: _, + }, + ) in predicate_info + { + snapshot_functions_to_predicates.insert(function_name.clone(), predicate_name.clone()); + predicates_to_snapshot_functions.insert(predicate_name.clone(), function_name); + predicates_to_snapshot_types.insert(predicate_name, snapshot_type); + } + for function in functions { + if function.kind == vir_low::FunctionKind::MemoryBlockBytes { + let predicate_name = MEMORY_BLOCK_PREDICATE_NAME; + assert!(predicates_to_snapshot_functions + .insert(predicate_name.to_string(), function.name.clone()) + .is_none()); + } + } + Self { + constant_constructor_names: snapshot_domains_info + .snapshot_domains + .values() + .flat_map(|domain| domain.constant_constructor_name.clone()) + .collect(), + domain_functions: domains + .iter() + .flat_map(|domain| { + domain + .functions + .iter() + .map(move |function| (function.name.clone(), function)) + }) + .collect(), + domains, + snapshot_functions_to_predicates, + predicates_to_snapshot_functions, + predicates_to_snapshot_types, + functions: functions + .iter() + .map(|function| (function.name.clone(), function)) + .collect(), + predicate_decls: predicate_decls + .iter() + .map(|predicate| (predicate.name.clone(), predicate)) + .collect(), + non_aliased_memory_block_addresses, + snapshot_domains_info, + extensionality_gas_constant, + encoder, + } + } + + pub(in super::super::super) fn get_domains(&self) -> &'a [vir_low::DomainDecl] { + self.domains + } + + pub(in super::super::super) fn get_function(&self, name: &str) -> &'a vir_low::FunctionDecl { + self.functions + .get(name) + .unwrap_or_else(|| panic!("Function not found: {}", name,)) + } + + pub(super) fn get_snapshot_type(&self, predicate_name: &str) -> Option { + // FIXME: Code duplication with + // prusti-viper/src/encoder/middle/core_proof/transformations/custom_heap_encoding/heap_encoder/predicates.rs + let predicate = self.predicate_decls[predicate_name]; + match predicate.kind { + vir_low::PredicateKind::MemoryBlock => { + use vir_low::macros::*; + Some(ty!(Bytes)) + } + vir_low::PredicateKind::Owned => Some( + self.predicates_to_snapshot_types + .get(predicate_name) + .unwrap_or_else(|| unreachable!("predicate not found: {}", predicate_name)) + .clone(), + ), + vir_low::PredicateKind::CloseFracRef + | vir_low::PredicateKind::LifetimeToken + | vir_low::PredicateKind::WithoutSnapshotWhole + | vir_low::PredicateKind::WithoutSnapshotWholeNonAliased + // | vir_low::PredicateKind::WithoutSnapshotFrac + | vir_low::PredicateKind::DeadLifetimeToken + | vir_low::PredicateKind::EndBorrowViewShift => None, + } + } + + pub(in super::super::super) fn get_snapshot_predicate( + &self, + function_name: &str, + ) -> Option<&str> { + let function = self.get_function(function_name); + match function.kind { + vir_low::FunctionKind::MemoryBlockBytes => Some(MEMORY_BLOCK_PREDICATE_NAME), + vir_low::FunctionKind::CallerFor => todo!(), + vir_low::FunctionKind::SnapRange => todo!(), + vir_low::FunctionKind::Snap => self + .snapshot_functions_to_predicates + .get(function_name) + .map(|s| s.as_str()), + } + } + + pub(super) fn get_predicate_snapshot_function(&self, predicate_name: &str) -> &str { + self.predicates_to_snapshot_functions + .get(predicate_name) + .unwrap_or_else(|| panic!("Predicate snapshot function not found: {}", predicate_name)) + } + + pub(super) fn get_non_aliased_memory_block_addresses( + &self, + ) -> &'a FxHashSet { + self.non_aliased_memory_block_addresses + } + + pub(in super::super::super) fn get_predicate_kind( + &self, + predicate_name: &str, + ) -> vir_low::PredicateKind { + self.predicate_decls[predicate_name].kind + } + + pub(super) fn is_predicate_kind_non_aliased(&self, predicate_name: &str) -> bool { + let kind = self + .predicate_decls + .get(predicate_name) + .unwrap_or_else(|| panic!("{predicate_name}")) + .kind; + if kind.is_non_aliased() { + true + } else { + config::end_borrow_view_shift_non_aliased() + && matches!(kind, vir_low::PredicateKind::EndBorrowViewShift) + } + } + + pub(super) fn get_binary_operator( + &self, + snapshot_domain_name: &str, + function_name: &str, + ) -> Option { + self.snapshot_domains_info + .snapshot_domains + .get(snapshot_domain_name) + .and_then(|snapshot_domain| { + snapshot_domain.binary_operators.get(function_name).cloned() + }) + } + + pub(super) fn get_constant_constructor( + &self, + snapshot_domain_name: &str, + ) -> &'a vir_low::DomainFunctionDecl { + let constructor_name = self + .snapshot_domains_info + .snapshot_domains + .get(snapshot_domain_name) + .unwrap() + .constant_constructor_name + .as_ref() + .unwrap_or_else(|| panic!("not found: {snapshot_domain_name}")); + self.domain_functions[constructor_name] + } + + pub(super) fn get_constant_destructor( + &self, + snapshot_domain_name: &str, + ) -> &vir_low::DomainFunctionDecl { + let destructor_name = self + .snapshot_domains_info + .snapshot_domains + .get(snapshot_domain_name) + .unwrap() + .constant_destructor_name + .as_ref() + .unwrap_or_else(|| panic!("not found: {snapshot_domain_name}")); + self.domain_functions[destructor_name] + } + + pub(super) fn is_constant_constructor(&self, function_name: &str) -> bool { + self.constant_constructor_names.contains(function_name) + } + + pub(super) fn get_constant_constructor_names(&self) -> &FxHashSet { + &self.constant_constructor_names + } + + pub(super) fn predicate_snapshots_extensionality_call( + &self, + left: vir_low::Expression, + right: vir_low::Expression, + position: vir_low::Position, + ) -> Option { + // The domain may be missing if the type is trusted. + let domain_name = self + .snapshot_domains_info + .type_domains + .get(left.get_type())?; + let function_name = self + .snapshot_domains_info + .snapshot_domains + .get(domain_name) + .unwrap_or_else(|| panic!("not found: {}", domain_name)) + .snapshot_equality + .as_ref() + .unwrap_or_else(|| panic!("not found: {}", domain_name)); + let call = vir_low::Expression::domain_function_call( + domain_name, + function_name, + vec![left, right, self.extensionality_gas_constant.clone()], + vir_low::Type::Bool, + ); + Some(call.set_default_position(position)) + } + + pub(super) fn get_bool_domain_info(&self) -> (vir_low::Type, SnapshotDomainInfo) { + let bool_type = self + .snapshot_domains_info + .bool_type + .as_ref() + .unwrap() + .clone(); + let bool_domain = &self.snapshot_domains_info.type_domains[&bool_type]; + let domain_info = self.snapshot_domains_info.snapshot_domains[bool_domain].clone(); + (bool_type, domain_info) + } + + pub(super) fn env(&mut self) -> &mut impl EncoderContext { + self.encoder + } + + pub(super) fn is_place_option_type(&self, ty: &vir_low::Type) -> bool { + match ty { + vir_low::Type::Domain(vir_low::ty::Domain { name }) + if name == vir_crate::common::builtin_constants::PLACE_OPTION_DOMAIN_NAME => + { + true + } + _ => false, + } + } + + pub(super) fn is_address_type(&self, ty: &vir_low::Type) -> bool { + match ty { + vir_low::Type::Domain(vir_low::ty::Domain { name }) + if name == vir_crate::common::builtin_constants::ADDRESS_DOMAIN_NAME => + { + true + } + _ => false, + } + } + + pub(super) fn is_lifetime_type(&self, ty: &vir_low::Type) -> bool { + match ty { + vir_low::Type::Domain(vir_low::ty::Domain { name }) + if name == vir_crate::common::builtin_constants::LIFETIME_DOMAIN_NAME => + { + true + } + _ => false, + } + } + + pub(super) fn is_bytes_type(&self, ty: &vir_low::Type) -> bool { + match ty { + vir_low::Type::Domain(vir_low::ty::Domain { name }) + if name == vir_crate::common::builtin_constants::BYTES_DOMAIN_NAME => + { + true + } + _ => false, + } + } + + // pub(super) fn get_bytes_base<'e>(&self, expression: &'e vir_low::Expression) -> Option<&'e vir_low::VariableDecl> { + // match expression { + // vir_low::Expression::Local(expression) if self.is_bytes_type(&expression.variable.ty)=> { + // Some(&expression.variable) + // } + // vir_low::Expression::DomainFuncApp(expression) if expression.arguments.len() == 1 => { + // // FIXME: Properly check that I am using only destructors and constructors. + // self.get_bytes_base(&expression.arguments[0]) + // }, + // _ => None, + // } + // } + + pub(super) fn is_place_non_aliased(&self, place: &vir_low::Expression) -> bool { + assert_eq!(place.get_type(), &vir_low::macros::ty!(PlaceOption)); + match place { + vir_low::Expression::DomainFuncApp(domain_func_app) + if domain_func_app.arguments.len() == 1 => + { + let argument = &domain_func_app.arguments[0]; + if domain_func_app.function_name == "place_option_some" { + true + } else { + self.is_place_non_aliased(argument) + } + } + vir_low::Expression::DomainFuncApp(domain_func_app) => { + assert_eq!(domain_func_app.function_name, "place_option_none"); + false + } + // vir_low::Expression::LabelledOld(labelled_old) => self.is_place_non_aliased(&labelled_old.base), + _ => unreachable!("place: {place}"), + } + } + + pub(super) fn is_address_non_aliased(&self, address: &vir_low::Expression) -> bool { + assert_eq!(address.get_type(), &vir_low::macros::ty!(Address)); + if self.non_aliased_memory_block_addresses.contains(address) { + true + } else { + match address { + vir_low::Expression::DomainFuncApp(domain_func_app) + if domain_func_app.arguments.len() == 1 => + { + if domain_func_app.function_name.starts_with("field_address$") { + // FIXME: Instead of using a string match, lookup in the + // context. + self.is_address_non_aliased(&domain_func_app.arguments[0]) + } else if domain_func_app + .function_name + .starts_with("variant_address$") + { + // FIXME: Instead of using a string match, lookup in the + // context. + self.is_address_non_aliased(&domain_func_app.arguments[0]) + } else if domain_func_app + .function_name + .starts_with("destructor$Snap$ref$Unique$") + { + false + } else if domain_func_app + .function_name + .starts_with("destructor$Snap$ref$Shared$") + { + false + } else if domain_func_app + .function_name + .starts_with("destructor$Snap$ptr$") + { + false + } else { + unreachable!("address: {domain_func_app}") + } + } + _ => unreachable!("address: {address}"), + } + } + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/trace_builder.rs b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/trace_builder.rs new file mode 100644 index 00000000000..b5830a9bd7c --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/transformations/symbolic_execution_new/trace_builder.rs @@ -0,0 +1,45 @@ +use super::block_builder::BlockBuilder; +use crate::encoder::errors::SpannedEncodingResult; +use std::collections::BTreeMap; +use vir_crate::low::{self as vir_low}; + +pub(super) struct TraceBuilder { + pub(super) blocks: BTreeMap, + pub(super) edge_blocks: BTreeMap<(vir_low::Label, vir_low::Label), Vec>, +} + +impl TraceBuilder { + pub(super) fn new() -> SpannedEncodingResult { + let builder = Self { + blocks: BTreeMap::new(), + edge_blocks: BTreeMap::new(), + }; + Ok(builder) + } + + pub(super) fn add_block( + &mut self, + label: vir_low::Label, + block: BlockBuilder, + ) -> SpannedEncodingResult<()> { + self.blocks.insert(label, block); + Ok(()) + } + + // pub(super) fn get_block_mut( + // &mut self, + // label: &vir_low::Label, + // ) -> SpannedEncodingResult<&mut BlockBuilder> { + // Ok(self.blocks.get_mut(label).unwrap()) + // } + + pub(super) fn add_edge_block( + &mut self, + source: vir_low::Label, + target: vir_low::Label, + statements: Vec, + ) -> SpannedEncodingResult<()> { + self.edge_blocks.insert((source, target), statements); + Ok(()) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/triggers/interface.rs b/prusti-viper/src/encoder/middle/core_proof/triggers/interface.rs new file mode 100644 index 00000000000..0e1eaeb1abf --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/triggers/interface.rs @@ -0,0 +1,114 @@ +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::lowerer::{DomainsLowererInterface, Lowerer}, +}; +use vir_crate::{ + common::{expression::QuantifierHelpers, identifier::WithIdentifier}, + low::{self as vir_low, operations::ty::Typed}, +}; + +const DOMAIN_NAME: &str = "Triggers"; + +pub(in super::super) trait TriggersInterface { + fn trigger_expression( + &mut self, + expression: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult; + fn call_trigger_function( + &mut self, + function_name: &str, + arguments: Vec, + position: vir_low::Position, + ) -> SpannedEncodingResult; +} + +impl<'p, 'v: 'p, 'tcx: 'v> TriggersInterface for Lowerer<'p, 'v, 'tcx> { + fn trigger_expression( + &mut self, + expression: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult { + let identifier = expression.get_type().get_identifier(); + let function_name = format!("trigger${}", identifier); + if !self + .triggers_state + .encoded_triggering_functions + .contains(&identifier) + { + self.triggers_state + .encoded_triggering_functions + .insert(identifier); + use vir_low::macros::*; + var_decls!(value: {expression.get_type().clone()}); + let call = self.create_domain_func_app( + DOMAIN_NAME, + function_name.clone(), + vec![value.clone().into()], + vir_low::Type::Bool, + Default::default(), + )?; + let body = vir_low::Expression::forall( + vec![value], + vec![vir_low::Trigger::new(vec![call.clone()])], + call, + ); + let axiom = + vir_low::DomainAxiomDecl::new(None, format!("{}$definition", function_name), body); + self.declare_axiom(DOMAIN_NAME, axiom)?; + } + self.create_domain_func_app( + DOMAIN_NAME, + function_name, + vec![expression], + vir_low::Type::Bool, + position, + ) + } + + fn call_trigger_function( + &mut self, + function_name: &str, + arguments: Vec, + position: vir_low::Position, + ) -> SpannedEncodingResult { + if !self + .triggers_state + .encoded_triggering_functions + .contains(function_name) + { + self.triggers_state + .encoded_triggering_functions + .insert(function_name.to_string()); + let mut variables = Vec::new(); + for (i, argument) in arguments.iter().enumerate() { + variables.push(vir_low::VariableDecl::new( + format!("_{}", i), + argument.get_type().clone(), + )); + } + let call = self.create_domain_func_app( + DOMAIN_NAME, + function_name, + variables.iter().map(|v| v.clone().into()).collect(), + vir_low::Type::Bool, + Default::default(), + )?; + let body = vir_low::Expression::forall( + variables, + vec![vir_low::Trigger::new(vec![call.clone()])], + call, + ); + let axiom = + vir_low::DomainAxiomDecl::new(None, format!("{}$definition", function_name), body); + self.declare_axiom(DOMAIN_NAME, axiom)?; + } + self.create_domain_func_app( + DOMAIN_NAME, + function_name, + arguments, + vir_low::Type::Bool, + position, + ) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/triggers/mod.rs b/prusti-viper/src/encoder/middle/core_proof/triggers/mod.rs new file mode 100644 index 00000000000..eb3e3a76fc3 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/triggers/mod.rs @@ -0,0 +1,4 @@ +mod interface; +mod state; + +pub(super) use self::{interface::TriggersInterface, state::TriggersState}; diff --git a/prusti-viper/src/encoder/middle/core_proof/triggers/state.rs b/prusti-viper/src/encoder/middle/core_proof/triggers/state.rs new file mode 100644 index 00000000000..8825c4c4ee6 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/triggers/state.rs @@ -0,0 +1,6 @@ +use std::collections::BTreeSet; + +#[derive(Default)] +pub(in super::super) struct TriggersState { + pub(super) encoded_triggering_functions: BTreeSet, +} diff --git a/prusti-viper/src/encoder/middle/core_proof/type_layouts/interface.rs b/prusti-viper/src/encoder/middle/core_proof/type_layouts/interface.rs index 90c7c1bac67..adc84aec979 100644 --- a/prusti-viper/src/encoder/middle/core_proof/type_layouts/interface.rs +++ b/prusti-viper/src/encoder/middle/core_proof/type_layouts/interface.rs @@ -2,15 +2,24 @@ use crate::encoder::{ errors::SpannedEncodingResult, high::{type_layouts::HighTypeLayoutsEncoderInterface, types::HighTypeEncoderInterface}, middle::core_proof::{ - lowerer::Lowerer, - snapshots::{IntoBuiltinMethodSnapshot, IntoProcedureSnapshot, IntoSnapshot}, + lowerer::{DomainsLowererInterface, Lowerer}, + snapshots::{ + IntoBuiltinMethodSnapshot, IntoProcedureSnapshot, IntoSnapshot, + SnapshotValidityInterface, SnapshotValuesInterface, + }, }, }; +use rustc_hash::FxHashSet; use vir_crate::{ low as vir_low, middle::{self as vir_mid, operations::const_generics::WithConstArguments}, }; +#[derive(Default)] +pub(in super::super) struct TypeLayoutsState { + encoded_size_functions: FxHashSet, +} + pub(in super::super) trait TypeLayoutsInterface { fn size_type_mid(&mut self) -> SpannedEncodingResult; fn size_type(&mut self) -> SpannedEncodingResult; @@ -23,10 +32,24 @@ pub(in super::super) trait TypeLayoutsInterface { ty: &vir_mid::Type, generics: &impl WithConstArguments, ) -> SpannedEncodingResult; + /// The size multiplied by `repetitions`. + fn encode_type_size_expression_repetitions( + &mut self, + ty: &vir_mid::Type, + generics: &impl WithConstArguments, + repetitions: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult; fn encode_type_padding_size_expression( &mut self, ty: &vir_mid::Type, ) -> SpannedEncodingResult; + fn encode_size_function_call_with_axioms( + &mut self, + function_name: String, + arguments: Vec, + position: vir_low::Position, + ) -> SpannedEncodingResult; } impl<'p, 'v: 'p, 'tcx: 'v> TypeLayoutsInterface for Lowerer<'p, 'v, 'tcx> { @@ -57,6 +80,24 @@ impl<'p, 'v: 'p, 'tcx: 'v> TypeLayoutsInterface for Lowerer<'p, 'v, 'tcx> { ); size.to_builtin_method_snapshot(self) } + fn encode_type_size_expression_repetitions( + &mut self, + ty: &vir_mid::Type, + generics: &impl WithConstArguments, + repetitions: vir_low::Expression, + position: vir_low::Position, + ) -> SpannedEncodingResult { + let size = self.encode_type_size_expression2(ty, generics)?; + let size_type = self.size_type_mid()?; + self.construct_binary_op_snapshot( + vir_mid::BinaryOpKind::Mul, + &size_type, + &size_type, + repetitions, + size, + position, + ) + } fn encode_type_padding_size_expression( &mut self, ty: &vir_mid::Type, @@ -67,4 +108,33 @@ impl<'p, 'v: 'p, 'tcx: 'v> TypeLayoutsInterface for Lowerer<'p, 'v, 'tcx> { .encode_type_padding_size_expression_mid(mir_type)?; size.to_builtin_method_snapshot(self) } + fn encode_size_function_call_with_axioms( + &mut self, + function_name: String, + arguments: Vec, + position: vir_low::Position, + ) -> SpannedEncodingResult { + let return_type = self.size_type()?; + let call = self.create_domain_func_app( + "Size", + function_name.clone(), + arguments, + return_type, + position, + )?; + if !self + .type_layouts_state + .encoded_size_functions + .contains(&function_name) + { + self.type_layouts_state + .encoded_size_functions + .insert(function_name.clone()); + let size_type = self.size_type_mid()?; + let body = self.encode_snapshot_valid_call_for_type(call.clone(), &size_type)?; + let axiom = vir_low::DomainAxiomDecl::new(None, format!("{function_name}$valid"), body); + self.declare_axiom("Size", axiom)?; + } + Ok(call) + } } diff --git a/prusti-viper/src/encoder/middle/core_proof/type_layouts/mod.rs b/prusti-viper/src/encoder/middle/core_proof/type_layouts/mod.rs index fa6023f5c88..bf611bc66b0 100644 --- a/prusti-viper/src/encoder/middle/core_proof/type_layouts/mod.rs +++ b/prusti-viper/src/encoder/middle/core_proof/type_layouts/mod.rs @@ -1,3 +1,3 @@ mod interface; -pub(super) use self::interface::TypeLayoutsInterface; +pub(super) use self::interface::{TypeLayoutsInterface, TypeLayoutsState}; diff --git a/prusti-viper/src/encoder/middle/core_proof/types/interface.rs b/prusti-viper/src/encoder/middle/core_proof/types/interface.rs index aa2b046f5dd..1ce11b94319 100644 --- a/prusti-viper/src/encoder/middle/core_proof/types/interface.rs +++ b/prusti-viper/src/encoder/middle/core_proof/types/interface.rs @@ -15,11 +15,14 @@ use crate::encoder::{ high::types::HighTypeEncoderInterface, middle::core_proof::{ addresses::AddressesInterface, + footprint::{DerefOwned, DerefOwnedRange, FootprintInterface}, lowerer::{DomainsLowererInterface, Lowerer}, snapshots::{ - IntoPureSnapshot, IntoSnapshot, SnapshotAdtsInterface, SnapshotDomainsInterface, - SnapshotValidityInterface, SnapshotValuesInterface, + IntoPureSnapshot, IntoSnapshot, IntoSnapshotLowerer, SnapshotAdtsInterface, + SnapshotDomainsInterface, SnapshotValidityInterface, SnapshotValuesInterface, + ValidityAssertionToSnapshot, }, + type_layouts::TypeLayoutsInterface, }, }; use prusti_common::config; @@ -31,7 +34,7 @@ use vir_crate::{ identifier::WithIdentifier, }, low::{self as vir_low}, - middle as vir_mid, + middle::{self as vir_mid}, }; #[derive(Default)] @@ -42,30 +45,35 @@ pub(in super::super) struct TypesState { encoded_unary_operations: FxHashSet, } -trait Private { - fn ensure_type_definition_for_decl( - &mut self, - ty: &vir_mid::Type, - type_decl: &vir_mid::TypeDecl, - ) -> SpannedEncodingResult<()>; - fn declare_simplification_axiom( - &mut self, - ty: &vir_mid::Type, - variant: &str, - parameters: Vec, - parameter_type: &vir_mid::Type, - simplification_result: vir_low::Expression, - ) -> SpannedEncodingResult<()>; - fn declare_evaluation_axiom( - &mut self, - ty: &vir_mid::Type, - variant: &str, - parameters: Vec, - evaluation_result: vir_low::Expression, - ) -> SpannedEncodingResult<()>; -} +// trait Private { +// fn ensure_type_definition_for_decl( +// &mut self, +// ty: &vir_mid::Type, +// type_decl: &vir_mid::TypeDecl, +// ) -> SpannedEncodingResult<()>; +// fn declare_simplification_axiom( +// &mut self, +// ty: &vir_mid::Type, +// variant: &str, +// parameters: Vec, +// parameter_type: &vir_mid::Type, +// simplification_result: vir_low::Expression, +// ) -> SpannedEncodingResult<()>; +// fn declare_evaluation_axiom( +// &mut self, +// ty: &vir_mid::Type, +// variant: &str, +// parameters: Vec, +// evaluation_result: vir_low::Expression, +// ) -> SpannedEncodingResult<()>; +// // fn purify_structural_invariant( +// // &mut self, +// // structural_invariant: Vec, +// // field_count: usize, +// // ) -> SpannedEncodingResult>; +// } -impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { +impl<'p, 'v: 'p, 'tcx: 'v> Lowerer<'p, 'v, 'tcx> { fn ensure_type_definition_for_decl( &mut self, ty: &vir_mid::Type, @@ -130,8 +138,53 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { field.ty.to_snapshot(self)?, )); } + let parameters_with_validity = decl.fields.len(); + let invariant = if let Some(invariant) = &decl.structural_invariant { + let (deref_fields, deref_range_fields) = + self.structural_invariant_to_deref_fields(invariant)?; + for DerefOwned { + field_name, + field_type, + .. + } in &deref_fields + { + parameters.push(vir_low::VariableDecl::new(field_name, field_type.clone())); + } + for DerefOwnedRange { + field_name, + field_type, + .. + } in &deref_range_fields + { + parameters.push(vir_low::VariableDecl::new(field_name, field_type.clone())); + } + let mut validity_assertion_encoder = + ValidityAssertionToSnapshot::new((deref_fields, deref_range_fields)); + // let invariant = self.structural_invariant_to_pure_expression( + // invariant.clone(), + // ty, + // decl, + // &mut parameters, + // )?; + let mut conjuncts = Vec::new(); + for expression in invariant { + conjuncts.push( + validity_assertion_encoder + .expression_to_snapshot(self, expression, true)?, + ); + // conjuncts.push(expression.to_pure_bool_expression(self)?); + } + conjuncts.into_iter().conjoin() //.remove_acc_predicates() + } else { + true.into() + }; self.register_struct_constructor(&domain_name, parameters.clone())?; - self.encode_validity_axioms_struct(&domain_name, parameters, true.into())?; + self.encode_validity_axioms_struct_with_invariant( + &domain_name, + parameters, + parameters_with_validity, + invariant, + )?; } vir_mid::TypeDecl::Enum(decl) => { let mut variants = Vec::new(); @@ -159,27 +212,42 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { vir_mid::TypeDecl::Pointer(decl) => { self.ensure_type_definition(&decl.target_type)?; let address_type = self.address_type()?; - self.register_constant_constructor(&domain_name, address_type.clone())?; - self.encode_validity_axioms_primitive(&domain_name, address_type, true.into())?; + let mut parameters = vec![vir_low::VariableDecl::new("address", address_type)]; + if decl.target_type.is_slice() { + let len_type = self.size_type()?; + parameters.push(vir_low::VariableDecl::new("len", len_type)); + } + self.register_struct_constructor(&domain_name, parameters.clone())?; + // self.register_constant_constructor(&domain_name, address_type.clone())?; + // self.encode_validity_axioms_primitive(&domain_name, address_type, true.into())?; + self.encode_validity_axioms_struct(&domain_name, parameters)?; } - vir_mid::TypeDecl::Reference(reference) => { - self.ensure_type_definition(&reference.target_type)?; - let target_type = reference.target_type.to_snapshot(self)?; - if reference.uniqueness.is_unique() { - let parameters = vars! { + vir_mid::TypeDecl::Reference(decl) => { + self.ensure_type_definition(&decl.target_type)?; + let target_type = decl.target_type.to_snapshot(self)?; + if decl.uniqueness.is_unique() { + let mut parameters = vars! { address: Address, target_current: {target_type.clone()}, target_final: {target_type} }; + if decl.target_type.is_slice() { + let len_type = self.size_type()?; + parameters.push(vir_low::VariableDecl::new("len", len_type)); + } self.register_struct_constructor(&domain_name, parameters.clone())?; - self.encode_validity_axioms_struct(&domain_name, parameters, true.into())?; + self.encode_validity_axioms_struct(&domain_name, parameters)?; } else { - let parameters = vars! { + let mut parameters = vars! { address: Address, target_current: {target_type.clone()} }; + if decl.target_type.is_slice() { + let len_type = self.size_type()?; + parameters.push(vir_low::VariableDecl::new("len", len_type)); + } self.register_struct_constructor(&domain_name, parameters.clone())?; - self.encode_validity_axioms_struct(&domain_name, parameters, true.into())?; + self.encode_validity_axioms_struct(&domain_name, parameters)?; let no_alloc_parameters = vars! { target_current: {target_type} }; self.register_alternative_constructor_with_injectivity_axioms( &domain_name, @@ -187,18 +255,16 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { true, no_alloc_parameters.clone(), )?; + let parameters_with_validity = no_alloc_parameters.len(); self.encode_validity_axioms_struct_alternative_constructor( &domain_name, "no_alloc", no_alloc_parameters, + parameters_with_validity, true.into(), )?; } } - vir_mid::TypeDecl::Never => { - self.register_struct_constructor(&domain_name, Vec::new())?; - self.encode_validity_axioms_struct(&domain_name, Vec::new(), false.into())?; - } _ => unimplemented!("type: {:?}", type_decl), }; Ok(()) @@ -283,6 +349,313 @@ impl<'p, 'v: 'p, 'tcx: 'v> Private for Lowerer<'p, 'v, 'tcx> { } Ok(()) } + + /// Encodes the following axiom: + /// + /// $$$ + /// (first * mul >= second * mul) == (first >= second) + /// $$$ + /// + /// This holds because we are dealing with unsigned integers. + fn declare_usize_mul_simplification_axiom( + &mut self, + ty: &vir_mid::Type, + variant: &str, + first: vir_low::VariableDecl, + second: vir_low::VariableDecl, + mul: vir_low::VariableDecl, + parameter_type: &vir_mid::Type, + ) -> SpannedEncodingResult<()> { + let domain_name = self.encode_snapshot_domain_name(ty)?; + let bool_domain_name = self.encode_snapshot_domain_name(&vir_mid::Type::Bool)?; + let bool_type = vir_mid::Type::Bool.to_snapshot(self)?; + let snapshot_type = ty.to_snapshot(self)?; + let first_mul = vir_low::Expression::domain_function_call( + &domain_name, + self.snapshot_constructor_struct_alternative_name(&domain_name, variant)?, + vec![first.clone().into(), mul.clone().into()], + snapshot_type.clone(), + ); + let second_mul = vir_low::Expression::domain_function_call( + &domain_name, + self.snapshot_constructor_struct_alternative_name(&domain_name, variant)?, + vec![second.clone().into(), mul.clone().into()], + snapshot_type, + ); + // let body = vir_low::Expression::forall( + // vec![first, second, mul], + // vec![vir_low::Trigger::new(vec![source.clone()])], + // expr! { ([source] == [target]) }, + // ); + let ge_cmp_name = + self.encode_binary_op_variant(vir_mid::BinaryOpKind::GeCmp, parameter_type)?; + let le_cmp_name = + self.encode_binary_op_variant(vir_mid::BinaryOpKind::LeCmp, parameter_type)?; + { + let source = vir_low::Expression::domain_function_call( + &bool_domain_name, + self.snapshot_constructor_struct_alternative_name(&bool_domain_name, &ge_cmp_name)?, + vec![first_mul.clone(), second_mul.clone()], + bool_type.clone(), + ); + let target = vir_low::Expression::domain_function_call( + &bool_domain_name, + self.snapshot_constructor_struct_alternative_name(&bool_domain_name, &ge_cmp_name)?, + vec![first.clone().into(), second.clone().into()], + bool_type.clone(), + ); + let axiom = vir_low::DomainRewriteRuleDecl { + comment: None, + name: format!("{variant}$simplification_axiom_ge_cmp"), + variables: vec![first.clone(), second.clone(), mul.clone()], + egg_only: true, + triggers: None, + source, + target, + }; + // self.declare_axiom(&domain_name, axiom)?; + self.declare_rewrite_rule(&domain_name, axiom)?; + } + { + let source = vir_low::Expression::domain_function_call( + &bool_domain_name, + self.snapshot_constructor_struct_alternative_name(&bool_domain_name, &le_cmp_name)?, + vec![first_mul, second_mul], + bool_type.clone(), + ); + let target = vir_low::Expression::domain_function_call( + &bool_domain_name, + self.snapshot_constructor_struct_alternative_name(&bool_domain_name, &le_cmp_name)?, + vec![first.clone().into(), second.clone().into()], + bool_type.clone(), + ); + let axiom = vir_low::DomainRewriteRuleDecl { + comment: None, + name: format!("{variant}$simplification_axiom_le_cmp"), + variables: vec![first.clone(), second.clone(), mul], + egg_only: true, + triggers: None, + source, + target, + }; + // self.declare_axiom(&domain_name, axiom)?; + self.declare_rewrite_rule(&domain_name, axiom)?; + } + { + let source = vir_low::Expression::domain_function_call( + &bool_domain_name, + self.snapshot_constructor_struct_alternative_name(&bool_domain_name, &le_cmp_name)?, + vec![first.clone().into(), second.clone().into()], + bool_type.clone(), + ); + let target = vir_low::Expression::domain_function_call( + &bool_domain_name, + self.snapshot_constructor_struct_alternative_name(&bool_domain_name, &ge_cmp_name)?, + vec![second.clone().into(), first.clone().into()], + bool_type.clone(), + ); + let axiom = vir_low::DomainRewriteRuleDecl { + comment: None, + name: format!("{variant}$simplification_axiom_le_ge_commute"), + variables: vec![first.clone(), second.clone()], + egg_only: true, + triggers: None, + source, + target, + }; + // self.declare_axiom(&domain_name, axiom)?; + self.declare_rewrite_rule(&domain_name, axiom)?; + } + { + let source = vir_low::Expression::domain_function_call( + &bool_domain_name, + self.snapshot_constructor_struct_alternative_name(&bool_domain_name, &ge_cmp_name)?, + vec![first.clone().into(), second.clone().into()], + bool_type.clone(), + ); + let target = vir_low::Expression::domain_function_call( + &bool_domain_name, + self.snapshot_constructor_struct_alternative_name(&bool_domain_name, &le_cmp_name)?, + vec![second.clone().into(), first.clone().into()], + bool_type, + ); + let axiom = vir_low::DomainRewriteRuleDecl { + comment: None, + name: format!("{variant}$simplification_axiom_ge_le_commute"), + variables: vec![first, second], + egg_only: true, + triggers: None, + source, + target, + }; + // self.declare_axiom(&domain_name, axiom)?; + self.declare_rewrite_rule(&domain_name, axiom)?; + } + Ok(()) + } + + fn declare_commutativity_axiom( + &mut self, + ty: &vir_mid::Type, + variant: &str, + left: vir_low::VariableDecl, + right: vir_low::VariableDecl, + ) -> SpannedEncodingResult<()> { + let domain_name = self.encode_snapshot_domain_name(ty)?; + let op_constructor_1 = vir_low::Expression::domain_function_call( + &domain_name, + self.snapshot_constructor_struct_alternative_name(&domain_name, variant)?, + vec![left.clone().into(), right.clone().into()], + ty.to_snapshot(self)?, + ); + let op_constructor_2 = vir_low::Expression::domain_function_call( + &domain_name, + self.snapshot_constructor_struct_alternative_name(&domain_name, variant)?, + vec![right.clone().into(), left.clone().into()], + ty.to_snapshot(self)?, + ); + let axiom = vir_low::DomainRewriteRuleDecl { + comment: None, + name: format!("{variant}$commutativity_axiom"), + egg_only: true, + variables: vec![left, right], + triggers: None, + source: op_constructor_1, + target: op_constructor_2, + }; + self.declare_rewrite_rule(&domain_name, axiom)?; + Ok(()) + } + + fn declare_zero_axioms( + &mut self, + op: vir_low::BinaryOpKind, + ty: &vir_mid::Type, + variant: &str, + variable: vir_low::VariableDecl, + ) -> SpannedEncodingResult<()> { + let domain_name = self.encode_snapshot_domain_name(ty)?; + let zero = self.construct_constant_snapshot(ty, 0.into(), Default::default())?; + let (source, target) = match op { + vir_low::BinaryOpKind::Add | vir_low::BinaryOpKind::Sub => { + let source = vir_low::Expression::domain_function_call( + &domain_name, + self.snapshot_constructor_struct_alternative_name(&domain_name, variant)?, + vec![variable.clone().into(), zero], + ty.to_snapshot(self)?, + ); + let target = variable.clone().into(); + (source, target) + } + vir_low::BinaryOpKind::Mul + | vir_low::BinaryOpKind::Div + | vir_low::BinaryOpKind::Mod => { + let source = vir_low::Expression::domain_function_call( + &domain_name, + self.snapshot_constructor_struct_alternative_name(&domain_name, variant)?, + vec![zero.clone(), variable.clone().into()], + ty.to_snapshot(self)?, + ); + let target = zero; + (source, target) + } + _ => { + return Ok(()); // No zero axiom for non-numeric operators. + } + }; + let axiom = vir_low::DomainRewriteRuleDecl { + comment: None, + name: format!("{variant}$zero_axiom"), + egg_only: true, + variables: vec![variable], + triggers: None, + source, + target, + }; + self.declare_rewrite_rule(&domain_name, axiom)?; + Ok(()) + } + + // fn purify_structural_invariant( + // &mut self, + // structural_invariant: Vec, + // field_count: usize, + // ) -> SpannedEncodingResult> { + + // // TODO: Create deref fields in vir_high together with a required + // // structural invariant that links their values? Probably does not work + // // because I need different treatment in predicate and snapshot + // // encoders. + + // // TODO: Maybe a better idea would be to have code that computes a + // // footprint of an expression? Then I could also use it for pure + // // functions. + + // struct Purifier<'l, 'p, 'v, 'tcx> { + // lowerer: &'l mut Lowerer<'p, 'v, 'tcx>, + // field_count: usize, + // } + // impl<'l, 'p, 'v, 'tcx> vir_mid::visitors::ExpressionFolder for Purifier<'l, 'p, 'v, 'tcx> { + // fn fold_acc_predicate_enum( + // &mut self, + // acc_predicate: vir_mid::AccPredicate, + // ) -> vir_mid::Expression { + // match *acc_predicate.predicate { + // vir_mid::Predicate::LifetimeToken(_) => { + // unimplemented!() + // } + // vir_mid::Predicate::MemoryBlockStack(_) + // | vir_mid::Predicate::MemoryBlockStackDrop(_) + // | vir_mid::Predicate::MemoryBlockHeap(_) + // | vir_mid::Predicate::MemoryBlockHeapDrop(_) => true.into(), + // vir_mid::Predicate::OwnedNonAliased(predicate) => { + // match predicate.place { + // vir_mid::Expression::Deref(vir_mid::Deref { + // base: + // box vir_mid::Expression::Field(vir_mid::Field { + // box base, + // field, + // .. + // }), + // ty, + // position, + // }) => { + // // let parameter = vir_mid::VariableDecl::new( + // // format!("{}$deref", field.name), + // // ty, + // // ); + // let app = vir_mid::Expression::builtin_func_app( + // vir_mid::BuiltinFunc::IsValid, + // Vec::new(), + // vec![ + // vir_mid::Expression::field( + // base, + // vir_mid::FieldDecl { + // name: format!("{}$deref", field.name), + // index: self.field_count, + // ty, + // }, + // position, + // )], + // vir_mid::Type::Bool, + // position, + // ); + // self.field_count += 1; + // app + // // self.lowerer.encode_snapshot_valid_call_for_type(parameter.into(), ty)? + // } + // _ => unimplemented!(), + // } + // } + // } + // } + // } + // let mut purifier = Purifier { lowerer: self, field_count }; + // Ok(structural_invariant + // .into_iter() + // .map(|expression| purifier.fold_expression(expression)) + // .collect()) + // } } pub(in super::super) trait TypesInterface { @@ -310,6 +683,8 @@ impl<'p, 'v: 'p, 'tcx: 'v> TypesInterface for Lowerer<'p, 'v, 'tcx> { | vir_mid::Type::MInt | vir_mid::Type::MPerm | vir_mid::Type::Lifetime + | vir_mid::Type::MByte + | vir_mid::Type::MBytes ) { // Natively supported types, nothing to do. return Ok(()); @@ -332,10 +707,10 @@ impl<'p, 'v: 'p, 'tcx: 'v> TypesInterface for Lowerer<'p, 'v, 'tcx> { } fn encode_unary_op_variant( &mut self, - op: vir_mid::UnaryOpKind, + op_mid: vir_mid::UnaryOpKind, argument_type: &vir_mid::Type, ) -> SpannedEncodingResult { - let variant_name = format!("{}_{}", op, argument_type.get_identifier()); + let variant_name = format!("{}_{}", op_mid, argument_type.get_identifier()); if !self .types_state .encoded_unary_operations @@ -348,14 +723,16 @@ impl<'p, 'v: 'p, 'tcx: 'v> TypesInterface for Lowerer<'p, 'v, 'tcx> { let snapshot_type = argument_type.to_snapshot(self)?; let result_type = argument_type; let result_domain = self.encode_snapshot_domain_name(result_type)?; + let op = op_mid.to_snapshot(self)?; self.register_alternative_constructor( &result_domain, &variant_name, + Some(op), + None, false, vars! { argument: {snapshot_type.clone()} }, )?; // Simplification axioms. - let op = op.to_snapshot(self)?; let simplification = match argument_type { vir_mid::Type::Bool => { assert_eq!(op, vir_low::UnaryOpKind::Not); @@ -395,10 +772,11 @@ impl<'p, 'v: 'p, 'tcx: 'v> TypesInterface for Lowerer<'p, 'v, 'tcx> { } fn encode_binary_op_variant( &mut self, - op: vir_mid::BinaryOpKind, + op_mid: vir_mid::BinaryOpKind, argument_type: &vir_mid::Type, ) -> SpannedEncodingResult { - let variant_name = format!("{}_{}", op, argument_type.get_identifier()); + let variant_name = format!("{}_{}", op_mid, argument_type.get_identifier()); + // format!("{}_{}", op, argument_type.get_identifier()); if !self .types_state .encoded_binary_operations @@ -409,16 +787,18 @@ impl<'p, 'v: 'p, 'tcx: 'v> TypesInterface for Lowerer<'p, 'v, 'tcx> { .insert(variant_name.clone()); use vir_low::macros::*; let snapshot_type = argument_type.to_snapshot(self)?; - let result_type = op.get_result_type(argument_type); + let result_type = op_mid.get_result_type(argument_type); let result_domain = self.encode_snapshot_domain_name(result_type)?; + let op = op_mid.to_snapshot(self)?; self.register_alternative_constructor( &result_domain, &variant_name, + None, + Some(op), false, vars! { left: {snapshot_type.clone()}, right: {snapshot_type.clone()} }, )?; // Simplification axioms. - let op = op.to_snapshot(self)?; let constant_type = match argument_type { vir_mid::Type::Bool => Some(ty! { Bool }), vir_mid::Type::Int(_) => Some(ty! {Int}), @@ -426,32 +806,58 @@ impl<'p, 'v: 'p, 'tcx: 'v> TypesInterface for Lowerer<'p, 'v, 'tcx> { _ => None, }; if let Some(constant_type) = constant_type { - var_decls! { left: {constant_type.clone()}, right: {constant_type} }; - let result = vir_low::Expression::binary_op_no_pos(op, expr! {left}, expr! {right}); - self.declare_simplification_axiom( - result_type, - &variant_name, - vec![left, right], - argument_type, - result, - )?; - var_decls! {left: {snapshot_type.clone()}, right: {snapshot_type}}; + var_decls! { left_const: {constant_type.clone()}, right_const: {constant_type} }; + let result = vir_low::Expression::binary_op_no_pos( + op, + expr! {left_const}, + expr! {right_const}, + ); + var_decls! {left_snap: {snapshot_type.clone()}, right_snap: {snapshot_type.clone()}}; let destructor_left = self.obtain_constant_value( argument_type, - left.clone().into(), + left_snap.clone().into(), Default::default(), )?; let destructor_right = self.obtain_constant_value( argument_type, - right.clone().into(), + right_snap.clone().into(), Default::default(), )?; + self.declare_simplification_axiom( + result_type, + &variant_name, + vec![left_const, right_const], + argument_type, + result, + )?; self.declare_evaluation_axiom( result_type, &variant_name, - vec![left, right], + vec![left_snap.clone(), right_snap.clone()], vir_low::Expression::binary_op_no_pos(op, destructor_left, destructor_right), )?; + if argument_type == &vir_mid::Type::Int(vir_mid::ty::Int::Usize) + && op == vir_low::BinaryOpKind::Mul + { + var_decls! { mul_snap: {snapshot_type} }; + self.declare_usize_mul_simplification_axiom( + result_type, + &variant_name, + left_snap.clone(), + right_snap.clone(), + mul_snap, + argument_type, + )?; + } + if matches!(op, vir_low::BinaryOpKind::Add | vir_low::BinaryOpKind::Mul) { + self.declare_commutativity_axiom( + result_type, + &variant_name, + left_snap.clone(), + right_snap, + )?; + } + self.declare_zero_axioms(op, result_type, &variant_name, left_snap)?; } else if op == vir_low::BinaryOpKind::EqCmp { // FIXME: For now, we treat Rust's == as bit equality. var_decls! { left: {snapshot_type.clone()}, right: {snapshot_type} }; diff --git a/prusti-viper/src/encoder/middle/core_proof/utils/place_domain_encoder.rs b/prusti-viper/src/encoder/middle/core_proof/utils/place_domain_encoder.rs index d5d85b9b1ec..a794988d892 100644 --- a/prusti-viper/src/encoder/middle/core_proof/utils/place_domain_encoder.rs +++ b/prusti-viper/src/encoder/middle/core_proof/utils/place_domain_encoder.rs @@ -23,6 +23,11 @@ pub(in super::super) trait PlaceExpressionDomainEncoder { lowerer: &mut Lowerer, arg: vir_low::Expression, ) -> SpannedEncodingResult; + fn encode_labelled_old( + &mut self, + expression: &vir_mid::expression::LabelledOld, + lowerer: &mut Lowerer, + ) -> SpannedEncodingResult; fn encode_array_index_axioms( &mut self, base_type: &vir_mid::Type, @@ -82,6 +87,16 @@ pub(in super::super) trait PlaceExpressionDomainEncoder { *position, )? } + vir_mid::Expression::LabelledOld(expression) => { + self.encode_labelled_old(expression, lowerer)? + } + vir_mid::Expression::EvalIn(expression) => { + self.encode_expression(&expression.body, lowerer)? + } + vir_mid::Expression::AddrOf(expression) => { + let parent = expression.base.clone().drop_last_reference_dereference(); + self.encode_expression(&parent, lowerer)? + } x => unimplemented!("{}", x), }; Ok(result) diff --git a/prusti-viper/src/encoder/middle/core_proof/viewshifts/interface.rs b/prusti-viper/src/encoder/middle/core_proof/viewshifts/interface.rs new file mode 100644 index 00000000000..37d415570e8 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/viewshifts/interface.rs @@ -0,0 +1,283 @@ +use super::state::{ViewShiftBody, ViewShiftSignature}; +use crate::encoder::{ + errors::SpannedEncodingResult, + middle::core_proof::lowerer::{Lowerer, MethodsLowererInterface, PredicatesLowererInterface}, +}; +use rustc_hash::FxHashMap; +use vir_crate::low::{self as vir_low, operations::ty::Typed}; + +pub(in super::super) trait ViewShiftsInterface { + fn encode_view_shift_predicate( + &mut self, + name: &str, + arguments: Vec, + position: vir_low::Position, + ) -> SpannedEncodingResult; + + /// Encode a viewshift to be used in the postcondition of a method and + /// declare its dependencies: + /// + /// 1. An opaque predicate used as a resource for the viewshift. + /// 2. A helper method that applies the viewshift. + fn encode_view_shift_return( + &mut self, + name: &str, + arguments: Vec, + precondition: Vec, + postcondition: Vec, + predicate_kind: vir_low::PredicateKind, + position: vir_low::Position, + ) -> SpannedEncodingResult; + + fn encode_apply_view_shift( + &mut self, + name: &str, + condition: Option, + arguments: Vec, + position: vir_low::Position, + ) -> SpannedEncodingResult; +} + +impl<'p, 'v: 'p, 'tcx: 'v> Lowerer<'p, 'v, 'tcx> { + fn encode_predicate_name(&mut self, name: &str) -> String { + format!("view_shift${name}") + } + + fn encode_method_name(&mut self, name: &str) -> String { + format!("apply_view_shift${name}") + } + + fn encode_view_shift_predicate_and_apply_method( + &mut self, + name: &str, + arguments: Vec, + mut precondition: Vec, + mut postcondition: Vec, + predicate_kind: vir_low::PredicateKind, + position: vir_low::Position, + ) -> SpannedEncodingResult<()> { + let mut parameters = Vec::new(); + let mut replacements = FxHashMap::default(); + for (i, argument) in arguments.iter().enumerate() { + if let vir_low::Expression::Local(local) = argument { + parameters.push(local.variable.clone()); + } else { + let parameter = + vir_low::VariableDecl::new(format!("arg${i}"), argument.get_type().clone()); + let parameter_expression = parameter.clone().into(); + replacements.insert(argument.clone(), parameter_expression); + parameters.push(parameter); + } + } + // precondition = precondition + // .into_iter() + // .map(|expression| expression.replace_subexpressions(&replacements)) + // .collect(); + // postcondition = postcondition + // .into_iter() + // .map(|expression| expression.replace_subexpressions(&replacements)) + // .collect(); + precondition = self.apply_replacements(precondition, &replacements); + postcondition = self.apply_replacements(postcondition, &replacements); + let predicate_name = self.encode_predicate_name(name); + // let view_shift_predicate = vir_low::Expression::predicate_access_predicate( + // predicate_name.clone(), + // parameters + // .iter() + // .map(|parameter| parameter.clone().into()) + // .collect(), + // vir_low::Expression::full_permission(), + // position, + // ); + let view_shift_predicate = self.encode_view_shift_predicate( + name, + parameters + .iter() + .map(|parameter| parameter.clone().into()) + .collect(), + position, + )?; + let view_shift_predicate_decl = vir_low::PredicateDecl::new( + predicate_name, + predicate_kind, + // ::WithoutSnapshotWhole, + parameters.clone(), + None, + ); + self.declare_predicate(view_shift_predicate_decl)?; + precondition.push(view_shift_predicate); + let apply_view_shift_method = vir_low::MethodDecl::new( + self.encode_method_name(name), + vir_low::MethodKind::MirOperation, + parameters, + Vec::new(), + precondition, + postcondition, + None, + ); + self.declare_method(apply_view_shift_method)?; + Ok(()) + } + + fn construct_view_shift_signature( + &mut self, + name: &str, + arguments: &[vir_low::Expression], + ) -> ViewShiftSignature { + let types = arguments + .iter() + .map(|argument| argument.get_type().clone()) + .collect(); + (name.to_string(), types) + } + + fn construct_replacements( + &self, + arguments: &[vir_low::Expression], + ) -> FxHashMap { + let mut replacements = FxHashMap::default(); + for (i, argument) in arguments.iter().enumerate() { + let parameter = + vir_low::VariableDecl::new(format!("arg${i}"), argument.get_type().clone()); + let parameter_expression = parameter.clone().into(); + replacements.insert(argument.clone(), parameter_expression); + } + replacements + } + + fn apply_replacements( + &self, + expression: Vec, + replacements: &FxHashMap, + ) -> Vec { + expression + .into_iter() + .map(|expression| expression.replace_subexpressions(replacements)) + .collect() + } + + fn construct_view_shift_body( + &mut self, + _name: &str, + arguments: &[vir_low::Expression], + precondition: &[vir_low::Expression], + postcondition: &[vir_low::Expression], + ) -> ViewShiftBody { + let replacements = self.construct_replacements(arguments); + let precondition = self.apply_replacements(precondition.to_vec(), &replacements); + let postcondition = self.apply_replacements(postcondition.to_vec(), &replacements); + (precondition, postcondition) + } + + fn register_view_shift_body( + &mut self, + name: &str, + arguments: &[vir_low::Expression], + precondition: &[vir_low::Expression], + postcondition: &[vir_low::Expression], + ) { + if !cfg!(debug_assertions) { + return; + } + let signature = self.construct_view_shift_signature(name, arguments); + let body = self.construct_view_shift_body(name, arguments, precondition, postcondition); + self.view_shifts_state + .encoded_view_content + .insert(signature, body); + } + + fn assert_same_view_shift( + &mut self, + name: &str, + arguments: &[vir_low::Expression], + precondition: &[vir_low::Expression], + postcondition: &[vir_low::Expression], + ) { + if !cfg!(debug_assertions) { + return; + } + let signature = self.construct_view_shift_signature(name, arguments); + let body = self.construct_view_shift_body(name, arguments, precondition, postcondition); + let old_body = self + .view_shifts_state + .encoded_view_content + .get(&signature) + .unwrap(); + assert_eq!(body, *old_body); + } +} + +impl<'p, 'v: 'p, 'tcx: 'v> ViewShiftsInterface for Lowerer<'p, 'v, 'tcx> { + fn encode_view_shift_predicate( + &mut self, + name: &str, + arguments: Vec, + position: vir_low::Position, + ) -> SpannedEncodingResult { + let predicate_name = self.encode_predicate_name(name); + let predicate_access = vir_low::Expression::predicate_access_predicate( + predicate_name, + arguments, + vir_low::Expression::full_permission(), + position, + ); + Ok(predicate_access) + } + + fn encode_view_shift_return( + &mut self, + name: &str, + arguments: Vec, + precondition: Vec, + postcondition: Vec, + predicate_kind: vir_low::PredicateKind, + position: vir_low::Position, + ) -> SpannedEncodingResult { + let signature = self.construct_view_shift_signature(name, &arguments); + if !self + .view_shifts_state + .encoded_view_shifts + .contains(&signature) + { + self.register_view_shift_body(name, &arguments, &precondition, &postcondition); + self.encode_view_shift_predicate_and_apply_method( + name, + arguments.clone(), + precondition, + postcondition, + predicate_kind, + position, + )?; + self.view_shifts_state.encoded_view_shifts.insert(signature); + } else { + self.assert_same_view_shift(name, &arguments, &precondition, &postcondition); + } + // let predicate_name = self.encode_predicate_name(name); + // let view_shift_predicate = vir_low::Expression::predicate_access_predicate( + // predicate_name, + // arguments, + // vir_low::Expression::full_permission(), + // position, + // ); + let view_shift_predicate = self.encode_view_shift_predicate(name, arguments, position)?; + Ok(view_shift_predicate) + } + + fn encode_apply_view_shift( + &mut self, + name: &str, + condition: Option, + arguments: Vec, + position: vir_low::Position, + ) -> SpannedEncodingResult { + let method_name = self.encode_method_name(name); + let method_call = + vir_low::Statement::method_call(method_name, arguments, Vec::new(), position); + let statement = if let Some(condition) = condition { + vir_low::Statement::conditional(condition, vec![method_call], Vec::new(), position) + } else { + method_call + }; + Ok(statement) + } +} diff --git a/prusti-viper/src/encoder/middle/core_proof/viewshifts/mod.rs b/prusti-viper/src/encoder/middle/core_proof/viewshifts/mod.rs new file mode 100644 index 00000000000..6ae972ca5f0 --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/viewshifts/mod.rs @@ -0,0 +1,4 @@ +mod interface; +mod state; + +pub(super) use self::{interface::ViewShiftsInterface, state::ViewShiftsState}; diff --git a/prusti-viper/src/encoder/middle/core_proof/viewshifts/state.rs b/prusti-viper/src/encoder/middle/core_proof/viewshifts/state.rs new file mode 100644 index 00000000000..1da31037ddf --- /dev/null +++ b/prusti-viper/src/encoder/middle/core_proof/viewshifts/state.rs @@ -0,0 +1,12 @@ +use rustc_hash::{FxHashMap, FxHashSet}; +use vir_crate::low::{self as vir_low}; + +pub(super) type ViewShiftSignature = (String, Vec); +pub(super) type ViewShiftBody = (Vec, Vec); + +#[derive(Default)] +pub(in super::super) struct ViewShiftsState { + pub(super) encoded_view_shifts: FxHashSet, + /// Used to debug assert that signature uniquely identifies the viewshift. + pub(super) encoded_view_content: FxHashMap, +} diff --git a/prusti-viper/src/encoder/mir/constants/interface.rs b/prusti-viper/src/encoder/mir/constants/interface.rs index 41b3dd1e814..d8cefab7de2 100644 --- a/prusti-viper/src/encoder/mir/constants/interface.rs +++ b/prusti-viper/src/encoder/mir/constants/interface.rs @@ -66,8 +66,41 @@ impl<'v, 'tcx: 'v> ConstantsEncoderInterface<'tcx> for super::super::super::Enco let ty = self.encode_type_high(mir_type)?; vir_high::Expression::constructor_no_pos(ty, Vec::new()) } + ty::TyKind::Ref(_, ty, _) => match ty.kind() { + ty::TyKind::Str => match constant.literal { + mir::ConstantKind::Val( + mir::interpret::ConstValue::Slice { data, start, end }, + _, + ) => { + let bytes = data + .inner() + .inspect_with_uninit_and_ptr_outside_interpreter(start..end); + let string = std::str::from_utf8(bytes).unwrap().to_string(); + let ty = self.encode_type_high(mir_type)?; + vir_high::Expression::constant_no_pos( + vir_high::expression::ConstantValue::String(string), + ty, + ) + } + _ => { + error_unsupported!( + "unsupported constant type (3) {:?} {:?} {:?}", + mir_type.kind(), + ty.kind(), + constant.literal + ); + } + }, + _ => { + error_unsupported!( + "unsupported constant type (2) {:?} {:?}", + mir_type.kind(), + ty.kind() + ); + } + }, _ => { - error_unsupported!("unsupported constant type {:?}", mir_type.kind()); + error_unsupported!("unsupported constant type (1) {:?}", mir_type.kind()); } }; Ok(expr) diff --git a/prusti-viper/src/encoder/mir/contracts/contracts.rs b/prusti-viper/src/encoder/mir/contracts/contracts.rs index 55477369c19..69e476610e4 100644 --- a/prusti-viper/src/encoder/mir/contracts/contracts.rs +++ b/prusti-viper/src/encoder/mir/contracts/contracts.rs @@ -2,10 +2,7 @@ use super::borrows::BorrowInfo; use crate::encoder::places; use prusti_interface::{environment::Environment, specs::typed}; use prusti_rustc_interface::{ - hir::{ - def_id::{DefId, LocalDefId}, - Mutability, - }, + hir::{def_id::DefId, Mutability}, middle::{mir, ty::subst::SubstsRef}, }; use rustc_hash::FxHashMap; @@ -44,12 +41,13 @@ where } impl ProcedureContractGeneric { - pub fn functional_precondition<'a, 'tcx>( + fn specification<'a, 'tcx>( &'a self, + specification_item: &typed::SpecificationItem>, env: &'a Environment<'tcx>, substs: SubstsRef<'tcx>, ) -> Vec<(DefId, SubstsRef<'tcx>)> { - match &self.specification.pres { + match specification_item { typed::SpecificationItem::Empty => vec![], typed::SpecificationItem::Inherent(pres) | typed::SpecificationItem::Refined(_, pres) => pres @@ -77,39 +75,174 @@ impl ProcedureContractGeneric { } } + pub fn functional_precondition<'a, 'tcx>( + &'a self, + env: &'a Environment<'tcx>, + substs: SubstsRef<'tcx>, + ) -> Vec<(DefId, SubstsRef<'tcx>)> { + self.specification(&self.specification.pres, env, substs) + // match &self.specification.pres { + // typed::SpecificationItem::Empty => vec![], + // typed::SpecificationItem::Inherent(pres) + // | typed::SpecificationItem::Refined(_, pres) => pres + // .iter() + // .map(|inherent_def_id| (*inherent_def_id, substs)) + // .collect(), + // typed::SpecificationItem::Inherited(pres) => pres + // .iter() + // .map(|inherited_def_id| { + // ( + // *inherited_def_id, + // // This uses the substs of the current method and + // // resolves them to the substs of the trait; however, + // // we are actually resolving to a specification item. + // // This works because the generics of the specification + // // items are the same as the generics of the method on + // // which they are declared. + // env.query + // .find_trait_method_substs(self.def_id, substs) + // .unwrap() + // .1, + // ) + // }) + // .collect(), + // } + } + + pub fn structural_precondition<'a, 'tcx>( + &'a self, + env: &'a Environment<'tcx>, + substs: SubstsRef<'tcx>, + ) -> Vec<(DefId, SubstsRef<'tcx>)> { + self.specification(&self.specification.structural_pres, env, substs) + // match &self.specification.structural_pres { + // typed::SpecificationItem::Empty => vec![], + // typed::SpecificationItem::Inherent(pres) + // | typed::SpecificationItem::Refined(_, pres) => pres + // .iter() + // .map(|inherent_def_id| (*inherent_def_id, substs)) + // .collect(), + // typed::SpecificationItem::Inherited(pres) => pres + // .iter() + // .map(|inherited_def_id| { + // ( + // *inherited_def_id, + // // This uses the substs of the current method and + // // resolves them to the substs of the trait; however, + // // we are actually resolving to a specification item. + // // This works because the generics of the specification + // // items are the same as the generics of the method on + // // which they are declared. + // env.query + // .find_trait_method_substs(self.def_id, substs) + // .unwrap() + // .1, + // ) + // }) + // .collect(), + // } + } + pub fn functional_postcondition<'a, 'tcx>( &'a self, env: &'a Environment<'tcx>, substs: SubstsRef<'tcx>, ) -> Vec<(DefId, SubstsRef<'tcx>)> { - match &self.specification.posts { - typed::SpecificationItem::Empty => vec![], - typed::SpecificationItem::Inherent(posts) - | typed::SpecificationItem::Refined(_, posts) => posts - .iter() - .map(|inherent_def_id| (*inherent_def_id, substs)) - .collect(), - typed::SpecificationItem::Inherited(posts) => posts - .iter() - .map(|inherited_def_id| { - ( - *inherited_def_id, - // Same comment as `functional_precondition` applies. - env.query - .find_trait_method_substs(self.def_id, substs) - .unwrap() - .1, - ) - }) - .collect(), - } + self.specification(&self.specification.posts, env, substs) + // match &self.specification.posts { + // typed::SpecificationItem::Empty => vec![], + // typed::SpecificationItem::Inherent(posts) + // | typed::SpecificationItem::Refined(_, posts) => posts + // .iter() + // .map(|inherent_def_id| (*inherent_def_id, substs)) + // .collect(), + // typed::SpecificationItem::Inherited(posts) => posts + // .iter() + // .map(|inherited_def_id| { + // ( + // *inherited_def_id, + // // Same comment as `functional_precondition` applies. + // env.query + // .find_trait_method_substs(self.def_id, substs) + // .unwrap() + // .1, + // ) + // }) + // .collect(), + // } + } + + pub fn panic_postcondition<'a, 'tcx>( + &'a self, + env: &'a Environment<'tcx>, + substs: SubstsRef<'tcx>, + ) -> Vec<(DefId, SubstsRef<'tcx>)> { + self.specification(&self.specification.panic_posts, env, substs) + // match &self.specification.panic_posts { + // typed::SpecificationItem::Empty => vec![], + // typed::SpecificationItem::Inherent(posts) + // | typed::SpecificationItem::Refined(_, posts) => posts + // .iter() + // .map(|inherent_def_id| (*inherent_def_id, substs)) + // .collect(), + // typed::SpecificationItem::Inherited(posts) => posts + // .iter() + // .map(|inherited_def_id| { + // ( + // *inherited_def_id, + // // Same comment as `functional_precondition` applies. + // env.query + // .find_trait_method_substs(self.def_id, substs) + // .unwrap() + // .1, + // ) + // }) + // .collect(), + // } + } + + pub fn structural_postcondition<'a, 'tcx>( + &'a self, + env: &'a Environment<'tcx>, + substs: SubstsRef<'tcx>, + ) -> Vec<(DefId, SubstsRef<'tcx>)> { + self.specification(&self.specification.structural_posts, env, substs) + // match &self.specification.structural_posts { + // typed::SpecificationItem::Empty => vec![], + // typed::SpecificationItem::Inherent(posts) + // | typed::SpecificationItem::Refined(_, posts) => posts + // .iter() + // .map(|inherent_def_id| (*inherent_def_id, substs)) + // .collect(), + // typed::SpecificationItem::Inherited(posts) => posts + // .iter() + // .map(|inherited_def_id| { + // ( + // *inherited_def_id, + // // Same comment as `functional_precondition` applies. + // env.query + // .find_trait_method_substs(self.def_id, substs) + // .unwrap() + // .1, + // ) + // }) + // .collect(), + // } + } + + pub fn structural_panic_postcondition<'a, 'tcx>( + &'a self, + env: &'a Environment<'tcx>, + substs: SubstsRef<'tcx>, + ) -> Vec<(DefId, SubstsRef<'tcx>)> { + self.specification(&self.specification.structural_panic_posts, env, substs) } pub fn functional_termination_measure<'a, 'tcx>( &'a self, env: &'a Environment<'tcx>, substs: SubstsRef<'tcx>, - ) -> Option<(LocalDefId, SubstsRef<'tcx>)> { + ) -> Option<(DefId, SubstsRef<'tcx>)> { match self.specification.terminates { typed::SpecificationItem::Empty => None, typed::SpecificationItem::Inherent(t) | typed::SpecificationItem::Refined(_, t) => { @@ -128,6 +261,67 @@ impl ProcedureContractGeneric { } } + pub fn broken_precondition_invariants<'a, 'tcx>( + &'a self, + env: &'a Environment<'tcx>, + substs: SubstsRef<'tcx>, + ) -> Vec<(DefId, SubstsRef<'tcx>)> { + match &self.specification.broken_pres { + typed::SpecificationItem::Empty => vec![], + typed::SpecificationItem::Inherent(pres) + | typed::SpecificationItem::Refined(_, pres) => pres + .iter() + .map(|inherent_def_id| (*inherent_def_id, substs)) + .collect(), + typed::SpecificationItem::Inherited(pres) => pres + .iter() + .map(|inherited_def_id| { + ( + *inherited_def_id, + // This uses the substs of the current method and + // resolves them to the substs of the trait; however, + // we are actually resolving to a specification item. + // This works because the generics of the specification + // items are the same as the generics of the method on + // which they are declared. + env.query + .find_trait_method_substs(self.def_id, substs) + .unwrap() + .1, + ) + }) + .collect(), + } + } + + pub fn broken_postcondition_invariants<'a, 'tcx>( + &'a self, + env: &'a Environment<'tcx>, + substs: SubstsRef<'tcx>, + ) -> Vec<(DefId, SubstsRef<'tcx>)> { + match &self.specification.broken_posts { + typed::SpecificationItem::Empty => vec![], + typed::SpecificationItem::Inherent(posts) + | typed::SpecificationItem::Refined(_, posts) => posts + .iter() + .map(|inherent_def_id| (*inherent_def_id, substs)) + .collect(), + typed::SpecificationItem::Inherited(posts) => posts + .iter() + .map(|inherited_def_id| { + ( + *inherited_def_id, + // Same comment as `functional_precondition` applies. + env.query + .find_trait_method_substs(self.def_id, substs) + .unwrap() + .1, + ) + }) + .collect(), + } + } + pub fn pledges(&self) -> impl Iterator + '_ { self.specification .pledges diff --git a/prusti-viper/src/encoder/mir/errors/interface.rs b/prusti-viper/src/encoder/mir/errors/interface.rs index 0060e77e9df..df366d15f08 100644 --- a/prusti-viper/src/encoder/mir/errors/interface.rs +++ b/prusti-viper/src/encoder/mir/errors/interface.rs @@ -26,13 +26,14 @@ pub(crate) trait ErrorInterface { position: vir_high::Position, error_ctxt: ErrorCtxt, ) -> vir_high::Position; + fn get_error_context(&mut self, position: vir_high::Position) -> ErrorCtxt; fn set_surrounding_error_context( - &mut self, + &self, position: vir_high::Position, error_ctxt: ErrorCtxt, ) -> vir_high::Position; fn set_surrounding_error_context_for_expression( - &mut self, + &self, expression: vir_high::Expression, default_position: vir_high::Position, error_ctxt: ErrorCtxt, @@ -92,8 +93,12 @@ impl<'v, 'tcx: 'v> ErrorInterface for super::super::super::Encoder<'v, 'tcx> { self.error_manager().set_error(new_position, error_ctxt); new_position.into() } + fn get_error_context(&mut self, position: vir_high::Position) -> ErrorCtxt { + assert!(!position.is_default()); + self.error_manager().get_error(position.into()) + } fn set_surrounding_error_context( - &mut self, + &self, position: vir_high::Position, error_ctxt: ErrorCtxt, ) -> vir_high::Position { @@ -106,14 +111,14 @@ impl<'v, 'tcx: 'v> ErrorInterface for super::super::super::Encoder<'v, 'tcx> { /// 1. `default_position` if `position.is_default()`. /// 2. With surrounding error context otherwise. fn set_surrounding_error_context_for_expression( - &mut self, + &self, expression: vir_high::Expression, default_position: vir_high::Position, error_ctxt: ErrorCtxt, ) -> vir_high::Expression { assert!(!default_position.is_default()); struct Visitor<'p, 'v: 'p, 'tcx: 'v> { - encoder: &'p mut super::super::super::Encoder<'v, 'tcx>, + encoder: &'p super::super::super::Encoder<'v, 'tcx>, default_position: vir_high::Position, error_ctxt: ErrorCtxt, } diff --git a/prusti-viper/src/encoder/mir/places/interface.rs b/prusti-viper/src/encoder/mir/places/interface.rs index 80e2e901785..2faa363578e 100644 --- a/prusti-viper/src/encoder/mir/places/interface.rs +++ b/prusti-viper/src/encoder/mir/places/interface.rs @@ -175,7 +175,22 @@ impl<'v, 'tcx: 'v> PlacesEncoderInterface<'tcx> for super::super::super::Encoder .encode_place_type_high(mir_type) .with_span(declaration_span)?; expr = match element { - mir::ProjectionElem::Deref => vir_high::Expression::deref_no_pos(expr, ty), + mir::ProjectionElem::Deref => { + let parent_mir_type = { + let prev_place_ref = mir::PlaceRef { + local: place.local, + projection: &place.projection[..i], + }; + prev_place_ref.ty(mir, self.env().tcx()) + }; + if parent_mir_type.ty.is_box() { + // unimplemented!("element: {element:?}"); + let field = vir_high::FieldDecl::new("val_ref", 0usize, ty.clone()); + vir_high::Expression::field_no_pos(expr, field) + } else { + vir_high::Expression::deref_no_pos(expr, ty) + } + } mir::ProjectionElem::Field(field, _) => { let parent_mir_type = { let prev_place_ref = mir::PlaceRef { @@ -189,7 +204,9 @@ impl<'v, 'tcx: 'v> PlacesEncoderInterface<'tcx> for super::super::super::Encoder .with_span(declaration_span)?; if parent_type.is_union() { // We treat union fields as variants. - let union_decl = self.encode_type_def_high(&parent_type)?.unwrap_union(); + let union_decl = self + .encode_type_def_high(&parent_type, false)? + .unwrap_union(); let variant = &union_decl.variants[field.index()]; let variant_index: vir_high::ty::VariantIndex = variant.name.clone().into(); let variant_type = parent_type.variant(variant_index.clone()); @@ -503,31 +520,35 @@ impl<'v, 'tcx: 'v> PlacesEncoderInterface<'tcx> for super::super::super::Encoder | (ty::TyKind::Uint(_), ty::TyKind::Char) | (ty::TyKind::Uint(_), ty::TyKind::Int(_)) | (ty::TyKind::Uint(_), ty::TyKind::Uint(_)) => { - let mut encoded_operand = self + let encoded_operand = self .encode_operand_high(mir, operand, span) .with_span(span)?; - if prusti_common::config::check_overflows() { - // Check the cast - // FIXME: Should use a high function. - let function_name = self - .encode_cast_function_use(src_ty, dst_ty) - .with_span(span)?; - let position = - self.error_manager() - .register_error(span, ErrorCtxt::TypeCast, def_id); - let call = vir_high::Expression::function_call( - function_name, - vec![], // FIXME: This is probably wrong. - vec![encoded_operand], - destination_type, - ) - .set_default_position(position.into()); - return Ok(call); - } else { - // Don't check the cast - encoded_operand.set_type(destination_type); - encoded_operand - } + // if prusti_common::config::check_overflows() { // TODO: Not checking casts is handled in into_low layer. + // // Check the cast + // // FIXME: Should use a high function. + // let function_name = self + // .encode_cast_function_use(src_ty, dst_ty) + // .with_span(span)?; + let position = + self.error_manager() + .register_error(span, ErrorCtxt::TypeCast, def_id); + // let call = vir_high::Expression::function_call( + // function_name, + // vec![], // FIXME: This is probably wrong. + // vec![encoded_operand], + // destination_type, + // ) + // .set_default_position(position.into()); + let source_type = self.encode_type_high(src_ty)?; + let call = vir_high::Expression::builtin_func_app( + vir_high::BuiltinFunc::CastIntToInt, + vec![source_type, destination_type.clone()], + vec![encoded_operand], + destination_type, + position.into(), + ); + return Ok(call); + // } } _ => { diff --git a/prusti-viper/src/encoder/mir/procedures/encoder/builtin_function_encoder.rs b/prusti-viper/src/encoder/mir/procedures/encoder/builtin_function_encoder.rs index 4f2b3f9d56c..b01ed44a28f 100644 --- a/prusti-viper/src/encoder/mir/procedures/encoder/builtin_function_encoder.rs +++ b/prusti-viper/src/encoder/mir/procedures/encoder/builtin_function_encoder.rs @@ -13,6 +13,8 @@ pub(super) trait BuiltinFuncAppEncoder<'p, 'v, 'tcx> { destination: mir::Place<'tcx>, target: &Option, cleanup: &Option, + original_lifetimes: &mut BTreeSet, + derived_lifetimes: &mut BTreeMap>, ) -> SpannedEncodingResult; } @@ -29,6 +31,8 @@ impl<'p, 'v, 'tcx> BuiltinFuncAppEncoder<'p, 'v, 'tcx> for super::ProcedureEncod destination: mir::Place<'tcx>, target: &Option, cleanup: &Option, + original_lifetimes: &mut BTreeSet, + derived_lifetimes: &mut BTreeMap>, ) -> SpannedEncodingResult { let full_called_function_name = self .encoder @@ -36,113 +40,145 @@ impl<'p, 'v, 'tcx> BuiltinFuncAppEncoder<'p, 'v, 'tcx> for super::ProcedureEncod .name .get_absolute_item_name(called_def_id); - let make_manual_assign = - |encoder: &mut Self, - block_builder: &mut BasicBlockBuilder, - rhs_gen: &mut dyn FnMut(_, Vec, _) -> vir_high::Expression| - -> SpannedEncodingResult<()> { - let (target_place, target_block) = (destination, target.unwrap()); - let position = encoder - .encoder - .error_manager() - .register_error(span, ErrorCtxt::WritePlace, encoder.def_id) - .into(); - let encoded_target_place = encoder - .encoder - .encode_place_high(encoder.mir, target_place, None)? - .set_default_position(position); - let encoded_args = args - .iter() - .map(|arg| encoder.encode_statement_operand(location, arg)) - .collect::, _>>()?; - for encoded_arg in encoded_args.iter() { - let statement = vir_high::Statement::consume_no_pos(encoded_arg.clone()); - block_builder.add_statement(encoder.encoder.set_statement_error_ctxt( - statement, - span, - ErrorCtxt::ProcedureCall, - encoder.def_id, - )?); - } - let target_place_local = if let Some(target_place_local) = target_place.as_local() { - target_place_local - } else { - unimplemented!() - }; - let size = encoder.encoder.encode_type_size_expression( - encoder - .encoder - .get_local_type(encoder.mir, target_place_local)?, - )?; - let target_memory_block = vir_high::Predicate::memory_block_stack_no_pos( - encoded_target_place.clone(), - size, - ); - block_builder.add_statement(encoder.encoder.set_statement_error_ctxt( - vir_high::Statement::exhale_no_pos(target_memory_block), - span, - ErrorCtxt::ProcedureCall, - encoder.def_id, - )?); - let inhale_statement = vir_high::Statement::inhale_no_pos( - vir_high::Predicate::owned_non_aliased_no_pos(encoded_target_place.clone()), - ); + let make_manual_assign = |encoder: &mut Self, + block_builder: &mut BasicBlockBuilder, + original_lifetimes: &mut BTreeSet, + derived_lifetimes: &mut BTreeMap>, + rhs_gen: &mut dyn FnMut( + _, + Vec, + _, + ) -> vir_high::Expression| + -> SpannedEncodingResult<()> { + let (target_place, target_block) = (destination, target.unwrap()); + let position = encoder + .encoder + .error_manager() + .register_error(span, ErrorCtxt::WritePlace, encoder.def_id) + .into(); + let encoded_target_place = encoder + .encoder + .encode_place_high(encoder.mir, target_place, None)? + .set_default_position(position); + let encoded_args = args + .iter() + .map(|arg| encoder.encode_statement_operand_no_refs(block_builder, location, arg)) + .collect::, _>>()?; + for encoded_arg in encoded_args.iter() { + let statement = vir_high::Statement::consume_no_pos(encoded_arg.clone()); block_builder.add_statement(encoder.encoder.set_statement_error_ctxt( - inhale_statement, + statement, span, ErrorCtxt::ProcedureCall, encoder.def_id, )?); - let type_arguments = encoder + } + let target_place_local = if let Some(target_place_local) = target_place.as_local() { + target_place_local + } else { + unimplemented!() + }; + let size = encoder.encoder.encode_type_size_expression( + encoder .encoder - .encode_generic_arguments_high(called_def_id, call_substs) - .with_span(span)?; + .get_local_type(encoder.mir, target_place_local)?, + )?; + let target_memory_block = + vir_high::Predicate::memory_block_stack_no_pos(encoded_target_place.clone(), size); + block_builder.add_statement(encoder.encoder.set_statement_error_ctxt( + vir_high::Statement::exhale_predicate_no_pos(target_memory_block), + span, + ErrorCtxt::ProcedureCall, + encoder.def_id, + )?); + let inhale_statement = vir_high::Statement::inhale_predicate_no_pos( + vir_high::Predicate::owned_non_aliased_no_pos(encoded_target_place.clone()), + ); + block_builder.add_statement(encoder.encoder.set_statement_error_ctxt( + inhale_statement, + span, + ErrorCtxt::ProcedureCall, + encoder.def_id, + )?); + let type_arguments = encoder + .encoder + .encode_generic_arguments_high(called_def_id, call_substs) + .with_span(span)?; - let encoded_arg_expressions = - encoded_args.into_iter().map(|arg| arg.expression).collect(); + let encoded_arg_expressions = + encoded_args.into_iter().map(|arg| arg.expression).collect(); - let target_type = encoded_target_place.get_type().clone(); + let target_type = encoded_target_place.get_type().clone(); - let expression = vir_high::Expression::equals( - encoded_target_place, - rhs_gen(type_arguments, encoded_arg_expressions, target_type), - ); - let assume_statement = encoder.encoder.set_statement_error_ctxt( - vir_high::Statement::assume_no_pos(expression), - span, - ErrorCtxt::UnexpectedAssumeMethodPostcondition, - encoder.def_id, - )?; - block_builder.add_statement(encoder.encoder.set_statement_error_ctxt( - assume_statement, - span, - ErrorCtxt::ProcedureCall, - encoder.def_id, - )?); - encoder.encode_lft_for_block(target_block, location, block_builder)?; - let target_label = encoder.encode_basic_block_label(target_block); - let successor = vir_high::Successor::Goto(target_label); - block_builder.set_successor_jump(successor); - Ok(()) - }; + let expression = vir_high::Expression::equals( + encoded_target_place, + rhs_gen(type_arguments, encoded_arg_expressions, target_type), + ); + let assume_statement = encoder.encoder.set_statement_error_ctxt( + vir_high::Statement::assume_no_pos(expression), + span, + ErrorCtxt::UnexpectedAssumeMethodPostcondition, + encoder.def_id, + )?; + block_builder.add_statement(encoder.encoder.set_statement_error_ctxt( + assume_statement, + span, + ErrorCtxt::ProcedureCall, + encoder.def_id, + )?); + encoder.encode_lft_for_block( + target_block, + location, + block_builder, + original_lifetimes, + derived_lifetimes, + )?; + encoder.add_predecessor(location.block, target_block)?; + let target_label = encoder.encode_basic_block_label(target_block); + let successor = vir_high::Successor::Goto(target_label); + block_builder.set_successor_jump(successor); + Ok(()) + }; let make_builtin_call = |encoder: &mut Self, block_builder: &mut BasicBlockBuilder, + original_lifetimes: &mut BTreeSet, + derived_lifetimes: &mut BTreeMap>, function| -> SpannedEncodingResult<()> { - make_manual_assign(encoder, block_builder, &mut |ty_args, args, target_ty| { - vir_high::Expression::builtin_func_app_no_pos(function, ty_args, args, target_ty) - })?; + make_manual_assign( + encoder, + block_builder, + original_lifetimes, + derived_lifetimes, + &mut |ty_args, args, target_ty| { + vir_high::Expression::builtin_func_app_no_pos( + function, ty_args, args, target_ty, + ) + }, + )?; Ok(()) }; let make_binop = |encoder: &mut Self, block_builder: &mut BasicBlockBuilder, + original_lifetimes: &mut BTreeSet, + derived_lifetimes: &mut BTreeMap>, op_kind| -> SpannedEncodingResult<()> { - make_manual_assign(encoder, block_builder, &mut |_ty_args, args, _target_ty| { - vir_high::Expression::binary_op_no_pos(op_kind, args[0].clone(), args[1].clone()) - })?; + make_manual_assign( + encoder, + block_builder, + original_lifetimes, + derived_lifetimes, + &mut |_ty_args, args, _target_ty| { + vir_high::Expression::binary_op_no_pos( + op_kind, + args[0].clone(), + args[1].clone(), + ) + }, + )?; Ok(()) }; @@ -156,7 +192,7 @@ impl<'p, 'v, 'tcx> BuiltinFuncAppEncoder<'p, 'v, 'tcx> for super::ProcedureEncod }) { let lhs = self - .encode_statement_operand(location, &args[0])? + .encode_statement_operand_no_refs(block_builder, location, &args[0])? .expression; if lhs.get_type() == &vir_high::Type::Int(vir_high::ty::Int::Unbounded) { use vir_high::BinaryOpKind::*; @@ -169,7 +205,13 @@ impl<'p, 'v, 'tcx> BuiltinFuncAppEncoder<'p, 'v, 'tcx> for super::ProcedureEncod ]; for op in ops { if op_name == op.0 { - make_binop(self, block_builder, op.1)?; + make_binop( + self, + block_builder, + original_lifetimes, + derived_lifetimes, + op.1, + )?; return Ok(true); } } @@ -200,51 +242,149 @@ impl<'p, 'v, 'tcx> BuiltinFuncAppEncoder<'p, 'v, 'tcx> for super::ProcedureEncod unimplemented!(); } } - "prusti_contracts::Int::new" => { - make_builtin_call(self, block_builder, vir_high::BuiltinFunc::NewInt)? - } - "prusti_contracts::Int::new_usize" => { - make_builtin_call(self, block_builder, vir_high::BuiltinFunc::NewInt)? + "prusti_contracts::prusti_take_lifetime" => make_builtin_call( + self, + block_builder, + original_lifetimes, + derived_lifetimes, + vir_high::BuiltinFunc::TakeLifetime, + )?, + "prusti_contracts::prusti_set_lifetime_for_raw_pointer_reference_casts" => { + // Do nothing, this function is used only by the drop + // elaboration pass. } - "prusti_contracts::Map::::empty" => { - make_builtin_call(self, block_builder, vir_high::BuiltinFunc::EmptyMap)? + "prusti_contracts::prusti_attach_drop_lifetime" => { + // Do nothing, this function is used only by the drop + // elaboration pass. } - "prusti_contracts::Map::::insert" => { - make_builtin_call(self, block_builder, vir_high::BuiltinFunc::UpdateMap)? + "prusti_contracts::Int::new" + | "prusti_contracts::Int::new_usize" + | "prusti_contracts::Int::new_isize" => make_builtin_call( + self, + block_builder, + original_lifetimes, + derived_lifetimes, + vir_high::BuiltinFunc::NewInt, + )?, + "prusti_contracts::Int::to_usize" | "prusti_contracts::Int::to_isize" => { + let (source_type, destination_type) = match full_called_function_name.as_str() { + "prusti_contracts::Int::new" => ( + vir_high::Type::Int(vir_high::ty::Int::I64), + vir_high::Type::Int(vir_high::ty::Int::Unbounded), + ), + "prusti_contracts::Int::new_usize" => ( + vir_high::Type::Int(vir_high::ty::Int::Usize), + vir_high::Type::Int(vir_high::ty::Int::Unbounded), + ), + "prusti_contracts::Int::new_isize" => ( + vir_high::Type::Int(vir_high::ty::Int::Isize), + vir_high::Type::Int(vir_high::ty::Int::Unbounded), + ), + "prusti_contracts::Int::to_usize" => ( + vir_high::Type::Int(vir_high::ty::Int::Unbounded), + vir_high::Type::Int(vir_high::ty::Int::Usize), + ), + "prusti_contracts::Int::to_isize" => ( + vir_high::Type::Int(vir_high::ty::Int::Unbounded), + vir_high::Type::Int(vir_high::ty::Int::Isize), + ), + _ => unreachable!("no further int functions"), + }; + let ty_args = vec![source_type, destination_type]; + make_manual_assign( + self, + block_builder, + original_lifetimes, + derived_lifetimes, + &mut |_, args, target_ty| { + vir_high::Expression::builtin_func_app_no_pos( + vir_high::BuiltinFunc::CastIntToInt, + ty_args.clone(), + args, + target_ty, + ) + }, + )? } + "prusti_contracts::Map::::empty" => make_builtin_call( + self, + block_builder, + original_lifetimes, + derived_lifetimes, + vir_high::BuiltinFunc::EmptyMap, + )?, + "prusti_contracts::Map::::insert" => make_builtin_call( + self, + block_builder, + original_lifetimes, + derived_lifetimes, + vir_high::BuiltinFunc::UpdateMap, + )?, "prusti_contracts::Map::::delete" => { unimplemented!() } - "prusti_contracts::Map::::len" => { - make_builtin_call(self, block_builder, vir_high::BuiltinFunc::MapLen)? - } - "prusti_contracts::Map::::contains" => { - make_builtin_call(self, block_builder, vir_high::BuiltinFunc::MapContains)? - } - "prusti_contracts::Map::::lookup" => { - make_builtin_call(self, block_builder, vir_high::BuiltinFunc::LookupMap)? - } - "prusti_contracts::Seq::::empty" => { - make_builtin_call(self, block_builder, vir_high::BuiltinFunc::EmptySeq)? - } - "prusti_contracts::Seq::::single" => { - make_builtin_call(self, block_builder, vir_high::BuiltinFunc::SingleSeq)? - } - "prusti_contracts::Seq::::concat" => { - make_builtin_call(self, block_builder, vir_high::BuiltinFunc::ConcatSeq)? - } - "prusti_contracts::Seq::::lookup" => { - make_builtin_call(self, block_builder, vir_high::BuiltinFunc::LookupSeq)? - } - "prusti_contracts::Ghost::::new" => { - make_manual_assign(self, block_builder, &mut |_, args, _| args[0].clone())? - } + "prusti_contracts::Map::::len" => make_builtin_call( + self, + block_builder, + original_lifetimes, + derived_lifetimes, + vir_high::BuiltinFunc::MapLen, + )?, + "prusti_contracts::Map::::contains" => make_builtin_call( + self, + block_builder, + original_lifetimes, + derived_lifetimes, + vir_high::BuiltinFunc::MapContains, + )?, + "prusti_contracts::Map::::lookup" => make_builtin_call( + self, + block_builder, + original_lifetimes, + derived_lifetimes, + vir_high::BuiltinFunc::LookupMap, + )?, + "prusti_contracts::Seq::::empty" => make_builtin_call( + self, + block_builder, + original_lifetimes, + derived_lifetimes, + vir_high::BuiltinFunc::EmptySeq, + )?, + "prusti_contracts::Seq::::single" => make_builtin_call( + self, + block_builder, + original_lifetimes, + derived_lifetimes, + vir_high::BuiltinFunc::SingleSeq, + )?, + "prusti_contracts::Seq::::concat" => make_builtin_call( + self, + block_builder, + original_lifetimes, + derived_lifetimes, + vir_high::BuiltinFunc::ConcatSeq, + )?, + "prusti_contracts::Seq::::lookup" => make_builtin_call( + self, + block_builder, + original_lifetimes, + derived_lifetimes, + vir_high::BuiltinFunc::LookupSeq, + )?, + "prusti_contracts::Ghost::::new" => make_manual_assign( + self, + block_builder, + original_lifetimes, + derived_lifetimes, + &mut |_, args, _| args[0].clone(), + )?, "prusti_contracts::snapshot_equality" => { unreachable!(); } "std::ops::Index::index" | "core::ops::Index::index" => { let lhs = self - .encode_statement_operand(location, &args[0])? + .encode_statement_operand_no_refs(block_builder, location, &args[0])? .expression; let typ = match lhs.get_type() { vir_high::Type::Reference(vir_high::ty::Reference { target_type, .. }) => { @@ -253,18 +393,26 @@ impl<'p, 'v, 'tcx> BuiltinFuncAppEncoder<'p, 'v, 'tcx> for super::ProcedureEncod _ => unreachable!(), }; match typ { - vir_high::Type::Sequence(..) => { - make_builtin_call(self, block_builder, vir_high::BuiltinFunc::LookupSeq)? - } - vir_high::Type::Map(..) => { - make_builtin_call(self, block_builder, vir_high::BuiltinFunc::LookupMap)? - } + vir_high::Type::Sequence(..) => make_builtin_call( + self, + block_builder, + original_lifetimes, + derived_lifetimes, + vir_high::BuiltinFunc::LookupSeq, + )?, + vir_high::Type::Map(..) => make_builtin_call( + self, + block_builder, + original_lifetimes, + derived_lifetimes, + vir_high::BuiltinFunc::LookupMap, + )?, _ => return Ok(false), } } "std::cmp::PartialEq::eq" => { let lhs = self - .encode_statement_operand(location, &args[0])? + .encode_statement_operand_no_refs(block_builder, location, &args[0])? .expression; if matches!( lhs.get_type(), @@ -276,12 +424,197 @@ impl<'p, 'v, 'tcx> BuiltinFuncAppEncoder<'p, 'v, 'tcx> for super::ProcedureEncod .. }) ) { - make_binop(self, block_builder, vir_high::BinaryOpKind::EqCmp)?; + make_binop( + self, + block_builder, + original_lifetimes, + derived_lifetimes, + vir_high::BinaryOpKind::EqCmp, + )?; return Ok(true); } else { return Ok(false); } } + "std::mem::forget" | "core::mem::forget" => { + assert_eq!(args.len(), 1); + let operand = &args[0]; + let mir_place = match operand { + mir::Operand::Move(place) => *place, + mir::Operand::Copy(_) => { + unimplemented!("operand {operand:?} is copy"); + } + mir::Operand::Constant(_) => unimplemented!("operand {operand:?} is constant"), + }; + let original_place = + self.encoder + .encode_place_high(self.mir, mir_place, Some(span))?; + let mut deallocation = Vec::new(); // TODO: Clean-up. + let mut place = None; + { + // FIXME: This deletes the move assignment that causes the fold before mem::forget. + let statements = block_builder.borrow_statements_hack(); + let mut i = statements.len(); + while i > 0 { + i -= 1; + let statement = &statements[i]; + if let vir_high::Statement::MovePlace(assign) = statement { + assert_eq!(assign.target, original_place); + place = Some(assign.source.clone()); + statements.remove(i); + break; + } + } + } + let place = place.unwrap(); + + let position = self + .encoder + .error_manager() + .register_error(span, ErrorCtxt::MemForget, self.def_id) + .into(); + self.add_drop_impl_deallocation_statements( + &mut deallocation, + position, + place.clone(), + )?; + let is_zst = deallocation.is_empty(); + let local = mir_place.as_local().unwrap(); + let memory_block = self + .encoder + .encode_memory_block_for_local(self.mir, local)?; + let mut original_memory_block = memory_block.clone(); + let vir_high::Predicate::MemoryBlockStack(original_memory_block_ref) = &mut original_memory_block else { + unreachable!() + }; + assert!(place.is_local(), "unimplemented!"); + original_memory_block_ref.place = place; + let dealloc_statement = + vir_high::Statement::exhale_predicate_no_pos(memory_block.clone()); + deallocation.push(self.encoder.set_surrounding_error_context_for_statement( + dealloc_statement, + position, + ErrorCtxt::MemForget, + )?); + let alloc_statement = vir_high::Statement::inhale_predicate_no_pos(memory_block); + deallocation.push(self.encoder.set_surrounding_error_context_for_statement( + alloc_statement, + position, + ErrorCtxt::MemForget, + )?); + if is_zst { + let dealloc_statement_original = + vir_high::Statement::exhale_predicate_no_pos(original_memory_block.clone()); + deallocation.push(self.encoder.set_surrounding_error_context_for_statement( + dealloc_statement_original, + position, + ErrorCtxt::MemForget, + )?); + } + let alloc_statement_original = + vir_high::Statement::inhale_predicate_no_pos(original_memory_block); + deallocation.push(self.encoder.set_surrounding_error_context_for_statement( + alloc_statement_original, + position, + ErrorCtxt::MemForget, + )?); + + let encoder = self; + + // FIXME: This code is copy-paste. + let (target_place, target_block) = (destination, target.unwrap()); + let position = encoder + .encoder + .error_manager() + .register_error(span, ErrorCtxt::WritePlace, encoder.def_id) + .into(); + let encoded_target_place = encoder + .encoder + .encode_place_high(encoder.mir, target_place, None)? + .set_default_position(position); + block_builder.add_statements(deallocation); + // let encoded_args = args + // .iter() + // .map(|arg| encoder.encode_statement_operand_no_refs(block_builder, location, arg)) + // .collect::, _>>()?; + // for encoded_arg in encoded_args.iter() { + // let statement = vir_high::Statement::consume_no_pos(encoded_arg.clone()); + // block_builder.add_statement(encoder.encoder.set_statement_error_ctxt( + // statement, + // span, + // ErrorCtxt::ProcedureCall, + // encoder.def_id, + // )?); + // } + let target_place_local = if let Some(target_place_local) = target_place.as_local() { + target_place_local + } else { + unimplemented!() + }; + let size = encoder.encoder.encode_type_size_expression( + encoder + .encoder + .get_local_type(encoder.mir, target_place_local)?, + )?; + let target_memory_block = vir_high::Predicate::memory_block_stack_no_pos( + encoded_target_place.clone(), + size, + ); + block_builder.add_statement(encoder.encoder.set_statement_error_ctxt( + vir_high::Statement::exhale_predicate_no_pos(target_memory_block), + span, + ErrorCtxt::ProcedureCall, + encoder.def_id, + )?); + let inhale_statement = vir_high::Statement::inhale_predicate_no_pos( + vir_high::Predicate::owned_non_aliased_no_pos(encoded_target_place), + ); + block_builder.add_statement(encoder.encoder.set_statement_error_ctxt( + inhale_statement, + span, + ErrorCtxt::ProcedureCall, + encoder.def_id, + )?); + // let type_arguments = encoder + // .encoder + // .encode_generic_arguments_high(called_def_id, call_substs) + // .with_span(span)?; + + // let encoded_arg_expressions = + // encoded_args.into_iter().map(|arg| arg.expression).collect(); + + // let target_type = encoded_target_place.get_type().clone(); + + // let expression = vir_high::Expression::equals( + // encoded_target_place, + // rhs_gen(type_arguments, encoded_arg_expressions, target_type), + // ); + // let assume_statement = encoder.encoder.set_statement_error_ctxt( + // vir_high::Statement::assume_no_pos(expression), + // span, + // ErrorCtxt::UnexpectedAssumeMethodPostcondition, + // encoder.def_id, + // )?; + // block_builder.add_statement(encoder.encoder.set_statement_error_ctxt( + // assume_statement, + // span, + // ErrorCtxt::ProcedureCall, + // encoder.def_id, + // )?); + encoder.encode_lft_for_block( + target_block, + location, + block_builder, + original_lifetimes, + derived_lifetimes, + )?; + encoder.add_predecessor(location.block, target_block)?; + let target_label = encoder.encode_basic_block_label(target_block); + let successor = vir_high::Successor::Goto(target_label); + block_builder.set_successor_jump(successor); + + return Ok(true); + } _ => return Ok(false), }; Ok(true) diff --git a/prusti-viper/src/encoder/mir/procedures/encoder/check_mode_converters.rs b/prusti-viper/src/encoder/mir/procedures/encoder/check_mode_converters.rs new file mode 100644 index 00000000000..4dc903e600a --- /dev/null +++ b/prusti-viper/src/encoder/mir/procedures/encoder/check_mode_converters.rs @@ -0,0 +1,231 @@ +use super::ProcedureEncoder; +use crate::encoder::errors::SpannedEncodingResult; + +use vir_crate::high::{self as vir_high}; + +impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { + /// Convert expression to the one usable for the current check mode: + /// + /// * For `Both` and `Specifications`: keep the expression unchanged. + /// * For `CoreProof` keep only the raw pointer dereferences because we need + /// to check that they are framed. + /// + /// If `disallow_permissions` is true, then checks that the expression does + /// not contain accesibility predicates. + pub(super) fn convert_expression_to_check_mode( + &mut self, + expression: vir_high::Expression, + _disallow_permissions: bool, + _allow_specs_in_memory_safety: bool, + _framing_variables: &[vir_high::VariableDecl], + ) -> SpannedEncodingResult> { + Ok(Some(expression)) + // if disallow_permissions && !expression.is_pure() { + // let span = self + // .encoder + // .error_manager() + // .position_manager() + // .get_span(expression.position().into()) + // .cloned() + // .unwrap(); + // return Err(SpannedEncodingError::incorrect( + // "only unsafe functions can use permissions in their contracts", + // span, + // )); + // } + // match self.check_mode { + // CheckMode::MemorySafety => { + // // Unsafe functions are checked with `CheckMode::UnsafeSafety`. For all + // // other functions it is forbidden to have accessibility + // // predicates in their contracts. + // assert!(disallow_permissions); + // // Framing will be checked with `CheckMode::MemorySafetyWithFunctional`. + // if allow_specs_in_memory_safety { + // Ok(Some(expression)) + // } else { + // Ok(None) + // } + // } + // CheckMode::MemorySafetyWithFunctional => { + // // Unsafe functions are checked with `CheckMode::UnsafeSafety`. For all + // // other functions it is forbidden to have accessibility + // // predicates in their contracts. + // assert!(disallow_permissions); + // // Framing is checked automatically by the encoding. + // Ok(Some(expression)) + // } // CheckMode::PurificationFunctional => { + // // unreachable!("outdated code"); + // // // // Unsafe functions are checked with `CheckMode::UnsafeSafety`. For all + // // // // other functions it is forbidden to have accessibility + // // // // predicates in their contracts. + // // // assert!(disallow_permissions); + // // // Ok(Some(expression)) + // // } + // // CheckMode::PurificationSoudness => { + // // unreachable!("outdated code"); + // // // // Check comment for `CheckMode::PurificationFunctional`. + // // // assert!(disallow_permissions); + // // // // Even though we forbid accessibility predicates in safe + // // // // functions, we may still have raw pointers in specifications + // // // // that are framed by type invariants. + // // // let dereferenced_places = expression.collect_guarded_dereferenced_places(); + // // // if dereferenced_places.is_empty() { + // // // Ok(None) + // // // } else { + // // // let framing_places: Vec = framing_variables + // // // .iter() + // // // .map(|var| var.clone().into()) + // // // .collect(); + // // // let check = construct_framing_assertion( + // // // self.encoder, + // // // dereferenced_places, + // // // &framing_places, + // // // )?; + // // // Ok(Some(check)) + // // // } + // // } + // // CheckMode::UnsafeSafety => { + // // // Framing is checked automatically by the encoding. + // // Ok(Some(expression)) + // // } + // } + } + + pub(super) fn convert_expression_to_check_mode_call_site( + &mut self, + expression: vir_high::Expression, + _is_unsafe: bool, + _is_checked: bool, + _framing_arguments: &[vir_high::Expression], + ) -> SpannedEncodingResult> { + Ok(Some(expression)) + // match self.check_mode { + // CheckMode::MemorySafety => { + // if is_unsafe || is_checked { + // // We are calling an unsafe function from a safe one. + // Ok(Some(expression)) + // } else { + // Ok(None) + // } + // } + // CheckMode::MemorySafetyWithFunctional + // // | CheckMode::PurificationFunctional + // // | CheckMode::UnsafeSafety + // => + // Ok(Some(expression)), + // // CheckMode::PurificationSoudness => { + // // unimplemented!(); + // // // let dereferenced_places = expression.collect_guarded_dereferenced_places(); + // // // let check = if dereferenced_places.is_empty() { + // // // if is_unsafe { + // // // Some(expression) + // // // } else { + // // // None + // // // } + // // // } else { + // // // let check = construct_framing_assertion( + // // // self.encoder, + // // // dereferenced_places, + // // // framing_arguments, + // // // )?; + // // // if is_unsafe { + // // // Some(vir_high::Expression::and(expression, check)) + // // // } else { + // // // Some(check) + // // // } + // // // }; + // // // Ok(check) + // // } + // } + } +} + +// fn construct_framing_assertion( +// encoder: &mut Encoder, +// dereferenced_places: Vec<(vir_high::Expression, vir_high::Expression)>, +// framing_places: &[vir_high::Expression], +// ) -> SpannedEncodingResult { +// let type_invariant_framing_places = +// construct_type_invariant_framing_places(encoder, framing_places)?; +// let mut type_invariant_framed_places = Vec::new(); +// for (guard, place) in dereferenced_places { +// if is_framed(&place, &type_invariant_framing_places) { +// let function = vir_high::Expression::builtin_func_app( +// vir_high::BuiltinFunc::EnsureOwnedPredicate, +// Vec::new(), +// vec![place.clone()], +// vir_high::Type::Bool, +// place.position(), +// ); +// let check = vir_high::Expression::implies(guard, function); +// type_invariant_framed_places.push(check); +// } else { +// unimplemented!("Outdated code."); +// // let span = encoder +// // .error_manager() +// // .position_manager() +// // .get_span(place.position().into()) +// // .cloned() +// // .unwrap(); +// // return Err(SpannedEncodingError::incorrect( +// // "the place must be framed by permissions", +// // span, +// // )); +// } +// } +// Ok(type_invariant_framed_places.into_iter().conjoin()) +// } + +// fn construct_type_invariant_framing_places( +// encoder: &mut Encoder, +// framing_places: &[vir_high::Expression], +// ) -> SpannedEncodingResult> { +// let type_invariant_framing_places = Vec::new(); +// for framing_place in framing_places { +// if framing_place.get_type().is_struct() { +// let type_decl = encoder +// .encode_type_def_high(framing_place.get_type(), true)? +// .unwrap_struct(); +// if let Some(invariants) = type_decl.structural_invariant { +// for expression in invariants { +// let _expression = expression.replace_self(framing_place); +// unimplemented!("Outdated code?"); +// // type_invariant_framing_places.extend(expression.collect_owned_places()); +// } +// } +// } +// } +// Ok(type_invariant_framing_places) +// } + +// fn is_framed( +// place: &vir_high::Expression, +// type_invariant_framing_places: &[vir_high::Expression], +// ) -> bool { +// for framing_place in type_invariant_framing_places { +// if is_framed_rec(framing_place, place, type_invariant_framing_places) { +// return true; +// } +// } +// false +// } + +// fn is_framed_rec( +// framing_place: &vir_high::Expression, +// place: &vir_high::Expression, +// type_invariant_framing_places: &[vir_high::Expression], +// ) -> bool { +// if framing_place == place { +// if let Some(pointer_place) = place.get_last_dereferenced_pointer() { +// is_framed(pointer_place, type_invariant_framing_places) +// } else { +// true +// } +// } else if place.is_deref() { +// false +// } else if let Some(parent) = place.get_parent_ref() { +// is_framed_rec(framing_place, parent, type_invariant_framing_places) +// } else { +// true +// } +// } diff --git a/prusti-viper/src/encoder/mir/procedures/encoder/elaborate_drops/mod.rs b/prusti-viper/src/encoder/mir/procedures/encoder/elaborate_drops/mod.rs index 632bd047d19..7ce6f6ea16b 100644 --- a/prusti-viper/src/encoder/mir/procedures/encoder/elaborate_drops/mod.rs +++ b/prusti-viper/src/encoder/mir/procedures/encoder/elaborate_drops/mod.rs @@ -1,3 +1,4 @@ +use self::pointer_reborrow::add_pointer_reborrow_facts; use crate::encoder::{ errors::{SpannedEncodingError, SpannedEncodingResult}, Encoder, @@ -18,8 +19,9 @@ use prusti_rustc_interface::{hir::def_id::DefId, middle::mir}; mod mir_dataflow; pub(super) mod mir_transform; +mod pointer_reborrow; -pub(super) fn elaborate_drops<'v, 'tcx: 'v>( +pub(super) fn get_and_elaborate_mir<'v, 'tcx: 'v>( encoder: &mut Encoder<'v, 'tcx>, def_id: DefId, procedure: &Procedure<'tcx>, @@ -44,7 +46,7 @@ pub(super) fn elaborate_drops<'v, 'tcx: 'v>( if config::dump_debug_info() { let local_def_id = def_id.expect_local(); let def_path = encoder.env().query.hir().def_path(local_def_id); - let graph = to_graphviz(&input_facts, &location_table, mir); + let graph = to_graphviz(&input_facts, &location_table, mir, &Vec::new()); prusti_common::report::log::report_with_writer( "graphviz_mir_dump_before_patch", format!("{}.dot", def_path.to_filename_friendly_no_crate()), @@ -57,12 +59,18 @@ pub(super) fn elaborate_drops<'v, 'tcx: 'v>( // but now it takes a mutable ref, so the dirty fix is to clone. let mut mir = mir.clone(); let drop_patch = self::mir_transform::run_pass(tcx, &mut mir); - let mir = apply_patch(drop_patch, &mir, &mut input_facts, &mut location_table); + let (mir, replace_terminator_locations) = + apply_patch(drop_patch, &mir, &mut input_facts, &mut location_table); if config::dump_debug_info() { let local_def_id = def_id.expect_local(); let def_path = encoder.env().query.hir().def_path(local_def_id); - let graph = to_graphviz(&input_facts, &location_table, &mir); + let graph = to_graphviz( + &input_facts, + &location_table, + &mir, + &replace_terminator_locations, + ); prusti_common::report::log::report_with_writer( "graphviz_mir_dump_after_patch", format!("{}.dot", def_path.to_filename_friendly_no_crate()), @@ -72,7 +80,34 @@ pub(super) fn elaborate_drops<'v, 'tcx: 'v>( validate(&input_facts, &location_table, &mir); - let lifetimes = Lifetimes::new(input_facts, location_table); + // When reborrowing a place whose last component is a raw pointer + // dereference, add a constraint that the lifetime for which the place is + // borrowed is shorter than the lifetime of the last reference. + + add_pointer_reborrow_facts(encoder, &mut input_facts, &mut location_table, &mir)?; + + if config::dump_debug_info() { + let local_def_id = def_id.expect_local(); + let def_path = encoder.env().query.hir().def_path(local_def_id); + let graph = to_graphviz( + &input_facts, + &location_table, + &mir, + &replace_terminator_locations, + ); + prusti_common::report::log::report_with_writer( + "graphviz_mir_dump_after_pointer_reborrow_facts", + format!("{}.dot", def_path.to_filename_friendly_no_crate()), + |writer| graph.write(writer).unwrap(), + ); + } + + let lifetimes = Lifetimes::new( + input_facts, + location_table, + replace_terminator_locations, + &mir, + ); Ok((mir, lifetimes)) } diff --git a/prusti-viper/src/encoder/mir/procedures/encoder/elaborate_drops/pointer_reborrow.rs b/prusti-viper/src/encoder/mir/procedures/encoder/elaborate_drops/pointer_reborrow.rs new file mode 100644 index 00000000000..6b8ffd3eb0b --- /dev/null +++ b/prusti-viper/src/encoder/mir/procedures/encoder/elaborate_drops/pointer_reborrow.rs @@ -0,0 +1,317 @@ +use crate::encoder::{errors::SpannedEncodingResult, Encoder}; +use log::debug; +use prusti_interface::environment::{ + borrowck::facts::Loan, + mir_body::borrowck::facts::{AllInputFacts, LocationTable, RichLocation}, +}; +use prusti_rustc_interface::middle::{ + mir, + ty::{self, TyCtxt}, +}; + +pub(super) fn add_pointer_reborrow_facts<'v, 'tcx: 'v>( + encoder: &Encoder<'v, 'tcx>, + borrowck_input_facts: &mut AllInputFacts, + location_table: &LocationTable, + body: &mir::Body<'tcx>, +) -> SpannedEncodingResult<()> { + let tcx = encoder.env().tcx(); + let mut lifetime_with_borrow_use = None; + for (block, data) in body.basic_blocks.iter_enumerated() { + match &data.terminator().kind { + mir::TerminatorKind::Call { + func: mir::Operand::Constant(box mir::Constant { literal, .. }), + args, + .. + } => { + if let ty::TyKind::FnDef(called_def_id, _) = literal.ty().kind() { + let full_called_function_name = + encoder.env().name.get_absolute_item_name(*called_def_id); + match full_called_function_name.as_str() { + "prusti_contracts::prusti_set_lifetime_for_raw_pointer_reference_casts" => { + assert_eq!(args.len(), 1); + let arg = &args[0]; + let mut statement_index = data.statements.len() - 1; + let argument_place = if let mir::Operand::Move(place) = arg { + place + } else { + unreachable!() + }; + let (place, borrow_use) = loop { + if let Some(statement) = data.statements.get(statement_index) { + if let mir::StatementKind::Assign(box (target_place, rvalue)) = + &statement.kind + { + if target_place == argument_place { + match rvalue { + mir::Rvalue::AddressOf(_, place) => { + let point_mid = location_table + .location_to_point(RichLocation::Mid( + mir::Location { + block, + statement_index, + }, + )); + let mut variable = None; + for (var, point) in + &borrowck_input_facts.var_used_at + { + if *point == point_mid { + assert!(variable.is_none()); + variable = Some(*var); + } + } + let mut path = None; + for (accessed_path, point) in + &borrowck_input_facts.path_accessed_at_base + { + if *point == point_mid { + assert!(path.is_none()); + path = Some(*accessed_path); + } + } + break ( + place, + (variable.unwrap(), path.unwrap()), + ); + } + _ => { + unimplemented!("rvalue: {:?}", rvalue); + } + } + } + } + statement_index -= 1; + } else { + unreachable!(); + } + }; + let ty::TyKind::Ref(reference_region, _, _) = place.ty(body, tcx).ty.kind() else { + unreachable!("place {place:?} must be a reference"); + }; + assert!(lifetime_with_borrow_use.is_none(), "the function can have only single prusti_set_lifetime_for_raw_pointer_reference_casts call"); + lifetime_with_borrow_use = Some((*reference_region, borrow_use)); + } + "prusti_contracts::prusti_attach_drop_lifetime" => { + assert_eq!(args.len(), 2); + let guard_arg = &args[0]; + let reference_arg = &args[1]; + let guard_place = if let mir::Operand::Move(place) = guard_arg { + place + } else { + unreachable!() + }; + let reference_place = if let mir::Operand::Move(place) = reference_arg { + place + } else { + unreachable!() + }; + let mut statement_index = data.statements.len() - 1; + let guard_local = loop { + if let Some(statement) = data.statements.get(statement_index) { + if let mir::StatementKind::Assign(box (target_place, rvalue)) = + &statement.kind + { + if target_place == guard_place { + match rvalue { + mir::Rvalue::AddressOf(_, place) => { + break place.as_local().unwrap(); + } + _ => { + unimplemented!("rvalue: {:?}", rvalue); + } + } + } + } + statement_index -= 1; + } else { + unreachable!(); + } + }; + let mut statement_index = data.statements.len() - 1; + let reference_place = loop { + if let Some(statement) = data.statements.get(statement_index) { + if let mir::StatementKind::Assign(box (target_place, rvalue)) = + &statement.kind + { + if target_place == reference_place { + match rvalue { + mir::Rvalue::AddressOf(_, place) => { + break *place; + } + _ => { + unimplemented!("rvalue: {:?}", rvalue); + } + } + } + } + statement_index -= 1; + } else { + unreachable!(); + } + }; + let ty::TyKind::Ref(reference_region, _, _) = reference_place.ty(body, tcx).ty.kind() else { + unreachable!("place {reference_place:?} must be a reference"); + }; + let ty::RegionKind::ReVar(reference_lifetime_id) = reference_region.kind() else { + unreachable!("reference_region: {:?}", reference_region); + }; + borrowck_input_facts + .drop_of_var_derefs_origin + .push((guard_local, reference_lifetime_id)); + } + _ => (), + } + } + } + _ => {} + } + } + let mut loan_counter = 0xFFFF_FF00u32; + for (block, data) in body.basic_blocks.iter_enumerated() { + for (statement_index, stmt) in data.statements.iter().enumerate() { + if let mir::StatementKind::Assign(box (_, source)) = &stmt.kind { + if let mir::Rvalue::Ref(reborrow_lifetime, _, place) = &source { + if let Some((reference_lifetime, borrow_use)) = lifetime_with_borrow_use { + if is_raw_pointer_deref(tcx, body, *place) { + // Add subset_base fact for the case when we are reborrowing from a raw pointer and + // the user set a lifetime to use for this case. + add_subset_base_fact( + borrowck_input_facts, + location_table, + block, + statement_index, + *reborrow_lifetime, + reference_lifetime, + Some(borrow_use), + ); + } + } + if let Some(reference_lifetime) = raw_pointer_reborrow(tcx, body, *place) { + // Add subset_base fact for the case when we are reborrowing from a place that + // originiates in a reference, but also contains a raw pointer. + add_subset_base_fact( + borrowck_input_facts, + location_table, + block, + statement_index, + *reborrow_lifetime, + reference_lifetime, + None, + ); + } else if lifetime_with_borrow_use.is_none() + && is_raw_pointer_deref(tcx, body, *place) + { + // We have a reborrow via raw pointer, but we cannot determine the lifetime + // because neither user told us to use one, nor it is a raw pointer rebborow. + // Therefore, we assume that this is a borrow of a memory location behind a + // raw pointer and create a new loan for that. + let new_loan = create_new_loan( + borrowck_input_facts, + location_table, + block, + statement_index, + *reborrow_lifetime, + *place, + &mut loan_counter, + ); + debug!("{block:?} {statement_index:?} {stmt:?} {new_loan:?}"); + } + } + } + } + } + Ok(()) +} + +fn add_subset_base_fact( + borrowck_input_facts: &mut AllInputFacts, + location_table: &LocationTable, + block: mir::BasicBlock, + statement_index: usize, + reborrow_lifetime: ty::Region<'_>, + reference_lifetime: ty::Region<'_>, + borrow_use: Option<( + mir::Local, + prusti_rustc_interface::dataflow::move_paths::MovePathIndex, + )>, +) { + let point = location_table.location_to_point(RichLocation::Mid(mir::Location { + block, + statement_index, + })); + let ty::RegionKind::ReVar(reborrow_lifetime_id) = reborrow_lifetime.kind() else { + unreachable!("reborrow_lifetime: {:?}", reborrow_lifetime); + }; + let ty::RegionKind::ReVar(reference_lifetime_id) = reference_lifetime.kind() else { + unreachable!("reference_lifetime: {:?}", reference_lifetime); + }; + borrowck_input_facts + .subset_base + .push((reference_lifetime_id, reborrow_lifetime_id, point)); + if let Some((variable, path)) = borrow_use { + borrowck_input_facts.var_used_at.push((variable, point)); + borrowck_input_facts + .path_accessed_at_base + .push((path, point)); + } +} + +fn create_new_loan( + borrowck_input_facts: &mut AllInputFacts, + location_table: &LocationTable, + block: mir::BasicBlock, + statement_index: usize, + reborrow_lifetime: ty::Region, + _place: mir::Place, + loan_counter: &mut u32, +) -> Loan { + let point = location_table.location_to_point(RichLocation::Mid(mir::Location { + block, + statement_index, + })); + let ty::RegionKind::ReVar(reborrow_lifetime_id) = reborrow_lifetime.kind() else { + unreachable!("reborrow_lifetime: {:?}", reborrow_lifetime); + }; + let loan = (*loan_counter).into(); + *loan_counter -= 1; + borrowck_input_facts + .loan_issued_at + .push((reborrow_lifetime_id, loan, point)); + loan +} + +fn is_raw_pointer_deref<'tcx>( + tcx: TyCtxt<'tcx>, + body: &mir::Body<'tcx>, + place: mir::Place<'tcx>, +) -> bool { + let projections = place.iter_projections().rev(); + for (place, projection) in projections { + if projection == mir::ProjectionElem::Deref && place.ty(body, tcx).ty.is_unsafe_ptr() { + return true; + } + } + false +} + +fn raw_pointer_reborrow<'tcx>( + tcx: TyCtxt<'tcx>, + body: &mir::Body<'tcx>, + place: mir::Place<'tcx>, +) -> Option> { + let mut projections = place.iter_projections().rev(); + for (place, projection) in projections.by_ref() { + if projection == mir::ProjectionElem::Deref && place.ty(body, tcx).ty.is_unsafe_ptr() { + break; + } + } + for (place, projection) in projections { + if projection == mir::ProjectionElem::Deref { + if let ty::TyKind::Ref(reference_region, _, _) = place.ty(body, tcx).ty.kind() { + return Some(*reference_region); + } + } + } + None +} diff --git a/prusti-viper/src/encoder/mir/procedures/encoder/initialisation.rs b/prusti-viper/src/encoder/mir/procedures/encoder/initialisation.rs index fc6ab05ee65..9d306d88aa3 100644 --- a/prusti-viper/src/encoder/mir/procedures/encoder/initialisation.rs +++ b/prusti-viper/src/encoder/mir/procedures/encoder/initialisation.rs @@ -1,6 +1,6 @@ use prusti_rustc_interface::{ dataflow::{ - impls::{MaybeInitializedPlaces, MaybeUninitializedPlaces}, + impls::{MaybeInitializedPlaces, MaybeLiveLocals, MaybeUninitializedPlaces}, move_paths::MoveData, un_derefer::UnDerefer, Analysis, MoveDataParamEnv, ResultsCursor, @@ -8,39 +8,60 @@ use prusti_rustc_interface::{ middle::{mir, ty::TyCtxt}, }; +// FIXME: Remove this file. It is not used anymore. pub(super) struct InitializationData<'mir, 'tcx> { + liveness: ResultsCursor<'mir, 'tcx, MaybeLiveLocals>, inits: ResultsCursor<'mir, 'tcx, MaybeInitializedPlaces<'mir, 'tcx>>, uninits: ResultsCursor<'mir, 'tcx, MaybeUninitializedPlaces<'mir, 'tcx>>, + move_env: &'mir MoveDataParamEnv<'tcx>, } impl<'mir, 'tcx> InitializationData<'mir, 'tcx> { pub(super) fn new( tcx: TyCtxt<'tcx>, body: &'mir mut mir::Body<'tcx>, - env: &'mir MoveDataParamEnv<'tcx>, + move_env: &'mir MoveDataParamEnv<'tcx>, un_derefer: &'mir UnDerefer<'tcx>, ) -> Self { - super::elaborate_drops::mir_transform::remove_dead_unwinds(tcx, body, env, un_derefer); + // FIXME: Check whether this call is needed. + super::elaborate_drops::mir_transform::remove_dead_unwinds(tcx, body, move_env, un_derefer); + let liveness = MaybeLiveLocals + .into_engine(tcx, body) + .pass_name("prusti_encoding") + .iterate_to_fixpoint() + .into_results_cursor(body); - let inits = MaybeInitializedPlaces::new(tcx, body, env) + let inits = MaybeInitializedPlaces::new(tcx, body, move_env) .into_engine(tcx, body) - .pass_name("elaborate_drops") + .pass_name("prusti_encoding") .iterate_to_fixpoint() .into_results_cursor(body); - let uninits = MaybeUninitializedPlaces::new(tcx, body, env) + let uninits = MaybeUninitializedPlaces::new(tcx, body, move_env) .mark_inactive_variants_as_uninit() .into_engine(tcx, body) - .pass_name("elaborate_drops") + .pass_name("prusti_encoding") .iterate_to_fixpoint() .into_results_cursor(body); - Self { inits, uninits } + Self { + inits, + uninits, + liveness, + move_env, + } } pub(super) fn seek_before(&mut self, loc: mir::Location) { + self.liveness.seek_before_primary_effect(loc); self.inits.seek_before_primary_effect(loc); self.uninits.seek_before_primary_effect(loc); } + pub(super) fn is_local_initialized_and_alive(&self, local: mir::Local) -> bool { + let path = self.move_env.move_data.rev_lookup.find_local(local); + self.liveness.get().contains(local) + && self.inits.get().contains(path) + && self.uninits.get().contains(path) + } } pub(super) fn create_move_data_param_env_and_un_derefer<'tcx>( diff --git a/prusti-viper/src/encoder/mir/procedures/encoder/lifetimes.rs b/prusti-viper/src/encoder/mir/procedures/encoder/lifetimes.rs index d989380f42d..7a79e511443 100644 --- a/prusti-viper/src/encoder/mir/procedures/encoder/lifetimes.rs +++ b/prusti-viper/src/encoder/mir/procedures/encoder/lifetimes.rs @@ -8,9 +8,12 @@ use crate::encoder::{ use prusti_interface::environment::{ debug_utils::to_text::ToText, mir_body::borrowck::facts::RichLocation, }; -use prusti_rustc_interface::middle::mir; +use prusti_rustc_interface::middle::{mir, ty}; use std::collections::{BTreeMap, BTreeSet}; -use vir_crate::high::{self as vir_high, builders::procedure::BasicBlockBuilder}; +use vir_crate::high::{ + self as vir_high, + builders::procedure::{BasicBlockBuilder, StatementSequenceBuilder}, +}; pub(super) trait LifetimesEncoder<'tcx> { fn encode_lft_for_statement_start( @@ -33,7 +36,11 @@ pub(super) trait LifetimesEncoder<'tcx> { &mut self, statement: Option<&mir::Statement<'tcx>>, ) -> SpannedEncodingResult)>>; - fn reborrow_operand_lifetime( + fn check_if_reborrow( + &self, + place: mir::Place<'tcx>, + ) -> Option<(mir::PlaceRef<'tcx>, ty::Region<'tcx>)>; + fn reborrow_operand_lifetime_to_ignore( &mut self, statement: Option<&mir::Statement<'tcx>>, ) -> Option; @@ -42,10 +49,13 @@ pub(super) trait LifetimesEncoder<'tcx> { target: mir::BasicBlock, location: mir::Location, block_builder: &mut BasicBlockBuilder, + current_original_lifetimes: &mut BTreeSet, + current_derived_lifetimes: &mut BTreeMap>, ) -> SpannedEncodingResult<()>; fn encode_lft_for_block_with_edge( &mut self, target: mir::BasicBlock, + is_unwind: bool, encoded_target: vir_high::BasicBlockId, location: mir::Location, block_builder: &mut BasicBlockBuilder, @@ -66,7 +76,7 @@ pub(super) trait LifetimesEncoder<'tcx> { &mut self, old_original_lifetimes: &BTreeSet, new_derived_lifetimes: &BTreeMap>, - new_reborrow_lifetime_to_remove: String, + new_reborrow_lifetime_to_remove: &str, lifetimes_to_create: &BTreeSet, ); fn remove_reborrow_lifetimes_set(&mut self, set: &mut BTreeSet); @@ -141,6 +151,9 @@ pub(super) trait LifetimesEncoder<'tcx> { from: RichLocation, to: RichLocation, ) -> SpannedEncodingResult<()>; + fn encode_dead_references_for_parameters( + &mut self, + ) -> SpannedEncodingResult>; fn encode_lft_assert_subset( &mut self, block_builder: &mut BasicBlockBuilder, @@ -220,7 +233,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> LifetimesEncoder<'tcx> for ProcedureEncoder<'p, 'v, ' "Prepare lifetimes for statement start {location:?}" )); let new_reborrow_lifetime_to_ignore: Option = - self.reborrow_operand_lifetime(statement); + self.reborrow_operand_lifetime_to_ignore(statement); self.encode_lft( block_builder, location, @@ -245,7 +258,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> LifetimesEncoder<'tcx> for ProcedureEncoder<'p, 'v, ' let mut new_derived_lifetimes = self.lifetimes.get_origin_contains_loan_at_mid(location); block_builder.add_comment(format!("Prepare lifetimes for statement mid {location:?}")); let new_reborrow_lifetime_to_ignore: Option = - self.reborrow_operand_lifetime(statement); + self.reborrow_operand_lifetime_to_ignore(statement); // FIXME: The lifetimes read via the reborrow statement are currently not killed. let reborrow_lifetimes = self.reborrow_lifetimes(statement)?; self.encode_lft( @@ -278,10 +291,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> LifetimesEncoder<'tcx> for ProcedureEncoder<'p, 'v, ' if let (Some(target_lifetime), Some(value_lifetime)) = (target_lifetime, value_lifetime) { - let values = [operand_lifetime, target_lifetime] - .iter() - .cloned() - .collect(); + let values = [operand_lifetime, target_lifetime].into_iter().collect(); return Ok(Some((value_lifetime, values))); } } @@ -290,7 +300,40 @@ impl<'p, 'v: 'p, 'tcx: 'v> LifetimesEncoder<'tcx> for ProcedureEncoder<'p, 'v, ' Ok(None) } - fn reborrow_operand_lifetime( + fn check_if_reborrow( + &self, + place: mir::Place<'tcx>, + ) -> Option<(mir::PlaceRef<'tcx>, ty::Region<'tcx>)> { + let result = super::utils::get_last_deref_with_lifetime( + self.encoder.env().tcx(), + self.mir, + place, + self.pointer_deref_lifetime, + ); + if let Some((_, region)) = result { + let lifetime = vir_high::ty::LifetimeConst::new(region.to_text()); + if self + .opened_reference_parameter_lifetimes + .contains(&lifetime) + { + // The lifetime is a lifetime of the `self` parameter of a + // `Drop` impl. Since this reference is already opened, we + // do not have reborrows. + return None; + } + } + result + // place + // .iter_projections() + // .filter(|(place, projection)| { + // projection == &mir::ProjectionElem::Deref + // && place.ty(self.mir, self.encoder.env().tcx()).ty.is_ref() + // }) + // .last() + // .map(|(place, _)| place) + } + + fn reborrow_operand_lifetime_to_ignore( &mut self, statement: Option<&mir::Statement<'tcx>>, ) -> Option { @@ -300,12 +343,17 @@ impl<'p, 'v: 'p, 'tcx: 'v> LifetimesEncoder<'tcx> for ProcedureEncoder<'p, 'v, ' mir::Rvalue::Ref(region, _borrow_kind, place), )) = &statement.kind { - let region_name: String = region.to_text(); - if let Some((_ref, projection)) = place.iter_projections().last() { - if projection == mir::ProjectionElem::Deref { - return Some(region_name); - } + let is_reborrow = self.check_if_reborrow(*place).is_some(); + if is_reborrow { + let region_name: String = region.to_text(); + return Some(region_name); } + // let region_name: String = region.to_text(); + // if let Some((_ref, projection)) = place.iter_projections().last() { + // if projection == mir::ProjectionElem::Deref { + // return Some(region_name); + // } + // } } } None @@ -316,40 +364,49 @@ impl<'p, 'v: 'p, 'tcx: 'v> LifetimesEncoder<'tcx> for ProcedureEncoder<'p, 'v, ' target: mir::BasicBlock, location: mir::Location, block_builder: &mut BasicBlockBuilder, + current_original_lifetimes: &mut BTreeSet, + current_derived_lifetimes: &mut BTreeMap>, ) -> SpannedEncodingResult<()> { let mut needed_derived_lifetimes = self.needed_derived_lifetimes_for_block(&target); - let mut current_derived_lifetimes = - self.lifetimes.get_origin_contains_loan_at_mid(location); - let mut current_original_lifetimes = self.lifetimes.get_loan_live_at_start(location); + // let mut current_derived_lifetimes = + // self.lifetimes.get_origin_contains_loan_at_mid(location); + // let mut current_original_lifetimes = self.lifetimes.get_loan_live_at_start(location); block_builder.add_comment(format!("Prepare lifetimes for block {target:?}")); + self.encode_lifetimes_dead_on_edge( + block_builder, + RichLocation::Mid(location), + RichLocation::Start(mir::Location { + block: target, + statement_index: 0, + }), + )?; self.encode_lft( block_builder, location, - &mut current_original_lifetimes, - &mut current_derived_lifetimes, + current_original_lifetimes, + current_derived_lifetimes, &mut needed_derived_lifetimes, true, None, None, )?; - self.reborrow_lifetimes_to_remove_for_block - .entry(target) - .or_insert_with(BTreeSet::new); let mut values = self .reborrow_lifetimes_to_remove_for_block .get(&self.current_basic_block.unwrap()) .unwrap() .clone(); - self.reborrow_lifetimes_to_remove_for_block - .get_mut(&target) - .unwrap() - .append(&mut values); + let target_entry = self + .reborrow_lifetimes_to_remove_for_block + .entry(target) + .or_insert_with(BTreeSet::new); + target_entry.append(&mut values); Ok(()) } fn encode_lft_for_block_with_edge( &mut self, target: mir::BasicBlock, + is_unwind: bool, encoded_target: vir_high::BasicBlockId, location: mir::Location, block_builder: &mut BasicBlockBuilder, @@ -361,7 +418,23 @@ impl<'p, 'v: 'p, 'tcx: 'v> LifetimesEncoder<'tcx> for ProcedureEncoder<'p, 'v, ' let fresh_destination_label = self.fresh_basic_block_label(); let mut intermediate_block_builder = block_builder.create_basic_block_builder(fresh_destination_label.clone()); + if is_unwind { + if let Some(statements) = self.add_specification_before_terminator.remove(&target) { + intermediate_block_builder.add_comment(format!( + "Add specification statements before {target:?} terminator" + )); + intermediate_block_builder.add_statements(statements); + } + } intermediate_block_builder.add_comment(format!("Prepare lifetimes for block {target:?}")); + self.encode_lifetimes_dead_on_edge( + &mut intermediate_block_builder, + RichLocation::Mid(location), + RichLocation::Start(mir::Location { + block: target, + statement_index: 0, + }), + )?; self.encode_lft( &mut intermediate_block_builder, location, @@ -409,7 +482,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> LifetimesEncoder<'tcx> for ProcedureEncoder<'p, 'v, ' let mut lifetimes_to_create = self.lifetimes_to_create(old_original_lifetimes, &new_original_lifetimes); let mut lifetime_backups: BTreeMap = BTreeMap::new(); - if let Some(new_reborrow_lifetime_to_remove) = new_reborrow_lifetime_to_remove { + if let Some(new_reborrow_lifetime_to_remove) = &new_reborrow_lifetime_to_remove { self.update_lifetimes_to_remove( old_original_lifetimes, new_derived_lifetimes, @@ -463,71 +536,87 @@ impl<'p, 'v: 'p, 'tcx: 'v> LifetimesEncoder<'tcx> for ProcedureEncoder<'p, 'v, ' fn update_lifetimes_to_remove( &mut self, - old_original_lifetimes: &BTreeSet, - new_derived_lifetimes: &BTreeMap>, - lifetime_to_ignore: String, - lifetimes_to_create: &BTreeSet, + _old_original_lifetimes: &BTreeSet, + _new_derived_lifetimes: &BTreeMap>, + _lifetime_to_ignore: &str, + _lifetimes_to_create: &BTreeSet, ) { - let mut new_lifetimes_to_ignore: BTreeSet = BTreeSet::new(); - for (lifetime, derived_from) in new_derived_lifetimes.clone() { - // NOTE: if the lifetime is not derived from at least one already existing - // original lifetime, we can not delete the lifetimes it is derived from. - let can_remove_lifetimes = !derived_from - .iter() - .filter(|&x| old_original_lifetimes.contains(x)) - .cloned() - .collect::>() - .is_empty(); - if lifetime == lifetime_to_ignore && can_remove_lifetimes { - new_lifetimes_to_ignore = derived_from - .clone() - .iter() - .filter(|x| lifetimes_to_create.contains(*x)) - .cloned() - .collect(); - } - } - self.reborrow_lifetimes_to_remove_for_block - .get_mut(&self.current_basic_block.unwrap()) - .unwrap() - .append(&mut new_lifetimes_to_ignore); + // FIXME: This is old code before switched to ignoring all zombies. + // + // let mut new_lifetimes_to_ignore: BTreeSet = BTreeSet::new(); + // for (lifetime, derived_from) in new_derived_lifetimes { + // // NOTE: if the lifetime is not derived from at least one already existing + // // original lifetime, we can not delete the lifetimes it is derived from. + // let can_remove_lifetimes = derived_from + // .iter() + // .any(|x| old_original_lifetimes.contains(x)); + // if lifetime == lifetime_to_ignore && can_remove_lifetimes { + // assert!(new_lifetimes_to_ignore.is_empty()); + // new_lifetimes_to_ignore.extend( + // derived_from + // .iter() + // .filter(|x| lifetimes_to_create.contains(*x)) + // .cloned(), + // ); + // } + // } + // self.reborrow_lifetimes_to_remove_for_block + // .get_mut(&self.current_basic_block.unwrap()) + // .unwrap() + // .append(&mut new_lifetimes_to_ignore); } fn remove_reborrow_lifetimes_set(&mut self, set: &mut BTreeSet) { - *set = set - .clone() - .iter() - .filter(|&lft| { + set.retain(|lft| { + !self + .reborrow_lifetimes_to_remove_for_block + .get(&self.current_basic_block.unwrap()) + .unwrap() + .contains(lft) + }); + // *set = set + // .clone() + // .iter() + // .filter(|&lft| { + // !self + // .reborrow_lifetimes_to_remove_for_block + // .get(&self.current_basic_block.unwrap()) + // .unwrap() + // .contains(lft) + // }) + // .cloned() + // .collect(); + } + + fn remove_reborrow_lifetimes_map(&mut self, map: &mut BTreeMap>) { + for (_lifetime, derived_from) in map { + derived_from.retain(|lft| { !self .reborrow_lifetimes_to_remove_for_block .get(&self.current_basic_block.unwrap()) .unwrap() .contains(lft) - }) - .cloned() - .collect(); - } - - fn remove_reborrow_lifetimes_map(&mut self, map: &mut BTreeMap>) { - *map = map - .clone() - .iter() - .map(|(lifetime, derived_from)| { - let updated_derived_from: BTreeSet = derived_from - .clone() - .iter() - .filter(|&lft| { - !self - .reborrow_lifetimes_to_remove_for_block - .get(&self.current_basic_block.unwrap()) - .unwrap() - .contains(lft) - }) - .cloned() - .collect(); - (lifetime.clone(), updated_derived_from) - }) - .collect(); + }); + } + // *map = map + // .clone() + // .iter() + // .map(|(lifetime, derived_from)| { + // let updated_derived_from: BTreeSet = derived_from + // .clone() + // .iter() + // .filter(|&lft| { + // !self + // .reborrow_lifetimes_to_remove_for_block + // .get(&self.current_basic_block.unwrap()) + // .unwrap() + // .contains(lft) + // }) + // .cloned() + // .collect(); + // (lifetime.clone(), updated_derived_from) + // }) + // .collect(); } fn encode_bor_shorten( @@ -561,14 +650,24 @@ impl<'p, 'v: 'p, 'tcx: 'v> LifetimesEncoder<'tcx> for ProcedureEncoder<'p, 'v, ' new_derived_lifetimes: &BTreeMap>, lifetime_backups: &mut BTreeMap, ) -> SpannedEncodingResult<()> { - for (lifetime, _) in old_derived_lifetimes.clone() { - if new_derived_lifetimes.contains_key(&lifetime) { - if let Some(var) = self.procedure.get_var_of_lifetime(&lifetime[..]) { - let object = self.encode_local(var)?; - let backup_var_name = - format!("old_{}_{}", lifetime.clone(), self.old_lifetime_ctr); - self.old_lifetime_ctr += 1; - lifetime_backups.insert(lifetime.clone(), (backup_var_name.clone(), object)); + for (lifetime, old_bounds) in old_derived_lifetimes { + if let Some(new_bounds) = new_derived_lifetimes.get(lifetime) { + if new_bounds != old_bounds { + // assert!( + // new_bounds.is_subset(old_bounds), + // "old_bounds: {:?}, new_bounds: {:?}, lifetime: {:?} at {location:?}", + // old_bounds, + // new_bounds, + // lifetime + // ); + if let Some(var) = self.procedure.get_var_of_lifetime(&lifetime[..]) { + let object = self.encode_local(var)?; + let backup_var_name = + format!("old_{}_{}", lifetime.clone(), self.old_lifetime_ctr); + self.old_lifetime_ctr += 1; + lifetime_backups + .insert(lifetime.clone(), (backup_var_name.clone(), object)); + } } } } @@ -609,11 +708,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> LifetimesEncoder<'tcx> for ProcedureEncoder<'p, 'v, ' block_builder.add_statement(self.set_statement_error( location, ErrorCtxt::LifetimeEncoding, - vir_high::Statement::lifetime_take_no_pos( - backup_var, - vec![lifetime_var], - self.lifetime_token_fractional_permission(self.lifetime_count), - ), + vir_high::Statement::ghost_assign_no_pos(backup_var.into(), lifetime_var.into()), )?); } Ok(()) @@ -715,7 +810,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> LifetimesEncoder<'tcx> for ProcedureEncoder<'p, 'v, ' )?); } if let Some((value_lifetime, lifetimes)) = reborrow_lifetimes { - let existing_lifetime = [self.encode_lft_variable(value_lifetime.clone())?].to_vec(); + let existing_lifetime = vec![self.encode_lft_variable(value_lifetime.clone())?]; for lifetime in lifetimes { if !new_derived_lifetimes.contains_key(lifetime) { let encoded_target = self.encode_lft_variable(lifetime.clone())?; @@ -750,7 +845,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> LifetimesEncoder<'tcx> for ProcedureEncoder<'p, 'v, ' ErrorCtxt::LifetimeEncoding, vir_high::Statement::dead_inclusion_no_pos(encoded_target, encoded_value), )?); - self.derived_lifetimes_yet_to_kill.remove(&lifetime); + // self.derived_lifetimes_yet_to_kill.remove(&lifetime); break; } } @@ -764,6 +859,14 @@ impl<'p, 'v: 'p, 'tcx: 'v> LifetimesEncoder<'tcx> for ProcedureEncoder<'p, 'v, ' location: mir::Location, lifetime: vir_high::ty::LifetimeConst, ) -> SpannedEncodingResult<()> { + if self + .opened_reference_parameter_lifetimes + .contains(&lifetime) + { + // The lifetimes of opened reference parameters should span the + // entire body of the function. + return Ok(()); + } block_builder.add_statement(self.set_statement_error( location, ErrorCtxt::LifetimeEncoding, @@ -779,15 +882,45 @@ impl<'p, 'v: 'p, 'tcx: 'v> LifetimesEncoder<'tcx> for ProcedureEncoder<'p, 'v, ' to: RichLocation, ) -> SpannedEncodingResult<()> { for lifetime in self.lifetimes.get_lifetimes_dead_on_edge(from, to) { - self.encode_dead_lifetime( - block_builder, - from.into_inner(), - vir_high::ty::LifetimeConst::new(lifetime.to_text()), - )?; + let lifetime = vir_high::ty::LifetimeConst::new(lifetime.to_text()); + if let Some(entries) = self.already_dead_lifetimes.get(&(from, to)) { + if entries.contains(&lifetime) { + continue; + } + } + self.encode_dead_lifetime(block_builder, from.into_inner(), lifetime)?; } Ok(()) } + fn encode_dead_references_for_parameters( + &mut self, + ) -> SpannedEncodingResult> { + // FIXME: This is apparently not needed because we generate the + // necessary dead-lifetime statement even for function parameters. + let statements = Vec::new(); + // if self.encoding_kind == ProcedureEncodingKind::PostconditionFrameCheck { + // return Ok(statements); + // } + // for mir_arg in self.mir.args_iter() { + // let parameter = self.encode_local(mir_arg)?; + // if let vir_high::Type::Reference(reference_type) = ¶meter.variable.ty { + // let position = parameter.position; + // let target_type = (*reference_type.target_type).clone(); + // let target = vir_high::Expression::deref_no_pos(parameter.into(), target_type); + // let statement = vir_high::Statement::dead_reference_no_pos(target); + // statements.add_statement( + // self.encoder.set_surrounding_error_context_for_statement( + // statement, + // position, + // ErrorCtxt::LifetimeEncoding, + // )?, + // ); + // } + // } + Ok(statements) + } + fn encode_lft_assert_subset( &mut self, block_builder: &mut BasicBlockBuilder, @@ -1100,10 +1233,9 @@ impl<'p, 'v: 'p, 'tcx: 'v> LifetimesEncoder<'tcx> for ProcedureEncoder<'p, 'v, ' permission_amount: vir_high::Expression, ) -> SpannedEncodingResult { self.encoder.set_statement_error_ctxt( - vir_high::Statement::inhale_no_pos(vir_high::Predicate::lifetime_token_no_pos( - lifetime_const, - permission_amount, - )), + vir_high::Statement::inhale_predicate_no_pos( + vir_high::Predicate::lifetime_token_no_pos(lifetime_const, permission_amount), + ), self.mir.span, ErrorCtxt::LifetimeInhale, self.def_id, @@ -1136,10 +1268,9 @@ impl<'p, 'v: 'p, 'tcx: 'v> LifetimesEncoder<'tcx> for ProcedureEncoder<'p, 'v, ' permission_amount: vir_high::Expression, ) -> SpannedEncodingResult { self.encoder.set_statement_error_ctxt( - vir_high::Statement::exhale_no_pos(vir_high::Predicate::lifetime_token_no_pos( - lifetime_const, - permission_amount, - )), + vir_high::Statement::exhale_predicate_no_pos( + vir_high::Predicate::lifetime_token_no_pos(lifetime_const, permission_amount), + ), self.mir.span, ErrorCtxt::LifetimeExhale, self.def_id, diff --git a/prusti-viper/src/encoder/mir/procedures/encoder/loops.rs b/prusti-viper/src/encoder/mir/procedures/encoder/loops.rs index 7af06a785b5..f59cfaf1a4c 100644 --- a/prusti-viper/src/encoder/mir/procedures/encoder/loops.rs +++ b/prusti-viper/src/encoder/mir/procedures/encoder/loops.rs @@ -37,7 +37,14 @@ impl<'p, 'v: 'p, 'tcx: 'v> super::ProcedureEncoder<'p, 'v, 'tcx> { { let specification = self.encoder.get_loop_specs(cl_def_id).unwrap(); let (spec, encoding_vec, err_ctxt) = match specification { - LoopSpecification::Invariant(inv) => { + LoopSpecification::Invariant { + def_id: inv, + is_structural, + } => { + if !self.check_mode.check_specifications() && !is_structural { + // Skip non-structural invariants in non-specification mode. + continue; + } (inv, &mut encoded_invariant_specs, ErrorCtxt::LoopInvariant) } LoopSpecification::Variant(var) => { @@ -51,6 +58,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> super::ProcedureEncoder<'p, 'v, 'tcx> { block, self.def_id, cl_substs, + false, )?, span, err_ctxt, @@ -69,7 +77,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> super::ProcedureEncoder<'p, 'v, 'tcx> { .map(|back_edge| self.encode_basic_block_label(*back_edge)) .collect() }; - self.init_data.seek_before(invariant_location); + // self.init_data.seek_before(invariant_location); // Encode permissions. let initialized_places = self.initialization.get_after_statement(invariant_location); diff --git a/prusti-viper/src/encoder/mir/procedures/encoder/mod.rs b/prusti-viper/src/encoder/mir/procedures/encoder/mod.rs index 6013c273f81..0f1dafeeb85 100644 --- a/prusti-viper/src/encoder/mir/procedures/encoder/mod.rs +++ b/prusti-viper/src/encoder/mir/procedures/encoder/mod.rs @@ -1,6 +1,10 @@ use self::{ - builtin_function_encoder::BuiltinFuncAppEncoder, initialisation::InitializationData, - lifetimes::LifetimesEncoder, specification_blocks::SpecificationBlocks, + builtin_function_encoder::BuiltinFuncAppEncoder, + initialisation::InitializationData, + lifetimes::LifetimesEncoder, + postcondition_mode::PostconditionMode, + // specification_regions::SpecificationRegionEncoding, + specification_blocks::SpecificationBlocks, }; use super::MirProcedureEncoderInterface; use crate::encoder::{ @@ -14,24 +18,31 @@ use crate::encoder::{ panics::MirPanicsEncoderInterface, places::PlacesEncoderInterface, predicates::MirPredicateEncoderInterface, + procedures::encoder::specification_blocks::specification_blocks_to_graph, pure::{PureFunctionEncoderInterface, SpecificationEncoderInterface}, spans::SpanInterface, specifications::SpecificationsInterface, type_layouts::MirTypeLayoutsEncoderInterface, + types::MirTypeEncoderInterface, }, mir_encoder::PRECONDITION_LABEL, Encoder, }; -use log::debug; +use log::{debug, trace}; use prusti_common::config; -use prusti_interface::environment::{ - debug_utils::to_text::ToText, - mir_analyses::{ - allocation::{compute_definitely_allocated, DefinitelyAllocatedAnalysisResult}, - initialization::{compute_definitely_initialized, DefinitelyInitializedAnalysisResult}, +use prusti_interface::{ + environment::{ + debug_utils::to_text::ToText, + is_checked_block_begin_marker, is_checked_block_end_marker, is_specification_begin_marker, + is_specification_end_marker, is_try_finally_begin_marker, is_try_finally_end_marker, + mir_analyses::{ + allocation::{compute_definitely_allocated, DefinitelyAllocatedAnalysisResult}, + initialization::{compute_definitely_initialized, DefinitelyInitializedAnalysisResult}, + }, + mir_body::borrowck::{facts::RichLocation, lifetimes::Lifetimes}, + Procedure, }, - mir_body::borrowck::{facts::RichLocation, lifetimes::Lifetimes}, - Procedure, + specs::typed::{Pledge, SpecificationItem}, }; use prusti_rustc_interface::{ data_structures::graph::WithStartNode, @@ -39,24 +50,26 @@ use prusti_rustc_interface::{ middle::{mir, ty, ty::subst::SubstsRef}, span::Span, }; -use rustc_hash::FxHashSet; +use rustc_hash::{FxHashMap, FxHashSet}; use std::collections::{BTreeMap, BTreeSet}; use vir_crate::{ common::{ check_mode::CheckMode, - expression::{BinaryOperationHelpers, UnaryOperationHelpers}, + expression::{BinaryOperationHelpers, ExpressionIterator, UnaryOperationHelpers}, position::Positioned, }, high::{ self as vir_high, builders::procedure::{ - BasicBlockBuilder, ProcedureBuilder, SuccessorBuilder, SuccessorExitKind, + BasicBlockBuilder, ProcedureBuilder, StatementSequenceBuilder, SuccessorBuilder, + SuccessorExitKind, }, operations::{lifetimes::WithLifetimes, ty::Typed}, }, }; mod builtin_function_encoder; +mod check_mode_converters; mod elaborate_drops; mod ghost; mod initialisation; @@ -65,16 +78,32 @@ mod loops; mod scc; pub mod specification_blocks; mod termination; +mod specifications; +mod utils; +mod specification_regions; +mod user_named_lifetimes; +mod postcondition_mode; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(super) enum ProcedureEncodingKind { + Regular, + PostconditionFrameCheck, +} pub(super) fn encode_procedure<'v, 'tcx: 'v>( encoder: &mut Encoder<'v, 'tcx>, def_id: DefId, check_mode: CheckMode, + encoding_kind: ProcedureEncodingKind, ) -> SpannedEncodingResult { let procedure = Procedure::new(encoder.env(), def_id); let tcx = encoder.env().tcx(); - let (mir, lifetimes) = self::elaborate_drops::elaborate_drops(encoder, def_id, &procedure)?; + let (mir, lifetimes) = + self::elaborate_drops::get_and_elaborate_mir(encoder, def_id, &procedure)?; let mir = &mir; // Mark body as immutable. + let is_unsafe_function = encoder.env().query.is_unsafe_function(def_id); + let no_panic: bool = encoder.no_panic(def_id, None); + let no_panic_ensures_postcondition = encoder.no_panic_ensures_postcondition(def_id, None); let (env, un_derefer) = self::initialisation::create_move_data_param_env_and_un_derefer(tcx, mir); // TODO: the clone is required so that we can remove dead unwinds @@ -82,7 +111,7 @@ pub(super) fn encode_procedure<'v, 'tcx: 'v>( let init_data = InitializationData::new(tcx, &mut no_dead_unwinds, &env, &un_derefer); let locals_without_explicit_allocation: BTreeSet<_> = mir.vars_and_temps_iter().collect(); let specification_blocks = - SpecificationBlocks::build(encoder.env().query, mir, &procedure, true); + SpecificationBlocks::build(encoder.env().query, mir, Some(&procedure), true); let initialization = compute_definitely_initialized(def_id, mir, encoder.env().tcx()); let allocation = compute_definitely_allocated(def_id, mir); let lifetime_count = lifetimes.lifetime_count(); @@ -94,10 +123,22 @@ pub(super) fn encode_procedure<'v, 'tcx: 'v>( BTreeMap::new(); let points_to_reborrow: BTreeSet = BTreeSet::new(); let current_basic_block = None; + let is_drop_impl = { + let is_drop_impl = encoder.env().query.is_drop_method_impl(def_id); + let function_name = encoder.env().name.get_absolute_item_name(def_id); + // FIXME: Remove this hack based assert after convinced that the query + // works reliably. + assert_eq!( + function_name.ends_with(" as std::ops::Drop>::drop"), + is_drop_impl + ); + is_drop_impl + }; let mut procedure_encoder = ProcedureEncoder { encoder, def_id, check_mode, + is_unsafe_function, procedure: &procedure, mir, init_data, @@ -105,11 +146,23 @@ pub(super) fn encode_procedure<'v, 'tcx: 'v>( allocation, lifetimes, reachable_blocks: Default::default(), + reachable_predecessors: Default::default(), specification_blocks, specification_block_encoding: Default::default(), + specification_region_encoding_statements: Default::default(), + specification_region_exit_target_block: Default::default(), + specification_on_drop_unwind: Default::default(), + specification_before_drop: Default::default(), + specification_after_drop: Default::default(), + add_specification_before_terminator: Default::default(), + add_function_panic_specification_before_lifetime_effects: Default::default(), + add_function_panic_specification_after_lifetime_effects: Default::default(), + locals_used_only_in_specification_regions: Default::default(), loop_invariant_encoding: Default::default(), - check_panics: config::check_panics() && check_mode != CheckMode::CoreProof, + check_panics: config::check_panics() && check_mode.check_specifications(), locals_without_explicit_allocation, + locals_live_in_block: Default::default(), + missing_live_locals: Default::default(), used_locals: Default::default(), fresh_id_generator: 0, lifetime_count, @@ -119,8 +172,21 @@ pub(super) fn encode_procedure<'v, 'tcx: 'v>( derived_lifetimes_yet_to_kill, points_to_reborrow, reborrow_lifetimes_to_remove_for_block, + already_dead_lifetimes: Default::default(), current_basic_block, termination_variable: None, + encoding_kind, + opened_reference_place_permissions: Default::default(), + opened_reference_witnesses: Default::default(), + user_named_lifetimes: Default::default(), + manually_managed_places: Default::default(), + stashed_ranges: Default::default(), + specification_expressions: Default::default(), + is_drop_impl, + opened_reference_parameter_lifetimes: Default::default(), + pointer_deref_lifetime: None, + no_panic, + no_panic_ensures_postcondition, }; procedure_encoder.encode() } @@ -129,6 +195,8 @@ struct ProcedureEncoder<'p, 'v: 'p, 'tcx: 'v> { encoder: &'p mut Encoder<'v, 'tcx>, def_id: DefId, check_mode: CheckMode, + encoding_kind: ProcedureEncodingKind, + is_unsafe_function: bool, procedure: &'p Procedure<'tcx>, mir: &'p mir::Body<'tcx>, init_data: InitializationData<'p, 'tcx>, @@ -137,10 +205,44 @@ struct ProcedureEncoder<'p, 'v: 'p, 'tcx: 'v> { lifetimes: Lifetimes, /// Blocks that we managed to reach when traversing from the entry block. reachable_blocks: FxHashSet, + /// Predecessors that we managed to reach and encode when traversing from + /// the entry block. For example, the specification blocks are not encoded. + reachable_predecessors: FxHashMap>, /// Information about the specification blocks. specification_blocks: SpecificationBlocks, /// Specifications to be inserted at the given point. specification_block_encoding: BTreeMap>, + /// Specification regions to be removed from the given point. + /// FIXME: We currently assume no branching in the specification region. + specification_region_encoding_statements: BTreeMap>, + /// The block immediatelly after the specification region to which the + /// execution should be resumed after the specification region is removed. + specification_region_exit_target_block: BTreeMap, + /// The specification region that should executed when the specified + /// expression is dropped. + specification_on_drop_unwind: FxHashMap, + /// The specification statements to be added before the drop of the + /// specified expression. + specification_before_drop: FxHashMap, + /// The specification statements to be added after the drop of the + /// specified expression. + specification_after_drop: FxHashMap, + /// The specification statements to be added before the terminator of the + /// specified block. + add_specification_before_terminator: BTreeMap>, + /// The specification statements to be added before the terminator of the + /// panic edge of the function call and before the lifetime updates for that + /// edge are done. + add_function_panic_specification_before_lifetime_effects: + BTreeMap<(mir::BasicBlock, mir::BasicBlock), Vec>, + /// The specification statements to be added before the terminator of the + /// panic edge of the function call, but after the lifetime updates for that + /// edge are done. + add_function_panic_specification_after_lifetime_effects: + BTreeMap<(mir::BasicBlock, mir::BasicBlock), Vec>, + /// The locals that are used only in the specification regions and, + /// therefore, `StorageLive`/`StorageDead` are not generated for them. + locals_used_only_in_specification_regions: BTreeSet, /// The loop invariant to be inserted at the end of the given basic block. loop_invariant_encoding: BTreeMap, check_panics: bool, @@ -148,6 +250,19 @@ struct ProcedureEncoder<'p, 'v: 'p, 'tcx: 'v> { /// `StorageLive`/`StorageDead`. Such locals are assumed to be alive through /// the entire body of the function. locals_without_explicit_allocation: BTreeSet, + /// The Rust compiler does not guarantee that each `StorageDead` is + /// dominated by a `StorageLive`: + /// + /// * https://github.com/rust-lang/rust/issues/99160 + /// * https://github.com/rust-lang/rust/issues/98896 + /// + /// Therefore, we track which locals are alive in each block and emit a fake + /// `StorageLive` in blocks that are merged with blocks in which the local + /// is alive. + locals_live_in_block: BTreeMap>, + /// `StorageDead` statements which apply to locals that are not guaranteed + /// to be alive. + missing_live_locals: Vec<(mir::BasicBlock, mir::Local)>, /// Locals that are used in the function. The unused locals are assumed to /// be a side effect of specification generation code and are not generated. used_locals: BTreeSet, @@ -159,32 +274,81 @@ struct ProcedureEncoder<'p, 'v: 'p, 'tcx: 'v> { derived_lifetimes_yet_to_kill: BTreeMap>, points_to_reborrow: BTreeSet, reborrow_lifetimes_to_remove_for_block: BTreeMap>, + /// A set of lifetimes, which we already ended on the given edge because the + /// user told us to do so. + already_dead_lifetimes: + BTreeMap<(RichLocation, RichLocation), Vec>, current_basic_block: Option, termination_variable: Option, + /// A map from opened reference place to the corresponding permission + /// variable. + opened_reference_place_permissions: + BTreeMap>, + /// A map from opened reference witnesses to the corresponding places and lifetimes. + opened_reference_witnesses: + BTreeMap, + /// The lifetimes extracted by the user by using `take_lifetime!` macro. + user_named_lifetimes: BTreeMap, + /// Places that are manually managed by the user and for which we should not + /// automatically generate open/close/fold/unfold statements. + /// FIXME: Not used, remove. + manually_managed_places: BTreeSet, + /// Information about stashed ranges with a given name: `(pointer, + /// start_index, end_index)`. + stashed_ranges: BTreeMap< + String, + ( + vir_high::Expression, + vir_high::Expression, + vir_high::Expression, + ), + >, + /// The encoded Prusti specification expressions used in specification + /// blocks. + /// + /// Specification ID → expresssion. + specification_expressions: BTreeMap, + is_drop_impl: bool, + /// The lifetime of the `self` argument in a `Drop` implementation. + opened_reference_parameter_lifetimes: Vec, + /// A lifetime to use when reborrowing a place behind a raw pointer dereference. + pointer_deref_lifetime: Option>, + /// This function is guaranteed not to panic even when its precondition is + /// violated. + no_panic: bool, + /// If this function did not panic, then its postcondition is guaranteed to + /// hold. + no_panic_ensures_postcondition: bool, } impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { fn encode(&mut self) -> SpannedEncodingResult { self.pure_sanity_checks()?; let name = format!( - "{}${}", + "{}${}${:?}", self.encoder.encode_item_name(self.def_id), - self.check_mode + self.check_mode, + self.encoding_kind, ); - let (allocate_parameters, deallocate_parameters) = self.encode_parameters()?; + let broken_invariants = self.encode_broken_invariants()?; + let (allocate_parameters, deallocate_parameters) = + self.encode_parameters(broken_invariants)?; let (allocate_returns, deallocate_returns) = self.encode_returns()?; self.lifetime_token_permission = Some(self.fresh_ghost_variable("lifetime_token_perm_amount", vir_high::Type::MPerm)); - let (assume_preconditions, assert_postconditions) = match self.check_mode { - CheckMode::CoreProof => { - // Unsafe functions will come with CheckMode::Both because they - // are allowed to have preconditions. - (Vec::new(), Vec::new()) - } - CheckMode::Both | CheckMode::Specifications => { - self.encode_functional_specifications()? - } - }; + let (assume_preconditions, assert_postconditions, assert_panic_postconditions) = + self.encode_functional_specifications()?; + let dead_references = self.encode_dead_references_for_parameters()?; + // match self.check_mode { + // CheckMode::CoreProof => { + // // Unsafe functions will come with CheckMode::Both because they + // // are allowed to have preconditions. + // (Vec::new(), Vec::new()) + // } + // CheckMode::Both | CheckMode::Specifications => { + // self.encode_functional_specifications()? + // } + // }; let (assume_lifetime_preconditions, assert_lifetime_postconditions) = self.encode_lifetime_specifications()?; let termination_initialization = self.encode_termination_initialization()?; @@ -200,15 +364,90 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { pre_statements.push(old_label); pre_statements.extend(termination_initialization); pre_statements.extend(allocate_returns); - let mut post_statements = assert_postconditions; - post_statements.extend(deallocate_parameters); - post_statements.extend(deallocate_returns); - post_statements.extend(assert_lifetime_postconditions); - let mut procedure_builder = - ProcedureBuilder::new(name, self.check_mode, pre_statements, post_statements); - self.encode_body(&mut procedure_builder)?; + let mut post_success_statements = dead_references; + post_success_statements.extend(assert_postconditions); + post_success_statements.extend(deallocate_parameters); + post_success_statements.extend(deallocate_returns); + let post_statements = assert_lifetime_postconditions; + let mut resume_panic_statements = vec![vir_high::Statement::leak_all()]; + if self.no_panic { + resume_panic_statements.push(self.encoder.set_statement_error_ctxt( + vir_high::Statement::assert_no_pos(false.into()), + self.mir.span, + ErrorCtxt::NoPanicPanics, + self.def_id, + )?); + } else { + resume_panic_statements.extend(assert_panic_postconditions); + } + let procedure_position = + self.encoder + .register_error(self.mir.span, ErrorCtxt::Unexpected, self.def_id); + let mut procedure_builder = ProcedureBuilder::new( + name, + self.check_mode, + procedure_position, + pre_statements, + post_success_statements, + post_statements, + resume_panic_statements, + ); + for local_index in 0..self.mir.local_decls.len() { + // We do not use `encode_local` to avoid marking the variable as + // used. + let variable = self + .encoder + .encode_local_high(self.mir, local_index.into())?; + procedure_builder.add_non_aliased_place(variable.into()); + } + match self.encoding_kind { + ProcedureEncodingKind::Regular => self.encode_body(&mut procedure_builder)?, + ProcedureEncodingKind::PostconditionFrameCheck => { + assert!( + !self.is_drop_impl, + "Drop impl does not have a postcondition and, therefore, should not be checked" + ); + self.encode_postcondition_frame_check(&mut procedure_builder)?; + } + } self.encode_implicit_allocations(&mut procedure_builder)?; - Ok(procedure_builder.build()) + let mut procedure = procedure_builder.build(); + self.add_missing_live_locals(&mut procedure)?; + Ok(procedure) + } + + fn add_missing_live_locals( + &mut self, + procedure: &mut vir_high::ProcedureDecl, + ) -> SpannedEncodingResult<()> { + if !config::create_missing_storage_live() { + return Ok(()); + } + // let predecessors = self.mir.basic_blocks.predecessors(); + let predecessors = self.reachable_predecessors.clone(); + for (block, missing_local) in std::mem::take(&mut self.missing_live_locals) { + for predecessor in &predecessors[&block] { + if let Some(locals_live_in_block) = self.locals_live_in_block.get(predecessor) { + if !locals_live_in_block.contains(&missing_local) { + let statements = self.encode_statement_storage_live( + missing_local, + mir::Location { + block: *predecessor, + statement_index: 0, + }, + )?; + let label = self.encode_basic_block_label(*predecessor); + procedure + .basic_blocks + .get_mut(&label) + .unwrap() + .statements + .splice(0..0, statements); + } + } + } + } + Ok(()) } fn pure_sanity_checks(&self) -> SpannedEncodingResult<()> { @@ -250,6 +489,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { fn encode_parameters( &mut self, + broken_invariants: Vec, ) -> SpannedEncodingResult<(Vec, Vec)> { let mut allocation = vec![vir_high::Statement::comment( "Allocate the parameters.".to_string(), @@ -257,30 +497,252 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let mut deallocation = vec![vir_high::Statement::comment( "Deallocate the parameters.".to_string(), )]; - for mir_arg in self.mir.args_iter() { + if self.is_drop_impl { + self.encode_drop_impl_parameters(&mut allocation, &mut deallocation)?; + return Ok((allocation, deallocation)); + } + assert_eq!(broken_invariants.len(), self.mir.args_iter().count()); + for (mir_arg, is_invariant_broken) in + self.mir.args_iter().zip(broken_invariants.into_iter()) + { let parameter = self.encode_local(mir_arg)?; - let alloc_statement = vir_high::Statement::inhale_no_pos( - vir_high::Predicate::owned_non_aliased_no_pos(parameter.clone().into()), - ); - allocation.push(self.encoder.set_surrounding_error_context_for_statement( - alloc_statement, - parameter.position, - ErrorCtxt::UnexpectedStorageLive, - )?); - let mir_type = self.encoder.get_local_type(self.mir, mir_arg)?; - let size = self.encoder.encode_type_size_expression(mir_type)?; - let dealloc_statement = vir_high::Statement::exhale_no_pos( - vir_high::Predicate::memory_block_stack_no_pos(parameter.clone().into(), size), - ); - deallocation.push(self.encoder.set_surrounding_error_context_for_statement( - dealloc_statement, - parameter.position, - ErrorCtxt::UnexpectedStorageDead, - )?); + if is_invariant_broken { + self.encode_broken_invariant_parameter( + parameter, + &mut allocation, + &mut deallocation, + )?; + } else { + self.encode_normal_parameter( + mir_arg, + parameter, + &mut allocation, + &mut deallocation, + )?; + } } Ok((allocation, deallocation)) } + fn encode_normal_parameter( + &mut self, + mir_arg: mir::Local, + parameter: vir_high::Local, + allocation: &mut Vec, + deallocation: &mut Vec, + ) -> SpannedEncodingResult<()> { + let alloc_statement = vir_high::Statement::inhale_predicate_no_pos( + vir_high::Predicate::owned_non_aliased_no_pos(parameter.clone().into()), + ); + allocation.push(self.encoder.set_surrounding_error_context_for_statement( + alloc_statement, + parameter.position, + ErrorCtxt::UnexpectedStorageLive, + )?); + let mir_type = self.encoder.get_local_type(self.mir, mir_arg)?; + let size = self.encoder.encode_type_size_expression(mir_type)?; + let dealloc_statement = vir_high::Statement::exhale_predicate_no_pos( + vir_high::Predicate::memory_block_stack_no_pos(parameter.clone().into(), size), + ); + deallocation.push(self.encoder.set_surrounding_error_context_for_statement( + dealloc_statement, + parameter.position, + ErrorCtxt::UnexpectedStorageDead, + )?); + Ok(()) + } + + fn encode_broken_invariant_parameter( + &mut self, + parameter: vir_high::Local, + allocation: &mut Vec, + deallocation: &mut Vec, + ) -> SpannedEncodingResult<()> { + assert!(self.is_unsafe_function, "TODO: a proper error message that broken invarianats are allowed only on unsafe functions."); + let vir_high::Type::Reference(reference) = parameter.get_type() else { + unimplemented!("TODO: A proper error message that broken invariants are allowed only on references."); + }; + self.opened_reference_parameter_lifetimes + .push(reference.lifetime.clone()); + let address_memory_block = + self.encode_reference_address_memory_block(parameter.clone().into())?; + let alloc_statement = + vir_high::Statement::inhale_predicate_no_pos(address_memory_block.clone()); + allocation.push(self.encoder.set_surrounding_error_context_for_statement( + alloc_statement, + parameter.position, + ErrorCtxt::UnexpectedStorageLive, + )?); + let dealloc_statement = vir_high::Statement::exhale_predicate_no_pos(address_memory_block); + deallocation.push(self.encoder.set_surrounding_error_context_for_statement( + dealloc_statement, + parameter.position, + ErrorCtxt::UnexpectedStorageDead, + )?); + let deref_place = vir_high::Expression::deref_no_pos( + parameter.clone().into(), + (*reference.target_type).clone(), + ); + let type_decl = self + .encoder + .encode_type_def_high(deref_place.get_type(), true)?; + match type_decl { + vir_high::TypeDecl::Struct(struct_decl) => { + for field in struct_decl.fields { + let field_place = vir_high::Expression::field(deref_place.clone(), field, parameter.position); + let predicate = vir_high::Predicate::owned_non_aliased_no_pos(field_place); + allocation.push(self.encoder.set_surrounding_error_context_for_statement( + vir_high::Statement::inhale_predicate_no_pos(predicate.clone()), + parameter.position, + ErrorCtxt::UnexpectedStorageDead, + )?); + deallocation.push(self.encoder.set_surrounding_error_context_for_statement( + vir_high::Statement::exhale_predicate_no_pos(predicate), + parameter.position, + ErrorCtxt::UnexpectedStorageDead, + )?); + } + } + _ => unimplemented!("TODO: A proper error message that broken invariants are allowed only on structs. Got: {}", type_decl), + } + Ok(()) + } + + fn encode_reference_address_memory_block( + &mut self, + place: vir_high::Expression, + ) -> SpannedEncodingResult { + let position = place.position(); + let vir_high::Type::Reference(reference) = place.get_type() else { + unreachable!(); + }; + let pointer_type = vir_high::Type::pointer((*reference.target_type).clone()); + let address_field = vir_high::FieldDecl::reference_address(reference.clone()); + let address_place = vir_high::Expression::field(place, address_field, position); + let size = self + .encoder + .encode_type_size_expression_high(pointer_type)?; + Ok(vir_high::Predicate::memory_block_stack_no_pos( + address_place, + size, + )) + } + + fn encode_drop_impl_parameters( + &mut self, + allocation: &mut Vec, + deallocation: &mut Vec, + ) -> SpannedEncodingResult<()> { + let self_mir_arg = { + let mut args_iter = self.mir.args_iter(); + let self_arg = args_iter.next().unwrap(); + assert!(args_iter.next().is_none()); + self_arg + }; + let self_parameter = self.encode_local(self_mir_arg).unwrap(); + let vir_high::Type::Reference(self_reference) = self_parameter.get_type() else { + unreachable!(); + }; + self.opened_reference_parameter_lifetimes + .push(self_reference.lifetime.clone()); + // let address_place = vir_high::Expression::field( + // self_parameter.clone().into(), + // vir_high::FieldDecl::reference_address(self_reference.clone()), + // // vir_high::FieldDecl::new( + // // ADDRESS_FIELD_NAME, + // // 0usize, + // // vir_high::Type::Int(vir_high::ty::Int::Usize), + // // ), + // self_parameter.position, + // ); + // let pointer_type = vir_high::Type::pointer((*self_reference.target_type).clone()); + // let size = self + // .encoder + // .encode_type_size_expression_high(pointer_type)?; + let address_memory_block = + self.encode_reference_address_memory_block(self_parameter.clone().into())?; + let alloc_statement = vir_high::Statement::inhale_predicate_no_pos( + address_memory_block.clone(), // vir_high::Predicate::memory_block_stack_no_pos(address_place.clone(), size.clone()), + ); + allocation.push(self.encoder.set_surrounding_error_context_for_statement( + alloc_statement, + self_parameter.position, + ErrorCtxt::UnexpectedStorageLive, + )?); + let deref_place = vir_high::Expression::deref_no_pos( + self_parameter.clone().into(), + (*self_reference.target_type).clone(), + ); + let alloc_statement = vir_high::Statement::inhale_predicate_no_pos( + vir_high::Predicate::owned_non_aliased_no_pos(deref_place.clone()), + ); + allocation.push(self.encoder.set_surrounding_error_context_for_statement( + alloc_statement, + self_parameter.position, + ErrorCtxt::UnexpectedStorageLive, + )?); + let dealloc_statement = vir_high::Statement::exhale_predicate_no_pos( + address_memory_block, // vir_high::Predicate::memory_block_stack_no_pos(address_place.clone(), size), + ); + deallocation.push(self.encoder.set_surrounding_error_context_for_statement( + dealloc_statement, + self_parameter.position, + ErrorCtxt::UnexpectedStorageDead, + )?); + self.add_drop_impl_deallocation_statements( + deallocation, + self_parameter.position, + deref_place, + )?; + Ok(()) + } + + fn add_drop_impl_deallocation_statements( + &mut self, + deallocation: &mut Vec, + position: vir_high::Position, + place: vir_high::Expression, + ) -> SpannedEncodingResult<()> { + let type_decl = self.encoder.encode_type_def_high(place.get_type(), true)?; + match type_decl { + vir_high::TypeDecl::Bool + | vir_high::TypeDecl::Int(_) + | vir_high::TypeDecl::Float(_) + | vir_high::TypeDecl::TypeVar(_) + | vir_high::TypeDecl::Pointer(_) => { + unreachable!("Drop on a primitive type."); + } + vir_high::TypeDecl::Tuple(_) => todo!(), + vir_high::TypeDecl::Struct(struct_decl) => { + for field in struct_decl.fields { + let field_place = vir_high::Expression::field(place.clone(), field, position); + let dealloc_statement = vir_high::Statement::exhale_predicate_no_pos( + vir_high::Predicate::owned_non_aliased_no_pos(field_place), + ); + deallocation.push(self.encoder.set_surrounding_error_context_for_statement( + dealloc_statement, + position, + ErrorCtxt::UnexpectedStorageDead, + )?); + } + } + vir_high::TypeDecl::Sequence(_) => todo!(), + vir_high::TypeDecl::Map(_) => todo!(), + vir_high::TypeDecl::Enum(_) => todo!(), + vir_high::TypeDecl::Union(_) => todo!(), + vir_high::TypeDecl::Array(_) => todo!(), + vir_high::TypeDecl::Slice(_) => todo!(), + vir_high::TypeDecl::Reference(_) => todo!(), + vir_high::TypeDecl::Never => todo!(), + vir_high::TypeDecl::Closure(_) => todo!(), + vir_high::TypeDecl::Unsupported(_) => todo!(), + vir_high::TypeDecl::Trusted(_) => { + unimplemented!("A proper error message that drops can be implemented only on non-trusted types"); + } + } + Ok(()) + } + fn encode_returns( &mut self, ) -> SpannedEncodingResult<(Vec, Vec)> { @@ -288,17 +750,16 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let mir_type = self.encoder.get_local_type(self.mir, mir::RETURN_PLACE)?; let size = self.encoder.encode_type_size_expression(mir_type)?; let alloc_statement = self.encoder.set_surrounding_error_context_for_statement( - vir_high::Statement::inhale_no_pos(vir_high::Predicate::memory_block_stack_no_pos( - return_local.clone().into(), - size, - )), + vir_high::Statement::inhale_predicate_no_pos( + vir_high::Predicate::memory_block_stack_no_pos(return_local.clone().into(), size), + ), return_local.position, ErrorCtxt::UnexpectedStorageLive, )?; let dealloc_statement = self.encoder.set_surrounding_error_context_for_statement( - vir_high::Statement::exhale_no_pos(vir_high::Predicate::owned_non_aliased_no_pos( - return_local.clone().into(), - )), + vir_high::Statement::exhale_predicate_no_pos( + vir_high::Predicate::owned_non_aliased_no_pos(return_local.clone().into()), + ), return_local.position, ErrorCtxt::UnexpectedStorageDead, )?; @@ -314,52 +775,345 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { )) } + fn check_refinement( + &self, + specification: &SpecificationItem, + ) -> SpannedEncodingResult<()> { + if let SpecificationItem::Refined(_, _) = specification { + if self.encoder.env().query.is_drop_method_impl(self.def_id) { + // Contract refinement is allowed only of Drop::drop of private + // structs. + let self_arg = self.mir.args_iter().next().unwrap(); + let self_type = self.encoder.get_local_type(self.mir, self_arg)?; + match self_type.kind() { + ty::TyKind::Ref(_, target_type, _) => match target_type.kind() { + ty::TyKind::Adt(adt_def, _) => { + let vis = self.encoder.env().tcx().visibility(adt_def.did()); + let module = self + .encoder + .env() + .tcx() + .parent_module_from_def_id(adt_def.did().as_local().unwrap()) + .to_def_id(); + match vis { + ty::Visibility::Restricted(struct_visibility_module) + if struct_visibility_module == module => + { + // The struct is private. + } + _ => { + unimplemented!( + "TODO: A proper error message that the struct {:?} must be private", + adt_def.did(), + ); + } + } + } + _ => unimplemented!(), + }, + _ => unimplemented!(), + } + } else { + unimplemented!("contract refinement not supported: {specification:?}"); + } + } + Ok(()) + } + + /// * `is_unsafe` – whether the function is unsafe and can have structural preconditions. + /// * `include_functional` – whether to include functional preconditions. fn encode_precondition_expressions( &mut self, procedure_contract: &ProcedureContractMirDef<'tcx>, call_substs: SubstsRef<'tcx>, + is_unsafe: bool, + include_functional: bool, arguments: &[vir_high::Expression], ) -> SpannedEncodingResult> { let mut preconditions = Vec::new(); - for (assertion, assertion_substs) in - procedure_contract.functional_precondition(self.encoder.env(), call_substs) - { - let expression = self.encoder.encode_assertion_high( - assertion, - None, - arguments, - None, - self.def_id, - assertion_substs, - )?; - preconditions.push(expression); + let structural_preconditions = + procedure_contract.structural_precondition(self.encoder.env(), call_substs); + if is_unsafe { + for (assertion, assertion_substs) in structural_preconditions { + let expression = self.encoder.encode_assertion_high( + assertion, + None, + arguments, + None, + self.def_id, + assertion_substs, + )?; + preconditions.push(expression); + } + } else if !structural_preconditions.is_empty() { + return Err(SpannedEncodingError::incorrect( + "structural preconditions allowed only on unsafe functions", + self.mir.span, + )); + } + if include_functional { + for (assertion, assertion_substs) in + procedure_contract.functional_precondition(self.encoder.env(), call_substs) + { + let expression = self.encoder.encode_assertion_high( + assertion, + None, + arguments, + None, + self.def_id, + assertion_substs, + )?; + if !expression.is_pure() { + let span = self + .encoder + .error_manager() + .position_manager() + .get_span(expression.position().into()) + .cloned() + .unwrap(); + return Err(SpannedEncodingError::incorrect( + "only structural specifications can contain permissions", + span, + )); + } + preconditions.push(expression); + } } Ok(preconditions) } + /// * `is_unsafe` – whether the function is unsafe and can have structural postconditions. + /// * `include_functional` – whether to include functional postconditions. fn encode_postcondition_expressions( &mut self, procedure_contract: &ProcedureContractMirDef<'tcx>, call_substs: SubstsRef<'tcx>, + mode: PostconditionMode, arguments: Vec, result: &vir_high::Expression, precondition_label: &str, ) -> SpannedEncodingResult> { + let broken_invariants = + self.encode_contract_broken_invariants(procedure_contract, call_substs)?; + let broken_invariant_mask = + self.encode_broken_invariant_argument_mask(procedure_contract, call_substs)?; + assert_eq!(arguments.len(), broken_invariant_mask.len()); let mut postconditions = Vec::new(); let arguments_in_old: Vec<_> = arguments .into_iter() - .map(|argument| { - let position = argument.position(); - vir_high::Expression::labelled_old( - precondition_label.to_string(), - argument, + .zip(broken_invariant_mask.into_iter()) + .map(|(argument, is_broken_invariant)| { + if is_broken_invariant { + argument + } else { + let position = argument.position(); + vir_high::Expression::labelled_old( + precondition_label.to_string(), + argument, + position, + ) + } + }) + .collect(); + let structural_postconditions = + procedure_contract.structural_postcondition(self.encoder.env(), call_substs); + if mode.is_unsafe_function() { + for (assertion, assertion_substs) in structural_postconditions { + let expression = self.encoder.encode_assertion_high( + assertion, + Some(precondition_label), + &arguments_in_old, + Some(result), + self.def_id, + assertion_substs, + )?; + let expression = self.desugar_pledges_in_postcondition( + precondition_label, + result, + expression, + &broken_invariants, + )?; + postconditions.push(expression); + } + } else if !structural_postconditions.is_empty() { + return Err(SpannedEncodingError::incorrect( + "structural postconditions allowed only on unsafe functions", + self.mir.span, + )); + } + if mode.include_functional_ensures() { + let postcondition_assertions = + procedure_contract.functional_postcondition(self.encoder.env(), call_substs); + if mode.is_drop_implementation() { + assert!( + postcondition_assertions.is_empty(), + "TODO: implement support for non-structural postconditions on drop" + ); + } + for (assertion, assertion_substs) in postcondition_assertions { + let expression = self.encoder.encode_assertion_high( + assertion, + Some(precondition_label), + &arguments_in_old, + Some(result), + self.def_id, + assertion_substs, + )?; + let expression = self.desugar_pledges_in_postcondition( + precondition_label, + result, + expression, + &broken_invariants, + )?; + if !(expression.is_pure()) { + let span = self + .encoder + .error_manager() + .position_manager() + .get_span(expression.position().into()) + .cloned() + .unwrap(); + return Err(SpannedEncodingError::incorrect( + "only structural specifications can contain permissions", + span, + )); + } + postconditions.push(expression); + } + let pledges = procedure_contract.pledges(); + for Pledge { + reference, + lhs: body_lhs, + rhs: body_rhs, + } in pledges + { + trace!( + "pledge reference={:?} lhs={:?} rhs={:?}", + reference, + body_lhs, + body_rhs + ); + assert!( + reference.is_none(), + "The reference should be none in postcondition." + ); + assert!(body_lhs.is_none(), "assert on expiry is not supported yet."); + let assertion_rhs = self.encoder.encode_assertion_high( + *body_rhs, + Some(precondition_label), + &arguments_in_old, + Some(result), + self.def_id, + call_substs, + )?; + let position = assertion_rhs.position(); + let expression = vir_high::Expression::builtin_func_app( + vir_high::BuiltinFunc::AfterExpiry, + Vec::new(), + vec![assertion_rhs], + vir_high::Type::Bool, position, - ) + ); + let expression = self.desugar_pledges_in_postcondition( + precondition_label, + result, + expression, + &broken_invariants, + )?; + assert!( + expression.is_pure(), + "TODO: A proper error message that functional postconditions must be pure ({:?}): {expression}", + procedure_contract.def_id, + ); + postconditions.push(expression); + } + } + assert!(!mode.include_panic_ensures()); + Ok(postconditions) + } + + /// * `is_unsafe` – whether the function is unsafe and can have structural postconditions. + /// * `include_functional` – whether to include functional postconditions. + fn encode_panic_postcondition_expressions( + &mut self, + procedure_contract: &ProcedureContractMirDef<'tcx>, + call_substs: SubstsRef<'tcx>, + mode: PostconditionMode, + arguments: Vec, + result: &vir_high::Expression, + precondition_label: &str, + ) -> SpannedEncodingResult> { + let broken_invariants = + self.encode_contract_broken_invariants(procedure_contract, call_substs)?; + let broken_invariant_mask = + self.encode_broken_invariant_argument_mask(procedure_contract, call_substs)?; + assert_eq!(arguments.len(), broken_invariant_mask.len()); + let mut postconditions = Vec::new(); + let arguments_in_old: Vec<_> = arguments + .into_iter() + .zip(broken_invariant_mask.into_iter()) + .map(|(argument, is_broken_invariant)| { + if is_broken_invariant { + argument + } else { + let position = argument.position(); + vir_high::Expression::labelled_old( + precondition_label.to_string(), + argument, + position, + ) + } }) .collect(); - for (assertion, assertion_substs) in - procedure_contract.functional_postcondition(self.encoder.env(), call_substs) - { + let structural_postconditions = + procedure_contract.structural_panic_postcondition(self.encoder.env(), call_substs); + if mode.is_unsafe_function() { + // Note: it is fine to have structural postconditions on both safe + // and unsafe functions. Only structural preconditions are not + // allowed on safe functions. Structural postconditions are useful + // for specifying that some additional property is preserved no + // matter how the function exits. Alternatively, the user would need + // to duplicate the specification in `#[ensures]` and + // `#[panic_ensures]`. + for (assertion, assertion_substs) in structural_postconditions { + let expression = self.encoder.encode_assertion_high( + assertion, + Some(precondition_label), + &arguments_in_old, + Some(result), + self.def_id, + assertion_substs, + )?; + let expression = self.desugar_pledges_in_postcondition( + precondition_label, + result, + expression, + &broken_invariants, + )?; + assert!(!expression.find(result), "TODO: A proper error message that structural panic postconditions must not contain the result ({:?}): {expression}", procedure_contract.def_id); + postconditions.push(expression); + } + } else if !structural_postconditions.is_empty() { + return Err(SpannedEncodingError::incorrect( + "structural panic postconditions allowed only on unsafe functions", + self.mir.span, + )); + } + assert!(mode.include_panic_ensures()); + let postcondition_assertions = + procedure_contract.panic_postcondition(self.encoder.env(), call_substs); + if mode.is_drop_implementation() { + assert!( + postcondition_assertions.is_empty(), + "TODO: implement support for panic postconditions on drop" + ); + if !postconditions.is_empty() { + // We have drop with postconditions, so make sure it does + // not panic. + postconditions.push(false.into()); + } + } + for (assertion, assertion_substs) in postcondition_assertions { let expression = self.encoder.encode_assertion_high( assertion, Some(precondition_label), @@ -368,6 +1122,25 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { self.def_id, assertion_substs, )?; + let expression = self.desugar_pledges_in_postcondition( + precondition_label, + result, + expression, + &broken_invariants, + )?; + if !(expression.is_pure()) { + let span = self + .encoder + .error_manager() + .position_manager() + .get_span(expression.position().into()) + .cloned() + .unwrap(); + return Err(SpannedEncodingError::incorrect( + "only structural specifications can contain permissions", + span, + )); + } postconditions.push(expression); } Ok(postconditions) @@ -375,7 +1148,11 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { fn encode_functional_specifications( &mut self, - ) -> SpannedEncodingResult<(Vec, Vec)> { + ) -> SpannedEncodingResult<( + Vec, + Vec, + Vec, + )> { let mir_span = self.mir.span; let substs = self.encoder.env().query.identity_substs(self.def_id); // Retrieve the contract @@ -386,41 +1163,345 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let mut preconditions = vec![vir_high::Statement::comment( "Assume functional preconditions.".to_string(), )]; + let mut precondition_conjuncts = Vec::new(); let mut arguments: Vec = Vec::new(); + let mut framing_variables = Vec::new(); for local in self.mir.args_iter() { - arguments.push(self.encode_local(local)?.into()); + let parameter = self.encode_local(local)?; + framing_variables.push(parameter.variable.clone()); + arguments.push(parameter.into()); } - for expression in - self.encode_precondition_expressions(&procedure_contract, substs, &arguments)? - { - let assume_statement = self.encoder.set_statement_error_ctxt( - vir_high::Statement::assume_no_pos(expression), - mir_span, - ErrorCtxt::UnexpectedAssumeMethodPrecondition, - self.def_id, - )?; - preconditions.push(assume_statement); + self.check_refinement(&procedure_contract.specification.pres)?; + for expression in self.encode_precondition_expressions( + &procedure_contract, + substs, + self.is_unsafe_function || self.is_drop_impl, + self.check_mode.check_specifications(), + &arguments, + )? { + if let Some(expression) = self.convert_expression_to_check_mode( + expression, + !self.is_unsafe_function, + false, + &framing_variables, + )? { + let expression_with_pos = self.encoder.set_expression_error_ctxt( + expression, + mir_span, + ErrorCtxt::UnexpectedAssumeMethodPrecondition, + self.def_id, + ); + // let inhale_statement = self.encoder.set_statement_error_ctxt( + // vir_high::Statement::inhale_expression_no_pos(expression), + // mir_span, + // ErrorCtxt::UnexpectedAssumeMethodPrecondition, + // self.def_id, + // )?; + precondition_conjuncts.push(expression_with_pos); + } } + let inhale_statement = self.encoder.set_statement_error_ctxt( + vir_high::Statement::inhale_expression_no_pos( + precondition_conjuncts.into_iter().conjoin(), + Some(PRECONDITION_LABEL.to_string()), + ), + mir_span, + ErrorCtxt::UnexpectedAssumeMethodPrecondition, + self.def_id, + )?; + preconditions.push(inhale_statement); + let mut postconditions = vec![vir_high::Statement::comment( "Assert functional postconditions.".to_string(), )]; - let result: vir_high::Expression = self.encode_local(mir::RETURN_PLACE)?.into(); + let mut panic_postconditions = vec![vir_high::Statement::comment( + "Assert panic postconditions.".to_string(), + )]; + let mut postcondition_conjuncts = Vec::new(); + let result_variable = self.encode_local(mir::RETURN_PLACE)?; + framing_variables.push(result_variable.variable.clone()); + let result: vir_high::Expression = result_variable.into(); + let mut all_pure = true; + self.check_refinement(&procedure_contract.specification.posts)?; + let postcondition_context = + if self.no_panic_ensures_postcondition && self.check_mode == CheckMode::MemorySafety { + ErrorCtxt::AssertMethodPostconditionNoPanic + } else { + ErrorCtxt::AssertMethodPostcondition + }; for expression in self.encode_postcondition_expressions( &procedure_contract, substs, + PostconditionMode::regular_exit_on_definition_side( + self.is_unsafe_function, + self.check_mode, + self.no_panic_ensures_postcondition, + self.is_drop_impl, + ), + arguments.clone(), + &result, + PRECONDITION_LABEL, + )? { + if let Some(expression) = self.convert_expression_to_check_mode( + expression, + !self.is_unsafe_function, + self.no_panic_ensures_postcondition, + &framing_variables, + )? { + // We use different encoding based on purity because: + // * Silicon reports a failing exhale statement. Therefore, + // having multiple statements allows having more precise error + // messages. + // * However, if we have permissions in the postcondition, we + // have to exhale it as a single statement to ensure that the + // permission frames what is following it. + all_pure = all_pure && expression.is_pure(); + if all_pure { + let exhale_statement = self.encoder.set_statement_error_ctxt( + vir_high::Statement::exhale_expression_no_pos(expression, None), + mir_span, + postcondition_context.clone(), + self.def_id, + )?; + postconditions.push(exhale_statement); + } else { + // let expression_with_pos = self.encoder.set_expression_error_ctxt( + // expression, + // mir_span, + // ErrorCtxt::AssertMethodPostcondition, + // self.def_id, + // ); + // postcondition_conjuncts.push(expression_with_pos); + postcondition_conjuncts.push(expression); + } + } + } + let exhale_statement = self.encoder.set_statement_error_ctxt( + vir_high::Statement::exhale_expression_no_pos( + postcondition_conjuncts.into_iter().conjoin(), + None, + ), + mir_span, + postcondition_context.clone(), + self.def_id, + )?; + postconditions.push(exhale_statement); + let mut panic_postcondition_conjuncts = Vec::new(); + for expression in self.encode_panic_postcondition_expressions( + &procedure_contract, + substs, + PostconditionMode::panic_exit_on_definition_side( + self.is_unsafe_function, + self.check_mode, + self.is_drop_impl, + ), arguments, &result, PRECONDITION_LABEL, )? { - let assert_statement = self.encoder.set_statement_error_ctxt( - vir_high::Statement::assert_no_pos(expression), - mir_span, - ErrorCtxt::AssertMethodPostcondition, - self.def_id, - )?; - postconditions.push(assert_statement); + if let Some(expression) = self.convert_expression_to_check_mode( + expression, + !self.is_unsafe_function, + self.no_panic_ensures_postcondition, + &framing_variables, + )? { + // We use different encoding based on purity because: + // * Silicon reports a failing exhale statement. Therefore, + // having multiple statements allows having more precise error + // messages. + // * However, if we have permissions in the postcondition, we + // have to exhale it as a single statement to ensure that the + // permission frames what is following it. + all_pure = all_pure && expression.is_pure(); + if all_pure { + let exhale_statement = self.encoder.set_statement_error_ctxt( + vir_high::Statement::exhale_expression_no_pos(expression, None), + mir_span, + postcondition_context.clone(), + self.def_id, + )?; + panic_postconditions.push(exhale_statement); + } else { + panic_postcondition_conjuncts.push(expression); + } + } } - Ok((preconditions, postconditions)) + let exhale_statement = self.encoder.set_statement_error_ctxt( + vir_high::Statement::exhale_expression_no_pos( + panic_postcondition_conjuncts.into_iter().conjoin(), + None, + ), + mir_span, + postcondition_context, + self.def_id, + )?; + panic_postconditions.push(exhale_statement); + + Ok((preconditions, postconditions, panic_postconditions)) + } + + /// Returns a list of reference-typed parameters for which the invariant is + /// potentially broken. + fn encode_broken_invariants(&mut self) -> SpannedEncodingResult> { + let mir_span = self.mir.span; + let substs = self.encoder.env().query.identity_substs(self.def_id); + // Retrieve the contract + let procedure_contract = self + .encoder + .get_mir_procedure_contract_for_def(self.def_id, substs) + .with_span(mir_span)?; + // let mut arguments: Vec = Vec::new(); + // for local in self.mir.args_iter() { + // let parameter = self.encode_local(local)?; + // arguments.push(parameter.into()); + // } + // let mut preconditions = Vec::new(); + // for (assertion, assertion_substs) in + // procedure_contract.broken_precondition_invariants(self.encoder.env(), substs) + // { + // let expression = self.encoder.encode_assertion_high( + // assertion, + // None, + // &arguments, + // None, + // self.def_id, + // assertion_substs, + // )?; + // match expression { + // vir_high::Expression::FuncApp(mut app) => { + // assert_eq!( + // app.function_name, + // "m_prusti_contracts$$prusti_broken_invariant" + // ); + // assert_eq!(app.arguments.len(), 1); + // match app.arguments.pop() { + // Some(vir_high::Expression::Local(local)) => { + // preconditions.push(local.variable); + // } + // _ => unreachable!(), + // } + // } + // _ => { + // unreachable!(); + // } + // } + // } + // Ok(preconditions) + self.encode_broken_invariant_argument_mask(&procedure_contract, substs) + } + + /// Returns a list of reference-typed parameters for which the invariant is + /// potentially broken. + fn encode_broken_invariant_argument_mask( + &mut self, + procedure_contract: &ProcedureContractMirDef<'tcx>, + substs: SubstsRef<'tcx>, + ) -> SpannedEncodingResult> { + let mut is_invariant_broken = vec![false; procedure_contract.args.len()]; + let broken_invariants = + self.encode_contract_broken_invariants(procedure_contract, substs)?; + let mut arguments: Vec = Vec::new(); + for local in &procedure_contract.args { + let parameter = self.encode_local(*local)?; + arguments.push(parameter.into()); + } + let mut found_count = 0; + for (i, arg) in arguments.iter().enumerate() { + if broken_invariants.contains(arg) { + is_invariant_broken[i] = true; + found_count += 1; + } + } + assert_eq!(found_count, broken_invariants.len()); + // let mut arguments: Vec = Vec::new(); + // for local in &procedure_contract.args { + // let parameter = self.encode_local(*local)?; + // arguments.push(parameter.into()); + // } + // let mut is_invariant_broken = vec![false; procedure_contract.args.len()]; + // for (assertion, assertion_substs) in + // procedure_contract.broken_precondition_invariants(self.encoder.env(), substs) + // { + // let expression = self.encoder.encode_assertion_high( + // assertion, + // None, + // &arguments, + // None, + // self.def_id, + // assertion_substs, + // )?; + // match expression { + // vir_high::Expression::FuncApp(mut app) => { + // assert_eq!( + // app.function_name, + // "m_prusti_contracts$$prusti_broken_invariant" + // ); + // assert_eq!(app.arguments.len(), 1); + // match app.arguments.pop() { + // Some(local) => { + // let mut found = false; + // for (i, arg) in arguments.iter().enumerate() { + // if arg == &local { + // is_invariant_broken[i] = true; + // found = true; + // break; + // } + // } + // assert!(found); + // } + // _ => unreachable!(), + // } + // } + // _ => { + // unreachable!(); + // } + // } + // } + Ok(is_invariant_broken) + } + + fn encode_contract_broken_invariants( + &mut self, + procedure_contract: &ProcedureContractMirDef<'tcx>, + substs: SubstsRef<'tcx>, + ) -> SpannedEncodingResult> { + let mut arguments: Vec = Vec::new(); + for local in &procedure_contract.args { + let parameter = self.encode_local(*local)?; + arguments.push(parameter.into()); + } + let mut broken_invariants = Vec::new(); + for (assertion, assertion_substs) in + procedure_contract.broken_precondition_invariants(self.encoder.env(), substs) + { + let expression = self.encoder.encode_assertion_high( + assertion, + None, + &arguments, + None, + self.def_id, + assertion_substs, + )?; + match expression { + vir_high::Expression::FuncApp(mut app) => { + assert_eq!( + app.function_name, + "m_prusti_contracts$$prusti_broken_invariant" + ); + assert_eq!(app.arguments.len(), 1); + match app.arguments.pop() { + Some(local) => { + broken_invariants.push(local); + } + _ => unreachable!(), + } + } + _ => { + unreachable!(); + } + } + } + Ok(broken_invariants) } fn encode_implicit_allocations( @@ -441,14 +1522,14 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { ); procedure_builder.add_alloc_statement( self.encoder.set_surrounding_error_context_for_statement( - vir_high::Statement::inhale_no_pos(predicate.clone()), + vir_high::Statement::inhale_predicate_no_pos(predicate.clone()), encoded_local.position, ErrorCtxt::UnexpectedStorageLive, )?, ); procedure_builder.add_dealloc_statement( self.encoder.set_surrounding_error_context_for_statement( - vir_high::Statement::exhale_no_pos(predicate.clone()), + vir_high::Statement::exhale_predicate_no_pos(predicate.clone()), encoded_local.position, ErrorCtxt::UnexpectedStorageLive, )?, @@ -473,15 +1554,17 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { } block_builder.build(); procedure_builder.set_entry(entry_label); - self.encode_specification_blocks()?; + self.encode_specification_blocks(procedure_builder.name())?; self.reachable_blocks .insert(self.mir.basic_blocks.start_node()); + let _predecessors = self.mir.basic_blocks.predecessors(); for (bb, data) in prusti_rustc_interface::middle::mir::traversal::reverse_postorder(self.mir) { if !self.specification_blocks.is_specification_block(bb) && self.reachable_blocks.contains(&bb) { + self.create_locals_live_entry(bb)?; self.encode_basic_block(procedure_builder, bb, data)?; } } @@ -490,6 +1573,377 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { "not consumed loop invariant: {:?}", self.loop_invariant_encoding.keys() ); + // assert!( // FIXME: Uncomment + // self.specification_region_encoding_statements.is_empty(), + // "not consumed specification region: {:?}", + // self.specification_region_encoding_statements.keys() + // ); + assert!( + self.specification_on_drop_unwind.is_empty(), + "not consumed specification on drop unwind: {:?}", + self.specification_on_drop_unwind.keys() + ); + // assert!( // FIXME: Uncomment + // self.specification_before_drop.is_empty(), + // "not consumed specification before drop: {:?}", + // self.specification_before_drop.keys() + // ); + // assert!( // FIXME: Uncomment + // self.specification_after_drop.is_empty(), + // "not consumed specification after drop: {:?}", + // self.specification_after_drop.keys() + // ); + assert!( + self.add_specification_before_terminator.is_empty(), + "not consumed specification before terminator: {:?}", + self.add_specification_before_terminator.keys() + ); + Ok(()) + } + + fn encode_postcondition_frame_check( + &mut self, + procedure_builder: &mut ProcedureBuilder, + ) -> SpannedEncodingResult<()> { + // FIXME: code duplication with encode_function_call. + let entry_label = vir_high::BasicBlockId::new("label_entry".to_string()); + let mut block_builder = procedure_builder.create_basic_block_builder(entry_label.clone()); + let location = mir::Location { + block: 0usize.into(), + statement_index: 0, + }; + let span = self.mir.span; + let called_def_id = self.def_id; + let call_substs = self.encoder.env().query.identity_substs(called_def_id); + let args: Vec<_> = self + .mir + .args_iter() + .map(|arg| mir::Operand::Move(arg.into())) + .collect(); + let target_place_local = mir::RETURN_PLACE; + let destination: mir::Place = target_place_local.into(); + // let target = Some(1usize.into()); + // let cleanup = Some(1usize.into()); + + let is_unsafe = self.encoder.env().query.is_unsafe_function(called_def_id); + let is_checked = false; + + // self.encode_function_call(&mut block_builder, location, span, called_def_id, call_substs, &args, destination, &target, &cleanup)?; + + let old_label = self.fresh_old_label(); + block_builder.add_statement(self.encoder.set_statement_error_ctxt( + vir_high::Statement::old_label_no_pos(old_label.clone()), + span, + ErrorCtxt::ProcedureCall, + self.def_id, + )?); + + let procedure_contract = self + .encoder + .get_mir_procedure_contract_for_call(self.def_id, called_def_id, call_substs) + .with_span(span)?; + let broken_invariants = + self.encode_broken_invariant_argument_mask(&procedure_contract, call_substs)?; + + let mut arguments = Vec::new(); + let mut consume_arguments = Vec::new(); + let mut broken_invariant_places = Vec::new(); + let mut broken_invariant_address_memory_blocks = Vec::new(); + for (arg, is_invariant_broken) in args.iter().zip(broken_invariants.iter()) { + // FIXME: Code repetition with encode_function_call. + arguments.push( + self.encoder + .encode_operand_high(self.mir, arg, span) + .with_span(span)?, + ); + if *is_invariant_broken { + match arg { + mir::Operand::Copy(_) => unimplemented!( + "TODO: A proper error message that only moved references are supported" + ), + mir::Operand::Move(place) => { + let encoded_place = self.encode_place(*place, None)?; + let address_memory_block = + self.encode_reference_address_memory_block(encoded_place)?; + broken_invariant_address_memory_blocks.push(address_memory_block.clone()); + let dealloc_address_statement = + vir_high::Statement::exhale_predicate_no_pos(address_memory_block); + consume_arguments.add_statement(self.encoder.set_statement_error_ctxt( + dealloc_address_statement, + span, + ErrorCtxt::ProcedureCall, + self.def_id, + )?); + let deref_place = self.encoder.env().tcx().mk_place_deref(*place); + for field_place in analysis::mir_utils::expand_struct_place( + deref_place, + self.mir, + self.encoder.env().tcx(), + None, + ) { + let encoded_arg = self.encode_place(field_place, None)?; + broken_invariant_places.push(encoded_arg.clone()); + let statement = vir_high::Statement::exhale_predicate_no_pos( + vir_high::Predicate::owned_non_aliased_no_pos(encoded_arg), + ); + consume_arguments.add_statement( + self.encoder.set_statement_error_ctxt( + statement, + span, + ErrorCtxt::ProcedureCall, + self.def_id, + )?, + ); + } + } + mir::Operand::Constant(_) => unimplemented!( + "TODO: A proper error message that only moved references are supported" + ), + } + } else { + let encoded_arg = + self.encode_statement_operand_no_refs(&mut consume_arguments, location, arg)?; + let statement = vir_high::Statement::consume_no_pos(encoded_arg); + consume_arguments.add_statement(self.encoder.set_statement_error_ctxt( + statement, + span, + ErrorCtxt::ProcedureCall, + self.def_id, + )?); + } + } + assert_eq!(arguments.len(), broken_invariants.len()); + + self.check_refinement(&procedure_contract.specification.pres)?; + let precondition_expressions = self.encode_precondition_expressions( + &procedure_contract, + call_substs, + self.is_unsafe_function, + self.check_mode.check_specifications(), + &arguments, + )?; + let mut precondition_conjuncts = Vec::new(); + for expression in precondition_expressions { + if let Some(expression) = self.convert_expression_to_check_mode_call_site( + expression, is_unsafe, is_checked, &arguments, + )? { + // let exhale_statement = self.encoder.set_statement_error_ctxt( + // vir_high::Statement::exhale_expression_no_pos(expression), + // span, + // ErrorCtxt::ExhaleMethodPrecondition, + // self.def_id, + // )?; + // block_builder.add_statement(exhale_statement); + let conjunct = self.encoder.set_expression_error_ctxt( + expression, + span, + ErrorCtxt::ExhaleMethodPrecondition, + self.def_id, + ); + precondition_conjuncts.push(conjunct); + } + } + let exhale_statement = self.encoder.set_statement_error_ctxt( + vir_high::Statement::exhale_expression_no_pos( + precondition_conjuncts.into_iter().conjoin(), + Some(old_label.clone()), + ), + span, + ErrorCtxt::ExhaleMethodPrecondition, + self.def_id, + )?; + block_builder.add_statement(exhale_statement); + block_builder.add_statements(consume_arguments); + + let position = self.register_error(location, ErrorCtxt::ProcedureCall); + let encoded_target_place = self + .encode_place(destination, None)? + .set_default_position(position); + self.check_refinement(&procedure_contract.specification.posts)?; + let postcondition_expressions = self.encode_postcondition_expressions( + &procedure_contract, + call_substs, + PostconditionMode::regular_exit_on_definition_side( + self.is_unsafe_function, + self.check_mode, + self.no_panic_ensures_postcondition, + self.is_drop_impl, + ), + arguments.clone(), + &encoded_target_place, + &old_label, + )?; + let panic_postcondition_expressions = self.encode_panic_postcondition_expressions( + &procedure_contract, + call_substs, + PostconditionMode::panic_exit_on_definition_side( + self.is_unsafe_function, + self.check_mode, + self.is_drop_impl, + ), + arguments.clone(), + &encoded_target_place, + &old_label, + )?; + let size = self.encoder.encode_type_size_expression( + self.encoder.get_local_type(self.mir, target_place_local)?, + )?; + let target_memory_block = + vir_high::Predicate::memory_block_stack_no_pos(encoded_target_place.clone(), size); + block_builder.add_statement(self.encoder.set_statement_error_ctxt( + vir_high::Statement::exhale_predicate_no_pos(target_memory_block), + span, + ErrorCtxt::ProcedureCall, + self.def_id, + )?); + let fresh_destination_label = self.fresh_basic_block_label(); + let mut destination_block = + block_builder.create_basic_block_builder(fresh_destination_label.clone()); + let statement = vir_high::Statement::inhale_predicate_no_pos( + vir_high::Predicate::owned_non_aliased_no_pos(encoded_target_place.clone()), + ); + destination_block.add_statement(self.encoder.set_statement_error_ctxt( + statement, + span, + ErrorCtxt::ProcedureCall, + self.def_id, + )?); + for memory_block in broken_invariant_address_memory_blocks { + let statement = vir_high::Statement::inhale_predicate_no_pos(memory_block); + destination_block.add_statement(self.encoder.set_statement_error_ctxt( + statement, + span, + ErrorCtxt::ProcedureCall, + self.def_id, + )?); + } + for encoded_place in broken_invariant_places { + let statement = vir_high::Statement::inhale_predicate_no_pos( + vir_high::Predicate::owned_non_aliased_no_pos(encoded_place), + ); + destination_block.add_statement(self.encoder.set_statement_error_ctxt( + statement, + span, + ErrorCtxt::ProcedureCall, + self.def_id, + )?); + } + let result_place = vec![encoded_target_place.clone()]; + let mut postcondition_conjuncts = Vec::new(); + for expression in postcondition_expressions { + if let Some(expression) = self.convert_expression_to_check_mode_call_site( + expression, + is_unsafe, + is_checked, + &result_place, + )? { + // let inhale_statement = self.encoder.set_statement_error_ctxt( + // vir_high::Statement::inhale_expression_no_pos(expression), + // span, + // ErrorCtxt::MethodPostconditionFraming, + // self.def_id, + // )?; + // block_builder.add_statement(inhale_statement); + let conjunct = self.encoder.set_expression_error_ctxt( + expression, + span, + ErrorCtxt::MethodPostconditionFraming, + self.def_id, + ); + postcondition_conjuncts.push(conjunct); + } + } + let inhale_statement = self.encoder.set_statement_error_ctxt( + vir_high::Statement::inhale_expression_no_pos( + postcondition_conjuncts.into_iter().conjoin(), + None, + ), + span, + ErrorCtxt::MethodPostconditionFraming, + self.def_id, + )?; + destination_block.add_statement(inhale_statement); + let assume_statement = self.encoder.set_statement_error_ctxt( + vir_high::Statement::assume_no_pos(false.into()), + span, + ErrorCtxt::UnexpectedAssumeEndMethodPostconditionFraming, + self.def_id, + )?; + destination_block.add_statement(assume_statement.clone()); + destination_block.set_successor_exit(SuccessorExitKind::Return); + destination_block.build(); + + let mut panic_postcondition_conjuncts = Vec::new(); + for expression in panic_postcondition_expressions { + if let Some(expression) = self.convert_expression_to_check_mode_call_site( + expression, + is_unsafe, + is_checked, + &result_place, + )? { + let conjunct = self.encoder.set_expression_error_ctxt( + expression, + span, + ErrorCtxt::MethodPostconditionFraming, + self.def_id, + ); + panic_postcondition_conjuncts.push(conjunct); + } + } + let panic_inhale_statement = self.encoder.set_statement_error_ctxt( + vir_high::Statement::inhale_expression_no_pos( + panic_postcondition_conjuncts.into_iter().conjoin(), + None, + ), + span, + ErrorCtxt::MethodPostconditionFraming, + self.def_id, + )?; + let fresh_cleanup_label = self.fresh_basic_block_label(); + let mut cleanup_block = + block_builder.create_basic_block_builder(fresh_cleanup_label.clone()); + cleanup_block.add_statement(panic_inhale_statement); + cleanup_block.add_statement(assume_statement); + cleanup_block.set_successor_exit(SuccessorExitKind::Return); + cleanup_block.build(); + + block_builder.set_successor_jump(vir_high::Successor::NonDetChoice( + fresh_destination_label, + fresh_cleanup_label, + )); + + block_builder.build(); + procedure_builder.set_entry(entry_label); + Ok(()) + } + + fn create_locals_live_entry(&mut self, bb: mir::BasicBlock) -> SpannedEncodingResult<()> { + let mut predecessors_iter = self + .reachable_predecessors + .entry(bb) + .or_default() + .iter() + .filter(|predecessor| { + !self + .specification_blocks + .is_specification_block(**predecessor) + && self.reachable_blocks.contains(predecessor) + }); + let locals_live_in_block = if let Some(first_predecessor) = predecessors_iter.next() { + let mut locals_live_in_block = self.locals_live_in_block[first_predecessor].clone(); + for predecessor in predecessors_iter { + if let Some(predecessor_locals) = &self.locals_live_in_block.get(predecessor) { + locals_live_in_block.retain(|local| predecessor_locals.contains(local)); + } + } + locals_live_in_block + } else { + BTreeSet::new() + }; + assert!(self + .locals_live_in_block + .insert(bb, locals_live_in_block) + .is_none()); Ok(()) } @@ -500,9 +1954,11 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { data: &mir::BasicBlockData<'tcx>, ) -> SpannedEncodingResult<()> { self.derived_lifetimes_yet_to_kill.clear(); - self.reborrow_lifetimes_to_remove_for_block + let to_remove = self + .reborrow_lifetimes_to_remove_for_block .entry(bb) .or_insert_with(BTreeSet::new); + to_remove.extend(self.lifetimes.get_all_ignored_loans()); self.current_basic_block = Some(bb); let label = self.encode_basic_block_label(bb); let mut block_builder = procedure_builder.create_basic_block_builder(label); @@ -541,7 +1997,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { self.encode_lifetimes_dead_on_edge( &mut block_builder, RichLocation::Mid(location), - RichLocation::Mid(mir::Location { + RichLocation::Start(mir::Location { block: location.block, statement_index: location.statement_index + 1, }), @@ -557,6 +2013,20 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { )?; } } + if let Some(statements) = self.add_specification_before_terminator.remove(&bb) { + self.apply_encoding_actions_on_edge( + &mut block_builder, + statements, + RichLocation::Mid(location), + RichLocation::Start(mir::Location { + block: location.block, + statement_index: location.statement_index + 1, + }), + &mut original_lifetimes, + &mut derived_lifetimes, + )?; + // block_builder.add_statements(statements); + } if let Some(terminator) = terminator { self.encode_lft_for_statement_mid( &mut block_builder, @@ -566,7 +2036,13 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { None, )?; let terminator = &terminator.kind; - self.encode_terminator(&mut block_builder, location, terminator)?; + self.encode_terminator( + &mut block_builder, + location, + terminator, + &mut original_lifetimes, + &mut derived_lifetimes, + )?; } if let Some(statement) = self.loop_invariant_encoding.remove(&bb) { if self.needs_termination(bb) @@ -593,34 +2069,40 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { ) -> SpannedEncodingResult<()> { block_builder.add_comment(format!("{location:?} {statement:?}")); match &statement.kind { + mir::StatementKind::StorageLive(local) + if self + .locals_used_only_in_specification_regions + .contains(local) => + { + block_builder.add_comment(format!("StorageLive for local {:?} is ignored because it is only used in specification regions", local)); + } + mir::StatementKind::StorageDead(local) + if self + .locals_used_only_in_specification_regions + .contains(local) => + { + block_builder.add_comment(format!("StorageDead for local {:?} is ignored because it is only used in specification regions", local)); + } mir::StatementKind::StorageLive(local) => { self.locals_without_explicit_allocation.remove(local); - let memory_block = self - .encoder - .encode_memory_block_for_local(self.mir, *local)?; - block_builder.add_statement(self.set_statement_error( - location, - ErrorCtxt::UnexpectedStorageLive, - vir_high::Statement::inhale_no_pos(memory_block), - )?); - let memory_block_drop = self - .encoder - .encode_memory_block_drop_for_local(self.mir, *local)?; - block_builder.add_statement(self.set_statement_error( - location, - ErrorCtxt::UnexpectedStorageLive, - vir_high::Statement::inhale_no_pos(memory_block_drop), - )?); + let block_locals = self.locals_live_in_block.get_mut(&location.block).unwrap(); + assert!(block_locals.insert(*local)); + let statements = self.encode_statement_storage_live(*local, location)?; + block_builder.add_statements(statements); } mir::StatementKind::StorageDead(local) => { self.locals_without_explicit_allocation.remove(local); + let block_locals = self.locals_live_in_block.get_mut(&location.block).unwrap(); + if !block_locals.remove(local) { + self.missing_live_locals.push((location.block, *local)); + } let memory_block = self .encoder .encode_memory_block_for_local(self.mir, *local)?; block_builder.add_statement(self.set_statement_error( location, ErrorCtxt::UnexpectedStorageDead, - vir_high::Statement::exhale_no_pos(memory_block), + vir_high::Statement::exhale_predicate_no_pos(memory_block), )?); let memory_block_drop = self .encoder @@ -628,7 +2110,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { block_builder.add_statement(self.set_statement_error( location, ErrorCtxt::UnexpectedStorageDead, - vir_high::Statement::exhale_no_pos(memory_block_drop), + vir_high::Statement::exhale_predicate_no_pos(memory_block_drop), )?); } mir::StatementKind::Assign(box (target, source)) => { @@ -645,6 +2127,31 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { Ok(()) } + fn encode_statement_storage_live( + &mut self, + local: mir::Local, + location: mir::Location, + ) -> SpannedEncodingResult> { + let mut statements = Vec::new(); + let memory_block = self + .encoder + .encode_memory_block_for_local(self.mir, local)?; + statements.push(self.set_statement_error( + location, + ErrorCtxt::UnexpectedStorageLive, + vir_high::Statement::inhale_predicate_no_pos(memory_block), + )?); + let memory_block_drop = self + .encoder + .encode_memory_block_drop_for_local(self.mir, local)?; + statements.push(self.set_statement_error( + location, + ErrorCtxt::UnexpectedStorageLive, + vir_high::Statement::inhale_predicate_no_pos(memory_block_drop), + )?); + Ok(statements) + } + fn encode_statement_assign( &mut self, block_builder: &mut BasicBlockBuilder, @@ -658,7 +2165,8 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { self.encode_assign_operand(block_builder, location, encoded_target, operand)?; } mir::Rvalue::Repeat(operand, count) => { - let encoded_operand = self.encode_statement_operand(location, operand)?; + let encoded_operand = + self.encode_statement_operand_no_refs(block_builder, location, operand)?; let encoded_count = self.encoder.compute_array_len(*count).with_span(span)?; let encoded_rvalue = vir_high::Rvalue::repeat(encoded_operand, encoded_count); let assign_statement = vir_high::Statement::assign( @@ -673,10 +2181,14 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { )?); } mir::Rvalue::Ref(region, borrow_kind, place) => { - let is_reborrow = place - .iter_projections() - .filter(|(_ref, projection)| projection == &mir::ProjectionElem::Deref) - .last(); + // let is_reborrow = place + // .iter_projections() + // .filter(|(place, projection)| { + // projection == &mir::ProjectionElem::Deref + // && place.ty(self.mir, self.encoder.env().tcx()).ty.is_ref() + // }) + // .last(); + let is_reborrow = self.check_if_reborrow(*place); let uniquness = match borrow_kind { mir::BorrowKind::Mut { .. } => vir_high::ty::Uniqueness::Unique, _ => vir_high::ty::Uniqueness::Shared, @@ -685,14 +2197,15 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let region_name = region.to_text(); let new_borrow_lifetime = vir_high::ty::LifetimeConst { name: region_name }; - let encoded_rvalue = if let Some((place, _)) = is_reborrow { - let reference_type = place.ty(self.mir, self.encoder.env().tcx()); - let deref_lifetime = match reference_type.ty.kind() { - ty::TyKind::Ref(region, _, _) => vir_high::ty::LifetimeConst { - name: region.to_text(), - }, - _ => unreachable!(), - }; + let encoded_rvalue = if let Some((_, region)) = is_reborrow { + // let reference_type = place.ty(self.mir, self.encoder.env().tcx()); + // let deref_lifetime = match reference_type.ty.kind() { + // ty::TyKind::Ref(region, _, _) => vir_high::ty::LifetimeConst { + // name: region.to_text(), + // }, + // _ => unreachable!(), + // }; + let deref_lifetime = vir_high::ty::LifetimeConst::new(region.to_text()); if let vir_high::Expression::Local(local) = &encoded_target { self.points_to_reborrow.insert(local.clone()); } @@ -741,10 +2254,26 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { vir_high::Statement::assign_no_pos(encoded_target, encoded_rvalue), )?); } - // mir::Rvalue::Cast(CastKind, Operand<'tcx>, Ty<'tcx>), + mir::Rvalue::Cast(_kind, operand, ty) => { + let encoded_operand = + self.encode_statement_operand_no_refs(block_builder, location, operand)?; + let ty = self.encoder.encode_type_high(*ty)?; + let encoded_rvalue = vir_high::Rvalue::cast(encoded_operand, ty); + block_builder.add_statement(self.set_statement_error( + location, + ErrorCtxt::Assign, + vir_high::Statement::assign_no_pos(encoded_target, encoded_rvalue), + )?); + // self.encode_assign_cast(block_builder, location, encoded_target, *kind, operand, *ty)?; + // TODO: For raw pointers do nothing because we care only about + // the type of the target. + // unimplemented!("kind={kind:?} operand={operand:?} ty={ty:?}"); + } mir::Rvalue::BinaryOp(op, box (left, right)) => { - let encoded_left = self.encode_statement_operand(location, left)?; - let encoded_right = self.encode_statement_operand(location, right)?; + let (encoded_left, left_post_statements) = + self.encode_statement_operand(block_builder, location, left)?; + let (encoded_right, right_post_statements) = + self.encode_statement_operand(block_builder, location, right)?; let kind = self.encode_binary_op_kind(*op, encoded_target.get_type())?; let encoded_rvalue = vir_high::Rvalue::binary_op(kind, encoded_left, encoded_right); block_builder.add_statement(self.set_statement_error( @@ -752,10 +2281,14 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { ErrorCtxt::Assign, vir_high::Statement::assign_no_pos(encoded_target, encoded_rvalue), )?); + block_builder.add_statements(left_post_statements); + block_builder.add_statements(right_post_statements); } mir::Rvalue::CheckedBinaryOp(op, box (left, right)) => { - let encoded_left = self.encode_statement_operand(location, left)?; - let encoded_right = self.encode_statement_operand(location, right)?; + let (encoded_left, left_post_statements) = + self.encode_statement_operand(block_builder, location, left)?; + let (encoded_right, right_post_statements) = + self.encode_statement_operand(block_builder, location, right)?; let kind = self.encode_binary_op_kind(*op, encoded_target.get_type())?; let encoded_rvalue = vir_high::Rvalue::checked_binary_op(kind, encoded_left, encoded_right); @@ -764,10 +2297,13 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { ErrorCtxt::Assign, vir_high::Statement::assign_no_pos(encoded_target, encoded_rvalue), )?); + block_builder.add_statements(left_post_statements); + block_builder.add_statements(right_post_statements); } // mir::Rvalue::NullaryOp(NullOp, Ty<'tcx>), mir::Rvalue::UnaryOp(op, operand) => { - let encoded_operand = self.encode_statement_operand(location, operand)?; + let (encoded_operand, post_statements) = + self.encode_statement_operand(block_builder, location, operand)?; let kind = match op { mir::UnOp::Not => vir_high::UnaryOpKind::Not, mir::UnOp::Neg => vir_high::UnaryOpKind::Minus, @@ -778,15 +2314,16 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { ErrorCtxt::Assign, vir_high::Statement::assign_no_pos(encoded_target, encoded_rvalue), )?); + block_builder.add_statements(post_statements); } mir::Rvalue::Discriminant(place) => { let encoded_place = self.encode_place(*place, None)?; - let deref_base = encoded_place.get_dereference_base().cloned(); - let source_permission = self.encode_open_reference( + // let deref_base = encoded_place.get_dereference_base().cloned(); + let source_permission = self.encode_automatic_open_reference( block_builder, location, - &deref_base, + // &deref_base, encoded_place.clone(), )?; @@ -800,10 +2337,10 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { vir_high::Statement::assign_no_pos(encoded_target, encoded_rvalue), )?); - self.encode_close_reference( + self.encode_automatic_close_reference( block_builder, location, - &deref_base, + // &deref_base, encoded_place, source_permission, )?; @@ -874,7 +2411,8 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let mut encoded_operands = Vec::new(); for operand in operands { - let mut encoded_operand = self.encode_statement_operand(location, operand)?; + let mut encoded_operand = + self.encode_statement_operand_no_refs(block_builder, location, operand)?; let new_expression = encoded_operand .expression .clone() @@ -906,95 +2444,184 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { Ok(()) } + // FIXME: Dead code, remove. + fn is_manually_managed(&self, place: &vir_high::Expression) -> bool { + for manual_place in &self.manually_managed_places { + if place.has_prefix(manual_place) { + return true; + } + } + false + } + fn encode_close_reference( &mut self, - block_builder: &mut BasicBlockBuilder, location: mir::Location, deref_base: &Option, place: vir_high::Expression, permission: Option, - ) -> SpannedEncodingResult<()> { + is_user_written: bool, + ) -> SpannedEncodingResult> { + let mut statement = None; if let Some(base) = deref_base { - if let vir_high::ty::Type::Reference(vir_high::ty::Reference { - lifetime, - uniqueness, - .. - }) = base.get_type() - { - if *uniqueness == vir_high::ty::Uniqueness::Unique { - block_builder.add_statement(self.set_statement_error( - location, - ErrorCtxt::CloseMutRef, - vir_high::Statement::close_mut_ref_no_pos( - lifetime.clone(), - self.lifetime_token_fractional_permission(self.lifetime_count), - place, - ), - )?); - } else { - block_builder.add_statement(self.set_statement_error( - location, - ErrorCtxt::CloseFracRef, - vir_high::Statement::close_frac_ref_no_pos( - lifetime.clone(), - self.lifetime_token_fractional_permission(self.lifetime_count), - place, - permission.unwrap(), - ), - )?); + match base.get_type() { + vir_high::ty::Type::Reference(vir_high::ty::Reference { + lifetime, + uniqueness, + .. + }) => { + if *uniqueness == vir_high::ty::Uniqueness::Unique { + statement = Some(self.set_statement_error( + location, + ErrorCtxt::CloseMutRef, + vir_high::Statement::close_mut_ref_no_pos( + lifetime.clone(), + self.lifetime_token_fractional_permission(self.lifetime_count), + place, + is_user_written, + ), + )?); + } else { + statement = Some(self.set_statement_error( + location, + ErrorCtxt::CloseFracRef, + vir_high::Statement::close_frac_ref_no_pos( + lifetime.clone(), + self.lifetime_token_fractional_permission(self.lifetime_count), + place, + permission.unwrap(), + is_user_written, + ), + )?); + } } - } else { - unreachable!(); - }; + vir_high::ty::Type::Pointer(_) => {} + _ => unreachable!(), + } } - Ok(()) + Ok(statement) } - fn encode_open_reference( + fn encode_automatic_close_reference( &mut self, - block_builder: &mut BasicBlockBuilder, + block_builder: &mut impl StatementSequenceBuilder, location: mir::Location, - deref_base: &Option, place: vir_high::Expression, - ) -> SpannedEncodingResult> { - let mut variable = None; - if let Some(base) = deref_base { - if let vir_high::ty::Type::Reference(vir_high::ty::Reference { - lifetime, - uniqueness, - .. - }) = base.get_type() - { - if *uniqueness == vir_high::ty::Uniqueness::Unique { - block_builder.add_statement(self.set_statement_error( - location, - ErrorCtxt::OpenMutRef, - vir_high::Statement::open_mut_ref_no_pos( - lifetime.clone(), - self.lifetime_token_fractional_permission(self.lifetime_count), - place, - ), - )?); - } else { - let permission = - self.fresh_ghost_variable("tmp_frac_ref_perm", vir_high::Type::MPerm); - variable = Some(permission.clone()); - block_builder.add_statement(self.set_statement_error( - location, - ErrorCtxt::OpenFracRef, - vir_high::Statement::open_frac_ref_no_pos( - lifetime.clone(), - permission, - self.lifetime_token_fractional_permission(self.lifetime_count), - place, - ), - )?); + permission: Option, + ) -> SpannedEncodingResult<()> { + if self.is_manually_managed(&place) { + return Ok(()); + } + let deref_base = place.get_dereference_base().cloned(); + let statement = + self.encode_close_reference(location, &deref_base, place, permission, false)?; + if let Some(statement) = statement { + block_builder.add_statement(statement); + } + Ok(()) + } + + fn encode_open_reference( + &mut self, + location: mir::Location, + deref_base: &Option, + place: vir_high::Expression, + is_user_written: bool, + ) -> SpannedEncodingResult<(Option, Option)> { + let mut variable = None; + let mut statement = None; + if let Some(base) = deref_base { + match base.get_type() { + vir_high::ty::Type::Reference(vir_high::ty::Reference { + lifetime, + uniqueness, + .. + }) => { + if *uniqueness == vir_high::ty::Uniqueness::Unique { + statement = Some(self.set_statement_error( + location, + ErrorCtxt::OpenMutRef, + vir_high::Statement::open_mut_ref_no_pos( + lifetime.clone(), + self.lifetime_token_fractional_permission(self.lifetime_count), + place, + is_user_written, + ), + )?); + } else { + let permission = + self.fresh_ghost_variable("tmp_frac_ref_perm", vir_high::Type::MPerm); + variable = Some(permission.clone()); + statement = Some(self.set_statement_error( + location, + ErrorCtxt::OpenFracRef, + vir_high::Statement::open_frac_ref_no_pos( + lifetime.clone(), + permission, + self.lifetime_token_fractional_permission(self.lifetime_count), + place, + is_user_written, + ), + )?); + } } - } else { - unreachable!("place: {} deref_base: {:?}", place, deref_base); + vir_high::ty::Type::Pointer(_) => { + // Note: if the dereferenced place is behind a raw pointer + // and reference, we require the user to manually open the + // reference. + } + _ => unreachable!("place: {} deref_base: {:?}", place, base), } }; - Ok(variable) + Ok((variable, statement)) + } + + fn encode_automatic_open_reference( + &mut self, + block_builder: &mut impl StatementSequenceBuilder, + location: mir::Location, + // deref_base: &Option, + place: vir_high::Expression, + ) -> SpannedEncodingResult> { + // return Ok(None); + if self.is_manually_managed(&place) { + return Ok(None); + } + let deref_place = place.get_dereference_base().cloned(); + let (variable, statement) = + self.encode_open_reference(location, &deref_place, place.clone(), false)?; + if let Some(statement) = statement { + block_builder.add_statement(statement); + } + if variable.is_some() { + Ok(variable) + } else { + Ok(self.lookup_opened_reference_place_permission(&place)) + // // Check whether the place was manually opened. FIXME: The + // // permission amount is cotrol-flow dependent and, therefore, should + // // be inserted by the fold-unfold algorithm. + // for (opened_place, variable) in &self.opened_reference_place_permissions { + // if place.has_prefix(opened_place) { + // return Ok(variable.clone()); + // } + // } + // Ok(None) + } + } + + /// Check whether the place was manually opened. FIXME: The + /// permission amount is cotrol-flow dependent and, therefore, should + /// be inserted by the fold-unfold algorithm. + fn lookup_opened_reference_place_permission( + &self, + place: &vir_high::Expression, + ) -> Option { + for (opened_place, variable) in &self.opened_reference_place_permissions { + if place.has_prefix(opened_place) { + return variable.clone(); + } + } + None } fn encode_assign_operand( @@ -1006,14 +2633,27 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { ) -> SpannedEncodingResult<()> { let span = self.encoder.get_span_of_location(self.mir, location); - let deref_base = encoded_target.get_dereference_base().cloned(); - let target_permission = self.encode_open_reference( + // let deref_base = encoded_target.get_dereference_base().cloned(); + let target_permission = self.encode_automatic_open_reference( block_builder, location, - &deref_base, + // &deref_base, encoded_target.clone(), )?; match operand { + mir::Operand::Move(source @ mir::Place { local, .. }) + | mir::Operand::Copy(source @ mir::Place { local, .. }) + if source.as_local().is_some() + && self + .locals_used_only_in_specification_regions + .contains(local) + && source.ty(self.mir, self.encoder.env().tcx()).ty.is_unit() => + { + block_builder.add_comment(format!( + "Assignment is ignored because {:?} is used only in specifications", + local + )); + } mir::Operand::Move(source) => { let encoded_source = self.encode_place(*source, Some(span))?; if let vir_high::Expression::Local(local_source) = &encoded_source { @@ -1036,11 +2676,11 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { "{encoded_source} is not place (encoded from: {source:?}" ); - let deref_base = encoded_source.get_dereference_base().cloned(); - let source_permission = self.encode_open_reference( + // let deref_base = encoded_source.get_dereference_base().cloned(); + let source_permission = self.encode_automatic_open_reference( block_builder, location, - &deref_base, + // &deref_base, encoded_source.clone(), )?; @@ -1050,14 +2690,14 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { vir_high::Statement::copy_place_no_pos( encoded_target.clone(), encoded_source.clone(), - source_permission.clone(), + // source_permission.clone(), ), )?); - self.encode_close_reference( + self.encode_automatic_close_reference( block_builder, location, - &deref_base, + // &deref_base, encoded_source, source_permission, )?; @@ -1078,10 +2718,10 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { } } - self.encode_close_reference( + self.encode_automatic_close_reference( block_builder, location, - &deref_base, + // &deref_base, encoded_target, target_permission, )?; @@ -1089,11 +2729,26 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { Ok(()) } + // fn encode_assign_cast( + // &mut self, + // block_builder: &mut BasicBlockBuilder, + // location: mir::Location, + // encoded_target: vir_crate::high::Expression, + // kind: mir::CastKind, + // operand: &mir::Operand<'tcx>, + // ty: ty::Ty<'tcx>, + // ) -> SpannedEncodingResult<()> { + // let span = self.encoder.get_span_of_location(self.mir, location); + // match ty {} + // } + fn encode_statement_operand( &mut self, + block_builder: &mut impl StatementSequenceBuilder, location: mir::Location, operand: &mir::Operand<'tcx>, - ) -> SpannedEncodingResult { + ) -> SpannedEncodingResult<(vir_high::Operand, Vec)> { + let mut post_statements = Vec::new(); let span = self.encoder.get_span_of_location(self.mir, location); let encoded_operand = match operand { mir::Operand::Move(source) => { @@ -1108,6 +2763,17 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let encoded_source = self .encode_place(*source, Some(span))? .set_default_position(position); + let source_permission = self.encode_automatic_open_reference( + block_builder, + location, + encoded_source.clone(), + )?; + self.encode_automatic_close_reference( + &mut post_statements, + location, + encoded_source.clone(), + source_permission, + )?; vir_high::Operand::new(vir_high::OperandKind::Copy, encoded_source) } mir::Operand::Constant(constant) => { @@ -1120,7 +2786,19 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { vir_high::Operand::new(vir_high::OperandKind::Constant, encoded_constant) } }; - Ok(encoded_operand) + Ok((encoded_operand, post_statements)) + } + + fn encode_statement_operand_no_refs( + &mut self, + block_builder: &mut impl StatementSequenceBuilder, + location: mir::Location, + operand: &mir::Operand<'tcx>, + ) -> SpannedEncodingResult { + let (operand, post_statements) = + self.encode_statement_operand(block_builder, location, operand)?; + assert!(post_statements.is_empty(), "unimplemented"); + Ok(operand) } fn encode_binary_op_kind( @@ -1172,13 +2850,22 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { block_builder: &mut BasicBlockBuilder, location: mir::Location, terminator: &mir::TerminatorKind<'tcx>, + original_lifetimes: &mut BTreeSet, + derived_lifetimes: &mut BTreeMap>, ) -> SpannedEncodingResult<()> { block_builder.add_comment(format!("{location:?} {terminator:?}")); let span = self.encoder.get_span_of_location(self.mir, location); use prusti_rustc_interface::middle::mir::TerminatorKind; let successor = match &terminator { TerminatorKind::Goto { target } => { - self.encode_lft_for_block(*target, location, block_builder)?; + self.encode_lft_for_block( + *target, + location, + block_builder, + original_lifetimes, + derived_lifetimes, + )?; + self.add_predecessor(location.block, *target)?; SuccessorBuilder::jump(vir_high::Successor::Goto( self.encode_basic_block_label(*target), )) @@ -1192,6 +2879,8 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { targets, discr, switch_ty, + original_lifetimes, + derived_lifetimes, )? } TerminatorKind::Resume => SuccessorBuilder::exit_resume_panic(), @@ -1237,6 +2926,8 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { target, cleanup, *fn_span, + original_lifetimes, + derived_lifetimes, )?; // The encoding of the call is expected to set the successor. return Ok(()); @@ -1249,12 +2940,15 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { cleanup, } => self.encode_terminator_assert( block_builder, + location, span, cond, *expected, msg, *target, *cleanup, + original_lifetimes, + derived_lifetimes, )?, // TerminatorKind::Yield { .. } => { // graph.add_exit_edge(bb, "yield"); @@ -1270,7 +2964,14 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { real_target, unwind: _, } => { - self.encode_lft_for_block(*real_target, location, block_builder)?; + self.encode_lft_for_block( + *real_target, + location, + block_builder, + original_lifetimes, + derived_lifetimes, + )?; + self.add_predecessor(location.block, *real_target)?; SuccessorBuilder::jump(vir_high::Successor::Goto( self.encode_basic_block_label(*real_target), )) @@ -1293,23 +2994,41 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { targets: &mir::SwitchTargets, discr: &mir::Operand<'tcx>, switch_ty: ty::Ty<'tcx>, + original_lifetimes: &mut BTreeSet, + derived_lifetimes: &mut BTreeMap>, ) -> SpannedEncodingResult { { - // Check whether we should not omit the spec block. + // Special handling of specifications: + // 1. Specificaiton blocks. + // 2. Specification regions. let all_targets = targets.all_targets(); if all_targets.len() == 2 { if let Some(spec) = all_targets .iter() .position(|target| self.specification_blocks.is_specification_block(*target)) { - let real_target = all_targets[(spec + 1) % 2]; + let mut real_target = all_targets[(spec + 1) % 2]; let spec_target = all_targets[spec]; block_builder.add_comment(format!("Specification from block: {spec_target:?}")); if let Some(statements) = self.specification_block_encoding.remove(&spec_target) { + // We have the specification block, add it here. block_builder.add_statements(statements); + } else if let Some(exit_target_block) = self + .specification_region_exit_target_block + .get(&spec_target) + { + // We have the specification region, use it as a real target. + real_target = *exit_target_block; } - self.encode_lft_for_block(real_target, location, block_builder)?; + self.encode_lft_for_block( + real_target, + location, + block_builder, + original_lifetimes, + derived_lifetimes, + )?; + self.add_predecessor(location.block, real_target)?; return Ok(SuccessorBuilder::jump(vir_high::Successor::Goto( self.encode_basic_block_label(real_target), ))); @@ -1326,6 +3045,15 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { ); let mut successors = Vec::new(); for (value, target) in targets.iter() { + self.add_predecessor(location.block, target)?; + // self.encode_lifetimes_dead_on_edge( + // block_builder, + // RichLocation::Mid(location), + // RichLocation::Start(mir::Location { + // block: target, + // statement_index: 0, + // }), + // )?; let encoded_condition = match switch_ty.kind() { ty::TyKind::Bool => { if value == 0 { @@ -1349,25 +3077,88 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let encoded_target = self.encode_basic_block_label(target); let encoded_target = self.encode_lft_for_block_with_edge( target, + false, encoded_target, location, block_builder, )?; successors.push((encoded_condition, encoded_target)); } + // self.encode_lifetimes_dead_on_edge( + // block_builder, + // RichLocation::Mid(location), + // RichLocation::Start(mir::Location { + // block: targets.otherwise(), + // statement_index: 0, + // }), + // )?; let otherwise = self.encode_basic_block_label(targets.otherwise()); let otherwise = self.encode_lft_for_block_with_edge( targets.otherwise(), + false, otherwise, location, block_builder, )?; + self.add_predecessor(location.block, targets.otherwise())?; successors.push((true.into(), otherwise)); Ok(SuccessorBuilder::jump(vir_high::Successor::GotoSwitch( successors, ))) } + fn encode_drop_contracts( + &mut self, + place: mir::Place<'tcx>, + precondition_label: &str, + ) -> SpannedEncodingResult, Vec)>> { + let place_ty = place.ty(self.mir, self.encoder.env().tcx()).ty; + if let Some(drop_method_def_id) = self.encoder.env().query.get_drop_method_id(place_ty) { + let substs = self.encoder.env().query.identity_substs(drop_method_def_id); + let procedure_contract = self + .encoder + .get_mir_procedure_contract_for_def(drop_method_def_id, substs) + .with_span(self.mir.span)?; + let self_place = self.encode_place(place, None)?; + let reference_type = vir_high::Type::reference( + vir_high::ty::LifetimeConst::erased(), + vir_high::ty::Uniqueness::Unique, + self_place.get_type().clone(), + ); + let arguments = vec![vir_high::Expression::addr_of_no_pos( + self_place, + reference_type, + )]; + let preconditions = self.encode_precondition_expressions( + &procedure_contract, + substs, + self.is_unsafe_function, + true, + &arguments, + )?; + let result = vir_high::VariableDecl::new( + "non-existant-result", + vir_high::Type::tuple(Vec::new(), Vec::new()), + ) + .into(); + let postconditions = self.encode_postcondition_expressions( + &procedure_contract, + substs, + PostconditionMode::regular_exit_on_call_side( + self.is_unsafe_function, + self.check_mode, + self.no_panic_ensures_postcondition, + false, + ), + arguments, + &result, + precondition_label, + )?; + Ok(Some((preconditions, postconditions))) + } else { + Ok(None) + } + } fn encode_terminator_drop( &mut self, block_builder: &mut BasicBlockBuilder, @@ -1380,6 +3171,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let target_block_label = self.encode_basic_block_label(target); let target_block_label = self.encode_lft_for_block_with_edge( target, + false, target_block_label, location, block_builder, @@ -1395,12 +3187,92 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { block_builder.add_statement(statement); } + let old_label = self.fresh_old_label(); + let mut contracts = self.encode_drop_contracts(place, &old_label)?; + + let place = self.encode_place(place, None)?; + let unwind_specification_statements = + if let Some(region) = self.specification_on_drop_unwind.remove(&place) { + Some( + self.specification_region_encoding_statements + .remove(®ion) + .unwrap(), + ) + } else { + None + }; + let before_drop_statements = + if let Some(region) = self.specification_before_drop.get(&place) { + Some( + self.specification_region_encoding_statements + .get(region) + .unwrap() + .clone(), + ) + } else { + None + }; + let after_drop_statements = if let Some(region) = self.specification_after_drop.get(&place) + { + Some( + self.specification_region_encoding_statements + .get(region) + .unwrap() + .clone(), + ) + } else { + None + }; + if let Some(statements) = before_drop_statements { + block_builder.add_statements(statements); + } + // FIXME: Assert that the lifetimes used in type of the place are alive // at this point (by exhaling them and inhaling). Do not forget to take // into account // https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.GenericParamDef.html#structfield.pure_wrt_drop - let argument = - vir_high::Operand::new(vir_high::OperandKind::Move, self.encode_place(place, None)?); + + let target_permission = + self.encode_automatic_open_reference(block_builder, location, place.clone())?; + let mut close_ref_statements = Vec::new(); + self.encode_automatic_close_reference( + &mut close_ref_statements, + location, + place.clone(), + target_permission, + )?; + self.add_specification_before_terminator + .insert(target, close_ref_statements.clone()); + + if let Some((preconditions, _)) = &mut contracts { + block_builder.add_statement(self.encoder.set_statement_error_ctxt( + vir_high::Statement::old_label_no_pos(old_label.clone()), + span, + ErrorCtxt::ProcedureCall, + self.def_id, + )?); + let mut precondition_conjuncts = Vec::new(); + for expression in std::mem::take(preconditions) { + let conjunct = self.encoder.set_expression_error_ctxt( + expression, + span, + ErrorCtxt::ExhaleMethodPrecondition, + self.def_id, + ); + precondition_conjuncts.push(conjunct); + } + let exhale_statement = self.encoder.set_statement_error_ctxt( + vir_high::Statement::exhale_expression_no_pos( + precondition_conjuncts.into_iter().conjoin(), + Some(old_label.clone()), + ), + span, + ErrorCtxt::ExhaleMethodPrecondition, + self.def_id, + )?; + block_builder.add_statement(exhale_statement); + } + let argument = vir_high::Operand::new(vir_high::OperandKind::Move, place); let statement = self.encoder.set_statement_error_ctxt( vir_high::Statement::consume_no_pos(argument), span, @@ -1409,19 +3281,64 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { )?; statement.check_no_default_position(); block_builder.add_statement(statement); - if let Some(unwind_block) = unwind { + if unwind.is_some() && contracts.is_none() { + // If we have a contract, then we assume that the drop will not panic. + let Some(unwind_block) = unwind else { + unreachable!() + }; let encoded_unwind_block_label = self.encode_basic_block_label(*unwind_block); let encoded_unwind_block_label = self.encode_lft_for_block_with_edge( *unwind_block, + true, encoded_unwind_block_label, location, block_builder, )?; + if let Some(statements) = unwind_specification_statements { + // We put the specification statements before the terminator + // because `DropAndReplace` desugaring adds an assignment and we + // need to put our specification after it. + assert!(close_ref_statements.is_empty(), "unimplemented: which statements should go first? Closing the reference or user's specification?"); + self.add_specification_before_terminator + .insert(*unwind_block, statements); + } else { + self.add_specification_before_terminator + .insert(*unwind_block, close_ref_statements); + } + self.add_predecessor(location.block, target)?; + self.add_predecessor(location.block, *unwind_block)?; Ok(SuccessorBuilder::jump(vir_high::Successor::NonDetChoice( target_block_label, encoded_unwind_block_label, ))) } else { + assert!(unwind_specification_statements.is_none(), "TODO: A proper error message that `on_drop_unwind!` can be used only with places that can be unwound."); + if let Some((_, postconditions)) = &mut contracts { + let mut postcondition_conjuncts = Vec::new(); + for expression in std::mem::take(postconditions) { + let conjunct = self.encoder.set_expression_error_ctxt( + expression, + span, + ErrorCtxt::UnexpectedAssumeMethodPostcondition, + self.def_id, + ); + postcondition_conjuncts.push(conjunct); + } + let inhale_statement = self.encoder.set_statement_error_ctxt( + vir_high::Statement::inhale_expression_no_pos( + postcondition_conjuncts.into_iter().conjoin(), + None, + ), + span, + ErrorCtxt::UnexpectedAssumeMethodPostcondition, + self.def_id, + )?; + block_builder.add_statement(inhale_statement); + } + if let Some(statements) = after_drop_statements { + block_builder.add_statements(statements); + } + self.add_predecessor(location.block, target)?; Ok(SuccessorBuilder::jump(vir_high::Successor::Goto( target_block_label, ))) @@ -1440,6 +3357,8 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { target: &Option, cleanup: &Option, _fn_span: Span, + original_lifetimes: &mut BTreeSet, + derived_lifetimes: &mut BTreeMap>, ) -> SpannedEncodingResult<()> { if let ty::TyKind::FnDef(called_def_id, call_substs) = ty.kind() { if !self.try_encode_builtin_call( @@ -1452,6 +3371,8 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { destination, target, cleanup, + original_lifetimes, + derived_lifetimes, )? { self.encode_function_call( block_builder, @@ -1463,6 +3384,8 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { destination, target, cleanup, + original_lifetimes.clone(), + derived_lifetimes.clone(), )?; } } else { @@ -1484,6 +3407,8 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { destination: mir::Place<'tcx>, target: &Option, cleanup: &Option, + original_lifetimes: BTreeSet, + derived_lifetimes: BTreeMap>, ) -> SpannedEncodingResult<()> { // The called method might be a trait method. // We try to resolve it to the concrete implementation @@ -1491,6 +3416,12 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let query = self.encoder.env().query; let (called_def_id, call_substs) = query.resolve_method_call(self.def_id, called_def_id, call_substs); + let is_unsafe = query.is_unsafe_function(called_def_id); + let no_panic_ensures_postcondition = self + .encoder + .no_panic_ensures_postcondition(called_def_id, Some(call_substs)); + let no_panic = self.encoder.no_panic(called_def_id, Some(call_substs)); + let is_checked = self.specification_blocks.is_checked(location.block); // find static lifetime to exhale let mut lifetimes_to_exhale_inhale: Vec = Vec::new(); @@ -1537,7 +3468,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let function_lifetime_take = vir_high::Statement::lifetime_take_no_pos( function_call_lifetime.clone(), derived_from.clone(), - self.lifetime_token_fractional_permission(self.lifetime_count * derived_from.len()), + self.lifetime_token_fractional_permission(2 * self.lifetime_count), ); block_builder.add_statement(self.set_statement_error( location, @@ -1571,32 +3502,91 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { ErrorCtxt::ProcedureCall, self.def_id, )?); + + let procedure_contract = self + .encoder + .get_mir_procedure_contract_for_call(self.def_id, called_def_id, call_substs) + .with_span(span)?; + let broken_invariants = + self.encode_broken_invariant_argument_mask(&procedure_contract, call_substs)?; + let mut arguments = Vec::new(); - for arg in args { + let mut consume_arguments = Vec::new(); + let mut broken_invariant_places = Vec::new(); + let mut broken_invariant_address_memory_blocks = Vec::new(); + for (arg, is_invariant_broken) in args.iter().zip(broken_invariants.iter()) { + // FIXME: Code repetition with encode_postcondition_frame_check arguments.push( self.encoder .encode_operand_high(self.mir, arg, span) .with_span(span)?, ); - let encoded_arg = self.encode_statement_operand(location, arg)?; - let statement = vir_high::Statement::consume_no_pos(encoded_arg); - block_builder.add_statement(self.encoder.set_statement_error_ctxt( - statement, - span, - ErrorCtxt::ProcedureCall, - self.def_id, - )?); + if *is_invariant_broken { + match arg { + mir::Operand::Copy(_) => unimplemented!( + "TODO: A proper error message that only moved references are supported" + ), + mir::Operand::Move(place) => { + let encoded_place = self.encode_place(*place, None)?; + let address_memory_block = + self.encode_reference_address_memory_block(encoded_place)?; + broken_invariant_address_memory_blocks.push(address_memory_block.clone()); + let dealloc_address_statement = + vir_high::Statement::exhale_predicate_no_pos(address_memory_block); + consume_arguments.add_statement(self.encoder.set_statement_error_ctxt( + dealloc_address_statement, + span, + ErrorCtxt::ProcedureCall, + self.def_id, + )?); + let deref_place = self.encoder.env().tcx().mk_place_deref(*place); + for field_place in analysis::mir_utils::expand_struct_place( + deref_place, + self.mir, + self.encoder.env().tcx(), + None, + ) { + let encoded_arg = self.encode_place(field_place, None)?; + broken_invariant_places.push(encoded_arg.clone()); + let statement = vir_high::Statement::exhale_predicate_no_pos( + vir_high::Predicate::owned_non_aliased_no_pos(encoded_arg), + ); + consume_arguments.add_statement( + self.encoder.set_statement_error_ctxt( + statement, + span, + ErrorCtxt::ProcedureCall, + self.def_id, + )?, + ); + } + } + mir::Operand::Constant(_) => unimplemented!( + "TODO: A proper error message that only moved references are supported" + ), + } + } else { + let encoded_arg = + self.encode_statement_operand_no_refs(&mut consume_arguments, location, arg)?; + let statement = vir_high::Statement::consume_no_pos(encoded_arg); + consume_arguments.add_statement(self.encoder.set_statement_error_ctxt( + statement, + span, + ErrorCtxt::ProcedureCall, + self.def_id, + )?); + } + // let encoded_arg = + // self.encode_statement_operand_no_refs(&mut consume_arguments, location, arg)?; + // let statement = vir_high::Statement::consume_no_pos(encoded_arg); + // consume_arguments.add_statement(self.encoder.set_statement_error_ctxt( + // statement, + // span, + // ErrorCtxt::ProcedureCall, + // self.def_id, + // )?); } - self.encode_exhale_lifetime_tokens( - block_builder, - &lifetimes_to_exhale_inhale, - derived_from.len() + 1, - )?; - - let procedure_contract = self - .encoder - .get_mir_procedure_contract_for_call(self.def_id, called_def_id, call_substs) - .with_span(span)?; + self.encode_exhale_lifetime_tokens(block_builder, &lifetimes_to_exhale_inhale, 4)?; if self.encoder.terminates(self.def_id, None) { self.encode_termination_measure_call_assertion( @@ -1608,19 +3598,58 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { )?; } - for expression in - self.encode_precondition_expressions(&procedure_contract, call_substs, &arguments)? - { - let assert_statement = self.encoder.set_statement_error_ctxt( - vir_high::Statement::assert_no_pos(expression), - span, - ErrorCtxt::ExhaleMethodPrecondition, - self.def_id, - )?; - if self.check_mode != CheckMode::CoreProof { - block_builder.add_statement(assert_statement); + let precondition_expressions = self.encode_precondition_expressions( + &procedure_contract, + call_substs, + is_unsafe, + self.check_mode.check_specifications(), + &arguments, + )?; + // let has_no_precondition = precondition_expressions.is_empty(); + let is_precondition_checked = self.check_mode.check_specifications() || is_checked; // || has_no_precondition; + let mut precondition_conjuncts = Vec::new(); + for expression in precondition_expressions { + if let Some(expression) = self.convert_expression_to_check_mode_call_site( + expression, is_unsafe, is_checked, &arguments, + )? { + // let exhale_statement = self.encoder.set_statement_error_ctxt( + // vir_high::Statement::exhale_expression_no_pos(expression), + // span, + // ErrorCtxt::ExhaleMethodPrecondition, + // self.def_id, + // )?; + // block_builder.add_statement(exhale_statement); + let conjunct = self.encoder.set_expression_error_ctxt( + expression, + span, + ErrorCtxt::ExhaleMethodPrecondition, + self.def_id, + ); + precondition_conjuncts.push(conjunct); } } + let exhale_statement = self.encoder.set_statement_error_ctxt( + vir_high::Statement::exhale_expression_no_pos( + precondition_conjuncts.into_iter().conjoin(), + Some(old_label.clone()), + ), + span, + ErrorCtxt::ExhaleMethodPrecondition, + self.def_id, + )?; + block_builder.add_statement(exhale_statement); + block_builder.add_statements(consume_arguments); + + let is_pure = self.encoder.is_pure(called_def_id, Some(call_substs)); + // if !is_pure && self.check_mode.is_purification_group() { + // let heap_havoc_statement = self.encoder.set_statement_error_ctxt( + // vir_high::Statement::heap_havoc_no_pos(), + // span, + // ErrorCtxt::ExhaleMethodPrecondition, + // self.def_id, + // )?; + // block_builder.add_statement(heap_havoc_statement); + // } if self.encoder.env().query.is_closure(called_def_id) { // Closure calls are wrapped around std::ops::Fn::call(), which receives @@ -1628,15 +3657,21 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { assert_eq!(args.len(), 2); unimplemented!(); } - + let position = self.register_error(location, ErrorCtxt::ProcedureCall); + let encoded_target_place = self + .encode_place(destination, None)? + .set_default_position(position); if let Some(target_block) = target { - let position = self.register_error(location, ErrorCtxt::ProcedureCall); - let encoded_target_place = self - .encode_place(destination, None)? - .set_default_position(position); + self.add_predecessor(location.block, *target_block)?; let postcondition_expressions = self.encode_postcondition_expressions( &procedure_contract, call_substs, + PostconditionMode::regular_exit_on_call_side( + is_unsafe, + self.check_mode, + no_panic_ensures_postcondition, + is_checked, + ), arguments.clone(), &encoded_target_place, &old_label, @@ -1650,7 +3685,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { size, ); block_builder.add_statement(self.encoder.set_statement_error_ctxt( - vir_high::Statement::exhale_no_pos(target_memory_block.clone()), + vir_high::Statement::exhale_predicate_no_pos(target_memory_block.clone()), span, ErrorCtxt::ProcedureCall, self.def_id, @@ -1660,8 +3695,29 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let fresh_destination_label = self.fresh_basic_block_label(); let mut post_call_block_builder = block_builder.create_basic_block_builder(fresh_destination_label.clone()); + self.add_predecessor(location.block, *target_block)?; post_call_block_builder.set_successor_jump(vir_high::Successor::Goto(target_label)); - let statement = vir_high::Statement::inhale_no_pos( + for memory_block in broken_invariant_address_memory_blocks { + let statement = vir_high::Statement::inhale_predicate_no_pos(memory_block); + post_call_block_builder.add_statement(self.encoder.set_statement_error_ctxt( + statement, + span, + ErrorCtxt::ProcedureCall, + self.def_id, + )?); + } + for encoded_place in broken_invariant_places { + let statement = vir_high::Statement::inhale_predicate_no_pos( + vir_high::Predicate::owned_non_aliased_no_pos(encoded_place), + ); + post_call_block_builder.add_statement(self.encoder.set_statement_error_ctxt( + statement, + span, + ErrorCtxt::ProcedureCall, + self.def_id, + )?); + } + let statement = vir_high::Statement::inhale_predicate_no_pos( vir_high::Predicate::owned_non_aliased_no_pos(encoded_target_place.clone()), ); post_call_block_builder.add_statement(self.encoder.set_statement_error_ctxt( @@ -1673,15 +3729,13 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { self.encode_inhale_lifetime_tokens( &mut post_call_block_builder, &lifetimes_to_exhale_inhale, - derived_from.len(), + 4, )?; let function_lifetime_return = self.encoder.set_statement_error_ctxt( vir_high::Statement::lifetime_return_no_pos( function_call_lifetime.clone(), derived_from.clone(), - self.lifetime_token_fractional_permission( - self.lifetime_count * derived_from.len(), - ), + self.lifetime_token_fractional_permission(self.lifetime_count * 2), ), self.mir.span, ErrorCtxt::LifetimeInhale, @@ -1689,20 +3743,53 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { )?; post_call_block_builder.add_statement(function_lifetime_return); - self.encode_lft_for_block(*target_block, location, &mut post_call_block_builder)?; + self.encode_lft_for_block( + *target_block, + location, + &mut post_call_block_builder, + &mut original_lifetimes.clone(), + &mut derived_lifetimes.clone(), + )?; + let result_place = vec![encoded_target_place.clone()]; + let mut postcondition_conjuncts = Vec::new(); for expression in postcondition_expressions { - let assume_statement = self.encoder.set_statement_error_ctxt( - vir_high::Statement::assume_no_pos(expression), - span, - ErrorCtxt::UnexpectedAssumeMethodPostcondition, - self.def_id, - )?; - if self.check_mode != CheckMode::CoreProof { - post_call_block_builder.add_statement(assume_statement); + if let Some(expression) = self.convert_expression_to_check_mode_call_site( + expression, + is_unsafe, + no_panic_ensures_postcondition || is_checked, + // // If we have no precondition, then we can soundly + // // allways include the function postcondition. + // has_no_precondition, + &result_place, + )? { + // let inhale_statement = self.encoder.set_statement_error_ctxt( + // vir_high::Statement::inhale_expression_no_pos(expression), + // span, + // ErrorCtxt::UnexpectedAssumeMethodPostcondition, + // self.def_id, + // )?; + // post_call_block_builder.add_statement(inhale_statement); + let conjunct = self.encoder.set_expression_error_ctxt( + expression, + span, + ErrorCtxt::UnexpectedAssumeMethodPostcondition, + self.def_id, + ); + postcondition_conjuncts.push(conjunct); } } - if self.encoder.is_pure(called_def_id, Some(call_substs)) + let inhale_statement = self.encoder.set_statement_error_ctxt( + vir_high::Statement::inhale_expression_no_pos( + postcondition_conjuncts.into_iter().conjoin(), + None, + ), + span, + ErrorCtxt::UnexpectedAssumeMethodPostcondition, + self.def_id, + )?; + post_call_block_builder.add_statement(inhale_statement); + if is_pure && !self.encoder.env().callee_reaches_caller( self.def_id, called_def_id, @@ -1723,11 +3810,11 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { .encode_generic_arguments_high(called_def_id, call_substs) .with_span(span)?; let expression = vir_high::Expression::equals( - encoded_target_place, + encoded_target_place.clone(), vir_high::Expression::function_call( function_name, type_arguments, - arguments, + arguments.clone(), return_type, ), ); @@ -1737,53 +3824,74 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { ErrorCtxt::UnexpectedAssumeMethodPostcondition, self.def_id, )?; - if self.check_mode != CheckMode::CoreProof { + if self.check_mode.check_specifications() + || is_checked + || no_panic_ensures_postcondition + // // If we have no precondition, then we can soundly + // // allways include the function postcondition. + // has_no_precondition + { post_call_block_builder.add_statement(assume_statement); } + } else { + // // FIXME: We do this because extern specs do not support primitive + // // types. + // let func_name = self.encoder.env().name.get_unique_item_name(called_def_id); + // if func_name.starts_with("std::ptr::mut_ptr::::is_null") + // || func_name.starts_with("core::std::ptr::mut_ptr::::is_null") { + // let type_arguments = self + // .encoder + // .encode_generic_arguments_high(called_def_id, call_substs) + // .with_span(span)?; + // let expression = vir_high::Expression::equals( + // encoded_target_place, + // vir_high::Expression::builtin_func_app_no_pos( + // vir_high::BuiltinFunc::IsNull, + // type_arguments, + // arguments, + // vir_high::Type::Bool, + // ), + // ); + // let assume_statement = self.encoder.set_statement_error_ctxt( + // vir_high::Statement::assume_no_pos(expression), + // span, + // ErrorCtxt::UnexpectedAssumeMethodPostcondition, + // self.def_id, + // )?; + // if self.check_mode != CheckMode::CoreProof { + // post_call_block_builder.add_statement(assume_statement); + // } + // } } post_call_block_builder.build(); if let Some(cleanup_block) = cleanup { - let encoded_cleanup_block = self.encode_basic_block_label(*cleanup_block); - let fresh_cleanup_label = self.fresh_basic_block_label(); - let mut cleanup_block_builder = - block_builder.create_basic_block_builder(fresh_cleanup_label.clone()); - cleanup_block_builder - .set_successor_jump(vir_high::Successor::Goto(encoded_cleanup_block)); - - let statement = vir_high::Statement::inhale_no_pos(target_memory_block); - cleanup_block_builder.add_statement(self.encoder.set_statement_error_ctxt( - statement, - span, - ErrorCtxt::ProcedureCall, - self.def_id, - )?); - - self.encode_inhale_lifetime_tokens( - &mut cleanup_block_builder, - &lifetimes_to_exhale_inhale, - derived_from.len(), - )?; - let function_lifetime_return = self.encoder.set_statement_error_ctxt( - vir_high::Statement::lifetime_return_no_pos( - function_call_lifetime, - derived_from.clone(), - self.lifetime_token_fractional_permission( - self.lifetime_count * derived_from.len(), - ), - ), - self.mir.span, - ErrorCtxt::LifetimeInhale, - self.def_id, - )?; - cleanup_block_builder.add_statement(function_lifetime_return); - self.encode_lft_for_block( + let panic_postcondition_expressions = self + .encode_panic_postcondition_expressions( + &procedure_contract, + call_substs, + PostconditionMode::panic_exit_on_call_side(is_unsafe, self.check_mode), + arguments.clone(), + &encoded_target_place, + &old_label, + )?; + let fresh_cleanup_label = self.encode_function_call_cleanup_block( *cleanup_block, + block_builder, location, - &mut cleanup_block_builder, + span, + is_precondition_checked, + no_panic, + Some(target_memory_block), + &lifetimes_to_exhale_inhale, + &derived_from, + function_call_lifetime, + &mut original_lifetimes.clone(), + &mut derived_lifetimes.clone(), + panic_postcondition_expressions, )?; - - cleanup_block_builder.build(); + self.add_predecessor(location.block, *target_block)?; + self.add_predecessor(location.block, *cleanup_block)?; block_builder.set_successor_jump(vir_high::Successor::NonDetChoice( fresh_destination_label, fresh_cleanup_label, @@ -1794,9 +3902,32 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { } else { unimplemented!(); } - } else if let Some(_cleanup_block) = cleanup { - // TODO: add panic postconditions. - unimplemented!(); + } else if let Some(cleanup_block) = cleanup { + let panic_postcondition_expressions = self.encode_panic_postcondition_expressions( + &procedure_contract, + call_substs, + PostconditionMode::panic_exit_on_call_side(is_unsafe, self.check_mode), + arguments.clone(), + &encoded_target_place, + &old_label, + )?; + let fresh_cleanup_label = self.encode_function_call_cleanup_block( + *cleanup_block, + block_builder, + location, + span, + is_precondition_checked, + no_panic, + None, + &lifetimes_to_exhale_inhale, + &derived_from, + function_call_lifetime, + &mut original_lifetimes.clone(), + &mut derived_lifetimes.clone(), + panic_postcondition_expressions, + )?; + self.add_predecessor(location.block, *cleanup_block)?; + block_builder.set_successor_jump(vir_high::Successor::Goto(fresh_cleanup_label)); } else { // TODO: Can we always soundly assume false here? unimplemented!(); @@ -1805,21 +3936,210 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { Ok(()) } - #[allow(clippy::too_many_arguments)] - fn encode_terminator_assert( + fn encode_function_call_cleanup_block( &mut self, + cleanup_block: mir::BasicBlock, block_builder: &mut BasicBlockBuilder, + location: mir::Location, span: Span, - cond: &mir::Operand<'tcx>, - expected: bool, - msg: &mir::AssertMessage<'tcx>, - target: mir::BasicBlock, - cleanup: Option, - ) -> SpannedEncodingResult { - let condition = self - .encoder - .encode_operand_high(self.mir, cond, span) - .with_default_span(span)?; + is_precondition_checked: bool, + no_panic: bool, + target_memory_block: Option, + lifetimes_to_exhale_inhale: &[String], + derived_from: &[vir_high::VariableDecl], + function_call_lifetime: vir_high::VariableDecl, + original_lifetimes: &mut BTreeSet, + derived_lifetimes: &mut BTreeMap>, + panic_postcondition_expressions: Vec, + ) -> SpannedEncodingResult { + let encoded_cleanup_block = self.encode_basic_block_label(cleanup_block); + let fresh_cleanup_label = self.fresh_basic_block_label(); + let mut cleanup_block_builder = + block_builder.create_basic_block_builder(fresh_cleanup_label.clone()); + self.add_predecessor(location.block, cleanup_block)?; + cleanup_block_builder.set_successor_jump(vir_high::Successor::Goto(encoded_cleanup_block)); + + if is_precondition_checked || no_panic { + // If the precondition is checked or the function is + // guaranteed to not panic, then the cleanup block is + // unreachable. + let statement = vir_high::Statement::assume_no_pos(false.into()); + cleanup_block_builder.add_statement(self.encoder.set_statement_error_ctxt( + statement, + span, + ErrorCtxt::ProcedureCall, + self.def_id, + )?); + } + + if let Some(target_memory_block) = target_memory_block { + let statement = vir_high::Statement::inhale_predicate_no_pos(target_memory_block); + cleanup_block_builder.add_statement(self.encoder.set_statement_error_ctxt( + statement, + span, + ErrorCtxt::ProcedureCall, + self.def_id, + )?); + } + + self.encode_inhale_lifetime_tokens( + &mut cleanup_block_builder, + lifetimes_to_exhale_inhale, + 4, + )?; + let function_lifetime_return = self.encoder.set_statement_error_ctxt( + vir_high::Statement::lifetime_return_no_pos( + function_call_lifetime, + derived_from.to_vec(), + self.lifetime_token_fractional_permission(2 * self.lifetime_count), + ), + self.mir.span, + ErrorCtxt::LifetimeInhale, + self.def_id, + )?; + cleanup_block_builder.add_statement(function_lifetime_return); + + let mut postcondition_conjuncts = Vec::new(); + for expression in panic_postcondition_expressions { + let conjunct = self.encoder.set_expression_error_ctxt( + expression, + span, + ErrorCtxt::UnexpectedAssumeMethodPostcondition, + self.def_id, + ); + postcondition_conjuncts.push(conjunct); + } + let inhale_statement = self.encoder.set_statement_error_ctxt( + vir_high::Statement::inhale_expression_no_pos( + postcondition_conjuncts.into_iter().conjoin(), + None, + ), + span, + ErrorCtxt::UnexpectedAssumeMethodPostcondition, + self.def_id, + )?; + cleanup_block_builder.add_statement(inhale_statement); + + if let Some(statements) = self + .add_function_panic_specification_before_lifetime_effects + .get(&(location.block, cleanup_block)) + { + // We need to add the statements before the expiration + // of the lifetime. Otherwise, the fold-unfold crashes. + self.apply_encoding_actions_on_edge( + &mut cleanup_block_builder, + statements.clone(), + RichLocation::Mid(location), + RichLocation::Start(mir::Location { + block: cleanup_block, + statement_index: 0, + }), + original_lifetimes, + derived_lifetimes, + )?; + } + + self.encode_lft_for_block( + cleanup_block, + location, + &mut cleanup_block_builder, + original_lifetimes, + derived_lifetimes, + )?; + + if let Some(statements) = self + .add_function_panic_specification_after_lifetime_effects + .get(&(location.block, cleanup_block)) + { + // FIXME: Is this needed? + cleanup_block_builder.add_statements(statements.clone()); + } + + cleanup_block_builder.build(); + Ok(fresh_cleanup_label) + } + + fn apply_encoding_actions_on_edge( + &mut self, + block_builder: &mut BasicBlockBuilder, + actions: Vec, + from: RichLocation, + _to: RichLocation, + original_lifetimes: &mut BTreeSet, + derived_lifetimes: &mut BTreeMap>, + ) -> SpannedEncodingResult<()> { + for action in actions { + if let vir_high::Statement::EncodingAction(encoding_action) = action { + match encoding_action.action { + vir_high::Action::EndLoan(action) => { + let location = from.into_inner(); + let mut new_original_lifetimes = original_lifetimes.clone(); + let mut new_derived_lifetimes = derived_lifetimes.clone(); + if let Some(mut loans) = new_derived_lifetimes.remove(&action.lifetime.name) + { + assert_eq!( + loans.len(), + 1, + "Currently only one loan per lifetime is supported" + ); + let loan = loans.pop_first().unwrap(); + assert!(new_original_lifetimes.remove(&loan)); + new_derived_lifetimes.retain(|_, loans| !loans.contains(&loan)); + self.encode_dead_lifetime( + block_builder, + from.into_inner(), + action.lifetime.clone(), + )?; + self.encode_lft_return( + block_builder, + location, + derived_lifetimes, + &new_derived_lifetimes, + )?; + self.encode_end_lft( + block_builder, + location, + original_lifetimes, + &new_original_lifetimes, + )?; + self.encode_dead_inclusion( + block_builder, + location, + &new_original_lifetimes, + )?; + // let entries = + // self.already_dead_lifetimes.entry((from, to)).or_default(); + // entries.push(action.lifetime); + *original_lifetimes = new_original_lifetimes; + *derived_lifetimes = new_derived_lifetimes; + } + } + } + } else { + block_builder.add_statement(action); + } + } + Ok(()) + } + + #[allow(clippy::too_many_arguments)] + fn encode_terminator_assert( + &mut self, + block_builder: &mut BasicBlockBuilder, + location: mir::Location, + span: Span, + cond: &mir::Operand<'tcx>, + expected: bool, + msg: &mir::AssertMessage<'tcx>, + target: mir::BasicBlock, + cleanup: Option, + original_lifetimes: &mut BTreeSet, + derived_lifetimes: &mut BTreeMap>, + ) -> SpannedEncodingResult { + let condition = self + .encoder + .encode_operand_high(self.mir, cond, span) + .with_default_span(span)?; let guard = if expected { condition @@ -1847,12 +4167,34 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { )?); } let successor = if let Some(cleanup) = cleanup { - let successors = vec![ - (guard, target_label), - (true.into(), self.encode_basic_block_label(cleanup)), - ]; + let target_label = self.encode_lft_for_block_with_edge( + target, + false, + target_label, + location, + block_builder, + )?; + let cleanup_label = self.encode_basic_block_label(cleanup); + let cleanup_label = self.encode_lft_for_block_with_edge( + cleanup, + true, + cleanup_label, + location, + block_builder, + )?; + self.add_predecessor(location.block, target)?; + self.add_predecessor(location.block, cleanup)?; + let successors = vec![(guard, target_label), (true.into(), cleanup_label)]; SuccessorBuilder::jump(vir_high::Successor::GotoSwitch(successors)) } else { + self.encode_lft_for_block( + target, + location, + block_builder, + original_lifetimes, + derived_lifetimes, + )?; + self.add_predecessor(location.block, target)?; SuccessorBuilder::jump(vir_high::Successor::Goto(target_label)) }; Ok(successor) @@ -1905,7 +4247,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { .set_surrounding_error_context_for_statement(statement, position, error_ctxt) } - fn encode_specification_blocks(&mut self) -> SpannedEncodingResult<()> { + fn encode_specification_blocks(&mut self, procedure_name: &str) -> SpannedEncodingResult<()> { // Collect the entry points into the specification blocks. let mut entry_points: BTreeMap<_, _> = self .specification_blocks @@ -1913,9 +4255,31 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { .map(|bb| (bb, Vec::new())) .collect(); + if config::dump_debug_info() { + let graph = specification_blocks_to_graph(self.mir, &self.specification_blocks); + prusti_common::report::log::report_with_writer( + "graphviz_mir_dump_specification_blocks", + format!("{}.dot", procedure_name), + |writer| graph.write(writer).unwrap(), + ); + } + // Encode the specification blocks. + + // First, encode all specification expressions because they are sometimes used before they are declared. + let mut encoded_blocks = FxHashSet::default(); + for bb in entry_points.keys() { + let block = &self.mir[*bb]; + if self.try_encode_specification_expression(*bb, block)? { + encoded_blocks.insert(*bb); + } + } + + // Encode the remaining specification blocks. for (bb, statements) in &mut entry_points { - self.encode_specification_block(*bb, statements)?; + if !encoded_blocks.contains(bb) { + self.encode_specification_block(*bb, statements, None)?; + } } assert!(self.specification_block_encoding.is_empty()); self.specification_block_encoding = entry_points; @@ -1935,6 +4299,60 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { .insert(invariant_location, statement); } + self.encode_specification_regions()?; + + for region in self.specification_blocks.try_finally_regions() { + let on_panic_specification_region_entry_block = self + .specification_blocks + .spec_id_to_entry_block(®ion.on_panic_specification_region_id); + let on_panic_statements = self + .specification_region_encoding_statements + .remove(&on_panic_specification_region_entry_block) + .unwrap(); + let finally_at_panic_start_specification_region_entry_block = self + .specification_blocks + .spec_id_to_entry_block(®ion.finally_at_panic_start_specification_region_id); + let finally_at_panic_start_statements = self + .specification_region_encoding_statements + .remove(&finally_at_panic_start_specification_region_entry_block) + .unwrap(); + let finally_at_resume_specification_region_entry_block = self + .specification_blocks + .spec_id_to_entry_block(®ion.finally_at_resume_specification_region_id); + let finally_at_resume_statements = self + .specification_region_encoding_statements + .remove(&finally_at_resume_specification_region_entry_block) + .unwrap(); + for edge in ®ion.function_panic_exit_edges { + let mut unwind_statements = on_panic_statements.clone(); + unwind_statements.extend(finally_at_panic_start_statements.clone()); + assert!(self + .add_function_panic_specification_before_lifetime_effects + .insert(*edge, unwind_statements) + .is_none()); + assert!(self + .add_function_panic_specification_after_lifetime_effects + .insert(*edge, finally_at_resume_statements.clone()) + .is_none()); + } + for (_source, target) in ®ion.panic_exit_edges { + let mut unwind_statements = on_panic_statements.clone(); + unwind_statements.extend(finally_at_panic_start_statements.clone()); + unwind_statements.extend(finally_at_resume_statements.clone()); + // FIXME: Not using source is probably wrong. + let _old_statements = self + .add_specification_before_terminator + .insert(*target, unwind_statements); + // FIXME: assert!(old_statements.is_none(), "old_statements: {:?}", old_statements); + } + let mut statements = finally_at_panic_start_statements; + statements.extend(finally_at_resume_statements); + assert!(self + .add_specification_before_terminator + .insert(region.regular_exit_target_block, statements) + .is_none()); + } + self.encode_ghost_blocks()?; Ok(()) @@ -1945,13 +4363,17 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { &mut self, bb: mir::BasicBlock, encoded_statements: &mut Vec, + region_entry_block: Option, ) -> SpannedEncodingResult<()> { let block = &self.mir[bb]; if false + // || self.try_encode_specification_expression(bb, block)? || self.try_encode_assert(bb, block, encoded_statements)? || self.try_encode_assume(bb, block, encoded_statements)? + || self.try_encode_case_split(bb, block, encoded_statements)? || self.try_encode_ghost_markers(bb, block, encoded_statements)? - || self.try_encode_specification_function_call(bb, block, encoded_statements)? + || self.try_encode_specification_markers(bb, block, encoded_statements)? + || self.try_encode_specification_function_call(bb, block, encoded_statements, region_entry_block)? { Ok(()) } else { @@ -1959,6 +4381,60 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { } } + /// Check whether this basic block defines a Prusti specification + /// expression. If it does, encoding it and save it under the given + /// specification id. + fn try_encode_specification_expression( + &mut self, + bb: mir::BasicBlock, + block: &mir::BasicBlockData<'tcx>, + ) -> SpannedEncodingResult { + for stmt in &block.statements { + if let mir::StatementKind::Assign(box ( + _, + mir::Rvalue::Aggregate(box mir::AggregateKind::Closure(cl_def_id, cl_substs), _), + )) = stmt.kind + { + let def_id = cl_def_id; + let expression = match self.encoder.get_prusti_specification_expression(def_id) { + Some(spec) => spec, + None => return Ok(false), + }; + + let span = self + .encoder + .get_definition_span(expression.expression.to_def_id()); + + // We do not know the error context here, so we use a dummy one. + let error_ctxt = ErrorCtxt::UnexpectedSpecificationExpression; + + let expression = self.encoder.set_expression_error_ctxt( + self.encoder.encode_loop_spec_high( + self.mir, + bb, + self.def_id, + cl_substs, + true, + )?, + span, + error_ctxt, + self.def_id, + ); + + let attrs = self.encoder.env().query.get_attributes(def_id); + let Some(raw_spec_id) = prusti_interface::utils::read_prusti_attr("spec_id", attrs) else { + unreachable!(); + }; + + self.specification_expressions + .insert(raw_spec_id, expression); + + return Ok(true); + } + } + Ok(false) + } + fn try_encode_assert( &mut self, bb: mir::BasicBlock, @@ -1983,13 +4459,20 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let error_ctxt = ErrorCtxt::Panic(PanicCause::Assert); let assert_expr = self.encoder.set_expression_error_ctxt( - self.encoder - .encode_loop_spec_high(self.mir, bb, self.def_id, cl_substs)?, + self.encoder.encode_loop_spec_high( + self.mir, + bb, + self.def_id, + cl_substs, + true, + )?, span, error_ctxt.clone(), self.def_id, ); + let assert_expr = self.desugar_pledges_in_asertion(assert_expr)?; + let assert_stmt = vir_high::Statement::assert_no_pos(assert_expr); let assert_stmt = self.encoder.set_statement_error_ctxt( assert_stmt, @@ -1998,7 +4481,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { self.def_id, )?; - if self.check_mode != CheckMode::CoreProof { + if assertion.is_structural || self.check_mode.check_specifications() { encoded_statements.push(assert_stmt); } @@ -2033,19 +4516,27 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let error_ctxt = ErrorCtxt::Assumption; let expr = self.encoder.set_expression_error_ctxt( - self.encoder - .encode_loop_spec_high(self.mir, bb, self.def_id, cl_substs)?, + self.encoder.encode_loop_spec_high( + self.mir, + bb, + self.def_id, + cl_substs, + true, + )?, span, error_ctxt.clone(), self.def_id, ); + let expr = self.desugar_pledges_in_asertion(expr)?; + let stmt = vir_high::Statement::assume_no_pos(expr); let stmt = self.encoder .set_statement_error_ctxt(stmt, span, error_ctxt, self.def_id)?; - if self.check_mode != CheckMode::CoreProof { + if assumption.is_structural || self.check_mode.check_specifications() { + assert!(config::allow_prusti_assume(), "TODO: A proper error message that `allow_prusti_assume` needs to be enabled."); encoded_statements.push(stmt); } @@ -2055,6 +4546,57 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { Ok(false) } + fn try_encode_case_split( + &mut self, + bb: mir::BasicBlock, + block: &mir::BasicBlockData<'tcx>, + encoded_statements: &mut Vec, + ) -> SpannedEncodingResult { + for stmt in &block.statements { + if let mir::StatementKind::Assign(box ( + _, + mir::Rvalue::Aggregate(box mir::AggregateKind::Closure(cl_def_id, cl_substs), _), + )) = stmt.kind + { + let case_split = match self.encoder.get_prusti_case_split(cl_def_id) { + Some(spec) => spec, + None => return Ok(false), + }; + + let span = self + .encoder + .get_definition_span(case_split.assertion.to_def_id()); + + let error_ctxt = ErrorCtxt::CaseSplit; + + let expr = self.encoder.set_expression_error_ctxt( + self.encoder.encode_loop_spec_high( + self.mir, + bb, + self.def_id, + cl_substs, + true, + )?, + span, + error_ctxt.clone(), + self.def_id, + ); + + let expr = self.desugar_pledges_in_asertion(expr)?; + + let stmt = vir_high::Statement::case_split_no_pos(expr); + let stmt = + self.encoder + .set_statement_error_ctxt(stmt, span, error_ctxt, self.def_id)?; + + encoded_statements.push(stmt); + + return Ok(true); + } + } + Ok(false) + } + fn try_encode_ghost_markers( &mut self, _bb: mir::BasicBlock, @@ -2075,14 +4617,53 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { Ok(false) } + fn try_encode_specification_markers( + &mut self, + _bb: mir::BasicBlock, + block: &mir::BasicBlockData<'tcx>, + _encoded_statements: &mut [vir_high::Statement], + ) -> SpannedEncodingResult { + let is_marker = is_specification_begin_marker(self.encoder.env().query, block).is_some() + || is_specification_end_marker(self.encoder.env().query, block) + || is_try_finally_begin_marker(self.encoder.env().query, block).is_some() + || is_try_finally_end_marker(self.encoder.env().query, block) + || is_checked_block_begin_marker(self.encoder.env().query, block) + || is_checked_block_end_marker(self.encoder.env().query, block); + // for stmt in &block.statements { + // if let mir::StatementKind::Assign(box ( + // _, + // mir::Rvalue::Aggregate(box mir::AggregateKind::Closure(cl_def_id, _), _), + // )) = stmt.kind + // { + // let is_begin = self + // .encoder + // .get_specification_region_begin(cl_def_id) + // .is_some(); + // let is_end = self + // .encoder + // .get_specification_region_end(cl_def_id) + // .is_some(); + // return Ok(is_begin || is_end); + // } + // } + Ok(is_marker) + } + + // TODO: Move this function to a separate file and extract nested functions. fn try_encode_specification_function_call( &mut self, bb: mir::BasicBlock, block: &mir::BasicBlockData<'tcx>, encoded_statements: &mut Vec, + region_entry_block: Option, ) -> SpannedEncodingResult { let span = self.encoder.get_mir_terminator_span(block.terminator()); - match &block.terminator().kind { + let location = mir::Location { + block: bb, + statement_index: block.statements.len(), + }; + let terminator_kind = &block.terminator().kind; + match terminator_kind { mir::TerminatorKind::Call { func: mir::Operand::Constant(box mir::Constant { literal, .. }), args, @@ -2095,10 +4676,34 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { if let ty::TyKind::FnDef(def_id, _substs) = literal.ty().kind() { let full_called_function_name = self.encoder.env().name.get_absolute_item_name(*def_id); - match full_called_function_name.as_ref() { - "prusti_contracts::prusti_set_union_active_field" => { - assert_eq!(args.len(), 1); - let argument_place = if let mir::Operand::Move(place) = args[0] { + enum ArgKind { + Place(vir_high::Expression), + String(String), + } + fn extract_args<'p, 'v: 'p, 'tcx: 'v>( + mir: &mir::Body<'tcx>, + args: &[mir::Operand<'tcx>], + block: &mir::BasicBlockData<'tcx>, + encoder: &mut ProcedureEncoder<'p, 'v, 'tcx>, + ) -> SpannedEncodingResult> { + // assert_eq!(args.len(), 1); + let mut encoded_args = Vec::new(); + for arg in args { + match arg { + mir::Operand::Move(_) => {} + mir::Operand::Constant(constant) => { + // FIXME: There should be a proper way of doing this. + let value = format!("{constant:?}"); + let value = + value.trim_start_matches("const \"").trim_end_matches('\"'); + encoded_args.push(ArgKind::String(value.to_string())); + continue; // FIXME: Do proper control flow. + } + _ => { + unreachable!() + } + } + let argument_place = if let mir::Operand::Move(place) = arg { place } else { unreachable!() @@ -2106,15 +4711,30 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { // Find the place whose address was stored in the argument by // iterating backwards through statements. let mut statement_index = block.statements.len() - 1; - let union_variant_place = loop { + let place = loop { if let Some(statement) = block.statements.get(statement_index) { - if let mir::StatementKind::Assign(box ( - target_place, - mir::Rvalue::AddressOf(_, union_variant_place), - )) = &statement.kind + if let mir::StatementKind::Assign(box (target_place, rvalue)) = + &statement.kind { - if *target_place == argument_place { - break union_variant_place; + if target_place == argument_place { + match rvalue { + mir::Rvalue::AddressOf(_, place) => { + break encoder.encode_place(*place, None)?; + } + mir::Rvalue::Use(operand) => { + break encoder + .encoder + .encode_operand_high( + mir, + operand, + statement.source_info.span, + ) + .with_span(statement.source_info.span)?; + } + _ => { + unimplemented!("rvalue: {:?}", rvalue); + } + } } } statement_index -= 1; @@ -2122,8 +4742,65 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { unreachable!(); } }; - let encoded_variant_place = - self.encode_place(*union_variant_place, None)?; + encoded_args.push(ArgKind::Place(place)); + } + Ok(encoded_args) + } + fn extract_places<'p, 'v: 'p, 'tcx: 'v>( + mir: &mir::Body<'tcx>, + args: &[mir::Operand<'tcx>], + block: &mir::BasicBlockData<'tcx>, + encoder: &mut ProcedureEncoder<'p, 'v, 'tcx>, + ) -> SpannedEncodingResult> { + let places = extract_args(mir, args, block, encoder)? + .into_iter() + .map(|arg| match arg { + ArgKind::Place(place) => place, + ArgKind::String(_) => unreachable!(), + }) + .collect(); + Ok(places) + } + fn extract_place<'p, 'v: 'p, 'tcx: 'v>( + mir: &mir::Body<'tcx>, + args: &[mir::Operand<'tcx>], + block: &mir::BasicBlockData<'tcx>, + encoder: &mut ProcedureEncoder<'p, 'v, 'tcx>, + ) -> SpannedEncodingResult { + assert_eq!(args.len(), 1); + Ok(extract_places(mir, args, block, encoder)?.pop().unwrap()) + } + match full_called_function_name.as_ref() { + "prusti_contracts::prusti_set_union_active_field" => { + assert_eq!(args.len(), 1); + // assert_eq!(args.len(), 1); + // let argument_place = if let mir::Operand::Move(place) = args[0] { + // place + // } else { + // unreachable!() + // }; + // // Find the place whose address was stored in the argument by + // // iterating backwards through statements. + // let mut statement_index = block.statements.len() - 1; + // let union_variant_place = loop { + // if let Some(statement) = block.statements.get(statement_index) { + // if let mir::StatementKind::Assign(box ( + // target_place, + // mir::Rvalue::AddressOf(_, union_variant_place), + // )) = &statement.kind + // { + // if *target_place == argument_place { + // break union_variant_place; + // } + // } + // statement_index -= 1; + // } else { + // unreachable!(); + // } + // }; + // let encoded_variant_place = + // self.encode_place(*union_variant_place, None)?; + let encoded_variant_place = extract_place(self.mir, args, block, self)?; let statement = self.encoder.set_statement_error_ctxt( vir_high::Statement::set_union_variant_no_pos( encoded_variant_place, @@ -2136,13 +4813,1001 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { encoded_statements.push(statement); Ok(true) } - _ => unreachable!(), - } - } else { - unreachable!(); - } - } - _ => unreachable!("block: {:?}", bb), - } - } + "prusti_contracts::prusti_manually_manage" => { + let encoded_place = extract_place(self.mir, args, block, self)?; + assert!(self.manually_managed_places.insert(encoded_place)); + Ok(true) + } + "prusti_contracts::prusti_pack_place" => { + let encoded_place = extract_place(self.mir, args, block, self)?; + // let permission_amount = + // self.lookup_opened_reference_place_permission(&encoded_place); + let statement = self.encoder.set_statement_error_ctxt( + vir_high::Statement::pack_no_pos( + encoded_place, + vir_high::PredicateKind::Owned, + None, + // permission_amount, + ), + span, + ErrorCtxt::Pack, + self.def_id, + )?; + statement.check_no_default_position(); + encoded_statements.push(statement); + Ok(true) + } + "prusti_contracts::prusti_unpack_place" => { + let encoded_place = extract_place(self.mir, args, block, self)?; + // let permission_amount = + // self.lookup_opened_reference_place_permission(&encoded_place); + let statement = self.encoder.set_statement_error_ctxt( + vir_high::Statement::unpack_no_pos( + encoded_place, + vir_high::PredicateKind::Owned, + None, + // permission_amount, + ), + span, + ErrorCtxt::Unpack, + self.def_id, + )?; + statement.check_no_default_position(); + encoded_statements.push(statement); + Ok(true) + } + "prusti_contracts::prusti_obtain_place" => { + let encoded_place = extract_place(self.mir, args, block, self)?; + let statement = self.encoder.set_statement_error_ctxt( + vir_high::Statement::obtain_no_pos( + encoded_place, + vir_high::PredicateKind::Owned, + ), + span, + ErrorCtxt::Unpack, + self.def_id, + )?; + statement.check_no_default_position(); + encoded_statements.push(statement); + Ok(true) + } + "prusti_contracts::prusti_pack_ref_place" => { + assert_eq!(args.len(), 2); + let mut encoded_args = extract_args(self.mir, args, block, self)?; + let ArgKind::Place(place) = encoded_args.pop().unwrap() else { + unreachable!("Wrong function parameters?"); + }; + let ArgKind::String(lifetime_name) = encoded_args.pop().unwrap() else { + unreachable!("Wrong function parameters?"); + }; + assert!(encoded_args.is_empty()); + let lifetime = self + .user_named_lifetimes + .get(&lifetime_name) + .unwrap() + .clone(); + // let permission_amount = + // self.lookup_opened_reference_place_permission(&place); + let statement = self.encoder.set_statement_error_ctxt( + vir_high::Statement::pack_no_pos( + place, + vir_high::PredicateKind::frac_ref(lifetime), + None, + // permission_amount, + ), + span, + ErrorCtxt::Pack, + self.def_id, + )?; + statement.check_no_default_position(); + encoded_statements.push(statement); + Ok(true) + } + "prusti_contracts::prusti_unpack_ref_place" => { + assert_eq!(args.len(), 2); + let mut encoded_args = extract_args(self.mir, args, block, self)?; + let ArgKind::Place(place) = encoded_args.pop().unwrap() else { + unreachable!("Wrong function parameters?"); + }; + let ArgKind::String(lifetime_name) = encoded_args.pop().unwrap() else { + unreachable!("Wrong function parameters?"); + }; + assert!(encoded_args.is_empty()); + let lifetime = self + .user_named_lifetimes + .get(&lifetime_name) + .unwrap() + .clone(); + // let encoded_place = extract_place(self.mir, args, block, self)?; + // let permission_amount = + // self.lookup_opened_reference_place_permission(&place); + let statement = self.encoder.set_statement_error_ctxt( + vir_high::Statement::unpack_no_pos( + place, + vir_high::PredicateKind::frac_ref(lifetime), + None, + // permission_amount, + ), + span, + ErrorCtxt::Unpack, + self.def_id, + )?; + statement.check_no_default_position(); + encoded_statements.push(statement); + Ok(true) + } + "prusti_contracts::prusti_pack_mut_ref_place" + | "prusti_contracts::prusti_pack_mut_ref_place_obligation" => { + assert_eq!(args.len(), 2); + let mut encoded_args = extract_args(self.mir, args, block, self)?; + let ArgKind::Place(place) = encoded_args.pop().unwrap() else { + unreachable!("Wrong function parameters?"); + }; + let ArgKind::String(lifetime_name) = encoded_args.pop().unwrap() else { + unreachable!("Wrong function parameters?"); + }; + assert!(encoded_args.is_empty()); + let lifetime = self + .user_named_lifetimes + .get(&lifetime_name) + .unwrap() + .clone(); + // let encoded_place = extract_place(self.mir, args, block, self)?; + // let permission_amount = + // self.lookup_opened_reference_place_permission(&place); + let with_obligation = if full_called_function_name + == "prusti_contracts::prusti_pack_mut_ref_place_obligation" + { + Some(self.lifetime_token_fractional_permission(self.lifetime_count)) + } else { + None + }; + let statement = self.encoder.set_statement_error_ctxt( + vir_high::Statement::pack_no_pos( + place, + vir_high::PredicateKind::unique_ref(lifetime), + with_obligation, + // permission_amount, + ), + span, + ErrorCtxt::Pack, + self.def_id, + )?; + statement.check_no_default_position(); + encoded_statements.push(statement); + Ok(true) + } + "prusti_contracts::prusti_unpack_mut_ref_place" + | "prusti_contracts::prusti_unpack_mut_ref_place_obligation" => { + assert_eq!(args.len(), 2); + let mut encoded_args = extract_args(self.mir, args, block, self)?; + let ArgKind::Place(place) = encoded_args.pop().unwrap() else { + unreachable!("Wrong function parameters?"); + }; + let ArgKind::String(lifetime_name) = encoded_args.pop().unwrap() else { + unreachable!("Wrong function parameters?"); + }; + assert!(encoded_args.is_empty()); + let lifetime = self + .user_named_lifetimes + .get(&lifetime_name) + .unwrap() + .clone(); + // let permission_amount = + // self.lookup_opened_reference_place_permission(&place); + let with_obligation = if full_called_function_name + == "prusti_contracts::prusti_unpack_mut_ref_place_obligation" + { + Some(self.lifetime_token_fractional_permission(self.lifetime_count)) + } else { + None + }; + let statement = self.encoder.set_statement_error_ctxt( + vir_high::Statement::unpack_no_pos( + place, + vir_high::PredicateKind::unique_ref(lifetime), + with_obligation, + // permission_amount, + ), + span, + ErrorCtxt::Unpack, + self.def_id, + )?; + // let encoded_place = extract_place(self.mir, args, block, self)?; + // let statement = self.encoder.set_statement_error_ctxt( + // vir_high::Statement::unpack_no_pos( + // encoded_place, + // vir_high::PredicateKind::UniqueRef, + // ), + // span, + // ErrorCtxt::Unpack, + // self.def_id, + // )?; + statement.check_no_default_position(); + encoded_statements.push(statement); + Ok(true) + } + "prusti_contracts::prusti_take_lifetime" => { + assert_eq!(args.len(), 2); + let mut encoded_args = extract_args(self.mir, args, block, self)?; + let ArgKind::String(lifetime_name) = encoded_args.pop().unwrap() else { + unreachable!("Wrong function parameters?"); + }; + let ArgKind::Place(place) = encoded_args.pop().unwrap() else { + unreachable!("Wrong function parameters?"); + }; + assert!(encoded_args.is_empty()); + let vir_high::ty::Type::Reference(ref_type) = place.get_type() else { + unimplemented!("FIXME: A proper error message."); + }; + let lifetime = ref_type.lifetime.clone(); + assert!(self + .user_named_lifetimes + .insert(lifetime_name, lifetime) + .is_none()); + Ok(true) + } + "prusti_contracts::prusti_end_loan" => { + assert_eq!(args.len(), 1); + let mut encoded_args = extract_args(self.mir, args, block, self)?; + let ArgKind::String(lifetime_name) = encoded_args.pop().unwrap() else { + unreachable!("Wrong function parameters?"); + }; + assert!(encoded_args.is_empty()); + let lifetime: vir_high::ty::LifetimeConst = self + .user_named_lifetimes + .get(&lifetime_name) + .unwrap() + .clone(); + let statement = self.encoder.set_statement_error_ctxt( + vir_high::Statement::encoding_action_no_pos( + vir_high::Action::end_loan(lifetime), + ), + span, + ErrorCtxt::Unpack, + self.def_id, + )?; + encoded_statements.push(statement); + Ok(true) + } + "prusti_contracts::prusti_set_lifetime_for_raw_pointer_reference_casts" => { + assert_eq!(args.len(), 1); + // FIXME: Code is very similar to + // prusti-viper/src/encoder/mir/procedures/encoder/elaborate_drops/pointer_reborrow.rs. + let arg = &args[0]; + let mut statement_index = block.statements.len() - 1; + let argument_place = if let mir::Operand::Move(place) = arg { + place + } else { + unreachable!() + }; + let place = loop { + if let Some(statement) = block.statements.get(statement_index) { + if let mir::StatementKind::Assign(box (target_place, rvalue)) = + &statement.kind + { + if target_place == argument_place { + match rvalue { + mir::Rvalue::AddressOf(_, place) => { + break place; + } + _ => { + unimplemented!("rvalue: {:?}", rvalue); + } + } + } + } + statement_index -= 1; + } else { + unreachable!(); + } + }; + let ty::TyKind::Ref(reference_region, _, _) = place.ty(self.mir, self.encoder.env().tcx()).ty.kind() else { + unreachable!("place {place:?} must be a reference"); + }; + self.pointer_deref_lifetime = Some(*reference_region); + Ok(true) + } + "prusti_contracts::prusti_attach_drop_lifetime" => { + assert_eq!(args.len(), 2); + // FIXME: Is doing nothing correct here? + Ok(true) + } + "prusti_contracts::prusti_join_place" => { + let encoded_place = extract_place(self.mir, args, block, self)?; + let statement = self.encoder.set_statement_error_ctxt( + vir_high::Statement::join_no_pos(encoded_place), + span, + ErrorCtxt::Pack, + self.def_id, + )?; + statement.check_no_default_position(); + encoded_statements.push(statement); + Ok(true) + } + "prusti_contracts::prusti_join_range" => { + assert_eq!(args.len(), 3); + let mut encoded_args = extract_args(self.mir, args, block, self)?; + let ArgKind::Place(end_index) = encoded_args.pop().unwrap() else { + unreachable!("Wrong function parameters?"); + }; + let ArgKind::Place(start_index) = encoded_args.pop().unwrap() else { + unreachable!("Wrong function parameters?"); + }; + let ArgKind::Place(pointer) = encoded_args.pop().unwrap() else { + unreachable!("Wrong function parameters?"); + }; + let statement = self.encoder.set_statement_error_ctxt( + vir_high::Statement::join_range_no_pos( + pointer, + start_index, + end_index, + ), + span, + ErrorCtxt::JoinRange, + self.def_id, + )?; + statement.check_no_default_position(); + encoded_statements.push(statement); + Ok(true) + } + "prusti_contracts::prusti_split_place" => { + let encoded_place = extract_place(self.mir, args, block, self)?; + let statement = self.encoder.set_statement_error_ctxt( + vir_high::Statement::split_no_pos(encoded_place), + span, + ErrorCtxt::Unpack, + self.def_id, + )?; + statement.check_no_default_position(); + encoded_statements.push(statement); + Ok(true) + } + "prusti_contracts::prusti_split_range" => { + assert_eq!(args.len(), 3); + let mut encoded_args = extract_args(self.mir, args, block, self)?; + let ArgKind::Place(end_index) = encoded_args.pop().unwrap() else { + unreachable!("Wrong function parameters?"); + }; + let ArgKind::Place(start_index) = encoded_args.pop().unwrap() else { + unreachable!("Wrong function parameters?"); + }; + let ArgKind::Place(pointer) = encoded_args.pop().unwrap() else { + unreachable!("Wrong function parameters?"); + }; + let statement = self.encoder.set_statement_error_ctxt( + vir_high::Statement::split_range_no_pos( + pointer, + start_index, + end_index, + ), + span, + ErrorCtxt::SplitRange, + self.def_id, + )?; + statement.check_no_default_position(); + encoded_statements.push(statement); + Ok(true) + } + "prusti_contracts::prusti_stash_range" => { + assert_eq!(args.len(), 4); + let mut encoded_args = extract_args(self.mir, args, block, self)?; + let ArgKind::String(stash_name) = encoded_args.pop().unwrap() else { + unreachable!("Wrong function parameters?"); + }; + let ArgKind::Place(end_index) = encoded_args.pop().unwrap() else { + unreachable!("Wrong function parameters?"); + }; + let ArgKind::Place(start_index) = encoded_args.pop().unwrap() else { + unreachable!("Wrong function parameters?"); + }; + let ArgKind::Place(pointer) = encoded_args.pop().unwrap() else { + unreachable!("Wrong function parameters?"); + }; + encoded_statements.push(vir_high::Statement::old_label( + stash_name.clone(), + self.encoder.register_error( + span, + ErrorCtxt::StashRange, + self.def_id, + ), + )); + let statement = self.encoder.set_statement_error_ctxt( + vir_high::Statement::stash_range_no_pos( + pointer.clone(), + start_index.clone(), + end_index.clone(), + stash_name.clone(), + ), + span, + ErrorCtxt::StashRange, + self.def_id, + )?; + statement.check_no_default_position(); + encoded_statements.push(statement); + encoded_statements.push(vir_high::Statement::old_label( + format!("{stash_name}$post"), + self.encoder.register_error( + span, + ErrorCtxt::StashRange, + self.def_id, + ), + )); + let position = pointer.position(); + let pointer = vir_high::Expression::labelled_old( + stash_name.clone(), + pointer, + position, + ); + assert!(self + .stashed_ranges + .insert(stash_name, (pointer, start_index, end_index)) + .is_none()); + Ok(true) + } + "prusti_contracts::prusti_restore_stash_range" => { + assert_eq!(args.len(), 3); + let mut encoded_args = extract_args(self.mir, args, block, self)?; + let ArgKind::String(stash_name) = encoded_args.pop().unwrap() else { + unreachable!("Wrong function parameters?"); + }; + let ArgKind::Place(new_start_index) = encoded_args.pop().unwrap() else { + unreachable!("Wrong function parameters?"); + }; + let ArgKind::Place(new_pointer) = encoded_args.pop().unwrap() else { + unreachable!("Wrong function parameters?"); + }; + let (old_pointer, old_start_index, old_end_index) = + self.stashed_ranges.get(&stash_name).unwrap().clone(); + let statement = self.encoder.set_statement_error_ctxt( + vir_high::Statement::stash_range_restore_no_pos( + old_pointer, + old_start_index, + old_end_index, + stash_name, + new_pointer, + new_start_index, + ), + span, + ErrorCtxt::RestoreStashRange, + self.def_id, + )?; + statement.check_no_default_position(); + encoded_statements.push(statement); + Ok(true) + } + "prusti_contracts::prusti_close_ref_place" => { + assert_eq!(args.len(), 2); + let mut encoded_args = extract_args(self.mir, args, block, self)?; + let ArgKind::String(witness) = encoded_args.pop().unwrap() else { + unreachable!() + }; + let ArgKind::String(place_spec_id) = encoded_args.pop().unwrap() else { + unreachable!() + }; + let user_place = self + .specification_expressions + .get(&place_spec_id) + .expect("FIXME: A proper error message") + .clone(); + let vir_high::Expression::AddrOf(vir_high::AddrOf { base: box user_place, .. }) = + user_place else { + unreachable!("place: {user_place}"); + }; + assert!(encoded_args.is_empty()); + // FIXME: These should actually remove the + // witnesses. However, since specification blocks + // are processed before all other blocks, the state + // cannot be easily transfered. A proper solution + // would be to check whether the state that uses the + // opened permission is dominated by the statement + // that opens the reference. Alternatively, we could + // have annotations that specify which permission + // amount to use for copy statements. Another + // alternative (probably the easiest) would be to + // make a static analysis that inserts the right + // permission amount into the copy statement. + // + // A proper solution probably would be to integrate + // this into fold-unfold algorithm. + let (place, lifetime) = self + .opened_reference_witnesses + .get(&witness) + .expect("FIXME: a proper error message"); + assert_eq!(place, &user_place, "FIXME: a proper error message"); + let variable = self + .opened_reference_place_permissions + .get(place) + .expect("FIXME: A proper error message"); + // let deref_base = place.get_last_dereferenced_reference().cloned(); + // let statement = self.encode_close_reference( + // location, + // &deref_base, + // place.clone(), + // variable.clone(), + // )?; + let statement = self.set_statement_error( + location, + ErrorCtxt::CloseFracRef, + vir_high::Statement::close_frac_ref_no_pos( + lifetime.clone(), + self.lifetime_token_fractional_permission(self.lifetime_count), + place.clone(), + variable.clone().unwrap(), + true, + ), + )?; + encoded_statements.push(statement); + // encoded_statements.push(statement.expect( + // "FIXME: A proper error message for closing not a reference", + // )); + Ok(true) + } + "prusti_contracts::prusti_open_ref_place" => { + assert_eq!(args.len(), 3); + let mut encoded_args = extract_args(self.mir, args, block, self)?; + let ArgKind::String(witness) = encoded_args.pop().unwrap() else { + unreachable!() + }; + let ArgKind::String(place_spec_id) = encoded_args.pop().unwrap() else { + unreachable!() + }; + let place = self + .specification_expressions + .get(&place_spec_id) + .unwrap() + .clone(); + let vir_high::Expression::AddrOf(vir_high::AddrOf { base: box place, .. }) = + place else { + unreachable!("place: {place}"); + }; + let ArgKind::String(lifetime_name) = encoded_args.pop().unwrap() else { + unreachable!() + }; + place.check_no_erased_lifetime(); + assert!(encoded_args.is_empty()); + let Some(lifetime) = self + .user_named_lifetimes + .get(&lifetime_name) + .cloned() else { + return Err(SpannedEncodingError::incorrect( + format!("Lifetime name `{lifetime_name}` not defined"), span)); + }; + let permission = self + .fresh_ghost_variable("tmp_frac_ref_perm", vir_high::Type::MPerm); + let variable = Some(permission.clone()); + let statement = self.set_statement_error( + location, + ErrorCtxt::OpenFracRef, + vir_high::Statement::open_frac_ref_no_pos( + lifetime.clone(), + permission, + self.lifetime_token_fractional_permission(self.lifetime_count), + place.clone(), + true, + ), + )?; + + // let deref_place = place.get_last_dereferenced_reference().cloned(); + // let (variable, statement) = + // self.encode_open_reference(location, &deref_place, place.clone())?; + encoded_statements.push(statement); + assert!(self + .opened_reference_place_permissions + .insert(place.clone(), variable) + .is_none()); + assert!(self + .opened_reference_witnesses + .insert(witness, (place, lifetime)) + .is_none()); + Ok(true) + } + "prusti_contracts::prusti_close_mut_ref_place" => { + assert_eq!(args.len(), 2); + let mut encoded_args = extract_args(self.mir, args, block, self)?; + let ArgKind::String(witness) = encoded_args.pop().unwrap() else { + unreachable!() + }; + let ArgKind::String(place_spec_id) = encoded_args.pop().unwrap() else { + unreachable!() + }; + let user_place = self + .specification_expressions + .get(&place_spec_id) + .expect("FIXME: A proper error message") + .clone(); + let vir_high::Expression::AddrOf(vir_high::AddrOf { base: box user_place, .. }) = + user_place else { + unreachable!("place: {user_place}"); + }; + assert!(encoded_args.is_empty()); + // FIXME: These should actually remove the + // witnesses. However, since specification blocks + // are processed before all other blocks, the state + // cannot be easily transfered. A proper solution + // would be to check whether the state that uses the + // opened permission is dominated by the statement + // that opens the reference. Alternatively, we could + // have annotations that specify which permission + // amount to use for copy statements. Another + // alternative (probably the easiest) would be to + // make a static analysis that inserts the right + // permission amount into the copy statement. + // + // A proper solution probably would be to integrate + // this into fold-unfold algorithm. + let (place, lifetime) = self + .opened_reference_witnesses + .get(&witness) + .unwrap_or_else(|| { + unimplemented!("FIXME: A proper error message: {witness}") + }); + assert_eq!(place, &user_place, "FIXME: a proper error message"); + // let variable = self + // .opened_reference_place_permissions + // .get(&place) + // .expect("FIXME: A proper error message"); + // let deref_base = place.get_last_dereferenced_reference().cloned(); + let statement = self.set_statement_error( + location, + ErrorCtxt::CloseMutRef, + vir_high::Statement::close_mut_ref_no_pos( + lifetime.clone(), + self.lifetime_token_fractional_permission(self.lifetime_count), + place.clone(), + true, + ), + )?; + // let statement = self.encode_close_reference( + // location, + // &deref_base, + // place.clone(), + // variable.clone(), + // )?; + // encoded_statements.push(statement.expect( + // "FIXME: A proper error message for closing not a reference", + // )); + encoded_statements.push(statement); + Ok(true) + } + "prusti_contracts::prusti_open_mut_ref_place" => { + assert_eq!(args.len(), 3); + let mut encoded_args = extract_args(self.mir, args, block, self)?; + let ArgKind::String(witness) = encoded_args.pop().unwrap() else { + unreachable!() + }; + let ArgKind::String(place_spec_id) = encoded_args.pop().unwrap() else { + unreachable!() + }; + let place = self + .specification_expressions + .get(&place_spec_id) + .unwrap() + .clone(); + let vir_high::Expression::AddrOf(vir_high::AddrOf { base: box place, .. }) = + place else { + unreachable!("place: {place}"); + }; + let ArgKind::String(lifetime_name) = encoded_args.pop().unwrap() else { + unreachable!() + }; + place.check_no_erased_lifetime(); + assert!(encoded_args.is_empty()); + // let lifetime = self + // .user_named_lifetimes + // .get(&lifetime_name) + // .unwrap() + // .clone(); + let Some(lifetime) = self + .user_named_lifetimes + .get(&lifetime_name) + .cloned() else { + return Err(SpannedEncodingError::incorrect( + format!("Lifetime name `{lifetime_name}` not defined"), span)); + }; + let statement = self.set_statement_error( + location, + ErrorCtxt::OpenMutRef, + vir_high::Statement::open_mut_ref_no_pos( + lifetime.clone(), + self.lifetime_token_fractional_permission(self.lifetime_count), + place.clone(), + true, + ), + )?; + encoded_statements.push(statement); + assert!(self + .opened_reference_place_permissions + .insert(place.clone(), None) + .is_none()); + assert!(self + .opened_reference_witnesses + .insert(witness, (place, lifetime)) + .is_none()); + Ok(true) + } + "prusti_contracts::prusti_restore_mut_borrowed" => { + assert_eq!(args.len(), 2); + let mut encoded_args = extract_args(self.mir, args, block, self)?; + let ArgKind::String(referenced_place_spec_id) = encoded_args.pop().unwrap() else { + unreachable!() + }; + let ArgKind::String(referencing_place_spec_id) = encoded_args.pop().unwrap() else { + unreachable!() + }; + let referencing_place = self + .specification_expressions + .get(&referencing_place_spec_id) + .expect("FIXME: A proper error message") + .clone(); + let referenced_place = self + .specification_expressions + .get(&referenced_place_spec_id) + .expect("FIXME: A proper error message") + .clone(); + let vir_high::Expression::AddrOf(vir_high::AddrOf { base: box referencing_place, .. }) = + referencing_place else { + unreachable!("place: {referencing_place}"); + }; + let vir_high::Type::Reference(vir_high::ty::Reference { + lifetime, + ..} ) = referencing_place.get_type() else { + unreachable!("TODO: a proper error message that {referencing_place} needs to be a reference"); + }; + let vir_high::Expression::AddrOf(vir_high::AddrOf { base: box referenced_place, .. }) = + referenced_place else { + unreachable!("place: {referenced_place}"); + }; + assert!(encoded_args.is_empty()); + let statement = self.set_statement_error( + location, + ErrorCtxt::RestoreMutBorrowed, + vir_high::Statement::restore_mut_borrowed_no_pos( + lifetime.clone(), + referenced_place, + referencing_place, + ), + )?; + encoded_statements.push(statement); + Ok(true) + } + "prusti_contracts::prusti_resolve" => { + assert_eq!(args.len(), 1); + let encoded_place = extract_place(self.mir, args, block, self)?; + let statement = self.set_statement_error( + location, + ErrorCtxt::Resolve, + vir_high::Statement::dead_reference_no_pos(encoded_place, None), + )?; + encoded_statements.push(statement); + Ok(true) + } + "prusti_contracts::prusti_resolve_range" => { + assert_eq!(args.len(), 6); + let mut encoded_args = extract_args(self.mir, args, block, self)?; + let ArgKind::Place(end_index) = encoded_args.pop().unwrap() else { + unreachable!("Wrong function parameters?"); + }; + let ArgKind::Place(start_index) = encoded_args.pop().unwrap() else { + unreachable!("Wrong function parameters?"); + }; + let ArgKind::Place(predicate_range_end_index) = encoded_args.pop().unwrap() else { + unreachable!("Wrong function parameters?"); + }; + let ArgKind::Place(predicate_range_start_index) = encoded_args.pop().unwrap() else { + unreachable!("Wrong function parameters?"); + }; + let ArgKind::Place(pointer) = encoded_args.pop().unwrap() else { + unreachable!("Wrong function parameters?"); + }; + let ArgKind::String(lifetime_name) = encoded_args.pop().unwrap() else { + unreachable!() + }; + let Some(lifetime) = self + .user_named_lifetimes + .get(&lifetime_name) + .cloned() else { + return Err(SpannedEncodingError::incorrect( + format!("Lifetime name `{lifetime_name}` not defined"), span)); + }; + let statement = self.set_statement_error( + location, + ErrorCtxt::Resolve, + vir_high::Statement::dead_reference_range_no_pos( + lifetime, + vir_high::ty::Uniqueness::Unique, + pointer, + predicate_range_start_index, + predicate_range_end_index, + start_index, + end_index, + ), + )?; + statement.check_no_default_position(); + encoded_statements.push(statement); + Ok(true) + } + "prusti_contracts::prusti_forget_initialization" => { + let encoded_place = extract_place(self.mir, args, block, self)?; + let statement = self.encoder.set_statement_error_ctxt( + vir_high::Statement::forget_initialization_no_pos(encoded_place), + span, + ErrorCtxt::ForgetInitialization, + self.def_id, + )?; + statement.check_no_default_position(); + encoded_statements.push(statement); + Ok(true) + } + "prusti_contracts::prusti_forget_initialization_range" => { + assert_eq!(args.len(), 3); + let mut encoded_args = extract_args(self.mir, args, block, self)?; + let ArgKind::Place(end_index) = encoded_args.pop().unwrap() else { + unreachable!("Wrong function parameters?"); + }; + let ArgKind::Place(start_index) = encoded_args.pop().unwrap() else { + unreachable!("Wrong function parameters?"); + }; + let ArgKind::Place(pointer) = encoded_args.pop().unwrap() else { + unreachable!("Wrong function parameters?"); + }; + let statement = self.encoder.set_statement_error_ctxt( + vir_high::Statement::forget_initialization_range_no_pos( + pointer, + start_index, + end_index, + ), + span, + ErrorCtxt::ForgetInitialization, + self.def_id, + )?; + statement.check_no_default_position(); + encoded_statements.push(statement); + Ok(true) + } + "prusti_contracts::prusti_on_drop_unwind" => { + let encoded_place = extract_place(self.mir, args, block, self)?; + let Some(region_entry_block) = region_entry_block else { + unreachable!() + }; + assert!(self + .specification_on_drop_unwind + .insert(encoded_place, region_entry_block) + .is_none()); + Ok(true) + } + "prusti_contracts::prusti_before_drop" => { + let encoded_place = extract_place(self.mir, args, block, self)?; + let Some(region_entry_block) = region_entry_block else { + unreachable!() + }; + assert!(self + .specification_before_drop + .insert(encoded_place, region_entry_block) + .is_none()); + Ok(true) + } + "prusti_contracts::prusti_after_drop" => { + let encoded_place = extract_place(self.mir, args, block, self)?; + let Some(region_entry_block) = region_entry_block else { + unreachable!() + }; + assert!(self + .specification_after_drop + .insert(encoded_place, region_entry_block) + .is_none()); + Ok(true) + } + "prusti_contracts::prusti_restore_place" => { + assert_eq!(args.len(), 2); + let mut encoded_places = extract_places(self.mir, args, block, self)?; + let restored_place = encoded_places.pop().unwrap(); + let borrowing_place = encoded_places.pop().unwrap(); + let statement = self.encoder.set_statement_error_ctxt( + vir_high::Statement::restore_raw_borrowed_no_pos( + borrowing_place, + restored_place, + ), + span, + ErrorCtxt::RestoreRawBorrowed, + self.def_id, + )?; + statement.check_no_default_position(); + encoded_statements.push(statement); + Ok(true) + } + "prusti_contracts::prusti_materialize_predicate" + | "prusti_contracts::prusti_quantified_predicate" => { + assert_eq!(args.len(), 1); + let mut encoded_args = extract_args(self.mir, args, block, self)?; + let ArgKind::String(predicate_spec_id) = encoded_args.pop().unwrap() else { + unreachable!() + }; + let predicate_expression = self + .specification_expressions + .get(&predicate_spec_id) + .expect("FIXME: A proper error message") + .clone(); + let predicate_expression = + self.resolve_lifetimes(predicate_expression)?; + assert!(encoded_args.is_empty()); + let vir_high::Expression::AccPredicate(acc_predicate) = predicate_expression else { + unimplemented!("FIXME: A proper error message") + }; + let predicate = *acc_predicate.predicate; + let check_that_exists = match full_called_function_name.as_ref() { + "prusti_contracts::prusti_materialize_predicate" => true, + "prusti_contracts::prusti_quantified_predicate" => false, + _ => unreachable!(), + }; + let statement = self.set_statement_error( + location, + ErrorCtxt::MaterializePredicate, + vir_high::Statement::materialize_predicate_no_pos( + predicate, + check_that_exists, + ), + )?; + encoded_statements.push(statement); + Ok(true) + } + "prusti_contracts::prusti_assume_allocation_never_fails" => { + assert!( + config::allow_assuming_allocation_never_fails(), + "TODO: A proper error message that allow_assuming_allocation_never_fails needs to be enabled." + ); + let assume = vir_high::Statement::assume_no_pos( + vir_high::Expression::builtin_func_app_no_pos( + vir_high::BuiltinFunc::AllocationNeverFails, + Vec::new(), + Vec::new(), + vir_high::Type::Bool, + ), + ); + let statement = self.set_statement_error( + location, + ErrorCtxt::UnexpectedAssumeAllocationNeverFails, + assume, + )?; + encoded_statements.push(statement); + Ok(true) + } + function_name => unreachable!("function: {}", function_name), + } + } else { + unreachable!(); + } + } + mir::TerminatorKind::SwitchInt { .. } | mir::TerminatorKind::Goto { .. } + if region_entry_block.is_some() => + { + // Ignored when encoding a specification region. + Ok(true) + } + _ => unreachable!("terminator {:?} at {:?} ", terminator_kind, bb), + } + } + + fn add_predecessor( + &mut self, + predecessor: mir::BasicBlock, + block: mir::BasicBlock, + ) -> SpannedEncodingResult<()> { + self.reachable_predecessors + .entry(block) + .or_default() + .insert(predecessor); + Ok(()) + } + + // fn is_pure(&self, def_id: DefId, substs: Option>) -> bool { + // self.encoder.is_pure(def_id, substs) + // // || { + // // // FIXME: We do this because extern specs do not support primitive + // // // types. + // // let func_name = self.encoder.env().name.get_unique_item_name(def_id); + // // func_name.starts_with("std::ptr::mut_ptr::::is_null") + // // || func_name.starts_with("core::std::ptr::mut_ptr::::is_null") + // // } + // } } diff --git a/prusti-viper/src/encoder/mir/procedures/encoder/postcondition_mode.rs b/prusti-viper/src/encoder/mir/procedures/encoder/postcondition_mode.rs new file mode 100644 index 00000000000..33088f624c5 --- /dev/null +++ b/prusti-viper/src/encoder/mir/procedures/encoder/postcondition_mode.rs @@ -0,0 +1,110 @@ +use vir_crate::common::check_mode::CheckMode; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(super) struct PostconditionMode { + is_unsafe_function: bool, + check_mode: CheckMode, + no_panic_ensures_postcondition: bool, + is_checked: bool, + panic_ensures: bool, + is_drop_implementation: bool, +} + +impl PostconditionMode { + pub(super) fn regular_exit_on_definition_side( + is_unsafe_function: bool, + check_mode: CheckMode, + no_panic_ensures_postcondition: bool, + is_drop_implementation: bool, + ) -> Self { + Self { + is_unsafe_function, + check_mode, + no_panic_ensures_postcondition, + panic_ensures: false, + is_drop_implementation, + is_checked: false, + } + } + + pub(super) fn regular_exit_on_call_side( + is_unsafe_function: bool, + check_mode: CheckMode, + no_panic_ensures_postcondition: bool, + is_checked: bool, + ) -> Self { + Self { + is_unsafe_function, + check_mode, + no_panic_ensures_postcondition, + panic_ensures: false, + is_drop_implementation: false, + is_checked, + } + } + + pub(super) fn panic_exit_on_definition_side( + is_unsafe_function: bool, + check_mode: CheckMode, + is_drop_implementation: bool, + ) -> Self { + Self { + is_unsafe_function, + check_mode, + no_panic_ensures_postcondition: false, + panic_ensures: true, + is_drop_implementation, + is_checked: false, + } + } + + pub(super) fn panic_exit_on_call_side(is_unsafe_function: bool, check_mode: CheckMode) -> Self { + Self { + is_unsafe_function, + check_mode, + no_panic_ensures_postcondition: false, + panic_ensures: true, + is_drop_implementation: false, + is_checked: false, + } + } + + pub(super) fn is_unsafe_function(&self) -> bool { + self.is_unsafe_function + } + + fn compute_include_functional_ensures(&self) -> bool { + (self.check_mode.check_specifications() + || self.no_panic_ensures_postcondition + || self.is_checked) + && !self.panic_ensures + } + + fn compute_include_panic_ensures(&self) -> bool { + self.panic_ensures + } + + pub(super) fn include_functional_ensures(&self) -> bool { + let functional = self.compute_include_functional_ensures(); + let panic = self.compute_include_panic_ensures(); + assert!( + !(functional && panic), + "Functional and panic postconditions are incompatible: {self:?}" + ); + functional + } + + pub(super) fn include_panic_ensures(&self) -> bool { + let functional = self.compute_include_functional_ensures(); + let panic = self.compute_include_panic_ensures(); + assert!( + !(functional && panic), + "Functional and panic postconditions are incompatible: {self:?}" + ); + panic + } + + pub(super) fn is_drop_implementation(&self) -> bool { + self.is_drop_implementation + } +} diff --git a/prusti-viper/src/encoder/mir/procedures/encoder/specification_blocks.rs b/prusti-viper/src/encoder/mir/procedures/encoder/specification_blocks.rs index c7912742909..34297d5ab36 100644 --- a/prusti-viper/src/encoder/mir/procedures/encoder/specification_blocks.rs +++ b/prusti-viper/src/encoder/mir/procedures/encoder/specification_blocks.rs @@ -1,9 +1,31 @@ -use prusti_interface::environment::{ - is_ghost_begin_marker, is_ghost_end_marker, is_loop_invariant_block, is_loop_variant_block, - is_marked_specification_block, EnvQuery, Procedure, +use prusti_interface::{ + environment::{ + debug_utils::to_text::ToText, is_checked_block_begin_marker, is_checked_block_end_marker, + is_ghost_begin_marker, is_ghost_end_marker, is_loop_invariant_block, is_loop_variant_block, + is_marked_specification_block, is_specification_begin_marker, is_specification_end_marker, + is_try_finally_begin_marker, is_try_finally_end_marker, EnvQuery, Procedure, + }, + specs::typed::SpecificationId, }; use prusti_rustc_interface::{data_structures::graph::WithSuccessors, middle::mir}; use std::collections::{BTreeMap, BTreeSet}; +use vir_crate::common::graphviz::{Graph, NodeBuilder}; + +struct SpecificationRegion { + body: BTreeSet, + exit_target_block: mir::BasicBlock, +} + +pub(super) struct TryFinallyRegion { + entry_block: mir::BasicBlock, + pub(super) regular_exit_target_block: mir::BasicBlock, + pub(super) panic_exit_edges: BTreeSet<(mir::BasicBlock, mir::BasicBlock)>, + pub(super) function_panic_exit_edges: BTreeSet<(mir::BasicBlock, mir::BasicBlock)>, + body: BTreeSet, + pub(super) on_panic_specification_region_id: SpecificationId, + pub(super) finally_at_panic_start_specification_region_id: SpecificationId, + pub(super) finally_at_resume_specification_region_id: SpecificationId, +} /// Information about the specification blocks. pub struct SpecificationBlocks { @@ -12,6 +34,15 @@ pub struct SpecificationBlocks { /// Blocks through which specifications are entered. specification_entry_blocks: BTreeSet, ghost_blocks: BTreeSet, + /// A region of specification blocks where key of the map indicates the + /// entry block. + specification_regions: BTreeMap, + specification_regions_by_spec_ids: BTreeMap, + /// `with_finally!` regions. + try_finally_regions: Vec, + /// The set of blocks in which we should check the preconditions of called + /// functions even in memory safety mode. + checked_blocks: BTreeSet, /// A set of blocks containing the loop invariant of a given loop in /// execution order. /// @@ -33,7 +64,7 @@ impl SpecificationBlocks { pub fn build<'tcx>( env_query: EnvQuery<'tcx>, body: &mir::Body<'tcx>, - procedure: &Procedure<'tcx>, + procedure: Option<&Procedure<'tcx>>, collect_loop_invariants: bool, ) -> Self { // Blocks that contain closures marked with `#[spec_only]` attributes. @@ -78,11 +109,13 @@ impl SpecificationBlocks { } // Collect loop invariant blocks. - let loop_info = procedure.loop_info(); - let predecessors = body.basic_blocks.predecessors(); let mut loop_invariant_blocks = BTreeMap::<_, LoopInvariantBlocks>::new(); let mut loop_spec_blocks_flat = BTreeSet::new(); if collect_loop_invariants { + let loop_info = procedure + .expect("procedure needs to be Some when collect_loop_invariants is true") + .loop_info(); + let predecessors = body.basic_blocks.predecessors(); // We use reverse_postorder here because we need to make sure that we // preserve the order of invariants in which they were specified by the // user. @@ -111,6 +144,78 @@ impl SpecificationBlocks { } } + // Collect `with_finally!` regions. + let mut try_finally_regions = Vec::new(); + { + for (bb, data) in mir::traversal::reverse_postorder(body) { + if let Some(( + on_panic_specification_region_id, + finally_at_panic_start_specification_region_id, + finally_at_resume_specification_region_id, + )) = is_try_finally_begin_marker(env_query, data) + { + let region = collect_try_finally_region( + env_query, + body, + bb, + on_panic_specification_region_id, + finally_at_panic_start_specification_region_id, + finally_at_resume_specification_region_id, + ); + try_finally_regions.push(region); + } + } + } + + let mut checked_blocks = BTreeSet::new(); + { + for (bb, data) in mir::traversal::reverse_postorder(body) { + if is_checked_block_begin_marker(env_query, data) { + collect_checked_region(env_query, body, bb, &mut checked_blocks); + } + } + } + + // Collect specification regions. Specification regions are all blocks + // that are reachable from a block containing + // `prusti_specification_begin` marker without going through the + // corresponding `prusti_specification_end` marker. + let mut all_specification_region_blocks = BTreeSet::new(); + let mut specification_regions = BTreeMap::new(); + let mut specification_regions_by_spec_ids = BTreeMap::new(); + { + for (bb, data) in mir::traversal::reverse_postorder(body) { + if let Some(spec_id) = is_specification_begin_marker(env_query, data) { + let (region, exit_target_block) = collect_specification_region( + env_query, + body, + bb, + &mut all_specification_region_blocks, + ); + specification_blocks.extend(region.iter().cloned()); + // Check that the `region` is a subset of `specification_blocks`. + assert!( + region.is_subset(&specification_blocks), + "{:?} ⊆ {:?}", + region, + specification_blocks + ); + assert!(specification_regions_by_spec_ids + .insert(spec_id, bb) + .is_none()); + assert!(specification_regions + .insert( + bb, + SpecificationRegion { + body: region, + exit_target_block + } + ) + .is_none()); + } + } + } + // Collect entry points. let mut specification_entry_blocks = BTreeSet::new(); for bb in body.basic_blocks.indices() { @@ -118,6 +223,7 @@ impl SpecificationBlocks { for successor in body.basic_blocks.successors(bb) { if specification_blocks.contains(&successor) && !loop_spec_blocks_flat.contains(&successor) + && !all_specification_region_blocks.contains(&successor) { specification_entry_blocks.insert(successor); } @@ -133,10 +239,14 @@ impl SpecificationBlocks { let mut queue = Vec::new(); for (bb, data) in mir::traversal::reverse_postorder(body) { - if is_ghost_begin_marker(env_query, data) { + if is_ghost_begin_marker(env_query, data) + && !all_specification_region_blocks.contains(&bb) + { queue.push(bb); } - if is_ghost_end_marker(env_query, data) { + if is_ghost_end_marker(env_query, data) + && !all_specification_region_blocks.contains(&bb) + { ghost_blocks.insert(bb); } } @@ -169,7 +279,11 @@ impl SpecificationBlocks { specification_blocks, specification_entry_blocks, loop_invariant_blocks, + specification_regions, + specification_regions_by_spec_ids, ghost_blocks, + try_finally_regions, + checked_blocks, } } @@ -177,7 +291,23 @@ impl SpecificationBlocks { self.specification_entry_blocks.iter().cloned() } - pub(super) fn is_specification_block(&self, bb: mir::BasicBlock) -> bool { + pub fn take_specification_regions( + &mut self, + ) -> impl Iterator)> { + std::mem::take(&mut self.specification_regions) + .into_iter() + .map( + |( + entry, + SpecificationRegion { + body: region, + exit_target_block: exit, + }, + )| (entry, exit, region), + ) + } + + pub fn is_specification_block(&self, bb: mir::BasicBlock) -> bool { self.specification_blocks.contains(&bb) } @@ -192,4 +322,305 @@ impl SpecificationBlocks { pub(super) fn ghost_blocks(&self) -> &BTreeSet { &self.ghost_blocks } + + pub(super) fn try_finally_regions(&self) -> &[TryFinallyRegion] { + &self.try_finally_regions + } + + pub(super) fn spec_id_to_entry_block(&self, spec_id: &SpecificationId) -> mir::BasicBlock { + self.specification_regions_by_spec_ids[spec_id] + } + + pub(super) fn is_checked(&self, bb: mir::BasicBlock) -> bool { + self.checked_blocks.contains(&bb) + } +} + +fn collect_try_finally_region( + env_query: EnvQuery, + body: &mir::Body, + entry_block: mir::BasicBlock, + on_panic_specification_region_id: SpecificationId, + finally_at_panic_start_specification_region_id: SpecificationId, + finally_at_resume_specification_region_id: SpecificationId, +) -> TryFinallyRegion { + let mut region = BTreeSet::new(); + let mut work_queue = vec![entry_block]; + let mut end_blocks = Vec::new(); + let mut panic_exit_edges = BTreeSet::new(); + let mut function_panic_exit_edges = BTreeSet::new(); + while let Some(bb) = work_queue.pop() { + if region.contains(&bb) { + continue; + } + region.insert(bb); + let data = &body.basic_blocks[bb]; + let before_end = data + .terminator() + .successors() + .any(|bb| is_try_finally_end_marker(env_query, &body.basic_blocks[bb])); + for succ in data.terminator().successors() { + let mut add_succ = || { + if before_end { + region.insert(succ); + end_blocks.push(succ); + } else { + work_queue.push(succ); + } + }; + if let Some(Some(unwind)) = data.terminator().unwind() { + if succ != *unwind { + add_succ(); + } else if let mir::TerminatorKind::Call { .. } = data.terminator().kind { + function_panic_exit_edges.insert((bb, succ)); + } else { + panic_exit_edges.insert((bb, succ)); + } + } else { + add_succ(); + } + } + } + let regular_exit_target_block = find_exit_target_block(body, end_blocks); + TryFinallyRegion { + entry_block, + regular_exit_target_block, + panic_exit_edges, + function_panic_exit_edges, + body: region, + on_panic_specification_region_id, + finally_at_panic_start_specification_region_id, + finally_at_resume_specification_region_id, + } +} + +fn collect_checked_region( + env_query: EnvQuery, + body: &mir::Body, + entry_block: mir::BasicBlock, + checked_blocks: &mut BTreeSet, +) { + let mut work_queue = vec![entry_block]; + while let Some(bb) = work_queue.pop() { + if checked_blocks.contains(&bb) { + continue; + } + checked_blocks.insert(bb); + let data = &body.basic_blocks[bb]; + let before_end = data + .terminator() + .successors() + .any(|bb| is_checked_block_end_marker(env_query, &body.basic_blocks[bb])); + for succ in data.terminator().successors() { + if before_end { + checked_blocks.insert(succ); + } else { + work_queue.push(succ); + } + } + } +} + +fn collect_specification_region( + env_query: EnvQuery, + body: &mir::Body, + entry_block: mir::BasicBlock, + all_specification_region_blocks: &mut BTreeSet, +) -> (BTreeSet, mir::BasicBlock) { + let mut region = BTreeSet::new(); + let mut work_queue = vec![entry_block]; + let mut end_blocks = Vec::new(); + while let Some(bb) = work_queue.pop() { + if region.contains(&bb) { + continue; + } + region.insert(bb); + all_specification_region_blocks.insert(bb); + let data = &body.basic_blocks[bb]; + let before_end = data + .terminator() + .successors() + .any(|bb| is_specification_end_marker(env_query, &body.basic_blocks[bb])); + for succ in data.terminator().successors() { + let mut add_succ = || { + if before_end { + region.insert(succ); + end_blocks.push(succ); + } else { + work_queue.push(succ); + } + }; + if let Some(Some(unwind)) = data.terminator().unwind() { + if succ != *unwind { + add_succ(); + } + } else { + add_succ(); + } + } + } + // let get_jump_target = |block| { + // let mut iterator = body.basic_blocks[block].terminator().successors(); + // let jump_target = iterator.next().unwrap(); + // assert!(iterator.next().is_none()); + // jump_target + // }; + // let exit_target_block = get_jump_target(end_blocks.pop().unwrap()); + // while let Some(end_block) = end_blocks.pop() { + // assert_eq!(exit_target_block, get_jump_target(end_block)); + // } + let exit_target_block = find_exit_target_block(body, end_blocks); + (region, exit_target_block) +} + +fn find_exit_target_block( + body: &mir::Body, + mut end_blocks: Vec, +) -> mir::BasicBlock { + let get_jump_target = |block| { + let mut iterator = body.basic_blocks[block].terminator().successors(); + let jump_target = iterator.next().unwrap(); + assert!(iterator.next().is_none()); + jump_target + }; + let exit_target_block = get_jump_target(end_blocks.pop().unwrap()); + while let Some(end_block) = end_blocks.pop() { + assert_eq!(exit_target_block, get_jump_target(end_block)); + } + exit_target_block +} + +pub(super) fn specification_blocks_to_graph( + body: &mir::Body, + specification_blocks: &SpecificationBlocks, +) -> Graph { + let mut graph = Graph::with_columns(&["location", "statement"]); + for bb in body.basic_blocks.indices() { + let style = if specification_blocks.is_specification_block(bb) { + "bgcolor=\"green\"" + } else { + "bgcolor=\"grey\"" + }; + let mut flags = String::new(); + if specification_blocks.specification_blocks.contains(&bb) { + flags.push_str("spec_block "); + } + if specification_blocks + .specification_entry_blocks + .contains(&bb) + { + flags.push_str("spec_entry_block "); + } + if specification_blocks.ghost_blocks.contains(&bb) { + flags.push_str("ghost_block "); + } + if specification_blocks.specification_regions.contains_key(&bb) { + flags.push_str("spec_region_entry "); + } + for (entry, region) in &specification_blocks.specification_regions { + if region.body.contains(&bb) { + flags.push_str(&format!( + "spec_region({} → {}) ", + entry.index(), + region.exit_target_block.index() + )); + } + if region.exit_target_block == bb { + flags.push_str(&format!("spec_region_exit({}) ", entry.index())); + } + } + for region in &specification_blocks.try_finally_regions { + if region.entry_block == bb { + flags.push_str(&format!( + "try_finally_region_entry({}, {}) ", + region.finally_at_panic_start_specification_region_id, + region.finally_at_resume_specification_region_id + )); + } + if region.body.contains(&bb) { + flags.push_str(&format!( + "try_finally_region({} → {}) ", + region.entry_block.index(), + region.regular_exit_target_block.index() + )); + } + if region.regular_exit_target_block == bb { + flags.push_str(&format!( + "try_finally_region_exit({}) ", + region.entry_block.index() + )); + } + // if region.finally_specification_region == bb { + // flags.push_str(&format!( + // "finally_block({} → {}) ", + // region.entry_block.index(), + // region.regular_exit_target_block.index() + // )); + // } + if region + .panic_exit_edges + .iter() + .any(|(_, exit_block)| exit_block == &bb) + { + flags.push_str(&format!( + "panic_exit_block({} → {}) ", + region.entry_block.index(), + region.regular_exit_target_block.index() + )); + } + } + let mut node_builder = graph.create_node_with_custom_style(bb.to_text(), style.to_string()); + node_builder.add_row_single(flags); + let mir::BasicBlockData { + statements, + terminator, + .. + } = &body[bb]; + let mut location = mir::Location { + block: bb, + statement_index: 0, + }; + let terminator_index = statements.len(); + while location.statement_index < terminator_index { + specification_blocks_to_graph_statement( + &mut node_builder, + location, + statements[location.statement_index].to_text(), + ); + location.statement_index += 1; + } + if let Some(terminator) = terminator { + specification_blocks_to_graph_statement( + &mut node_builder, + location, + terminator.to_text(), + ); + } + node_builder.build(); + if let Some(terminator) = terminator { + specification_blocks_to_graph_terminator(&mut graph, bb, terminator); + } + } + graph +} + +fn specification_blocks_to_graph_statement( + node_builder: &mut NodeBuilder, + location: mir::Location, + statement_text: String, +) { + let mut row_builder = node_builder.create_row(); + row_builder.set("location", location.to_text()); + row_builder.set("statement", statement_text); + row_builder.build(); +} + +fn specification_blocks_to_graph_terminator( + graph: &mut Graph, + bb: mir::BasicBlock, + terminator: &mir::Terminator<'_>, +) { + terminator + .successors() + .for_each(|succ| graph.add_regular_edge(bb.to_text(), succ.to_text())); } diff --git a/prusti-viper/src/encoder/mir/procedures/encoder/specification_regions.rs b/prusti-viper/src/encoder/mir/procedures/encoder/specification_regions.rs new file mode 100644 index 00000000000..60d49b607df --- /dev/null +++ b/prusti-viper/src/encoder/mir/procedures/encoder/specification_regions.rs @@ -0,0 +1,59 @@ +use crate::encoder::errors::SpannedEncodingResult; +use prusti_rustc_interface::middle::mir::{self}; +use rustc_hash::FxHashSet; + +// pub(super) struct SpecificationRegionEncoding { +// pub(super) exit_target_block: mir::BasicBlock, +// /// FIXME: We currently assume no branching in the specification region. +// pub(super) statements: Vec, +// } + +impl<'p, 'v: 'p, 'tcx: 'v> super::ProcedureEncoder<'p, 'v, 'tcx> { + pub(super) fn encode_specification_regions(&mut self) -> SpannedEncodingResult<()> { + for (entry_block, exit_target_block, region) in + self.specification_blocks.take_specification_regions() + { + // First, encode all specification expressions because they are sometimes used before they are declared. + let mut encoded_blocks = FxHashSet::default(); + for bb in ®ion { + let block = &self.mir[*bb]; + if self.try_encode_specification_expression(*bb, block)? { + encoded_blocks.insert(*bb); + } + } + + // Encode the remaining specification blocks. + let mut statements = Vec::new(); + for bb in ®ion { + if !encoded_blocks.contains(bb) { + self.encode_specification_block(*bb, &mut statements, Some(entry_block))?; + } + } + + // let encoding = SpecificationRegionEncoding { + // exit_target_block, + // statements, + // }; + assert!(self + .specification_region_encoding_statements + .insert(entry_block, statements,) + .is_none()); + assert!(self + .specification_region_exit_target_block + .insert(entry_block, exit_target_block,) + .is_none()); + for bb in ®ion { + for statement in &self.mir.basic_blocks[*bb].statements { + match statement.kind { + mir::StatementKind::StorageLive(local) + | mir::StatementKind::StorageDead(local) => { + self.locals_used_only_in_specification_regions.insert(local); + } + _ => {} + } + } + } + } + Ok(()) + } +} diff --git a/prusti-viper/src/encoder/mir/procedures/encoder/specifications.rs b/prusti-viper/src/encoder/mir/procedures/encoder/specifications.rs new file mode 100644 index 00000000000..9603e9da61c --- /dev/null +++ b/prusti-viper/src/encoder/mir/procedures/encoder/specifications.rs @@ -0,0 +1,195 @@ +use super::ProcedureEncoder; +use crate::{ + encoder::{ + errors::{SpannedEncodingError, SpannedEncodingResult}, + mir_encoder::PRECONDITION_LABEL, + Encoder, + }, + error_incorrect, +}; +use vir_crate::high::{ + self as vir_high, + operations::ty::Typed, + visitors::{default_fallible_fold_labelled_old, ExpressionFallibleFolder}, +}; + +impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { + pub(super) fn desugar_pledges_in_postcondition( + &mut self, + precondition_label: &str, + result: &vir_high::Expression, + expression: vir_high::Expression, + broken_invariant_places: &[vir_high::Expression], + ) -> SpannedEncodingResult { + let mut rewriter = Rewriter { + encoder: self.encoder, + precondition_label, + result: Some(result), + broken_invariant_places, + current_state: CurrentState::Postcondition, + }; + rewriter.fallible_fold_expression(expression) + } + + pub(super) fn desugar_pledges_in_asertion( + &mut self, + expression: vir_high::Expression, + ) -> SpannedEncodingResult { + let mut rewriter = Rewriter { + encoder: self.encoder, + precondition_label: PRECONDITION_LABEL, + result: None, + broken_invariant_places: &[], + current_state: CurrentState::Body, + }; + rewriter.fallible_fold_expression(expression) + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +enum CurrentState { + Precondition, + Body, + Postcondition, + AfterExpiry, + BeforeExpiry, +} + +struct Rewriter<'a, 'v, 'tcx> { + encoder: &'a mut Encoder<'v, 'tcx>, + precondition_label: &'a str, + result: Option<&'a vir_high::Expression>, + broken_invariant_places: &'a [vir_high::Expression], + current_state: CurrentState, +} + +impl<'a, 'v, 'tcx> ExpressionFallibleFolder for Rewriter<'a, 'v, 'tcx> { + type Error = SpannedEncodingError; + + fn fallible_fold_labelled_old_enum( + &mut self, + labelled_old: vir_high::LabelledOld, + ) -> Result { + let old_state = self.current_state; + if labelled_old.label == self.precondition_label { + self.current_state = CurrentState::Precondition; + } + let labelled_old = default_fallible_fold_labelled_old(self, labelled_old)?; + self.current_state = old_state; + // FIXME: The lower layers currently assume that old wraps only the + // locals. We should eventually fix it, but for now we just rewrite the + // most common patterns into the desires shape. + if let vir_high::Expression::AddrOf(vir_high::AddrOf { + base: + box vir_high::Expression::Deref(vir_high::Deref { + base: box vir_high::Expression::Local(local), + position: deref_position, + ty: deref_ty, + }), + position: addr_of_position, + ty: addr_of_ty, + }) = *labelled_old.base + { + Ok(vir_high::Expression::addr_of( + vir_high::Expression::deref( + vir_high::Expression::labelled_old( + labelled_old.label, + vir_high::Expression::Local(local), + labelled_old.position, + ), + deref_ty, + deref_position, + ), + addr_of_ty, + addr_of_position, + )) + } else { + Ok(vir_high::Expression::LabelledOld(labelled_old)) + } + } + + fn fallible_fold_builtin_func_app_enum( + &mut self, + mut builtin_func_app: vir_high::BuiltinFuncApp, + ) -> Result { + let old_state = self.current_state; + let expression = match builtin_func_app.function { + vir_high::BuiltinFunc::AfterExpiry => { + assert!(builtin_func_app.arguments.len() == 1); + self.current_state = CurrentState::AfterExpiry; + let expression = builtin_func_app.arguments.pop().unwrap(); + self.fallible_fold_expression(expression)? + } + vir_high::BuiltinFunc::BeforeExpiry => { + assert!(builtin_func_app.arguments.len() == 1); + self.current_state = CurrentState::BeforeExpiry; + let expression = builtin_func_app.arguments.pop().unwrap(); + self.fallible_fold_expression(expression)? + } + _ => vir_high::Expression::BuiltinFuncApp( + self.fallible_fold_builtin_func_app(builtin_func_app)?, + ), + }; + self.current_state = old_state; + Ok(expression) + } + + fn fallible_fold_deref_enum( + &mut self, + deref: vir_high::Deref, + ) -> Result { + let deref = self.fallible_fold_deref(deref)?; + let expression = if deref.base.get_type().is_unique_reference() { + match self.current_state { + CurrentState::Precondition => { + if self + .result + .map(|result| deref.base.has_prefix(result)) + .unwrap_or(false) + { + let span = self + .encoder + .error_manager() + .position_manager() + .get_span(deref.position.into()) + .unwrap() + .clone(); + error_incorrect!(span => "Function result cannot be dereferenced in precondition state"); + } else { + vir_high::Expression::Deref(deref) + } + } + CurrentState::Body => vir_high::Expression::Deref(deref), + CurrentState::Postcondition => { + if deref.base.has_prefix(self.result.unwrap()) + || self.broken_invariant_places.iter().any(|place| { + assert!(place.is_local(), "unimplemented"); + deref.base.get_base() == place.get_base() + }) + { + vir_high::Expression::Deref(deref) + } else { + vir_high::Expression::final_(*deref.base, deref.ty, deref.position) + } + } + CurrentState::AfterExpiry | CurrentState::BeforeExpiry => { + vir_high::Expression::final_(*deref.base, deref.ty, deref.position) + } + } + } else { + vir_high::Expression::Deref(deref) + }; + Ok(expression) + } + + fn fallible_fold_trigger( + &mut self, + mut trigger: vir_high::Trigger, + ) -> Result { + for term in std::mem::take(&mut trigger.terms) { + let term = self.fallible_fold_expression(term)?; + trigger.terms.push(term); + } + Ok(trigger) + } +} diff --git a/prusti-viper/src/encoder/mir/procedures/encoder/termination.rs b/prusti-viper/src/encoder/mir/procedures/encoder/termination.rs index 06376864ac8..0371e40a006 100644 --- a/prusti-viper/src/encoder/mir/procedures/encoder/termination.rs +++ b/prusti-viper/src/encoder/mir/procedures/encoder/termination.rs @@ -12,8 +12,11 @@ use prusti_rustc_interface::{ span::Span, }; use vir_crate::{ - common::{check_mode::CheckMode, expression::BinaryOperationHelpers}, - high::{self as vir_high, builders::procedure::BasicBlockBuilder}, + common::expression::BinaryOperationHelpers, + high::{ + self as vir_high, + builders::procedure::{BasicBlockBuilder, StatementSequenceBuilder}, + }, }; pub(super) enum TerminationMeasure { @@ -49,7 +52,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> super::ProcedureEncoder<'p, 'v, 'tcx> { })?; let expression = self.encoder.encode_assertion_high( - expr.to_def_id(), + expr, None, arguments, None, @@ -80,7 +83,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> super::ProcedureEncoder<'p, 'v, 'tcx> { arguments.push(self.encode_local(local)?.into()); } - if self.encoder.terminates(self.def_id, None) && self.check_mode != CheckMode::CoreProof { + if self.encoder.terminates(self.def_id, None) && self.check_mode.check_specifications() { let termination_expr = self.encode_termination_expression( &procedure_contract, mir_span, diff --git a/prusti-viper/src/encoder/mir/procedures/encoder/user_named_lifetimes.rs b/prusti-viper/src/encoder/mir/procedures/encoder/user_named_lifetimes.rs new file mode 100644 index 00000000000..6dd1abb1fca --- /dev/null +++ b/prusti-viper/src/encoder/mir/procedures/encoder/user_named_lifetimes.rs @@ -0,0 +1,68 @@ +use super::ProcedureEncoder; +use crate::encoder::errors::{SpannedEncodingError, SpannedEncodingResult}; +use std::collections::BTreeMap; +use vir_crate::high::{self as vir_high, visitors::ExpressionFallibleFolder}; + +impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { + pub(super) fn resolve_lifetimes( + &mut self, + expression: vir_high::Expression, + ) -> SpannedEncodingResult { + // FIXME: The resolving of BuildingUniqueRefPredicateWithRealLifetime + // is done in prusti-viper/src/encoder/mir/pure/pure_functions/cleaner.rs + let mut resolver = Resolver { + user_named_lifetimes: &self.user_named_lifetimes, + }; + resolver.fallible_fold_expression(expression) + } +} + +struct Resolver<'a> { + user_named_lifetimes: &'a BTreeMap, +} + +impl<'a> ExpressionFallibleFolder for Resolver<'a> { + type Error = SpannedEncodingError; + + fn fallible_fold_builtin_func_app_enum( + &mut self, + mut builtin_func_app: vir_high::BuiltinFuncApp, + ) -> Result { + let result = match builtin_func_app.function { + vir_high::BuiltinFunc::BuildingUniqueRefPredicate => { + let place = builtin_func_app.arguments.pop().unwrap(); + let lifetime = builtin_func_app.arguments.pop().unwrap(); + let vir_high::Expression::Constant(vir_high::Constant { + value: vir_high::expression::ConstantValue::String(lifetime), + .. + }) = lifetime else { + unreachable!("lifetime: {lifetime:?}") + }; + let lifetime = self.user_named_lifetimes.get(&lifetime).unwrap().clone(); + let position = builtin_func_app.position; + vir_high::Expression::acc_predicate( + vir_high::Predicate::unique_ref(lifetime, place, position), + position, + ) + } + vir_high::BuiltinFunc::BuildingFracRefPredicate => { + let place = builtin_func_app.arguments.pop().unwrap(); + let lifetime = builtin_func_app.arguments.pop().unwrap(); + let vir_high::Expression::Constant(vir_high::Constant { + value: vir_high::expression::ConstantValue::String(lifetime), + .. + }) = lifetime else { + unreachable!("lifetime: {lifetime:?}") + }; + let lifetime = self.user_named_lifetimes.get(&lifetime).unwrap().clone(); + let position = builtin_func_app.position; + vir_high::Expression::acc_predicate( + vir_high::Predicate::frac_ref(lifetime, place, position), + position, + ) + } + _ => vir_high::Expression::BuiltinFuncApp(builtin_func_app), + }; + Ok(result) + } +} diff --git a/prusti-viper/src/encoder/mir/procedures/encoder/utils.rs b/prusti-viper/src/encoder/mir/procedures/encoder/utils.rs new file mode 100644 index 00000000000..cf258aba7ca --- /dev/null +++ b/prusti-viper/src/encoder/mir/procedures/encoder/utils.rs @@ -0,0 +1,44 @@ +use prusti_rustc_interface::middle::{ + mir, + ty::{self, TyCtxt}, +}; + +pub(super) fn get_last_deref_with_lifetime<'tcx>( + tcx: TyCtxt<'tcx>, + body: &mir::Body<'tcx>, + place: mir::Place<'tcx>, + pointer_deref_lifetime: Option>, +) -> Option<(mir::PlaceRef<'tcx>, ty::Region<'tcx>)> { + let deref_reference = get_last_reference_deref(tcx, body, place); + if deref_reference.is_some() { + deref_reference + } else if let Some(pointer_deref_lifetime) = pointer_deref_lifetime { + place + .iter_projections() + .rev() + .filter(|(_, projection)| projection == &mir::ProjectionElem::Deref) + .last() + .map(|(place, _)| (place, pointer_deref_lifetime)) + } else { + None + } +} + +fn get_last_reference_deref<'tcx>( + tcx: TyCtxt<'tcx>, + body: &mir::Body<'tcx>, + place: mir::Place<'tcx>, +) -> Option<(mir::PlaceRef<'tcx>, ty::Region<'tcx>)> { + place + .iter_projections() + .rev() + .filter(|(_, projection)| projection == &mir::ProjectionElem::Deref) + .flat_map(|(place, _)| { + if let ty::TyKind::Ref(reference_region, _, _) = place.ty(body, tcx).ty.kind() { + Some((place, *reference_region)) + } else { + None + } + }) + .last() +} diff --git a/prusti-viper/src/encoder/mir/procedures/interface.rs b/prusti-viper/src/encoder/mir/procedures/interface.rs index 1904f372bca..7824bfefb43 100644 --- a/prusti-viper/src/encoder/mir/procedures/interface.rs +++ b/prusti-viper/src/encoder/mir/procedures/interface.rs @@ -1,7 +1,11 @@ use crate::encoder::{ errors::SpannedEncodingResult, - mir::{procedures::passes, spans::SpanInterface}, + mir::{ + procedures::{encoder::ProcedureEncodingKind, passes}, + spans::SpanInterface, + }, }; +use prusti_common::config; use prusti_rustc_interface::{hir::def_id::DefId, middle::mir, span::Span}; use rustc_hash::FxHashMap; use vir_crate::{common::check_mode::CheckMode, high as vir_high}; @@ -17,7 +21,7 @@ pub(crate) trait MirProcedureEncoderInterface<'tcx> { &mut self, proc_def_id: DefId, check_mode: CheckMode, - ) -> SpannedEncodingResult; + ) -> SpannedEncodingResult>; fn get_span_of_location(&self, mir: &mir::Body<'tcx>, location: mir::Location) -> Span; } @@ -27,17 +31,36 @@ impl<'v, 'tcx: 'v> MirProcedureEncoderInterface<'tcx> for super::super::super::E &mut self, proc_def_id: DefId, check_mode: CheckMode, - ) -> SpannedEncodingResult { - let procedure = super::encoder::encode_procedure(self, proc_def_id, check_mode)?; + ) -> SpannedEncodingResult> { + let procedure = super::encoder::encode_procedure( + self, + proc_def_id, + check_mode, + ProcedureEncodingKind::Regular, + )?; let procedure = passes::run_passes(self, procedure)?; + let mut procedures = Vec::new(); + if check_mode.check_core_proof() + && config::verify_postcondition_frame_check() + && !self.env().query.is_drop_method_impl(proc_def_id) + { + let postcondition_check = super::encoder::encode_procedure( + self, + proc_def_id, + check_mode, + ProcedureEncodingKind::PostconditionFrameCheck, + )?; + procedures.push(postcondition_check); + } assert!( self.mir_procedure_encoder_state .encoded_procedure_def_ids .insert(procedure.name.clone(), (proc_def_id, check_mode)) .is_none(), - "The procedure was encoed twice: {proc_def_id:?}" + "The procedure was encoded twice: {proc_def_id:?}" ); - Ok(procedure) + procedures.push(procedure); + Ok(procedures) } fn get_span_of_location(&self, mir: &mir::Body<'tcx>, location: mir::Location) -> Span { self.get_mir_location_span(mir, location) diff --git a/prusti-viper/src/encoder/mir/procedures/passes/assertions.rs b/prusti-viper/src/encoder/mir/procedures/passes/assertions.rs index d50a34c4e08..484ce99ee67 100644 --- a/prusti-viper/src/encoder/mir/procedures/passes/assertions.rs +++ b/prusti-viper/src/encoder/mir/procedures/passes/assertions.rs @@ -25,7 +25,7 @@ pub(in super::super) fn propagate_assertions_back<'v, 'tcx: 'v>( can_be_soundly_skipped = match &block.statements[statement_index] { vir_high::Statement::Comment(_) | vir_high::Statement::OldLabel(_) - | vir_high::Statement::Inhale(vir_high::Inhale { + | vir_high::Statement::InhalePredicate(vir_high::InhalePredicate { predicate: vir_high::Predicate::LifetimeToken(_) | vir_high::Predicate::MemoryBlockStack(_) @@ -34,10 +34,12 @@ pub(in super::super) fn propagate_assertions_back<'v, 'tcx: 'v>( | vir_high::Predicate::MemoryBlockHeapDrop(_), position: _, }) - | vir_high::Statement::Exhale(_) + | vir_high::Statement::ExhalePredicate(_) + | vir_high::Statement::ExhaleExpression(_) | vir_high::Statement::Consume(_) | vir_high::Statement::Havoc(_) | vir_high::Statement::GhostHavoc(_) + | vir_high::Statement::HeapHavoc(_) | vir_high::Statement::Assert(_) | vir_high::Statement::MovePlace(_) | vir_high::Statement::CopyPlace(_) @@ -49,6 +51,8 @@ pub(in super::super) fn propagate_assertions_back<'v, 'tcx: 'v>( | vir_high::Statement::SetUnionVariant(_) | vir_high::Statement::NewLft(_) | vir_high::Statement::EndLft(_) + | vir_high::Statement::DeadReference(_) + | vir_high::Statement::DeadReferenceRange(_) | vir_high::Statement::DeadLifetime(_) | vir_high::Statement::DeadInclusion(_) | vir_high::Statement::LifetimeTake(_) @@ -59,8 +63,26 @@ pub(in super::super) fn propagate_assertions_back<'v, 'tcx: 'v>( | vir_high::Statement::CloseMutRef(_) | vir_high::Statement::CloseFracRef(_) | vir_high::Statement::BorShorten(_) => true, - vir_high::Statement::Assume(_) | vir_high::Statement::Inhale(_) => false, + vir_high::Statement::Pack(_) + | vir_high::Statement::Unpack(_) + | vir_high::Statement::Obtain(_) + | vir_high::Statement::Join(_) + | vir_high::Statement::JoinRange(_) + | vir_high::Statement::Split(_) + | vir_high::Statement::SplitRange(_) + | vir_high::Statement::ForgetInitialization(_) + | vir_high::Statement::ForgetInitializationRange(_) + | vir_high::Statement::RestoreRawBorrowed(_) + | vir_high::Statement::RestoreMutBorrowed(_) + | vir_high::Statement::Assume(_) + | vir_high::Statement::InhalePredicate(_) + | vir_high::Statement::InhaleExpression(_) + | vir_high::Statement::StashRange(_) + | vir_high::Statement::StashRangeRestore(_) + | vir_high::Statement::MaterializePredicate(_) + | vir_high::Statement::CaseSplit(_) => false, vir_high::Statement::LoopInvariant(_) => unreachable!(), + vir_high::Statement::EncodingAction(_) => unreachable!(), }; } if statement_index == 0 { diff --git a/prusti-viper/src/encoder/mir/procedures/passes/loop_desugaring.rs b/prusti-viper/src/encoder/mir/procedures/passes/loop_desugaring.rs index 287de540068..2894da84de3 100644 --- a/prusti-viper/src/encoder/mir/procedures/passes/loop_desugaring.rs +++ b/prusti-viper/src/encoder/mir/procedures/passes/loop_desugaring.rs @@ -70,8 +70,9 @@ pub(in super::super) fn desugar_loops<'v, 'tcx: 'v>( "Loop Invariant Functional Specifications".to_string(), )); for assertion in &loop_invariant.functional_specifications { + let old_label = None; // We do not have `old` that would refer to a loop invariant. let statement = encoder.set_surrounding_error_context_for_statement( - vir_high::Statement::assert_no_pos(assertion.clone()), + vir_high::Statement::exhale_expression_no_pos(assertion.clone(), old_label), loop_invariant.position, ErrorCtxt::AssertLoopInvariantOnEntry, )?; @@ -121,8 +122,9 @@ pub(in super::super) fn desugar_loops<'v, 'tcx: 'v>( } for assertion in loop_invariant.functional_specifications { + let old_label = None; // We do not have `old` that would refer to a loop invariant. let statement = encoder.set_surrounding_error_context_for_statement( - vir_high::Statement::assume_no_pos(assertion), + vir_high::Statement::inhale_expression_no_pos(assertion, old_label), loop_invariant.position, ErrorCtxt::UnexpectedAssumeLoopInvariantOnEntry, )?; @@ -197,8 +199,9 @@ fn duplicate_blocks<'v, 'tcx: 'v>( if bb == invariant_block { let loop_invariant = block.statements.pop().unwrap().unwrap_loop_invariant(); for assertion in loop_invariant.functional_specifications { + let old_label = None; // We do not have `old` that would refer to a loop invariant. let statement = encoder.set_surrounding_error_context_for_statement( - vir_high::Statement::assert_no_pos(assertion), + vir_high::Statement::exhale_expression_no_pos(assertion, old_label), loop_invariant.position, ErrorCtxt::AssertLoopInvariantAfterIteration, )?; diff --git a/prusti-viper/src/encoder/mir/pure/interpreter/interpreter_high.rs b/prusti-viper/src/encoder/mir/pure/interpreter/interpreter_high.rs index 44f37bc2bdd..aa14f7d5ab5 100644 --- a/prusti-viper/src/encoder/mir/pure/interpreter/interpreter_high.rs +++ b/prusti-viper/src/encoder/mir/pure/interpreter/interpreter_high.rs @@ -17,6 +17,7 @@ use crate::encoder::{ casts::CastsEncoderInterface, generics::MirGenericsEncoderInterface, places::PlacesEncoderInterface, + procedures::encoder::specification_blocks::SpecificationBlocks, pure::{ interpreter::BackwardMirInterpreter, PureEncodingContext, PureFunctionEncoderInterface, SpecificationEncoderInterface, @@ -29,7 +30,7 @@ use crate::encoder::{ }; use log::{debug, trace}; use prusti_common::vir_high_local; -use prusti_interface::environment::mir_utils::SliceOrArrayRef; +use prusti_interface::environment::{debug_utils::to_text::ToText, mir_utils::SliceOrArrayRef}; use prusti_rustc_interface::{ hir::def_id::DefId, middle::{mir, ty, ty::subst::SubstsRef}, @@ -50,6 +51,9 @@ pub(in super::super) struct ExpressionBackwardInterpreter<'p, 'v: 'p, 'tcx: 'v> encoder: &'p Encoder<'v, 'tcx>, /// MIR of the pure function being encoded. mir: &'p mir::Body<'tcx>, + /// The specification blocks used in the pure function. When encoding + /// something else than a pure function, this is None. + specification_blocks: Option, /// MirEncoder of the pure function being encoded. mir_encoder: MirEncoder<'p, 'v, 'tcx>, /// How panics are handled depending on the encoding context. @@ -73,9 +77,20 @@ impl<'p, 'v: 'p, 'tcx: 'v> ExpressionBackwardInterpreter<'p, 'v, 'tcx> { caller_def_id: DefId, substs: SubstsRef<'tcx>, ) -> Self { + let specification_blocks = if pure_encoding_context == PureEncodingContext::Code { + Some(SpecificationBlocks::build( + encoder.env().query, + mir, + None, + false, + )) + } else { + None + }; Self { encoder, mir, + specification_blocks, mir_encoder: MirEncoder::new(encoder, mir, def_id), pure_encoding_context, caller_def_id, @@ -122,6 +137,30 @@ impl<'p, 'v: 'p, 'tcx: 'v> ExpressionBackwardInterpreter<'p, 'v, 'tcx> { arguments.push(encoded_operand); } match aggregate { + mir::AggregateKind::Closure(def_id, substs) + if self.encoder.is_spec_closure(*def_id) => + { + let cl_substs = substs.as_closure(); + let position = lhs.position(); + for (field_index, field_ty) in cl_substs.upvar_tys().enumerate() { + let operand = &operands[field_index]; + let encoded_operand = self.encode_operand(operand, span)?; + let field_name = format!("closure_{field_index}"); + let encoded_field_type = self.encoder.encode_type_high(field_ty)?; + let field_decl = + vir_high::FieldDecl::new(field_name, field_index, encoded_field_type); + // Note: We are using `lhs`, which is the closure variable + // outside of the closure as it was inside the closure. This + // sometimes works because we are not checking that `lhs` + // type is `Closure` not, `&Closure`. However, we need to try both + // `_1.closure_0` and `_1.*.closure_0` as substitution targets. + let closure_self_deref = lhs.clone().deref(ty.clone(), position); + let field_place = closure_self_deref.field(field_decl.clone(), position); + state.substitute_value(&field_place, encoded_operand.clone()); + let field_place = lhs.clone().field(field_decl, position); + state.substitute_value(&field_place, encoded_operand); + } + } mir::AggregateKind::Array(_) | mir::AggregateKind::Tuple | mir::AggregateKind::Closure(_, _) => { @@ -161,7 +200,13 @@ impl<'p, 'v: 'p, 'tcx: 'v> ExpressionBackwardInterpreter<'p, 'v, 'tcx> { rhs: &mir::Rvalue<'tcx>, span: Span, ) -> SpannedEncodingResult<()> { - let encoded_lhs = self.encode_place(lhs)?.erase_lifetime(); + let encoded_lhs = self.encode_place(lhs)?; + // Our encoding for field assignments is unsound, so just disable them + // for now. FIXME: Have a proper error message. + assert!( + encoded_lhs.is_local(), + "Currently only local variables as assignment targets are supported" + ); let ty = self .encoder .encode_type_of_place_high(self.mir, lhs) @@ -239,7 +284,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ExpressionBackwardInterpreter<'p, 'v, 'tcx> { .with_span(span)?; state.substitute_value(&encoded_lhs, expr); } - &mir::Rvalue::Ref(_, kind, place) => { + &mir::Rvalue::Ref(region, kind, place) => { if !matches!( kind, mir::BorrowKind::Unique | mir::BorrowKind::Mut { .. } | mir::BorrowKind::Shared @@ -254,7 +299,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ExpressionBackwardInterpreter<'p, 'v, 'tcx> { .encoder .encode_type_of_place_high(self.mir, place) .with_span(span)?; - let pure_lifetime = vir_high::ty::LifetimeConst::erased(); + let pure_lifetime = vir_high::ty::LifetimeConst::new(region.to_text()); let uniqueness = if matches!(kind, mir::BorrowKind::Mut { .. }) { vir_high::ty::Uniqueness::Unique } else { @@ -298,6 +343,30 @@ impl<'p, 'v: 'p, 'tcx: 'v> ExpressionBackwardInterpreter<'p, 'v, 'tcx> { )); } } + mir::Rvalue::Cast( + mir::CastKind::Pointer(ty::adjustment::PointerCast::MutToConstPointer), + operand, + _cast_ty, + ) => { + let arg = self.encode_operand(operand, span)?; + let expr = vir_high::Expression::builtin_func_app_no_pos( + vir_high::BuiltinFunc::CastMutToConstPointer, + Vec::new(), + vec![arg], + encoded_lhs.get_type().clone(), + ); + state.substitute_value(&encoded_lhs, expr); + } + mir::Rvalue::Cast(mir::CastKind::PtrToPtr, operand, _cast_ty) => { + let arg = self.encode_operand(operand, span)?; + let expr = vir_high::Expression::builtin_func_app_no_pos( + vir_high::BuiltinFunc::CastPtrToPtr, + Vec::new(), + vec![arg], + encoded_lhs.get_type().clone(), + ); + state.substitute_value(&encoded_lhs, expr); + } mir::Rvalue::Cast(kind, _, _) => { return Err(SpannedEncodingError::unsupported( format!("unsupported kind of cast: {kind:?}"), @@ -323,8 +392,19 @@ impl<'p, 'v: 'p, 'tcx: 'v> ExpressionBackwardInterpreter<'p, 'v, 'tcx> { let expr = vir_high::Expression::constructor_no_pos(ty, arguments); state.substitute_value(&encoded_lhs, expr); } + mir::Rvalue::AddressOf(_, place) => { + let encoded_place = self.encoder.encode_place_high(self.mir, *place, None)?; + let ty = self + .encoder + .encode_type_of_place_high(self.mir, *place) + .with_span(span)?; + let expr = vir_high::Expression::addr_of_no_pos( + encoded_place, + vir_high::Type::pointer(ty), + ); + state.substitute_value(&encoded_lhs, expr); + } mir::Rvalue::ThreadLocalRef(..) - | mir::Rvalue::AddressOf(..) | mir::Rvalue::ShallowInitBox(..) | mir::Rvalue::NullaryOp(..) => { return Err(SpannedEncodingError::unsupported( @@ -583,11 +663,29 @@ impl<'p, 'v: 'p, 'tcx: 'v> ExpressionBackwardInterpreter<'p, 'v, 'tcx> { }); } else if let Some(proc_name) = proc_name.strip_prefix("prusti_contracts::Int::") { assert!(type_arguments.is_empty()); - return match proc_name { - "new" => builtin((NewInt, Type::Int(Int::Unbounded))), - "new_usize" => builtin((NewInt, Type::Int(Int::Unbounded))), + match proc_name { + "new" => { + return builtin((NewInt, Type::Int(Int::Unbounded))); + } + "new_usize" => { + return builtin((NewInt, Type::Int(Int::Unbounded))); + } + "new_isize" => { + return builtin((NewInt, Type::Int(Int::Unbounded))); + } + _ => {} + }; + let (source_type, destination_type) = match proc_name { + "to_usize" => (Type::Int(Int::Unbounded), Type::Int(Int::Usize)), + "to_isize" => (Type::Int(Int::Unbounded), Type::Int(Int::Isize)), _ => unreachable!("no further int functions"), }; + return subst_with(vir_high::Expression::builtin_func_app_no_pos( + vir_high::BuiltinFunc::CastIntToInt, + vec![source_type, destination_type.clone()], + encoded_args.into(), + destination_type, + )); } else if let Some(proc_name) = proc_name.strip_prefix("prusti_contracts::Ghost::::") { return match proc_name { "new" => subst_with(encoded_args[0].clone()), @@ -649,6 +747,208 @@ impl<'p, 'v: 'p, 'tcx: 'v> ExpressionBackwardInterpreter<'p, 'v, 'tcx> { } match proc_name { + "prusti_contracts::prusti_own" => { + assert_eq!(encoded_args.len(), 1); + let place = encoded_args[0].clone(); + let position = place.position(); + let encoded_rhs = vir_high::Expression::acc_predicate( + vir_high::Predicate::owned_non_aliased(place, position), + position, + ); + subst_with(encoded_rhs) + } + "prusti_contracts::prusti_own_range" => { + assert_eq!(encoded_args.len(), 3); + let address = encoded_args[0].clone(); + let start = encoded_args[1].clone(); + let end = encoded_args[2].clone(); + let position = address.position(); + let encoded_rhs = vir_high::Expression::acc_predicate( + vir_high::Predicate::owned_range(address, start, end, position), + position, + ); + subst_with(encoded_rhs) + } + "prusti_contracts::prusti_deref_own" => { + assert_eq!(encoded_args.len(), 2); + let ref_type = encoded_lhs.get_type().clone(); + builtin((DerefOwn, ref_type)) + } + "prusti_contracts::prusti_raw" => { + assert_eq!(encoded_args.len(), 2); + let address = encoded_args[0].clone(); + let size = encoded_args[1].clone(); + let position = address.position(); + let encoded_rhs = vir_high::Expression::acc_predicate( + vir_high::Predicate::memory_block_heap(address, size, position), + position, + ); + subst_with(encoded_rhs) + } + "prusti_contracts::prusti_raw_range" => { + assert_eq!(encoded_args.len(), 4); + let address = encoded_args[0].clone(); + let size = encoded_args[1].clone(); + let start = encoded_args[2].clone(); + let end = encoded_args[3].clone(); + let position = address.position(); + let encoded_rhs = vir_high::Expression::acc_predicate( + vir_high::Predicate::memory_block_heap_range( + address, size, start, end, position, + ), + position, + ); + subst_with(encoded_rhs) + } + "prusti_contracts::prusti_raw_range_guarded" => { + assert_eq!(encoded_args.len(), 4); + let address = encoded_args[0].clone(); + let size = encoded_args[1].clone(); + let vir_high::Expression::Quantifier(quantifier) = self.encoder.encode_prusti_operation_high( + proc_name, + span, + encoded_args.to_vec(), + self.caller_def_id, + substs, + )? else { + unreachable!(); + }; + assert_eq!( + quantifier.kind, + vir_high::expression::QuantifierKind::ForAll + ); + assert_eq!(quantifier.variables.len(), 1); + let index_variable = quantifier.variables[0].clone(); + let position = address.position(); + let encoded_rhs = vir_high::Expression::acc_predicate( + vir_high::Predicate::memory_block_heap_range_guarded( + address, + size, + index_variable, + *quantifier.body, + quantifier.triggers, + position, + ), + position, + ); + subst_with(encoded_rhs) + } + "prusti_contracts::prusti_raw_dealloc" => { + assert_eq!(encoded_args.len(), 2); + let address = encoded_args[0].clone(); + let size = encoded_args[1].clone(); + let position = address.position(); + let encoded_rhs = vir_high::Expression::acc_predicate( + vir_high::Predicate::memory_block_heap_drop(address, size, position), + position, + ); + subst_with(encoded_rhs) + } + "prusti_contracts::prusti_unq" => { + assert_eq!(encoded_args.len(), 2); + let lifetime = encoded_args[0].clone(); + let place = encoded_args[1].clone(); + let position = place.position(); + let encoded_rhs = vir_high::Expression::builtin_func_app( + vir_high::BuiltinFunc::BuildingUniqueRefPredicate, + Vec::new(), + vec![lifetime, place], + vir_high::Type::Bool, + position, + ); + subst_with(encoded_rhs) + } + "prusti_contracts::prusti_unq_real_lifetime" => { + assert_eq!(encoded_args.len(), 2); + let lifetime = encoded_args[0].clone(); + let place = encoded_args[1].clone(); + let position = place.position(); + let encoded_rhs = vir_high::Expression::builtin_func_app( + vir_high::BuiltinFunc::BuildingUniqueRefPredicateWithRealLifetime, + Vec::new(), + vec![lifetime, place], + vir_high::Type::Bool, + position, + ); + subst_with(encoded_rhs) + } + "prusti_contracts::prusti_unq_real_lifetime_range" => { + assert_eq!(encoded_args.len(), 4); + let lifetime = encoded_args[0].clone(); + let address = encoded_args[1].clone(); + let start = encoded_args[2].clone(); + let end = encoded_args[3].clone(); + let position = address.position(); + let encoded_rhs = vir_high::Expression::builtin_func_app( + vir_high::BuiltinFunc::BuildingUniqueRefPredicateRangeWithRealLifetime, + Vec::new(), + vec![lifetime, address, start, end], + vir_high::Type::Bool, + position, + ); + subst_with(encoded_rhs) + } + "prusti_contracts::prusti_shr" => { + assert_eq!(encoded_args.len(), 2); + let lifetime = encoded_args[0].clone(); + let place = encoded_args[1].clone(); + let position = place.position(); + let encoded_rhs = vir_high::Expression::builtin_func_app( + vir_high::BuiltinFunc::BuildingFracRefPredicate, + Vec::new(), + vec![lifetime, place], + vir_high::Type::Bool, + position, + ); + subst_with(encoded_rhs) + } + "prusti_contracts::prusti_bytes" => { + assert_eq!(encoded_args.len(), 2); + builtin((MemoryBlockBytes, vir_high::Type::MBytes)) + } + "prusti_contracts::prusti_bytes_ptr" => { + assert_eq!(encoded_args.len(), 2); + builtin((MemoryBlockBytesPtr, vir_high::Type::MBytes)) + } + "prusti_contracts::read_byte" => { + assert_eq!(encoded_args.len(), 2); + builtin((ReadByte, vir_high::Type::MByte)) + } + "prusti_contracts::address_offset" | "prusti_contracts::address_offset_mut" => { + assert_eq!(encoded_args.len(), 2); + builtin((PtrAddressOffset, encoded_args[0].get_type().clone())) + } + "prusti_contracts::address_from" => { + assert_eq!(encoded_args.len(), 2); + builtin(( + PtrAddressOffsetFrom, + vir_high::Type::Int(vir_high::ty::Int::Unbounded), + )) + } + "prusti_contracts::same_allocation" => { + assert_eq!(encoded_args.len(), 2); + builtin((PtrSameAllocation, vir_high::Type::Bool)) + } + "prusti_contracts::fresh_allocation" => { + assert_eq!(encoded_args.len(), 1); + builtin((PtrFreshAllocation, vir_high::Type::Bool)) + } + "prusti_contracts::range_contains" => { + assert_eq!(encoded_args.len(), 3); + builtin((PtrRangeContains, vir_high::Type::Bool)) + } + "prusti_contracts::prusti_unpacking" => { + assert_eq!(encoded_args.len(), 2); + let place = encoded_args[0].clone(); + let body = encoded_args[1].clone(); + let position = place.position(); + let encoded_rhs = vir_high::Expression::unfolding( + vir_high::Predicate::owned_non_aliased(place, position), + body, + position, + ); + subst_with(encoded_rhs) + } "prusti_contracts::old" => { let argument = encoded_args.last().cloned().unwrap(); let position = argument.position(); @@ -659,6 +959,36 @@ impl<'p, 'v: 'p, 'tcx: 'v> ExpressionBackwardInterpreter<'p, 'v, 'tcx> { ); subst_with(encoded_rhs) } + "prusti_contracts::prusti_old_local" => { + assert_eq!(encoded_args.len(), 1); + let argument = encoded_args.last().cloned().unwrap(); + let position = argument.position(); + let vir_high::Type::Reference(reference_type) = argument.get_type() else { + unreachable!("Expected a reference type; got: {:?}", argument.get_type()); + }; + let target_type = (*reference_type.target_type).clone(); + let deref = argument.deref(target_type, position); + let encoded_rhs = vir_high::Expression::labelled_old( + PRECONDITION_LABEL.to_string(), + deref, + position, + ); + subst_with(encoded_rhs) + } + "prusti_contracts::prusti_eval_in" | "prusti_contracts::prusti_eval_in_quantified" => { + assert_eq!(encoded_args.len(), 2); + let predicate = encoded_args[0].clone(); + let argument = encoded_args[1].clone(); + let position = argument.position(); + let context_kind = if proc_name == "prusti_contracts::prusti_eval_in_quantified" { + vir_high::EvalInContextKind::QuantifiedPredicate + } else { + vir_high::EvalInContextKind::Predicate + }; + let encoded_rhs = + vir_high::Expression::eval_in(predicate, context_kind, argument, position); + subst_with(encoded_rhs) + } "prusti_contracts::snapshot_equality" => { let position = encoded_args[0].position(); let encoded_rhs = vir_high::Expression::builtin_func_app( @@ -671,8 +1001,35 @@ impl<'p, 'v: 'p, 'tcx: 'v> ExpressionBackwardInterpreter<'p, 'v, 'tcx> { subst_with(encoded_rhs) } "prusti_contracts::before_expiry" => { - // self.encode_call_before_expiry()? - unimplemented!(); + assert_eq!(encoded_args.len(), 1); + let position = encoded_args[0].position(); + let ty = encoded_args[0].get_type().clone(); + let encoded_rhs = vir_high::Expression::builtin_func_app( + vir_high::BuiltinFunc::BeforeExpiry, + Vec::new(), + encoded_args.into(), + ty, + position, + ); + subst_with(encoded_rhs) + } + "prusti_contracts::multiply_int" => { + assert_eq!(encoded_args.len(), 2); + subst_with(vir_high::Expression::builtin_func_app_no_pos( + Multiply, + vec![Type::Int(vir_high::ty::Int::Unbounded)], + encoded_args.into(), + Type::Int(vir_high::ty::Int::Unbounded), + )) + } + "prusti_contracts::multiply_usize" => { + assert_eq!(encoded_args.len(), 2); + subst_with(vir_high::Expression::builtin_func_app_no_pos( + Multiply, + vec![Type::Int(vir_high::ty::Int::Usize)], + encoded_args.into(), + Type::Int(vir_high::ty::Int::Usize), + )) } "std::cmp::PartialEq::eq" | "core::cmp::PartialEq::eq" if self.has_structural_eq_impl(&args[0]).with_span(span)? => @@ -717,11 +1074,19 @@ impl<'p, 'v: 'p, 'tcx: 'v> ExpressionBackwardInterpreter<'p, 'v, 'tcx> { builtin((LookupMap, ref_type)) } Type::Reference(Reference { - target_type: box Type::Sequence(_), + target_type: + box Type::Sequence(vir_high::ty::Sequence { + box element_type, .. + }), .. }) => { let ref_type = encoded_lhs.get_type().clone(); - builtin((LookupSeq, ref_type)) + subst_with(vir_high::Expression::builtin_func_app_no_pos( + LookupSeq, + vec![element_type.clone()], + encoded_args.into(), + ref_type, + )) } _ => self .encode_call_index( @@ -735,6 +1100,63 @@ impl<'p, 'v: 'p, 'tcx: 'v> ExpressionBackwardInterpreter<'p, 'v, 'tcx> { .map(Some), } } + "std::ptr::const_ptr::::is_null" + | "std::ptr::mut_ptr::::is_null" => { + assert_eq!(encoded_args.len(), 1); + builtin((PtrIsNull, vir_high::Type::Bool)) + } + "std::ptr::const_ptr::::offset" + | "std::ptr::mut_ptr::::offset" => { + assert_eq!(encoded_args.len(), 2); + builtin((PtrOffset, encoded_args[0].get_type().clone())) + } + "std::ptr::const_ptr::::wrapping_offset" + | "std::ptr::mut_ptr::::wrapping_offset" => { + assert_eq!(encoded_args.len(), 2); + builtin((PtrWrappingOffset, encoded_args[0].get_type().clone())) + } + "std::ptr::const_ptr::::add" + | "std::ptr::mut_ptr::::add" => { + assert_eq!(encoded_args.len(), 2); + builtin((PtrAdd, encoded_args[0].get_type().clone())) + } + "std::mem::size_of" => { + assert_eq!(encoded_args.len(), 0); + assert_eq!(type_arguments.len(), 1); + let size_ty = vir_high::Type::Int(vir_high::ty::Int::Usize); + // match &type_arguments[0] { + // vir_high::Type::Int(ty) + // if !matches!( + // ty, + // vir_high::ty::Int::Isize + // | vir_high::ty::Int::Usize + // | vir_high::ty::Int::Char + // ) => + // { + // let size = match ty { + // vir_high::ty::Int::I8 => 1, + // vir_high::ty::Int::I16 => 2, + // vir_high::ty::Int::I32 => 4, + // vir_high::ty::Int::I64 => 8, + // vir_high::ty::Int::I128 => 16, + // vir_high::ty::Int::U8 => 1, + // vir_high::ty::Int::U16 => 2, + // vir_high::ty::Int::U32 => 4, + // vir_high::ty::Int::U64 => 8, + // vir_high::ty::Int::U128 => 16, + // _ => unreachable!(), + // }; + // let value = vir_high::Expression::constant_no_pos(size.into(), size_ty); + // subst_with(value) + // } + // _ => builtin((Size, size_ty)), + // } + builtin((Size, size_ty)) + } + "std::mem::align_of" => { + assert_eq!(encoded_args.len(), 0); + builtin((Align, vir_high::Type::Int(vir_high::ty::Int::Usize))) + } // Prusti-specific syntax // TODO: check we are in a spec function @@ -751,6 +1173,9 @@ impl<'p, 'v: 'p, 'tcx: 'v> ExpressionBackwardInterpreter<'p, 'v, 'tcx> { )?; subst_with(expr) } + "prusti_contracts::allocation_never_fails" => { + builtin((AllocationNeverFails, vir_high::Type::Bool)) + } _ => Ok(None), } } @@ -871,7 +1296,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> BackwardMirInterpreter<'tcx> #[tracing::instrument(level = "debug", skip(self, states))] fn apply_terminator( &self, - _bb: mir::BasicBlock, + bb: mir::BasicBlock, terminator: &mir::Terminator<'tcx>, states: FxHashMap, ) -> Result { @@ -947,6 +1372,18 @@ impl<'p, 'v: 'p, 'tcx: 'v> BackwardMirInterpreter<'tcx> func: mir::Operand::Constant(box mir::Constant { literal, .. }), .. } => { + if self + .specification_blocks + .as_ref() + .map(|sb| sb.is_specification_block(bb)) + .unwrap_or(false) + { + if let Some(target) = target { + return Ok(states[target].clone()); + } else { + unimplemented!(); + } + } self.apply_call_terminator(args, *destination, target, literal.ty(), states, span)? } @@ -1044,6 +1481,15 @@ impl<'p, 'v: 'p, 'tcx: 'v> BackwardMirInterpreter<'tcx> statement: &mir::Statement<'tcx>, state: &mut Self::State, ) -> Result<(), Self::Error> { + if self + .specification_blocks + .as_ref() + .map(|sb| sb.is_specification_block(bb)) + .unwrap_or(false) + { + trace!("Skipping statement because inside a specification block"); + return Ok(()); + } let span = statement.source_info.span; let location = mir::Location { block: bb, diff --git a/prusti-viper/src/encoder/mir/pure/interpreter/state_high.rs b/prusti-viper/src/encoder/mir/pure/interpreter/state_high.rs index d5891166365..292c58e4fd2 100644 --- a/prusti-viper/src/encoder/mir/pure/interpreter/state_high.rs +++ b/prusti-viper/src/encoder/mir/pure/interpreter/state_high.rs @@ -62,15 +62,16 @@ impl ExprBackwardInterpreterState { target: &vir_high::Expression, replacement: vir_high::Expression, ) { - let mut target = target.clone().substitute_types(&self.substs); - let mut replacement = replacement.substitute_types(&self.substs); + let target = target.clone().substitute_types(&self.substs); + let replacement = replacement.substitute_types(&self.substs); if let Some(curr_expr) = self.expr.as_mut() { // Replace two times to avoid cloning `expr`, which could be big. let expr = mem::replace(curr_expr, true.into()); - target = target.erase_lifetime(); - replacement = replacement.erase_lifetime(); - let mut new_expr = expr.replace_place(&target, &replacement); //.simplify_addr_of(); + // target = target.erase_lifetime(); + // replacement = replacement.erase_lifetime(); + let new_expr = expr.replace_place(&target, &replacement); //.simplify_addr_of(); + let mut new_expr = new_expr.simplify_out_constructors(); mem::swap(curr_expr, &mut new_expr); } } diff --git a/prusti-viper/src/encoder/mir/pure/pure_functions/cleaner.rs b/prusti-viper/src/encoder/mir/pure/pure_functions/cleaner.rs new file mode 100644 index 00000000000..8c34b45bec1 --- /dev/null +++ b/prusti-viper/src/encoder/mir/pure/pure_functions/cleaner.rs @@ -0,0 +1,383 @@ +use crate::encoder::{ + errors::{SpannedEncodingError, SpannedEncodingResult}, + Encoder, +}; +use prusti_interface::{data::ProcedureDefId, environment::debug_utils::to_text::ToText}; +use prusti_rustc_interface::{middle::ty, span::Span}; +use std::collections::BTreeMap; +use vir_crate::{ + common::{expression::SyntacticEvaluation, position::Positioned}, + high::{ + self as vir_high, + operations::ty::Typed, + visitors::{ + default_fallible_fold_acc_predicate, default_fallible_fold_binary_op, + default_fallible_fold_unfolding, ExpressionFallibleFolder, + }, + }, +}; + +/// When encoding an assertion we sometimes get strange artefacts as a result of +/// using procedural macros. This functions removes them. +pub(super) fn clean_encoding_result<'p, 'v: 'p, 'tcx: 'v>( + encoder: &'p Encoder<'v, 'tcx>, + expression: vir_high::Expression, + proc_def_id: ProcedureDefId, + substs: ty::SubstsRef<'tcx>, + span: Span, +) -> SpannedEncodingResult { + let _position = expression.position(); + let lifetime_remap = construct_lifetime_remap(encoder, proc_def_id, substs)?; + let mut cleaner = Cleaner { + encoder, + span, + lifetime_remap, + }; + + let expression = cleaner.fallible_fold_expression(expression)?; + let expression = expression.simplify(); + check_permission_always_positive(proc_def_id, &expression)?; + + Ok(expression) +} + +fn construct_lifetime_remap<'p, 'v: 'p, 'tcx: 'v>( + encoder: &'p Encoder<'v, 'tcx>, + proc_def_id: ProcedureDefId, + substs: ty::SubstsRef<'tcx>, +) -> SpannedEncodingResult> { + let identity_substs = encoder.env().query.identity_substs(proc_def_id); + let mut lifetime_remap = BTreeMap::new(); + for (identity_arg, arg) in identity_substs.iter().zip(substs.iter()) { + match identity_arg.unpack() { + ty::subst::GenericArgKind::Lifetime(lifetime) => match *lifetime { + ty::RegionKind::ReEarlyBound(data) => { + let ty::subst::GenericArgKind::Lifetime(replacement_lifetime) = arg.unpack() else { + unreachable!(); + }; + lifetime_remap.insert( + data.name.to_string(), + vir_high::ty::LifetimeConst { + name: replacement_lifetime.to_text(), + }, + ); + } + ty::RegionKind::ReLateBound(_, _) => todo!(), + ty::RegionKind::ReFree(_) => todo!(), + ty::RegionKind::ReStatic => todo!(), + ty::RegionKind::ReVar(_) => todo!(), + ty::RegionKind::RePlaceholder(_) => todo!(), + ty::RegionKind::ReErased => todo!(), + ty::RegionKind::ReError(_) => todo!(), + }, + _ => {} + } + } + Ok(lifetime_remap) +} + +struct Cleaner<'p, 'v: 'p, 'tcx: 'v> { + encoder: &'p Encoder<'v, 'tcx>, + lifetime_remap: BTreeMap, + span: Span, +} + +impl<'p, 'v: 'p, 'tcx: 'v> Cleaner<'p, 'v, 'tcx> { + fn lifetime_name_to_lifetime_const( + &self, + lifetime: vir_high::Expression, + ) -> vir_high::ty::LifetimeConst { + let vir_high::Expression::Constant(vir_high::Constant { + value: vir_high::expression::ConstantValue::String(lifetime), + .. + }) = lifetime else { + unreachable!("lifetime: {lifetime:?}") + }; + self.lifetime_remap.get(&lifetime).unwrap().clone() + } +} + +fn peel_addr_of(place: vir_high::Expression) -> vir_high::Expression { + match place { + vir_high::Expression::AddrOf(vir_high::AddrOf { base, .. }) => *base, + _ => { + unreachable!("must be addr_of: {}", place) + } + } +} + +fn clean_acc_predicate(predicate: vir_high::Predicate) -> vir_high::Predicate { + match predicate { + vir_high::Predicate::OwnedNonAliased(mut predicate) => { + // FIXME: Rename OwnedNonAliased to Owned. + predicate.place = peel_addr_of(predicate.place); + if !predicate.place.is_behind_pointer_dereference() { + // FIXME: A proper error message + unimplemented!("Must be behind pointer dereference: {}", predicate.place) + } + vir_high::Predicate::OwnedNonAliased(predicate) + } + // vir_high::Predicate::OwnedNonAliased(vir_high::OwnedNonAliased { + // place: vir_high::Expression::AddrOf(vir_high::AddrOf { base, .. }), position + // }) => { + // vir_high::Predicate::owned_non_aliased(*base, position) + // } + vir_high::Predicate::UniqueRef(mut predicate) => { + predicate.place = peel_addr_of(predicate.place); + if !predicate.place.is_behind_pointer_dereference() { + // FIXME: A proper error message + unimplemented!("Must be behind pointer dereference: {}", predicate.place) + } + vir_high::Predicate::UniqueRef(predicate) + } + vir_high::Predicate::MemoryBlockHeap(mut predicate) => { + predicate.address = peel_addr_of(predicate.address); + if !predicate.address.is_behind_pointer_dereference() { + // FIXME: A proper error message + unimplemented!("Must be behind pointer dereference: {}", predicate.address) + } + vir_high::Predicate::MemoryBlockHeap(predicate) + } + vir_high::Predicate::MemoryBlockHeapRange(predicate) => { + // predicate.address = peel_addr_of(predicate.address); + vir_high::Predicate::MemoryBlockHeapRange(predicate) + } + vir_high::Predicate::MemoryBlockHeapRangeGuarded(predicate) => { + vir_high::Predicate::MemoryBlockHeapRangeGuarded(predicate) + } + vir_high::Predicate::MemoryBlockHeapDrop(mut predicate) => { + predicate.address = peel_addr_of(predicate.address); + if !predicate.address.is_behind_pointer_dereference() { + // FIXME: A proper error message + unimplemented!("Must be behind pointer dereference: {}", predicate.address) + } + vir_high::Predicate::MemoryBlockHeapDrop(predicate) + } + vir_high::Predicate::OwnedRange(predicate) => { + // predicate.address = peel_addr_of(predicate.address); + vir_high::Predicate::OwnedRange(predicate) + } + _ => unimplemented!("{:?}", predicate), + } +} + +impl<'p, 'v: 'p, 'tcx: 'v> ExpressionFallibleFolder for Cleaner<'p, 'v, 'tcx> { + type Error = SpannedEncodingError; + + fn fallible_fold_acc_predicate( + &mut self, + mut acc_predicate: vir_high::AccPredicate, + ) -> Result { + let predicate = clean_acc_predicate(*acc_predicate.predicate); + acc_predicate.predicate = Box::new(predicate); + default_fallible_fold_acc_predicate(self, acc_predicate) + } + + fn fallible_fold_unfolding( + &mut self, + mut unfolding: vir_high::Unfolding, + ) -> Result { + let predicate = clean_acc_predicate(*unfolding.predicate); + unfolding.predicate = Box::new(predicate); + default_fallible_fold_unfolding(self, unfolding) + } + + fn fallible_fold_conditional_enum( + &mut self, + conditional: vir_high::Conditional, + ) -> Result { + let conditional = self.fallible_fold_conditional(conditional)?; + let expression = match conditional { + _ if conditional.guard.is_true() => *conditional.then_expr, + _ if conditional.guard.is_false() => *conditional.else_expr, + vir_high::Conditional { + guard: + box vir_high::Expression::UnaryOp(vir_high::UnaryOp { + op_kind: vir_high::UnaryOpKind::Not, + argument: guard, + .. + }), + then_expr, + else_expr, + position, + } if then_expr.is_false() || then_expr.is_true() => { + // This happens due to short-circuiting in Rust. + if then_expr.is_false() { + vir_high::Expression::BinaryOp(vir_high::BinaryOp { + op_kind: vir_high::BinaryOpKind::And, + left: guard, + right: else_expr, + position, + }) + } else if then_expr.is_true() { + if !guard.is_pure() { + return Err(SpannedEncodingError::incorrect( + "permission predicates can be only in positive positions", + self.span, + )); + } + vir_high::Expression::BinaryOp(vir_high::BinaryOp { + op_kind: vir_high::BinaryOpKind::Implies, + left: guard, + right: else_expr, + position, + }) + } else { + unreachable!(); + } + } + _ if conditional.else_expr.is_true() => { + // Clean up stuff generated by `own!` expansion. + if !conditional.guard.is_pure() { + unimplemented!("TODO: A proper error message: {conditional}") + } + vir_high::Expression::BinaryOp(vir_high::BinaryOp { + op_kind: vir_high::BinaryOpKind::Implies, + left: conditional.guard, + right: conditional.then_expr, + position: conditional.position, + }) + } + _ if conditional.else_expr.is_false() => { + // Clean up stuff generated by `own!` expansion. + vir_high::Expression::BinaryOp(vir_high::BinaryOp { + op_kind: vir_high::BinaryOpKind::And, + left: conditional.guard, + right: conditional.then_expr, + position: conditional.position, + }) + } + _ => { + if !conditional.guard.is_pure() { + unimplemented!("TODO: A proper error message: {conditional}") + } + return Ok(vir_high::Expression::Conditional(conditional)); + } + }; + Ok(expression) + } + + fn fallible_fold_binary_op( + &mut self, + binary_op: vir_high::BinaryOp, + ) -> Result { + if binary_op.op_kind != vir_high::BinaryOpKind::And && !binary_op.left.is_pure() { + unimplemented!("TODO: A proper error message.") + } + if !matches!( + binary_op.op_kind, + vir_high::BinaryOpKind::And | vir_high::BinaryOpKind::Implies + ) && !binary_op.right.is_pure() + { + unimplemented!("TODO: A proper error message.") + } + default_fallible_fold_binary_op(self, binary_op) + } + + fn fallible_fold_builtin_func_app_enum( + &mut self, + mut builtin_func_app: vir_high::BuiltinFuncApp, + ) -> Result { + match builtin_func_app.function { + vir_high::BuiltinFunc::MemoryBlockBytes => { + let address = builtin_func_app.arguments[0].clone(); + builtin_func_app.arguments[0] = peel_addr_of(address); + } + vir_high::BuiltinFunc::MemoryBlockBytesPtr => { + let pointer = builtin_func_app.arguments[0].clone(); + let vir_high::Type::Pointer(pointer_type) = pointer.get_type() else { + unreachable!("pointer.get_type() should be Pointer, got: {}", pointer.get_type()); + }; + let target_type = (*pointer_type.target_type).clone(); + let position = pointer.position(); + let pointer_deref = pointer.deref(target_type, position); + builtin_func_app.function = vir_high::BuiltinFunc::MemoryBlockBytes; + builtin_func_app.arguments[0] = pointer_deref; + } + vir_high::BuiltinFunc::BuildingUniqueRefPredicateWithRealLifetime => { + let place = peel_addr_of(builtin_func_app.arguments.pop().unwrap()); + let lifetime = + self.lifetime_name_to_lifetime_const(builtin_func_app.arguments.pop().unwrap()); + let position = builtin_func_app.position; + let predicate = vir_high::Expression::acc_predicate( + vir_high::Predicate::unique_ref(lifetime, place, position), + position, + ); + return Ok(predicate); + } + vir_high::BuiltinFunc::BuildingUniqueRefPredicateRangeWithRealLifetime => { + let end_index = builtin_func_app.arguments.pop().unwrap(); + let start_index = builtin_func_app.arguments.pop().unwrap(); + let address = builtin_func_app.arguments.pop().unwrap(); + let lifetime = + self.lifetime_name_to_lifetime_const(builtin_func_app.arguments.pop().unwrap()); + let position = builtin_func_app.position; + let predicate = vir_high::Expression::acc_predicate( + vir_high::Predicate::unique_ref_range( + lifetime, + address, + start_index, + end_index, + position, + ), + position, + ); + return Ok(predicate); + } + _ => {} + } + Ok(vir_high::Expression::BuiltinFuncApp( + self.fallible_fold_builtin_func_app(builtin_func_app)?, + )) + } + + fn fallible_fold_quantifier( + &mut self, + quantifier: vir_high::Quantifier, + ) -> Result { + // Quantifier bodies are already cleaned. + Ok(quantifier) + } +} + +fn check_permission_always_positive( + proc_def_id: ProcedureDefId, + expression: &vir_high::Expression, +) -> SpannedEncodingResult<()> { + match expression { + vir_high::Expression::AccPredicate(_) => { + // Accessibility predicate in the positive position. + } + vir_high::Expression::BinaryOp(binary_op_expression) => { + match binary_op_expression.op_kind { + vir_high::BinaryOpKind::And => { + check_permission_always_positive(proc_def_id, &binary_op_expression.left)?; + check_permission_always_positive(proc_def_id, &binary_op_expression.right)?; + } + vir_high::BinaryOpKind::Implies => { + assert!( + binary_op_expression.left.is_pure(), + "{proc_def_id:?} {expression}" + ); + check_permission_always_positive(proc_def_id, &binary_op_expression.right)?; + } + _ => { + assert!(expression.is_pure(), "{proc_def_id:?} {expression}"); + } + } + } + vir_high::Expression::Conditional(conditional_expression) => { + assert!( + conditional_expression.guard.is_pure(), + "{proc_def_id:?} {}", + conditional_expression.guard + ); + check_permission_always_positive(proc_def_id, &conditional_expression.then_expr)?; + check_permission_always_positive(proc_def_id, &conditional_expression.else_expr)?; + } + _ => { + assert!(expression.is_pure(), "{proc_def_id:?} {expression}"); + } + } + Ok(()) +} diff --git a/prusti-viper/src/encoder/mir/pure/pure_functions/encoder_high.rs b/prusti-viper/src/encoder/mir/pure/pure_functions/encoder_high.rs index 9062e611852..4e9f00015d6 100644 --- a/prusti-viper/src/encoder/mir/pure/pure_functions/encoder_high.rs +++ b/prusti-viper/src/encoder/mir/pure/pure_functions/encoder_high.rs @@ -99,16 +99,14 @@ pub(super) fn encode_pure_expression<'p, 'v: 'p, 'tcx: 'v>( parent_def_id, substs, ); + let span = encoder.env().query.get_def_span(proc_def_id); let state = run_backward_interpretation(&mir, &interpreter)?.ok_or_else(|| { - SpannedEncodingError::incorrect( - format!("procedure {proc_def_id:?} contains a loop"), - encoder.env().query.get_def_span(proc_def_id), - ) + SpannedEncodingError::incorrect(format!("procedure {proc_def_id:?} contains a loop"), span) })?; let body = state.into_expr().ok_or_else(|| { SpannedEncodingError::internal( format!("failed to encode function's body: {proc_def_id:?}"), - encoder.env().query.get_def_span(proc_def_id), + span, ) })?; debug!( @@ -117,6 +115,7 @@ pub(super) fn encode_pure_expression<'p, 'v: 'p, 'tcx: 'v>( ); // FIXME: Traverse the encoded function and check that all used types are // Copy. Doing this before encoding causes too many false positives. + let body = super::cleaner::clean_encoding_result(encoder, body, proc_def_id, substs, span)?; Ok(body) } @@ -198,7 +197,10 @@ impl<'p, 'v: 'p, 'tcx: 'v> PureEncoder<'p, 'v, 'tcx> { #[tracing::instrument(level = "debug", skip(self), fields(proc_def_id = ?self.proc_def_id))] fn encode_function_decl(&self) -> SpannedEncodingResult { - let is_bodyless = self.encoder.is_trusted(self.proc_def_id, Some(self.substs)) + let is_bodyless = (self.encoder.is_trusted(self.proc_def_id, Some(self.substs)) + && !self + .encoder + .is_non_verified_pure(self.proc_def_id, Some(self.substs))) || !self.encoder.env().query.has_body(self.proc_def_id); let body = if is_bodyless { None @@ -369,7 +371,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> PureEncoder<'p, 'v, 'tcx> { self.parent_def_id, assertion_substs, )?; - self.encoder.error_manager().set_error( + self.encoder.error_manager().set_surrounding_error_context( encoded_assertion.position().into(), ErrorCtxt::PureFunctionDefinition, ); diff --git a/prusti-viper/src/encoder/mir/pure/pure_functions/interface.rs b/prusti-viper/src/encoder/mir/pure/pure_functions/interface.rs index bea769e4d09..5a00ce45406 100644 --- a/prusti-viper/src/encoder/mir/pure/pure_functions/interface.rs +++ b/prusti-viper/src/encoder/mir/pure/pure_functions/interface.rs @@ -333,7 +333,8 @@ impl<'v, 'tcx: 'v> PureFunctionEncoderInterface<'v, 'tcx> let maybe_identifier: SpannedEncodingResult = (|| { let proc_kind = self.get_proc_kind(proc_def_id, Some(substs)); - let is_bodyless = self.is_trusted(proc_def_id, Some(substs)) + let is_bodyless = (self.is_trusted(proc_def_id, Some(substs)) + && !self.is_non_verified_pure(proc_def_id, Some(substs))) || !self.env().query.has_body(proc_def_id); let mut function = if is_bodyless { pure_function_encoder.encode_bodyless_function()? diff --git a/prusti-viper/src/encoder/mir/pure/pure_functions/mod.rs b/prusti-viper/src/encoder/mir/pure/pure_functions/mod.rs index ab45f165594..422695dce1d 100644 --- a/prusti-viper/src/encoder/mir/pure/pure_functions/mod.rs +++ b/prusti-viper/src/encoder/mir/pure/pure_functions/mod.rs @@ -6,6 +6,7 @@ //! Encoders of pure functions. +mod cleaner; mod interface; mod encoder_high; mod encoder_poly; diff --git a/prusti-viper/src/encoder/mir/pure/specifications/encoder_high.rs b/prusti-viper/src/encoder/mir/pure/specifications/encoder_high.rs index d250405e775..0fc90382974 100644 --- a/prusti-viper/src/encoder/mir/pure/specifications/encoder_high.rs +++ b/prusti-viper/src/encoder/mir/pure/specifications/encoder_high.rs @@ -14,16 +14,14 @@ use crate::encoder::{ mir_encoder::{MirEncoder, PlaceEncoder}, Encoder, }; + use prusti_common::config; use prusti_rustc_interface::{ hir::def_id::DefId, middle::{ty, ty::subst::SubstsRef}, span::Span, }; -use vir_crate::{ - common::expression::{BinaryOperationHelpers, ExpressionIterator, QuantifierHelpers}, - high as vir_high, -}; +use vir_crate::{common::expression::QuantifierHelpers, high as vir_high}; fn simplify(expression: vir_high::Expression) -> vir_high::Expression { if prusti_common::config::unsafe_core_proof() { @@ -40,11 +38,14 @@ pub(super) fn inline_closure_high<'tcx>( args: Vec, parent_def_id: DefId, substs: SubstsRef<'tcx>, + keep_lifetimes: bool, ) -> SpannedEncodingResult { - let mir = encoder - .env() - .body - .get_closure_body(def_id, substs, parent_def_id); + let mir = encoder.env().body.get_closure_body_lifetimes_opt( + def_id, + substs, + parent_def_id, + keep_lifetimes, + ); assert_eq!(mir.arg_count, args.len() + 1); let mut body_replacements = vec![]; for (arg_idx, arg_local) in mir.args_iter().enumerate() { @@ -55,14 +56,12 @@ pub(super) fn inline_closure_high<'tcx>( } else { args[arg_idx - 1].clone().into() }; - body_replacements.push((local.erase_lifetime(), argument.erase_lifetime())); + body_replacements.push((local, argument)); } - Ok(simplify( - encoder - .encode_pure_expression_high(def_id, parent_def_id, substs)? - .erase_lifetime() - .replace_multiple_places(&body_replacements), - )) + let expression = encoder + .encode_pure_expression_high(def_id, parent_def_id, substs)? + .replace_multiple_places(&body_replacements); + Ok(simplify(expression)) } #[allow(clippy::unnecessary_unwrap)] @@ -75,15 +74,31 @@ pub(super) fn inline_spec_item_high<'tcx>( parent_def_id: DefId, substs: SubstsRef<'tcx>, ) -> SpannedEncodingResult { + assert_eq!( + substs.len(), + encoder.env().query.identity_substs(def_id).len() + ); + let mir = encoder .env() .body - .get_spec_body(def_id, substs, parent_def_id); + .get_expression_body(def_id, substs, parent_def_id); assert_eq!( mir.arg_count, target_args.len() + usize::from(target_return.is_some()), "def_id: {def_id:?}" ); + + // let mir = encoder + // .env() + // .body + // .get_spec_body(def_id, substs, parent_def_id); + // assert_eq!( + // mir.arg_count, + // target_args.len() + if target_return.is_some() { 1 } else { 0 }, + // "def_id: {:?}", + // def_id + // ); let mir_encoder = MirEncoder::new(encoder, &mir, def_id); let mut body_replacements = vec![]; for (arg_idx, arg_local) in mir.args_iter().enumerate() { @@ -107,13 +122,15 @@ pub(super) fn inline_spec_item_high<'tcx>( }, )); } - Ok(simplify( - encoder - .encode_pure_expression_high(def_id, parent_def_id, substs)? - .replace_multiple_places(&body_replacements), - )) + let expression = encoder.encode_pure_expression_high(def_id, parent_def_id, substs)?; + let expression = expression.replace_multiple_places(&body_replacements); + Ok(simplify(expression)) } +/// This encodes not only quantifiers, but also quantifier-like things such as +/// quantified permissions. It assumes that the last substitution is the closure +/// representing the quantifier body and the second-to-last substitution +/// represents triggers. pub(super) fn encode_quantifier_high<'tcx>( encoder: &Encoder<'_, 'tcx>, _span: Span, // TODO: use span somehow? or remove arg @@ -134,32 +151,39 @@ pub(super) fn encode_quantifier_high<'tcx>( // |qvars...| -> bool { }, // ) - let cl_type_body = substs.type_at(1); + let last_substitution_index = substs.len() - 1; + let cl_type_body = substs.type_at(last_substitution_index); let (body_def_id, body_substs, _, args, _) = extract_closure_from_ty(encoder.env().query, cl_type_body); let mut encoded_qvars = vec![]; - let mut bounds = vec![]; + // let mut bounds = vec![]; for (arg_idx, arg_ty) in args.into_iter().enumerate() { let qvar_ty = encoder.encode_type_high(arg_ty).unwrap(); let qvar_name = format!("_{}_quant_{}", arg_idx, body_def_id.index.index()); let encoded_qvar = vir_high::VariableDecl::new(qvar_name, qvar_ty); - if config::check_overflows() { - bounds.extend(encoder.encode_type_bounds_high(&encoded_qvar.clone().into(), arg_ty)); - } else if config::encode_unsigned_num_constraint() { - if let ty::TyKind::Uint(_) = arg_ty.kind() { - let expr = - vir_high::Expression::less_equals(0u32.into(), encoded_qvar.clone().into()); - bounds.push(expr); - } - } + // Instead of the bounds we use the snapshot validity function. + // if config::check_overflows() { + // bounds.extend(encoder.encode_type_bounds_high(&encoded_qvar.clone().into(), arg_ty)); + // } else if config::encode_unsigned_num_constraint() { + // if let ty::TyKind::Uint(_) = arg_ty.kind() { + // let expr = + // vir_high::Expression::less_equals(0u32.into(), encoded_qvar.clone().into()); + // bounds.push(expr); + // } + // } encoded_qvars.push(encoded_qvar); } // TODO: implement trigger and trigger set checks + let second_to_last_substitution_index = substs.len() - 2; + let second_to_last_encoded_arg_index = encoded_args.len() - 2; let mut encoded_trigger_sets = vec![]; - for (trigger_set_idx, ty_trigger_set) in - substs.type_at(0).tuple_fields().into_iter().enumerate() + for (trigger_set_idx, ty_trigger_set) in substs + .type_at(second_to_last_substitution_index) + .tuple_fields() + .into_iter() + .enumerate() { let mut encoded_triggers = vec![]; for (trigger_idx, ty_trigger) in ty_trigger_set.tuple_fields().into_iter().enumerate() { @@ -175,52 +199,59 @@ pub(super) fn encode_quantifier_high<'tcx>( trigger_idx, encoder.encode_type_high(ty_trigger)?, ); - encoded_triggers.push(inline_closure_high( + let encoded_trigger = inline_closure_high( encoder, trigger_def_id, // FIXME: check whether the closure expression does not need to // be wrapped in `addr_of` like in `encode_invariant_high`. vir_high::Expression::field_no_pos( - vir_high::Expression::field_no_pos(encoded_args[0].clone(), set_field), + vir_high::Expression::field_no_pos( + encoded_args[second_to_last_encoded_arg_index].clone(), + set_field, + ), trigger_field, ), encoded_qvars.clone(), parent_def_id, trigger_substs, - )?); + config::unsafe_core_proof(), + )?; + encoded_triggers.push(encoded_trigger); } encoded_trigger_sets.push(vir_high::Trigger::new(encoded_triggers)); } + let last_encoded_arg_index = encoded_args.len() - 1; let encoded_body = inline_closure_high( encoder, body_def_id, - encoded_args[1].clone(), + encoded_args[last_encoded_arg_index].clone(), encoded_qvars.clone(), parent_def_id, body_substs, + config::unsafe_core_proof(), )?; // TODO: implement cache-friendly qvar renaming - let final_body = if bounds.is_empty() { - encoded_body - } else if is_exists { - vir_high::Expression::and(bounds.into_iter().conjoin(), encoded_body) - } else { - vir_high::Expression::implies(bounds.into_iter().conjoin(), encoded_body) - }; + // let final_body = if bounds.is_empty() { + // encoded_body + // } else if is_exists { + // vir_high::Expression::and(bounds.into_iter().conjoin(), encoded_body) + // } else { + // vir_high::Expression::implies(bounds.into_iter().conjoin(), encoded_body) + // }; if is_exists { Ok(vir_high::Expression::exists( encoded_qvars, encoded_trigger_sets, - simplify(final_body), + simplify(encoded_body), )) } else { Ok(vir_high::Expression::forall( encoded_qvars, encoded_trigger_sets, - simplify(final_body), + simplify(encoded_body), )) } } diff --git a/prusti-viper/src/encoder/mir/pure/specifications/interface.rs b/prusti-viper/src/encoder/mir/pure/specifications/interface.rs index ff05151e2dd..35d4a62ff50 100644 --- a/prusti-viper/src/encoder/mir/pure/specifications/interface.rs +++ b/prusti-viper/src/encoder/mir/pure/specifications/interface.rs @@ -64,6 +64,7 @@ pub(crate) trait SpecificationEncoderInterface<'tcx> { invariant_block: mir::BasicBlock, // in which the invariant is defined parent_def_id: DefId, substs: SubstsRef<'tcx>, + keep_lifetimes: bool, ) -> SpannedEncodingResult; fn encode_prusti_operation( @@ -114,6 +115,11 @@ impl<'v, 'tcx: 'v> SpecificationEncoderInterface<'tcx> for crate::encoder::Encod parent_def_id, substs, ), + "prusti_contracts::prusti_raw_range_guarded" => { + // We just pretend that this is a regular quantifier and then + // transform it later. + encode_quantifier_high(self, span, encoded_args, false, parent_def_id, substs) + } _ => unimplemented!(), } } @@ -121,13 +127,13 @@ impl<'v, 'tcx: 'v> SpecificationEncoderInterface<'tcx> for crate::encoder::Encod fn encode_assertion_high( &self, assertion: DefId, - _pre_label: Option<&str>, // TODO: use pre_label (map labels) + pre_label: Option<&str>, target_args: &[vir_high::Expression], target_return: Option<&vir_high::Expression>, parent_def_id: DefId, substs: SubstsRef<'tcx>, ) -> SpannedEncodingResult { - let encoded_assertion = inline_spec_item_high( + let mut encoded_assertion = inline_spec_item_high( self, assertion, target_args, @@ -136,6 +142,18 @@ impl<'v, 'tcx: 'v> SpecificationEncoderInterface<'tcx> for crate::encoder::Encod parent_def_id, substs, )?; + + // map old labels + if let Some(pre_label) = pre_label { + encoded_assertion = encoded_assertion.map_old_expression_label(|label| { + if label == PRECONDITION_LABEL { + pre_label.to_string() + } else { + label + } + }); + } + let position = self .error_manager() .register_span(parent_def_id, self.env().query.get_def_span(assertion)); @@ -148,11 +166,12 @@ impl<'v, 'tcx: 'v> SpecificationEncoderInterface<'tcx> for crate::encoder::Encod invariant_block: mir::BasicBlock, // in which the invariant is defined parent_def_id: DefId, substs: SubstsRef<'tcx>, + keep_lifetimes: bool, ) -> SpannedEncodingResult { - // identify previous block: there should only be one - let predecessors = &mir.basic_blocks.predecessors()[invariant_block]; - assert_eq!(predecessors.len(), 1); - let predecessor = predecessors[0]; + // // identify previous block: there should only be one + // let predecessors = &mir.basic_blocks.predecessors()[invariant_block]; + // assert_eq!(predecessors.len(), 1); + // let predecessor = predecessors[0]; // identify closure aggregate assign (the invariant body) let closure_assigns = mir.basic_blocks[invariant_block] @@ -190,7 +209,8 @@ impl<'v, 'tcx: 'v> SpecificationEncoderInterface<'tcx> for crate::encoder::Encod .encode_place_high(mir, inv_cl_expr, Some(span)) .with_span(span)?; let closure_borrow_type = vir_high::Type::reference( - vir_high::ty::LifetimeConst::erased(), + // vir_high::ty::LifetimeConst::erased(), + vir_high::ty::LifetimeConst::new("fixme_closure_lifetime"), vir_high::ty::Uniqueness::Shared, inv_cl_expr_encoded.get_type().clone(), ); @@ -206,22 +226,23 @@ impl<'v, 'tcx: 'v> SpecificationEncoderInterface<'tcx> for crate::encoder::Encod vec![], parent_def_id, substs, - )? - .erase_lifetime(); + keep_lifetimes, + )?; // backward interpret the body to get rid of the upvars let interpreter = ExpressionBackwardInterpreter::new( self, mir, parent_def_id, - PureEncodingContext::Code, + PureEncodingContext::Assertion, parent_def_id, substs, ); let invariant = run_backward_interpretation_point_to_point( mir, &interpreter, - predecessor, + // predecessor, + invariant_block, invariant_block, inv_loc + 1, // include the closure assign itself crate::encoder::mir::pure::interpreter::state_high::ExprBackwardInterpreterState::new_defined(encoded_invariant), @@ -353,7 +374,7 @@ impl<'v, 'tcx: 'v> SpecificationEncoderInterface<'tcx> for crate::encoder::Encod self, mir, parent_def_id, - PureEncodingContext::Code, + PureEncodingContext::Assertion, parent_def_id, ); let invariant = run_backward_interpretation_point_to_point( diff --git a/prusti-viper/src/encoder/mir/specifications/interface.rs b/prusti-viper/src/encoder/mir/specifications/interface.rs index 73a829e7e60..2ecdc1356db 100644 --- a/prusti-viper/src/encoder/mir/specifications/interface.rs +++ b/prusti-viper/src/encoder/mir/specifications/interface.rs @@ -80,6 +80,16 @@ pub(crate) trait SpecificationsInterface<'tcx> { fn is_trusted(&self, def_id: DefId, substs: Option>) -> bool; + fn is_non_verified_pure(&self, def_id: DefId, substs: Option>) -> bool; + + fn no_panic(&self, def_id: DefId, substs: Option>) -> bool; + + fn no_panic_ensures_postcondition( + &self, + def_id: DefId, + substs: Option>, + ) -> bool; + fn get_predicate_body(&self, def_id: DefId, substs: SubstsRef<'tcx>) -> Option; fn terminates(&self, def_id: DefId, substs: Option>) -> bool; @@ -97,6 +107,9 @@ pub(crate) trait SpecificationsInterface<'tcx> { /// Get the prusti assumption fn get_prusti_assumption(&self, def_id: DefId) -> Option; + /// Get the Prusti case split. + fn get_prusti_case_split(&self, def_id: DefId) -> Option; + /// Get the prusti refutation fn get_prusti_refutation(&self, def_id: DefId) -> Option; @@ -106,6 +119,21 @@ pub(crate) trait SpecificationsInterface<'tcx> { /// Get the end marker of the ghost block fn get_ghost_end(&self, def_id: DefId) -> Option; + /// Get the begin marker of the specification region. + fn get_specification_region_begin( + &self, + def_id: DefId, + ) -> Option; + + /// Get the end marker of the specification region. + fn get_specification_region_end(&self, def_id: DefId) -> Option; + + /// Get the prusti specification expression + fn get_prusti_specification_expression( + &self, + def_id: DefId, + ) -> Option; + /// Get the specifications attached to a function. fn get_procedure_specs( &self, @@ -177,6 +205,54 @@ impl<'v, 'tcx: 'v> SpecificationsInterface<'tcx> for super::super::super::Encode .unwrap_or(false) } + #[tracing::instrument(level = "trace", skip(self), ret)] + fn is_non_verified_pure(&self, def_id: DefId, substs: Option>) -> bool { + let substs = substs.unwrap_or_else(|| self.env().query.identity_substs(def_id)); + let query = SpecQuery::GetProcKind(def_id, substs); + self.specifications_state + .specs + .borrow_mut() + .get_and_refine_proc_spec(self.env(), query) + .and_then(|spec| { + spec.non_verified_pure + .extract_with_selective_replacement() + .copied() + }) + .unwrap_or(false) + } + + #[tracing::instrument(level = "trace", skip(self), ret)] + fn no_panic(&self, def_id: DefId, substs: Option>) -> bool { + let substs = substs.unwrap_or_else(|| self.env().query.identity_substs(def_id)); + let query = SpecQuery::GetProcKind(def_id, substs); + self.specifications_state + .specs + .borrow_mut() + .get_and_refine_proc_spec(self.env(), query) + .and_then(|spec| spec.no_panic.extract_with_selective_replacement().copied()) + .unwrap_or(false) + } + + #[tracing::instrument(level = "trace", skip(self), ret)] + fn no_panic_ensures_postcondition( + &self, + def_id: DefId, + substs: Option>, + ) -> bool { + let substs = substs.unwrap_or_else(|| self.env().query.identity_substs(def_id)); + let query = SpecQuery::GetProcKind(def_id, substs); + self.specifications_state + .specs + .borrow_mut() + .get_and_refine_proc_spec(self.env(), query) + .and_then(|spec| { + spec.no_panic_ensures_postcondition + .extract_with_selective_replacement() + .copied() + }) + .unwrap_or(false) + } + #[tracing::instrument(level = "trace", skip(self), ret)] fn get_predicate_body(&self, def_id: DefId, substs: SubstsRef<'tcx>) -> Option { let query = SpecQuery::FunctionDefEncoding(def_id, substs); @@ -237,6 +313,14 @@ impl<'v, 'tcx: 'v> SpecificationsInterface<'tcx> for super::super::super::Encode .cloned() } + fn get_prusti_case_split(&self, def_id: DefId) -> Option { + self.specifications_state + .specs + .borrow() + .get_case_split(&def_id) + .cloned() + } + fn get_prusti_refutation(&self, def_id: DefId) -> Option { self.specifications_state .specs @@ -261,6 +345,36 @@ impl<'v, 'tcx: 'v> SpecificationsInterface<'tcx> for super::super::super::Encode .cloned() } + fn get_specification_region_begin( + &self, + def_id: DefId, + ) -> Option { + self.specifications_state + .specs + .borrow() + .get_specification_region_begin(&def_id) + .cloned() + } + + fn get_specification_region_end(&self, def_id: DefId) -> Option { + self.specifications_state + .specs + .borrow() + .get_specification_region_end(&def_id) + .cloned() + } + + fn get_prusti_specification_expression( + &self, + def_id: DefId, + ) -> Option { + self.specifications_state + .specs + .borrow() + .get_specification_expression(&def_id) + .cloned() + } + fn get_procedure_specs( &self, def_id: DefId, diff --git a/prusti-viper/src/encoder/mir/specifications/specs.rs b/prusti-viper/src/encoder/mir/specifications/specs.rs index ce904970f15..903545a256d 100644 --- a/prusti-viper/src/encoder/mir/specifications/specs.rs +++ b/prusti-viper/src/encoder/mir/specifications/specs.rs @@ -11,7 +11,8 @@ use prusti_interface::{ specs::typed::{ DefSpecificationMap, GhostBegin, GhostEnd, LoopSpecification, ProcedureSpecification, ProcedureSpecificationKind, ProcedureSpecificationKindError, PrustiAssertion, - PrustiAssumption, PrustiRefutation, Refinable, SpecificationItem, TypeSpecification, + PrustiAssumption, PrustiCaseSplit, PrustiRefutation, Refinable, SpecificationExpression, + SpecificationItem, SpecificationRegionBegin, SpecificationRegionEnd, TypeSpecification, }, PrustiError, }; @@ -91,6 +92,11 @@ impl<'tcx> Specifications<'tcx> { self.user_typed_specs.get_assumption(def_id) } + #[tracing::instrument(level = "trace", skip(self))] + pub(super) fn get_case_split(&self, def_id: &DefId) -> Option<&PrustiCaseSplit> { + self.user_typed_specs.get_case_split(def_id) + } + #[tracing::instrument(level = "trace", skip(self))] pub(super) fn get_refutation(&self, def_id: &DefId) -> Option<&PrustiRefutation> { self.user_typed_specs.get_refutation(def_id) @@ -106,6 +112,30 @@ impl<'tcx> Specifications<'tcx> { self.user_typed_specs.get_ghost_end(def_id) } + #[tracing::instrument(level = "trace", skip(self))] + pub(super) fn get_specification_region_begin( + &self, + def_id: &DefId, + ) -> Option<&SpecificationRegionBegin> { + self.user_typed_specs.get_specification_region_begin(def_id) + } + + #[tracing::instrument(level = "trace", skip(self))] + pub(super) fn get_specification_region_end( + &self, + def_id: &DefId, + ) -> Option<&SpecificationRegionEnd> { + self.user_typed_specs.get_specification_region_end(def_id) + } + + #[tracing::instrument(level = "trace", skip(self))] + pub(super) fn get_specification_expression( + &self, + def_id: &DefId, + ) -> Option<&SpecificationExpression> { + self.user_typed_specs.get_specification_expression(def_id) + } + #[tracing::instrument(level = "trace", skip(self, env))] pub(super) fn get_and_refine_proc_spec<'a, 'env: 'a>( &'a mut self, diff --git a/prusti-viper/src/encoder/mir/type_layouts/interface.rs b/prusti-viper/src/encoder/mir/type_layouts/interface.rs index 788c108eee2..1f0821a9429 100644 --- a/prusti-viper/src/encoder/mir/type_layouts/interface.rs +++ b/prusti-viper/src/encoder/mir/type_layouts/interface.rs @@ -3,6 +3,10 @@ use prusti_rustc_interface::middle::ty; use vir_crate::high::{self as vir_high, operations::const_generics::WithConstArguments}; pub(crate) trait MirTypeLayoutsEncoderInterface<'tcx> { + fn encode_type_size_expression_high( + &self, + ty: vir_high::Type, + ) -> SpannedEncodingResult; fn encode_type_size_expression( &self, ty: ty::Ty<'tcx>, @@ -14,11 +18,11 @@ pub(crate) trait MirTypeLayoutsEncoderInterface<'tcx> { } impl<'v, 'tcx: 'v> MirTypeLayoutsEncoderInterface<'tcx> for super::super::super::Encoder<'v, 'tcx> { - fn encode_type_size_expression( + fn encode_type_size_expression_high( &self, - ty: ty::Ty<'tcx>, + ty: vir_high::Type, ) -> SpannedEncodingResult { - let encoded_ty = self.encode_type_high(ty)?.erase_lifetimes(); + let encoded_ty = ty.erase_lifetimes(); let usize = vir_high::Type::Int(vir_high::ty::Int::Usize); let const_arguments = encoded_ty.get_const_arguments(); let function_call = vir_high::Expression::builtin_func_app_no_pos( @@ -30,6 +34,24 @@ impl<'v, 'tcx: 'v> MirTypeLayoutsEncoderInterface<'tcx> for super::super::super: Ok(function_call) } + fn encode_type_size_expression( + &self, + ty: ty::Ty<'tcx>, + ) -> SpannedEncodingResult { + let encoded_ty = self.encode_type_high(ty)?; + self.encode_type_size_expression_high(encoded_ty) + // .erase_lifetimes(); + // let usize = vir_high::Type::Int(vir_high::ty::Int::Usize); + // let const_arguments = encoded_ty.get_const_arguments(); + // let function_call = vir_high::Expression::builtin_func_app_no_pos( + // vir_high::BuiltinFunc::Size, + // vec![encoded_ty], + // const_arguments, + // usize, + // ); + // Ok(function_call) + } + fn encode_type_padding_size_expression( &self, ty: ty::Ty<'tcx>, diff --git a/prusti-viper/src/encoder/mir/types/encoder.rs b/prusti-viper/src/encoder/mir/types/encoder.rs index 4cc5f8703fb..e5af9c2b0c1 100644 --- a/prusti-viper/src/encoder/mir/types/encoder.rs +++ b/prusti-viper/src/encoder/mir/types/encoder.rs @@ -6,9 +6,10 @@ use super::{helpers::compute_discriminant_values, interface::MirTypeEncoderInterface}; use crate::encoder::{ - errors::{EncodingResult, SpannedEncodingError, SpannedEncodingResult, WithSpan}, + errors::{EncodingResult, ErrorCtxt, SpannedEncodingError, SpannedEncodingResult, WithSpan}, mir::{ - constants::ConstantsEncoderInterface, generics::MirGenericsEncoderInterface, + constants::ConstantsEncoderInterface, errors::ErrorInterface, + generics::MirGenericsEncoderInterface, pure::SpecificationEncoderInterface, specifications::SpecificationsInterface, types::helpers::compute_discriminant_ranges, }, Encoder, @@ -50,11 +51,23 @@ impl<'p, 'v, 'r: 'v, 'tcx: 'v> TypeEncoder<'p, 'v, 'tcx> { | "prusti_contracts::Map" | "prusti_contracts::Int" | "prusti_contracts::Ghost" + | "prusti_contracts::Byte" + | "prusti_contracts::Bytes" ) } - fn is_trusted_type(&self, did: DefId) -> bool { - if let Some(type_specs) = self.encoder.get_type_specs(did) { + fn is_trusted_type(&self, adt_def: &ty::AdtDef) -> bool { + if adt_def.is_struct() && !adt_def.is_box() && !adt_def.did().is_local() { + let type_name: &str = &self + .encoder + .env() + .name + .get_absolute_item_name(adt_def.did()); + if !type_name.starts_with("prusti_contracts::") { + return true; + } + } + if let Some(type_specs) = self.encoder.get_type_specs(adt_def.did()) { *type_specs.trusted.expect_inherent() } else { false @@ -66,10 +79,7 @@ impl<'p, 'v, 'r: 'v, 'tcx: 'v> TypeEncoder<'p, 'v, 'tcx> { } #[tracing::instrument(level = "debug", skip(self), fields(ty = ?self.ty))] - pub fn encode_type( - self, - const_arguments: &[vir::Expression], - ) -> SpannedEncodingResult { + pub fn encode_type(self) -> SpannedEncodingResult { // self.encode_polymorphic_predicate_use() let lifetimes = self.encoder.get_lifetimes_from_type_high(self.ty)?; let result = match self.ty.kind() { @@ -104,7 +114,7 @@ impl<'p, 'v, 'r: 'v, 'tcx: 'v> TypeEncoder<'p, 'v, 'tcx> { vir::Type::reference(lifetime, uniqueness, self.encoder.encode_type_high(*ty)?) } - ty::TyKind::Adt(adt_def, substs) if self.is_trusted_type(adt_def.did()) => { + ty::TyKind::Adt(adt_def, substs) if self.is_trusted_type(adt_def) => { vir::Type::trusted( encode_trusted_name(self.encoder, adt_def.did()), self.encode_substs(substs), @@ -139,6 +149,10 @@ impl<'p, 'v, 'r: 'v, 'tcx: 'v> TypeEncoder<'p, 'v, 'tcx> { }) } else if type_name == "prusti_contracts::Int" { vir::Type::Int(vir::ty::Int::Unbounded) + } else if type_name == "prusti_contracts::Byte" { + vir::Type::MByte + } else if type_name == "prusti_contracts::Bytes" { + vir::Type::MBytes } else if type_name == "prusti_contracts::Ghost" { (*enc_substs[0]).clone() } else { @@ -193,22 +207,26 @@ impl<'p, 'v, 'r: 'v, 'tcx: 'v> TypeEncoder<'p, 'v, 'tcx> { ty::TyKind::Str => vir::Type::Str, ty::TyKind::Array(elem_ty, size) => { - let (array_len, tail): (_, &[vir::Expression]) = - if let Some((array_len, tail)) = const_arguments.split_first() { - (array_len.clone(), tail) - } else { - let array_len: usize = self - .compute_array_len(*size) - .with_span(self.get_definition_span())? - .try_into() - .unwrap(); - (array_len.into(), &[]) - }; + // let (array_len, tail): (_, &[vir::Expression]) = + // if let Some((array_len, tail)) = const_arguments.split_first() { + // (array_len.clone(), tail) + // } else { + // let array_len: usize = self + // .compute_array_len(*size) + // .with_span(self.get_definition_span())? + // .try_into() + // .unwrap(); + // (array_len.into(), &[]) + // }; + let array_len: usize = self + .compute_array_len(*size) + .with_span(self.get_definition_span())? + .try_into() + .unwrap(); let lifetimes = self.encoder.get_lifetimes_from_type_high(*elem_ty)?; vir::Type::array( - vir::ty::ConstGenericArgument::new(Some(Box::new(array_len))), - self.encoder - .encode_type_high_with_const_arguments(*elem_ty, tail)?, + vir::ty::ConstGenericArgument::new(Some(Box::new(array_len.into()))), + self.encoder.encode_type_high(*elem_ty)?, lifetimes, ) } @@ -344,7 +362,11 @@ impl<'p, 'v, 'r: 'v, 'tcx: 'v> TypeEncoder<'p, 'v, 'tcx> { /// Encodes a type predicate for the given type. #[tracing::instrument(level = "debug", skip(self), fields(ty = ?self.ty))] - pub fn encode_type_def_high(self) -> SpannedEncodingResult { + pub fn encode_type_def_high( + self, + ty: &vir::Type, + with_invariant: bool, + ) -> SpannedEncodingResult { let type_decl = match self.ty.kind() { ty::TyKind::Bool => vir::TypeDecl::bool(), ty::TyKind::Int(_) | ty::TyKind::Uint(_) | ty::TyKind::Char => { @@ -447,17 +469,24 @@ impl<'p, 'v, 'r: 'v, 'tcx: 'v> TypeEncoder<'p, 'v, 'tcx> { }), "prusti_contracts::Ghost" => { if let ty::subst::GenericArgKind::Type(ty) = substs[0].unpack() { - Self::new(self.encoder, ty).encode_type_def_high()? + let encoded_type = Self::new(self.encoder, ty).encode_type()?; + Self::new(self.encoder, ty) + .encode_type_def_high(&encoded_type, with_invariant)? } else { unreachable!("no type parameter given for Ghost") } } + "prusti_contracts::Bytes" | "prusti_contracts::Byte" => vir::TypeDecl::trusted( + encode_trusted_name(self.encoder, adt_def.did()), + Vec::new(), + Vec::new(), + ), _ => { - unreachable!(); + unreachable!("unexpected mathematical type: {type_name}"); } } } - ty::TyKind::Adt(adt_def, substs) if self.is_trusted_type(adt_def.did()) => { + ty::TyKind::Adt(adt_def, substs) if self.is_trusted_type(adt_def) => { let lifetimes = self.encoder.get_lifetimes_from_substs(substs)?; let const_parameters = self.encoder.get_const_parameters_from_substs(substs)?; vir::TypeDecl::trusted( @@ -466,9 +495,15 @@ impl<'p, 'v, 'r: 'v, 'tcx: 'v> TypeEncoder<'p, 'v, 'tcx> { const_parameters, ) } - ty::TyKind::Adt(adt_def, substs) => { - encode_adt_def(self.encoder, *adt_def, substs, None)? - } + ty::TyKind::Adt(adt_def, substs) => encode_adt_def( + self.encoder, + ty, + self.ty, + *adt_def, + substs, + None, + with_invariant, + )?, ty::TyKind::Never => vir::TypeDecl::never(), ty::TyKind::Param(param_ty) => { vir::TypeDecl::type_var(param_ty.name.as_str().to_string()) @@ -699,10 +734,35 @@ fn encode_trusted_name<'v, 'tcx: 'v>(encoder: &Encoder<'v, 'tcx>, did: DefId) -> fn encode_variant<'v, 'tcx: 'v>( encoder: &Encoder<'v, 'tcx>, name: String, + mir_type: ty::Ty<'tcx>, substs: ty::subst::SubstsRef<'tcx>, variant: &ty::VariantDef, + mut structural_invariant: Option>, + def_id: Option, ) -> SpannedEncodingResult { let tcx = encoder.env().tcx(); + if structural_invariant.is_some() { + let def_id = def_id.unwrap(); + // Get the module containing the given `def_id`. + let module = tcx + .parent_module_from_def_id(def_id.as_local().unwrap()) + .to_def_id(); + // Check that all fields are private. + for field in &variant.fields { + match field.vis { + ty::Visibility::Restricted(field_visibility_module) + if field_visibility_module == module => + { + // The field is private. + } + _ => { + unimplemented!( + "TODO: A proper error message that the field {field:?} must be private" + ); + } + } + } + } let mut fields = Vec::new(); for (field_index, field) in variant.fields.iter().enumerate() { let field_name = crate::encoder::encoder::encode_field_name(field.ident(tcx).as_str()); @@ -719,19 +779,95 @@ fn encode_variant<'v, 'tcx: 'v>( } let lifetimes = encoder.get_lifetimes_from_substs(substs)?; let const_parameters = encoder.get_const_parameters_from_substs(substs)?; - let variant = vir::type_decl::Struct::new(name, lifetimes, const_parameters, fields); + let position = if let Some(def_id) = def_id { + let span = encoder.env().query.get_def_span(def_id); + let position = encoder + .error_manager() + .register_error(span, ErrorCtxt::TypeInvariantDefinition, def_id) + .into(); + if let Some(structural_invariant) = &mut structural_invariant { + for expression in std::mem::take(structural_invariant) { + structural_invariant.push(encoder.set_surrounding_error_context_for_expression( + expression, + position, + ErrorCtxt::TypeInvariantDefinition, + )); + } + } + position + } else { + Default::default() + }; + let size = if let Ok(layout) = tcx.layout_of(ty::ParamEnv::reveal_all().and(mir_type)) { + Some(layout.size.bytes()) + } else { + None + }; + let variant = vir::type_decl::Struct::new_with_pos( + name, + lifetimes, + const_parameters, + structural_invariant, + fields, + size, + position, + ); Ok(variant) } +fn encode_structural_invariant<'v, 'tcx: 'v>( + encoder: &Encoder<'v, 'tcx>, + ty: &vir::Type, + substs: ty::subst::SubstsRef<'tcx>, + did: DefId, +) -> SpannedEncodingResult>> { + let invariant = if let Some(specs) = encoder.get_type_specs(did) { + match &specs.structural_invariant { + prusti_interface::specs::typed::SpecificationItem::Empty => None, + prusti_interface::specs::typed::SpecificationItem::Inherent(invs) => { + Some( + invs.iter() + .map(|inherent_def_id| { + encoder.encode_assertion_high( + *inherent_def_id, + None, + &[vir::Expression::self_variable(ty.clone())], + None, + // true, + *inherent_def_id, + substs, + ) + }) + .collect::, _>>()?, + ) + } + _ => todo!(), + // TODO(inv): handle invariant inheritance + } + } else { + None + }; + Ok(invariant) +} + +/// `with_invariant` is used to break infinite recursion. #[tracing::instrument(level = "debug", skip(encoder))] pub(super) fn encode_adt_def<'v, 'tcx>( encoder: &Encoder<'v, 'tcx>, + ty: &vir::Type, + mir_type: ty::Ty<'tcx>, adt_def: ty::AdtDef<'tcx>, substs: ty::subst::SubstsRef<'tcx>, variant_index: Option, + with_invariant: bool, ) -> SpannedEncodingResult { let lifetimes = encoder.get_lifetimes_from_substs(substs)?; let const_parameters = encoder.get_const_parameters_from_substs(substs)?; + let structural_invariant = if with_invariant { + encode_structural_invariant(encoder, ty, substs, adt_def.did())? + } else { + None + }; let tcx = encoder.env().tcx(); if adt_def.is_box() { debug!("ADT {:?} is a box", adt_def); @@ -742,7 +878,10 @@ pub(super) fn encode_adt_def<'v, 'tcx>( encode_box_name(), lifetimes, const_parameters, + structural_invariant, vec![field], + None, + Default::default(), )) } else if adt_def.is_struct() { debug!("ADT {:?} is a struct", adt_def); @@ -750,10 +889,22 @@ pub(super) fn encode_adt_def<'v, 'tcx>( let name = encode_struct_name(encoder, adt_def.did()); let variant = adt_def.non_enum_variant(); Ok(vir::TypeDecl::Struct(encode_variant( - encoder, name, substs, variant, + encoder, + name, + mir_type, + substs, + variant, + structural_invariant, + Some(adt_def.did()), )?)) } else if adt_def.is_union() { debug!("ADT {:?} is a union", adt_def); + if structural_invariant.is_some() { + return Err(SpannedEncodingError::unsupported( + "Structural invariants are not supported on unions", + encoder.env().query.get_def_span(adt_def.did()), + )); + } if !config::unsafe_core_proof() { return Err(SpannedEncodingError::unsupported( "unions are not supported", @@ -777,7 +928,9 @@ pub(super) fn encode_adt_def<'v, 'tcx>( field_name, lifetimes.clone(), const_parameters.clone(), + None, vec![encoded_field], + None, ); variants.push(variant); } @@ -793,6 +946,12 @@ pub(super) fn encode_adt_def<'v, 'tcx>( )) } else if adt_def.is_enum() { debug!("ADT {:?} is an enum", adt_def); + if structural_invariant.is_some() { + return Err(SpannedEncodingError::unsupported( + "Structural invariants are not supported on enums", + encoder.env().query.get_def_span(adt_def.did()), + )); + } let name = encode_enum_name(encoder, adt_def.did()); let num_variants = adt_def.variants().len(); debug!("ADT {:?} is enum with {} variants", adt_def, num_variants); @@ -800,7 +959,15 @@ pub(super) fn encode_adt_def<'v, 'tcx>( // FIXME: Currently fold-unfold assumes that everything that // has only a single variant is a struct. let variant = &adt_def.variants()[0usize.into()]; - vir::TypeDecl::Struct(encode_variant(encoder, name, substs, variant)?) + vir::TypeDecl::Struct(encode_variant( + encoder, + name, + mir_type, + substs, + variant, + None, + Default::default(), + )?) } else if let Some(_variant_index) = variant_index { // let variant = &adt_def.variants()[variant_index]; // vir::TypeDecl::Struct(encode_variant(encoder, name, substs, variant)?) @@ -813,7 +980,15 @@ pub(super) fn encode_adt_def<'v, 'tcx>( let mut variants = Vec::new(); for variant in adt_def.variants() { let name = variant.ident(tcx).to_string(); - let encoded_variant = encode_variant(encoder, name, substs, variant)?; + let encoded_variant = encode_variant( + encoder, + name, + mir_type, + substs, + variant, + None, + Default::default(), + )?; variants.push(encoded_variant); } let mir_discriminant_type = match adt_def.repr().discr_type() { diff --git a/prusti-viper/src/encoder/mir/types/interface.rs b/prusti-viper/src/encoder/mir/types/interface.rs index 1b26bdf00b8..738d5d012d2 100644 --- a/prusti-viper/src/encoder/mir/types/interface.rs +++ b/prusti-viper/src/encoder/mir/types/interface.rs @@ -12,7 +12,10 @@ use prusti_rustc_interface::{ }; use rustc_hash::FxHashMap; use std::cell::RefCell; -use vir_crate::{common::expression::less_equals, high as vir_high, polymorphic as vir}; +use vir_crate::{ + common::{builtin_constants::DISCRIMINANT_FIELD_NAME, expression::less_equals}, + high as vir_high, polymorphic as vir, +}; #[derive(Default)] pub(crate) struct MirTypeEncoderState<'tcx> { @@ -56,11 +59,11 @@ pub(crate) trait MirTypeEncoderInterface<'tcx> { ty: ty::Ty<'tcx>, ) -> SpannedEncodingResult>; fn encode_type_high(&self, ty: ty::Ty<'tcx>) -> SpannedEncodingResult; - fn encode_type_high_with_const_arguments( - &self, - ty: ty::Ty<'tcx>, - const_arguments: &[vir_high::Expression], - ) -> SpannedEncodingResult; + // fn encode_type_high_with_const_arguments( + // &self, + // ty: ty::Ty<'tcx>, + // const_arguments: &[vir_high::Expression], + // ) -> SpannedEncodingResult; fn encode_place_type_high(&self, ty: mir::tcx::PlaceTy<'tcx>) -> EncodingResult; fn encode_enum_variant_index_high( @@ -77,13 +80,14 @@ pub(crate) trait MirTypeEncoderInterface<'tcx> { fn encode_type_def_high( &self, ty: &vir_high::Type, + with_invariant: bool, ) -> SpannedEncodingResult; - fn encode_adt_def( - &self, - adt_def: ty::AdtDef<'tcx>, - substs: ty::subst::SubstsRef<'tcx>, - variant_index: Option, - ) -> SpannedEncodingResult; + // fn encode_adt_def( + // &self, + // adt_def: ty::AdtDef<'tcx>, + // substs: ty::subst::SubstsRef<'tcx>, + // variant_index: Option, + // ) -> SpannedEncodingResult; fn encode_type_bounds_high( &self, var: &vir_high::Expression, @@ -114,7 +118,7 @@ impl<'v, 'tcx: 'v> MirTypeEncoderInterface<'tcx> for super::super::super::Encode vir::Field::new(name, vir::Type::typed_ref("")) } fn encode_discriminant_field(&self) -> vir::Field { - let name = "discriminant"; + let name = DISCRIMINANT_FIELD_NAME; vir::Field::new(name, vir::Type::Int) } fn encode_field( @@ -124,7 +128,7 @@ impl<'v, 'tcx: 'v> MirTypeEncoderInterface<'tcx> for super::super::super::Encode use_span: Option, declaration_span: Span, ) -> SpannedEncodingResult { - let type_decl = self.encode_type_def_high(ty)?; + let type_decl = self.encode_type_def_high(ty, false)?; let primary_span = if let Some(use_span) = use_span { use_span } else { @@ -224,15 +228,6 @@ impl<'v, 'tcx: 'v> MirTypeEncoderInterface<'tcx> for super::super::super::Encode Ok(const_parameters) } fn encode_type_high(&self, ty: ty::Ty<'tcx>) -> SpannedEncodingResult { - // FIXME: Remove encode_type_high_with_const_arguments because it is a - // failed attempt. - self.encode_type_high_with_const_arguments(ty, &[]) - } - fn encode_type_high_with_const_arguments( - &self, - ty: ty::Ty<'tcx>, - const_arguments: &[vir_high::Expression], - ) -> SpannedEncodingResult { if !self .mir_type_encoder_state .encoded_types @@ -240,7 +235,7 @@ impl<'v, 'tcx: 'v> MirTypeEncoderInterface<'tcx> for super::super::super::Encode .contains_key(ty.kind()) { let type_encoder = TypeEncoder::new(self, ty); - let encoded_type = type_encoder.encode_type(const_arguments)?; + let encoded_type = type_encoder.encode_type()?; assert!(self .mir_type_encoder_state .encoded_types @@ -251,15 +246,15 @@ impl<'v, 'tcx: 'v> MirTypeEncoderInterface<'tcx> for super::super::super::Encode // vir_high::Type type. However, this should not be the problem for // using the inverse because we care only between differences that // are not dropped in the translation. - self.mir_type_encoder_state - .encoded_types_inverse - .borrow_mut() - .insert(encoded_type.clone(), ty); - let encoded_type = encoded_type.erase_lifetimes().erase_const_generics(); self.mir_type_encoder_state .encoded_types_inverse .borrow_mut() .insert(encoded_type, ty); + // let encoded_type = encoded_type.erase_lifetimes().erase_const_generics(); + // self.mir_type_encoder_state + // .encoded_types_inverse + // .borrow_mut() + // .insert(encoded_type, ty); } let encoded_type = self.mir_type_encoder_state.encoded_types.borrow()[ty.kind()].clone(); Ok(encoded_type) @@ -347,6 +342,7 @@ impl<'v, 'tcx: 'v> MirTypeEncoderInterface<'tcx> for super::super::super::Encode fn encode_type_def_high( &self, ty: &vir_high::Type, + with_invariant: bool, ) -> SpannedEncodingResult { if !self .mir_type_encoder_state @@ -362,12 +358,15 @@ impl<'v, 'tcx: 'v> MirTypeEncoderInterface<'tcx> for super::super::super::Encode lifetimes, }) => { let encoded_enum = self - .encode_type_def_high(&vir_high::Type::enum_( - name.clone(), - arguments.clone(), - None, - lifetimes.clone(), - ))? + .encode_type_def_high( + &vir_high::Type::enum_( + name.clone(), + arguments.clone(), + None, + lifetimes.clone(), + ), + with_invariant, + )? .unwrap_enum(); vir_high::TypeDecl::Struct(encoded_enum.into_variant(&variant.index).unwrap()) } @@ -378,37 +377,45 @@ impl<'v, 'tcx: 'v> MirTypeEncoderInterface<'tcx> for super::super::super::Encode lifetimes, }) => { let encoded_union = self - .encode_type_def_high(&vir_high::Type::union_( - name.clone(), - arguments.clone(), - None, - lifetimes.clone(), - ))? + .encode_type_def_high( + &vir_high::Type::union_( + name.clone(), + arguments.clone(), + None, + lifetimes.clone(), + ), + with_invariant, + )? .unwrap_union(); vir_high::TypeDecl::Struct(encoded_union.into_variant(&variant.index).unwrap()) } _ => { let original_ty = self.decode_type_high(ty); let type_encoder = TypeEncoder::new(self, original_ty); - type_encoder.encode_type_def_high()? + type_encoder.encode_type_def_high(ty, with_invariant)? } }; - self.mir_type_encoder_state - .encoded_type_decls - .borrow_mut() - .insert(ty.clone(), encoded_type); + if with_invariant { + // Cache only the fully encoded version. + self.mir_type_encoder_state + .encoded_type_decls + .borrow_mut() + .insert(ty.clone(), encoded_type); + } else { + return Ok(encoded_type); + } } let encoded_type = self.mir_type_encoder_state.encoded_type_decls.borrow()[ty].clone(); Ok(encoded_type) } - fn encode_adt_def( - &self, - adt_def: ty::AdtDef<'tcx>, - substs: ty::subst::SubstsRef<'tcx>, - variant_index: Option, - ) -> SpannedEncodingResult { - super::encoder::encode_adt_def(self, adt_def, substs, variant_index) - } + // fn encode_adt_def( + // &self, + // adt_def: ty::AdtDef<'tcx>, + // substs: ty::subst::SubstsRef<'tcx>, + // variant_index: Option, + // ) -> SpannedEncodingResult { + // super::encoder::encode_adt_def(self, ty, adt_def, substs, variant_index) + // } fn encode_type_bounds_high( &self, var: &vir_high::Expression, diff --git a/prusti-viper/src/encoder/mirror_function_encoder.rs b/prusti-viper/src/encoder/mirror_function_encoder.rs index 1c09951cc30..49d2d8ef819 100644 --- a/prusti-viper/src/encoder/mirror_function_encoder.rs +++ b/prusti-viper/src/encoder/mirror_function_encoder.rs @@ -78,7 +78,7 @@ impl MirrorEncoder { function.posts.push(vir::Expr::InhaleExhale( vir::InhaleExhale { inhale_expr: Box::new(vir::Expr::eq_cmp( vir::Expr::local( - vir::LocalVar::new("__result", function.return_type.clone()), + vir::LocalVar::new(vir_crate::common::builtin_constants::RESULT_VARIABLE_NAME, function.return_type.clone()), ), vir::Expr::domain_func_app( mirror_func.clone(), diff --git a/prusti-viper/src/encoder/procedure_encoder.rs b/prusti-viper/src/encoder/procedure_encoder.rs index 34c8992a83c..44d65e471ce 100644 --- a/prusti-viper/src/encoder/procedure_encoder.rs +++ b/prusti-viper/src/encoder/procedure_encoder.rs @@ -149,7 +149,7 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { let init_info = InitInfo::new(mir, tcx, proc_def_id, &mir_encoder) .with_default_span(procedure.get_span())?; - let specification_blocks = SpecificationBlocks::build(encoder.env().query, mir, procedure, false); + let specification_blocks = SpecificationBlocks::build(encoder.env().query, mir, None, false); let cfg_method = vir::CfgMethod::new( // method name @@ -5320,7 +5320,9 @@ impl<'p, 'v: 'p, 'tcx: 'v> ProcedureEncoder<'p, 'v, 'tcx> { cl_substs, )?); let invariant = match spec { - prusti_interface::specs::typed::LoopSpecification::Invariant(inv) => inv, + // Poly does not distinguish between structural and + // non-structural loop invariants. + prusti_interface::specs::typed::LoopSpecification::Invariant{ def_id: inv, ..} => inv, _ => continue, }; encoded_spec_spans.push(self.encoder.env().tcx().def_span(invariant)); diff --git a/prusti-viper/src/encoder/purifier.rs b/prusti-viper/src/encoder/purifier.rs index bf8f073061f..a76fcbbfd84 100644 --- a/prusti-viper/src/encoder/purifier.rs +++ b/prusti-viper/src/encoder/purifier.rs @@ -4,7 +4,7 @@ // represent types like "snapshot of X". Resolve SnapOf in snapshot patcher. use rustc_hash::{FxHashMap, FxHashSet}; -use vir_crate::polymorphic::{self as vir, ExprFolder, ExprWalker, StmtFolder, StmtWalker}; +use vir_crate::{polymorphic::{self as vir, ExprFolder, ExprWalker, StmtFolder, StmtWalker}, common::builtin_constants::DISCRIMINANT_VARIABLE_NAME}; use crate::encoder::{high::types::HighTypeEncoderInterface, Encoder}; use log::{debug, trace}; @@ -495,7 +495,7 @@ impl ExprFolder for Purifier<'_, '_, '_> { position: *local_pos, }); let discriminant_func = vir::DomainFunc { - name: "discriminant$".to_string(), + name: DISCRIMINANT_VARIABLE_NAME.to_string(), type_arguments, formal_args: vec![local_var.clone()], return_type: vir::Type::Int, diff --git a/prusti-viper/src/encoder/snapshot/encoder.rs b/prusti-viper/src/encoder/snapshot/encoder.rs index 7f72c402ebc..e670d4ff190 100644 --- a/prusti-viper/src/encoder/snapshot/encoder.rs +++ b/prusti-viper/src/encoder/snapshot/encoder.rs @@ -26,7 +26,10 @@ use prusti_rustc_interface::{ use rustc_hash::FxHashMap; use std::rc::Rc; use vir_crate::{ - common::identifier::WithIdentifier, + common::{ + builtin_constants::{DISCRIMINANT_FIELD_NAME, DISCRIMINANT_VARIABLE_NAME}, + identifier::WithIdentifier, + }, polymorphic as vir, polymorphic::{ ContainerOpKind, Expr, ExprIterator, FallibleExprFolder, FallibleStmtFolder, @@ -313,7 +316,7 @@ impl SnapshotEncoder { let snapshot = self.decode_snapshot(encoder, expr.get_type())?; match (field.name.as_str(), snapshot) { ( - "discriminant", + DISCRIMINANT_FIELD_NAME, Snapshot::Complex { discriminant_func, .. }, @@ -1648,7 +1651,7 @@ impl SnapshotEncoder { // encode discriminant function let discriminant_func = vir::DomainFunc { - name: "discriminant$".to_string(), + name: DISCRIMINANT_VARIABLE_NAME.to_string(), type_arguments: vec![snapshot_type.clone()], formal_args: vec![arg_dom_local.clone()], return_type: Type::Int, diff --git a/prusti-viper/src/encoder/typed/to_middle/expression.rs b/prusti-viper/src/encoder/typed/to_middle/expression.rs index d75408523f3..fa9f7051fc0 100644 --- a/prusti-viper/src/encoder/typed/to_middle/expression.rs +++ b/prusti-viper/src/encoder/typed/to_middle/expression.rs @@ -1,7 +1,9 @@ use crate::encoder::errors::SpannedEncodingError; use vir_crate::{ middle as vir_mid, - middle::operations::{TypedToMiddleExpressionLowerer, TypedToMiddleType}, + middle::operations::{ + TypedToMiddleExpressionLowerer, TypedToMiddlePredicate, TypedToMiddleType, + }, typed as vir_typed, }; @@ -65,4 +67,11 @@ impl<'v, 'tcx> TypedToMiddleExpressionLowerer for crate::encoder::Encoder<'v, 't index: variant_index.index, }) } + + fn typed_to_middle_expression_predicate( + &self, + predicate: vir_typed::Predicate, + ) -> Result { + predicate.typed_to_middle_predicate(self) + } } diff --git a/prusti-viper/src/encoder/typed/to_middle/predicate.rs b/prusti-viper/src/encoder/typed/to_middle/predicate.rs index 385a20e09a4..a62f5ec4e73 100644 --- a/prusti-viper/src/encoder/typed/to_middle/predicate.rs +++ b/prusti-viper/src/encoder/typed/to_middle/predicate.rs @@ -30,4 +30,18 @@ impl<'v, 'tcx> TypedToMiddlePredicateLowerer for crate::encoder::Encoder<'v, 'tc name: lifetime_const.name, }) } + + fn typed_to_middle_predicate_trigger( + &self, + trigger: vir_typed::Trigger, + ) -> Result { + trigger.typed_to_middle_expression(self) + } + + fn typed_to_middle_predicate_variable_decl( + &self, + variable: vir_typed::VariableDecl, + ) -> Result { + variable.typed_to_middle_expression(self) + } } diff --git a/prusti-viper/src/encoder/typed/to_middle/statement.rs b/prusti-viper/src/encoder/typed/to_middle/statement.rs index 052d9ca9b73..cb93a3df895 100644 --- a/prusti-viper/src/encoder/typed/to_middle/statement.rs +++ b/prusti-viper/src/encoder/typed/to_middle/statement.rs @@ -4,7 +4,7 @@ use vir_crate::{ self as vir_mid, operations::{ TypedToMiddleExpression, TypedToMiddlePredicate, TypedToMiddleRvalue, - TypedToMiddleStatementLowerer, + TypedToMiddleStatementLowerer, TypedToMiddleType, }, }, typed as vir_typed, @@ -110,4 +110,160 @@ impl<'v, 'tcx> TypedToMiddleStatementLowerer for crate::encoder::Encoder<'v, 'tc ) -> Result { unreachable!("ObtainMutRef statement cannot be lowered"); } + + fn typed_to_middle_statement_statement_unpack( + &self, + _statement: vir_typed::Unpack, + ) -> Result { + unreachable!("Unpack statement cannot be lowered"); + } + + fn typed_to_middle_statement_statement_pack( + &self, + _statement: vir_typed::Pack, + ) -> Result { + unreachable!("Pack statement cannot be lowered"); + } + + fn typed_to_middle_statement_statement_obtain( + &self, + _: vir_typed::Obtain, + ) -> Result { + unreachable!("Obtain statement cannot be lowered"); + } + + fn typed_to_middle_statement_statement_forget_initialization( + &self, + _statement: vir_typed::ForgetInitialization, + ) -> Result { + unreachable!("ForgetInitialization statement cannot be lowered"); + } + + fn typed_to_middle_statement_statement_forget_initialization_range( + &self, + _statement: vir_typed::ForgetInitializationRange, + ) -> Result { + unreachable!("ForgetInitializationRange statement cannot be lowered"); + } + + fn typed_to_middle_statement_statement_split( + &self, + _statement: vir_typed::Split, + ) -> Result { + unreachable!("Split statement cannot be lowered"); + } + + fn typed_to_middle_statement_statement_join( + &self, + _statement: vir_typed::Join, + ) -> Result { + unreachable!("Join statement cannot be lowered"); + } + + fn typed_to_middle_statement_dead_reference( + &self, + statement: vir_typed::DeadReference, + ) -> Result { + let is_blocked_by_reborrow = match statement.is_blocked_by_reborrow { + Some(lifetime) => Some(lifetime.typed_to_middle_type(self)?), + None => None, + }; + Ok(vir_mid::statement::DeadReference { + target: statement.target.typed_to_middle_expression(self)?, + is_blocked_by_reborrow, + condition: None, + position: statement.position, + }) + } + + fn typed_to_middle_statement_copy_place( + &self, + _statement: vir_typed::CopyPlace, + ) -> Result { + unreachable!("CopyPlace statement cannot be automatically lowered"); + } + + fn typed_to_middle_statement_close_frac_ref( + &self, + _statement: vir_typed::CloseFracRef, + ) -> Result { + unreachable!("CloseFracRef statement cannot be automatically lowered"); + } + + fn typed_to_middle_statement_close_mut_ref( + &self, + _statement: vir_typed::CloseMutRef, + ) -> Result { + unreachable!("CloseMutRef statement cannot be automatically lowered"); + } + + fn typed_to_middle_statement_open_frac_ref( + &self, + _statement: vir_typed::OpenFracRef, + ) -> Result { + unreachable!("OpenFracRef statement cannot be automatically lowered"); + } + + fn typed_to_middle_statement_open_mut_ref( + &self, + _statement: vir_typed::OpenMutRef, + ) -> Result { + unreachable!("OpenMutRef statement cannot be automatically lowered"); + } + + fn typed_to_middle_statement_uniqueness( + &self, + uniqueness: vir_typed::ty::Uniqueness, + ) -> Result { + Ok(match uniqueness { + vir_typed::ty::Uniqueness::Shared => vir_mid::ty::Uniqueness::Shared, + vir_typed::ty::Uniqueness::Unique => vir_mid::ty::Uniqueness::Unique, + }) + } + + fn typed_to_middle_statement_restore_mut_borrowed( + &self, + statement: vir_typed::RestoreMutBorrowed, + ) -> Result { + Ok(vir_mid::statement::RestoreMutBorrowed { + lifetime: statement.lifetime.typed_to_middle_type(self)?, + place: statement + .referenced_place + .typed_to_middle_expression(self)?, + is_reborrow: false, + borrowing_place: Some( + statement + .referencing_place + .typed_to_middle_expression(self)?, + ), + condition: None, + position: statement.position, + }) + } + + fn typed_to_middle_statement_statement_encoding_action( + &self, + _action: vir_typed::EncodingAction, + ) -> Result { + unreachable!("EncodingAction statement must be desugared in high-level VIR"); + } + + fn typed_to_middle_statement_restore_raw_borrowed( + &self, + action: vir_typed::RestoreRawBorrowed, + ) -> Result { + Ok(vir_mid::statement::RestoreRawBorrowed { + borrowing_place: action.borrowing_place.typed_to_middle_expression(self)?, + restored_place: action.restored_place.typed_to_middle_expression(self)?, + condition: None, + position: action.position, + }) + } + + // fn typed_to_middle_statement_statement_restore( + // &self, + // _statement: vir_typed::Restore, + // ) -> Result { + // unreachable!("Restore statement cannot be lowered"); + // } } diff --git a/prusti-viper/src/encoder/typed/to_middle/ty.rs b/prusti-viper/src/encoder/typed/to_middle/ty.rs index 4232dfe347a..11c8147e95b 100644 --- a/prusti-viper/src/encoder/typed/to_middle/ty.rs +++ b/prusti-viper/src/encoder/typed/to_middle/ty.rs @@ -45,6 +45,9 @@ impl<'v, 'tcx> MiddleToTypedTypeUpperer for crate::encoder::Encoder<'v, 'tcx> { vir_mid::expression::ConstantValue::BigInt(value) => { vir_typed::expression::ConstantValue::BigInt(value) } + vir_mid::expression::ConstantValue::String(value) => { + vir_typed::expression::ConstantValue::String(value) + } vir_mid::expression::ConstantValue::Float(value) => { vir_typed::expression::ConstantValue::Float(value) } diff --git a/prusti-viper/src/encoder/typed/to_middle/type_decl.rs b/prusti-viper/src/encoder/typed/to_middle/type_decl.rs index b60bb88f41c..2895a20c736 100644 --- a/prusti-viper/src/encoder/typed/to_middle/type_decl.rs +++ b/prusti-viper/src/encoder/typed/to_middle/type_decl.rs @@ -75,4 +75,11 @@ impl<'v, 'tcx> TypedToMiddleTypeDeclLowerer for crate::encoder::Encoder<'v, 'tcx vir_typed::ty::EnumSafety::Union => vir_mid::ty::EnumSafety::Union, }) } + + fn typed_to_middle_type_decl_position( + &self, + position: vir_typed::Position, + ) -> Result { + Ok(position) + } } diff --git a/prusti-viper/src/lib.rs b/prusti-viper/src/lib.rs index 77d9f8a80c3..d50f74b5178 100644 --- a/prusti-viper/src/lib.rs +++ b/prusti-viper/src/lib.rs @@ -9,6 +9,7 @@ #![feature(try_blocks)] #![feature(never_type)] #![feature(btree_drain_filter)] +#![feature(hash_drain_filter)] #![feature(decl_macro)] #![feature(drain_filter)] #![feature(let_chains)] diff --git a/prusti-viper/src/verifier.rs b/prusti-viper/src/verifier.rs index 019abb0ed40..afd11822b70 100644 --- a/prusti-viper/src/verifier.rs +++ b/prusti-viper/src/verifier.rs @@ -28,7 +28,6 @@ use prusti_server::{ VerificationRequest, ViperBackendConfig, }; use viper::{self, PersistentCache, Viper}; -use vir_crate::common::check_mode::CheckMode; /// A verifier is an object for verifying a single crate, potentially /// many times. @@ -78,6 +77,10 @@ impl<'v, 'tcx> Verifier<'v, 'tcx> { let polymorphic_programs = self.encoder.get_viper_programs(); + if config::viper_backend() == "svirpti" { + return self.encoder.verify_core_proof_programs(); + } + let mut programs: Vec = if config::simplify_encoding() { stopwatch.start_next("optimizing Viper program"); let source_file_name = self.encoder.env().name.source_file_name(); @@ -224,16 +227,17 @@ fn verify_programs( .to_owned(); let verification_requests = programs.into_iter().map(|mut program| { let program_name = program.get_name().to_string(); - let check_mode = program.get_check_mode(); + // let check_mode = program.get_check_mode(); // Prepend the Rust file name to the program. program.set_name(format!("{rust_program_name}_{program_name}")); - let backend = if check_mode == CheckMode::Specifications { - config::verify_specifications_backend() - } else { - config::viper_backend() - } - .parse() - .unwrap(); + // let backend = if check_mode == CheckMode::PurificationFunctional { + // config::verify_specifications_backend() + // } else { + // config::viper_backend() + // } + // .parse() + // .unwrap(); + let backend = config::viper_backend().parse().unwrap(); let request = VerificationRequest { program, backend_config: ViperBackendConfig::new(backend), diff --git a/prusti/src/callbacks.rs b/prusti/src/callbacks.rs index f8018ade5ee..574df0ae60b 100644 --- a/prusti/src/callbacks.rs +++ b/prusti/src/callbacks.rs @@ -26,7 +26,7 @@ pub struct PrustiCompilerCalls; #[tracing::instrument(level = "debug", skip(tcx))] fn mir_borrowck<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> mir_borrowck<'tcx> { // *Don't take MIR bodies with borrowck info if we won't need them* - if !is_spec_fn(tcx, def_id.to_def_id()) { + if !is_spec_fn(tcx, def_id.to_def_id()) || config::unsafe_core_proof() { let def_kind = tcx.def_kind(def_id.to_def_id()); let is_anon_const = matches!(def_kind, DefKind::AnonConst); // Anon Const bodies have already been stolen and so will result in a crash diff --git a/scripts/helper_functions.py b/scripts/helper_functions.py index 299a7b6fca6..e6c48d3f190 100644 --- a/scripts/helper_functions.py +++ b/scripts/helper_functions.py @@ -176,6 +176,11 @@ def run_command(args, env=None, cwd=None, on_exit=None, report_time=False): if env is None: env = get_env() start_time = datetime.datetime.now() + # Make sure the command does not consume more than 4GB of memory on Linux. +# if sys.platform in ("linux", "linux2"): +# import resource +# limit = 8 * 1024 * 1024 * 1024 +# resource.setrlimit(resource.RLIMIT_AS, (limit, limit)) completed = subprocess.run(args, env=env, cwd=cwd, shell=(os.name == 'nt')) if report_time: print(datetime.datetime.now() - start_time) diff --git a/scripts/verify_test.py b/scripts/verify_test.py index 2d0acb5d99c..1e4e8016c30 100644 --- a/scripts/verify_test.py +++ b/scripts/verify_test.py @@ -142,7 +142,7 @@ def verify_test(args): report("Found test: {}", test_path) compile_flags = extract_test_compile_flags(test_path) env = get_env() - if 'prusti-tests/tests/verify_overflow/' in test_path: + if 'prusti-tests/tests/verify_overflow/' in test_path or 'prusti-tests/tests/verify_overflow_core_proof/' in test_path: env['PRUSTI_CHECK_OVERFLOWS'] = 'true' else: env['PRUSTI_CHECK_OVERFLOWS'] = 'false' diff --git a/smt-log-analyzer/src/lib.rs b/smt-log-analyzer/src/lib.rs index 2c8e8e6bcf2..000919ce219 100644 --- a/smt-log-analyzer/src/lib.rs +++ b/smt-log-analyzer/src/lib.rs @@ -34,7 +34,9 @@ pub struct Settings { fn process_line(settings: &Settings, state: &mut State, line: &str) -> Result<(), Error> { let mut parser = Parser::from_line(line); - match parser.parse_event_kind()? { + let event_kind = parser.parse_event_kind()?; + state.register_event_kind(event_kind); + match event_kind { EventKind::Pop => { let scopes_to_pop = parser.parse_number()?; let active_scopes_count = parser.parse_number()?; @@ -129,7 +131,13 @@ fn process_line(settings: &Settings, state: &mut State, line: &str) -> Result<() EventKind::Instance => { state.register_instance()?; } - EventKind::Unrecognized => {} + EventKind::DecideAndOr => { + let term_id = parser.parse_id()?; + let undef_child_id = parser.parse_id()?; + // FIXME: This information seems to be useless. + state.register_decide_and_or_term(term_id, undef_child_id); + } + _ => {} } Ok(()) } @@ -153,6 +161,7 @@ pub fn analyze( state.register_theory(parser::TheoryKind::Basic); state.register_theory(parser::TheoryKind::Datatype); state.register_theory(parser::TheoryKind::UserSort); + state.register_theory(parser::TheoryKind::PseudoBooleans); // Tracing triggers. state.mark_quantifier_for_tracing(settings.trace_quantifier_triggers); diff --git a/smt-log-analyzer/src/parser.rs b/smt-log-analyzer/src/parser.rs index 0e25e207ee5..cfc15d4ba89 100644 --- a/smt-log-analyzer/src/parser.rs +++ b/smt-log-analyzer/src/parser.rs @@ -4,6 +4,7 @@ use crate::{ }; use std::str::CharIndices; +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] pub(crate) enum EventKind { Pop, Push, @@ -15,6 +16,20 @@ pub(crate) enum EventKind { Unrecognized, AttachMeaning, MkVar, + ToolVersion, + AttachVarNames, + MkProof, + AttachEnode, + EndOfInstance, + MkLambda, + BeginCheck, + Assign, + EqExpl, + DecideAndOr, + ResolveLit, + ResolveProcess, + Conflict, + Eof, } #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] @@ -23,6 +38,7 @@ pub(crate) enum TheoryKind { Basic, Datatype, UserSort, + PseudoBooleans, } pub(crate) enum QuantTerm { @@ -137,11 +153,20 @@ impl<'a> Parser<'a> { "inst-discovered" => EventKind::InstDiscovered, "instance" => EventKind::Instance, "attach-meaning" => EventKind::AttachMeaning, - "tool-version" | "attach-var-names" | "mk-proof" | "attach-enode" - | "end-of-instance" | "mk-lambda" | "begin-check" | "assign" | "eq-expl" - | "decide-and-or" | "resolve-lit" | "resolve-process" | "conflict" | "eof" => { - EventKind::Unrecognized - } + "tool-version" => EventKind::ToolVersion, + "attach-var-names" => EventKind::AttachVarNames, + "mk-proof" => EventKind::MkProof, + "attach-enode" => EventKind::AttachEnode, + "end-of-instance" => EventKind::EndOfInstance, + "mk-lambda" => EventKind::MkLambda, + "begin-check" => EventKind::BeginCheck, + "assign" => EventKind::Assign, + "eq-expl" => EventKind::EqExpl, + "decide-and-or" => EventKind::DecideAndOr, + "resolve-lit" => EventKind::ResolveLit, + "resolve-process" => EventKind::ResolveProcess, + "conflict" => EventKind::Conflict, + "eof" => EventKind::Eof, x => unimplemented!("got: {:?}", x), }; self.consume(']')?; @@ -158,6 +183,7 @@ impl<'a> Parser<'a> { "arith" => TheoryKind::Arith, "datatype" => TheoryKind::Datatype, "user-sort" => TheoryKind::UserSort, + "pb" => TheoryKind::PseudoBooleans, _ => { eprintln!("kind: {kind:?}"); eprintln!("self; {:?}", self.error(ErrorKind::ConsumeFailed)); diff --git a/smt-log-analyzer/src/state.rs b/smt-log-analyzer/src/state.rs index c519d1ca99e..0f45fe254ac 100644 --- a/smt-log-analyzer/src/state.rs +++ b/smt-log-analyzer/src/state.rs @@ -1,10 +1,9 @@ -use csv::Writer; - use crate::{ error::Error, - parser::TheoryKind, + parser::{EventKind, TheoryKind}, types::{Level, QuantifierId, TermId, BUILTIN_QUANTIFIER_ID}, }; +use csv::Writer; use rustc_hash::{FxHashMap, FxHashSet}; use std::fmt::Write; @@ -84,6 +83,8 @@ struct LargestPop { pub(crate) struct State { quantifiers: FxHashMap, terms: FxHashMap, + /// Frequencies of each event kind. + event_kind_counters: FxHashMap, /// The currently matched quantifiers (via [new-match]) at a given level. quantifiers_matched_events: FxHashMap>, /// The currently discovered quantifiers (via [inst-discovered]) at a given level. @@ -116,9 +117,15 @@ pub(crate) struct State { current_active_scopes_count: Level, traced_quantifier: Option, traced_quantifier_triggers: Option, + decide_and_or_terms: Vec<(TermId, String, TermId, String)>, } impl State { + pub(crate) fn register_event_kind(&mut self, event_kind: EventKind) { + let entry = self.event_kind_counters.entry(event_kind).or_insert(0); + *entry += 1; + } + pub(crate) fn register_label(&mut self, label: String) { self.trace.push(BasicBlockVisitedEvent { level: self.current_active_scopes_count, @@ -298,6 +305,20 @@ impl State { .insert(term_id, Term::AttachMeaning { ident, value }); } + pub(crate) fn register_decide_and_or_term(&mut self, term_id: TermId, undef_child_id: TermId) { + let mut rendered_term = String::new(); + self.render_term(term_id, &mut rendered_term, 30).unwrap(); + let mut rendered_undef_child = String::new(); + self.render_term(undef_child_id, &mut rendered_undef_child, 30) + .unwrap(); + self.decide_and_or_terms.push(( + term_id, + rendered_term, + undef_child_id, + rendered_undef_child, + )); + } + pub(crate) fn active_scopes_count(&self) -> Level { self.current_active_scopes_count } @@ -450,6 +471,16 @@ impl State { } pub(crate) fn write_statistics(&self, input_file: &str) { + { + let mut writer = Writer::from_path(format!("{input_file}.event-kinds.csv")).unwrap(); + writer.write_record(["Event Kind", "Count"]).unwrap(); + for (event_kind, counter) in &self.event_kind_counters { + writer + .write_record([format!("{event_kind:?}"), counter.to_string()]) + .unwrap(); + } + } + { // [instance] – the number of quantifier instantiations. let mut writer = Writer::from_path(format!("{input_file}.instances.csv")).unwrap(); @@ -612,6 +643,29 @@ impl State { } } + { + // [decide-and-or] – Case splits. + let mut writer = Writer::from_path(format!("{input_file}.decide-and-or.csv")).unwrap(); + writer + .write_record([ + "TermId", + "Rendered Term", + "Undef child ID", + "Rendered undef child", + ]) + .unwrap(); + for (term_id, rendered_term, child_id, rendered_child) in &self.decide_and_or_terms { + writer + .write_record([ + &term_id.to_string(), + rendered_term, + &child_id.to_string(), + rendered_child, + ]) + .unwrap(); + } + } + { println!( "The largest number of quantifier matches removed in a single “pop {}” operation: {}", diff --git a/vir-gen/src/ast/mod.rs b/vir-gen/src/ast/mod.rs index fc7f44f82a1..21a6f09ab9f 100644 --- a/vir-gen/src/ast/mod.rs +++ b/vir-gen/src/ast/mod.rs @@ -50,6 +50,8 @@ pub(crate) struct DeriveLower { } pub(crate) struct CustomDeriveOptions { + /// The type of the trait that should be derived. + pub(crate) trait_type: syn::Type, /// The fields that should be ignored when deriving. pub(crate) ignored_fields: Vec, } diff --git a/vir-gen/src/deriver/lower.rs b/vir-gen/src/deriver/lower.rs index dcb97a15035..6437f06c59f 100644 --- a/vir-gen/src/deriver/lower.rs +++ b/vir-gen/src/deriver/lower.rs @@ -554,6 +554,20 @@ impl<'a> Deriver<'a> { ty.map(|element| self.#inner_method_name(*element).map(Box::new)).transpose() } } + } else if container_ident_first == "Option" && container_ident_second == "Vec" { + let inner_method_name = self.encode_name(inner_ident); + parse_quote! { + fn #method_name( + #self_parameter, + ty: #container_ident_first < #container_ident_second < #parameter_type > > + ) -> Result< #container_ident_first < #container_ident_second < #return_type > >, Self::Error> { + ty.map(|elements| + elements.into_iter().map(|element| { + self.#inner_method_name(element) + }).collect() + ).transpose() + } + } } else { unimplemented!( "first: {} second: {}", diff --git a/vir-gen/src/deriver/singles.rs b/vir-gen/src/deriver/singles.rs index 8fb3b6e8b62..6d9051ba506 100644 --- a/vir-gen/src/deriver/singles.rs +++ b/vir-gen/src/deriver/singles.rs @@ -31,11 +31,13 @@ pub(super) fn derive(items: &mut Vec) -> Result<(), syn::Error> { struct_item.fields.iter(), None, )), - CustomDerive::Hash(options) => derived_items.push(derive_hash( - &struct_item.ident, - struct_item.fields.iter(), - options, - )), + CustomDerive::Hash(options) => { + derived_items.push(derive_hash_for_struct( + &struct_item.ident, + struct_item.fields.iter(), + options, + )) + } CustomDerive::PartialEq(options) => { derived_items.push(derive_partial_eq( &struct_item.ident, @@ -80,8 +82,13 @@ pub(super) fn derive(items: &mut Vec) -> Result<(), syn::Error> { match derive { CustomDerive::New => unimplemented!(), CustomDerive::NewWithPos => unimplemented!(), - CustomDerive::Hash(_options) => { - derive_paths.push(syn::parse_quote! {Hash}); + CustomDerive::Hash(options) => { + // derive_paths.push(syn::parse_quote! {Hash}); + derived_items.push(derive_hash_for_enum( + &enum_item.ident, + enum_item.variants.iter(), + options, + )); } CustomDerive::PartialEq(_options) => { derive_paths.push(syn::parse_quote! {PartialEq}); @@ -165,7 +172,7 @@ fn derive_new<'a>( } } -fn derive_hash<'a>( +fn derive_hash_for_struct<'a>( struct_ident: &syn::Ident, fields_iter: impl Iterator, options: CustomDeriveOptions, @@ -179,8 +186,69 @@ fn derive_hash<'a>( }); } } + let trait_type = &options.trait_type; + parse_quote! { + impl #trait_type for #struct_ident { + #[allow(unused_variables)] + fn hash(&self, state: &mut H) { + #(#statements)* + } + } + } +} + +fn derive_hash_for_enum<'a>( + struct_ident: &syn::Ident, + variants_iter: impl Iterator, + options: CustomDeriveOptions, +) -> syn::Item { + let mut statements = Vec::::new(); + statements.push(parse_quote! { + let __self_tag = core::mem::discriminant(self); + }); + statements.push(parse_quote! { + ::core::hash::Hash::hash(&__self_tag, state); + }); + let trait_type = &options.trait_type; + let mut arms = Vec::::new(); + for variant in variants_iter { + let name = &variant.ident; + match &variant.fields { + syn::Fields::Named(named_fields) => { + unimplemented!("named fields not supported yet: {named_fields:?}") + } + syn::Fields::Unnamed(unnamed_fields) => { + let mut arm_idents = Vec::::new(); + let mut arm_statements: Vec = Vec::new(); + for (i, field) in unnamed_fields.unnamed.iter().enumerate() { + let name = syn::Ident::new(&format!("__self_value_{}", i), field.span()); + arm_statements.push(parse_quote! { + #trait_type::hash(#name, state); + }); + arm_idents.push(name); + } + arms.push(parse_quote! { + Self::#name(#(#arm_idents),*) => { + #(#arm_statements)* + } + }); + } + syn::Fields::Unit => { + arms.push(parse_quote! { + Self::#name => {} + }); + } + } + } + if !arms.is_empty() { + statements.push(parse_quote! { + match self { + #(#arms),* + } + }); + } parse_quote! { - impl core::hash::Hash for #struct_ident { + impl #trait_type for #struct_ident { #[allow(unused_variables)] fn hash(&self, state: &mut H) { #(#statements)* @@ -212,8 +280,9 @@ fn derive_partial_eq<'a>( #(#conjuncts)&&* } }; + let trait_type = &options.trait_type; parse_quote! { - impl PartialEq for #struct_ident { + impl #trait_type for #struct_ident { #[allow(unused_variables)] fn eq(&self, other: &Self) -> bool { #body @@ -245,8 +314,9 @@ fn derive_ord<'a>( } else { parse_quote! { std::cmp::Ordering::Equal } }; + let trait_type = &options.trait_type; parse_quote! { - impl Ord for #struct_ident { + impl #trait_type for #struct_ident { #[allow(unused_variables)] fn cmp(&self, other: &Self) -> std::cmp::Ordering { #comparison diff --git a/vir-gen/src/generator/to_tokens.rs b/vir-gen/src/generator/to_tokens.rs index 575bbf88a38..e2df6345b9d 100644 --- a/vir-gen/src/generator/to_tokens.rs +++ b/vir-gen/src/generator/to_tokens.rs @@ -10,8 +10,9 @@ use quote::{quote, ToTokens}; impl ToTokens for CustomDeriveOptions { fn to_tokens(&self, tokens: &mut TokenStream) { + let trait_type = &self.trait_type; let fields = &self.ignored_fields; - tokens.extend(quote! {ignore=[#(#fields),*]}) + tokens.extend(quote! {trait_type=#trait_type,ignore=[#(#fields),*]}) } } diff --git a/vir-gen/src/helpers.rs b/vir-gen/src/helpers.rs index 8e2196ca8cf..ea8ce74267b 100644 --- a/vir-gen/src/helpers.rs +++ b/vir-gen/src/helpers.rs @@ -32,7 +32,7 @@ pub fn prefixed_method_name_from_camel_raw(prefix: &str, ident: &syn::Ident) -> } } match new_ident.as_ref() { - "struct" | "enum" | "union" | "type" | "ref" | "move" => { + "struct" | "enum" | "union" | "type" | "ref" | "move" | "final" => { new_ident.push('_'); new_ident } diff --git a/vir-gen/src/lib.rs b/vir-gen/src/lib.rs index 8b5e7687739..5e251c8174a 100644 --- a/vir-gen/src/lib.rs +++ b/vir-gen/src/lib.rs @@ -81,7 +81,7 @@ pub fn generate_vir(defs_dir: &std::path::Path, out_dir: &std::path::Path) { // Write the tokens (and errors) to a tree of folders if !error_tokens.is_empty() { - unreachable!("{:?}", error_tokens); + unreachable!("{}", error_tokens); } let mut modules_tree = declarations.to_modules_tree(); error_tokens.to_tokens(&mut modules_tree.tokens); diff --git a/vir-gen/src/parser/ast.rs b/vir-gen/src/parser/ast.rs index 22e390f219f..df7abc92da0 100644 --- a/vir-gen/src/parser/ast.rs +++ b/vir-gen/src/parser/ast.rs @@ -12,6 +12,7 @@ mod kw { syn::custom_keyword!(Hash); syn::custom_keyword!(PartialEq); syn::custom_keyword!(Ord); + syn::custom_keyword!(trait_type); syn::custom_keyword!(ignore); } @@ -119,14 +120,24 @@ impl Parse for DeriveLower { impl Parse for CustomDeriveOptions { fn parse(input: ParseStream) -> syn::Result { + input.parse::()?; + input.parse::()?; + let trait_type = input.parse()?; + input.parse::()?; input.parse::()?; input.parse::()?; let content; bracketed!(content in input); - let fields = - syn::punctuated::Punctuated::<_, Token![,]>::parse_separated_nonempty(&content)?; + let ignored_fields = if content.is_empty() { + Vec::new() + } else { + let fields = + syn::punctuated::Punctuated::<_, Token![,]>::parse_separated_nonempty(&content)?; + fields.into_iter().collect() + }; Ok(Self { - ignored_fields: fields.into_iter().collect(), + trait_type, + ignored_fields, }) } } diff --git a/vir/defs/high/ast/expression.rs b/vir/defs/high/ast/expression.rs index fb685c77844..1723b1b53a6 100644 --- a/vir/defs/high/ast/expression.rs +++ b/vir/defs/high/ast/expression.rs @@ -1,6 +1,7 @@ pub(crate) use super::{ field::FieldDecl, position::Position, + predicate::Predicate, ty::{Type, VariantIndex}, variable::VariableDecl, }; @@ -21,6 +22,8 @@ pub enum Expression { Field(Field), /// A reference or pointer dereference. (Sometimes can fail.) Deref(Deref), + /// A reference or pointer dereference. (Sometimes can fail.) + Final(Final), /// The inverse of Deref. AddrOf(AddrOf), LabelledOld(LabelledOld), @@ -45,6 +48,12 @@ pub enum Expression { /// * field that encodes the variant // FIXME: Is downcast really needed? Isn't variant enough? Downcast(Downcast), + /// An accessibility predicate such as `own`. + AccPredicate(AccPredicate), + /// An unpacking of an accessibility predicate. + Unfolding(Unfolding), + /// `eval_in(predicate, argument)` expression. + EvalIn(EvalIn), } #[display(fmt = "{}", "variable.name")] @@ -85,6 +94,13 @@ pub struct Deref { pub position: Position, } +#[display(fmt = "{}.^", base)] +pub struct Final { + pub base: Box, + pub ty: Type, + pub position: Position, +} + #[display(fmt = "{}.&", base)] pub struct AddrOf { pub base: Box, @@ -111,6 +127,7 @@ pub enum ConstantValue { Int(i64), BigInt(String), Float(FloatConst), + String(String), /// All function pointers share the same constant, because their function /// is determined by the type system. FnPtr, @@ -233,6 +250,7 @@ pub enum BuiltinFunc { SnapshotEquality, Size, PaddingSize, + Align, Discriminant, LifetimeIncluded, LifetimeIntersect, @@ -249,6 +267,59 @@ pub enum BuiltinFunc { NewInt, Index, Len, + /// A ghost function for computing offset. + PtrAddressOffset, + /// Special-cased `wrapping_offset` function on pointers. + PtrWrappingOffset, + /// Special-cased `offset` function on pointers. + PtrOffset, + /// Special-cased `add` function on pointers. + PtrAdd, + /// A ghost inverse function for `PtrAddressOffset` that gives a distance in the number of elements. + PtrAddressOffsetFrom, + /// Special-cased `is_null` function on pointers. + PtrIsNull, + /// Express that two pointers point to the same allocation. + PtrSameAllocation, + /// Express that the pointer allocation is fresh. + PtrFreshAllocation, + /// Special-cased function whether a range of addresses contains another + /// address. + PtrRangeContains, + IsValid, // TODO: Delete. + EnsureOwnedPredicate, + // GetSnapshot, + /// Take the inner-most lifetime of a place. + TakeLifetime, + /// Read a single Byte from a sequence of Bytes. + ReadByte, + /// Retrieve the bytes of a memory block. + MemoryBlockBytes, + MemoryBlockBytesPtr, + /// Dereference a raw pointer at a given index. + DerefOwn, + /// Cast `*mut T` to `*const T`. + CastMutToConstPointer, + /// Change the type of the pointer. + CastPtrToPtr, + /// Cast from some integer type to another. + CastIntToInt, + BeforeExpiry, + AfterExpiry, + /// A function represents UniqueRef with its arguments not yet properly typed. + /// This version uses a lifetime identifier. + BuildingUniqueRefPredicate, + /// A function represents UniqueRef with its arguments not yet properly typed. This + /// version uses the real lifetime like `'a` instead of a lifetime identifier. + BuildingUniqueRefPredicateWithRealLifetime, + /// Same as `BuildingUniqueRefPredicateWithRealLifetime`, but for QPs. + BuildingUniqueRefPredicateRangeWithRealLifetime, + /// A function represents FracRef with its arguments not yet properly typed. + BuildingFracRefPredicate, + /// A function that signales whether allocation can fail. + AllocationNeverFails, + /// An opaque multiplication to avoid non-linear arithmetic. + Multiply, } #[display(fmt = "__builtin__{}({})", function, "display::cjoin(arguments)")] @@ -268,3 +339,42 @@ pub struct Downcast { pub field: FieldDecl, pub position: Position, } + +#[display(fmt = "acc({})", predicate)] +pub struct AccPredicate { + pub predicate: Box, + pub position: Position, +} + +#[display(fmt = "unfolding({}, {})", predicate, body)] +pub struct Unfolding { + pub predicate: Box, + pub body: Box, + pub position: Position, +} + +#[derive(Copy)] +pub enum EvalInContextKind { + /// The standard `eval_in(acc(...), ...)` context. + Predicate, + /// The standard `eval_in_quantified(acc(...), ...)` context. + QuantifiedPredicate, + /// Like `Predicate`, but the predicate is an opened ref predicate. + OpenedRefPredicate, + /// This is a `folding` expression generated by fold-unfold. + SafeConstructor, + // /// The `eval_in(old(...), ...)` context. + // Old, + // /// Like `Old`, but the predicate is an opened ref predicate. + // OldOpenedRefPredicate, +} + +#[display(fmt = "eval_in<{}>({}, {})", context_kind, context, body)] +pub struct EvalIn { + /// Either a predicate access predicate if evaluated in a current state or a + /// predicate access predicate wrapped in old if evaluated in an old state. + pub context: Box, + pub context_kind: EvalInContextKind, + pub body: Box, + pub position: Position, +} diff --git a/vir/defs/high/ast/function.rs b/vir/defs/high/ast/function.rs index bd52dccf969..f2a4bbe9d5c 100644 --- a/vir/defs/high/ast/function.rs +++ b/vir/defs/high/ast/function.rs @@ -8,7 +8,7 @@ use crate::common::display; "display::cjoin(parameters)", return_type, "display::foreach!(\" requires {}\n\", pres)", - "display::foreach!(\" ensures {}\n\", pres)", + "display::foreach!(\" ensures {}\n\", posts)", "display::option!(body, \"{{ {} }}\n\", \"\")" )] pub struct FunctionDecl { diff --git a/vir/defs/high/ast/mod.rs b/vir/defs/high/ast/mod.rs index 7d93ccce553..cbe37ba38cf 100644 --- a/vir/defs/high/ast/mod.rs +++ b/vir/defs/high/ast/mod.rs @@ -4,10 +4,10 @@ Clone, serde::Serialize, serde::Deserialize, - PartialEq(ignore=[position]), + PartialEq(trait_type=std::cmp::PartialEq,ignore=[position, lifetimes, lifetime]), Eq, - Hash(ignore=[position]), - Ord(ignore=[position]), + Hash(trait_type=core::hash::Hash,ignore=[position, lifetimes, lifetime]), + Ord(trait_type=std::cmp::Ord,ignore=[position, lifetimes, lifetime]), )] #![derive_for_all_structs(new, new_with_pos)] diff --git a/vir/defs/high/ast/predicate.rs b/vir/defs/high/ast/predicate.rs index a1c1f68fbe7..9bebc42bbdc 100644 --- a/vir/defs/high/ast/predicate.rs +++ b/vir/defs/high/ast/predicate.rs @@ -1,7 +1,13 @@ pub(crate) use super::super::{ - ast::{expression::Expression, position::Position, ty::LifetimeConst}, + ast::{ + expression::{Expression, Trigger}, + position::Position, + ty::LifetimeConst, + variable::VariableDecl, + }, operations_internal::ty::Typed, }; +use crate::common::display; #[derive_helpers] #[derive_visitors] @@ -11,8 +17,16 @@ pub enum Predicate { MemoryBlockStack(MemoryBlockStack), MemoryBlockStackDrop(MemoryBlockStackDrop), MemoryBlockHeap(MemoryBlockHeap), + MemoryBlockHeapRange(MemoryBlockHeapRange), + MemoryBlockHeapRangeGuarded(MemoryBlockHeapRangeGuarded), MemoryBlockHeapDrop(MemoryBlockHeapDrop), OwnedNonAliased(OwnedNonAliased), + OwnedRange(OwnedRange), + OwnedSet(OwnedSet), + UniqueRef(UniqueRef), + UniqueRefRange(UniqueRefRange), + FracRef(FracRef), + FracRefRange(FracRefRange), } #[display(fmt = "acc(LifetimeToken({}), {})", lifetime, permission)] @@ -60,6 +74,38 @@ pub struct MemoryBlockHeap { pub position: Position, } +#[display( + fmt = "MemoryBlockHeapRange({}, {}, {}, {})", + address, + size, + start_index, + end_index +)] +pub struct MemoryBlockHeapRange { + pub address: Expression, + pub size: Expression, + pub start_index: Expression, + pub end_index: Expression, + pub position: Position, +} + +#[display( + fmt = "MemoryBlockHeapRangeGuarded({}, {}, |{}| {}, triggers=[{}])", + address, + size, + index_variable, + guard, + "display::join(\"; \", triggers)" +)] +pub struct MemoryBlockHeapRangeGuarded { + pub address: Expression, + pub size: Expression, + pub index_variable: VariableDecl, + pub guard: Expression, + pub triggers: Vec, + pub position: Position, +} + /// A permission to deallocate a (precisely) matching `MemoryBlockHeap`. #[display(fmt = "MemoryBlockHeapDrop({}, {})", address, size)] pub struct MemoryBlockHeapDrop { @@ -74,3 +120,70 @@ pub struct OwnedNonAliased { pub place: Expression, pub position: Position, } + +/// A range of owned predicates of a specific type. `start_index` is inclusive +/// and `end_index` is exclusive. +#[display(fmt = "OwnedRange({}, {}, {})", address, start_index, end_index)] +pub struct OwnedRange { + pub address: Expression, + pub start_index: Expression, + pub end_index: Expression, + pub position: Position, +} + +/// A set of owned predicates of a specific type. +#[display(fmt = "OwnedSet({})", set)] +pub struct OwnedSet { + pub set: Expression, + pub position: Position, +} + +/// A unique reference predicate of a specific type. +#[display(fmt = "UniqueRef({}, {})", lifetime, place)] +pub struct UniqueRef { + pub lifetime: LifetimeConst, + pub place: Expression, + pub position: Position, +} + +/// A range of unique reference predicates of a specific type. `start_index` is +/// inclusive and `end_index` is exclusive. +#[display( + fmt = "UniqueRefRange({}, {}, {}, {})", + lifetime, + address, + start_index, + end_index +)] +pub struct UniqueRefRange { + pub lifetime: LifetimeConst, + pub address: Expression, + pub start_index: Expression, + pub end_index: Expression, + pub position: Position, +} + +/// A shared reference predicate of a specific type. +#[display(fmt = "FracRef({}, {})", lifetime, place)] +pub struct FracRef { + pub lifetime: LifetimeConst, + pub place: Expression, + pub position: Position, +} + +/// A range of shared reference predicates of a specific type. `start_index` is +/// inclusive and `end_index` is exclusive. +#[display( + fmt = "FracRefRange({}, {}, {}, {})", + lifetime, + address, + start_index, + end_index +)] +pub struct FracRefRange { + pub lifetime: LifetimeConst, + pub address: Expression, + pub start_index: Expression, + pub end_index: Expression, + pub position: Position, +} diff --git a/vir/defs/high/ast/rvalue.rs b/vir/defs/high/ast/rvalue.rs index 290182bbd9b..0a32fe5b72f 100644 --- a/vir/defs/high/ast/rvalue.rs +++ b/vir/defs/high/ast/rvalue.rs @@ -16,7 +16,7 @@ pub enum Rvalue { // ThreadLocalRef(ThreadLocalRef), AddressOf(AddressOf), Len(Len), - // Cast(Cast), + Cast(Cast), BinaryOp(BinaryOp), CheckedBinaryOp(CheckedBinaryOp), // NullaryOp(NullaryOp), @@ -66,6 +66,13 @@ pub struct Len { pub place: Expression, } +#[display(fmt = "cast({} -> {})", operand, ty)] +pub struct Cast { + // TODO: kind: CastKind, + pub operand: Operand, + pub ty: Type, +} + #[display(fmt = "{}({}, {})", kind, left, right)] pub struct BinaryOp { pub kind: BinaryOpKind, diff --git a/vir/defs/high/ast/statement.rs b/vir/defs/high/ast/statement.rs index e3fc011c1e3..ecdfc689c51 100644 --- a/vir/defs/high/ast/statement.rs +++ b/vir/defs/high/ast/statement.rs @@ -4,7 +4,7 @@ pub(crate) use super::{ position::Position, predicate::Predicate, rvalue::{Operand, Rvalue}, - ty::{LifetimeConst, Type}, + ty::{LifetimeConst, Type, Uniqueness}, variable::VariableDecl, }; use crate::common::display; @@ -17,13 +17,16 @@ use std::collections::BTreeSet; pub enum Statement { Comment(Comment), OldLabel(OldLabel), - Inhale(Inhale), - Exhale(Exhale), + InhalePredicate(InhalePredicate), + ExhalePredicate(ExhalePredicate), + InhaleExpression(InhaleExpression), + ExhaleExpression(ExhaleExpression), + Assume(Assume), + Assert(Assert), Consume(Consume), Havoc(Havoc), GhostHavoc(GhostHavoc), - Assume(Assume), - Assert(Assert), + HeapHavoc(HeapHavoc), LoopInvariant(LoopInvariant), MovePlace(MovePlace), CopyPlace(CopyPlace), @@ -33,8 +36,22 @@ pub enum Statement { GhostAssign(GhostAssign), LeakAll(LeakAll), SetUnionVariant(SetUnionVariant), + Pack(Pack), + Unpack(Unpack), + Obtain(Obtain), + Join(Join), + JoinRange(JoinRange), + Split(Split), + SplitRange(SplitRange), + StashRange(StashRange), + StashRangeRestore(StashRangeRestore), + ForgetInitialization(ForgetInitialization), + ForgetInitializationRange(ForgetInitializationRange), + RestoreRawBorrowed(RestoreRawBorrowed), NewLft(NewLft), EndLft(EndLft), + DeadReference(DeadReference), + DeadReferenceRange(DeadReferenceRange), DeadLifetime(DeadLifetime), DeadInclusion(DeadInclusion), LifetimeTake(LifetimeTake), @@ -44,7 +61,11 @@ pub enum Statement { OpenFracRef(OpenFracRef), CloseMutRef(CloseMutRef), CloseFracRef(CloseFracRef), + RestoreMutBorrowed(RestoreMutBorrowed), BorShorten(BorShorten), + MaterializePredicate(MaterializePredicate), + EncodingAction(EncodingAction), + CaseSplit(CaseSplit), } #[display(fmt = "// {}", comment)] @@ -59,16 +80,18 @@ pub struct OldLabel { pub position: Position, } -/// Inhale the permission denoted by the place. -#[display(fmt = "inhale {}", predicate)] -pub struct Inhale { +/// Inhale the permission denoted by the place. This operation is automatically +/// managed by fold-unfold. +#[display(fmt = "inhale-pred {}", predicate)] +pub struct InhalePredicate { pub predicate: Predicate, pub position: Position, } -#[display(fmt = "exhale {}", predicate)] -/// Exhale the permission denoted by the place. -pub struct Exhale { +#[display(fmt = "exhale-pred {}", predicate)] +/// Exhale the permission denoted by the place. This operation is automatically +/// managed by fold-unfold. +pub struct ExhalePredicate { pub predicate: Predicate, pub position: Position, } @@ -88,20 +111,47 @@ pub struct Havoc { } #[display(fmt = "ghost-havoc {}", variable)] +/// Havoc the local variable. pub struct GhostHavoc { pub variable: VariableDecl, pub position: Position, } +#[display(fmt = "heap-havoc")] +/// Havoc the heap. +pub struct HeapHavoc { + pub position: Position, +} + +#[display(fmt = "inhale-expr {}", expression)] +/// Inhale the boolean expression. This operation is ignored by fold-unfold. +pub struct InhaleExpression { + pub expression: Expression, + /// The label statement that immediatelly follows this inhale statement. Used + /// by `SelfFramingAssertionToSnapshot`. + pub label: Option, + pub position: Position, +} + +#[display(fmt = "exhale-expr {}", expression)] +/// Exhale the boolean expression. This operation is ignored by fold-unfold. +pub struct ExhaleExpression { + pub expression: Expression, + /// The label statement that immediatelly preceeds this exhale statement. + /// Used by `SelfFramingAssertionToSnapshot`. + pub label: Option, + pub position: Position, +} + #[display(fmt = "assume {}", expression)] -/// Assume the boolean expression. +/// Assume the pure boolean expression. pub struct Assume { pub expression: Expression, pub position: Position, } #[display(fmt = "assert {}", expression)] -/// Assert the boolean expression. +/// Assert the pure boolean expression. pub struct Assert { pub expression: Expression, pub position: Position, @@ -170,20 +220,11 @@ pub struct MovePlace { pub position: Position, } -#[display( - fmt = "copy{} {} ← {}", - "display::option!(source_permission, \"({})\", \"\")", - target, - source -)] +#[display(fmt = "copy {} ← {}", target, source)] /// Copy assignment. -/// -/// If `source_permission` is `None`, it means `write`. Otherwise, it is a -/// variable denoting the permission amount. pub struct CopyPlace { pub target: Expression, pub source: Expression, - pub source_permission: Option, pub position: Position, } @@ -252,6 +293,134 @@ pub struct SetUnionVariant { pub position: Position, } +#[derive_helpers] +#[derive(derive_more::From, derive_more::IsVariant, derive_more::Unwrap)] +pub enum PredicateKind { + Owned, + UniqueRef(UniqueRef), + FracRef(FracRef), +} + +pub struct UniqueRef { + pub lifetime: LifetimeConst, +} + +pub struct FracRef { + pub lifetime: LifetimeConst, +} + +#[display(fmt = "pack-{} {}", predicate_kind, place)] +pub struct Pack { + pub place: Expression, + pub predicate_kind: PredicateKind, + pub with_obligation: Option, + pub position: Position, +} + +#[display(fmt = "unpack-{} {}", predicate_kind, place)] +pub struct Unpack { + pub place: Expression, + pub predicate_kind: PredicateKind, + // Does it come with an obligation to pack? If yes, the permission amount it needs to return. + pub with_obligation: Option, + pub position: Position, +} + +#[display(fmt = "obtain-{} {}", predicate_kind, place)] +pub struct Obtain { + pub place: Expression, + pub predicate_kind: PredicateKind, + pub position: Position, +} + +#[display(fmt = "join {}", place)] +pub struct Join { + pub place: Expression, + pub position: Position, +} + +#[display(fmt = "join-range {} {} {}", address, start_index, end_index)] +pub struct JoinRange { + pub address: Expression, + pub start_index: Expression, + pub end_index: Expression, + pub position: Position, +} + +#[display(fmt = "split {}", place)] +pub struct Split { + pub place: Expression, + pub position: Position, +} + +#[display(fmt = "split-range {} {} {}", address, start_index, end_index)] +pub struct SplitRange { + pub address: Expression, + pub start_index: Expression, + pub end_index: Expression, + pub position: Position, +} + +#[display( + fmt = "stash-range {} {} {} {}", + address, + start_index, + end_index, + label +)] +pub struct StashRange { + pub address: Expression, + pub start_index: Expression, + pub end_index: Expression, + pub label: String, + pub position: Position, +} + +#[display( + fmt = "stash-range-restore {} {} {} {} → {} {}", + old_address, + old_start_index, + old_end_index, + old_label, + new_address, + new_start_index +)] +pub struct StashRangeRestore { + pub old_address: Expression, + pub old_start_index: Expression, + pub old_end_index: Expression, + pub old_label: String, + pub new_address: Expression, + pub new_start_index: Expression, + pub position: Position, +} + +#[display(fmt = "forget-initialization {}", place)] +pub struct ForgetInitialization { + pub place: Expression, + pub position: Position, +} + +#[display( + fmt = "forget-initialization-range {} {} {}", + address, + start_index, + end_index +)] +pub struct ForgetInitializationRange { + pub address: Expression, + pub start_index: Expression, + pub end_index: Expression, + pub position: Position, +} + +#[display(fmt = "restore {} --* {}", borrowing_place, restored_place)] +pub struct RestoreRawBorrowed { + pub borrowing_place: Expression, + pub restored_place: Expression, + pub position: Position, +} + #[display(fmt = "{} = newlft()", target)] pub struct NewLft { pub target: VariableDecl, @@ -264,6 +433,34 @@ pub struct EndLft { pub position: Position, } +#[display(fmt = "dead-reference({})", target)] +pub struct DeadReference { + pub target: Expression, + pub is_blocked_by_reborrow: Option, + pub position: Position, +} + +#[display( + fmt = "dead-reference-range {} {} {} {}", + lifetime, + address, + start_index, + end_index +)] +pub struct DeadReferenceRange { + pub lifetime: LifetimeConst, + pub uniqueness: Uniqueness, + pub address: Expression, + /// We need `predicate_range_start_index` and `predicate_range_end_index` to + /// be able to generate proper triggers: they are used to match the + /// `own_range!` predicate. + pub predicate_range_start_index: Expression, + pub predicate_range_end_index: Expression, + pub start_index: Expression, + pub end_index: Expression, + pub position: Position, +} + #[display(fmt = "dead-lifetime({})", lifetime)] pub struct DeadLifetime { pub lifetime: LifetimeConst, @@ -311,24 +508,27 @@ pub struct ObtainMutRef { } #[display( - fmt = "open_mut_ref({}, rd({}), {})", + fmt = "open_mut_ref({}, rd({}), {}, user-written: {})", lifetime, lifetime_token_permission, - place + place, + is_user_written )] pub struct OpenMutRef { pub lifetime: LifetimeConst, pub lifetime_token_permission: Expression, pub place: Expression, + pub is_user_written: bool, pub position: Position, } #[display( - fmt = "{} := open_frac_ref({}, rd({}), {})", + fmt = "{} := open_frac_ref({}, rd({}), {}, user-written: {})", predicate_permission_amount, lifetime, lifetime_token_permission, - place + place, + is_user_written )] pub struct OpenFracRef { pub lifetime: LifetimeConst, @@ -337,28 +537,32 @@ pub struct OpenFracRef { /// The permission amount taken from the token. pub lifetime_token_permission: Expression, pub place: Expression, + pub is_user_written: bool, pub position: Position, } #[display( - fmt = "close_mut_ref({}, rd({}), {})", + fmt = "close_mut_ref({}, rd({}), {}, user-written: {})", lifetime, lifetime_token_permission, - place + place, + is_user_written )] pub struct CloseMutRef { pub lifetime: LifetimeConst, pub lifetime_token_permission: Expression, pub place: Expression, + pub is_user_written: bool, pub position: Position, } #[display( - fmt = "close_frac_ref({}, rd({}), {}, {})", + fmt = "close_frac_ref({}, rd({}), {}, {}, user-written: {})", lifetime, lifetime_token_permission, place, - predicate_permission_amount + predicate_permission_amount, + is_user_written )] pub struct CloseFracRef { pub lifetime: LifetimeConst, @@ -367,6 +571,20 @@ pub struct CloseFracRef { pub place: Expression, /// The permission amount that we get for accessing `Owned`. pub predicate_permission_amount: VariableDecl, + pub is_user_written: bool, + pub position: Position, +} + +#[display( + fmt = "restore-mut-borrowed({} = &{} {})", + referencing_place, + lifetime, + referenced_place +)] +pub struct RestoreMutBorrowed { + pub lifetime: LifetimeConst, + pub referenced_place: Expression, + pub referencing_place: Expression, pub position: Position, } @@ -384,3 +602,38 @@ pub struct BorShorten { pub lifetime_token_permission: Expression, pub position: Position, } + +#[display(fmt = "materialize_predicate({}, {})", predicate, check_that_exists)] +pub struct MaterializePredicate { + pub predicate: Predicate, + /// Whether we should check that the predicate chunk actually exists. + /// `materialize_predicate!` corresponds to `true` and `quantified_predicate!` + /// corresponds to `false`. + pub check_that_exists: bool, + pub position: Position, +} + +#[derive_helpers] +#[derive_visitors] +#[derive(derive_more::From, derive_more::IsVariant, derive_more::Unwrap)] +pub enum Action { + EndLoan(EndLoan), +} + +pub struct EndLoan { + pub lifetime: LifetimeConst, +} + +/// An action to be performed when generating the encoding. +#[display(fmt = "encoding-action {}", action)] +pub struct EncodingAction { + pub action: Action, + pub position: Position, +} + +#[display(fmt = "case-split {}", expression)] +/// Case-split on a pure boolean expression. This operation is ignored by fold-unfold. +pub struct CaseSplit { + pub expression: Expression, + pub position: Position, +} diff --git a/vir/defs/high/ast/ty.rs b/vir/defs/high/ast/ty.rs index 52368a6eabb..4a1648aeb88 100644 --- a/vir/defs/high/ast/ty.rs +++ b/vir/defs/high/ast/ty.rs @@ -14,6 +14,10 @@ pub enum Type { MFloat64, /// Viper permission amount. MPerm, + /// Mathematical Byte. + MByte, + /// A sequence of mathematical Bytes. + MBytes, Lifetime, /// Rust's Bool allocated on the Viper heap. Bool, diff --git a/vir/defs/high/ast/type_decl.rs b/vir/defs/high/ast/type_decl.rs index f4768418715..324663d5acf 100644 --- a/vir/defs/high/ast/type_decl.rs +++ b/vir/defs/high/ast/type_decl.rs @@ -1,6 +1,7 @@ pub(crate) use super::{ expression::Expression, field::FieldDecl, + position::Position, ty::{GenericType, LifetimeConst, Type, Uniqueness}, variable::VariableDecl, }; @@ -70,7 +71,11 @@ pub struct Struct { pub name: String, pub lifetimes: Vec, pub const_parameters: Vec, + pub structural_invariant: Option>, pub fields: Vec, + /// The size of the struct if known at compile time. + pub size: Option, + pub position: Position, } pub type DiscriminantValue = i128; diff --git a/vir/defs/high/cfg/mod.rs b/vir/defs/high/cfg/mod.rs index 0d2a2709bf6..15b879db1e2 100644 --- a/vir/defs/high/cfg/mod.rs +++ b/vir/defs/high/cfg/mod.rs @@ -4,9 +4,9 @@ Clone, serde::Serialize, serde::Deserialize, - PartialEq(ignore=[position]), + PartialEq(trait_type=std::cmp::PartialEq,ignore=[position]), Eq, - Hash(ignore=[position]) + Hash(trait_type=core::hash::Hash,ignore=[position]) )] pub mod procedure; diff --git a/vir/defs/high/cfg/procedure.rs b/vir/defs/high/cfg/procedure.rs index 757598f4e19..4f3f46fc83b 100644 --- a/vir/defs/high/cfg/procedure.rs +++ b/vir/defs/high/cfg/procedure.rs @@ -1,5 +1,6 @@ use super::super::ast::{ expression::{Expression, Local}, + position::Position, statement::Statement, }; use crate::common::{check_mode::CheckMode, display}; @@ -14,9 +15,13 @@ use std::collections::BTreeMap; pub struct ProcedureDecl { pub name: String, pub check_mode: CheckMode, + /// Stack variables are by default non-aliased. This property is exploited + /// by optimizations. + pub non_aliased_places: Vec, pub entry: BasicBlockId, pub exit: BasicBlockId, pub basic_blocks: BTreeMap, + pub position: Position, } #[derive(PartialOrd, Ord, derive_more::Constructor, derive_more::AsRef)] diff --git a/vir/defs/high/mod.rs b/vir/defs/high/mod.rs index 8460b5e512f..939b241f9c6 100644 --- a/vir/defs/high/mod.rs +++ b/vir/defs/high/mod.rs @@ -5,25 +5,29 @@ pub(crate) mod operations_internal; pub use self::{ ast::{ expression::{ - self, visitors, AddrOf, BinaryOp, BinaryOpKind, BuiltinFunc, BuiltinFuncApp, - Conditional, Constant, Constructor, ContainerOp, Deref, Downcast, Expression, Field, - FuncApp, LabelledOld, LetExpr, Local, Quantifier, Seq, Trigger, UnaryOp, UnaryOpKind, - Variant, + self, visitors, AccPredicate, AddrOf, BinaryOp, BinaryOpKind, BuiltinFunc, + BuiltinFuncApp, Conditional, Constant, Constructor, ContainerOp, Deref, Downcast, + EvalIn, EvalInContextKind, Expression, Field, Final, FuncApp, LabelledOld, LetExpr, + Local, Quantifier, Seq, Trigger, UnaryOp, UnaryOpKind, Unfolding, Variant, }, field::FieldDecl, function::FunctionDecl, position::Position, predicate::{ LifetimeToken, MemoryBlockHeap, MemoryBlockHeapDrop, MemoryBlockStack, - MemoryBlockStackDrop, Predicate, + MemoryBlockStackDrop, OwnedNonAliased, Predicate, }, rvalue::{Operand, OperandKind, Rvalue}, statement::{ - Assert, Assign, Assume, BorShorten, CloseFracRef, CloseMutRef, Comment, Consume, - CopyPlace, DeadInclusion, DeadLifetime, EndLft, Exhale, GhostAssign, GhostHavoc, Havoc, - Inhale, LeakAll, LifetimeReturn, LifetimeTake, LoopInvariant, MovePlace, NewLft, - ObtainMutRef, OldLabel, OpenFracRef, OpenMutRef, SetUnionVariant, Statement, - WriteAddress, WritePlace, + Action, Assert, Assign, Assume, BorShorten, CaseSplit, CloseFracRef, CloseMutRef, + Comment, Consume, CopyPlace, DeadInclusion, DeadLifetime, DeadReference, + DeadReferenceRange, EncodingAction, EndLft, ExhaleExpression, ExhalePredicate, + ForgetInitialization, ForgetInitializationRange, FracRef, GhostAssign, GhostHavoc, + Havoc, HeapHavoc, InhaleExpression, InhalePredicate, Join, JoinRange, LeakAll, + LifetimeReturn, LifetimeTake, LoopInvariant, MaterializePredicate, MovePlace, NewLft, + Obtain, ObtainMutRef, OldLabel, OpenFracRef, OpenMutRef, Pack, PredicateKind, + RestoreMutBorrowed, RestoreRawBorrowed, SetUnionVariant, Split, SplitRange, StashRange, + StashRangeRestore, Statement, UniqueRef, Unpack, WriteAddress, WritePlace, }, ty::{self, Type}, type_decl::{self, DiscriminantRange, DiscriminantValue, TypeDecl}, diff --git a/vir/defs/high/operations_internal/const_generics/common.rs b/vir/defs/high/operations_internal/const_generics/common.rs index 05d243d6a62..2441234105b 100644 --- a/vir/defs/high/operations_internal/const_generics/common.rs +++ b/vir/defs/high/operations_internal/const_generics/common.rs @@ -41,6 +41,7 @@ impl WithConstArguments for Rvalue { Self::Repeat(value) => value.get_const_arguments(), Self::AddressOf(value) => value.get_const_arguments(), Self::Len(value) => value.get_const_arguments(), + Self::Cast(value) => value.get_const_arguments(), Self::BinaryOp(value) => value.get_const_arguments(), Self::CheckedBinaryOp(value) => value.get_const_arguments(), Self::UnaryOp(value) => value.get_const_arguments(), @@ -82,6 +83,14 @@ impl WithConstArguments for Len { } } +impl WithConstArguments for Cast { + fn get_const_arguments(&self) -> Vec { + let mut arguments = self.operand.get_const_arguments(); + arguments.extend(self.ty.get_const_arguments()); + arguments + } +} + impl WithConstArguments for BinaryOp { fn get_const_arguments(&self) -> Vec { let mut arguments = self.left.get_const_arguments(); diff --git a/vir/defs/high/operations_internal/expression.rs b/vir/defs/high/operations_internal/expression.rs index 72cdd733cdc..5dfa3e11165 100644 --- a/vir/defs/high/operations_internal/expression.rs +++ b/vir/defs/high/operations_internal/expression.rs @@ -2,17 +2,18 @@ use super::{ super::ast::{ expression::{ visitors::{ - default_fold_expression, default_fold_quantifier, default_walk_expression, - ExpressionFolder, ExpressionWalker, + default_fold_expression, default_fold_quantifier, default_walk_binary_op, + default_walk_expression, ExpressionFolder, ExpressionWalker, }, *, }, position::Position, + predicate::visitors::{PredicateFolder, PredicateWalker}, ty::{self, visitors::TypeFolder, LifetimeConst, Type}, }, ty::Typed, }; -use crate::common::expression::SyntacticEvaluation; +use crate::common::expression::{ExpressionIterator, SyntacticEvaluation, UnaryOperationHelpers}; use std::collections::BTreeMap; impl From for Expression { @@ -46,7 +47,7 @@ impl BinaryOpKind { impl Expression { /// Only defined for places. pub fn get_base(&self) -> VariableDecl { - debug_assert!(self.is_place()); + debug_assert!(self.is_place(), "{self} is not a place"); match self { Expression::Local(Local { variable, .. }) => variable.clone(), Expression::LabelledOld(LabelledOld { base, .. }) => base.get_base(), @@ -61,16 +62,69 @@ impl Expression { Expression::Variant(Variant { box ref base, .. }) | Expression::Field(Field { box ref base, .. }) | Expression::Deref(Deref { box ref base, .. }) - | Expression::AddrOf(AddrOf { box ref base, .. }) => Some(base), + | Expression::AddrOf(AddrOf { box ref base, .. }) + | Expression::EvalIn(EvalIn { + body: box ref base, .. + }) => Some(base), + Expression::LabelledOld(_) => None, + Expression::BuiltinFuncApp(BuiltinFuncApp { + function: BuiltinFunc::Index, + arguments, + .. + }) => Some(&arguments[0]), + expr => unreachable!("{}", expr), + } + } + pub fn get_parent_ref_step_into_old(&self) -> Option<&Expression> { + if let Expression::LabelledOld(LabelledOld { box ref base, .. }) = self { + Some(base) + } else { + self.get_parent_ref() + } + } + /// Peels only up to functions. + pub fn get_parent_ref_of_place_like(&self) -> Option<&Expression> { + match self { + Expression::Local(_) => None, + Expression::Variant(Variant { box ref base, .. }) + | Expression::Field(Field { box ref base, .. }) + | Expression::Deref(Deref { box ref base, .. }) + | Expression::AddrOf(AddrOf { box ref base, .. }) + | Expression::EvalIn(EvalIn { + body: box ref base, .. + }) => Some(base), Expression::LabelledOld(_) => None, Expression::BuiltinFuncApp(BuiltinFuncApp { function: BuiltinFunc::Index, arguments, .. }) => Some(&arguments[0]), + Expression::FuncApp(_) => None, expr => unreachable!("{}", expr), } } + /// Create a new place with the provided parent. + pub fn with_new_parent(&self, new_parent: Self) -> Self { + match self { + Expression::Variant(expression) => Expression::Variant(Variant { + base: Box::new(new_parent), + ..expression.clone() + }), + Expression::Field(expression) => Expression::Field(Field { + base: Box::new(new_parent), + ..expression.clone() + }), + Expression::Deref(expression) => Expression::Deref(Deref { + base: Box::new(new_parent), + ..expression.clone() + }), + Expression::AddrOf(expression) => Expression::AddrOf(AddrOf { + base: Box::new(new_parent), + ..expression.clone() + }), + _ => unreachable!("Cannot change parent for {}", self), + } + } /// Only defined for places. pub fn try_into_parent(self) -> Option { debug_assert!(self.is_place()); @@ -113,7 +167,8 @@ impl Expression { | Expression::Field(Field { base, .. }) | Expression::Deref(Deref { base, .. }) | Expression::AddrOf(AddrOf { base, .. }) - | Expression::LabelledOld(LabelledOld { base, .. }) => base.is_place(), + | Expression::LabelledOld(LabelledOld { base, .. }) + | Expression::EvalIn(EvalIn { body: base, .. }) => base.is_place(), Expression::BuiltinFuncApp(BuiltinFuncApp { function: BuiltinFunc::Index, arguments, @@ -122,6 +177,69 @@ impl Expression { _ => false, } } + /// Returns all places: + /// + /// * If a place is not inside `old`, then it is returned as it is. + /// * If a place is inside `old`, then it is returned as `old(variable)`. + /// * If a local is a quantified variable, then ignores it. + pub fn collect_all_places_with_old_locals(&self) -> Vec { + struct Collector { + // We use `Vec` instead of `HashSet` to make sure we are + // deterministic. + places: Vec, + old_label: Option, + stack: super::quantifiers::BoundVariableStack, + } + impl ExpressionWalker for Collector { + fn walk_expression(&mut self, expression: &Expression) { + if self.old_label.is_none() && expression.is_place() { + self.places.push(expression.clone()); + } else { + default_walk_expression(self, expression) + } + } + fn walk_local(&mut self, local: &Local) { + if self.stack.contains(&local.variable) { + return; + } + let Some(label) = &self.old_label else { + unreachable!("something went wrong; this should be reachable only with old set"); + }; + let position = local.position; + self.places.push(Expression::labelled_old( + label.clone(), + Expression::Local(local.clone()), + position, + )); + } + fn walk_predicate(&mut self, predicate: &Predicate) { + PredicateWalker::walk_predicate(self, predicate) + } + fn walk_labelled_old(&mut self, labelled_old: &LabelledOld) { + let old_label = + std::mem::replace(&mut self.old_label, Some(labelled_old.label.clone())); + ExpressionWalker::walk_expression(self, &labelled_old.base); + self.old_label = old_label; + } + fn walk_quantifier_enum(&mut self, quantifier: &Quantifier) { + self.stack.push(&quantifier.variables); + self.walk_quantifier(quantifier); + self.stack.pop(); + } + } + impl PredicateWalker for Collector { + fn walk_expression(&mut self, expr: &Expression) { + ExpressionWalker::walk_expression(self, expr) + } + } + let mut collector = Collector { + places: Vec::new(), + old_label: None, + stack: Default::default(), + }; + ExpressionWalker::walk_expression(&mut collector, self); + collector.places + } /// Check whether the place is a dereference of a reference and if that is /// the case, returns the uniqueness guarantees given by this reference. pub fn get_dereference_kind(&self) -> Option<(ty::LifetimeConst, ty::Uniqueness)> { @@ -138,7 +256,7 @@ impl Expression { { return Some((lifetime.clone(), *uniqueness)); } else { - unreachable!(); + return None; } } } @@ -180,8 +298,8 @@ impl Expression { } } - /// Check whether the place is a dereference of a reference and if that is - /// the case, return its base. + /// Check whether the place is a dereference if that is the case, return its + /// base. pub fn get_dereference_base(&self) -> Option<&Expression> { assert!(self.is_place()); if let Expression::Deref(Deref { box base, .. }) = self { @@ -193,6 +311,161 @@ impl Expression { } } + /// Check whether the place is a dereference of a reference and if that is + /// the case, return its base. + pub fn get_last_dereferenced_reference(&self) -> Option<&Expression> { + assert!(self.is_place()); + if let Expression::Deref(Deref { box base, .. }) = self { + if let Type::Reference(_) = base.get_type() { + Some(base) + } else { + base.get_last_dereferenced_reference() + } + } else if let Some(parent) = self.get_parent_ref() { + parent.get_last_dereferenced_reference() + } else { + None + } + } + + pub fn drop_last_reference_dereference(self) -> Self { + assert!(self.is_place()); + struct Folder { + found_reference_dereference: bool, + } + impl ExpressionFolder for Folder { + fn fold_deref_enum(&mut self, expr: Deref) -> Expression { + if self.found_reference_dereference { + Expression::Deref(expr) + } else { + self.found_reference_dereference = true; + *expr.base + } + } + } + let mut folder = Folder { + found_reference_dereference: false, + }; + let result = folder.fold_expression(self); + assert!(folder.found_reference_dereference); + result + } + + /// Same as `get_last_dereferenced_reference`, just returns the first + /// reference. + pub fn get_first_dereferenced_reference(&self) -> Option<&Expression> { + assert!(self.is_place()); + if let Expression::Deref(Deref { box base, .. }) = self { + let parent_ref = base.get_first_dereferenced_reference(); + if parent_ref.is_some() { + parent_ref + } else if let Type::Reference(_) = base.get_type() { + Some(base) + } else { + None + } + } else if let Some(parent) = self.get_parent_ref() { + parent.get_first_dereferenced_reference() + } else { + None + } + } + + pub fn is_behind_pointer_dereference(&self) -> bool { + if let Some(parent) = self.get_parent_ref_of_place_like() { + if self.is_deref() && parent.get_type().is_pointer() { + return true; + } + parent.is_behind_pointer_dereference() + } else { + false + } + } + + pub fn get_last_dereference(&self) -> Option<&Deref> { + if let Expression::Deref(deref) = self { + return Some(deref); + } + if let Some(parent) = self.get_parent_ref_of_place_like() { + parent.get_last_dereference() + } else { + None + } + } + + pub fn get_last_dereferenced_pointer(&self) -> Option<&Expression> { + if let Some(parent) = self.get_parent_ref_of_place_like() { + if self.is_deref() && parent.get_type().is_pointer() { + return Some(parent); + } + parent.get_last_dereferenced_pointer() + } else { + None + } + } + + pub fn get_first_dereferenced_pointer(&self) -> Option<&Expression> { + assert!(self.is_place()); + if let Some(last_pointer) = self.get_last_dereferenced_pointer() { + if let Some(parent) = last_pointer.get_first_dereferenced_pointer() { + Some(parent) + } else { + Some(last_pointer) + } + } else { + None + } + } + + pub fn get_deref_uniqueness(&self) -> Option { + assert!(self.is_place()); + if let Some(parent) = self.get_parent_ref() { + let parent_uniqueness = parent.get_deref_uniqueness(); + if self.is_deref() { + if let Type::Reference(ty::Reference { uniqueness, .. }) = parent.get_type() { + if let Some(parent_uniqueness) = parent_uniqueness { + if parent_uniqueness == ty::Uniqueness::Shared { + return Some(parent_uniqueness); + } + } + return Some(*uniqueness); + } + } + parent_uniqueness + } else { + None + } + } + + pub fn get_last_old_label(&self) -> Option<&Expression> { + assert!(self.is_place()); + if self.is_labelled_old() { + return Some(self); + } + if let Some(parent) = self.get_parent_ref() { + parent.get_last_old_label() + } else { + None + } + } + + pub fn get_first_old_label(&self) -> Option<&Expression> { + assert!(self.is_place()); + if let Some(last_old) = self.get_last_old_label() { + if let Some(parent) = last_old.get_parent_ref() { + if let Some(first_old) = parent.get_first_old_label() { + Some(first_old) + } else { + Some(last_old) + } + } else { + Some(last_old) + } + } else { + None + } + } + #[must_use] pub fn erase_lifetime(self) -> Expression { struct DefaultLifetimeEraser {} @@ -218,6 +491,22 @@ impl Expression { DefaultLifetimeEraser {}.fold_expression(self) } + pub fn check_no_erased_lifetime(&self) { + struct LifetimeChecker {} + impl ExpressionWalker for LifetimeChecker { + fn walk_type(&mut self, ty: &Type) { + ty.check_no_erased_lifetime(); + } + fn walk_variable_decl(&mut self, variable_decl: &VariableDecl) { + variable_decl.ty.check_no_erased_lifetime(); + } + fn walk_field_decl(&mut self, field_decl: &FieldDecl) { + field_decl.ty.check_no_erased_lifetime(); + } + } + LifetimeChecker {}.walk_expression(self); + } + #[must_use] pub fn replace_lifetimes( self, @@ -322,12 +611,29 @@ impl Expression { default_fold_expression(self, expression) } } + fn fold_predicate(&mut self, predicate: Predicate) -> Predicate { + PredicateFolder::fold_predicate(self, predicate) + } + fn fold_trigger(&mut self, trigger: Trigger) -> Trigger { + Trigger::new( + trigger + .terms + .into_iter() + .map(|term| ExpressionFolder::fold_expression(self, term)) + .collect::>(), + ) + } + } + impl<'a> PredicateFolder for PlaceReplacer<'a> { + fn fold_expression(&mut self, expression: Expression) -> Expression { + ExpressionFolder::fold_expression(self, expression) + } } let mut replacer = PlaceReplacer { target, replacement, }; - replacer.fold_expression(self) + ExpressionFolder::fold_expression(&mut replacer, self) } #[must_use] pub fn replace_multiple_places(self, replacements: &[(Expression, Expression)]) -> Self { @@ -353,8 +659,8 @@ impl Expression { // (1) skip replacements where `src` uses a quantified variable; // (2) rename with a fresh name the quantified variables that conflict with `dst`. for (src, dst) in self.replacements.iter() { - if quantifier.variables.contains(&src.get_base()) - || quantifier.variables.contains(&dst.get_base()) + if src.any_variable(|variable| quantifier.variables.contains(&variable)) + || dst.any_variable(|variable| quantifier.variables.contains(&variable)) { unimplemented!( "replace_multiple_places doesn't handle replacements that conflict \ @@ -364,8 +670,76 @@ impl Expression { } Expression::Quantifier(default_fold_quantifier(self, quantifier)) } + + fn fold_predicate(&mut self, predicate: Predicate) -> Predicate { + PredicateFolder::fold_predicate(self, predicate) + } + + fn fold_trigger(&mut self, trigger: Trigger) -> Trigger { + Trigger::new( + trigger + .terms + .into_iter() + .map(|term| ExpressionFolder::fold_expression(self, term)) + .collect::>(), + ) + } + } + impl<'a> PredicateFolder for PlaceReplacer<'a> { + fn fold_expression(&mut self, expression: Expression) -> Expression { + ExpressionFolder::fold_expression(self, expression) + } + } + let mut replacer = PlaceReplacer { replacements }; + ExpressionFolder::fold_expression(&mut replacer, self) + } + #[must_use] + pub fn replace_self(self, replacement: &Expression) -> Self { + struct PlaceReplacer<'a> { + replacement: &'a Expression, + } + impl<'a> ExpressionFolder for PlaceReplacer<'a> { + fn fold_local_enum(&mut self, local: Local) -> Expression { + if local.variable.is_self_variable() { + assert_eq!( + &local.variable.ty, + self.replacement.get_type(), + "{} → {}", + local.variable.ty, + self.replacement + ); + self.replacement.clone() + } else { + Expression::Local(local) + } + } + fn fold_predicate(&mut self, predicate: Predicate) -> Predicate { + PredicateFolder::fold_predicate(self, predicate) + } + + fn fold_trigger(&mut self, trigger: Trigger) -> Trigger { + Trigger::new( + trigger + .terms + .into_iter() + .map(|term| ExpressionFolder::fold_expression(self, term)) + .collect::>(), + ) + } + } + impl<'a> PredicateFolder for PlaceReplacer<'a> { + fn fold_expression(&mut self, expression: Expression) -> Expression { + ExpressionFolder::fold_expression(self, expression) + } + } + let mut replacer = PlaceReplacer { replacement }; + ExpressionFolder::fold_expression(&mut replacer, self) + } + pub fn peel_unfoldings(&self) -> &Self { + match self { + Expression::Unfolding(unfolding) => unfolding.body.peel_unfoldings(), + _ => self, } - PlaceReplacer { replacements }.fold_expression(self) } #[must_use] pub fn map_old_expression_label(self, substitutor: F) -> Self @@ -396,8 +770,29 @@ impl Expression { position, } } + fn fold_predicate(&mut self, predicate: Predicate) -> Predicate { + PredicateFolder::fold_predicate(self, predicate) + } + fn fold_trigger(&mut self, trigger: Trigger) -> Trigger { + Trigger::new( + trigger + .terms + .into_iter() + .map(|term| ExpressionFolder::fold_expression(self, term)) + .collect::>(), + ) + } + } + impl PredicateFolder for OldExpressionLabelSubstitutor + where + T: Fn(String) -> String, + { + fn fold_expression(&mut self, expression: Expression) -> Expression { + ExpressionFolder::fold_expression(self, expression) + } } - OldExpressionLabelSubstitutor { substitutor }.fold_expression(self) + let mut substitutor = OldExpressionLabelSubstitutor { substitutor }; + ExpressionFolder::fold_expression(&mut substitutor, self) } /// Simplify `Deref(AddrOf(P))` to `P`. #[must_use] @@ -421,6 +816,43 @@ impl Expression { } Simplifier.fold_expression(self) } + /// Simplify `construtor(arg1, arg2, ..., argn).field_k` to `argk`. + pub fn simplify_out_constructors(self) -> Self { + struct Simplifier; + impl ExpressionFolder for Simplifier { + fn fold_expression(&mut self, expression: Expression) -> Expression { + match expression { + Expression::Field(Field { + base: box Expression::Constructor(constructor), + field, + position: _, + }) => ExpressionFolder::fold_expression( + self, + constructor.arguments[field.index].clone(), + ), + _ => default_fold_expression(self, expression), + } + } + fn fold_predicate(&mut self, predicate: Predicate) -> Predicate { + PredicateFolder::fold_predicate(self, predicate) + } + fn fold_trigger(&mut self, trigger: Trigger) -> Trigger { + Trigger::new( + trigger + .terms + .into_iter() + .map(|term| ExpressionFolder::fold_expression(self, term)) + .collect::>(), + ) + } + } + impl PredicateFolder for Simplifier { + fn fold_expression(&mut self, expression: Expression) -> Expression { + ExpressionFolder::fold_expression(self, expression) + } + } + ExpressionFolder::fold_expression(&mut Simplifier, self) + } fn apply_simplification_rules(self) -> Self { let mut expression = self; loop { @@ -529,8 +961,25 @@ impl Expression { let expression = default_fold_expression(self, expression); expression.apply_simplification_rules() } + fn fold_predicate(&mut self, predicate: Predicate) -> Predicate { + PredicateFolder::fold_predicate(self, predicate) + } + fn fold_trigger(&mut self, trigger: Trigger) -> Trigger { + Trigger::new( + trigger + .terms + .into_iter() + .map(|term| ExpressionFolder::fold_expression(self, term)) + .collect::>(), + ) + } } - Simplifier.fold_expression(self) + impl PredicateFolder for Simplifier { + fn fold_expression(&mut self, expression: Expression) -> Expression { + ExpressionFolder::fold_expression(self, expression) + } + } + ExpressionFolder::fold_expression(&mut Simplifier, self) } pub fn find(&self, sub_target: &Expression) -> bool { pub struct ExprFinder<'a> { @@ -545,13 +994,26 @@ impl Expression { default_walk_expression(self, expr) } } + fn walk_predicate(&mut self, predicate: &Predicate) { + PredicateWalker::walk_predicate(self, predicate) + } + fn walk_trigger(&mut self, trigger: &Trigger) { + for term in &trigger.terms { + ExpressionWalker::walk_expression(self, term) + } + } + } + impl<'a> PredicateWalker for ExprFinder<'a> { + fn walk_expression(&mut self, expr: &Expression) { + ExpressionWalker::walk_expression(self, expr) + } } let mut finder = ExprFinder { sub_target, found: false, }; - finder.walk_expression(self); + ExpressionWalker::walk_expression(&mut finder, self); finder.found } pub fn function_call>( @@ -686,4 +1148,274 @@ impl Expression { pub fn full_permission() -> Self { Self::constant_no_pos(ConstantValue::Int(1), Type::MPerm) } + + pub fn is_pure(&self) -> bool { + struct Checker { + is_pure: bool, + } + impl ExpressionWalker for Checker { + fn walk_acc_predicate(&mut self, _: &AccPredicate) { + self.is_pure = false; + } + fn walk_eval_in(&mut self, eval_in: &EvalIn) { + self.walk_expression(&eval_in.body); + } + } + let mut checker = Checker { is_pure: true }; + checker.walk_expression(self); + checker.is_pure + } + + pub fn contains_result(&self) -> bool { + struct Checker { + contains_result: bool, + } + impl ExpressionWalker for Checker { + fn walk_variable_decl(&mut self, variable: &VariableDecl) { + if variable.is_result_variable() { + self.contains_result = true; + } + } + fn walk_eval_in(&mut self, eval_in: &EvalIn) { + self.walk_expression(&eval_in.body); + } + } + let mut checker = Checker { + contains_result: false, + }; + checker.walk_expression(self); + checker.contains_result + } +} + +/// Methods for collecting places. +impl Expression { + /// Returns place used in `own`. + pub fn collect_owned_places(&self) -> (Vec, Vec) { + struct Collector { + owned_places: Vec, + owned_range_addresses: Vec, + } + impl<'a> ExpressionWalker for Collector { + fn walk_acc_predicate(&mut self, acc_predicate: &AccPredicate) { + match &*acc_predicate.predicate { + Predicate::LifetimeToken(_) + | Predicate::MemoryBlockStack(_) + | Predicate::MemoryBlockStackDrop(_) + | Predicate::MemoryBlockHeap(_) + | Predicate::MemoryBlockHeapRange(_) + | Predicate::MemoryBlockHeapRangeGuarded(_) + | Predicate::MemoryBlockHeapDrop(_) => {} + Predicate::OwnedNonAliased(predicate) => { + self.owned_places.push(predicate.place.clone()); + } + Predicate::OwnedRange(predicate) => { + self.owned_range_addresses.push(predicate.address.clone()); + } + Predicate::OwnedSet(predicate) => { + unimplemented!("predicate: {}", predicate); + } + Predicate::UniqueRef(predicate) => { + self.owned_places.push(predicate.place.clone()); + } + Predicate::UniqueRefRange(predicate) => { + self.owned_range_addresses.push(predicate.address.clone()); + } + Predicate::FracRef(predicate) => { + self.owned_places.push(predicate.place.clone()); + } + Predicate::FracRefRange(predicate) => { + self.owned_range_addresses.push(predicate.address.clone()); + } + } + } + } + let mut collector = Collector { + owned_places: Vec::new(), + owned_range_addresses: Vec::new(), + }; + collector.walk_expression(self); + (collector.owned_places, collector.owned_range_addresses) + } + + /// Returns places used in `own` with path conditions that guard them. + pub fn collect_guarded_owned_places(&self) -> Vec<(Expression, Expression)> { + struct Collector { + path_condition: Vec, + owned_places: Vec<(Expression, Expression)>, + } + impl<'a> ExpressionWalker for Collector { + fn walk_acc_predicate(&mut self, acc_predicate: &AccPredicate) { + match &*acc_predicate.predicate { + Predicate::LifetimeToken(_) + | Predicate::MemoryBlockStack(_) + | Predicate::MemoryBlockStackDrop(_) + | Predicate::MemoryBlockHeap(_) + | Predicate::MemoryBlockHeapRange(_) + | Predicate::MemoryBlockHeapRangeGuarded(_) + | Predicate::MemoryBlockHeapDrop(_) => {} + Predicate::OwnedNonAliased(predicate) => { + self.owned_places.push(( + self.path_condition.iter().cloned().conjoin(), + predicate.place.clone(), + )); + } + Predicate::OwnedRange(predicate) => { + unimplemented!("predicate: {}", predicate); + } + Predicate::OwnedSet(predicate) => { + unimplemented!("predicate: {}", predicate); + } + Predicate::UniqueRef(predicate) => { + self.owned_places.push(( + self.path_condition.iter().cloned().conjoin(), + predicate.place.clone(), + )); + } + Predicate::UniqueRefRange(predicate) => { + unimplemented!("predicate: {}", predicate); + } + Predicate::FracRef(predicate) => { + self.owned_places.push(( + self.path_condition.iter().cloned().conjoin(), + predicate.place.clone(), + )); + } + Predicate::FracRefRange(predicate) => { + unimplemented!("predicate: {}", predicate); + } + } + } + fn walk_binary_op(&mut self, binary_op: &BinaryOp) { + if binary_op.op_kind == BinaryOpKind::Implies { + self.path_condition.push((*binary_op.left).clone()); + self.walk_expression(&binary_op.right); + self.path_condition.pop(); + } else { + default_walk_binary_op(self, binary_op); + } + } + fn walk_conditional(&mut self, conditional: &Conditional) { + self.path_condition.push((*conditional.guard).clone()); + self.walk_expression(&conditional.then_expr); + let guard = self.path_condition.pop().unwrap(); + self.path_condition.push(Expression::not(guard)); + self.walk_expression(&conditional.else_expr); + self.path_condition.pop(); + } + } + let mut collector = Collector { + path_condition: Vec::new(), + owned_places: Vec::new(), + }; + collector.walk_expression(self); + collector.owned_places + } + + /// Returns the expression with all pure parts removed and implications + /// converted into conditionals. + /// + /// This method is different from `collect_guarded_owned_places` in that it + /// still returns a single expression preserving most of the original + /// structure. + pub fn convert_into_permission_expression(self) -> Expression { + struct Remover {} + impl<'a> ExpressionFolder for Remover { + fn fold_expression(&mut self, expression: Expression) -> Expression { + if expression.is_pure() { + true.into() + } else { + default_fold_expression(self, expression) + } + } + fn fold_binary_op_enum(&mut self, binary_op: BinaryOp) -> Expression { + if binary_op.op_kind == BinaryOpKind::Implies { + let guard = *binary_op.left; + let then_expr = self.fold_expression(*binary_op.right); + let else_expr = false.into(); + Expression::conditional(guard, then_expr, else_expr, binary_op.position) + } else { + Expression::BinaryOp(self.fold_binary_op(binary_op)) + } + } + } + let mut remover = Remover {}; + remover.fold_expression(self) + } + + /// Returns places that contain dereferences with their path conditions. + pub fn collect_guarded_dereferenced_places(&self) -> Vec<(Expression, Expression)> { + struct Collector { + path_condition: Vec, + deref_places: Vec<(Expression, Expression)>, + } + impl<'a> ExpressionWalker for Collector { + fn walk_expression(&mut self, expression: &Expression) { + if expression.is_place() { + if expression.get_last_dereferenced_pointer().is_some() { + self.deref_places.push(( + self.path_condition.iter().cloned().conjoin(), + expression.clone(), + )); + } + } else { + default_walk_expression(self, expression) + } + } + fn walk_binary_op(&mut self, binary_op: &BinaryOp) { + if binary_op.op_kind == BinaryOpKind::Implies { + self.walk_expression(&binary_op.left); + self.path_condition.push((*binary_op.left).clone()); + self.walk_expression(&binary_op.right); + self.path_condition.pop(); + } else { + default_walk_binary_op(self, binary_op); + } + } + fn walk_conditional(&mut self, conditional: &Conditional) { + self.walk_expression(&conditional.guard); + self.path_condition.push((*conditional.guard).clone()); + self.walk_expression(&conditional.then_expr); + let guard = self.path_condition.pop().unwrap(); + self.path_condition.push(Expression::not(guard)); + self.walk_expression(&conditional.else_expr); + self.path_condition.pop(); + } + } + let mut collector = Collector { + path_condition: Vec::new(), + deref_places: Vec::new(), + }; + collector.walk_expression(self); + collector.deref_places + } + + pub fn any_variable(&self, predicate: F) -> bool + where + F: Fn(&VariableDecl) -> bool, + { + struct Collector + where + F: Fn(&VariableDecl) -> bool, + { + predicate: F, + found: bool, + } + impl<'a, F> ExpressionWalker for Collector + where + F: Fn(&VariableDecl) -> bool, + { + fn walk_variable_decl(&mut self, variable: &VariableDecl) { + if (self.predicate)(variable) { + self.found = true; + } + } + } + let mut collector = Collector { + predicate, + found: false, + }; + collector.walk_expression(self); + collector.found + } } diff --git a/vir/defs/high/operations_internal/graphviz.rs b/vir/defs/high/operations_internal/graphviz.rs index dc94f6a7b55..7ca52872c4e 100644 --- a/vir/defs/high/operations_internal/graphviz.rs +++ b/vir/defs/high/operations_internal/graphviz.rs @@ -2,7 +2,7 @@ use super::super::{ ast::statement::Statement, cfg::procedure::{BasicBlock, ProcedureDecl, Successor}, }; -use crate::common::graphviz::{escape_html, Graph, NodeBuilder, ToGraphviz}; +use crate::common::graphviz::{escape_html, escape_html_wrap, Graph, NodeBuilder, ToGraphviz}; use std::io::Write; impl ToGraphviz for ProcedureDecl { @@ -48,9 +48,12 @@ fn block_to_graph_node(block: &BasicBlock, node_builder: &mut NodeBuilder) { for statement in &block.statements { let statement_string = match statement { Statement::Comment(statement) => { - format!("{}", escape_html(statement)) + format!( + "{}", + escape_html_wrap(statement) + ) } - _ => escape_html(statement.to_string()), + _ => escape_html_wrap(statement.to_string()), }; node_builder.add_row_sequence(vec![statement_string]); } diff --git a/vir/defs/high/operations_internal/helpers.rs b/vir/defs/high/operations_internal/helpers.rs index aeca7d5ae0f..03536c7bcf5 100644 --- a/vir/defs/high/operations_internal/helpers.rs +++ b/vir/defs/high/operations_internal/helpers.rs @@ -117,22 +117,44 @@ impl ConstantHelpers for Expression { impl SyntacticEvaluation for Expression { fn is_true(&self) -> bool { - matches!( - self, + match self { Self::Constant(Constant { value: ConstantValue::Bool(true), .. - }) - ) + }) => true, + Self::UnaryOp(UnaryOp { + op_kind: UnaryOpKind::Not, + argument, + .. + }) => argument.is_false(), + Self::BinaryOp(BinaryOp { + op_kind: BinaryOpKind::Or, + left, + right, + .. + }) => left.is_true() || right.is_true(), + _ => false, + } } fn is_false(&self) -> bool { - matches!( - self, + match self { Self::Constant(Constant { value: ConstantValue::Bool(false), .. - }) - ) + }) => true, + Self::UnaryOp(UnaryOp { + op_kind: UnaryOpKind::Not, + argument, + .. + }) => argument.is_true(), + Self::BinaryOp(BinaryOp { + op_kind: BinaryOpKind::And, + left, + right, + .. + }) => left.is_false() || right.is_false(), + _ => false, + } } fn is_zero(&self) -> bool { matches!( diff --git a/vir/defs/high/operations_internal/identifier/predicate.rs b/vir/defs/high/operations_internal/identifier/predicate.rs index 5f1a5b0427a..90cdea914a7 100644 --- a/vir/defs/high/operations_internal/identifier/predicate.rs +++ b/vir/defs/high/operations_internal/identifier/predicate.rs @@ -14,8 +14,16 @@ impl WithIdentifier for Predicate { Self::MemoryBlockStack(predicate) => predicate.get_identifier(), Self::MemoryBlockStackDrop(predicate) => predicate.get_identifier(), Self::MemoryBlockHeap(predicate) => predicate.get_identifier(), + Self::MemoryBlockHeapRange(predicate) => predicate.get_identifier(), + Self::MemoryBlockHeapRangeGuarded(predicate) => predicate.get_identifier(), Self::MemoryBlockHeapDrop(predicate) => predicate.get_identifier(), Self::OwnedNonAliased(predicate) => predicate.get_identifier(), + Self::OwnedRange(predicate) => predicate.get_identifier(), + Self::OwnedSet(predicate) => predicate.get_identifier(), + Self::UniqueRef(predicate) => predicate.get_identifier(), + Self::UniqueRefRange(predicate) => predicate.get_identifier(), + Self::FracRef(predicate) => predicate.get_identifier(), + Self::FracRefRange(predicate) => predicate.get_identifier(), } } } @@ -44,6 +52,18 @@ impl WithIdentifier for predicate::MemoryBlockHeap { } } +impl WithIdentifier for predicate::MemoryBlockHeapRange { + fn get_identifier(&self) -> String { + "MemoryBlockHeapRange".to_string() + } +} + +impl WithIdentifier for predicate::MemoryBlockHeapRangeGuarded { + fn get_identifier(&self) -> String { + "MemoryBlockHeapRangeGuarded".to_string() + } +} + impl WithIdentifier for predicate::MemoryBlockHeapDrop { fn get_identifier(&self) -> String { "MemoryBlockHeapDrop".to_string() @@ -55,3 +75,42 @@ impl WithIdentifier for predicate::OwnedNonAliased { format!("OwnedNonAliased${}", self.place.get_type().get_identifier()) } } + +impl WithIdentifier for predicate::OwnedRange { + fn get_identifier(&self) -> String { + format!("OwnedRange${}", self.address.get_type().get_identifier()) + } +} + +impl WithIdentifier for predicate::OwnedSet { + fn get_identifier(&self) -> String { + format!("OwnedSet${}", self.set.get_type().get_identifier()) + } +} + +impl WithIdentifier for predicate::UniqueRef { + fn get_identifier(&self) -> String { + format!("UniqueRef${}", self.place.get_type().get_identifier()) + } +} + +impl WithIdentifier for predicate::UniqueRefRange { + fn get_identifier(&self) -> String { + format!( + "UniqueRefRange${}", + self.address.get_type().get_identifier() + ) + } +} + +impl WithIdentifier for predicate::FracRef { + fn get_identifier(&self) -> String { + format!("FracRef${}", self.place.get_type().get_identifier()) + } +} + +impl WithIdentifier for predicate::FracRefRange { + fn get_identifier(&self) -> String { + format!("FracRefRange${}", self.address.get_type().get_identifier()) + } +} diff --git a/vir/defs/high/operations_internal/identifier/rvalue.rs b/vir/defs/high/operations_internal/identifier/rvalue.rs index c9ecdc33215..7999057c4d3 100644 --- a/vir/defs/high/operations_internal/identifier/rvalue.rs +++ b/vir/defs/high/operations_internal/identifier/rvalue.rs @@ -1,4 +1,7 @@ -use super::super::{super::ast::rvalue::*, ty::Typed}; +use super::{ + super::{super::ast::rvalue::*, ty::Typed}, + common::append_type_arguments, +}; use crate::common::identifier::WithIdentifier; impl WithIdentifier for Rvalue { @@ -7,6 +10,7 @@ impl WithIdentifier for Rvalue { Self::Repeat(value) => value.get_identifier(), Self::AddressOf(value) => value.get_identifier(), Self::Len(value) => value.get_identifier(), + Self::Cast(value) => value.get_identifier(), Self::BinaryOp(value) => value.get_identifier(), Self::CheckedBinaryOp(value) => value.get_identifier(), Self::UnaryOp(value) => value.get_identifier(), @@ -48,6 +52,16 @@ impl WithIdentifier for Len { } } +impl WithIdentifier for Cast { + fn get_identifier(&self) -> String { + format!( + "Cast${}${}", + self.operand.get_identifier(), + self.ty.get_identifier() + ) + } +} + impl WithIdentifier for UnaryOp { fn get_identifier(&self) -> String { format!("UnaryOp${}${}", self.kind, self.argument.get_identifier()) @@ -84,7 +98,13 @@ impl WithIdentifier for Discriminant { impl WithIdentifier for Aggregate { fn get_identifier(&self) -> String { - format!("Aggregate${}", self.ty.get_identifier()) + let mut identifier = format!("Aggregate${}", self.ty.get_identifier()); + identifier.push('$'); + for operand in &self.operands { + identifier.push_str(&operand.get_identifier()); + identifier.push('$'); + } + identifier } } diff --git a/vir/defs/high/operations_internal/identifier/ty.rs b/vir/defs/high/operations_internal/identifier/ty.rs index c8ac0b8585f..a11dc7433d2 100644 --- a/vir/defs/high/operations_internal/identifier/ty.rs +++ b/vir/defs/high/operations_internal/identifier/ty.rs @@ -9,6 +9,8 @@ impl WithIdentifier for ty::Type { ty::Type::MFloat32 => "MFloat32".to_string(), ty::Type::MFloat64 => "MFloat64".to_string(), ty::Type::MPerm => "MPerm".to_string(), + ty::Type::MByte => "MByte".to_string(), + ty::Type::MBytes => "MBytes".to_string(), ty::Type::Bool => "Bool".to_string(), ty::Type::Int(ty) => ty.get_identifier(), ty::Type::Sequence(ty) => ty.get_identifier(), diff --git a/vir/defs/high/operations_internal/lifetimes/common.rs b/vir/defs/high/operations_internal/lifetimes/common.rs index 7e644d94d9e..b4835a0e102 100644 --- a/vir/defs/high/operations_internal/lifetimes/common.rs +++ b/vir/defs/high/operations_internal/lifetimes/common.rs @@ -51,6 +51,7 @@ impl WithLifetimes for Rvalue { Self::Repeat(value) => value.get_lifetimes(), Self::AddressOf(value) => value.get_lifetimes(), Self::Len(value) => value.get_lifetimes(), + Self::Cast(value) => value.get_lifetimes(), Self::BinaryOp(value) => value.get_lifetimes(), Self::CheckedBinaryOp(value) => value.get_lifetimes(), Self::UnaryOp(value) => value.get_lifetimes(), @@ -96,6 +97,14 @@ impl WithLifetimes for Len { } } +impl WithLifetimes for Cast { + fn get_lifetimes(&self) -> Vec { + let mut lifetimes = self.operand.get_lifetimes(); + lifetimes.extend(self.ty.get_lifetimes()); + lifetimes + } +} + impl WithLifetimes for BinaryOp { fn get_lifetimes(&self) -> Vec { let mut lifetimes = self.left.get_lifetimes(); diff --git a/vir/defs/high/operations_internal/mod.rs b/vir/defs/high/operations_internal/mod.rs index ff70077842f..6d97aa9d559 100644 --- a/vir/defs/high/operations_internal/mod.rs +++ b/vir/defs/high/operations_internal/mod.rs @@ -16,3 +16,4 @@ pub mod successor; pub mod ty; pub mod type_decl; pub mod variable; +pub mod quantifiers; diff --git a/vir/defs/high/operations_internal/position/expressions.rs b/vir/defs/high/operations_internal/position/expressions.rs index e9e5bd86136..ee83da64aad 100644 --- a/vir/defs/high/operations_internal/position/expressions.rs +++ b/vir/defs/high/operations_internal/position/expressions.rs @@ -9,6 +9,7 @@ impl Positioned for Expression { Self::Variant(expression) => expression.position(), Self::Field(expression) => expression.position(), Self::Deref(expression) => expression.position(), + Self::Final(expression) => expression.position(), Self::AddrOf(expression) => expression.position(), Self::LabelledOld(expression) => expression.position(), Self::Constant(expression) => expression.position(), @@ -22,6 +23,9 @@ impl Positioned for Expression { Self::FuncApp(expression) => expression.position(), Self::Downcast(expression) => expression.position(), Self::BuiltinFuncApp(expression) => expression.position(), + Self::AccPredicate(expression) => expression.position(), + Self::Unfolding(expression) => expression.position(), + Self::EvalIn(expression) => expression.position(), } } } @@ -56,6 +60,12 @@ impl Positioned for Deref { } } +impl Positioned for Final { + fn position(&self) -> Position { + self.position + } +} + impl Positioned for AddrOf { fn position(&self) -> Position { self.position @@ -133,3 +143,21 @@ impl Positioned for Downcast { self.position } } + +impl Positioned for AccPredicate { + fn position(&self) -> Position { + self.position + } +} + +impl Positioned for Unfolding { + fn position(&self) -> Position { + self.position + } +} + +impl Positioned for EvalIn { + fn position(&self) -> Position { + self.position + } +} diff --git a/vir/defs/high/operations_internal/position/mod.rs b/vir/defs/high/operations_internal/position/mod.rs index 3705384ac71..5ae756e1a4c 100644 --- a/vir/defs/high/operations_internal/position/mod.rs +++ b/vir/defs/high/operations_internal/position/mod.rs @@ -1,2 +1,3 @@ mod expressions; mod statement; +mod type_decl; diff --git a/vir/defs/high/operations_internal/position/statement.rs b/vir/defs/high/operations_internal/position/statement.rs index 0b9ae927f4c..cb2392832de 100644 --- a/vir/defs/high/operations_internal/position/statement.rs +++ b/vir/defs/high/operations_internal/position/statement.rs @@ -6,10 +6,13 @@ impl Positioned for Statement { match self { Self::Comment(statement) => statement.position(), Self::OldLabel(statement) => statement.position(), - Self::Inhale(statement) => statement.position(), - Self::Exhale(statement) => statement.position(), + Self::InhalePredicate(statement) => statement.position(), + Self::ExhalePredicate(statement) => statement.position(), + Self::InhaleExpression(statement) => statement.position(), + Self::ExhaleExpression(statement) => statement.position(), Self::Havoc(statement) => statement.position(), Self::GhostHavoc(statement) => statement.position(), + Self::HeapHavoc(statement) => statement.position(), Self::Assume(statement) => statement.position(), Self::Assert(statement) => statement.position(), Self::LoopInvariant(statement) => statement.position(), @@ -22,8 +25,22 @@ impl Positioned for Statement { Self::Consume(statement) => statement.position(), Self::LeakAll(statement) => statement.position(), Self::SetUnionVariant(statement) => statement.position(), + Self::Pack(statement) => statement.position(), + Self::Unpack(statement) => statement.position(), + Self::Obtain(statement) => statement.position(), + Self::Join(statement) => statement.position(), + Self::JoinRange(statement) => statement.position(), + Self::Split(statement) => statement.position(), + Self::SplitRange(statement) => statement.position(), + Self::StashRange(statement) => statement.position(), + Self::StashRangeRestore(statement) => statement.position(), + Self::ForgetInitialization(statement) => statement.position(), + Self::ForgetInitializationRange(statement) => statement.position(), + Self::RestoreRawBorrowed(statement) => statement.position(), Self::NewLft(statement) => statement.position(), Self::EndLft(statement) => statement.position(), + Self::DeadReference(statement) => statement.position(), + Self::DeadReferenceRange(statement) => statement.position(), Self::DeadLifetime(statement) => statement.position(), Self::DeadInclusion(statement) => statement.position(), Self::LifetimeTake(statement) => statement.position(), @@ -33,7 +50,11 @@ impl Positioned for Statement { Self::OpenFracRef(statement) => statement.position(), Self::CloseMutRef(statement) => statement.position(), Self::CloseFracRef(statement) => statement.position(), + Self::RestoreMutBorrowed(statement) => statement.position(), Self::BorShorten(statement) => statement.position(), + Self::MaterializePredicate(statement) => statement.position(), + Self::EncodingAction(statement) => statement.position(), + Self::CaseSplit(statement) => statement.position(), } } } @@ -50,13 +71,25 @@ impl Positioned for OldLabel { } } -impl Positioned for Inhale { +impl Positioned for InhalePredicate { fn position(&self) -> Position { self.position } } -impl Positioned for Exhale { +impl Positioned for ExhalePredicate { + fn position(&self) -> Position { + self.position + } +} + +impl Positioned for InhaleExpression { + fn position(&self) -> Position { + self.position + } +} + +impl Positioned for ExhaleExpression { fn position(&self) -> Position { self.position } @@ -74,6 +107,12 @@ impl Positioned for GhostHavoc { } } +impl Positioned for HeapHavoc { + fn position(&self) -> Position { + self.position + } +} + impl Positioned for GhostAssign { fn position(&self) -> Position { self.position @@ -146,6 +185,78 @@ impl Positioned for SetUnionVariant { } } +impl Positioned for Pack { + fn position(&self) -> Position { + self.position + } +} + +impl Positioned for Unpack { + fn position(&self) -> Position { + self.position + } +} + +impl Positioned for Obtain { + fn position(&self) -> Position { + self.position + } +} + +impl Positioned for Join { + fn position(&self) -> Position { + self.position + } +} + +impl Positioned for JoinRange { + fn position(&self) -> Position { + self.position + } +} + +impl Positioned for Split { + fn position(&self) -> Position { + self.position + } +} + +impl Positioned for SplitRange { + fn position(&self) -> Position { + self.position + } +} + +impl Positioned for StashRange { + fn position(&self) -> Position { + self.position + } +} + +impl Positioned for StashRangeRestore { + fn position(&self) -> Position { + self.position + } +} + +impl Positioned for ForgetInitialization { + fn position(&self) -> Position { + self.position + } +} + +impl Positioned for ForgetInitializationRange { + fn position(&self) -> Position { + self.position + } +} + +impl Positioned for RestoreRawBorrowed { + fn position(&self) -> Position { + self.position + } +} + impl Positioned for NewLft { fn position(&self) -> Position { self.position @@ -158,6 +269,18 @@ impl Positioned for EndLft { } } +impl Positioned for DeadReference { + fn position(&self) -> Position { + self.position + } +} + +impl Positioned for DeadReferenceRange { + fn position(&self) -> Position { + self.position + } +} + impl Positioned for DeadLifetime { fn position(&self) -> Position { self.position @@ -212,8 +335,32 @@ impl Positioned for CloseFracRef { } } +impl Positioned for RestoreMutBorrowed { + fn position(&self) -> Position { + self.position + } +} + impl Positioned for BorShorten { fn position(&self) -> Position { self.position } } + +impl Positioned for MaterializePredicate { + fn position(&self) -> Position { + self.position + } +} + +impl Positioned for EncodingAction { + fn position(&self) -> Position { + self.position + } +} + +impl Positioned for CaseSplit { + fn position(&self) -> Position { + self.position + } +} diff --git a/vir/defs/high/operations_internal/position/type_decl.rs b/vir/defs/high/operations_internal/position/type_decl.rs new file mode 100644 index 00000000000..8482ae7b43e --- /dev/null +++ b/vir/defs/high/operations_internal/position/type_decl.rs @@ -0,0 +1,33 @@ +use super::super::super::ast::type_decl::*; +use crate::common::position::Positioned; + +impl Positioned for TypeDecl { + fn position(&self) -> Position { + match self { + Self::Bool => Default::default(), + Self::Int(_) => Default::default(), + Self::Float(_) => Default::default(), + Self::TypeVar(_) => Default::default(), + Self::Tuple(_) => Default::default(), + Self::Struct(decl) => decl.position(), + Self::Sequence(_) => Default::default(), + Self::Map(_) => Default::default(), + Self::Enum(_) => Default::default(), + Self::Union(_) => Default::default(), + Self::Array(_) => Default::default(), + Self::Slice(_) => Default::default(), + Self::Reference(_) => Default::default(), + Self::Pointer(_) => Default::default(), + Self::Never => Default::default(), + Self::Closure(_) => Default::default(), + Self::Unsupported(_) => Default::default(), + Self::Trusted(_) => Default::default(), + } + } +} + +impl Positioned for Struct { + fn position(&self) -> Position { + self.position + } +} diff --git a/vir/defs/high/operations_internal/predicate.rs b/vir/defs/high/operations_internal/predicate.rs index 88f3f66c3ef..44475700007 100644 --- a/vir/defs/high/operations_internal/predicate.rs +++ b/vir/defs/high/operations_internal/predicate.rs @@ -32,6 +32,22 @@ impl Predicate { predicate.size.get_type().clone(), ] } + Self::MemoryBlockHeapRange(predicate) => { + // FIXME: This is probably wrong: we need to use the type of the + // target. + vec![ + predicate.address.get_type().clone(), + predicate.size.get_type().clone(), + ] + } + Self::MemoryBlockHeapRangeGuarded(predicate) => { + // FIXME: This is probably wrong: we need to use the type of the + // target. + vec![ + predicate.address.get_type().clone(), + predicate.size.get_type().clone(), + ] + } Self::MemoryBlockHeapDrop(predicate) => { vec![ predicate.address.get_type().clone(), @@ -41,8 +57,35 @@ impl Predicate { Self::OwnedNonAliased(predicate) => { vec![predicate.place.get_type().clone()] } + Self::OwnedRange(predicate) => { + // FIXME: This is probably wrong: we need to use the type of the + // target. + vec![predicate.address.get_type().clone()] + } + Self::OwnedSet(predicate) => { + // FIXME: This is probably wrong: we need to use the type of the + // target of the pointer stored in the set. + vec![predicate.set.get_type().clone()] + } + Self::UniqueRef(predicate) => { + vec![predicate.place.get_type().clone()] + } + Self::UniqueRefRange(predicate) => { + // FIXME: This is probably wrong: we need to use the type of the + // target. + vec![predicate.address.get_type().clone()] + } + Self::FracRef(predicate) => { + vec![predicate.place.get_type().clone()] + } + Self::FracRefRange(predicate) => { + // FIXME: This is probably wrong: we need to use the type of the + // target. + vec![predicate.address.get_type().clone()] + } } } + pub fn check_no_default_position(&self) { struct Checker; impl PredicateWalker for Checker { @@ -55,4 +98,23 @@ impl Predicate { } Checker.walk_predicate(self) } + + pub fn get_heap_location_mut(&mut self) -> Option<&mut Expression> { + match self { + Self::LifetimeToken(_) => None, + Self::MemoryBlockStack(_) => None, + Self::MemoryBlockStackDrop(_) => None, + Self::MemoryBlockHeap(predicate) => Some(&mut predicate.address), + Self::MemoryBlockHeapRange(predicate) => Some(&mut predicate.address), + Self::MemoryBlockHeapRangeGuarded(predicate) => Some(&mut predicate.address), + Self::MemoryBlockHeapDrop(predicate) => Some(&mut predicate.address), + Self::OwnedNonAliased(predicate) => Some(&mut predicate.place), + Self::OwnedRange(predicate) => Some(&mut predicate.address), + Self::OwnedSet(predicate) => Some(&mut predicate.set), + Self::UniqueRef(predicate) => Some(&mut predicate.place), + Self::UniqueRefRange(predicate) => Some(&mut predicate.address), + Self::FracRef(predicate) => Some(&mut predicate.place), + Self::FracRefRange(predicate) => Some(&mut predicate.address), + } + } } diff --git a/vir/defs/high/operations_internal/procedure.rs b/vir/defs/high/operations_internal/procedure.rs index 6082bebdc63..069b95a5816 100644 --- a/vir/defs/high/operations_internal/procedure.rs +++ b/vir/defs/high/operations_internal/procedure.rs @@ -92,43 +92,43 @@ impl ProcedureDecl { .map(|(name, ty)| VariableDecl { name, ty }) .collect() } - pub fn get_topological_sort(&self) -> Vec { - if self.basic_blocks.is_empty() { - Vec::new() - } else { - let mut visited: BTreeMap<_, _> = self - .basic_blocks - .keys() - .map(|label| (label.clone(), false)) - .collect(); - let mut topo_sorted = Vec::::with_capacity(self.basic_blocks.len()); - *visited.get_mut(&self.entry).unwrap() = true; - for label in self.basic_blocks.keys() { - if !visited[label] { - self.topological_sort_impl(&mut visited, &mut topo_sorted, label); - } - } - topo_sorted.push(self.entry.clone()); - topo_sorted.reverse(); - topo_sorted - } - } - fn topological_sort_impl( - &self, - visited: &mut BTreeMap, - topo_sorted: &mut Vec, - current_label: &BasicBlockId, - ) { - assert!(!visited[current_label]); - *visited.get_mut(current_label).unwrap() = true; - let current_block = &self.basic_blocks[current_label]; - for block_index in current_block.successor.get_following() { - if !visited[block_index] { - self.topological_sort_impl(visited, topo_sorted, block_index); - } - } - topo_sorted.push(current_label.clone()) - } + // pub fn get_topological_sort(&self) -> Vec { + // if self.basic_blocks.is_empty() { + // Vec::new() + // } else { + // let mut visited: BTreeMap<_, _> = self + // .basic_blocks + // .keys() + // .map(|label| (label.clone(), false)) + // .collect(); + // let mut topo_sorted = Vec::::with_capacity(self.basic_blocks.len()); + // *visited.get_mut(&self.entry).unwrap() = true; + // for label in self.basic_blocks.keys() { + // if !visited[label] { + // self.topological_sort_impl(&mut visited, &mut topo_sorted, label); + // } + // } + // topo_sorted.push(self.entry.clone()); + // topo_sorted.reverse(); + // topo_sorted + // } + // } + // fn topological_sort_impl( + // &self, + // visited: &mut BTreeMap, + // topo_sorted: &mut Vec, + // current_label: &BasicBlockId, + // ) { + // assert!(!visited[current_label]); + // *visited.get_mut(current_label).unwrap() = true; + // let current_block = &self.basic_blocks[current_label]; + // for block_index in current_block.successor.get_following() { + // if !visited[block_index] { + // self.topological_sort_impl(visited, topo_sorted, block_index); + // } + // } + // topo_sorted.push(current_label.clone()) + // } /// To know which trace was taken to reach a specific basic block, we /// sometimes keep a record of visited blocks. However, this method fails if /// one trace is a strict subset of another trace. This, for example, @@ -193,6 +193,10 @@ impl Cfg for ProcedureDecl { type BasicBlockIdIterator<'a> = std::collections::btree_map::Keys<'a, Self::BasicBlockId, Self::BasicBlock>; + fn entry(&self) -> &Self::BasicBlockId { + &self.entry + } + fn get_basic_block(&self, bb: &Self::BasicBlockId) -> Option<&Self::BasicBlock> { self.basic_blocks.get(bb) } diff --git a/vir/defs/high/operations_internal/quantifiers.rs b/vir/defs/high/operations_internal/quantifiers.rs new file mode 100644 index 00000000000..c46ce3da9d0 --- /dev/null +++ b/vir/defs/high/operations_internal/quantifiers.rs @@ -0,0 +1,78 @@ +use super::super::ast::{ + expression::{visitors::ExpressionWalker, Expression}, + position::Position, + ty::*, + variable::*, +}; +use std::collections::BTreeSet; + +#[derive(Default)] +pub struct BoundVariableStack { + stack: Vec>, +} + +impl BoundVariableStack { + pub fn contains_name(&self, variable_name: &str) -> bool { + self.stack.iter().any(|set| set.contains(variable_name)) + } + + pub fn contains(&self, variable: &VariableDecl) -> bool { + self.contains_name(&variable.name) + } + + pub fn push_names(&mut self, variable_names: BTreeSet) { + self.stack.push(variable_names); + } + + pub fn push(&mut self, variables: &[VariableDecl]) { + self.push_names( + variables + .iter() + .map(|variable| variable.name.clone()) + .collect(), + ) + } + + pub fn push_single(&mut self, variable: &VariableDecl) { + self.push_names(std::iter::once(variable.name.clone()).collect()) + } + + pub fn pop_names(&mut self) { + assert!(self.stack.pop().is_some()); + } + + pub fn pop(&mut self) { + self.pop_names(); + } + + fn expressions_contains_bound_variables(&self, expressions: &[Expression]) -> bool { + struct Walker<'a> { + bound_variable_stack: &'a BoundVariableStack, + contains_bound_variables: bool, + } + impl<'a> ExpressionWalker for Walker<'a> { + fn walk_variable_decl(&mut self, variable: &VariableDecl) { + if self.bound_variable_stack.contains(variable) { + self.contains_bound_variables = true; + } + } + } + let mut walker = Walker { + bound_variable_stack: self, + contains_bound_variables: false, + }; + for expression in expressions { + walker.walk_expression(expression); + } + walker.contains_bound_variables + } +} + +impl Drop for BoundVariableStack { + fn drop(&mut self) { + // Check when not panicking. + if !std::thread::panicking() { + assert!(self.stack.is_empty()); + } + } +} diff --git a/vir/defs/high/operations_internal/special_variables.rs b/vir/defs/high/operations_internal/special_variables.rs index 2a0ceb78d23..94931149dca 100644 --- a/vir/defs/high/operations_internal/special_variables.rs +++ b/vir/defs/high/operations_internal/special_variables.rs @@ -6,18 +6,58 @@ use super::super::ast::{ variable::VariableDecl, }; +impl VariableDecl { + pub fn self_variable(ty: Type) -> Self { + VariableDecl::new(crate::common::builtin_constants::SELF_VARIABLE_NAME, ty) + } + + pub fn is_self_variable(&self) -> bool { + self.name == crate::common::builtin_constants::SELF_VARIABLE_NAME + } + + pub fn result_variable(ty: Type) -> Self { + VariableDecl::new(crate::common::builtin_constants::RESULT_VARIABLE_NAME, ty) + } + + pub fn is_result_variable(&self) -> bool { + self.name == crate::common::builtin_constants::RESULT_VARIABLE_NAME + } + + pub fn discriminant_variable() -> Self { + VariableDecl::new( + crate::common::builtin_constants::DISCRIMINANT_VARIABLE_NAME, + Type::MInt, + ) + } +} + impl Expression { + pub fn self_variable(ty: Type) -> Self { + let variable = VariableDecl::self_variable(ty); + Expression::local_no_pos(variable) + } + + pub fn is_self_variable(&self) -> bool { + if let Expression::Local(Local { variable, .. }) = self { + variable.is_self_variable() + } else { + false + } + } + pub fn discriminant() -> Self { - let variable = VariableDecl::new("discriminant$", Type::MInt); + let variable = VariableDecl::discriminant_variable(); Expression::local_no_pos(variable) } + pub fn is_discriminant(&self) -> bool { if let Expression::Local(Local { variable, .. }) = self { - variable.name == "discriminant$" + variable.name == crate::common::builtin_constants::DISCRIMINANT_VARIABLE_NAME } else { false } } + pub fn is_discriminant_field(&self) -> bool { if let Expression::Field(Field { field, .. }) = self { field.is_discriminant() @@ -25,16 +65,40 @@ impl Expression { false } } + + pub fn is_address_field(&self) -> bool { + if let Expression::Field(Field { field, .. }) = self { + field.is_address() + } else { + false + } + } } const DISCRIMINANT_INDEX: usize = 100000; impl FieldDecl { pub fn discriminant(ty: Type) -> Self { - FieldDecl::new("discriminant", DISCRIMINANT_INDEX, ty) + FieldDecl::new( + crate::common::builtin_constants::DISCRIMINANT_FIELD_NAME, + DISCRIMINANT_INDEX, + ty, + ) } pub fn is_discriminant(&self) -> bool { - self.name == "discriminant" && self.index == DISCRIMINANT_INDEX + self.name == crate::common::builtin_constants::DISCRIMINANT_FIELD_NAME + && self.index == DISCRIMINANT_INDEX + } + pub fn reference_address(reference_type: ty::Reference) -> Self { + let ty = Type::pointer(*reference_type.target_type); + FieldDecl::new( + crate::common::builtin_constants::ADDRESS_FIELD_NAME, + 0usize, + ty, + ) + } + pub fn is_address(&self) -> bool { + self.name == crate::common::builtin_constants::ADDRESS_FIELD_NAME && self.index == 0usize } } diff --git a/vir/defs/high/operations_internal/ty.rs b/vir/defs/high/operations_internal/ty.rs index 6ca408ac373..f99dc803ea2 100644 --- a/vir/defs/high/operations_internal/ty.rs +++ b/vir/defs/high/operations_internal/ty.rs @@ -96,6 +96,15 @@ impl Type { } DefaultLifetimeEraser {}.fold_type(self.clone()) } + pub fn check_no_erased_lifetime(&self) { + struct LifetimeChecker {} + impl TypeWalker for LifetimeChecker { + fn walk_lifetime_const(&mut self, lifetime: &LifetimeConst) { + assert_ne!(lifetime.name, LifetimeConst::erased().name); + } + } + LifetimeChecker {}.walk_type(self); + } #[must_use] pub fn replace_lifetimes( self, @@ -200,6 +209,13 @@ impl Type { _ => false, } } + pub fn is_unique_reference(&self) -> bool { + if let Type::Reference(Reference { uniqueness, .. }) = self { + uniqueness.is_unique() + } else { + false + } + } } impl AsRef for VariantIndex { @@ -254,7 +270,7 @@ impl super::super::ast::type_decl::Union { impl LifetimeConst { pub fn erased() -> Self { LifetimeConst { - name: String::from("lft_erased"), + name: String::from(crate::common::builtin_constants::ERASED_LIFETIME_NAME), } } } @@ -357,6 +373,7 @@ impl Typed for Expression { Expression::Variant(expression) => expression.get_type(), Expression::Field(expression) => expression.get_type(), Expression::Deref(expression) => expression.get_type(), + Expression::Final(expression) => expression.get_type(), Expression::AddrOf(expression) => expression.get_type(), Expression::LabelledOld(expression) => expression.get_type(), Expression::Constant(expression) => expression.get_type(), @@ -370,6 +387,9 @@ impl Typed for Expression { Expression::FuncApp(expression) => expression.get_type(), Expression::BuiltinFuncApp(expression) => expression.get_type(), Expression::Downcast(expression) => expression.get_type(), + Expression::AccPredicate(expression) => expression.get_type(), + Expression::Unfolding(expression) => expression.get_type(), + Expression::EvalIn(expression) => expression.get_type(), } } fn set_type(&mut self, new_type: Type) { @@ -379,6 +399,7 @@ impl Typed for Expression { Expression::Variant(expression) => expression.set_type(new_type), Expression::Field(expression) => expression.set_type(new_type), Expression::Deref(expression) => expression.set_type(new_type), + Expression::Final(expression) => expression.set_type(new_type), Expression::AddrOf(expression) => expression.set_type(new_type), Expression::LabelledOld(expression) => expression.set_type(new_type), Expression::Constant(expression) => expression.set_type(new_type), @@ -392,6 +413,9 @@ impl Typed for Expression { Expression::FuncApp(expression) => expression.set_type(new_type), Expression::BuiltinFuncApp(expression) => expression.set_type(new_type), Expression::Downcast(expression) => expression.set_type(new_type), + Expression::AccPredicate(expression) => expression.set_type(new_type), + Expression::Unfolding(expression) => expression.set_type(new_type), + Expression::EvalIn(expression) => expression.set_type(new_type), } } } @@ -440,6 +464,15 @@ impl Typed for Deref { } } +impl Typed for Final { + fn get_type(&self) -> &Type { + &self.ty + } + fn set_type(&mut self, new_type: Type) { + self.ty = new_type; + } +} + impl Typed for AddrOf { fn get_type(&self) -> &Type { &self.ty @@ -502,6 +535,22 @@ impl Typed for BinaryOp { } } fn set_type(&mut self, new_type: Type) { + assert!( + !matches!( + self.op_kind, + BinaryOpKind::EqCmp + | BinaryOpKind::NeCmp + | BinaryOpKind::GtCmp + | BinaryOpKind::GeCmp + | BinaryOpKind::LtCmp + | BinaryOpKind::LeCmp + | BinaryOpKind::And + | BinaryOpKind::Or + | BinaryOpKind::Implies + ), + "cannot change the type of {:?}", + self.op_kind + ); self.left.set_type(new_type.clone()); self.right.set_type(new_type); } @@ -582,3 +631,30 @@ impl Typed for Downcast { self.base.set_type(new_type); } } + +impl Typed for AccPredicate { + fn get_type(&self) -> &Type { + &Type::Bool + } + fn set_type(&mut self, _new_type: Type) { + unreachable!(); + } +} + +impl Typed for Unfolding { + fn get_type(&self) -> &Type { + self.body.get_type() + } + fn set_type(&mut self, new_type: Type) { + self.body.set_type(new_type) + } +} + +impl Typed for EvalIn { + fn get_type(&self) -> &Type { + self.body.get_type() + } + fn set_type(&mut self, new_type: Type) { + self.body.set_type(new_type) + } +} diff --git a/vir/defs/high/operations_internal/type_decl.rs b/vir/defs/high/operations_internal/type_decl.rs index 687e2fc60b9..973ac34862a 100644 --- a/vir/defs/high/operations_internal/type_decl.rs +++ b/vir/defs/high/operations_internal/type_decl.rs @@ -4,6 +4,12 @@ use super::super::ast::{ type_decl::{Enum, Struct, Trusted, Tuple, TypeDecl, Union}, }; +impl Struct { + pub fn is_manually_managed_type(&self) -> bool { + self.structural_invariant.is_some() + } +} + impl Enum { pub fn variant(&self, variant_name: &str) -> Option<&Struct> { self.variants diff --git a/vir/defs/low/ast/expression.rs b/vir/defs/low/ast/expression.rs index 90cefcd028c..21eadc12f13 100644 --- a/vir/defs/low/ast/expression.rs +++ b/vir/defs/low/ast/expression.rs @@ -3,7 +3,7 @@ use crate::common::display; #[derive_helpers] #[derive_visitors] -#[derive(derive_more::From, derive_more::IsVariant)] +#[derive(derive_more::From, derive_more::IsVariant, derive_more::Unwrap)] pub enum Expression { /// A Viper variable. /// @@ -30,6 +30,7 @@ pub enum Expression { FuncApp(FuncApp), DomainFuncApp(DomainFuncApp), InhaleExhale(InhaleExhale), + SmtOperation(SmtOperation), } #[display(fmt = "{}", "variable.name")] @@ -90,21 +91,14 @@ pub struct FieldAccessPredicate { pub position: Position, } -#[display( - fmt = "(unfolding acc({}({}), {}) in {})", - predicate, - "display::cjoin(arguments)", - permission, - base -)] +#[display(fmt = "(unfolding {} in {})", predicate, base)] pub struct Unfolding { - pub predicate: String, - pub arguments: Vec, - pub permission: Box, + pub predicate: PredicateAccessPredicate, pub base: Box, pub position: Position, } +#[derive(Copy)] pub enum UnaryOpKind { Not, Minus, @@ -172,6 +166,7 @@ pub struct ContainerOp { pub position: Position, } +#[derive(Copy)] pub enum ContainerOpKind { SeqEmpty, SeqConstructor, @@ -220,13 +215,15 @@ pub enum QuantifierKind { } #[display( - fmt = "{}(|{}| {}, triggers=[{}])", + fmt = "{}{}(|{}| {}, triggers=[{}])", kind, + "display::option!(name, \"<{}>\", \"\")", "display::cjoin(variables)", body, "display::join(\"; \", triggers)" )] pub struct Quantifier { + pub name: Option, pub kind: QuantifierKind, pub variables: Vec, pub triggers: Vec, @@ -242,9 +239,15 @@ pub struct LetExpr { pub position: Position, } +pub enum FuncAppContext { + Default, + QuantifiedPermission, +} + #[display(fmt = "{}({})", function_name, "display::cjoin(arguments)")] pub struct FuncApp { pub function_name: String, + pub context: FuncAppContext, pub arguments: Vec, pub parameters: Vec, pub return_type: Type, @@ -272,3 +275,15 @@ pub struct InhaleExhale { pub exhale_expression: Box, pub position: Position, } + +pub enum SmtOperationKind { + PbQe, +} + +#[display(fmt = "{}({})", operation_kind, "display::cjoin(arguments)")] +pub struct SmtOperation { + pub operation_kind: SmtOperationKind, + pub arguments: Vec, + pub return_type: Type, + pub position: Position, +} diff --git a/vir/defs/low/ast/function.rs b/vir/defs/low/ast/function.rs index c4784dee55c..b2f37d13be9 100644 --- a/vir/defs/low/ast/function.rs +++ b/vir/defs/low/ast/function.rs @@ -4,6 +4,8 @@ use crate::common::display; pub enum FunctionKind { MemoryBlockBytes, CallerFor, + Snap, + SnapRange, } #[display( @@ -13,7 +15,7 @@ pub enum FunctionKind { "display::cjoin(parameters)", return_type, "display::foreach!(\" requires {}\n\", pres)", - "display::foreach!(\" ensures {}\n\", pres)", + "display::foreach!(\" ensures {}\n\", posts)", "display::option!(body, \"{{ {} }}\n\", \"\")" )] pub struct FunctionDecl { diff --git a/vir/defs/low/ast/mod.rs b/vir/defs/low/ast/mod.rs index 59a41cb7413..729e6f2c19e 100644 --- a/vir/defs/low/ast/mod.rs +++ b/vir/defs/low/ast/mod.rs @@ -4,9 +4,10 @@ Clone, serde::Serialize, serde::Deserialize, - PartialEq(ignore=[position]), + PartialEq(trait_type=std::cmp::PartialEq,ignore=[position]), Eq, - Hash(ignore=[position]) + Hash(trait_type=core::hash::Hash,ignore=[position]), + Hash(trait_type=crate::common::traits::HashWithPosition,ignore=[]) )] #![derive_for_all_structs(new, new_with_pos)] diff --git a/vir/defs/low/ast/predicate.rs b/vir/defs/low/ast/predicate.rs index dbecbc3ad5d..da7f4e97f31 100644 --- a/vir/defs/low/ast/predicate.rs +++ b/vir/defs/low/ast/predicate.rs @@ -1,14 +1,34 @@ use super::{expression::Expression, variable::VariableDecl}; use crate::common::display; +#[derive(Copy)] +pub enum PredicateKind { + MemoryBlock, + Owned, + LifetimeToken, + CloseFracRef, + // /// Can be aliased, permission from range (0; 1) + // WithoutSnapshotFrac, + /// Can be aliased, permission is either 0 or 1. + WithoutSnapshotWhole, + /// Cannot be aliased, permission is either 0 or 1. + WithoutSnapshotWholeNonAliased, + /// Can be aliased, duplicable. + DeadLifetimeToken, + /// Cannot be aliased, identified by non-SSA lifetime. + EndBorrowViewShift, +} + #[display( - fmt = "predicate {}({}){}\n", + fmt = "predicate<{}> {}({}){}\n", + kind, "name", "display::cjoin(parameters)", "display::option!(body, \" {{\n {}\n}}\", \";\")" )] pub struct PredicateDecl { pub name: String, + pub kind: PredicateKind, pub parameters: Vec, pub body: Option, } diff --git a/vir/defs/low/ast/statement.rs b/vir/defs/low/ast/statement.rs index 1c2f9756af7..d87bb3a811d 100644 --- a/vir/defs/low/ast/statement.rs +++ b/vir/defs/low/ast/statement.rs @@ -6,6 +6,7 @@ use crate::common::display; #[derive(derive_more::From, derive_more::IsVariant)] pub enum Statement { Comment(Comment), + Label(Label), LogEvent(LogEvent), Assume(Assume), Assert(Assert), @@ -17,6 +18,8 @@ pub enum Statement { MethodCall(MethodCall), Assign(Assign), Conditional(Conditional), + MaterializePredicate(MaterializePredicate), + CaseSplit(CaseSplit), } #[display(fmt = "// {}", comment)] @@ -24,10 +27,17 @@ pub struct Comment { pub comment: String, } +#[display(fmt = "label {}", label)] +pub struct Label { + pub label: String, + pub position: Position, +} + #[display(fmt = "log-event {}", expression)] /// Log an event by assuming a (fresh) domain function. pub struct LogEvent { pub expression: Expression, + pub position: Position, } #[display(fmt = "assume {}", expression)] @@ -106,3 +116,20 @@ pub struct Conditional { pub else_branch: Vec, pub position: Position, } + +#[display(fmt = "materialize_predicate({}, {})", predicate, check_that_exists)] +pub struct MaterializePredicate { + pub predicate: Expression, + /// Whether we should check that the predicate chunk actually exists. + /// `materialize_predicate!` corresponds to `true` and `quantified_predicate!` + /// corresponds to `false`. + pub check_that_exists: bool, + pub position: Position, +} + +#[display(fmt = "case-split {}", expression)] +/// Case-split on a pure boolean expression. This operation is ignored by fold-unfold. +pub struct CaseSplit { + pub expression: Expression, + pub position: Position, +} diff --git a/vir/defs/low/ast/ty.rs b/vir/defs/low/ast/ty.rs index c8952156e63..e2308b62bee 100644 --- a/vir/defs/low/ast/ty.rs +++ b/vir/defs/low/ast/ty.rs @@ -23,7 +23,9 @@ pub enum Float { } pub enum BitVector { + #[display(fmt = "S{}", "self")] Signed(BitVectorSize), + #[display(fmt = "U{}", "self")] Unsigned(BitVectorSize), } @@ -35,18 +37,22 @@ pub enum BitVectorSize { BV128, } +#[display(fmt = "Seq({})", element_type)] pub struct Seq { pub element_type: Box, } +#[display(fmt = "Set({})", element_type)] pub struct Set { pub element_type: Box, } +#[display(fmt = "MultiSet({})", element_type)] pub struct MultiSet { pub element_type: Box, } +#[display(fmt = "D({})", name)] pub struct Domain { pub name: String, } diff --git a/vir/defs/low/cfg/mod.rs b/vir/defs/low/cfg/mod.rs index 7322bd5527b..5091fae1503 100644 --- a/vir/defs/low/cfg/mod.rs +++ b/vir/defs/low/cfg/mod.rs @@ -4,9 +4,9 @@ Clone, serde::Serialize, serde::Deserialize, - PartialEq(ignore=[position]), + PartialEq(trait_type=std::cmp::PartialEq,ignore=[position]), Eq, - Hash(ignore=[position]) + Hash(trait_type=core::hash::Hash,ignore=[position]) )] #![derive_for_all_structs(new, new_with_pos)] diff --git a/vir/defs/low/cfg/procedure.rs b/vir/defs/low/cfg/procedure.rs index c1c48658d07..dcc5923ecb8 100644 --- a/vir/defs/low/cfg/procedure.rs +++ b/vir/defs/low/cfg/procedure.rs @@ -1,33 +1,39 @@ use crate::{ common::display, - low::ast::{expression::Expression, statement::Statement, variable::VariableDecl}, + low::ast::{ + expression::Expression, position::Position, statement::Statement, variable::VariableDecl, + }, }; +use std::collections::BTreeMap; #[display( fmt = "procedure {} {{\n{}\n{}}}\n", name, "display::foreach!(\" var {};\n\", locals)", - "display::foreach!(\"{}\n\", basic_blocks)" + "display::foreach2!(\" label {}\n{}\", basic_blocks.keys(), basic_blocks.values())" )] pub struct ProcedureDecl { pub name: String, pub locals: Vec, - pub basic_blocks: Vec, + pub custom_labels: Vec