commit b929dc334d51032892ab5c64b2536149ae8c821b Author: Jeehoon Kang Date: Tue Mar 17 17:31:16 2020 +0900 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..53eaa21 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +**/*.rs.bk diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..a376713 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,349 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "hermit-abi 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "backtrace" +version = "0.3.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace-sys 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "backtrace-sys" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cc" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "clap" +version = "2.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "yaml-rust 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "either" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "failure" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace 0.3.45 (registry+https://github.com/rust-lang/crates.io-index)", + "failure_derive 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "failure_derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", + "synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "getrandom" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", + "wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "hermit-abi" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "itertools" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "kecc" +version = "0.1.0" +dependencies = [ + "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lang-c 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "lang-c" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.2.67" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "ppv-lite86" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "proc-macro2" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "quote" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "redox_syscall" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "remove_dir_all" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "syn" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "synstructure" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tempfile" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", + "remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-width" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unicode-xid" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "vec_map" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "yaml-rust" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +"checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +"checksum backtrace 0.3.45 (registry+https://github.com/rust-lang/crates.io-index)" = "ad235dabf00f36301792cfe82499880ba54c6486be094d1047b02bacb67c14e8" +"checksum backtrace-sys 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)" = "ca797db0057bae1a7aa2eef3283a874695455cecf08a43bfb8507ee0ebc1ed69" +"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +"checksum cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)" = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd" +"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" +"checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" +"checksum failure 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "b8529c2421efa3066a5cbd8063d2244603824daccb6936b079010bb2aa89464b" +"checksum failure_derive 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "030a733c8287d6213886dd487564ff5c8f6aae10278b3588ed177f9d18f8d231" +"checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" +"checksum hermit-abi 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1010591b26bbfe835e9faeabeb11866061cc7dcebffd56ad7d0942d0e61aefd8" +"checksum itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484" +"checksum lang-c 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "43d8e04e01e7e22312294e6aaa1e121192b103abf9408800fc20ee85c67ccc0f" +"checksum libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)" = "eb147597cdf94ed43ab7a9038716637d2d1bf2bc571da995d0028dec06bd3018" +"checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" +"checksum proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435" +"checksum quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f" +"checksum rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +"checksum rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +"checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +"checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" +"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" +"checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" +"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +"checksum syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)" = "123bd9499cfb380418d509322d7a6d52e5315f064fe4b3ad18a53d6b92c07859" +"checksum synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" +"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" +"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +"checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" +"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" +"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" +"checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" +"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" +"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +"checksum yaml-rust 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e66366e18dc58b46801afbf2ca7661a9f59cc8c5962c29892b6039b4f86fa992" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..eb926c6 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "kecc" +version = "0.1.0" +authors = ["Chunmyong Park ", "Jeehoon Kang ", "Hyunsu Kim "] +edition = "2018" +default-run = "kecc" + +description = "KAIST Educational C Compiler" +homepage = "https://github.com/kaist-cp/kecc" +repository = "https://github.com/kaist-cp/kecc" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +name = "kecc" +path = "src/lib.rs" + +[[bin]] +name = "kecc" +path = "bin/kecc.rs" + +[[bin]] +name = "fuzz" +path = "bin/fuzz.rs" + +[dependencies] +clap = { version = "2.33.0", features = ["yaml"] } +lang-c = "0.7.0" +itertools = "0.8" +failure = "0.1.6" +tempfile = "3.1.0" diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..f2b6599 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,43 @@ +def setupRust() { + sh "rustup component add rustfmt clippy" + sh "rustup install nightly" + sh "cargo update" + sh "cargo" +} + +pipeline { + agent { + docker { + image 'rust:latest' + } + } + + stages { + stage('Rustfmt') { + steps { + setupRust() + sh "cargo fmt --all -- --check" + } + } + stage('Clippy') { + steps { + setupRust() + sh "cargo clippy --all" + } + } + stage('Build') { + steps { + setupRust() + sh "cargo build" + sh "cargo build --release" + } + } + stage('Test') { + steps { + setupRust() + sh "cargo test" + sh "cargo test --release" + } + } + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..705a10d --- /dev/null +++ b/README.md @@ -0,0 +1,63 @@ +# KECC: KAIST Educational C Compiler + +## Install + +Install [rustup](https://rustup.rs/). + + +## Build + +```sh +cargo build # debug build +cargo build --release # release build +``` + + +## Run + +```sh +cargo run -- -h # print options +cargo run -- -p examples/fibonacci.c # parse +cargo run -- -i examples/fibonacci.c # irgen +cargo run -- examples/fibonacci.c # compile + +cargo run --release -- examples/fibonacci.c # compile with release build +``` + + +## Test + +``` +cargo test # debug build test +cargo test --release # release build test + +cargo test # run a particular test +``` + +`` can be `test_ast_print`, `ir_smoke`, ... + + +## Fuzzing + +### Install + +```sh +# Ubuntu 18.04 or higher +apt install -y make cmake python3 + +# MacOS +xcode-select install +brew install cmake python3 +``` + +### Run + +The following script generates 10 random test cases and tests your C AST printer: + +```sh +python3 tests/fuzz.py --help # print options +python3 tests/fuzz.py --print -n10 # test C AST printer for 10 times +``` + +We use [Csmith](https://embed.cs.utah.edu/csmith/) to randomly generate C source codes. Csmith will +be automatically downloaded and built by the test script. diff --git a/bin/fuzz.rs b/bin/fuzz.rs new file mode 100644 index 0000000..a1a4b18 --- /dev/null +++ b/bin/fuzz.rs @@ -0,0 +1,26 @@ +#[macro_use] +extern crate clap; +use clap::{crate_authors, crate_description, crate_version, App}; + +#[macro_use] +extern crate kecc; + +use kecc::{Parse, Translate}; + +fn main() { + let yaml = load_yaml!("fuzz_cli.yml"); + #[allow(deprecated)] + let matches = App::from_yaml(yaml) + .version(crate_version!()) + .about(crate_description!()) + .author(crate_authors!(", ")) + .get_matches(); + + let input = matches.value_of("INPUT").unwrap(); + let unit = ok_or_exit!(Parse::default().translate(&input), 1); + + if matches.is_present("print") { + kecc::write_c_test(&unit); + return; + } +} diff --git a/bin/fuzz_cli.yml b/bin/fuzz_cli.yml new file mode 100644 index 0000000..7b15e7a --- /dev/null +++ b/bin/fuzz_cli.yml @@ -0,0 +1,10 @@ +name: fuzz +args: + - print: + short: p + long: print + help: Fuzzes C AST printer + - INPUT: + help: Sets the input file to use + required: true + index: 1 diff --git a/bin/kecc.rs b/bin/kecc.rs new file mode 100644 index 0000000..ca2ec6e --- /dev/null +++ b/bin/kecc.rs @@ -0,0 +1,52 @@ +#[macro_use] +extern crate clap; +use clap::{crate_authors, crate_description, crate_version, App}; + +#[macro_use] +extern crate kecc; + +use kecc::{Codegen, Irgen, Optimize, Parse, Translate, O1}; + +fn main() { + let yaml = load_yaml!("kecc_cli.yml"); + #[allow(deprecated)] + let matches = App::from_yaml(yaml) + .version(crate_version!()) + .about(crate_description!()) + .author(crate_authors!(", ")) + .get_matches(); + + let input = matches.value_of("INPUT").unwrap(); + let unit = ok_or_exit!(Parse::default().translate(&input), 1); + + let output = matches.value_of("output").unwrap_or_else(|| "-"); + let mut output: Box = if output == "-" { + Box::new(::std::io::stdout()) + } else { + Box::new(ok_or_exit!(::std::fs::File::open(output), 1)) + }; + + if matches.is_present("print") { + kecc::write_c(&unit, &mut output).unwrap(); + return; + } + + let mut ir = match Irgen::default().translate(&unit) { + Ok(ir) => ir, + Err(irgen_error) => { + println!("{}", irgen_error); + return; + } + }; + if matches.is_present("irgen") { + kecc::write_ir(&ir, &mut output).unwrap(); + return; + } + + if matches.is_present("optimize") { + O1::default().optimize(&mut ir); + } + + let asm = ok_or_exit!(Codegen::default().translate(&ir), 1); + kecc::write_asm(&asm, &mut output); +} diff --git a/bin/kecc_cli.yml b/bin/kecc_cli.yml new file mode 100644 index 0000000..5537f9d --- /dev/null +++ b/bin/kecc_cli.yml @@ -0,0 +1,24 @@ +name: kecc +args: + - print: + short: p + long: print + help: Prints the input file's AST + - irgen: + short: i + long: irgen + help: Generates IR + - optimize: + short: O + long: optimize + help: Optimizes IR + - output: + short: o + long: output + value_name: FILE + help: Sets the output file to use + takes_value: true + - INPUT: + help: Sets the input file to use + required: true + index: 1 diff --git a/bors.toml b/bors.toml new file mode 100644 index 0000000..2daddc9 --- /dev/null +++ b/bors.toml @@ -0,0 +1,2 @@ +status = [ "continuous-integration/jenkins/branch" ] +delete_merged_branches = true diff --git a/examples/alignof.c b/examples/alignof.c new file mode 100644 index 0000000..6e12fb7 --- /dev/null +++ b/examples/alignof.c @@ -0,0 +1,3 @@ +int main() { + return _Alignof(const int); +} diff --git a/examples/bar.c b/examples/bar.c new file mode 100644 index 0000000..140aad2 --- /dev/null +++ b/examples/bar.c @@ -0,0 +1,6 @@ +int bar(int x, int y, int z){ + int arith_mean = (x + y + z) / 3; + int ugly_mean = (((x + y) / 2) * 2 + z) / 3; + if (x == y) { return y; } + else { return z; } +} diff --git a/examples/comma.c b/examples/comma.c new file mode 100644 index 0000000..ddb618c --- /dev/null +++ b/examples/comma.c @@ -0,0 +1,6 @@ +int main() +{ + int y = 2; + int x = (y += 2, 2, y + 3); + return x; +} diff --git a/examples/cond.c b/examples/cond.c new file mode 100644 index 0000000..900be5e --- /dev/null +++ b/examples/cond.c @@ -0,0 +1,6 @@ +int main() +{ + int y = 1; + int x = 0; + return (x == y) ? 2 : 5; +} diff --git a/examples/fib2.c b/examples/fib2.c new file mode 100644 index 0000000..61f1d78 --- /dev/null +++ b/examples/fib2.c @@ -0,0 +1,7 @@ +int (fibonacci)(int n) { + if (n < 2) { + return n; + } + + return fibonacci(n - 2) + fibonacci(n - 1); +} diff --git a/examples/fib3.c b/examples/fib3.c new file mode 100644 index 0000000..a2aa1f7 --- /dev/null +++ b/examples/fib3.c @@ -0,0 +1,20 @@ +int fibonacci(int n) { + int i = 0; + int t1 = 0, t2 = 1, next_term = 0; + + if (n < 2) { + return n; + } + + for (i = 1; i < n; ++i) { + next_term = t1 + t2; + t1 = t2; + t2 = next_term; + } + + return t2; +} + +int main() { + return fibonacci(9); +} diff --git a/examples/fib4.c b/examples/fib4.c new file mode 100644 index 0000000..ecd45ed --- /dev/null +++ b/examples/fib4.c @@ -0,0 +1,22 @@ +int fibonacci(int n) { + int i = 0; + int t1 = 0, t2 = 1, next_term = 0; + + if (n < 2) { + return n; + } + + i = 1; + while (i < n) { + next_term = t1 + t2; + t1 = t2; + t2 = next_term; + ++i; + } + + return t2; +} + +int main() { + return fibonacci(9); +} diff --git a/examples/fib5.c b/examples/fib5.c new file mode 100644 index 0000000..534c526 --- /dev/null +++ b/examples/fib5.c @@ -0,0 +1,22 @@ +int fibonacci(int n) { + int i = 0; + int t1 = 0, t2 = 1, next_term = 0; + + if (n < 2) { + return n; + } + + i = 1; + do { + next_term = t1 + t2; + t1 = t2; + t2 = next_term; + ++i; + } while (i < n); + + return t2; +} + +int main() { + return fibonacci(9); +} diff --git a/examples/fibonacci.c b/examples/fibonacci.c new file mode 100644 index 0000000..f0767de --- /dev/null +++ b/examples/fibonacci.c @@ -0,0 +1,11 @@ +int fibonacci(int n) { + if (n < 2) { + return n; + } + + return fibonacci(n - 2) + fibonacci(n - 1); +} + +int main() { + return fibonacci(9); +} diff --git a/examples/foo.c b/examples/foo.c new file mode 100644 index 0000000..b831a1a --- /dev/null +++ b/examples/foo.c @@ -0,0 +1,8 @@ +int foo(int x, int y, int z){ + if (x == y) { return y; } + else { return z; } +} + +int main() { + return foo(0, 1, -1); +} diff --git a/examples/foo2.c b/examples/foo2.c new file mode 100644 index 0000000..801ffdd --- /dev/null +++ b/examples/foo2.c @@ -0,0 +1,9 @@ +int main() { + int i = 0; + for (int i = 0; i < 10; ++i) { + int i = 0; + int k = 0; + } + + return 0; +} diff --git a/examples/foo3.c b/examples/foo3.c new file mode 100644 index 0000000..a5ebf07 --- /dev/null +++ b/examples/foo3.c @@ -0,0 +1,13 @@ +int g = 10; + +int foo(int, int k); + +int main() { + int i = g; + + return foo(i, i); +} + +int foo(int i, int j) { + return i + j + g; +} diff --git a/examples/foo4.c b/examples/foo4.c new file mode 100644 index 0000000..c690400 --- /dev/null +++ b/examples/foo4.c @@ -0,0 +1,15 @@ +int foo(int i, int j, int k) { + return i + j + k; +} + +int (* foo2())(int, int, int){ + return foo; +} + +int (* (* foo3())())(int, int, int){ + return foo2; +} + +int main() { + return foo3()()(2, 2, 2); +} diff --git a/examples/negate.c b/examples/negate.c new file mode 100644 index 0000000..25356be --- /dev/null +++ b/examples/negate.c @@ -0,0 +1,8 @@ +int foo(int x, int y, int z){ + if (!(x == y)) { return y; } + else { return z; } +} + +int main() { + return foo(0, 1, -1); +} diff --git a/examples/pointer.c b/examples/pointer.c new file mode 100644 index 0000000..39067cd --- /dev/null +++ b/examples/pointer.c @@ -0,0 +1,15 @@ +int* foo(int *a){ + return a; +} + +int main(){ + int a = 1; + int *p = &a; + int **p2 = &*&p; + int *p3 = *&p; + + *&*foo(*p2) += 1; + *foo(p3) += 1; + + return a; +} diff --git a/examples/return_void.c b/examples/return_void.c new file mode 100644 index 0000000..93f284c --- /dev/null +++ b/examples/return_void.c @@ -0,0 +1,7 @@ +void foo() { +} + +int main() { + foo(); + return 0; +} diff --git a/examples/simple.c b/examples/simple.c new file mode 100644 index 0000000..a382e8e --- /dev/null +++ b/examples/simple.c @@ -0,0 +1,4 @@ +int main() +{ + int x = 1; +} diff --git a/examples/simple_for.c b/examples/simple_for.c new file mode 100644 index 0000000..552fd1a --- /dev/null +++ b/examples/simple_for.c @@ -0,0 +1,9 @@ +int main() +{ + int i; + int sum = 0; + for (i = 0; i < 11; ++i) { + sum += i; + } + return sum; +} diff --git a/examples/simple_if.c b/examples/simple_if.c new file mode 100644 index 0000000..4429714 --- /dev/null +++ b/examples/simple_if.c @@ -0,0 +1,7 @@ +int (fibonacci)(int n) { + if (n < 2) { + n += 2; + } + + return fibonacci(n - 2) + fibonacci(n - 1); +} diff --git a/examples/sizeof.c b/examples/sizeof.c new file mode 100644 index 0000000..2520d24 --- /dev/null +++ b/examples/sizeof.c @@ -0,0 +1,3 @@ +int main() { + return sizeof(const int); +} diff --git a/examples/switch.c b/examples/switch.c new file mode 100644 index 0000000..e0d2209 --- /dev/null +++ b/examples/switch.c @@ -0,0 +1,20 @@ +int main() { + int a = 1; + int b = 0; + switch (a) { + case 0: { + b += 1; + break; + } + case 1: { + b += 2; + break; + } + default: { + b += 3; + break; + } + } + + return b; +} diff --git a/examples/temp.c b/examples/temp.c new file mode 100644 index 0000000..6acc0f9 --- /dev/null +++ b/examples/temp.c @@ -0,0 +1,5 @@ +int fibonacci(int n) { + while (n + n) { + return n; + } +} diff --git a/examples/temp2.c b/examples/temp2.c new file mode 100644 index 0000000..fd7fbab --- /dev/null +++ b/examples/temp2.c @@ -0,0 +1,26 @@ +int f(int i, int const a[const i]) { + int temp = 0; + const float temp2 = 0.f, temp3 = 0.f; + temp = sizeof(unsigned char); + temp = _Alignof(unsigned char); + + struct color { int number; char name; } c; + c.name; + struct color *cp = &c; + cp->name; + + for(int i = 0, j = 0; i < 10; ++i) { + break; + } + + switch(temp) { + case 1: { + break; + } + default: { + break; + } + } + + return temp; +} diff --git a/rust-toolchain b/rust-toolchain new file mode 100644 index 0000000..a50908c --- /dev/null +++ b/rust-toolchain @@ -0,0 +1 @@ +1.42.0 diff --git a/scripts/make-public.py b/scripts/make-public.py new file mode 100755 index 0000000..dac0c63 --- /dev/null +++ b/scripts/make-public.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 + +"""Makes public skeleton. +""" + +import os +import subprocess +import itertools +import argparse +import sys +import re + +if __name__ == "__main__": + for fullname in os.listdir("src"): + (filename, ext) = os.path.splitext(fullname) + + if ext == ".public": + os.rename(os.path.join("src", fullname), os.path.join("src", filename)) diff --git a/src/asm.rs b/src/asm.rs new file mode 100644 index 0000000..7af62f9 --- /dev/null +++ b/src/asm.rs @@ -0,0 +1,2 @@ +/// TODO +pub struct Asm {} diff --git a/src/assert_ast_equiv.rs b/src/assert_ast_equiv.rs new file mode 100644 index 0000000..8f9a566 --- /dev/null +++ b/src/assert_ast_equiv.rs @@ -0,0 +1,543 @@ +#![allow(unused_variables)] + +use lang_c::ast::*; +use lang_c::span::Node; + +use std::ops::Deref; + +use itertools::izip; + +trait IsEquiv { + fn is_equiv(&self, other: &Self) -> bool; +} + +impl IsEquiv for Node { + fn is_equiv(&self, other: &Self) -> bool { + self.node.is_equiv(&other.node) + } +} + +impl IsEquiv for Box { + fn is_equiv(&self, other: &Self) -> bool { + self.deref().is_equiv(other.deref()) + } +} + +impl IsEquiv for &T { + fn is_equiv(&self, other: &Self) -> bool { + (*self).is_equiv(*other) + } +} + +impl IsEquiv for Option { + fn is_equiv(&self, other: &Self) -> bool { + match (self, other) { + (Some(lhs), Some(rhs)) => lhs.is_equiv(rhs), + (None, None) => true, + _ => false, + } + } +} + +impl IsEquiv for Vec { + fn is_equiv(&self, other: &Self) -> bool { + self.len() == other.len() && izip!(self, other).all(|(lhs, rhs)| lhs.is_equiv(rhs)) + } +} + +impl IsEquiv for TranslationUnit { + fn is_equiv(&self, other: &Self) -> bool { + self.0.is_equiv(&other.0) + } +} + +impl IsEquiv for ExternalDeclaration { + fn is_equiv(&self, other: &Self) -> bool { + match (self, other) { + (Self::Declaration(decl), Self::Declaration(other_decl)) => decl.is_equiv(other_decl), + (Self::FunctionDefinition(fdef), Self::FunctionDefinition(other_fdef)) => { + fdef.is_equiv(other_fdef) + } + _ => false, + } + } +} + +impl IsEquiv for Declaration { + fn is_equiv(&self, other: &Self) -> bool { + self.specifiers.is_equiv(&other.specifiers) && self.declarators.is_equiv(&other.declarators) + } +} + +impl IsEquiv for FunctionDefinition { + fn is_equiv(&self, other: &Self) -> bool { + self.specifiers.is_equiv(&other.specifiers) + && self.declarator.is_equiv(&other.declarator) + && self.declarations.is_equiv(&other.declarations) + && self.statement.is_equiv(&other.statement) + } +} + +impl IsEquiv for InitDeclarator { + fn is_equiv(&self, other: &Self) -> bool { + self.declarator.is_equiv(&other.declarator) && self.initializer.is_equiv(&other.initializer) + } +} + +impl IsEquiv for Initializer { + fn is_equiv(&self, other: &Self) -> bool { + match (self, other) { + (Self::Expression(expr), Self::Expression(other_expr)) => expr.is_equiv(other_expr), + _ => false, + } + } +} + +impl IsEquiv for Declarator { + fn is_equiv(&self, other: &Self) -> bool { + self.kind.is_equiv(&other.kind) && self.derived.is_equiv(&other.derived) + } +} + +impl IsEquiv for DeclaratorKind { + fn is_equiv(&self, other: &Self) -> bool { + match (self, other) { + (Self::Identifier(identifier), Self::Identifier(other_identifier)) => { + identifier.node.name == other_identifier.node.name + } + (Self::Declarator(decl), Self::Declarator(other_decl)) => decl.is_equiv(&other_decl), + _ => false, + } + } +} + +impl IsEquiv for DerivedDeclarator { + fn is_equiv(&self, other: &Self) -> bool { + match (self, other) { + (Self::Pointer(pointer_qualifiers), Self::Pointer(other_pointer_qualifiers)) => { + pointer_qualifiers.is_equiv(other_pointer_qualifiers) + } + (Self::Array(array_decl), Self::Array(other_array_decl)) => { + let array_decl = &array_decl.node; + let other_array_decl = &other_array_decl.node; + + array_decl.qualifiers.is_equiv(&other_array_decl.qualifiers) + && array_decl.size.is_equiv(&other_array_decl.size) + } + (Self::Function(func_decl), Self::Function(other_func_decl)) => { + let params = &func_decl.node.parameters; + let other_params = &other_func_decl.node.parameters; + params.is_equiv(other_params) + } + (Self::KRFunction(kr_func_decl), Self::KRFunction(other_kr_func_decl)) => { + kr_func_decl.is_equiv(&other_kr_func_decl) + } + _ => false, + } + } +} + +impl IsEquiv for PointerQualifier { + fn is_equiv(&self, other: &Self) -> bool { + match (self, other) { + (Self::TypeQualifier(type_qualifier), Self::TypeQualifier(other_type_qualifier)) => { + type_qualifier.is_equiv(other_type_qualifier) + } + _ => false, + } + } +} + +impl IsEquiv for ArraySize { + fn is_equiv(&self, other: &Self) -> bool { + match (self, other) { + (Self::Unknown, Self::Unknown) => true, + (Self::VariableUnknown, Self::VariableUnknown) => true, + (Self::VariableExpression(expr), Self::VariableExpression(other_expr)) => { + expr.is_equiv(&other_expr) + } + (Self::StaticExpression(expr), Self::StaticExpression(other_expr)) => { + expr.is_equiv(&other_expr) + } + _ => false, + } + } +} + +impl IsEquiv for ParameterDeclaration { + fn is_equiv(&self, other: &Self) -> bool { + self.specifiers.is_equiv(&other.specifiers) + && self + .declarator + .as_ref() + .map(|d| &d.node) + .is_equiv(&other.declarator.as_ref().map(|d| &d.node)) + } +} + +impl IsEquiv for Statement { + fn is_equiv(&self, other: &Self) -> bool { + match (self, other) { + (Self::Labeled(stmt), Self::Labeled(other_stmt)) => { + stmt.node.label.is_equiv(&other_stmt.node.label) + && stmt.node.statement.is_equiv(&other_stmt.node.statement) + } + (Self::Compound(items), Self::Compound(other_items)) => items.is_equiv(other_items), + (Self::Expression(expr), Self::Expression(other_expr)) => { + expr.as_ref().is_equiv(&other_expr.as_ref()) + } + (Self::If(stmt), Self::If(other_stmt)) => { + let else_stmt = stmt.node.else_statement.as_ref(); + let other_else_stmt = other_stmt.node.else_statement.as_ref(); + stmt.node.condition.is_equiv(&other_stmt.node.condition) + && stmt + .node + .then_statement + .is_equiv(&other_stmt.node.then_statement) + && else_stmt.is_equiv(&other_else_stmt) + } + (Self::Switch(stmt), Self::Switch(other_stmt)) => { + stmt.node.expression.is_equiv(&other_stmt.node.expression) + && stmt.node.statement.is_equiv(&other_stmt.node.statement) + } + (Self::While(stmt), Self::While(other_stmt)) => { + stmt.node.expression.is_equiv(&other_stmt.node.expression) + && stmt.node.statement.is_equiv(&other_stmt.node.statement) + } + (Self::DoWhile(stmt), Self::DoWhile(other_stmt)) => { + stmt.node.statement.is_equiv(&other_stmt.node.statement) + && stmt.node.expression.is_equiv(&other_stmt.node.expression) + } + (Self::For(stmt), Self::For(other_stmt)) => { + stmt.node.initializer.is_equiv(&other_stmt.node.initializer) + && stmt + .node + .condition + .as_ref() + .is_equiv(&other_stmt.node.condition.as_ref()) + && stmt + .node + .step + .as_ref() + .is_equiv(&other_stmt.node.step.as_ref()) + && stmt.node.statement.is_equiv(&other_stmt.node.statement) + } + (Self::Goto(label), Self::Goto(other_label)) => label.is_equiv(other_label), + (Self::Continue, Self::Continue) => true, + (Self::Break, Self::Break) => true, + (Self::Return(expr), Self::Return(other_expr)) => expr.is_equiv(other_expr), + _ => false, + } + } +} + +impl IsEquiv for Label { + fn is_equiv(&self, other: &Self) -> bool { + match (self, other) { + (Self::Identifier(ident), Self::Identifier(other_ident)) => ident.is_equiv(other_ident), + (Self::Case(expr), Self::Case(other_expr)) => expr.is_equiv(other_expr), + (Self::Default, Self::Default) => true, + _ => false, + } + } +} + +impl IsEquiv for Identifier { + fn is_equiv(&self, other: &Self) -> bool { + self.name == other.name + } +} + +impl IsEquiv for ForInitializer { + fn is_equiv(&self, other: &Self) -> bool { + match (self, other) { + (Self::Empty, Self::Empty) => true, + (Self::Expression(expr), Self::Expression(other_expr)) => expr.is_equiv(other_expr), + (Self::Declaration(decl), Self::Declaration(other_decl)) => decl.is_equiv(other_decl), + _ => false, + } + } +} + +impl IsEquiv for Expression { + fn is_equiv(&self, other: &Self) -> bool { + match (self, other) { + (Self::Identifier(identifier), Self::Identifier(other_identifier)) => { + identifier.is_equiv(other_identifier) + } + (Self::Constant(constant), Self::Constant(other_constant)) => { + constant.is_equiv(other_constant) + } + (Self::StringLiteral(other_string_lit), Self::StringLiteral(string_lit)) => { + string_lit.is_equiv(other_string_lit) + } + (Self::Member(member), Self::Member(other_member)) => member.is_equiv(other_member), + (Self::Call(call), Self::Call(other_call)) => call.is_equiv(other_call), + (Self::SizeOf(typename), Self::SizeOf(other_typename)) => { + typename.is_equiv(other_typename) + } + (Self::AlignOf(typename), Self::AlignOf(other_typename)) => { + typename.is_equiv(other_typename) + } + (Self::UnaryOperator(unary), Self::UnaryOperator(other_unary)) => { + unary.node.operator.is_equiv(&other_unary.node.operator) + && unary.node.operand.is_equiv(&other_unary.node.operand) + } + (Self::Cast(cast), Self::Cast(other_cast)) => { + cast.node.type_name.is_equiv(&other_cast.node.type_name) + && cast.node.expression.is_equiv(&other_cast.node.expression) + } + (Self::BinaryOperator(binary), Self::BinaryOperator(other_binary)) => { + binary.node.lhs.is_equiv(&other_binary.node.lhs) + && binary.node.operator.is_equiv(&other_binary.node.operator) + && binary.node.rhs.is_equiv(&other_binary.node.rhs) + } + (Self::Conditional(conditional), Self::Conditional(other_conditional)) => { + conditional + .node + .condition + .is_equiv(&other_conditional.node.condition) + && conditional + .node + .then_expression + .is_equiv(&other_conditional.node.then_expression) + && conditional + .node + .else_expression + .is_equiv(&other_conditional.node.else_expression) + } + (Self::Comma(exprs), Self::Comma(other_exprs)) => { + exprs.as_ref().is_equiv(other_exprs.as_ref()) + } + _ => false, + } + } +} + +impl IsEquiv for TypeName { + fn is_equiv(&self, other: &Self) -> bool { + self.specifiers.is_equiv(&other.specifiers) && self.declarator.is_equiv(&other.declarator) + } +} + +impl IsEquiv for SpecifierQualifier { + fn is_equiv(&self, other: &Self) -> bool { + match (self, other) { + (Self::TypeSpecifier(type_specifier), Self::TypeSpecifier(other_type_specifier)) => { + type_specifier.is_equiv(other_type_specifier) + } + + (Self::TypeQualifier(type_qualifier), Self::TypeQualifier(other_type_qualifier)) => { + type_qualifier.is_equiv(other_type_qualifier) + } + _ => false, + } + } +} + +impl IsEquiv for MemberExpression { + fn is_equiv(&self, other: &Self) -> bool { + self.expression.is_equiv(&other.expression) + && self.operator.is_equiv(&other.operator) + && self.identifier.is_equiv(&other.identifier) + } +} + +impl IsEquiv for MemberOperator { + fn is_equiv(&self, other: &Self) -> bool { + self == other + } +} + +impl IsEquiv for UnaryOperator { + fn is_equiv(&self, other: &Self) -> bool { + self == other + } +} + +impl IsEquiv for BinaryOperator { + fn is_equiv(&self, other: &Self) -> bool { + self == other + } +} + +impl IsEquiv for Constant { + fn is_equiv(&self, other: &Self) -> bool { + match (self, other) { + (Self::Integer(integer), Self::Integer(other_integer)) => { + integer.base.is_equiv(&other_integer.base) + && integer.number == other_integer.number + && integer.suffix.is_equiv(&other_integer.suffix) + } + (Self::Float(float), Self::Float(other_float)) => { + float.base == other_float.base + && float.number == other_float.number + && float.suffix.is_equiv(&other_float.suffix) + } + (Self::Character(literal), Self::Character(other_literal)) => literal == other_literal, + _ => false, + } + } +} + +impl IsEquiv for IntegerBase { + fn is_equiv(&self, other: &Self) -> bool { + self == other + } +} + +impl IsEquiv for IntegerSuffix { + fn is_equiv(&self, other: &Self) -> bool { + self.unsigned == other.unsigned && self.size == other.size + } +} + +impl IsEquiv for FloatSuffix { + fn is_equiv(&self, other: &Self) -> bool { + self.imaginary == other.imaginary && self.format == other.format + } +} + +impl IsEquiv for StringLiteral { + fn is_equiv(&self, other: &Self) -> bool { + self == other + } +} + +impl IsEquiv for BlockItem { + fn is_equiv(&self, other: &Self) -> bool { + match (self, other) { + (Self::Declaration(decl), Self::Declaration(other_decl)) => decl.is_equiv(other_decl), + (Self::Statement(statement), Self::Statement(other_statement)) => { + statement.is_equiv(other_statement) + } + _ => false, + } + } +} + +impl IsEquiv for DeclarationSpecifier { + fn is_equiv(&self, other: &Self) -> bool { + match (self, other) { + ( + Self::StorageClass(storage_class_spec), + Self::StorageClass(other_storage_class_spec), + ) => storage_class_spec.is_equiv(other_storage_class_spec), + (Self::TypeSpecifier(type_specifier), Self::TypeSpecifier(other_type_specifier)) => { + type_specifier.is_equiv(other_type_specifier) + } + (Self::TypeQualifier(type_qualifier), Self::TypeQualifier(other_type_qualifier)) => { + type_qualifier.is_equiv(other_type_qualifier) + } + _ => false, + } + } +} + +impl IsEquiv for StorageClassSpecifier { + fn is_equiv(&self, other: &Self) -> bool { + self == other + } +} + +impl IsEquiv for TypeSpecifier { + fn is_equiv(&self, other: &Self) -> bool { + match (self, other) { + (Self::Void, Self::Void) => true, + (Self::Char, Self::Char) => true, + (Self::Short, Self::Short) => true, + (Self::Int, Self::Int) => true, + (Self::Long, Self::Long) => true, + (Self::Float, Self::Float) => true, + (Self::Double, Self::Double) => true, + (Self::Signed, Self::Signed) => true, + (Self::Unsigned, Self::Unsigned) => true, + (Self::Bool, Self::Bool) => true, + (Self::Struct(struct_type), Self::Struct(other_struct_type)) => { + struct_type.is_equiv(other_struct_type) + } + (Self::Enum(enum_type), Self::Enum(other_enum_type)) => { + enum_type.is_equiv(other_enum_type) + } + (Self::TypedefName(identifier), Self::TypedefName(other_identifier)) => { + identifier.is_equiv(other_identifier) + } + _ => false, + } + } +} + +impl IsEquiv for StructType { + fn is_equiv(&self, other: &Self) -> bool { + self.declarations.is_equiv(&other.declarations) + && self.kind.is_equiv(&other.kind) + && self.identifier.is_equiv(&other.identifier) + } +} + +impl IsEquiv for StructKind { + fn is_equiv(&self, other: &Self) -> bool { + self == other + } +} + +impl IsEquiv for StructDeclaration { + fn is_equiv(&self, other: &Self) -> bool { + match (self, other) { + (Self::Field(struct_field), Self::Field(other_struct_field)) => { + struct_field.is_equiv(other_struct_field) + } + _ => false, + } + } +} + +impl IsEquiv for StructField { + fn is_equiv(&self, other: &Self) -> bool { + self.specifiers.is_equiv(&other.specifiers) && self.declarators.is_equiv(&other.declarators) + } +} + +impl IsEquiv for StructDeclarator { + fn is_equiv(&self, other: &Self) -> bool { + self.declarator.is_equiv(&other.declarator) && self.bit_width.is_equiv(&other.bit_width) + } +} + +impl IsEquiv for EnumType { + fn is_equiv(&self, other: &Self) -> bool { + self.identifier.is_equiv(&other.identifier) && self.enumerators.is_equiv(&other.enumerators) + } +} + +impl IsEquiv for Enumerator { + fn is_equiv(&self, other: &Self) -> bool { + self.identifier.is_equiv(&other.identifier) && self.expression.is_equiv(&other.expression) + } +} + +impl IsEquiv for TypeQualifier { + fn is_equiv(&self, other: &Self) -> bool { + match (self, other) { + (Self::Const, Self::Const) => true, + _ => false, + } + } +} + +impl IsEquiv for CallExpression { + fn is_equiv(&self, other: &Self) -> bool { + self.callee.is_equiv(&other.callee) && self.arguments.is_equiv(&other.arguments) + } +} + +pub fn assert_ast_equiv(lhs: &TranslationUnit, rhs: &TranslationUnit) { + if !lhs.is_equiv(rhs) { + panic!( + r#"assertion failed: `(left.is_equiv(right))` + left: `{:?}`, + right: `{:?}`"#, + lhs, rhs + ) + } +} diff --git a/src/codegen.rs b/src/codegen.rs new file mode 100644 index 0000000..20ee581 --- /dev/null +++ b/src/codegen.rs @@ -0,0 +1,15 @@ +use crate::asm::Asm; +use crate::ir; +use crate::Translate; + +#[derive(Default)] +pub struct Codegen {} + +impl Translate for Codegen { + type Target = Asm; + type Error = (); + + fn translate(&mut self, _source: &ir::TranslationUnit) -> Result { + unimplemented!() + } +} diff --git a/src/ir.rs b/src/ir.rs new file mode 100644 index 0000000..bb8e052 --- /dev/null +++ b/src/ir.rs @@ -0,0 +1,1096 @@ +use core::fmt; +use itertools::izip; +use lang_c::ast; +use lang_c::span::Node; +use std::collections::HashMap; +use std::hash::{Hash, Hasher}; +use std::ops::Deref; + +use failure::Fail; + +pub trait HasDtype { + fn dtype(&self) -> Dtype; +} + +#[derive(Debug, PartialEq, Fail)] +pub enum DtypeError { + /// For uncommon error + #[fail(display = "{}", message)] + Misc { message: String }, +} + +#[derive(Debug, PartialEq)] +pub struct TranslationUnit { + pub decls: HashMap, +} + +#[derive(Debug, PartialEq, Clone)] +pub enum Declaration { + Variable { + dtype: Dtype, + initializer: Option, + }, + Function { + signature: FunctionSignature, + definition: Option, + }, +} + +impl Declaration { + /// Create an appropriate declaration according to `dtype`. + /// + /// # Example + /// + /// If `int g = 0;` is declared, `dtype` is + /// `ir::Dtype::Int{ width:32, is_signed:true, is_const:false }`. + /// In this case, `ir::Declaration::Variable{ dtype, initializer: Some(Constant::I32(1)) }` + /// is generated. + /// + /// Conversely, if `int foo();` is declared, `dtype` is + /// `ir::Dtype::Function{ret: Scalar(Int), params: []}`. + /// Thus, in this case, `ir::Declaration::Function` is generated. + pub fn from_dtype(dtype: Dtype) -> Result { + match &dtype { + Dtype::Unit { .. } => Err(DtypeError::Misc { + message: "storage size of `void` isn't known".to_string(), + }), + Dtype::Int { .. } | Dtype::Float { .. } | Dtype::Pointer { .. } => { + Ok(Declaration::Variable { + dtype, + initializer: None, + }) + } + Dtype::Function { .. } => Ok(Declaration::Function { + signature: FunctionSignature::new(dtype), + definition: None, + }), + } + } + + pub fn get_variable(&self) -> Option<(&Dtype, &Option)> { + if let Self::Variable { dtype, initializer } = self { + Some((dtype, initializer)) + } else { + None + } + } + + pub fn get_function(&self) -> Option<(&FunctionSignature, &Option)> { + if let Self::Function { + signature, + definition, + } = self + { + Some((signature, definition)) + } else { + None + } + } + + /// Check if type is conflicting for pre-declared one + /// + /// In case of `Variable`, need to check if the two types are exactly the same. + /// On the other hand, in the case of `Function`, outermost `const` of return type and + /// parameters one is not an issue of concern. + pub fn is_compatible(&self, other: &Declaration) -> bool { + match (self, other) { + (Self::Variable { dtype, .. }, Self::Variable { dtype: other, .. }) => dtype == other, + ( + Self::Function { signature, .. }, + Self::Function { + signature: other, .. + }, + ) => signature.dtype().is_compatible(&other.dtype()), + _ => false, + } + } +} + +impl HasDtype for Declaration { + fn dtype(&self) -> Dtype { + match self { + Self::Variable { dtype, .. } => dtype.clone(), + Self::Function { signature, .. } => signature.dtype(), + } + } +} + +#[derive(Debug, PartialEq, Clone)] +pub struct FunctionDefinition { + /// Element that must allocate memory before a function can be executed + pub allocations: Vec, + pub blocks: HashMap, + pub bid_init: BlockId, +} + +#[derive(Debug, PartialEq, Clone)] +pub struct FunctionSignature { + pub ret: Dtype, + pub params: Vec, +} + +impl FunctionSignature { + pub fn new(dtype: Dtype) -> Self { + let (ret, params) = dtype + .get_function_inner() + .expect("function signature's dtype must be function type"); + Self { + ret: ret.clone(), + params: params.clone(), + } + } +} + +impl HasDtype for FunctionSignature { + fn dtype(&self) -> Dtype { + Dtype::function(self.ret.clone(), self.params.clone()) + } +} + +#[derive(Default)] +struct Specifier { + scalar: Option, + signed_option: Option, + is_const: bool, +} + +impl Specifier { + #[inline] + fn analyze_ast_type_specifiers( + &mut self, + type_specifier: &ast::TypeSpecifier, + ) -> Result<(), DtypeError> { + match type_specifier { + ast::TypeSpecifier::Unsigned | ast::TypeSpecifier::Signed => { + if self.signed_option.is_some() { + return Err(DtypeError::Misc { + message: "duplicate signed option".to_string(), + }); + } + self.signed_option = Some(type_specifier.clone()); + } + ast::TypeSpecifier::Void + | ast::TypeSpecifier::Char + | ast::TypeSpecifier::Int + | ast::TypeSpecifier::Float => { + if self.scalar.is_some() { + return Err(DtypeError::Misc { + message: "two or more scalar types in declaration specifiers".to_string(), + }); + } + self.scalar = Some(type_specifier.clone()); + } + _ => todo!("support more like `double` in the future"), + } + + Ok(()) + } + + #[inline] + fn analyze_ast_type_qualifier( + &mut self, + type_qualifier: &ast::TypeQualifier, + ) -> Result<(), DtypeError> { + match type_qualifier { + ast::TypeQualifier::Const => { + // duplicate `const` is allowed + self.is_const = true; + } + _ => panic!("type qualifier is unsupported except `const`"), + } + + Ok(()) + } + + pub fn from_ast_typename_specifier( + &mut self, + typename_specifier: &ast::SpecifierQualifier, + ) -> Result<(), DtypeError> { + match typename_specifier { + ast::SpecifierQualifier::TypeSpecifier(type_specifier) => { + self.analyze_ast_type_specifiers(&type_specifier.node)? + } + ast::SpecifierQualifier::TypeQualifier(type_qualifier) => { + self.analyze_ast_type_qualifier(&type_qualifier.node)? + } + } + + Ok(()) + } + + pub fn from_ast_declaration_specifier( + &mut self, + declaration_specifier: &ast::DeclarationSpecifier, + ) -> Result<(), DtypeError> { + match declaration_specifier { + // TODO: `dtype` must be defined taking into account all specifier information. + ast::DeclarationSpecifier::StorageClass(_storage_class_spec) => { + todo!("analyze storage class specifier keyword to create correct `dtype`") + } + ast::DeclarationSpecifier::TypeSpecifier(type_specifier) => { + self.analyze_ast_type_specifiers(&type_specifier.node)? + } + ast::DeclarationSpecifier::TypeQualifier(type_qualifier) => { + self.analyze_ast_type_qualifier(&type_qualifier.node)? + } + _ => panic!("is_unsupported"), + } + + Ok(()) + } + + pub fn from_ast_typename_specifiers( + typename_specifiers: &[Node], + ) -> Result { + let mut specifier = Specifier::default(); + + for ast_spec in typename_specifiers { + specifier.from_ast_typename_specifier(&ast_spec.node)?; + } + + Ok(specifier) + } + + pub fn from_ast_declaration_specifiers( + declaration_specifiers: &[Node], + ) -> Result { + let mut specifier = Specifier::default(); + + for ast_spec in declaration_specifiers { + specifier.from_ast_declaration_specifier(&ast_spec.node)?; + } + + Ok(specifier) + } +} + +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub enum Dtype { + Unit { + is_const: bool, + }, + Int { + width: usize, + is_signed: bool, + is_const: bool, + }, + Float { + width: usize, + is_const: bool, + }, + Pointer { + inner: Box, + is_const: bool, + }, + Function { + ret: Box, + params: Vec, + }, +} + +impl Dtype { + pub const BOOL: Self = Self::int(1); + pub const CHAR: Self = Self::int(8); + pub const SHORT: Self = Self::int(16); + pub const INT: Self = Self::int(32); + pub const LONG: Self = Self::int(64); + pub const LONGLONG: Self = Self::int(64); + + pub const FLOAT: Self = Self::float(32); + pub const DOUBLE: Self = Self::float(64); + + const WIDTH_OF_BYTE: usize = 8; + // TODO: consider architecture dependency in the future + const WIDTH_OF_POINTER: usize = 32; + + #[inline] + pub fn unit() -> Self { + Self::Unit { is_const: false } + } + + #[inline] + const fn int(width: usize) -> Self { + Self::Int { + width, + is_signed: true, + is_const: false, + } + } + + #[inline] + const fn float(width: usize) -> Self { + Self::Float { + width, + is_const: false, + } + } + + #[inline] + pub fn pointer(inner: Dtype) -> Self { + Self::Pointer { + inner: Box::new(inner), + is_const: false, + } + } + + #[inline] + pub fn function(ret: Dtype, params: Vec) -> Self { + Self::Function { + ret: Box::new(ret), + params, + } + } + + #[inline] + pub fn get_int_width(&self) -> Option { + if let Self::Int { width, .. } = self { + Some(*width) + } else { + None + } + } + + #[inline] + pub fn get_float_width(&self) -> Option { + if let Self::Float { width, .. } = self { + Some(*width) + } else { + None + } + } + + #[inline] + pub fn get_pointer_inner(&self) -> Option<&Dtype> { + if let Self::Pointer { inner, .. } = self { + Some(inner.deref()) + } else { + None + } + } + + #[inline] + pub fn get_function_inner(&self) -> Option<(&Dtype, &Vec)> { + if let Self::Function { ret, params } = self { + Some((ret.deref(), params)) + } else { + None + } + } + + #[inline] + pub fn is_scalar(&self) -> bool { + match self { + Self::Unit { .. } => todo!(), + Self::Int { .. } => true, + Self::Float { .. } => true, + _ => false, + } + } + + #[inline] + pub fn is_signed(&self) -> bool { + match self { + Self::Int { is_signed, .. } => *is_signed, + _ => panic!("only `Dtype::Int` can be judged whether it is sigend"), + } + } + + #[inline] + pub fn is_const(&self) -> bool { + match self { + Self::Unit { is_const } => *is_const, + Self::Int { is_const, .. } => *is_const, + Self::Float { is_const, .. } => *is_const, + Self::Pointer { is_const, .. } => *is_const, + Self::Function { .. } => { + panic!("there should be no case that check whether `Function` is `const`") + } + } + } + + /// Derive a data type containing scalar type from type specifier. + pub fn from_ast_type_specifier(type_specifier: &ast::TypeSpecifier) -> Self { + match type_specifier { + ast::TypeSpecifier::Void => Self::unit(), + ast::TypeSpecifier::Unsigned | ast::TypeSpecifier::Signed => { + panic!("signed option to scalar is not supported") + } + ast::TypeSpecifier::Bool => Self::BOOL, + ast::TypeSpecifier::Char => Self::CHAR, + ast::TypeSpecifier::Short => Self::SHORT, + ast::TypeSpecifier::Int => Self::INT, + ast::TypeSpecifier::Long => Self::LONG, + ast::TypeSpecifier::Float => Self::FLOAT, + ast::TypeSpecifier::Double => Self::DOUBLE, + _ => panic!("is unsupported"), + } + } + + /// Apply signed option to `Dtype`. + pub fn apply_signed_option( + self, + signed_option: &ast::TypeSpecifier, + ) -> Result { + let is_signed = match signed_option { + ast::TypeSpecifier::Signed => true, + ast::TypeSpecifier::Unsigned => false, + _ => panic!( + "`signed_option` must be `TypeSpecifier::Signed` or `TypeSpecifier::Unsigned`" + ), + }; + + match self { + Self::Unit { .. } => Err(DtypeError::Misc { + message: "`void` cannot matched with signed option".to_string(), + }), + Self::Int { + width, is_const, .. + } => Ok(Self::int(width).set_signed(is_signed).set_const(is_const)), + Self::Float { .. } => Err(DtypeError::Misc { + message: "floating point cannot matched with signed option".to_string(), + }), + Self::Pointer { .. } | Self::Function { .. } => { + panic!("cannot apply signed option to `Self::Pointer` and `Self::Function`") + } + } + } + + pub fn set_const(self, is_const: bool) -> Self { + match self { + Self::Unit { .. } => Self::Unit { is_const }, + Self::Int { + width, is_signed, .. + } => Self::Int { + width, + is_signed, + is_const, + }, + Self::Float { width, .. } => Self::Float { width, is_const }, + Self::Pointer { inner, .. } => Self::Pointer { inner, is_const }, + Self::Function { .. } => panic!("`const` cannot be applied to `Dtype::Function`"), + } + } + + /// Return byte size of `Dtype` + pub fn size_of(&self) -> Result { + // TODO: consider complex type like array, structure in the future + match self { + Self::Unit { .. } => Ok(0), + Self::Int { width, .. } => Ok(*width / Self::WIDTH_OF_BYTE), + Self::Float { width, .. } => Ok(*width / Self::WIDTH_OF_BYTE), + Self::Pointer { .. } => Ok(Self::WIDTH_OF_POINTER / Self::WIDTH_OF_BYTE), + Self::Function { .. } => Err(DtypeError::Misc { + message: "`sizeof` cannot be used with function types".to_string(), + }), + } + } + + /// Return alignment requirements of `Dtype` + pub fn align_of(&self) -> Result { + // TODO: consider complex type like array, structure in the future + // TODO: when considering complex type like a structure, + // the calculation method should be different from `Dtype::size_of`. + match self { + Self::Unit { .. } => Ok(0), + Self::Int { width, .. } => Ok(*width / Self::WIDTH_OF_BYTE), + Self::Float { width, .. } => Ok(*width / Self::WIDTH_OF_BYTE), + Self::Pointer { .. } => Ok(Self::WIDTH_OF_POINTER / Self::WIDTH_OF_BYTE), + Self::Function { .. } => Err(DtypeError::Misc { + message: "`alignof` cannot be used with function types".to_string(), + }), + } + } + + pub fn set_signed(self, is_signed: bool) -> Self { + match self { + Self::Int { + width, is_const, .. + } => Self::Int { + width, + is_signed, + is_const, + }, + _ => panic!("`signed` and `unsigned` only be applied to `Dtype::Int`"), + } + } + + /// Derive a data type from typename. + pub fn from_ast_typename(type_name: &ast::TypeName) -> Result { + let spec = Specifier::from_ast_typename_specifiers(&type_name.specifiers)?; + let base_dtype = Self::from_ast_specifiers(spec)?; + + if let Some(declarator) = &type_name.declarator { + Self::from_ast_declarator(&declarator.node, base_dtype) + } else { + Ok(base_dtype) + } + } + + /// Derive a data type from declaration specifiers. + pub fn from_ast_declaration_specifiers( + specifiers: &[Node], + ) -> Result { + let spec = Specifier::from_ast_declaration_specifiers(specifiers)?; + Self::from_ast_specifiers(spec) + } + + /// Derive a data type containing scalar type from specifiers. + /// + /// # Example + /// + /// For declaration is `const unsigned int * p`, `specifiers` is `const unsigned int`, + /// and the result is `Dtype::Int{ width: 32, is_signed: false, is_const: ture }` + fn from_ast_specifiers(spec: Specifier) -> Result { + // Generate appropriate `Dtype` object if `scalar` is `Some` + let dtype = spec.scalar.map(|t| Dtype::from_ast_type_specifier(&t)); + + // Update `dtype` obtained above or generate appropriate `Dtype` object if + let dtype = match (spec.signed_option, dtype) { + (Some(signed_option), Some(dtype)) => Some(dtype.apply_signed_option(&signed_option)?), + (Some(signed_option), None) => { + // If `signed_option` is used alone, it is used as`type_specifier`. + // For example, `signed` can be replaced with `int` if it used alone. + Some(Dtype::default().apply_signed_option(&signed_option)?) + } + (None, dtype) => dtype, + }; + + // Determining the final form of `dtype` according to the value of `is_const` + let dtype = match (dtype, spec.is_const) { + (Some(dtype), is_const) => { + assert!(!dtype.is_const()); + dtype.set_const(is_const) + } + // If type specifier missing, defaults to `int`. + // For example, `const` can be replaced with `const int` if it used alone. + (None, true) => Dtype::default().set_const(true), + (None, false) => { + panic!("at least one valid declaration specifier is needed to generate `Dtype`") + } + }; + + Ok(dtype) + } + + /// Generate `Dtype` based on pointer qualifiers and `base_dtype` which has scalar type. + /// + /// let's say declaration is `const int * const * const a;`. + /// If `base_dtype` represents `const int *`, + /// `qualifiers` represents `const` between first and second asterisk. + /// + /// The important point here is that `qualifiers` exists between asterisks and asterisks or + /// asterisks and identifiers. + /// + /// # Arguments + /// + /// * `qualifiers` - Pointer qualifiers requiring conversion to 'Dtype' immediately + /// * `base_dtype` - Part that has been converted to 'Dtype' on the declaration + /// + pub fn from_ast_pointer_qualifiers( + qualifiers: &[Node], + base_dtype: Self, + ) -> Result { + let mut specifier = Specifier::default(); + + for qualifier in qualifiers { + match &qualifier.node { + ast::PointerQualifier::TypeQualifier(type_qualifier) => { + specifier.analyze_ast_type_qualifier(&type_qualifier.node)?; + } + ast::PointerQualifier::Extension(_) => { + panic!("ast::PointerQualifier::Extension is unsupported") + } + } + } + + Ok(base_dtype.set_const(specifier.is_const)) + } + + /// Generate `Dtype` based on declarator and `base_dtype` which has scalar type. + /// + /// let's say declaration is `const int * const * const a;`. + /// In general `base_dtype` start with `const int` which has scalar type and + /// `declarator` represents `* const * const` with `ast::Declarator` + /// + /// # Arguments + /// + /// * `declarator` - Parts requiring conversion to 'Dtype' on the declaration + /// * `base_dtype` - Part that has been converted to 'Dtype' on the declaration + /// + pub fn from_ast_declarator( + declarator: &ast::Declarator, + base_dtype: Self, + ) -> Result { + let mut base_dtype = base_dtype; + + for derived_decl in &declarator.derived { + base_dtype = match &derived_decl.node { + ast::DerivedDeclarator::Pointer(pointer_qualifiers) => { + let ptr_dtype = Dtype::pointer(base_dtype); + Self::from_ast_pointer_qualifiers(pointer_qualifiers, ptr_dtype)? + } + ast::DerivedDeclarator::Array(_array_decl) => todo!(), + ast::DerivedDeclarator::Function(func_decl) => { + let params = func_decl + .node + .parameters + .iter() + .map(|p| Self::from_ast_parameter_declaration(&p.node)) + .collect::, _>>()?; + Self::function(base_dtype, params) + } + ast::DerivedDeclarator::KRFunction(kr_func_decl) => { + // K&R function is allowed only when it has no parameter + assert!(kr_func_decl.is_empty()); + Self::function(base_dtype, Vec::new()) + } + }; + } + + let declarator_kind = &declarator.kind; + match &declarator_kind.node { + ast::DeclaratorKind::Abstract => panic!("ast::DeclaratorKind::Abstract is unsupported"), + ast::DeclaratorKind::Identifier(_) => Ok(base_dtype), + ast::DeclaratorKind::Declarator(declarator) => { + Self::from_ast_declarator(&declarator.node, base_dtype) + } + } + } + + /// Generate `Dtype` based on parameter declaration + pub fn from_ast_parameter_declaration( + parameter_decl: &ast::ParameterDeclaration, + ) -> Result { + let spec = Specifier::from_ast_declaration_specifiers(¶meter_decl.specifiers)?; + let base_dtype = Self::from_ast_specifiers(spec)?; + + if let Some(declarator) = ¶meter_decl.declarator { + Self::from_ast_declarator(&declarator.node, base_dtype) + } else { + Ok(base_dtype) + } + } + + /// Check whether type conflict exists between the two `Dtype` objects. + /// + /// let's say expression is `const int a = 0; int b = 0; int c = a + b`. + /// Although `const int` of `a` and `int` of `b` looks different, `Plus`(+) operations between + /// these two types are possible without any type-casting. There is no conflict between + /// `const int` and `int`. + /// + /// However, only the outermost const is ignored. + /// If check equivalence between `const int *const` and `int *`, result is false. Because + /// the second `const` (means left most `const`) of the `const int *const` is missed in `int *`. + /// By the way, outermost `const` (means right most `const`) is not a consideration here. + pub fn is_compatible(&self, other: &Self) -> bool { + match (self, other) { + (Self::Unit { .. }, Self::Unit { .. }) + | (Self::Int { .. }, Self::Int { .. }) + | (Self::Float { .. }, Self::Float { .. }) + | (Self::Pointer { .. }, Self::Pointer { .. }) => { + self.clone().set_const(false) == other.clone().set_const(false) + } + ( + Self::Function { ret, params }, + Self::Function { + ret: other_ret, + params: other_params, + }, + ) => { + ret == other_ret + && params.len() == other_params.len() + && izip!(params, other_params).all(|(l, r)| l.is_compatible(r)) + } + _ => false, + } + } +} + +impl fmt::Display for Dtype { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Unit { is_const } => write!(f, "{}unit", if *is_const { "const " } else { "" }), + Self::Int { + width, + is_signed, + is_const, + } => write!( + f, + "{}{}{}", + if *is_const { "const " } else { "" }, + if *is_signed { "i" } else { "u" }, + width + ), + Self::Float { width, is_const } => { + write!(f, "{}f{}", if *is_const { "const " } else { "" }, width) + } + Self::Pointer { inner, is_const } => { + write!(f, "{}* {}", inner, if *is_const { "const" } else { "" }) + } + Self::Function { ret, params } => write!( + f, + "{} ({})", + ret, + params + .iter() + .map(|p| p.to_string()) + .collect::>() + .join(", ") + ), + } + } +} + +impl Default for Dtype { + fn default() -> Self { + // default dtype is `int`(i32) + Self::INT + } +} + +#[derive(Debug, Eq, Clone)] +pub enum RegisterId { + // `name` field of `Local` is unnecessary, but it is helpful when read printed IR + Local { name: String, id: usize }, + Arg { id: usize }, + Temp { bid: BlockId, iid: usize }, +} + +impl RegisterId { + pub fn local(name: String, id: usize) -> Self { + Self::Local { name, id } + } + + pub fn arg(id: usize) -> Self { + Self::Arg { id } + } + + pub fn temp(bid: BlockId, iid: usize) -> Self { + Self::Temp { bid, iid } + } +} + +impl fmt::Display for RegisterId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Local { name, id } => write!(f, "%(local:{}:{})", name, id), + Self::Arg { id } => write!(f, "%(arg:{})", id), + Self::Temp { bid, iid } => write!(f, "%({}:{})", bid, iid), + } + } +} + +impl PartialEq for RegisterId { + fn eq(&self, other: &RegisterId) -> bool { + match (self, other) { + (Self::Local { id, .. }, Self::Local { id: other_id, .. }) => id == other_id, + (Self::Arg { id }, Self::Arg { id: other_id }) => id == other_id, + ( + Self::Temp { bid, iid }, + Self::Temp { + bid: other_bid, + iid: other_iid, + }, + ) => bid == other_bid && iid == other_iid, + _ => false, + } + } +} + +impl Hash for RegisterId { + fn hash(&self, state: &mut H) { + match self { + Self::Local { id, .. } => id.hash(state), + Self::Arg { id } => id.hash(state), + Self::Temp { bid, iid } => { + bid.hash(state); + iid.hash(state); + } + } + } +} + +#[derive(Debug, PartialEq, Clone)] +pub enum Constant { + Unit, + Int { + value: u128, + width: usize, + is_signed: bool, + }, + Float { + /// `value` may be `f32`, but it is possible to consider it as `f64`. + /// + /// * Casting from an f32 to an f64 is perfect and lossless (f32 -> f64) + /// * Casting from an f64 to an f32 will produce the closest possible value (f64 -> f32) + /// https://doc.rust-lang.org/stable/reference/expressions/operator-expr.html#type-cast-expressions + value: f64, + width: usize, + }, + GlobalVariable { + name: String, + dtype: Dtype, + }, +} + +impl Constant { + #[inline] + pub fn is_integer_constant(&self) -> bool { + if let Self::Int { .. } = self { + true + } else { + false + } + } + + pub fn unit() -> Self { + Constant::Unit + } + + #[inline] + pub fn int(value: u128, dtype: Dtype) -> Self { + let width = dtype.get_int_width().expect("`dtype` must be `Dtype::Int`"); + let is_signed = dtype.is_signed(); + + Constant::Int { + value, + width, + is_signed, + } + } + + #[inline] + pub fn float(value: f64, dtype: Dtype) -> Self { + let width = dtype + .get_float_width() + .expect("`dtype` must be `Dtype::Float`"); + + Constant::Float { value, width } + } + + #[inline] + pub fn global_variable(name: String, dtype: Dtype) -> Self { + Self::GlobalVariable { name, dtype } + } + + pub fn from_ast(constant: &ast::Constant) -> Self { + match constant { + ast::Constant::Integer(integer) => { + let is_signed = !integer.suffix.unsigned; + + let dtype = match integer.suffix.size { + ast::IntegerSize::Int => Dtype::INT, + ast::IntegerSize::Long => Dtype::LONG, + ast::IntegerSize::LongLong => Dtype::LONGLONG, + } + .set_signed(is_signed); + + let value = if is_signed { + integer.number.parse::().unwrap() as u128 + } else { + integer.number.parse::().unwrap() + }; + + Self::int(value, dtype) + } + ast::Constant::Float(float) => { + let (dtype, value) = match float.suffix.format { + ast::FloatFormat::Float => { + // Casting from an f32 to an f64 is perfect and lossless (f32 -> f64) + // https://doc.rust-lang.org/stable/reference/expressions/operator-expr.html#type-cast-expressions + (Dtype::FLOAT, float.number.parse::().unwrap() as f64) + } + ast::FloatFormat::Double => { + (Dtype::DOUBLE, float.number.parse::().unwrap()) + } + ast::FloatFormat::LongDouble => { + panic!("`FloatFormat::LongDouble` is_unsupported") + } + ast::FloatFormat::TS18661Format(_) => { + panic!("`FloatFormat::TS18661Format` is_unsupported") + } + }; + + Self::float(value, dtype) + } + ast::Constant::Character(character) => { + let dtype = Dtype::CHAR; + let value = character.parse::().unwrap() as u128; + + Self::int(value, dtype) + } + } + } + + #[inline] + pub fn from_ast_expression(expr: &ast::Expression) -> Option { + if let ast::Expression::Constant(constant) = expr { + Some(Self::from_ast(&constant.node)) + } else { + None + } + } + + #[inline] + pub fn from_ast_initializer(initializer: &ast::Initializer) -> Option { + if let ast::Initializer::Expression(expr) = &initializer { + Self::from_ast_expression(&expr.node) + } else { + None + } + } +} + +impl fmt::Display for Constant { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Unit => write!(f, "unit"), + Self::Int { value, .. } => write!(f, "{}", value), + Self::Float { value, .. } => write!(f, "{}", value), + Self::GlobalVariable { name, .. } => write!(f, "%{}", name), + } + } +} + +impl HasDtype for Constant { + fn dtype(&self) -> Dtype { + match self { + Self::Unit => Dtype::unit(), + Self::Int { + width, is_signed, .. + } => Dtype::int(*width).set_signed(*is_signed), + Self::Float { width, .. } => Dtype::float(*width), + Self::GlobalVariable { dtype, .. } => Dtype::pointer(dtype.clone()), + } + } +} + +#[derive(Debug, PartialEq, Clone)] +pub enum Operand { + Constant(Constant), + Register { rid: RegisterId, dtype: Dtype }, +} + +impl Operand { + pub fn constant(value: Constant) -> Self { + Self::Constant(value) + } + + pub fn register(rid: RegisterId, dtype: Dtype) -> Self { + Self::Register { rid, dtype } + } + + pub fn get_register(&self) -> Option<(RegisterId, Dtype)> { + if let Self::Register { rid, dtype } = self { + Some((rid.clone(), dtype.clone())) + } else { + None + } + } +} + +impl fmt::Display for Operand { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Constant(value) => write!(f, "{}", value), + Self::Register { rid, .. } => write!(f, "{}", rid), + } + } +} + +impl HasDtype for Operand { + fn dtype(&self) -> Dtype { + match self { + Self::Constant(value) => value.dtype(), + Self::Register { dtype, .. } => dtype.clone(), + } + } +} + +#[derive(Debug, PartialEq, Clone)] +pub enum Instruction { + // TODO: the variants of Instruction will be added in the future + BinOp { + op: ast::BinaryOperator, + lhs: Operand, + rhs: Operand, + dtype: Dtype, + }, + UnaryOp { + op: ast::UnaryOperator, + operand: Operand, + dtype: Dtype, + }, + Store { + ptr: Operand, + value: Operand, + }, + Load { + ptr: Operand, + }, + Call { + callee: Operand, + args: Vec, + return_type: Dtype, + }, + TypeCast { + value: Operand, + target_dtype: Dtype, + }, +} + +impl HasDtype for Instruction { + fn dtype(&self) -> Dtype { + match self { + Self::BinOp { dtype, .. } => dtype.clone(), + Self::UnaryOp { dtype, .. } => dtype.clone(), + Self::Store { .. } => Dtype::unit(), + Self::Load { ptr } => ptr + .dtype() + .get_pointer_inner() + .expect("Load instruction must have pointer value as operand") + .deref() + .clone() + .set_const(false), + Self::Call { return_type, .. } => return_type.clone(), + Self::TypeCast { target_dtype, .. } => target_dtype.clone(), + } + } +} + +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct BlockId(pub usize); + +impl fmt::Display for BlockId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "b{}", self.0) + } +} + +// TODO +#[derive(Debug, PartialEq, Clone)] +pub enum BlockExit { + Jump { + bid: BlockId, + }, + ConditionalJump { + condition: Operand, + bid_then: BlockId, + bid_else: BlockId, + }, + Switch { + value: Operand, + default: BlockId, + cases: Vec<(Constant, BlockId)>, + }, + Return { + value: Operand, + }, + Unreachable, +} + +#[derive(Debug, PartialEq, Clone)] +pub struct Block { + pub instructions: Vec, + pub exit: BlockExit, +} diff --git a/src/irgen.rs b/src/irgen.rs new file mode 100644 index 0000000..6508200 --- /dev/null +++ b/src/irgen.rs @@ -0,0 +1,26 @@ +use std::fmt; + +use lang_c::ast::*; + +use crate::*; + +#[derive(Default)] +pub struct Irgen {} + +#[derive(Debug, PartialEq)] +pub struct IrgenError {} + +impl fmt::Display for IrgenError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "IrgenError") + } +} + +impl Translate for Irgen { + type Target = ir::TranslationUnit; + type Error = IrgenError; + + fn translate(&mut self, _unit: &TranslationUnit) -> Result { + todo!("homework 2") + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..6e5fe58 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,37 @@ +#![deny(warnings)] + +mod utils; + +pub mod asm; +pub mod ir; + +mod codegen; +mod irgen; +mod optimize; +mod parse; + +pub mod run_ir; +mod write_asm; +mod write_base; +mod write_c; +mod write_ir; + +pub mod assert_ast_equiv; +pub mod write_c_test; + +pub use utils::*; + +pub use asm::Asm; + +pub use codegen::Codegen; +pub use irgen::Irgen; +pub use optimize::{O0, O1}; +pub use parse::Parse; +pub use utils::{Optimize, Repeat, Translate}; + +pub use write_asm::write_asm; +pub use write_c::write_c; +pub use write_ir::write_ir; + +pub use assert_ast_equiv::assert_ast_equiv; +pub use write_c_test::write_c_test; diff --git a/src/optimize.rs b/src/optimize.rs new file mode 100644 index 0000000..b583ed1 --- /dev/null +++ b/src/optimize.rs @@ -0,0 +1,31 @@ +use crate::ir; +use crate::{Optimize, Repeat}; + +#[derive(Default)] +pub struct O0 {} + +#[derive(Default)] +pub struct Mem2reg {} + +#[derive(Default)] +pub struct Gvn {} + +pub type O1 = Repeat<(Mem2reg, Gvn)>; + +impl Optimize for O0 { + fn optimize(&mut self, _code: &mut ir::TranslationUnit) -> bool { + false + } +} + +impl Optimize for Mem2reg { + fn optimize(&mut self, _code: &mut ir::TranslationUnit) -> bool { + unimplemented!() + } +} + +impl Optimize for Gvn { + fn optimize(&mut self, _code: &mut ir::TranslationUnit) -> bool { + unimplemented!() + } +} diff --git a/src/parse.rs b/src/parse.rs new file mode 100644 index 0000000..77b24b5 --- /dev/null +++ b/src/parse.rs @@ -0,0 +1,551 @@ +use std::ops::Deref; +use std::path::Path; + +use lang_c::ast::*; +use lang_c::driver::{parse, Config, Error as ParseError}; +use lang_c::span::Node; + +use crate::Translate; + +#[derive(Debug)] +pub enum Error { + ParseError(ParseError), + Unsupported, +} + +#[derive(Default)] +pub struct Parse {} + +impl> Translate

for Parse { + type Target = TranslationUnit; + type Error = Error; + + fn translate(&mut self, source: &P) -> Result { + let config = Config::default(); + let ast = parse(&config, source).map_err(Error::ParseError)?; + let unit = ast.unit; + + unit.assert_supported(); + Ok(unit) + } +} + +trait AssertSupported { + fn assert_supported(&self); +} + +impl AssertSupported for Node { + fn assert_supported(&self) { + self.node.assert_supported(); + } +} + +impl AssertSupported for Option { + fn assert_supported(&self) { + if let Some(this) = self { + this.assert_supported(); + } + } +} + +impl AssertSupported for Box { + fn assert_supported(&self) { + self.deref().assert_supported(); + } +} + +impl AssertSupported for Vec { + fn assert_supported(&self) { + self.iter().for_each(AssertSupported::assert_supported); + } +} + +impl AssertSupported for [T] { + fn assert_supported(&self) { + self.iter().for_each(AssertSupported::assert_supported); + } +} + +impl AssertSupported for TranslationUnit { + fn assert_supported(&self) { + self.0.assert_supported(); + } +} + +impl AssertSupported for ExternalDeclaration { + fn assert_supported(&self) { + match self { + Self::Declaration(decl) => decl.assert_supported(), + Self::StaticAssert(_) => panic!("ExternalDeclaration::StaticAssert"), + Self::FunctionDefinition(fdef) => fdef.assert_supported(), + } + } +} + +impl AssertSupported for Declaration { + fn assert_supported(&self) { + self.specifiers.assert_supported(); + self.declarators.assert_supported(); + } +} + +impl AssertSupported for FunctionDefinition { + fn assert_supported(&self) { + self.specifiers.assert_supported(); + self.declarator.assert_supported(); + self.declarations.is_empty(); + self.statement.assert_supported(); + } +} + +impl AssertSupported for DeclarationSpecifier { + fn assert_supported(&self) { + match self { + Self::StorageClass(_) => panic!("DeclarationSpecifier::StorageClass"), + Self::TypeSpecifier(type_specifier) => type_specifier.assert_supported(), + Self::TypeQualifier(type_qualifier) => type_qualifier.assert_supported(), + Self::Function(_) => panic!("DeclarationSpecifier::Function"), + Self::Alignment(_) => panic!("DeclarationSpecifier::Alignment"), + Self::Extension(_) => panic!("DeclarationSpecifier::Extension"), + } + } +} + +impl AssertSupported for TypeSpecifier { + fn assert_supported(&self) { + match self { + Self::Void => (), + Self::Char => (), + Self::Short => (), + Self::Int => (), + Self::Long => (), + Self::Float => (), + Self::Double => (), + Self::Signed => (), + Self::Unsigned => (), + Self::Bool => (), + Self::Complex => panic!("TypeSpecifier::Complex"), + Self::Atomic(_) => panic!("TypeSpecifier::Atomic"), + Self::Struct(struct_type) => struct_type.assert_supported(), + Self::Enum(_) => panic!("TypeSpecifier::Enum"), + Self::TypedefName(_) => panic!("TypeSpecifier::TypedefName"), + Self::TypeOf(_) => panic!("TypeSpecifier::TypeOf"), + Self::TS18661Float(_) => panic!("TypeSpecifier::TS18661Float"), + } + } +} + +impl AssertSupported for StructType { + fn assert_supported(&self) { + self.kind.assert_supported(); + self.declarations.assert_supported(); + } +} + +impl AssertSupported for StructDeclaration { + fn assert_supported(&self) { + match self { + Self::Field(field) => field.assert_supported(), + Self::StaticAssert(_) => panic!("StructDeclaration::StaticAssert"), + } + } +} + +impl AssertSupported for StructField { + fn assert_supported(&self) { + self.specifiers.assert_supported(); + self.declarators.assert_supported(); + } +} + +impl AssertSupported for StructDeclarator { + fn assert_supported(&self) { + self.declarator.assert_supported(); + assert_eq!(true, self.bit_width.is_none()); + } +} + +impl AssertSupported for StructKind { + fn assert_supported(&self) { + match self { + Self::Struct => (), + Self::Union => panic!("StructKind::Union"), + } + } +} + +impl AssertSupported for AlignmentSpecifier { + fn assert_supported(&self) { + match self { + Self::Type(typename) => typename.assert_supported(), + Self::Constant(_) => panic!(AlignmentSpecifier::Constant), + } + } +} + +impl AssertSupported for InitDeclarator { + fn assert_supported(&self) { + self.declarator.assert_supported(); + self.initializer.assert_supported(); + } +} + +impl AssertSupported for Initializer { + fn assert_supported(&self) { + match self { + Self::Expression(expr) => expr.assert_supported(), + Self::List(_) => panic!("Initializer::List"), + } + } +} + +impl AssertSupported for Declarator { + fn assert_supported(&self) { + self.kind.assert_supported(); + self.derived.assert_supported(); + self.extensions.is_empty(); + } +} + +impl AssertSupported for DerivedDeclarator { + fn assert_supported(&self) { + match self { + Self::Pointer(pointer_qualifiers) => pointer_qualifiers.assert_supported(), + Self::Array(array_decl) => array_decl.assert_supported(), + Self::Function(func_decl) => func_decl.assert_supported(), + // Support when K&R function has no parameter + Self::KRFunction(kr_func_decl) => assert_eq!(true, kr_func_decl.is_empty()), + } + } +} + +impl AssertSupported for PointerQualifier { + fn assert_supported(&self) { + match self { + Self::TypeQualifier(type_qualifier) => type_qualifier.assert_supported(), + Self::Extension(_) => panic!("PointerQualifier::Extension"), + } + } +} + +impl AssertSupported for ArrayDeclarator { + fn assert_supported(&self) { + self.qualifiers.assert_supported(); + self.size.assert_supported(); + } +} + +impl AssertSupported for TypeQualifier { + fn assert_supported(&self) { + match self { + Self::Const => (), + _ => panic!("TypeQualifier::_"), + } + } +} + +impl AssertSupported for ArraySize { + fn assert_supported(&self) { + match self { + Self::VariableExpression(expr) => expr.assert_supported(), + _ => panic!("ArraySize::_"), + } + } +} + +impl AssertSupported for FunctionDeclarator { + fn assert_supported(&self) { + self.parameters.assert_supported(); + assert_eq!(self.ellipsis, Ellipsis::None); + } +} + +impl AssertSupported for ParameterDeclaration { + fn assert_supported(&self) { + self.specifiers.assert_supported(); + self.declarator.assert_supported(); + self.extensions.is_empty(); + } +} + +impl AssertSupported for DeclaratorKind { + fn assert_supported(&self) { + match self { + Self::Abstract => panic!("DeclaratorKind::Abstract"), + Self::Identifier(_) => (), + Self::Declarator(decl) => decl.assert_supported(), + } + } +} + +impl AssertSupported for BlockItem { + fn assert_supported(&self) { + match self { + Self::Declaration(decl) => decl.assert_supported(), + Self::StaticAssert(_) => panic!("BlockItem::StaticAssert"), + Self::Statement(stmt) => stmt.assert_supported(), + } + } +} + +impl AssertSupported for ForInitializer { + fn assert_supported(&self) { + match self { + Self::Empty => (), + Self::Expression(expr) => expr.assert_supported(), + Self::Declaration(decl) => decl.assert_supported(), + Self::StaticAssert(_) => panic!("ForInitializer::StaticAssert"), + } + } +} + +impl AssertSupported for Statement { + fn assert_supported(&self) { + match self { + Self::Labeled(_) => panic!("Statement::Labeled"), + Self::Compound(items) => items.assert_supported(), + Self::Expression(expr) => expr.assert_supported(), + Self::If(stmt) => { + stmt.node.condition.assert_supported(); + stmt.node.then_statement.assert_supported(); + stmt.node.else_statement.assert_supported(); + } + Self::Switch(stmt) => stmt.assert_supported(), + Self::While(stmt) => { + stmt.node.expression.assert_supported(); + stmt.node.statement.assert_supported(); + } + Self::DoWhile(stmt) => { + stmt.node.statement.assert_supported(); + stmt.node.expression.assert_supported(); + } + Self::For(stmt) => { + stmt.node.initializer.assert_supported(); + stmt.node.condition.assert_supported(); + stmt.node.step.assert_supported(); + stmt.node.statement.assert_supported(); + } + Self::Goto(_) => panic!("Statement::Goto"), + Self::Continue | Self::Break => (), + Self::Return(expr) => expr.assert_supported(), + Self::Asm(_) => panic!("Statement::Asm"), + } + } +} + +impl AssertSupported for SwitchStatement { + fn assert_supported(&self) { + self.expression.assert_supported(); + + let items = if let Statement::Compound(items) = &self.statement.node { + items + } else { + panic!("`Statement` in the `switch` is unsupported except `Statement::Compound`") + }; + + for item in items { + let stmt = if let BlockItem::Statement(stmt) = &item.node { + &stmt.node + } else { + panic!( + "`BlockItem` in the `Statement::Compound` of the `switch` \ + is unsupported except `BlockItem::Statement`" + ) + }; + + let stmt_in_label = if let Statement::Labeled(label_stmt) = stmt { + label_stmt.node.label.assert_supported(); + &label_stmt.node.statement.node + } else { + panic!( + "`BlockItem::Statement` in the `Statement::Compound` of the `switch` \ + is unsupported except `Statement::Labeled`" + ) + }; + + let items = if let Statement::Compound(items) = stmt_in_label { + items + } else { + panic!("`Statement` in the `label` is unsupported except `Statement::Compound`") + }; + + // Split last and all the rest of the elements of the `Compound` items + let (last, items) = items + .split_last() + .unwrap_or_else(|| panic!("`Statement::Compound` has no item")); + + for item in items { + match &item.node { + BlockItem::Declaration(decl) => decl.assert_supported(), + BlockItem::StaticAssert(_) => panic!("BlockItem::StaticAssert"), + BlockItem::Statement(stmt) => { + assert_ne!( + &stmt.node, + &Statement::Break, + "`BlockItem::Statement` in the `Statement::Compound` of the \ + `label` should not be `Statement::Break` except the last one" + ); + stmt.assert_supported(); + } + } + } + + // The last element of the `items` must be `Statement::Break` + let stmt = if let BlockItem::Statement(stmt) = &last.node { + &stmt.node + } else { + panic!( + "`BlockItem` in the `Statement::Compound` of the `label` \ + is unsupported except `BlockItem::Statement`" + ) + }; + + assert_eq!( + stmt, + &Statement::Break, + "the last `BlockItem` in the `Statement::Compound` \ + of the `label` must be `Statement::Break`" + ); + } + } +} + +impl AssertSupported for Expression { + fn assert_supported(&self) { + match self { + Self::Identifier(_) => (), + Self::Constant(constant) => constant.assert_supported(), + Self::StringLiteral(_) => panic!("Expression:StringLiteral"), + Self::GenericSelection(_) => panic!("Expression:GenericSelection"), + Self::Member(member) => member.assert_supported(), + Self::Call(call) => call.assert_supported(), + Self::CompoundLiteral(_) => panic!("Expression::CompoundLiteral"), + Self::SizeOf(typename) => typename.assert_supported(), + Self::AlignOf(typename) => typename.assert_supported(), + Self::UnaryOperator(unary) => unary.assert_supported(), + Self::Cast(cast) => cast.assert_supported(), + Self::BinaryOperator(binary) => binary.assert_supported(), + Self::Conditional(conditional) => conditional.assert_supported(), + Self::Comma(exprs) => exprs.assert_supported(), + Self::OffsetOf(_) => panic!("Expression::OffsetOf"), + Self::VaArg(_) => panic!("Expression::VaArg"), + Self::Statement(_) => panic!("Expression::Statement"), + } + } +} + +impl AssertSupported for Label { + fn assert_supported(&self) { + match self { + Self::Identifier(_) => panic!("Label::Identifier"), + Self::Case(_) => (), + Self::Default => (), + } + } +} + +impl AssertSupported for MemberExpression { + fn assert_supported(&self) { + self.expression.assert_supported(); + } +} + +impl AssertSupported for CallExpression { + fn assert_supported(&self) { + self.callee.assert_supported(); + self.arguments.assert_supported(); + } +} + +impl AssertSupported for TypeName { + fn assert_supported(&self) { + self.specifiers.assert_supported(); + self.declarator.assert_supported(); + } +} + +impl AssertSupported for SpecifierQualifier { + fn assert_supported(&self) { + match self { + Self::TypeSpecifier(type_specifier) => type_specifier.assert_supported(), + Self::TypeQualifier(type_qualifier) => type_qualifier.assert_supported(), + } + } +} + +impl AssertSupported for UnaryOperatorExpression { + fn assert_supported(&self) { + self.operator.assert_supported(); + self.operand.assert_supported(); + } +} + +impl AssertSupported for CastExpression { + fn assert_supported(&self) { + self.type_name.assert_supported(); + self.expression.assert_supported(); + } +} + +impl AssertSupported for BinaryOperatorExpression { + fn assert_supported(&self) { + self.operator.assert_supported(); + self.lhs.assert_supported(); + self.rhs.assert_supported(); + } +} + +impl AssertSupported for Constant { + fn assert_supported(&self) { + match self { + Self::Integer(integer) => integer.assert_supported(), + Self::Float(float) => float.assert_supported(), + Self::Character(_) => (), + } + } +} + +impl AssertSupported for Integer { + fn assert_supported(&self) { + assert_eq!(false, self.suffix.imaginary); + } +} + +impl AssertSupported for Float { + fn assert_supported(&self) { + assert_eq!(self.base, FloatBase::Decimal); + self.suffix.format.assert_supported(); + assert_eq!(false, self.suffix.imaginary); + } +} + +impl AssertSupported for FloatFormat { + fn assert_supported(&self) { + match self { + Self::Float => (), + Self::Double => (), + Self::LongDouble => (), + Self::TS18661Format(_) => panic!("TS18861"), + } + } +} + +impl AssertSupported for UnaryOperator { + fn assert_supported(&self) { + if let Self::SizeOf = self { + panic!("UnaryOperaotr::SizeOf") + } + } +} + +impl AssertSupported for BinaryOperator { + fn assert_supported(&self) {} +} + +impl AssertSupported for ConditionalExpression { + fn assert_supported(&self) { + self.condition.assert_supported(); + self.then_expression.assert_supported(); + self.else_expression.assert_supported(); + } +} diff --git a/src/run_ir.rs b/src/run_ir.rs new file mode 100644 index 0000000..511b96c --- /dev/null +++ b/src/run_ir.rs @@ -0,0 +1,592 @@ +use crate::ir::*; +use crate::*; + +use failure::Fail; +use std::collections::HashMap; +use std::mem; + +use itertools::izip; + +// TODO: the variants of Value will be added in the future +#[derive(Debug, PartialEq, Clone)] +pub enum Value { + Undef, + Unit, + Int(i32), + Float(f32), + Bool(bool), + Pointer { bid: Option, offset: usize }, +} + +impl Value { + #[inline] + fn pointer(bid: Option, offset: usize) -> Self { + Self::Pointer { bid, offset } + } + + #[inline] + fn get_bool(self) -> Option { + if let Value::Bool(value) = self { + Some(value) + } else { + None + } + } + + #[inline] + fn get_pointer(self) -> Option<(Option, usize)> { + if let Value::Pointer { bid, offset } = self { + Some((bid, offset)) + } else { + None + } + } + + #[inline] + fn nullptr() -> Self { + Self::Pointer { + bid: None, + offset: 0, + } + } + + #[inline] + fn default_from_dtype(dtype: &Dtype) -> Self { + match dtype { + // TODO: consider `Unit` value in the future + ir::Dtype::Unit { .. } => todo!(), + ir::Dtype::Int { width, .. } => match width { + 32 => Self::Int(i32::default()), + _ => todo!("other cases will be covered"), + }, + ir::Dtype::Float { .. } => Self::Float(f32::default()), + ir::Dtype::Pointer { .. } => Self::nullptr(), + ir::Dtype::Function { .. } => panic!("function types do not have a default value"), + } + } +} + +#[derive(Debug, PartialEq, Fail)] +pub enum InterpreterError { + #[fail(display = "current block is unreachable")] + Unreachable, + #[fail(display = "ir has no main function")] + NoMainFunction, + #[fail(display = "ir has no function definition of {} function", func_name)] + NoFunctionDefinition { func_name: String }, + #[fail( + display = "{}:{}:{} / Undef value cannot be used as an operand", + func_name, bid, iid + )] + Undef { + func_name: String, + bid: BlockId, + iid: usize, + }, +} + +#[derive(Debug, PartialEq, Clone)] +struct Pc { + pub bid: BlockId, + pub iid: usize, +} + +impl Pc { + fn new(bid: BlockId) -> Pc { + Pc { bid, iid: 0 } + } + + fn increment(&mut self) { + self.iid += 1; + } +} + +#[derive(Debug, PartialEq, Clone)] +struct RegisterMap { + inner: HashMap, +} + +impl RegisterMap { + fn new() -> Self { + Self { + inner: HashMap::new(), + } + } +} + +#[derive(Default, Debug, PartialEq, Clone)] +/// Bidirectional map between the name of a global variable and memory box id +struct GlobalMap { + /// Map name of a global variable to memory box id + /// + /// Since IR treats global variable as `Constant::GlobalVariable`, + /// the interpreter should be able to generate pointer values by infer 'bid' + /// from the 'name' of the global variable. + var_to_bid: HashMap, + /// Map memory box id to the name of a global variable + /// + /// When a function call occurs, the interpreter should be able to find `name` of the function + /// from `bid` of the `callee` which is a function pointer. + bid_to_var: HashMap, +} + +impl GlobalMap { + /// Create a bi-directional mapping between `var` and `bid`. + fn insert(&mut self, var: String, bid: usize) -> Result<(), InterpreterError> { + if self.var_to_bid.insert(var.clone(), bid).is_some() { + panic!("variable name should be unique in IR") + } + if self.bid_to_var.insert(bid, var).is_some() { + panic!("`bid` is connected to only one `var`") + } + + Ok(()) + } + + fn get_bid(&self, var: &str) -> Option { + self.var_to_bid.get(var).cloned() + } + + fn get_var(&self, bid: usize) -> Option { + self.bid_to_var.get(&bid).cloned() + } +} + +#[derive(Debug, PartialEq, Clone)] +struct StackFrame<'i> { + pub pc: Pc, + pub registers: RegisterMap, + pub func_name: String, + pub func_def: &'i FunctionDefinition, +} + +impl<'i> StackFrame<'i> { + fn new(bid: BlockId, func_name: String, func_def: &'i FunctionDefinition) -> Self { + StackFrame { + pc: Pc::new(bid), + registers: RegisterMap::new(), + func_name, + func_def, + } + } +} + +mod calculator { + use super::Value; + use lang_c::ast; + + pub fn calculate_binary_operator_expression( + op: &ast::BinaryOperator, + lhs: Value, + rhs: Value, + ) -> Result { + match (op, lhs, rhs) { + (_, Value::Undef, _) => Err(()), + (_, _, Value::Undef) => Err(()), + (ast::BinaryOperator::Plus, Value::Int(lhs), Value::Int(rhs)) => { + Ok(Value::Int(lhs + rhs)) + } + (ast::BinaryOperator::Minus, Value::Int(lhs), Value::Int(rhs)) => { + Ok(Value::Int(lhs - rhs)) + } + (ast::BinaryOperator::Equals, Value::Int(lhs), Value::Int(rhs)) => { + Ok(Value::Bool(lhs == rhs)) + } + (ast::BinaryOperator::NotEquals, Value::Int(lhs), Value::Int(rhs)) => { + Ok(Value::Bool(lhs != rhs)) + } + (ast::BinaryOperator::Less, Value::Int(lhs), Value::Int(rhs)) => { + Ok(Value::Bool(lhs < rhs)) + } + _ => todo!(), + } + } + + pub fn calculate_unary_operator_expression( + op: &ast::UnaryOperator, + operand: Value, + ) -> Result { + match (op, operand) { + (_, Value::Undef) => Err(()), + (ast::UnaryOperator::Plus, Value::Int(value)) => Ok(Value::Int(value)), + (ast::UnaryOperator::Minus, Value::Int(value)) => Ok(Value::Int(-value)), + (ast::UnaryOperator::Negate, Value::Bool(value)) => Ok(Value::Bool(!value)), + _ => todo!(), + } + } +} + +// TODO: allocation fields will be added in the future +// TODO: program fields will be added in the future +#[derive(Debug, PartialEq)] +struct State<'i> { + /// A data structure that maps each global variable to a pointer value + /// When function call occurs, `registers` can be initialized by `global_registers` + pub global_map: GlobalMap, + pub stack_frame: StackFrame<'i>, + pub stack: Vec>, + // TODO: memory type should change to Vec> + pub memory: Vec>, + pub ir: &'i TranslationUnit, +} + +impl<'i> State<'i> { + fn new(ir: &'i TranslationUnit, args: Vec) -> Result { + // Interpreter starts with the main function + let func_name = String::from("main"); + let func = ir + .decls + .get(&func_name) + .ok_or_else(|| InterpreterError::NoMainFunction)?; + let (_, func_def) = func + .get_function() + .ok_or_else(|| InterpreterError::NoMainFunction)?; + let func_def = func_def + .as_ref() + .ok_or_else(|| InterpreterError::NoFunctionDefinition { + func_name: func_name.clone(), + })?; + + // Create State + let mut state = State { + global_map: GlobalMap::default(), + stack_frame: StackFrame::new(func_def.bid_init.clone(), func_name, func_def), + stack: Vec::new(), + memory: Vec::new(), + ir, + }; + + state.alloc_global_variable()?; + + // Initialize state with main function and args + state.pass_arguments(args)?; + state.alloc_local_variable()?; + + Ok(state) + } + + fn alloc_global_variable(&mut self) -> Result<(), InterpreterError> { + for (name, decl) in &self.ir.decls { + // Memory allocation + let bid = self.alloc_memory(&decl.dtype())?; + self.global_map.insert(name.clone(), bid)?; + + // Initialize allocated memory space + match decl { + Declaration::Variable { dtype, initializer } => { + let value = if let Some(constant) = initializer { + self.constant_to_value(constant.clone()) + } else { + Value::default_from_dtype(dtype) + }; + + self.memory[bid][0] = value; + } + // If functin declaration, skip initialization + Declaration::Function { .. } => (), + } + } + + Ok(()) + } + + fn pass_arguments(&mut self, args: Vec) -> Result<(), InterpreterError> { + for (i, value) in args.iter().enumerate() { + self.register_write(RegisterId::arg(i), value.clone()); + } + + Ok(()) + } + + fn alloc_local_variable(&mut self) -> Result<(), InterpreterError> { + // add alloc register + for (id, allocation) in self.stack_frame.func_def.allocations.iter().enumerate() { + let bid = self.alloc_memory(&allocation)?; + let ptr = Value::pointer(Some(bid), 0); + let rid = RegisterId::local("".to_string(), id); + + self.register_write(rid, ptr) + } + + Ok(()) + } + + fn alloc_memory(&mut self, dtype: &Dtype) -> Result { + // TODO: memory block will be handled as Vec + let memory_block = match dtype { + Dtype::Unit { .. } => vec![], + Dtype::Int { width, .. } => match width { + 32 => vec![Value::Undef], + _ => todo!(), + }, + Dtype::Float { .. } => todo!(), + Dtype::Pointer { .. } => vec![Value::Undef], + Dtype::Function { .. } => vec![], + }; + + self.memory.push(memory_block); + + Ok(self.memory.len() - 1) + } + + fn preprocess_args( + &self, + signature: &FunctionSignature, + args: &[Operand], + ) -> Result, InterpreterError> { + // Check that the dtype of each args matches the expected + if !(args.len() == signature.params.len() + && izip!(args, &signature.params).all(|(a, d)| a.dtype().is_compatible(d))) + { + panic!("dtype of args and params must be compatible") + } + + args.iter() + .map(|a| self.get_value(a.clone())) + .collect::, _>>() + } + + fn step(&mut self) -> Result, InterpreterError> { + let block = self + .stack_frame + .func_def + .blocks + .get(&self.stack_frame.pc.bid.clone()) + .expect("block matched with `bid` must be exist"); + + if block.instructions.len() == self.stack_frame.pc.iid { + self.interpret_block_exit(&block.exit) + } else { + let instr = block + .instructions + .get(self.stack_frame.pc.iid) + .expect("instruction matched with `iid` must be exist"); + + self.interpret_instruction(instr) + } + } + + fn run(&mut self) -> Result { + loop { + if let Some(value) = self.step()? { + // TODO: Before return, free memory allocated in a function + + // restore previous state + let prev_stack_frame = some_or!(self.stack.pop(), { + return Ok(value); + }); + self.stack_frame = prev_stack_frame; + + // create temporary register to write return value + let register = + RegisterId::temp(self.stack_frame.pc.bid.clone(), self.stack_frame.pc.iid); + self.register_write(register, value); + self.stack_frame.pc.increment(); + } + } + } + + fn interpret_block_exit( + &mut self, + block_exit: &BlockExit, + ) -> Result, InterpreterError> { + match block_exit { + BlockExit::Jump { bid } => { + self.stack_frame.pc = Pc::new(bid.clone()); + Ok(None) + } + BlockExit::ConditionalJump { + condition, + bid_then, + bid_else, + } => { + let value = self.get_value(condition.clone())?; + let value = value.get_bool().expect("`condition` must be `Value::Bool`"); + + self.stack_frame.pc = Pc::new(if value { + bid_then.clone() + } else { + bid_else.clone() + }); + Ok(None) + } + BlockExit::Switch { + value, + default, + cases, + } => { + let value = self.get_value(value.clone())?; + + // TODO: consider different integer `width` in the future + let bid_next = cases + .iter() + .find(|(c, _)| value == self.constant_to_value(c.clone())) + .map(|(_, bid)| bid.clone()) + .unwrap_or_else(|| default.clone()); + + self.stack_frame.pc = Pc::new(bid_next); + + Ok(None) + } + BlockExit::Return { value } => Ok(Some(self.get_value(value.clone())?)), + BlockExit::Unreachable => Err(InterpreterError::Unreachable), + } + } + + fn interpret_instruction( + &mut self, + instruction: &Instruction, + ) -> Result, InterpreterError> { + let result = match instruction { + Instruction::BinOp { op, lhs, rhs, .. } => { + let lhs = self.get_value(lhs.clone())?; + let rhs = self.get_value(rhs.clone())?; + + calculator::calculate_binary_operator_expression(&op, lhs, rhs).map_err(|_| { + InterpreterError::Undef { + func_name: self.stack_frame.func_name.clone(), + bid: self.stack_frame.pc.bid.clone(), + iid: self.stack_frame.pc.iid, + } + })? + } + Instruction::UnaryOp { op, operand, .. } => { + let operand = self.get_value(operand.clone())?; + + calculator::calculate_unary_operator_expression(&op, operand).map_err(|_| { + InterpreterError::Undef { + func_name: self.stack_frame.func_name.clone(), + bid: self.stack_frame.pc.bid.clone(), + iid: self.stack_frame.pc.iid, + } + })? + } + Instruction::Store { ptr, value, .. } => { + let ptr = self.get_value(ptr.clone())?; + let value = self.get_value(value.clone())?; + + self.memory_store(ptr, value)?; + + Value::Unit + } + Instruction::Load { ptr, .. } => { + let ptr = self.get_value(ptr.clone())?; + + self.memory_load(ptr)? + } + Instruction::Call { callee, args, .. } => { + let ptr = self.get_value(callee.clone())?; + + // Get function name from pointer + let (bid, _) = ptr.get_pointer().expect("`ptr` must be `Value::Pointer`"); + let bid = bid.expect("pointer for global variable must have bid value"); + let callee_name = self + .global_map + .get_var(bid) + .expect("bid must have relation with global variable"); + + let func = self + .ir + .decls + .get(&callee_name) + .expect("function must be declared before being called"); + let (func_signature, func_def) = func + .get_function() + .expect("`func` must be function declaration"); + let func_def = + func_def + .as_ref() + .ok_or_else(|| InterpreterError::NoFunctionDefinition { + func_name: callee_name.clone(), + })?; + + let args = self.preprocess_args(func_signature, args)?; + + let stack_frame = StackFrame::new(func_def.bid_init.clone(), callee_name, func_def); + let prev_stack_frame = mem::replace(&mut self.stack_frame, stack_frame); + self.stack.push(prev_stack_frame); + + // Initialize state with function obtained by callee and args + self.pass_arguments(args)?; + self.alloc_local_variable()?; + + return Ok(None); + } + _ => todo!("{:?} will be supported in the future", instruction), + }; + + let register = RegisterId::temp(self.stack_frame.pc.bid.clone(), self.stack_frame.pc.iid); + self.register_write(register, result); + self.stack_frame.pc.increment(); + + Ok(None) + } + + fn get_value(&self, operand: Operand) -> Result { + match &operand { + Operand::Constant(value) => Ok(self.constant_to_value(value.clone())), + Operand::Register { rid, .. } => Ok(self.register_read(rid.clone())), + } + } + + fn constant_to_value(&self, value: Constant) -> Value { + match value { + Constant::Unit => Value::Unit, + // TODO: consider `width` and `is_signed` in the future + Constant::Int { value, .. } => Value::Int(value as i32), + Constant::Float { value, .. } => Value::Float(value as f32), + Constant::GlobalVariable { name, .. } => { + let bid = self + .global_map + .get_bid(&name) + .expect("The name matching `bid` must exist."); + + // Generate appropriate pointer from `bid` + Value::Pointer { + bid: Some(bid), + offset: 0, + } + } + } + } + + fn register_write(&mut self, rid: RegisterId, value: Value) { + let _ = self.stack_frame.registers.inner.insert(rid, value); + } + + fn register_read(&self, rid: RegisterId) -> Value { + self.stack_frame + .registers + .inner + .get(&rid) + .cloned() + .expect("`rid` must be assigned before it can be used") + } + + fn memory_store(&mut self, pointer: Value, value: Value) -> Result<(), InterpreterError> { + let (bid, offset) = pointer + .get_pointer() + .expect("`pointer` must be `Value::Pointer` to access memory"); + + let bid = bid.expect("write to memory using constant value address is not allowed"); + self.memory[bid][offset] = value; + + Ok(()) + } + + fn memory_load(&self, pointer: Value) -> Result { + let (bid, offset) = pointer + .get_pointer() + .expect("`pointer` must be `Value::Pointer` to access memory"); + + let bid = bid.expect("read from memory using constant value address is not allowed"); + + Ok(self.memory[bid][offset].clone()) + } +} + +#[inline] +pub fn run_ir(ir: &TranslationUnit, args: Vec) -> Result { + let mut init_state = State::new(ir, args)?; + init_state.run() +} diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..369a485 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,81 @@ +#[macro_export] +/// Ok or executing the given expression. +macro_rules! ok_or { + ($e:expr, $err:expr) => {{ + match $e { + Ok(r) => r, + Err(_) => $err, + } + }}; +} + +#[macro_export] +/// Some or executing the given expression. +macro_rules! some_or { + ($e:expr, $err:expr) => {{ + match $e { + Some(r) => r, + None => $err, + } + }}; +} + +#[macro_export] +/// Ok or exiting the process. +macro_rules! ok_or_exit { + ($e:expr, $code:expr) => {{ + match $e { + Ok(r) => r, + Err(e) => { + eprintln!("{:?}", e); + ::std::process::exit($code); + } + } + }}; +} + +#[macro_export] +/// Ok or exiting the process. +macro_rules! some_or_exit { + ($e:expr, $code:expr) => {{ + match $e { + Some(r) => r, + None => ::std::process::exit($code), + } + }}; +} + +pub trait Translate { + type Target; + type Error; + + fn translate(&mut self, source: &S) -> Result; +} + +pub trait Optimize { + fn optimize(&mut self, code: &mut T) -> bool; +} + +#[derive(Default)] +pub struct Repeat { + inner: O, +} + +impl, O2: Optimize> Optimize for (O1, O2) { + fn optimize(&mut self, code: &mut T) -> bool { + let changed1 = self.0.optimize(code); + let changed2 = self.1.optimize(code); + changed1 || changed2 + } +} + +impl> Optimize for Repeat { + fn optimize(&mut self, code: &mut T) -> bool { + if !self.inner.optimize(code) { + return false; + } + + while self.inner.optimize(code) {} + true + } +} diff --git a/src/write_asm.rs b/src/write_asm.rs new file mode 100644 index 0000000..a057b20 --- /dev/null +++ b/src/write_asm.rs @@ -0,0 +1,5 @@ +use crate::asm::Asm; + +pub fn write_asm(_asm: &Asm, _write: &mut dyn ::std::io::Write) { + unimplemented!(); +} diff --git a/src/write_base.rs b/src/write_base.rs new file mode 100644 index 0000000..dd5e849 --- /dev/null +++ b/src/write_base.rs @@ -0,0 +1,18 @@ +use std::io::{Result, Write}; + +#[inline] +pub fn write_indent(indent: usize, write: &mut dyn Write) -> Result<()> { + write!(write, "{}", " ".repeat(indent)) +} + +pub trait WriteLine { + fn write_line(&self, indent: usize, write: &mut dyn Write) -> Result<()>; +} + +pub trait WriteString { + fn write_string(&self) -> String; +} + +pub trait WriteOp { + fn write_operation(&self) -> String; +} diff --git a/src/write_c.rs b/src/write_c.rs new file mode 100644 index 0000000..bbb5878 --- /dev/null +++ b/src/write_c.rs @@ -0,0 +1,51 @@ +use lang_c::ast::*; +use lang_c::span::Node; + +use std::io::{Result, Write}; +use std::ops::Deref; + +use crate::write_base::*; + +impl WriteLine for Node { + fn write_line(&self, indent: usize, write: &mut dyn Write) -> Result<()> { + self.node.write_line(indent, write) + } +} + +impl WriteString for Node { + fn write_string(&self) -> String { + self.node.write_string() + } +} + +impl WriteString for Box { + fn write_string(&self) -> String { + self.deref().write_string() + } +} + +impl WriteString for &T { + fn write_string(&self) -> String { + (*self).write_string() + } +} + +impl WriteString for Option { + fn write_string(&self) -> String { + if let Some(this) = self { + this.write_string() + } else { + "".to_string() + } + } +} + +impl WriteLine for TranslationUnit { + fn write_line(&self, _indent: usize, _write: &mut dyn Write) -> Result<()> { + todo!("homework 1") + } +} + +pub fn write_c(unit: &TranslationUnit, write: &mut dyn Write) -> Result<()> { + unit.write_line(0, write) +} diff --git a/src/write_c_test.rs b/src/write_c_test.rs new file mode 100644 index 0000000..d34ecfa --- /dev/null +++ b/src/write_c_test.rs @@ -0,0 +1,20 @@ +use lang_c::ast::*; +use std::fs::File; +use tempfile::tempdir; + +use crate::*; + +pub fn write_c_test(unit: &TranslationUnit) { + let temp_dir = tempdir().expect("temp dir creation failed"); + let temp_file_path = temp_dir.path().join("temp.c"); + let mut temp_file = File::create(&temp_file_path).unwrap(); + + write_c(&unit, &mut temp_file).unwrap(); + + let new_unit = Parse::default() + .translate(&temp_file_path.as_path()) + .expect("parse failed while parsing file from implemented printer"); + drop(temp_file); + assert_ast_equiv(&unit, &new_unit); + temp_dir.close().expect("temp dir deletion failed"); +} diff --git a/src/write_ir.rs b/src/write_ir.rs new file mode 100644 index 0000000..dd2937a --- /dev/null +++ b/src/write_ir.rs @@ -0,0 +1,224 @@ +use crate::ir::*; + +use std::io::{Result, Write}; + +use crate::write_base::*; +use crate::*; + +use lang_c::ast; + +impl WriteLine for TranslationUnit { + fn write_line(&self, indent: usize, write: &mut dyn Write) -> Result<()> { + write_indent(indent, write)?; + writeln!(write, "")?; + writeln!(write)?; + for (name, decl) in &self.decls { + let _ = some_or!(decl.get_variable(), continue); + (name, decl).write_line(indent, write)?; + } + + writeln!(write)?; + writeln!(write)?; + write_indent(indent, write)?; + writeln!(write, "")?; + writeln!(write)?; + for (name, decl) in &self.decls { + let _ = some_or!(decl.get_function(), continue); + (name, decl).write_line(indent, write)?; + } + + Ok(()) + } +} + +impl WriteLine for (&String, &Declaration) { + fn write_line(&self, indent: usize, write: &mut dyn Write) -> Result<()> { + let name = self.0; + let decl = self.1; + + match decl { + Declaration::Variable { dtype, .. } => { + writeln!(write, "{} = {}", name, dtype)?; + } + Declaration::Function { + signature, + definition, + } => { + let declaration = format!( + "{} @{}({})", + signature.ret, + name, + signature + .params + .iter() + .map(|d| d.to_string()) + .collect::>() + .join(", "), + ); + + match definition.as_ref() { + Some(defintion) => { + // print meta data for function + writeln!( + write, + "; function meta data:\n; bid_init: {}\n; allocations: {}", + defintion.bid_init, + defintion + .allocations + .iter() + .enumerate() + .map(|(i, a)| format!("{}:{}", i, a)) + .collect::>() + .join(", ") + )?; + + // print function definition + writeln!(write, "define {} {{", declaration)?; + + for (id, block) in &defintion.blocks { + writeln!(write, "; {}", id)?; + (id, block).write_line(indent + 1, write)?; + writeln!(write)?; + } + + writeln!(write, "}}")?; + writeln!(write)?; + } + None => { + // print declaration line only + writeln!(write, "declare {}", declaration)?; + writeln!(write)?; + } + } + } + } + + Ok(()) + } +} + +impl WriteLine for (&BlockId, &Block) { + fn write_line(&self, indent: usize, write: &mut dyn Write) -> Result<()> { + for (i, instr) in self.1.instructions.iter().enumerate() { + write_indent(indent, write)?; + writeln!( + write, + "{}:{} = {}", + RegisterId::temp(self.0.clone(), i), + instr.dtype(), + instr.write_string() + )?; + } + + write_indent(indent, write)?; + writeln!(write, "{}", self.1.exit.write_string())?; + + Ok(()) + } +} + +impl WriteString for Instruction { + fn write_string(&self) -> String { + match self { + Instruction::BinOp { op, lhs, rhs, .. } => format!( + "{} {} {}", + op.write_operation(), + lhs.write_string(), + rhs.write_string() + ), + Instruction::UnaryOp { op, operand, .. } => { + format!("{} {}", op.write_operation(), operand.write_string(),) + } + Instruction::Store { ptr, value } => { + format!("store {} {}", value.write_string(), ptr.write_string()) + } + Instruction::Load { ptr } => format!("load {}", ptr.write_string()), + Instruction::Call { callee, args, .. } => format!( + "call {}({})", + callee, + args.iter() + .map(WriteString::write_string) + .collect::>() + .join(", ") + ), + Instruction::TypeCast { + value, + target_dtype, + } => format!("typecast {} to {}", value.write_string(), target_dtype), + } + } +} + +impl WriteString for Operand { + fn write_string(&self) -> String { + format!("{}:{}", self, self.dtype()) + } +} + +impl WriteOp for ast::BinaryOperator { + fn write_operation(&self) -> String { + match self { + Self::Multiply => "mul", + Self::Divide => "div", + Self::Modulo => "mod", + Self::Plus => "add", + Self::Minus => "sub", + Self::Equals => "cmp eq", + Self::NotEquals => "cmp ne", + Self::Less => "cmp lt", + Self::LessOrEqual => "cmp le", + Self::Greater => "cmp gt", + Self::GreaterOrEqual => "cmp ge", + _ => todo!(), + } + .to_string() + } +} + +impl WriteOp for ast::UnaryOperator { + fn write_operation(&self) -> String { + match self { + Self::Minus => "minus", + _ => todo!(), + } + .to_string() + } +} + +impl WriteString for BlockExit { + fn write_string(&self) -> String { + match self { + BlockExit::Jump { bid } => format!("j {}", bid), + BlockExit::ConditionalJump { + condition, + bid_then, + bid_else, + } => format!( + "br {}, {}, {}", + condition.write_string(), + bid_then, + bid_else + ), + BlockExit::Switch { + value, + default, + cases, + } => format!( + "switch {}, default: {} [\n{}\n ]", + value.write_string(), + default, + cases + .iter() + .map(|(v, b)| format!(" {}:{}, {}", v, v.dtype(), b)) + .collect::>() + .join("\n") + ), + BlockExit::Return { value } => format!("ret {}", value.write_string()), + BlockExit::Unreachable => "\t\t\t\t; error state".to_string(), + } + } +} + +pub fn write_ir(ir: &TranslationUnit, write: &mut dyn Write) -> Result<()> { + ir.write_line(0, write) +} diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 0000000..922dc3d --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1,2 @@ +/platform.info +/csmith-* diff --git a/tests/ast_printer_test.rs b/tests/ast_printer_test.rs new file mode 100644 index 0000000..e071c68 --- /dev/null +++ b/tests/ast_printer_test.rs @@ -0,0 +1,21 @@ +use std::path::Path; + +use kecc::*; + +#[test] +fn ast_printer_test() { + let mut parse = Parse::default(); + let dir_path = Path::new("examples/"); + let dir = dir_path.read_dir().expect("read_dir call failed"); + for entry in dir { + let test_file = ok_or!(entry, continue); + let test_unit = parse.translate(&test_file.path().as_path()).expect( + &format!( + "parse failed {:?}", + test_file.path().into_os_string().to_str().unwrap() + ) + .to_owned(), + ); + write_c_test(&test_unit); + } +} diff --git a/tests/fuzz.py b/tests/fuzz.py new file mode 100644 index 0000000..9ffdfe2 --- /dev/null +++ b/tests/fuzz.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 + +"""Fuzzes KECC. + +For customization, one may restrict/loosen the replacement rule by adding/deleting the pair into +below `REPLACE_DICT`. + +""" + +import os +import subprocess +import itertools +import argparse +import sys +import re + +REPLACE_DICT = { + "#include \"csmith.h\"": "", + "volatile ": "", + "uint16_t": "unsigned int", + "uint32_t": "unsigned int", + "int16_t": "int", + "int32_t": "int", + "uint": "unsigned int", +} +CSMITH_DIR = "csmith-2.3.0" + +def install_csmith(tests_dir, bin_file): + global CSMITH_DIR + csmith_root_dir = os.path.join(tests_dir, CSMITH_DIR) + if not os.path.exists(bin_file): + subprocess.Popen(["curl", "https://embed.cs.utah.edu/csmith/" + CSMITH_DIR + ".tar.gz", "-o", CSMITH_DIR + ".tar.gz"], cwd=tests_dir).communicate() + subprocess.Popen(["tar", "xzvf", CSMITH_DIR + ".tar.gz"], cwd=tests_dir).communicate() + subprocess.Popen("cmake .; make -j", shell=True, cwd=csmith_root_dir).communicate() + else: + print("Using the existing csmith...") + +def generate(tests_dir, bin_file, runtime, file_name): + """Feeding options to built Csmith to randomly generate test case. + + For generality, I disabled most of the features that are enabled by default. + FYI, please take a look at `-h` flag. By adding or deleting one of `--blah-blah...` + in `options` list below, csmith will be able to generate corresponding test case. + A developer may customize the options to meet one's needs for testing. + """ + global CSMITH_DIR + options = ["--no-builtins", "--no-safe-math", "--no-unions"] + args = [bin_file] + options + dst_path = os.path.join(runtime, file_name) + + with open(dst_path, 'w') as f_dst: + subprocess.Popen(args, cwd=tests_dir, stdout=f_dst).wait() + f_dst.flush() + + return dst_path + +def preprocess(src_path, file_name): + """Preprocessing test case to fit in kecc parser specification. + + It resolves an issue that arbitrarily included header file to hinder parsing. + """ + global REPLACE_DICT, CSMITH_DIR + with open(src_path, 'r') as src: + src = str(src.read()) + + for _from, _to in REPLACE_DICT.items(): + src = src.replace(_from, _to) + + with open(os.path.join(os.path.dirname(src_path), file_name), 'w') as dst: + dst.write(str(src)) + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Fuzzing KECC.') + parser.add_argument('-n', '--num', type=int, help='The number of tests') + parser.add_argument('-p', '--print', action='store_true', help='Fuzzing C AST printer') + args = parser.parse_args() + + if args.print: + cargo_arg = "-p" + else: + raise "Specify fuzzing argument" + + tests_dir = os.path.abspath(os.path.dirname(__file__)) + csmith_bin = os.path.abspath(os.path.join(tests_dir, CSMITH_DIR, "src/csmith")) + csmith_runtime = os.path.abspath(os.path.join(tests_dir, CSMITH_DIR, "runtime/")) + install_csmith(tests_dir, csmith_bin) + + # Run cargo test infinitely + TEST_FAIL_TOKEN = "test result: FAILED." + raw_test_file = "raw_test.c" + test_file = "test.c" + try: + if args.num is None: + print("Fuzzing with infinitely many test cases. Please press [ctrl+C] to break.") + iterator = itertools.count(0) + else: + print("Fuzzing with {} test cases.".format(args.num)) + iterator = range(args.num) + + for i in iterator: + print("Test case #{}".format(i)) + preprocess(generate(tests_dir, csmith_bin, csmith_runtime, raw_test_file), test_file) + args = ["cargo", "run", "--release", "--bin", "fuzz", "--", cargo_arg, os.path.join(csmith_runtime, test_file)] + proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=tests_dir) + test_result = str(proc.stdout.read()) + if TEST_FAIL_TOKEN in test_result: + sys.exit("[Error] Test failed. Check `{}` that failed to parse.\nTo investigate, run '{}` in terminal." + .format(os.path.join(csmith_runtime, test_file), " ".join(args))) + + except KeyboardInterrupt: + proc.terminate() + print("\n[KeyboardInterrupt] Test ended") diff --git a/tests/ir_test.rs b/tests/ir_test.rs new file mode 100644 index 0000000..2b63e94 --- /dev/null +++ b/tests/ir_test.rs @@ -0,0 +1,68 @@ +use kecc::run_ir::*; +use kecc::*; +use std::path::Path; + +// TODO: cover all examples in the future +#[test] +fn ir_interpreter_test() { + // Test toy example + assert_eq!(run_example("examples/foo.c"), Ok(Value::Int(-1))); + + // Test toy example with negate unary operator + assert_eq!(run_example("examples/negate.c"), Ok(Value::Int(1))); + + // Test fibonacci function with for-loop + assert_eq!(run_example("examples/fib3.c"), Ok(Value::Int(34))); + + // Test fibonacci function with while-loop + assert_eq!(run_example("examples/fib4.c"), Ok(Value::Int(34))); + + // Test fibonacci function with do-while-loop + assert_eq!(run_example("examples/fib5.c"), Ok(Value::Int(34))); + + // Test fibonacci function with recursive function call + assert_eq!(run_example("examples/fibonacci.c"), Ok(Value::Int(34))); + + // Test example with global variable + assert_eq!(run_example("examples/foo3.c"), Ok(Value::Int(30))); + + // Test example with comma expressions + assert_eq!(run_example("examples/comma.c"), Ok(Value::Int(7))); + + // Test example with complex function call + assert_eq!(run_example("examples/foo4.c"), Ok(Value::Int(6))); + + // Test example with pointer + assert_eq!(run_example("examples/pointer.c"), Ok(Value::Int(3))); + + // Test example with sizeof + assert_eq!(run_example("examples/sizeof.c"), Ok(Value::Int(4))); + + // Test example with alignof + assert_eq!(run_example("examples/alignof.c"), Ok(Value::Int(4))); + + // Test example with simple for statement + assert_eq!(run_example("examples/simple_for.c"), Ok(Value::Int(55))); + + // Test example with conditional expression + assert_eq!(run_example("examples/cond.c"), Ok(Value::Int(5))); + + // Test example with switch statement + assert_eq!(run_example("examples/switch.c"), Ok(Value::Int(2))); +} + +fn run_example(example_path: &str) -> Result { + let example_path = Path::new(example_path); + let unit = Parse::default() + .translate(&example_path) + .expect("parse failed"); + let ir = Irgen::default() + .translate(&unit) + .expect("failed to generate ir"); + + // TODO: consider command line arguments in the future + // TODO: randomly generate argument values + let args = Vec::new(); + + run_ir(&ir, args) +}