├── .gitignore ├── .gitmodules ├── EqSat ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── Minimization │ └── TruthTables │ │ ├── 1variable_truthtable.txt │ │ ├── 2variable_truthtable.bc │ │ ├── 2variable_truthtable.txt │ │ ├── 3variable_truthtable.bc │ │ ├── 3variable_truthtable.txt │ │ ├── 4variable_truthtable.bc │ │ └── 4variable_truthtable.txt ├── egraph │ ├── Cargo.lock │ ├── Cargo.toml │ ├── src │ │ ├── abstract_interpretation.rs │ │ ├── analysis.rs │ │ ├── classification.rs │ │ ├── cost.rs │ │ ├── expr.rs │ │ ├── ffi_utils.rs │ │ ├── main.rs │ │ └── rules │ │ │ ├── bitwise_power_of_two.rs │ │ │ ├── duplicate_children_mul_add.rs │ │ │ ├── factor_integer_gcd.rs │ │ │ ├── mod.rs │ │ │ └── rewrite_power.rs │ ├── target │ │ ├── .rustc_info.json │ │ └── CACHEDIR.TAG │ └── test-input.txt ├── src │ ├── isle │ │ └── mba.isle │ ├── main.rs │ ├── mba.rs │ ├── simple_ast.rs │ └── truth_table_database.rs └── test-input.txt ├── LICENSE.txt ├── Mba.Simplifier ├── Bindings │ ├── AstCtx.cs │ ├── AstIdx.cs │ ├── EqualitySaturation.cs │ └── TruthTableDb.cs ├── LinEq │ ├── LinearEquation.cs │ ├── LinearEquationSolver.cs │ ├── LinearSystem.cs │ └── PolyInverter.cs ├── Mba.Simplifier.csproj ├── Minimization │ ├── AnfMinimizer.cs │ ├── BooleanMinimizer.cs │ ├── EspressoMinimizer.cs │ ├── GroebnerBasis.cs │ ├── GroebnerMinimizer.cs │ ├── TableDatabase.cs │ ├── TruthTable.cs │ ├── TruthTableEncoder.cs │ └── TruthTables │ │ ├── 1variable_truthtable.txt │ │ ├── 2variable_truthtable.bc │ │ ├── 2variable_truthtable.txt │ │ ├── 3variable_truthtable.bc │ │ ├── 3variable_truthtable.txt │ │ ├── 4variable_truthtable.bc │ │ └── 4variable_truthtable.txt ├── Pipeline │ ├── DecisionTable.cs │ ├── GeneralSimplifier.cs │ ├── Intermediate │ │ └── IntermediatePoly.cs │ ├── LinearCongruenceSolver.cs │ ├── LinearSimplifier.cs │ └── MixedPolynomialSimplifier.cs ├── Polynomial │ ├── DensePolynomial.cs │ ├── Monomial.cs │ ├── PolynomialEvaluator.cs │ ├── PolynomialReducer.cs │ ├── PowerCollections │ │ ├── Algorithms.cs │ │ ├── Bag.cs │ │ ├── CollectionBase.cs │ │ ├── Comparers.cs │ │ ├── Hash.cs │ │ ├── ListBase.cs │ │ ├── OrderedBag.cs │ │ ├── Pair.cs │ │ ├── ReadOnlyCollectionBase.cs │ │ ├── ReadOnlyListBase.cs │ │ ├── RedBlack.cs │ │ ├── Set.cs │ │ ├── Strings.cs │ │ └── Util.cs │ └── SparsePolynomial.cs ├── Properties │ └── launchSettings.json └── Utility │ ├── AstRewriter.cs │ ├── RustAstParser.cs │ └── Z3Translator.cs ├── README.md ├── Simplifier.sln └── Simplifier ├── Datasets ├── msimba.txt ├── multivariate64.txt ├── permutation64.txt └── univariate64.txt ├── Program.cs ├── Properties └── launchSettings.json └── Simplifier.csproj /.gitignore: -------------------------------------------------------------------------------- 1 | # The following command works for downloading when using Git for Windows: 2 | # curl -LOf http://gist.githubusercontent.com/kmorcinek/2710267/raw/.gitignore 3 | # 4 | # Download this file using PowerShell v3 under Windows with the following comand: 5 | # Invoke-WebRequest https://gist.githubusercontent.com/kmorcinek/2710267/raw/ -OutFile .gitignore 6 | # 7 | # or wget: 8 | # wget --no-check-certificate http://gist.githubusercontent.com/kmorcinek/2710267/raw/.gitignore 9 | 10 | # User-specific files 11 | *.suo 12 | *.user 13 | *.sln.docstates 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Rr]elease/ 18 | x64/ 19 | [Bb]in/ 20 | [Oo]bj/ 21 | # build folder is nowadays used for build scripts and should not be ignored 22 | #build/ 23 | 24 | # NuGet Packages 25 | *.nupkg 26 | # The packages folder can be ignored because of Package Restore 27 | **/packages/* 28 | # except build/, which is used as an MSBuild target. 29 | !**/packages/build/ 30 | # Uncomment if necessary however generally it will be regenerated when needed 31 | #!**/packages/repositories.config 32 | 33 | # MSTest test Results 34 | [Tt]est[Rr]esult*/ 35 | [Bb]uild[Ll]og.* 36 | 37 | # Pre-built LLVM binaries. 38 | llvm-15.0.3-win64/ 39 | 40 | *_i.c 41 | *_p.c 42 | *.ilk 43 | *.meta 44 | *.obj 45 | *.pch 46 | *.pdb 47 | *.pgc 48 | *.pgd 49 | *.rsp 50 | *.sbr 51 | *.tlb 52 | *.tli 53 | *.tlh 54 | *.tmp 55 | *.tmp_proj 56 | *.log 57 | *.vspscc 58 | *.vssscc 59 | .builds 60 | *.pidb 61 | *.log 62 | *.scc 63 | *.exe 64 | *.bin 65 | *.pdb 66 | 67 | # OS generated files # 68 | .DS_Store* 69 | Icon? 70 | 71 | # Visual C++ cache files 72 | ipch/ 73 | *.aps 74 | *.ncb 75 | *.opensdf 76 | *.sdf 77 | *.cachefile 78 | 79 | # Visual Studio profiler 80 | *.psess 81 | *.vsp 82 | *.vspx 83 | 84 | # Guidance Automation Toolkit 85 | *.gpState 86 | 87 | # ReSharper is a .NET coding add-in 88 | _ReSharper*/ 89 | *.[Rr]e[Ss]harper 90 | 91 | # TeamCity is a build add-in 92 | _TeamCity* 93 | 94 | # DotCover is a Code Coverage Tool 95 | *.dotCover 96 | 97 | # NCrunch 98 | *.ncrunch* 99 | .*crunch*.local.xml 100 | 101 | # Installshield output folder 102 | [Ee]xpress/ 103 | 104 | # DocProject is a documentation generator add-in 105 | DocProject/buildhelp/ 106 | DocProject/Help/*.HxT 107 | DocProject/Help/*.HxC 108 | DocProject/Help/*.hhc 109 | DocProject/Help/*.hhk 110 | DocProject/Help/*.hhp 111 | DocProject/Help/Html2 112 | DocProject/Help/html 113 | 114 | # Click-Once directory 115 | publish/ 116 | 117 | # Publish Web Output 118 | *.Publish.xml 119 | 120 | # Windows Azure Build Output 121 | csx 122 | *.build.csdef 123 | 124 | # Windows Store app package directory 125 | AppPackages/ 126 | 127 | # Others 128 | *.Cache 129 | ClientBin/ 130 | [Ss]tyle[Cc]op.* 131 | ~$* 132 | *~ 133 | *.dbmdl 134 | *.[Pp]ublish.xml 135 | *.pfx 136 | *.publishsettings 137 | modulesbin/ 138 | tempbin/ 139 | 140 | # EPiServer Site file (VPP) 141 | AppData/ 142 | 143 | # RIA/Silverlight projects 144 | Generated_Code/ 145 | 146 | # Backup & report files from converting an old project file to a newer 147 | # Visual Studio version. Backup files are not needed, because we have git ;-) 148 | _UpgradeReport_Files/ 149 | Backup*/ 150 | UpgradeLog*.XML 151 | UpgradeLog*.htm 152 | 153 | # vim 154 | *.txt~ 155 | *.swp 156 | *.swo 157 | 158 | # Temp files when opening LibreOffice on ubuntu 159 | .~lock.* 160 | 161 | # svn 162 | .svn 163 | 164 | # CVS - Source Control 165 | **/CVS/ 166 | 167 | # Remainings from resolving conflicts in Source Control 168 | *.orig 169 | 170 | # SQL Server files 171 | **/App_Data/*.mdf 172 | **/App_Data/*.ldf 173 | **/App_Data/*.sdf 174 | 175 | 176 | #LightSwitch generated files 177 | GeneratedArtifacts/ 178 | _Pvt_Extensions/ 179 | ModelManifest.xml 180 | 181 | # ========================= 182 | # Windows detritus 183 | # ========================= 184 | 185 | # Windows image file caches 186 | Thumbs.db 187 | ehthumbs.db 188 | 189 | # Folder config file 190 | Desktop.ini 191 | 192 | # Recycle Bin used on file shares 193 | $RECYCLE.BIN/ 194 | 195 | # Mac desktop service store files 196 | .DS_Store 197 | 198 | # SASS Compiler cache 199 | .sass-cache 200 | 201 | # Visual Studio 2014 CTP 202 | **/*.sln.ide 203 | 204 | # Visual Studio temp something 205 | .vs/ 206 | 207 | # dotnet stuff 208 | project.lock.json 209 | 210 | # VS 2015+ 211 | *.vc.vc.opendb 212 | *.vc.db 213 | 214 | # Rider 215 | .idea/ 216 | 217 | # Visual Studio Code 218 | .vscode/ 219 | 220 | # Output folder used by Webpack or other FE stuff 221 | **/node_modules/* 222 | **/wwwroot/* 223 | 224 | # SpecFlow specific 225 | *.feature.cs 226 | *.feature.xlsx.* 227 | *.Specs_*.html 228 | 229 | ##### 230 | # End of core ignore list, below put you custom 'per project' settings (patterns or path) 231 | ##### 232 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "MSiMBA"] 2 | path = MSiMBA 3 | url = https://github.com/mazeworks-security/MSiMBA.git 4 | -------------------------------------------------------------------------------- /EqSat/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /EqSat/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "ahash" 7 | version = "0.7.8" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" 10 | dependencies = [ 11 | "getrandom", 12 | "once_cell", 13 | "version_check", 14 | ] 15 | 16 | [[package]] 17 | name = "ahash" 18 | version = "0.8.11" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" 21 | dependencies = [ 22 | "cfg-if", 23 | "getrandom", 24 | "once_cell", 25 | "version_check", 26 | "zerocopy", 27 | ] 28 | 29 | [[package]] 30 | name = "autocfg" 31 | version = "1.1.0" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 34 | 35 | [[package]] 36 | name = "byteorder" 37 | version = "1.5.0" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 40 | 41 | [[package]] 42 | name = "cc" 43 | version = "1.0.90" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" 46 | 47 | [[package]] 48 | name = "cfg-if" 49 | version = "1.0.0" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 52 | 53 | [[package]] 54 | name = "cranelift-isle" 55 | version = "0.102.1" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "ae769b235f6ea2f86623a3ff157cc04a4ff131dc9fe782c2ebd35f272043581e" 58 | 59 | [[package]] 60 | name = "egg" 61 | version = "0.9.5" 62 | source = "git+https://github.com/egraphs-good/egg.git#c7e928499799198f5ec6327c08fdd949470d064a" 63 | dependencies = [ 64 | "env_logger", 65 | "fxhash", 66 | "hashbrown", 67 | "indexmap", 68 | "instant", 69 | "log", 70 | "saturating", 71 | "smallvec", 72 | "symbol_table", 73 | "symbolic_expressions", 74 | "thiserror", 75 | ] 76 | 77 | [[package]] 78 | name = "egraph" 79 | version = "0.1.0" 80 | dependencies = [ 81 | "egg", 82 | "libc", 83 | "rand", 84 | ] 85 | 86 | [[package]] 87 | name = "env_logger" 88 | version = "0.9.3" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" 91 | dependencies = [ 92 | "log", 93 | ] 94 | 95 | [[package]] 96 | name = "eq-sat" 97 | version = "0.1.0" 98 | dependencies = [ 99 | "ahash 0.8.11", 100 | "cranelift-isle", 101 | "egg", 102 | "egraph", 103 | "foldhash", 104 | "libc", 105 | "mimalloc", 106 | "rand", 107 | ] 108 | 109 | [[package]] 110 | name = "foldhash" 111 | version = "0.1.0" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "4deb59dd6330afa472c000b86c0c9ada26274836eb59563506c3e34e4bb9a819" 114 | 115 | [[package]] 116 | name = "fxhash" 117 | version = "0.2.1" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" 120 | dependencies = [ 121 | "byteorder", 122 | ] 123 | 124 | [[package]] 125 | name = "getrandom" 126 | version = "0.2.12" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" 129 | dependencies = [ 130 | "cfg-if", 131 | "libc", 132 | "wasi", 133 | ] 134 | 135 | [[package]] 136 | name = "hashbrown" 137 | version = "0.12.3" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 140 | dependencies = [ 141 | "ahash 0.7.8", 142 | ] 143 | 144 | [[package]] 145 | name = "indexmap" 146 | version = "1.9.3" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 149 | dependencies = [ 150 | "autocfg", 151 | "hashbrown", 152 | ] 153 | 154 | [[package]] 155 | name = "instant" 156 | version = "0.1.12" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 159 | dependencies = [ 160 | "cfg-if", 161 | ] 162 | 163 | [[package]] 164 | name = "libc" 165 | version = "0.2.153" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" 168 | 169 | [[package]] 170 | name = "libmimalloc-sys" 171 | version = "0.1.35" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "3979b5c37ece694f1f5e51e7ecc871fdb0f517ed04ee45f88d15d6d553cb9664" 174 | dependencies = [ 175 | "cc", 176 | "libc", 177 | ] 178 | 179 | [[package]] 180 | name = "log" 181 | version = "0.4.21" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" 184 | 185 | [[package]] 186 | name = "mimalloc" 187 | version = "0.1.39" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "fa01922b5ea280a911e323e4d2fd24b7fe5cc4042e0d2cda3c40775cdc4bdc9c" 190 | dependencies = [ 191 | "libmimalloc-sys", 192 | ] 193 | 194 | [[package]] 195 | name = "once_cell" 196 | version = "1.19.0" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 199 | 200 | [[package]] 201 | name = "ppv-lite86" 202 | version = "0.2.17" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 205 | 206 | [[package]] 207 | name = "proc-macro2" 208 | version = "1.0.79" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" 211 | dependencies = [ 212 | "unicode-ident", 213 | ] 214 | 215 | [[package]] 216 | name = "quote" 217 | version = "1.0.35" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" 220 | dependencies = [ 221 | "proc-macro2", 222 | ] 223 | 224 | [[package]] 225 | name = "rand" 226 | version = "0.8.5" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 229 | dependencies = [ 230 | "libc", 231 | "rand_chacha", 232 | "rand_core", 233 | ] 234 | 235 | [[package]] 236 | name = "rand_chacha" 237 | version = "0.3.1" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 240 | dependencies = [ 241 | "ppv-lite86", 242 | "rand_core", 243 | ] 244 | 245 | [[package]] 246 | name = "rand_core" 247 | version = "0.6.4" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 250 | dependencies = [ 251 | "getrandom", 252 | ] 253 | 254 | [[package]] 255 | name = "saturating" 256 | version = "0.1.0" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "ece8e78b2f38ec51c51f5d475df0a7187ba5111b2a28bdc761ee05b075d40a71" 259 | 260 | [[package]] 261 | name = "smallvec" 262 | version = "1.13.2" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 265 | 266 | [[package]] 267 | name = "symbol_table" 268 | version = "0.2.0" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "32bf088d1d7df2b2b6711b06da3471bc86677383c57b27251e18c56df8deac14" 271 | dependencies = [ 272 | "ahash 0.7.8", 273 | "hashbrown", 274 | ] 275 | 276 | [[package]] 277 | name = "symbolic_expressions" 278 | version = "5.0.3" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "7c68d531d83ec6c531150584c42a4290911964d5f0d79132b193b67252a23b71" 281 | 282 | [[package]] 283 | name = "syn" 284 | version = "2.0.55" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0" 287 | dependencies = [ 288 | "proc-macro2", 289 | "quote", 290 | "unicode-ident", 291 | ] 292 | 293 | [[package]] 294 | name = "thiserror" 295 | version = "1.0.58" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" 298 | dependencies = [ 299 | "thiserror-impl", 300 | ] 301 | 302 | [[package]] 303 | name = "thiserror-impl" 304 | version = "1.0.58" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" 307 | dependencies = [ 308 | "proc-macro2", 309 | "quote", 310 | "syn", 311 | ] 312 | 313 | [[package]] 314 | name = "unicode-ident" 315 | version = "1.0.12" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 318 | 319 | [[package]] 320 | name = "version_check" 321 | version = "0.9.4" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 324 | 325 | [[package]] 326 | name = "wasi" 327 | version = "0.11.0+wasi-snapshot-preview1" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 330 | 331 | [[package]] 332 | name = "zerocopy" 333 | version = "0.7.35" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 336 | dependencies = [ 337 | "zerocopy-derive", 338 | ] 339 | 340 | [[package]] 341 | name = "zerocopy-derive" 342 | version = "0.7.35" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 345 | dependencies = [ 346 | "proc-macro2", 347 | "quote", 348 | "syn", 349 | ] 350 | -------------------------------------------------------------------------------- /EqSat/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "eq-sat" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [lib] 9 | crate-type = ["cdylib"] 10 | path = "src/main.rs" 11 | 12 | 13 | [dependencies] 14 | egg = { git = "https://github.com/egraphs-good/egg.git" } 15 | rand = "0.8.5" 16 | libc = "0.2.149" 17 | cranelift-isle = "0.102.1" 18 | ahash = "0.8.11" 19 | mimalloc = { version = "*", default-features = false } 20 | egraph = { path = "./egraph" } 21 | foldhash = "=0.1.0" 22 | 23 | [profile.release] 24 | debug = true 25 | debuginfo-level = 2 26 | panic = "abort" 27 | lto = true 28 | codegen-units = 1 29 | -------------------------------------------------------------------------------- /EqSat/Minimization/TruthTables/1variable_truthtable.txt: -------------------------------------------------------------------------------- 1 | (x&~x) 2 | ~x 3 | x 4 | ~(x&~x) 5 | -------------------------------------------------------------------------------- /EqSat/Minimization/TruthTables/2variable_truthtable.bc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mazeworks-security/Simplifier/acea9cb7977f40faa27c6cd031b00454968c3d9a/EqSat/Minimization/TruthTables/2variable_truthtable.bc -------------------------------------------------------------------------------- /EqSat/Minimization/TruthTables/2variable_truthtable.txt: -------------------------------------------------------------------------------- 1 | (x&~x) 2 | ~(x|y) 3 | ~(x|~y) 4 | ~x 5 | (x&~y) 6 | ~y 7 | (x^y) 8 | ~(x&y) 9 | (x&y) 10 | ~(x^y) 11 | y 12 | ~(x&~y) 13 | x 14 | (x|~y) 15 | (x|y) 16 | ~(x&~x) 17 | -------------------------------------------------------------------------------- /EqSat/Minimization/TruthTables/3variable_truthtable.bc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mazeworks-security/Simplifier/acea9cb7977f40faa27c6cd031b00454968c3d9a/EqSat/Minimization/TruthTables/3variable_truthtable.bc -------------------------------------------------------------------------------- /EqSat/Minimization/TruthTables/3variable_truthtable.txt: -------------------------------------------------------------------------------- 1 | (x&(~x)) 2 | (((~z)&(~y))&(~x)) 3 | (((~z)&(~y))&x) 4 | ((~z)&(~y)) 5 | (((~z)&y)&(~x)) 6 | ((~z)&(~x)) 7 | ((~z)&(x^y)) 8 | (((~z)&(~x))|((~z)&(~y))) 9 | (((~z)&y)&x) 10 | ((~z)&(~(x^y))) 11 | ((~z)&x) 12 | (((~z)&x)|((~z)&(~y))) 13 | ((~z)&y) 14 | (((~z)&(~x))|((~z)&y)) 15 | (((~z)&x)|((~z)&y)) 16 | (~z) 17 | ((z&(~y))&(~x)) 18 | ((~y)&(~x)) 19 | ((~y)&(x^z)) 20 | (((~y)&(~x))|((~z)&(~y))) 21 | ((y^z)&(~x)) 22 | (((~y)&(~x))|((~z)&(~x))) 23 | (((~y)&(x^z))|((~z)&(x^y))) 24 | ((((~y)&(~x))|((~z)&(~x)))|((~z)&(~y))) 25 | ((x^z)&(y^z)) 26 | (((~y)&(~x))|((~z)&(~(x^y)))) 27 | (((~y)&(x^z))|((~z)&x)) 28 | (((~y)&(~x))|((~z)&x)) 29 | (((y^z)&(~x))|((~z)&y)) 30 | (((~y)&(~x))|((~z)&y)) 31 | ((((y&x)^x)^y)^z) 32 | (((~y)&(~x))|(~z)) 33 | ((z&(~y))&x) 34 | ((~y)&(~(x^z))) 35 | ((~y)&x) 36 | (((~y)&x)|((~z)&(~y))) 37 | ((~(x^z))&(y^z)) 38 | (((~y)&(~(x^z)))|((~z)&(~x))) 39 | (((~y)&x)|((~z)&(x^y))) 40 | (((~y)&x)|((~z)&(~x))) 41 | ((y^z)&x) 42 | (((~y)&(~(x^z)))|((~z)&(~(x^y)))) 43 | (((~y)&x)|((~z)&x)) 44 | ((((~y)&x)|((~z)&x))|((~z)&(~y))) 45 | (((y^z)&x)|((~z)&y)) 46 | (~(((y&x)^x)^z)) 47 | (((~y)&x)|((~z)&y)) 48 | (((~y)&x)|(~z)) 49 | (z&(~y)) 50 | (((~y)&(~x))|(z&(~y))) 51 | (((~y)&x)|(z&(~y))) 52 | (~y) 53 | (((y^z)&(~x))|(z&(~y))) 54 | (((~z)&(~x))|(z&(~y))) 55 | ((((z&x)^x)^y)^z) 56 | (((~z)&(~x))|(~y)) 57 | (((y^z)&x)|(z&(~y))) 58 | (~(((z&x)^x)^y)) 59 | (((~z)&x)|(z&(~y))) 60 | (((~z)&x)|(~y)) 61 | (y^z) 62 | (((~z)&(~x))|(y^z)) 63 | (((~z)&x)|(y^z)) 64 | ((~y)|(~z)) 65 | ((z&y)&(~x)) 66 | ((~(y^z))&(~x)) 67 | ((x^z)&(~(y^z))) 68 | (((~(y^z))&(~x))|((~z)&(~y))) 69 | (y&(~x)) 70 | ((y&(~x))|((~z)&(~x))) 71 | ((y&(~x))|((~z)&(x^y))) 72 | ((y&(~x))|((~z)&(~y))) 73 | (y&(x^z)) 74 | ((y&(x^z))|((~z)&(~(x^y)))) 75 | ((y&(x^z))|((~z)&x)) 76 | (~(((y&x)^y)^z)) 77 | ((y&(~x))|((~z)&y)) 78 | (((y&(~x))|((~z)&(~x)))|((~z)&y)) 79 | ((y&(~x))|((~z)&x)) 80 | ((y&(~x))|(~z)) 81 | (z&(~x)) 82 | (((~y)&(~x))|(z&(~x))) 83 | (((~y)&(x^z))|(z&(~x))) 84 | ((z&(~x))|((~z)&(~y))) 85 | ((y&(~x))|(z&(~x))) 86 | (~x) 87 | ((((z&y)^x)^y)^z) 88 | (((~z)&(~y))|(~x)) 89 | ((y&(x^z))|(z&(~x))) 90 | (~(((z&y)^x)^y)) 91 | (x^z) 92 | (((~z)&(~y))|(x^z)) 93 | ((z&(~x))|((~z)&y)) 94 | (((~z)&y)|(~x)) 95 | (((~z)&y)|(x^z)) 96 | ((~x)|(~z)) 97 | (z&(x^y)) 98 | (((~y)&(~(x^z)))|(z&(x^y))) 99 | (((~y)&x)|(z&(x^y))) 100 | (~(((z&x)^y)^z)) 101 | ((y&(~x))|(z&(x^y))) 102 | (~(((z&y)^x)^z)) 103 | (x^y) 104 | (((~z)&(~y))|(x^y)) 105 | ((y&(x^z))|(z&(x^y))) 106 | (~((x^y)^z)) 107 | ((z&y)^x) 108 | (((~z)&(~y))|(~((x^y)^z))) 109 | ((z&x)^y) 110 | (((~z)&y)|(~((x^y)^z))) 111 | (((~z)&y)|(x^y)) 112 | ((x^y)|(~z)) 113 | ((z&(~x))|(z&(~y))) 114 | ((((~y)&(~x))|(z&(~x)))|(z&(~y))) 115 | (((~y)&x)|(z&(~x))) 116 | ((z&(~x))|(~y)) 117 | ((y&(~x))|(z&(~y))) 118 | ((z&(~y))|(~x)) 119 | ((z&(~y))|(x^y)) 120 | ((~x)|(~y)) 121 | ((y&x)^z) 122 | ((z&(~y))|(~((x^y)^z))) 123 | ((z&(~y))|(x^z)) 124 | ((x^z)|(~y)) 125 | ((z&(~x))|(y^z)) 126 | ((~x)|(y^z)) 127 | ((x^z)|(y^z)) 128 | (((~x)|(~y))|(~z)) 129 | ((z&y)&x) 130 | ((~(x^z))&(~(y^z))) 131 | ((~(y^z))&x) 132 | (((~(y^z))&x)|((~z)&(~y))) 133 | (y&(~(x^z))) 134 | ((y&(~(x^z)))|((~z)&(~x))) 135 | ((y&(~(x^z)))|((~z)&(x^y))) 136 | (~((y&x)^z)) 137 | (y&x) 138 | ((y&x)|((~z)&(~(x^y)))) 139 | ((y&x)|((~z)&x)) 140 | ((y&x)|((~z)&(~y))) 141 | ((y&x)|((~z)&y)) 142 | ((y&x)|((~z)&(~x))) 143 | (((y&x)|((~z)&x))|((~z)&y)) 144 | ((y&x)|(~z)) 145 | (z&(~(x^y))) 146 | (((~y)&(~x))|(z&(~(x^y)))) 147 | (((~y)&(x^z))|(z&(~(x^y)))) 148 | (~((z&x)^y)) 149 | ((y&(~(x^z)))|(z&(~(x^y)))) 150 | (~((z&y)^x)) 151 | ((x^y)^z) 152 | (((~z)&(~y))|((x^y)^z)) 153 | ((y&x)|(z&(~(x^y)))) 154 | (~(x^y)) 155 | (((z&y)^x)^z) 156 | (((~z)&(~y))|(~(x^y))) 157 | (((z&x)^y)^z) 158 | (((~z)&y)|(~(x^y))) 159 | (((~z)&y)|((x^y)^z)) 160 | ((~(x^y))|(~z)) 161 | (z&x) 162 | (((~y)&(~(x^z)))|(z&x)) 163 | (((~y)&x)|(z&x)) 164 | ((z&x)|((~z)&(~y))) 165 | ((y&(~(x^z)))|(z&x)) 166 | (~(x^z)) 167 | (((z&y)^x)^y) 168 | (((~z)&(~y))|(~(x^z))) 169 | ((y&x)|(z&x)) 170 | (~((((z&y)^x)^y)^z)) 171 | x 172 | (((~z)&(~y))|x) 173 | ((z&x)|((~z)&y)) 174 | (((~z)&y)|(~(x^z))) 175 | (((~z)&y)|x) 176 | (x|(~z)) 177 | ((z&x)|(z&(~y))) 178 | (((~y)&(~x))|(z&x)) 179 | ((((~y)&x)|(z&x))|(z&(~y))) 180 | ((z&x)|(~y)) 181 | (((y&x)^y)^z) 182 | ((z&(~y))|(~(x^z))) 183 | ((z&(~y))|((x^y)^z)) 184 | ((~(x^z))|(~y)) 185 | ((y&x)|(z&(~y))) 186 | ((z&(~y))|(~(x^y))) 187 | ((z&(~y))|x) 188 | (x|(~y)) 189 | ((z&x)|(y^z)) 190 | ((~(x^z))|(y^z)) 191 | (x|(y^z)) 192 | ((x|(~y))|(~z)) 193 | (z&y) 194 | (((~(y^z))&(~x))|(z&y)) 195 | (((~(y^z))&x)|(z&y)) 196 | (~(y^z)) 197 | ((y&(~x))|(z&y)) 198 | (((~z)&(~x))|(z&y)) 199 | (((z&x)^x)^y) 200 | (((~z)&(~x))|(~(y^z))) 201 | ((y&x)|(z&y)) 202 | (~((((z&x)^x)^y)^z)) 203 | (((~z)&x)|(z&y)) 204 | (((~z)&x)|(~(y^z))) 205 | y 206 | (((~z)&(~x))|y) 207 | (((~z)&x)|y) 208 | (y|(~z)) 209 | ((z&(~x))|(z&y)) 210 | (((~y)&(~x))|(z&y)) 211 | (((y&x)^x)^z) 212 | ((z&(~x))|(~(y^z))) 213 | (((y&(~x))|(z&(~x)))|(z&y)) 214 | ((z&y)|(~x)) 215 | ((z&y)|((x^y)^z)) 216 | ((~x)|(~(y^z))) 217 | ((y&x)|(z&(~x))) 218 | ((z&y)|(~(x^y))) 219 | ((z&y)|(x^z)) 220 | ((x^z)|(~(y^z))) 221 | ((z&(~x))|y) 222 | ((~x)|y) 223 | ((x^z)|y) 224 | (((~x)|y)|(~z)) 225 | ((z&x)|(z&y)) 226 | (~((((y&x)^x)^y)^z)) 227 | (((~y)&x)|(z&y)) 228 | ((z&x)|(~(y^z))) 229 | ((y&(~x))|(z&x)) 230 | ((z&y)|(~(x^z))) 231 | ((z&y)|(x^y)) 232 | ((~(x^z))|(~(y^z))) 233 | (((y&x)|(z&x))|(z&y)) 234 | ((z&y)|(~((x^y)^z))) 235 | ((z&y)|x) 236 | (x|(~(y^z))) 237 | ((z&x)|y) 238 | ((~(x^z))|y) 239 | (x|y) 240 | ((x|y)|(~z)) 241 | z 242 | (((~y)&(~x))|z) 243 | (((~y)&x)|z) 244 | ((~y)|z) 245 | ((y&(~x))|z) 246 | ((~x)|z) 247 | ((x^y)|z) 248 | (((~x)|(~y))|z) 249 | ((y&x)|z) 250 | ((~(x^y))|z) 251 | (x|z) 252 | ((x|(~y))|z) 253 | (y|z) 254 | (((~x)|y)|z) 255 | ((x|y)|z) 256 | (~(x&(~x))) 257 | -------------------------------------------------------------------------------- /EqSat/Minimization/TruthTables/4variable_truthtable.bc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mazeworks-security/Simplifier/acea9cb7977f40faa27c6cd031b00454968c3d9a/EqSat/Minimization/TruthTables/4variable_truthtable.bc -------------------------------------------------------------------------------- /EqSat/egraph/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "egraph" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib", "lib"] 8 | path = "src/main.rs" 9 | 10 | [dependencies] 11 | egg = { git = "https://github.com/egraphs-good/egg.git" } 12 | libc = "0.2.149" 13 | rand = "0.8.5" -------------------------------------------------------------------------------- /EqSat/egraph/src/abstract_interpretation.rs: -------------------------------------------------------------------------------- 1 | use libc::{c_char, c_void}; 2 | use std::{ffi::CStr, ptr}; 3 | 4 | use crate::ffi_utils::marshal_string; 5 | 6 | #[derive(Copy, Clone)] 7 | pub enum KnownBitsTransferOpcode { 8 | Add = 0, 9 | Mul = 1, 10 | // Note that POW is not supported. 11 | // If a POW can be represented as a shift left then the shl transfer function should be used. 12 | // Otherwise you'll need to implement POWs as repeated multiplications. 13 | Shl = 2, 14 | And = 3, 15 | Or = 4, 16 | Xor = 5, 17 | Neg = 6, 18 | } 19 | 20 | #[derive(Debug, Clone, Copy)] 21 | pub struct KnownBitsWrapper { 22 | ptr: *const c_void, 23 | } 24 | 25 | // Note: For now the KnownBits FFI interface has been replaced with a dummy implementation. 26 | // We rely on LLVM's KnownBits implementation, but do not want to require that users have LLVM installed. 27 | // TODO: Use a macro to conditionally enable the FFI imports for known bits. 28 | 29 | /* 30 | // FFI imports. 31 | #[link(name = "LLVMInterop", kind = "raw-dylib")] 32 | extern "C" { 33 | // Given an integer bit width, create a knownbits object of that bitwidth, with all bits initialized to unknown. 34 | fn ffi_CreateKnownBits(bitWidth: u32) -> *const c_void; 35 | // Create a knownbits object for the given constant. Note that only up to 64 bits is supported now. 36 | pub fn ffi_CreateKnownBitsForConstant(bitWidth: u32, value: u64) -> *const c_void; 37 | // Compute a new knownbits object given an opcode and a sedt of operands. 38 | // Note that the opcode is just the 'KnownBitsTransferOpcode` casted to an integer. 39 | pub fn ffi_ComputeKnownBits( 40 | opcode: u8, 41 | lhs: *const c_void, 42 | rhs: *const c_void, 43 | ) -> *const c_void; 44 | pub fn ffi_UnionKnownBits( 45 | lhs: *const c_void, 46 | rhs: *const c_void, 47 | out: *const *mut c_void, 48 | ) -> bool; 49 | pub fn ffi_TryConstant(kb: *const c_void, out: *mut u64) -> bool; 50 | pub fn ffi_KnownBitsToStr(kb: *const c_void) -> *const c_char; 51 | pub fn ffi_NumKnownBits(kb: *const c_void) -> u64; 52 | pub fn ffi_AreKnownBitsEqual(lhs: *const c_void, rhs: *const c_void) -> bool; 53 | pub fn ffi_HaveNoCommonBitsSet(lhs: *const c_void, rhs: *const c_void) -> bool; 54 | pub fn ffi_IsSubset(lhs: *const c_void, rhs: *const c_void) -> bool; 55 | } 56 | */ 57 | 58 | // Given an integer bit width, create a knownbits object of that bitwidth, with all bits initialized to unknown. 59 | fn ffi_CreateKnownBits(bitWidth: u32) -> *const c_void { 60 | panic!("") 61 | } 62 | // Create a knownbits object for the given constant. Note that only up to 64 bits is supported now. 63 | pub fn ffi_CreateKnownBitsForConstant(bitWidth: u32, value: u64) -> *const c_void { 64 | panic!("") 65 | } 66 | // Compute a new knownbits object given an opcode and a sedt of operands. 67 | // Note that the opcode is just the 'KnownBitsTransferOpcode` casted to an integer. 68 | pub fn ffi_ComputeKnownBits(opcode: u8, lhs: *const c_void, rhs: *const c_void) -> *const c_void { 69 | panic!("") 70 | } 71 | pub fn ffi_UnionKnownBits(lhs: *const c_void, rhs: *const c_void, out: *const *mut c_void) -> bool { 72 | panic!("") 73 | } 74 | 75 | pub fn ffi_TryConstant(kb: *const c_void, out: *mut u64) -> bool { 76 | panic!("") 77 | } 78 | pub fn ffi_KnownBitsToStr(kb: *const c_void) -> *const c_char { 79 | panic!("") 80 | } 81 | pub fn ffi_NumKnownBits(kb: *const c_void) -> u64 { 82 | panic!("") 83 | } 84 | pub fn ffi_AreKnownBitsEqual(lhs: *const c_void, rhs: *const c_void) -> bool { 85 | panic!("") 86 | } 87 | pub fn ffi_HaveNoCommonBitsSet(lhs: *const c_void, rhs: *const c_void) -> bool { 88 | panic!("") 89 | } 90 | pub fn ffi_IsSubset(lhs: *const c_void, rhs: *const c_void) -> bool { 91 | panic!("") 92 | } 93 | 94 | const DISABLE_KNOWN_BITS: bool = true; 95 | 96 | impl KnownBitsWrapper { 97 | pub fn no_common_bits_set(&self, rhs: KnownBitsWrapper) -> bool { 98 | if DISABLE_KNOWN_BITS { 99 | return false; 100 | } 101 | 102 | unsafe { ffi_HaveNoCommonBitsSet(self.ptr, rhs.ptr) } 103 | } 104 | 105 | pub fn is_subset_of_this(&self, rhs: KnownBitsWrapper) -> bool { 106 | if DISABLE_KNOWN_BITS { 107 | return false; 108 | } 109 | unsafe { ffi_IsSubset(self.ptr, rhs.ptr) } 110 | } 111 | 112 | pub fn num_known_bits(&self) -> u64 { 113 | if DISABLE_KNOWN_BITS { 114 | return 0; 115 | } 116 | 117 | unsafe { ffi_NumKnownBits(self.ptr) } 118 | } 119 | 120 | pub fn to_str(&self) -> String { 121 | if DISABLE_KNOWN_BITS { 122 | return "Disabled".to_string(); 123 | } 124 | 125 | unsafe { marshal_string(ffi_KnownBitsToStr(self.ptr)) } 126 | } 127 | 128 | pub fn try_as_const(&self) -> Option { 129 | if DISABLE_KNOWN_BITS { 130 | return None; 131 | } 132 | 133 | let mut out: u64 = 0; 134 | unsafe { 135 | if ffi_TryConstant(self.ptr, &mut out) { 136 | Some(out) 137 | } else { 138 | None 139 | } 140 | } 141 | } 142 | } 143 | 144 | pub fn are_known_bits_equal(kb1: KnownBitsWrapper, kb2: KnownBitsWrapper) -> bool { 145 | if DISABLE_KNOWN_BITS { 146 | return true; 147 | } 148 | 149 | unsafe { ffi_AreKnownBitsEqual(kb1.ptr, kb2.ptr) } 150 | } 151 | 152 | // Wrapper methods around FFI imports. 153 | pub fn create_empty_known_bits(width: u32) -> KnownBitsWrapper { 154 | if DISABLE_KNOWN_BITS { 155 | return KnownBitsWrapper { 156 | ptr: std::ptr::null(), 157 | }; 158 | } 159 | 160 | unsafe { 161 | let ptr = ffi_CreateKnownBits(width); 162 | return KnownBitsWrapper { ptr }; 163 | }; 164 | } 165 | 166 | pub fn create_constant_known_bits(width: u32, value: u64) -> KnownBitsWrapper { 167 | if DISABLE_KNOWN_BITS { 168 | return create_empty_known_bits(width); 169 | } 170 | 171 | unsafe { 172 | let ptr = ffi_CreateKnownBitsForConstant(width, value); 173 | return KnownBitsWrapper { ptr }; 174 | }; 175 | } 176 | 177 | pub fn compute_known_bits( 178 | opcode: KnownBitsTransferOpcode, 179 | lhs: KnownBitsWrapper, 180 | maybe_rhs: Option, 181 | ) -> KnownBitsWrapper { 182 | if DISABLE_KNOWN_BITS { 183 | return KnownBitsWrapper { 184 | ptr: std::ptr::null(), 185 | }; 186 | } 187 | 188 | unsafe { 189 | // return CreateEmptyKnownBits(64); 190 | // If the operand is negation then there is no RHS. 191 | // In that case we set the RHS to `new KnownBitsWrapper(nullptr)`. 192 | let nullptr: *const c_void = ptr::null(); 193 | let none = KnownBitsWrapper { ptr: nullptr }; 194 | let rhs = maybe_rhs.unwrap_or(none); 195 | 196 | // Execute and return the result from the known bits transfer function wrapper. 197 | let ptr = ffi_ComputeKnownBits(opcode as u8, lhs.ptr, rhs.ptr); 198 | return KnownBitsWrapper { ptr }; 199 | }; 200 | } 201 | 202 | pub fn union_known_bits(lhs: KnownBitsWrapper, rhs: KnownBitsWrapper) -> Option { 203 | if DISABLE_KNOWN_BITS { 204 | return None; 205 | } 206 | 207 | unsafe { 208 | let mut new_ptr: *mut c_void = std::ptr::null_mut(); 209 | let res = ffi_UnionKnownBits(lhs.ptr, rhs.ptr, &new_ptr); 210 | 211 | if res { 212 | Some(KnownBitsWrapper { ptr: new_ptr }) 213 | } else { 214 | None 215 | } 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /EqSat/egraph/src/analysis.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | abstract_interpretation::{ 3 | are_known_bits_equal, compute_known_bits, create_constant_known_bits, 4 | create_empty_known_bits, union_known_bits, KnownBitsTransferOpcode, KnownBitsWrapper, 5 | }, 6 | classification::{classify, AstClassification, MbaInfo}, 7 | cost::{get_cost, EGraphCostFn}, 8 | expr::Expr, 9 | }; 10 | 11 | use egg::*; 12 | 13 | pub type EEGraph = egg::EGraph; 14 | pub type Rewrite = egg::Rewrite; 15 | 16 | // Since Egg only supports a single analysis class per egraph, 17 | // we must perform multiple analyses at once. Namely constant folding, classification(e.g., "is this mba linear?"), and known bits analysis. 18 | #[derive(Default)] 19 | pub struct MbaAnalysis; 20 | impl Analysis for MbaAnalysis { 21 | type Data = MbaInfo; 22 | 23 | fn make(egraph: &mut EEGraph, enode: &Expr) -> Self::Data { 24 | let transfer = |op, a: &Id, b: &Id| -> KnownBitsWrapper { 25 | let lhs = egraph[*a].data.known_bits; 26 | let rhs = egraph[*b].data.known_bits; 27 | 28 | compute_known_bits(op, lhs, Some(rhs)) 29 | }; 30 | 31 | let to = match enode { 32 | Expr::Add([a, b]) => transfer(KnownBitsTransferOpcode::Add, a, b), 33 | Expr::Mul([a, b]) => transfer(KnownBitsTransferOpcode::Mul, a, b), 34 | Expr::Shl([a, b]) => transfer(KnownBitsTransferOpcode::Shl, a, b), 35 | Expr::And([a, b]) => transfer(KnownBitsTransferOpcode::And, a, b), 36 | Expr::Or([a, b]) => transfer(KnownBitsTransferOpcode::Or, a, b), 37 | Expr::Xor([a, b]) => transfer(KnownBitsTransferOpcode::Xor, a, b), 38 | Expr::Neg([a]) => compute_known_bits( 39 | KnownBitsTransferOpcode::Neg, 40 | egraph[*a].data.known_bits, 41 | None, 42 | ), 43 | Expr::Constant(c) => create_constant_known_bits(64, reduce_modulo(*c) as u64), 44 | _ => create_empty_known_bits(64), 45 | }; 46 | 47 | if let Some(cst) = to.try_as_const() { 48 | return MbaInfo::new( 49 | AstClassification::Constant { 50 | value: cst as u128 as i128, 51 | }, 52 | Some(cst as u128 as i128), 53 | 1, 54 | to, 55 | ); 56 | } 57 | 58 | let classification = classify(egraph, enode).unwrap(); 59 | let cost = get_cost(egraph, enode); 60 | 61 | // If we classified the AST and returned a new PatternAst, that means 62 | // constant folding succeeded. Now we return the newly detected 63 | // classification and the pattern ast 64 | match classification { 65 | AstClassification::Constant { value } => { 66 | return MbaInfo::new(classification, Some(value), 1, to); 67 | } 68 | _ => {} 69 | }; 70 | 71 | // Otherwise we've classified the AST but there was no constant folding 72 | // to be performed. 73 | MbaInfo::new(classification, None, cost, to) 74 | } 75 | 76 | fn merge(&mut self, to: &mut Self::Data, from: Self::Data) -> DidMerge { 77 | let kb1 = to.known_bits; 78 | let kb2 = from.known_bits; 79 | 80 | // Already known 81 | if to.const_fold.is_some() { 82 | return DidMerge(false, from.const_fold.is_some() /* maybe */); 83 | } 84 | 85 | if let Some(new_cst) = from.const_fold { 86 | // Const in rhs, update analysis 87 | to.known_bits = from.known_bits; 88 | to.const_fold = Some(new_cst as u128 as i128); 89 | to.cost = 1; 90 | return DidMerge(true, false); 91 | } 92 | 93 | // Union until a fixedpoint is reached. 94 | if let Some(new) = union_known_bits(kb1, kb2) { 95 | to.known_bits = new; 96 | 97 | if let Some(cst) = new.try_as_const() { 98 | to.const_fold = Some(cst as u128 as i128); 99 | to.cost = 1; 100 | return DidMerge(true, true); // yep 101 | } 102 | 103 | let new_for_to = !are_known_bits_equal(new, kb1); 104 | let new_for_from = !are_known_bits_equal(new, kb2); 105 | return DidMerge(new_for_to, new_for_from); 106 | } 107 | 108 | // Otherwise no updates occurred. 109 | return DidMerge(false, false); 110 | } 111 | 112 | fn modify(egraph: &mut EEGraph, id: Id) { 113 | let expr = &egraph[id]; 114 | if let Some(new) = &egraph[id].data.const_fold { 115 | let c = Expr::Constant(*new); 116 | let new_id = egraph.add(c); 117 | 118 | egraph.union(id, new_id); 119 | 120 | // To not prune, comment this out 121 | egraph[id].nodes.retain(|n| n.is_leaf()); 122 | } 123 | } 124 | } 125 | 126 | // Reduce the integer mod 2^64 127 | pub fn reduce_modulo(n: i128) -> i128 { 128 | return (n as u64) as i128; 129 | } 130 | 131 | pub fn try_fold_constant(egraph: &EEGraph, enode: &Expr) -> Option { 132 | let x = |i: &Id| match egraph[*i].data.classification { 133 | AstClassification::Constant { value } => Some(reduce_modulo(value)), 134 | _ => None, 135 | }; 136 | 137 | Some(match enode { 138 | Expr::Constant(c) => reduce_modulo(*c), 139 | Expr::Add([a, b]) => { 140 | let val = x(a)?.wrapping_add(x(b)?); 141 | reduce_modulo(val) 142 | } 143 | Expr::Mul([a, b]) => { 144 | let val = x(a)?.wrapping_mul(x(b)?); 145 | reduce_modulo(val) 146 | } 147 | Expr::Pow([a, b]) => { 148 | let val = x(a)?.wrapping_pow(x(b)?.try_into().unwrap()); 149 | reduce_modulo(val) 150 | } 151 | Expr::And([a, b]) => { 152 | let val = x(a)? & x(b)?; 153 | reduce_modulo(val) 154 | } 155 | Expr::Or([a, b]) => { 156 | let val = x(a)? | x(b)?; 157 | reduce_modulo(val) 158 | } 159 | Expr::Xor([a, b]) => { 160 | let val = x(a)? ^ x(b)?; 161 | reduce_modulo(val) 162 | } 163 | Expr::Neg([a]) => { 164 | let val = !x(a)?; 165 | reduce_modulo(val) 166 | } 167 | Expr::Ashr([a, b]) => { 168 | let op1 = x(a)?; 169 | let op2 = x(b)?; 170 | let ashr = fold_ashr(op1, op2); 171 | reduce_modulo(ashr) 172 | } 173 | Expr::Lshr([a, b]) => { 174 | let op1 = x(a)?; 175 | let op2 = x(b)?; 176 | let ashr = fold_lshr(op1, op2); 177 | reduce_modulo(ashr) 178 | } 179 | Expr::Shl([a, b]) => { 180 | let op1 = x(a)?; 181 | let op2 = x(b)?; 182 | let ashr = fold_shl(op1, op2); 183 | reduce_modulo(ashr) 184 | } 185 | Expr::Symbol(_) => return None, 186 | }) 187 | } 188 | 189 | fn fold_ashr(a: i128, b: i128) -> i128 { 190 | let ashr64 = (a as i64) >> (b as i64); 191 | return ashr64 as i128; 192 | } 193 | 194 | fn fold_lshr(a: i128, b: i128) -> i128 { 195 | let op1 = (a as u128) as u64; 196 | let op2 = (b as u128) as u64; 197 | let lshr64 = op1 >> op2; 198 | return (lshr64 as u128) as i128; 199 | } 200 | 201 | fn fold_shl(a: i128, b: i128) -> i128 { 202 | let op1 = (a as u128) as u64; 203 | let op2 = (b as u128) as u64; 204 | let shl64 = op1 << op2; 205 | return (shl64 as u128) as i128; 206 | } 207 | -------------------------------------------------------------------------------- /EqSat/egraph/src/classification.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use egg::*; 4 | 5 | use crate::{ 6 | abstract_interpretation::KnownBitsWrapper, 7 | analysis::{reduce_modulo, try_fold_constant, EEGraph}, 8 | cost::Cost, 9 | expr::Expr, 10 | }; 11 | 12 | #[derive(Debug, Clone, Copy)] 13 | pub enum AstClassification { 14 | Unknown, 15 | Constant { value: i128 }, 16 | Bitwise, 17 | Linear { is_variable: bool }, 18 | Nonlinear, 19 | Mixed, 20 | } 21 | 22 | #[derive(Debug, Clone)] 23 | pub struct MbaInfo { 24 | pub classification: AstClassification, 25 | 26 | // If a node was constant folded during analysis, this was the result. 27 | pub const_fold: Option, 28 | 29 | pub cost: Cost, 30 | 31 | pub known_bits: KnownBitsWrapper, 32 | } 33 | 34 | impl MbaInfo { 35 | pub fn new( 36 | classification: AstClassification, 37 | const_fold: Option, 38 | cost: Cost, 39 | known_bits: KnownBitsWrapper, 40 | ) -> Self { 41 | Self { 42 | classification, 43 | const_fold, 44 | cost, 45 | known_bits, 46 | } 47 | } 48 | } 49 | 50 | pub fn classify(egraph: &EEGraph, enode: &Expr) -> Option { 51 | let op = |i: &Id| egraph[*i].data.classification; 52 | 53 | // If we have any operation that may be folded into a constant(e.g. x + 0, x ** 0), then do it. 54 | if let Some(const_folded) = try_fold_constant(egraph, enode) { 55 | return Some(AstClassification::Constant { 56 | value: reduce_modulo(const_folded), 57 | }); 58 | } 59 | 60 | // Otherwise const folding has failed. Now we need to classify it. 61 | let result: AstClassification = match enode { 62 | Expr::Constant(def) => AstClassification::Constant { 63 | value: reduce_modulo(*def), 64 | }, 65 | Expr::Symbol(..) => AstClassification::Linear { is_variable: true }, 66 | Expr::Pow(..) => AstClassification::Nonlinear, 67 | Expr::Mul([a, b]) => { 68 | // At this point constant propagation has handled all cases where two constant children are used. 69 | // So now we only have to handle the other cases. First we start by checking the classification of the other(non constant) child. 70 | let other = get_non_constant_child_classification(op(a), op(b)); 71 | 72 | let result = match other { 73 | Some(AstClassification::Unknown) => { 74 | unreachable!("Ast classification cannot be unknown.") 75 | } 76 | Some(AstClassification::Constant { .. }) => unreachable!( 77 | "Constant propagation should have folded multiplication of constants." 78 | ), 79 | // const * bitwise = mixed expression 80 | Some(AstClassification::Bitwise) => AstClassification::Mixed, 81 | // const * linear = linear 82 | Some(AstClassification::Linear { .. }) => { 83 | AstClassification::Linear { is_variable: false } 84 | } 85 | // const * nonlinear = nonlinear 86 | Some(AstClassification::Nonlinear) => AstClassification::Nonlinear, 87 | // const * mixed(bitwise and arithmetic) = mixed 88 | Some(AstClassification::Mixed) => AstClassification::Mixed, 89 | // If neither operand is a constant then the expression is not linear. 90 | None => AstClassification::Nonlinear, 91 | }; 92 | 93 | return Some(result); 94 | } 95 | 96 | Expr::Add([a, b]) => { 97 | // Adding any operand (A) to any non linear operand (B) is always non linear. 98 | let children = [op(a), op(b)]; 99 | if children 100 | .into_iter() 101 | .any(|x| matches!(x, AstClassification::Nonlinear)) 102 | { 103 | return Some(AstClassification::Nonlinear); 104 | }; 105 | 106 | // At this point we've established (above^) that there are no nonlinear children. 107 | // This leaves potentially constant, linear, bitwise, and mixed expressions left. 108 | // So now we check if either operand is mixed(bitwise + arithmetic) or bitwise. 109 | // In both cases, adding anything to a mixed or bitwise expression will be considered a mixed expression. 110 | if children 111 | .into_iter() 112 | .any(|x| matches!(x, AstClassification::Mixed | AstClassification::Bitwise)) 113 | { 114 | return Some(AstClassification::Mixed); 115 | }; 116 | 117 | // Now an expression is either a constant or a linear child. 118 | // If any child is linear then we consider this to be a linear arithmetic expression. 119 | // Note that constant folding has already eliminated addition of constants. 120 | if children.into_iter().any(|x| match x { 121 | AstClassification::Linear { .. } => true, 122 | AstClassification::Bitwise => false, 123 | _ => false, 124 | }) { 125 | return Some(AstClassification::Linear { 126 | is_variable: (false), 127 | }); 128 | }; 129 | 130 | // This should never happen. 131 | unreachable!() 132 | } 133 | Expr::Neg([a]) => classify_bitwise(op(a), None), 134 | Expr::And([a, b]) => classify_bitwise(op(a), Some(op(b))), 135 | Expr::Or([a, b]) => classify_bitwise(op(a), Some(op(b))), 136 | Expr::Xor([a, b]) => classify_bitwise(op(a), Some(op(b))), 137 | Expr::Ashr(_) => AstClassification::Nonlinear, 138 | Expr::Lshr(_) => AstClassification::Nonlinear, 139 | Expr::Shl(_) => AstClassification::Nonlinear, 140 | }; 141 | 142 | Some(result) 143 | } 144 | 145 | fn classify_bitwise(a: AstClassification, b: Option) -> AstClassification { 146 | // TODO: Throw if we see negation with a constant, that should be fixed. 147 | let children = if let Some(maybe_b) = b { 148 | vec![a, maybe_b] 149 | } else { 150 | vec![a] 151 | }; 152 | 153 | // Check if the expression contains constants or arithmetic expressions. 154 | let contains_constant_or_arithmetic = children.iter().any(|x| match x { 155 | AstClassification::Constant { .. } => true, 156 | // We only want to match linear arithmetic expressions - variables are excluded here. 157 | AstClassification::Linear { is_variable } => !*is_variable, 158 | _ => false, 159 | }); 160 | 161 | // Check if the expression contains constants or arithmetic expressions. 162 | let contains_mixed_or_non_linear = children 163 | .iter() 164 | .any(|x| matches!(x, AstClassification::Mixed | AstClassification::Nonlinear)); 165 | 166 | // Bitwise expressions are considered to be nonlinear if they contain constants, 167 | // arithmetic(linear) expressions, or non linear subexpressions. 168 | if contains_constant_or_arithmetic || contains_mixed_or_non_linear { 169 | return AstClassification::Nonlinear; 170 | } else if children.iter().any(|x: &AstClassification| match x { 171 | AstClassification::Linear { is_variable } => !*is_variable, 172 | AstClassification::Mixed => true, 173 | _ => false, 174 | }) { 175 | return AstClassification::Mixed; 176 | } 177 | 178 | // If none of the children are nonlinear or arithmetic then this is a pure 179 | // bitwise expression. 180 | AstClassification::Bitwise 181 | } 182 | 183 | // If either one of the children is a constant, return the other one. 184 | fn get_non_constant_child_classification( 185 | a: AstClassification, 186 | b: AstClassification, 187 | ) -> Option { 188 | let mut const_child: Option = None; 189 | let mut other_child: Option = None; 190 | if matches!(a, AstClassification::Constant { .. }) { 191 | const_child = Some(a); 192 | } else { 193 | other_child = Some(a); 194 | } 195 | 196 | if matches!(b, AstClassification::Constant { .. }) { 197 | const_child = Some(b); 198 | } else { 199 | other_child = Some(b); 200 | } 201 | 202 | const_child?; 203 | 204 | other_child 205 | } 206 | -------------------------------------------------------------------------------- /EqSat/egraph/src/cost.rs: -------------------------------------------------------------------------------- 1 | use egg::*; 2 | 3 | use crate::{ 4 | abstract_interpretation::{ 5 | compute_known_bits, create_constant_known_bits, create_empty_known_bits, 6 | KnownBitsTransferOpcode, KnownBitsWrapper, 7 | }, 8 | analysis::{EEGraph, MbaAnalysis}, 9 | classification::{classify, AstClassification}, 10 | expr::Expr, 11 | }; 12 | 13 | pub type Cost = i64; 14 | 15 | pub fn get_cost(egraph: &EEGraph, enode: &Expr) -> Cost { 16 | let cost = |i: &Id| egraph[*i].data.cost; 17 | let res = match enode { 18 | Expr::Add([a, b]) | Expr::Mul([a, b]) => cost(a).saturating_add(cost(b)), 19 | Expr::Pow([a, b]) | Expr::And([a, b]) | Expr::Or([a, b]) | Expr::Xor([a, b]) => { 20 | cost(a).saturating_add(cost(b)) 21 | } 22 | Expr::Neg([a]) => cost(a).saturating_add((1)), 23 | Expr::Constant(_) | Expr::Symbol(_) => 1, 24 | Expr::Ashr([a, b]) => cost(a).saturating_add(cost(b)).saturating_add(2), 25 | Expr::Lshr([a, b]) => cost(a).saturating_add(cost(b)).saturating_add(2), 26 | Expr::Shl([a, b]) => cost(a).saturating_add(cost(b)).saturating_add(2), 27 | }; 28 | 29 | return res; 30 | } 31 | 32 | pub struct EGraphCostFn<'a> { 33 | pub egraph: &'a EGraph, 34 | } 35 | 36 | impl<'a> CostFunction for EGraphCostFn<'a> { 37 | type Cost = usize; 38 | fn cost(&mut self, enode: &Expr, mut _costs: C) -> Self::Cost 39 | where 40 | C: FnMut(Id) -> Self::Cost, 41 | { 42 | let mut op_cost: usize = match enode { 43 | Expr::Add(_) => 2, 44 | Expr::Mul(_) => 2, 45 | Expr::Pow(_) | Expr::And(_) | Expr::Or(_) | Expr::Xor(_) => 2, 46 | Expr::Neg(_) => 1, 47 | Expr::Constant(_) | Expr::Symbol(_) => 1, 48 | Expr::Ashr(_) => 2, 49 | Expr::Lshr(_) => 2, 50 | Expr::Shl(_) => 2, 51 | }; 52 | 53 | let class = classify(self.egraph, enode).unwrap(); 54 | op_cost = match class { 55 | AstClassification::Nonlinear => op_cost + 10, 56 | AstClassification::Mixed => op_cost + 1, 57 | _ => op_cost, 58 | }; 59 | 60 | let transfer = |op, a: &Id, b: &Id| -> KnownBitsWrapper { 61 | let lhs = self.egraph[*a].data.known_bits; 62 | let rhs = self.egraph[*b].data.known_bits; 63 | 64 | compute_known_bits(op, lhs, Some(rhs)) 65 | }; 66 | 67 | let to = match enode { 68 | Expr::Add([a, b]) => transfer(KnownBitsTransferOpcode::Add, a, b), 69 | Expr::Mul([a, b]) => transfer(KnownBitsTransferOpcode::Mul, a, b), 70 | Expr::Shl([a, b]) => transfer(KnownBitsTransferOpcode::Shl, a, b), 71 | Expr::And([a, b]) => transfer(KnownBitsTransferOpcode::And, a, b), 72 | Expr::Or([a, b]) => transfer(KnownBitsTransferOpcode::Or, a, b), 73 | Expr::Xor([a, b]) => transfer(KnownBitsTransferOpcode::Xor, a, b), 74 | Expr::Neg([a]) => compute_known_bits( 75 | KnownBitsTransferOpcode::Neg, 76 | self.egraph[*a].data.known_bits, 77 | None, 78 | ), 79 | Expr::Constant(c) => create_constant_known_bits(64, reduce_modulo(*c) as u64), 80 | _ => create_empty_known_bits(64), 81 | }; 82 | 83 | let popcount = 64 - to.num_known_bits(); 84 | op_cost = op_cost.saturating_add(popcount as usize); 85 | 86 | // Compute the final cost. 87 | let mut res: usize = enode.fold(op_cost, |sum, id| sum.saturating_add(_costs(id))); 88 | 89 | return res; 90 | } 91 | } 92 | 93 | // Reduce the integer mod 2^64 94 | pub fn reduce_modulo(n: i128) -> i128 { 95 | return (n as u64) as i128; 96 | } 97 | -------------------------------------------------------------------------------- /EqSat/egraph/src/expr.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::{CStr, CString}; 2 | 3 | use crate::{ 4 | analysis::{self, MbaAnalysis}, 5 | ffi_utils::marshal_string, 6 | }; 7 | use analysis::EEGraph; 8 | use egg::*; 9 | use libc::c_char; 10 | 11 | define_language! { 12 | pub enum Expr { 13 | // arithmetic operations 14 | "+" = Add([Id; 2]), // (+ a b) 15 | "*" = Mul([Id; 2]), // (* a b) 16 | "**" = Pow([Id; 2]), // (** a b) 17 | // bitwise operations 18 | "&" = And([Id; 2]), // (& a b) 19 | "|" = Or([Id; 2]), // (| a b) 20 | "^" = Xor([Id; 2]), // (^ a b) 21 | "~" = Neg([Id; 1]), // (~ a) 22 | 23 | ">>>" = Ashr([Id; 2]), 24 | ">>" = Lshr([Id; 2]), 25 | "<<" = Shl([Id; 2]), 26 | 27 | // Values: 28 | Constant(i128), // (int) 29 | Symbol(Symbol), // (x) 30 | } 31 | } 32 | 33 | impl Expr { 34 | pub fn num(&self) -> Option { 35 | match self { 36 | Expr::Constant(n) => Some(*n), 37 | _ => None, 38 | } 39 | } 40 | } 41 | 42 | // Below is an FFI interface for egraphs and Expr instances. 43 | #[no_mangle] 44 | pub extern "C" fn CreateEGraph() -> *mut EEGraph { 45 | let analysis = MbaAnalysis {}; 46 | let egraph = EEGraph::new(analysis); 47 | let pgraph = Box::new(egraph); 48 | return Box::into_raw(pgraph); 49 | } 50 | 51 | #[no_mangle] 52 | pub extern "C" fn EGraphAddConstant(egraph: *mut EEGraph, constant: u64) -> u32 { 53 | // We don't want to apply sign extension when upcasting from 64 bits to 128 bits. 54 | // Instead, we want to zero extend to an i128, *then* cast the unsigned integer to a signed integer. 55 | let extended = (constant as u128) as i128; 56 | 57 | unsafe { 58 | let id = (*egraph).add(Expr::Constant(extended)); 59 | return get_id_u32(id); 60 | } 61 | } 62 | 63 | #[no_mangle] 64 | pub extern "C" fn EGraphAddSymbol(egraph: *mut EEGraph, s: *const c_char) -> u32 { 65 | let str = marshal_string(s).to_owned(); 66 | 67 | unsafe { 68 | let id = (*egraph).add(Expr::Symbol(str.parse().unwrap())); 69 | return get_id_u32(id); 70 | } 71 | } 72 | 73 | #[no_mangle] 74 | pub extern "C" fn EGraphAddAdd(egraph: *mut EEGraph, a: u32, b: u32) -> u32 { 75 | unsafe { 76 | let op1 = Id::from(a as usize); 77 | let op2 = Id::from(b as usize); 78 | let id = (*egraph).add(Expr::Add([op1, op2])); 79 | return get_id_u32(id); 80 | } 81 | } 82 | 83 | #[no_mangle] 84 | pub extern "C" fn EGraphAddMul(egraph: *mut EEGraph, a: u32, b: u32) -> u32 { 85 | unsafe { 86 | let op1 = Id::from(a as usize); 87 | let op2 = Id::from(b as usize); 88 | let id = (*egraph).add(Expr::Mul([op1, op2])); 89 | return get_id_u32(id); 90 | } 91 | } 92 | 93 | #[no_mangle] 94 | pub extern "C" fn EGraphAddPow(egraph: *mut EEGraph, a: u32, b: u32) -> u32 { 95 | unsafe { 96 | let op1 = Id::from(a as usize); 97 | let op2 = Id::from(b as usize); 98 | let id = (*egraph).add(Expr::Pow([op1, op2])); 99 | return get_id_u32(id); 100 | } 101 | } 102 | 103 | #[no_mangle] 104 | pub extern "C" fn EGraphAddAnd(egraph: *mut EEGraph, a: u32, b: u32) -> u32 { 105 | unsafe { 106 | let op1 = Id::from(a as usize); 107 | let op2 = Id::from(b as usize); 108 | let id = (*egraph).add(Expr::And([op1, op2])); 109 | return get_id_u32(id); 110 | } 111 | } 112 | 113 | #[no_mangle] 114 | pub extern "C" fn EGraphAddOr(egraph: *mut EEGraph, a: u32, b: u32) -> u32 { 115 | unsafe { 116 | let op1 = Id::from(a as usize); 117 | let op2 = Id::from(b as usize); 118 | let id = (*egraph).add(Expr::Or([op1, op2])); 119 | return get_id_u32(id); 120 | } 121 | } 122 | 123 | #[no_mangle] 124 | pub extern "C" fn EGraphAddXor(egraph: *mut EEGraph, a: u32, b: u32) -> u32 { 125 | unsafe { 126 | let op1 = Id::from(a as usize); 127 | let op2 = Id::from(b as usize); 128 | let id = (*egraph).add(Expr::Xor([op1, op2])); 129 | return get_id_u32(id); 130 | } 131 | } 132 | 133 | #[no_mangle] 134 | pub extern "C" fn EGraphAddNeg(egraph: *mut EEGraph, a: u32) -> u32 { 135 | unsafe { 136 | let op1 = Id::from(a as usize); 137 | let id = (*egraph).add(Expr::Neg([op1])); 138 | return get_id_u32(id); 139 | } 140 | } 141 | 142 | #[no_mangle] 143 | pub extern "C" fn EGraphAddAshr(egraph: *mut EEGraph, a: u32, b: u32) -> u32 { 144 | unsafe { 145 | let op1 = Id::from(a as usize); 146 | let op2 = Id::from(b as usize); 147 | let id = (*egraph).add(Expr::Ashr([op1, op2])); 148 | return get_id_u32(id); 149 | } 150 | } 151 | 152 | #[no_mangle] 153 | pub extern "C" fn EGraphAddLshr(egraph: *mut EEGraph, a: u32, b: u32) -> u32 { 154 | unsafe { 155 | let op1 = Id::from(a as usize); 156 | let op2 = Id::from(b as usize); 157 | let id = (*egraph).add(Expr::Lshr([op1, op2])); 158 | return get_id_u32(id); 159 | } 160 | } 161 | 162 | #[no_mangle] 163 | pub extern "C" fn EGraphAddShl(egraph: *mut EEGraph, a: u32, b: u32) -> u32 { 164 | unsafe { 165 | let op1 = Id::from(a as usize); 166 | let op2 = Id::from(b as usize); 167 | let id = (*egraph).add(Expr::Shl([op1, op2])); 168 | return get_id_u32(id); 169 | } 170 | } 171 | 172 | #[no_mangle] 173 | pub extern "C" fn RecExprGetChildCount(expr: *const RecExpr) -> usize { 174 | unsafe { 175 | return (*expr).as_ref().len(); 176 | } 177 | } 178 | 179 | #[no_mangle] 180 | pub extern "C" fn RecExprGetChild(expr: *const RecExpr, index: usize) -> *const Expr { 181 | let expr2 = unsafe { &*expr }; 182 | 183 | let child = &(expr2.as_ref()[index]); 184 | 185 | return Box::into_raw(Box::new((*child).clone())); 186 | } 187 | 188 | #[no_mangle] 189 | pub extern "C" fn RecExprGetIsDag(expr: *const RecExpr) -> bool { 190 | let expr2 = &unsafe { &*expr }; 191 | 192 | return expr2.is_dag(); 193 | } 194 | 195 | #[no_mangle] 196 | pub extern "C" fn ExprGetChildrenCount(expr: *const Expr) -> usize { 197 | let expr2 = &unsafe { &*expr }; 198 | 199 | return expr2.children().len(); 200 | } 201 | 202 | #[no_mangle] 203 | // The behavior is really strange here, see the rust implementation of "to_sexp_rec". 204 | // The index returned here is actually an index into the parent RecExpr's node list. 205 | pub extern "C" fn ExprGetChildIndex(expr: *const Expr, index: usize) -> usize { 206 | let expr2 = &unsafe { &*expr }; 207 | 208 | return usize::from(expr2.children()[index]); 209 | } 210 | 211 | #[no_mangle] 212 | pub extern "C" fn ExprIsAdd(expr: *const Expr) -> bool { 213 | let expr2 = unsafe { &*expr }; 214 | 215 | return match expr2 { 216 | Expr::Add(_) => true, 217 | _ => false, 218 | }; 219 | } 220 | 221 | #[no_mangle] 222 | pub extern "C" fn ExprIsMul(expr: *const Expr) -> bool { 223 | let expr2 = unsafe { &*expr }; 224 | 225 | return match *expr2 { 226 | Expr::Mul(_) => true, 227 | _ => false, 228 | }; 229 | } 230 | 231 | #[no_mangle] 232 | pub extern "C" fn ExprIsPow(expr: *const Expr) -> bool { 233 | let expr2 = unsafe { &*expr }; 234 | 235 | return match *expr2 { 236 | Expr::Pow(_) => true, 237 | _ => false, 238 | }; 239 | } 240 | 241 | #[no_mangle] 242 | pub extern "C" fn ExprIsAnd(expr: *const Expr) -> bool { 243 | let expr2 = unsafe { &*expr }; 244 | 245 | return match expr2 { 246 | Expr::And(_) => true, 247 | _ => false, 248 | }; 249 | } 250 | 251 | #[no_mangle] 252 | pub extern "C" fn ExprIsOr(expr: *const Expr) -> bool { 253 | unsafe { 254 | return matches!(*expr, Expr::Or(_)); 255 | }; 256 | } 257 | 258 | #[no_mangle] 259 | pub extern "C" fn ExprIsXor(expr: *const Expr) -> bool { 260 | let expr2 = &unsafe { &*expr }; 261 | 262 | match expr2 { 263 | Expr::Xor(_) => true, 264 | _ => false, 265 | } 266 | } 267 | 268 | #[no_mangle] 269 | pub extern "C" fn ExprIsNeg(expr: *const Expr) -> bool { 270 | let expr2 = unsafe { (*(expr)).clone() }; 271 | 272 | let is_bool: bool = match expr2 { 273 | Expr::Neg(_) => true, 274 | _ => false, 275 | }; 276 | 277 | return is_bool; 278 | } 279 | 280 | #[no_mangle] 281 | pub extern "C" fn ExprIsAshr(expr: *const Expr) -> bool { 282 | let expr2 = &unsafe { &*expr }; 283 | 284 | return match expr2 { 285 | Expr::Ashr(_) => true, 286 | _ => false, 287 | }; 288 | } 289 | 290 | #[no_mangle] 291 | pub extern "C" fn ExprIsLshr(expr: *const Expr) -> bool { 292 | let expr2 = &unsafe { &*expr }; 293 | 294 | return match expr2 { 295 | Expr::Lshr(_) => true, 296 | _ => false, 297 | }; 298 | } 299 | 300 | #[no_mangle] 301 | pub extern "C" fn ExprIsShl(expr: *const Expr) -> bool { 302 | let expr2 = &unsafe { &*expr }; 303 | 304 | return match expr2 { 305 | Expr::Shl(_) => true, 306 | _ => false, 307 | }; 308 | } 309 | 310 | #[no_mangle] 311 | pub extern "C" fn ExprIsConstant(expr: *const Expr) -> bool { 312 | let expr2 = &unsafe { &*expr }; 313 | 314 | return match expr2 { 315 | Expr::Constant(_) => true, 316 | _ => false, 317 | }; 318 | } 319 | 320 | #[no_mangle] 321 | pub extern "C" fn ExprGetConstantValue(expr: *const Expr) -> u64 { 322 | let expr2 = &unsafe { &*expr }; 323 | 324 | return match expr2 { 325 | Expr::Constant(value) => (*value) as u128 as u64, 326 | _ => panic!("Not a constant!"), 327 | }; 328 | } 329 | 330 | #[no_mangle] 331 | pub extern "C" fn ExprIsSymbol(expr: *const Expr) -> bool { 332 | let expr2 = &unsafe { &*expr }; 333 | 334 | return match expr2 { 335 | Expr::Symbol(_) => true, 336 | _ => false, 337 | }; 338 | } 339 | 340 | #[no_mangle] 341 | pub extern "C" fn ExprGetSymbolName(expr: *const Expr) -> *mut c_char { 342 | let expr2 = &unsafe { &*expr }; 343 | 344 | return match expr2 { 345 | Expr::Symbol(value) => CString::new(value.to_string()).unwrap().into_raw(), 346 | _ => panic!("Not a symbol!"), 347 | }; 348 | } 349 | 350 | // Cast an egg 'Id' instance to a u32. 351 | pub fn get_id_u32(id: Id) -> u32 { 352 | let id: usize = id.into(); 353 | 354 | return id as u32; 355 | } 356 | -------------------------------------------------------------------------------- /EqSat/egraph/src/ffi_utils.rs: -------------------------------------------------------------------------------- 1 | use libc::c_char; 2 | use std::ffi::{CStr, CString}; 3 | 4 | pub fn marshal_string(s: *const c_char) -> String { 5 | let c_str = unsafe { 6 | assert!(!s.is_null()); 7 | 8 | CStr::from_ptr(s) 9 | }; 10 | 11 | return c_str.to_str().unwrap().to_string(); 12 | } 13 | -------------------------------------------------------------------------------- /EqSat/egraph/src/main.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | analysis::MbaAnalysis, 3 | cost::EGraphCostFn, 4 | rules::make_simplification_rules, 5 | time::{Duration, Instant}, 6 | }; 7 | 8 | use egg::*; 9 | use expr::Expr; 10 | use std::time; 11 | 12 | mod abstract_interpretation; 13 | mod analysis; 14 | mod classification; 15 | mod cost; 16 | mod expr; 17 | mod ffi_utils; 18 | mod rules; 19 | 20 | fn main() { 21 | let expr = read_expr_from_args(); 22 | println!("Attempting to simplify expression: {}", expr); 23 | 24 | let mut string = expr.as_str().to_owned(); 25 | for i in 0..10 { 26 | let num = 1000; 27 | println!("deobfuscating"); 28 | string = simplify_via_eqsat(string.clone().as_ref(), Some(num)).clone(); 29 | } 30 | 31 | println!("Final result: {}", string); 32 | } 33 | 34 | fn read_expr_from_args() -> String { 35 | let mut args = std::env::args().skip(1); 36 | 37 | if let Some(next) = args.next() { 38 | next 39 | } else { 40 | std::fs::read_to_string("test-input.txt").unwrap() 41 | } 42 | } 43 | 44 | /// parse an expression, simplify it using egg, and pretty print it back out 45 | pub fn simplify_via_eqsat(s: &str, milliseconds: Option) -> String { 46 | let expr: RecExpr = s.parse().unwrap(); 47 | 48 | let ms = if milliseconds.is_some() { 49 | milliseconds.unwrap() 50 | } else { 51 | 500 52 | }; 53 | // Create the runner. You can enable explain_equivalence to explain the equivalence, 54 | // but it comes at a severe performance penalty. 55 | let explain_equivalence = false; 56 | let mut runner: Runner = Runner::default() 57 | .with_time_limit(Duration::from_millis(ms)) 58 | .with_scheduler( 59 | BackoffScheduler::default() 60 | .with_ban_length(5) 61 | .with_initial_match_limit(1_000_00), 62 | ) 63 | .with_node_limit(1000000 * 10) 64 | .with_iter_limit(500 * 10); 65 | 66 | if explain_equivalence { 67 | runner = runner.with_explanations_enabled(); 68 | } 69 | 70 | runner = runner.with_expr(&expr); 71 | 72 | let rules = make_simplification_rules(); 73 | 74 | let start = Instant::now(); 75 | runner = runner.run(&rules); 76 | 77 | let root = runner.roots[0]; 78 | 79 | // use an Extractor to pick the best element of the root eclass 80 | let (best_cost, best) = if true { 81 | let mut cost_func = EGraphCostFn { 82 | egraph: &runner.egraph, 83 | }; 84 | 85 | let extractor = Extractor::new(&runner.egraph, cost_func); 86 | 87 | let res = extractor.find_best(root); 88 | 89 | res 90 | } else { 91 | let extractor = Extractor::new(&runner.egraph, AstSize); 92 | extractor.find_best(root) 93 | }; 94 | 95 | let best = best.to_string(); 96 | let duration = start.elapsed(); 97 | best 98 | } 99 | -------------------------------------------------------------------------------- /EqSat/egraph/src/rules/bitwise_power_of_two.rs: -------------------------------------------------------------------------------- 1 | use egg::*; 2 | 3 | use crate::{ 4 | analysis::{EEGraph, MbaAnalysis}, 5 | expr::Expr, 6 | }; 7 | 8 | use super::read_constant; 9 | 10 | #[derive(Debug)] 11 | pub struct BitwisePowerOfTwoFactorApplier { 12 | pub x_factor: &'static str, 13 | pub y_factor: &'static str, 14 | } 15 | 16 | // Given an AND, XOR, or OR, of `2*x&2*y`, where a power of two can be factored 17 | // out of its children, transform it into `2*(x&y)`. 18 | // TODO: As of right now this transform only works if the constant multiplier is a power of 2, 19 | // but it should work if any power of two can be factored out of the immediate multipliers. 20 | impl Applier for BitwisePowerOfTwoFactorApplier { 21 | fn apply_one( 22 | &self, 23 | egraph: &mut EEGraph, 24 | eclass: Id, 25 | subst: &Subst, 26 | _searcher_ast: Option<&PatternAst>, 27 | _rule_name: Symbol, 28 | ) -> Vec { 29 | // Get the eclass, expression, and of the expressions relating to X. 30 | let x_id = subst["?x".parse().unwrap()]; 31 | 32 | let x_factor_data = &egraph[subst[self.x_factor.parse().unwrap()]].data; 33 | let x_factor_constant = read_constant(x_factor_data).unwrap(); 34 | 35 | // Get the eclass, expression, and of the expressions relating to X. 36 | let y_id = subst["?y".parse().unwrap()]; 37 | 38 | let y_factor_data = &egraph[subst[self.y_factor.parse().unwrap()]].data; 39 | let y_factor_constant = read_constant(y_factor_data).unwrap(); 40 | 41 | let min = x_factor_constant.min(y_factor_constant); 42 | let min_id = egraph.add(Expr::Constant(min)); 43 | let max = x_factor_constant.max(y_factor_constant); 44 | 45 | // Here we're dealing with expressions like "4*x&4*y" and ""4*x&8*y", 46 | // where x_factor and y_factor are the constant multipliers. 47 | // If the constant multipliers are the same, then for example 48 | // we can factor `4*x&4*y` into `4*(x&y)`. 49 | let factored: Id = if min == max { 50 | // Create an egraph node for (x & y). 51 | let anded = egraph.add(Expr::And([x_id, y_id])); 52 | 53 | // Create an egraph node for factored_out_constant * (x & y); 54 | egraph.add(Expr::Mul([min_id, anded])) 55 | } 56 | // If the factors are not equal(e.g. "4*x&8*y"), then we need to factor 57 | // out only the minimum factor, giving us something like "4*(x&2*y)". 58 | else { 59 | let remaining_factor = egraph.add(Expr::Constant(max / min)); 60 | 61 | // If x has the large factor then the RHS becomes ((max/min) * x) & y; 62 | let rhs: Id = if x_factor_constant == max { 63 | let x_times_remaining_factor = egraph.add(Expr::Mul([remaining_factor, x_id])); 64 | egraph.add(Expr::And([x_times_remaining_factor, y_id])) 65 | // If y has the large factor then the RHS becomes ((max/min) * y) & x; 66 | } else { 67 | let y_times_remaining_factor = egraph.add(Expr::Mul([remaining_factor, y_id])); 68 | egraph.add(Expr::And([y_times_remaining_factor, x_id])) 69 | }; 70 | 71 | // Create the final expression of (min * factored_rhs); 72 | egraph.add(Expr::Mul([min_id, rhs])) 73 | }; 74 | 75 | if egraph.union(eclass, factored) { 76 | vec![factored] 77 | } else { 78 | vec![] 79 | } 80 | } 81 | } 82 | 83 | pub fn is_power_of_two(var: &str, var2: &str) -> impl Fn(&mut EEGraph, Id, &Subst) -> bool { 84 | let var = var.parse().unwrap(); 85 | let var2: Var = var2.parse().unwrap(); 86 | 87 | move |egraph, _, subst| { 88 | let v1 = if let Some(c) = read_constant(&egraph[subst[var]].data) { 89 | c & (c - 1) == 0 && c != 0 90 | } else { 91 | false 92 | }; 93 | 94 | let v2 = if let Some(c) = read_constant(&egraph[subst[var2]].data) { 95 | c & (c - 1) == 0 && c != 0 96 | } else { 97 | false 98 | }; 99 | 100 | v1 && v2 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /EqSat/egraph/src/rules/duplicate_children_mul_add.rs: -------------------------------------------------------------------------------- 1 | use egg::*; 2 | 3 | use crate::{ 4 | analysis::{EEGraph, MbaAnalysis}, 5 | expr::Expr, 6 | }; 7 | 8 | use super::{read_constant, read_known_bits}; 9 | 10 | #[derive(Debug)] 11 | pub struct DuplicateChildrenMulAddApplier { 12 | pub const_factor: &'static str, 13 | pub x_factor: &'static str, 14 | } 15 | 16 | impl Applier for DuplicateChildrenMulAddApplier { 17 | fn apply_one( 18 | &self, 19 | egraph: &mut EEGraph, 20 | eclass: Id, 21 | subst: &Subst, 22 | _searcher_ast: Option<&PatternAst>, 23 | _rule_name: Symbol, 24 | ) -> Vec { 25 | let new_const_expr = &egraph[subst[self.const_factor.parse().unwrap()]].data; 26 | 27 | let const_factor = read_constant(new_const_expr).unwrap(); 28 | 29 | let x = subst[self.x_factor.parse().unwrap()]; 30 | 31 | let new_const = egraph.add(Expr::Constant(const_factor + 1)); 32 | let new_expr = egraph.add(Expr::Mul([new_const, x])); 33 | 34 | if egraph.union(eclass, new_expr) { 35 | vec![new_expr] 36 | } else { 37 | vec![] 38 | } 39 | } 40 | } 41 | 42 | pub fn is_const(var: &str) -> impl Fn(&mut EEGraph, Id, &Subst) -> bool { 43 | let var = var.parse().unwrap(); 44 | 45 | move |egraph, _, subst| read_constant(&egraph[subst[var]].data).is_some() 46 | } 47 | 48 | pub fn are_const(var1: &str, var2: &str) -> impl Fn(&mut EEGraph, Id, &Subst) -> bool { 49 | let var1 = var1.parse().unwrap(); 50 | let var2 = var2.parse().unwrap(); 51 | move |egraph, _, subst| { 52 | read_constant(&egraph[subst[var1]].data).is_some() 53 | && read_constant(&egraph[subst[var2]].data).is_some() 54 | } 55 | } 56 | 57 | pub fn are_minus_const(var1: &str, var2: &str) -> impl Fn(&mut EEGraph, Id, &Subst) -> bool { 58 | let var1 = var1.parse().unwrap(); 59 | let var2 = var2.parse().unwrap(); 60 | move |egraph, _, subst| { 61 | let c1 = read_constant(&egraph[subst[var1]].data); 62 | let c2 = read_constant(&egraph[subst[var2]].data); 63 | if (c1.is_none() || c2.is_none()) { 64 | return false; 65 | } 66 | 67 | return c2.unwrap() == -c1.unwrap(); 68 | } 69 | } 70 | 71 | pub fn are_disjoint_const(var1: &str, var2: &str) -> impl Fn(&mut EEGraph, Id, &Subst) -> bool { 72 | let var1 = var1.parse().unwrap(); 73 | let var2 = var2.parse().unwrap(); 74 | move |egraph, _, subst| { 75 | let c1 = read_constant(&egraph[subst[var1]].data); 76 | let c2 = read_constant(&egraph[subst[var2]].data); 77 | if (c1.is_none() || c2.is_none()) { 78 | return false; 79 | } 80 | 81 | return (c1.unwrap() & c2.unwrap()) == 0; 82 | } 83 | } 84 | 85 | pub fn are_disjoint_known_bits( 86 | var1: &str, 87 | var2: &str, 88 | ) -> impl Fn(&mut EEGraph, Id, &Subst) -> bool { 89 | let var1 = var1.parse().unwrap(); 90 | let var2 = var2.parse().unwrap(); 91 | move |egraph, _, subst| { 92 | let c1 = read_known_bits(&egraph[subst[var1]].data); 93 | let c2 = read_known_bits(&egraph[subst[var2]].data); 94 | 95 | // Return if c1 and c2 are disjoint 96 | return c1.no_common_bits_set(c2); 97 | } 98 | } 99 | 100 | pub fn are_subset_known_bits(var1: &str, var2: &str) -> impl Fn(&mut EEGraph, Id, &Subst) -> bool { 101 | let var1 = var1.parse().unwrap(); 102 | let var2 = var2.parse().unwrap(); 103 | move |egraph, _, subst| { 104 | let c1 = read_known_bits(&egraph[subst[var1]].data); 105 | let c2 = read_known_bits(&egraph[subst[var2]].data); 106 | 107 | // Return if c1 and c2 are disjoint 108 | return c1.is_subset_of_this(c2); 109 | } 110 | } 111 | 112 | pub fn are_negated_const(var1: &str, var2: &str) -> impl Fn(&mut EEGraph, Id, &Subst) -> bool { 113 | let var1 = var1.parse().unwrap(); 114 | let var2 = var2.parse().unwrap(); 115 | move |egraph, _, subst| { 116 | let c1 = read_constant(&egraph[subst[var1]].data); 117 | let c2 = read_constant(&egraph[subst[var2]].data); 118 | if (c1.is_none() || c2.is_none()) { 119 | return false; 120 | } 121 | 122 | let c1 = c1.unwrap(); 123 | let c2 = c2.unwrap(); 124 | let negated = (((!c1) as u64) as u128) as i128; 125 | return c2 == negated; 126 | } 127 | } 128 | 129 | pub fn const_a_contains_b(var1: &str, var2: &str) -> impl Fn(&mut EEGraph, Id, &Subst) -> bool { 130 | let var1 = var1.parse().unwrap(); 131 | let var2 = var2.parse().unwrap(); 132 | move |egraph, _, subst| { 133 | let c1 = read_constant(&egraph[subst[var1]].data); 134 | let c2 = read_constant(&egraph[subst[var2]].data); 135 | if (c1.is_none() || c2.is_none()) { 136 | return false; 137 | } 138 | 139 | return (c1.unwrap() & c2.unwrap()) == c2.unwrap(); 140 | } 141 | } 142 | 143 | pub fn is_negative_const(var: &str) -> impl Fn(&mut EEGraph, Id, &Subst) -> bool { 144 | let var = var.parse().unwrap(); 145 | 146 | move |egraph, _, subst| { 147 | let maybe_const = read_constant(&egraph[subst[var]].data); 148 | if maybe_const.is_none() { 149 | return false; 150 | } 151 | 152 | let constant = maybe_const.unwrap(); 153 | if (constant.is_negative()) { 154 | return true; 155 | } 156 | 157 | return false; 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /EqSat/egraph/src/rules/factor_integer_gcd.rs: -------------------------------------------------------------------------------- 1 | use egg::*; 2 | 3 | use crate::{ 4 | analysis::{EEGraph, MbaAnalysis}, 5 | Expr, 6 | }; 7 | 8 | use super::read_constant; 9 | 10 | #[derive(Debug)] 11 | pub struct FactorIntegerGcdApplier { 12 | pub x_factor: &'static str, 13 | pub y_factor: &'static str, 14 | } 15 | 16 | // Given (const_1 * x) + (const_2 * y), try to factor out the greatest common constant denominator. 17 | // E.g. (9 * x) + (-9 * y) becomes 9 * (x + -y) 18 | // E.g. (3 * x) + (9 * y) becomes 3 * (x + 9y) 19 | impl Applier for FactorIntegerGcdApplier { 20 | fn apply_one( 21 | &self, 22 | egraph: &mut EEGraph, 23 | eclass: Id, 24 | subst: &Subst, 25 | _searcher_ast: Option<&PatternAst>, 26 | _rule_name: Symbol, 27 | ) -> Vec { 28 | // Get the eclass, expression, and of the expressions relating to X. 29 | let x_id = subst["?x".parse().unwrap()]; 30 | 31 | let x_factor_data = &egraph[subst[self.x_factor.parse().unwrap()]].data; 32 | let x_factor_constant = read_constant(x_factor_data).unwrap(); 33 | 34 | // Get the eclass, expression, and of the expressions relating to X. 35 | let y_id = subst["?y".parse().unwrap()]; 36 | let y_factor_data = &egraph[subst[self.y_factor.parse().unwrap()]].data; 37 | let y_factor_constant = read_constant(y_factor_data).unwrap(); 38 | 39 | // Compute the GCD. 40 | let gcd = gcd_of_two_numbers(x_factor_constant, y_factor_constant); 41 | let gcd_id = egraph.add(Expr::Constant(gcd)); 42 | 43 | // Compute the factored expression. 44 | let lhs = without_gcd(egraph, x_id, x_factor_constant, gcd); 45 | let rhs = without_gcd(egraph, y_id, y_factor_constant, gcd); 46 | let added = egraph.add(Expr::Add([lhs, rhs])); 47 | let factored = egraph.add(Expr::Mul([gcd_id, added])); 48 | 49 | if egraph.union(eclass, factored) { 50 | vec![factored] 51 | } else { 52 | vec![] 53 | } 54 | } 55 | } 56 | 57 | fn without_gcd(egraph: &mut EEGraph, x: Id, x_factor_constant: i128, gcd: i128) -> egg::Id { 58 | let factor_without_gcd = x_factor_constant / gcd; 59 | // If we've completely factored out the constant then remove it. 60 | if factor_without_gcd == 1 { 61 | return x; 62 | // Otherwise we couldn't factor out the constant completely. 63 | } else { 64 | let without_gcd_id = egraph.add(Expr::Constant(factor_without_gcd)); 65 | return egraph.add(Expr::Mul([x, without_gcd_id])); 66 | } 67 | } 68 | 69 | pub fn has_significant_gcd(var: &str, var2: &str) -> impl Fn(&mut EEGraph, Id, &Subst) -> bool { 70 | let var = var.parse().unwrap(); 71 | let var2: Var = var2.parse().unwrap(); 72 | 73 | move |egraph, _, subst| { 74 | let v1 = read_constant(&egraph[subst[var]].data); 75 | let v2 = read_constant(&egraph[subst[var2]].data); 76 | if v1.is_none() || v2.is_none() { 77 | return false; 78 | } 79 | if (v1.unwrap() == 0 || v2.unwrap() == 0) { 80 | return false; 81 | } 82 | 83 | let minus_one = 18446744073709551615; 84 | if (v1.unwrap() == minus_one || v2.unwrap() == minus_one) { 85 | return false; 86 | } 87 | 88 | let gcd = gcd_of_two_numbers(v1.unwrap(), v2.unwrap()); 89 | let result = gcd != 0 && gcd != 1 && gcd != minus_one; 90 | if (gcd == 2) { 91 | return false; 92 | } 93 | 94 | return result; 95 | } 96 | } 97 | 98 | /// returns the greatest common divisor of n numbers 99 | pub fn gcd(nums: &[i128]) -> i128 { 100 | if nums.len() == 1 { 101 | return nums[0]; 102 | } 103 | let a = nums[0]; 104 | let b = gcd(&nums[1..]); 105 | gcd_of_two_numbers(a, b) 106 | } 107 | 108 | fn gcd_of_two_numbers(a: i128, b: i128) -> i128 { 109 | if b == 0 { 110 | return a; 111 | } 112 | gcd_of_two_numbers(b, a % b) 113 | } 114 | -------------------------------------------------------------------------------- /EqSat/egraph/src/rules/rewrite_power.rs: -------------------------------------------------------------------------------- 1 | use egg::*; 2 | 3 | use crate::{ 4 | analysis::{EEGraph, MbaAnalysis}, 5 | Expr, 6 | }; 7 | 8 | use super::read_constant; 9 | 10 | #[derive(Debug)] 11 | pub struct RewritePowerApplier { 12 | pub a: Var, 13 | pub b: Var, 14 | pub exponent: Var, 15 | } 16 | 17 | impl Applier for RewritePowerApplier { 18 | fn apply_one( 19 | &self, 20 | egraph: &mut EEGraph, 21 | eclass: Id, 22 | subst: &Subst, 23 | _searcher_ast: Option<&PatternAst>, 24 | _rule_name: Symbol, 25 | ) -> Vec { 26 | let exponent_id = subst[self.exponent]; 27 | let exponent_data = &egraph[exponent_id].data; 28 | let exponent_constant = read_constant(exponent_data).unwrap(); 29 | 30 | let a_id = subst[self.a]; 31 | let a_data = &egraph[a_id].data; 32 | let a_constant = read_constant(a_data).unwrap(); 33 | 34 | let b_id = subst[self.b]; 35 | 36 | let const_value = if let Ok(exponent_constant) = u32::try_from(exponent_constant) { 37 | a_constant.pow(exponent_constant) 38 | } else { 39 | return Vec::new(); 40 | }; 41 | 42 | let const_id = egraph.add(Expr::Constant(const_value)); 43 | let id = egraph.add(Expr::Pow([b_id, exponent_id])); 44 | let new_expr = egraph.add(Expr::Mul([id, const_id])); 45 | 46 | if egraph.union(eclass, new_expr) { 47 | vec![new_expr] 48 | } else { 49 | vec![] 50 | } 51 | } 52 | } 53 | 54 | pub fn can_rewrite_power( 55 | a: &'static str, 56 | exponent: &'static str, 57 | ) -> impl Fn(&mut EEGraph, Id, &Subst) -> bool { 58 | let a = a.parse().unwrap(); 59 | let b = "?b".parse().unwrap(); 60 | let exponent = exponent.parse().unwrap(); 61 | 62 | move |egraph, _, subst| { 63 | let a = &egraph[subst[a]].data; 64 | let b = &egraph[subst[b]].data; 65 | let exponent = &egraph[subst[exponent]].data; 66 | read_constant(a).is_some() 67 | && read_constant(b).is_none() 68 | && read_constant(exponent).is_some() 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /EqSat/egraph/target/.rustc_info.json: -------------------------------------------------------------------------------- 1 | {"rustc_fingerprint":8459807527357223545,"outputs":{"15729799797837862367":{"success":true,"status":"","code":0,"stdout":"___.exe\nlib___.rlib\n___.dll\n___.dll\n___.lib\n___.dll\nC:\\Users\\colton\\.rustup\\toolchains\\stable-x86_64-pc-windows-msvc\npacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"msvc\"\ntarget_family=\"windows\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"windows\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"pc\"\nwindows\n","stderr":""},"4614504638168534921":{"success":true,"status":"","code":0,"stdout":"rustc 1.74.0 (79e9716c9 2023-11-13)\nbinary: rustc\ncommit-hash: 79e9716c980570bfd1f666e3b16ac583f0168962\ncommit-date: 2023-11-13\nhost: x86_64-pc-windows-msvc\nrelease: 1.74.0\nLLVM version: 17.0.4\n","stderr":""}},"successes":{}} -------------------------------------------------------------------------------- /EqSat/egraph/target/CACHEDIR.TAG: -------------------------------------------------------------------------------- 1 | Signature: 8a477f597d28d172789f06886806bc55 2 | # This file is a cache directory tag created by cargo. 3 | # For information about cache directory tags see https://bford.info/cachedir/ 4 | -------------------------------------------------------------------------------- /EqSat/egraph/test-input.txt: -------------------------------------------------------------------------------- 1 | (+ 1168 (+ (+ (* -32 (& 18 a3)) (* 19 (& 32 a3))) (* 6 (& 64 a3)))) -------------------------------------------------------------------------------- /EqSat/src/main.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | 3 | use egraph::simplify_via_eqsat; 4 | use libc::c_char; 5 | use std::{ 6 | collections::HashMap, 7 | ffi::{CStr, CString}, 8 | fs::{self, File}, 9 | io::{BufRead, BufReader}, 10 | path::Path, 11 | time::Instant, 12 | }; 13 | 14 | use crate::{ 15 | mba::Context as MbaContext, 16 | simple_ast::{recursive_simplify, Arena, AstPrinter, Context as Ctx}, 17 | truth_table_database::TruthTableDatabase, 18 | }; 19 | 20 | use egg::*; 21 | use simple_ast::{marshal_string, AstData, AstIdx, SimpleAst}; 22 | 23 | use mimalloc::MiMalloc; 24 | 25 | #[global_allocator] 26 | static GLOBAL: MiMalloc = MiMalloc; 27 | 28 | mod mba; 29 | mod simple_ast; 30 | mod truth_table_database; 31 | 32 | #[no_mangle] 33 | pub extern "C" fn SimplifyViaEqsat(s: *const c_char, ms: u64) -> *mut c_char { 34 | let str = marshal_string(s).to_owned(); 35 | 36 | let res = simplify_via_eqsat(&str, Some(ms)); 37 | unsafe { 38 | return CString::new(res).unwrap().into_raw(); 39 | } 40 | 41 | println!(" "); 42 | } 43 | 44 | fn read_expr_from_args() -> String { 45 | let mut args = std::env::args().skip(1); 46 | 47 | if let Some(next) = args.next() { 48 | next 49 | } else { 50 | std::fs::read_to_string("test-input.txt").unwrap() 51 | } 52 | } 53 | 54 | fn main() {} 55 | -------------------------------------------------------------------------------- /EqSat/src/truth_table_database.rs: -------------------------------------------------------------------------------- 1 | use core::num; 2 | use std::arch::x86_64::_popcnt64; 3 | use std::ffi::CStr; 4 | use std::fs::{self, File}; 5 | use std::io::{BufRead, BufReader, Read}; 6 | use std::mem::MaybeUninit; 7 | use std::os::raw::{c_char, c_uint, c_void}; 8 | use std::path::Path; 9 | 10 | use crate::simple_ast::{AstIdx, AstPrinter, Context, SimpleAst}; 11 | 12 | pub struct TruthTableDatabase { 13 | two_var_truth_table: Vec, 14 | three_var_truth_table: Vec, 15 | four_var_truth_table: Vec, 16 | } 17 | 18 | pub struct TruthTable { 19 | pub num_vars: u32, 20 | pub arr: *mut u64, 21 | } 22 | 23 | impl TruthTable { 24 | pub fn get_num_bits(&self) -> u32 { 25 | return 1 << self.num_vars; 26 | } 27 | 28 | pub fn get_num_words(&self) -> usize { 29 | let num_bits = self.get_num_bits(); 30 | if num_bits <= 64 { 31 | return 1; 32 | } else { 33 | return (num_bits >> 6) as usize; 34 | } 35 | } 36 | 37 | pub fn get_mut_arr(&self) -> &mut [u64] { 38 | unsafe { 39 | return std::slice::from_raw_parts_mut(self.arr, self.get_num_words()); 40 | } 41 | } 42 | 43 | pub fn get_bit(&self, safe_arr: &mut [u64], index: u32) -> u8 { 44 | let word_idx = index >> 6; 45 | let bit_idx = index - (64 * word_idx); 46 | unsafe { 47 | let word = safe_arr.get_unchecked(word_idx as usize) >> bit_idx; 48 | return (word & 1) as u8; 49 | } 50 | } 51 | 52 | pub fn set_bit(&self, safe_arr: &mut [u64], index: u32, value: u8) { 53 | let word_idx = index >> 6; 54 | let bit_idx = index - (64 * word_idx); 55 | 56 | unsafe { 57 | let word = safe_arr.get_unchecked_mut(word_idx as usize); 58 | *word &= !(1 << bit_idx); 59 | *word |= (value as u64) << bit_idx; 60 | } 61 | } 62 | 63 | pub fn negate(&self) { 64 | let arr = self.get_mut_arr(); 65 | for i in 0..self.get_num_bits() { 66 | let bit = self.get_bit(arr, i); 67 | self.set_bit(arr, i, bit ^ 1); 68 | } 69 | } 70 | } 71 | 72 | // Two, three, and four variable boolean truth table utility. 73 | impl TruthTableDatabase { 74 | pub fn new() -> Self { 75 | let mut two_var_tt: Vec = Vec::new(); 76 | Self::load_truth_table_from_bin(2, &mut two_var_tt); 77 | 78 | let mut three_var_tt: Vec = Vec::new(); 79 | Self::load_truth_table_from_bin(3, &mut three_var_tt); 80 | 81 | let mut four_var_tt: Vec = Vec::new(); 82 | Self::load_truth_table_from_bin(4, &mut four_var_tt); 83 | 84 | return TruthTableDatabase { 85 | two_var_truth_table: two_var_tt, 86 | three_var_truth_table: three_var_tt, 87 | four_var_truth_table: four_var_tt, 88 | }; 89 | } 90 | 91 | fn load_truth_table_from_bin(num_vars: u64, output: &mut Vec) { 92 | let path = format!( 93 | "Minimization\\TruthTables\\{}variable_truthtable.bc", 94 | num_vars 95 | ); 96 | let bytes = Self::get_file_as_byte_vec(&path, output); 97 | } 98 | 99 | fn get_file_as_byte_vec(filename: &String, buffer: &mut Vec) { 100 | let mut f = File::open(&filename).expect("no file found"); 101 | let metadata = fs::metadata(&filename).expect("unable to read metadata"); 102 | f.read_to_end(buffer).expect("buffer overflow"); 103 | } 104 | 105 | pub fn get_truth_table_entry( 106 | db: &mut TruthTableDatabase, 107 | ctx: &mut Context, 108 | var_count: u32, 109 | vars: *const AstIdx, 110 | idx: usize, 111 | ) -> AstIdx { 112 | let table = match var_count { 113 | 2 => &db.two_var_truth_table, 114 | 3 => &db.three_var_truth_table, 115 | 4 => &db.four_var_truth_table, 116 | _ => panic!("truth table database only supports 2, 3, or 4 variables!"), 117 | }; 118 | 119 | // Compute the offset into the table. 120 | let offset_idx = 8 * idx; 121 | let constant = decode_u32(table, offset_idx); 122 | 123 | let mut i = constant as usize; 124 | Self::parse_binary_boolean_func(ctx, vars, table, i) 125 | } 126 | 127 | fn parse_binary_boolean_func( 128 | ctx: &mut Context, 129 | vars: *const AstIdx, 130 | bytes: &Vec, 131 | start: usize, 132 | ) -> AstIdx { 133 | let mut offset = start; 134 | let this_ctx = ctx; 135 | let opcode = *bytes.get(start).unwrap(); 136 | offset += 4; 137 | 138 | match opcode { 139 | // Symbol 140 | 2 => { 141 | let var_index = *bytes.get(offset).unwrap(); 142 | unsafe { *vars.add(var_index as usize) } 143 | } 144 | 145 | 8 | 9 | 10 => { 146 | let a_offset = decode_u32(bytes, offset); 147 | offset += 4; 148 | let b_offset = decode_u32(bytes, offset); 149 | 150 | let a = Self::parse_binary_boolean_func(this_ctx, vars, bytes, a_offset as usize); 151 | let b = Self::parse_binary_boolean_func(this_ctx, vars, bytes, b_offset as usize); 152 | 153 | match opcode { 154 | 8 => this_ctx.arena.and(a, b), 155 | 9 => this_ctx.arena.or(a, b), 156 | 10 => this_ctx.arena.xor(a, b), 157 | _ => panic!("Not a valid boolean!"), 158 | } 159 | } 160 | 161 | 11 => { 162 | let a_offset = decode_u32(bytes, offset); 163 | let a = Self::parse_binary_boolean_func(this_ctx, vars, bytes, a_offset as usize); 164 | this_ctx.arena.neg(a) 165 | } 166 | 167 | _ => panic!("Unsupported numeric opcode {}!", opcode), 168 | } 169 | } 170 | 171 | pub fn get_boolean_cost(db: &mut TruthTableDatabase, var_count: u32, idx: usize) -> u32 { 172 | let table = match var_count { 173 | 2 => &db.two_var_truth_table, 174 | 3 => &db.three_var_truth_table, 175 | 4 => &db.four_var_truth_table, 176 | _ => panic!("truth table database only supports 2, 3, or 4 variables!"), 177 | }; 178 | 179 | // Each boolean has a "header" consisting of (u32 file_offset, u32 cost). 180 | // Here we dereference the cost. 181 | let offset_idx = (8 * idx) + 4; 182 | let constant = decode_u32(table, offset_idx); 183 | 184 | constant 185 | } 186 | } 187 | 188 | fn decode_u32(bytes: &Vec, offset: usize) -> u32 { 189 | let mut offset_idx = offset; 190 | let mut constant: u32 = 0; 191 | for c in 0..4 { 192 | let byte = *bytes.get(offset_idx).unwrap(); 193 | offset_idx += 1; 194 | 195 | constant |= ((byte as u32) << (c * 8)); 196 | } 197 | 198 | constant 199 | } 200 | -------------------------------------------------------------------------------- /EqSat/test-input.txt: -------------------------------------------------------------------------------- 1 | (+ 1168 (+ (+ (* -32 (& 18 a3)) (* 19 (& 32 a3))) (* 6 (& 64 a3)))) -------------------------------------------------------------------------------- /Mba.Simplifier/Bindings/AstIdx.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Mba.Simplifier.Bindings 8 | { 9 | public struct AstIdx 10 | { 11 | public uint Idx; 12 | 13 | public AstIdx(uint idx) 14 | { 15 | Idx = idx; 16 | } 17 | 18 | public override string ToString() 19 | { 20 | if (ctx == null) 21 | return Idx.ToString(); 22 | return ctx.GetAstString(Idx); 23 | } 24 | 25 | public unsafe static implicit operator uint(AstIdx reg) => reg.Idx; 26 | 27 | public unsafe static implicit operator AstIdx(uint reg) => new AstIdx(reg); 28 | 29 | // This is a hack to allow viewing AST nodes in the debugger. 30 | public static AstCtx ctx; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Mba.Simplifier/Bindings/EqualitySaturation.cs: -------------------------------------------------------------------------------- 1 | using Mba.Ast; 2 | using Mba.Common.Interop; 3 | using Mba.Common.Utility; 4 | using Mba.Interop; 5 | using Mba.Parsing; 6 | using Mba.Utility; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Runtime.InteropServices; 11 | using System.Text; 12 | using System.Threading.Tasks; 13 | 14 | namespace Mba.Simplifier.Bindings 15 | { 16 | public static class EqualitySaturation 17 | { 18 | public static unsafe AstNode Run(AstNode input, ulong ms) 19 | { 20 | var str = EggFormatter.FormatAst(input); 21 | var outStr = StringMarshaler.AcquireString(Api.SimplifyViaEqsat(new MarshaledString(str), ms)); 22 | var output = EggExpressionParser.Parse(outStr, input.BitSize); 23 | return output; 24 | } 25 | 26 | protected static class Api 27 | { 28 | [DllImport("eq_sat")] 29 | public unsafe static extern sbyte* SimplifyViaEqsat(sbyte* pStr, ulong ms); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Mba.Simplifier/Bindings/TruthTableDb.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.InteropServices; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using static Mba.Simplifier.Bindings.AstCtx; 8 | 9 | namespace Mba.Simplifier.Bindings 10 | { 11 | public struct OpaqueTruthTableDb { } 12 | 13 | public class TruthTableDb 14 | { 15 | private nint handle; 16 | 17 | public TruthTableDb(nint handle) 18 | { 19 | this.handle = handle; 20 | } 21 | 22 | public unsafe TruthTableDb() 23 | { 24 | handle = (nint)Api.CreateTruthTableDb(); 25 | } 26 | 27 | public unsafe AstIdx GetBoolean(AstCtx ctx, uint varCount, List vars, ulong idx) 28 | { 29 | var span = CollectionsMarshal.AsSpan(vars); 30 | fixed (AstIdx* arrPtr = &span[0]) 31 | { 32 | return Api.GetTruthTableDbEntry(this, ctx, varCount, arrPtr, idx); 33 | } 34 | } 35 | 36 | public unsafe uint GetBooleanCost(uint varCount, ulong idx) 37 | { 38 | return Api.GetTruthTableDbEntryCost(this,varCount, idx); 39 | } 40 | 41 | public unsafe static implicit operator OpaqueTruthTableDb*(TruthTableDb ctx) => (OpaqueTruthTableDb*)ctx.handle; 42 | 43 | public unsafe static implicit operator TruthTableDb(OpaqueTruthTableDb* ctx) => new TruthTableDb((nint)ctx); 44 | 45 | protected static class Api 46 | { 47 | [DllImport("eq_sat")] 48 | public unsafe static extern OpaqueTruthTableDb* CreateTruthTableDb(); 49 | 50 | [DllImport("eq_sat")] 51 | public unsafe static extern AstIdx GetTruthTableDbEntry(OpaqueTruthTableDb* db, OpaqueAstCtx* ctx, uint varCount, AstIdx* variableArray, ulong idx); 52 | 53 | [DllImport("eq_sat")] 54 | public unsafe static extern uint GetTruthTableDbEntryCost(OpaqueTruthTableDb* db, uint varCount, ulong idx); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Mba.Simplifier/LinEq/LinearEquation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Mba.Simplifier.LinEq 8 | { 9 | public class LinearEquation 10 | { 11 | public ulong[] coeffs; 12 | 13 | public ulong result = 0; 14 | 15 | public int NumVars => coeffs.Length; 16 | 17 | public LinearEquation(int numElements) 18 | { 19 | coeffs = new ulong[numElements]; 20 | } 21 | 22 | public override string ToString() 23 | { 24 | // Note that the highest degree terms come first. 25 | var terms = new List(); 26 | for (int i = 0; i < coeffs.Length; i++) 27 | { 28 | var str = $"{coeffs[i]}*m{coeffs.Length - 1 - i}"; 29 | terms.Add(str); 30 | } 31 | 32 | var sum = string.Join(" + ", terms); 33 | sum = $"({sum}) == {result}"; 34 | return sum; 35 | } 36 | 37 | public int FirstNonZeroIdx() 38 | { 39 | for (int i = 0; i < coeffs.Length; i++) 40 | { 41 | if (coeffs[i] != 0) 42 | return i; 43 | } 44 | 45 | return int.MaxValue; 46 | } 47 | 48 | public int GetLeadingZeroCount() 49 | { 50 | int count = 0; 51 | for (int i = 0; i < coeffs.Length; i++) 52 | { 53 | var coeff = coeffs[i]; 54 | if (coeff == 0) 55 | count++; 56 | else 57 | return count; 58 | } 59 | 60 | return count; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Mba.Simplifier/LinEq/LinearEquationSolver.cs: -------------------------------------------------------------------------------- 1 | using Mba.Simplifier.Pipeline; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Mba.Simplifier.LinEq 9 | { 10 | public class LinearEquationSolver 11 | { 12 | private readonly ulong moduloMask; 13 | 14 | private readonly LinearCongruenceSolver congruenceSolver; 15 | 16 | private readonly LinearSystem linearSystem; 17 | 18 | public static ulong[]? Solve(LinearSystem linearSystem) 19 | => new LinearEquationSolver(linearSystem).Solve(); 20 | 21 | private LinearEquationSolver(LinearSystem linearSystem) 22 | { 23 | this.moduloMask = linearSystem.ModuloMask; 24 | this.congruenceSolver = new LinearCongruenceSolver((UInt128)moduloMask); 25 | this.linearSystem = linearSystem; 26 | } 27 | 28 | private ulong[]? Solve() 29 | { 30 | // Try to convert the linear system to row echelon form. 31 | EnterRowEchelonForm(); 32 | linearSystem.Equations.RemoveAll(x => x.GetLeadingZeroCount() == x.NumVars); 33 | linearSystem.Sort(); 34 | 35 | // TODO: We still may be able to find a solution in both of these cases. 36 | if (linearSystem.Equations.Count != linearSystem.NumVars) 37 | return null; 38 | for (int i = 0; i < linearSystem.Equations.Count; i++) 39 | { 40 | var eq = linearSystem.Equations[i]; 41 | if (eq.GetLeadingZeroCount() != i) 42 | return null; 43 | } 44 | 45 | // Enumerate the possible solutions until we find a fit. 46 | var solutionMap = new ulong[linearSystem.NumVars]; 47 | bool success = EnumerateSolutions(solutionMap, linearSystem.NumVars - 1); 48 | if (!success) 49 | return null; 50 | 51 | return solutionMap; 52 | } 53 | 54 | private void EnterRowEchelonForm() 55 | { 56 | var varCount = linearSystem.NumVars; 57 | for (int varIdx = 0; varIdx < varCount; varIdx++) 58 | { 59 | // First we sort(ascending order) by the number of leading zero coefficients. 60 | linearSystem.Sort(); 61 | 62 | // Identify the first coefficient that can be used to eliminate all other coefficients for this variable. 63 | var pivotIdx = SelectPivot(varIdx); 64 | // If we cannot find a coefficient to eliminate all others by, we still may be able to eliminate others 65 | // after solving a linear congruence. 66 | if (pivotIdx == -1) 67 | { 68 | EliminateViaSubtraction(varIdx); 69 | linearSystem.Sort(); 70 | continue; 71 | } 72 | 73 | // Eliminate all other coefficients for this variable. 74 | var ourCoeff = linearSystem.Equations[pivotIdx].coeffs[varIdx]; 75 | for (int i = pivotIdx + 1; i < linearSystem.Equations.Count; i++) 76 | { 77 | var otherEq = linearSystem.Equations[i]; 78 | var otherCoeff = otherEq.coeffs[varIdx]; 79 | if (otherCoeff == 0) 80 | continue; 81 | 82 | // Find the coefficient that would allow us to reduce the other coefficients to zero. 83 | var reducer = GetCoeffReducer(ourCoeff, otherCoeff); 84 | if (reducer == null) 85 | continue; 86 | 87 | AddMultipleTo(varIdx, pivotIdx, i, reducer.Value); 88 | } 89 | } 90 | } 91 | 92 | // Select a linear equation to be used as a pivot. 93 | private int SelectPivot(int varIdx) 94 | { 95 | int firstIdx = -1; 96 | var best = (-1, -1); 97 | for (int i = 0; i < linearSystem.Equations.Count; i++) 98 | { 99 | var lineq = linearSystem.Equations[i]; 100 | var coeff = lineq.coeffs[varIdx]; 101 | if (coeff == 0) 102 | continue; 103 | var trailingZeroes = lineq.GetLeadingZeroCount(); 104 | if (trailingZeroes != varIdx) 105 | continue; 106 | 107 | if (firstIdx == -1) 108 | firstIdx = i; 109 | 110 | // Skip if a modular inverse does not exist. 111 | // TODO: Use fast modular inverse, skip if coefficient is not odd 112 | var inv = GetModularInverse(coeff); 113 | if (coeff != 1 && inv == null) 114 | continue; 115 | 116 | var leadingZeroes = lineq.coeffs.TakeWhile(x => x == 0).Count(); 117 | if (leadingZeroes > best.Item2) 118 | { 119 | best = (i, leadingZeroes); 120 | } 121 | } 122 | 123 | // If this has less trailing zeroes than the best candidate, we bail out. 124 | // This would increase the number of non zero entries across the whole linear system if used. 125 | if (best.Item2 != varIdx) 126 | return -1; 127 | // If the first non zero idx is the best candidate, we keep it. 128 | if (firstIdx == best.Item1) 129 | return firstIdx; 130 | 131 | var firstNonZeroIdx = firstIdx; 132 | var old = linearSystem.Equations[firstIdx]; 133 | var firstInvertibleIdx = best.Item1; 134 | var nnew = linearSystem.Equations[firstInvertibleIdx]; 135 | linearSystem.Equations[firstNonZeroIdx] = nnew; 136 | linearSystem.Equations[firstInvertibleIdx] = old; 137 | return firstNonZeroIdx; 138 | } 139 | 140 | private ulong? GetModularInverse(ulong coeff) 141 | { 142 | var lc = congruenceSolver.LinearCongruence((UInt128)coeff, 1, (UInt128)moduloMask + 1); 143 | if (lc == null) 144 | return null; 145 | if (lc.d == 0) 146 | return null; 147 | 148 | return (ulong)congruenceSolver.GetSolution(0, lc); 149 | } 150 | 151 | private ulong? GetCoeffReducer(ulong ourCoeff, ulong otherCoeff) 152 | { 153 | var inv = moduloMask & (moduloMask * otherCoeff); 154 | 155 | var lc = congruenceSolver.LinearCongruence(ourCoeff, inv, (UInt128)moduloMask + 1); 156 | if (lc == null) 157 | return null; 158 | if (lc.d == 0) 159 | return null; 160 | 161 | var sol = (ulong)congruenceSolver.GetSolution(0, lc); 162 | return sol; 163 | } 164 | 165 | private bool EliminateViaSubtraction(int varIdx) 166 | { 167 | var firstIdx = linearSystem.Equations.FindIndex(x => x.GetLeadingZeroCount() == varIdx); 168 | if (firstIdx == -1) 169 | return false; 170 | 171 | bool changed = false; 172 | for (int a = firstIdx; a < linearSystem.Equations.Count; a++) 173 | { 174 | for (int b = a + 1; b < linearSystem.Equations.Count; b++) 175 | { 176 | bool eliminated = TryEliminateBy(a, b, varIdx); 177 | if (!eliminated) 178 | eliminated = TryEliminateBy(b, a, varIdx); 179 | 180 | changed |= eliminated; 181 | } 182 | } 183 | 184 | return changed; 185 | } 186 | 187 | private bool TryEliminateBy(int a, int b, int varIdx) 188 | { 189 | var aCoeff = linearSystem.Equations[a].coeffs[varIdx]; 190 | if (aCoeff == 0) 191 | return false; 192 | var bCoeff = linearSystem.Equations[b].coeffs[varIdx]; 193 | if (bCoeff == 0) 194 | return false; 195 | 196 | var oldCoeff = bCoeff; 197 | bCoeff = moduloMask & moduloMask * bCoeff; 198 | var lc = congruenceSolver.LinearCongruence(aCoeff, bCoeff, (UInt128)moduloMask + 1); 199 | if (lc == null) 200 | return false; 201 | 202 | var reducer = (ulong)congruenceSolver.GetSolution(0, lc); 203 | 204 | AddMultipleTo(varIdx, a, b, reducer); 205 | return true; 206 | } 207 | 208 | private void AddMultipleTo(int varIdx, int fromIdx, int toIdx, ulong multiple) 209 | { 210 | var ourEq = linearSystem.Equations[fromIdx]; 211 | var ourCoeff = ourEq.coeffs[varIdx]; 212 | var ourResult = ourEq.result; 213 | 214 | var otherEq = linearSystem.Equations[toIdx]; 215 | var otherCoeff = otherEq.coeffs[varIdx]; 216 | 217 | var mul = Mul(ourEq, multiple); 218 | var add = Add(otherEq, mul); 219 | var newResult = moduloMask & (moduloMask & multiple * ourResult) + otherEq.result; 220 | add.result = newResult; 221 | linearSystem.Equations[toIdx] = add; 222 | } 223 | 224 | private LinearEquation Mul(LinearEquation a, ulong coeff) 225 | { 226 | var clone = new LinearEquation(a.coeffs.Length); 227 | clone.coeffs = a.coeffs.ToArray(); 228 | for (int i = 0; i < clone.coeffs.Length; i++) 229 | { 230 | clone.coeffs[i] = moduloMask & clone.coeffs[i] * coeff; 231 | } 232 | 233 | return clone; 234 | } 235 | 236 | private LinearEquation Add(LinearEquation a, LinearEquation b) 237 | { 238 | var clone = new LinearEquation(a.coeffs.Length); 239 | for (int i = 0; i < clone.coeffs.Length; i++) 240 | { 241 | clone.coeffs[i] = moduloMask & a.coeffs[i] + b.coeffs[i]; 242 | } 243 | return clone; 244 | } 245 | 246 | private bool EnumerateSolutions(ulong[] solutionMap, int varIdx) 247 | { 248 | // Adjust the rhs of the equation to account for the solutions of the other variables. 249 | var lineq = linearSystem.Equations[varIdx]; 250 | var result = lineq.result; 251 | for (int i = varIdx + 1; i < linearSystem.NumVars; i++) 252 | { 253 | var coeff = lineq.coeffs[i]; 254 | var mul = coeff * solutionMap[i]; 255 | result -= mul; 256 | result &= moduloMask; 257 | } 258 | 259 | var ourCoeff = lineq.coeffs[varIdx]; 260 | var lc = congruenceSolver.LinearCongruence(ourCoeff, result, (UInt128)moduloMask + 1); 261 | if (lc == null) 262 | return false; 263 | int limit = lc.d > 255 ? 255 : (int)lc.d; 264 | for (int solutionIdx = 0; solutionIdx < limit; solutionIdx++) 265 | { 266 | var solution = (ulong)congruenceSolver.GetSolution((UInt128)solutionIdx, lc); 267 | solutionMap[varIdx] = solution; 268 | 269 | if (varIdx == 0) 270 | return true; 271 | 272 | else 273 | { 274 | bool success = EnumerateSolutions(solutionMap, varIdx - 1); 275 | if (success) 276 | return success; 277 | } 278 | } 279 | 280 | return false; 281 | } 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /Mba.Simplifier/LinEq/LinearSystem.cs: -------------------------------------------------------------------------------- 1 | using Mba.Utility; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Mba.Simplifier.LinEq 9 | { 10 | public class LinearSystem 11 | { 12 | public ulong ModuloMask { get; } 13 | 14 | public int NumVars { get; } 15 | 16 | public List Equations { get; set; } 17 | 18 | public LinearSystem(uint bitSize, int numVars, List equations) 19 | { 20 | ModuloMask = (ulong)ModuloReducer.GetMask(bitSize); 21 | NumVars = numVars; 22 | Equations = equations; 23 | } 24 | 25 | // Sort the equations by their number of leading zeroes. 26 | // In the case of a tie, we pick the one with the smallest coefficient. 27 | public void Sort() 28 | { 29 | Equations.Sort((x, y) => Compare(x, y)); 30 | } 31 | 32 | // Convert the system of equations to a set of z3 constraints, for debugging purposes. 33 | public string ToZ3String() 34 | { 35 | var sb = new StringBuilder(); 36 | foreach (var linEq in Equations) 37 | sb.AppendLine($"s.add({linEq})"); 38 | return sb.ToString(); 39 | } 40 | 41 | private static int Compare(LinearEquation a, LinearEquation b) 42 | { 43 | var firstA = a.FirstNonZeroIdx(); 44 | var firstB = b.FirstNonZeroIdx(); 45 | if (firstA == firstB) 46 | { 47 | if (a.ToString() == b.ToString()) 48 | return 0; 49 | 50 | return a.coeffs[firstA].CompareTo(b.coeffs[firstB]); 51 | } 52 | if (firstA < firstB) 53 | return -1; 54 | 55 | 56 | return 1; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Mba.Simplifier/LinEq/PolyInverter.cs: -------------------------------------------------------------------------------- 1 | using Mba.Simplifier.Pipeline; 2 | using Mba.Simplifier.Polynomial; 3 | using Mba.Utility; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Diagnostics; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Channels; 10 | using System.Threading.Tasks; 11 | 12 | namespace Mba.Simplifier.LinEq 13 | { 14 | /// 15 | /// Class for inverting binary permutation polynomials. 16 | /// 17 | public static class PolyInverter 18 | { 19 | // Tries to find an inverse for the given polynomial. 20 | public static SparsePolynomial? TryInvert(SparsePolynomial poly) 21 | { 22 | // Construct a system of linear equations 23 | var linearSystem = GetInverseLinearSystem(poly); 24 | 25 | // Solve the linear system. 26 | var inverseCoeffs = LinearEquationSolver.Solve(linearSystem); 27 | if (inverseCoeffs == null) 28 | return null; 29 | 30 | // Construct the inverse polynomial. 31 | // Note that we invert the coefficients such that the lower degree terms come first. 32 | var invPoly = new SparsePolynomial(poly.numVars, poly.width); 33 | var coeffs = inverseCoeffs.Reverse().ToArray(); 34 | for(int i = 0; i < coeffs.Length; i++) 35 | { 36 | var monom = new Monomial((byte)i); 37 | invPoly.SetCoeff(monom, coeffs[i]); 38 | } 39 | 40 | return invPoly; 41 | } 42 | 43 | // Construct a linear system where the solution is the inverse of the polynomial. 44 | private static LinearSystem GetInverseLinearSystem(SparsePolynomial poly) 45 | { 46 | var numVars = poly.coeffs.Count * 2; 47 | int numEquations = numVars * 2; 48 | var equations = new List(); 49 | for (int i = 0; i < numEquations; i++) 50 | equations.Add(new LinearEquation(numVars)); 51 | 52 | var linearSystem = new LinearSystem(poly.width, numVars, equations); 53 | // Evaluate the polynomial on one point for each degree. 54 | var points = new List(); 55 | for (int i = 0; i < numEquations; i++) 56 | { 57 | var eval = PolynomialEvaluator.Eval(poly, new ulong[] { (ulong)i }); 58 | points.Add(poly.moduloMask & eval); 59 | } 60 | 61 | // Fill in the linear system 62 | for (int pointIdx = 0; pointIdx < points.Count; pointIdx++) 63 | { 64 | // For the lhs, we plug in the result of the polynomial evaluation 65 | linearSystem.Equations[pointIdx].coeffs[0] = 1; 66 | for (int i = 1; i < numVars; i++) 67 | { 68 | var c = points[pointIdx]; 69 | var coeff = poly.moduloMask & PolynomialEvaluator.Pow(c, (byte)i); 70 | linearSystem.Equations[pointIdx].coeffs[i] = coeff; 71 | } 72 | 73 | linearSystem.Equations[pointIdx].result = (ulong)pointIdx; 74 | } 75 | 76 | // Place the highest degree terms first, such that we solve for the constant offset first. 77 | foreach (var linEq in linearSystem.Equations) 78 | linEq.coeffs = linEq.coeffs.Reverse().ToArray(); 79 | 80 | return linearSystem; 81 | } 82 | 83 | public static SparsePolynomial Get8BitPermPoly() 84 | { 85 | var poly = new SparsePolynomial(1, (byte)8); 86 | poly.SetCoeff(new Monomial(0), 132); 87 | poly.SetCoeff(new Monomial(1), 185); 88 | poly.SetCoeff(new Monomial(2), 42); 89 | return poly; 90 | } 91 | 92 | public static SparsePolynomial Get64BitPermPoly() 93 | { 94 | var poly = new SparsePolynomial(1, (byte)64); 95 | poly.SetCoeff(new Monomial(0), 9193501499183852111); 96 | poly.SetCoeff(new Monomial(1), 5260339532280414813); 97 | poly.SetCoeff(new Monomial(2), 14929154534604275712); 98 | poly.SetCoeff(new Monomial(3), 3178634119571570688); 99 | return poly; 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Mba.Simplifier/Mba.Simplifier.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | True 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | Always 20 | 21 | 22 | Always 23 | 24 | 25 | Always 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | Always 37 | 38 | 39 | Always 40 | 41 | 42 | Always 43 | 44 | 45 | Always 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /Mba.Simplifier/Minimization/BooleanMinimizer.cs: -------------------------------------------------------------------------------- 1 | using Mba.Ast; 2 | using Mba.Common.MSiMBA; 3 | using Mba.Simplifier.Bindings; 4 | using Mba.Simplifier.Pipeline; 5 | using Mba.Testing; 6 | using Mba.Utility; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Diagnostics; 10 | using System.Linq; 11 | using System.Text; 12 | using System.Threading.Tasks; 13 | 14 | namespace Mba.Simplifier.Minimization 15 | { 16 | public static class BooleanMinimizer 17 | { 18 | private const bool useLegacyMinimizer = false; 19 | 20 | public static AstIdx GetBitwise(AstCtx ctx, IReadOnlyList variables, TruthTable truthTable, bool negate = false) 21 | { 22 | // If requested, negate the result vector to find a negated expression. 23 | if (negate) 24 | { 25 | truthTable.Negate(); 26 | } 27 | 28 | // Exit early if the boolean function is a constant. 29 | var asConstant = AsConstant(ctx, truthTable, ctx.GetWidth(variables[0])); 30 | if (asConstant != null) 31 | return asConstant.Value; 32 | 33 | if (variables.Count == 1) 34 | { 35 | return truthTable.GetBit(0) == false ? variables[0] : ctx.Neg(variables[0]); 36 | } 37 | 38 | // If there are four or less variables, we can pull the optimal representation from the truth table. 39 | // TODO: One could possibly construct a 5 variable truth table for all 5 variable NPN classes. 40 | if (variables.Count <= 4) 41 | { 42 | return FromTruthTable(ctx, variables, truthTable); 43 | } 44 | 45 | // For debugging purposes we still want to keep the legacy boolean minimization logic around. 46 | if (useLegacyMinimizer) 47 | { 48 | // Otherwise use Espresso to compute a semi optimal version of the boolean function. 49 | var xnf = AnfMinimizer.SimplifyBoolean(ctx, variables, truthTable); 50 | var dnf = EspressoMinimizer.SimplifyBoolean(ctx, truthTable.AsList(), variables).ast; 51 | 52 | var c1 = LinearSimplifier.GetCost(ctx, xnf, false, 1); 53 | var c2 = LinearSimplifier.GetCost(ctx, dnf, false, 1); 54 | if (c1 < c2) 55 | return xnf; 56 | return dnf; 57 | } 58 | 59 | // Though now we prefer to use the new minimizer implemented purely in rust. It's faster and generally yields better results. 60 | return ctx.MinimizeAnf(TableDatabase.Instance.db, truthTable, (List)variables, MultibitSiMBA.JitPage.Value); 61 | } 62 | 63 | private static AstIdx? AsConstant(AstCtx ctx, TruthTable table, uint width) 64 | { 65 | var first = table.GetBit(0); 66 | for (int i = 1; i < table.NumBits; i++) 67 | { 68 | if (table.GetBit(i) != first) 69 | return null; 70 | } 71 | 72 | ulong constant = first ? (ulong)ModuloReducer.GetMask(width) : 0; 73 | return ctx.Constant(constant, width); 74 | } 75 | 76 | public static AstIdx FromTruthTable(AstCtx ctx, IReadOnlyList variables, TruthTable truthTable) 77 | { 78 | // Fetch the truth table entry corresponding to this node. 79 | var ast = TableDatabase.Instance.GetTableEntry(ctx, (List)variables, (int)(uint)truthTable.arr[0]); 80 | return ast; 81 | } 82 | 83 | public static AstNode RewriteUsingNewVariables(AstNode ast, Func getVar) 84 | { 85 | var op1 = () => RewriteUsingNewVariables(ast.Children[0], getVar); 86 | var op2 = () => RewriteUsingNewVariables(ast.Children[1], getVar); 87 | return ast switch 88 | { 89 | ConstNode constNode => new ConstNode(constNode.Value, ast.BitSize), 90 | VarNode varNode => getVar(varNode), 91 | PowerNode powerNode => new PowerNode(op1(), op2()), 92 | AddNode => new AddNode(op1(), op2()), 93 | MulNode mulNode => new MulNode(op1(), op2()), 94 | AndNode andNode => new AndNode(op1(), op2()), 95 | OrNode orNode => new OrNode(op1(), op2()), 96 | XorNode => new XorNode(op1(), op2()), 97 | NegNode => new NegNode(op1()), 98 | }; 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Mba.Simplifier/Minimization/EspressoMinimizer.cs: -------------------------------------------------------------------------------- 1 | using Mba.Ast; 2 | using Mba.Interop; 3 | using Mba.Testing; 4 | using Mba.Common.Interop; 5 | using Microsoft.Z3; 6 | using System; 7 | using System.Collections.Concurrent; 8 | using System.Collections.Generic; 9 | using System.Diagnostics; 10 | using System.Linq; 11 | using System.Runtime.InteropServices; 12 | using System.Text; 13 | using System.Threading.Tasks; 14 | using Mba.Common.Utility; 15 | using System.Reflection.Metadata.Ecma335; 16 | using Mba.Common.Minimization; 17 | using Mba.Simplifier.Bindings; 18 | 19 | namespace Mba.Simplifier.Minimization 20 | { 21 | public static class EspressoMinimizer 22 | { 23 | public static (AstIdx ast, List onOffs) SimplifyBoolean(AstCtx ctx, List resultVector, IReadOnlyList variables) 24 | { 25 | // Run espresso. 26 | var pla = GeneratePLA(resultVector, variables); 27 | var output = EspressoApi.Run(pla); 28 | 29 | // Parse espresso's output into a boolean function. 30 | return ParsePLA(ctx, output, resultVector, variables); 31 | } 32 | 33 | // Generate an instance of Espresso's file format. 34 | // https://user.engineering.uiowa.edu/~switchin/OldSwitching/espresso.5.html 35 | public static string GeneratePLA(List resultVector, IReadOnlyList variables) 36 | { 37 | ulong varCount = (ulong)variables.Count; 38 | var numCombinations = (ulong)Math.Pow(2, varCount); 39 | Debug.Assert(numCombinations == (ulong)resultVector.Count); 40 | 41 | var sb = new StringBuilder(); 42 | sb.AppendLine($".i {varCount}"); 43 | sb.AppendLine($".o 1"); 44 | 45 | for (ulong i = 0; i < numCombinations; i++) 46 | { 47 | // Evaluate the ast at this combination of zeroes and ones. 48 | for (uint v = 0; v < varCount; v++) 49 | { 50 | // Compute a mask for this this variable. 51 | ulong varMask = (ulong)1 << (ushort)v; 52 | 53 | // Compute the value of this specific variable. 54 | ulong varValue = i & varMask; 55 | 56 | // Shift the value back down 57 | varValue = varValue >> (ushort)v; 58 | 59 | // Set the variable value. 60 | var node = variables[(int)v]; 61 | sb.Append(varValue); 62 | } 63 | 64 | // Isolate only the first bit. 65 | var eval = resultVector[(int)i]; 66 | sb.AppendLine($" {eval}"); 67 | } 68 | sb.AppendLine(".e"); 69 | 70 | return sb.ToString(); 71 | } 72 | 73 | private static (AstIdx ast, List onOffs) ParsePLA(AstCtx ctx, string output, List resultVector, IReadOnlyList variables) 74 | { 75 | var lines = output.Split(new string[] { "\r\n", "\r", "\n" }, 76 | StringSplitOptions.None).ToList(); 77 | var firstIndex = lines.IndexOf(lines.Single(x => x.Contains(".p"))); 78 | lines = lines.Skip(firstIndex + 1).TakeWhile(x => !x.StartsWith(".e")).ToList(); 79 | 80 | var width = ctx.GetWidth(variables[0]); 81 | var onOff = new List(); 82 | var terms = new List(); 83 | foreach (var line in lines) 84 | { 85 | var andClauses = new List(); 86 | var strTt = line.Split(" ", StringSplitOptions.RemoveEmptyEntries)[0]; 87 | 88 | ulong demandedMask = 0; 89 | ulong dontCareMask = 0; 90 | for (int i = 0; i < variables.Count; i++) 91 | { 92 | var c = strTt[i]; 93 | // - means don't care 94 | if (c == '-') 95 | { 96 | dontCareMask = dontCareMask | 1u << (ushort)i; 97 | continue; 98 | } 99 | 100 | else if (c == '0') 101 | { 102 | // Negated bits can be implicitly inferred using the dontcare + demanded mask. 103 | andClauses.Add(ctx.Neg(variables[i])); 104 | } 105 | 106 | else if (c == '1') 107 | { 108 | demandedMask |= 1u << (ushort)i; 109 | andClauses.Add(variables[i]); 110 | } 111 | } 112 | 113 | onOff.Add(new(demandedMask, dontCareMask)); 114 | var anded = ctx.And(andClauses); 115 | terms.Add(anded); 116 | } 117 | 118 | if (!terms.Any()) 119 | { 120 | Debug.Assert(resultVector.All(x => x == 0)); 121 | var constant = ctx.Constant(0, width); 122 | return (constant, onOff); 123 | } 124 | 125 | var ored = ctx.Or(terms); 126 | return (ored, onOff); 127 | } 128 | 129 | public static AstNode ToBoolean(IReadOnlyList variables, List onOffset) 130 | { 131 | List terms = new(); 132 | foreach (var set in onOffset) 133 | { 134 | List clauses = new(); 135 | for (int i = 0; i < variables.Count; i++) 136 | { 137 | // Skip if the variable is not demanded. 138 | ulong varMask = 1u << (ushort)i; 139 | bool isDontCare = (set.DontCareMask & varMask) != 0; 140 | if (isDontCare) 141 | continue; 142 | 143 | bool isNegated = (set.DemandedMask & varMask) == 0; 144 | if (isNegated) 145 | clauses.Add(new NegNode(variables[i])); 146 | else 147 | clauses.Add(variables[i]); 148 | } 149 | 150 | if (!clauses.Any()) 151 | continue; 152 | 153 | var anded = clauses.And(); 154 | terms.Add(anded); 155 | } 156 | 157 | var or = terms.Or(); 158 | return or; 159 | } 160 | } 161 | 162 | // Espresso FFI wrapper 163 | public static class EspressoApi 164 | { 165 | // Thread safe boolean simplification cache. 166 | private static ConcurrentDictionary espressoCache = new(); 167 | 168 | public static unsafe string Run(string lines) 169 | { 170 | if (espressoCache.TryGetValue(lines, out string value)) 171 | return value; 172 | 173 | // Run espresso. 174 | var ms = new MarshaledString(lines); 175 | sbyte* buffer; 176 | run_espresso_from_data(ms, (uint)ms.Length, &buffer); 177 | var output = StringMarshaler.AcquireString(buffer); 178 | 179 | espressoCache.TryAdd(lines, output); 180 | return output; 181 | } 182 | 183 | [DllImport("Espresso")] 184 | public unsafe static extern sbyte* run_espresso_from_data(sbyte* data, uint l, sbyte** output); 185 | 186 | [DllImport("Espresso")] 187 | public unsafe static extern sbyte* run_d1merge_from_data(sbyte* data, uint l, sbyte** output); 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /Mba.Simplifier/Minimization/GroebnerBasis.cs: -------------------------------------------------------------------------------- 1 | using Mba.Common.MSiMBA; 2 | using Mba.Simplifier.Bindings; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using System.Linq; 7 | using System.Runtime.InteropServices; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace Mba.Simplifier.Minimization 12 | { 13 | public class GroebnerBasis 14 | { 15 | private readonly TruthTable table; 16 | 17 | private readonly ulong[] variableCombinations; 18 | 19 | private readonly List groupSizes; 20 | 21 | public static (List> gb, bool negated) Compute(TruthTable table) => new GroebnerBasis(table).Compute(); 22 | 23 | private GroebnerBasis(TruthTable table) 24 | { 25 | this.table = table; 26 | variableCombinations = MultibitSiMBA.GetVariableCombinations(table.NumVars); 27 | groupSizes = MultibitSiMBA.GetGroupSizes(table.NumVars); 28 | } 29 | 30 | private unsafe (List> gb, bool negated) Compute() 31 | { 32 | // Eliminate the nil entry if possible. 33 | bool negated = false; 34 | if(table.GetBit(0)) 35 | { 36 | negated = true; 37 | table.Negate(); 38 | } 39 | 40 | // Construct a system of boolean polynomials out of the truth table(ignoring nil rows) 41 | var polys = new List>(); 42 | for(int i = 0; i < table.NumBits; i++) 43 | { 44 | // Skip nil rows 45 | if (!table.GetBit(i)) 46 | continue; 47 | 48 | // If the row is positive, construct algebraic normal form for this row. 49 | // TODO: Use a more space / time efficienty method, 'GetRowAnf' is overkill. 50 | var monoms = GetRowAnf(i); 51 | polys.Add(monoms); 52 | } 53 | 54 | // Serialize the buffer to a C-compatible memory representation. 55 | var inBuffer = SerializeSystem(polys); 56 | 57 | // Construct a groebner basis 58 | uint* outBuffer; 59 | uint outSize = 0; 60 | fixed (uint* ptr = &inBuffer[0]) 61 | { 62 | outBuffer = Api.GetGroebnerBasis((uint)table.NumVars, ptr, &outSize); 63 | } 64 | 65 | var groebnerBasis = DeserializeSystem(outBuffer); 66 | Api.FreeGroebnerBasis((nint)outBuffer); 67 | 68 | return (groebnerBasis, negated); 69 | } 70 | 71 | // Convert a single truth table row to algebraic normal form 72 | private unsafe List GetRowAnf(int idx) 73 | { 74 | var resultVec = new ulong[table.NumBits]; 75 | resultVec[idx] = 1; 76 | 77 | // Keep track of which variables are demanded by which combination, 78 | // as well as which result vector idx corresponds to which combination. 79 | var groupSizes = MultibitSiMBA.GetGroupSizes(table.NumVars); 80 | List<(ulong trueMask, int resultVecIdx)> combToMaskAndIdx = new(); 81 | for (int i = 0; i < variableCombinations.Length; i++) 82 | { 83 | var comb = variableCombinations[i]; 84 | var myIndex = MultibitSiMBA.GetGroupSizeIndex(groupSizes, comb); 85 | combToMaskAndIdx.Add((comb, (int)myIndex)); 86 | } 87 | 88 | var varCount = table.NumVars; 89 | bool onlyOneVar = varCount == 1; 90 | int width = (int)(varCount == 1 ? 1 : 2u << (ushort)(varCount - 1)); 91 | List terms = new(); 92 | fixed (ulong* ptr = &resultVec[0]) 93 | { 94 | for (int i = 0; i < variableCombinations.Length; i++) 95 | { 96 | // Fetch the result vector index for this conjunction. 97 | // If the coefficient is zero, we can skip it. 98 | var comb = variableCombinations[i]; 99 | var (trueMask, index) = combToMaskAndIdx[i]; 100 | var coeff = ptr[index]; 101 | if (coeff == 0) 102 | continue; 103 | 104 | // Subtract the coefficient from the result vector. 105 | MultibitSiMBA.SubtractCoeff(1, ptr, 0, coeff, index, width, varCount, onlyOneVar, trueMask); 106 | terms.Add((uint)variableCombinations[i]); 107 | } 108 | } 109 | 110 | return terms; 111 | } 112 | 113 | private uint[] SerializeSystem(List> polys) 114 | { 115 | // Compute the size of the buffer 116 | uint wordSize = 1; 117 | foreach(var poly in polys) 118 | { 119 | wordSize += 1; 120 | wordSize += (uint)poly.Count; 121 | } 122 | 123 | // Allocate the buffer 124 | var buffer = new uint[wordSize]; 125 | 126 | // Serialize the system to the buffer 127 | int idx = 0; 128 | buffer[idx] = (uint)polys.Count; 129 | idx += 1; 130 | foreach(var poly in polys) 131 | { 132 | buffer[idx] = (uint)poly.Count; 133 | idx += 1; 134 | foreach(var monom in poly) 135 | { 136 | // Write the monomial to the buffer. 137 | // MAYBE: we need to shift all of the conjunctions up by one, because SymbSAT uses the first bit of the monomial to represent the constant offset. 138 | buffer[idx] = (monom << 1); 139 | //buffer[idx] = monom; 140 | idx += 1; 141 | } 142 | } 143 | 144 | return buffer; 145 | } 146 | 147 | private unsafe List> DeserializeSystem(uint* outBuffer) 148 | { 149 | var system = new List>(); 150 | 151 | var polyCount = *outBuffer; 152 | outBuffer++; 153 | 154 | for(int _ = 0; _ < polyCount; _++) 155 | { 156 | var monomCount = *outBuffer; 157 | outBuffer++; 158 | 159 | var poly = new List(); 160 | for (int i = 0; i < monomCount; i++) 161 | { 162 | var monom = *outBuffer; 163 | poly.Add(monom >> 1); // Shift the conjunction down by one to account for SymbSAT's internal representation 164 | outBuffer++; 165 | } 166 | 167 | system.Add(poly); 168 | } 169 | 170 | return system; 171 | } 172 | 173 | public static class Api 174 | { 175 | [DllImport("Mba.FFI")] 176 | public unsafe static extern uint* GetGroebnerBasis(uint numVars, uint* inBuffer, uint* outSize); 177 | 178 | [DllImport("Mba.FFI")] 179 | public unsafe static extern uint* FreeGroebnerBasis(nint buffer); 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /Mba.Simplifier/Minimization/GroebnerMinimizer.cs: -------------------------------------------------------------------------------- 1 | using Mba.Simplifier.Bindings; 2 | using Mba.Simplifier.Pipeline; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Mba.Simplifier.Minimization 10 | { 11 | // Boolean minimization algorithm heavily based on "Gröbner Bases for Boolean Function Minimization" (https://ceur-ws.org/Vol-3455/short4.pdf) 12 | public class GroebnerMinimizer 13 | { 14 | private readonly AstCtx ctx; 15 | 16 | private readonly IReadOnlyList variables; 17 | 18 | private readonly TruthTable truthTable; 19 | 20 | private readonly Dictionary demandedVarsMap = new(); 21 | 22 | public static AstIdx Run(AstCtx ctx, IReadOnlyList variables, TruthTable truthTable) 23 | => new GroebnerMinimizer(ctx, variables, truthTable).Run(); 24 | 25 | private GroebnerMinimizer(AstCtx ctx, IReadOnlyList variables, TruthTable truthTable) 26 | { 27 | this.ctx = ctx; 28 | this.variables = variables; 29 | this.truthTable = truthTable; 30 | } 31 | 32 | private AstIdx Run() 33 | { 34 | // Compute a groebner basis for this truth table 35 | var (gb, negated) = GroebnerBasis.Compute(truthTable); 36 | 37 | // Set the initial demanded variable masks. 38 | for (int i = 0; i < variables.Count; i++) 39 | { 40 | var mask = 1u << i; 41 | var var = variables[i]; 42 | demandedVarsMap.Add(var, mask); 43 | } 44 | 45 | // Factor each member of the groebner basis 46 | var terms = new List(); 47 | foreach(var conjs in gb) 48 | { 49 | terms.Add(AnfMinimizer.Factor(ctx, variables, conjs, demandedVarsMap).Value); 50 | } 51 | 52 | // Combine them into a single boolean 53 | var result = ctx.Or(terms); 54 | 55 | return negated ? ctx.Neg(result) : result; 56 | } 57 | 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Mba.Simplifier/Minimization/TableDatabase.cs: -------------------------------------------------------------------------------- 1 | using Mba.Ast; 2 | using Mba.Simplifier.Bindings; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using System.Linq; 7 | using System.Reflection; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace Mba.Simplifier.Minimization 12 | { 13 | public class TableDatabase 14 | { 15 | private IReadOnlyList Tables { get; } 16 | 17 | public readonly TruthTableDb db; 18 | 19 | public static readonly TableDatabase Instance = new(); 20 | 21 | private TableDatabase() 22 | { 23 | Tables = new List() 24 | { 25 | LoadTruthTableBinary(2), 26 | LoadTruthTableBinary(3), 27 | LoadTruthTableBinary(4), 28 | }; 29 | 30 | db = new TruthTableDb(); 31 | } 32 | 33 | public static unsafe byte[] LoadTruthTableBinary(int numVars) 34 | { 35 | // Fetch the serialized truth table from our embedded resources. 36 | var path = $"{numVars}variable_truthtable.bc"; 37 | var name = Assembly.GetExecutingAssembly().GetManifestResourceNames().Single(x => x.Contains(path)); 38 | var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(name); 39 | var bytes = new byte[stream.Length]; 40 | stream.Read(bytes, 0, bytes.Length); 41 | return bytes; 42 | } 43 | 44 | public unsafe AstIdx GetTableEntry(AstCtx ctx, List vars, int index) 45 | { 46 | return db.GetBoolean(ctx, (uint)vars.Count, vars, (ulong)index); 47 | } 48 | 49 | public unsafe uint GetTableEntryCost(AstCtx ctx, int varCount, int index) 50 | { 51 | return db.GetBooleanCost((uint)varCount, (ulong)index); 52 | } 53 | 54 | private static unsafe AstIdx ParseBinaryBooleanFunc(AstCtx ctx, IReadOnlyList vars, byte* bytes, ref uint i) 55 | { 56 | byte opcode = bytes[i]; 57 | i += 1; 58 | 59 | var binop = (AstOp opcode, ref uint i) 60 | => ctx.Binop(opcode, ParseBinaryBooleanFunc(ctx, vars, bytes, ref i), ParseBinaryBooleanFunc(ctx, vars, bytes, ref i)); 61 | 62 | switch (opcode) 63 | { 64 | case 0: 65 | ulong constant = *(ulong*)&bytes[i]; 66 | return ctx.Constant(constant, ctx.GetWidth(vars[0])); 67 | case 2: 68 | byte idx = bytes[i]; 69 | i += 1; 70 | return vars[idx]; 71 | case 8: 72 | return binop(AstOp.And, ref i); 73 | case 9: 74 | return binop(AstOp.Or, ref i); 75 | case 10: 76 | return binop(AstOp.Xor, ref i); 77 | case 11: 78 | var a = ParseBinaryBooleanFunc(ctx, vars, bytes, ref i); 79 | return ctx.Neg(a); 80 | // Other operators (add, mul, pow) will not be present in serialized binary truth tables. 81 | default: 82 | throw new InvalidOperationException($"Unrecognized opcode: {opcode}"); 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Mba.Simplifier/Minimization/TruthTable.cs: -------------------------------------------------------------------------------- 1 | using Mba.Simplifier.Bindings; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Diagnostics.CodeAnalysis; 6 | using System.Linq; 7 | using System.Runtime.CompilerServices; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace Mba.Simplifier.Minimization 12 | { 13 | public struct TruthTable 14 | { 15 | public int NumVars { get; } 16 | 17 | public int NumBits => 1 << (ushort)NumVars; 18 | 19 | public readonly ulong[] arr; 20 | 21 | public TruthTable(int numVars) 22 | { 23 | this.NumVars = numVars; 24 | int width = NumBits <= 64 ? 1 : (NumBits >> 6); 25 | arr = new ulong[width]; 26 | } 27 | 28 | public bool GetBit(int index) 29 | { 30 | var wordIdx = index >> 6; 31 | var bitIdx = index - (64 * wordIdx); 32 | return Convert.ToBoolean(1 & (arr[wordIdx] >> (ushort)bitIdx)); 33 | } 34 | 35 | public void SetBit(int index, bool bitVal) 36 | { 37 | var word = index >> 6; 38 | var bitIdx = index - (64 * word); 39 | 40 | var val = Convert.ToUInt64(bitVal); 41 | arr[word] &= ~(1ul << bitIdx); 42 | arr[word] |= (val << bitIdx); 43 | } 44 | 45 | public void Negate() 46 | { 47 | for (int i = 0; i < NumBits; i++) 48 | SetBit(i, GetBit(i) ? false : true); 49 | } 50 | 51 | public void Or(TruthTable other) 52 | { 53 | for (int i = 0; i < arr.Length; i++) 54 | arr[i] |= other.arr[i]; 55 | } 56 | 57 | public void Clear() 58 | { 59 | for (int i = 0; i < arr.Length; i++) 60 | arr[i] = 0; 61 | } 62 | 63 | public bool IsDisjoint(TruthTable other) 64 | { 65 | for (int i = 0; i < arr.Length; i++) 66 | { 67 | if ((arr[i] & other.arr[i]) != 0) 68 | return false; 69 | } 70 | 71 | return true; 72 | } 73 | 74 | public TruthTable Clone() 75 | { 76 | var table = new TruthTable(NumVars); 77 | for(int i = 0; i < arr.Length ; i++) 78 | table.arr[i] = arr[i]; 79 | return table; 80 | } 81 | 82 | public override int GetHashCode() 83 | { 84 | int hash = 17; 85 | foreach(var value in arr) 86 | hash = hash * 23 + value.GetHashCode(); 87 | return hash; 88 | } 89 | 90 | public override bool Equals([NotNullWhen(true)] object? obj) 91 | { 92 | if(obj is not TruthTable table) 93 | return false; 94 | if (NumVars != table.NumVars) 95 | return false; 96 | 97 | for(int i = 0; i < arr.Length; i++) 98 | { 99 | if (arr[i] != table.arr[i]) 100 | return false; 101 | } 102 | 103 | return true; 104 | } 105 | 106 | public List AsList() 107 | { 108 | var vec = new List(); 109 | for (ushort i = 0; i < (ushort)NumBits; i++) 110 | { 111 | var value = GetBit(i); 112 | if (value) 113 | vec.Add(1); 114 | else 115 | vec.Add(0); 116 | } 117 | 118 | return vec; 119 | } 120 | 121 | public int[] AsArray() 122 | { 123 | var arr = new int[NumBits]; 124 | for (ushort i = 0; i < (ushort)NumBits; i++) 125 | { 126 | var value = GetBit(i); 127 | arr[i] = value ? 1 : 0; 128 | } 129 | 130 | return arr; 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /Mba.Simplifier/Minimization/TruthTableEncoder.cs: -------------------------------------------------------------------------------- 1 | using Mba.Simplifier.Bindings; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Mba.Simplifier.Minimization 10 | { 11 | // Class for encoding tables of minimized boolean functions. 12 | public class TruthTableEncoder 13 | { 14 | private const int MAX_TABLES = 4; 15 | 16 | private const int BIT_WIDTH = 64; 17 | 18 | private readonly AstCtx ctx = new AstCtx(); 19 | 20 | private List variables; 21 | 22 | private IReadOnlyList> inputTables; 23 | 24 | public void Run() 25 | { 26 | Console.WriteLine(" "); 27 | AstIdx.ctx = ctx; 28 | variables = Enumerable.Range(0, MAX_TABLES).Select(x => ctx.Symbol($"v{x}", BIT_WIDTH)).ToList(); 29 | inputTables = GetInputTables(ctx, variables); 30 | 31 | // Write the serialized truth table to disk. 32 | for(int i = 0; i < inputTables.Count; i++) 33 | { 34 | var encoded = EncodeTable((uint)i + 2, inputTables[i]); 35 | Directory.CreateDirectory("Output"); 36 | File.WriteAllBytes($@"Output\{i + 2}variable_truthtable.bc", encoded.ToArray()); 37 | } 38 | } 39 | 40 | private static IReadOnlyList> GetInputTables(AstCtx ctx, List variables) 41 | { 42 | var tables = new List>(); 43 | // Load all 2^t bitwise functions for each variable count 44 | for (int numVars = 2; numVars <= MAX_TABLES; numVars++) 45 | { 46 | var table = new List(); 47 | for (int vecIdx = 0; vecIdx < (int)Math.Pow(2, Math.Pow(2, numVars)); vecIdx++) 48 | { 49 | var result = TableDatabase.Instance.GetTableEntry(ctx, variables.Take(numVars).ToList(), vecIdx); 50 | table.Add(result); 51 | } 52 | 53 | tables.Add(table); 54 | } 55 | 56 | return tables; 57 | } 58 | 59 | private IReadOnlyList EncodeTable(uint numVars, List inputTable) 60 | { 61 | var cache = new Dictionary(); 62 | 63 | // We want to start the table of truth tables using 2^t ulongs, where the first 4 bytes indicate the cost of the AST, 64 | // and the next 4 bytes indicate what offset to start deserializing the AST at. 65 | var buffer = new List(); 66 | for(int tableIdx = 0; tableIdx < inputTable.Count; tableIdx++) 67 | { 68 | // Initially we set the "start deserializing index" to zero, because the serialized ASTs have a variable length.s 69 | EncodeUint(buffer, 0); 70 | EncodeUint(buffer, ctx.GetCost(inputTable[tableIdx])); 71 | } 72 | 73 | // Serialize each boolean expr 74 | for (int tableIdx = 0; tableIdx < inputTable.Count; tableIdx++) 75 | { 76 | // Serialize the boolean expression 77 | var start = Serialize(buffer, inputTable[tableIdx], cache); 78 | // Update the index map to point to the start of the serialized expr 79 | var bytes = BitConverter.GetBytes((uint)start); 80 | for(int byteOffset = 0; byteOffset < 4; byteOffset++) 81 | { 82 | buffer[(tableIdx * 8) + byteOffset] = bytes[byteOffset]; 83 | } 84 | 85 | // Verify that deserialization yields the same result 86 | var deserialized = GetTableAst(ctx, buffer, variables, (uint)tableIdx); 87 | if (deserialized != inputTable[tableIdx]) 88 | throw new InvalidOperationException("Deserialization failure!"); 89 | } 90 | 91 | // Serialize each boolean expr 92 | for (int tableIdx = 0; tableIdx < inputTable.Count; tableIdx++) 93 | { 94 | // Verify that deserialization yields the same result 95 | var deserialized = GetTableAst(ctx, buffer, variables, (uint)tableIdx); 96 | if (deserialized != inputTable[tableIdx]) 97 | throw new InvalidOperationException("Deserialization failure!"); 98 | 99 | } 100 | 101 | return buffer; 102 | } 103 | 104 | private int Serialize(List buffer, AstIdx idx, Dictionary cache) 105 | { 106 | // Optionally enable sharing of DAG nodes. 107 | if (cache.TryGetValue(idx, out var existing)) 108 | return existing; 109 | 110 | var opcode = ctx.GetOpcode(idx); 111 | switch(opcode) 112 | { 113 | case AstOp.Symbol: 114 | var len = buffer.Count; 115 | EncodeUint(buffer, GetOpcodeId(opcode)); 116 | EncodeUint(buffer, (byte)variables.IndexOf(idx)); 117 | cache[idx] = len; 118 | return len; 119 | case AstOp.And: 120 | case AstOp.Or: 121 | case AstOp.Xor: 122 | var a = Serialize(buffer, ctx.GetOp0(idx), cache); 123 | var b = Serialize(buffer, ctx.GetOp1(idx), cache); 124 | var offset = buffer.Count; 125 | EncodeUint(buffer, GetOpcodeId(opcode)); 126 | EncodeUint(buffer, (uint)a); 127 | EncodeUint(buffer, (uint)b); 128 | cache[idx] = offset; 129 | return offset; 130 | case AstOp.Neg: 131 | var src = Serialize(buffer, ctx.GetOp0(idx), cache); 132 | var negOffset = buffer.Count; 133 | EncodeUint(buffer, GetOpcodeId(opcode)); 134 | EncodeUint(buffer, (uint)src); 135 | cache[idx] = negOffset; 136 | return negOffset; 137 | default: 138 | throw new InvalidOperationException($"Cannot encode type {opcode}"); 139 | } 140 | } 141 | 142 | private static AstIdx GetTableAst(AstCtx ctx, List buffer, List variables, uint tableIdx) 143 | { 144 | var offset = 8 * tableIdx; 145 | var start = DecodeUint(buffer, (int)offset); 146 | return Deserialize(ctx, buffer, variables, (int)start); 147 | } 148 | 149 | private static AstIdx Deserialize(AstCtx ctx, List buffer, List variables, int offset) 150 | { 151 | var id = buffer[offset]; 152 | offset += 4; 153 | 154 | switch(id) 155 | { 156 | case 2: 157 | var symbolIdx = buffer[offset]; 158 | return variables[symbolIdx]; 159 | case 8: 160 | case 9: 161 | case 10: 162 | var aOffset = DecodeUint(buffer, offset); 163 | offset += 4; 164 | var bOffset = DecodeUint(buffer, offset); 165 | 166 | var a = Deserialize(ctx, buffer, variables, (int)aOffset); 167 | 168 | var b = Deserialize(ctx, buffer, variables, (int)bOffset); 169 | 170 | var opcode = id switch 171 | { 172 | 8 => AstOp.And, 173 | 9 => AstOp.Or, 174 | 10 => AstOp.Xor, 175 | }; 176 | 177 | return ctx.Binop(opcode, a, b); 178 | 179 | case 11: 180 | var srcOffset = DecodeUint(buffer, offset); 181 | var src = Deserialize(ctx, buffer, variables, (int)srcOffset); 182 | return ctx.Neg(src); 183 | 184 | default: 185 | throw new InvalidOperationException($"Cannot deserialize opcode {id}"); 186 | } 187 | } 188 | 189 | private static byte GetOpcodeId(AstOp opcode) 190 | { 191 | return opcode switch 192 | { 193 | AstOp.Symbol => 2, 194 | AstOp.And => 8, 195 | AstOp.Or => 9, 196 | AstOp.Xor => 10, 197 | AstOp.Neg => 11, 198 | _ => throw new InvalidOperationException($"Cannot encode type {opcode}") 199 | }; 200 | } 201 | 202 | private static void EncodeByte(List buffer, byte value) 203 | { 204 | buffer.Add(value); 205 | } 206 | 207 | private static void EncodeUint(List buffer, uint value) 208 | { 209 | var bytes = BitConverter.GetBytes(value); 210 | buffer.AddRange(bytes); 211 | } 212 | 213 | private static uint DecodeUint(List buffer, int start) 214 | { 215 | return BitConverter.ToUInt32(buffer.Skip(start).Take(4).ToArray()); 216 | } 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /Mba.Simplifier/Minimization/TruthTables/1variable_truthtable.txt: -------------------------------------------------------------------------------- 1 | (x&~x) 2 | ~x 3 | x 4 | ~(x&~x) 5 | -------------------------------------------------------------------------------- /Mba.Simplifier/Minimization/TruthTables/2variable_truthtable.bc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mazeworks-security/Simplifier/acea9cb7977f40faa27c6cd031b00454968c3d9a/Mba.Simplifier/Minimization/TruthTables/2variable_truthtable.bc -------------------------------------------------------------------------------- /Mba.Simplifier/Minimization/TruthTables/2variable_truthtable.txt: -------------------------------------------------------------------------------- 1 | (x&~x) 2 | ~(x|y) 3 | ~(x|~y) 4 | ~x 5 | (x&~y) 6 | ~y 7 | (x^y) 8 | ~(x&y) 9 | (x&y) 10 | ~(x^y) 11 | y 12 | ~(x&~y) 13 | x 14 | (x|~y) 15 | (x|y) 16 | ~(x&~x) 17 | -------------------------------------------------------------------------------- /Mba.Simplifier/Minimization/TruthTables/3variable_truthtable.bc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mazeworks-security/Simplifier/acea9cb7977f40faa27c6cd031b00454968c3d9a/Mba.Simplifier/Minimization/TruthTables/3variable_truthtable.bc -------------------------------------------------------------------------------- /Mba.Simplifier/Minimization/TruthTables/3variable_truthtable.txt: -------------------------------------------------------------------------------- 1 | (x&(~x)) 2 | (((~z)&(~y))&(~x)) 3 | (((~z)&(~y))&x) 4 | ((~z)&(~y)) 5 | (((~z)&y)&(~x)) 6 | ((~z)&(~x)) 7 | ((~z)&(x^y)) 8 | (((~z)&(~x))|((~z)&(~y))) 9 | (((~z)&y)&x) 10 | ((~z)&(~(x^y))) 11 | ((~z)&x) 12 | (((~z)&x)|((~z)&(~y))) 13 | ((~z)&y) 14 | (((~z)&(~x))|((~z)&y)) 15 | (((~z)&x)|((~z)&y)) 16 | (~z) 17 | ((z&(~y))&(~x)) 18 | ((~y)&(~x)) 19 | ((~y)&(x^z)) 20 | (((~y)&(~x))|((~z)&(~y))) 21 | ((y^z)&(~x)) 22 | (((~y)&(~x))|((~z)&(~x))) 23 | (((~y)&(x^z))|((~z)&(x^y))) 24 | ((((~y)&(~x))|((~z)&(~x)))|((~z)&(~y))) 25 | ((x^z)&(y^z)) 26 | (((~y)&(~x))|((~z)&(~(x^y)))) 27 | (((~y)&(x^z))|((~z)&x)) 28 | (((~y)&(~x))|((~z)&x)) 29 | (((y^z)&(~x))|((~z)&y)) 30 | (((~y)&(~x))|((~z)&y)) 31 | ((((y&x)^x)^y)^z) 32 | (((~y)&(~x))|(~z)) 33 | ((z&(~y))&x) 34 | ((~y)&(~(x^z))) 35 | ((~y)&x) 36 | (((~y)&x)|((~z)&(~y))) 37 | ((~(x^z))&(y^z)) 38 | (((~y)&(~(x^z)))|((~z)&(~x))) 39 | (((~y)&x)|((~z)&(x^y))) 40 | (((~y)&x)|((~z)&(~x))) 41 | ((y^z)&x) 42 | (((~y)&(~(x^z)))|((~z)&(~(x^y)))) 43 | (((~y)&x)|((~z)&x)) 44 | ((((~y)&x)|((~z)&x))|((~z)&(~y))) 45 | (((y^z)&x)|((~z)&y)) 46 | (~(((y&x)^x)^z)) 47 | (((~y)&x)|((~z)&y)) 48 | (((~y)&x)|(~z)) 49 | (z&(~y)) 50 | (((~y)&(~x))|(z&(~y))) 51 | (((~y)&x)|(z&(~y))) 52 | (~y) 53 | (((y^z)&(~x))|(z&(~y))) 54 | (((~z)&(~x))|(z&(~y))) 55 | ((((z&x)^x)^y)^z) 56 | (((~z)&(~x))|(~y)) 57 | (((y^z)&x)|(z&(~y))) 58 | (~(((z&x)^x)^y)) 59 | (((~z)&x)|(z&(~y))) 60 | (((~z)&x)|(~y)) 61 | (y^z) 62 | (((~z)&(~x))|(y^z)) 63 | (((~z)&x)|(y^z)) 64 | ((~y)|(~z)) 65 | ((z&y)&(~x)) 66 | ((~(y^z))&(~x)) 67 | ((x^z)&(~(y^z))) 68 | (((~(y^z))&(~x))|((~z)&(~y))) 69 | (y&(~x)) 70 | ((y&(~x))|((~z)&(~x))) 71 | ((y&(~x))|((~z)&(x^y))) 72 | ((y&(~x))|((~z)&(~y))) 73 | (y&(x^z)) 74 | ((y&(x^z))|((~z)&(~(x^y)))) 75 | ((y&(x^z))|((~z)&x)) 76 | (~(((y&x)^y)^z)) 77 | ((y&(~x))|((~z)&y)) 78 | (((y&(~x))|((~z)&(~x)))|((~z)&y)) 79 | ((y&(~x))|((~z)&x)) 80 | ((y&(~x))|(~z)) 81 | (z&(~x)) 82 | (((~y)&(~x))|(z&(~x))) 83 | (((~y)&(x^z))|(z&(~x))) 84 | ((z&(~x))|((~z)&(~y))) 85 | ((y&(~x))|(z&(~x))) 86 | (~x) 87 | ((((z&y)^x)^y)^z) 88 | (((~z)&(~y))|(~x)) 89 | ((y&(x^z))|(z&(~x))) 90 | (~(((z&y)^x)^y)) 91 | (x^z) 92 | (((~z)&(~y))|(x^z)) 93 | ((z&(~x))|((~z)&y)) 94 | (((~z)&y)|(~x)) 95 | (((~z)&y)|(x^z)) 96 | ((~x)|(~z)) 97 | (z&(x^y)) 98 | (((~y)&(~(x^z)))|(z&(x^y))) 99 | (((~y)&x)|(z&(x^y))) 100 | (~(((z&x)^y)^z)) 101 | ((y&(~x))|(z&(x^y))) 102 | (~(((z&y)^x)^z)) 103 | (x^y) 104 | (((~z)&(~y))|(x^y)) 105 | ((y&(x^z))|(z&(x^y))) 106 | (~((x^y)^z)) 107 | ((z&y)^x) 108 | (((~z)&(~y))|(~((x^y)^z))) 109 | ((z&x)^y) 110 | (((~z)&y)|(~((x^y)^z))) 111 | (((~z)&y)|(x^y)) 112 | ((x^y)|(~z)) 113 | ((z&(~x))|(z&(~y))) 114 | ((((~y)&(~x))|(z&(~x)))|(z&(~y))) 115 | (((~y)&x)|(z&(~x))) 116 | ((z&(~x))|(~y)) 117 | ((y&(~x))|(z&(~y))) 118 | ((z&(~y))|(~x)) 119 | ((z&(~y))|(x^y)) 120 | ((~x)|(~y)) 121 | ((y&x)^z) 122 | ((z&(~y))|(~((x^y)^z))) 123 | ((z&(~y))|(x^z)) 124 | ((x^z)|(~y)) 125 | ((z&(~x))|(y^z)) 126 | ((~x)|(y^z)) 127 | ((x^z)|(y^z)) 128 | (((~x)|(~y))|(~z)) 129 | ((z&y)&x) 130 | ((~(x^z))&(~(y^z))) 131 | ((~(y^z))&x) 132 | (((~(y^z))&x)|((~z)&(~y))) 133 | (y&(~(x^z))) 134 | ((y&(~(x^z)))|((~z)&(~x))) 135 | ((y&(~(x^z)))|((~z)&(x^y))) 136 | (~((y&x)^z)) 137 | (y&x) 138 | ((y&x)|((~z)&(~(x^y)))) 139 | ((y&x)|((~z)&x)) 140 | ((y&x)|((~z)&(~y))) 141 | ((y&x)|((~z)&y)) 142 | ((y&x)|((~z)&(~x))) 143 | (((y&x)|((~z)&x))|((~z)&y)) 144 | ((y&x)|(~z)) 145 | (z&(~(x^y))) 146 | (((~y)&(~x))|(z&(~(x^y)))) 147 | (((~y)&(x^z))|(z&(~(x^y)))) 148 | (~((z&x)^y)) 149 | ((y&(~(x^z)))|(z&(~(x^y)))) 150 | (~((z&y)^x)) 151 | ((x^y)^z) 152 | (((~z)&(~y))|((x^y)^z)) 153 | ((y&x)|(z&(~(x^y)))) 154 | (~(x^y)) 155 | (((z&y)^x)^z) 156 | (((~z)&(~y))|(~(x^y))) 157 | (((z&x)^y)^z) 158 | (((~z)&y)|(~(x^y))) 159 | (((~z)&y)|((x^y)^z)) 160 | ((~(x^y))|(~z)) 161 | (z&x) 162 | (((~y)&(~(x^z)))|(z&x)) 163 | (((~y)&x)|(z&x)) 164 | ((z&x)|((~z)&(~y))) 165 | ((y&(~(x^z)))|(z&x)) 166 | (~(x^z)) 167 | (((z&y)^x)^y) 168 | (((~z)&(~y))|(~(x^z))) 169 | ((y&x)|(z&x)) 170 | (~((((z&y)^x)^y)^z)) 171 | x 172 | (((~z)&(~y))|x) 173 | ((z&x)|((~z)&y)) 174 | (((~z)&y)|(~(x^z))) 175 | (((~z)&y)|x) 176 | (x|(~z)) 177 | ((z&x)|(z&(~y))) 178 | (((~y)&(~x))|(z&x)) 179 | ((((~y)&x)|(z&x))|(z&(~y))) 180 | ((z&x)|(~y)) 181 | (((y&x)^y)^z) 182 | ((z&(~y))|(~(x^z))) 183 | ((z&(~y))|((x^y)^z)) 184 | ((~(x^z))|(~y)) 185 | ((y&x)|(z&(~y))) 186 | ((z&(~y))|(~(x^y))) 187 | ((z&(~y))|x) 188 | (x|(~y)) 189 | ((z&x)|(y^z)) 190 | ((~(x^z))|(y^z)) 191 | (x|(y^z)) 192 | ((x|(~y))|(~z)) 193 | (z&y) 194 | (((~(y^z))&(~x))|(z&y)) 195 | (((~(y^z))&x)|(z&y)) 196 | (~(y^z)) 197 | ((y&(~x))|(z&y)) 198 | (((~z)&(~x))|(z&y)) 199 | (((z&x)^x)^y) 200 | (((~z)&(~x))|(~(y^z))) 201 | ((y&x)|(z&y)) 202 | (~((((z&x)^x)^y)^z)) 203 | (((~z)&x)|(z&y)) 204 | (((~z)&x)|(~(y^z))) 205 | y 206 | (((~z)&(~x))|y) 207 | (((~z)&x)|y) 208 | (y|(~z)) 209 | ((z&(~x))|(z&y)) 210 | (((~y)&(~x))|(z&y)) 211 | (((y&x)^x)^z) 212 | ((z&(~x))|(~(y^z))) 213 | (((y&(~x))|(z&(~x)))|(z&y)) 214 | ((z&y)|(~x)) 215 | ((z&y)|((x^y)^z)) 216 | ((~x)|(~(y^z))) 217 | ((y&x)|(z&(~x))) 218 | ((z&y)|(~(x^y))) 219 | ((z&y)|(x^z)) 220 | ((x^z)|(~(y^z))) 221 | ((z&(~x))|y) 222 | ((~x)|y) 223 | ((x^z)|y) 224 | (((~x)|y)|(~z)) 225 | ((z&x)|(z&y)) 226 | (~((((y&x)^x)^y)^z)) 227 | (((~y)&x)|(z&y)) 228 | ((z&x)|(~(y^z))) 229 | ((y&(~x))|(z&x)) 230 | ((z&y)|(~(x^z))) 231 | ((z&y)|(x^y)) 232 | ((~(x^z))|(~(y^z))) 233 | (((y&x)|(z&x))|(z&y)) 234 | ((z&y)|(~((x^y)^z))) 235 | ((z&y)|x) 236 | (x|(~(y^z))) 237 | ((z&x)|y) 238 | ((~(x^z))|y) 239 | (x|y) 240 | ((x|y)|(~z)) 241 | z 242 | (((~y)&(~x))|z) 243 | (((~y)&x)|z) 244 | ((~y)|z) 245 | ((y&(~x))|z) 246 | ((~x)|z) 247 | ((x^y)|z) 248 | (((~x)|(~y))|z) 249 | ((y&x)|z) 250 | ((~(x^y))|z) 251 | (x|z) 252 | ((x|(~y))|z) 253 | (y|z) 254 | (((~x)|y)|z) 255 | ((x|y)|z) 256 | (~(x&(~x))) 257 | -------------------------------------------------------------------------------- /Mba.Simplifier/Minimization/TruthTables/4variable_truthtable.bc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mazeworks-security/Simplifier/acea9cb7977f40faa27c6cd031b00454968c3d9a/Mba.Simplifier/Minimization/TruthTables/4variable_truthtable.bc -------------------------------------------------------------------------------- /Mba.Simplifier/Pipeline/DecisionTable.cs: -------------------------------------------------------------------------------- 1 | using Mba.Simplifier.Minimization; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Mba.Simplifier.Pipeline 9 | { 10 | [Flags] 11 | public enum Decision : byte 12 | { 13 | None = 1, 14 | First = 2, 15 | Second = 4, 16 | Both = 8, 17 | } 18 | 19 | // This is a quaternary truth table implemented in a somewhat adhoc way. 20 | // TODO: Abstract truth table struct to support arbitrary sizes. 21 | // Alternative TODO: Compact two decisions into a single `byte`, instead of allocating a byte for each decision 22 | public class DecisionTable 23 | { 24 | private readonly int numVars; 25 | 26 | public int NumBits => 1 << (ushort)numVars; 27 | 28 | public readonly Decision[] arr; 29 | 30 | public DecisionTable(int numVars) 31 | { 32 | this.numVars = numVars; 33 | arr = new Decision[NumBits]; 34 | } 35 | 36 | public Decision GetDecision(int index) 37 | { 38 | return arr[index]; 39 | } 40 | 41 | public void AddDecision(int index, Decision value) 42 | { 43 | arr[index] |= value; 44 | } 45 | 46 | public void SetDecision(int index, Decision value) 47 | { 48 | arr[index] = value; 49 | } 50 | 51 | public List> AsList() 52 | { 53 | var output = new List>(); 54 | foreach(var decision in arr) 55 | { 56 | var curr = new List(); 57 | if (decision.HasFlag(Decision.None)) 58 | curr.Add(Decision.None); 59 | if (decision.HasFlag(Decision.First)) 60 | curr.Add(Decision.First); 61 | if (decision.HasFlag(Decision.Second)) 62 | curr.Add(Decision.Second); 63 | if (decision.HasFlag(Decision.Both)) 64 | curr.Add(Decision.Both); 65 | 66 | output.Add(curr); 67 | } 68 | 69 | return output; 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Mba.Simplifier/Pipeline/Intermediate/IntermediatePoly.cs: -------------------------------------------------------------------------------- 1 | using Mba.Simplifier.Bindings; 2 | using Mba.Utility; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace Mba.Simplifier.Pipeline.Intermediate 11 | { 12 | // This is an intermediate stage between our SparsePolynomial representation, and the 13 | // representation that's necessary for arbitrary polynomial reduction. 14 | // In the future these should be abstracted into a single data structure. 15 | public class IntermediatePoly 16 | { 17 | public readonly uint bitWidth; 18 | 19 | public readonly ulong moduloMask; 20 | 21 | public readonly Dictionary coeffs = new(); 22 | 23 | public IntermediatePoly(uint bitWidth) 24 | { 25 | this.bitWidth = bitWidth; 26 | moduloMask = (ulong)ModuloReducer.GetMask(bitWidth); 27 | } 28 | 29 | public void Sum(IntermediateMonomial monomial, ulong value) 30 | { 31 | bool contained = coeffs.TryGetValue(monomial, out var old); 32 | old += value; 33 | old &= moduloMask; 34 | if (old == 0) 35 | { 36 | if(contained) 37 | coeffs.Remove(monomial); 38 | return; 39 | } 40 | coeffs[monomial] = old; 41 | } 42 | 43 | public override string ToString() 44 | { 45 | var terms = new List(); 46 | foreach(var (monom, coeff) in coeffs) 47 | terms.Add($"{coeff}*({monom})"); 48 | 49 | return String.Join(" + ", terms); 50 | } 51 | 52 | public static IntermediatePoly Add(IReadOnlyList polys) 53 | { 54 | var width = polys.First().bitWidth; 55 | ulong constSum = 0; 56 | var outPoly = new IntermediatePoly(width); 57 | foreach (var poly in polys) 58 | { 59 | foreach (var (monom, coeff) in poly.coeffs) 60 | { 61 | outPoly.Sum(monom, coeff); 62 | } 63 | } 64 | 65 | return outPoly; 66 | } 67 | 68 | public static IntermediatePoly Mul(AstCtx ctx, IReadOnlyList polys) 69 | { 70 | var width = polys.First().bitWidth; 71 | ulong coeffSum = 1; 72 | var outPoly = new IntermediatePoly(width); 73 | // Set the initial polynomial to `1`. 74 | outPoly.coeffs[IntermediateMonomial.Constant(ctx, width)] = 1; 75 | foreach (var poly in polys) 76 | { 77 | outPoly = Mul(ctx, outPoly, poly); 78 | } 79 | 80 | return outPoly; 81 | } 82 | 83 | public static IntermediatePoly Mul(AstCtx ctx, IntermediatePoly a, IntermediatePoly b) 84 | { 85 | var outPoly = new IntermediatePoly(a.bitWidth); 86 | foreach (var (monomA, coeffA) in a.coeffs) 87 | { 88 | var isAConstant = IsConstant(ctx, monomA); 89 | foreach (var (monomB, coeffB) in b.coeffs) 90 | { 91 | var newCoeff = coeffA * coeffB; 92 | newCoeff &= a.moduloMask; 93 | 94 | // Then we need to construct a new monomial. 95 | var newMonom = Mul(ctx, monomA, monomB); 96 | outPoly.Sum(newMonom, newCoeff); 97 | } 98 | } 99 | 100 | return outPoly; 101 | } 102 | 103 | public static IntermediateMonomial Mul(AstCtx ctx, IntermediateMonomial a, IntermediateMonomial b) 104 | { 105 | // If both are a constant, return the first monomial. 106 | var isAConstant = IsConstant(ctx, a); 107 | var isBConstant = IsConstant(ctx, b); 108 | if (isAConstant && isBConstant) 109 | return a; 110 | // If one is a constant, return the other. 111 | if (isAConstant) 112 | return b; 113 | if (isBConstant) 114 | return a; 115 | // Otherwise we need to multiply. 116 | var outputDict = new Dictionary(); 117 | foreach (var (basis, deg) in a.varDegrees) 118 | { 119 | outputDict.TryAdd(basis, 0); 120 | outputDict[basis] += deg; 121 | } 122 | foreach (var (basis, deg) in b.varDegrees) 123 | { 124 | outputDict.TryAdd(basis, 0); 125 | outputDict[basis] += deg; 126 | } 127 | 128 | return new IntermediateMonomial(outputDict); 129 | } 130 | 131 | public static bool IsConstant(AstCtx ctx, IntermediateMonomial monom) 132 | { 133 | if (monom.varDegrees.Count != 1) 134 | return false; 135 | var asConstant = ctx.TryGetConstantValue(monom.varDegrees.First().Key); 136 | if (asConstant == null) 137 | return false; 138 | Debug.Assert(asConstant.Value == 1); 139 | Debug.Assert(monom.varDegrees.First().Value <= 1); 140 | return true; 141 | } 142 | } 143 | 144 | public class IntermediateMonomial : IEquatable 145 | { 146 | public IReadOnlyDictionary varDegrees; 147 | 148 | private readonly int hash; 149 | 150 | public IntermediateMonomial(IReadOnlyDictionary varDegrees) 151 | { 152 | this.varDegrees = varDegrees; 153 | hash = ComputeHash(varDegrees); 154 | } 155 | 156 | public override int GetHashCode() 157 | { 158 | return hash; 159 | } 160 | 161 | public override string ToString() 162 | { 163 | List terms = new(); 164 | bool unroll = true; 165 | foreach(var (var, deg) in varDegrees) 166 | { 167 | if (!unroll) 168 | terms.Add($"{var}**{deg}"); 169 | else 170 | terms.Add(String.Join("*", Enumerable.Repeat(var.ToString(), (int)deg))); 171 | } 172 | 173 | return String.Join("*", terms); 174 | } 175 | 176 | private static int ComputeHash(IReadOnlyDictionary varDegrees) 177 | { 178 | int hash = 17; 179 | foreach (var (var, deg) in varDegrees) 180 | { 181 | // The hashes must not be dependent on one another, since the order of the dictionary is not guaranteed. 182 | var tempHash = (var.GetHashCode() * 31) + (deg.GetHashCode() * 17); 183 | hash += tempHash; 184 | } 185 | 186 | return hash; 187 | } 188 | 189 | public static IntermediateMonomial Constant(AstCtx ctx, uint width) 190 | { 191 | var constant = ctx.Constant(1, (byte)width); 192 | // Represent a constant as a constant monomial of `1`, with the constant being contained in the coefficient. 193 | var constMonom = new IntermediateMonomial(new Dictionary() { { constant, 1 } }); 194 | return constMonom; 195 | } 196 | 197 | public bool Equals(IntermediateMonomial? other) 198 | { 199 | if (hash != other.GetHashCode()) 200 | return false; 201 | if (varDegrees.Count != other.varDegrees.Count) 202 | return false; 203 | foreach(var (var, deg) in varDegrees) 204 | { 205 | if (!other.varDegrees.TryGetValue(var, out var otherDeg)) 206 | return false; 207 | if (deg != otherDeg) 208 | return false; 209 | } 210 | 211 | return true; 212 | } 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /Mba.Simplifier/Pipeline/LinearCongruenceSolver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Mba.Simplifier.Pipeline 8 | { 9 | public record Lc(UInt128 d, UInt128 x0, UInt128 n); 10 | 11 | public class LinearCongruenceSolver 12 | { 13 | private readonly UInt128 moduloMask; 14 | 15 | public LinearCongruenceSolver(UInt128 moduloMask) 16 | { 17 | this.moduloMask = moduloMask; 18 | } 19 | 20 | public Lc? LinearCongruence(UInt128 A, UInt128 B, UInt128 N) 21 | { 22 | A = R(A % N); 23 | B = R(B % N); 24 | 25 | UInt128 u = 0, v = 0; 26 | 27 | UInt128[] person = ExtendedEuclidean(A, N); 28 | UInt128 d = person[0]; 29 | u = person[1]; 30 | v = person[2]; 31 | 32 | // No solution exists 33 | if (R(B % d) != 0) 34 | { 35 | return null; 36 | } 37 | 38 | // Else, initialize the value of x0 39 | UInt128 x0 = R((u * R((B / d)))) % N; 40 | x0 = R(x0); 41 | if (x0 < 0) 42 | x0 += N; 43 | 44 | x0 = R(x0); 45 | return new Lc(d, x0, N); 46 | } 47 | 48 | public UInt128[] ExtendedEuclidean(UInt128 a, UInt128 b) 49 | { 50 | // Base Case 51 | if (a == 0) 52 | { 53 | return new UInt128[] { b, 0, 1 }; 54 | } 55 | else 56 | { 57 | UInt128 x1 = 1, y1 = 1; 58 | UInt128[] gcdy = ExtendedEuclidean(b % a, a); 59 | UInt128 gcd = gcdy[0]; 60 | x1 = gcdy[1]; 61 | y1 = gcdy[2]; 62 | 63 | UInt128 y = x1; 64 | UInt128 x = y1 - (UInt128)(R(b / a)) * x1; 65 | 66 | return new UInt128[] { gcd, x, y }; 67 | } 68 | } 69 | 70 | public UInt128 GetSolution(UInt128 i, Lc solutions) 71 | { 72 | UInt128 an = R(solutions.x0 + i * R(solutions.n / solutions.d)) % solutions.n; 73 | an = R(an); 74 | return an; 75 | } 76 | 77 | private UInt128 R(UInt128 a) 78 | => moduloMask & a; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Mba.Simplifier/Polynomial/DensePolynomial.cs: -------------------------------------------------------------------------------- 1 | using Mba.Utility; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Mba.Simplifier.Polynomial 10 | { 11 | /// 12 | /// Dense representation of a multivariate polynomial. 13 | /// 14 | public unsafe class DensePolynomial 15 | { 16 | public readonly byte width; 17 | 18 | public readonly ulong moduloMask; 19 | 20 | public readonly int[] dimensions; 21 | 22 | public readonly ulong[] coeffs; 23 | 24 | public int NumVars => dimensions.Length; 25 | 26 | public DensePolynomial(byte width, int[] maxVarDegrees) 27 | { 28 | this.width = width; 29 | moduloMask = (ulong)ModuloReducer.GetMask(width); 30 | dimensions = maxVarDegrees.Select(x => x + 1).ToArray(); 31 | int arrSize = 1; 32 | foreach (var deg in maxVarDegrees) 33 | { 34 | arrSize *= (deg + 1); 35 | } 36 | 37 | coeffs = new ulong[arrSize]; 38 | } 39 | 40 | public Monomial GetMonomial(int index) 41 | { 42 | var degrees = GetDegrees(index); 43 | var mDegs = new byte[degrees.Length]; 44 | for(int i = 0; i < degrees.Length; i++) 45 | mDegs[i] = (byte)degrees[i]; 46 | var m = new Monomial(mDegs); 47 | return m; 48 | } 49 | 50 | public ulong GetCoeff(Monomial monom) 51 | { 52 | return coeffs[GetMonomIdx(monom)]; 53 | } 54 | 55 | public int GetMonomIdx(Monomial monom) 56 | { 57 | var degrees = new int[monom.GetNumVars()]; 58 | for(int i = 0; i < degrees.Length; i++) 59 | degrees[i] = monom.GetVarDeg(i); 60 | 61 | var idx = GetIndex(degrees); 62 | return idx; 63 | } 64 | 65 | public ulong GetCoeff(params int[] degrees) 66 | { 67 | var idx = GetIndex(degrees); 68 | return coeffs[idx]; 69 | } 70 | 71 | public void SetCoeff(Monomial monomial, ulong value) 72 | { 73 | SetCoeff(GetMonomIdx(monomial), value); 74 | } 75 | 76 | public void SetCoeff(int index, ulong value) 77 | { 78 | value &= moduloMask; 79 | coeffs[index] = value; 80 | } 81 | 82 | public void Sum(Monomial monomial, ulong value) 83 | { 84 | Sum(GetMonomIdx(monomial), value); 85 | } 86 | 87 | public void Sum(int index, ulong value) 88 | { 89 | coeffs[index] += value; 90 | coeffs[index] &= moduloMask; 91 | } 92 | 93 | public int GetIndex(params int[] degrees) 94 | { 95 | if (dimensions.Length != degrees.Length) 96 | throw new ArgumentException("Dimensions and indices must have the same length."); 97 | 98 | int index = 0; 99 | for (int i = 0; i < dimensions.Length; i++) 100 | { 101 | index *= dimensions[i]; 102 | index += degrees[i]; 103 | } 104 | return index; 105 | } 106 | 107 | public int[] GetDegrees(int index) 108 | { 109 | int[] indices = new int[dimensions.Length]; 110 | 111 | for (int i = dimensions.Length - 1; i >= 0; i--) 112 | { 113 | indices[i] = (int)((uint)index % (uint)dimensions[i]); 114 | index = (int)((uint)index / (uint)dimensions[i]); 115 | } 116 | 117 | return indices; 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Mba.Simplifier/Polynomial/Monomial.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Mba.Simplifier.Polynomial 9 | { 10 | // Represents a multivariate monomial, with up to 8 variables. 11 | public struct Monomial : IEquatable, IComparable 12 | { 13 | // Each byte represents the degree of a variable. 14 | // Note that 255 is a special value representing the end of the degree list. 15 | private readonly ulong value = ulong.MaxValue; 16 | 17 | public IReadOnlyList Degrees => BitConverter.GetBytes(value).TakeWhile(x => x != 255).ToList(); 18 | 19 | public Monomial(params byte[] varDegrees) 20 | { 21 | Debug.Assert(varDegrees.Length <= 8); 22 | for (int i = 0; i < varDegrees.Length; i++) 23 | { 24 | var degree = (ulong)varDegrees[i]; 25 | var shiftBy = (ushort)(i * 8); 26 | var mask = 255ul << shiftBy; 27 | value &= ~mask; 28 | value |= (degree << shiftBy); 29 | } 30 | } 31 | 32 | public Monomial(int varDeg) : this((byte)varDeg) 33 | { 34 | 35 | } 36 | 37 | public byte GetVarDeg(int varIdx) 38 | { 39 | var shiftBy = 8 * varIdx; 40 | return (byte)(value >> (ushort)shiftBy); 41 | } 42 | 43 | public uint GetNumVars() 44 | { 45 | uint total = 0; 46 | for (int i = 0; i < 8; i++) 47 | { 48 | var degree = GetVarDeg(i); 49 | if (degree == byte.MaxValue) 50 | break; 51 | total += 1; 52 | } 53 | 54 | return total; 55 | } 56 | 57 | public uint GetTotalDeg() 58 | { 59 | uint total = 0; 60 | for (int i = 0; i < 8; i++) 61 | { 62 | var degree = GetVarDeg(i); 63 | if (degree == byte.MaxValue) 64 | break; 65 | 66 | total += degree; 67 | } 68 | 69 | return total; 70 | } 71 | 72 | public bool Equals(Monomial other) 73 | { 74 | return value == other.value; 75 | } 76 | 77 | // Compare the two monomials. 78 | // If they are equal, return 0. 79 | // If a is greater than b, return 1. 80 | // If a is less than b, return -1. 81 | public int CompareTo(Monomial b) 82 | { 83 | var a = this; 84 | 85 | if (a.Equals(b)) 86 | return 0; 87 | var totalA = a.GetTotalDeg(); 88 | var totalB = b.GetTotalDeg(); 89 | if (totalA > totalB) 90 | return 1; 91 | else if (totalB > totalA) 92 | return -1; 93 | 94 | for (int i = 0; i < 8; i++) 95 | { 96 | var degA = a.GetVarDeg(i); 97 | var degB = b.GetVarDeg(i); 98 | if (degA == byte.MaxValue) 99 | return degB; 100 | if (degB == byte.MaxValue) 101 | return degA; 102 | if (degA == degB) 103 | continue; 104 | if (degA > degB) 105 | return 1; 106 | else 107 | return -1; 108 | } 109 | return 0; 110 | } 111 | 112 | public override string ToString() 113 | { 114 | return ToString(true); 115 | } 116 | 117 | public string ToString(bool canonicalBasis = true) 118 | { 119 | var varDegrees = Degrees; 120 | List powers = new(); 121 | for (int i = 0; i < varDegrees.Count; i++) 122 | { 123 | var degree = varDegrees[i]; 124 | if (degree == byte.MaxValue) 125 | break; 126 | 127 | if (degree == 0) 128 | { 129 | continue; 130 | } 131 | 132 | var varName = $"x{i}"; 133 | if (degree == 1) 134 | { 135 | powers.Add(varName); 136 | continue; 137 | } 138 | 139 | bool unroll = true; 140 | string pow = null; 141 | if (canonicalBasis) 142 | pow = GetCanonicalBasisStr(varName, degree, unroll); 143 | else 144 | pow = GetFactorialBasisStr(varName, degree); 145 | powers.Add(pow); 146 | } 147 | 148 | var txt = String.Join("*", powers); 149 | return txt; 150 | } 151 | 152 | private string GetCanonicalBasisStr(string varName, int degree, bool unroll) 153 | { 154 | var pow = unroll ? String.Join("*", Enumerable.Repeat(varName, degree)) : $"{varName}**{degree}"; 155 | return pow; 156 | } 157 | 158 | private string GetFactorialBasisStr(string varName, int degree) 159 | { 160 | List factors = new(); 161 | for(int i = 1; i <= degree; i++) 162 | { 163 | if (i == 1) 164 | factors.Add($"{varName}"); 165 | else 166 | factors.Add($"({varName}-{i - 1})"); 167 | } 168 | 169 | return String.Join("*", factors); 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /Mba.Simplifier/Polynomial/PolynomialEvaluator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Mba.Simplifier.Polynomial 8 | { 9 | public static class PolynomialEvaluator 10 | { 11 | public static ulong Eval(SparsePolynomial poly, ulong[] inputs, bool canonicalBasis = true) 12 | { 13 | ulong sum = 0; 14 | var varCount = poly.numVars; 15 | foreach(var (monom, coeff) in poly.coeffs) 16 | { 17 | ulong result = 1; 18 | for(int i = 0; i < varCount; i++) 19 | { 20 | var deg = monom.GetVarDeg(i); 21 | if (deg == 0) 22 | continue; 23 | if (canonicalBasis) 24 | result *= Pow(inputs[i], deg); 25 | else 26 | result *= FactorialPow(inputs[i], deg); 27 | } 28 | 29 | result *= coeff; 30 | sum += result; 31 | } 32 | 33 | return sum; 34 | } 35 | 36 | // TODO: Use repeated squaring algorithm for high degrees. 37 | public static ulong Pow(ulong x, byte power) 38 | { 39 | if (power == 0) 40 | return 1; 41 | if (power == 1) 42 | return x; 43 | 44 | var original = x; 45 | var originalBv = x; 46 | for(byte i = 1; i < power; i++) 47 | { 48 | x *= originalBv; 49 | } 50 | 51 | return x; 52 | } 53 | 54 | public static ulong FactorialPow(ulong x, byte power) 55 | { 56 | if (power == 0) 57 | return 1; 58 | if (power == 1) 59 | return x; 60 | 61 | var original = x; 62 | var originalBv = x; 63 | for (byte i = 1; i < power; i++) 64 | { 65 | x *= (originalBv - (ulong)i); 66 | } 67 | 68 | return x; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Mba.Simplifier/Polynomial/PowerCollections/Strings.cs: -------------------------------------------------------------------------------- 1 | //****************************** 2 | // Written by Peter Golde 3 | // Copyright (c) 2004-2005, Wintellect 4 | // 5 | // Use and restribution of this code is subject to the license agreement 6 | // contained in the file "License.txt" accompanying this file. 7 | //****************************** 8 | 9 | using System; 10 | 11 | namespace Wintellect.PowerCollections 12 | { 13 | /// 14 | /// A holder class for localizable strings that are used. Currently, these are not loaded from resources, but 15 | /// just coded into this class. To make this library localizable, simply change this class to load the 16 | /// given strings from resources. 17 | /// 18 | internal static class Strings 19 | { 20 | public static readonly string UncomparableType = "Type \"{0}\" does not implement IComparable<{0}> or IComparable."; 21 | public static readonly string ArgMustNotBeNegative = "The argument may not be less than zero."; 22 | public static readonly string ArrayTooSmall = "The array is too small to hold all of the items."; 23 | public static readonly string KeyNotFound = "The key was not found in the collection."; 24 | public static readonly string ResetNotSupported = "Reset is not supported on this enumerator."; 25 | public static readonly string CannotModifyCollection = "The \"{0}\" collection is read-only and cannot be modified."; 26 | public static readonly string KeyAlreadyPresent = "The key was already present in the dictionary."; 27 | public static readonly string WrongType = "The value \"{0}\" isn't of type \"{1}\" and can't be used in this generic collection."; 28 | public static readonly string MustOverrideOrReimplement = "This method must be overridden or re-implemented in the derived class."; 29 | public static readonly string MustOverrideIndexerGet = "The get accessor of the indexer must be overridden."; 30 | public static readonly string MustOverrideIndexerSet = "The set accessor of the indexer must be overridden."; 31 | public static readonly string OutOfViewRange = "The argument is outside the range of this View."; 32 | public static readonly string TypeNotCloneable = "Type \"{0}\" does not implement ICloneable."; 33 | public static readonly string ChangeDuringEnumeration = "Collection was modified during an enumeration."; 34 | public static readonly string InconsistentComparisons = "The two collections cannot be combined because they use different comparison operations."; 35 | public static readonly string CollectionIsEmpty = "The collection is empty."; 36 | public static readonly string BadComparandType = "Comparand is not of the correct type."; 37 | public static readonly string CollectionTooLarge = "The collection has become too large."; 38 | public static readonly string InvalidLoadFactor = "The load factor must be between 0.25 and 0.95."; 39 | public static readonly string CapacityLessThanCount = "The capacity may not be less than Count."; 40 | public static readonly string ListIsReadOnly = "The list may not be read only."; 41 | public static readonly string CollectionIsReadOnly = "The collection may not be read only."; 42 | public static readonly string IdentityComparerNoCompare = "The Compare method is not supported on an identity comparer."; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Mba.Simplifier/Polynomial/PowerCollections/Util.cs: -------------------------------------------------------------------------------- 1 | //****************************** 2 | // Written by Peter Golde 3 | // Copyright (c) 2004-2005, Wintellect 4 | // 5 | // Use and restribution of this code is subject to the license agreement 6 | // contained in the file "License.txt" accompanying this file. 7 | //****************************** 8 | 9 | using System; 10 | using System.Collections; 11 | using System.Collections.Generic; 12 | 13 | namespace Wintellect.PowerCollections 14 | { 15 | /// 16 | /// A holder class for various internal utility functions that need to be shared. 17 | /// 18 | internal static class Util 19 | { 20 | /// 21 | /// Determine if a type is cloneable: either a value type or implementing 22 | /// ICloneable. 23 | /// 24 | /// Type to check. 25 | /// Returns if the type is a value type, and does not implement ICloneable. 26 | /// True if the type is cloneable. 27 | public static bool IsCloneableType(Type type, out bool isValue) 28 | { 29 | isValue = false; 30 | #if PCL 31 | 32 | return false; 33 | #else 34 | if (typeof(ICloneable).IsAssignableFrom(type)) { 35 | return true; 36 | } 37 | else if (type.IsValueType) { 38 | isValue = true; 39 | return true; 40 | } 41 | else 42 | return false; 43 | #endif 44 | 45 | } 46 | 47 | /// 48 | /// Returns the simple name of the class, for use in exception messages. 49 | /// 50 | /// The simple name of this class. 51 | public static string SimpleClassName(Type type) 52 | { 53 | string name = type.Name; 54 | 55 | // Just use the simple name. 56 | int index = name.IndexOfAny(new char[] { '<', '{', '`' }); 57 | if (index >= 0) 58 | name = name.Substring(0, index); 59 | 60 | return name; 61 | } 62 | 63 | /// 64 | /// Wrap an enumerable so that clients can't get to the underlying 65 | /// implementation via a down-cast. 66 | /// 67 | #if !PCL 68 | [Serializable] 69 | #endif 70 | class WrapEnumerable : IEnumerable 71 | { 72 | IEnumerable wrapped; 73 | 74 | /// 75 | /// Create the wrapper around an enumerable. 76 | /// 77 | /// IEnumerable to wrap. 78 | public WrapEnumerable(IEnumerable wrapped) 79 | { 80 | this.wrapped = wrapped; 81 | } 82 | 83 | public IEnumerator GetEnumerator() 84 | { 85 | return wrapped.GetEnumerator(); 86 | } 87 | 88 | IEnumerator IEnumerable.GetEnumerator() 89 | { 90 | return ((IEnumerable)wrapped).GetEnumerator(); 91 | } 92 | } 93 | 94 | /// 95 | /// Wrap an enumerable so that clients can't get to the underlying 96 | /// implementation via a down-case 97 | /// 98 | /// Enumerable to wrap. 99 | /// A wrapper around the enumerable. 100 | public static IEnumerable CreateEnumerableWrapper(IEnumerable wrapped) 101 | { 102 | return new WrapEnumerable(wrapped); 103 | } 104 | 105 | /// 106 | /// Gets the hash code for an object using a comparer. Correctly handles 107 | /// null. 108 | /// 109 | /// Item to get hash code for. Can be null. 110 | /// The comparer to use. 111 | /// The hash code for the item. 112 | public static int GetHashCode(T item, IEqualityComparer equalityComparer) 113 | { 114 | if (item == null) 115 | return 0x1786E23C; 116 | else 117 | return equalityComparer.GetHashCode(item); 118 | } 119 | 120 | /// 121 | /// Lookup table for . 122 | /// 123 | /// 124 | /// https://graphics.stanford.edu/~seander/bithacks.html#IntegerLogDeBruijn 125 | /// 126 | private static readonly int[] MultiplyDeBruijnBitPosition = 127 | { 128 | 0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30, 129 | 8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31 130 | }; 131 | 132 | /// 133 | /// Gets the log-base-2 of a positive integer value. 134 | /// 135 | /// Value whose log-base-2 to calculate. 136 | /// The log-base-2 of the value. 137 | /// 138 | /// https://graphics.stanford.edu/~seander/bithacks.html#IntegerLogDeBruijn 139 | /// 140 | public static int LogBase2(uint v) 141 | { 142 | // first round down to one less than a power of 2 143 | v |= v >> 1; 144 | v |= v >> 2; 145 | v |= v >> 4; 146 | v |= v >> 8; 147 | v |= v >> 16; 148 | 149 | return MultiplyDeBruijnBitPosition[unchecked((uint)(v * 0x07C4ACDDUL)) >> 27]; 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /Mba.Simplifier/Polynomial/SparsePolynomial.cs: -------------------------------------------------------------------------------- 1 | using Mba.Utility; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Mba.Simplifier.Polynomial 10 | { 11 | public class SparsePolynomial 12 | { 13 | public readonly Dictionary coeffs = new(); 14 | 15 | public readonly ulong moduloMask; 16 | 17 | public readonly int numVars; 18 | 19 | public readonly byte width; 20 | 21 | public ulong this[Monomial key] 22 | { 23 | get => GetCoeff(key); 24 | set => SetCoeff(key, value); 25 | } 26 | 27 | public SparsePolynomial(int numVars, byte width) 28 | { 29 | moduloMask = (ulong)ModuloReducer.GetMask(width); 30 | this.numVars = numVars; 31 | this.width = width; 32 | } 33 | 34 | public ulong GetCoeff(Monomial index) 35 | { 36 | return coeffs[index]; 37 | } 38 | 39 | public ulong GetCoeffOrZero(Monomial index) 40 | { 41 | ulong coeff = 0; 42 | coeffs.TryGetValue(index, out coeff); 43 | return coeff; 44 | } 45 | 46 | public bool TryGetCoeff(Monomial index, out ulong coeff) 47 | => coeffs.TryGetValue(index, out coeff); 48 | 49 | public bool ContainsMonomial(Monomial index) 50 | => coeffs.ContainsKey(index); 51 | 52 | public void SetCoeff(Monomial index, ulong value) 53 | { 54 | value &= moduloMask; 55 | coeffs[index] = value; 56 | } 57 | 58 | public bool Sum(Monomial index, ulong value) 59 | { 60 | value &= moduloMask; 61 | ulong sum = 0; 62 | bool contains = TryGetCoeff(index, out sum); 63 | sum += value; 64 | SetCoeff(index, sum); 65 | return contains; 66 | } 67 | 68 | public void Multiply(ulong coeff) 69 | { 70 | coeff &= moduloMask; 71 | 72 | foreach (var (monom, oldCoeff) in coeffs.ToList()) 73 | { 74 | var newCoeff = (moduloMask & oldCoeff) * coeff; 75 | SetCoeff(monom, newCoeff); 76 | } 77 | } 78 | 79 | public IReadOnlyList GetOrderedMonomials() 80 | { 81 | var bar = coeffs.Keys.OrderBy(x => x).ToList(); 82 | return bar; 83 | } 84 | 85 | private int CompareTo(Monomial a, Monomial b) 86 | { 87 | // Compare the two monomials. 88 | // If they are equal, return 0. 89 | // If a is greater than b, return 1. 90 | // If a is less than b, return -1. 91 | if (a.Equals(b)) 92 | return 0; 93 | var totalA = a.GetTotalDeg(); 94 | var totalB = b.GetTotalDeg(); 95 | if (totalA > totalB) 96 | return 1; 97 | else if (totalB > totalA) 98 | return -1; 99 | 100 | for (int i = 0; i < numVars; i++) 101 | { 102 | var degA = a.GetVarDeg(i); 103 | var degB = b.GetVarDeg(i); 104 | if (degA == degB) 105 | continue; 106 | if (degA > degB) 107 | return 1; 108 | else 109 | return -1; 110 | } 111 | return 0; 112 | } 113 | 114 | public override string ToString() 115 | { 116 | return ToString(true); 117 | } 118 | 119 | public string ToString(bool canonicalBasis = true) 120 | { 121 | var keys = GetOrderedMonomials(); 122 | List terms = new(); 123 | foreach (var key in keys) 124 | { 125 | var coeff = GetCoeff(key); 126 | if (coeff == 0) 127 | continue; 128 | terms.Add($"{coeff}*{key.ToString(canonicalBasis)}"); 129 | } 130 | 131 | var txt = String.Join(" + ", terms.ToArray()); 132 | return txt; 133 | } 134 | 135 | public SparsePolynomial Clone() 136 | { 137 | var clone = new SparsePolynomial(numVars, width); 138 | foreach (var (monom, coeff) in coeffs) 139 | clone.SetCoeff(monom, coeff); 140 | 141 | return clone; 142 | } 143 | 144 | public static SparsePolynomial GetUnivariate(byte width, params ulong[] coeffs) 145 | { 146 | var poly = new SparsePolynomial(1, width); 147 | for (int i = 0; i < coeffs.Length; i++) 148 | { 149 | var key = new Monomial((byte)i); 150 | poly[key] = coeffs[i]; 151 | } 152 | return poly; 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /Mba.Simplifier/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Mba.Simplifier": { 4 | "commandName": "Project", 5 | "nativeDebugging": true 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /Mba.Simplifier/Utility/AstRewriter.cs: -------------------------------------------------------------------------------- 1 | using Mba.Simplifier.Bindings; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Mba.Simplifier.Utility 9 | { 10 | public static class AstRewriter 11 | { 12 | public static AstIdx ChangeBitwidth(AstCtx ctx, AstIdx node, uint newWidth) 13 | { 14 | var opcode = ctx.GetOpcode(node); 15 | var binop = () => ctx.Binop(opcode, ChangeBitwidth(ctx, ctx.GetOp0(node), newWidth), ChangeBitwidth(ctx,ctx.GetOp1(node), newWidth)); 16 | 17 | return opcode switch 18 | { 19 | AstOp.Add => binop(), 20 | AstOp.Mul => binop(), 21 | AstOp.And => binop(), 22 | AstOp.Or => binop(), 23 | AstOp.Xor => binop(), 24 | AstOp.Neg => ctx.Neg(ChangeBitwidth(ctx, ctx.GetOp0(node), newWidth)), 25 | AstOp.Constant => ctx.Constant(ctx.GetConstantValue(node), newWidth), 26 | AstOp.Symbol => ctx.Symbol(ctx.GetSymbolName(node), (byte)newWidth), 27 | _ => throw new InvalidOperationException($"Cannot change width of opcode {opcode}"), 28 | }; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Mba.Simplifier/Utility/RustAstParser.cs: -------------------------------------------------------------------------------- 1 | using Mba.Ast; 2 | using Mba.Parsing; 3 | using Mba.Simplifier.Bindings; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace Mba.Simplifier.Utility 11 | { 12 | public class RustAstParser 13 | { 14 | private readonly AstCtx ctx; 15 | 16 | private readonly string input; 17 | 18 | private readonly byte bitWidth; 19 | 20 | public static AstIdx Parse(AstCtx ctx, string input, uint bitWidth) 21 | => new RustAstParser(ctx, input, bitWidth).Parse(); 22 | 23 | private RustAstParser(AstCtx ctx, string input, uint bitSize) 24 | { 25 | this.ctx = ctx; 26 | this.input = input; 27 | this.bitWidth = (byte)bitSize; 28 | } 29 | 30 | private AstIdx Parse() 31 | { 32 | // For now we just reuse MSiMBA's ast parser and translate the result. 33 | var ast = AstParser.Parse(input, bitWidth); 34 | return Convert(ast); 35 | } 36 | 37 | public AstIdx Convert(AstNode node) 38 | { 39 | var binop = (AstOp op) => ctx.Binop(op, Convert(node.Children[0]), Convert(node.Children[1])); 40 | 41 | return node.Kind switch 42 | { 43 | AstKind.Const => ctx.Constant((ulong)(node as ConstNode).Value, bitWidth), 44 | AstKind.Var => ctx.Symbol((node as VarNode).Name, bitWidth), 45 | AstKind.Add => binop(AstOp.Add), 46 | AstKind.Power => binop(AstOp.Pow), 47 | AstKind.Mul => binop(AstOp.Mul), 48 | AstKind.And => binop(AstOp.And), 49 | AstKind.Or => binop(AstOp.Or), 50 | AstKind.Xor => binop(AstOp.Xor), 51 | AstKind.Neg => ctx.Neg(Convert(node.Children[0])), 52 | _ => throw new InvalidOperationException($"Ast kind {node.Kind} is not supported!") 53 | }; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Mba.Simplifier/Utility/Z3Translator.cs: -------------------------------------------------------------------------------- 1 | using Mba.Ast; 2 | using Mba.Simplifier.Bindings; 3 | using Microsoft.Z3; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace Mba.Simplifier.Utility 11 | { 12 | public class Z3Translator 13 | { 14 | private readonly AstCtx ctx; 15 | 16 | private readonly Context z3Ctx; 17 | 18 | private readonly Dictionary cache = new Dictionary(); 19 | 20 | public Z3Translator(AstCtx ctx) : this(ctx, new Context()) 21 | { 22 | } 23 | 24 | public Z3Translator(AstCtx ctx, Context z3Context) 25 | { 26 | this.ctx = ctx; 27 | this.z3Ctx = z3Context; 28 | } 29 | 30 | public Expr Translate(AstIdx idx) 31 | { 32 | if (cache.TryGetValue(idx, out var existing)) 33 | return existing; 34 | 35 | var op0 = () => (BitVecExpr)Translate(ctx.GetOp0(idx)); 36 | var op1 = () => (BitVecExpr)Translate(ctx.GetOp1(idx)); 37 | 38 | var opcode = ctx.GetOpcode(idx); 39 | Expr z3Ast = opcode switch 40 | { 41 | AstOp.Add => z3Ctx.MkBVAdd(op0(), op1()), 42 | AstOp.Mul => z3Ctx.MkBVMul(op0(), op1()), 43 | AstOp.Pow => Power(idx), 44 | AstOp.And => z3Ctx.MkBVAND(op0(), op1()), 45 | AstOp.Or => z3Ctx.MkBVOR(op0(), op1()), 46 | AstOp.Xor => z3Ctx.MkBVXOR(op0(), op1()), 47 | AstOp.Neg => z3Ctx.MkBVNot(op0()), 48 | AstOp.Constant => z3Ctx.MkBV(ctx.GetConstantValue(idx), ctx.GetWidth(idx)), 49 | AstOp.Symbol => z3Ctx.MkBVConst(ctx.GetSymbolName(idx), ctx.GetWidth(idx)), 50 | AstOp.Zext => z3Ctx.MkZeroExt((uint)ctx.GetWidth(idx) - ctx.GetWidth(ctx.GetOp0(idx)), op0()), 51 | _ => throw new InvalidOperationException($"Cannot translate opcode {opcode} to z3!") 52 | }; 53 | 54 | cache[idx] = z3Ast; 55 | return z3Ast; 56 | } 57 | 58 | private Expr Power(AstIdx idx) 59 | { 60 | var lhs = ctx.GetOp0(idx); 61 | var rhs = ctx.GetOp1(idx); 62 | if (!ctx.IsConstant(rhs)) 63 | throw new InvalidOperationException($"Z3 does not support exponeniation to an unknown degree!"); 64 | 65 | var width = ctx.GetWidth(idx); 66 | return Power(Translate(lhs), ctx.GetConstantValue(rhs), width); 67 | } 68 | 69 | private Expr Power(Expr x, ulong y, uint bitWidth) 70 | { 71 | if (y == 0) 72 | return z3Ctx.MkBV(1, bitWidth); 73 | if (y == 1) 74 | return x; 75 | 76 | var originalBv = x; 77 | for (ulong i = 0; i < y - 1; i++) 78 | { 79 | // x = x * original; 80 | x = z3Ctx.MkBVMul((BitVecExpr)x, (BitVecExpr)originalBv); 81 | } 82 | 83 | return x; 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simplifier 2 | `Simplifier` is a framework for the deobfuscation of Mixed Boolean-Arithmetic (MBA) expressions. It was designed with a focus on performance and scalability to real-world MBAs. 3 | 4 | ## Design 5 | We implement several structural and algorithmic optimizations, namely: 6 | 7 | * Expressions are represented as hash-consed, immutable nodes in a directed acyclic graph 8 | * Term rewriting is performed using ISLE, allowing rules to be expressed declaratively and lowered to more efficient tree matching code. 9 | * Caching is used to avoid duplicated work 10 | * The number of substitutions and SiMBA invocations is carefully minimized 11 | 12 | # Usage 13 | To simplify a single expression, use: 14 | ``` 15 | Simplifier.exe "expr" 16 | ``` 17 | 18 | As an example: 19 | ``` 20 | $ Simplifier.exe "7471873370*~(16832296021645948&y) + 3735936685*16832296021645948 + 3735936685*y + 7471922744 - 49374" 21 | 22 | Expression: (((((7471873370*(~(16832296021645948&y)))+(3735936685*16832296021645948))+(3735936685*y))+7471922744)-49374) 23 | 24 | Simplified to: (3735936685*(16832296021645948^y)) 25 | ``` 26 | 27 | The executable accepts several arguments, e.g., `-b` for specifying the bit width of the input expression, and `-z` for proving the equivalence between the input expression and the simplified result. Use the command line option `-h` to see the available settings. 28 | 29 | # Supported Platforms 30 | `Simplifier` is only supported on Windows. Note that both Visual Studio 2022 and ClangCL are required to build the project. 31 | 32 | # Building 33 | Clone `Simplifier`: 34 | ``` 35 | git clone --recursive https://github.com/mazeworks-security/Simplifier.git 36 | cd Simplifier 37 | ``` 38 | 39 | Build `EqSat` in release mode: 40 | ``` 41 | cd EqSat 42 | cargo build --release 43 | ``` 44 | Build `Simplifier` in release mode: 45 | - Open `Simplifier.sln` with visual studio 46 | - Change the build configuration from `Debug` to `Release` 47 | - Build the solution 48 | 49 | Building `Simplifier` requires .NET 8 and Visual Studio 2022 w/ ClangCL. 50 | 51 | # Status 52 | `Simplifier` has reached a stage where it works quite well on general MBAs. That said, it is still under active development. 53 | 54 | # Equality Saturation 55 | `Simplifier` contains an equality saturation based simplifier. To enable it alongside the standard simplification routine, use the `-e` flag. 56 | 57 | Example: 58 | ``` 59 | Simplifier.exe "x+y" -e 60 | ``` 61 | 62 | Note that the equality saturation based simplifier should not be used in general - it is superseded by the general simplification algorithm. -------------------------------------------------------------------------------- /Simplifier.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.10.35027.167 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Simplifier", "Simplifier\Simplifier.csproj", "{642494C9-A7B6-42CB-BE2F-BDBD5B9B032D}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mba.Simplifier", "Mba.Simplifier\Mba.Simplifier.csproj", "{5D15CF3B-745A-40A9-837C-76ED6BA19A36}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mba.Common", "MSiMBA\Mba.Common\Mba.Common.csproj", "{ED2C2D1C-B482-4890-B070-A74CED7DC953}" 11 | EndProject 12 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Mba.FFI", "MSiMBA\Mba.FFI\Mba.FFI.vcxproj", "{FE5E55D5-A764-4CD9-B8A8-EAC3B2149C55}" 13 | EndProject 14 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Espresso", "MSiMBA\Espresso\Espresso.vcxproj", "{C66D6CA2-8FA5-4512-8F14-2311A3BD7E1B}" 15 | EndProject 16 | Global 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|Any CPU = Debug|Any CPU 19 | Debug|x64 = Debug|x64 20 | Debug|x86 = Debug|x86 21 | Release|Any CPU = Release|Any CPU 22 | Release|x64 = Release|x64 23 | Release|x86 = Release|x86 24 | EndGlobalSection 25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 26 | {642494C9-A7B6-42CB-BE2F-BDBD5B9B032D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {642494C9-A7B6-42CB-BE2F-BDBD5B9B032D}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {642494C9-A7B6-42CB-BE2F-BDBD5B9B032D}.Debug|x64.ActiveCfg = Debug|Any CPU 29 | {642494C9-A7B6-42CB-BE2F-BDBD5B9B032D}.Debug|x64.Build.0 = Debug|Any CPU 30 | {642494C9-A7B6-42CB-BE2F-BDBD5B9B032D}.Debug|x86.ActiveCfg = Debug|Any CPU 31 | {642494C9-A7B6-42CB-BE2F-BDBD5B9B032D}.Debug|x86.Build.0 = Debug|Any CPU 32 | {642494C9-A7B6-42CB-BE2F-BDBD5B9B032D}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {642494C9-A7B6-42CB-BE2F-BDBD5B9B032D}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {642494C9-A7B6-42CB-BE2F-BDBD5B9B032D}.Release|x64.ActiveCfg = Release|Any CPU 35 | {642494C9-A7B6-42CB-BE2F-BDBD5B9B032D}.Release|x64.Build.0 = Release|Any CPU 36 | {642494C9-A7B6-42CB-BE2F-BDBD5B9B032D}.Release|x86.ActiveCfg = Release|Any CPU 37 | {642494C9-A7B6-42CB-BE2F-BDBD5B9B032D}.Release|x86.Build.0 = Release|Any CPU 38 | {5D15CF3B-745A-40A9-837C-76ED6BA19A36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {5D15CF3B-745A-40A9-837C-76ED6BA19A36}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {5D15CF3B-745A-40A9-837C-76ED6BA19A36}.Debug|x64.ActiveCfg = Debug|Any CPU 41 | {5D15CF3B-745A-40A9-837C-76ED6BA19A36}.Debug|x64.Build.0 = Debug|Any CPU 42 | {5D15CF3B-745A-40A9-837C-76ED6BA19A36}.Debug|x86.ActiveCfg = Debug|Any CPU 43 | {5D15CF3B-745A-40A9-837C-76ED6BA19A36}.Debug|x86.Build.0 = Debug|Any CPU 44 | {5D15CF3B-745A-40A9-837C-76ED6BA19A36}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 | {5D15CF3B-745A-40A9-837C-76ED6BA19A36}.Release|Any CPU.Build.0 = Release|Any CPU 46 | {5D15CF3B-745A-40A9-837C-76ED6BA19A36}.Release|x64.ActiveCfg = Release|Any CPU 47 | {5D15CF3B-745A-40A9-837C-76ED6BA19A36}.Release|x64.Build.0 = Release|Any CPU 48 | {5D15CF3B-745A-40A9-837C-76ED6BA19A36}.Release|x86.ActiveCfg = Release|Any CPU 49 | {5D15CF3B-745A-40A9-837C-76ED6BA19A36}.Release|x86.Build.0 = Release|Any CPU 50 | {ED2C2D1C-B482-4890-B070-A74CED7DC953}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 51 | {ED2C2D1C-B482-4890-B070-A74CED7DC953}.Debug|Any CPU.Build.0 = Debug|Any CPU 52 | {ED2C2D1C-B482-4890-B070-A74CED7DC953}.Debug|x64.ActiveCfg = Debug|x64 53 | {ED2C2D1C-B482-4890-B070-A74CED7DC953}.Debug|x64.Build.0 = Debug|x64 54 | {ED2C2D1C-B482-4890-B070-A74CED7DC953}.Debug|x86.ActiveCfg = Debug|Any CPU 55 | {ED2C2D1C-B482-4890-B070-A74CED7DC953}.Debug|x86.Build.0 = Debug|Any CPU 56 | {ED2C2D1C-B482-4890-B070-A74CED7DC953}.Release|Any CPU.ActiveCfg = Release|Any CPU 57 | {ED2C2D1C-B482-4890-B070-A74CED7DC953}.Release|Any CPU.Build.0 = Release|Any CPU 58 | {ED2C2D1C-B482-4890-B070-A74CED7DC953}.Release|x64.ActiveCfg = Release|x64 59 | {ED2C2D1C-B482-4890-B070-A74CED7DC953}.Release|x64.Build.0 = Release|x64 60 | {ED2C2D1C-B482-4890-B070-A74CED7DC953}.Release|x86.ActiveCfg = Release|Any CPU 61 | {ED2C2D1C-B482-4890-B070-A74CED7DC953}.Release|x86.Build.0 = Release|Any CPU 62 | {FE5E55D5-A764-4CD9-B8A8-EAC3B2149C55}.Debug|Any CPU.ActiveCfg = Release|x64 63 | {FE5E55D5-A764-4CD9-B8A8-EAC3B2149C55}.Debug|Any CPU.Build.0 = Release|x64 64 | {FE5E55D5-A764-4CD9-B8A8-EAC3B2149C55}.Debug|x64.ActiveCfg = Release|x64 65 | {FE5E55D5-A764-4CD9-B8A8-EAC3B2149C55}.Debug|x64.Build.0 = Release|x64 66 | {FE5E55D5-A764-4CD9-B8A8-EAC3B2149C55}.Debug|x86.ActiveCfg = Release|x64 67 | {FE5E55D5-A764-4CD9-B8A8-EAC3B2149C55}.Debug|x86.Build.0 = Release|x64 68 | {FE5E55D5-A764-4CD9-B8A8-EAC3B2149C55}.Release|Any CPU.ActiveCfg = Release|x64 69 | {FE5E55D5-A764-4CD9-B8A8-EAC3B2149C55}.Release|Any CPU.Build.0 = Release|x64 70 | {FE5E55D5-A764-4CD9-B8A8-EAC3B2149C55}.Release|x64.ActiveCfg = Release|x64 71 | {FE5E55D5-A764-4CD9-B8A8-EAC3B2149C55}.Release|x64.Build.0 = Release|x64 72 | {FE5E55D5-A764-4CD9-B8A8-EAC3B2149C55}.Release|x86.ActiveCfg = Release|x64 73 | {FE5E55D5-A764-4CD9-B8A8-EAC3B2149C55}.Release|x86.Build.0 = Release|x64 74 | {C66D6CA2-8FA5-4512-8F14-2311A3BD7E1B}.Debug|Any CPU.ActiveCfg = Release|x64 75 | {C66D6CA2-8FA5-4512-8F14-2311A3BD7E1B}.Debug|Any CPU.Build.0 = Release|x64 76 | {C66D6CA2-8FA5-4512-8F14-2311A3BD7E1B}.Debug|x64.ActiveCfg = Release|x64 77 | {C66D6CA2-8FA5-4512-8F14-2311A3BD7E1B}.Debug|x64.Build.0 = Release|x64 78 | {C66D6CA2-8FA5-4512-8F14-2311A3BD7E1B}.Debug|x86.ActiveCfg = Release|Win32 79 | {C66D6CA2-8FA5-4512-8F14-2311A3BD7E1B}.Debug|x86.Build.0 = Release|Win32 80 | {C66D6CA2-8FA5-4512-8F14-2311A3BD7E1B}.Release|Any CPU.ActiveCfg = Release|x64 81 | {C66D6CA2-8FA5-4512-8F14-2311A3BD7E1B}.Release|Any CPU.Build.0 = Release|x64 82 | {C66D6CA2-8FA5-4512-8F14-2311A3BD7E1B}.Release|x64.ActiveCfg = Release|x64 83 | {C66D6CA2-8FA5-4512-8F14-2311A3BD7E1B}.Release|x64.Build.0 = Release|x64 84 | {C66D6CA2-8FA5-4512-8F14-2311A3BD7E1B}.Release|x86.ActiveCfg = Release|Win32 85 | {C66D6CA2-8FA5-4512-8F14-2311A3BD7E1B}.Release|x86.Build.0 = Release|Win32 86 | EndGlobalSection 87 | GlobalSection(SolutionProperties) = preSolution 88 | HideSolutionNode = FALSE 89 | EndGlobalSection 90 | GlobalSection(ExtensibilityGlobals) = postSolution 91 | SolutionGuid = {9021B55A-C862-4078-B9DD-EF670568BD01} 92 | EndGlobalSection 93 | EndGlobal 94 | -------------------------------------------------------------------------------- /Simplifier/Program.cs: -------------------------------------------------------------------------------- 1 | using Mba.Common.MSiMBA; 2 | using Mba.Parsing; 3 | using Mba.Simplifier.Bindings; 4 | using Mba.Simplifier.Minimization; 5 | using Mba.Simplifier.Pipeline; 6 | using Mba.Simplifier.Utility; 7 | using Mba.Utility; 8 | using Microsoft.Z3; 9 | using System.ComponentModel; 10 | using System.Diagnostics; 11 | 12 | bool printUsage = false; 13 | uint bitWidth = 64; 14 | bool useEqsat = false; 15 | bool proveEquivalence = false; 16 | string inputText = null; 17 | 18 | var printHelp = () => 19 | { 20 | Console.WriteLine("Usage: Simplifier.exe"); 21 | Console.WriteLine("Command line input not preceded by the option indicators below are considered to be input expressions. Only one input expression is accepted."); 22 | Console.WriteLine("Command line options:"); 23 | Console.WriteLine(" -h: print usage"); 24 | Console.WriteLine(" -b: specify the bit number of variables (default is 64)"); 25 | Console.WriteLine(" -z: enable a check for valid simplification using Z3"); 26 | Console.WriteLine(" -e: enable equality saturation based simplification"); 27 | }; 28 | 29 | for (int i = 0; i < args.Length; i++) 30 | { 31 | var arg = args[i]; 32 | switch (arg) 33 | { 34 | case "-h": 35 | printUsage = true; 36 | break; 37 | case "-b": 38 | bitWidth = uint.Parse(args[i + 1]); 39 | i++; 40 | break; 41 | case "-z": 42 | proveEquivalence = true; 43 | break; 44 | case "-e": 45 | useEqsat = true; 46 | break; 47 | default: 48 | if (inputText != null) 49 | throw new ArgumentException($"Found more than one expression argument. Received both {inputText} and {args[i]}"); 50 | inputText = args[i]; 51 | break; 52 | } 53 | } 54 | 55 | if (inputText == null || printUsage) 56 | { 57 | printHelp(); 58 | return; 59 | } 60 | 61 | // For now we only support integer widths of up to 64 bits. 62 | const int maxWidth = 64; 63 | if (bitWidth > maxWidth) 64 | throw new InvalidOperationException($"Received bit width {bitWidth}, which is greater than the max width {maxWidth}"); 65 | 66 | var ctx = new AstCtx(); 67 | AstIdx.ctx = ctx; 68 | var id = RustAstParser.Parse(ctx, inputText, bitWidth); 69 | 70 | Console.WriteLine($"\nExpression: {ctx.GetAstString(id)}\n\n\n"); 71 | 72 | var input = id; 73 | id = ctx.RecursiveSimplify(id); 74 | for (int i = 0; i < 3; i++) 75 | { 76 | // Apply our simplification procedure. 77 | var simplifier = new GeneralSimplifier(ctx); 78 | // Run the simplification pipeline. 79 | id = simplifier.SimplifyGeneral(id); 80 | // Try to expand and reduce the polynomial parts(if any exist). 81 | if(ctx.GetHasPoly(id)) 82 | id = simplifier.ExpandReduce(id); 83 | 84 | if (!useEqsat) 85 | continue; 86 | if (bitWidth != 64) 87 | throw new InvalidOperationException($"Equality saturation is only supported for 64-bit expressions"); 88 | 89 | // Apply equality saturation. 90 | ulong timeout = 2000; 91 | Console.WriteLine($"Running equality saturation with {timeout}ms timeout..."); 92 | var ast = AstParser.Parse(ctx.GetAstString(id), bitWidth); 93 | var egg = EqualitySaturation.Run(ast, timeout); 94 | id = RustAstParser.Parse(ctx, egg.ToString(), bitWidth); 95 | 96 | // Apply term rewriting. 97 | id = ctx.RecursiveSimplify(id); 98 | Console.WriteLine($"Eqsat run {i} yielded: {ctx.GetAstString(id)}\n\n"); 99 | } 100 | 101 | 102 | Console.WriteLine($"Simplified to: {ctx.GetAstString(id)}\n\nwith cost: {ctx.GetCost(id)}"); 103 | 104 | if (!proveEquivalence) 105 | return; 106 | 107 | var z3Ctx = new Context(); 108 | var translator = new Z3Translator(ctx, z3Ctx); 109 | var before = translator.Translate(input); 110 | var after = translator.Translate(id); 111 | var solver = z3Ctx.MkSolver("QF_BV"); 112 | 113 | // Set the maximum timeout to 10 seconds. 114 | var p = z3Ctx.MkParams(); 115 | uint solverLimit = 10000; 116 | p.Add("timeout", solverLimit); 117 | solver.Parameters = p; 118 | 119 | Console.WriteLine("Proving equivalence...\n"); 120 | solver.Add(z3Ctx.MkNot(z3Ctx.MkEq(before, after))); 121 | var check = solver.Check(); 122 | 123 | var printModel = (Model model) => 124 | { 125 | var values = model.Consts.Select(x => $"{x.Key.Name} = {(long)ulong.Parse(model.Eval(x.Value).ToString())}"); 126 | return $"[{String.Join(", ", values)}]"; 127 | }; 128 | 129 | if (check == Status.UNSATISFIABLE) 130 | Console.WriteLine("Expressions are equivalent."); 131 | else if (check == Status.SATISFIABLE) 132 | Console.WriteLine($"Expressions are not equivalent. Counterexample:\n{printModel(solver.Model)}"); 133 | else 134 | Console.WriteLine($"Solver timed out - expressions are probably equivalent. Could not find counterexample within {solverLimit}ms"); -------------------------------------------------------------------------------- /Simplifier/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Simplifier": { 4 | "commandName": "Project", 5 | "nativeDebugging": true 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /Simplifier/Simplifier.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | Always 13 | 14 | 15 | Always 16 | 17 | 18 | Always 19 | 20 | 21 | Always 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | --------------------------------------------------------------------------------