├── LICENSE ├── Makefile.in ├── README.md ├── config.h.in ├── configure ├── configure.ac ├── doc └── limitations.md ├── lib ├── cache.cpp ├── cfg.cpp ├── check.cpp ├── dead_code_elimination.cpp ├── loop_unroll.cpp ├── memory_opt.cpp ├── read_aarch64.cpp ├── read_ir.cpp ├── read_riscv.cpp ├── simplify_insts.cpp ├── smt_cvc5.cpp ├── smt_z3.cpp ├── smtgcc.cpp ├── smtgcc.h ├── util.cpp ├── validate_ir.cpp └── vrp.cpp ├── plugin ├── aarch64.cpp ├── gimple_conv.cpp ├── gimple_conv.h ├── riscv.cpp ├── smtgcc-check-refine.cpp ├── smtgcc-tv-backend.cpp └── smtgcc-tv.cpp └── tools ├── smtgcc-check-refine.cpp ├── smtgcc-check-ub.cpp └── smtgcc-opt.cpp /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile.in: -------------------------------------------------------------------------------- 1 | CC:=@TARGET_COMPILER@ 2 | GCCPLUGINS_DIR:= $(shell $(CC) --print-file-name=plugin) 3 | ARCH:= $(shell $(CC) -dumpmachine) 4 | 5 | CXXFLAGS:=-std=gnu++20 -I. -Ilib -g -fPIC -fno-rtti -O2 -Wall -Wextra 6 | ifeq ($(findstring aarch64, $(ARCH)), aarch64) 7 | CXXFLAGS += -DSMTGCC_AARCH64 8 | else ifeq ($(findstring riscv, $(ARCH)), riscv) 9 | CXXFLAGS += -DSMTGCC_RISCV 10 | endif 11 | 12 | LIBS:=@LIBS@ 13 | 14 | .PHONY: all clean 15 | 16 | tools = \ 17 | smtgcc-check-refine \ 18 | smtgcc-check-ub \ 19 | smtgcc-opt 20 | 21 | plugins = \ 22 | smtgcc-check-refine.so \ 23 | smtgcc-tv.so 24 | ifeq ($(findstring aarch64, $(ARCH)), aarch64) 25 | plugins += smtgcc-tv-backend.so 26 | else ifeq ($(findstring riscv, $(ARCH)), riscv) 27 | plugins += smtgcc-tv-backend.so 28 | endif 29 | 30 | lib_sources = \ 31 | lib/cache.cpp \ 32 | lib/cfg.cpp \ 33 | lib/check.cpp \ 34 | lib/dead_code_elimination.cpp \ 35 | lib/loop_unroll.cpp \ 36 | lib/memory_opt.cpp \ 37 | lib/read_aarch64.cpp \ 38 | lib/read_ir.cpp \ 39 | lib/read_riscv.cpp \ 40 | lib/simplify_insts.cpp \ 41 | lib/smt_cvc5.cpp \ 42 | lib/smt_z3.cpp \ 43 | lib/smtgcc.cpp \ 44 | lib/util.cpp \ 45 | lib/validate_ir.cpp \ 46 | lib/vrp.cpp 47 | 48 | lib_objects = $(lib_sources:.cpp=.o) 49 | 50 | tools_sources = \ 51 | tools/smtgcc-check-refine.cpp \ 52 | tools/smtgcc-check-ub.cpp \ 53 | tools/smtgcc-opt.cpp 54 | tools_objects = $(tools_sources:.cpp=.o) 55 | 56 | plugin_sources = \ 57 | plugin/gimple_conv.cpp \ 58 | plugin/smtgcc-check-refine.cpp \ 59 | plugin/smtgcc-tv-backend.cpp \ 60 | plugin/smtgcc-tv.cpp 61 | ifeq ($(findstring aarch64, $(ARCH)), aarch64) 62 | plugin_sources += plugin/aarch64.cpp 63 | backend_objs = plugin/aarch64.o 64 | else ifeq ($(findstring riscv, $(ARCH)), riscv) 65 | plugin_sources += plugin/riscv.cpp 66 | backend_objs = plugin/riscv.o 67 | endif 68 | plugin_objects = $(plugin_sources:.cpp=.o) 69 | 70 | 71 | all: $(tools) $(plugins) 72 | 73 | install: $(plugins) 74 | for plugin in $(plugins); do \ 75 | install $$plugin $(GCCPLUGINS_DIR); \ 76 | done 77 | 78 | clean: 79 | rm -f $(lib_objects) $(tools_objects) $(plugin_objects) $(tools) $(plugins) 80 | 81 | distclean: clean 82 | rm -f Makefile config.h config.log config.status 83 | 84 | $(lib_objects) $(tools_objects): config.h lib/smtgcc.h Makefile 85 | $(lib_objects) $(tools_objects): %.o: %.cpp 86 | $(CXX) $(CXXFLAGS) -c $< -o $@ 87 | 88 | $(plugin_objects): config.h lib/smtgcc.h plugin/gimple_conv.h Makefile 89 | $(plugin_objects): %.o: %.cpp 90 | $(CXX) $(CXXFLAGS) -I$(GCCPLUGINS_DIR)/include -c $< -o $@ 91 | 92 | smtgcc-check-refine: tools/smtgcc-check-refine.o $(lib_objects) 93 | $(CXX) $(CXXFLAGS) tools/smtgcc-check-refine.o -o $@ $(lib_objects) $(LIBS) 94 | smtgcc-check-ub: tools/smtgcc-check-ub.o $(lib_objects) 95 | $(CXX) $(CXXFLAGS) tools/smtgcc-check-ub.o -o $@ $(lib_objects) $(LIBS) 96 | 97 | smtgcc-opt: tools/smtgcc-opt.o $(lib_objects) 98 | $(CXX) $(CXXFLAGS) tools/smtgcc-opt.o -o $@ $(lib_objects) $(LIBS) 99 | 100 | smtgcc-check-refine.so: plugin/smtgcc-check-refine.o plugin/gimple_conv.o $(lib_objects) 101 | $(CXX) $(CXXFLAGS) -shared plugin/smtgcc-check-refine.o plugin/gimple_conv.o $(lib_objects) -o $@ $(LIBS) 102 | 103 | smtgcc-tv-backend.so: plugin/smtgcc-tv-backend.o plugin/gimple_conv.o $(backend_objs) $(lib_objects) 104 | $(CXX) $(CXXFLAGS) -shared plugin/smtgcc-tv-backend.o plugin/gimple_conv.o $(backend_objs) $(lib_objects) -o $@ $(LIBS) 105 | 106 | smtgcc-tv.so: plugin/smtgcc-tv.o plugin/gimple_conv.o $(lib_objects) 107 | $(CXX) $(CXXFLAGS) -shared plugin/smtgcc-tv.o plugin/gimple_conv.o $(lib_objects) -o $@ $(LIBS) 108 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # smtgcc 2 | This is an implementation of translation validation for GCC (similar to LLVM's [Alive2](https://github.com/AliveToolkit/alive2)), used to find bugs in the compiler. 3 | 4 | The main functionality is in a plugin, which is passed to GCC when compiling: 5 | ``` 6 | gcc -O3 -fplugin=smtgcc-tv file.c 7 | ``` 8 | This plugin checks the GCC IR (Intermediate Representation) before and after each optimization pass and reports an error if the IR after a pass is not a refinement of the input IR (i.e., the optimized code does not behave the same as the input source code, indicating that GCC has miscompiled the program). While the tool has some limitations, it has already discovered several bugs in GCC. A partial list of bugs found includes: 9 | [106513](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106513), 10 | [106523](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106523), 11 | [106744](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106744), 12 | [106883](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106883), 13 | [106884](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106884), 14 | [106990](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106990), 15 | [108625](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108625), 16 | [109626](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109626), 17 | [110434](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=110434), 18 | [110487](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=110487), 19 | [110495](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=110495), 20 | [110554](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=110554), 21 | [110760](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=110760), 22 | [111257](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=111257), 23 | [111280](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=111280), 24 | [111494](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=111494), 25 | [112736](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=112736), 26 | [113588](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=113588), 27 | [113590](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=113590), 28 | [113630](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=113630), 29 | [113703](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=113703), 30 | [114032](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=114032), 31 | [114056](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=114056), 32 | [114090](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=114090), 33 | [116120](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=116120), 34 | [116355](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=116355), 35 | [117186](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=117186), 36 | [117688](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=117688), 37 | [117690](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=117690), 38 | [117692](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=117692), 39 | [117927](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=117927), 40 | [118174](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=118174), 41 | [118669](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=118669), 42 | [119399](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=119399), 43 | [119720](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=119720), 44 | [119971](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=119971), 45 | [120333](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=120333). 46 | 47 | The implementation is described in a series of blog posts. The first posts describe a previous version of this tool ([pysmtgcc](https://github.com/kristerw/pysmtgcc)), but the general ideas are the same for both tools: 48 | 1. [Writing a GCC plugin in Python](https://kristerw.github.io/2022/10/20/gcc-python-plugin/) 49 | 2. [Verifying GCC optimizations using an SMT solver](https://kristerw.github.io/2022/11/01/verifying-optimizations/) 50 | 3. [Memory representation](https://kristerw.github.io/2023/07/17/memory-representation/) 51 | 4. [Address calculations](https://kristerw.github.io/2023/07/18/address-calculations/) 52 | 5. [Pointer alignment](https://kristerw.github.io/2023/07/20/pointer-alignment/) 53 | 6. Problems with pointers 54 | 7. Uninitialized memory 55 | 8. Control flow 56 | 57 | # Compiling smtgcc 58 | You must have the Z3 SMT solver installed. For example, as 59 | ``` 60 | sudo apt install libz3-dev 61 | ``` 62 | Configuring and building `smtgcc` is done by `configure` and `make`, and you must specify the target compiler for which to build the GCC plugins 63 | ``` 64 | ./configure --with-target-compiler=/path/to/install/bin/gcc 65 | make 66 | ``` 67 | 68 | # plugins 69 | 70 | ## smtgcc-tv 71 | smtgcc-tv compares the IR before/after each GIMPLE pass and complains if the resulting IR is not a refinement of the input (i.e. if the GIMPLE pass miscompiled the program). 72 | 73 | For example, compiling the function `foo` from [PR 111494](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=111494) 74 | ```c 75 | int a[32]; 76 | int foo(int n) { 77 | int sum = 0; 78 | for (int i = 0; i < n; i++) 79 | sum += a[i]; 80 | return sum; 81 | } 82 | ``` 83 | with a compiler where the bug is not fixed (for example, the current trunk GCC) using `smtgcc-tv.so` 84 | ``` 85 | gcc -O3 -fno-strict-aliasing -c -fplugin=/path/to/smtgcc-tv.so pr111494.c 86 | ``` 87 | gives us the output 88 | ``` 89 | pr111494.c: In function 'foo': 90 | pr111494.c:2:5: note: ifcvt -> dce: Transformation is not correct (UB) 91 | .param0 = #x00000005 92 | .memory = (let ((a!1 (store (store (store ((as const (Array (_ BitVec 64) (_ BitVec 8))) 93 | [...] 94 | ``` 95 | telling us that the output IR of the dce pass is not a refinement of the input that comes from ifcvt (in this case the error is in the vectorizer pass, but we are treating vect followed by dce as one pass because of [PR 111257](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=111257)) because the result has more UB than the original, and the tool give us an example for `n = 5` where this happens. 96 | 97 | ### Limitations 98 | Limitations in the current version: 99 | * Function calls are not implemented. 100 | * Exceptions are not implemented. 101 | * Only tested on C and C++ source code. 102 | * Only little endian targets are supported. 103 | * Irreducible loops are not handled. 104 | * Memory semantics is not correct: 105 | - Strict aliasing does not work, so you must pass `-fno-strict-aliasing` to the compiler. 106 | - Handling of pointer provenance is too restrictive. 107 | - ... 108 | 109 | ## smtgcc-tv-backend 110 | smtgcc-tv-backend compares the IR from the last GIMPLE pass with the generated assembly code and reports an error if the resulting assembly code is not a refinement of the IR (i.e., if the backend has miscompiled the program). 111 | 112 | The plugin supports the RISC-V RV32G and RV64G base profiles and the Zba, Zbb, Zbc, and Zbs extensions. It can be invoked as follows: 113 | ``` 114 | riscv64-elf-gcc -O3 -march=rv64gc_zba_zbb_zbs -fno-section-anchors -fno-strict-aliasing -c -fplugin=/path/to/smtgcc-tv-backend.so file.c 115 | ``` 116 | ### Limitations 117 | The limitations of smtgcc-tv-backend include all those listed for smtgcc-tv, with the following additional limitations: 118 | * You must pass `-fno-section-anchors` to the compiler. 119 | * Address allocation of local variables differs between the IR and the generated assembly. As a result, the plugin disables checks when a local address is written to memory or cast to an integer type to avoid false positives. 120 | 121 | ## smtgcc-check-refine 122 | smtgcc-check-refine requires the translation unit to consist of two functions named `src` and `tgt`, and it verifies that `tgt` is a refinement of `src`. 123 | 124 | For example, testing changing the order of signed addition 125 | ```c 126 | int src(int a, int b, int c) 127 | { 128 | return a + c + b; 129 | } 130 | 131 | int tgt(int a, int b, int c) 132 | { 133 | return a + b + c; 134 | } 135 | ``` 136 | by compiling as 137 | ``` 138 | gcc -O3 -fno-strict-aliasing -c -fplugin=/path/to/smtgcc-check-refine.so example.c 139 | ``` 140 | gives us the output 141 | ``` 142 | example.c: In function 'tgt': 143 | example.c:6:5: note: Transformation is not correct (UB) 144 | .param2 = #x9620d6eb 145 | .param0 = #x7edbb92a 146 | .param1 = #x062be612 147 | ``` 148 | telling us that `tgt` invokes undefined behavior in cases where `src` does not, 149 | and gives us an example of input where this happens (the values are, unfortunately, written as unsigned values. In this case, it means `[c = -1776232725, a = 2128329002, b = 103540242]`). 150 | 151 | **Note**: smtgcc-check-refine works on the IR from the ssa pass, i.e., early enough that the compiler has not done many optimizations. But GCC does peephole optimizations earlier (even when compiling as `-O0`), so we need to prevent that from happening when testing such optimizations. The pre-GIMPLE optimizations are done one statement at a time, so we can disable the optimization by splitting the optimized pattern into two statements. For example, to check the following optimization 152 | ``` 153 | -(a - b) -> b - a 154 | ``` 155 | we can write the test as 156 | ``` 157 | int src(int a, int b) 158 | { 159 | int t = a - b; 160 | return -t; 161 | } 162 | 163 | int tgt(int a, int b) 164 | { 165 | return b - a; 166 | } 167 | ``` 168 | Another way to verify such optimizations is to write the test in GIMPLE and pass the `-fgimple` flag to the compiler. 169 | 170 | It is good practice to check with `-fdump-tree-ssa` that the IR used by the tool looks as expected. 171 | 172 | ### Limitations 173 | smtgcc-check-refine has the same limitations as smtgcc-tv. 174 | 175 | # Environment variables 176 | * `SMTGCC_VERBOSE` — Print debug information while running. Valid value 0-3, higher value prints more information (Default: 0) 177 | * `SMTGCC_TIMEOUT` — SMT solver timeout (Default: 120000) 178 | * `SMTGCC_MEMORY_LIMIT` — SMT solver memory use limit in megabytes (Default: 5120) 179 | * `SMTGCC_CACHE` — Set to "redis" to use a Redis database to cache SMT queries. 180 | * `SMTGCC_ASM` — Set to the file name of the assembly to override the default when using smtgcc-tv-backend. 181 | -------------------------------------------------------------------------------- /config.h.in: -------------------------------------------------------------------------------- 1 | /* config.h.in. Generated from configure.ac by autoheader. */ 2 | 3 | /* Define to 1 if you have hiredis. */ 4 | #undef HAVE_HIREDIS 5 | 6 | /* Define to 1 if you have cvc5. */ 7 | #undef HAVE_LIBCVC5 8 | 9 | /* Define to 1 if you have z3. */ 10 | #undef HAVE_LIBZ3 11 | 12 | /* Define to the address where bug reports for this package should be sent. */ 13 | #undef PACKAGE_BUGREPORT 14 | 15 | /* Define to the full name of this package. */ 16 | #undef PACKAGE_NAME 17 | 18 | /* Define to the full name and version of this package. */ 19 | #undef PACKAGE_STRING 20 | 21 | /* Define to the one symbol short name of this package. */ 22 | #undef PACKAGE_TARNAME 23 | 24 | /* Define to the home page for this package. */ 25 | #undef PACKAGE_URL 26 | 27 | /* Define to the version of this package. */ 28 | #undef PACKAGE_VERSION 29 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | AC_INIT([smtgcc],[0.0]) 2 | 3 | AC_PROG_CXX() 4 | AC_LANG([C++]) 5 | 6 | AC_MSG_CHECKING([Compiler to compile plugins for]) 7 | AC_ARG_WITH([target-compiler], 8 | [AS_HELP_STRING([--with-target-compiler=PATH], 9 | [Path to the compiler to use with the plugins])], 10 | [TARGET_COMPILER="$withval"], 11 | [TARGET_COMPILER=$(which gcc)]) 12 | AC_MSG_RESULT([$TARGET_COMPILER]) 13 | AC_SUBST([TARGET_COMPILER]) 14 | 15 | AC_MSG_CHECKING([for -lz3]) 16 | save_LIBS=$LIBS 17 | LIBS="-lz3 $LIBS" 18 | AC_LINK_IFELSE([AC_LANG_PROGRAM([[ 19 | #include 20 | ]], [[ 21 | z3::context ctx; 22 | ]])], [AC_DEFINE([HAVE_LIBZ3], [1], [Define to 1 if you have z3.]) 23 | AC_MSG_RESULT([yes])], 24 | [LIBS=$save_LIBS 25 | AC_MSG_RESULT([no])]) 26 | 27 | AC_MSG_CHECKING([for -lcvc5]) 28 | save_LIBS=$LIBS 29 | LIBS="-lcvc5 $LIBS" 30 | AC_LINK_IFELSE([AC_LANG_PROGRAM([[ 31 | #include 32 | ]], [[ 33 | cvc5::Solver solver; 34 | solver.setOption("produce-models", "true"); 35 | ]])], [AC_DEFINE([HAVE_LIBCVC5], [1], [Define to 1 if you have cvc5.]) 36 | AC_MSG_RESULT([yes])], 37 | [LIBS=$save_LIBS 38 | AC_MSG_RESULT([no])]) 39 | 40 | AC_MSG_CHECKING([for -lhiredis]) 41 | save_LIBS=$LIBS 42 | LIBS="-lhiredis $LIBS" 43 | AC_LINK_IFELSE([AC_LANG_PROGRAM([[ 44 | #include 45 | ]], [[ 46 | redisConnect("", 0); 47 | ]])], [AC_DEFINE([HAVE_HIREDIS], [1], [Define to 1 if you have hiredis.]) 48 | AC_MSG_RESULT([yes])], 49 | [LIBS=$save_LIBS 50 | AC_MSG_RESULT([no])]) 51 | 52 | AC_CONFIG_HEADERS([config.h]) 53 | AC_CONFIG_FILES([Makefile]) 54 | AC_OUTPUT() 55 | -------------------------------------------------------------------------------- /doc/limitations.md: -------------------------------------------------------------------------------- 1 | # Limitations 2 | 3 | ## Loops 4 | 5 | ### Limitations on loop iterations 6 | smtgcc cannot handle loops directly, so it unrolls them for a fixed number of iterations (currently 16). Any iterations beyond this are treated as UB. 7 | 8 | For example, a loop like: 9 | ```c 10 | for (int i = 0; i < n; i++) 11 | { 12 | ... 13 | } 14 | ``` 15 | is only checked for n < 16. Bugs that only appear in later iterations are not detected by smtgcc. 16 | 17 | A loop like: 18 | ```c 19 | for (int i = 0; i < 100; i++) 20 | { 21 | ... 22 | } 23 | ``` 24 | always exceeds the limit, so it is treated as UB and skipped entirely. 25 | 26 | ### Backend checking: Loops from structure assignment, etc. 27 | The backend may lower structure assignments, `memcpy`, `memset`, etc., into loops that iterate beyond smtgcc's unroll limit. smtgcc will then report a false alarm because it considers the assembly code to have more UB than the original (as loops iterating beyond smtgcc's unroll limit are treated as UB). 28 | 29 | This issue can be observed with the code below when compiled for RISC-V with -march=rv64gcv: 30 | ```c 31 | struct S { 32 | unsigned char a[320]; 33 | } s1, s2; 34 | 35 | void foo() 36 | { 37 | s1 = s2; 38 | } 39 | ``` 40 | The code is generated as: 41 | ``` 42 | foo: 43 | lui a5,%hi(s1) 44 | lui a4,%hi(s2) 45 | addi a5,a5,%lo(s1) 46 | addi a4,a4,%lo(s2) 47 | li a2,320 48 | .L2: 49 | vsetvli a3,a2,e8,m1,ta,ma 50 | vle8.v v1,0(a4) 51 | sub a2,a2,a3 52 | add a4,a4,a3 53 | vse8.v v1,0(a5) 54 | add a5,a5,a3 55 | bne a2,zero,.L2 56 | ret 57 | ``` 58 | which requires 20 iterations to copy the structure, and smtgcc reports: 59 | ``` 60 | foo.c:foo: Transformation is not correct (UB) 61 | ``` 62 | 63 | ### Backend checking: Loop canonicalization 64 | The backend may rewrite loops in a way that causes problems for smtgcc, as it currently does not perform loop canonicalization. 65 | 66 | To illustrate the problem, consider a loop: 67 | ```c 68 | do { 69 | ... 70 | } while (a || b); 71 | ``` 72 | Depending on how the backend lowers this, the assembly might look like two nested loops: 73 | ```asm 74 | .L2: 75 | ... 76 | bne a0,zero,.L2 77 | bne a1,zero,.L2 78 | ``` 79 | or a single loop: 80 | ```asm 81 | .L2: 82 | ... 83 | or a2,a0,a1 84 | bne a2,zero,.L2 85 | ``` 86 | The first case is unrolled as two nested loops, effectively unrolled 16*16 iterations, while the second case is unrolled for only 16 iterations. 87 | 88 | Simple loops like these are usually handled correctly by the tool, but more complex loops can cause issues where smtgcc may interpret the original source code as having two nested loops, while the assembly has only one. 89 | 90 | ## Memory 91 | TODO 92 | 93 | ## Floating-point 94 | TODO 95 | -------------------------------------------------------------------------------- /lib/cache.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "config.h" 5 | 6 | #ifdef HAVE_HIREDIS 7 | #include 8 | #endif 9 | 10 | #include "smtgcc.h" 11 | 12 | using namespace std::string_literals; 13 | namespace smtgcc { 14 | namespace { 15 | 16 | // The xxhash XXH64 algorithm as specified in: 17 | // https://github.com/Cyan4973/xxHash/blob/dev/doc/xxhash_spec.md 18 | struct Hash 19 | { 20 | template 21 | void add(const T& data) 22 | { 23 | uint64_t stripe_size = data_size % sizeof(stripe); 24 | char *src = (char *)&data; 25 | uint64_t size = sizeof(data); 26 | while (size > 0) 27 | { 28 | uint64_t len = std::min(size, sizeof(stripe) - stripe_size); 29 | memcpy((char *)&stripe + stripe_size, src, len); 30 | src += len; 31 | stripe_size += len; 32 | size -= len; 33 | if (stripe_size == sizeof(stripe)) 34 | { 35 | for (int i = 0; i < 4; i++) 36 | acc[i] = round(acc[i], stripe[i]); 37 | stripe_size = 0; 38 | } 39 | } 40 | data_size += sizeof(data); 41 | } 42 | 43 | std::string finish() 44 | { 45 | uint64_t a; 46 | if (data_size < sizeof(stripe)) 47 | a = prime5; 48 | else 49 | { 50 | a = rot(acc[0], 1); 51 | a += rot(acc[1], 7); 52 | a += rot(acc[2], 12); 53 | a += rot(acc[3], 18); 54 | for (int i = 0; i < 4; i++) 55 | a = (a ^ round(0, acc[i])) * prime1 + prime4; 56 | } 57 | 58 | a = a + data_size; 59 | 60 | if (uint64_t remaining = data_size % sizeof(stripe); remaining != 0) 61 | { 62 | char *src = (char *)&stripe; 63 | while (remaining >= 8) 64 | { 65 | uint64_t lane; 66 | memcpy(&lane, src, 8); 67 | src += 8; 68 | remaining -= 8; 69 | a = rot(a ^ round(0, lane), 27) * prime1 + prime4; 70 | } 71 | if (remaining >= 4) 72 | { 73 | uint32_t lane; 74 | memcpy(&lane, src, 4); 75 | src += 4; 76 | remaining -= 4; 77 | a = a ^ (lane * prime1); 78 | a = rot(a, 23) * prime2 + prime3; 79 | } 80 | while (remaining >= 1) 81 | { 82 | uint8_t lane; 83 | memcpy(&lane, src, 1); 84 | src += 1; 85 | remaining -= 1; 86 | a = a ^ (lane * prime5); 87 | a = rot(a, 11) * prime1; 88 | } 89 | } 90 | 91 | a = (a ^ (a >> 33)) * prime2; 92 | a = (a ^ (a >> 29)) * prime3; 93 | a = a ^ (a >> 32); 94 | 95 | char str[16 + 1]; 96 | char *p = str; 97 | for (int i = 15; i >= 0; i--) 98 | *p++ = hex_char((a >> (i * 4)) & 0xf); 99 | *p++ = 0; 100 | 101 | return std::string(str); 102 | } 103 | 104 | private: 105 | char hex_char(int x) 106 | { 107 | if (x < 10) 108 | return '0' + x; 109 | else 110 | return 'a' + (x - 10); 111 | } 112 | 113 | uint64_t rot(uint64_t x, uint64_t n) 114 | { 115 | return (x << n) | x >> (64 - n); 116 | } 117 | 118 | uint64_t round(uint64_t acc, uint64_t lane) 119 | { 120 | return rot(acc + (lane * prime2), 31) * prime1; 121 | } 122 | 123 | const uint64_t prime1 = 0x9E3779B185EBCA87; 124 | const uint64_t prime2 = 0xC2B2AE3D27D4EB4F; 125 | const uint64_t prime3 = 0x165667B19E3779F9; 126 | const uint64_t prime4 = 0x85EBCA77C2B2AE63; 127 | const uint64_t prime5 = 0x27D4EB2F165667C5; 128 | 129 | uint64_t data_size = 0; 130 | uint64_t acc[4] = {prime1 + prime2, prime2, 0, -prime1}; 131 | uint64_t stripe[4]; 132 | }; 133 | 134 | } // end anonymous namespace 135 | 136 | Cache::Cache(Function *func) 137 | { 138 | if (config.redis_cache) 139 | key = hash(func); 140 | } 141 | 142 | std::string Cache::hash(Function *func) 143 | { 144 | Hash h; 145 | 146 | // Add a version to the hash. 147 | // 148 | // We should try to remember to update the version when the implementation 149 | // changes in a way that invalidates the cache. In practice, this only 150 | // applies when the SMT code is updated or when inst_info is reordered. 151 | // Adding or removing inst_info values does not require the version to be 152 | // updated, as we instead add the number of inst_info as a separate value. 153 | const uint32_t version = 0; 154 | h.add(version); 155 | const uint32_t nof_inst_info = inst_info.size(); 156 | h.add(nof_inst_info); 157 | 158 | // We add the SMT timeout/memory_limit to prevent the cache from returning 159 | // a time out status from a previous run when we try again with a larger 160 | // timeout. 161 | h.add(config.timeout); 162 | h.add(config.memory_limit); 163 | 164 | // Module config 165 | h.add(func->module->ptr_bits); 166 | h.add(func->module->ptr_id_bits); 167 | h.add(func->module->ptr_offset_bits); 168 | 169 | // Hash the function. 170 | for (Basic_block *bb : func->bbs) 171 | { 172 | for (auto phi : bb->phis) 173 | { 174 | assert(phi->has_lhs()); 175 | h.add(phi->op); 176 | h.add(phi->bitsize); 177 | h.add(phi->id); 178 | for (auto [arg_inst, arg_bb] : phi->phi_args) 179 | { 180 | h.add(arg_inst->id); 181 | h.add(arg_bb->id); 182 | } 183 | } 184 | for (Inst *inst = bb->first_inst; inst; inst = inst->next) 185 | { 186 | h.add(inst->op); 187 | if (inst->has_lhs()) 188 | h.add(inst->id); 189 | switch (inst->iclass()) 190 | { 191 | case Inst_class::nullary: 192 | case Inst_class::iunary: 193 | case Inst_class::funary: 194 | case Inst_class::ibinary: 195 | case Inst_class::fbinary: 196 | case Inst_class::icomparison: 197 | case Inst_class::fcomparison: 198 | case Inst_class::conv: 199 | case Inst_class::ternary: 200 | for (uint16_t i = 0; i < inst->nof_args; i++) 201 | h.add(inst->args[i]->id); 202 | break; 203 | case Inst_class::special: 204 | if (inst->op == Op::BR) 205 | { 206 | if (inst->nof_args) 207 | { 208 | assert(inst->nof_args == 1); 209 | h.add(inst->args[0]->id); 210 | h.add(inst->u.br3.true_bb->id); 211 | h.add(inst->u.br3.false_bb->id); 212 | } 213 | else 214 | h.add(inst->u.br1.dest_bb->id); 215 | } 216 | else if (inst->op == Op::RET) 217 | { 218 | for (uint16_t i = 0; i < inst->nof_args; i++) 219 | h.add(inst->args[i]->id); 220 | } 221 | else if (inst->op == Op::VALUE) 222 | { 223 | h.add(inst->bitsize); 224 | h.add(inst->u.value.value); 225 | } 226 | else 227 | assert(0); 228 | } 229 | } 230 | } 231 | 232 | return h.finish(); 233 | } 234 | 235 | std::optional Cache::get() 236 | { 237 | #ifdef HAVE_HIREDIS 238 | if (config.redis_cache) 239 | { 240 | redisContext *ctx = redisConnect("127.0.0.1", 6379); 241 | if (!ctx) 242 | { 243 | fprintf(stderr, "SMTGCC: Cannot allocate Redis context\n"); 244 | return {}; 245 | } 246 | if (ctx->err) 247 | { 248 | fprintf(stderr, "SMTGCC: Redis error: %s\n", ctx->errstr); 249 | redisFree(ctx); 250 | return {}; 251 | } 252 | 253 | redisReply *reply = 254 | (redisReply *)redisCommand(ctx, "GET %s", key.c_str()); 255 | if (!reply) 256 | { 257 | fprintf(stderr, "SMTGCC: Redis error: %s\n", ctx->errstr); 258 | redisFree(ctx); 259 | return {}; 260 | } 261 | if (reply->type == REDIS_REPLY_NIL) 262 | { 263 | freeReplyObject(reply); 264 | redisFree(ctx); 265 | return {}; 266 | } 267 | 268 | Result_status status; 269 | switch (reply->str[0]) 270 | { 271 | case 'c': 272 | status = Result_status::correct; 273 | break; 274 | case 'i': 275 | status = Result_status::incorrect; 276 | break; 277 | case 'u': 278 | status = Result_status::unknown; 279 | break; 280 | default: 281 | throw Not_implemented("Unhandled value read from database"); 282 | } 283 | std::optional message; 284 | if (reply->str[1]) 285 | message = std::string(&reply->str[1]); 286 | 287 | freeReplyObject(reply); 288 | redisFree(ctx); 289 | 290 | return Solver_result(status, message); 291 | } 292 | #endif 293 | 294 | return {}; 295 | } 296 | 297 | void Cache::set(Solver_result result) 298 | { 299 | #ifdef HAVE_HIREDIS 300 | if (config.redis_cache) 301 | { 302 | redisContext *ctx = redisConnect("127.0.0.1", 6379); 303 | if (!ctx) 304 | { 305 | fprintf(stderr, "SMTGCC: Cannot allocate Redis context\n"); 306 | return; 307 | } 308 | if (ctx->err) 309 | { 310 | fprintf(stderr, "SMTGCC: Redis error: %s\n", ctx->errstr); 311 | redisFree(ctx); 312 | return; 313 | } 314 | 315 | std::string value; 316 | switch (result.status) 317 | { 318 | case Result_status::correct: 319 | value = 'c'; 320 | break; 321 | case Result_status::incorrect: 322 | value = 'i'; 323 | break; 324 | case Result_status::unknown: 325 | value = 'u'; 326 | break; 327 | } 328 | if (result.message) 329 | value = value + *result.message; 330 | 331 | redisReply *reply = (redisReply *)redisCommand(ctx, "SET %s %s", 332 | key.c_str(), 333 | value.c_str()); 334 | if (!reply || reply->type == REDIS_REPLY_ERROR) 335 | fprintf(stderr, "SMTGCC: Redis error: %s\n", ctx->errstr); 336 | if (reply) 337 | freeReplyObject(reply); 338 | redisFree(ctx); 339 | } 340 | #endif 341 | } 342 | 343 | } // end namespace smtgcc 344 | -------------------------------------------------------------------------------- /lib/cfg.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "smtgcc.h" 5 | 6 | namespace smtgcc { 7 | namespace { 8 | 9 | void dfs_walk(Basic_block *bb, std::vector& bbs, std::set& visited) 10 | { 11 | visited.insert(bb); 12 | for (auto succ : bb->succs) 13 | { 14 | if (!visited.contains(succ)) 15 | dfs_walk(succ, bbs, visited); 16 | } 17 | bbs.push_back(bb); 18 | } 19 | 20 | void inverse_dfs_walk(Basic_block *bb, std::vector& bbs, std::set& visited) 21 | { 22 | visited.insert(bb); 23 | for (auto pred : bb->preds) 24 | { 25 | if (!visited.contains(pred)) 26 | inverse_dfs_walk(pred, bbs, visited); 27 | } 28 | bbs.push_back(bb); 29 | } 30 | 31 | void remove_dead_bbs(std::vector& dead_bbs) 32 | { 33 | for (auto bb : dead_bbs) 34 | { 35 | for (auto succ : bb->succs) 36 | { 37 | for (auto phi : succ->phis) 38 | { 39 | phi->remove_phi_arg(bb); 40 | } 41 | } 42 | } 43 | 44 | for (auto bb : dead_bbs) 45 | { 46 | for (Inst *phi : bb->phis) 47 | { 48 | phi->remove_phi_args(); 49 | } 50 | } 51 | 52 | // We must remove instructions in reverse post order, but it is 53 | // not guaranteed that the dead BBs are in RPO, so we remove the 54 | // instructions we can remove, and iterate over the BBs until all 55 | // are removed. 56 | while (!dead_bbs.empty()) 57 | { 58 | for (int i = dead_bbs.size() - 1; i >= 0; i--) 59 | { 60 | Basic_block *bb = dead_bbs[i]; 61 | for (Inst *inst = bb->last_inst; inst;) 62 | { 63 | Inst *next_inst = inst->prev; 64 | if (inst->used_by.empty()) 65 | destroy_instruction(inst); 66 | inst = next_inst; 67 | } 68 | } 69 | while (!dead_bbs.empty()) 70 | { 71 | Basic_block *bb = dead_bbs.back(); 72 | if (bb->last_inst) 73 | break; 74 | dead_bbs.pop_back(); 75 | destroy_basic_block(bb); 76 | } 77 | } 78 | } 79 | 80 | // Remove empty BBs ending in unconditional branch by letting the 81 | // predecessors call the successor. 82 | void remove_empty_bb(Basic_block *bb) 83 | { 84 | if (bb->first_inst->op != Op::BR 85 | || bb->first_inst->nof_args != 0 86 | || bb->phis.size() != 0) 87 | return; 88 | 89 | Basic_block *dest_bb = bb->succs[0]; 90 | if (bb == dest_bb) 91 | return; 92 | 93 | if (!dest_bb->phis.empty()) 94 | { 95 | // We cannot remove this BB if any predecessor already branches to 96 | // the destination, as that would result in duplicated entries in 97 | // the phi node. 98 | for (auto pred : bb->preds) 99 | { 100 | auto it = std::find(pred->succs.begin(), pred->succs.end(), dest_bb); 101 | if (it != pred->succs.end()) 102 | return; 103 | } 104 | } 105 | 106 | for (auto phi : dest_bb->phis) 107 | { 108 | Inst *inst = phi->get_phi_arg(bb); 109 | phi->remove_phi_arg(bb); 110 | for (auto pred : bb->preds) 111 | { 112 | phi->add_phi_arg(inst, pred); 113 | } 114 | } 115 | 116 | while (!bb->preds.empty()) 117 | { 118 | Basic_block *pred = bb->preds.back(); 119 | assert(pred->last_inst->op == Op::BR); 120 | if (pred->last_inst->nof_args == 0) 121 | { 122 | destroy_instruction(pred->last_inst); 123 | pred->build_br_inst(dest_bb); 124 | } 125 | else 126 | { 127 | Inst *cond = pred->last_inst->args[0]; 128 | Basic_block *true_bb = pred->last_inst->u.br3.true_bb; 129 | Basic_block *false_bb = pred->last_inst->u.br3.false_bb; 130 | if (true_bb == bb) 131 | true_bb = dest_bb; 132 | if (false_bb == bb) 133 | false_bb = dest_bb; 134 | 135 | if (true_bb == false_bb) 136 | { 137 | assert(dest_bb->phis.empty()); 138 | destroy_instruction(pred->last_inst); 139 | pred->build_br_inst(dest_bb); 140 | } 141 | else 142 | { 143 | destroy_instruction(pred->last_inst); 144 | pred->build_br_inst(cond, true_bb, false_bb); 145 | } 146 | } 147 | } 148 | 149 | destroy_instruction(bb->first_inst); 150 | bb->build_br_inst(bb); 151 | } 152 | 153 | bool is_always_ub(Basic_block *bb) 154 | { 155 | return bb->first_inst->op == Op::UB && is_value_one(bb->first_inst->args[0]); 156 | } 157 | 158 | } // end anonymous namespace 159 | 160 | void clear_dominance(Function *func) 161 | { 162 | func->has_dominance = false; 163 | func->nearest_dom.clear(); 164 | func->nearest_postdom.clear(); 165 | } 166 | 167 | // We assume the CFG is loop-free and no dead BBs. 168 | void calculate_dominance(Function *func) 169 | { 170 | clear_dominance(func); 171 | 172 | // We must set has_dominance early as we call the dominance functions (for 173 | // cases we know it is safe) while we create the dominance information. 174 | func->has_dominance = true; 175 | 176 | // Calculate func->nearest_dom 177 | { 178 | std::vector post; 179 | post.reserve(func->bbs.size()); 180 | std::set visited; 181 | dfs_walk(func->bbs.front(), post, visited); 182 | func->nearest_dom.insert({post.back(), post.back()}); 183 | for (size_t i = 1; i < post.size(); i++) 184 | { 185 | Basic_block *bb = post[post.size() - i - 1]; 186 | assert(!bb->preds.empty()); 187 | Basic_block *dom = bb->preds[0]; 188 | for (;;) 189 | { 190 | bool found_dom = true; 191 | for (auto pred : bb->preds) 192 | { 193 | found_dom = found_dom && dominates(dom, pred); 194 | } 195 | if (found_dom) 196 | break; 197 | dom = func->nearest_dom.at(dom); 198 | } 199 | func->nearest_dom.insert({bb, dom}); 200 | } 201 | } 202 | 203 | // Calculate func->nearest_postdom 204 | { 205 | std::vector post; 206 | post.reserve(func->bbs.size()); 207 | std::set visited; 208 | inverse_dfs_walk(func->bbs.back(), post, visited); 209 | func->nearest_postdom.insert({post.back(), post.back()}); 210 | for (size_t i = 1; i < post.size(); i++) 211 | { 212 | Basic_block *bb = post[post.size() - i - 1]; 213 | assert(!bb->succs.empty()); 214 | Basic_block *dom = bb->succs[0]; 215 | for (;;) 216 | { 217 | bool found_dom = true; 218 | for (auto succ : bb->succs) 219 | { 220 | found_dom = found_dom && postdominates(dom, succ); 221 | } 222 | if (found_dom) 223 | break; 224 | dom = func->nearest_postdom.at(dom); 225 | } 226 | func->nearest_postdom.insert({bb, dom}); 227 | } 228 | } 229 | } 230 | 231 | Basic_block *nearest_dominator(const Basic_block *bb) 232 | { 233 | assert(bb->func->has_dominance); 234 | return bb->func->nearest_dom.at(bb); 235 | } 236 | 237 | // Check if bb1 dominates bb2 238 | bool dominates(const Basic_block *bb1, const Basic_block *bb2) 239 | { 240 | assert(bb1->func->has_dominance); 241 | for (;;) 242 | { 243 | if (bb1 == bb2) 244 | return true; 245 | if (bb2 == bb2->func->bbs.front()) 246 | return false; 247 | bb2 = bb2->func->nearest_dom.at(bb2); 248 | } 249 | } 250 | 251 | // Check if bb1 postdominates bb2 252 | bool postdominates(const Basic_block *bb1, const Basic_block *bb2) 253 | { 254 | assert(bb1->func->has_dominance); 255 | for (;;) 256 | { 257 | if (bb1 == bb2) 258 | return true; 259 | if (bb2 == bb2->func->bbs.back()) 260 | return false; 261 | bb2 = bb2->func->nearest_postdom.at(bb2); 262 | } 263 | } 264 | 265 | void reverse_post_order(Function *func) 266 | { 267 | auto it = std::find_if(func->bbs.begin(), func->bbs.end(), 268 | [](const Basic_block *bb) { 269 | return bb->last_inst->op == Op::RET; 270 | }); 271 | assert(it != func->bbs.end()); 272 | Basic_block *exit_bb = *it; 273 | 274 | std::vector post; 275 | post.reserve(func->bbs.size()); 276 | std::set visited; 277 | dfs_walk(func->bbs[0], post, visited); 278 | if (!visited.contains(exit_bb)) 279 | throw Not_implemented("unreachable exit BB (infinite loop)"); 280 | if (post.size() != func->bbs.size()) 281 | { 282 | std::vector dead_bbs; 283 | for (auto bb : func->bbs) 284 | { 285 | if (!visited.contains(bb)) 286 | dead_bbs.push_back(bb); 287 | } 288 | remove_dead_bbs(dead_bbs); 289 | } 290 | func->bbs.clear(); 291 | std::reverse_copy(post.begin(), post.end(), std::back_inserter(func->bbs)); 292 | if (func->bbs.back() != exit_bb) 293 | { 294 | auto it2 = std::find(func->bbs.begin(), func->bbs.end(), exit_bb); 295 | if (it2 != func->bbs.end()) 296 | func->bbs.erase(it2); 297 | func->bbs.push_back(exit_bb); 298 | } 299 | } 300 | 301 | bool has_loops(Function *func) 302 | { 303 | std::set visited; 304 | for (auto bb : func->bbs) 305 | { 306 | visited.insert(bb); 307 | for (auto succ : bb->succs) 308 | { 309 | if (visited.contains(succ)) 310 | return true; 311 | } 312 | } 313 | return false; 314 | } 315 | 316 | bool simplify_cfg(Function *func) 317 | { 318 | bool modified = false; 319 | 320 | for (auto bb : func->bbs) 321 | { 322 | // br 0, .1, .2 -> br .2 323 | // br 1, .1, .2 -> br .1 324 | if (bb->last_inst->op == Op::BR 325 | && bb->last_inst->nof_args == 1) 326 | { 327 | Inst *branch = bb->last_inst; 328 | Inst *cond = bb->last_inst->args[0]; 329 | if (cond->op == Op::VALUE) 330 | { 331 | Basic_block *taken_bb = 332 | cond->value() ? branch->u.br3.true_bb : branch->u.br3.false_bb; 333 | Basic_block *not_taken_bb = 334 | cond->value() ? branch->u.br3.false_bb : branch->u.br3.true_bb; 335 | for (auto phi : not_taken_bb->phis) 336 | { 337 | phi->remove_phi_arg(bb); 338 | } 339 | destroy_instruction(branch); 340 | bb->build_br_inst(taken_bb); 341 | modified = true; 342 | } 343 | } 344 | 345 | // If a BB with exactly one predecessor and successor follows a BB 346 | // with exactly one successor, then we may move all its instructions 347 | // to the predecessor BB (and the now empty BB will be removed later). 348 | if (bb->preds.size() == 1 349 | && bb->preds[0] != bb 350 | && bb->preds[0] != func->bbs[0] 351 | && bb->preds[0]->succs.size() == 1 352 | && bb->succs.size() == 1 353 | && bb->phis.size() == 0) 354 | { 355 | Inst *last_inst = bb->preds[0]->last_inst; 356 | while (bb->first_inst->op != Op::BR) 357 | { 358 | bb->first_inst->move_before(last_inst); 359 | } 360 | } 361 | 362 | // Eliminate conditional branches to always UB BBs. For example: 363 | // 364 | // .1: 365 | // br %10, .2, .3 366 | // 367 | // .2: 368 | // ub 1 369 | // br .4 370 | // 371 | // .3: 372 | // ... 373 | // 374 | // is optimized to: 375 | // 376 | // .1: 377 | // ub %10 378 | // br .3 379 | // 380 | // .3: 381 | // ... 382 | if (config.optimize_ub 383 | && bb->last_inst->op == Op::BR 384 | && bb->last_inst->nof_args == 1) 385 | { 386 | Inst *branch = bb->last_inst; 387 | Basic_block *taken_bb = nullptr; 388 | Basic_block *not_taken_bb = nullptr; 389 | if (is_always_ub(branch->u.br3.true_bb)) 390 | { 391 | bb->build_inst(Op::UB, branch->args[0]); 392 | taken_bb = branch->u.br3.false_bb; 393 | not_taken_bb = branch->u.br3.true_bb; 394 | } 395 | else if (is_always_ub(branch->u.br3.false_bb)) 396 | { 397 | bb->build_inst(Op::UB, bb->build_inst(Op::NOT, branch->args[0])); 398 | taken_bb = branch->u.br3.true_bb; 399 | not_taken_bb = branch->u.br3.false_bb; 400 | } 401 | if (taken_bb) 402 | { 403 | for (auto phi : not_taken_bb->phis) 404 | { 405 | phi->remove_phi_arg(bb); 406 | } 407 | destroy_instruction(branch); 408 | bb->build_br_inst(taken_bb); 409 | modified = true; 410 | } 411 | } 412 | 413 | // GCC sometimes needs an extra empty BB because a phi node in the 414 | // destination must be able to distinguish the two sources, which 415 | // in our IR looks like: 416 | // 417 | // .4: 418 | // %64 = flt %58, %22 419 | // br %64, .5, .6 420 | // 421 | // .5: 422 | // br .6 423 | // 424 | // .6: 425 | // %67 = phi [ %39, .4 ], [ %45, .5 ] 426 | // 427 | // Change this to an Op::ITE instruction. 428 | if (bb->succs.size() == 2 429 | && ((bb->succs[0]->succs.size() == 1 430 | && bb->succs[0]->phis.empty() 431 | && bb->succs[0]->first_inst->op == Op::BR 432 | && bb->succs[0]->succs[0] == bb->succs[1] 433 | && !bb->succs[1]->phis.empty()) 434 | || (bb->succs[1]->succs.size() == 1 435 | && bb->succs[1]->phis.empty() 436 | && bb->succs[1]->first_inst->op == Op::BR 437 | && bb->succs[1]->succs[0] == bb->succs[0] 438 | && !bb->succs[0]->phis.empty()))) 439 | { 440 | Basic_block *empty_bb; 441 | Basic_block *dest_bb; 442 | if (bb->succs[0]->phis.empty()) 443 | { 444 | empty_bb = bb->succs[0]; 445 | dest_bb = bb->succs[1]; 446 | } 447 | else 448 | { 449 | empty_bb = bb->succs[1]; 450 | dest_bb = bb->succs[0]; 451 | } 452 | Basic_block *true_bb = bb->succs[0]; 453 | Inst *cond = bb->last_inst->args[0]; 454 | 455 | for (auto phi : dest_bb->phis) 456 | { 457 | Inst *inst1 = phi->get_phi_arg(empty_bb); 458 | Inst *inst2 = phi->get_phi_arg(bb); 459 | if (true_bb != empty_bb) 460 | std::swap(inst1, inst2); 461 | Inst *ite = bb->build_inst(Op::ITE, cond, inst1, inst2); 462 | phi->update_phi_arg(ite, bb); 463 | } 464 | 465 | destroy_instruction(bb->last_inst); 466 | bb->build_br_inst(dest_bb); 467 | modified = true; 468 | } 469 | 470 | // br (not x), .1, .2 -> br x, .2, .1 471 | if (bb->last_inst->op == Op::BR 472 | && bb->last_inst->nof_args == 1 473 | && bb->last_inst->args[0]->op == Op::NOT) 474 | { 475 | Inst *branch = bb->last_inst; 476 | Inst *cond = branch->args[0]->args[0]; 477 | Basic_block *true_bb = branch->u.br3.false_bb; 478 | Basic_block *false_bb = branch->u.br3.true_bb; 479 | destroy_instruction(branch); 480 | bb->build_br_inst(cond, true_bb, false_bb); 481 | } 482 | 483 | // Remove empty BBs ending in unconditional branch by letting the 484 | // predecessors call the successor. 485 | remove_empty_bb(bb); 486 | } 487 | 488 | reverse_post_order(func); 489 | 490 | return modified; 491 | } 492 | 493 | bool simplify_cfg(Module *module) 494 | { 495 | bool modified = false; 496 | for (auto func : module->functions) 497 | modified |= simplify_cfg(func); 498 | return modified; 499 | } 500 | 501 | } // end namespace smtgcc 502 | -------------------------------------------------------------------------------- /lib/dead_code_elimination.cpp: -------------------------------------------------------------------------------- 1 | // Remove trivially dead instructions. 2 | 3 | #include 4 | 5 | #include "smtgcc.h" 6 | 7 | namespace smtgcc { 8 | 9 | namespace { 10 | 11 | bool is_false(Inst *inst) 12 | { 13 | return inst->op == Op::VALUE && inst->value() == 0; 14 | } 15 | 16 | bool is_true(Inst *inst) 17 | { 18 | return inst->op == Op::VALUE && inst->value() == 1; 19 | } 20 | 21 | void destroy(Inst *inst) 22 | { 23 | // Memory instructions must be kept until the memory optimization passes. 24 | if (inst->op == Op::MEMORY) 25 | return; 26 | 27 | destroy_instruction(inst); 28 | } 29 | 30 | // Remove all instructions (except the "ub 1") for a basic block that 31 | // is always UB. 32 | void clear_ub_bb(Basic_block *bb, bool is_loopfree) 33 | { 34 | if (is_loopfree 35 | && bb->last_inst->op == Op::BR && bb->last_inst->nof_args == 1) 36 | { 37 | // Change the conditional branch to an unconditional branch. It does 38 | // not matter which branch we take since the execution is UB anyway. 39 | Inst *cond = bb->last_inst->args[0]; 40 | cond->replace_use_with(bb->last_inst, bb->value_inst(1, 1)); 41 | } 42 | 43 | Inst *found_ub = nullptr; 44 | for (Inst *inst = bb->last_inst->prev; inst;) 45 | { 46 | Inst *next_inst = inst->prev; 47 | if (inst->op == Op::UB && is_true(inst->args[0])) 48 | { 49 | if (found_ub) 50 | destroy(inst); 51 | else 52 | found_ub = inst; 53 | } 54 | else if(inst->has_lhs() && !inst->used_by.empty()) 55 | { 56 | if (is_loopfree) 57 | { 58 | inst->replace_all_uses_with(bb->value_inst(0, inst->bitsize)); 59 | destroy(inst); 60 | } 61 | } 62 | else 63 | destroy(inst); 64 | inst = next_inst; 65 | } 66 | assert(found_ub); 67 | 68 | while (!bb->phis.empty()) 69 | { 70 | Inst *inst = bb->phis.back(); 71 | if (!inst->used_by.empty()) 72 | inst->replace_all_uses_with(bb->value_inst(0, inst->bitsize)); 73 | destroy(inst); 74 | } 75 | 76 | if (bb->first_inst != found_ub) 77 | found_ub->move_before(bb->first_inst); 78 | } 79 | 80 | } // end anonymous namespace 81 | 82 | void dead_code_elimination(Function *func) 83 | { 84 | bool is_loopfree = !has_loops(func); 85 | uint32_t nof_inst = 0; 86 | for (int i = func->bbs.size() - 1; i >= 0; i--) 87 | { 88 | Basic_block *bb = func->bbs[i]; 89 | 90 | // Propagate "always UB" from successors (this BB is always UB if 91 | // all its successors are always UB). 92 | if (config.optimize_ub 93 | && bb != func->bbs[0] 94 | && !bb->succs.empty() 95 | && (bb->first_inst->op != Op::UB 96 | || !is_true(bb->first_inst->args[0]))) 97 | { 98 | bool succs_are_ub = true; 99 | for (auto succ : bb->succs) 100 | { 101 | succs_are_ub = 102 | succs_are_ub && succ->first_inst->op == Op::UB 103 | && is_true(succ->first_inst->args[0]); 104 | } 105 | if (succs_are_ub) 106 | bb->build_inst(Op::UB, bb->value_inst(1, 1)); 107 | } 108 | 109 | // Remove dead instructions. 110 | for (Inst *inst = bb->last_inst; inst;) 111 | { 112 | Inst *next_inst = inst->prev; 113 | if (inst->has_lhs() && inst->used_by.empty()) 114 | destroy(inst); 115 | else if (config.optimize_ub 116 | && bb != func->bbs[0] 117 | && inst->op == Op::UB && is_true(inst->args[0])) 118 | { 119 | clear_ub_bb(bb, is_loopfree); 120 | break; 121 | } 122 | else if (inst->op == Op::UB && is_false(inst->args[0])) 123 | destroy(inst); 124 | else if (inst->op == Op::ASSERT && is_true(inst->args[0])) 125 | destroy(inst); 126 | else 127 | nof_inst++; 128 | inst = next_inst; 129 | } 130 | 131 | // Remove dead phi-nodes. 132 | std::vector dead_phis; 133 | for (auto phi : bb->phis) 134 | { 135 | if (phi->used_by.empty()) 136 | dead_phis.push_back(phi); 137 | } 138 | for (auto phi : dead_phis) 139 | { 140 | destroy(phi); 141 | } 142 | } 143 | 144 | if (nof_inst > max_nof_inst) 145 | throw Not_implemented("too many instructions"); 146 | 147 | if (config.optimize_ub) 148 | { 149 | for (auto bb : func->bbs) 150 | { 151 | // Propagate "always UB" from predecessors (this BB is always UB if 152 | // all its predecessors are always UB). 153 | if (bb != func->bbs.back() 154 | && !bb->preds.empty() 155 | && (bb->first_inst->op != Op::UB 156 | || !is_true(bb->first_inst->args[0]))) 157 | { 158 | bool preds_are_ub = true; 159 | for (auto pred : bb->preds) 160 | { 161 | preds_are_ub = 162 | preds_are_ub && pred->first_inst->op == Op::UB 163 | && is_true(pred->first_inst->args[0]); 164 | } 165 | if (preds_are_ub) 166 | { 167 | bb->build_inst(Op::UB, bb->value_inst(1, 1)); 168 | clear_ub_bb(bb, is_loopfree); 169 | } 170 | } 171 | } 172 | } 173 | } 174 | 175 | void dead_code_elimination(Module *module) 176 | { 177 | for (auto func : module->functions) 178 | dead_code_elimination(func); 179 | } 180 | 181 | } // end namespace smtgcc 182 | -------------------------------------------------------------------------------- /lib/loop_unroll.cpp: -------------------------------------------------------------------------------- 1 | // Unroll loops. 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "smtgcc.h" 9 | 10 | #include "stdio.h" 11 | 12 | using namespace std::string_literals; 13 | 14 | namespace smtgcc { 15 | namespace { 16 | 17 | struct Loop 18 | { 19 | // The basic blocks of the loop, in reverse post order. 20 | std::vector bbs; 21 | 22 | // The exit blocks for the original loop, in reverse post order. 23 | std::vector exit_blocks; 24 | }; 25 | 26 | class Loop_finder 27 | { 28 | Function *func; 29 | 30 | std::map nbr2last; 31 | 32 | // Map between BB and its index in the function. 33 | std::map idx; 34 | 35 | bool is_ancestor(size_t, size_t); 36 | 37 | public: 38 | Loop_finder(Function *func); 39 | std::optional find_loop(); 40 | }; 41 | 42 | class Unroller 43 | { 44 | Loop& loop; 45 | Function *func; 46 | std::map curr_inst; 47 | std::map curr_bb; 48 | Basic_block *next_loop_header = nullptr; 49 | 50 | void build_new_loop_exit(); 51 | void update_loop_exit(Basic_block *orig_loop_bb, 52 | Basic_block *current_iter_bb, Basic_block *exit_bb); 53 | void duplicate(Inst *inst, Basic_block *bb); 54 | Inst *translate(Inst *inst); 55 | Basic_block *translate(Basic_block *bb); 56 | Inst *get_phi(std::map& bb2phi, 57 | Basic_block *bb, Inst *inst); 58 | void ensure_lcssa(Inst *inst); 59 | void create_lcssa(); 60 | void unroll_one_iteration(); 61 | 62 | public: 63 | Unroller(Loop& loop); 64 | void unroll(int nof_unroll); 65 | }; 66 | 67 | template 68 | bool contains(std::vector& vec, const T& value) 69 | { 70 | return std::find(vec.begin(), vec.end(), value) != vec.end(); 71 | } 72 | 73 | template 74 | void push_unique(std::vector& vec, const T& value) 75 | { 76 | if (std::find(vec.begin(), vec.end(), value) == vec.end()) 77 | vec.push_back(value); 78 | } 79 | 80 | Unroller::Unroller(Loop& loop) : loop{loop} 81 | { 82 | func = loop.bbs.back()->func; 83 | } 84 | 85 | Inst *Unroller::get_phi(std::map& bb2phi, Basic_block *bb, Inst *inst) 86 | { 87 | if (bb2phi.contains(bb)) 88 | return bb2phi[bb]; 89 | assert(!contains(loop.exit_blocks, bb)); 90 | 91 | Inst *phi = bb->build_phi_inst(inst->bitsize); 92 | bb2phi.insert({bb, phi}); 93 | assert(bb->preds.size() > 0); 94 | for (auto pred : bb->preds) 95 | { 96 | phi->add_phi_arg(get_phi(bb2phi, pred, inst), pred); 97 | } 98 | 99 | return phi; 100 | } 101 | 102 | // Check that the instruction is used in LCSSA-safe way (i.e., all uses are 103 | // either in the loop, or in a phi-node at a loop exit). If not, insert a new 104 | // phi-node and make all loop-external uses use that. 105 | void Unroller::ensure_lcssa(Inst *inst) 106 | { 107 | std::vector invalid_use; 108 | for (auto use : inst->used_by) 109 | { 110 | if (!contains(loop.bbs, use->bb)) 111 | push_unique(invalid_use, use); 112 | } 113 | 114 | if (invalid_use.empty()) 115 | return; 116 | 117 | std::map bb2phi; 118 | for (auto exit_block : loop.exit_blocks) 119 | { 120 | Inst *phi = exit_block->build_phi_inst(inst->bitsize); 121 | bb2phi.insert({exit_block, phi}); 122 | for (auto pred : exit_block->preds) 123 | phi->add_phi_arg(inst, pred); 124 | } 125 | while (!invalid_use.empty()) 126 | { 127 | Inst *use = invalid_use.back(); 128 | invalid_use.pop_back(); 129 | if (use->op == Op::PHI) 130 | { 131 | for (auto phi_arg : use->phi_args) 132 | { 133 | if (phi_arg.inst == inst 134 | && !contains(loop.bbs, phi_arg.bb)) 135 | { 136 | Inst *arg_inst = get_phi(bb2phi, phi_arg.bb, inst); 137 | use->update_phi_arg(arg_inst, phi_arg.bb); 138 | } 139 | } 140 | } 141 | else 142 | inst->replace_use_with(use, get_phi(bb2phi, use->bb, inst)); 143 | } 144 | } 145 | 146 | // Update all uses outside the loop to use a phi node in the loop exit block. 147 | void Unroller::create_lcssa() 148 | { 149 | build_new_loop_exit(); 150 | 151 | for (auto bb : loop.bbs) 152 | { 153 | for (auto phi : bb->phis) 154 | { 155 | ensure_lcssa(phi); 156 | } 157 | for (Inst *inst = bb->first_inst; inst; inst = inst->next) 158 | { 159 | ensure_lcssa(inst); 160 | } 161 | } 162 | } 163 | 164 | Loop_finder::Loop_finder(Function *func) : func{func} 165 | { 166 | for (size_t i = 0; i < func->bbs.size(); i++) 167 | { 168 | idx.insert({func->bbs[i], i}); 169 | } 170 | } 171 | 172 | bool Loop_finder::is_ancestor(size_t w, size_t v) 173 | { 174 | return w <= v && v <= nbr2last.at(w); 175 | } 176 | 177 | // Find a loop to unroll. 178 | // 179 | // This is an implementation of the algorithm described in the paper 180 | // "Nesting of Reducible and Irreducible Loops" by Paul Havlak, but 181 | // simplified to only return one loop (the innermost loop in case of 182 | // nested loops). 183 | std::optional Loop_finder::find_loop() 184 | { 185 | std::map nbr2bb; 186 | std::map bb2nbr; 187 | 188 | // Calculate preorder numbers. 189 | size_t nbr = 0; 190 | { 191 | std::set visited; 192 | std::vector worklist; 193 | worklist.push_back(func->bbs.front()); 194 | while (!worklist.empty()) 195 | { 196 | Basic_block *bb = worklist.back(); 197 | if (visited.contains(bb)) 198 | { 199 | worklist.pop_back(); 200 | nbr2last.insert({bb2nbr.at(bb), nbr - 1}); 201 | } 202 | else 203 | { 204 | visited.insert(bb); 205 | nbr2bb.insert({nbr, bb}); 206 | bb2nbr.insert({bb, nbr}); 207 | nbr++; 208 | for (auto succ : bb->succs) 209 | { 210 | if (!visited.contains(succ)) 211 | worklist.push_back(succ); 212 | } 213 | } 214 | } 215 | assert(nbr == func->bbs.size()); 216 | } 217 | 218 | // Make lists of predecessors (backedge and other). 219 | std::map> back_preds; 220 | std::map> nonback_preds; 221 | for (size_t w = 0; w < nbr; w++) 222 | { 223 | for (auto pred : nbr2bb.at(w)->preds) 224 | { 225 | size_t v = bb2nbr.at(pred); 226 | if (is_ancestor(w, v)) 227 | back_preds[w].insert(v); 228 | else 229 | nonback_preds[w].insert(v); 230 | } 231 | } 232 | 233 | for (ssize_t w = nbr - 1; w >= 0; w--) 234 | { 235 | std::set P; 236 | 237 | for (auto v : back_preds[w]) 238 | { 239 | if (v != w) 240 | P.insert(v); 241 | else 242 | { 243 | Loop loop; 244 | Basic_block *bb = nbr2bb.at(w); 245 | loop.bbs.push_back(bb); 246 | for (auto succ : bb->succs) 247 | { 248 | if (succ != bb) 249 | push_unique(loop.exit_blocks, succ); 250 | } 251 | assert(loop.exit_blocks.size() < 2); 252 | return loop; 253 | } 254 | } 255 | 256 | std::vector worklist(P.begin(), P.end()); 257 | while (!worklist.empty()) 258 | { 259 | size_t x = worklist.back(); 260 | worklist.pop_back(); 261 | for (auto y : nonback_preds[x]) 262 | { 263 | if (!is_ancestor(w, y)) 264 | throw Not_implemented("irreducible loop"); 265 | if (y != w && !P.contains(y)) 266 | { 267 | P.insert(y); 268 | worklist.push_back(y); 269 | } 270 | } 271 | } 272 | 273 | if (!P.empty()) 274 | { 275 | Loop loop; 276 | P.insert(w); 277 | for (auto p : P) 278 | { 279 | Basic_block *bb = nbr2bb.at(p); 280 | loop.bbs.push_back(bb); 281 | for (auto succ : bb->succs) 282 | { 283 | if (!P.contains(bb2nbr.at(succ))) 284 | push_unique(loop.exit_blocks, succ); 285 | } 286 | } 287 | 288 | std::sort(loop.bbs.begin(), loop.bbs.end(), 289 | [this](Basic_block *a, Basic_block *b) { 290 | return idx.at(a) < idx.at(b); 291 | }); 292 | std::sort(loop.exit_blocks.begin(), loop.exit_blocks.end(), 293 | [this](Basic_block *a, Basic_block *b) { 294 | return idx.at(a) < idx.at(b); 295 | }); 296 | 297 | return loop; 298 | } 299 | } 300 | 301 | return {}; 302 | } 303 | 304 | std::optional find_loop(Function *func) 305 | { 306 | Loop_finder loop_finder(func); 307 | std::optional loop = loop_finder.find_loop(); 308 | if (loop && loop->exit_blocks.empty()) 309 | throw Not_implemented("infinite loop"); 310 | return loop; 311 | } 312 | 313 | // Get the SSA variable (i.e., instruction) corresponding to the input SSA 314 | // variable for use in this iteration. 315 | Inst *Unroller::translate(Inst *inst) 316 | { 317 | auto I = curr_inst.find(inst); 318 | if (I != curr_inst.end()) 319 | return I->second; 320 | return inst; 321 | } 322 | 323 | Basic_block *Unroller::translate(Basic_block *bb) 324 | { 325 | auto I = curr_bb.find(bb); 326 | if (I != curr_bb.end()) 327 | return I->second; 328 | return bb; 329 | } 330 | 331 | // Insert new exit blocks to ensure that all predecessors in the exit block 332 | // are within the loop and that no other basic block (except the loop header) 333 | // has predecessors within the loop. 334 | void Unroller::build_new_loop_exit() 335 | { 336 | for (size_t i = 0; i < loop.exit_blocks.size(); i++) 337 | { 338 | Basic_block *orig_exit_block = loop.exit_blocks[i]; 339 | Basic_block *exit_block = func->build_bb(); 340 | exit_block->build_br_inst(orig_exit_block); 341 | std::map phi_map; 342 | for (auto phi : orig_exit_block->phis) 343 | { 344 | Inst *new_phi = exit_block->build_phi_inst(phi->bitsize); 345 | phi_map.insert({phi, new_phi}); 346 | phi->add_phi_arg(new_phi, exit_block); 347 | } 348 | loop.exit_blocks[i] = exit_block; 349 | 350 | // Update the branches within the loop to use the new loop exit. 351 | for (auto bb : loop.bbs) 352 | { 353 | assert(bb->last_inst->op == Op::BR); 354 | bool updated = false; 355 | if (bb->last_inst->nof_args == 0) 356 | { 357 | Basic_block *dest_bb = bb->last_inst->u.br1.dest_bb; 358 | if (dest_bb == orig_exit_block) 359 | { 360 | destroy_instruction(bb->last_inst); 361 | bb->build_br_inst(exit_block); 362 | updated = true; 363 | } 364 | } 365 | else 366 | { 367 | Inst *arg = bb->last_inst->args[0]; 368 | Basic_block *true_bb = bb->last_inst->u.br3.true_bb; 369 | Basic_block *false_bb = bb->last_inst->u.br3.false_bb; 370 | if (true_bb == orig_exit_block || false_bb == orig_exit_block) 371 | { 372 | if (true_bb == orig_exit_block) 373 | true_bb = exit_block; 374 | if (false_bb == orig_exit_block) 375 | false_bb = exit_block; 376 | destroy_instruction(bb->last_inst); 377 | bb->build_br_inst(arg, true_bb, false_bb); 378 | updated = true; 379 | } 380 | } 381 | if (updated) 382 | { 383 | for (auto phi : orig_exit_block->phis) 384 | { 385 | Inst *phi_arg = phi->get_phi_arg(bb); 386 | phi->remove_phi_arg(bb); 387 | phi_map.at(phi)->add_phi_arg(phi_arg, bb); 388 | } 389 | } 390 | } 391 | } 392 | } 393 | 394 | // Update phi nodes in the exit block to handle a new predecessor for the 395 | // current iteration of the loop. 396 | void Unroller::update_loop_exit(Basic_block *orig_loop_bb, Basic_block *current_iter_bb, Basic_block *exit_bb) 397 | { 398 | for (auto phi : exit_bb->phis) 399 | { 400 | Inst *inst = phi->get_phi_arg(orig_loop_bb); 401 | phi->add_phi_arg(translate(inst), current_iter_bb); 402 | } 403 | } 404 | 405 | void Unroller::duplicate(Inst *inst, Basic_block *bb) 406 | { 407 | Inst *new_inst = nullptr; 408 | Inst_class iclass = inst->iclass(); 409 | switch (iclass) 410 | { 411 | case Inst_class::iunary: 412 | case Inst_class::funary: 413 | { 414 | Inst *arg = translate(inst->args[0]); 415 | new_inst = bb->build_inst(inst->op, arg); 416 | } 417 | break; 418 | case Inst_class::icomparison: 419 | case Inst_class::fcomparison: 420 | case Inst_class::ibinary: 421 | case Inst_class::fbinary: 422 | case Inst_class::conv: 423 | { 424 | Inst *arg1 = translate(inst->args[0]); 425 | Inst *arg2 = translate(inst->args[1]); 426 | new_inst = bb->build_inst(inst->op, arg1, arg2); 427 | } 428 | break; 429 | case Inst_class::ternary: 430 | { 431 | Inst *arg1 = translate(inst->args[0]); 432 | Inst *arg2 = translate(inst->args[1]); 433 | Inst *arg3 = translate(inst->args[2]); 434 | new_inst = bb->build_inst(inst->op, arg1, arg2, arg3); 435 | } 436 | break; 437 | default: 438 | if (inst->op == Op::BR) 439 | { 440 | if (inst->nof_args == 0) 441 | { 442 | Basic_block *dest_bb = translate(inst->u.br1.dest_bb); 443 | bb->build_br_inst(dest_bb); 444 | if (contains(loop.exit_blocks, dest_bb)) 445 | update_loop_exit(inst->bb, bb, dest_bb); 446 | } 447 | else 448 | { 449 | Inst *arg = translate(inst->args[0]); 450 | Basic_block *true_bb = translate(inst->u.br3.true_bb); 451 | Basic_block *false_bb = translate(inst->u.br3.false_bb); 452 | bb->build_br_inst(arg, true_bb, false_bb); 453 | if (contains(loop.exit_blocks, true_bb)) 454 | update_loop_exit(inst->bb, bb, true_bb); 455 | if (contains(loop.exit_blocks, false_bb)) 456 | update_loop_exit(inst->bb, bb, false_bb); 457 | } 458 | return; 459 | } 460 | else if (inst->op == Op::PHI) 461 | { 462 | new_inst = bb->build_phi_inst(inst->bitsize); 463 | for (auto [arg_inst, arg_bb] : inst->phi_args) 464 | { 465 | new_inst->add_phi_arg(translate(arg_inst), translate(arg_bb)); 466 | } 467 | } 468 | else 469 | throw Not_implemented("unroller::duplicate: "s + inst->name()); 470 | } 471 | assert(new_inst); 472 | curr_inst[inst] = new_inst; 473 | } 474 | 475 | void Unroller::unroll_one_iteration() 476 | { 477 | Basic_block *current_loop_header = next_loop_header; 478 | 479 | // Copy the loop header. 480 | { 481 | Basic_block *src_bb = loop.bbs[0]; 482 | Basic_block *dst_bb = current_loop_header; 483 | 484 | // We must translate phi nodes in two steps, because we may have 485 | // .2: 486 | // %10 = phi [ %7, .1 ], [ %5, .6 ], [ %49, .5 ] 487 | // %12 = phi [ %5, .1 ], [ %10, .6 ], [ %10, .5 ] 488 | // where phi %12 uses the value of phi %10 from the previous iteration. 489 | // So we must translate all phi nodes before writing the new phi nodes 490 | // to the translation table. 491 | std::map tmp_curr_inst; 492 | for (auto src_phi : src_bb->phis) 493 | { 494 | Inst *dst_phi = dst_bb->build_phi_inst(src_phi->bitsize); 495 | tmp_curr_inst.insert({src_phi, dst_phi}); 496 | for (auto [arg_inst, arg_bb] : src_phi->phi_args) 497 | { 498 | if (contains(loop.bbs, arg_bb)) 499 | dst_phi->add_phi_arg(translate(arg_inst), translate(arg_bb)); 500 | } 501 | } 502 | for (auto [phi, translated_phi] : tmp_curr_inst) 503 | { 504 | curr_inst[phi] = translated_phi; 505 | } 506 | 507 | for (Inst *inst = src_bb->first_inst; 508 | inst != src_bb->last_inst; 509 | inst = inst->next) 510 | { 511 | duplicate(inst, dst_bb); 512 | } 513 | } 514 | 515 | curr_bb[loop.bbs[0]] = current_loop_header; 516 | for (size_t i = 1; i < loop.bbs.size(); i++) 517 | { 518 | Basic_block *src_bb = loop.bbs[i]; 519 | curr_bb[src_bb] = func->build_bb(); 520 | } 521 | 522 | // Copy the rest of the basic blocks for this iteration. 523 | for (size_t i = 1; i < loop.bbs.size(); i++) 524 | { 525 | Basic_block *src_bb = loop.bbs[i]; 526 | Basic_block *dst_bb = translate(src_bb); 527 | for (auto phi : src_bb->phis) 528 | { 529 | duplicate(phi, dst_bb); 530 | } 531 | for (Inst *inst = src_bb->first_inst; 532 | inst != src_bb->last_inst; 533 | inst = inst->next) 534 | { 535 | duplicate(inst, dst_bb); 536 | } 537 | } 538 | 539 | // Create the loop header for the next iteration, so that the back 540 | // edges will be created as branches to the next iteration. 541 | next_loop_header = func->build_bb(); 542 | curr_bb[loop.bbs[0]] = next_loop_header; 543 | 544 | duplicate(loop.bbs[0]->last_inst, current_loop_header); 545 | for (size_t i = 1; i < loop.bbs.size(); i++) 546 | { 547 | Basic_block *src_bb = loop.bbs[i]; 548 | Basic_block *dst_bb = translate(src_bb); 549 | duplicate(src_bb->last_inst, dst_bb); 550 | } 551 | 552 | curr_bb[loop.bbs[0]] = current_loop_header; 553 | } 554 | 555 | void Unroller::unroll(int nof_unroll) 556 | { 557 | create_lcssa(); 558 | 559 | Basic_block *first_unrolled = func->build_bb(); 560 | next_loop_header = first_unrolled; 561 | 562 | for (int i = 0; i < nof_unroll - 1; i++) 563 | { 564 | unroll_one_iteration(); 565 | } 566 | 567 | // The last block is for cases the program loops more than our unroll limit. 568 | // This makes our analysis invalid, so we mark this as UB. 569 | // We must make it branch to an extit block. It does not matter which 570 | // exit block we use, but it seems likely that the last exit block is 571 | // the best. 572 | Basic_block *last_exit_block = loop.exit_blocks.back(); 573 | Basic_block *last_bb = next_loop_header; 574 | last_bb->build_inst(Op::UB, last_bb->value_inst(1, 1)); 575 | last_bb->build_br_inst(last_exit_block); 576 | for (auto phi : last_exit_block->phis) 577 | { 578 | phi->add_phi_arg(last_bb->value_inst(0, phi->bitsize), last_bb); 579 | } 580 | 581 | // Update the original loop to only do the first iteration. 582 | Basic_block *loop_header = loop.bbs[0]; 583 | std::vector deleted_branches; 584 | for (auto bb : loop.bbs) 585 | { 586 | assert(bb->last_inst->op == Op::BR); 587 | if (bb->last_inst->nof_args == 0) 588 | { 589 | Basic_block *dest_bb = bb->last_inst->u.br1.dest_bb; 590 | if (dest_bb == loop_header) 591 | { 592 | deleted_branches.push_back(bb); 593 | dest_bb = first_unrolled; 594 | destroy_instruction(bb->last_inst); 595 | bb->build_br_inst(dest_bb); 596 | } 597 | } 598 | else 599 | { 600 | Basic_block *true_bb = bb->last_inst->u.br3.true_bb; 601 | Basic_block *false_bb = bb->last_inst->u.br3.false_bb; 602 | if (true_bb == loop_header || false_bb == loop_header) 603 | { 604 | deleted_branches.push_back(bb); 605 | if (true_bb == loop_header) 606 | true_bb = first_unrolled; 607 | if (false_bb == loop_header) 608 | false_bb = first_unrolled; 609 | Inst *cond = bb->last_inst->args[0]; 610 | destroy_instruction(bb->last_inst); 611 | bb->build_br_inst(cond, true_bb, false_bb); 612 | } 613 | } 614 | } 615 | for (auto bb : deleted_branches) 616 | { 617 | for (auto phi : loop_header->phis) 618 | { 619 | phi->remove_phi_arg(bb); 620 | } 621 | } 622 | } 623 | 624 | } // end anonymous namespace 625 | 626 | bool loop_unroll(Function *func, int nof_unroll) 627 | { 628 | bool unrolled = false; 629 | 630 | while (std::optional loop = find_loop(func)) 631 | { 632 | Unroller unroller(*loop); 633 | unroller.unroll(nof_unroll); 634 | reverse_post_order(func); 635 | simplify_insts(func); 636 | dead_code_elimination(func); 637 | simplify_cfg(func); 638 | unrolled = true; 639 | } 640 | 641 | // Report error if we could not unroll all loops. 642 | if (has_loops(func)) 643 | throw Not_implemented("loops"); 644 | 645 | return unrolled; 646 | } 647 | 648 | bool loop_unroll(Module *module) 649 | { 650 | bool unrolled = false; 651 | for (auto func : module->functions) 652 | unrolled |= loop_unroll(func); 653 | return unrolled; 654 | } 655 | 656 | } // end namespace smtgcc 657 | -------------------------------------------------------------------------------- /lib/memory_opt.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "smtgcc.h" 5 | 6 | namespace smtgcc { 7 | 8 | namespace { 9 | 10 | // Maximum number of bytes we track in an object. 11 | uint64_t max_mem_unroll_limit = 10000; 12 | 13 | bool is_local_memory(Inst *inst) 14 | { 15 | if (inst->op != Op::MEMORY) 16 | return false; 17 | uint64_t id = inst->args[0]->value(); 18 | return (id >> (inst->bb->func->module->ptr_id_bits - 1)) != 0; 19 | } 20 | 21 | // Return a vector containing all the function's memory instructions. 22 | std::vector collect_mem(Function *func) 23 | { 24 | std::vector mem; 25 | for (Inst *inst = func->bbs[0]->first_inst; inst; inst = inst->next) 26 | { 27 | if (inst->op == Op::MEMORY) 28 | mem.push_back(inst); 29 | } 30 | return mem; 31 | } 32 | 33 | // Ensure the memory instructions in the IR come in the same order as in the 34 | // mem vector. 35 | void reorder_mem(std::vector& mem) 36 | { 37 | if (mem.empty()) 38 | return; 39 | 40 | Basic_block *bb = mem[0]->bb; 41 | Inst *curr_inst = bb->first_inst; 42 | while (curr_inst->op == Op::VALUE) 43 | curr_inst = curr_inst->next; 44 | if (curr_inst != mem[0]) 45 | { 46 | mem[0]->move_before(curr_inst); 47 | curr_inst = mem[0]; 48 | } 49 | for (auto inst : mem) 50 | { 51 | if (inst != curr_inst) 52 | inst->move_after(curr_inst); 53 | curr_inst = inst; 54 | } 55 | } 56 | 57 | // Make a copy of inst in the first BB of the function func. 58 | Inst *clone_inst(Inst *inst, Function *func) 59 | { 60 | Basic_block *bb = func->bbs[0]; 61 | if (inst->op == Op::VALUE) 62 | { 63 | return bb->value_inst(inst->value(), inst->bitsize); 64 | } 65 | 66 | if (inst->op == Op::MEMORY) 67 | { 68 | Inst *arg1 = clone_inst(inst->args[0], func); 69 | Inst *arg2 = clone_inst(inst->args[1], func); 70 | Inst *arg3 = clone_inst(inst->args[2], func); 71 | return bb->build_inst(Op::MEMORY, arg1, arg2, arg3); 72 | } 73 | 74 | throw smtgcc::Not_implemented("clone_inst: unhandled instruction"); 75 | } 76 | 77 | // Return true if the instruction is an unused memory instruction. 78 | // Usage in the entry BB is not counted -- those are only used for 79 | // initialization and are therefore not relevant for the function if 80 | // the memory does not have any other use. 81 | bool is_unused_memory(Inst *memory_inst) 82 | { 83 | if (memory_inst->op != Op::MEMORY) 84 | return false; 85 | Basic_block *entry_bb = memory_inst->bb->func->bbs[0]; 86 | assert(memory_inst->bb == entry_bb); 87 | if (memory_inst->used_by.empty()) 88 | return true; 89 | 90 | // Check that all uses (and uses of uses) are in the entry block. 91 | // If not, then the memory_inst is not unused. 92 | std::set visited; 93 | std::vector sinks; 94 | std::vector worklist; 95 | worklist.insert(std::end(worklist), std::begin(memory_inst->used_by), 96 | std::end(memory_inst->used_by)); 97 | while (!worklist.empty()) 98 | { 99 | Inst *inst = worklist.back(); 100 | worklist.pop_back(); 101 | if (inst->bb != entry_bb) 102 | return false; 103 | if (visited.contains(inst)) 104 | continue; 105 | 106 | visited.insert(inst); 107 | for (auto used_by : inst->used_by) 108 | { 109 | if (used_by->op == Op::SET_MEM_INDEF 110 | || used_by->op == Op::STORE) 111 | sinks.push_back(used_by); 112 | else 113 | worklist.push_back(used_by); 114 | } 115 | } 116 | 117 | // We have now verified that all uses of memory_inst are in the entry block. 118 | // However, this does not guarantee that the memory block is unused! For 119 | // example, consider code of the form 120 | // int a; 121 | // int *p = &a; 122 | // where the use of `a` in the entry block is used to initialize `p`, 123 | // which may be used in the body of the function. We must therefore check 124 | // that all the store instructions (and other "sink" instructions) identified 125 | // above operate on the memory_inst memory block. 126 | visited.clear(); 127 | for (auto sink_inst : sinks) 128 | { 129 | assert(worklist.empty()); 130 | 131 | worklist.push_back(sink_inst->args[0]); 132 | while (!worklist.empty()) 133 | { 134 | Inst *inst = worklist.back(); 135 | worklist.pop_back(); 136 | if (visited.contains(inst)) 137 | continue; 138 | visited.insert(inst); 139 | if (inst->op == Op::VALUE) 140 | { 141 | // A constant is always a valid starting point. Nothing to do. 142 | } 143 | else if (inst->op == Op::MEMORY) 144 | { 145 | if (inst != memory_inst) 146 | { 147 | // memory_inst is used when initializing a different 148 | // memory block. I.e. memory_inst is not unused. 149 | return false; 150 | } 151 | } 152 | else 153 | { 154 | assert(inst->nof_args > 0); 155 | for (uint64_t i = 0; i < inst->nof_args; i++) 156 | worklist.push_back(inst->args[i]); 157 | } 158 | } 159 | } 160 | 161 | return true; 162 | } 163 | 164 | void remove_unused_memory(Inst *memory_inst) 165 | { 166 | Basic_block *entry_bb = memory_inst->bb->func->bbs[0]; 167 | assert(memory_inst->op == Op::MEMORY); 168 | assert(memory_inst->bb == entry_bb); 169 | 170 | std::vector worklist; 171 | worklist.push_back(memory_inst); 172 | while (!worklist.empty()) 173 | { 174 | Inst *inst = worklist.back(); 175 | assert(inst->bb == entry_bb); 176 | if (inst->used_by.empty()) 177 | { 178 | worklist.pop_back(); 179 | destroy_instruction(inst); 180 | } 181 | else 182 | { 183 | Inst *used_by = *inst->used_by.begin(); 184 | worklist.push_back(used_by); 185 | } 186 | } 187 | } 188 | 189 | void store_load_forwarding(Function *func) 190 | { 191 | std::map> bb2mem_indef; 192 | std::map> bb2mem_flag; 193 | std::map> bb2stores; 194 | 195 | for (auto bb : func->bbs) 196 | { 197 | std::map mem_indef; 198 | std::map mem_flag; 199 | std::map stores; 200 | 201 | if (bb->preds.size() == 1) 202 | { 203 | mem_indef = bb2mem_indef.at(bb->preds[0]); 204 | mem_flag = bb2mem_flag.at(bb->preds[0]); 205 | stores = bb2stores.at(bb->preds[0]); 206 | } 207 | else if (bb->preds.size() == 2) 208 | { 209 | if (bb2mem_indef.at(bb->preds[0]) == bb2mem_indef.at(bb->preds[1])) 210 | mem_indef = bb2mem_indef.at(bb->preds[0]); 211 | if (bb2mem_flag.at(bb->preds[0]) == bb2mem_flag.at(bb->preds[1])) 212 | mem_flag = bb2mem_flag.at(bb->preds[0]); 213 | if (bb2stores.at(bb->preds[0]) == bb2stores.at(bb->preds[1])) 214 | stores = bb2stores.at(bb->preds[0]); 215 | } 216 | 217 | for (Inst *inst = bb->first_inst; inst;) 218 | { 219 | Inst *next_inst = inst->next; 220 | 221 | switch (inst->op) 222 | { 223 | case Op::MEMORY: 224 | { 225 | uint64_t id = inst->args[0]->value(); 226 | uint64_t size = inst->args[1]->value(); 227 | uint32_t flags = inst->args[2]->value(); 228 | uint64_t addr = id << func->module->ptr_id_low; 229 | Inst *indef; 230 | if (flags & MEM_UNINIT) 231 | indef = bb->value_inst(255, 8); 232 | else 233 | indef = bb->value_inst(0, 8); 234 | size = std::min(size, max_mem_unroll_limit); 235 | for (uint64_t i = 0; i < size; i++) 236 | { 237 | mem_indef[addr + i] = indef; 238 | } 239 | } 240 | break; 241 | case Op::SET_MEM_INDEF: 242 | { 243 | Inst *ptr = inst->args[0]; 244 | if (ptr->op == Op::VALUE) 245 | mem_indef[ptr->value()] = inst; 246 | else 247 | mem_indef.clear(); 248 | } 249 | break; 250 | case Op::GET_MEM_INDEF: 251 | { 252 | Inst *ptr = inst->args[0]; 253 | if (ptr->op == Op::VALUE) 254 | { 255 | uint64_t ptr_val = ptr->value(); 256 | if (mem_indef.contains(ptr_val)) 257 | { 258 | Inst *value = mem_indef.at(ptr_val); 259 | if (value->op == Op::SET_MEM_INDEF) 260 | value = value->args[1]; 261 | else 262 | assert(value->op == Op::VALUE); 263 | inst->replace_all_uses_with(value); 264 | destroy_instruction(inst); 265 | } 266 | } 267 | } 268 | break; 269 | case Op::SET_MEM_FLAG: 270 | { 271 | Inst *ptr = inst->args[0]; 272 | if (ptr->op == Op::VALUE) 273 | mem_flag[ptr->value()] = inst; 274 | else 275 | mem_flag.clear(); 276 | } 277 | break; 278 | case Op::GET_MEM_FLAG: 279 | { 280 | Inst *ptr = inst->args[0]; 281 | if (ptr->op == Op::VALUE) 282 | { 283 | uint64_t ptr_val = ptr->value(); 284 | if (mem_flag.contains(ptr_val)) 285 | { 286 | Inst *set_mem_flag = mem_flag.at(ptr_val); 287 | Inst *value = set_mem_flag->args[1]; 288 | inst->replace_all_uses_with(value); 289 | destroy_instruction(inst); 290 | } 291 | } 292 | } 293 | break; 294 | case Op::STORE: 295 | { 296 | Inst *ptr = inst->args[0]; 297 | if (ptr->op == Op::VALUE) 298 | stores[ptr->value()] = inst; 299 | else 300 | stores.clear(); 301 | } 302 | break; 303 | case Op::LOAD: 304 | { 305 | Inst *ptr = inst->args[0]; 306 | if (ptr->op == Op::VALUE) 307 | { 308 | uint64_t ptr_val = ptr->value(); 309 | if (stores.contains(ptr_val)) 310 | { 311 | Inst *store = stores.at(ptr_val); 312 | Inst *value = store->args[1]; 313 | inst->replace_all_uses_with(value); 314 | destroy_instruction(inst); 315 | } 316 | } 317 | } 318 | break; 319 | default: 320 | break; 321 | } 322 | 323 | inst = next_inst; 324 | } 325 | 326 | bb2mem_indef[bb] = std::move(mem_indef); 327 | bb2mem_flag[bb] = std::move(mem_flag); 328 | bb2stores[bb] = std::move(stores); 329 | } 330 | } 331 | 332 | void dead_store_elim(Function *func) 333 | { 334 | std::set mem_indef; 335 | std::set mem_flag; 336 | std::set stores; 337 | 338 | // Seed the sets with the addresses of local memory, which will mark 339 | // earlier stores as dead if they are not read. 340 | for (Inst *inst = func->bbs[0]->first_inst; inst; inst = inst->next) 341 | { 342 | if (inst->op != Op::MEMORY) 343 | continue; 344 | if (!is_local_memory(inst)) 345 | continue; 346 | 347 | uint64_t id = inst->args[0]->value(); 348 | uint64_t mem_addr = id << func->module->ptr_id_low; 349 | uint64_t size = inst->args[1]->value(); 350 | size = std::min(max_mem_unroll_limit, size); 351 | for (uint64_t i = 0; i < size; i++) 352 | { 353 | uint64_t addr = mem_addr + i; 354 | mem_indef.insert(addr); 355 | mem_flag.insert(addr); 356 | stores.insert(addr); 357 | } 358 | } 359 | 360 | Basic_block *prev_bb = nullptr; 361 | for (int i = func->bbs.size() - 1; i >= 0; i--) 362 | { 363 | Basic_block *bb = func->bbs[i]; 364 | if (bb->succs.size() > 1 365 | || (bb->succs.size() == 1 && bb->succs[0] != prev_bb)) 366 | { 367 | mem_indef.clear(); 368 | mem_flag.clear(); 369 | stores.clear(); 370 | } 371 | 372 | for (Inst *inst = bb->last_inst; inst;) 373 | { 374 | Inst *prev_inst = inst->prev; 375 | 376 | switch (inst->op) 377 | { 378 | case Op::SET_MEM_INDEF: 379 | { 380 | Inst *ptr = inst->args[0]; 381 | if (ptr->op == Op::VALUE) 382 | { 383 | uint64_t ptr_val = ptr->value(); 384 | if (mem_indef.contains(ptr_val)) 385 | destroy_instruction(inst); 386 | else 387 | mem_indef.insert(ptr_val); 388 | } 389 | } 390 | break; 391 | case Op::GET_MEM_INDEF: 392 | { 393 | Inst *ptr = inst->args[0]; 394 | if (ptr->op == Op::VALUE) 395 | mem_indef.erase(ptr->value()); 396 | else 397 | mem_indef.clear(); 398 | } 399 | break; 400 | case Op::SET_MEM_FLAG: 401 | { 402 | Inst *ptr = inst->args[0]; 403 | if (ptr->op == Op::VALUE) 404 | { 405 | uint64_t ptr_val = ptr->value(); 406 | if (mem_flag.contains(ptr_val)) 407 | destroy_instruction(inst); 408 | else 409 | mem_flag.insert(ptr_val); 410 | } 411 | } 412 | break; 413 | case Op::GET_MEM_FLAG: 414 | { 415 | Inst *ptr = inst->args[0]; 416 | if (ptr->op == Op::VALUE) 417 | mem_flag.erase(ptr->value()); 418 | else 419 | mem_flag.clear(); 420 | } 421 | break; 422 | case Op::STORE: 423 | { 424 | Inst *ptr = inst->args[0]; 425 | if (ptr->op == Op::VALUE) 426 | { 427 | uint64_t ptr_val = ptr->value(); 428 | if (stores.contains(ptr_val)) 429 | destroy_instruction(inst); 430 | else 431 | stores.insert(ptr_val); 432 | } 433 | } 434 | break; 435 | case Op::LOAD: 436 | { 437 | Inst *ptr = inst->args[0]; 438 | if (ptr->op == Op::VALUE) 439 | stores.erase(ptr->value()); 440 | else 441 | stores.clear(); 442 | } 443 | break; 444 | default: 445 | break; 446 | } 447 | 448 | inst = prev_inst; 449 | } 450 | prev_bb = bb; 451 | } 452 | } 453 | 454 | Inst *get_value(Inst *inst) 455 | { 456 | if (inst->op != Op::LOAD) 457 | return nullptr; 458 | 459 | Inst *ptr = inst->args[0]; 460 | Inst *memory = nullptr; 461 | if (ptr->op == Op::MEMORY) 462 | memory = ptr; 463 | else if (ptr->op == Op::ADD 464 | && ptr->args[0]->op == Op::MEMORY 465 | && ptr->args[1]->op == Op::VALUE) 466 | memory = ptr->args[0]; 467 | if (!memory) 468 | return nullptr; 469 | 470 | if (!(memory->args[2]->value() & MEM_CONST)) 471 | return nullptr; 472 | 473 | Basic_block *entry_bb = inst->bb->func->bbs[0]; 474 | for (Inst *inst = entry_bb->last_inst; inst; inst = inst->prev) 475 | { 476 | if (inst->op == Op::STORE 477 | && (inst->args[0] == ptr 478 | || (inst->args[0]->op == Op::ADD 479 | && ptr->op == Op::ADD 480 | && inst->args[0]->args[0] == ptr->args[0] 481 | && inst->args[0]->args[1] == ptr->args[1]))) 482 | return inst->args[1]; 483 | } 484 | 485 | return nullptr; 486 | } 487 | 488 | void forward_const(Function *func) 489 | { 490 | for (auto bb : func->bbs) 491 | { 492 | if (bb == func->bbs[0]) 493 | { 494 | // The entry block may, in some cases (such as for bit fields), 495 | // create the value by a sequence where it loads/stores the 496 | // value multiple times, and this naive forwarding implementation 497 | // then creates values that are used before being defined. But 498 | // there is no need to forward within the entry block, so just 499 | // skip it. 500 | continue; 501 | } 502 | 503 | for (Inst *inst = bb->first_inst; inst;) 504 | { 505 | Inst *next_inst = inst->next; 506 | if (Inst *value = get_value(inst)) 507 | { 508 | inst->replace_all_uses_with(value); 509 | destroy_instruction(inst); 510 | } 511 | inst = next_inst; 512 | } 513 | } 514 | } 515 | 516 | } // end anonymous namespace 517 | 518 | // This function makes the memory instructions consistent between src and tgt: 519 | // * src and tgt have the same global memory instructions 520 | // * global memory that is unused in both src and tgt is removed 521 | // * unused local memory is removed 522 | // * the memory instructions are emitted in the same order 523 | // This makes checking faster as more can be CSEd between src and tgt. 524 | void canonicalize_memory(Module *module) 525 | { 526 | Function *src = module->functions[0]; 527 | Function *tgt = module->functions[1]; 528 | if (src->name != "src") 529 | std::swap(src, tgt); 530 | 531 | struct { 532 | bool operator()(const Inst *a, const Inst *b) const { 533 | return a->args[0]->value() < b->args[0]->value(); 534 | } 535 | } comp; 536 | 537 | // Substitute load from constant memory with the actual value. This is 538 | // also done by later load-to-store forwarding, but doing it here may 539 | // remove the need to set up the constant memory and therefore make 540 | // more code CSE and leave less memory for the SMT solver to track. 541 | if (config.optimize_ub) 542 | { 543 | forward_const(src); 544 | forward_const(tgt); 545 | } 546 | 547 | // Dead instructions using Op::MEMORY may make the code below treat the 548 | // memory as used. Run DCE first to ensure we get the intended result. 549 | dead_code_elimination(module); 550 | 551 | std::vector src_mem = collect_mem(src); 552 | std::vector tgt_mem = collect_mem(tgt); 553 | std::sort(src_mem.begin(), src_mem.end(), comp); 554 | std::sort(tgt_mem.begin(), tgt_mem.end(), comp); 555 | 556 | // Add missing global memory instructions. 557 | std::vector missing_src; 558 | std::vector missing_tgt; 559 | std::set_difference(tgt_mem.begin(), tgt_mem.end(), 560 | src_mem.begin(), src_mem.end(), 561 | std::back_inserter(missing_src), comp); 562 | std::set_difference(src_mem.begin(), src_mem.end(), 563 | tgt_mem.begin(), tgt_mem.end(), 564 | std::back_inserter(missing_tgt), comp); 565 | for (auto inst : missing_src) 566 | { 567 | if (!is_local_memory(inst)) 568 | src_mem.push_back(clone_inst(inst, src)); 569 | } 570 | for (auto inst : missing_tgt) 571 | { 572 | if (!is_local_memory(inst)) 573 | tgt_mem.push_back(clone_inst(inst, tgt)); 574 | } 575 | 576 | // Ensure that both src and tgt use the same instruction order. 577 | // The global memory is placed before the local memory. 578 | std::sort(src_mem.begin(), src_mem.end(), comp); 579 | std::sort(tgt_mem.begin(), tgt_mem.end(), comp); 580 | reorder_mem(src_mem); 581 | reorder_mem(tgt_mem); 582 | 583 | // Remove global memory that is unused in both src and tgt. 584 | std::vector remove; 585 | for (size_t i = 0; i < src_mem.size(); i++) 586 | { 587 | if (is_local_memory(src_mem[i])) 588 | break; 589 | __int128 src_arg1 = src_mem[i]->args[0]->value(); 590 | __int128 src_arg2 = src_mem[i]->args[1]->value(); 591 | __int128 src_arg3 = src_mem[i]->args[2]->value(); 592 | __int128 tgt_arg1 = tgt_mem[i]->args[0]->value(); 593 | __int128 tgt_arg2 = tgt_mem[i]->args[1]->value(); 594 | __int128 tgt_arg3 = tgt_mem[i]->args[2]->value(); 595 | if (!(src_arg3 & MEM_KEEP) 596 | && src_arg1 == tgt_arg1 597 | && src_arg2 == tgt_arg2 598 | && src_arg3 == tgt_arg3 599 | && is_unused_memory(src_mem[i]) 600 | && is_unused_memory(tgt_mem[i])) 601 | { 602 | remove.push_back(src_mem[i]); 603 | remove.push_back(tgt_mem[i]); 604 | } 605 | } 606 | 607 | // Remove unised local memory. 608 | for (auto inst : src_mem) 609 | { 610 | if (!(inst->args[2]->value() & MEM_KEEP) 611 | && is_local_memory(inst) 612 | && is_unused_memory(inst)) 613 | remove.push_back(inst); 614 | } 615 | for (auto inst : tgt_mem) 616 | { 617 | if (!(inst->args[2]->value() & MEM_KEEP) 618 | && is_local_memory(inst) 619 | && is_unused_memory(inst)) 620 | remove.push_back(inst); 621 | } 622 | 623 | if (!remove.empty()) 624 | { 625 | for (auto inst : remove) 626 | { 627 | remove_unused_memory(inst); 628 | } 629 | 630 | // Removing memory may open up new opportunities. For example, consider: 631 | // int b; 632 | // int *p = &b; 633 | // b may become dead after we remove p. Therefore, we need to rerun the 634 | // pass. 635 | canonicalize_memory(module); 636 | } 637 | } 638 | 639 | void ls_elim(Function *func) 640 | { 641 | store_load_forwarding(func); 642 | dead_store_elim(func); 643 | } 644 | 645 | void ls_elim(Module *module) 646 | { 647 | for (auto func : module->functions) 648 | ls_elim(func); 649 | } 650 | 651 | } // end namespace smtgcc 652 | -------------------------------------------------------------------------------- /lib/smtgcc.h: -------------------------------------------------------------------------------- 1 | #ifndef SMTGCC_H 2 | #define SMTGCC_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #define MEM_KEEP 1 13 | #define MEM_CONST 2 14 | #define MEM_UNINIT 4 15 | 16 | namespace smtgcc { 17 | 18 | const int unroll_limit = 16; 19 | 20 | const uint32_t max_nof_bb = 50000; 21 | const uint32_t max_nof_inst = 100000; 22 | 23 | struct Not_implemented 24 | { 25 | Not_implemented(const std::string& msg) : msg{msg} {} 26 | const std::string msg; 27 | }; 28 | 29 | struct Parse_error 30 | { 31 | Parse_error(const std::string& msg, int line) : msg{msg}, line{line} {} 32 | const std::string msg; 33 | int line; 34 | }; 35 | 36 | enum class Op : uint8_t { 37 | // Integer comparison 38 | EQ, 39 | NE, 40 | SLE, 41 | SLT, 42 | ULE, 43 | ULT, 44 | 45 | // Floating-point comparison 46 | FEQ, 47 | FLE, 48 | FLT, 49 | FNE, 50 | 51 | // Nullary 52 | MEM_ARRAY, 53 | MEM_FLAG_ARRAY, 54 | MEM_SIZE_ARRAY, 55 | MEM_INDEF_ARRAY, 56 | 57 | // Integer unary 58 | ASSERT, 59 | FREE, 60 | GET_MEM_FLAG, 61 | GET_MEM_SIZE, 62 | GET_MEM_INDEF, 63 | IS_CONST_MEM, 64 | IS_INF, 65 | IS_NAN, 66 | IS_NONCANONICAL_NAN, 67 | LOAD, 68 | MOV, 69 | NEG, 70 | NOT, 71 | READ, 72 | REGISTER, 73 | SIMP_BARRIER, 74 | SRC_ASSERT, 75 | TGT_ASSERT, 76 | UB, 77 | 78 | // Floating-point unary 79 | FABS, 80 | FNEG, 81 | NAN, 82 | 83 | // Integer binary 84 | ADD, 85 | AND, 86 | ARRAY_GET_FLAG, 87 | ARRAY_GET_SIZE, 88 | ARRAY_GET_INDEF, 89 | ARRAY_LOAD, 90 | ASHR, 91 | CONCAT, 92 | LSHR, 93 | MUL, 94 | OR, 95 | PARAM, 96 | PRINT, 97 | SADD_WRAPS, 98 | SDIV, 99 | SET_MEM_FLAG, 100 | SET_MEM_INDEF, 101 | SHL, 102 | SMUL_WRAPS, 103 | SRC_RETVAL, 104 | SRC_UB, 105 | SREM, 106 | SSUB_WRAPS, 107 | STORE, 108 | SUB, 109 | SYMBOLIC, 110 | TGT_RETVAL, 111 | TGT_UB, 112 | UDIV, 113 | UREM, 114 | WRITE, 115 | XOR, 116 | 117 | // Floating-point binary 118 | FADD, 119 | FDIV, 120 | FMUL, 121 | FSUB, 122 | 123 | // Ternary 124 | ARRAY_SET_FLAG, 125 | ARRAY_SET_SIZE, 126 | ARRAY_SET_INDEF, 127 | ARRAY_STORE, 128 | EXIT, 129 | EXTRACT, 130 | ITE, 131 | MEMORY, 132 | SRC_EXIT, 133 | SRC_MEM, 134 | TGT_EXIT, 135 | TGT_MEM, 136 | 137 | // Conversions 138 | F2S, 139 | F2U, 140 | FCHPREC, 141 | S2F, 142 | SEXT, 143 | U2F, 144 | ZEXT, 145 | 146 | // Special 147 | BR, 148 | PHI, 149 | RET, 150 | VALUE, 151 | }; 152 | 153 | enum class Inst_class : uint8_t { 154 | // Nullary operations 155 | nullary, 156 | 157 | // Unary operations 158 | iunary, 159 | funary, 160 | 161 | // Binary operations 162 | ibinary, 163 | fbinary, 164 | icomparison, 165 | fcomparison, 166 | conv, 167 | 168 | // Ternary operations 169 | ternary, 170 | 171 | // Misc 172 | special 173 | }; 174 | 175 | struct Inst_info { 176 | const char *name; 177 | Op op; 178 | Inst_class iclass; 179 | bool has_lhs; 180 | bool is_commutative; 181 | }; 182 | 183 | extern const std::array inst_info; 184 | 185 | struct Module; 186 | struct Function; 187 | struct Basic_block; 188 | struct Inst; 189 | 190 | struct Phi_arg { 191 | Inst *inst; 192 | Basic_block *bb; 193 | }; 194 | 195 | struct Inst { 196 | uint32_t bitsize = 0; 197 | Op op; 198 | uint16_t nof_args = 0; 199 | Inst *args[3]; 200 | Basic_block *bb = nullptr; 201 | Inst *prev = nullptr; 202 | Inst *next = nullptr; 203 | uint32_t id; 204 | std::set used_by; 205 | std::vector phi_args; 206 | 207 | union { 208 | struct { 209 | Basic_block *dest_bb; 210 | } br1; 211 | struct { 212 | Basic_block *true_bb; 213 | Basic_block *false_bb; 214 | } br3; 215 | struct { 216 | unsigned __int128 value; 217 | } value; 218 | } u; 219 | 220 | Inst_class iclass() const 221 | { 222 | return inst_info[(int)op].iclass; 223 | } 224 | const char *name() const 225 | { 226 | return inst_info[(int)op].name; 227 | } 228 | bool has_lhs() const 229 | { 230 | return inst_info[(int)op].has_lhs; 231 | } 232 | bool is_commutative() const 233 | { 234 | return inst_info[(int)op].is_commutative; 235 | } 236 | unsigned __int128 value() const; 237 | __int128 signed_value() const; 238 | void insert_after(Inst *inst); 239 | void insert_before(Inst *inst); 240 | void move_after(Inst *inst); 241 | void move_before(Inst *inst); 242 | void replace_use_with(Inst *use, Inst *new_inst); 243 | void replace_all_uses_with(Inst *inst); 244 | void update_uses(); 245 | Inst *get_phi_arg(Basic_block *bb); 246 | void update_phi_arg(Inst *inst, Basic_block *bb); 247 | void add_phi_arg(Inst *inst, Basic_block *bb); 248 | void remove_phi_arg(Basic_block *bb); 249 | void remove_phi_args(); 250 | void print(FILE *stream) const; 251 | 252 | Inst(); 253 | }; 254 | 255 | struct Basic_block { 256 | std::vector phis; 257 | std::vector preds; 258 | std::vector succs; 259 | 260 | Inst *first_inst = nullptr; 261 | Inst *last_inst = nullptr; 262 | Function *func; 263 | int id; 264 | 265 | void insert_last(Inst *inst); 266 | void insert_phi(Inst *inst); 267 | Inst *build_inst(Op op); 268 | Inst *build_inst(Op op, Inst *arg); 269 | Inst *build_inst(Op op, uint32_t arg_val); 270 | Inst *build_inst(Op op, Inst *arg1, Inst *arg2); 271 | Inst *build_inst(Op op, Inst *arg1, uint32_t arg2_val); 272 | Inst *build_inst(Op op, Inst *arg1, Inst *arg2, Inst *arg3); 273 | Inst *build_inst(Op op, Inst *arg1, uint32_t arg2_val, uint32_t arg3_val); 274 | Inst *build_phi_inst(int bitsize); 275 | Inst *build_ret_inst(); 276 | Inst *build_ret_inst(Inst *arg); 277 | Inst *build_ret_inst(Inst *arg1, Inst *arg2); 278 | Inst *build_br_inst(Basic_block *dest_bb); 279 | Inst *build_br_inst(Inst *cond, Basic_block *true_bb, Basic_block *false_bb); 280 | Inst *build_extract_id(Inst *arg); 281 | Inst *build_extract_offset(Inst *arg); 282 | Inst *build_extract_bit(Inst *arg, uint32_t bit_idx); 283 | Inst *build_trunc(Inst *arg, uint32_t nof_bits); 284 | Inst *value_inst(unsigned __int128 value, uint32_t bitsize); 285 | Inst *value_m1_inst(uint32_t bitsize); 286 | void print(FILE *stream) const; 287 | }; 288 | 289 | struct Function { 290 | public: 291 | std::string name; 292 | std::vector bbs; 293 | std::map, Inst *> values; 294 | Inst *last_value_inst = nullptr; 295 | 296 | // Data for dominance calculations. 297 | bool has_dominance = false; 298 | std::map nearest_dom; 299 | std::map nearest_postdom; 300 | 301 | Basic_block *build_bb(); 302 | Inst *value_inst(unsigned __int128 value, uint32_t bitsize); 303 | void rename(const std::string& str); 304 | void canonicalize(); 305 | void reset_ir_id(); 306 | Function *clone(Module *dest_module); 307 | void print(FILE *stream) const; 308 | Module *module; 309 | private: 310 | int next_bb_id = 0; 311 | }; 312 | 313 | struct Module { 314 | std::vector functions; 315 | Function *build_function(const std::string& name); 316 | void canonicalize(); 317 | Module *clone(); 318 | void print(FILE *stream) const; 319 | uint32_t ptr_bits; 320 | uint32_t ptr_id_bits; 321 | uint32_t ptr_id_high; 322 | uint32_t ptr_id_low; 323 | uint32_t ptr_offset_bits; 324 | uint32_t ptr_offset_high; 325 | uint32_t ptr_offset_low; 326 | }; 327 | 328 | struct Config 329 | { 330 | Config(); 331 | int verbose = 0; 332 | 333 | // SMT solver timeout in ms. 334 | int timeout = 120000; 335 | 336 | // SMT solver memory limit in megabytes. 337 | int memory_limit = 5 * 1024; 338 | 339 | // Optimize based on UB, such as removing instructions if all uses are in 340 | // UB paths. 341 | // 342 | // This implies that check_refine must perform the UB check before checking 343 | // abort/exit, retval, or memory. 344 | bool optimize_ub = true; 345 | 346 | bool redis_cache = false; 347 | }; 348 | 349 | extern Config config; 350 | 351 | enum class Result_status { 352 | correct, incorrect, unknown 353 | }; 354 | 355 | struct Solver_result { 356 | Result_status status; 357 | std::optional message; 358 | }; 359 | 360 | Module *create_module(uint32_t ptr_bits, uint32_t id_bits, uint32_t offset_bits); 361 | void destroy_module(Module *); 362 | void destroy_function(Function *); 363 | void destroy_basic_block(Basic_block *); 364 | void destroy_instruction(Inst *); 365 | 366 | Inst *create_inst(Op op); 367 | Inst *create_inst(Op op, Inst *arg); 368 | Inst *create_inst(Op op, Inst *arg1, Inst *arg2); 369 | Inst *create_inst(Op op, Inst *arg1, uint32_t arg2_val); 370 | Inst *create_inst(Op op, Inst *arg1, Inst *arg2, Inst *arg3); 371 | Inst *create_inst(Op op, Inst *arg1, uint32_t arg2_val, uint32_t arg3_val); 372 | Inst *create_phi_inst(int bitsize); 373 | Inst *create_ret_inst(); 374 | Inst *create_ret_inst(Inst *arg); 375 | Inst *create_ret_inst(Inst *arg1, Inst *arg2); 376 | Inst *create_br_inst(Basic_block *dest_bb); 377 | Inst *create_br_inst(Inst *cond, Basic_block *true_bb, Basic_block *false_bb); 378 | 379 | /* Opt level runs all optimization <= the level: 380 | * 0: Dead code elimination 381 | * 1: Registers are eliminated. Simple peephole optimizations. */ 382 | void optimize_func(Function *func, int opt_level); 383 | void optimize_module(Module *module, int opt_level); 384 | 385 | struct SStats { 386 | std::array time = {0, 0, 0, 0}; 387 | bool skipped = true; 388 | }; 389 | 390 | uint64_t get_time(); 391 | 392 | // cache.cpp 393 | struct Cache 394 | { 395 | Cache(Function *func); 396 | std::optional get(); 397 | void set(Solver_result result); 398 | 399 | private: 400 | std::string key; 401 | std::string hash(Function *func); 402 | }; 403 | 404 | // cfg.cpp 405 | void clear_dominance(Function *func); 406 | void calculate_dominance(Function *func); 407 | void reverse_post_order(Function *func); 408 | bool has_loops(Function *func); 409 | bool simplify_cfg(Function *func); 410 | bool simplify_cfg(Module *module); 411 | Basic_block *nearest_dominator(const Basic_block *bb); 412 | bool dominates(const Basic_block *bb1, const Basic_block *bb2); 413 | bool postdominates(const Basic_block *bb1, const Basic_block *bb2); 414 | 415 | // check.cpp 416 | bool identical(Function *func1, Function *func2); 417 | Solver_result check_refine(Module *module, bool run_simplify_inst = true); 418 | Solver_result check_assert(Function *func); 419 | Solver_result check_ub(Function *func); 420 | void convert(Module *module); 421 | 422 | // dead_code_elimination.cpp 423 | void dead_code_elimination(Function *func); 424 | void dead_code_elimination(Module *module); 425 | 426 | // loop_unroll.cpp 427 | bool loop_unroll(Function *func, int nof_unroll = unroll_limit); 428 | bool loop_unroll(Module *module); 429 | 430 | // memory_opt.cpp 431 | void canonicalize_memory(Module *module); 432 | void ls_elim(Function *func); 433 | void ls_elim(Module *module); 434 | 435 | // read_ir.cpp 436 | Module *parse_ir(std::string const& file_name); 437 | 438 | struct MemoryObject { 439 | std::string sym_name; 440 | uint64_t id; 441 | uint64_t size; 442 | uint64_t flags; 443 | }; 444 | 445 | // read_aarch64.cpp 446 | struct Aarch64RegIdx { 447 | static constexpr uint64_t x0 = 0; 448 | static constexpr uint64_t x1 = 1; 449 | static constexpr uint64_t x2 = 2; 450 | static constexpr uint64_t x3 = 3; 451 | static constexpr uint64_t x4 = 4; 452 | static constexpr uint64_t x5 = 5; 453 | static constexpr uint64_t x6 = 6; 454 | static constexpr uint64_t x7 = 7; 455 | static constexpr uint64_t x8 = 8; 456 | static constexpr uint64_t x9 = 9; 457 | static constexpr uint64_t x10 = 10; 458 | static constexpr uint64_t x11 = 11; 459 | static constexpr uint64_t x12 = 12; 460 | static constexpr uint64_t x13 = 13; 461 | static constexpr uint64_t x14 = 14; 462 | static constexpr uint64_t x15 = 15; 463 | static constexpr uint64_t x16 = 16; 464 | static constexpr uint64_t x17 = 17; 465 | static constexpr uint64_t x18 = 18; 466 | static constexpr uint64_t x19 = 19; 467 | static constexpr uint64_t x20 = 20; 468 | static constexpr uint64_t x21 = 21; 469 | static constexpr uint64_t x22 = 22; 470 | static constexpr uint64_t x23 = 23; 471 | static constexpr uint64_t x24 = 24; 472 | static constexpr uint64_t x25 = 25; 473 | static constexpr uint64_t x26 = 26; 474 | static constexpr uint64_t x27 = 27; 475 | static constexpr uint64_t x28 = 28; 476 | static constexpr uint64_t x29 = 29; 477 | static constexpr uint64_t x30 = 30; 478 | static constexpr uint64_t x31 = 31; 479 | 480 | static constexpr uint64_t z0 = 32; 481 | static constexpr uint64_t z1 = 33; 482 | static constexpr uint64_t z2 = 34; 483 | static constexpr uint64_t z3 = 35; 484 | static constexpr uint64_t z4 = 36; 485 | static constexpr uint64_t z5 = 37; 486 | static constexpr uint64_t z6 = 38; 487 | static constexpr uint64_t z7 = 39; 488 | static constexpr uint64_t z8 = 40; 489 | static constexpr uint64_t z9 = 41; 490 | static constexpr uint64_t z10 = 42; 491 | static constexpr uint64_t z11 = 43; 492 | static constexpr uint64_t z12 = 44; 493 | static constexpr uint64_t z13 = 45; 494 | static constexpr uint64_t z14 = 46; 495 | static constexpr uint64_t z15 = 47; 496 | static constexpr uint64_t z16 = 48; 497 | static constexpr uint64_t z17 = 49; 498 | static constexpr uint64_t z18 = 50; 499 | static constexpr uint64_t z19 = 51; 500 | static constexpr uint64_t z20 = 52; 501 | static constexpr uint64_t z21 = 53; 502 | static constexpr uint64_t z22 = 54; 503 | static constexpr uint64_t z23 = 55; 504 | static constexpr uint64_t z24 = 56; 505 | static constexpr uint64_t z25 = 57; 506 | static constexpr uint64_t z26 = 58; 507 | static constexpr uint64_t z27 = 50; 508 | static constexpr uint64_t z28 = 60; 509 | static constexpr uint64_t z29 = 61; 510 | static constexpr uint64_t z30 = 62; 511 | static constexpr uint64_t z31 = 63; 512 | 513 | static constexpr uint64_t p0 = 64; 514 | static constexpr uint64_t p1 = 65; 515 | static constexpr uint64_t p2 = 66; 516 | static constexpr uint64_t p3 = 67; 517 | static constexpr uint64_t p4 = 68; 518 | static constexpr uint64_t p5 = 69; 519 | static constexpr uint64_t p6 = 70; 520 | static constexpr uint64_t p7 = 71; 521 | static constexpr uint64_t p8 = 72; 522 | static constexpr uint64_t p9 = 73; 523 | static constexpr uint64_t p10 = 74; 524 | static constexpr uint64_t p11 = 75; 525 | static constexpr uint64_t p12 = 76; 526 | static constexpr uint64_t p13 = 77; 527 | static constexpr uint64_t p14 = 78; 528 | static constexpr uint64_t p15 = 79; 529 | 530 | static constexpr uint64_t sp = 80; 531 | 532 | // Condition flags 533 | static constexpr uint64_t n = 81; 534 | static constexpr uint64_t z = 82; 535 | static constexpr uint64_t c = 83; 536 | static constexpr uint64_t v = 84; 537 | 538 | // Pseudo condition flags 539 | static constexpr uint64_t hi = 85; 540 | static constexpr uint64_t lt = 86; 541 | static constexpr uint64_t gt = 87; 542 | 543 | // Pseudo registers tracking abort/exit 544 | static constexpr uint64_t abort = 88; 545 | static constexpr uint64_t exit = 89; 546 | static constexpr uint64_t exit_val = 90; 547 | }; 548 | 549 | struct aarch64_state { 550 | std::vector registers; 551 | 552 | // The memory instruction corresponding to each symbol. 553 | std::map sym_name2mem; 554 | 555 | std::string file_name; 556 | std::string func_name; 557 | Module *module; 558 | Basic_block *entry_bb; 559 | Basic_block *exit_bb; 560 | uint32_t reg_bitsize; 561 | uint32_t freg_bitsize; 562 | std::vector memory_objects; 563 | int next_local_id; 564 | }; 565 | Function *parse_aarch64(std::string const& file_name, aarch64_state *state); 566 | 567 | // read_riscv.cpp 568 | struct RiscvRegIdx { 569 | static constexpr uint64_t x0 = 0; 570 | static constexpr uint64_t x1 = 1; 571 | static constexpr uint64_t x2 = 2; 572 | static constexpr uint64_t x3 = 3; 573 | static constexpr uint64_t x4 = 4; 574 | static constexpr uint64_t x5 = 5; 575 | static constexpr uint64_t x6 = 6; 576 | static constexpr uint64_t x7 = 7; 577 | static constexpr uint64_t x8 = 8; 578 | static constexpr uint64_t x9 = 9; 579 | static constexpr uint64_t x10 = 10; 580 | static constexpr uint64_t x11 = 11; 581 | static constexpr uint64_t x12 = 12; 582 | static constexpr uint64_t x13 = 13; 583 | static constexpr uint64_t x14 = 14; 584 | static constexpr uint64_t x15 = 15; 585 | static constexpr uint64_t x16 = 16; 586 | static constexpr uint64_t x17 = 17; 587 | static constexpr uint64_t x18 = 18; 588 | static constexpr uint64_t x19 = 19; 589 | static constexpr uint64_t x20 = 20; 590 | static constexpr uint64_t x21 = 21; 591 | static constexpr uint64_t x22 = 22; 592 | static constexpr uint64_t x23 = 23; 593 | static constexpr uint64_t x24 = 24; 594 | static constexpr uint64_t x25 = 25; 595 | static constexpr uint64_t x26 = 26; 596 | static constexpr uint64_t x27 = 27; 597 | static constexpr uint64_t x28 = 28; 598 | static constexpr uint64_t x29 = 29; 599 | static constexpr uint64_t x30 = 30; 600 | static constexpr uint64_t x31 = 31; 601 | 602 | static constexpr uint64_t f0 = 32; 603 | static constexpr uint64_t f1 = 33; 604 | static constexpr uint64_t f2 = 34; 605 | static constexpr uint64_t f3 = 35; 606 | static constexpr uint64_t f4 = 36; 607 | static constexpr uint64_t f5 = 37; 608 | static constexpr uint64_t f6 = 38; 609 | static constexpr uint64_t f7 = 39; 610 | static constexpr uint64_t f8 = 40; 611 | static constexpr uint64_t f9 = 41; 612 | static constexpr uint64_t f10 = 42; 613 | static constexpr uint64_t f11 = 43; 614 | static constexpr uint64_t f12 = 44; 615 | static constexpr uint64_t f13 = 45; 616 | static constexpr uint64_t f14 = 46; 617 | static constexpr uint64_t f15 = 47; 618 | static constexpr uint64_t f16 = 48; 619 | static constexpr uint64_t f17 = 49; 620 | static constexpr uint64_t f18 = 50; 621 | static constexpr uint64_t f19 = 51; 622 | static constexpr uint64_t f20 = 52; 623 | static constexpr uint64_t f21 = 53; 624 | static constexpr uint64_t f22 = 54; 625 | static constexpr uint64_t f23 = 55; 626 | static constexpr uint64_t f24 = 56; 627 | static constexpr uint64_t f25 = 57; 628 | static constexpr uint64_t f26 = 58; 629 | static constexpr uint64_t f27 = 50; 630 | static constexpr uint64_t f28 = 60; 631 | static constexpr uint64_t f29 = 61; 632 | static constexpr uint64_t f30 = 62; 633 | static constexpr uint64_t f31 = 63; 634 | 635 | static constexpr uint64_t v0 = 64; 636 | static constexpr uint64_t v1 = 65; 637 | static constexpr uint64_t v2 = 66; 638 | static constexpr uint64_t v3 = 67; 639 | static constexpr uint64_t v4 = 68; 640 | static constexpr uint64_t v5 = 69; 641 | static constexpr uint64_t v6 = 70; 642 | static constexpr uint64_t v7 = 71; 643 | static constexpr uint64_t v8 = 72; 644 | static constexpr uint64_t v9 = 73; 645 | static constexpr uint64_t v10 = 74; 646 | static constexpr uint64_t v11 = 75; 647 | static constexpr uint64_t v12 = 76; 648 | static constexpr uint64_t v13 = 77; 649 | static constexpr uint64_t v14 = 78; 650 | static constexpr uint64_t v15 = 79; 651 | static constexpr uint64_t v16 = 80; 652 | static constexpr uint64_t v17 = 81; 653 | static constexpr uint64_t v18 = 82; 654 | static constexpr uint64_t v19 = 83; 655 | static constexpr uint64_t v20 = 84; 656 | static constexpr uint64_t v21 = 85; 657 | static constexpr uint64_t v22 = 86; 658 | static constexpr uint64_t v23 = 87; 659 | static constexpr uint64_t v24 = 88; 660 | static constexpr uint64_t v25 = 89; 661 | static constexpr uint64_t v26 = 90; 662 | static constexpr uint64_t v27 = 91; 663 | static constexpr uint64_t v28 = 92; 664 | static constexpr uint64_t v29 = 93; 665 | static constexpr uint64_t v30 = 94; 666 | static constexpr uint64_t v31 = 95; 667 | 668 | static constexpr uint64_t vsew = 96; 669 | static constexpr uint64_t vl = 97; 670 | 671 | // Pseudo registers tracking abort/exit 672 | static constexpr uint64_t abort = 98; 673 | static constexpr uint64_t exit = 99; 674 | static constexpr uint64_t exit_val = 100; 675 | }; 676 | 677 | struct riscv_state { 678 | std::vector registers; 679 | 680 | // The memory instruction corresponding to each symbol. 681 | std::map sym_name2mem; 682 | 683 | std::string file_name; 684 | std::string func_name; 685 | Module *module; 686 | Basic_block *entry_bb; 687 | Basic_block *exit_bb; 688 | uint32_t reg_bitsize; 689 | uint32_t freg_bitsize; 690 | uint32_t vreg_bitsize; 691 | std::vector memory_objects; 692 | int next_local_id; 693 | }; 694 | Function *parse_riscv(std::string const& file_name, riscv_state *state); 695 | 696 | // simplify_insts.cpp 697 | struct Simplify_config { 698 | virtual Inst *get_inst(Op, Inst *) 699 | { 700 | return nullptr; 701 | } 702 | virtual Inst *get_inst(Op, Inst *, Inst *) 703 | { 704 | return nullptr; 705 | } 706 | virtual Inst *get_inst(Op, Inst *, Inst *, Inst *) 707 | { 708 | return nullptr; 709 | } 710 | virtual void set_inst(Inst *, Op, Inst *) 711 | { 712 | } 713 | virtual void set_inst(Inst *, Op, Inst *, Inst *) 714 | { 715 | } 716 | virtual void set_inst(Inst *, Op, Inst *, Inst *, Inst *) 717 | { 718 | } 719 | }; 720 | 721 | Inst *constant_fold_inst(Inst *inst); 722 | Inst *simplify_inst(Inst *inst, Simplify_config *config = nullptr); 723 | void simplify_insts(Function *func); 724 | void simplify_insts(Module *module); 725 | void simplify_mem(Function *func); 726 | void simplify_mem(Module *module); 727 | 728 | // smt_cvc5.cpp 729 | std::pair check_refine_cvc5(Function *func); 730 | std::pair check_assert_cvc5(Function *func); 731 | std::pair check_ub_cvc5(Function *func); 732 | 733 | // smt_z3.cpp 734 | std::pair check_refine_z3(Function *func); 735 | std::pair check_assert_z3(Function *func); 736 | std::pair check_ub_z3(Function *func); 737 | 738 | // util.cpp 739 | uint32_t popcount(unsigned __int128 x); 740 | uint32_t clz(unsigned __int128 x); 741 | uint32_t ctz(unsigned __int128 x); 742 | bool is_pow2(unsigned __int128 x); 743 | bool is_value_zero(Inst *inst); 744 | bool is_value_one(Inst *inst); 745 | bool is_value_signed_min(Inst *inst); 746 | bool is_value_signed_min(Inst *inst, uint32_t bitsize); 747 | bool is_value_signed_max(Inst *inst); 748 | bool is_value_signed_max(Inst *inst, uint32_t bitsize); 749 | bool is_value_m1(Inst *inst); 750 | bool is_value_pow2(Inst *inst); 751 | Inst *gen_fmin(Basic_block *bb, Inst *elem1, Inst *elem2); 752 | Inst *gen_fmax(Basic_block *bb, Inst *elem1, Inst *elem2); 753 | Inst *gen_bitreverse(Basic_block *bb, Inst *arg); 754 | Inst *gen_clz(Basic_block *bb, Inst *arg); 755 | Inst *gen_clrsb(Basic_block *bb, Inst *arg); 756 | Inst *gen_ctz(Basic_block *bb, Inst *arg); 757 | Inst *gen_popcount(Basic_block *bb, Inst *arg); 758 | Inst *gen_bswap(Basic_block *bb, Inst *arg); 759 | 760 | // validate_ir.cpp 761 | void validate(Module *module); 762 | void validate(Function *func); 763 | 764 | // vrp.cpp 765 | void vrp(Function *func); 766 | void vrp(Module *module); 767 | 768 | } // end namespace smtgcc 769 | 770 | #endif 771 | -------------------------------------------------------------------------------- /lib/util.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "smtgcc.h" 4 | 5 | namespace smtgcc { 6 | 7 | namespace { 8 | 9 | Inst *gen_fmin_fmax(Basic_block *bb, Inst *elem1, Inst *elem2, bool is_min) 10 | { 11 | Inst *is_nan = bb->build_inst(Op::IS_NAN, elem2); 12 | Inst *cmp; 13 | if (is_min) 14 | cmp = bb->build_inst(Op::FLT, elem1, elem2); 15 | else 16 | cmp = bb->build_inst(Op::FLT, elem2, elem1); 17 | Inst *res1 = bb->build_inst(Op::ITE, cmp, elem1, elem2); 18 | Inst *res2 = bb->build_inst(Op::ITE, is_nan, elem1, res1); 19 | // 0.0 and -0.0 is equal as floating-point values, and fmin(0.0, -0.0) 20 | // may return eiter of them. But we treat them as 0.0 > -0.0 here, 21 | // otherwise we will report miscompilations when GCC switch the order 22 | // of the arguments. 23 | Inst *zero = bb->value_inst(0, elem1->bitsize); 24 | Inst *is_zero1 = bb->build_inst(Op::FEQ, elem1, zero); 25 | Inst *is_zero2 = bb->build_inst(Op::FEQ, elem2, zero); 26 | Inst *is_zero = bb->build_inst(Op::AND, is_zero1, is_zero2); 27 | Inst *cmp2; 28 | if (is_min) 29 | cmp2 = bb->build_inst(Op::SLT, elem1, elem2); 30 | else 31 | cmp2 = bb->build_inst(Op::SLT, elem2, elem1); 32 | Inst *res3 = bb->build_inst(Op::ITE, cmp2, elem1, elem2); 33 | Inst *res = bb->build_inst(Op::ITE, is_zero, res3, res2); 34 | return res; 35 | } 36 | 37 | } // end anonymous namespace 38 | 39 | uint32_t popcount(unsigned __int128 x) 40 | { 41 | uint32_t result = 0; 42 | for (int i = 0; i < 4; i++) 43 | { 44 | uint32_t t = x >> (i* 32); 45 | result += __builtin_popcount(t); 46 | } 47 | return result; 48 | } 49 | 50 | uint32_t clz(unsigned __int128 x) 51 | { 52 | uint32_t result = 0; 53 | for (int i = 0; i < 4; i++) 54 | { 55 | uint32_t t = x >> ((3 - i) * 32); 56 | if (t) 57 | return result + __builtin_clz(t); 58 | result += 32; 59 | } 60 | return result; 61 | } 62 | 63 | uint32_t ctz(unsigned __int128 x) 64 | { 65 | uint32_t result = 0; 66 | for (int i = 0; i < 4; i++) 67 | { 68 | uint32_t t = x >> (i * 32); 69 | if (t) 70 | return result + __builtin_ctz(t); 71 | result += 32; 72 | } 73 | return result; 74 | } 75 | 76 | bool is_pow2(unsigned __int128 x) 77 | { 78 | return x != 0 && (x & (x - 1)) == 0; 79 | } 80 | 81 | bool is_value_zero(Inst *inst) 82 | { 83 | return inst->op == Op::VALUE && inst->value() == 0; 84 | } 85 | 86 | bool is_value_one(Inst *inst) 87 | { 88 | return inst->op == Op::VALUE && inst->value() == 1; 89 | } 90 | 91 | bool is_value_signed_min(Inst *inst) 92 | { 93 | if (inst->op != Op::VALUE) 94 | return false; 95 | unsigned __int128 smin = ((unsigned __int128)1) << (inst->bitsize - 1); 96 | return inst->value() == smin; 97 | } 98 | 99 | bool is_value_signed_min(Inst *inst, uint32_t bitsize) 100 | { 101 | if (inst->op != Op::VALUE) 102 | return false; 103 | __int128 smin = ((unsigned __int128)1) << (bitsize - 1); 104 | smin = (smin << (128 - bitsize)) >> (128 - bitsize); 105 | return inst->signed_value() == smin; 106 | } 107 | 108 | bool is_value_signed_max(Inst *inst) 109 | { 110 | if (inst->op != Op::VALUE) 111 | return false; 112 | unsigned __int128 smax = (((unsigned __int128)1) << (inst->bitsize - 1)) - 1; 113 | return inst->value() == smax; 114 | } 115 | 116 | bool is_value_signed_max(Inst *inst, uint32_t bitsize) 117 | { 118 | if (inst->op != Op::VALUE) 119 | return false; 120 | unsigned __int128 smax = (((unsigned __int128)1) << (bitsize - 1)) - 1; 121 | return inst->value() == smax; 122 | } 123 | 124 | bool is_value_m1(Inst *inst) 125 | { 126 | if (inst->op != Op::VALUE) 127 | return false; 128 | unsigned __int128 m1 = ~((unsigned __int128)0); 129 | m1 = (m1 << (128 - inst->bitsize)) >> (128 - inst->bitsize); 130 | return inst->value() == m1; 131 | } 132 | 133 | bool is_value_pow2(Inst *inst) 134 | { 135 | if (inst->op != Op::VALUE) 136 | return false; 137 | return is_pow2(inst->value()); 138 | } 139 | 140 | Inst *gen_fmin(Basic_block *bb, Inst *elem1, Inst *elem2) 141 | { 142 | return gen_fmin_fmax(bb, elem1, elem2, true); 143 | } 144 | 145 | Inst *gen_fmax(Basic_block *bb, Inst *elem1, Inst *elem2) 146 | { 147 | return gen_fmin_fmax(bb, elem1, elem2, false); 148 | } 149 | 150 | Inst *gen_bitreverse(Basic_block *bb, Inst *arg) 151 | { 152 | Inst *inst = bb->build_trunc(arg, 1); 153 | for (uint32_t i = 1; i < arg->bitsize; i += 1) 154 | { 155 | Inst *bit = bb->build_extract_bit(arg, i); 156 | inst = bb->build_inst(Op::CONCAT, inst, bit); 157 | } 158 | return inst; 159 | } 160 | 161 | Inst *gen_clz(Basic_block *bb, Inst *arg) 162 | { 163 | Inst *inst = bb->value_inst(arg->bitsize, arg->bitsize); 164 | for (unsigned i = 0; i < arg->bitsize; i++) 165 | { 166 | Inst *bit = bb->build_extract_bit(arg, i); 167 | Inst *val = bb->value_inst(arg->bitsize - i - 1, arg->bitsize); 168 | inst = bb->build_inst(Op::ITE, bit, val, inst); 169 | } 170 | return inst; 171 | } 172 | 173 | Inst *gen_ctz(Basic_block *bb, Inst *arg) 174 | { 175 | Inst *inst = bb->value_inst(arg->bitsize, arg->bitsize); 176 | for (int i = arg->bitsize - 1; i >= 0; i--) 177 | { 178 | Inst *bit = bb->build_extract_bit(arg, i); 179 | Inst *val = bb->value_inst(i, arg->bitsize); 180 | inst = bb->build_inst(Op::ITE, bit, val, inst); 181 | } 182 | return inst; 183 | } 184 | 185 | Inst *gen_clrsb(Basic_block *bb, Inst *arg) 186 | { 187 | Inst *signbit = bb->build_extract_bit(arg, arg->bitsize - 1); 188 | Inst *inst = bb->value_inst(arg->bitsize - 1, arg->bitsize); 189 | for (unsigned i = 0; i < arg->bitsize - 1; i++) 190 | { 191 | Inst *bit = bb->build_extract_bit(arg, i); 192 | Inst *cmp = bb->build_inst(Op::NE, bit, signbit); 193 | Inst *val = bb->value_inst(arg->bitsize - i - 2, arg->bitsize); 194 | inst = bb->build_inst(Op::ITE, cmp, val, inst); 195 | } 196 | return inst; 197 | } 198 | 199 | Inst *gen_popcount(Basic_block *bb, Inst *arg) 200 | { 201 | uint32_t bitsize = std::bit_width(arg->bitsize); 202 | Inst *bit = bb->build_extract_bit(arg, 0); 203 | Inst *inst = bb->build_inst(Op::ZEXT, bit, bitsize); 204 | for (uint32_t i = 1; i < arg->bitsize; i++) 205 | { 206 | bit = bb->build_extract_bit(arg, i); 207 | Inst *ext = bb->build_inst(Op::ZEXT, bit, bitsize); 208 | inst = bb->build_inst(Op::ADD, inst, ext); 209 | } 210 | inst = bb->build_inst(Op::ZEXT, inst, arg->bitsize); 211 | return inst; 212 | } 213 | 214 | Inst *gen_bswap(Basic_block *bb, Inst *arg) 215 | { 216 | Inst *inst = bb->build_trunc(arg, 8); 217 | for (uint32_t i = 8; i < arg->bitsize; i += 8) 218 | { 219 | Inst *byte = bb->build_inst(Op::EXTRACT, arg, i + 7, i); 220 | inst = bb->build_inst(Op::CONCAT, inst, byte); 221 | } 222 | return inst; 223 | } 224 | 225 | } // end namespace smtgcc 226 | -------------------------------------------------------------------------------- /lib/validate_ir.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "smtgcc.h" 6 | 7 | // TODO: Change all assert to a validation_assert (that is not removed in 8 | // NDEBUG builds, or maybe a more descriptive exception? 9 | 10 | namespace smtgcc { 11 | 12 | namespace { 13 | 14 | void validate(Inst *inst) 15 | { 16 | // Some instructions are required to be placed in the entry block. 17 | if (inst->op == Op::PARAM 18 | || inst->op == Op::MEMORY 19 | || inst->op == Op::VALUE) 20 | { 21 | assert(inst->bb == inst->bb->func->bbs[0]); 22 | } 23 | 24 | // RET and BR must be the last instruction in the basic block. 25 | // All other instructions must have a next instruction. 26 | if (inst->op == Op::RET || inst->op == Op::BR) 27 | { 28 | assert(inst == inst->bb->last_inst); 29 | assert(!inst->next); 30 | } 31 | else 32 | assert(inst->next); 33 | 34 | // The next and prev instructions (if any) must be in the same basic 35 | // block as the original instruction. 36 | if (inst->prev) 37 | assert(inst->bb == inst->prev->bb); 38 | if (inst->next) 39 | assert(inst->bb == inst->next->bb); 40 | } 41 | 42 | void validate(Basic_block *bb) 43 | { 44 | // There must be instructions in the BB. 45 | assert(bb->first_inst); 46 | assert(bb->last_inst); 47 | 48 | // Check that the first and last instructions actually are the first 49 | // and last instructions in the basic block. 50 | assert(!bb->first_inst->prev); 51 | assert(!bb->last_inst->next); 52 | 53 | // Predecessors must not be in preds multiple times. 54 | std::set pred_set(bb->preds.begin(), bb->preds.end()); 55 | assert(bb->preds.size() == pred_set.size()); 56 | 57 | // Each predecessor must have this basic block as a successor. 58 | for (Basic_block *pred_bb : bb->preds) 59 | { 60 | auto it = std::find(pred_bb->succs.begin(), pred_bb->succs.end(), bb); 61 | assert(it != pred_bb->succs.end()); 62 | } 63 | 64 | // The successors must agree with the last instruction, and the last BB 65 | // (and no other) must end by a RET instruction. 66 | assert(bb->succs.size() < 3); 67 | if (bb->succs.size() == 0) 68 | { 69 | assert(bb->last_inst->op == Op::RET); 70 | } 71 | else if (bb->succs.size() == 1) 72 | { 73 | assert(bb->last_inst->op == Op::BR); 74 | assert(bb->last_inst->u.br1.dest_bb == bb->succs[0]); 75 | } 76 | else 77 | { 78 | assert(bb->succs.size() == 2); 79 | assert(bb->last_inst->op == Op::BR); 80 | Basic_block *true_bb = bb->last_inst->u.br3.true_bb; 81 | Basic_block *false_bb = bb->last_inst->u.br3.false_bb; 82 | assert(bb->succs[0] == true_bb && bb->succs[1] == false_bb); 83 | } 84 | 85 | // Phi nodes must have one argument for each predecessor. 86 | for (Inst *phi : bb->phis) 87 | { 88 | assert(phi->phi_args.size() == bb->preds.size()); 89 | for (auto [arg_inst, arg_bb] : phi->phi_args) 90 | { 91 | auto it = std::find(bb->preds.begin(), bb->preds.end(), arg_bb); 92 | assert(it != bb->preds.end()); 93 | } 94 | } 95 | 96 | // TODO: For each inst, check that its used_by is correct. 97 | for (Inst *inst = bb->first_inst; inst; inst = inst->next) 98 | { 99 | assert(inst->bb == bb); 100 | validate(inst); 101 | } 102 | } 103 | 104 | } // end anonymous namespace 105 | 106 | void validate(Function *func) 107 | { 108 | assert(func->bbs.size() > 0); 109 | for (Basic_block *bb : func->bbs) 110 | { 111 | validate(bb); 112 | } 113 | 114 | // Check that each instruction has been defined before use. 115 | std::set defined; 116 | for (Basic_block *bb : func->bbs) 117 | { 118 | for (Inst *phi : bb->phis) 119 | { 120 | assert(!defined.contains(phi)); 121 | defined.insert(phi); 122 | } 123 | for (Inst *inst = bb->first_inst; inst; inst = inst->next) 124 | { 125 | assert(!defined.contains(inst)); 126 | for (unsigned i = 0 ; i < inst->nof_args; i++) 127 | { 128 | assert(defined.contains(inst->args[i])); 129 | } 130 | defined.insert(inst); 131 | } 132 | } 133 | } 134 | 135 | void validate(Module *module) 136 | { 137 | for (auto func : module->functions) 138 | validate(func); 139 | } 140 | 141 | } // end namespace smtgcc 142 | -------------------------------------------------------------------------------- /lib/vrp.cpp: -------------------------------------------------------------------------------- 1 | // This file contains a simple optimization pass that tracks how many 2 | // leading and trailing zeros each SSA variable has. The main usage of 3 | // this pass is to remove redundant UB checks generated from GIMPLE 4 | // range information. 5 | #include 6 | 7 | #include "smtgcc.h" 8 | 9 | namespace smtgcc { 10 | 11 | namespace { 12 | 13 | class Vrp 14 | { 15 | std::map leading_zeros_map; 16 | std::map trailing_zeros_map; 17 | Function *func; 18 | 19 | uint32_t leading_zeros(Inst *inst); 20 | uint32_t trailing_zeros(Inst *inst); 21 | void handle_add(Inst *inst); 22 | void handle_and(Inst *inst); 23 | void handle_ashr(Inst *inst); 24 | void handle_extract(Inst *inst); 25 | void handle_concat(Inst *inst); 26 | void handle_ite(Inst *inst); 27 | void handle_lshr(Inst *inst); 28 | void handle_memory(Inst *inst); 29 | void handle_mov(Inst *inst); 30 | void handle_mul(Inst *inst); 31 | void handle_neg(Inst *inst); 32 | void handle_or(Inst *inst); 33 | void handle_phi(Inst *inst); 34 | void handle_sadd_wraps(Inst *inst); 35 | void handle_sext(Inst *inst); 36 | void handle_slt(Inst *inst); 37 | void handle_shl(Inst *inst); 38 | void handle_ssub_wraps(Inst *inst); 39 | void handle_ult(Inst *inst); 40 | void handle_value(Inst *inst); 41 | void handle_xor(Inst *inst); 42 | void handle_zext(Inst *inst); 43 | void handle_inst(Inst *inst); 44 | 45 | public: 46 | Vrp(Function *func); 47 | }; 48 | 49 | uint32_t Vrp::leading_zeros(Inst *inst) 50 | { 51 | auto I = leading_zeros_map.find(inst); 52 | if (I != leading_zeros_map.end()) 53 | return I->second; 54 | return 0; 55 | } 56 | 57 | uint32_t Vrp::trailing_zeros(Inst *inst) 58 | { 59 | auto I = trailing_zeros_map.find(inst); 60 | if (I != trailing_zeros_map.end()) 61 | return I->second; 62 | return 0; 63 | } 64 | 65 | void Vrp::handle_add(Inst *inst) 66 | { 67 | Inst *const arg1 = inst->args[0]; 68 | Inst *const arg2 = inst->args[1]; 69 | 70 | uint32_t lz = std::min(leading_zeros(arg1), leading_zeros(arg2)); 71 | uint32_t tz = std::min(trailing_zeros(arg1), trailing_zeros(arg2)); 72 | if (tz > 0) 73 | trailing_zeros_map.insert({inst, tz}); 74 | if (lz > 1) 75 | leading_zeros_map.insert({inst, lz - 1}); 76 | } 77 | 78 | void Vrp::handle_and(Inst *inst) 79 | { 80 | Inst *const arg1 = inst->args[0]; 81 | Inst *const arg2 = inst->args[1]; 82 | 83 | uint32_t lz = std::max(leading_zeros(arg1), leading_zeros(arg2)); 84 | uint32_t tz = std::max(trailing_zeros(arg1), trailing_zeros(arg2)); 85 | 86 | if (arg2->op == Op::VALUE && arg2->value() != 0) 87 | { 88 | // Delete "and x, c" if we know that all the bits that are 0 in the 89 | // constant c are already 0 in x. 90 | { 91 | uint32_t c_lz = clz(arg2->value()) - (128 - arg2->bitsize); 92 | uint32_t c_tz = ctz(arg2->value()); 93 | if (c_lz <= leading_zeros(arg1) 94 | && c_tz <= trailing_zeros(arg1) 95 | && c_lz + popcount(arg2->value()) + c_tz == arg2->bitsize) 96 | { 97 | inst->replace_all_uses_with(arg1); 98 | return; 99 | } 100 | } 101 | 102 | // Simplify "and x, c" by clearing the bits of the constant c for which 103 | // we know the bits in x are 0. 104 | { 105 | unsigned __int128 value = arg2->value(); 106 | 107 | unsigned __int128 lz_mask = -1; 108 | uint32_t lz_shift = (128 - inst->bitsize) + lz; 109 | assert(lz_shift <= 128); 110 | lz_mask = (lz_shift < 128) ? lz_mask >> lz_shift : 0; 111 | value = value & lz_mask; 112 | 113 | unsigned __int128 tz_mask = -1; 114 | uint32_t tz_shift = tz; 115 | assert(tz_shift <= 128); 116 | tz_mask = (tz_shift < 128) ? tz_mask << tz_shift : 0; 117 | value = value & tz_mask; 118 | 119 | if (value != arg2->value()) 120 | { 121 | Inst *v = inst->bb->value_inst(value, inst->bitsize); 122 | handle_inst(v); 123 | Inst *new_inst = create_inst(Op::AND, arg1, v); 124 | new_inst->insert_before(inst); 125 | inst->replace_all_uses_with(new_inst); 126 | handle_inst(new_inst); 127 | return; 128 | } 129 | } 130 | } 131 | 132 | if (tz > 0) 133 | trailing_zeros_map.insert({inst, tz}); 134 | if (lz > 0) 135 | leading_zeros_map.insert({inst, lz}); 136 | } 137 | 138 | void Vrp::handle_ashr(Inst *inst) 139 | { 140 | Inst *const arg1 = inst->args[0]; 141 | Inst *const arg2 = inst->args[1]; 142 | 143 | uint32_t lz = leading_zeros(arg1); 144 | uint32_t tz = trailing_zeros(arg1); 145 | 146 | if (lz > 0) 147 | { 148 | Inst *lshr = create_inst(Op::LSHR, arg1, arg2); 149 | lshr->insert_before(inst); 150 | inst->replace_all_uses_with(lshr); 151 | handle_inst(lshr); 152 | return; 153 | } 154 | 155 | if (arg2->op == Op::VALUE) 156 | { 157 | uint32_t shift = arg2->value(); 158 | if (tz > shift) 159 | trailing_zeros_map.insert({inst, tz - shift}); 160 | } 161 | } 162 | 163 | void Vrp::handle_extract(Inst *inst) 164 | { 165 | Inst *const arg1 = inst->args[0]; 166 | uint32_t hi = inst->args[1]->value(); 167 | uint32_t lo = inst->args[2]->value(); 168 | 169 | uint32_t lz = leading_zeros(arg1); 170 | uint32_t tz = trailing_zeros(arg1); 171 | 172 | if (inst->bitsize <= 128 173 | && (hi < tz || lo > (arg1->bitsize - lz))) 174 | { 175 | Inst *zero = inst->bb->value_inst(0, inst->bitsize); 176 | handle_inst(zero); 177 | inst->replace_all_uses_with(zero); 178 | return; 179 | } 180 | 181 | if (lo < tz) 182 | trailing_zeros_map.insert({inst, tz - lo}); 183 | if (hi >= arg1->bitsize - lz) 184 | leading_zeros_map.insert({inst, hi - (arg1->bitsize - 1 - lz)}); 185 | } 186 | 187 | void Vrp::handle_concat(Inst *inst) 188 | { 189 | Inst *const arg1 = inst->args[0]; 190 | Inst *const arg2 = inst->args[1]; 191 | 192 | if (uint32_t lz = leading_zeros(arg1); lz > 0) 193 | { 194 | if (lz == arg1->bitsize) 195 | lz += leading_zeros(arg2); 196 | leading_zeros_map.insert({inst, lz}); 197 | } 198 | 199 | if (uint32_t tz = trailing_zeros(arg2); tz > 0) 200 | { 201 | if (tz == arg2->bitsize) 202 | tz += trailing_zeros(arg1); 203 | trailing_zeros_map.insert({inst, tz}); 204 | } 205 | } 206 | 207 | void Vrp::handle_ite(Inst *inst) 208 | { 209 | Inst *const arg2 = inst->args[1]; 210 | Inst *const arg3 = inst->args[2]; 211 | 212 | uint32_t lz = std::min(leading_zeros(arg2), leading_zeros(arg3)); 213 | uint32_t tz = std::min(trailing_zeros(arg2), trailing_zeros(arg3)); 214 | if (tz > 0) 215 | trailing_zeros_map.insert({inst, tz}); 216 | if (lz > 1) 217 | leading_zeros_map.insert({inst, lz}); 218 | } 219 | 220 | void Vrp::handle_lshr(Inst *inst) 221 | { 222 | Inst *const arg1 = inst->args[0]; 223 | Inst *const arg2 = inst->args[1]; 224 | 225 | uint32_t lz = leading_zeros(arg1); 226 | uint32_t tz = trailing_zeros(arg1); 227 | 228 | if (arg2->op == Op::VALUE) 229 | { 230 | uint32_t shift = arg2->value(); 231 | if (lz + shift > 0) 232 | leading_zeros_map.insert({inst, std::min(lz + shift, inst->bitsize)}); 233 | if (tz > shift) 234 | trailing_zeros_map.insert({inst, tz - shift}); 235 | } 236 | } 237 | 238 | void Vrp::handle_memory(Inst *inst) 239 | { 240 | if (inst->bb->func->module->ptr_offset_low == 0) 241 | trailing_zeros_map.insert({inst, inst->bb->func->module->ptr_offset_bits}); 242 | } 243 | 244 | void Vrp::handle_mov(Inst *inst) 245 | { 246 | Inst *const arg1 = inst->args[0]; 247 | 248 | if (uint32_t tz = trailing_zeros(arg1); tz > 0) 249 | trailing_zeros_map.insert({inst, tz}); 250 | if (uint32_t lz = leading_zeros(arg1); lz > 0) 251 | leading_zeros_map.insert({inst, lz}); 252 | } 253 | 254 | void Vrp::handle_mul(Inst *inst) 255 | { 256 | Inst *const arg1 = inst->args[0]; 257 | Inst *const arg2 = inst->args[1]; 258 | 259 | uint32_t tz = trailing_zeros(arg1) + trailing_zeros(arg2); 260 | if (tz > 0) 261 | trailing_zeros_map.insert({inst, tz}); 262 | } 263 | 264 | void Vrp::handle_neg(Inst *inst) 265 | { 266 | Inst *const arg1 = inst->args[0]; 267 | 268 | if (uint32_t tz = trailing_zeros(arg1); tz > 0) 269 | trailing_zeros_map.insert({inst, tz}); 270 | } 271 | 272 | void Vrp::handle_or(Inst *inst) 273 | { 274 | Inst *const arg1 = inst->args[0]; 275 | Inst *const arg2 = inst->args[1]; 276 | 277 | uint32_t lz = std::min(leading_zeros(arg1), leading_zeros(arg2)); 278 | uint32_t tz = std::min(trailing_zeros(arg1), trailing_zeros(arg2)); 279 | if (tz > 0) 280 | trailing_zeros_map.insert({inst, tz}); 281 | if (lz > 0) 282 | leading_zeros_map.insert({inst, lz}); 283 | } 284 | 285 | void Vrp::handle_phi(Inst *inst) 286 | { 287 | assert(!inst->phi_args.empty()); 288 | bool all_has_lz = true; 289 | bool all_has_tz = true; 290 | for (auto& phi_arg : inst->phi_args) 291 | { 292 | all_has_lz = all_has_lz && leading_zeros(phi_arg.inst) > 0; 293 | all_has_tz = all_has_tz && trailing_zeros(phi_arg.inst) > 0; 294 | } 295 | 296 | if (all_has_lz) 297 | { 298 | uint32_t lz = inst->bitsize; 299 | for (auto& phi_arg : inst->phi_args) 300 | { 301 | lz = std::min(lz, leading_zeros(phi_arg.inst)); 302 | } 303 | if (lz > 0) 304 | leading_zeros_map.insert({inst, lz}); 305 | } 306 | 307 | if (all_has_tz) 308 | { 309 | uint32_t tz = inst->bitsize; 310 | for (auto& phi_arg : inst->phi_args) 311 | { 312 | tz = std::min(tz, trailing_zeros(phi_arg.inst)); 313 | } 314 | if (tz > 0) 315 | trailing_zeros_map.insert({inst, tz}); 316 | } 317 | } 318 | 319 | void Vrp::handle_sadd_wraps(Inst *inst) 320 | { 321 | Inst *const arg1 = inst->args[0]; 322 | Inst *const arg2 = inst->args[1]; 323 | 324 | if (arg1->bitsize <= 128 325 | && leading_zeros(arg1) > 1 326 | && leading_zeros(arg2) > 1) 327 | { 328 | Inst *zero = inst->bb->value_inst(0, 1); 329 | handle_inst(zero); 330 | inst->replace_all_uses_with(zero); 331 | return; 332 | } 333 | } 334 | 335 | void Vrp::handle_sext(Inst *inst) 336 | { 337 | Inst *const arg1 = inst->args[0]; 338 | Inst *const arg2 = inst->args[1]; 339 | 340 | if (leading_zeros(arg1) > 0) 341 | { 342 | Inst *zext = create_inst(Op::ZEXT, arg1, arg2); 343 | zext->insert_before(inst); 344 | inst->replace_all_uses_with(zext); 345 | handle_inst(zext); 346 | return; 347 | } 348 | 349 | if (uint32_t tz = trailing_zeros(arg1); tz > 0) 350 | trailing_zeros_map.insert({inst, tz}); 351 | } 352 | 353 | void Vrp::handle_slt(Inst *inst) 354 | { 355 | Inst *const arg1 = inst->args[0]; 356 | Inst *const arg2 = inst->args[1]; 357 | 358 | // slt x, 0 -> 0 if x has leading zeros. 359 | if (leading_zeros(arg1) > 0 && is_value_zero(arg2)) 360 | { 361 | Inst *zero = inst->bb->value_inst(0, 1); 362 | handle_inst(zero); 363 | inst->replace_all_uses_with(zero); 364 | return; 365 | } 366 | 367 | // slt c, x -> false if c >= the maximum value of x (that is, the value 368 | // where all bits not being leading or trailing zeros are set to 1). 369 | if (arg1->op == Op::VALUE && leading_zeros(arg2) > 0) 370 | { 371 | unsigned __int128 lz_mask = -1; 372 | uint32_t lz_shift = ((128 - arg2->bitsize) + leading_zeros(arg2)); 373 | assert(lz_shift <= 128); 374 | lz_mask = (lz_shift < 128) ? lz_mask >> lz_shift : 0; 375 | 376 | unsigned __int128 tz_mask = -1; 377 | uint32_t tz_shift = trailing_zeros(arg2); 378 | assert(tz_shift <= 128); 379 | tz_mask = (tz_shift < 128) ? tz_mask << tz_shift : 0; 380 | 381 | __int128 max_value = lz_mask & tz_mask; 382 | if (arg1->signed_value() >= max_value) 383 | { 384 | Inst *zero = inst->bb->value_inst(0, 1); 385 | handle_inst(zero); 386 | inst->replace_all_uses_with(zero); 387 | return; 388 | } 389 | } 390 | } 391 | 392 | void Vrp::handle_shl(Inst *inst) 393 | { 394 | Inst *const arg1 = inst->args[0]; 395 | Inst *const arg2 = inst->args[1]; 396 | 397 | uint32_t lz = leading_zeros(arg1); 398 | uint32_t tz = trailing_zeros(arg1); 399 | 400 | if (arg2->op == Op::VALUE) 401 | { 402 | uint32_t shift = arg2->value(); 403 | if (lz > shift) 404 | leading_zeros_map.insert({inst, lz - shift}); 405 | if (tz + shift > 0) 406 | trailing_zeros_map.insert({inst, std::min(tz + shift, inst->bitsize)}); 407 | } 408 | } 409 | 410 | void Vrp::handle_ssub_wraps(Inst *inst) 411 | { 412 | Inst *const arg1 = inst->args[0]; 413 | Inst *const arg2 = inst->args[1]; 414 | 415 | if (leading_zeros(arg1) > 1 && leading_zeros(arg2) > 1) 416 | { 417 | Inst *zero = inst->bb->value_inst(0, 1); 418 | handle_inst(zero); 419 | inst->replace_all_uses_with(zero); 420 | return; 421 | } 422 | } 423 | 424 | void Vrp::handle_ult(Inst *inst) 425 | { 426 | Inst *const arg1 = inst->args[0]; 427 | Inst *const arg2 = inst->args[1]; 428 | 429 | // ult c, x -> false if c >= the maximum value of x (that is, the value 430 | // where all bits not being leading or trailing zeros are set to 1). 431 | if (arg1->op == Op::VALUE) 432 | { 433 | unsigned __int128 lz_mask = -1; 434 | uint32_t lz_shift = (128 - arg2->bitsize) + leading_zeros(arg2); 435 | assert(lz_shift <= 128); 436 | lz_mask = (lz_shift < 128) ? lz_mask >> lz_shift : 0; 437 | 438 | unsigned __int128 tz_mask = -1; 439 | uint32_t tz_shift = trailing_zeros(arg2); 440 | assert(tz_shift <= 128); 441 | tz_mask = (tz_shift < 128) ? tz_mask << tz_shift : 0; 442 | 443 | unsigned __int128 max_value = lz_mask & tz_mask; 444 | if (arg1->value() >= max_value) 445 | { 446 | Inst *zero = inst->bb->value_inst(0, 1); 447 | handle_inst(zero); 448 | inst->replace_all_uses_with(zero); 449 | return; 450 | } 451 | } 452 | } 453 | 454 | void Vrp::handle_value(Inst *inst) 455 | { 456 | uint64_t lz = clz(inst->value()) - (128 - inst->bitsize); 457 | if (lz > 0) 458 | leading_zeros_map.insert({inst, lz}); 459 | uint64_t tz = std::min(ctz(inst->value()), inst->bitsize); 460 | if (tz > 0) 461 | trailing_zeros_map.insert({inst, tz}); 462 | } 463 | 464 | void Vrp::handle_xor(Inst *inst) 465 | { 466 | Inst *const arg1 = inst->args[0]; 467 | Inst *const arg2 = inst->args[1]; 468 | 469 | uint32_t lz = std::min(leading_zeros(arg1), leading_zeros(arg2)); 470 | uint32_t tz = std::min(trailing_zeros(arg1), trailing_zeros(arg2)); 471 | if (tz > 0) 472 | trailing_zeros_map.insert({inst, tz}); 473 | if (lz > 0) 474 | leading_zeros_map.insert({inst, lz}); 475 | } 476 | 477 | void Vrp::handle_zext(Inst *inst) 478 | { 479 | Inst *const arg1 = inst->args[0]; 480 | 481 | if (uint32_t tz = trailing_zeros(arg1); tz > 0) 482 | trailing_zeros_map.insert({inst, tz}); 483 | 484 | uint32_t lz = leading_zeros(arg1) + (inst->bitsize - arg1->bitsize); 485 | leading_zeros_map.insert({inst, lz}); 486 | } 487 | 488 | void Vrp::handle_inst(Inst *inst) 489 | { 490 | if (!inst->has_lhs()) 491 | return; 492 | 493 | switch (inst->op) 494 | { 495 | case Op::ADD: 496 | handle_add(inst); 497 | break; 498 | case Op::AND: 499 | handle_and(inst); 500 | break; 501 | case Op::ASHR: 502 | handle_ashr(inst); 503 | break; 504 | case Op::EXTRACT: 505 | handle_extract(inst); 506 | break; 507 | case Op::CONCAT: 508 | handle_concat(inst); 509 | break; 510 | case Op::ITE: 511 | handle_ite(inst); 512 | break; 513 | case Op::LSHR: 514 | handle_lshr(inst); 515 | break; 516 | case Op::MEMORY: 517 | handle_memory(inst); 518 | break; 519 | case Op::MOV: 520 | handle_mov(inst); 521 | break; 522 | case Op::MUL: 523 | handle_mul(inst); 524 | break; 525 | case Op::NEG: 526 | handle_neg(inst); 527 | break; 528 | case Op::OR: 529 | handle_or(inst); 530 | break; 531 | case Op::PHI: 532 | handle_phi(inst); 533 | break; 534 | case Op::SADD_WRAPS: 535 | handle_sadd_wraps(inst); 536 | break; 537 | case Op::SEXT: 538 | handle_sext(inst); 539 | break; 540 | case Op::SLT: 541 | handle_slt(inst); 542 | break; 543 | case Op::SHL: 544 | handle_shl(inst); 545 | break; 546 | case Op::SSUB_WRAPS: 547 | handle_ssub_wraps(inst); 548 | break; 549 | case Op::ULT: 550 | handle_ult(inst); 551 | break; 552 | case Op::VALUE: 553 | handle_value(inst); 554 | break; 555 | case Op::XOR: 556 | handle_xor(inst); 557 | break; 558 | case Op::ZEXT: 559 | handle_zext(inst); 560 | break; 561 | default: 562 | break; 563 | } 564 | 565 | // Replace the value with 0 if the leading/trailing zeros cover the 566 | // full bitwidth. 567 | if (inst->op != Op::VALUE 568 | && inst->bitsize <= 128 569 | && leading_zeros(inst) + trailing_zeros(inst) >= inst->bitsize) 570 | { 571 | Inst *zero = inst->bb->value_inst(0, inst->bitsize); 572 | handle_inst(zero); 573 | inst->replace_all_uses_with(zero); 574 | } 575 | } 576 | 577 | Vrp::Vrp(Function *func) : func{func} 578 | { 579 | assert(!has_loops(func)); 580 | for (Basic_block *bb : func->bbs) 581 | { 582 | for (auto phi : bb->phis) 583 | { 584 | handle_inst(phi); 585 | } 586 | for (Inst *inst = bb->first_inst; inst; inst = inst->next) 587 | { 588 | handle_inst(inst); 589 | } 590 | } 591 | } 592 | 593 | } // end anonymous namespace 594 | 595 | void vrp(Function *func) 596 | { 597 | Vrp vrp_pass(func); 598 | } 599 | 600 | void vrp(Module *module) 601 | { 602 | for (auto func : module->functions) 603 | vrp(func); 604 | } 605 | 606 | } // end namespace smtgcc 607 | -------------------------------------------------------------------------------- /plugin/aarch64.cpp: -------------------------------------------------------------------------------- 1 | #include "gcc-plugin.h" 2 | #include "tree.h" 3 | 4 | #include 5 | 6 | #include "gimple_conv.h" 7 | 8 | using namespace smtgcc; 9 | 10 | namespace { 11 | 12 | const int stack_size = 1024 * 100; 13 | 14 | void build_return(aarch64_state *rstate, Function *src_func, function *fun) 15 | { 16 | Basic_block *bb = rstate->exit_bb; 17 | tree ret_type = TREE_TYPE(DECL_RESULT(fun->decl)); 18 | 19 | Basic_block *src_last_bb = src_func->bbs.back(); 20 | assert(src_last_bb->last_inst->op == Op::RET); 21 | uint64_t ret_bitsize = 0; 22 | if (src_last_bb->last_inst->nof_args > 0) 23 | ret_bitsize = src_last_bb->last_inst->args[0]->bitsize; 24 | if (ret_bitsize == 0) 25 | { 26 | bb->build_ret_inst(); 27 | return; 28 | } 29 | 30 | if (SCALAR_FLOAT_TYPE_P(ret_type) && ret_bitsize <= rstate->freg_bitsize) 31 | { 32 | Inst *retval = 33 | bb->build_inst(Op::READ, rstate->registers[Aarch64RegIdx::z0]); 34 | if (ret_bitsize < retval->bitsize) 35 | retval = bb->build_trunc(retval, ret_bitsize); 36 | bb->build_ret_inst(retval); 37 | return; 38 | } 39 | 40 | if ((INTEGRAL_TYPE_P(ret_type) || POINTER_TYPE_P(ret_type)) 41 | && ret_bitsize <= rstate->reg_bitsize) 42 | { 43 | Inst *retval = 44 | bb->build_inst(Op::READ, rstate->registers[Aarch64RegIdx::x0]); 45 | if (ret_bitsize < retval->bitsize) 46 | retval = bb->build_trunc(retval, ret_bitsize); 47 | bb->build_ret_inst(retval); 48 | return; 49 | } 50 | 51 | throw Not_implemented("aarch64: Unhandled return type"); 52 | } 53 | 54 | void write_reg(Basic_block *bb, Inst *reg, Inst *value) 55 | { 56 | assert(reg->op == Op::REGISTER); 57 | if (reg->bitsize > value->bitsize) 58 | value = bb->build_inst(Op::ZEXT, value, reg->bitsize); 59 | bb->build_inst(Op::WRITE, reg, value); 60 | } 61 | 62 | } // end anonymous namespace 63 | 64 | aarch64_state setup_aarch64_function(CommonState *state, Function *src_func, function *fun) 65 | { 66 | Module *module = src_func->module; 67 | 68 | aarch64_state rstate; 69 | rstate.reg_bitsize = 64; 70 | rstate.freg_bitsize = 128; 71 | rstate.module = module; 72 | rstate.memory_objects = state->memory_objects; 73 | rstate.func_name = IDENTIFIER_POINTER(DECL_ASSEMBLER_NAME(fun->decl)); 74 | rstate.file_name = DECL_SOURCE_FILE(fun->decl); 75 | 76 | Function *tgt = module->build_function("tgt"); 77 | rstate.entry_bb = tgt->build_bb(); 78 | rstate.exit_bb = tgt->build_bb(); 79 | 80 | assert(module->functions.size() == 2); 81 | Basic_block *bb = rstate.entry_bb; 82 | 83 | // Registers x0-x31. 84 | for (int i = 0; i < 32; i++) 85 | rstate.registers.push_back(bb->build_inst(Op::REGISTER, 64)); 86 | 87 | // Registers z0-z31. 88 | for (int i = 0; i < 32; i++) 89 | rstate.registers.push_back(bb->build_inst(Op::REGISTER, 128)); 90 | 91 | // Registers p0-p16. 92 | for (int i = 0; i < 16; i++) 93 | rstate.registers.push_back(bb->build_inst(Op::REGISTER, 16)); 94 | 95 | // SP 96 | rstate.registers.push_back(bb->build_inst(Op::REGISTER, 64)); 97 | 98 | // Condition flags: N, Z, C, V 99 | for (int i = 0; i < 4; i++) 100 | rstate.registers.push_back(bb->build_inst(Op::REGISTER, 1)); 101 | 102 | // Pseudo condition flags 103 | for (int i = 0; i < 3; i++) 104 | rstate.registers.push_back(bb->build_inst(Op::REGISTER, 1)); 105 | 106 | // Pseudo registers tracking abort/exit 107 | rstate.registers.push_back(bb->build_inst(Op::REGISTER, 1)); 108 | rstate.registers.push_back(bb->build_inst(Op::REGISTER, 1)); 109 | rstate.registers.push_back(bb->build_inst(Op::REGISTER, 32)); 110 | 111 | // Create MEMORY instructions for the global variables we saw in the 112 | // GIMPLE IR. 113 | for (const auto& mem_obj : rstate.memory_objects) 114 | { 115 | Inst *id = bb->value_inst(mem_obj.id, module->ptr_id_bits); 116 | Inst *size = bb->value_inst(mem_obj.size, module->ptr_offset_bits); 117 | Inst *flags = bb->value_inst(mem_obj.flags, 32); 118 | Inst *mem = bb->build_inst(Op::MEMORY, id, size, flags); 119 | rstate.sym_name2mem.insert({mem_obj.sym_name, mem}); 120 | } 121 | 122 | // Set up the stack. 123 | rstate.next_local_id = ((int64_t)-1) << (module->ptr_id_bits - 1); 124 | assert(stack_size < (((uint64_t)1) << module->ptr_offset_bits)); 125 | Inst *id = bb->value_inst(rstate.next_local_id++, module->ptr_id_bits); 126 | Inst *mem_size = bb->value_inst(stack_size, module->ptr_offset_bits); 127 | Inst *flags = bb->value_inst(0, 32); 128 | Inst *stack = bb->build_inst(Op::MEMORY, id, mem_size, flags); 129 | Inst *size = bb->value_inst(stack_size, stack->bitsize); 130 | stack = bb->build_inst(Op::ADD, stack, size); 131 | bb->build_inst(Op::WRITE, rstate.registers[Aarch64RegIdx::sp], stack); 132 | 133 | uint32_t reg_nbr = 0; 134 | uint32_t freg_nbr = 0; 135 | 136 | build_return(&rstate, src_func, fun); 137 | 138 | // Set up the PARAM instructions and copy the result to the correct 139 | // register or memory as required by the ABI. 140 | int param_number = 0; 141 | std::vector stack_values; 142 | for (tree decl = DECL_ARGUMENTS(fun->decl); decl; decl = DECL_CHAIN(decl)) 143 | { 144 | uint32_t bitsize = bitsize_for_type(TREE_TYPE(decl)); 145 | if (bitsize <= 0) 146 | throw Not_implemented("Parameter size == 0"); 147 | 148 | Inst *param_nbr = bb->value_inst(param_number, 32); 149 | Inst *param_bitsize = bb->value_inst(bitsize, 32); 150 | Inst *param = bb->build_inst(Op::PARAM, param_nbr, param_bitsize); 151 | 152 | tree type = TREE_TYPE(decl); 153 | if (param_number == 0 154 | && !strcmp(IDENTIFIER_POINTER(DECL_NAME(fun->decl)), "__ct_base ")) 155 | { 156 | // TODO: The "this" pointer in C++ constructors needs to be handled 157 | // as a special case in the same way as in gimple_conv.cpp when 158 | // setting up the parameters. 159 | throw Not_implemented("setup_aarch64_function: C++ constructors"); 160 | } 161 | 162 | if (SCALAR_FLOAT_TYPE_P(type) && param->bitsize <= rstate.freg_bitsize) 163 | { 164 | if (freg_nbr >= 8) 165 | throw Not_implemented("setup_aarch64_function: too many params"); 166 | write_reg(bb, rstate.registers[Aarch64RegIdx::z0 + freg_nbr], param); 167 | freg_nbr++; 168 | } 169 | else if ((INTEGRAL_TYPE_P(type) || POINTER_TYPE_P(type)) 170 | && param->bitsize <= rstate.reg_bitsize) 171 | { 172 | if (reg_nbr >= 8) 173 | throw Not_implemented("setup_aarch64_function: too many params"); 174 | write_reg(bb, rstate.registers[Aarch64RegIdx::x0 + reg_nbr], param); 175 | reg_nbr++; 176 | } 177 | else 178 | throw Not_implemented("setup_aarch64_function: param type not handled"); 179 | 180 | param_number++; 181 | } 182 | 183 | if (!stack_values.empty()) 184 | { 185 | uint32_t reg_size = rstate.reg_bitsize / 8; 186 | uint32_t size = stack_values.size() * reg_size; 187 | size = (size + 15) & ~15; // Keep the stack 16-bytes aligned. 188 | Inst *size_inst = bb->value_inst(size, rstate.reg_bitsize); 189 | Inst *sp_reg = rstate.registers[2]; 190 | Inst *sp = bb->build_inst(Op::READ, sp_reg); 191 | sp = bb->build_inst(Op::SUB, sp, size_inst); 192 | bb->build_inst(Op::WRITE, sp_reg, sp); 193 | 194 | for (auto value : stack_values) 195 | { 196 | if (!value) 197 | { 198 | Inst *size_inst = bb->value_inst(reg_size, sp->bitsize); 199 | sp = bb->build_inst(Op::ADD, sp, size_inst); 200 | continue; 201 | } 202 | 203 | for (uint32_t i = 0; i < reg_size; i++) 204 | { 205 | Inst *high = bb->value_inst(i * 8 + 7, 32); 206 | Inst *low = bb->value_inst(i * 8, 32); 207 | Inst *byte = bb->build_inst(Op::EXTRACT, value, high, low); 208 | bb->build_inst(Op::STORE, sp, byte); 209 | Inst *one = bb->value_inst(1, sp->bitsize); 210 | sp = bb->build_inst(Op::ADD, sp, one); 211 | } 212 | } 213 | } 214 | 215 | // Functionality for abort/exit. 216 | { 217 | Inst *b0 = bb->value_inst(0, 1); 218 | Inst *zero = bb->value_inst(0, 32); 219 | bb->build_inst(Op::WRITE, rstate.registers[Aarch64RegIdx::abort], b0); 220 | bb->build_inst(Op::WRITE, rstate.registers[Aarch64RegIdx::exit], b0); 221 | bb->build_inst(Op::WRITE, rstate.registers[Aarch64RegIdx::exit_val], zero); 222 | 223 | Inst *abort_cond = 224 | rstate.exit_bb->build_inst(Op::READ, 225 | rstate.registers[Aarch64RegIdx::abort]); 226 | Inst *exit_cond = 227 | rstate.exit_bb->build_inst(Op::READ, 228 | rstate.registers[Aarch64RegIdx::exit]); 229 | Inst *exit_val = 230 | rstate.exit_bb->build_inst(Op::READ, 231 | rstate.registers[Aarch64RegIdx::exit_val]); 232 | rstate.exit_bb->build_inst(Op::EXIT, abort_cond, exit_cond, exit_val); 233 | } 234 | 235 | return rstate; 236 | } 237 | -------------------------------------------------------------------------------- /plugin/gimple_conv.h: -------------------------------------------------------------------------------- 1 | #ifndef SMTGCC_GIMPLE_CONV_H 2 | #define SMTGCC_GIMPLE_CONV_H 3 | 4 | #include 5 | #include 6 | 7 | #include "smtgcc.h" 8 | 9 | enum class Arch { 10 | gimple, 11 | aarch64, 12 | riscv 13 | }; 14 | 15 | struct CommonState { 16 | CommonState(Arch arch = Arch::gimple); 17 | 18 | // ID 0 - reserved for NULL 19 | // 1 - reserved for anonymous memory 20 | int64_t id_local = 0; 21 | int64_t id_global = 2; 22 | std::map decl2id; 23 | 24 | int64_t ptr_id_min; 25 | int64_t ptr_id_max; 26 | 27 | Arch arch; 28 | 29 | std::vector memory_objects; 30 | }; 31 | 32 | smtgcc::Function *process_function(smtgcc::Module *module, CommonState *, function *fun, bool is_tgt_func); 33 | void unroll_and_optimize(smtgcc::Function *func); 34 | void unroll_and_optimize(smtgcc::Module *module); 35 | smtgcc::Module *create_module(Arch arch = Arch::gimple); 36 | uint64_t bitsize_for_type(tree type); 37 | unsigned __int128 get_int_cst_val(tree expr); 38 | 39 | smtgcc::aarch64_state setup_aarch64_function(CommonState *state, smtgcc::Function *src_func, function *fun); 40 | smtgcc::riscv_state setup_riscv_function(CommonState *state, smtgcc::Function *src_func, function *fun); 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /plugin/riscv.cpp: -------------------------------------------------------------------------------- 1 | #include "gcc-plugin.h" 2 | #include "tree.h" 3 | 4 | #include 5 | 6 | #include "gimple_conv.h" 7 | 8 | using namespace smtgcc; 9 | 10 | namespace { 11 | 12 | const int stack_size = 1024 * 100; 13 | 14 | struct Regs 15 | { 16 | Inst *regs[2] = {nullptr, nullptr}; 17 | Inst *fregs[2] = {nullptr, nullptr}; 18 | }; 19 | 20 | Inst *pad_to_freg_size(riscv_state *rstate, Inst *inst) 21 | { 22 | assert(inst->bitsize <= rstate->freg_bitsize); 23 | if (inst->bitsize < rstate->freg_bitsize) 24 | { 25 | uint32_t padding_bitsize = rstate->freg_bitsize - inst->bitsize; 26 | Inst *m1 = inst->bb->value_m1_inst(padding_bitsize); 27 | inst = inst->bb->build_inst(Op::CONCAT, m1, inst); 28 | } 29 | return inst; 30 | } 31 | 32 | Inst *pad_to_reg_size(riscv_state *rstate, Inst *inst, tree type) 33 | { 34 | assert(inst->bitsize <= rstate->reg_bitsize); 35 | if (inst->bitsize < rstate->reg_bitsize) 36 | { 37 | if (INTEGRAL_TYPE_P(type) && TYPE_UNSIGNED(type)) 38 | inst = inst->bb->build_inst(Op::ZEXT, inst, rstate->reg_bitsize); 39 | else 40 | inst = inst->bb->build_inst(Op::SEXT, inst, rstate->reg_bitsize); 41 | } 42 | return inst; 43 | } 44 | 45 | struct struct_elem { 46 | tree fld; 47 | uint64_t bit_offset; 48 | }; 49 | 50 | // Flatten structure fields as described in the hardware floating-point 51 | // calling convention. 52 | // Returns false if the structure cannot be handled by the calling convention 53 | // (and therefore must fall back to the integer calling convention). 54 | bool flatten_struct(riscv_state *rstate, tree struct_type, std::vector& elems, int& nof_r, int& nof_f, uint64_t bitoffset = 0) 55 | { 56 | for (tree fld = TYPE_FIELDS(struct_type); fld; fld = DECL_CHAIN(fld)) 57 | { 58 | if (TREE_CODE(fld) != FIELD_DECL) 59 | continue; 60 | tree type = TREE_TYPE(fld); 61 | uint64_t bitsize = bitsize_for_type(type); 62 | if (bitsize == 0) 63 | continue; 64 | uint64_t off = get_int_cst_val(DECL_FIELD_OFFSET(fld)); 65 | uint64_t bitoff = get_int_cst_val(DECL_FIELD_BIT_OFFSET(fld)); 66 | uint64_t fld_bitoffset = 8 * off + bitoff + bitoffset; 67 | if (SCALAR_FLOAT_TYPE_P(type) && bitsize <= rstate->freg_bitsize) 68 | { 69 | elems.push_back({fld, fld_bitoffset}); 70 | nof_f++; 71 | } 72 | else if (COMPLEX_FLOAT_TYPE_P(type) && 73 | bitsize <= 2 * rstate->freg_bitsize) 74 | { 75 | elems.push_back({fld, fld_bitoffset}); 76 | nof_f += 2; 77 | } 78 | else if (INTEGRAL_TYPE_P(type) && bitsize <= rstate->reg_bitsize) 79 | { 80 | elems.push_back({fld, fld_bitoffset}); 81 | nof_r++; 82 | } 83 | else if (TREE_CODE(type) == RECORD_TYPE) 84 | { 85 | if (!flatten_struct(rstate, type, elems, nof_r, nof_f, fld_bitoffset)) 86 | return false; 87 | } 88 | else 89 | return false; 90 | } 91 | 92 | if (nof_f == 0) 93 | return false; 94 | if (nof_r + nof_f > 2) 95 | return false; 96 | 97 | return true; 98 | } 99 | 100 | // Determines if the structure can be handled by the hardware floating-point 101 | // calling convention, and in that case, splits the structure into 102 | // instructions for each register the structure will be passed in. 103 | std::optional regs_for_fp_struct(riscv_state *rstate, Inst *value, tree struct_type, int nof_available_fpregs) 104 | { 105 | std::vector elems; 106 | int nof_r = 0; 107 | int nof_f = 0; 108 | if (!flatten_struct(rstate, struct_type, elems, nof_r, nof_f)) 109 | return {}; 110 | 111 | Basic_block *bb = value->bb; 112 | Regs regs; 113 | int reg_nbr = 0; 114 | int freg_nbr = 0; 115 | for (auto [fld, fld_bitoffset] : elems) 116 | { 117 | tree type = TREE_TYPE(fld); 118 | uint64_t low_val = fld_bitoffset; 119 | uint64_t high_val = low_val + bitsize_for_type(type) - 1; 120 | Inst *inst = bb->build_inst(Op::EXTRACT, value, high_val, low_val); 121 | if (SCALAR_FLOAT_TYPE_P(type)) 122 | { 123 | assert(freg_nbr < 2); 124 | regs.fregs[freg_nbr++] = pad_to_freg_size(rstate, inst); 125 | } 126 | else if (COMPLEX_FLOAT_TYPE_P(type)) 127 | { 128 | assert(freg_nbr == 0); 129 | uint64_t elt_bitsize = value->bitsize / 2; 130 | assert(elt_bitsize == 16 || elt_bitsize == 32 || elt_bitsize == 64); 131 | Inst *reg_value = bb->build_trunc(value, elt_bitsize); 132 | regs.fregs[freg_nbr++] = pad_to_freg_size(rstate, reg_value); 133 | Inst *high = bb->value_inst(value->bitsize - 1, 32); 134 | Inst *low = bb->value_inst(elt_bitsize, 32); 135 | reg_value = bb->build_inst(Op::EXTRACT, value, high, low); 136 | regs.fregs[freg_nbr++] = pad_to_freg_size(rstate, reg_value); 137 | } 138 | else if (INTEGRAL_TYPE_P(type)) 139 | { 140 | assert(reg_nbr < 2); 141 | regs.regs[reg_nbr++] = pad_to_reg_size(rstate, inst, type); 142 | } 143 | else 144 | assert(0); 145 | } 146 | 147 | if (nof_available_fpregs < freg_nbr) 148 | return {}; 149 | 150 | return regs; 151 | } 152 | 153 | // Determines if the value can be passed in registers, and in that case, 154 | // splits the structure into instructions for each register the structure 155 | // will be passed in. 156 | std::optional regs_for_value(riscv_state *rstate, Inst *value, tree type, int nof_available_fpregs) 157 | { 158 | Basic_block *bb = value->bb; 159 | 160 | // Handle the hardware floating-point calling convention. 161 | if (nof_available_fpregs > 0) 162 | { 163 | if (COMPLEX_FLOAT_TYPE_P(type) 164 | && value->bitsize <= 2 * rstate->freg_bitsize 165 | && nof_available_fpregs > 1) 166 | { 167 | Regs regs; 168 | uint64_t elt_bitsize = value->bitsize / 2; 169 | assert(elt_bitsize == 16 || elt_bitsize == 32 || elt_bitsize == 64); 170 | Inst *reg_value = bb->build_trunc(value, elt_bitsize); 171 | regs.fregs[0] = pad_to_freg_size(rstate, reg_value); 172 | Inst *high = bb->value_inst(value->bitsize - 1, 32); 173 | Inst *low = bb->value_inst(elt_bitsize, 32); 174 | reg_value = bb->build_inst(Op::EXTRACT, value, high, low); 175 | regs.fregs[1] = pad_to_freg_size(rstate, reg_value); 176 | return regs; 177 | } 178 | if (SCALAR_FLOAT_TYPE_P(type) && value->bitsize <= rstate->freg_bitsize) 179 | { 180 | Regs regs; 181 | regs.fregs[0] = pad_to_freg_size(rstate, value); 182 | return regs; 183 | } 184 | if (TREE_CODE(type) == RECORD_TYPE) 185 | { 186 | std::optional res = 187 | regs_for_fp_struct(rstate, value, type, nof_available_fpregs); 188 | if (res) 189 | return res; 190 | } 191 | } 192 | 193 | // Handle the integer calling convention. 194 | if (value->bitsize <= 2 * rstate->reg_bitsize) 195 | { 196 | // Pad it out to a multiple of the register size. 197 | uint32_t num_regs = value->bitsize <= rstate->reg_bitsize ? 1 : 2; 198 | if (value->bitsize < rstate->reg_bitsize * num_regs) 199 | { 200 | bool is_unsigned = INTEGRAL_TYPE_P(type) && TYPE_UNSIGNED(type); 201 | Inst *bs_inst = 202 | bb->value_inst(rstate->reg_bitsize * num_regs, 32); 203 | if (is_unsigned && value->bitsize != 32) 204 | value = bb->build_inst(Op::ZEXT, value, bs_inst); 205 | else 206 | value = bb->build_inst(Op::SEXT, value, bs_inst); 207 | } 208 | 209 | Regs regs; 210 | regs.regs[0] = bb->build_trunc(value, rstate->reg_bitsize); 211 | if (num_regs > 1) 212 | { 213 | Inst *high = bb->value_inst(value->bitsize - 1, 32); 214 | Inst *low = bb->value_inst(rstate->reg_bitsize, 32); 215 | regs.regs[1] = bb->build_inst(Op::EXTRACT, value, high, low); 216 | } 217 | return regs; 218 | } 219 | 220 | return {}; 221 | } 222 | 223 | void build_return(riscv_state *rstate, Function *src_func, function *fun, uint32_t *reg_nbr) 224 | { 225 | Function *tgt = rstate->module->functions[1]; 226 | Basic_block *bb = rstate->exit_bb; 227 | tree ret_type = TREE_TYPE(DECL_RESULT(fun->decl)); 228 | 229 | Basic_block *src_last_bb = src_func->bbs.back(); 230 | assert(src_last_bb->last_inst->op == Op::RET); 231 | uint64_t ret_bitsize = 0; 232 | if (src_last_bb->last_inst->nof_args > 0) 233 | ret_bitsize = src_last_bb->last_inst->args[0]->bitsize; 234 | if (ret_bitsize == 0) 235 | { 236 | bb->build_ret_inst(); 237 | return; 238 | } 239 | 240 | // Handle the hardware floating-point calling convention. 241 | std::vector elems; 242 | int nof_r = 0; 243 | int nof_f = 0; 244 | if (TREE_CODE(ret_type) == RECORD_TYPE 245 | && flatten_struct(rstate, ret_type, elems, nof_r, nof_f)) 246 | { 247 | uint32_t reg_nbr = RiscvRegIdx::x10; 248 | uint32_t freg_nbr = RiscvRegIdx::f10; 249 | Inst *retval = nullptr; 250 | for (auto [fld, fld_bitoffset] : elems) 251 | { 252 | tree type = TREE_TYPE(fld); 253 | uint64_t type_bitsize = bitsize_for_type(type); 254 | Inst *value = nullptr; 255 | if (SCALAR_FLOAT_TYPE_P(type)) 256 | value = bb->build_inst(Op::READ, rstate->registers[freg_nbr++]); 257 | else if (INTEGRAL_TYPE_P(type)) 258 | value = bb->build_inst(Op::READ, rstate->registers[reg_nbr++]); 259 | else if (COMPLEX_FLOAT_TYPE_P(type)) 260 | { 261 | assert(elems.size() == 1); 262 | uint64_t elt_bitsize = type_bitsize / 2; 263 | assert(elt_bitsize == 16 264 | || elt_bitsize == 32 265 | || elt_bitsize == 64); 266 | Inst *real = 267 | bb->build_inst(Op::READ, rstate->registers[RiscvRegIdx::f10]); 268 | real = bb->build_trunc(real, elt_bitsize); 269 | Inst *imag = 270 | bb->build_inst(Op::READ, rstate->registers[RiscvRegIdx::f11]); 271 | imag = bb->build_trunc(imag, elt_bitsize); 272 | value = 273 | bb->build_ret_inst(bb->build_inst(Op::CONCAT, imag, real)); 274 | return; 275 | } 276 | if (fld_bitoffset > 0 && (!retval || retval->bitsize < fld_bitoffset)) 277 | { 278 | uint64_t nof_pad = 279 | retval ? fld_bitoffset - retval->bitsize : fld_bitoffset; 280 | Inst *pad = bb->value_inst(0, nof_pad); 281 | if (retval) 282 | retval = bb->build_inst(Op::CONCAT, pad, retval); 283 | else 284 | retval = pad; 285 | } 286 | value = bb->build_trunc(value, type_bitsize); 287 | if (retval) 288 | retval = bb->build_inst(Op::CONCAT, value, retval); 289 | else 290 | retval = value; 291 | } 292 | if (retval->bitsize != ret_bitsize) 293 | { 294 | Inst *pad = bb->value_inst(0, ret_bitsize - retval->bitsize); 295 | retval = bb->build_inst(Op::CONCAT, pad, retval); 296 | } 297 | bb->build_ret_inst(retval); 298 | return; 299 | } 300 | if (COMPLEX_FLOAT_TYPE_P(ret_type) 301 | && ret_bitsize <= 2 * rstate->freg_bitsize) 302 | { 303 | uint64_t elt_bitsize = ret_bitsize / 2; 304 | assert(elt_bitsize == 16 || elt_bitsize == 32 || elt_bitsize == 64); 305 | Inst *real = 306 | bb->build_inst(Op::READ, rstate->registers[RiscvRegIdx::f10]); 307 | real = bb->build_trunc(real, elt_bitsize); 308 | Inst *imag = 309 | bb->build_inst(Op::READ, rstate->registers[RiscvRegIdx::f11]); 310 | imag = bb->build_trunc(imag, elt_bitsize); 311 | bb->build_ret_inst(bb->build_inst(Op::CONCAT, imag, real)); 312 | return; 313 | } 314 | if (SCALAR_FLOAT_TYPE_P(ret_type) && ret_bitsize <= rstate->freg_bitsize) 315 | { 316 | Inst *retval = 317 | bb->build_inst(Op::READ, rstate->registers[RiscvRegIdx::f10]); 318 | if (ret_bitsize < retval->bitsize) 319 | retval = bb->build_trunc(retval, ret_bitsize); 320 | bb->build_ret_inst(retval); 321 | return; 322 | } 323 | 324 | // Handle the integer calling convention. 325 | if (ret_bitsize <= 2 * rstate->reg_bitsize) 326 | { 327 | Inst *retval = 328 | bb->build_inst(Op::READ, rstate->registers[RiscvRegIdx::x10]); 329 | if (retval->bitsize < ret_bitsize) 330 | { 331 | Inst *inst = 332 | bb->build_inst(Op::READ, rstate->registers[RiscvRegIdx::x11]); 333 | retval = bb->build_inst(Op::CONCAT, inst, retval); 334 | } 335 | if (ret_bitsize < retval->bitsize) 336 | retval = bb->build_trunc(retval, ret_bitsize); 337 | bb->build_ret_inst(retval); 338 | } 339 | else 340 | { 341 | // Return of values wider than 2*reg_bitsize are passed in memory, 342 | // where the address is specified by an implicit first parameter. 343 | assert((ret_bitsize & 7) == 0); 344 | Inst *id = 345 | tgt->value_inst(rstate->next_local_id++, tgt->module->ptr_id_bits); 346 | Inst *mem_size = 347 | tgt->value_inst(ret_bitsize / 8, tgt->module->ptr_offset_bits); 348 | Inst *flags = tgt->value_inst(0, 32); 349 | 350 | Basic_block *entry_bb = rstate->entry_bb; 351 | Inst *ret_mem = 352 | entry_bb->build_inst(Op::MEMORY, id, mem_size, flags); 353 | Inst *reg = rstate->registers[(*reg_nbr)++]; 354 | entry_bb->build_inst(Op::WRITE, reg, ret_mem); 355 | 356 | // Generate the return value from the value returned in memory. 357 | uint64_t size = ret_bitsize / 8; 358 | Inst *retval = 0; 359 | for (uint64_t i = 0; i < size; i++) 360 | { 361 | Inst *offset = bb->value_inst(i, ret_mem->bitsize); 362 | Inst *ptr = bb->build_inst(Op::ADD, ret_mem, offset); 363 | Inst *data_byte = bb->build_inst(Op::LOAD, ptr); 364 | if (retval) 365 | retval = bb->build_inst(Op::CONCAT, data_byte, retval); 366 | else 367 | retval = data_byte; 368 | } 369 | bb->build_ret_inst(retval); 370 | } 371 | } 372 | 373 | } // end anonymous namespace 374 | 375 | riscv_state setup_riscv_function(CommonState *state, Function *src_func, function *fun) 376 | { 377 | Module *module = src_func->module; 378 | 379 | riscv_state rstate; 380 | rstate.reg_bitsize = TARGET_64BIT ? 64 : 32; 381 | rstate.freg_bitsize = 64; 382 | rstate.vreg_bitsize = 128; 383 | rstate.module = module; 384 | rstate.memory_objects = state->memory_objects; 385 | rstate.func_name = IDENTIFIER_POINTER(DECL_ASSEMBLER_NAME(fun->decl)); 386 | rstate.file_name = DECL_SOURCE_FILE(fun->decl); 387 | 388 | Function *tgt = module->build_function("tgt"); 389 | rstate.entry_bb = tgt->build_bb(); 390 | rstate.exit_bb = tgt->build_bb(); 391 | 392 | assert(module->functions.size() == 2); 393 | Basic_block *bb = rstate.entry_bb; 394 | 395 | // Registers x0-x31. 396 | for (int i = 0; i < 32; i++) 397 | { 398 | Inst *reg = bb->build_inst(Op::REGISTER, rstate.reg_bitsize); 399 | rstate.registers.push_back(reg); 400 | } 401 | 402 | // Registers f0-f31. 403 | for (int i = 0; i < 32; i++) 404 | { 405 | Inst *reg = bb->build_inst(Op::REGISTER, rstate.freg_bitsize); 406 | rstate.registers.push_back(reg); 407 | } 408 | 409 | // Registers v0-v31. 410 | for (int i = 0; i < 32; i++) 411 | { 412 | Inst *reg = bb->build_inst(Op::REGISTER, rstate.vreg_bitsize); 413 | rstate.registers.push_back(reg); 414 | } 415 | 416 | // vtype 417 | rstate.registers.push_back(bb->build_inst(Op::REGISTER, 3)); 418 | 419 | // vl 420 | rstate.registers.push_back(bb->build_inst(Op::REGISTER, rstate.reg_bitsize)); 421 | 422 | // Pseudo registers tracking abort/exit 423 | rstate.registers.push_back(bb->build_inst(Op::REGISTER, 1)); 424 | rstate.registers.push_back(bb->build_inst(Op::REGISTER, 1)); 425 | rstate.registers.push_back(bb->build_inst(Op::REGISTER, 32)); 426 | 427 | // Create MEMORY instructions for the global variables we saw in the 428 | // GIMPLE IR. 429 | for (const auto& mem_obj : rstate.memory_objects) 430 | { 431 | Inst *id = bb->value_inst(mem_obj.id, module->ptr_id_bits); 432 | Inst *size = bb->value_inst(mem_obj.size, module->ptr_offset_bits); 433 | Inst *flags = bb->value_inst(mem_obj.flags, 32); 434 | Inst *mem = bb->build_inst(Op::MEMORY, id, size, flags); 435 | rstate.sym_name2mem.insert({mem_obj.sym_name, mem}); 436 | } 437 | 438 | // Set up the stack. 439 | rstate.next_local_id = -128; 440 | assert(stack_size < (((uint64_t)1) << module->ptr_offset_bits)); 441 | Inst *id = bb->value_inst(rstate.next_local_id++, module->ptr_id_bits); 442 | Inst *mem_size = bb->value_inst(stack_size, module->ptr_offset_bits); 443 | Inst *flags = bb->value_inst(0, 32); 444 | Inst *stack = bb->build_inst(Op::MEMORY, id, mem_size, flags); 445 | Inst *size = bb->value_inst(stack_size, stack->bitsize); 446 | stack = bb->build_inst(Op::ADD, stack, size); 447 | bb->build_inst(Op::WRITE, rstate.registers[RiscvRegIdx::x2], stack); 448 | 449 | uint32_t reg_nbr = RiscvRegIdx::x10; 450 | uint32_t freg_nbr = RiscvRegIdx::f10; 451 | 452 | build_return(&rstate, src_func, fun, ®_nbr); 453 | 454 | // Set up the PARAM instructions and copy the result to the correct 455 | // register or memory as required by the ABI. 456 | int param_number = 0; 457 | std::vector stack_values; 458 | for (tree decl = DECL_ARGUMENTS(fun->decl); decl; decl = DECL_CHAIN(decl)) 459 | { 460 | uint32_t bitsize = bitsize_for_type(TREE_TYPE(decl)); 461 | if (bitsize <= 0) 462 | throw Not_implemented("Parameter size == 0"); 463 | 464 | Inst *param_nbr = bb->value_inst(param_number, 32); 465 | Inst *param_bitsize = bb->value_inst(bitsize, 32); 466 | Inst *param = bb->build_inst(Op::PARAM, param_nbr, param_bitsize); 467 | 468 | tree type = TREE_TYPE(decl); 469 | if (param_number == 0 470 | && !strcmp(IDENTIFIER_POINTER(DECL_NAME(fun->decl)), "__ct_base ")) 471 | { 472 | // TODO: The "this" pointer in C++ constructors needs to be handled 473 | // as a special case in the same way as in gimple_conv.cpp when 474 | // setting up the parameters. 475 | throw Not_implemented("setup_riscv_function: C++ constructors"); 476 | } 477 | 478 | std::optional arg_regs = 479 | regs_for_value(&rstate, param, type, RiscvRegIdx::f18 - freg_nbr); 480 | if (arg_regs 481 | && !(TARGET_VECTOR 482 | && VECTOR_TYPE_P(type) 483 | && reg_nbr > RiscvRegIdx::x17)) 484 | { 485 | // Ensure the stack is aligned when writing values wider than 486 | // one register. 487 | if (reg_nbr > RiscvRegIdx::x17 488 | && (*arg_regs).regs[0] 489 | && (*arg_regs).regs[1] 490 | && (stack_values.size() & 1)) 491 | stack_values.push_back(nullptr); 492 | 493 | if ((*arg_regs).regs[0]) 494 | { 495 | if (reg_nbr > RiscvRegIdx::x17) 496 | stack_values.push_back((*arg_regs).regs[0]); 497 | else 498 | { 499 | Inst *reg = rstate.registers[reg_nbr++]; 500 | bb->build_inst(Op::WRITE, reg, (*arg_regs).regs[0]); 501 | } 502 | } 503 | if ((*arg_regs).regs[1]) 504 | { 505 | if (reg_nbr > RiscvRegIdx::x17) 506 | stack_values.push_back((*arg_regs).regs[1]); 507 | else 508 | { 509 | Inst *reg = rstate.registers[reg_nbr++]; 510 | bb->build_inst(Op::WRITE, reg, (*arg_regs).regs[1]); 511 | } 512 | } 513 | 514 | if ((*arg_regs).fregs[0]) 515 | { 516 | assert(freg_nbr < RiscvRegIdx::f18); 517 | Inst *reg = rstate.registers[freg_nbr++]; 518 | bb->build_inst(Op::WRITE, reg, (*arg_regs).fregs[0]); 519 | } 520 | if ((*arg_regs).fregs[1]) 521 | { 522 | assert(freg_nbr < RiscvRegIdx::f18); 523 | Inst *reg = rstate.registers[freg_nbr++]; 524 | bb->build_inst(Op::WRITE, reg, (*arg_regs).fregs[1]); 525 | } 526 | } 527 | else 528 | { 529 | // The argument is passed in memory. 530 | assert(param->bitsize % 8 == 0); 531 | if (rstate.next_local_id == 0) 532 | throw Not_implemented("too many local variables"); 533 | Inst *id = 534 | bb->value_inst(rstate.next_local_id++, module->ptr_id_bits); 535 | uint64_t size = param->bitsize / 8; 536 | Inst *mem_size = bb->value_inst(size, module->ptr_offset_bits); 537 | Inst *flags = bb->value_inst(0, 32); 538 | Inst *ptr = bb->build_inst(Op::MEMORY, id, mem_size, flags); 539 | for (uint64_t i = 0; i < size; i++) 540 | { 541 | Inst *size_inst = bb->value_inst(i, ptr->bitsize); 542 | Inst *addr = bb->build_inst(Op::ADD, ptr, size_inst); 543 | Inst *byte = bb->build_inst(Op::EXTRACT, param, i * 8 + 7, i * 8); 544 | bb->build_inst(Op::STORE, addr, byte); 545 | } 546 | 547 | if (reg_nbr > RiscvRegIdx::x17) 548 | stack_values.push_back(ptr); 549 | else 550 | { 551 | Inst *reg = rstate.registers[reg_nbr++]; 552 | bb->build_inst(Op::WRITE, reg, ptr); 553 | } 554 | } 555 | 556 | param_number++; 557 | } 558 | 559 | if (!stack_values.empty()) 560 | { 561 | uint32_t reg_size = rstate.reg_bitsize / 8; 562 | uint32_t size = stack_values.size() * reg_size; 563 | size = (size + 15) & ~15; // Keep the stack 16-bytes aligned. 564 | Inst *size_inst = bb->value_inst(size, rstate.reg_bitsize); 565 | Inst *sp_reg = rstate.registers[RiscvRegIdx::x2]; 566 | Inst *sp = bb->build_inst(Op::READ, sp_reg); 567 | sp = bb->build_inst(Op::SUB, sp, size_inst); 568 | bb->build_inst(Op::WRITE, sp_reg, sp); 569 | 570 | for (auto value : stack_values) 571 | { 572 | if (!value) 573 | { 574 | Inst *size_inst = bb->value_inst(reg_size, sp->bitsize); 575 | sp = bb->build_inst(Op::ADD, sp, size_inst); 576 | continue; 577 | } 578 | 579 | for (uint32_t i = 0; i < reg_size; i++) 580 | { 581 | Inst *high = bb->value_inst(i * 8 + 7, 32); 582 | Inst *low = bb->value_inst(i * 8, 32); 583 | Inst *byte = bb->build_inst(Op::EXTRACT, value, high, low); 584 | bb->build_inst(Op::STORE, sp, byte); 585 | Inst *one = bb->value_inst(1, sp->bitsize); 586 | sp = bb->build_inst(Op::ADD, sp, one); 587 | } 588 | } 589 | } 590 | 591 | // Functionality for abort/exit. 592 | { 593 | Inst *b0 = bb->value_inst(0, 1); 594 | Inst *zero = bb->value_inst(0, 32); 595 | bb->build_inst(Op::WRITE, rstate.registers[RiscvRegIdx::abort], b0); 596 | bb->build_inst(Op::WRITE, rstate.registers[RiscvRegIdx::exit], b0); 597 | bb->build_inst(Op::WRITE, rstate.registers[RiscvRegIdx::exit_val], zero); 598 | 599 | Inst *abort_cond = 600 | rstate.exit_bb->build_inst(Op::READ, 601 | rstate.registers[RiscvRegIdx::abort]); 602 | Inst *exit_cond = 603 | rstate.exit_bb->build_inst(Op::READ, 604 | rstate.registers[RiscvRegIdx::exit]); 605 | Inst *exit_val = 606 | rstate.exit_bb->build_inst(Op::READ, 607 | rstate.registers[RiscvRegIdx::exit_val]); 608 | rstate.exit_bb->build_inst(Op::EXIT, abort_cond, exit_cond, exit_val); 609 | } 610 | 611 | return rstate; 612 | } 613 | -------------------------------------------------------------------------------- /plugin/smtgcc-check-refine.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "gcc-plugin.h" 4 | #include "plugin-version.h" 5 | #include "tree-pass.h" 6 | #include "context.h" 7 | #include "tree.h" 8 | #include "diagnostic-core.h" 9 | 10 | #include "smtgcc.h" 11 | #include "gimple_conv.h" 12 | 13 | using namespace std::string_literals; 14 | using namespace smtgcc; 15 | 16 | int plugin_is_GPL_compatible; 17 | 18 | const pass_data tv_pass_data = 19 | { 20 | GIMPLE_PASS, 21 | "smtgcc-refine-check", 22 | OPTGROUP_NONE, 23 | TV_NONE, 24 | PROP_gimple_any, 25 | 0, 26 | 0, 27 | 0, 28 | 0 29 | }; 30 | 31 | struct tv_pass : gimple_opt_pass 32 | { 33 | tv_pass(gcc::context *ctx) 34 | : gimple_opt_pass(tv_pass_data, ctx) 35 | { 36 | module = create_module(); 37 | } 38 | unsigned int execute(function *fun) final override; 39 | Module *module; 40 | bool error_has_been_reported = false; 41 | }; 42 | 43 | unsigned int tv_pass::execute(function *fun) 44 | { 45 | if (error_has_been_reported) 46 | return 0; 47 | 48 | try 49 | { 50 | CommonState state; 51 | const char *name = function_name(fun); 52 | if (strcmp(name, "src") && strcmp(name, "tgt")) 53 | { 54 | fprintf(stderr, "Error: function name must be \"src\" or \"tgt\"\n"); 55 | error_has_been_reported = true; 56 | return 0; 57 | } 58 | bool is_tgt_func = !strcmp(name, "tgt"); 59 | 60 | Function *func = process_function(module, &state, fun, is_tgt_func); 61 | unroll_and_optimize(func); 62 | if (module->functions.size() == 2) 63 | { 64 | // TODO: Is canonicalize memory needed? It should obviously be 65 | // the same globals in both. 66 | canonicalize_memory(module); 67 | simplify_mem(module); 68 | ls_elim(module); 69 | simplify_insts(module); 70 | dead_code_elimination(module); 71 | simplify_cfg(module); 72 | 73 | Solver_result result = check_refine(module); 74 | if (result.status != Result_status::correct) 75 | { 76 | assert(result.message); 77 | std::string msg = *result.message; 78 | msg.pop_back(); 79 | inform(DECL_SOURCE_LOCATION(cfun->decl), "%s", msg.c_str()); 80 | } 81 | } 82 | } 83 | catch (Not_implemented& error) 84 | { 85 | fprintf(stderr, "Not implemented: %s\n", error.msg.c_str()); 86 | error_has_been_reported = true; 87 | } 88 | return 0; 89 | } 90 | 91 | int 92 | plugin_init(struct plugin_name_args *plugin_info, 93 | struct plugin_gcc_version *version) 94 | { 95 | if (!plugin_default_version_check(version, &gcc_version)) 96 | return 1; 97 | 98 | const char * const plugin_name = plugin_info->base_name; 99 | struct register_pass_info tv_pass_info; 100 | tv_pass_info.pass = new tv_pass(g); 101 | tv_pass_info.reference_pass_name = "ssa"; 102 | tv_pass_info.ref_pass_instance_number = 1; 103 | tv_pass_info.pos_op = PASS_POS_INSERT_AFTER; 104 | register_callback(plugin_name, PLUGIN_PASS_MANAGER_SETUP, NULL, 105 | &tv_pass_info); 106 | 107 | return 0; 108 | } 109 | -------------------------------------------------------------------------------- /plugin/smtgcc-tv-backend.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "gcc-plugin.h" 4 | #include "plugin-version.h" 5 | #include "tree-pass.h" 6 | #include "context.h" 7 | #include "tree.h" 8 | #include "diagnostic-core.h" 9 | 10 | #include "smtgcc.h" 11 | #include "gimple_conv.h" 12 | 13 | using namespace std::string_literals; 14 | using namespace smtgcc; 15 | 16 | int plugin_is_GPL_compatible; 17 | 18 | const pass_data tv_pass_data = 19 | { 20 | GIMPLE_PASS, 21 | "smtgcc-tv-backend", 22 | OPTGROUP_NONE, 23 | TV_NONE, 24 | PROP_cfg, 25 | 0, 26 | 0, 27 | 0, 28 | 0 29 | }; 30 | 31 | struct tv_pass : gimple_opt_pass 32 | { 33 | tv_pass(gcc::context *ctx) 34 | : gimple_opt_pass(tv_pass_data, ctx) 35 | { 36 | } 37 | unsigned int execute(function *fun) final override; 38 | #ifdef SMTGCC_AARCH64 39 | std::vector functions; 40 | #endif 41 | #ifdef SMTGCC_RISCV 42 | std::vector functions; 43 | #endif 44 | }; 45 | 46 | unsigned int tv_pass::execute(function *fun) 47 | { 48 | try 49 | { 50 | #if defined(SMTGCC_AARCH64) 51 | CommonState state(Arch::aarch64); 52 | Module *module = create_module(Arch::aarch64); 53 | #elif defined(SMTGCC_RISCV) 54 | CommonState state(Arch::riscv); 55 | Module *module = create_module(Arch::riscv); 56 | #endif 57 | Function *src = process_function(module, &state, fun, false); 58 | src->name = "src"; 59 | unroll_and_optimize(src); 60 | 61 | #if defined(SMTGCC_AARCH64) 62 | aarch64_state rstate = setup_aarch64_function(&state, src, fun); 63 | #elif defined(SMTGCC_RISCV) 64 | riscv_state rstate = setup_riscv_function(&state, src, fun); 65 | #endif 66 | functions.push_back(rstate); 67 | } 68 | catch (Not_implemented& error) 69 | { 70 | fprintf(stderr, "Not implemented: %s\n", error.msg.c_str()); 71 | } 72 | return 0; 73 | } 74 | 75 | static Inst *extract_vec_elem(Basic_block *bb, Inst *inst, uint32_t elem_bitsize, uint32_t idx) 76 | { 77 | if (idx == 0 && inst->bitsize == elem_bitsize) 78 | return inst; 79 | assert(inst->bitsize % elem_bitsize == 0); 80 | Inst *high = bb->value_inst(idx * elem_bitsize + elem_bitsize - 1, 32); 81 | Inst *low = bb->value_inst(idx * elem_bitsize, 32); 82 | return bb->build_inst(Op::EXTRACT, inst, high, low); 83 | } 84 | 85 | // Return the element size to use for a vector phi if this is a vector phi. 86 | // For this to be considered a vector phi, all uses must be an Op::EXTRACT 87 | // of a size that divides the phi bitsize, and the extraction must be 88 | // extraced from a correctly aligned position. We then returns the smallest 89 | // size of the extract. 90 | // TODO: We currently skip 8 as this is to agressive when the phi is stored. 91 | // This need a better heuristic. 92 | 93 | // Return the element size to use for a vector phi if this is a vector phi. 94 | // For this to be considered a vector phi, all uses must be an Op::EXTRACT 95 | // of a size that divides the phi bitsize, and the extraction must be 96 | // extracted from a correctly aligned position. We then return the smallest 97 | // size of the extractions. 98 | // TODO: We currently skip element size <= 8 as this is too aggressive when 99 | // the phi is stored. This needs a better heuristic. 100 | static std::optional get_phi_elem_bitsize(Inst *phi) 101 | { 102 | assert(phi->op == Op::PHI); 103 | 104 | // Vectors are at lest 128 bits. 105 | if (phi->bitsize < 128) 106 | return {}; 107 | 108 | uint32_t elem_bitsize = phi->bitsize; 109 | for (auto use : phi->used_by) 110 | { 111 | if (use->op != Op::EXTRACT) 112 | return {}; 113 | if (use->bitsize <= 8) 114 | return {}; 115 | elem_bitsize = std::min(elem_bitsize, use->bitsize); 116 | if (use->args[2]->value() % elem_bitsize != 0) 117 | return {}; 118 | } 119 | 120 | return elem_bitsize; 121 | } 122 | 123 | static Inst *split_phi(Inst *phi, uint64_t elem_bitsize, std::map, std::vector>& cache) 124 | { 125 | assert(phi->op == Op::PHI); 126 | assert(phi->bitsize % elem_bitsize == 0); 127 | if (phi->bitsize == elem_bitsize) 128 | return phi; 129 | Inst *res = nullptr; 130 | uint32_t nof_elem = phi->bitsize / elem_bitsize; 131 | std::vector phis; 132 | phis.reserve(nof_elem); 133 | for (uint64_t i = 0; i < nof_elem; i++) 134 | { 135 | Inst *inst = phi->bb->build_phi_inst(elem_bitsize); 136 | phis.push_back(inst); 137 | if (res) 138 | { 139 | Inst *concat = create_inst(Op::CONCAT, inst, res); 140 | if (res->op == Op::PHI) 141 | { 142 | if (phi->bb->first_inst) 143 | concat->insert_before(phi->bb->first_inst); 144 | else 145 | phi->bb->insert_last(concat); 146 | } 147 | else 148 | concat->insert_after(res); 149 | res = concat; 150 | } 151 | else 152 | res = inst; 153 | } 154 | phi->replace_all_uses_with(res); 155 | 156 | for (auto [arg_inst, arg_bb] : phi->phi_args) 157 | { 158 | std::vector& split = cache[{arg_inst, elem_bitsize}]; 159 | if (split.empty()) 160 | { 161 | for (uint64_t i = 0; i < nof_elem; i++) 162 | { 163 | Inst *inst = 164 | extract_vec_elem(arg_inst->bb, arg_inst, elem_bitsize, i); 165 | split.push_back(inst); 166 | } 167 | } 168 | for (uint64_t i = 0; i < nof_elem; i++) 169 | { 170 | phis[i]->add_phi_arg(split[i], arg_bb); 171 | } 172 | } 173 | 174 | return res; 175 | } 176 | 177 | // Note: This assumes that we do not have any loops. 178 | static void eliminate_registers(Function *func) 179 | { 180 | std::map> bb2reg_values; 181 | 182 | // Collect all registers. This is not completely necessary, but we want 183 | // to iterate over the registers in a consisten order when we create 184 | // phi-nodes etc. and iterating over the maps could change order between 185 | // different runs. 186 | std::vector registers; 187 | for (Inst *inst = func->bbs[0]->first_inst; 188 | inst; 189 | inst = inst->next) 190 | { 191 | if (inst->op == Op::REGISTER) 192 | { 193 | Basic_block *bb = func->bbs[0]; 194 | registers.push_back(inst); 195 | // TODO: Should be an arbitrary value (i.e., a symbolic value) 196 | // instead of 0. 197 | bb2reg_values[bb][inst] = bb->value_inst(0, inst->bitsize); 198 | } 199 | } 200 | 201 | for (Basic_block *bb : func->bbs) 202 | { 203 | std::map& reg_values = 204 | bb2reg_values[bb]; 205 | if (bb->preds.size() == 1) 206 | { 207 | reg_values = bb2reg_values.at(bb->preds[0]); 208 | } 209 | else if (bb->preds.size() > 1) 210 | { 211 | for (auto reg : registers) 212 | { 213 | Inst *phi = bb->build_phi_inst(reg->bitsize); 214 | reg_values[reg] = phi; 215 | for (auto pred_bb : bb->preds) 216 | { 217 | if (!bb2reg_values.at(pred_bb).contains(reg)) 218 | throw Not_implemented("eliminate_registers: Read of uninit register"); 219 | Inst *arg = bb2reg_values.at(pred_bb).at(reg); 220 | phi->add_phi_arg(arg, pred_bb); 221 | } 222 | } 223 | } 224 | 225 | Inst *inst = bb->first_inst; 226 | while (inst) 227 | { 228 | Inst *next_inst = inst->next; 229 | if (inst->op == Op::READ) 230 | { 231 | if (!reg_values.contains(inst->args[0])) 232 | throw Not_implemented("eliminate_registers: Read of uninit register"); 233 | inst->replace_all_uses_with(reg_values.at(inst->args[0])); 234 | destroy_instruction(inst); 235 | } 236 | else if (inst->op == Op::WRITE) 237 | { 238 | reg_values[inst->args[0]] = inst->args[1]; 239 | destroy_instruction(inst); 240 | } 241 | inst = next_inst; 242 | } 243 | } 244 | 245 | for (auto inst : registers) 246 | { 247 | assert(inst->used_by.size() == 0); 248 | destroy_instruction(inst); 249 | } 250 | 251 | for (int i = func->bbs.size() - 1; i >= 0; i--) 252 | { 253 | Basic_block *bb = func->bbs[i]; 254 | std::vector> phis; 255 | for (auto phi : bb->phis) 256 | { 257 | if (std::optional elem_bitsize = get_phi_elem_bitsize(phi)) 258 | phis.push_back({phi, *elem_bitsize}); 259 | } 260 | std::map, std::vector> cache; 261 | for (auto [phi, elem_bitsize] : phis) 262 | split_phi(phi, elem_bitsize, cache); 263 | } 264 | } 265 | 266 | static void finish(void *, void *data) 267 | { 268 | if (seen_error()) 269 | return; 270 | 271 | const char *file_name = getenv("SMTGCC_ASM"); 272 | if (!file_name) 273 | file_name = asm_file_name; 274 | 275 | struct tv_pass *my_pass = (struct tv_pass *)data; 276 | for (auto& state : my_pass->functions) 277 | { 278 | if (config.verbose > 0) 279 | fprintf(stderr, "SMTGCC: Checking %s\n", state.func_name.c_str()); 280 | 281 | try 282 | { 283 | Module *module = state.module; 284 | #if defined(SMTGCC_AARCH64) 285 | Function *func = parse_aarch64(file_name, &state); 286 | #elif defined(SMTGCC_RISCV) 287 | Function *func = parse_riscv(file_name, &state); 288 | #endif 289 | validate(func); 290 | 291 | simplify_cfg(func); 292 | if (loop_unroll(func)) 293 | { 294 | bool cfg_modified; 295 | do 296 | { 297 | simplify_insts(func); 298 | dead_code_elimination(func); 299 | cfg_modified = simplify_cfg(func); 300 | } 301 | while (cfg_modified); 302 | } 303 | 304 | eliminate_registers(func); 305 | validate(func); 306 | 307 | // Simplify the code several times -- this is often necessary 308 | // as instruction simplification enables new CFG simplifications 309 | // that then enable new instruction simplifications. 310 | // This is handled during unrolling for the GIMPLE passes, but 311 | // it does not work here because we must do unrolling before 312 | // eliminating the register instructions. 313 | simplify_insts(func); 314 | dead_code_elimination(func); 315 | simplify_cfg(func); 316 | vrp(func); 317 | bool cfg_modified; 318 | do 319 | { 320 | simplify_insts(func); 321 | dead_code_elimination(func); 322 | cfg_modified = simplify_cfg(func); 323 | } 324 | while (cfg_modified); 325 | 326 | canonicalize_memory(module); 327 | simplify_mem(module); 328 | ls_elim(module); 329 | do 330 | { 331 | simplify_insts(module); 332 | dead_code_elimination(module); 333 | cfg_modified = simplify_cfg(module); 334 | } 335 | while (cfg_modified); 336 | 337 | Solver_result result = check_refine(module); 338 | if (result.status != Result_status::correct) 339 | { 340 | assert(result.message); 341 | std::string msg = *result.message; 342 | msg.pop_back(); 343 | fprintf(stderr, "%s:%s: %s\n", state.file_name.c_str(), 344 | state.func_name.c_str(), msg.c_str()); 345 | } 346 | } 347 | catch (Parse_error error) 348 | { 349 | fprintf(stderr, "%s:%d: Parse error: %s\n", file_name, error.line, 350 | error.msg.c_str()); 351 | } 352 | catch (Not_implemented& error) 353 | { 354 | fprintf(stderr, "Not implemented: %s\n", error.msg.c_str()); 355 | } 356 | } 357 | } 358 | 359 | int 360 | plugin_init(struct plugin_name_args *plugin_info, 361 | struct plugin_gcc_version *version) 362 | { 363 | if (!plugin_default_version_check(version, &gcc_version)) 364 | return 1; 365 | 366 | const char * const plugin_name = plugin_info->base_name; 367 | struct register_pass_info tv_pass_info; 368 | struct tv_pass *my_pass = new tv_pass(g); 369 | tv_pass_info.pass = my_pass; 370 | tv_pass_info.reference_pass_name = "optimized"; 371 | tv_pass_info.ref_pass_instance_number = 1; 372 | tv_pass_info.pos_op = PASS_POS_INSERT_AFTER; 373 | register_callback(plugin_name, PLUGIN_PASS_MANAGER_SETUP, NULL, 374 | &tv_pass_info); 375 | 376 | register_callback(plugin_name, PLUGIN_FINISH, finish, (void*)my_pass); 377 | 378 | return 0; 379 | } 380 | -------------------------------------------------------------------------------- /plugin/smtgcc-tv.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "gcc-plugin.h" 5 | #include "plugin-version.h" 6 | #include "tree-pass.h" 7 | #include "tree.h" 8 | #include "cgraph.h" 9 | #include "tree.h" 10 | #include "diagnostic-core.h" 11 | 12 | #include "smtgcc.h" 13 | #include "gimple_conv.h" 14 | 15 | using namespace std::string_literals; 16 | using namespace smtgcc; 17 | 18 | int plugin_is_GPL_compatible; 19 | 20 | // Function keeping track of the translation validation information for 21 | // a function. 22 | struct tv_function 23 | { 24 | std::string prev_pass_name; 25 | std::string pass_name; 26 | bool in_ssa_form = false; 27 | std::set errors; 28 | Module *module = nullptr; 29 | CommonState *state = nullptr; 30 | 31 | void check(); 32 | void delete_ir(); 33 | }; 34 | 35 | struct my_plugin { 36 | bool has_run_ssa_pass = false; 37 | bool new_functions_are_ssa = false; 38 | 39 | std::map fun2tvfun; 40 | }; 41 | 42 | // Delete the IR (if any) from the previous pass. 43 | void tv_function::delete_ir() 44 | { 45 | if (module) 46 | { 47 | destroy_module(module); 48 | module = nullptr; 49 | } 50 | if (state) 51 | { 52 | delete state; 53 | state = nullptr; 54 | } 55 | prev_pass_name = ""; 56 | } 57 | 58 | static Function *convert_function(Module *module, CommonState *state, std::set& errors, bool is_tgt_func = false) 59 | { 60 | try 61 | { 62 | Function *func = process_function(module, state, cfun, is_tgt_func); 63 | func->rename(is_tgt_func ? "tgt" : "src"); 64 | return func; 65 | } 66 | catch (Not_implemented& error) 67 | { 68 | if (!errors.contains(error.msg)) 69 | { 70 | fprintf(stderr, "Not implemented: %s\n", error.msg.c_str()); 71 | errors.insert(error.msg); 72 | } 73 | } 74 | return nullptr; 75 | } 76 | 77 | void tv_function::check() 78 | { 79 | try 80 | { 81 | unroll_and_optimize(module); 82 | canonicalize_memory(module); 83 | simplify_mem(module); 84 | ls_elim(module); 85 | bool cfg_modified; 86 | do 87 | { 88 | simplify_insts(module); 89 | dead_code_elimination(module); 90 | cfg_modified = simplify_cfg(module); 91 | } 92 | while (cfg_modified); 93 | 94 | validate(module); 95 | 96 | Solver_result result = check_refine(module); 97 | if (result.status != Result_status::correct) 98 | { 99 | assert(result.message); 100 | std::string warning = 101 | prev_pass_name + " -> " + pass_name + ": " + *result.message; 102 | warning.pop_back(); 103 | inform(DECL_SOURCE_LOCATION(cfun->decl), "%s", warning.c_str()); 104 | } 105 | } 106 | catch (Not_implemented& error) 107 | { 108 | if (!errors.contains(error.msg)) 109 | { 110 | fprintf(stderr, "Not implemented: %s\n", error.msg.c_str()); 111 | errors.insert(error.msg); 112 | } 113 | } 114 | } 115 | 116 | static void check(opt_pass *pass, tv_function *tv_fun) 117 | { 118 | Module *next_module = create_module(); 119 | CommonState *next_state = new CommonState(); 120 | Function *next_func = 121 | convert_function(next_module, next_state, tv_fun->errors); 122 | if (!next_func) 123 | { 124 | destroy_module(next_module); 125 | delete next_state; 126 | tv_fun->delete_ir(); 127 | tv_fun->pass_name = pass->name; 128 | return; 129 | } 130 | 131 | if (tv_fun->module) 132 | { 133 | tv_fun->module->canonicalize(); 134 | next_module->canonicalize(); 135 | if (!identical(tv_fun->module->functions[0], next_module->functions[0])) 136 | { 137 | if (config.verbose > 0) 138 | fprintf(stderr, "SMTGCC: Checking %s -> %s : %s\n", 139 | tv_fun->prev_pass_name.c_str(), tv_fun->pass_name.c_str(), 140 | function_name(cfun)); 141 | 142 | Function *func = convert_function(tv_fun->module, tv_fun->state, 143 | tv_fun->errors, true); 144 | if (!func) 145 | { 146 | destroy_module(next_module); 147 | delete next_state; 148 | tv_fun->delete_ir(); 149 | tv_fun->pass_name = pass->name; 150 | return; 151 | } 152 | tv_fun->check(); 153 | } 154 | tv_fun->delete_ir(); 155 | } 156 | 157 | tv_fun->module = next_module; 158 | tv_fun->state = next_state; 159 | tv_fun->prev_pass_name = tv_fun->pass_name; 160 | tv_fun->pass_name = pass->name; 161 | } 162 | 163 | static void ipa_pass(opt_pass *pass, my_plugin *plugin_data) 164 | { 165 | if (plugin_data->has_run_ssa_pass) 166 | plugin_data->new_functions_are_ssa = true; 167 | 168 | struct cgraph_node *node; 169 | FOR_EACH_FUNCTION_WITH_GIMPLE_BODY(node) 170 | { 171 | function *fun = node->get_fun(); 172 | if (!plugin_data->fun2tvfun.contains(DECL_UID(fun->decl))) 173 | continue; 174 | tv_function *tv_fun = plugin_data->fun2tvfun.at(DECL_UID(fun->decl)); 175 | if (!tv_fun->in_ssa_form) 176 | continue; 177 | if (!tv_fun->module) 178 | continue; 179 | 180 | push_cfun(fun); 181 | check(pass, tv_fun); 182 | tv_fun->delete_ir(); 183 | pop_cfun(); 184 | } 185 | } 186 | 187 | static void gimple_pass(opt_pass *pass, my_plugin *plugin_data) 188 | { 189 | tv_function *tv_fun; 190 | if (!plugin_data->fun2tvfun.contains(DECL_UID(cfun->decl))) 191 | { 192 | tv_fun = new tv_function; 193 | plugin_data->fun2tvfun[DECL_UID(cfun->decl)] = tv_fun; 194 | if (plugin_data->new_functions_are_ssa) 195 | tv_fun->in_ssa_form = true; 196 | } 197 | else 198 | tv_fun = plugin_data->fun2tvfun.at(DECL_UID(cfun->decl)); 199 | 200 | if (tv_fun->pass_name == "ssa") 201 | { 202 | plugin_data->has_run_ssa_pass = true; 203 | tv_fun->in_ssa_form = true; 204 | } 205 | 206 | if (!tv_fun->in_ssa_form) 207 | { 208 | tv_fun->delete_ir(); 209 | tv_fun->pass_name = pass->name; 210 | return; 211 | } 212 | 213 | if (tv_fun->module && tv_fun->pass_name == "vect") 214 | { 215 | // The vectorizer modifies a copy of the scalar loop in-place 216 | // and relies on dce to remove unused calculations. Some of the 217 | // unused instruction may start to overflow from the vectorization 218 | // (see PR 111257), so we must wait for the following dce pass 219 | // before checking the IR. 220 | tv_fun->pass_name = pass->name; 221 | return; 222 | } 223 | 224 | check(pass, tv_fun); 225 | } 226 | 227 | static void pass_execution(void *event_data, void *data) 228 | { 229 | opt_pass *pass = (opt_pass *)event_data; 230 | my_plugin *plugin_data = (my_plugin *)data; 231 | 232 | if (pass->name[0] == '*') 233 | return; 234 | 235 | if (pass->type == GIMPLE_PASS) 236 | gimple_pass(pass, plugin_data); 237 | else if (pass->type == IPA_PASS || pass->type == SIMPLE_IPA_PASS) 238 | ipa_pass(pass, plugin_data); 239 | } 240 | 241 | int 242 | plugin_init(struct plugin_name_args *plugin_info, 243 | struct plugin_gcc_version *version) 244 | { 245 | if (!plugin_default_version_check(version, &gcc_version)) 246 | return 1; 247 | 248 | const char * const plugin_name = plugin_info->base_name; 249 | my_plugin *mp = new my_plugin; 250 | register_callback(plugin_name, PLUGIN_PASS_EXECUTION, pass_execution, (void*)mp); 251 | 252 | return 0; 253 | } 254 | -------------------------------------------------------------------------------- /tools/smtgcc-check-refine.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "smtgcc.h" 4 | 5 | using namespace smtgcc; 6 | 7 | int main(int argc, char **argv) 8 | { 9 | if (argc != 2) 10 | { 11 | fprintf(stderr, "Error: argc == %d\n", argc); 12 | exit(1); 13 | } 14 | 15 | try { 16 | Module *module = parse_ir(argv[1]); 17 | 18 | if (module->functions.size() != 2) 19 | { 20 | fprintf(stderr, "Error: Nof functions != 2\n"); 21 | exit(1); 22 | } 23 | Function *src = module->functions[0]; 24 | Function *tgt = module->functions[1]; 25 | if (src->name != "src") 26 | std::swap(src, tgt); 27 | if (src->name != "src" || tgt->name != "tgt") 28 | { 29 | fprintf(stderr, "Error: The function names must be src and tgt.\n"); 30 | exit(1); 31 | } 32 | 33 | Solver_result result = check_refine(module); 34 | if (result.status != Result_status::correct) 35 | { 36 | assert(result.message); 37 | fprintf(stderr, "%s", (*result.message).c_str()); 38 | } 39 | 40 | destroy_module(module); 41 | } 42 | catch (Parse_error error) 43 | { 44 | fprintf(stderr, "Parse error: line %d: %s\n", 45 | error.line, error.msg.c_str()); 46 | } 47 | catch (Not_implemented error) 48 | { 49 | fprintf(stderr, "Not implemented: %s\n", error.msg.c_str()); 50 | } 51 | 52 | return 0; 53 | } 54 | -------------------------------------------------------------------------------- /tools/smtgcc-check-ub.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "smtgcc.h" 4 | 5 | using namespace smtgcc; 6 | 7 | int main(int argc, char **argv) 8 | { 9 | if (argc != 2) 10 | { 11 | fprintf(stderr, "Error: argc == %d\n", argc); 12 | exit(1); 13 | } 14 | 15 | try { 16 | Module *module = parse_ir(argv[1]); 17 | 18 | for (auto func : module->functions) 19 | { 20 | Solver_result result = check_ub(func); 21 | if (result.status != Result_status::correct) 22 | { 23 | assert(result.message); 24 | fprintf(stderr, "%s: %s", func->name.c_str(), 25 | (*result.message).c_str()); 26 | } 27 | } 28 | 29 | destroy_module(module); 30 | } 31 | catch (Parse_error error) 32 | { 33 | fprintf(stderr, "Parse error: line %d: %s\n", 34 | error.line, error.msg.c_str()); 35 | } 36 | catch (Not_implemented error) 37 | { 38 | fprintf(stderr, "Not implemented: %s\n", error.msg.c_str()); 39 | } 40 | 41 | return 0; 42 | } 43 | -------------------------------------------------------------------------------- /tools/smtgcc-opt.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "smtgcc.h" 7 | 8 | using namespace smtgcc; 9 | 10 | std::vector opts; 11 | 12 | void print_help(FILE *f) 13 | { 14 | const char* help_message = R"( 15 | Usage: smtgcc-opt [OPTION]... [FILE] 16 | Perform optimizations on the specified FILE. 17 | 18 | Options: 19 | -h, --help Display this help message and exit. 20 | -c Check that the optimizations are correct. 21 | -simplify_inst Run instruction simplification optimization. 22 | -simplify_cfg Run control flow graph simplification optimization. 23 | -simplify_mem Run memory simplification optimization. 24 | -dce Run dead code elimination optimization. 25 | -vrp Run value range propagation optimization. 26 | -loop_unroll Run loop unrolling optimization. 27 | -ls_elim Run load/store elimination optimization. 28 | -canonicalize_memory Harmonize memory between src and tgt. 29 | -convert Run the conversion transformation pass. 30 | 31 | Examples: 32 | smtgcc-opt -simplify_inst example.ir 33 | Run the instruction simplification optimization on 'example.ir'. 34 | 35 | smtgcc-opt -c -simplify_inst -dce example.ir 36 | Run instructions simplification and dead code elimination optimizations 37 | on 'example.ir' and check the result of each pass. 38 | )"; 39 | 40 | fprintf(f, "%s", help_message); 41 | } 42 | 43 | void check(Module *orig_module, Module *module, const char *opt) 44 | { 45 | size_t nof_funcs = module->functions.size(); 46 | assert(orig_module->functions.size() == nof_funcs); 47 | for (size_t i = 0; i < nof_funcs; i++) 48 | { 49 | if (has_loops(orig_module->functions[i])) 50 | { 51 | fprintf(stderr, "warning: " 52 | "%s(%s): Functions with loops cannot be checked with -c\n", 53 | opt, orig_module->functions[i]->name.c_str()); 54 | return; 55 | } 56 | 57 | assert(orig_module->functions[i]->name == module->functions[i]->name); 58 | Module *m = create_module(module->ptr_bits, module->ptr_id_bits, 59 | module->ptr_offset_bits); 60 | Function *src_func = orig_module->functions[i]->clone(m); 61 | src_func->rename("src"); 62 | Function *tgt_func = module->functions[i]->clone(m); 63 | tgt_func->rename("tgt"); 64 | 65 | Solver_result result = check_refine(m, false); 66 | if (result.status != Result_status::correct) 67 | { 68 | assert(result.message); 69 | module->functions[i]->print(stderr); 70 | fprintf(stderr, "Error: %s(%s): %s", opt, 71 | module->functions[i]->name.c_str(), 72 | (*result.message).c_str()); 73 | exit(1); 74 | } 75 | 76 | destroy_module(m); 77 | } 78 | } 79 | 80 | int main(int argc, char **argv) 81 | { 82 | const char *file_name = nullptr; 83 | bool flag_c = false; 84 | 85 | for (int i = 1; i < argc; i++) 86 | { 87 | const char *arg = argv[i]; 88 | if (arg[0] != '-') 89 | { 90 | if (i != argc - 1) 91 | { 92 | print_help(stderr); 93 | exit(1); 94 | } 95 | file_name = arg; 96 | } 97 | else if (!strcmp(arg, "-h") || !strcmp(arg, "--help")) 98 | { 99 | print_help(stdout); 100 | exit(0); 101 | } 102 | else if (!strcmp(arg, "-c")) 103 | flag_c = true; 104 | else if (!strcmp(arg, "-simplify_inst") 105 | || !strcmp(arg, "-simplify_cfg") 106 | || !strcmp(arg, "-simplify_mem") 107 | || !strcmp(arg, "-dce") 108 | || !strcmp(arg, "-vrp") 109 | || !strcmp(arg, "-loop_unroll") 110 | || !strcmp(arg, "-ls_elim") 111 | || !strcmp(arg, "-canonicalize_memory") 112 | || !strcmp(arg, "-convert")) 113 | opts.push_back(arg); 114 | else 115 | { 116 | print_help(stderr); 117 | exit(1); 118 | } 119 | } 120 | 121 | if (!file_name) 122 | { 123 | print_help(stderr); 124 | exit(1); 125 | } 126 | 127 | try { 128 | Module *module = parse_ir(file_name); 129 | 130 | for (const std::string& opt : opts) 131 | { 132 | Module *orig_module = nullptr; 133 | if (flag_c) 134 | { 135 | if (opt == "-convert") 136 | fprintf(stderr, "warning: -convert cannot be checked with -c\n"); 137 | else 138 | orig_module = module->clone(); 139 | } 140 | 141 | if (opt == "-simplify_inst") 142 | simplify_insts(module); 143 | else if (opt == "-simplify_cfg") 144 | simplify_cfg(module); 145 | else if (opt == "-simplify_mem") 146 | simplify_mem(module); 147 | else if (opt == "-dce") 148 | dead_code_elimination(module); 149 | else if (opt == "-vrp") 150 | vrp(module); 151 | else if (opt == "-loop_unroll") 152 | loop_unroll(module); 153 | else if (opt == "-ls_elim") 154 | ls_elim(module); 155 | else if (opt == "-canonicalize_memory") 156 | canonicalize_memory(module); 157 | else if (opt == "-convert") 158 | { 159 | for (auto func : module->functions) 160 | { 161 | if (has_loops(func)) 162 | { 163 | fprintf(stderr, "error: " 164 | "-convert cannot transform functions with loops\n"); 165 | exit(1); 166 | } 167 | } 168 | convert(module); 169 | } 170 | 171 | if (orig_module) 172 | { 173 | check(orig_module, module, opt.c_str()); 174 | destroy_module(orig_module); 175 | orig_module = nullptr; 176 | } 177 | } 178 | 179 | module->canonicalize(); 180 | module->print(stdout); 181 | 182 | destroy_module(module); 183 | } 184 | catch (Parse_error error) 185 | { 186 | fprintf(stderr, "%s:%d: Parse error: %s\n", file_name, error.line, 187 | error.msg.c_str()); 188 | } 189 | catch (Not_implemented error) 190 | { 191 | fprintf(stderr, "Not implemented: %s\n", error.msg.c_str()); 192 | } 193 | 194 | return 0; 195 | } 196 | --------------------------------------------------------------------------------