├── .github └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── CompareSize.py ├── GenerateFiles.py ├── LICENSE ├── LICENSE-Apache ├── Makefile ├── Readme.md ├── Tester.py ├── config.toml.template ├── java-linker ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs ├── jvm-unknown-unknown.json.template ├── library ├── build.gradle.kts └── src │ └── main │ └── kotlin │ └── org │ └── rustlang │ └── core │ └── Core.kt ├── proguard └── default.pro ├── rust-toolchain.toml ├── shim-metadata-gen ├── Cargo.lock ├── Cargo.toml ├── core.json └── src │ └── main.rs ├── src ├── lib.rs ├── lower1.rs ├── lower1 │ ├── control_flow.rs │ ├── control_flow │ │ ├── checked_ops.rs │ │ └── rvalue.rs │ ├── operand.rs │ ├── operand │ │ ├── experimental.rs │ │ └── float.rs │ ├── place.rs │ └── types.rs ├── lower2.rs ├── lower2 │ ├── consts.rs │ ├── helpers.rs │ ├── jvm_gen.rs │ ├── shim.rs │ └── translator.rs ├── oomir.rs ├── oomir │ └── interpret.rs ├── optimise1.rs └── optimise1 │ ├── dataflow.rs │ ├── reachability.rs │ └── reorganisation.rs └── tests └── binary ├── binsearch ├── .cargo │ └── config.toml ├── Cargo.lock ├── Cargo.toml ├── java-output.expected ├── no_jvm_target.flag └── src │ └── main.rs ├── collatz ├── .cargo │ └── config.toml ├── Cargo.lock ├── Cargo.toml ├── java-output.expected ├── no_jvm_target.flag └── src │ └── main.rs ├── enums ├── .cargo │ └── config.toml ├── Cargo.lock ├── Cargo.toml ├── java-output.expected ├── no_jvm_target.flag ├── script.sh └── src │ └── main.rs ├── fibonacci ├── .cargo │ └── config.toml ├── Cargo.lock ├── Cargo.toml ├── java-output.expected ├── no_jvm_target.flag └── src │ └── main.rs ├── impl ├── .cargo │ └── config.toml ├── Cargo.lock ├── Cargo.toml ├── java-output.expected ├── no_jvm_target.flag └── src │ └── main.rs ├── is_even_plus_one ├── .cargo │ └── config.toml ├── Cargo.lock ├── Cargo.toml ├── java-output.expected ├── no_jvm_target.flag └── src │ └── main.rs ├── just_main_func ├── Cargo.lock ├── Cargo.toml ├── build.sh ├── java-output.expected └── src │ └── main.rs ├── mutable_borrow ├── .cargo │ └── config.toml ├── Cargo.lock ├── Cargo.toml ├── java-output.expected ├── no_jvm_target.flag └── src │ └── main.rs ├── ops ├── .cargo │ └── config.toml ├── Cargo.lock ├── Cargo.toml ├── java-output.expected ├── no_jvm_target.flag └── src │ └── main.rs ├── panic ├── .cargo │ └── config.toml ├── Cargo.lock ├── Cargo.toml ├── java-output.expected ├── java-returncode.expected ├── no_jvm_target.flag └── src │ └── main.rs ├── primes ├── .cargo │ └── config.toml ├── Cargo.lock ├── Cargo.toml ├── java-output.expected ├── no_jvm_target.flag └── src │ └── main.rs ├── rsa ├── .cargo │ └── config.toml ├── Cargo.lock ├── Cargo.toml ├── java-output.expected ├── no_jvm_target.flag └── src │ └── main.rs ├── structs ├── .cargo │ └── config.toml ├── Cargo.lock ├── Cargo.toml ├── java-output.expected ├── no_jvm_target.flag └── src │ └── main.rs └── traits ├── .cargo └── config.toml ├── Cargo.lock ├── Cargo.toml ├── java-output.expected ├── no_jvm_target.flag └── src └── main.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | build-and-test: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | # 1. Checkout Code 15 | - name: Checkout repository 16 | uses: actions/checkout@v4 17 | 18 | # 2. Set up Rust 19 | - name: Set up Rust (nightly) 20 | uses: dtolnay/rust-toolchain@stable 21 | with: 22 | toolchain: nightly 23 | components: rustc-dev llvm-tools cargo 24 | 25 | # 4. Set up Java 26 | - name: Set up Java 27 | uses: actions/setup-java@v4 28 | with: 29 | distribution: 'temurin' 30 | java-version: '21' 31 | 32 | # 5. Set up Python 33 | - name: Set up Python 34 | uses: actions/setup-python@v5 35 | with: 36 | python-version: '3.x' 37 | 38 | # 6. Set up Gradle 39 | - name: Set up Gradle 40 | uses: gradle/actions/setup-gradle@v4 41 | 42 | # 7. Make just the files needed to cargo cache 43 | - name: Make Gen Files 44 | run: make gen-files 45 | 46 | # 8. Cargo Caching (Crucial for speed) 47 | - name: Cache cargo dependencies 48 | uses: Swatinem/rust-cache@v2 49 | 50 | # 9. Build using the CI-specific make target 51 | - name: Build CI Target 52 | run: make ci # Use your optimized CI build command 53 | 54 | # 10. Run tests 55 | - name: Run integration tests (debug mode) 56 | run: python3 Tester.py 57 | 58 | # 11. Run tests in release mode 59 | - name: Run integration tests (release mode) 60 | run: python3 Tester.py --release -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignores relvant to Rust projects within this tree 2 | **/target/* 3 | 4 | # Ignores relevant to Kotlin projects within this tree 5 | **/.gradle/* 6 | **/build/* 7 | **/.kotlin/* 8 | 9 | # Files generated by Tester.py 10 | **/*.generated 11 | **/*.diff 12 | 13 | # ICE files generated by Rustc 14 | **/*.txt 15 | 16 | # Crash logs generated by the JVM 17 | **/*.log 18 | 19 | # Generated by GenerateFiles.py 20 | /config.toml 21 | jvm-unknown-unknown.json 22 | 23 | # Vendored files 24 | **/vendor/* -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "autocfg" 16 | version = "1.4.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 19 | 20 | [[package]] 21 | name = "bigdecimal" 22 | version = "0.4.8" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "1a22f228ab7a1b23027ccc6c350b72868017af7ea8356fbdf19f8d991c690013" 25 | dependencies = [ 26 | "autocfg", 27 | "libm", 28 | "num-bigint", 29 | "num-integer", 30 | "num-traits", 31 | ] 32 | 33 | [[package]] 34 | name = "bitflags" 35 | version = "2.9.0" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" 38 | 39 | [[package]] 40 | name = "block-buffer" 41 | version = "0.10.4" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 44 | dependencies = [ 45 | "generic-array", 46 | ] 47 | 48 | [[package]] 49 | name = "byteorder" 50 | version = "1.5.0" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 53 | 54 | [[package]] 55 | name = "cfg-if" 56 | version = "1.0.0" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 59 | 60 | [[package]] 61 | name = "cpufeatures" 62 | version = "0.2.17" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 65 | dependencies = [ 66 | "libc", 67 | ] 68 | 69 | [[package]] 70 | name = "crunchy" 71 | version = "0.2.3" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" 74 | 75 | [[package]] 76 | name = "crypto-common" 77 | version = "0.1.6" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 80 | dependencies = [ 81 | "generic-array", 82 | "typenum", 83 | ] 84 | 85 | [[package]] 86 | name = "digest" 87 | version = "0.10.7" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 90 | dependencies = [ 91 | "block-buffer", 92 | "crypto-common", 93 | ] 94 | 95 | [[package]] 96 | name = "equivalent" 97 | version = "1.0.2" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 100 | 101 | [[package]] 102 | name = "generic-array" 103 | version = "0.14.7" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 106 | dependencies = [ 107 | "typenum", 108 | "version_check", 109 | ] 110 | 111 | [[package]] 112 | name = "half" 113 | version = "2.6.0" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" 116 | dependencies = [ 117 | "cfg-if", 118 | "crunchy", 119 | ] 120 | 121 | [[package]] 122 | name = "hashbrown" 123 | version = "0.15.2" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" 126 | 127 | [[package]] 128 | name = "indexmap" 129 | version = "2.9.0" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" 132 | dependencies = [ 133 | "equivalent", 134 | "hashbrown", 135 | ] 136 | 137 | [[package]] 138 | name = "itoa" 139 | version = "1.0.15" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 142 | 143 | [[package]] 144 | name = "libc" 145 | version = "0.2.172" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" 148 | 149 | [[package]] 150 | name = "libm" 151 | version = "0.2.13" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "c9627da5196e5d8ed0b0495e61e518847578da83483c37288316d9b2e03a7f72" 154 | 155 | [[package]] 156 | name = "md5" 157 | version = "0.7.0" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" 160 | 161 | [[package]] 162 | name = "memchr" 163 | version = "2.7.4" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 166 | 167 | [[package]] 168 | name = "num-bigint" 169 | version = "0.4.6" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" 172 | dependencies = [ 173 | "num-integer", 174 | "num-traits", 175 | ] 176 | 177 | [[package]] 178 | name = "num-integer" 179 | version = "0.1.46" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" 182 | dependencies = [ 183 | "num-traits", 184 | ] 185 | 186 | [[package]] 187 | name = "num-traits" 188 | version = "0.2.19" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 191 | dependencies = [ 192 | "autocfg", 193 | ] 194 | 195 | [[package]] 196 | name = "proc-macro2" 197 | version = "1.0.94" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" 200 | dependencies = [ 201 | "unicode-ident", 202 | ] 203 | 204 | [[package]] 205 | name = "quote" 206 | version = "1.0.40" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 209 | dependencies = [ 210 | "proc-macro2", 211 | ] 212 | 213 | [[package]] 214 | name = "regex" 215 | version = "1.11.1" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 218 | dependencies = [ 219 | "aho-corasick", 220 | "memchr", 221 | "regex-automata", 222 | "regex-syntax", 223 | ] 224 | 225 | [[package]] 226 | name = "regex-automata" 227 | version = "0.4.9" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 230 | dependencies = [ 231 | "aho-corasick", 232 | "memchr", 233 | "regex-syntax", 234 | ] 235 | 236 | [[package]] 237 | name = "regex-syntax" 238 | version = "0.8.5" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 241 | 242 | [[package]] 243 | name = "ristretto_classfile" 244 | version = "0.18.1" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "d14935dc5310b835f5f5f816653c2d026d34a52d9a76fd84f8dd39f8eff2fffd" 247 | dependencies = [ 248 | "bitflags", 249 | "byteorder", 250 | "indexmap", 251 | "thiserror", 252 | ] 253 | 254 | [[package]] 255 | name = "rustc_codegen_jvm" 256 | version = "0.1.0" 257 | dependencies = [ 258 | "bigdecimal", 259 | "half", 260 | "libc", 261 | "md5", 262 | "num-bigint", 263 | "num-traits", 264 | "regex", 265 | "ristretto_classfile", 266 | "serde", 267 | "serde_json", 268 | "sha2", 269 | "zerocopy", 270 | ] 271 | 272 | [[package]] 273 | name = "ryu" 274 | version = "1.0.20" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 277 | 278 | [[package]] 279 | name = "serde" 280 | version = "1.0.219" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 283 | dependencies = [ 284 | "serde_derive", 285 | ] 286 | 287 | [[package]] 288 | name = "serde_derive" 289 | version = "1.0.219" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 292 | dependencies = [ 293 | "proc-macro2", 294 | "quote", 295 | "syn", 296 | ] 297 | 298 | [[package]] 299 | name = "serde_json" 300 | version = "1.0.140" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" 303 | dependencies = [ 304 | "itoa", 305 | "memchr", 306 | "ryu", 307 | "serde", 308 | ] 309 | 310 | [[package]] 311 | name = "sha2" 312 | version = "0.10.9" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" 315 | dependencies = [ 316 | "cfg-if", 317 | "cpufeatures", 318 | "digest", 319 | ] 320 | 321 | [[package]] 322 | name = "syn" 323 | version = "2.0.100" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" 326 | dependencies = [ 327 | "proc-macro2", 328 | "quote", 329 | "unicode-ident", 330 | ] 331 | 332 | [[package]] 333 | name = "thiserror" 334 | version = "2.0.12" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" 337 | dependencies = [ 338 | "thiserror-impl", 339 | ] 340 | 341 | [[package]] 342 | name = "thiserror-impl" 343 | version = "2.0.12" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" 346 | dependencies = [ 347 | "proc-macro2", 348 | "quote", 349 | "syn", 350 | ] 351 | 352 | [[package]] 353 | name = "typenum" 354 | version = "1.18.0" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" 357 | 358 | [[package]] 359 | name = "unicode-ident" 360 | version = "1.0.18" 361 | source = "registry+https://github.com/rust-lang/crates.io-index" 362 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 363 | 364 | [[package]] 365 | name = "version_check" 366 | version = "0.9.5" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 369 | 370 | [[package]] 371 | name = "zerocopy" 372 | version = "0.8.25" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" 375 | dependencies = [ 376 | "zerocopy-derive", 377 | ] 378 | 379 | [[package]] 380 | name = "zerocopy-derive" 381 | version = "0.8.25" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" 384 | dependencies = [ 385 | "proc-macro2", 386 | "quote", 387 | "syn", 388 | ] 389 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustc_codegen_jvm" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | bigdecimal = "0.4.8" 8 | half = "2.6.0" 9 | libc = "0.2.172" 10 | md5 = "0.7.0" 11 | num-bigint = "0.4.6" 12 | num-traits = "0.2.19" 13 | regex = "1.11.1" 14 | ristretto_classfile = "0.18.1" 15 | serde = { version = "1.0.219", features = ["derive"] } 16 | serde_json = "1.0.140" 17 | sha2 = "0.10.9" 18 | zerocopy = "0.8.25" 19 | 20 | [lib] 21 | crate-type = ["dylib"] 22 | 23 | [package.metadata.rust-analyzer] 24 | rustc_private=true 25 | -------------------------------------------------------------------------------- /GenerateFiles.py: -------------------------------------------------------------------------------- 1 | import os 2 | import platform 3 | 4 | # get the absolute path of the current working directory 5 | current_directory = os.path.dirname(os.path.abspath(__file__)) 6 | 7 | # find .template files in this current directory 8 | template_files = [] 9 | for root, dirs, files in os.walk(current_directory): 10 | for file in files: 11 | if file.endswith(".template"): 12 | template_files.append(os.path.join(root, file)) 13 | 14 | # copy them, removing the .template extension 15 | # replace all instances of "../../.." with the absolute path of the current working directory 16 | # Determine the appropriate dynamic library extension for the host platform 17 | if platform.system() == "Windows": 18 | lib_extension = ".dll" 19 | elif platform.system() == "Darwin": # macOS 20 | lib_extension = ".dylib" 21 | else: # Assume Linux or other Unix-like systems 22 | lib_extension = ".so" 23 | 24 | for template_file in template_files: 25 | with open(template_file, 'r') as f: 26 | content = f.read() 27 | content = content.replace("../../..", current_directory) 28 | content = content.replace(".dylib", lib_extension) 29 | new_file_path = os.path.splitext(template_file)[0] 30 | with open(new_file_path, 'w') as f: 31 | f.write(content) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 IntegralPilot 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE-Apache: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2025 IntegralPilot 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # === Phony Targets === 2 | .PHONY: all help clean rust-components rust clean-rust java-linker clean-java-linker \ 3 | shim-metadata-gen clean-shim-metadata-gen vendor-r8 clean-vendor-r8 \ 4 | library clean-library gen-files clean-gen-files ci 5 | 6 | # === Terminal Colors === 7 | GREEN := \033[1;32m 8 | CYAN := \033[1;36m 9 | RESET := \033[0m 10 | 11 | # === Directory Variables === 12 | JAVA_LINKER_DIR := java-linker 13 | SHIM_METADATA_GEN_DIR := shim-metadata-gen 14 | LIBRARY_DIR := library 15 | LIBRARY_JAR := $(LIBRARY_DIR)/build/libs/library-0.1.0.jar 16 | RUST_SOURCES := $(shell find $(SHIM_METADATA_GEN_DIR)/src -type f -name '*.rs') 17 | 18 | # === Default Target === 19 | ifeq ($(IS_CI),1) 20 | all: rust java-linker vendor-r8 21 | @echo "$(GREEN)✨ Build complete in CI mode! ✨$(RESET)" 22 | else 23 | all: rust gen-files java-linker vendor-r8 24 | @echo "$(GREEN)✨ Build complete! ✨$(RESET)" 25 | endif 26 | 27 | # === CI Target === 28 | ci: 29 | $(MAKE) all IS_CI=1 30 | 31 | # === Help === 32 | help: 33 | @echo "$(CYAN)🛠️ Makefile for building the project$(RESET)" 34 | @echo "" 35 | @echo "Available targets:" 36 | @echo " make all - Build all components" 37 | @echo " make ci - Build all components in CI mode (skips rust-components and shim-metadata-gen)" 38 | @echo " make clean - Clean all components" 39 | @echo " make rust-components - Install needed Rust components" 40 | @echo " make rust - Build the Rust root project" 41 | @echo " make java-linker - Build the Java Linker subproject" 42 | @echo " make library - Build the standard library shim" 43 | @echo " make gen-files - Generate necessary files from templates" 44 | @echo " make clean-* - Clean individual components" 45 | 46 | # === Needed rust components === 47 | rust-components: 48 | @echo "$(CYAN)🔧 Installing Rust components...$(RESET)" 49 | rustup component add rustc-dev llvm-tools 50 | 51 | # === Rust root project (Cargo) === 52 | ifeq ($(IS_CI),1) 53 | rust: $(SHIM_METADATA_GEN_DIR)/core.json 54 | @echo "$(CYAN)📦 Building Rust root project...$(RESET)" 55 | RUSTFLAGS="-Awarnings" cargo build 56 | else 57 | rust: $(SHIM_METADATA_GEN_DIR)/core.json rust-components 58 | @echo "$(CYAN)📦 Building Rust root project...$(RESET)" 59 | RUSTFLAGS="-Awarnings" cargo build 60 | endif 61 | 62 | clean-rust: 63 | @echo "$(CYAN)🧹 Cleaning Rust root project...$(RESET)" 64 | cargo clean 65 | 66 | # === Java Linker Subproject === 67 | java-linker: 68 | @echo "$(CYAN)📦 Building Java Linker...$(RESET)" 69 | cd $(JAVA_LINKER_DIR) && RUSTFLAGS="-Awarnings" cargo build 70 | 71 | clean-java-linker: 72 | @echo "$(CYAN)🧹 Cleaning Java Linker...$(RESET)" 73 | cd $(JAVA_LINKER_DIR) && cargo clean 74 | 75 | # === Library Shim Metadata Generator === 76 | $(SHIM_METADATA_GEN_DIR)/core.json: $(RUST_SOURCES) library clean-shim-metadata-gen-json-files 77 | @if [ "$(IS_CI)" = "1" ]; then \ 78 | echo "$(CYAN)CI mode: skipping shim-metadata-gen$(RESET)"; \ 79 | else \ 80 | echo "$(CYAN)🛠️ Generating library shim metadata...$(RESET)"; \ 81 | cd $(SHIM_METADATA_GEN_DIR) && cargo run -- ../$(LIBRARY_JAR) ./core.json; \ 82 | fi 83 | 84 | clean-shim-metadata-gen: 85 | @echo "$(CYAN)🧹 Cleaning shim-metadata-gen...$(RESET)" 86 | cd $(SHIM_METADATA_GEN_DIR) && cargo clean 87 | 88 | clean-shim-metadata-gen-json-files: 89 | @if [ "$(IS_CI)" = "1" ]; then \ 90 | echo "$(CYAN)CI mode: skipping cleaning shim-metadata-gen JSON files$(RESET)"; \ 91 | else \ 92 | echo "$(CYAN)🧹 Cleaning shim-metadata-gen JSON files...$(RESET)"; \ 93 | rm -f $(SHIM_METADATA_GEN_DIR)/*.json; \ 94 | fi 95 | 96 | # === Standard Library Shim (Gradle) === 97 | library: $(LIBRARY_JAR) 98 | 99 | $(LIBRARY_JAR): 100 | @echo "$(CYAN)📚 Building standard library shim...$(RESET)" 101 | ifeq ($(IS_CI),1) 102 | cd $(LIBRARY_DIR) && gradle --no-daemon build && cd build/distributions && unzip -o library-0.1.0.zip 103 | else 104 | cd $(LIBRARY_DIR) && gradle build && cd build/distributions && unzip -o library-0.1.0.zip 105 | endif 106 | 107 | clean-library: 108 | @echo "$(CYAN)🧹 Cleaning library shim...$(RESET)" 109 | ifeq ($(IS_CI),1) 110 | cd $(LIBRARY_DIR) && gradle --no-daemon clean 111 | else 112 | cd $(LIBRARY_DIR) && gradle clean 113 | endif 114 | 115 | # === Generate files from templates === 116 | gen-files: clean-gen-files 117 | @echo "$(CYAN)🛠️ Generating files from templates...$(RESET)" 118 | python3 GenerateFiles.py 119 | @echo "$(CYAN)🛠️ Files generated!$(RESET)" 120 | 121 | clean-gen-files: 122 | @echo "$(CYAN)🧹 Cleaning template generated files...$(RESET)" 123 | rm -f jvm-unknown-unknown.json config.toml 124 | 125 | # === Vendoring of R8 === 126 | vendor-r8: 127 | @echo "$(CYAN)📦 Vendoring R8...$(RESET)" 128 | mkdir -p ./vendor && curl -L -o ./vendor/r8.jar https://maven.google.com/com/android/tools/r8/8.9.35/r8-8.9.35.jar 129 | @echo "$(CYAN)📦 R8 vendored!$(RESET)" 130 | 131 | clean-vendor-r8: 132 | @echo "$(CYAN)🧹 Cleaning vendored R8...$(RESET)" 133 | rm -rf ./vendor/r8.jar 134 | 135 | # === Clean All === 136 | clean: clean-rust clean-java-linker clean-library clean-shim-metadata-gen clean-gen-files clean-vendor-r8 137 | @echo "$(GREEN)🧼 All clean!$(RESET)" 138 | 139 | rebuild-shim: clean-library library shim-metadata-gen/core.json rust -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # rustc_codegen_jvm 🚀 2 | 3 | [![License: MIT/Apache-2.0](https://img.shields.io/badge/license-MIT%20%7C%20Apache--2.0-blue.svg)](https://opensource.org/licenses/MIT) 4 | [![CI](https://github.com/IntegralPilot/rustc_codegen_jvm/actions/workflows/ci.yml/badge.svg)](https://github.com/IntegralPilot/rustc_codegen_jvm/actions) 5 | 6 | A **custom Rust compiler backend** that emits Java Virtual Machine bytecode. 7 | Compile your Rust code into a runnable `.jar` on JVM 8+! 8 | 9 | --- 10 | 11 | ## 📖 Table of Contents 12 | 13 | 1. [Demos](#demos) 14 | 2. [Features](#features) 15 | 3. [How It Works](#how-it-works) 16 | 4. [Prerequisites](#prerequisites) 17 | 5. [Installation & Build](#installation--build) 18 | 6. [Usage](#usage) 19 | 7. [Running Tests](#running-tests) 20 | 8. [Project Structure](#project-structure) 21 | 9. [Contributing](#contributing) 22 | 10. [License](#license) 23 | 24 | --- 25 | 26 | ## 🔥 Demos 27 | All examples live in `tests/binary` and are compiled to JVM bytecode & run/tested on the CI on every commit. Some exciting demos made in pure-Rust include: 28 | 29 | - **[RSA](tests/binary/rsa/src/main.rs)** encryption/decryption 30 | - **[Binary search](tests/binary/binsearch/src/main.rs)** algorithm 31 | - **[Fibonacci](tests/binary/fibonacci/src/main.rs)** sequence generator 32 | - **[Collatz conjecture](tests/binary/collatz/src/main.rs)** verifier 33 | - **[Large prime](tests/binary/primes/src/main.rs)** generator 34 | - Use of nested data structures: enums, structs, tuples, arrays, slices (**[enums](tests/binary/enums/src/main.rs)**, **[structs](tests/binary/structs/src/main.rs)** - both tests use arrays and tuples) 35 | * **[Implementation blocks](tests/binary/impl/src/main.rs)** and **[traits](tests/binary/traits/src/main.rs)** (including dynamic dispatch!) 36 | - …and more! 37 | 38 | --- 39 | 40 | ## ✨ Features 41 | 42 | - **Minimal `no_std` & `no_core`** programs via `jvm-unknown-unknown` 43 | - Optimisations including constant folding and propogation, dead code elimination, and more to generate efficient JVM bytecode 44 | - Basic `core` support on host target for JVM output 45 | - Arithmetic (integers + floats, incl. checked ops) 46 | - Comparisons, bitwise & logical ops 47 | - Control flow: `if`/`else`, `match`, `for`, `while`, `loop` 48 | - Type casting (`as`), primitive types 49 | - Function calls (recursion supported) 50 | - Arrays & slices with nested indexing 51 | - Structs, tuples, enums (both C‑like and Rust‑style) 52 | - Executable `.jar` generation for binary crates 53 | - Mutable borrowing, references, and dereferencing 54 | - Implementations for ADTs, including using and returning `self`, `&self`, `&mut self` 55 | - Traits, including dynamic dispatch (`&dyn Trait`) 56 | - **Integration tests** for all features, in debug and release modes 57 | 58 | 🚧 **Next Milestone:** Full support for the Rust `core` crate. 59 | 60 | --- 61 | 62 | ## ⚙️ How It Works 63 | 64 | 1. **Rustc Frontend → MIR** 65 | Standard `rustc` parses your code into Mid‑level IR (MIR). 66 | 2. **MIR → OOMIR** 67 | Custom “Object‑Oriented MIR” simplifies MIR into OOP‑style constructs. 68 | _(see `src/lower1.rs`)_ 69 | 3. **OOMIR optimiser** 70 | Optimises OOMIR using constant folding, dead code elimination, and more. 71 | _(see `src/optimise1.rs`)_ 72 | - **Constant Folding**: Evaluates constant expressions at compile time. 73 | - **Constant Propagation**: Replaces variables with their constant values. 74 | - **Dead Code Elimination**: Removes unused code paths. 75 | - **Algebraic Simplification**: Simplifies expressions using algebraic identities. 76 | 4. **OOMIR → JVM Classfile** 77 | Translate to `.class` files using `ristretto_classfile`. 78 | _(see `src/lower2.rs`)_ 79 | 5. **R8 pass** 80 | `r8` adds stack map frames (neeeded to run on JVM 8+) and applies some further optimisations. 81 | 6. **Link & Package** 82 | `java-linker` bundles `.class` files into a runnable `.jar` with `META-INF/MANIFEST.MF`. 83 | 84 | --- 85 | 86 | ## 🛠 Prerequisites 87 | 88 | - **Rust Nightly** (`rustup default nightly`) 89 | - **Gradle 8.5+** (`gradle` in PATH) 90 | - **JDK 8+** (`java` in PATH, and the `JAVA_HOME` environment variable set) 91 | - **Python 3** (`python3` in PATH) 92 | 93 | --- 94 | 95 | ## 🏗 Installation & Build 96 | 97 | ```bash 98 | # Clone & enter repo 99 | git clone https://github.com/IntegralPilot/rustc_codegen_jvm.git 100 | cd rustc_codegen_jvm 101 | 102 | # Build everything 103 | make all 104 | ``` 105 | 106 | This will compile: 107 | 108 | - `rustc_codegen_jvm` backend library 109 | - `java-linker` 110 | - Kotlin shim for `core` (once core support is reached, this will no longer be needed) 111 | - Generate `config.toml` & `jvm-unknown-unknown.json` 112 | 113 | If you relocate the repo, re-run: 114 | ```bash 115 | make gen-files 116 | ``` 117 | 118 | --- 119 | 120 | ## 🚀 Usage 121 | 122 | 1. **Configure your project** 123 | In *your* Rust project directory, create or update `.cargo/config.toml` by copying the generated template (will be at the root of this repo after running make). Also, your `Cargo.toml` needs to contain the following (used to pass flags differentiating between debug and release builds to the linker): 124 | 125 | ```toml 126 | cargo-features = ["profile-rustflags"] 127 | ``` 128 | 129 | 2. **Build with Cargo** 130 | ```bash 131 | cargo build # debug 132 | cargo build --release # optimized 133 | ``` 134 | 135 | 3. **Run the `.jar`** 136 | ```bash 137 | java -jar target/debug/deps/your_crate*.jar 138 | ``` 139 | 140 | --- 141 | 142 | ## 🧪 Running Tests 143 | 144 | Ensure the toolchain is built: 145 | 146 | ```bash 147 | make all 148 | # If you moved the repo: 149 | make gen-files 150 | ``` 151 | 152 | Then: 153 | 154 | ```bash 155 | python3 Tester.py 156 | # or with --release for release‑mode tests 157 | ``` 158 | 159 | Look for `✅ All tests passed!` or inspect `.generated` files on failure. 160 | 161 | --- 162 | 163 | ## 📂 Project Structure 164 | 165 | ``` 166 | . 167 | ├── src/ # rustc_codegen_jvm backend 168 | │ ├── lib.rs 169 | │ ├── lower1.rs # MIR → OOMIR 170 | │ ├── lower2.rs # OOMIR → JVM bytecode 171 | │ └── oomir.rs # OOMIR definitions 172 | ├── java-linker/ # Bundles .class files into .jar 173 | ├── tests/binary/ # Integration tests 174 | ├── library/ # Kotlin shim for Rust core library 175 | ├── shim-metadata-gen/ # Generates core.json metadata 176 | ├── proguard/ # .pro rules used for r8 177 | ├── Makefile # build & gen-files 178 | ├── config.toml.template 179 | ├── jvm-unknown-unknown.json.template 180 | ├── Tester.py # test runner 181 | ├── GenerateFiles.py # regenerates config & target spec 182 | └── LICENSE, LICENSE-Apache 183 | ``` 184 | 185 | --- 186 | 187 | ## 🤝 Contributing 188 | 189 | Contributions, issues & PRs welcome! :) 190 | 191 | --- 192 | 193 | ## 📄 License 194 | 195 | Dual‑licensed under **MIT** OR **Apache 2.0** at your option: 196 | 197 | 198 | -------------------------------------------------------------------------------- /Tester.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | import subprocess 4 | import sys 5 | import argparse 6 | 7 | def read_from_file(path: str) -> str: 8 | with open(path, "r") as f: 9 | return f.read() 10 | 11 | def normalize_name(test_name: str) -> str: 12 | return test_name.replace("_", " ").capitalize() 13 | 14 | def run_command(cmd: list, cwd=None): 15 | proc = subprocess.run(cmd, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) 16 | return proc 17 | 18 | def write_to_file(path: str, content: str): 19 | with open(path, "w") as f: 20 | f.write(content) 21 | 22 | def process_test(test_dir: str, release_mode: bool): 23 | test_name = os.path.basename(test_dir) 24 | normalized = normalize_name(test_name) 25 | print(f"|-- Test '{test_name}' ({normalized})") 26 | 27 | print("|--- 🧼 Cleaning test folder...") 28 | proc = run_command(["cargo", "clean"], cwd=test_dir) 29 | if proc.returncode != 0: 30 | fail_path = os.path.join(test_dir, "cargo-clean-fail.generated") 31 | output = f"STDOUT:\n{proc.stdout}\n\nSTDERR:\n{proc.stderr}" 32 | write_to_file(fail_path, output) 33 | print(f"|---- ❌ cargo clean exited with code {proc.returncode}") 34 | return False 35 | 36 | print("|--- ⚒️ Building with Cargo...") 37 | build_cmd = ["cargo", "build", "--release"] if release_mode else ["cargo", "build"] 38 | no_jvm_target = os.path.join(test_dir, "no_jvm_target.flag") 39 | if not os.path.exists(no_jvm_target): 40 | print("|---- 🛠️ Building with JVM target...") 41 | build_cmd.extend(["--target", "../../../jvm-unknown-unknown.json"]) 42 | proc = run_command(build_cmd, cwd=test_dir) 43 | if proc.returncode != 0: 44 | fail_path = os.path.join(test_dir, "cargo-build-fail.generated") 45 | output = f"STDOUT:\n{proc.stdout}\n\nSTDERR:\n{proc.stderr}" 46 | write_to_file(fail_path, output) 47 | print(f"|---- ❌ cargo build exited with code {proc.returncode}") 48 | return False 49 | 50 | print("|--- 🤖 Running with Java...") 51 | target_dir = "release" if release_mode else "debug" 52 | if os.path.exists(no_jvm_target): 53 | jar_path = os.path.join(test_dir, "target", target_dir, "deps", f"{test_name}-*.jar") 54 | jar_file = None 55 | for file in os.listdir(os.path.join(test_dir, "target", target_dir, "deps")): 56 | if file.startswith(test_name) and file.endswith(".jar"): 57 | jar_file = file 58 | break 59 | if jar_file is None: 60 | print("|---- ❌ No jar file found in target/{target_dir}/deps") 61 | return False 62 | os.makedirs(os.path.join(test_dir, "target", "jvm-unknown-unknown", target_dir), exist_ok=True) 63 | os.rename(os.path.join(test_dir, "target", target_dir, "deps", jar_file), 64 | os.path.join(test_dir, "target", "jvm-unknown-unknown", target_dir, f"{test_name}.jar")) 65 | 66 | jar_path = os.path.join(test_dir, "target", "jvm-unknown-unknown", target_dir, f"{test_name}.jar") 67 | proc = run_command(["java", "-cp", f"library/build/distributions/library-0.1.0/lib/library-0.1.0.jar:library/build/distributions/library-0.1.0/lib/kotlin-stdlib-2.1.20.jar:{jar_path}", test_name]) 68 | 69 | expected_returncode_file = os.path.join(test_dir, "java-returncode.expected") 70 | if os.path.exists(expected_returncode_file): 71 | expected_returncode = int(read_from_file(expected_returncode_file).strip()) 72 | if proc.returncode != expected_returncode: 73 | fail_path = os.path.join(test_dir, "java-returncode-fail.generated") 74 | output = f"Expected return code: {expected_returncode}\nActual return code: {proc.returncode}\n\nSTDOUT:\n{proc.stdout}\n\nSTDERR:\n{proc.stderr}" 75 | write_to_file(fail_path, output) 76 | print(f"|---- ❌ java exited with code {proc.returncode}, expected {expected_returncode}") 77 | return False 78 | else: 79 | if proc.returncode != 0: 80 | fail_path = os.path.join(test_dir, "java-fail.generated") 81 | output = f"STDOUT:\n{proc.stdout}\n\nSTDERR:\n{proc.stderr}" 82 | write_to_file(fail_path, output) 83 | print(f"|---- ❌ java exited with code {proc.returncode}") 84 | return False 85 | 86 | expected_file = os.path.join(test_dir, "java-output.release.expected") if release_mode else os.path.join(test_dir, "java-output.expected") 87 | if not os.path.exists(expected_file) and release_mode: 88 | expected_file = os.path.join(test_dir, "java-output.expected") 89 | 90 | if os.path.exists(expected_file): 91 | expected_output = read_from_file(expected_file) 92 | if expected_output.strip() == "": 93 | expected_output = "STDOUT:STDERR:" 94 | else: 95 | expected_output = expected_output.replace("\n", "") 96 | actual_output = f"STDOUT:{proc.stdout.strip()}STDERR:{proc.stderr.strip()}" 97 | actual_output = actual_output.replace("\n", "") 98 | if actual_output != expected_output.strip(): 99 | diff_path = os.path.join(test_dir, "output-diff.generated") 100 | write_to_file(diff_path, actual_output) 101 | print("|---- ❌ java output did not match expected output") 102 | return False 103 | else: 104 | print("|--- ✅ Output matches expected output!") 105 | else: 106 | print("|--- ⚠️ Expected output file not found. Skipping comparison.") 107 | 108 | print("|--- ✅ Binary test passed!") 109 | return True 110 | 111 | def main(): 112 | parser = argparse.ArgumentParser(description="Tester for Rustc's JVM Codegen Backend") 113 | parser.add_argument("--release", action="store_true", help="Run cargo in release mode") 114 | parser.add_argument("--only-run", type=str, help="Comma-separated list of specific test names to run") 115 | parser.add_argument("--dont-run", type=str, help="Comma-separated list of specific test names to exclude") 116 | args = parser.parse_args() 117 | 118 | print("🧪 Tester for Rustc's JVM Codegen Backend started!") 119 | overall_success = True 120 | 121 | if args.release: 122 | print("|- ⚒️ Running in release mode") 123 | 124 | print(" ") 125 | 126 | # Gather test directories 127 | binary_dir = os.path.join("tests", "binary") 128 | if os.path.isdir(binary_dir): 129 | binary_tests = [os.path.join(binary_dir, d) for d in os.listdir(binary_dir) if os.path.isdir(os.path.join(binary_dir, d))] 130 | else: 131 | binary_tests = [] 132 | 133 | # Filter based on --only-run 134 | if args.only_run: 135 | requested_tests = set([name.strip() for name in args.only_run.split(",")]) 136 | binary_tests = [t for t in binary_tests if os.path.basename(t) in requested_tests] 137 | 138 | # Exclude tests based on --dont-run 139 | if args.dont_run: 140 | excluded_tests = set([name.strip() for name in args.dont_run.split(",")]) 141 | binary_tests = [t for t in binary_tests if os.path.basename(t) not in excluded_tests] 142 | 143 | print(f"|- 📦 Running {len(binary_tests)} binary build test(s)...") 144 | for test_dir in binary_tests: 145 | if not process_test(test_dir, args.release): 146 | overall_success = False 147 | 148 | print("") 149 | if overall_success: 150 | print("|-✅ All tests passed!") 151 | sys.exit(0) 152 | else: 153 | print("|- ❌ Some tests failed!") 154 | sys.exit(1) 155 | 156 | if __name__ == "__main__": 157 | main() 158 | -------------------------------------------------------------------------------- /config.toml.template: -------------------------------------------------------------------------------- 1 | [build] 2 | rustflags = [ 3 | "-Z", "codegen-backend=../../../target/debug/librustc_codegen_jvm.dylib", 4 | "-C", "linker=../../../java-linker/target/debug/java-linker", 5 | "-C", "link-args=../../../library/build/distributions/library-0.1.0/lib/library-0.1.0.jar ../../../library/build/distributions/library-0.1.0/lib/kotlin-stdlib-2.1.20.jar ../../../library/build/distributions/library-0.1.0/lib/annotations-13.0.jar --r8-jar ../../../vendor/r8.jar --proguard-config ../../../proguard/default.pro" 6 | ] 7 | 8 | # Throwing a JVM exception will unwind and give a stack trace, no need for rust to handle unwinding. 9 | [profile.debug] 10 | panic = "abort" 11 | 12 | [profile.release] 13 | panic = "abort" 14 | rustflags = [ 15 | "-C", "link-args=--release" 16 | ] -------------------------------------------------------------------------------- /java-linker/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "java-linker" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | regex = "1.11.1" 8 | ristretto_classfile = "0.17.0" 9 | tempfile = "3.19.1" 10 | zip = "2.5.0" 11 | -------------------------------------------------------------------------------- /jvm-unknown-unknown.json.template: -------------------------------------------------------------------------------- 1 | { 2 | "arch": "jvm", 3 | "binary-format": "wasm", 4 | "data-layout": "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-i128:128-n32:64-S128-ni:1:10:20", 5 | "dll-prefix": "", 6 | "dll-suffix": ".class", 7 | "eh-frame-header": false, 8 | "emit-debug-gdb-scripts": false, 9 | "exe-suffix": ".jar", 10 | "linker": "../../../java-linker/target/debug/java-linker", 11 | "default-codegen-backend": "../../../target/debug/librustc_codegen_jvm.dylib", 12 | "max-atomic-width": 64, 13 | "metadata": { 14 | "description": "Java Bytecode", 15 | "host_tools": false, 16 | "std": false, 17 | "tier": 3 18 | }, 19 | "panic-strategy": "abort", 20 | "target-pointer-width": "32", 21 | "llvm-target": "jvm-unknown-unknown" 22 | } -------------------------------------------------------------------------------- /library/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "2.1.20" 3 | application 4 | } 5 | 6 | group = "org.rustlang" 7 | version = "0.1.0" 8 | 9 | repositories { 10 | mavenCentral() 11 | } 12 | 13 | dependencies { 14 | implementation(kotlin("stdlib")) 15 | } 16 | 17 | kotlin { 18 | jvmToolchain(21) 19 | } -------------------------------------------------------------------------------- /proguard/default.pro: -------------------------------------------------------------------------------- 1 | -keep public class * { public static void main(java.lang.String[]); } 2 | -keep class * { 3 | ; 4 | } 5 | -keepclassmembers class * implements * { 6 | ; 7 | } -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly" 3 | components = ["rustc-dev", "rust-src", "llvm-tools-preview"] 4 | -------------------------------------------------------------------------------- /shim-metadata-gen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "shim-metadata-gen" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | ristretto_classfile = "0.17.0" 8 | serde = { version = "1.0.219", features = ["derive"] } 9 | serde_json = "1.0.140" 10 | zip = "2.6.1" 11 | -------------------------------------------------------------------------------- /shim-metadata-gen/core.json: -------------------------------------------------------------------------------- 1 | { 2 | "arguments_new_const_1": { 3 | "descriptor": "([Ljava/lang/String;)Ljava/lang/String;", 4 | "is_static": true 5 | }, 6 | "core_assert_failed": { 7 | "descriptor": "(Ljava/lang/String;)V", 8 | "is_static": true 9 | }, 10 | "core_fmt_rt_argument_new_display": { 11 | "descriptor": "(Ljava/lang/Object;)Ljava/lang/String;", 12 | "is_static": true 13 | }, 14 | "core_fmt_rt_argument_new_display_bool": { 15 | "descriptor": "(Z)Ljava/lang/String;", 16 | "is_static": true 17 | }, 18 | "core_fmt_rt_argument_new_display_f64": { 19 | "descriptor": "(D)Ljava/lang/String;", 20 | "is_static": true 21 | }, 22 | "core_fmt_rt_argument_new_display_i32": { 23 | "descriptor": "(I)Ljava/lang/String;", 24 | "is_static": true 25 | }, 26 | "core_fmt_rt_arguments_new_const_1": { 27 | "descriptor": "([Ljava/lang/String;)Ljava/lang/String;", 28 | "is_static": true 29 | }, 30 | "core_panicking_panic": { 31 | "descriptor": "(Ljava/lang/String;)V", 32 | "is_static": true 33 | }, 34 | "core_slice_u8_starts_with": { 35 | "descriptor": "(Ljava/lang/Object;Ljava/lang/Object;)Z", 36 | "is_static": true 37 | }, 38 | "core_str_str_starts_with_char": { 39 | "descriptor": "(Ljava/lang/Object;Ljava/lang/Object;)Z", 40 | "is_static": true 41 | }, 42 | "deref": { 43 | "descriptor": "(Ljava/lang/Object;)Ljava/lang/Object;", 44 | "is_static": true 45 | }, 46 | "encode_utf8_raw": { 47 | "descriptor": "(J[[S)[[S", 48 | "is_static": true 49 | }, 50 | "eq": { 51 | "descriptor": "(Ljava/lang/Object;Ljava/lang/Object;)Z", 52 | "is_static": true 53 | }, 54 | "equal": { 55 | "descriptor": "(Ljava/lang/Object;Ljava/lang/Object;)Z", 56 | "is_static": true 57 | }, 58 | "f128_eq": { 59 | "descriptor": "(Ljava/lang/Object;Ljava/lang/Object;)Z", 60 | "is_static": true 61 | }, 62 | "f16_eq": { 63 | "descriptor": "(Ljava/lang/Object;Ljava/lang/Object;)Z", 64 | "is_static": true 65 | }, 66 | "f32_eq": { 67 | "descriptor": "(Ljava/lang/Object;Ljava/lang/Object;)Z", 68 | "is_static": true 69 | }, 70 | "f32_f32_eq": { 71 | "descriptor": "(Ljava/lang/Object;Ljava/lang/Object;)Z", 72 | "is_static": true 73 | }, 74 | "f64_eq": { 75 | "descriptor": "(Ljava/lang/Object;Ljava/lang/Object;)Z", 76 | "is_static": true 77 | }, 78 | "fromShortArray": { 79 | "descriptor": "([S)Ljava/lang/String;", 80 | "is_static": true 81 | }, 82 | "i32_i32_i32_eq": { 83 | "descriptor": "(Ljava/lang/Object;Ljava/lang/Object;)Z", 84 | "is_static": true 85 | }, 86 | "i32_u8_eq": { 87 | "descriptor": "(Ljava/lang/Object;Ljava/lang/Object;)Z", 88 | "is_static": true 89 | }, 90 | "i64_eq": { 91 | "descriptor": "(Ljava/lang/Object;Ljava/lang/Object;)Z", 92 | "is_static": true 93 | }, 94 | "option_is_none": { 95 | "descriptor": "(Ljava/lang/Object;)Z", 96 | "is_static": true 97 | }, 98 | "option_unwrap": { 99 | "descriptor": "(Ljava/lang/Object;)Ljava/lang/Object;", 100 | "is_static": true 101 | }, 102 | "option_usize_eq": { 103 | "descriptor": "(Ljava/lang/Object;Ljava/lang/Object;)Z", 104 | "is_static": true 105 | }, 106 | "option_usize_is_none": { 107 | "descriptor": "(Ljava/lang/Object;)Z", 108 | "is_static": true 109 | }, 110 | "panic_fmt": { 111 | "descriptor": "(Ljava/lang/Object;)V", 112 | "is_static": true 113 | }, 114 | "raw_eq_u8_8": { 115 | "descriptor": "(Ljava/lang/Object;Ljava/lang/Object;)Z", 116 | "is_static": true 117 | }, 118 | "str_eq": { 119 | "descriptor": "(Ljava/lang/String;Ljava/lang/String;)Z", 120 | "is_static": true 121 | }, 122 | "toShortArray": { 123 | "descriptor": "(Ljava/lang/String;)[S", 124 | "is_static": true 125 | }, 126 | "to_string": { 127 | "descriptor": "(Ljava/lang/Object;)Ljava/lang/String;", 128 | "is_static": true 129 | }, 130 | "u8_8_eq": { 131 | "descriptor": "(Ljava/lang/Object;Ljava/lang/Object;)Z", 132 | "is_static": true 133 | }, 134 | "u8_eq": { 135 | "descriptor": "(Ljava/lang/Object;Ljava/lang/Object;)Z", 136 | "is_static": true 137 | }, 138 | "u8_equal": { 139 | "descriptor": "(Ljava/lang/Object;Ljava/lang/Object;)Z", 140 | "is_static": true 141 | } 142 | } -------------------------------------------------------------------------------- /shim-metadata-gen/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | use std::env; 3 | use std::fs; 4 | use std::io::{Cursor, Read, Write}; 5 | use std::path::PathBuf; 6 | use std::process::exit; 7 | 8 | use ristretto_classfile::{ 9 | ClassFile, MethodAccessFlags, 10 | }; 11 | use serde::Serialize; 12 | use zip::ZipArchive; 13 | 14 | #[derive(Serialize, Debug, Clone)] 15 | struct ShimInfo { 16 | descriptor: String, 17 | is_static: bool, 18 | } 19 | 20 | // Use BTreeMap to keep the shims sorted by function (key) name 21 | type ShimMap = BTreeMap; 22 | 23 | fn main() { 24 | let args: Vec = env::args().collect(); 25 | if args.len() != 3 { 26 | eprintln!("Usage: {} ", args[0]); 27 | exit(1); 28 | } 29 | 30 | let jar_path = PathBuf::from(&args[1]); 31 | let output_path = PathBuf::from(&args[2]); 32 | 33 | println!("Input JAR: {:?}", jar_path); 34 | println!("Output JSON: {:?}", output_path); 35 | 36 | match generate_metadata(&jar_path, &output_path) { 37 | Ok(count) => { 38 | println!("Successfully generated metadata for {} shim methods.", count); 39 | } 40 | Err(e) => { 41 | eprintln!("Error: {}", e); 42 | exit(1); 43 | } 44 | } 45 | } 46 | 47 | /// Reads the specified class from the JAR, parses it, and generates the JSON metadata file. 48 | fn generate_metadata(jar_path: &PathBuf, output_path: &PathBuf) -> Result { 49 | let target_class_name_internal = "org/rustlang/core/Core"; 50 | let target_class_path = format!("{}.class", target_class_name_internal); 51 | 52 | // 1. Open the JAR (Zip Archive) 53 | let file = fs::File::open(jar_path) 54 | .map_err(|e| format!("Failed to open JAR file {:?}: {}", jar_path, e))?; 55 | let mut archive = ZipArchive::new(file) 56 | .map_err(|e| format!("Failed to read JAR archive {:?}: {}", jar_path, e))?; 57 | 58 | // 2. Find and Read the target .class file 59 | let mut class_file_entry = archive 60 | .by_name(&target_class_path) 61 | .map_err(|e| format!("Class '{}' not found in JAR {:?}: {}", target_class_path, jar_path, e))?; 62 | 63 | let mut class_data = Vec::new(); 64 | class_file_entry 65 | .read_to_end(&mut class_data) 66 | .map_err(|e| format!("Failed to read '{}' from JAR: {}", target_class_path, e))?; 67 | 68 | println!("Read {} bytes for {}", class_data.len(), target_class_path); 69 | 70 | // 3. Parse the .class file using ristretto_classfile 71 | let parsed_class = ClassFile::from_bytes(&mut Cursor::new(class_data)) 72 | .map_err(|e| format!("Failed to parse '{}': {:?}", target_class_path, e))?; 73 | println!("Successfully parsed class file."); 74 | 75 | // 4. Build the ShimMap (Method Name -> ShimInfo) 76 | let mut shim_map: ShimMap = BTreeMap::new(); 77 | let cp = &parsed_class.constant_pool; // Borrow constant pool for lookups 78 | 79 | for method in &parsed_class.methods { 80 | let method_name = cp 81 | .try_get_utf8(method.name_index) 82 | .map_err(|e| format!("Failed to get method name at index {}: {:?}", method.name_index, e))? 83 | .to_string(); 84 | 85 | // Skip constructors and static initializers 86 | if method_name == "" || method_name == "" { 87 | continue; 88 | } 89 | 90 | // --- Filter for shims --- 91 | // Convention: Assuming all public static methods in this class are potential shims 92 | let is_public = method.access_flags.contains(MethodAccessFlags::PUBLIC); 93 | let is_static = method.access_flags.contains(MethodAccessFlags::STATIC); 94 | 95 | if is_public && is_static { 96 | let descriptor = cp 97 | .try_get_utf8(method.descriptor_index) 98 | .map_err(|e| { 99 | format!( 100 | "Failed to get descriptor for method '{}' at index {}: {:?}", 101 | method_name, method.descriptor_index, e 102 | ) 103 | })? 104 | .to_string(); 105 | 106 | println!( 107 | " Found potential shim: '{}', Descriptor: '{}'", 108 | method_name, descriptor 109 | ); 110 | 111 | shim_map.insert( 112 | method_name, // Assumes this matches make_jvm_safe output 113 | ShimInfo { 114 | descriptor, 115 | is_static: true, // We already filtered for static 116 | }, 117 | ); 118 | } else { 119 | println!(" Skipping non-public-static method: '{}'", method_name); 120 | } 121 | } 122 | 123 | // 5. Serialize the map to JSON (keys will be in sorted order because of BTreeMap) 124 | let json_output = serde_json::to_string_pretty(&shim_map) 125 | .map_err(|e| format!("Failed to serialize metadata to JSON: {}", e))?; 126 | 127 | // 6. Write JSON to the output file 128 | let mut output_file = fs::File::create(output_path) 129 | .map_err(|e| format!("Failed to create output file {:?}: {}", output_path, e))?; 130 | 131 | output_file 132 | .write_all(json_output.as_bytes()) 133 | .map_err(|e| format!("Failed to write JSON to {:?}: {}", output_path, e))?; 134 | 135 | Ok(shim_map.len()) 136 | } -------------------------------------------------------------------------------- /src/lower1.rs: -------------------------------------------------------------------------------- 1 | //! This is the stage 1 lowering pass of the compiler. 2 | //! It is responsible for coverting the MIR into a lower-level IR, called OOMIR (see src/oomir.rs). 3 | //! It is a simple pass that converts the MIR into a more object-oriented representation. 4 | 5 | // lower1.rs 6 | //! This module converts Rust MIR into an object-oriented MIR (OOMIR) 7 | //! that sits between MIR and JVM bytecode. It supports a subset of Rust constructs 8 | //! (arithmetic, branching, returns) and can be extended to support more of Rust. 9 | 10 | use crate::oomir; 11 | use control_flow::convert_basic_block; 12 | use rustc_middle::{ 13 | mir::Body, 14 | ty::{Instance, TyCtxt}, 15 | }; 16 | use std::collections::HashMap; 17 | use types::ty_to_oomir_type; 18 | 19 | mod control_flow; 20 | pub mod operand; 21 | pub mod place; 22 | pub mod types; 23 | 24 | /// Converts a MIR Body into an OOMIR Function. 25 | /// This function extracts a function’s signature (currently minimal) and builds 26 | /// a control flow graph of basic blocks. 27 | pub fn mir_to_oomir<'tcx>( 28 | tcx: TyCtxt<'tcx>, 29 | instance: Instance<'tcx>, 30 | mir: &mut Body<'tcx>, 31 | ) -> (oomir::Function, HashMap) { 32 | // Get a function name from the instance. 33 | let fn_name = tcx.item_name(instance.def_id()).to_string(); 34 | 35 | // Extract function signature 36 | let mir_sig = tcx.type_of(instance.def_id()).skip_binder().fn_sig(tcx); 37 | let params_ty = mir_sig.inputs(); 38 | let return_ty = mir_sig.output(); 39 | 40 | let data_types = &mut HashMap::new(); 41 | 42 | let params_oomir_ty: Vec = params_ty 43 | .skip_binder() 44 | .iter() 45 | .map(|ty| ty_to_oomir_type(*ty, tcx, data_types)) 46 | .collect(); 47 | let return_oomir_ty: oomir::Type = ty_to_oomir_type(return_ty.skip_binder(), tcx, data_types); 48 | 49 | let mut signature = oomir::Signature { 50 | params: params_oomir_ty, 51 | ret: Box::new(return_oomir_ty.clone()), // Clone here to pass to convert_basic_block 52 | }; 53 | 54 | // check if txc.entry_fn() matches the DefId of the function 55 | // note: libraries exist and don't have an entry function, handle that case 56 | if let Some(entry_fn) = tcx.entry_fn(()) { 57 | if entry_fn.0 == instance.def_id() { 58 | // see if the name is "main" 59 | if fn_name == "main" { 60 | // manually override the signature to match the JVM main method 61 | signature = oomir::Signature { 62 | params: vec![oomir::Type::Array(Box::new(oomir::Type::Class( 63 | "java/lang/String".to_string(), 64 | )))], 65 | ret: Box::new(oomir::Type::Void), 66 | }; 67 | } 68 | } 69 | } 70 | 71 | // Build a CodeBlock from the MIR basic blocks. 72 | let mut basic_blocks = HashMap::new(); 73 | // MIR guarantees that the start block is BasicBlock 0. 74 | let entry_label = "bb0".to_string(); 75 | 76 | let mir_cloned = mir.clone(); 77 | 78 | // Need read-only access to mir for local_decls inside the loop 79 | for (bb, bb_data) in mir.basic_blocks_mut().iter_enumerated() { 80 | let bb_ir = convert_basic_block( 81 | bb, 82 | bb_data, 83 | tcx, 84 | &mir_cloned, 85 | &return_oomir_ty, 86 | &mut basic_blocks, 87 | data_types, 88 | ); // Pass return type here 89 | basic_blocks.insert(bb_ir.label.clone(), bb_ir); 90 | } 91 | 92 | /*let mut instrs = vec![]; 93 | 94 | // Initialize local variables (excluding return place and arguments) 95 | // MIR local indices: 96 | // 0: return place 97 | // 1..=arg_count: arguments 98 | // arg_count+1..: user variables and temporaries 99 | for (local_index, local_decl) in mir_cloned.local_decls.iter_enumerated() { 100 | // local_index has type Local 101 | // local_decl has type &LocalDecl<'tcx> 102 | 103 | // Skip return place (_0) and arguments (_1 to _arg_count) 104 | // They are initialized by return value / function call respectively. 105 | if local_index.index() == 0 || local_index.index() <= mir_cloned.arg_count { 106 | continue; // Skip this local 107 | } 108 | 109 | let ty = local_decl.ty; 110 | let oomir_ty = ty_to_oomir_type(ty, tcx, data_types); 111 | 112 | // Only add initialization if the type has a default value we can represent 113 | let default = oomir_ty.get_default_value(data_types); 114 | if default.is_none() { 115 | continue; 116 | } 117 | // Get the underlying usize index for formatting 118 | let idx_usize = local_index.index(); 119 | instrs.push(Instruction::Move { 120 | dest: format!("_{}", idx_usize), 121 | src: Operand::Constant(default.unwrap()), 122 | }); 123 | } 124 | 125 | // add instrs to the start of the entry block 126 | let entry_block = basic_blocks.get_mut(&entry_label).unwrap(); 127 | entry_block.instructions.splice(0..0, instrs);*/ 128 | 129 | let codeblock = oomir::CodeBlock { 130 | basic_blocks, 131 | entry: entry_label, 132 | }; 133 | 134 | // Return the OOMIR representation of the function. 135 | ( 136 | oomir::Function { 137 | name: fn_name, 138 | signature, 139 | body: codeblock, 140 | }, 141 | data_types.clone(), 142 | ) 143 | } 144 | -------------------------------------------------------------------------------- /src/lower1/control_flow/checked_ops.rs: -------------------------------------------------------------------------------- 1 | use crate::oomir::{Constant, Instruction, Operand, Type}; 2 | 3 | pub fn emit_checked_arithmetic_oomir_instructions( 4 | dest_base_name: &str, 5 | op1: &Operand, 6 | op2: &Operand, 7 | op_ty: &Type, 8 | operation: &str, // "add", "sub", "mul" 9 | unique_id_offset: usize, // Used to ensure unique labels/temps 10 | ) -> (Vec, String, String) { 11 | let mut generated_instructions = Vec::new(); 12 | let unique_id = unique_id_offset; // Use offset for uniqueness 13 | 14 | let tmp_result = format!("{}_{}_chk_res_{}", dest_base_name, operation, unique_id); 15 | let tmp_overflow = format!("{}_{}_chk_ovf_{}", dest_base_name, operation, unique_id); 16 | 17 | if matches!(op_ty, Type::Class(c) if c == crate::lower2::BIG_INTEGER_CLASS || c == crate::lower2::BIG_DECIMAL_CLASS) 18 | { 19 | // For BigInt/BigDec, overflow doesn't happen in the fixed-size sense. 20 | // Perform the regular operation and always set overflow to false. 21 | 22 | // 1. Perform the actual operation 23 | let op_instr = match operation { 24 | "add" => Instruction::Add { 25 | dest: tmp_result.clone(), 26 | op1: op1.clone(), 27 | op2: op2.clone(), 28 | }, 29 | "sub" => Instruction::Sub { 30 | dest: tmp_result.clone(), 31 | op1: op1.clone(), 32 | op2: op2.clone(), 33 | }, 34 | "mul" => Instruction::Mul { 35 | dest: tmp_result.clone(), 36 | op1: op1.clone(), 37 | op2: op2.clone(), 38 | }, 39 | _ => panic!( 40 | "Unsupported checked operation for BigInt/BigDec: {}", 41 | operation 42 | ), 43 | }; 44 | generated_instructions.push(op_instr); 45 | 46 | // 2. Set overflow flag to false 47 | generated_instructions.push(Instruction::Move { 48 | dest: tmp_overflow.clone(), 49 | src: Operand::Constant(Constant::Boolean(false)), 50 | }); 51 | 52 | return (generated_instructions, tmp_result, tmp_overflow); 53 | } 54 | 55 | // --- Handle Primitive Integer/Float Types --- 56 | 57 | // Generate unique temporary variable names and labels (only needed for primitives) 58 | let tmp_a = format!("{}_{}_chk_a_{}", dest_base_name, operation, unique_id); 59 | let tmp_b = format!("{}_{}_chk_b_{}", dest_base_name, operation, unique_id); 60 | 61 | // Labels for control flow within this sequence 62 | let label_check_neg = format!( 63 | "label_{}_{}_chk_neg_{}", 64 | dest_base_name, operation, unique_id 65 | ); 66 | let label_overflow = format!( 67 | "label_{}_{}_chk_ovf_{}", 68 | dest_base_name, operation, unique_id 69 | ); 70 | let label_no_overflow = format!( 71 | "label_{}_{}_chk_no_ovf_{}", 72 | dest_base_name, operation, unique_id 73 | ); 74 | let label_end = format!( 75 | "label_{}_{}_chk_end_{}", 76 | dest_base_name, operation, unique_id 77 | ); 78 | 79 | // Labels for intermediate targets within the checks 80 | let lbl_pos_check_b_non_neg = format!( 81 | "lbl_{}_{}_pos_chk_b_non_neg_{}", 82 | dest_base_name, operation, unique_id 83 | ); 84 | let lbl_pos_check_final_cmp = format!( 85 | "lbl_{}_{}_pos_chk_final_cmp_{}", 86 | dest_base_name, operation, unique_id 87 | ); 88 | let lbl_neg_check_b_non_pos = format!( 89 | "lbl_{}_{}_neg_chk_b_non_pos_{}", 90 | dest_base_name, operation, unique_id 91 | ); 92 | let lbl_neg_check_final_cmp = format!( 93 | "lbl_{}_{}_neg_chk_final_cmp_{}", 94 | dest_base_name, operation, unique_id 95 | ); 96 | 97 | // Get MIN/MAX constants and the type-specific zero constant for primitives 98 | let (const_max_op, const_min_op, zero_const_op) = match op_ty { 99 | Type::I8 => ( 100 | Some(Constant::I8(i8::MAX)), 101 | Some(Constant::I8(i8::MIN)), 102 | Some(Constant::I8(0)), 103 | ), 104 | Type::I16 => ( 105 | Some(Constant::I16(i16::MAX)), 106 | Some(Constant::I16(i16::MIN)), 107 | Some(Constant::I16(0)), 108 | ), 109 | Type::I32 => ( 110 | Some(Constant::I32(i32::MAX)), 111 | Some(Constant::I32(i32::MIN)), 112 | Some(Constant::I32(0)), 113 | ), 114 | Type::I64 => ( 115 | Some(Constant::I64(i64::MAX)), 116 | Some(Constant::I64(i64::MIN)), 117 | Some(Constant::I64(0)), 118 | ), 119 | _ => panic!( 120 | "Checked arithmetic MIN/MAX/Zero constants not defined for OOMIR type {:?}", 121 | op_ty 122 | ), 123 | }; 124 | 125 | // Ensure we have constants for integer types before proceeding with integer logic 126 | let (const_max, const_min, zero_const) = match (const_max_op, const_min_op, zero_const_op) { 127 | (Some(max), Some(min), Some(zero)) => (max, min, zero), 128 | (_, _, _) => { 129 | // currently impossible for good for correct error reporting in future 130 | panic!( 131 | "Checked arithmetic MAX constant not defined for OOMIR type {:?}", 132 | op_ty 133 | ) 134 | } 135 | }; 136 | 137 | // --- Load operands into temporary variables (for primitive integer logic) --- 138 | generated_instructions.push(Instruction::Move { 139 | dest: tmp_a.clone(), 140 | src: op1.clone(), 141 | }); 142 | generated_instructions.push(Instruction::Move { 143 | dest: tmp_b.clone(), 144 | src: op2.clone(), 145 | }); 146 | 147 | let op1_var = Operand::Variable { 148 | name: tmp_a.clone(), 149 | ty: op_ty.clone(), 150 | }; 151 | let op2_var = Operand::Variable { 152 | name: tmp_b.clone(), 153 | ty: op_ty.clone(), 154 | }; 155 | 156 | // --- Start Positive Overflow Check (Integer Logic) --- 157 | let tmp_cmp1 = format!("{}_{}_chk_cmp1_{}", dest_base_name, operation, unique_id); 158 | generated_instructions.push(Instruction::Gt { 159 | dest: tmp_cmp1.clone(), 160 | op1: op1_var.clone(), 161 | op2: Operand::Constant(zero_const.clone()), 162 | }); 163 | generated_instructions.push(Instruction::Branch { 164 | condition: Operand::Variable { 165 | name: tmp_cmp1.clone(), 166 | ty: Type::Boolean, 167 | }, 168 | true_block: lbl_pos_check_b_non_neg.clone(), 169 | false_block: label_check_neg.clone(), 170 | }); 171 | 172 | // --- Positive Check: Check B --- 173 | generated_instructions.push(Instruction::Label { 174 | name: lbl_pos_check_b_non_neg.clone(), 175 | }); 176 | let tmp_cmp2 = format!("{}_{}_chk_cmp2_{}", dest_base_name, operation, unique_id); 177 | generated_instructions.push(Instruction::Gt { 178 | dest: tmp_cmp2.clone(), 179 | op1: op2_var.clone(), 180 | op2: Operand::Constant(zero_const.clone()), 181 | }); 182 | generated_instructions.push(Instruction::Branch { 183 | condition: Operand::Variable { 184 | name: tmp_cmp2.clone(), 185 | ty: Type::Boolean, 186 | }, 187 | true_block: lbl_pos_check_final_cmp.clone(), 188 | false_block: label_check_neg.clone(), // If b <= 0, can't positive overflow, check neg 189 | }); 190 | 191 | // --- Positive Check: Final Comparison (b > MAX - a) --- 192 | generated_instructions.push(Instruction::Label { 193 | name: lbl_pos_check_final_cmp.clone(), 194 | }); 195 | let tmp_max_minus_a = format!( 196 | "{}_{}_chk_max_minus_a_{}", 197 | dest_base_name, operation, unique_id 198 | ); 199 | let tmp_cmp3 = format!("{}_{}_chk_cmp3_{}", dest_base_name, operation, unique_id); 200 | generated_instructions.push(Instruction::Sub { 201 | dest: tmp_max_minus_a.clone(), 202 | op1: Operand::Constant(const_max.clone()), // MAX 203 | op2: op1_var.clone(), // a 204 | }); 205 | generated_instructions.push(Instruction::Gt { 206 | dest: tmp_cmp3.clone(), 207 | op1: op2_var.clone(), // b 208 | op2: Operand::Variable { 209 | name: tmp_max_minus_a.clone(), 210 | ty: op_ty.clone(), 211 | }, // MAX - a 212 | }); 213 | generated_instructions.push(Instruction::Branch { 214 | condition: Operand::Variable { 215 | name: tmp_cmp3.clone(), 216 | ty: Type::Boolean, 217 | }, 218 | true_block: label_overflow.clone(), // If b > MAX - a, OVERFLOW 219 | false_block: label_check_neg.clone(), // Else, check negative overflow 220 | }); 221 | 222 | // --- Start Negative Overflow Check --- 223 | generated_instructions.push(Instruction::Label { 224 | name: label_check_neg.clone(), 225 | }); 226 | let tmp_cmp4 = format!("{}_{}_chk_cmp4_{}", dest_base_name, operation, unique_id); 227 | generated_instructions.push(Instruction::Lt { 228 | dest: tmp_cmp4.clone(), 229 | op1: op1_var.clone(), 230 | op2: Operand::Constant(zero_const.clone()), 231 | }); 232 | generated_instructions.push(Instruction::Branch { 233 | condition: Operand::Variable { 234 | name: tmp_cmp4.clone(), 235 | ty: Type::Boolean, 236 | }, 237 | true_block: lbl_neg_check_b_non_pos.clone(), // If a < 0, check b 238 | false_block: label_no_overflow.clone(), // If a >= 0, can't negative overflow (already checked pos) 239 | }); 240 | 241 | // --- Negative Check: Check B --- 242 | generated_instructions.push(Instruction::Label { 243 | name: lbl_neg_check_b_non_pos.clone(), 244 | }); 245 | let tmp_cmp5 = format!("{}_{}_chk_cmp5_{}", dest_base_name, operation, unique_id); 246 | generated_instructions.push(Instruction::Lt { 247 | dest: tmp_cmp5.clone(), 248 | op1: op2_var.clone(), 249 | op2: Operand::Constant(zero_const.clone()), 250 | }); 251 | generated_instructions.push(Instruction::Branch { 252 | condition: Operand::Variable { 253 | name: tmp_cmp5.clone(), 254 | ty: Type::Boolean, 255 | }, 256 | true_block: lbl_neg_check_final_cmp.clone(), // If b < 0, do final check 257 | false_block: label_no_overflow.clone(), // If b >= 0, can't negative overflow 258 | }); 259 | 260 | // --- Negative Check: Final Comparison (b < MIN - a) --- 261 | generated_instructions.push(Instruction::Label { 262 | name: lbl_neg_check_final_cmp.clone(), 263 | }); 264 | let tmp_min_minus_a = format!( 265 | "{}_{}_chk_min_minus_a_{}", 266 | dest_base_name, operation, unique_id 267 | ); 268 | let tmp_cmp6 = format!("{}_{}_chk_cmp6_{}", dest_base_name, operation, unique_id); 269 | generated_instructions.push(Instruction::Sub { 270 | dest: tmp_min_minus_a.clone(), 271 | op1: Operand::Constant(const_min.clone()), // MIN 272 | op2: op1_var.clone(), // a 273 | }); 274 | generated_instructions.push(Instruction::Lt { 275 | dest: tmp_cmp6.clone(), 276 | op1: op2_var.clone(), // b 277 | op2: Operand::Variable { 278 | name: tmp_min_minus_a.clone(), 279 | ty: op_ty.clone(), 280 | }, // MIN - a 281 | }); 282 | generated_instructions.push(Instruction::Branch { 283 | condition: Operand::Variable { 284 | name: tmp_cmp6.clone(), 285 | ty: Type::Boolean, 286 | }, 287 | true_block: label_overflow.clone(), // If b < MIN - a, OVERFLOW 288 | false_block: label_no_overflow.clone(), // Else, NO overflow 289 | }); 290 | 291 | // --- Overflow Path --- 292 | generated_instructions.push(Instruction::Label { 293 | name: label_overflow.clone(), 294 | }); 295 | generated_instructions.push(Instruction::Move { 296 | dest: tmp_overflow.clone(), 297 | src: Operand::Constant(Constant::Boolean(true)), // Set overflow flag 298 | }); 299 | // Store zero in result on overflow (consistent with Rust's checked_add etc.) 300 | generated_instructions.push(Instruction::Move { 301 | dest: tmp_result.clone(), 302 | src: Operand::Constant(zero_const.clone()), 303 | }); 304 | generated_instructions.push(Instruction::Jump { 305 | target: label_end.clone(), 306 | }); 307 | 308 | // --- No Overflow Path --- 309 | generated_instructions.push(Instruction::Label { 310 | name: label_no_overflow.clone(), 311 | }); 312 | generated_instructions.push(Instruction::Move { 313 | dest: tmp_overflow.clone(), 314 | src: Operand::Constant(Constant::Boolean(false)), // Clear overflow flag 315 | }); 316 | // Perform actual operation 317 | let op_instr = match operation { 318 | "add" => Instruction::Add { 319 | dest: tmp_result.clone(), 320 | op1: op1_var.clone(), 321 | op2: op2_var.clone(), 322 | }, 323 | "sub" => Instruction::Sub { 324 | dest: tmp_result.clone(), 325 | op1: op1_var.clone(), 326 | op2: op2_var.clone(), 327 | }, 328 | "mul" => Instruction::Mul { 329 | dest: tmp_result.clone(), 330 | op1: op1_var.clone(), 331 | op2: op2_var.clone(), 332 | }, 333 | // Add other checked operations (div, rem, shl, shr?) here if needed 334 | _ => panic!( 335 | "Unsupported checked operation for Primitives: {}", 336 | operation 337 | ), 338 | }; 339 | generated_instructions.push(op_instr); 340 | generated_instructions.push(Instruction::Jump { 341 | target: label_end.clone(), 342 | }); 343 | 344 | // --- End Path --- 345 | generated_instructions.push(Instruction::Label { 346 | name: label_end.clone(), 347 | }); 348 | // Result is in tmp_result, overflow flag in tmp_overflow. 349 | 350 | (generated_instructions, tmp_result, tmp_overflow) 351 | } 352 | -------------------------------------------------------------------------------- /src/lower1/operand/float.rs: -------------------------------------------------------------------------------- 1 | use num_bigint::BigUint; 2 | use num_traits::{One, ToPrimitive, Zero}; 3 | 4 | /// Convert an `f128` (binary128) to a decimal `String` with up to 34 significant digits, 5 | /// rounding to nearest, ties-to-even. 6 | /// 7 | /// Special‑cases: 8 | /// - `NaN` → `"NaN"` 9 | /// - `+∞` → `"inf"`, `-∞` → `"-inf"` 10 | /// - `0.0` → `"0.0"`, `-0.0` → `"-0.0"` 11 | pub fn f128_to_string(x: f128) -> String { 12 | // --- Special cases --- 13 | if x.is_nan() { 14 | return "NaN".to_string(); 15 | } 16 | if x.is_infinite() { 17 | return if x.is_sign_negative() { 18 | "-inf".into() 19 | } else { 20 | "inf".into() 21 | }; 22 | } 23 | if x == 0.0_f128 { 24 | // preserves sign of zero 25 | return if x.is_sign_negative() { 26 | "-0.0".into() 27 | } else { 28 | "0.0".into() 29 | }; 30 | } 31 | 32 | // --- Unpack IEEE‑754 bits --- 33 | const PREC: usize = 34; // max significant decimal digits for f128 34 | const EXP_BIAS: i32 = 16383; // binary128 exponent bias 35 | 36 | // Transmute to raw bits 37 | let bits: u128 = x.to_bits(); 38 | let sign_bit = (bits >> 127) != 0; 39 | let exp_bits = ((bits >> 112) & 0x7fff) as i32; 40 | let frac_bits = bits & ((1u128 << 112) - 1); 41 | 42 | // Build the integer mantissa and true exponent 43 | let (mantissa, exp2) = if exp_bits == 0 { 44 | // subnormal: exponent = 1-bias, no implicit leading 1 45 | (BigUint::from(frac_bits), 1 - EXP_BIAS - 112) 46 | } else { 47 | // normal: implicit leading 1 48 | ( 49 | BigUint::from(frac_bits) + (BigUint::one() << 112), 50 | exp_bits - EXP_BIAS - 112, 51 | ) 52 | }; 53 | 54 | // Scale into an integer numerator / denominator = mantissa * 2^exp2 55 | let mut num = mantissa.clone(); 56 | let mut den = BigUint::one(); 57 | if exp2 >= 0 { 58 | num <<= exp2 as usize; 59 | } else { 60 | den <<= (-exp2) as usize; 61 | } 62 | 63 | // --- Integer part + remainder --- 64 | let int_part = &num / &den; 65 | let rem = num % &den; 66 | 67 | // Convert the integer part to decimal 68 | let mut int_str = int_part.to_str_radix(10); 69 | 70 | // --- Fractional digits generation (PREC+1 for rounding) --- 71 | let mut frac_digits: Vec = Vec::new(); 72 | let mut rem2 = rem.clone(); 73 | for _ in 0..=PREC { 74 | if rem2.is_zero() { 75 | break; 76 | } 77 | rem2 *= 10u32; 78 | let d = (&rem2 / &den).to_u8().unwrap(); 79 | frac_digits.push(d); 80 | rem2 %= &den; 81 | } 82 | 83 | // --- Round to nearest, ties-to-even --- 84 | if frac_digits.len() > PREC { 85 | let next = frac_digits[PREC]; 86 | // any non‑zero bits beyond PREC+1 make it “> 5” 87 | let tie_or_above = next > 5 || (next == 5 && rem2 != BigUint::zero()); 88 | 89 | let mut round_up = false; 90 | if tie_or_above { 91 | if next > 5 { 92 | round_up = true; 93 | } else { 94 | // exactly 5: round to even last digit 95 | round_up = frac_digits[PREC - 1] % 2 == 1; 96 | } 97 | } 98 | frac_digits.truncate(PREC); 99 | 100 | if round_up { 101 | // propagate carry in the fractional digits 102 | let mut i = PREC - 1; 103 | loop { 104 | if frac_digits[i] == 9 { 105 | frac_digits[i] = 0; 106 | if i == 0 { 107 | // carry into integer part 108 | let mut big_int = BigUint::parse_bytes(int_str.as_bytes(), 10).unwrap(); 109 | big_int += BigUint::one(); 110 | int_str = big_int.to_str_radix(10); 111 | break; 112 | } 113 | i -= 1; 114 | } else { 115 | frac_digits[i] += 1; 116 | break; 117 | } 118 | } 119 | } 120 | } 121 | 122 | // Drop any trailing zeros in the fraction 123 | while frac_digits.last() == Some(&0) { 124 | frac_digits.pop(); 125 | } 126 | 127 | // --- Assemble final string --- 128 | let mut out = String::new(); 129 | if sign_bit { 130 | out.push('-'); 131 | } 132 | out.push_str(&int_str); 133 | 134 | if !frac_digits.is_empty() { 135 | out.push('.'); 136 | for &d in &frac_digits { 137 | out.push((b'0' + d) as char); 138 | } 139 | } 140 | 141 | out 142 | } 143 | -------------------------------------------------------------------------------- /src/lower2.rs: -------------------------------------------------------------------------------- 1 | // src/lower2/mod.rs 2 | 3 | //! This module converts OOMIR into JVM bytecode. 4 | 5 | use crate::oomir::{self, DataType}; 6 | use jvm_gen::{ 7 | create_data_type_classfile_for_class, create_data_type_classfile_for_interface, 8 | create_default_constructor, 9 | }; 10 | use translator::FunctionTranslator; 11 | 12 | use ristretto_classfile::{ 13 | self as jvm, ClassAccessFlags, ClassFile, ConstantPool, MethodAccessFlags, Version, 14 | attributes::{Attribute, MaxStack}, 15 | }; 16 | use rustc_middle::ty::TyCtxt; 17 | use std::collections::HashMap; 18 | 19 | mod consts; 20 | mod helpers; 21 | mod jvm_gen; 22 | mod shim; 23 | mod translator; 24 | 25 | pub const BIG_INTEGER_CLASS: &str = "java/math/BigInteger"; 26 | pub const BIG_DECIMAL_CLASS: &str = "java/math/BigDecimal"; 27 | 28 | /// Converts an OOMIR module into JVM class files 29 | /// Returns a HashMap where the key is the JVM class name (with '/') and the value is the bytecode 30 | pub fn oomir_to_jvm_bytecode( 31 | module: &oomir::Module, 32 | _tcx: TyCtxt, // Keep tcx in signature if needed later, but unused now 33 | ) -> jvm::Result>> { 34 | // Map to store the generated class files (Class Name -> Bytes) 35 | let mut generated_classes: HashMap> = HashMap::new(); 36 | 37 | // --- 1. Generate the Main Module Class (containing functions) --- 38 | { 39 | // Scope block for the main class generation 40 | let mut main_cp = ConstantPool::default(); 41 | // Convert module name to JVM internal format (replace '.' with '/') 42 | let main_class_name_jvm = module.name.replace('.', "/"); 43 | let super_class_name_jvm = "java/lang/Object"; // Standard superclass 44 | 45 | let super_class_index = main_cp.add_class(super_class_name_jvm)?; 46 | let this_class_index = main_cp.add_class(&main_class_name_jvm)?; 47 | let code_attribute_name_index = main_cp.add_utf8("Code")?; 48 | 49 | let mut methods: Vec = Vec::new(); 50 | let mut has_constructor = false; 51 | 52 | for function in module.functions.values() { 53 | // Don't create a default constructor if the OOMIR provided one 54 | if function.name == "" { 55 | has_constructor = true; 56 | } 57 | 58 | let name_index = main_cp.add_utf8(&function.name)?; 59 | let descriptor_index = main_cp.add_utf8(&function.signature.to_string())?; 60 | 61 | // Translate the function body using its own constant pool reference 62 | let translator = FunctionTranslator::new( 63 | function, 64 | &mut main_cp, // Use the main class's constant pool 65 | module, 66 | true, 67 | ); 68 | let (jvm_code, max_locals_val) = translator.translate()?; 69 | 70 | let max_stack_val = jvm_code.max_stack(&main_cp)?; 71 | 72 | let code_attribute = Attribute::Code { 73 | name_index: code_attribute_name_index, 74 | max_stack: max_stack_val, 75 | max_locals: max_locals_val, 76 | code: jvm_code, 77 | exception_table: Vec::new(), 78 | attributes: Vec::new(), 79 | }; 80 | 81 | let mut method = jvm::Method::default(); 82 | // Assume static for now, adjust if instance methods are needed 83 | method.access_flags = MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC; 84 | if function.name == "" { 85 | // Constructors cannot be static 86 | method.access_flags = MethodAccessFlags::PUBLIC; 87 | } 88 | method.name_index = name_index; 89 | method.descriptor_index = descriptor_index; 90 | method.attributes.push(code_attribute); 91 | 92 | methods.push(method); 93 | } 94 | 95 | // Add a default constructor if none was provided in OOMIR 96 | if !has_constructor && !module.functions.contains_key("") { 97 | methods.push(create_default_constructor(&mut main_cp, super_class_index)?); 98 | } 99 | 100 | let mut class_file = ClassFile { 101 | version: Version::Java8 { minor: 0 }, 102 | constant_pool: main_cp, // Move the main constant pool here 103 | access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::SUPER, 104 | this_class: this_class_index, 105 | super_class: super_class_index, 106 | interfaces: Vec::new(), 107 | fields: Vec::new(), // Main class might not have fields unless they are static globals 108 | methods, 109 | attributes: Vec::new(), 110 | }; 111 | 112 | // Add SourceFile attribute 113 | let source_file_name = format!( 114 | "{}.rs", 115 | module.name.split('.').last().unwrap_or(&module.name) 116 | ); // Simple name 117 | let source_file_utf8_index = class_file.constant_pool.add_utf8(&source_file_name)?; 118 | let source_file_attr_name_index = class_file.constant_pool.add_utf8("SourceFile")?; 119 | class_file.attributes.push(Attribute::SourceFile { 120 | name_index: source_file_attr_name_index, 121 | source_file_index: source_file_utf8_index, 122 | }); 123 | 124 | // Serialize the main class file 125 | let mut byte_vector = Vec::new(); 126 | class_file.to_bytes(&mut byte_vector)?; 127 | generated_classes.insert(main_class_name_jvm.clone(), byte_vector); 128 | 129 | println!("Generated main class: {}", main_class_name_jvm); 130 | } 131 | 132 | // --- 2. Generate Class Files for Data Types --- 133 | for (dt_name_oomir, data_type) in &module.data_types { 134 | println!("Generating data type class: {}", dt_name_oomir); 135 | 136 | let mut data_type = data_type.clone(); 137 | 138 | data_type.clean_duplicates(); 139 | 140 | match data_type { 141 | DataType::Class { 142 | is_abstract, 143 | super_class, 144 | fields, 145 | methods, 146 | interfaces, 147 | } => { 148 | // Create and serialize the class file for this data type 149 | let dt_bytecode = create_data_type_classfile_for_class( 150 | &dt_name_oomir, 151 | fields.clone(), 152 | is_abstract, 153 | methods.clone(), 154 | super_class.as_deref().unwrap_or("java/lang/Object"), 155 | interfaces.clone(), 156 | &module, 157 | )?; 158 | generated_classes.insert(dt_name_oomir.clone(), dt_bytecode); 159 | } 160 | DataType::Interface { methods } => { 161 | // Create and serialize the class file for this data type 162 | let dt_bytecode = 163 | create_data_type_classfile_for_interface(&dt_name_oomir, &methods)?; 164 | generated_classes.insert(dt_name_oomir.clone(), dt_bytecode); 165 | } 166 | } 167 | } 168 | 169 | Ok(generated_classes) // Return the map containing all generated classes 170 | } 171 | -------------------------------------------------------------------------------- /src/lower2/consts.rs: -------------------------------------------------------------------------------- 1 | use super::helpers::are_types_jvm_compatible; 2 | use crate::oomir::{self, Signature, Type}; 3 | use ristretto_classfile::{ 4 | self as jvm, ConstantPool, 5 | attributes::{ArrayType, Instruction}, 6 | }; 7 | 8 | // Helper to get the appropriate integer constant loading instruction 9 | pub fn get_int_const_instr(cp: &mut ConstantPool, val: i32) -> Instruction { 10 | match val { 11 | // Direct iconst mapping 12 | -1 => Instruction::Iconst_m1, 13 | 0 => Instruction::Iconst_0, 14 | 1 => Instruction::Iconst_1, 15 | 2 => Instruction::Iconst_2, 16 | 3 => Instruction::Iconst_3, 17 | 4 => Instruction::Iconst_4, 18 | 5 => Instruction::Iconst_5, 19 | 20 | // Bipush range (-128 to 127), excluding the iconst values already handled 21 | v @ -128..=-2 | v @ 6..=127 => Instruction::Bipush(v as i8), 22 | 23 | // Sipush range (-32768 to 32767), excluding the bipush range 24 | v @ -32768..=-129 | v @ 128..=32767 => Instruction::Sipush(v as i16), 25 | 26 | // Use LDC for values outside the -32768 to 32767 range 27 | v => { 28 | let index = cp 29 | .add_integer(v) 30 | .expect("Failed to add integer to constant pool"); 31 | if let Ok(idx8) = u8::try_from(index) { 32 | Instruction::Ldc(idx8) 33 | } else { 34 | Instruction::Ldc_w(index) 35 | } 36 | } 37 | } 38 | } 39 | 40 | // Helper to get the appropriate long constant loading instruction 41 | pub fn get_long_const_instr(cp: &mut ConstantPool, val: i64) -> Instruction { 42 | // <-- Add `cp: &mut ConstantPool` 43 | match val { 44 | 0 => Instruction::Lconst_0, 45 | 1 => Instruction::Lconst_1, 46 | _ => { 47 | // Add the long value to the constant pool. 48 | let index = cp 49 | .add_long(val) 50 | .expect("Failed to add long to constant pool"); 51 | // Ldc2_w is used for long/double constants and always takes a u16 index. 52 | Instruction::Ldc2_w(index) 53 | } 54 | } 55 | } 56 | 57 | // Helper to get the appropriate float constant loading instruction 58 | pub fn get_float_const_instr(cp: &mut ConstantPool, val: f32) -> Instruction { 59 | if val == 0.0 { 60 | Instruction::Fconst_0 61 | } else if val == 1.0 { 62 | Instruction::Fconst_1 63 | } else if val == 2.0 { 64 | Instruction::Fconst_2 65 | } else { 66 | // Add the float value to the constant pool. 67 | let index = cp 68 | .add_float(val) 69 | .expect("Failed to add float to constant pool"); 70 | // Ldc2_w is used for long/double constants and always takes a u16 index. 71 | Instruction::Ldc_w(index) 72 | } 73 | } 74 | 75 | // Helper to get the appropriate double constant loading instruction 76 | pub fn get_double_const_instr(cp: &mut ConstantPool, val: f64) -> Instruction { 77 | // Using bit representation for exact zero comparison is more robust 78 | if val.to_bits() == 0.0f64.to_bits() { 79 | // Handles +0.0 and -0.0 80 | Instruction::Dconst_0 81 | } else if val == 1.0 { 82 | Instruction::Dconst_1 83 | } else { 84 | // Add the double value to the constant pool. 85 | let index = cp 86 | .add_double(val) 87 | .expect("Failed to add double to constant pool"); 88 | // Ldc2_w is used for long/double constants and always takes a u16 index. 89 | Instruction::Ldc2_w(index) 90 | } 91 | } 92 | 93 | /// Appends JVM instructions for loading a constant onto the stack. 94 | pub fn load_constant( 95 | instructions: &mut Vec, 96 | cp: &mut ConstantPool, 97 | constant: &oomir::Constant, 98 | ) -> Result<(), jvm::Error> { 99 | use jvm::attributes::Instruction as JI; 100 | use oomir::Constant as OC; 101 | 102 | let mut instructions_to_add = Vec::new(); 103 | 104 | match constant { 105 | OC::I8(v) => instructions_to_add.push(get_int_const_instr(cp, *v as i32)), 106 | OC::I16(v) => instructions_to_add.push(get_int_const_instr(cp, *v as i32)), 107 | OC::I32(v) => instructions_to_add.push(get_int_const_instr(cp, *v)), 108 | OC::I64(v) => instructions_to_add.push(get_long_const_instr(cp, *v)), 109 | OC::F32(v) => instructions_to_add.push(get_float_const_instr(cp, *v)), 110 | OC::F64(v) => instructions_to_add.push(get_double_const_instr(cp, *v)), 111 | OC::Boolean(v) => instructions_to_add.push(if *v { JI::Iconst_1 } else { JI::Iconst_0 }), 112 | OC::Char(v) => instructions_to_add.push(get_int_const_instr(cp, *v as i32)), 113 | OC::String(s) => { 114 | let index = cp.add_string(s)?; 115 | instructions_to_add.push(if let Ok(idx8) = u8::try_from(index) { 116 | JI::Ldc(idx8) 117 | } else { 118 | JI::Ldc_w(index) 119 | }); 120 | } 121 | OC::Class(c) => { 122 | let index = cp.add_class(c)?; 123 | instructions_to_add.push(if let Ok(idx8) = u8::try_from(index) { 124 | JI::Ldc(idx8) 125 | } else { 126 | JI::Ldc_w(index) 127 | }); 128 | } 129 | OC::Array(elem_ty, elements) => { 130 | let array_len = elements.len(); 131 | 132 | // 1. Push array size onto stack 133 | instructions_to_add.push(get_int_const_instr(cp, array_len as i32)); 134 | 135 | // 2. Create the new array (primitive or reference) 136 | if let Some(atype_code) = elem_ty.to_jvm_primitive_array_type_code() { 137 | let array_type = ArrayType::from_bytes(&mut std::io::Cursor::new(vec![atype_code])) // Wrap atype_code in Cursor> 138 | .map_err(|e| jvm::Error::VerificationError { 139 | context: format!("Attempting to load constant {:?}", constant), // Use Display formatting for the error type if available 140 | message: format!( 141 | "Invalid primitive array type code {}: {:?}", 142 | atype_code, e 143 | ), 144 | })?; 145 | instructions_to_add.push(JI::Newarray(array_type)); // Stack: [arrayref] 146 | } else if let Some(internal_name) = elem_ty.to_jvm_internal_name() { 147 | let class_index = cp.add_class(&internal_name)?; 148 | instructions_to_add.push(JI::Anewarray(class_index)); // Stack: [arrayref] 149 | } else { 150 | return Err(jvm::Error::VerificationError { 151 | context: format!("Attempting to load constant {:?}", constant), 152 | message: format!("Cannot create JVM array for element type: {:?}", elem_ty), 153 | }); 154 | } 155 | 156 | let store_instruction = elem_ty.get_jvm_array_store_instruction().ok_or_else(|| { 157 | jvm::Error::VerificationError { 158 | context: format!("Attempting to load constant {:?}", constant), 159 | message: format!( 160 | "Cannot determine array store instruction for type: {:?}", 161 | elem_ty 162 | ), 163 | } 164 | })?; 165 | 166 | // 3. Populate the array 167 | for (i, element_const) in elements.iter().enumerate() { 168 | let constant_type = Type::from_constant(element_const); 169 | if &constant_type != elem_ty.as_ref() 170 | && !are_types_jvm_compatible(&constant_type, elem_ty) 171 | { 172 | return Err(jvm::Error::VerificationError { 173 | context: format!("Attempting to load constant {:?}", constant), 174 | message: format!( 175 | "Type mismatch in Constant::Array: expected {:?}, found {:?} for element {}", 176 | elem_ty, constant_type, i 177 | ), 178 | }); 179 | } 180 | 181 | instructions_to_add.push(JI::Dup); // Stack: [arrayref, arrayref] 182 | instructions_to_add.push(get_int_const_instr(cp, i as i32)); // Stack: [arrayref, arrayref, index] 183 | 184 | // --- Corrected Element Loading --- 185 | // 1. Record the length of the main instruction vector *before* the recursive call. 186 | let original_jvm_len = instructions.len(); 187 | 188 | // 2. Make the recursive call. This *will* append instructions to instructions. 189 | load_constant(instructions, cp, element_const)?; 190 | 191 | // 3. Determine the range of instructions added by the recursive call. 192 | let new_jvm_len = instructions.len(); 193 | 194 | // 4. If instructions were added, copy them from instructions to instructions_to_add. 195 | if new_jvm_len > original_jvm_len { 196 | // Create a slice referencing the newly added instructions 197 | let added_instructions_slice = &instructions[original_jvm_len..new_jvm_len]; 198 | // Extend the temporary vector with a clone of these instructions 199 | instructions_to_add.extend_from_slice(added_instructions_slice); 200 | } 201 | 202 | // 5. Remove the instructions just added by the recursive call from instructions. 203 | // We truncate back to the length it had *before* the recursive call. 204 | instructions.truncate(original_jvm_len); 205 | // Now, instructions is back to its state before loading the element, 206 | // and instructions_to_add contains the necessary Dup, index, element load instructions. 207 | 208 | // Add the array store instruction to the temporary vector 209 | instructions_to_add.push(store_instruction.clone()); // Stack: [arrayref] 210 | } 211 | // Final stack state after loop: [arrayref] (the populated array) 212 | } 213 | OC::Instance { 214 | class_name, 215 | fields, 216 | params, 217 | } => { 218 | // 1. Add Class reference to constant pool 219 | let class_index = cp.add_class(class_name)?; 220 | 221 | // 2. Determine constructor parameter types and signature descriptor 222 | let param_types = params 223 | .iter() 224 | .map(Type::from_constant) // Get oomir::Type from each oomir::Constant 225 | .collect::>(); 226 | 227 | let constructor_signature = Signature { 228 | ret: Box::new(Type::Void), // Constructors are void methods in bytecode 229 | params: param_types, 230 | }; 231 | // Assuming Signature::to_string() produces the correct JVM descriptor format, e.g., "(Ljava/lang/String;I)V" 232 | let constructor_descriptor = constructor_signature.to_string(); 233 | 234 | // 3. Add Method reference for the constructor "" with the determined signature 235 | let constructor_ref_index = cp.add_method_ref( 236 | class_index, 237 | "", // Standard name for constructors 238 | &constructor_descriptor, // Use the calculated descriptor 239 | )?; 240 | // 4. Generate instructions to create the object and set its fields 241 | 242 | // a. Emit 'new' instruction: Create uninitialized object 243 | instructions_to_add.push(JI::New(class_index)); // Stack: [uninitialized_ref] 244 | 245 | // b. Emit 'dup' instruction: Duplicate ref (one for invokespecial, one for result/fields) 246 | instructions_to_add.push(JI::Dup); // Stack: [uninitialized_ref, uninitialized_ref] 247 | 248 | // c. Load constructor parameters onto the stack IN ORDER 249 | for param_const in params { 250 | // Recursively load the constant value for the parameter. 251 | // Append the instructions directly to our temporary list. 252 | load_constant(&mut instructions_to_add, cp, param_const)?; 253 | // Stack: [uninitialized_ref, uninitialized_ref, param1, ..., param_i] 254 | } 255 | 256 | // d. Emit 'invokespecial' to call the constructor 257 | // Consumes the top ref and all params, initializes the object pointed to by the second ref. 258 | instructions_to_add.push(JI::Invokespecial(constructor_ref_index)); // Stack: [initialized_ref] 259 | 260 | // e. Iterate through fields to set them *after* construction 261 | for (field_name, field_value) in fields { 262 | // i. Duplicate the now *initialized* object reference (needed for putfield) 263 | instructions_to_add.push(JI::Dup); // Stack: [initialized_ref, initialized_ref] 264 | 265 | // ii. Load the field's value onto the stack 266 | load_constant(&mut instructions_to_add, cp, field_value)?; 267 | // Stack: [initialized_ref, initialized_ref, field_value] (size 1 or 2) 268 | 269 | // iii. Add Field reference 270 | let field_type = Type::from_constant(field_value); 271 | // Assuming Type::to_jvm_descriptor() produces the correct JVM type descriptor, e.g., "Ljava/lang/String;", "I" 272 | let field_descriptor = field_type.to_jvm_descriptor(); 273 | let field_ref_index = 274 | cp.add_field_ref(class_index, field_name, &field_descriptor)?; 275 | 276 | // iv. Emit 'putfield' 277 | instructions_to_add.push(JI::Putfield(field_ref_index)); 278 | // Stack: [initialized_ref] (putfield consumes the top ref and the value) 279 | } 280 | // After the loop, the final initialized_ref is left on the stack. 281 | } 282 | }; 283 | 284 | // Append the generated instructions for this constant (now including array logic) 285 | instructions.extend(instructions_to_add); 286 | 287 | Ok(()) 288 | } 289 | -------------------------------------------------------------------------------- /src/lower2/jvm_gen.rs: -------------------------------------------------------------------------------- 1 | // src/lower2/jvm_gen.rs 2 | 3 | use super::{FunctionTranslator, consts::load_constant}; 4 | use crate::oomir::{self, DataTypeMethod, Signature, Type}; 5 | 6 | use ristretto_classfile::{ 7 | self as jvm, BaseType, ClassAccessFlags, ClassFile, ConstantPool, FieldAccessFlags, 8 | MethodAccessFlags, Version, 9 | attributes::{Attribute, Instruction, MaxStack}, 10 | }; 11 | use std::collections::HashMap; 12 | 13 | /// Creates a default constructor `()V` that just calls `super()`. 14 | pub(super) fn create_default_constructor( 15 | // pub(super) or pub(crate) 16 | cp: &mut ConstantPool, 17 | super_class_index: u16, 18 | ) -> jvm::Result { 19 | let code_attr_name_index = cp.add_utf8("Code")?; 20 | let init_name_index = cp.add_utf8("")?; 21 | let init_desc_index = cp.add_utf8("()V")?; 22 | 23 | // Add reference to super.()V 24 | let super_init_ref_index = cp.add_method_ref(super_class_index, "", "()V")?; 25 | 26 | let instructions = vec![ 27 | Instruction::Aload_0, 28 | Instruction::Invokespecial(super_init_ref_index), 29 | Instruction::Return, 30 | ]; 31 | 32 | let max_stack = 1; 33 | let max_locals = 1; 34 | 35 | let code_attribute = Attribute::Code { 36 | name_index: code_attr_name_index, 37 | max_stack, 38 | max_locals, 39 | code: instructions, 40 | exception_table: Vec::new(), 41 | attributes: Vec::new(), 42 | }; 43 | 44 | Ok(jvm::Method { 45 | access_flags: MethodAccessFlags::PUBLIC, 46 | name_index: init_name_index, 47 | descriptor_index: init_desc_index, 48 | attributes: vec![code_attribute], 49 | }) 50 | } 51 | 52 | /// Converts an OOMIR Type to a Ristretto FieldType for class field definitions. 53 | pub(super) fn oomir_type_to_ristretto_field_type( 54 | // pub(super) or pub(crate) 55 | type2: &oomir::Type, 56 | ) -> jvm::FieldType { 57 | match type2 { 58 | oomir::Type::I8 => jvm::FieldType::Base(BaseType::Byte), 59 | oomir::Type::I16 => jvm::FieldType::Base(BaseType::Short), 60 | oomir::Type::I32 => jvm::FieldType::Base(BaseType::Int), 61 | oomir::Type::I64 => jvm::FieldType::Base(BaseType::Long), 62 | oomir::Type::F32 => jvm::FieldType::Base(BaseType::Float), 63 | oomir::Type::F64 => jvm::FieldType::Base(BaseType::Double), 64 | oomir::Type::Boolean => jvm::FieldType::Base(BaseType::Boolean), 65 | oomir::Type::Char => jvm::FieldType::Base(BaseType::Char), 66 | oomir::Type::String => jvm::FieldType::Object("java/lang/String".to_string()), 67 | oomir::Type::Reference(ref2) => { 68 | let inner_ty = ref2.as_ref(); 69 | oomir_type_to_ristretto_field_type(inner_ty) 70 | } 71 | oomir::Type::Array(inner_ty) | oomir::Type::MutableReference(inner_ty) => { 72 | let inner_field_type = oomir_type_to_ristretto_field_type(inner_ty); 73 | jvm::FieldType::Array(Box::new(inner_field_type)) 74 | } 75 | oomir::Type::Class(name) | oomir::Type::Interface(name) => { 76 | jvm::FieldType::Object(name.clone()) 77 | } 78 | oomir::Type::Void => { 79 | panic!("Void type cannot be used as a field type"); 80 | } 81 | } 82 | } 83 | 84 | /// Creates a ClassFile (as bytes) for a given OOMIR DataType that's a class 85 | pub(super) fn create_data_type_classfile_for_class( 86 | // pub(super) or pub(crate) 87 | class_name_jvm: &str, 88 | fields: Vec<(String, Type)>, 89 | is_abstract: bool, 90 | methods: HashMap, 91 | super_class_name_jvm: &str, 92 | implements_interfaces: Vec, 93 | module: &oomir::Module, 94 | ) -> jvm::Result> { 95 | let mut cp = ConstantPool::default(); 96 | 97 | let this_class_index = cp.add_class(class_name_jvm)?; 98 | 99 | let super_class_index = cp.add_class(super_class_name_jvm)?; 100 | 101 | // --- Process Implemented Interfaces --- 102 | let mut interface_indices: Vec = Vec::with_capacity(implements_interfaces.len()); 103 | for interface_name in &implements_interfaces { 104 | // Add the interface name to the constant pool as a Class reference 105 | let interface_index = cp.add_class(interface_name)?; 106 | interface_indices.push(interface_index); 107 | } 108 | 109 | // --- Create Fields --- 110 | let mut jvm_fields: Vec = Vec::new(); 111 | for (field_name, field_ty) in &fields { 112 | let name_index = cp.add_utf8(field_name)?; 113 | let descriptor = field_ty.to_jvm_descriptor(); // Ensure this method exists on oomir::Type 114 | let descriptor_index = cp.add_utf8(&descriptor)?; 115 | 116 | let field = jvm::Field { 117 | access_flags: FieldAccessFlags::PUBLIC, 118 | name_index, 119 | descriptor_index, 120 | field_type: oomir_type_to_ristretto_field_type(field_ty), // Use helper 121 | attributes: Vec::new(), 122 | }; 123 | jvm_fields.push(field); 124 | println!(" - Added field: {} {}", field_name, descriptor); 125 | } 126 | 127 | // --- Create Default Constructor --- 128 | let constructor = create_default_constructor(&mut cp, super_class_index)?; 129 | let jvm_methods = vec![constructor]; 130 | 131 | // --- Assemble ClassFile --- 132 | let mut class_file = ClassFile { 133 | version: Version::Java8 { minor: 0 }, 134 | constant_pool: cp, 135 | access_flags: ClassAccessFlags::PUBLIC 136 | | ClassAccessFlags::SUPER 137 | | if is_abstract { 138 | ClassAccessFlags::ABSTRACT 139 | } else { 140 | ClassAccessFlags::FINAL 141 | }, 142 | this_class: this_class_index, 143 | super_class: super_class_index, 144 | interfaces: interface_indices, 145 | fields: jvm_fields, 146 | methods: jvm_methods, 147 | attributes: Vec::new(), 148 | }; 149 | 150 | // Check for jvm_methods 151 | for (method_name, method) in methods.iter() { 152 | match method { 153 | DataTypeMethod::SimpleConstantReturn(return_type, return_const) => { 154 | let method_desc = format!("(){}", return_type.to_jvm_descriptor()); 155 | 156 | // Add the method to the class file 157 | let name_index = class_file.constant_pool.add_utf8(&method_name)?; 158 | let descriptor_index: u16 = class_file.constant_pool.add_utf8(method_desc)?; 159 | 160 | let mut attributes = vec![]; 161 | let mut is_abstract = false; 162 | 163 | match return_const { 164 | Some(rc) => attributes.push(create_code_from_method_name_and_constant_return( 165 | &rc, 166 | &mut class_file.constant_pool, 167 | )?), 168 | None => { 169 | is_abstract = true; 170 | } 171 | } 172 | 173 | let jvm_method = jvm::Method { 174 | access_flags: MethodAccessFlags::PUBLIC 175 | | if is_abstract { 176 | MethodAccessFlags::ABSTRACT 177 | } else { 178 | MethodAccessFlags::FINAL 179 | }, 180 | name_index, 181 | descriptor_index, 182 | attributes, 183 | }; 184 | 185 | class_file.methods.push(jvm_method); 186 | } 187 | DataTypeMethod::Function(function) => { 188 | // Translate the function body using its own constant pool reference 189 | let translator = 190 | FunctionTranslator::new(function, &mut class_file.constant_pool, module, false); 191 | let (jvm_code, max_locals_val) = translator.translate()?; 192 | 193 | let max_stack_val = jvm_code.max_stack(&class_file.constant_pool)?; 194 | 195 | let code_attribute = Attribute::Code { 196 | name_index: class_file.constant_pool.add_utf8("Code")?, 197 | max_stack: max_stack_val, 198 | max_locals: max_locals_val, 199 | code: jvm_code, 200 | exception_table: Vec::new(), 201 | attributes: Vec::new(), 202 | }; 203 | 204 | let name_index = class_file.constant_pool.add_utf8(method_name)?; 205 | let descriptor_index = class_file 206 | .constant_pool 207 | .add_utf8(&function.signature.to_string())?; 208 | 209 | let jvm_method = jvm::Method { 210 | access_flags: MethodAccessFlags::PUBLIC, 211 | name_index, 212 | descriptor_index, 213 | attributes: vec![code_attribute], 214 | }; 215 | 216 | class_file.methods.push(jvm_method); 217 | } 218 | } 219 | } 220 | 221 | // --- Add SourceFile Attribute --- 222 | let simple_name = class_name_jvm.split('/').last().unwrap_or(class_name_jvm); 223 | let source_file_name = format!("{}.rs", simple_name); 224 | let source_file_utf8_index = class_file.constant_pool.add_utf8(&source_file_name)?; 225 | let source_file_attr_name_index = class_file.constant_pool.add_utf8("SourceFile")?; 226 | class_file.attributes.push(Attribute::SourceFile { 227 | name_index: source_file_attr_name_index, 228 | source_file_index: source_file_utf8_index, 229 | }); 230 | 231 | // --- Serialize --- 232 | let mut byte_vector = Vec::new(); 233 | class_file.to_bytes(&mut byte_vector)?; 234 | 235 | Ok(byte_vector) 236 | } 237 | 238 | /// Creates a ClassFile (as bytes) for a given OOMIR DataType that's an interface 239 | pub(super) fn create_data_type_classfile_for_interface( 240 | interface_name_jvm: &str, // Renamed for clarity 241 | methods: &HashMap, 242 | ) -> jvm::Result> { 243 | let mut cp = ConstantPool::default(); 244 | 245 | let this_class_index = cp.add_class(interface_name_jvm)?; 246 | 247 | // Interfaces always implicitly extend Object, and must specify it in the classfile 248 | let super_class_index = cp.add_class("java/lang/Object")?; 249 | 250 | // --- Create Abstract Methods --- 251 | let mut jvm_methods: Vec = Vec::new(); 252 | for (method_name, signature) in methods { 253 | // Construct the descriptor: (param1_desc param2_desc ...)return_desc 254 | let mut descriptor = String::from("("); 255 | for param_type in &signature.params { 256 | descriptor.push_str(¶m_type.to_jvm_descriptor()); 257 | } 258 | descriptor.push(')'); 259 | descriptor.push_str(&signature.ret.to_jvm_descriptor()); 260 | 261 | let name_index = cp.add_utf8(method_name)?; 262 | let descriptor_index = cp.add_utf8(&descriptor)?; 263 | 264 | // Interface methods are implicitly public and abstract (unless 'default' or 'static') 265 | // We assume these are the standard abstract interface methods. 266 | let jvm_method = jvm::Method { 267 | access_flags: MethodAccessFlags::PUBLIC | MethodAccessFlags::ABSTRACT, 268 | name_index, 269 | descriptor_index, 270 | attributes: Vec::new(), // Abstract methods have no Code attribute 271 | }; 272 | jvm_methods.push(jvm_method); 273 | // Consider using tracing or logging 274 | // println!(" - Added interface method: {} {}", method_name, descriptor); 275 | } 276 | 277 | // --- Assemble ClassFile --- 278 | let mut class_file = ClassFile { 279 | version: Version::Java8 { minor: 0 }, // Or higher if using default/static methods 280 | constant_pool: cp, 281 | access_flags: ClassAccessFlags::PUBLIC 282 | | ClassAccessFlags::INTERFACE 283 | | ClassAccessFlags::ABSTRACT, 284 | // Note: ACC_SUPER is generally not set for interfaces, though JVM might tolerate it. 285 | this_class: this_class_index, 286 | super_class: super_class_index, // Must be java/lang/Object 287 | interfaces: Vec::new(), // Interfaces implemented by *this* interface (if any) 288 | fields: Vec::new(), // Interfaces can have static final fields, but not requested here 289 | methods: jvm_methods, // Only the abstract methods defined above 290 | attributes: Vec::new(), // SourceFile added below 291 | }; 292 | 293 | // --- Add SourceFile Attribute --- 294 | let simple_name = interface_name_jvm 295 | .split('/') 296 | .last() 297 | .unwrap_or(interface_name_jvm); 298 | let source_file_name = format!("{}.rs", simple_name); // Or .java 299 | let source_file_utf8_index = class_file.constant_pool.add_utf8(&source_file_name)?; 300 | let source_file_attr_name_index = class_file.constant_pool.add_utf8("SourceFile")?; 301 | class_file.attributes.push(Attribute::SourceFile { 302 | name_index: source_file_attr_name_index, 303 | source_file_index: source_file_utf8_index, 304 | }); 305 | 306 | // --- Serialize --- 307 | let mut byte_vector = Vec::new(); 308 | class_file.to_bytes(&mut byte_vector)?; 309 | 310 | Ok(byte_vector) 311 | } 312 | 313 | /// Creates a code attribute for a method that returns a constant value. 314 | fn create_code_from_method_name_and_constant_return( 315 | return_const: &oomir::Constant, 316 | cp: &mut ConstantPool, 317 | ) -> jvm::Result { 318 | let code_attr_name_index = cp.add_utf8("Code")?; 319 | let return_ty = Type::from_constant(return_const); 320 | 321 | // Create the instructions based on the constant type 322 | let mut instructions = Vec::new(); 323 | 324 | load_constant(&mut instructions, cp, return_const)?; 325 | 326 | // add an instruction to return the value that we just loaded onto the stack 327 | let return_instr = match return_ty { 328 | oomir::Type::I8 329 | | oomir::Type::I16 330 | | oomir::Type::I32 331 | | oomir::Type::Boolean 332 | | oomir::Type::Char => Instruction::Ireturn, 333 | oomir::Type::I64 => Instruction::Lreturn, 334 | oomir::Type::F32 => Instruction::Freturn, 335 | oomir::Type::F64 => Instruction::Dreturn, 336 | oomir::Type::Reference(_) 337 | | oomir::Type::MutableReference(_) 338 | | oomir::Type::Array(_) 339 | | oomir::Type::String 340 | | oomir::Type::Class(_) 341 | | oomir::Type::Interface(_) => Instruction::Areturn, 342 | oomir::Type::Void => Instruction::Return, // Should not happen with Some(op) 343 | }; 344 | 345 | instructions.push(return_instr); 346 | 347 | let max_stack = 1; 348 | let max_locals = 1; 349 | 350 | let code_attribute = Attribute::Code { 351 | name_index: code_attr_name_index, 352 | max_stack, 353 | max_locals, 354 | code: instructions, 355 | exception_table: Vec::new(), 356 | attributes: Vec::new(), 357 | }; 358 | 359 | Ok(code_attribute) 360 | } 361 | -------------------------------------------------------------------------------- /src/lower2/shim.rs: -------------------------------------------------------------------------------- 1 | // src/lower2/shim.rs 2 | 3 | use serde::Deserialize; 4 | use std::{collections::HashMap, str, sync::OnceLock}; 5 | 6 | // --- Standard Library Shim Metadata Loader --- 7 | 8 | #[derive(Deserialize, Debug, Clone)] 9 | pub(super) struct ShimInfo { 10 | // pub(super) or pub(crate) 11 | pub(super) descriptor: String, 12 | pub(super) is_static: bool, 13 | } 14 | 15 | // Key: Simplified function name (output of make_jvm_safe) 16 | pub(super) type ShimMap = HashMap; 17 | 18 | // --- Lazy Static Loader for Shims (Reads JSON File) --- 19 | 20 | static SHIM_METADATA: OnceLock> = OnceLock::new(); 21 | 22 | pub(super) fn get_shim_metadata() -> Result<&'static ShimMap, &'static str> { 23 | SHIM_METADATA 24 | .get_or_init(|| { 25 | const JSON_BYTES: &[u8] = include_bytes!("../../shim-metadata-gen/core.json"); // Adjust path relative to THIS file 26 | 27 | let json_str = str::from_utf8(JSON_BYTES) 28 | .map_err(|e| format!("Failed to decode embedded JSON bytes as UTF-8: {}", e))?; 29 | 30 | serde_json::from_str(json_str) 31 | .map_err(|e| format!("Failed to parse embedded JSON string: {}", e)) 32 | }) 33 | .as_ref() 34 | .map_err(|e| e.as_str()) 35 | } 36 | -------------------------------------------------------------------------------- /src/optimise1.rs: -------------------------------------------------------------------------------- 1 | use crate::{lower1::operand::extract_number_from_operand, oomir::*}; 2 | use std::collections::{HashMap, HashSet, VecDeque}; 3 | 4 | mod dataflow; 5 | mod reachability; 6 | mod reorganisation; 7 | 8 | use dataflow::{analyze_constant_propagation, process_block_instructions}; 9 | use reachability::{find_reachable_blocks, get_instruction_successors}; 10 | use reorganisation::{ 11 | convert_labels_to_basic_blocks_in_function, eliminate_duplicate_basic_blocks, 12 | }; 13 | 14 | // --- Data Structures --- 15 | 16 | #[derive(Debug, Clone)] 17 | struct BasicBlockInfo { 18 | original_block: BasicBlock, 19 | predecessors: HashSet, 20 | successors: HashSet, 21 | } 22 | 23 | type ConstantMap = HashMap; 24 | 25 | type DataflowResult = HashMap; 26 | 27 | // --- CFG Construction --- 28 | 29 | fn build_cfg(code_block: &CodeBlock) -> HashMap { 30 | let mut cfg: HashMap = code_block 31 | .basic_blocks 32 | .iter() 33 | .map(|(label, block)| { 34 | ( 35 | label.clone(), 36 | BasicBlockInfo { 37 | original_block: block.clone(), 38 | predecessors: HashSet::new(), 39 | successors: HashSet::new(), 40 | }, 41 | ) 42 | }) 43 | .collect(); 44 | 45 | if cfg.is_empty() { 46 | return cfg; 47 | } 48 | 49 | // --- Determine Successors --- 50 | let mut all_successors: HashMap> = HashMap::new(); 51 | let cfg_keys: HashSet = cfg.keys().cloned().collect(); 52 | 53 | for (label, info) in &cfg { 54 | if let Some(terminator) = info.original_block.instructions.last() { 55 | let successors = get_instruction_successors(terminator); 56 | let valid_successors: Vec = successors 57 | .into_iter() 58 | .filter(|succ_label| { 59 | if cfg_keys.contains(succ_label) { 60 | true 61 | } else { 62 | eprintln!( 63 | "Warning: Block '{}' refers to non-existent successor '{}'", 64 | label, succ_label 65 | ); 66 | false 67 | } 68 | }) 69 | .collect(); 70 | all_successors.insert(label.clone(), valid_successors); 71 | } else { 72 | eprintln!("Warning: Block '{}' has no instructions.", label); 73 | all_successors.insert(label.clone(), vec![]); 74 | } 75 | } 76 | 77 | // --- Populate Successors and Predecessors --- 78 | for (label, successors) in &all_successors { 79 | if let Some(info) = cfg.get_mut(label) { 80 | info.successors.extend(successors.iter().cloned()); 81 | } 82 | for successor_label in successors { 83 | if let Some(successor_info) = cfg.get_mut(successor_label) { 84 | successor_info.predecessors.insert(label.clone()); 85 | } 86 | } 87 | } 88 | 89 | cfg 90 | } 91 | 92 | // --- Transformation Phase --- 93 | 94 | fn transform_function( 95 | function: &mut Function, 96 | cfg: &HashMap, 97 | analysis_result: &DataflowResult, 98 | data_types: &HashMap, 99 | ) { 100 | let mut optimized_blocks_intermediate: HashMap = HashMap::new(); 101 | let mut optimized_successors: HashMap> = HashMap::new(); 102 | // Populate all labels from the original CFG before the loop 103 | let all_original_labels: HashSet = cfg.keys().cloned().collect(); 104 | 105 | // --- Pass 1: Optimize instructions and determine new successors --- 106 | for (label, info) in cfg { 107 | // Iterate using original CFG structure 108 | let block_entry_state = analysis_result 109 | .get(label) 110 | .expect("Analysis result missing for block"); 111 | 112 | let (_, transformed_instructions) = 113 | process_block_instructions(info, block_entry_state, true, data_types); 114 | 115 | let optimized_block = BasicBlock { 116 | label: label.clone(), 117 | instructions: transformed_instructions, 118 | }; 119 | // Store the potentially optimized block using its original label 120 | optimized_blocks_intermediate.insert(label.clone(), optimized_block); 121 | 122 | let mut current_successors = HashSet::new(); 123 | // Get the block we just inserted to find its *new* terminator 124 | if let Some(opt_block) = optimized_blocks_intermediate.get(label) { 125 | if let Some(terminator) = opt_block.instructions.last() { 126 | let succ_labels = get_instruction_successors(terminator); 127 | // Filter successors against the set of original labels. 128 | // This ensures edges are kept even if the target block hasn't 129 | // been visited in this loop iteration yet. 130 | current_successors.extend( 131 | succ_labels 132 | .into_iter() 133 | .filter(|s| all_original_labels.contains(s)), 134 | ); 135 | } 136 | } else { 137 | // This case should likely not happen if we just inserted it 138 | eprintln!( 139 | "Internal Warning: optimized block {} not found immediately after insertion.", 140 | label 141 | ); 142 | } 143 | optimized_successors.insert(label.clone(), current_successors); 144 | } 145 | 146 | // --- Pass 2: Find reachable blocks based on optimized structure --- 147 | let reachable_labels = find_reachable_blocks( 148 | &function.body.entry, 149 | &optimized_successors, 150 | &all_original_labels, 151 | ); 152 | 153 | // --- Pass 3: Build the final function body with only reachable blocks --- 154 | // (Keep the previous fix - don't remove empty reachable blocks) 155 | let mut final_basic_blocks = HashMap::new(); 156 | for label in &reachable_labels { 157 | // Get the block from the intermediate results using the reachable label 158 | if let Some(block) = optimized_blocks_intermediate.get(label) { 159 | // Add the reachable block (including potentially empty ones) 160 | final_basic_blocks.insert(label.clone(), block.clone()); 161 | } else { 162 | // This suggests reachable_labels contains a label not in intermediate map, 163 | // which would be an internal error (shouldn't happen if all_original_labels was used correctly). 164 | eprintln!( 165 | "Internal Error: Reachable label '{}' not found in intermediate blocks.", 166 | label 167 | ); 168 | } 169 | } 170 | 171 | // --- Entry Point Handling & Cleanup --- 172 | // Check reachability against the original cfg's keyset size or existence check 173 | if !reachable_labels.contains(&function.body.entry) && !cfg.is_empty() { 174 | eprintln!( 175 | "Warning: Original entry block '{}' became unreachable in function '{}'.", 176 | function.body.entry, function.name 177 | ); 178 | if final_basic_blocks.is_empty() { 179 | println!( 180 | "Function '{}' appears fully optimized away or is empty.", 181 | function.name 182 | ); 183 | function.body.basic_blocks.clear(); 184 | } else { 185 | eprintln!( 186 | "ERROR: Function '{}' has reachable blocks but the original entry '{}' is not reachable. The resulting IR may be invalid.", 187 | function.name, function.body.entry 188 | ); 189 | // Attempt to recover by picking a new entry point (arbitrarily) 190 | if let Some(new_entry_label) = final_basic_blocks.keys().next() { 191 | eprintln!("Attempting to set new entry point to '{}'", new_entry_label); 192 | function.body.entry = new_entry_label.clone(); 193 | } else { 194 | eprintln!( 195 | "CRITICAL ERROR: final_basic_blocks is not empty but has no keys after entry removal." 196 | ); 197 | // Maybe clear blocks if we can't even find a new entry? 198 | function.body.basic_blocks.clear(); 199 | } 200 | } 201 | // Handle case where original entry existed but function optimized to empty 202 | } else if final_basic_blocks.is_empty() && cfg.contains_key(&function.body.entry) { 203 | println!("Function '{}' optimized to be empty.", function.name); 204 | function.body.basic_blocks.clear(); 205 | } 206 | 207 | function.body.basic_blocks = final_basic_blocks; 208 | } 209 | 210 | // --- Main Optimization Entry Points --- 211 | 212 | pub fn optimise_function( 213 | mut function: Function, 214 | data_types: &HashMap, 215 | ) -> Function { 216 | if function.body.basic_blocks.is_empty() { 217 | println!( 218 | "Skipping optimization for empty function: {}", 219 | function.name 220 | ); 221 | return function; 222 | } 223 | println!("Optimizing function: {}", function.name); 224 | 225 | // 0. Run needed reorganisation passes 226 | convert_labels_to_basic_blocks_in_function(&mut function); 227 | eliminate_duplicate_basic_blocks(&mut function); 228 | 229 | // 1. Build Initial CFG 230 | let cfg = build_cfg(&function.body); 231 | if cfg.is_empty() && !function.body.basic_blocks.is_empty() { 232 | eprintln!( 233 | "Warning: CFG construction failed for non-empty function {}", 234 | function.name 235 | ); 236 | return function; // Avoid panic if CFG fails 237 | } 238 | 239 | // 2. Perform Dataflow Analysis (Constant Propagation) 240 | // Ensure entry point exists in CFG before analysis 241 | if !cfg.contains_key(&function.body.entry) && !cfg.is_empty() { 242 | eprintln!( 243 | "ERROR: Entry block '{}' not found in CFG for function {}. Skipping optimization.", 244 | function.body.entry, function.name 245 | ); 246 | // This might happen if the entry block itself has no instructions or references invalid blocks. 247 | return function; 248 | } 249 | let analysis_result = analyze_constant_propagation(&function.body.entry, &cfg); 250 | 251 | // 3. Transform & Perform Dead Code Elimination 252 | transform_function(&mut function, &cfg, &analysis_result, data_types); 253 | 254 | // 4. Eliminate duplicate basic blocks (re-pass-through after transformation) 255 | eliminate_duplicate_basic_blocks(&mut function); 256 | 257 | // TODO: Further optimization passes? (Copy propagation, dead store elimination, etc.) 258 | 259 | println!("Finished optimizing function: {}", function.name); 260 | function 261 | } 262 | 263 | pub fn optimise_module(module: Module) -> Module { 264 | let old_funcs = module.functions; 265 | let mut new_funcs = HashMap::new(); 266 | println!("Optimizing module: {}", module.name); 267 | for (name, func) in old_funcs { 268 | println!("Optimizing function: {}", name); 269 | // Pass data_types needed for analysis/transformation 270 | let new_func = optimise_function(func, &module.data_types); 271 | new_funcs.insert(name, new_func); 272 | } 273 | println!("Optimization complete for module: {}", module.name); 274 | Module { 275 | name: module.name, 276 | functions: new_funcs, 277 | data_types: module.data_types, // Assume data_types are read-only for opts 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /src/optimise1/reachability.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | // --- Reachability Analysis for DCE --- 4 | 5 | // Helper to get potential successors from a single instruction 6 | pub fn get_instruction_successors(instruction: &Instruction) -> Vec { 7 | match instruction { 8 | Instruction::Jump { target } => vec![target.clone()], 9 | Instruction::Branch { 10 | true_block, 11 | false_block, 12 | .. 13 | } => vec![true_block.clone(), false_block.clone()], 14 | Instruction::Switch { 15 | targets, otherwise, .. 16 | } => { 17 | let mut succs: Vec = targets.iter().map(|(_, target)| target.clone()).collect(); 18 | succs.push(otherwise.clone()); 19 | succs 20 | } 21 | Instruction::Return { .. } => vec![], // No successors within the function 22 | Instruction::ThrowNewWithMessage { .. } => vec![], // No successors within the function (usually) 23 | // Other instructions are not terminators 24 | _ => vec![], 25 | } 26 | } 27 | 28 | pub fn find_reachable_blocks( 29 | entry_label: &str, 30 | // Provides the successor edges for *all* potential blocks after optimization 31 | successors_map: &HashMap>, 32 | // Needed to know which block labels *exist* in the optimized set 33 | all_block_labels: &HashSet, 34 | ) -> HashSet { 35 | let mut reachable: HashSet = HashSet::new(); 36 | let mut worklist: VecDeque = VecDeque::new(); 37 | 38 | if all_block_labels.contains(entry_label) { 39 | reachable.insert(entry_label.to_string()); 40 | worklist.push_back(entry_label.to_string()); 41 | } else if !all_block_labels.is_empty() { 42 | println!( 43 | "Warning: Entry block '{}' not found in optimized block set.", 44 | entry_label 45 | ); 46 | } 47 | 48 | while let Some(current_label) = worklist.pop_front() { 49 | // Get successors from the provided map, defaulting to empty if not found 50 | if let Some(successors) = successors_map.get(¤t_label) { 51 | for successor_label in successors { 52 | // Ensure the successor actually exists in the set of blocks we generated 53 | if all_block_labels.contains(successor_label) 54 | && reachable.insert(successor_label.clone()) 55 | { 56 | // If the insert was successful (i.e., it wasn't already reachable) 57 | worklist.push_back(successor_label.clone()); 58 | } 59 | } 60 | } 61 | // If a block isn't in successors_map, it has no successors (e.g., ends in Return) 62 | } 63 | 64 | reachable 65 | } 66 | -------------------------------------------------------------------------------- /src/optimise1/reorganisation.rs: -------------------------------------------------------------------------------- 1 | // Needed re-organisation of the instructions prior to optimisation, to make it easier 2 | 3 | use super::*; 4 | 5 | /// Checks if an OOMIR instruction terminates a basic block. 6 | fn is_terminator(instruction: Option<&Instruction>) -> bool { 7 | match instruction { 8 | Some(Instruction::Jump { .. }) 9 | | Some(Instruction::Branch { .. }) 10 | | Some(Instruction::Return { .. }) 11 | | Some(Instruction::ThrowNewWithMessage { .. }) 12 | | Some(Instruction::Switch { .. }) => true, 13 | _ => false, 14 | } 15 | } 16 | 17 | /// Modifies a Function's CodeBlock in place to eliminate `Instruction::Label` 18 | /// within basic blocks by splitting blocks and adding explicit jumps. 19 | pub fn convert_labels_to_basic_blocks_in_function(function: &mut Function) { 20 | let mut needs_another_pass = true; 21 | 22 | while needs_another_pass { 23 | needs_another_pass = false; 24 | let mut new_blocks_this_pass: HashMap = HashMap::new(); 25 | // Collect keys first to iterate over, allowing mutation of the map's values 26 | let block_names: HashSet = function.body.basic_blocks.keys().cloned().collect(); 27 | 28 | for block_name in block_names.clone() { 29 | // Re-fetch the block mutably in each iteration of the outer loop, 30 | // as `new_blocks_this_pass` might have been merged in the previous pass. 31 | // Check if the block still exists (it might have been replaced if split started at index 0) 32 | if let Some(block) = function.body.basic_blocks.get_mut(&block_name) { 33 | let original_instructions = std::mem::take(&mut block.instructions); // Temporarily take ownership 34 | let mut current_instructions: Vec = Vec::new(); 35 | let mut current_block_label = block.label.clone(); // Label of the block segment we are currently building 36 | 37 | for (_, instruction) in original_instructions.into_iter().enumerate() { 38 | match instruction { 39 | Instruction::Label { 40 | name: next_label_name, 41 | } => { 42 | // --- Found a label: Finalize the *current* segment --- 43 | needs_another_pass = true; // Signal that we changed something 44 | 45 | // 1. Add jump if the last instruction wasn't a terminator 46 | if !is_terminator(current_instructions.last()) { 47 | current_instructions.push(Instruction::Jump { 48 | target: next_label_name.clone(), 49 | }); 50 | } 51 | 52 | // 2. Update the *current* block (or the first segment) 53 | // If i == 0, the original block becomes empty and should be removed later? 54 | // No, we update the block associated with current_block_label. 55 | if block.label == current_block_label { 56 | // This is the first segment, update the original block 57 | block.instructions = current_instructions; 58 | } else { 59 | // This is a subsequent segment, create/update in new_blocks 60 | let segment_block = BasicBlock { 61 | label: current_block_label.clone(), 62 | instructions: current_instructions, 63 | }; 64 | if let Some(existing) = new_blocks_this_pass 65 | .insert(current_block_label.clone(), segment_block) 66 | { 67 | // This case should ideally not happen if labels are unique and splitting logic is correct 68 | eprintln!( 69 | "Warning: Overwriting newly created block segment '{}'", 70 | existing.label 71 | ); 72 | } 73 | } 74 | 75 | // 3. Prepare for the *next* segment starting with the label 76 | current_instructions = Vec::new(); // Start fresh instructions 77 | current_block_label = next_label_name.clone(); // The new segment gets the label's name 78 | 79 | // 4. Check for conflicts *before* adding the new block placeholder 80 | if block_names.contains(¤t_block_label) 81 | || new_blocks_this_pass.contains_key(¤t_block_label) 82 | { 83 | panic!( 84 | "Label conflict: Label '{}' found inside block '{}' already exists as a block name.", 85 | current_block_label, block_name 86 | ); 87 | } 88 | // Add a placeholder so future conflicts are detected immediately 89 | new_blocks_this_pass.insert( 90 | current_block_label.clone(), 91 | BasicBlock { 92 | label: current_block_label.clone(), 93 | instructions: vec![], 94 | }, 95 | ); 96 | 97 | // Label instruction is consumed, don't add it to any block's list 98 | } 99 | _ => { 100 | // Regular instruction, add it to the current segment 101 | current_instructions.push(instruction); 102 | } 103 | } 104 | } // End loop through instructions 105 | 106 | // After iterating through all instructions of the original block: 107 | // Put the remaining instructions into the correct block 108 | if block.label == current_block_label { 109 | // The last segment belongs to the original block 110 | block.instructions = current_instructions; 111 | } else { 112 | // The last segment is a new block (or updates a placeholder) 113 | let final_segment_block = BasicBlock { 114 | label: current_block_label.clone(), 115 | instructions: current_instructions, 116 | }; 117 | new_blocks_this_pass.insert(current_block_label.clone(), final_segment_block); 118 | } 119 | 120 | // If we split, we break from processing more blocks in this *inner* loop 121 | // and restart the pass, because the block_names list might be outdated. 122 | // However, processing all blocks listed in `block_names` initially is safer 123 | // to avoid issues with modifying the map while iterating indirectly. 124 | // The `while needs_another_pass` loop handles the reprocessing. 125 | } // end if let Some(block) 126 | } // End loop through block_names 127 | 128 | // Merge the newly created blocks from this pass into the main map 129 | function.body.basic_blocks.extend(new_blocks_this_pass); 130 | } // End while needs_another_pass 131 | } 132 | 133 | pub fn eliminate_duplicate_basic_blocks(func: &mut Function) { 134 | if func.body.basic_blocks.len() <= 1 { 135 | // Nothing to deduplicate if there's 0 or 1 block. 136 | return; 137 | } 138 | 139 | // 1. Group blocks by their instruction sequences. 140 | // Key: The vector of instructions (cloned). 141 | // Value: A vector of labels of blocks having this exact instruction sequence. 142 | let mut instruction_groups: HashMap, Vec> = HashMap::new(); 143 | for (label, block) in &func.body.basic_blocks { 144 | // Clone instructions to use as a key. Consider hashing if performance is critical 145 | // and instruction vectors are very large, but direct comparison is safer. 146 | instruction_groups 147 | .entry(block.instructions.clone()) 148 | .or_default() 149 | .push(label.clone()); 150 | } 151 | 152 | // 2. Identify duplicates and choose canonical blocks. 153 | // Build a redirection map: duplicate_label -> canonical_label 154 | // Keep track of labels to preserve (non-duplicates or canonical ones). 155 | let mut redirects: HashMap = HashMap::new(); 156 | let mut preserved_labels: HashSet = HashSet::new(); 157 | 158 | for labels in instruction_groups.values() { 159 | if labels.len() > 1 { 160 | // Found duplicates! 161 | // Choose the first label as the canonical one (arbitrary but consistent). 162 | let canonical_label = labels[0].clone(); 163 | preserved_labels.insert(canonical_label.clone()); 164 | 165 | // Map all other duplicate labels to the canonical one. 166 | for duplicate_label in labels.iter().skip(1) { 167 | redirects.insert(duplicate_label.clone(), canonical_label.clone()); 168 | // Don't add duplicate_label to preserved_labels yet. 169 | } 170 | } else { 171 | // This block is unique, preserve its label. 172 | preserved_labels.insert(labels[0].clone()); 173 | } 174 | } 175 | 176 | // If no redirects were created, no duplicates were found. 177 | if redirects.is_empty() { 178 | return; 179 | } 180 | 181 | // 3. Update the function's entry point if it points to a duplicate. 182 | if let Some(canonical_entry) = redirects.get(&func.body.entry) { 183 | func.body.entry = canonical_entry.clone(); 184 | } 185 | 186 | // 4. Update jumps, branches, and switches in the *preserved* blocks. 187 | for label in &preserved_labels { 188 | // We must get a mutable reference *after* iterating immutably above. 189 | if let Some(block) = func.body.basic_blocks.get_mut(label) { 190 | for instruction in &mut block.instructions { 191 | match instruction { 192 | Instruction::Jump { target } => { 193 | if let Some(canonical_target) = redirects.get(target) { 194 | *target = canonical_target.clone(); 195 | } 196 | } 197 | Instruction::Branch { 198 | true_block, 199 | false_block, 200 | .. 201 | } => { 202 | if let Some(canonical_target) = redirects.get(true_block) { 203 | *true_block = canonical_target.clone(); 204 | } 205 | if let Some(canonical_target) = redirects.get(false_block) { 206 | *false_block = canonical_target.clone(); 207 | } 208 | } 209 | Instruction::Switch { 210 | targets, otherwise, .. 211 | } => { 212 | if let Some(canonical_target) = redirects.get(otherwise) { 213 | *otherwise = canonical_target.clone(); 214 | } 215 | for (_, target_label) in targets.iter_mut() { 216 | if let Some(canonical_target) = redirects.get(target_label) { 217 | *target_label = canonical_target.clone(); 218 | } 219 | } 220 | } 221 | // Other instructions don't contain block labels as targets. 222 | _ => {} 223 | } 224 | } 225 | } 226 | } 227 | 228 | // 5. Remove the duplicate basic blocks from the function body. 229 | func.body 230 | .basic_blocks 231 | .retain(|label, _| preserved_labels.contains(label)); 232 | 233 | // Optional: Add a check or logging for removed blocks 234 | // println!("Removed {} duplicate blocks.", redirects.len()); 235 | } 236 | -------------------------------------------------------------------------------- /tests/binary/binsearch/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | ../../../../config.toml -------------------------------------------------------------------------------- /tests/binary/binsearch/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "binsearch" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /tests/binary/binsearch/Cargo.toml: -------------------------------------------------------------------------------- 1 | cargo-features = ["profile-rustflags"] 2 | 3 | [package] 4 | name = "binsearch" 5 | version = "0.1.0" 6 | edition = "2024" 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /tests/binary/binsearch/java-output.expected: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IntegralPilot/rustc_codegen_jvm/81ad9806e7f70f980ed85753c5199aa317f182b8/tests/binary/binsearch/java-output.expected -------------------------------------------------------------------------------- /tests/binary/binsearch/no_jvm_target.flag: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IntegralPilot/rustc_codegen_jvm/81ad9806e7f70f980ed85753c5199aa317f182b8/tests/binary/binsearch/no_jvm_target.flag -------------------------------------------------------------------------------- /tests/binary/binsearch/src/main.rs: -------------------------------------------------------------------------------- 1 | /// Binary search for u32 slices. 2 | /// Returns `Some(index)` if `target` is found, or `None` otherwise. 3 | pub fn binary_search(slice: &[u32], target: u32) -> Option { 4 | let mut low = 0; 5 | let mut high = slice.len(); 6 | 7 | while low < high { 8 | // mid = floor((low + high) / 2) without overflow 9 | let mid = low + (high - low) / 2; 10 | let v = slice[mid]; 11 | if v < target { 12 | low = mid + 1; 13 | } else if v > target { 14 | high = mid; 15 | } else { 16 | return Some(mid); 17 | } 18 | } 19 | 20 | None 21 | } 22 | 23 | fn main() { 24 | // demo array (must be sorted!) 25 | let arr = [1, 2, 3, 5, 8, 13, 21]; 26 | 27 | // successful searches 28 | assert!(binary_search(&arr, 1) == Some(0)); 29 | assert!(binary_search(&arr, 5) == Some(3)); 30 | assert!(binary_search(&arr, 21) == Some(6)); 31 | 32 | // unsuccessful searches 33 | assert!(binary_search(&arr, 0).is_none()); 34 | assert!(binary_search(&arr, 4).is_none()); 35 | assert!(binary_search(&arr, 22).is_none()); 36 | } -------------------------------------------------------------------------------- /tests/binary/collatz/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | ../../../../config.toml -------------------------------------------------------------------------------- /tests/binary/collatz/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "collatz" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /tests/binary/collatz/Cargo.toml: -------------------------------------------------------------------------------- 1 | cargo-features = ["profile-rustflags"] 2 | 3 | [package] 4 | name = "collatz" 5 | version = "0.1.0" 6 | edition = "2024" 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /tests/binary/collatz/java-output.expected: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IntegralPilot/rustc_codegen_jvm/81ad9806e7f70f980ed85753c5199aa317f182b8/tests/binary/collatz/java-output.expected -------------------------------------------------------------------------------- /tests/binary/collatz/no_jvm_target.flag: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IntegralPilot/rustc_codegen_jvm/81ad9806e7f70f980ed85753c5199aa317f182b8/tests/binary/collatz/no_jvm_target.flag -------------------------------------------------------------------------------- /tests/binary/collatz/src/main.rs: -------------------------------------------------------------------------------- 1 | fn collatz(n: u32) -> u32 { 2 | if n == 1 { 3 | 1 4 | } else if n % 2 == 0 { 5 | collatz(n / 2) 6 | } else { 7 | collatz(3 * n + 1) 8 | } 9 | } 10 | 11 | fn check_up_to(current: u32, limit: u32) { 12 | if current > limit { 13 | return; 14 | } else { 15 | let result = collatz(current); 16 | if result != 1 { 17 | panic!("The collatz conjecture broke? This shouldn't happen."); 18 | } 19 | check_up_to(current + 1, limit); 20 | } 21 | } 22 | 23 | fn main() { 24 | // test check_up_to a few times 25 | check_up_to(1, 10); 26 | check_up_to(70, 100); 27 | check_up_to(1000, 1010); 28 | check_up_to(10000, 10010); 29 | } 30 | -------------------------------------------------------------------------------- /tests/binary/enums/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | ../../../../config.toml -------------------------------------------------------------------------------- /tests/binary/enums/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "enums" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /tests/binary/enums/Cargo.toml: -------------------------------------------------------------------------------- 1 | cargo-features = ["profile-rustflags"] 2 | 3 | [package] 4 | name = "enums" 5 | version = "0.1.0" 6 | edition = "2024" 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /tests/binary/enums/java-output.expected: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IntegralPilot/rustc_codegen_jvm/81ad9806e7f70f980ed85753c5199aa317f182b8/tests/binary/enums/java-output.expected -------------------------------------------------------------------------------- /tests/binary/enums/no_jvm_target.flag: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IntegralPilot/rustc_codegen_jvm/81ad9806e7f70f980ed85753c5199aa317f182b8/tests/binary/enums/no_jvm_target.flag -------------------------------------------------------------------------------- /tests/binary/enums/script.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Tell cargo-minimize to expect standard Cargo output format 4 | echo "minimize-fmt-cargo" 5 | 6 | # --- Configuration --- 7 | # Choose a unique string that ONLY appears when your specific linker error occurs. 8 | # Good candidates from your previous output: 9 | # "Error: java.lang.ArrayIndexOutOfBoundsException" 10 | # "--- R8 FAILED ---" 11 | # "Error creating JAR file: R8 optimization failed" 12 | # Let's use the most specific one related to the R8 crash: 13 | #EXPECTED_ERROR_SUBSTRING="ArrayIndexOutOfBoundsException" 14 | # Or use: 15 | EXPECTED_ERROR_SUBSTRING="linking with" 16 | 17 | # --- Execution --- 18 | # Create a temporary file to capture stderr + stdout 19 | # Using mktemp is safer than fixed filenames 20 | OUTPUT_FILE=$(mktemp) 21 | 22 | # Ensure the temporary file is removed when the script exits, regardless of success/failure 23 | trap 'rm -f "$OUTPUT_FILE"' EXIT 24 | 25 | echo "--- Running build command, capturing output to $OUTPUT_FILE ---" 26 | # Run the build, redirecting both stdout and stderr (2>&1) to the temp file 27 | cargo build --release > "$OUTPUT_FILE" 2>&1 28 | EXIT_CODE=$? 29 | echo "--- Build command finished with exit code: $EXIT_CODE ---" 30 | 31 | REPRODUCED=1 # Default to 1 (failure / not reproduced) 32 | 33 | # Check if the build command failed (non-zero exit code, likely 101 for cargo) 34 | if [ $EXIT_CODE != 0 ]; then 35 | echo "Build command failed (code $EXIT_CODE). Checking output for specific error string..." 36 | # Search (-q quiet mode) for the specific error string within the captured output 37 | if grep -q "$EXPECTED_ERROR_SUBSTRING" "$OUTPUT_FILE"; then 38 | echo "Specific error substring \"$EXPECTED_ERROR_SUBSTRING\" found in output. Error Reproduced." 39 | REPRODUCED=0 # Set to 0 (success / reproduced) 40 | else 41 | echo "Build failed, but the specific error substring \"$EXPECTED_ERROR_SUBSTRING\" was *NOT* found." 42 | echo "--- Build Output (showing why it didn't match) ---" 43 | cat "$OUTPUT_FILE" 44 | echo "--- End Build Output ---" 45 | # Keep REPRODUCED=1 (not the error we are looking for) 46 | fi 47 | else 48 | echo "Build command succeeded (code 0). Error Not Reproduced." 49 | # Keep REPRODUCED=1 50 | fi 51 | 52 | # Explicitly remove the temp file (trap should also cover this) 53 | rm -f "$OUTPUT_FILE" 54 | 55 | echo "--- Script exiting with code $REPRODUCED (0=reproduced, 1=not reproduced) ---" 56 | exit $REPRODUCED -------------------------------------------------------------------------------- /tests/binary/enums/src/main.rs: -------------------------------------------------------------------------------- 1 | struct ConfigData { 2 | id: u32, 3 | enabled: bool, 4 | params: (f32, f32), // Nested tuple 5 | } 6 | 7 | enum ComplexEnum<'a> { 8 | SimpleVariant, // No data 9 | Count(i64), // Single primitive data 10 | Coords((i32, i32, i32)), // Tuple data 11 | UserData { name: &'a str, age: u8 }, // Struct-like variant 12 | RawData([u8; 8]), // Array data 13 | Settings(ConfigData), // Struct data 14 | } 15 | 16 | fn main() { 17 | // Initialize with one variant 18 | let mut current_state = ComplexEnum::UserData { 19 | name: "Alice", 20 | age: 30, 21 | }; 22 | 23 | // Access initial values using match 24 | match current_state { 25 | ComplexEnum::UserData { ref name, age } => { 26 | assert!(*name == "Alice", "Initial name should be Alice"); 27 | assert!(age == 30, "Initial age should be 30"); 28 | } 29 | _ => panic!("Initial state should be UserData!"), 30 | } 31 | 32 | // Access initial values using if let 33 | if let ComplexEnum::UserData { name, .. } = current_state { 34 | assert!(name.starts_with('A'), "Name should start with A"); 35 | } else { 36 | panic!("Still expect UserData here!"); 37 | } 38 | 39 | // Mutate the enum variable to a different variant (Settings) 40 | current_state = ComplexEnum::Settings(ConfigData { 41 | id: 123, 42 | enabled: true, 43 | params: (1.0, -0.5), 44 | }); 45 | 46 | // Access nested values in the new variant 47 | match current_state { 48 | ComplexEnum::Settings(config) => { 49 | assert!(config.id == 123, "Settings ID should be 123"); 50 | assert!(config.enabled, "Settings should be enabled"); 51 | assert!(config.params.0 == 1.0, "Settings params.0 should be 1.0"); 52 | assert!(config.params.1 == -0.5, "Settings params.1 should be -0.5"); 53 | } 54 | _ => panic!("State should now be Settings!"), 55 | } 56 | 57 | // Mutate the enum variable to a different variant (RawData) 58 | current_state = ComplexEnum::RawData([0, 1, 2, 3, 4, 5, 6, 7]); 59 | 60 | // Mutate data *inside* the RawData variant 61 | let mut mutated_internally = false; 62 | match &mut current_state { 63 | // Use mutable borrow (&mut) to modify internal data 64 | ComplexEnum::RawData(data_array) => { 65 | assert!(data_array[0] == 0, "RawData[0] should be 0 initially"); 66 | assert!(data_array[7] == 7, "RawData[7] should be 7 initially"); 67 | 68 | // Modify elements 69 | data_array[0] = 100; 70 | data_array[7] = 200; 71 | data_array[1] *= 5; // Modify based on existing value 72 | 73 | mutated_internally = true; 74 | } 75 | _ => { /*No mutation needed for other branches in this step*/ } 76 | } 77 | assert!(mutated_internally, "Internal mutation should have happened"); 78 | 79 | // Assert internal mutations in RawData 80 | if let ComplexEnum::RawData(data_array) = current_state { 81 | assert!(data_array[0] == 100, "RawData[0] should now be 100"); 82 | assert!(data_array[1] == 5, "RawData[1] should now be 5 (1*5)"); 83 | assert!(data_array[7] == 200, "RawData[7] should now be 200"); 84 | } else { 85 | panic!("State should still be RawData after internal mutation!"); 86 | } 87 | 88 | // Mutate data *inside* the nested ConfigData struct within Settings variant 89 | current_state = ComplexEnum::Settings(ConfigData { 90 | // Reset state 91 | id: 999, 92 | enabled: false, 93 | params: (0.0, 0.0), 94 | }); 95 | 96 | match &mut current_state { 97 | ComplexEnum::Settings(config) => { 98 | config.enabled = true; // Mutate bool field 99 | config.params.1 = config.params.0 + 10.0; // Mutate tuple field 100 | config.id += 1; // Mutate id field 101 | } 102 | _ => panic!("State should be Settings for nested mutation!"), 103 | } 104 | 105 | // Assert internal nested mutations 106 | match current_state { 107 | ComplexEnum::Settings(config) => { 108 | assert!(config.id == 1000, "ConfigData id should be 1000"); 109 | assert!(config.enabled, "ConfigData enabled should be true"); 110 | assert!( 111 | config.params.0 == 0.0, 112 | "ConfigData params.0 should be unchanged" 113 | ); 114 | assert!(config.params.1 == 10.0, "ConfigData params.1 should be 10.0"); 115 | } 116 | _ => panic!("State should still be Settings after nested mutation!"), 117 | } 118 | 119 | // Test remaining variants 120 | current_state = ComplexEnum::Count(5000); 121 | if let ComplexEnum::Count(c) = current_state { 122 | assert!(c == 5000); 123 | } else { 124 | panic!("State should be Count"); 125 | } 126 | 127 | current_state = ComplexEnum::Coords((-10, 0, 20)); 128 | if let ComplexEnum::Coords((x, y, z)) = current_state { 129 | assert!(x == -10); 130 | assert!(y == 0); 131 | assert!(z == 20); 132 | } else { 133 | panic!("State should be Coords"); 134 | } 135 | if let ComplexEnum::Coords((x, y, z)) = current_state { 136 | assert!(x == -10); 137 | assert!(y == 0); 138 | assert!(z == 20); 139 | } else { 140 | panic!("State should be Coords"); 141 | } 142 | 143 | current_state = ComplexEnum::SimpleVariant; 144 | if let ComplexEnum::SimpleVariant = current_state { 145 | // Do nothing, this is the expected state 146 | } else { 147 | panic!("State should be SimpleVariant"); 148 | } 149 | } -------------------------------------------------------------------------------- /tests/binary/fibonacci/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | ../../../../config.toml -------------------------------------------------------------------------------- /tests/binary/fibonacci/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "fibonacci" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /tests/binary/fibonacci/Cargo.toml: -------------------------------------------------------------------------------- 1 | cargo-features = ["profile-rustflags"] 2 | 3 | [package] 4 | name = "fibonacci" 5 | version = "0.1.0" 6 | edition = "2024" 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /tests/binary/fibonacci/java-output.expected: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IntegralPilot/rustc_codegen_jvm/81ad9806e7f70f980ed85753c5199aa317f182b8/tests/binary/fibonacci/java-output.expected -------------------------------------------------------------------------------- /tests/binary/fibonacci/no_jvm_target.flag: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IntegralPilot/rustc_codegen_jvm/81ad9806e7f70f980ed85753c5199aa317f182b8/tests/binary/fibonacci/no_jvm_target.flag -------------------------------------------------------------------------------- /tests/binary/fibonacci/src/main.rs: -------------------------------------------------------------------------------- 1 | fn fibonacci(n: usize) -> usize { 2 | if n == 0 { return 0; } 3 | if n == 1 { return 1; } 4 | 5 | let mut a: usize = 0; 6 | let mut b: usize = 1; 7 | let mut i: usize = 2; 8 | 9 | while i <= n { 10 | let temp = a + b; 11 | a = b; 12 | b = temp; 13 | i += 1; 14 | } 15 | 16 | b 17 | } 18 | 19 | fn fib_recursive(n: usize) -> usize { 20 | match n { 21 | 0 => 0, 22 | 1 => 1, 23 | _ => fib_recursive(n - 1) + fib_recursive(n - 2), 24 | } 25 | } 26 | 27 | fn main() { 28 | assert!(fibonacci(0) == 0); 29 | assert!(fibonacci(1) == 1); 30 | assert!(fibonacci(5) == 5); 31 | assert!(fibonacci(10) == 55); 32 | assert!(fibonacci(15) == 610); 33 | assert!(fibonacci(20) == 6765); 34 | assert!(fibonacci(25) == 75025); 35 | assert!(fibonacci(30) == 832040); 36 | 37 | assert!(fib_recursive(0) == 0); 38 | assert!(fib_recursive(1) == 1); 39 | assert!(fib_recursive(5) == 5); 40 | assert!(fib_recursive(10) == 55); 41 | assert!(fib_recursive(15) == 610); 42 | assert!(fib_recursive(20) == 6765); 43 | assert!(fib_recursive(25) == 75025); 44 | assert!(fib_recursive(30) == 832040); 45 | } -------------------------------------------------------------------------------- /tests/binary/impl/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | ../../../../config.toml -------------------------------------------------------------------------------- /tests/binary/impl/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "impl" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /tests/binary/impl/Cargo.toml: -------------------------------------------------------------------------------- 1 | cargo-features = ["profile-rustflags"] 2 | 3 | [package] 4 | name = "impl" 5 | version = "0.1.0" 6 | edition = "2024" 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /tests/binary/impl/java-output.expected: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IntegralPilot/rustc_codegen_jvm/81ad9806e7f70f980ed85753c5199aa317f182b8/tests/binary/impl/java-output.expected -------------------------------------------------------------------------------- /tests/binary/impl/no_jvm_target.flag: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IntegralPilot/rustc_codegen_jvm/81ad9806e7f70f980ed85753c5199aa317f182b8/tests/binary/impl/no_jvm_target.flag -------------------------------------------------------------------------------- /tests/binary/impl/src/main.rs: -------------------------------------------------------------------------------- 1 | // A struct representing a simple counter with a name (using a static string slice) 2 | struct NamedCounter { 3 | name: &'static str, 4 | count: u32, 5 | limit: u32, 6 | enabled: bool, 7 | } 8 | 9 | impl NamedCounter { 10 | // --- Associated Functions (Constructors) --- 11 | 12 | // Primary constructor 13 | fn new(name: &'static str, limit: u32) -> Self { 14 | NamedCounter { 15 | name, 16 | count: 0, 17 | limit, 18 | enabled: true, // Start enabled by default 19 | } 20 | } 21 | 22 | // Another constructor for a disabled counter 23 | fn new_disabled(name: &'static str, limit: u32) -> Self { 24 | let mut counter = Self::new(name, limit); // Call the primary constructor 25 | counter.enabled = false; 26 | counter 27 | } 28 | 29 | // --- Methods taking &self (Immutable Access) --- 30 | 31 | // Get the current count 32 | fn get_count(&self) -> u32 { 33 | self.count 34 | } 35 | 36 | // Get the name 37 | fn get_name(&self) -> &'static str { 38 | self.name 39 | } 40 | 41 | // Get the limit 42 | fn get_limit(&self) -> u32 { 43 | self.limit 44 | } 45 | 46 | // Check if the counter is enabled 47 | fn is_enabled(&self) -> bool { 48 | self.enabled 49 | } 50 | 51 | // Check if the counter has reached its limit 52 | fn is_at_limit(&self) -> bool { 53 | // Example of calling other &self methods 54 | self.get_count() >= self.get_limit() 55 | } 56 | 57 | // --- Methods taking &mut self (Mutable Access) --- 58 | 59 | // Increment the counter by 1, respecting the limit and enabled status. 60 | // Returns true if incremented, false otherwise. 61 | fn increment(&mut self) -> bool { 62 | if !self.enabled { 63 | // Not enabled, cannot increment 64 | return false; 65 | } 66 | if self.is_at_limit() { // Calls &self method `is_at_limit` 67 | // Already at limit, cannot increment 68 | return false; 69 | } 70 | 71 | // Can increment 72 | self.count += 1; 73 | true // Return true indicating success 74 | } 75 | 76 | // Increment the counter by a specific amount. Clamps at the limit. 77 | // Returns the actual amount the counter was incremented by. 78 | fn increment_by(&mut self, amount: u32) -> u32 { 79 | if !self.enabled { 80 | return 0; // Not enabled, incremented by 0 81 | } 82 | 83 | let current_count = self.count; 84 | let potential_count = current_count + amount; 85 | 86 | if potential_count >= self.limit { 87 | // Clamp to limit 88 | self.count = self.limit; 89 | // Return how much was actually added to reach the limit 90 | self.limit - current_count 91 | } else { 92 | // Increase count by the full amount 93 | self.count = potential_count; 94 | amount // Full amount was added 95 | } 96 | } 97 | 98 | // Reset the counter to zero 99 | fn reset(&mut self) { 100 | self.count = 0; 101 | } 102 | 103 | // Enable the counter 104 | fn enable(&mut self) { 105 | self.enabled = true; 106 | } 107 | 108 | // Disable the counter 109 | fn disable(&mut self) { 110 | self.enabled = false; 111 | } 112 | 113 | // Set a new limit. Clamps count if necessary. 114 | fn set_limit(&mut self, new_limit: u32) { 115 | self.limit = new_limit; 116 | // Clamp count if it now exceeds the new limit 117 | if self.count > self.limit { 118 | self.count = self.limit; 119 | } 120 | } 121 | } // end impl NamedCounter 122 | 123 | 124 | fn main() { 125 | // === Test Case 1: Basic Operations === 126 | let mut counter1 = NamedCounter::new("Clicks", 5); 127 | 128 | // Initial state assertions 129 | assert!(counter1.get_name() == "Clicks"); 130 | assert!(counter1.get_count() == 0); 131 | assert!(counter1.get_limit() == 5); 132 | assert!(counter1.is_enabled()); 133 | assert!(!counter1.is_at_limit()); 134 | 135 | // Test increment 136 | assert!(counter1.increment()); // Should succeed (returns true) 137 | assert!(counter1.get_count() == 1); 138 | assert!(!counter1.is_at_limit()); 139 | 140 | // Test increment_by 141 | let added = counter1.increment_by(2); 142 | assert!(added == 2); // Should have added 2 143 | assert!(counter1.get_count() == 3); 144 | assert!(!counter1.is_at_limit()); 145 | 146 | // Increment to limit 147 | assert!(counter1.increment()); // count = 4, returns true 148 | assert!(counter1.get_count() == 4); 149 | assert!(counter1.increment()); // count = 5 (at limit), returns true 150 | assert!(counter1.get_count() == 5); 151 | assert!(counter1.is_at_limit()); 152 | 153 | // Try incrementing past limit 154 | assert!(!counter1.increment()); // Should fail (returns false) 155 | assert!(counter1.get_count() == 5); // Count should remain 5 156 | assert!(counter1.is_at_limit()); 157 | 158 | // Try increment_by past limit (clamping) 159 | let added_past_limit = counter1.increment_by(3); 160 | assert!(added_past_limit == 0); // Added 0 because already at limit 5 161 | assert!(counter1.get_count() == 5); 162 | assert!(counter1.is_at_limit()); 163 | 164 | 165 | // === Test Case 2: Disabling and Enabling === 166 | counter1.disable(); 167 | assert!(!counter1.is_enabled()); 168 | assert!(counter1.get_count() == 5); // Count unchanged 169 | 170 | // Try incrementing while disabled 171 | assert!(!counter1.increment()); // Should fail (returns false) 172 | assert!(counter1.get_count() == 5); 173 | 174 | // Try increment_by while disabled 175 | let added_while_disabled = counter1.increment_by(2); 176 | assert!(added_while_disabled == 0); // Added 0 177 | assert!(counter1.get_count() == 5); 178 | 179 | // Re-enable 180 | counter1.enable(); 181 | assert!(counter1.is_enabled()); 182 | assert!(!counter1.increment()); // Still at limit, should fail (returns false) 183 | assert!(counter1.get_count() == 5); 184 | 185 | 186 | // === Test Case 3: Changing Limit === 187 | counter1.set_limit(10); 188 | assert!(counter1.get_limit() == 10); 189 | assert!(counter1.get_count() == 5); // Count is still 5 190 | assert!(counter1.is_enabled()); 191 | assert!(!counter1.is_at_limit()); // No longer at limit 192 | 193 | // Increment now that limit is higher 194 | assert!(counter1.increment()); // count = 6, returns true 195 | assert!(counter1.get_count() == 6); 196 | 197 | // Increment_by with new limit 198 | let added_new_limit = counter1.increment_by(3); // 6 + 3 = 9 199 | assert!(added_new_limit == 3); // Added 3 200 | assert!(counter1.get_count() == 9); 201 | assert!(!counter1.is_at_limit()); 202 | 203 | // Increment_by that hits the new limit exactly 204 | let added_to_limit = counter1.increment_by(1); // 9 + 1 = 10 205 | assert!(added_to_limit == 1); // Added 1 206 | assert!(counter1.get_count() == 10); 207 | assert!(counter1.is_at_limit()); 208 | 209 | // Increment_by that exceeds new limit (clamping) 210 | let added_over_limit = counter1.increment_by(5); // Try 10 + 5 -> clamps to 10 211 | assert!(added_over_limit == 0); // Added 0 because already at limit 10 212 | assert!(counter1.get_count() == 10); 213 | assert!(counter1.is_at_limit()); 214 | 215 | // Lower the limit below the current count 216 | counter1.set_limit(7); 217 | assert!(counter1.get_limit() == 7); 218 | assert!(counter1.get_count() == 7); // Count should be clamped to new limit 219 | assert!(counter1.is_at_limit()); 220 | 221 | // Try incrementing after clamping 222 | assert!(!counter1.increment()); // Should fail (at new limit, returns false) 223 | assert!(counter1.get_count() == 7); 224 | 225 | 226 | // === Test Case 4: Resetting === 227 | counter1.reset(); 228 | assert!(counter1.get_count() == 0); 229 | assert!(counter1.get_limit() == 7); // Limit unchanged by reset 230 | assert!(counter1.is_enabled()); // Enabled status unchanged by reset 231 | assert!(!counter1.is_at_limit()); 232 | 233 | 234 | // === Test Case 5: Disabled Constructor === 235 | let mut counter2 = NamedCounter::new_disabled("Skips", 100); 236 | 237 | // Initial state assertions for disabled counter 238 | assert!(counter2.get_name() == "Skips"); 239 | assert!(counter2.get_count() == 0); 240 | assert!(counter2.get_limit() == 100); 241 | assert!(!counter2.is_enabled()); // Should be disabled 242 | assert!(!counter2.is_at_limit()); 243 | 244 | // Try incrementing while initially disabled 245 | assert!(!counter2.increment()); // returns false 246 | assert!(counter2.get_count() == 0); 247 | 248 | // Enable and increment 249 | counter2.enable(); 250 | assert!(counter2.is_enabled()); 251 | assert!(counter2.increment()); // returns true 252 | assert!(counter2.get_count() == 1); 253 | 254 | 255 | // === Test Case 6: Edge case with large numbers / saturation === 256 | let mut counter3 = NamedCounter::new("OverflowTest", u32::MAX); 257 | assert!(counter3.get_count() == 0); 258 | assert!(counter3.get_limit() == u32::MAX); 259 | 260 | // Increment by a large amount, but less than limit 261 | let added_large = counter3.increment_by(u32::MAX - 10); 262 | assert!(added_large == u32::MAX - 10); 263 | assert!(counter3.get_count() == u32::MAX - 10); 264 | 265 | // Increment to exactly the limit 266 | let added_to_max = counter3.increment_by(10); 267 | assert!(added_to_max == 10); 268 | assert!(counter3.get_count() == u32::MAX); 269 | assert!(counter3.is_at_limit()); 270 | 271 | // Try to increment past MAX (should add 0 due to limit check/saturation) 272 | let added_past_max = counter3.increment_by(5); 273 | assert!(added_past_max == 0); 274 | assert!(counter3.get_count() == u32::MAX); 275 | 276 | // If execution reaches here without panicking, all assertions passed. 277 | } -------------------------------------------------------------------------------- /tests/binary/is_even_plus_one/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | ../../../../config.toml -------------------------------------------------------------------------------- /tests/binary/is_even_plus_one/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "is_even_plus_one" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /tests/binary/is_even_plus_one/Cargo.toml: -------------------------------------------------------------------------------- 1 | cargo-features = ["profile-rustflags"] 2 | 3 | [package] 4 | name = "is_even_plus_one" 5 | version = "0.1.0" 6 | edition = "2024" 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /tests/binary/is_even_plus_one/java-output.expected: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IntegralPilot/rustc_codegen_jvm/81ad9806e7f70f980ed85753c5199aa317f182b8/tests/binary/is_even_plus_one/java-output.expected -------------------------------------------------------------------------------- /tests/binary/is_even_plus_one/no_jvm_target.flag: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IntegralPilot/rustc_codegen_jvm/81ad9806e7f70f980ed85753c5199aa317f182b8/tests/binary/is_even_plus_one/no_jvm_target.flag -------------------------------------------------------------------------------- /tests/binary/is_even_plus_one/src/main.rs: -------------------------------------------------------------------------------- 1 | fn is_even_plus_one(n: i32) -> i32 { 2 | if n % 2 == 0 { 3 | n + 1 4 | } else { 5 | n - 1 6 | } 7 | } 8 | 9 | fn main() { 10 | let result = is_even_plus_one(10); 11 | // (not assert_eq! because that does dereferencing which we don't support yet) 12 | // also no formatting because that's in `alloc`, we're just trying to get `core` working first 13 | assert!(result == 11, "Expected 11!"); 14 | let another_result = is_even_plus_one(7); 15 | assert!(another_result == 6, "Expected 6!"); 16 | } -------------------------------------------------------------------------------- /tests/binary/just_main_func/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "just_main_func" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /tests/binary/just_main_func/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "just_main_func" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] -------------------------------------------------------------------------------- /tests/binary/just_main_func/build.sh: -------------------------------------------------------------------------------- 1 | cargo clean 2 | cargo build --target ../../../jvm-unknown-unknown.json -------------------------------------------------------------------------------- /tests/binary/just_main_func/java-output.expected: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IntegralPilot/rustc_codegen_jvm/81ad9806e7f70f980ed85753c5199aa317f182b8/tests/binary/just_main_func/java-output.expected -------------------------------------------------------------------------------- /tests/binary/just_main_func/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![feature(no_core)] 3 | #![feature(lang_items)] 4 | #![no_core] 5 | 6 | fn main() { 7 | } 8 | 9 | #[lang = "sized"] 10 | trait Sized {} -------------------------------------------------------------------------------- /tests/binary/mutable_borrow/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | ../../../../config.toml -------------------------------------------------------------------------------- /tests/binary/mutable_borrow/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "mutable_borrow" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /tests/binary/mutable_borrow/Cargo.toml: -------------------------------------------------------------------------------- 1 | cargo-features = ["profile-rustflags"] 2 | 3 | [package] 4 | name = "mutable_borrow" 5 | version = "0.1.0" 6 | edition = "2024" 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /tests/binary/mutable_borrow/java-output.expected: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IntegralPilot/rustc_codegen_jvm/81ad9806e7f70f980ed85753c5199aa317f182b8/tests/binary/mutable_borrow/java-output.expected -------------------------------------------------------------------------------- /tests/binary/mutable_borrow/no_jvm_target.flag: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IntegralPilot/rustc_codegen_jvm/81ad9806e7f70f980ed85753c5199aa317f182b8/tests/binary/mutable_borrow/no_jvm_target.flag -------------------------------------------------------------------------------- /tests/binary/mutable_borrow/src/main.rs: -------------------------------------------------------------------------------- 1 | // Simple struct 2 | struct Point { 3 | x: i32, 4 | y: i32, 5 | } 6 | 7 | // Function taking a mutable borrow of a Point field 8 | fn make_y_negative(p_y: &mut i32) { 9 | if *p_y > 0 { 10 | *p_y = -(*p_y); 11 | } 12 | assert!(*p_y <= 0); 13 | } 14 | 15 | // Function taking a shared borrow of a Point and reading 16 | fn get_x_coord(p: &Point) -> i32 { 17 | p.x // Just read the x field 18 | } 19 | 20 | // Function taking a mutable borrow of the whole Point 21 | fn shift_point(p: &mut Point) { 22 | p.x += 10; // Modify field directly via mutable borrow of struct 23 | make_y_negative(&mut p.y); // Re-borrow field mutably and pass to another function 24 | p.x += 5; // Modify field again after inner borrow ended 25 | } 26 | 27 | 28 | fn main() { 29 | // 1. Initial setup 30 | let mut point = Point { x: 1, y: 5 }; 31 | assert!(point.x == 1 && point.y == 5); 32 | 33 | // 2. Test shared borrow 34 | let current_x = get_x_coord(&point); // Pass shared borrow 35 | assert!(current_x == 1); 36 | // Point should be unchanged after shared borrow 37 | assert!(point.x == 1 && point.y == 5); 38 | 39 | // 3. Test mutable borrow of the whole struct 40 | shift_point(&mut point); // Pass mutable borrow 41 | 42 | // 4. Assert final state after mutable borrow 43 | // Inside shift_point: 44 | // p.x = 1 + 10 = 11 45 | // p.y = -(5) = -5 (via make_y_negative(&mut p.y)) 46 | // p.x = 11 + 5 = 16 47 | assert!(point.x == 16); 48 | assert!(point.y == -5); 49 | } -------------------------------------------------------------------------------- /tests/binary/ops/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | ../../../../config.toml -------------------------------------------------------------------------------- /tests/binary/ops/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "ops" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /tests/binary/ops/Cargo.toml: -------------------------------------------------------------------------------- 1 | cargo-features = ["profile-rustflags"] 2 | 3 | [package] 4 | name = "ops" 5 | version = "0.1.0" 6 | edition = "2024" 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /tests/binary/ops/java-output.expected: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IntegralPilot/rustc_codegen_jvm/81ad9806e7f70f980ed85753c5199aa317f182b8/tests/binary/ops/java-output.expected -------------------------------------------------------------------------------- /tests/binary/ops/no_jvm_target.flag: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IntegralPilot/rustc_codegen_jvm/81ad9806e7f70f980ed85753c5199aa317f182b8/tests/binary/ops/no_jvm_target.flag -------------------------------------------------------------------------------- /tests/binary/ops/src/main.rs: -------------------------------------------------------------------------------- 1 | #![feature(f16)] 2 | #![feature(f128)] 3 | 4 | macro_rules! test_comparisons { 5 | ($type:ty, $a:expr, $b:expr, $c:expr, $d:expr, $zero:expr, $nzero:expr) => { 6 | assert!($a == $b, concat!(stringify!($type), ": a == b")); 7 | assert!($a != $c, concat!(stringify!($type), ": a != c")); 8 | assert!($a < $c, concat!(stringify!($type), ": a < c")); 9 | assert!($a <= $b, concat!(stringify!($type), ": a <= b")); 10 | assert!($c > $a, concat!(stringify!($type), ": c > a")); 11 | assert!($a >= $b, concat!(stringify!($type), ": a >= b")); 12 | assert!($d < $a, concat!(stringify!($type), ": d < a")); 13 | }; 14 | } 15 | 16 | macro_rules! test_comparisons_float { 17 | ($type:ty, $a:expr, $b:expr, $c:expr, $d:expr, $zero:expr, $nzero:expr, $nan:expr) => { 18 | assert!($a == $b, concat!(stringify!($type), ": a == b")); 19 | assert!($a != $c, concat!(stringify!($type), ": a != c")); 20 | assert!($a < $c, concat!(stringify!($type), ": a < c")); 21 | assert!($a <= $b, concat!(stringify!($type), ": a <= b")); 22 | assert!($c > $a, concat!(stringify!($type), ": c > a")); 23 | assert!($a >= $b, concat!(stringify!($type), ": a >= b")); 24 | assert!($d < $a, concat!(stringify!($type), ": d < a")); 25 | assert!($zero == $nzero, concat!(stringify!($type), ": 0.0 == -0.0")); 26 | assert!(!$nan.eq(&$nan), concat!(stringify!($type), ": !(nan == nan)")); 27 | assert!($nan != $nan, concat!(stringify!($type), ": nan != nan")); 28 | }; 29 | } 30 | 31 | macro_rules! test_comparisons_float_no_nan { 32 | ($type:ty, $a:expr, $b:expr, $c:expr, $d:expr, $zero:expr, $nzero:expr) => { 33 | assert!($a == $b, concat!(stringify!($type), ": a == b")); 34 | assert!($a != $c, concat!(stringify!($type), ": a != c")); 35 | assert!($a < $c, concat!(stringify!($type), ": a < c")); 36 | assert!($a <= $b, concat!(stringify!($type), ": a <= b")); 37 | assert!($c > $a, concat!(stringify!($type), ": c > a")); 38 | assert!($a >= $b, concat!(stringify!($type), ": a >= b")); 39 | assert!($d < $a, concat!(stringify!($type), ": d < a")); 40 | assert!($zero == $nzero, concat!(stringify!($type), ": 0.0 == -0.0")); 41 | }; 42 | } 43 | 44 | macro_rules! test_binary_ops { 45 | ($type:ty, $a:expr, $b:expr, $zero:expr, $and:expr, $or:expr, $xor:expr) => { 46 | assert!(($a & $b) == $and, concat!(stringify!($type), ": a & b")); 47 | assert!(($a | $b) == $or, concat!(stringify!($type), ": a | b")); 48 | assert!(($a ^ $b) == $xor, concat!(stringify!($type), ": a ^ b")); 49 | assert!(($a & $zero) == $zero, concat!(stringify!($type), ": a & 0")); 50 | assert!(($a | $zero) == $a, concat!(stringify!($type), ": a | 0")); 51 | assert!(($a ^ $zero) == $a, concat!(stringify!($type), ": a ^ 0")); 52 | }; 53 | } 54 | 55 | macro_rules! test_ops { 56 | ($type:ty, $a:expr, $b:expr, $zero:expr, $add:expr, $sub:expr, $mul:expr, $div:expr) => { 57 | assert!($a + $b == $add, concat!(stringify!($type), ": a + b")); 58 | assert!($a - $b == $sub, concat!(stringify!($type), ": a - b")); 59 | assert!($a * $b == $mul, concat!(stringify!($type), ": a * b")); 60 | assert!($a / $b == $div, concat!(stringify!($type), ": a / b")); 61 | assert!($a + $zero == $a, concat!(stringify!($type), ": a + 0")); 62 | assert!($a - $zero == $a, concat!(stringify!($type), ": a - 0")); 63 | assert!($a * $zero == $zero, concat!(stringify!($type), ": a * 0")); 64 | }; 65 | } 66 | 67 | fn main() { 68 | // u8 comparisons 69 | test_comparisons!(u8, 5u8, 5u8, 10u8, 2u8, 0u8, 0u8); 70 | 71 | // i8 comparisons 72 | test_comparisons!(i8, 5i8, 5i8, 10i8, -2i8, 0i8, 0i8); 73 | 74 | // u16 comparisons 75 | test_comparisons!(u16, 5u16, 5u16, 10u16, 2u16, 0u16, 0u16); 76 | 77 | // i16 comparisons 78 | test_comparisons!(i16, 5i16, 5i16, 10i16, -2i16, 0i16, 0i16); 79 | 80 | // f16 comparisons 81 | test_comparisons_float!(f16, 5.0f16, 5.0f16, 10.5f16, -2.1f16, 0.0f16, -0.0f16, f16::NAN); 82 | 83 | // u32 comparisons 84 | test_comparisons!(u32, 5u32, 5u32, 10u32, 2u32, 0u32, 0u32); 85 | 86 | // i32 comparisons 87 | test_comparisons!(i32, 5i32, 5i32, 10i32, -2i32, 0i32, 0i32); 88 | 89 | // f32 comparisons 90 | test_comparisons_float!(f32, 5.0f32, 5.0f32, 10.5f32, -2.1f32, 0.0f32, -0.0f32, f32::NAN); 91 | 92 | // i64 comparisons 93 | test_comparisons!(i64, 500i64, 500i64, 1000i64, -200i64, 0i64, 0i64); 94 | 95 | // f64 comparisons 96 | test_comparisons_float!(f64, 5.0f64, 5.0f64, 10.5f64, -2.1f64, 0.0f64, -0.0f64, f64::NAN); 97 | 98 | // u128 comparisons 99 | test_comparisons!(u128, 5_000_000_000_000_000_000_000u128, 5_000_000_000_000_000_000_000u128, 10_000_000_000_000_000_000_000u128, 2u128, 0u128, 0u128); 100 | 101 | // i128 comparisons 102 | test_comparisons!(i128, 5_000_000_000_000_000_000_000i128, 5_000_000_000_000_000_000_000i128, 10_000_000_000_000_000_000_000i128, -2i128, 0i128, 0i128); 103 | 104 | // f128 comparisons 105 | test_comparisons_float_no_nan!(f128, 5.0f128, 5.0f128, 10.5f128, -2.1f128, 0.0f128, -0.0f128); 106 | 107 | // u8 binary operations 108 | test_binary_ops!( 109 | u8, 110 | 0b1100_1010u8, 111 | 0b1010_0110u8, 112 | 0u8, 113 | 0b1000_0010u8, 114 | 0b1110_1110u8, 115 | 0b0110_1100u8 116 | ); 117 | 118 | // i8 binary operations 119 | test_binary_ops!( 120 | i8, 121 | -54i8, // 0b11001010 122 | -90i8, // 0b10100110 123 | 0i8, 124 | -126i8, // a & b = 0b10000010 125 | -18i8, // a | b = 0b11101110 126 | 108i8 // a ^ b = 0b01101100 127 | ); 128 | 129 | // u16 binary operations 130 | test_binary_ops!( 131 | u16, 132 | 0xACF0u16, // 1010110011110000 133 | 0x5A0Fu16, // 0101101000001111 134 | 0u16, 135 | 0x0800u16, // a & b 136 | 0xFEFFu16, // a | b 137 | 0xF6FFu16 // a ^ b 138 | ); 139 | 140 | // i16 binary operations 141 | test_binary_ops!( 142 | i16, 143 | -21264i16, // 0xACF0 144 | 23055i16, // 0x5A0F 145 | 0i16, 146 | 2048i16, // a & b = 0x0800 147 | -257i16, // a | b = 0xFEFF 148 | -2305i16 // a ^ b = 0xF6FF 149 | ); 150 | 151 | // u32 binary operations 152 | test_binary_ops!( 153 | u32, 154 | 0xDEADBEEFu32, 155 | 0xFEEDC0DEu32, 156 | 0u32, 157 | 0xDEAD80CEu32, // a & b 158 | 0xFEEDFEFFu32, // a | b 159 | 0x20407E31u32 // a ^ b 160 | ); 161 | 162 | // i32 binary operations 163 | test_binary_ops!( 164 | i32, 165 | 0b1100_1010i32, 166 | 0b1010_0110i32, 167 | 0i32, 168 | 0b1000_0010i32, 169 | 0b1110_1110i32, 170 | 0b0110_1100i32 171 | ); 172 | 173 | // u64 binary operations 174 | test_binary_ops!( 175 | u64, 176 | 0x1234_5678_9ABC_DEF0u64, 177 | 0x0FED_CBA9_8765_4321u64, 178 | 0u64, 179 | 0x0224_4228_8224_4220u64, 180 | 0x1FFD_DFF9_9FFD_DFF1u64, 181 | 0x1DD9_9DD1_1DD9_9DD1u64 182 | ); 183 | 184 | // i64 binary operations 185 | test_binary_ops!( 186 | i64, 187 | 0x1234_5678_9ABC_DEF0i64, 188 | 0x0FED_CBA9_8765_4321i64, 189 | 0i64, 190 | 0x0224_4228_8224_4220i64, 191 | 0x1FFD_DFF9_9FFD_DFF1i64, 192 | 0x1DD9_9DD1_1DD9_9DD1i64 193 | ); 194 | 195 | // u128 binary operations 196 | test_binary_ops!( 197 | u128, 198 | 0x1234_5678_9ABC_DEF0_1234_5678_9ABC_DEF0u128, 199 | 0x0FED_CBA9_8765_4321_0FED_CBA9_8765_4321u128, 200 | 0u128, 201 | 0x0224_4228_8224_4220_0224_4228_8224_4220u128, 202 | 0x1FFD_DFF9_9FFD_DFF1_1FFD_DFF9_9FFD_DFF1u128, 203 | 0x1DD9_9DD1_1DD9_9DD1_1DD9_9DD1_1DD9_9DD1u128 204 | ); 205 | 206 | // i128 binary operations 207 | test_binary_ops!( 208 | i128, 209 | 0x1234_5678_9ABC_DEF0_1234_5678_9ABC_DEF0i128, 210 | 0x0FED_CBA9_8765_4321_0FED_CBA9_8765_4321i128, 211 | 0i128, 212 | 0x0224_4228_8224_4220_0224_4228_8224_4220i128, 213 | 0x1FFD_DFF9_9FFD_DFF1_1FFD_DFF9_9FFD_DFF1i128, 214 | 0x1DD9_9DD1_1DD9_9DD1_1DD9_9DD1_1DD9_9DD1i128 215 | ); 216 | 217 | // u8 operations 218 | test_ops!(u8, 10u8, 5u8, 0u8, 15u8, 5u8, 50u8, 2u8); 219 | 220 | // i8 operations 221 | test_ops!(i8, 10i8, 5i8, 0i8, 15i8, 5i8, 50i8, 2i8); 222 | 223 | // u16 operations 224 | test_ops!(u16, 10u16, 5u16, 0u16, 15u16, 5u16, 50u16, 2u16); 225 | 226 | // i16 operations 227 | test_ops!(i16, 10i16, 5i16, 0i16, 15i16, 5i16, 50i16, 2i16); 228 | 229 | // f16 operations 230 | test_ops!(f16, 10.0f16, 2.0f16, 0.0f16, 12.0f16, 8.0f16, 20.0f16, 5.0f16); 231 | 232 | // u32 operations 233 | test_ops!(u32, 10000u32, 5000u32, 0u32, 15000u32, 5000u32, 50000000u32, 2u32); 234 | 235 | // i32 operations 236 | test_ops!(i32, 10000i32, 5000i32, 0i32, 15000i32, 5000i32, 50000000i32, 2i32); 237 | 238 | // f32 operations 239 | test_ops!(f32, 10.5f32, 2.5f32, 0.0f32, 13.0f32, 8.0f32, 26.25f32, 4.2f32); 240 | 241 | // u64 operations 242 | test_ops!(u64, 1000000u64, 200000u64, 0u64, 1200000u64, 800000u64, 200000000000u64, 5u64); 243 | 244 | // i64 operations 245 | test_ops!(i64, 1000000i64, 200000i64, 0i64, 1200000i64, 800000i64, 200000000000i64, 5i64); 246 | 247 | // f64 operations 248 | test_ops!(f64, 10.5f64, 2.5f64, 0.0f64, 13.0f64, 8.0f64, 26.25f64, 4.2f64); 249 | 250 | // u128 operations 251 | test_ops!( 252 | u128, 253 | 1000000000000000000u128, 254 | 200000000000000000u128, 255 | 0u128, 256 | 1200000000000000000u128, 257 | 800000000000000000u128, 258 | 200000000000000000000000000000000000u128, 259 | 5u128 260 | ); 261 | 262 | // i128 operations 263 | test_ops!( 264 | i128, 265 | 1000000000000000000i128, 266 | 200000000000000000i128, 267 | 0i128, 268 | 1200000000000000000i128, 269 | 800000000000000000i128, 270 | 200000000000000000000000000000000000i128, 271 | 5i128 272 | ); 273 | 274 | // f128 operations 275 | test_ops!( 276 | f128, 277 | 10.0f128, 278 | 2.0f128, 279 | 0.0f128, 280 | 12.0f128, 281 | 8.0f128, 282 | 20.0f128, 283 | 5.0f128 284 | ); 285 | } -------------------------------------------------------------------------------- /tests/binary/panic/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | ../../../../config.toml -------------------------------------------------------------------------------- /tests/binary/panic/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "panic" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /tests/binary/panic/Cargo.toml: -------------------------------------------------------------------------------- 1 | cargo-features = ["profile-rustflags"] 2 | 3 | [package] 4 | name = "panic" 5 | version = "0.1.0" 6 | edition = "2024" 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /tests/binary/panic/java-output.expected: -------------------------------------------------------------------------------- 1 | STDOUT:STDERR:Exception in thread "main" java.lang.RuntimeException: Rust panic: This is a panic message! at org.rustlang.core.Core.panic_fmt(Core.kt:81) at panic.main(SourceFile) -------------------------------------------------------------------------------- /tests/binary/panic/java-returncode.expected: -------------------------------------------------------------------------------- 1 | 1 -------------------------------------------------------------------------------- /tests/binary/panic/no_jvm_target.flag: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IntegralPilot/rustc_codegen_jvm/81ad9806e7f70f980ed85753c5199aa317f182b8/tests/binary/panic/no_jvm_target.flag -------------------------------------------------------------------------------- /tests/binary/panic/src/main.rs: -------------------------------------------------------------------------------- 1 | // Tests panicking. Formatting is technically `alloc` stuff, we're only trying to get `core` working for now 2 | // Tester.py compares the actual panic output (message + backtrace) with the expected output 3 | 4 | fn main() { 5 | panic!("This is a panic message!"); 6 | } 7 | -------------------------------------------------------------------------------- /tests/binary/primes/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | ../../../../config.toml -------------------------------------------------------------------------------- /tests/binary/primes/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "primes" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /tests/binary/primes/Cargo.toml: -------------------------------------------------------------------------------- 1 | cargo-features = ["profile-rustflags"] 2 | 3 | [package] 4 | name = "primes" 5 | version = "0.1.0" 6 | edition = "2024" 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /tests/binary/primes/java-output.expected: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IntegralPilot/rustc_codegen_jvm/81ad9806e7f70f980ed85753c5199aa317f182b8/tests/binary/primes/java-output.expected -------------------------------------------------------------------------------- /tests/binary/primes/no_jvm_target.flag: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IntegralPilot/rustc_codegen_jvm/81ad9806e7f70f980ed85753c5199aa317f182b8/tests/binary/primes/no_jvm_target.flag -------------------------------------------------------------------------------- /tests/binary/primes/src/main.rs: -------------------------------------------------------------------------------- 1 | fn is_prime(n: u32) -> bool { 2 | if n < 2 { 3 | return false; 4 | } 5 | if n == 2 || n == 3 { 6 | return true; 7 | } 8 | if n % 2 == 0 { 9 | return false; 10 | } 11 | 12 | let mut i = 3; 13 | while i * i <= n { 14 | if n % i == 0 { 15 | return false; 16 | } 17 | i += 2; 18 | } 19 | true 20 | } 21 | 22 | fn find_primes(start: u32, count: usize, buffer: &mut [u32]) -> usize { 23 | let mut found = 0; 24 | let mut candidate = if start % 2 == 0 { start + 1 } else { start }; 25 | 26 | while found < count && found < buffer.len() { 27 | if is_prime(candidate) { 28 | buffer[found] = candidate; 29 | found += 1; 30 | } 31 | candidate += 2; 32 | } 33 | 34 | found 35 | } 36 | 37 | fn main() { 38 | let mut primes = [0u32; 10]; 39 | let found = find_primes(10_000, 10, &mut primes); 40 | 41 | let mut i: usize = 0; 42 | while i < found { 43 | assert!(is_prime(primes[i])); 44 | i += 1; 45 | } 46 | } -------------------------------------------------------------------------------- /tests/binary/rsa/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | ../../../../config.toml -------------------------------------------------------------------------------- /tests/binary/rsa/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "rsa" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /tests/binary/rsa/Cargo.toml: -------------------------------------------------------------------------------- 1 | cargo-features = ["profile-rustflags"] 2 | 3 | [package] 4 | name = "rsa" 5 | version = "0.1.0" 6 | edition = "2024" 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /tests/binary/rsa/java-output.expected: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IntegralPilot/rustc_codegen_jvm/81ad9806e7f70f980ed85753c5199aa317f182b8/tests/binary/rsa/java-output.expected -------------------------------------------------------------------------------- /tests/binary/rsa/no_jvm_target.flag: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IntegralPilot/rustc_codegen_jvm/81ad9806e7f70f980ed85753c5199aa317f182b8/tests/binary/rsa/no_jvm_target.flag -------------------------------------------------------------------------------- /tests/binary/rsa/src/main.rs: -------------------------------------------------------------------------------- 1 | struct PublicKey { 2 | e: u64, 3 | n: u64, 4 | } 5 | 6 | struct PrivateKey { 7 | d: u64, 8 | n: u64, 9 | } 10 | 11 | // Modular exponentiation: (base^exp) % modulus 12 | fn mod_exp(mut base: u64, mut exp: u64, modulus: u64) -> u64 { 13 | let mut result = 1; 14 | base %= modulus; 15 | 16 | while exp > 0 { 17 | if exp % 2 == 1 { 18 | result = (result * base) % modulus; 19 | } 20 | exp >>= 1; 21 | base = (base * base) % modulus; 22 | } 23 | 24 | result 25 | } 26 | 27 | // Extended Euclidean Algorithm to find modular inverse 28 | fn mod_inv(a: i64, m: i64) -> i64 { 29 | let (mut m0, mut x0, mut x1) = (m, 0, 1); 30 | let mut a = a; 31 | 32 | while a > 1 { 33 | let q = a / m0; 34 | let t = m0; 35 | m0 = a % m0; 36 | a = t; 37 | let t = x0; 38 | x0 = x1 - q * x0; 39 | x1 = t; 40 | } 41 | 42 | if x1 < 0 { 43 | x1 += m; 44 | } 45 | 46 | x1 47 | } 48 | 49 | // RSA key generation (with hardcoded small primes for simplicity) 50 | fn generate_keys() -> (PublicKey, PrivateKey) { 51 | let p = 61; 52 | let q = 53; 53 | let n = p * q; 54 | let phi = (p - 1) * (q - 1); 55 | let e = 17; 56 | let d = mod_inv(e as i64, phi as i64) as u64; 57 | 58 | ( 59 | PublicKey { e, n }, 60 | PrivateKey { d, n }, 61 | ) 62 | } 63 | 64 | // RSA encryption: c = m^e mod n 65 | fn encrypt(pub_key: &PublicKey, message: u64) -> u64 { 66 | mod_exp(message, pub_key.e, pub_key.n) 67 | } 68 | 69 | // RSA decryption: m = c^d mod n 70 | fn decrypt(priv_key: &PrivateKey, ciphertext: u64) -> u64 { 71 | mod_exp(ciphertext, priv_key.d, priv_key.n) 72 | } 73 | 74 | // Main test function 75 | fn main() { 76 | let (public_key, private_key) = generate_keys(); 77 | 78 | // Test message 79 | let message: u64 = 42; 80 | assert!(message < public_key.n); 81 | 82 | let encrypted = encrypt(&public_key, message); 83 | let decrypted = decrypt(&private_key, encrypted); 84 | 85 | // Check correctness 86 | assert!(decrypted == message); 87 | assert!(encrypted != message); // Make sure encryption changes the message 88 | } -------------------------------------------------------------------------------- /tests/binary/structs/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | ../../../../config.toml -------------------------------------------------------------------------------- /tests/binary/structs/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "structs" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /tests/binary/structs/Cargo.toml: -------------------------------------------------------------------------------- 1 | cargo-features = ["profile-rustflags"] 2 | 3 | [package] 4 | name = "structs" 5 | version = "0.1.0" 6 | edition = "2024" 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /tests/binary/structs/java-output.expected: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IntegralPilot/rustc_codegen_jvm/81ad9806e7f70f980ed85753c5199aa317f182b8/tests/binary/structs/java-output.expected -------------------------------------------------------------------------------- /tests/binary/structs/no_jvm_target.flag: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IntegralPilot/rustc_codegen_jvm/81ad9806e7f70f980ed85753c5199aa317f182b8/tests/binary/structs/no_jvm_target.flag -------------------------------------------------------------------------------- /tests/binary/structs/src/main.rs: -------------------------------------------------------------------------------- 1 | struct Inner { 2 | x: i32, 3 | y: (i32, i32), 4 | } 5 | 6 | struct Outer<'a> { 7 | label: &'a str, 8 | inner_struct: Inner, 9 | data: [i32; 3], 10 | } 11 | 12 | fn main() { 13 | // === Nested STRUCT + TUPLE + ARRAY === 14 | let mut outer = Outer { 15 | label: "start", 16 | inner_struct: Inner { 17 | x: 100, 18 | y: (5, 10), 19 | }, 20 | data: [1, 2, 3], 21 | }; 22 | 23 | // === Access nested values === 24 | assert!(outer.label == "start"); 25 | assert!(outer.inner_struct.x == 100, "Inner x should be 100"); 26 | assert!(outer.inner_struct.y.0 == 5, "Inner tuple y.0 should be 5"); 27 | assert!(outer.inner_struct.y.1 == 10, "Inner tuple y.1 should be 10"); 28 | assert!(outer.data[0] == 1, "Array element at index 0 should be 1"); 29 | 30 | // === Mutate nested values === 31 | outer.label = "updated"; 32 | outer.inner_struct.x = 200; 33 | outer.inner_struct.y.1 = 999; 34 | outer.data[1] = 42; 35 | 36 | // === Assert mutations === 37 | assert!(outer.label == "updated", "Outer label should be updated"); 38 | assert!(outer.inner_struct.x == 200, "Inner x should now be 200"); 39 | assert!(outer.inner_struct.y.1 == 999, "Inner tuple y.1 should now be 999"); 40 | assert!(outer.data[1] == 42, "Array element at index 1 should now be 42"); 41 | 42 | // === Tuple nesting test === 43 | let mut big_tuple = ( 44 | (10, 20), 45 | Inner { x: 50, y: (7, 8) }, 46 | ["a", "b", "c"], 47 | ); 48 | 49 | // Access nested tuple values 50 | assert!((big_tuple.0).1 == 20, "First tuple's second element should be 20"); 51 | assert!(big_tuple.1.y.0 == 7, "Nested tuple inside struct should be 7"); 52 | assert!(big_tuple.2[2] == "c", "Array inside tuple should contain 'c' at index 2"); 53 | 54 | // Mutate nested values 55 | (big_tuple.0).0 = 99; 56 | big_tuple.1.x = 123; 57 | big_tuple.2[1] = "z"; 58 | 59 | // Assert changes 60 | assert!((big_tuple.0).0 == 99, "Tuple value mutated to 99"); 61 | assert!(big_tuple.1.x == 123, "Inner struct field x mutated to 123"); 62 | assert!(big_tuple.2[1] == "z", "Tuple's array element at index 1 mutated to 'z'"); 63 | } 64 | -------------------------------------------------------------------------------- /tests/binary/traits/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | ../../../../config.toml -------------------------------------------------------------------------------- /tests/binary/traits/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "traits" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /tests/binary/traits/Cargo.toml: -------------------------------------------------------------------------------- 1 | cargo-features = ["profile-rustflags"] 2 | 3 | [package] 4 | name = "traits" 5 | version = "0.1.0" 6 | edition = "2024" 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /tests/binary/traits/java-output.expected: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IntegralPilot/rustc_codegen_jvm/81ad9806e7f70f980ed85753c5199aa317f182b8/tests/binary/traits/java-output.expected -------------------------------------------------------------------------------- /tests/binary/traits/no_jvm_target.flag: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IntegralPilot/rustc_codegen_jvm/81ad9806e7f70f980ed85753c5199aa317f182b8/tests/binary/traits/no_jvm_target.flag -------------------------------------------------------------------------------- /tests/binary/traits/src/main.rs: -------------------------------------------------------------------------------- 1 | trait Calculator { 2 | // Performs a primary calculation 3 | fn calculate(&self, a: i32, b: i32) -> i32; 4 | 5 | // Modifies internal state based on input 6 | fn accumulate(&mut self, value: i32); 7 | 8 | // Returns the current internal state 9 | fn get_value(&self) -> i32; 10 | 11 | // Returns an identifier for the calculator type 12 | fn id(&self) -> u8; 13 | } 14 | 15 | // --- First Implementation --- 16 | struct SimpleAdder { 17 | current_total: i32, 18 | } 19 | 20 | impl Calculator for SimpleAdder { 21 | fn calculate(&self, a: i32, b: i32) -> i32 { 22 | a + b // Simple addition 23 | } 24 | 25 | fn accumulate(&mut self, value: i32) { 26 | self.current_total += value; 27 | } 28 | 29 | fn get_value(&self) -> i32 { 30 | self.current_total 31 | } 32 | 33 | fn id(&self) -> u8 { 34 | 1 // Identifier for SimpleAdder 35 | } 36 | } 37 | 38 | // --- Second Implementation --- 39 | struct Multiplier { 40 | current_product: i32, 41 | } 42 | 43 | impl Calculator for Multiplier { 44 | fn calculate(&self, a: i32, b: i32) -> i32 { 45 | a * b // Multiplication 46 | } 47 | 48 | fn accumulate(&mut self, value: i32) { 49 | if value == 0 { 50 | // Explicitly do nothing if value is 0, preserving the current product. 51 | return; 52 | } 53 | 54 | if self.current_product == 0 { 55 | // If the current product is 0, accumulating a non-zero value should 56 | // just set the product to that value. Avoids 0 * value = 0. 57 | self.current_product = value; 58 | } else { 59 | // Otherwise (current product is non-zero and value is non-zero), multiply. 60 | self.current_product *= value; 61 | } 62 | } 63 | 64 | 65 | fn get_value(&self) -> i32 { 66 | self.current_product 67 | } 68 | 69 | fn id(&self) -> u8 { 70 | 6 // Identifier for Multiplier 71 | } 72 | } 73 | 74 | // Takes an immutable trait object reference 75 | fn perform_calculation(calc: &dyn Calculator, x: i32, y: i32) -> i32 { 76 | calc.calculate(x, y) 77 | } 78 | 79 | // Takes a mutable trait object reference 80 | fn update_state(calc: &mut dyn Calculator, val: i32) { 81 | calc.accumulate(val); 82 | } 83 | 84 | // Checks properties via immutable trait object 85 | fn check_properties(calc: &dyn Calculator) -> (i32, u8) { 86 | (calc.get_value(), calc.id()) 87 | } 88 | 89 | 90 | fn main() { 91 | let mut adder = SimpleAdder { current_total: 10 }; 92 | 93 | // Direct calls 94 | assert!(adder.calculate(5, 3) == 8); 95 | assert!(adder.get_value() == 10); 96 | assert!(adder.id() == 1); 97 | 98 | adder.accumulate(5); 99 | assert!(adder.get_value() == 15); 100 | 101 | // Immutable Trait Object (&dyn Calculator) 102 | let adder_ref: &dyn Calculator = &adder; 103 | assert!(adder_ref.calculate(10, 20) == 30); 104 | assert!(adder_ref.get_value() == 15); // State reflects previous mutation 105 | assert!(adder_ref.id() == 1); 106 | 107 | // Pass immutable trait object to function 108 | let result1 = perform_calculation(&adder, 100, 50); 109 | assert!(result1 == 150); 110 | let (val1, id1) = check_properties(&adder); 111 | assert!(val1 == 15); 112 | assert!(id1 == 1); 113 | 114 | 115 | // Mutable Trait Object (&mut dyn Calculator) 116 | let adder_mut_ref: &mut dyn Calculator = &mut adder; 117 | adder_mut_ref.accumulate(-7); 118 | // Check state change via original variable AFTER mutable borrow ends 119 | assert!(adder.get_value() == 8); // 15 - 7 = 8 120 | 121 | // Pass mutable trait object to function 122 | update_state(&mut adder, 2); 123 | assert!(adder.get_value() == 10); // 8 + 2 = 10 124 | 125 | 126 | let mut multiplier = Multiplier { current_product: 2 }; 127 | 128 | // Direct calls 129 | assert!(multiplier.calculate(5, 3) == 15); 130 | assert!(multiplier.get_value() == 2); 131 | assert!(multiplier.id() == 6); 132 | 133 | multiplier.accumulate(4); // state becomes 2 * 4 = 8 134 | assert!(multiplier.get_value() == 8); 135 | 136 | // Immutable Trait Object (&dyn Calculator) 137 | let multiplier_ref: &dyn Calculator = &multiplier; 138 | assert!(multiplier_ref.calculate(6, 7) == 42); 139 | assert!(multiplier_ref.get_value() == 8); // State reflects previous mutation 140 | assert!(multiplier_ref.id() == 6); 141 | 142 | // Pass immutable trait object to function 143 | let result2 = perform_calculation(&multiplier, -2, 9); 144 | assert!(result2 == -18); 145 | let (val2, id2) = check_properties(&multiplier); 146 | assert!(val2 == 8); 147 | assert!(id2 == 6); 148 | 149 | // Mutable Trait Object (&mut dyn Calculator) 150 | let multiplier_mut_ref: &mut dyn Calculator = &mut multiplier; 151 | multiplier_mut_ref.accumulate(3); 152 | // Check state change via original variable AFTER mutable borrow ends 153 | assert!(multiplier.get_value() == 24); // 8 * 3 = 24 154 | 155 | // Pass mutable trait object to function 156 | update_state(&mut multiplier, -2); 157 | assert!(multiplier.get_value() == -48); // 24 * -2 = -48 158 | 159 | // Check zero accumulation behaviour 160 | update_state(&mut multiplier, 0); 161 | assert!(multiplier.get_value() == -48); // Should not change when multiplying by 0 162 | 163 | // Final check: use different trait objects in sequence 164 | let calc1: &dyn Calculator = &SimpleAdder { current_total: 100 }; 165 | let calc2: &dyn Calculator = &Multiplier { current_product: 10 }; 166 | 167 | assert!(perform_calculation(calc1, 1, 1) == 2); 168 | assert!(check_properties(calc1) == (100, 1)); 169 | 170 | assert!(perform_calculation(calc2, 2, 3) == 6); 171 | assert!(check_properties(calc2) == (10, 6)); 172 | 173 | // If we reach here without panic, the test passes 174 | } --------------------------------------------------------------------------------