diff --git a/Cargo.lock b/Cargo.lock index c6dd6bf..2b7169f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,8 +13,8 @@ name = "atty" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "hermit-abi 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", + "hermit-abi 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -28,19 +28,19 @@ name = "backtrace" version = "0.3.46" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "backtrace-sys 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", + "backtrace-sys 0.1.36 (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.68 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.69 (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.35" +version = "0.1.36" 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.68 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -104,16 +104,16 @@ 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.68 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.69 (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.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -157,20 +157,21 @@ dependencies = [ "failure 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "hexf 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "itertools 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "lang-c 0.8.0 (git+https://github.com/kaist-cp/lang-c)", + "lang-c 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "ordered-float 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "peg 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "wait-timeout 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "lang-c" -version = "0.8.0" -source = "git+https://github.com/kaist-cp/lang-c#44687707f07fb8c5869936dfc549459ecb30d3be" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "libc" -version = "0.2.68" +version = "0.2.69" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -189,6 +190,30 @@ dependencies = [ "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "peg" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "peg-macros 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "peg-runtime 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "peg-macros" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "peg-runtime 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "peg-runtime" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "ppv-lite86" version = "0.2.6" @@ -234,7 +259,7 @@ 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.68 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.69 (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)", @@ -333,7 +358,7 @@ 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.68 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.69 (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)", @@ -373,7 +398,7 @@ name = "wait-timeout" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -410,7 +435,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" "checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" "checksum backtrace 0.3.46 (registry+https://github.com/rust-lang/crates.io-index)" = "b1e692897359247cc6bb902933361652380af0f1b7651ae5c5013407f30e109e" -"checksum backtrace-sys 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "7de8aba10a69c8e8d7622c5710229485ec32e9d55fdad160ea559c086fdcd118" +"checksum backtrace-sys 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)" = "78848718ee1255a2485d1309ad9cdecfc2e7d0362dd11c6829364c6b35ae1bc7" "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" @@ -419,15 +444,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "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.10 (registry+https://github.com/rust-lang/crates.io-index)" = "725cf19794cf90aa94e65050cb4191ff5d8fa87a498383774c47b332e3af952e" +"checksum hermit-abi 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8a0d737e0f947a1864e93d33fdef4af8445a00d1ed8dc0c8ddb73139ea6abf15" "checksum hexf 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e54653cc32d838771a36532647afad59c4bf7155745eeeec406f71fd5d7e7538" "checksum hexf-impl 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "22eadcfadba76a730b2764eaa577d045f35e0ef5174b9c5b46adf1ee42b85e12" "checksum hexf-parse 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "79296f72d53a89096cbc9a88c9547ee8dfe793388674620e2207593d370550ac" "checksum itertools 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" -"checksum lang-c 0.8.0 (git+https://github.com/kaist-cp/lang-c)" = "" -"checksum libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)" = "dea0c0405123bba743ee3f91f49b1c7cfb684eef0da0a50110f758ccf24cdff0" +"checksum lang-c 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2554b3a7a324e3d8ad63603fe6729c4c144bab2ab36742c45a72788bff50e8ec" +"checksum libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)" = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005" "checksum num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" "checksum ordered-float 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "18869315e81473c951eb56ad5558bbc56978562d3ecfb87abb7a1e944cea4518" +"checksum peg 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9075875c14bb21f25f11cad4b6ad2e4dd443b8fb83900b2fbdd6ebd744b82e97" +"checksum peg-macros 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c24c165fd39e995246140cc78df55c56c6733ba87e6658cb3e197b8856c62852" +"checksum peg-runtime 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0c1a2897e69d986c7986747ebad425cf03746ec5e3e09bb3b2600f91301ba864" "checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" "checksum proc-macro-hack 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b773f824ff2a495833f85fcdddcf85e096949971decada2e93249fa2c6c3d32f" "checksum proc-macro-hack-impl 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0f674ccc446da486175527473ec8aa064f980b0966bbf767ee743a5dff6244a7" diff --git a/Cargo.toml b/Cargo.toml index a357e3b..fffea63 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,11 +25,11 @@ path = "bin/fuzz.rs" [dependencies] clap = { version = "2.33.0", features = ["yaml"] } -# TODO: use crates.io version when https://github.com/vickenty/lang-c/pull/16 is merged -lang-c = { git = "https://github.com/kaist-cp/lang-c" } +lang-c = "0.8.1" itertools = "0.9.0" failure = "0.1.7" tempfile = "3.1.0" ordered-float = "1.0" hexf = "0.1.0" wait-timeout = "0.2.0" +peg = "0.6.2" diff --git a/Jenkinsfile b/Jenkinsfile index d414326..856a506 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -38,9 +38,9 @@ pipeline { // When `cargo test` runs, the function `it_works()` is called in a new thread. // The stack size of a new thread is `2 MiB` on Linux, and this small stack size // can cause `stack-overflow` error when testing stack-intensive code. - // For this reason, we need to increase the default size of stack to `4 MiB`. - sh "RUST_MIN_STACK=4194304 cargo test" - sh "RUST_MIN_STACK=4194304 cargo test --release" + // For this reason, we need to increase the default size of stack to `8 MiB`. + sh "RUST_MIN_STACK=8388608 cargo test" + sh "RUST_MIN_STACK=8388608 cargo test --release" } } } diff --git a/bin/kecc.rs b/bin/kecc.rs index 663c80b..3c61edd 100644 --- a/bin/kecc.rs +++ b/bin/kecc.rs @@ -1,12 +1,17 @@ +use std::ffi::OsStr; +use std::path::Path; #[macro_use] extern crate clap; use clap::{crate_authors, crate_description, crate_version, App}; +use lang_c::ast::TranslationUnit; + #[macro_use] extern crate kecc; use kecc::{ - write, Asmgen, Deadcode, Gvn, Irgen, Mem2reg, Optimize, Parse, SimplifyCfg, Translate, O1, + ir, write, Asmgen, Deadcode, Gvn, IrParse, Irgen, Mem2reg, Optimize, Parse, SimplifyCfg, + Translate, O1, }; fn main() { @@ -19,7 +24,7 @@ fn main() { .get_matches(); let input = matches.value_of("INPUT").unwrap(); - let unit = ok_or_exit!(Parse::default().translate(&input), 1); + let input = Path::new(input); let output = matches.value_of("output").unwrap_or_else(|| "-"); let mut output: Box = if output == "-" { @@ -28,47 +33,87 @@ fn main() { Box::new(ok_or_exit!(::std::fs::File::open(output), 1)) }; + let ext = input.extension(); + if ext == Some(OsStr::new("c")) { + let input = ok_or_exit!(Parse::default().translate(&input), 1); + compile_c(&input, &mut output, &matches); + } else if ext == Some(OsStr::new("ir")) { + let mut input = ok_or_exit!(IrParse::default().translate(&input), 1); + compile_ir(&mut input, &mut output, &matches); + } else { + panic!("Unsupported file extension: {:?}", ext); + } +} + +fn compile_c( + input: &TranslationUnit, + output: &mut dyn ::std::io::Write, + matches: &clap::ArgMatches<'_>, +) { if matches.is_present("parse") { return; } if matches.is_present("print") { - write(&unit, &mut output).unwrap(); + write(input, output).unwrap(); return; } - let mut ir = match Irgen::default().translate(&unit) { + let mut ir = match Irgen::default().translate(input) { Ok(ir) => ir, Err(irgen_error) => { println!("{}", irgen_error); return; } }; + if matches.is_present("irgen") { - write(&ir, &mut output).unwrap(); + write(&ir, output).unwrap(); + return; + } + + compile_ir(&mut ir, output, matches) +} + +fn compile_ir( + input: &mut ir::TranslationUnit, + output: &mut dyn ::std::io::Write, + matches: &clap::ArgMatches<'_>, +) { + if matches.is_present("irparse") { + return; + } + + if matches.is_present("irprint") { + write(input, output).unwrap(); return; } if matches.is_present("optimize") { - O1::default().optimize(&mut ir); + O1::default().optimize(input); } else { if matches.is_present("simplify-cfg") { - SimplifyCfg::default().optimize(&mut ir); + SimplifyCfg::default().optimize(input); } if matches.is_present("mem2erg") { - Mem2reg::default().optimize(&mut ir); + Mem2reg::default().optimize(input); } if matches.is_present("deadcode") { - Deadcode::default().optimize(&mut ir); + Deadcode::default().optimize(input); } if matches.is_present("gvn") { - Gvn::default().optimize(&mut ir); + Gvn::default().optimize(input); } } - let asm = ok_or_exit!(Asmgen::default().translate(&ir), 1); - write(&asm, &mut output).unwrap(); + if matches.is_present("iroutput") { + write(input, output).unwrap(); + return; + } + + let asm = ok_or_exit!(Asmgen::default().translate(input), 1); + write(&asm, output).unwrap(); } diff --git a/bin/kecc_cli.yml b/bin/kecc_cli.yml index 2e3d4fd..30c1b65 100644 --- a/bin/kecc_cli.yml +++ b/bin/kecc_cli.yml @@ -6,11 +6,17 @@ args: - print: short: p long: print - help: Prints the input file's AST + help: Prints the input AST - irgen: short: i long: irgen help: Generates IR + - irparse: + long: irparse + help: Parses the input IR file + - irprint: + long: irprint + help: Prints the input IR AST - optimize: short: O long: optimize @@ -27,6 +33,9 @@ args: - gvn: long: gvn help: Performs gvn + - iroutput: + long: iroutput + help: Prints the output IR - output: short: o long: output diff --git a/examples/simplify_cfg/const_prop.input.ir b/examples/simplify_cfg/const_prop.input.ir new file mode 100644 index 0000000..6eae30d --- /dev/null +++ b/examples/simplify_cfg/const_prop.input.ir @@ -0,0 +1,98 @@ +fun i32 @const_prop_same { +init: + bid: b0 + allocations: + +block b0: + br undef:i1 b1() b1() + +block b1: + ret 0:i1 +} + +fun i32 @const_prop_true { +init: + bid: b0 + allocations: + +block b0: + br 1:i1 b1() b2() + +block b1: + ret 0:i1 + +block b2: + ret 0:i1 +} + +fun i32 @const_prop_false { +init: + bid: b0 + allocations: + +block b0: + br 0:i1 b1() b2() + +block b1: + ret 0:i1 + +block b2: + ret 0:i1 +} + +fun i32 @const_prop_switch_same { +init: + bid: b0 + allocations: + +block b0: + switch 42:i32 default b1() [ + 2:i32 b1() + 3:i32 b1() + ] + +block b1: + ret 0:i1 +} + +fun i32 @const_prop_switch_case { +init: + bid: b0 + allocations: + +block b0: + switch 2:i32 default b1() [ + 2:i32 b2() + 3:i32 b3() + ] + +block b1: + ret 0:i1 + +block b2: + ret 0:i1 + +block b3: + ret 0:i1 +} + +fun i32 @const_prop_switch_default { +init: + bid: b0 + allocations: + +block b0: + switch 42:i32 default b1() [ + 2:i32 b2() + 3:i32 b3() + ] + +block b1: + ret 0:i1 + +block b2: + ret 0:i1 + +block b3: + ret 0:i1 +} diff --git a/examples/simplify_cfg/const_prop.output.ir b/examples/simplify_cfg/const_prop.output.ir new file mode 100644 index 0000000..7432ca7 --- /dev/null +++ b/examples/simplify_cfg/const_prop.output.ir @@ -0,0 +1,94 @@ +fun i32 @const_prop_same { +init: + bid: b0 + allocations: + + +block b0: + j b1() + +block b1: + ret 0:i1 +} + +fun i32 @const_prop_true { +init: + bid: b0 + allocations: + + +block b0: + j b1() + +block b1: + ret 0:i1 + +block b2: + ret 0:i1 +} + +fun i32 @const_prop_false { +init: + bid: b0 + allocations: + + +block b0: + j b2() + +block b1: + ret 0:i1 + +block b2: + ret 0:i1 +} + +fun i32 @const_prop_switch_same { +init: + bid: b0 + allocations: + +block b0: + j b1() + +block b1: + ret 0:i1 +} + +fun i32 @const_prop_switch_case { +init: + bid: b0 + allocations: + + +block b0: + j b2() + +block b1: + ret 0:i1 + +block b2: + ret 0:i1 + +block b3: + ret 0:i1 +} + +fun i32 @const_prop_switch_default { +init: + bid: b0 + allocations: + + +block b0: + j b1() + +block b1: + ret 0:i1 + +block b2: + ret 0:i1 + +block b3: + ret 0:i1 +} diff --git a/examples/simplify_cfg/empty.input.ir b/examples/simplify_cfg/empty.input.ir new file mode 100644 index 0000000..60871c1 --- /dev/null +++ b/examples/simplify_cfg/empty.input.ir @@ -0,0 +1,35 @@ +fun i32 @foo { +init: + bid: b0 + allocations: + +block b0: + j b1(42:i32) + +block b1: + %b1:p0:i32 + j b3() + +block b3: + ret 37:i32 +} + +fun i32 @bar { +init: + bid: b0 + allocations: + +block b0: + j b1(42:i32) + +block b1: + %b1:p0:i32 + br 1:i1 b2() b2() + +block b2: + j b3() + +block b3: + %b3:i0:i1 = cmp eq 0:i32 0:i32 + ret 37:i32 +} diff --git a/examples/simplify_cfg/empty.output.ir b/examples/simplify_cfg/empty.output.ir new file mode 100644 index 0000000..9b521a3 --- /dev/null +++ b/examples/simplify_cfg/empty.output.ir @@ -0,0 +1,35 @@ +fun i32 @foo { +init: + bid: b0 + allocations: + +block b0: + j b1(42:i32) + +block b1: + %b1:p0:i32 + ret 37:i32 + +block b3: + ret 37:i32 +} + +fun i32 @bar { +init: + bid: b0 + allocations: + +block b0: + j b1(42:i32) + +block b1: + %b1:p0:i32 + br 1:i1 b3() b3() + +block b2: + j b3() + +block b3: + %b3:i0:i1 = cmp eq 0:i32 0:i32 + ret 37:i32 +} diff --git a/examples/simplify_cfg/merge.input.ir b/examples/simplify_cfg/merge.input.ir new file mode 100644 index 0000000..d501835 --- /dev/null +++ b/examples/simplify_cfg/merge.input.ir @@ -0,0 +1,14 @@ +fun i32 @foo { +init: + bid: b0 + allocations: + +block b0: + j b1(42:i32, 37:i32) + +block b1: + %b1:p0:i32 + %b1:p1:i32 + %b1:i0:i32 = add %b1:p0:i32 %b1:p1:i32 + ret %b1:i0:i32 +} diff --git a/examples/simplify_cfg/merge.output.ir b/examples/simplify_cfg/merge.output.ir new file mode 100644 index 0000000..7ee69be --- /dev/null +++ b/examples/simplify_cfg/merge.output.ir @@ -0,0 +1,9 @@ +fun i32 @foo { +init: + bid: b0 + allocations: + +block b0: + %b0:i0:i32 = add 42:i32 37:i32 + ret %b0:i0:i32 +} diff --git a/examples/simplify_cfg/reach.input.ir b/examples/simplify_cfg/reach.input.ir new file mode 100644 index 0000000..24c0153 --- /dev/null +++ b/examples/simplify_cfg/reach.input.ir @@ -0,0 +1,17 @@ +fun i32 @foo { +init: + bid: b0 + allocations: + +block b0: + j b1() + +block b1: + j b0() + +block b2: + j b3() + +block b3: + j b2() +} diff --git a/examples/simplify_cfg/reach.output.ir b/examples/simplify_cfg/reach.output.ir new file mode 100644 index 0000000..32ce0e1 --- /dev/null +++ b/examples/simplify_cfg/reach.output.ir @@ -0,0 +1,11 @@ +fun i32 @foo { +init: + bid: b0 + allocations: + +block b0: + j b1() + +block b1: + j b0() +} diff --git a/src/ir/dtype.rs b/src/ir/dtype.rs index adacf38..f718573 100644 --- a/src/ir/dtype.rs +++ b/src/ir/dtype.rs @@ -946,7 +946,11 @@ impl fmt::Display for Dtype { write!(f, "{}f{}", if *is_const { "const " } else { "" }, width) } Self::Pointer { inner, is_const } => { - write!(f, "{}*{}", inner, if *is_const { "const" } else { "" }) + if *is_const { + write!(f, "*const {}", inner) + } else { + write!(f, "*{}", inner) + } } Self::Array { inner, size, .. } => write!(f, "[{} x {}]", size, inner,), Self::Struct { diff --git a/src/ir/mod.rs b/src/ir/mod.rs index 1c0aa46..3e1f317 100644 --- a/src/ir/mod.rs +++ b/src/ir/mod.rs @@ -1,5 +1,6 @@ mod dtype; mod interp; +mod parse; mod write_ir; use core::convert::TryFrom; @@ -13,13 +14,14 @@ use std::hash::{Hash, Hasher}; pub use dtype::{Dtype, DtypeError, HasDtype}; pub use interp::{interp, Value}; +pub use parse::Parse; -#[derive(Debug, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub struct TranslationUnit { pub decls: HashMap, } -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum Declaration { Variable { dtype: Dtype, @@ -312,7 +314,16 @@ impl JumpArg { impl fmt::Display for JumpArg { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}({:?})", self.bid, self.args) + write!( + f, + "{}({})", + self.bid, + self.args + .iter() + .map(|a| a.to_string()) + .collect::>() + .join(", ") + ) } } @@ -728,7 +739,7 @@ impl fmt::Display for Constant { } ), Self::Float { value, .. } => write!(f, "{}", value), - Self::GlobalVariable { name, .. } => write!(f, "%{}", name), + Self::GlobalVariable { name, .. } => write!(f, "@{}", name), } } } diff --git a/src/ir/parse.rs b/src/ir/parse.rs new file mode 100644 index 0000000..2938b86 --- /dev/null +++ b/src/ir/parse.rs @@ -0,0 +1,327 @@ +use std::fs; +use std::path::Path; + +use lang_c::ast::{BinaryOperator, UnaryOperator}; + +use crate::ir::*; +use crate::Translate; + +peg::parser! { + grammar ir_parse() for str { + rule whitespace() = quiet!{[' ' | '\n' | '\t']} + + rule _() = whitespace()* + + rule __() = whitespace()+ + + pub rule translation_unit() -> TranslationUnit + = _ ds:(named_decl() ** __) _ { + let mut decls = HashMap::new(); + for decl in ds { + let result = decls.insert(decl.name.unwrap(), decl.inner); + assert!(result.is_none()); + } + TranslationUnit { decls } + } + + rule named_decl() -> Named = + "var" __ dtype:dtype() __ var:global_variable() _ "=" _ initializer:initializer() { + Named::new(Some(var), Declaration::Variable { + dtype: dtype, + initializer, + }) + } + / + "fun" __ dtype:dtype() __ var:global_variable() _ "{" _ fun_body:fun_body() _ "}" { + Named::new(Some(var), Declaration::Function { + signature: FunctionSignature::new(Dtype::function(Dtype::int(32), Vec::new())), + definition: Some(fun_body), + }) + } + / + "fun" __ dtype:dtype() __ var:global_variable() { + Named::new(Some(var), Declaration::Function { + signature: FunctionSignature::new(Dtype::function(Dtype::int(32), Vec::new())), + definition: None, + }) + } + + rule dtype() -> Dtype = + "unit" { Dtype::unit() } + / + "u" n:number() { Dtype::int(n).set_signed(false) } + / + "i" n:number() { Dtype::int(n) } + / + "*" _ inner:dtype() { Dtype::pointer(inner) } + / expected!("dtype") + + rule id() -> String + = n:$(['_' | 'a'..='z' | 'A'..='Z']['_' | 'a'..='z' | 'A'..='Z' | '0'..='9']*) { + String::from(n) + } + / expected!("id") + + rule global_variable() -> String + = "@" id:id() { + id + } + / expected!("global-variable") + + rule arg() -> usize // TODO + = "" { + todo!() + } + + rule fun_body() -> FunctionDefinition + = "init:" __ "bid:" _ bid_init:bid() _ "allocations:" _ allocations:(allocation() ** __) _ blocks:(block() ** __) { + FunctionDefinition { + allocations: allocations.into_iter().map(|a| a.1).collect(), + blocks: blocks.into_iter().collect(), + bid_init, + } + } + + rule allocation() -> (usize, Named) + = "%l" number:number() ":" dtype:dtype() ":" name:id() { + (number, Named::new(Some(name), dtype)) + } + + rule block() -> (BlockId, Block) + = "block" __ bid:bid() _ ":" _ phinodes:(phinode() ** __) _ instructions:(instruction() ** __) _ exit:exit() { + if !phinodes.iter().enumerate().all(|(i1, (bid2, i2, _))| bid == *bid2 && i1 == *i2) { + panic!("Phinode id mismatches"); + } + + if !instructions.iter().enumerate().all(|(i1, (bid2, i2, _))| bid == *bid2 && i1 == *i2) { + panic!("Instruction id mismatches"); + } + + (bid, + Block { + phinodes: phinodes.into_iter().map(|(_, _, phi)| phi).collect(), + instructions: instructions.into_iter().map(|(_, _, instr)| instr).collect(), + exit, + }) + } + + rule number() -> usize + = n:$(['0'..='9']+) { + n.parse().unwrap() + } + / expected!("number") + + rule bid() -> BlockId + = "b" n:number() { + BlockId(n) + } + / expected!("bid") + + rule phinode() -> (BlockId, usize, Named) + = "%" bid:bid() ":p" number:number() ":" dtype:dtype() name:(":" name:id() { name })? { + (bid, number, Named::new(name, dtype)) + } + / expected!("phinode") + + rule instruction() -> (BlockId, usize, Named) + = "%" bid:bid() ":i" number:number() ":" dtype:dtype() name:(":" name:id() { name })? _ "=" _ instruction:instruction_inner() { + (bid, number, Named::new(name, instruction)) + } + / expected!("instruction") + + rule instruction_inner() -> Instruction = + "call" __ callee:operand() _ "(" _ args:(operand() ** (_ "," _)) _ ")" { + Instruction::Call { + callee, + args, + return_type: Dtype::unit(), // TODO + } + } + / + "load" __ ptr:operand() { + Instruction::Load { ptr } + } + / + "store" __ value:operand() __ ptr:operand() { + Instruction::Store { ptr, value } + } + / + "typecast" __ value:operand() __ "to" __ target_dtype:dtype() { + Instruction::TypeCast { value, target_dtype } + } + / + "minus" __ operand:operand() { + let dtype = operand.dtype(); + Instruction::UnaryOp { + op: UnaryOperator::Minus, + operand, + dtype, + } + } + / + op:arith_op() __ lhs:operand() __ rhs:operand() { + let dtype = lhs.dtype(); + assert_eq!(&dtype, &rhs.dtype()); + Instruction::BinOp { + op, + lhs, + rhs, + dtype, + } + } + / + "cmp" __ op:comparison_op() __ lhs:operand() __ rhs:operand() { + assert_eq!(lhs.dtype(), rhs.dtype()); + Instruction::BinOp { + op, + lhs, + rhs, + dtype: Dtype::BOOL, + } + } + / + "" { + todo!() + } + / expected!("instruction_inner") + + rule arith_op() -> BinaryOperator = + "add" { BinaryOperator::Plus } + / + "sub" { BinaryOperator::Minus } + + rule comparison_op() -> BinaryOperator = + "eq" { BinaryOperator::Equals } + / + "ne" { BinaryOperator::NotEquals } + / + "lt" { BinaryOperator::Less } + / + "le" { BinaryOperator::LessOrEqual } + / + "gt" { BinaryOperator::Greater } + / + "ge" { BinaryOperator::GreaterOrEqual } + + rule exit() -> BlockExit = + "j" __ arg:jump_arg() { + BlockExit::Jump { arg } + } + / + "br" __ condition:operand() __ arg_then:jump_arg() __ arg_else:jump_arg() { + BlockExit::ConditionalJump { condition, arg_then, arg_else } + } + / + "switch" __ value:operand() __ "default" __ default:jump_arg() _ "[" _ cases:(switch_case() ** __) _ "]" { + BlockExit::Switch { value, default, cases } + } + / + "ret" __ value:operand() { + BlockExit::Return { value } + } + / + "unreachable" { + BlockExit::Unreachable + } + + rule constant() -> Constant = + n:number() { + Constant::int(n as _, Dtype::int(128)) // TODO: the right dtype + } + / + "undef" { + Constant::undef(Dtype::unit()) // TODO + } + / + "unit" { + Constant::undef(Dtype::unit()) // TODO + } + / + "" { + todo!() + } + + + rule register_id() -> RegisterId = + "%l" id:number() { + RegisterId::local(id) + } + / + "%" bid:bid() ":p" id:number() { + RegisterId::arg(bid, id) + } + / + "%" bid:bid() ":i" id:number() { + RegisterId::temp(bid, id) + } + + rule operand() -> Operand = + name:global_variable() { + Operand::Constant(Constant::GlobalVariable { + name, + dtype: Dtype::unit(), // TODO + }) + } + / + constant:constant() ":" dtype:dtype() { + let constant = match (&constant, &dtype) { + (Constant::Int { value, .. }, Dtype::Int { width, is_signed, .. }) => { + Constant::Int { + value: *value, + width: *width, + is_signed: *is_signed, + } + } + (Constant::Undef { .. }, _) => { + Constant::undef(dtype.clone()) + } + _ => constant.clone(), + }; + Operand::Constant(constant) + } + / + rid:register_id() ":" dtype:dtype() { + Operand::Register { rid, dtype } + } + + rule jump_arg() -> JumpArg + = bid:bid() _ "(" _ args:(operand() ** (_ "," _)) _ ")" { + JumpArg { bid, args } + } + + rule switch_case() -> (Constant, JumpArg) + = operand:operand() __ jump_arg:jump_arg() { + let constant = operand.get_constant().unwrap().clone(); + (constant, jump_arg) + } + + rule initializer() -> Option = + "default" { + None + } + / + "" { + todo!() + } + } +} + +#[derive(Debug)] +pub enum Error { + IoError(std::io::Error), + ParseError(peg::error::ParseError), +} + +#[derive(Default)] +pub struct Parse {} + +impl> Translate

for Parse { + type Target = TranslationUnit; + type Error = Error; + + fn translate(&mut self, source: &P) -> Result { + let ir = fs::read_to_string(source).map_err(Error::IoError)?; + let ir = ir_parse::translation_unit(&ir).map_err(Error::ParseError)?; + Ok(ir) + } +} diff --git a/src/ir/write_ir.rs b/src/ir/write_ir.rs index d51a769..79dedd4 100644 --- a/src/ir/write_ir.rs +++ b/src/ir/write_ir.rs @@ -9,21 +9,14 @@ 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); + writeln!(write)?; (name, decl).write_line(indent, write)?; } @@ -40,73 +33,56 @@ impl WriteLine for (&String, &Declaration) { Declaration::Variable { dtype, initializer } => { writeln!( write, - "{} = {} {}", + "var {} @{} = {}", + dtype, name, if let Some(init) = initializer { init.write_string() } else { "default".to_string() - }, - dtype + } )?; } Declaration::Function { signature, definition, } => { - let declaration = format!( - "{} @{}({})", - signature.ret, - name, - signature - .params - .iter() - .map(|d| d.to_string()) - .collect::>() - .join(", "), - ); + if let Some(definition) = definition.as_ref() { + // print function definition + writeln!(write, "fun {} @{} {{", signature.ret, name)?; + // print meta data for function + writeln!( + write, + "init:\n bid: {}\n allocations: \n{}", + definition.bid_init, + definition + .allocations + .iter() + .enumerate() + .map(|(i, a)| format!( + " %l{}:{}{}", + i, + a.deref(), + if let Some(name) = a.name() { + format!(":{}", name) + } else { + "".into() + } + )) + .collect::>() + .join("\n") + )?; - match definition.as_ref() { - Some(defintion) => { - // print function definition - writeln!(write, "define {} {{", declaration)?; - // print meta data for function - writeln!( - write, - "init:\n bid: {}\n allocations: \n{}\n", - defintion.bid_init, - defintion - .allocations - .iter() - .enumerate() - .map(|(i, a)| format!( - " %l{}:{}{}", - i, - a.deref(), - if let Some(name) = a.name() { - format!(":{}", name) - } else { - "".into() - } - )) - .collect::>() - .join("\n") - )?; - - for (id, block) in &defintion.blocks { - writeln!(write, "block {}", 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)?; + for (id, block) in &definition.blocks { + writeln!(write, "\nblock {}:", id)?; + (id, block).write_line(indent + 1, write)?; } + + writeln!(write, "}}")?; + } else { + // print declaration line only + writeln!(write, "fun {} @{}", signature.ret, name)?; + writeln!(write)?; } } } @@ -260,7 +236,7 @@ impl WriteString for BlockExit { default, cases, } => format!( - "switch {}, default: {} [\n{}\n ]", + "switch {} default: {} [\n{}\n ]", value.write_string(), default, cases diff --git a/src/lib.rs b/src/lib.rs index 9135206..d55ede3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,9 @@ #![deny(warnings)] +// Neccessary for skeleton code. #![allow(unreachable_code)] +// Necessary to allow `iter.fold(false, |l, r| l || r)`. It's used when iteration should not be +// short-circuited. +#![allow(clippy::unnecessary_fold)] mod tests; mod utils; @@ -18,7 +22,11 @@ pub use utils::*; pub use write_base::write; pub use c::Parse; +pub use ir::Parse as IrParse; pub use asmgen::Asmgen; pub use irgen::Irgen; -pub use opt::{Deadcode, Gvn, Mem2reg, Optimize, Repeat, SimplifyCfg, O0, O1}; +pub use opt::{ + Deadcode, FunctionPass, Gvn, Mem2reg, Optimize, Repeat, SimplifyCfg, SimplifyCfgConstProp, + SimplifyCfgEmpty, SimplifyCfgMerge, SimplifyCfgReach, O0, O1, +}; diff --git a/src/opt/mod.rs b/src/opt/mod.rs index 6ef0c04..7e10fa4 100644 --- a/src/opt/mod.rs +++ b/src/opt/mod.rs @@ -9,7 +9,9 @@ mod simplify_cfg; pub use deadcode::Deadcode; pub use gvn::Gvn; pub use mem2reg::Mem2reg; -pub use simplify_cfg::SimplifyCfg; +pub use simplify_cfg::{ + SimplifyCfg, SimplifyCfgConstProp, SimplifyCfgEmpty, SimplifyCfgMerge, SimplifyCfgReach, +}; use crate::ir; @@ -63,7 +65,10 @@ where T: Optimize, { fn optimize(&mut self, code: &mut ir::TranslationUnit) -> bool { - code.decls.iter_mut().any(|(_, decl)| self.optimize(decl)) + code.decls + .iter_mut() + .map(|(_, decl)| self.optimize(decl)) + .fold(false, |l, r| l || r) } } diff --git a/src/opt/simplify_cfg.rs b/src/opt/simplify_cfg.rs index 006660a..12eb1e8 100644 --- a/src/opt/simplify_cfg.rs +++ b/src/opt/simplify_cfg.rs @@ -3,7 +3,7 @@ use crate::opt::FunctionPass; use crate::*; pub type SimplifyCfg = - FunctionPass>; + FunctionPass>; /// Simplifies block exits by propagating constants. #[derive(Default)] @@ -17,6 +17,10 @@ pub struct SimplifyCfgReach {} #[derive(Default)] pub struct SimplifyCfgMerge {} +/// Removes empty blocks +#[derive(Default)] +pub struct SimplifyCfgEmpty {} + impl Optimize for SimplifyCfgConstProp { fn optimize(&mut self, _code: &mut FunctionDefinition) -> bool { todo!("homework 3") @@ -34,3 +38,9 @@ impl Optimize for SimplifyCfgMerge { todo!("homework 3") } } + +impl Optimize for SimplifyCfgEmpty { + fn optimize(&mut self, _code: &mut FunctionDefinition) -> bool { + todo!("homework 3") + } +} diff --git a/src/tests.rs b/src/tests.rs index e178b78..36a3cc2 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,5 +1,6 @@ use lang_c::ast::*; use std::fs::{self, File}; +use std::io::{stderr, Write}; use std::path::Path; use std::process::{Command, Stdio}; use tempfile::tempdir; @@ -32,7 +33,7 @@ pub fn test_irgen(unit: &TranslationUnit, path: &Path) { .expect("failed to parse the given program"); let file_path = path.display().to_string(); - let bin_path = path.with_extension("exe").as_path().display().to_string(); + let bin_path = path.with_extension("irgen").as_path().display().to_string(); // Compile c file: If fails, test is vacuously success if !Command::new("gcc") @@ -90,3 +91,108 @@ pub fn test_irgen(unit: &TranslationUnit, path: &Path) { println!("gcc: {}, kecc: {}", status as i8, value as i8); assert_eq!(status as i8, value as i8); } + +pub fn test_irparse(unit: &TranslationUnit, path: &Path) { + // Check if the file has .c extension + assert_eq!(path.extension(), Some(std::ffi::OsStr::new("c"))); + + // Test parse + c::Parse::default() + .translate(&path) + .expect("failed to parse the given program"); + + let file_path = path.display().to_string(); + let bin_path = path + .with_extension("irparse") + .as_path() + .display() + .to_string(); + + // Compile c file: If fails, test is vacuously success + if !Command::new("gcc") + .args(&["-O1", &file_path, "-o", &bin_path]) + .stderr(Stdio::null()) + .status() + .unwrap() + .success() + { + return; + } + + // Execute compiled executable + let mut child = Command::new(fs::canonicalize(bin_path.clone()).unwrap()) + .spawn() + .expect("failed to execute the compiled executable"); + + Command::new("rm") + .arg(bin_path) + .status() + .expect("failed to remove compiled executable"); + + let status = some_or!( + child + .wait_timeout_ms(500) + .expect("failed to obtain exit status from child process"), + { + println!("timeout occurs"); + child.kill().unwrap(); + child.wait().unwrap(); + return; + } + ); + let _status = some_or!(status.code(), return); + + let ir = match Irgen::default().translate(unit) { + Ok(ir) => ir, + Err(irgen_error) => panic!("{}", irgen_error), + }; + + 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(); + + crate::write(&ir, &mut temp_file).unwrap(); + + let new_ir = ir::Parse::default() + .translate(&temp_file_path.as_path()) + .expect("parse failed while parsing the output from implemented printer"); + drop(temp_file); + assert_eq!(ir, new_ir); + temp_dir.close().expect("temp dir deletion failed"); +} + +pub fn test_opt, P2: AsRef, O: Optimize>( + from: &P1, + to: &P2, + opt: &mut O, +) { + let from = ir::Parse::default() + .translate(from) + .expect("parse failed while parsing the output from implemented printer"); + let mut ir = from.clone(); + let to = ir::Parse::default() + .translate(to) + .expect("parse failed while parsing the output from implemented printer"); + opt.optimize(&mut ir); + + if ir != to { + stderr() + .lock() + .write_fmt(format_args!( + "[test_opt] actual outcome mismatches with the expected outcome.\n\n[before opt]" + )) + .unwrap(); + crate::write(&from, &mut stderr()).unwrap(); + stderr() + .lock() + .write_fmt(format_args!("\n[after opt]")) + .unwrap(); + crate::write(&ir, &mut stderr()).unwrap(); + stderr() + .lock() + .write_fmt(format_args!("\n[after opt (expected)]")) + .unwrap(); + crate::write(&to, &mut stderr()).unwrap(); + panic!("[test_opt]"); + } +} diff --git a/tests/test_examples.rs b/tests/test_examples.rs index 142b8fd..4bec451 100644 --- a/tests/test_examples.rs +++ b/tests/test_examples.rs @@ -5,7 +5,7 @@ use lang_c::ast::*; use kecc::*; -fn test_dir(path: &Path, f: F) +fn test_dir(path: &Path, ext: &OsStr, f: F) where F: Fn(&TranslationUnit, &Path), { @@ -15,7 +15,7 @@ where let entry = ok_or!(entry, continue); let path = entry.path(); - if !(path.is_file() && path.extension() == Some(&OsStr::new("c"))) { + if !(path.is_file() && path.extension() == Some(ext)) { continue; } @@ -33,11 +33,45 @@ where #[test] fn test_examples_write_c() { - test_dir(Path::new("examples/"), test_write_c); - test_dir(Path::new("examples/hw1"), test_write_c); + test_dir(Path::new("examples/"), &OsStr::new("c"), test_write_c); + test_dir(Path::new("examples/hw1"), &OsStr::new("c"), test_write_c); } #[test] fn test_examples_irgen() { - test_dir(Path::new("examples/"), test_irgen); + test_dir(Path::new("examples/"), &OsStr::new("c"), test_irgen); +} + +// TODO: make it work! +#[test] +#[ignore] +fn test_examples_irparse() { + test_dir(Path::new("examples/"), &OsStr::new("c"), test_irparse); +} + +#[test] +fn test_examples_simplify_cfg() { + test_opt( + &Path::new("examples/simplify_cfg/const_prop.input.ir"), + &Path::new("examples/simplify_cfg/const_prop.output.ir"), + &mut FunctionPass::::default(), + ); + + test_opt( + &Path::new("examples/simplify_cfg/reach.input.ir"), + &Path::new("examples/simplify_cfg/reach.output.ir"), + &mut FunctionPass::::default(), + ); + + test_opt( + &Path::new("examples/simplify_cfg/merge.input.ir"), + &Path::new("examples/simplify_cfg/merge.output.ir"), + &mut FunctionPass::::default(), + ); + + test_opt( + &Path::new("examples/simplify_cfg/empty.input.ir"), + &Path::new("examples/simplify_cfg/empty.output.ir"), + &mut FunctionPass::::default(), + ); }