From 094cbfdd2c836ec8f746e053fd0f155acbfade7c Mon Sep 17 00:00:00 2001 From: Janggun Lee Date: Mon, 21 Nov 2022 15:27:04 +0900 Subject: [PATCH] Lots of improvements. * Better script names and grammar fix. * Bump Rust * Enforce more lints. * Improve few struct definitions by removing box. * Many minor implementation improvements. --- .gitignore | 6 +- Cargo.lock | 12 +- Cargo.toml | 4 +- README.md | 64 ++-- bin/kecc.rs | 4 +- rust-toolchain | 2 +- scripts/copy-to-kecc-public.sh | 3 - .../{grade-hw7.sh => grade-asmgen-small.sh} | 2 +- .../{grade-hw7-small.sh => grade-asmgen.sh} | 2 +- scripts/{grade-hw6.sh => grade-deadcode.sh} | 2 +- scripts/{grade-hw5.sh => grade-gvn.sh} | 2 +- ...rade-hw2-small.sh => grade-irgen-small.sh} | 2 +- scripts/{grade-hw2.sh => grade-irgen.sh} | 2 +- scripts/{grade-hw4.sh => grade-mem2reg.sh} | 2 +- .../{grade-hw3.sh => grade-simplify_cfg.sh} | 2 +- scripts/{grade-hw1.sh => grade-write_c.sh} | 2 +- scripts/make-public.py | 19 - scripts/make-submissions.sh | 14 +- scripts/update-public.sh | 16 - src/asm/mod.rs | 41 +-- src/asm/write_asm.rs | 6 +- src/asmgen/mod.rs | 2 +- src/c/ast_equiv.rs | 6 +- src/c/parse.rs | 2 +- src/ir/dtype.rs | 344 +++++++++--------- src/ir/equiv.rs | 2 +- src/ir/interp.rs | 103 +++--- src/ir/mod.rs | 111 +++--- src/ir/parse.rs | 28 +- src/ir/visualize.rs | 9 +- src/ir/write_ir.rs | 6 +- src/irgen/mod.rs | 4 +- src/lib.rs | 41 ++- src/opt/deadcode.rs | 2 +- src/opt/gvn.rs | 2 +- src/opt/mem2reg.rs | 2 +- src/opt/mod.rs | 8 +- src/opt/simplify_cfg.rs | 8 +- src/tests.rs | 47 +-- tests/test_examples.rs | 8 +- 40 files changed, 446 insertions(+), 498 deletions(-) delete mode 100755 scripts/copy-to-kecc-public.sh rename scripts/{grade-hw7.sh => grade-asmgen-small.sh} (91%) rename scripts/{grade-hw7-small.sh => grade-asmgen.sh} (93%) rename scripts/{grade-hw6.sh => grade-deadcode.sh} (92%) rename scripts/{grade-hw5.sh => grade-gvn.sh} (68%) rename scripts/{grade-hw2-small.sh => grade-irgen-small.sh} (92%) rename scripts/{grade-hw2.sh => grade-irgen.sh} (94%) rename scripts/{grade-hw4.sh => grade-mem2reg.sh} (92%) rename scripts/{grade-hw3.sh => grade-simplify_cfg.sh} (91%) rename scripts/{grade-hw1.sh => grade-write_c.sh} (94%) delete mode 100755 scripts/make-public.py delete mode 100755 scripts/update-public.sh diff --git a/.gitignore b/.gitignore index a55d6a3..01c49fd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -/target -/hw*.zip -/final.zip +target +*.zip +rustfmt.toml **/*.rs.bk diff --git a/Cargo.lock b/Cargo.lock index 9e2019c..c3336e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,9 +33,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.0.17" +version = "4.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06badb543e734a2d6568e19a40af66ed5364360b9226184926f89d229b4b4267" +checksum = "91b9970d7505127a162fdaa9b96428d28a479ba78c9ec7550a63a5d9863db682" dependencies = [ "atty", "bitflags", @@ -48,9 +48,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.0.13" +version = "4.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42f169caba89a7d512b5418b09864543eeb4d497416c917d7137863bd2076ad" +checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014" dependencies = [ "heck", "proc-macro-error", @@ -178,9 +178,9 @@ checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" [[package]] name = "ordered-float" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f74e330193f90ec45e2b257fa3ef6df087784157ac1ad2c1e71c62837b03aa7" +checksum = "d84eb1409416d254e4a9c8fa56cc24701755025b458f0fcd8e59e1f5f40c23bf" dependencies = [ "num-traits", ] diff --git a/Cargo.toml b/Cargo.toml index 2e47205..4c763df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,12 +29,12 @@ required-features = ["build-bin"] build-bin = ["clap"] [dependencies] -clap = { version = "4.0.17", features = ["derive"], optional = true } +clap = { version = "4.0.22", features = ["derive"], optional = true } thiserror = "1.0.37" lang-c = "0.14.0" itertools = "0.10.5" tempfile = "3.3.0" -ordered-float = "3.3.0" +ordered-float = "3.4.0" hexf-parse = "0.2.1" wait-timeout = "0.2.0" peg = "0.8.1" diff --git a/README.md b/README.md index 3a4dd2c..3b60980 100644 --- a/README.md +++ b/README.md @@ -59,9 +59,11 @@ RUST_MIN_STACK=33554432 cargo nextest run test_examples_end_to_end # run irge ## Fuzzing -We encourage you to do homework using the test-driven development approach (TDD). You randomly -generate test input, and if it fails, then reduce it as much as possible and manually inspect the -reduced test input. For example: +We encourage you to do homework using the test-driven development (TDD)approach. You will +randomly generate a test input, and if it fails, +reduce it as much as possible and +manually inspect the reduced test input. +For example: ```sh # Randomly generates test inputs and tests them @@ -75,8 +77,7 @@ cat tests/test_reduced.c ``` `` can be `--print` or `--irgen`. It shall be the one used in [Run](#run). - -For more information on usage, please refer to the [Fuzzer User's Manual](tests/README.md). +For more information, please refer to the [Fuzzer User's Manual](tests/README.md). ### Install @@ -95,18 +96,18 @@ python3 tests/fuzz.py --help # print options python3 tests/fuzz.py --print -n10 # test C AST printer for 10 times ``` -We use `csmith` to randomly generate C source codes. `csmith` will be automatically downloaded and -built by the test script. For more information, we refer to the -[Csmith](https://embed.cs.utah.edu/csmith/) homepage. +We use `csmith` to randomly generate C source codes. +`csmith` will be automatically downloaded and built by the test script. +For more information, we refer to the [Csmith](https://embed.cs.utah.edu/csmith/) homepage. ### Reduce -When the fuzzer finds a buggy input program for your compiler, it is highly likely that the input -program is too big to manually inspect. We use `creduce` that reduces the buggy input program as -much as possible. +When the fuzzer finds a buggy input program for your compiler, +the input program is likely too big to manually inspect. +We use `creduce` that reduces the buggy input program as much as possible. -Suppose `tests/test_polished.c` is the buggy input program. Then the following script reduces the -program to `tests/test_reduced.c`: +Suppose `tests/test_polished.c` is the buggy input program. +Then the following script reduces the program to `tests/test_reduced.c`: ```sh python3 tests/fuzz.py --reduce @@ -114,15 +115,17 @@ python3 tests/fuzz.py --reduce `` can be `--print` or `--irgen`. It shall be the one used in [Run](#run). -### How it reduces test case? +### How does it reduces the test case? -The script performs unguided test-case reduction using `creduce`: given a buggy program, it randomly -reduces the program; check if the reduced program still fails on the test, and if so, replaces the -given program with the reduced one; repeat until you get a small enough buggy program. For more -information, we refer to the [Creduce](https://embed.cs.utah.edu/creduce/) homepage. +The script performs unguided test-case reduction using `creduce`: given a buggy program, it +randomly reduces the program; +check if the reduced program still fails on the test, and +if so, replaces the given program with the reduced one; +repeat until you get a small enough buggy program. +For more information, we refer to the [Creduce](https://embed.cs.utah.edu/creduce/) homepage. -**[NOTICE]** The fuzzer supports Ubuntu 20.04 only. It may work for other platforms, but if it -doesn't, please run the fuzzer in Ubuntu 20.04. +**[NOTICE]** The fuzzer only supports Ubuntu 20.04. +It may work for other platforms, but if it doesn't, please run the fuzzer in Ubuntu 20.04. ## Running RISC-V Binaries @@ -156,19 +159,22 @@ echo $? ### Debugging Assembly -You can use QEMU's debugging facilities to investigate the generated assembly works correctly. +You can use QEMU's debugging facilities to investigate whether the generated assembly works correctly. -Open two terminal windows. In one, compile the assembly with `-ggdb` option and starts up gdb server with 8888 port. (If the port 8888 is already in use, then try with different port like 8889, 8890, ...) +Open two terminal windows. +In one, compile the assembly with `-ggdb` option and start up a gdb server with 8888 port. +(If 8888 is already in use, then try with a different port like 8889, 8890, ...) ```sh # Link to an RISC-V executable with `-ggdb` option riscv64-linux-gnu-gcc -ggdb -static hello.S -o hello # Emulate the executable and wait for a debugging connection from GDB -qemu-riscv64-static -g 8888 hello +qemu-riscv64-static -g 8888 hello ``` -In the second terminal, run `gdb-multiarch` and set some configurations. You should see something like this, +In the second terminal, run `gdb-multiarch` and set some configurations. +You should see something like this, ``` $ gdb-multiarch @@ -194,7 +200,7 @@ Remote debugging using localhost:8888 warning: No executable has been specified and target does not support determining executable automatically. Try using the "file" command. 0x0000000000010348 in ?? () -(gdb) file hello +(gdb) file hello A program is being debugged already. Are you sure you want to change the file? (y or n) y Reading symbols from hello... @@ -205,10 +211,11 @@ Dump of assembler code for function main: 0x000000000001044c <+6>: sd s0,96(sp) 0x000000000001044e <+8>: addi s0,sp,104 End of assembler dump. -(gdb) +(gdb) ``` -Now you can debug the assembly using the GDB commands. For more information on GDB commands, see: +Now you can debug the assembly using the GDB commands. +For more information on GDB commands, see: - Full guide: http://sourceware.org/gdb/current/onlinedocs/gdb/ - Cheatsheet: https://cs.brown.edu/courses/cs033/docs/guides/gdb.pdf @@ -224,4 +231,5 @@ make run ## Submission - Submit the corresponding files to [gg.kaist.ac.kr](https://gg.kaist.ac.kr). -- Run `./scripts/make-submissions.sh` to generate `hw2.zip` to `final.zip`, which you should submit for homework 2 to final project. +- Run `./scripts/make-submissions.sh` to generate `irgen.zip` to `final.zip`, + which you should submit for homework 2 to the final project. diff --git a/bin/kecc.rs b/bin/kecc.rs index 1b75af6..7c18829 100644 --- a/bin/kecc.rs +++ b/bin/kecc.rs @@ -212,8 +212,8 @@ fn compile_ir( assert_eq!(width, 32); assert!(is_signed); - // When obtain status from `gcc` executable process, status value is truncated to byte size. - // So, we also truncate result value to byte size before printing it. + // When obtaining status from `gcc` executable process, the status value is truncated to + // byte size. So, we also truncate the result value to byte size before printing it. println!("[result] {:?}", value as u8); return; } diff --git a/rust-toolchain b/rust-toolchain index 9405730..902c741 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -1.64.0 +1.65.0 diff --git a/scripts/copy-to-kecc-public.sh b/scripts/copy-to-kecc-public.sh deleted file mode 100755 index b679503..0000000 --- a/scripts/copy-to-kecc-public.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -rsync --exclude=".git" --delete --archive ./ ../kecc-public diff --git a/scripts/grade-hw7.sh b/scripts/grade-asmgen-small.sh similarity index 91% rename from scripts/grade-hw7.sh rename to scripts/grade-asmgen-small.sh index 71a3f61..6dea427 100755 --- a/scripts/grade-hw7.sh +++ b/scripts/grade-asmgen-small.sh @@ -8,4 +8,4 @@ cargo fmt --all -- --check # run `cargo fmt` to auto-correct format. cargo clippy # Run tests. -RUST_MIN_STACK=33554432 cargo test --release test_examples_asmgen +RUST_MIN_STACK=33554432 cargo test --release test_examples_asmgen_small -- --nocapture diff --git a/scripts/grade-hw7-small.sh b/scripts/grade-asmgen.sh similarity index 93% rename from scripts/grade-hw7-small.sh rename to scripts/grade-asmgen.sh index 8660a1b..304ecaf 100755 --- a/scripts/grade-hw7-small.sh +++ b/scripts/grade-asmgen.sh @@ -8,4 +8,4 @@ cargo fmt --all -- --check # run `cargo fmt` to auto-correct format. cargo clippy # Run tests. -RUST_MIN_STACK=33554432 cargo test --release test_examples_asmgen_small +RUST_MIN_STACK=33554432 cargo test --release test_examples_asmgen -- --nocapture diff --git a/scripts/grade-hw6.sh b/scripts/grade-deadcode.sh similarity index 92% rename from scripts/grade-hw6.sh rename to scripts/grade-deadcode.sh index 43fbe4a..b3ed3f2 100755 --- a/scripts/grade-hw6.sh +++ b/scripts/grade-deadcode.sh @@ -8,4 +8,4 @@ cargo fmt --all -- --check # run `cargo fmt` to auto-correct format. cargo clippy # Run tests. -RUST_MIN_STACK=33554432 cargo test --release test_examples_deadcode +RUST_MIN_STACK=33554432 cargo test --release test_examples_deadcode -- --nocapture diff --git a/scripts/grade-hw5.sh b/scripts/grade-gvn.sh similarity index 68% rename from scripts/grade-hw5.sh rename to scripts/grade-gvn.sh index 85f20bf..5852c2b 100755 --- a/scripts/grade-hw5.sh +++ b/scripts/grade-gvn.sh @@ -8,4 +8,4 @@ cargo fmt --all -- --check # run `cargo fmt` to auto-correct format. cargo clippy # Run tests. -RUST_MIN_STACK=33554432 cargo test --release test_examples_gvn +RUST_MIN_STACK=33554432 cargo test --release test_examples_gvn -- --nocapture diff --git a/scripts/grade-hw2-small.sh b/scripts/grade-irgen-small.sh similarity index 92% rename from scripts/grade-hw2-small.sh rename to scripts/grade-irgen-small.sh index 63f9643..0781572 100755 --- a/scripts/grade-hw2-small.sh +++ b/scripts/grade-irgen-small.sh @@ -8,4 +8,4 @@ cargo fmt --all -- --check # run `cargo fmt` to auto-correct format. cargo clippy # Run tests. -RUST_MIN_STACK=33554432 cargo test --release test_examples_irgen_small +RUST_MIN_STACK=33554432 cargo test --release test_examples_irgen_small --nocapture diff --git a/scripts/grade-hw2.sh b/scripts/grade-irgen.sh similarity index 94% rename from scripts/grade-hw2.sh rename to scripts/grade-irgen.sh index 39b7032..3341f82 100755 --- a/scripts/grade-hw2.sh +++ b/scripts/grade-irgen.sh @@ -8,5 +8,5 @@ cargo fmt --all -- --check # run `cargo fmt` to auto-correct format. cargo clippy # Run tests. -RUST_MIN_STACK=33554432 cargo test --release test_examples_irgen +RUST_MIN_STACK=33554432 cargo test --release test_examples_irgen -- --nocapture RUST_MIN_STACK=33554432 python3 tests/fuzz.py --irgen -n80 --seed 22 diff --git a/scripts/grade-hw4.sh b/scripts/grade-mem2reg.sh similarity index 92% rename from scripts/grade-hw4.sh rename to scripts/grade-mem2reg.sh index 2491237..2e2e388 100755 --- a/scripts/grade-hw4.sh +++ b/scripts/grade-mem2reg.sh @@ -8,4 +8,4 @@ cargo fmt --all -- --check # run `cargo fmt` to auto-correct format. cargo clippy # Run tests. -RUST_MIN_STACK=33554432 cargo test --release test_examples_mem2reg +RUST_MIN_STACK=33554432 cargo test --release test_examples_mem2reg -- --nocapture diff --git a/scripts/grade-hw3.sh b/scripts/grade-simplify_cfg.sh similarity index 91% rename from scripts/grade-hw3.sh rename to scripts/grade-simplify_cfg.sh index 743994b..4a03a5e 100755 --- a/scripts/grade-hw3.sh +++ b/scripts/grade-simplify_cfg.sh @@ -8,4 +8,4 @@ cargo fmt --all -- --check # run `cargo fmt` to auto-correct format. cargo clippy # Run tests. -RUST_MIN_STACK=33554432 cargo test --release test_examples_simplify_cfg +RUST_MIN_STACK=33554432 cargo test --release test_examples_simplify_cfg -- --nocapture diff --git a/scripts/grade-hw1.sh b/scripts/grade-write_c.sh similarity index 94% rename from scripts/grade-hw1.sh rename to scripts/grade-write_c.sh index 148881f..f504917 100755 --- a/scripts/grade-hw1.sh +++ b/scripts/grade-write_c.sh @@ -8,5 +8,5 @@ cargo fmt --all -- --check # run `cargo fmt` to auto-correct format. cargo clippy # Run tests. -RUST_MIN_STACK=33554432 cargo test --release test_examples_write_c +RUST_MIN_STACK=33554432 cargo test --release test_examples_write_c -- --nocapture RUST_MIN_STACK=33554432 python3 tests/fuzz.py --print -n80 --seed 22 diff --git a/scripts/make-public.py b/scripts/make-public.py deleted file mode 100755 index 7210d27..0000000 --- a/scripts/make-public.py +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env python3 - -"""Makes public skeleton. -""" - -import os -import subprocess -import itertools -import argparse -import sys -import re - -if __name__ == "__main__": - for root, dir, files in os.walk("src"): - for f in files: - (filename, ext) = os.path.splitext(f) - - if ext == ".public": - os.rename(os.path.join(root, f), os.path.join(root, filename)) diff --git a/scripts/make-submissions.sh b/scripts/make-submissions.sh index b8a11cd..29ff41b 100755 --- a/scripts/make-submissions.sh +++ b/scripts/make-submissions.sh @@ -1,13 +1,13 @@ #!/usr/bin/env bash # Clears the previous submissions. -rm -rf hw2.zip hw3.zip hw4.zip hw5.zip hw6.zip hw7.zip +rm -rf irgen.zip simplify_cfg.zip mem2reg.zip gvn.zip deadcode.zip asmgen.zip final.zip # Creates new submissions. -zip hw2.zip -j src/c/write_c.rs src/irgen/mod.rs -zip hw3.zip -j src/opt/opt_utils.rs src/opt/simplify_cfg.rs -zip hw4.zip -j src/opt/opt_utils.rs src/opt/mem2reg.rs -zip hw5.zip -j src/opt/opt_utils.rs src/opt/gvn.rs -zip hw6.zip -j src/opt/opt_utils.rs src/opt/deadcode.rs -zip hw7.zip -r src/c/write_c.rs src/irgen/mod.rs src/opt/opt_utils.rs src/asmgen/*.rs +zip irgen.zip -j src/c/write_c.rs src/irgen/mod.rs +zip simplify_cfg.zip -j src/opt/opt_utils.rs src/opt/simplify_cfg.rs +zip mem2reg.zip -j src/opt/opt_utils.rs src/opt/mem2reg.rs +zip gvn.zip -j src/opt/opt_utils.rs src/opt/gvn.rs +zip deadcode.zip -j src/opt/opt_utils.rs src/opt/deadcode.rs +zip asmgen.zip -r src/c/write_c.rs src/irgen/mod.rs src/opt/opt_utils.rs src/asmgen/*.rs zip final.zip -r src/ diff --git a/scripts/update-public.sh b/scripts/update-public.sh deleted file mode 100755 index 430118b..0000000 --- a/scripts/update-public.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash - -files="src/asmgen/mod.rs src/c/write_c.rs src/irgen/mod.rs src/opt/deadcode.rs src/opt/gvn.rs src/opt/mem2reg.rs src/opt/opt_utils.rs src/opt/simplify_cfg.rs" - -for file in $files; do - mv $file $file.public -done - -# deleted: src/asmgen/mod.rs.public -# deleted: src/c/write_c.rs.public -# deleted: src/irgen/mod.rs.public -# deleted: src/opt/deadcode.rs.public -# deleted: src/opt/gvn.rs.public -# deleted: src/opt/mem2reg.rs.public -# deleted: src/opt/opt_utils.rs.public -# deleted: src/opt/simplify_cfg.rs.public diff --git a/src/asm/mod.rs b/src/asm/mod.rs index c488274..3569d82 100644 --- a/src/asm/mod.rs +++ b/src/asm/mod.rs @@ -6,9 +6,6 @@ use crate::write_base::*; use core::convert::TryFrom; use core::fmt; -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Todo {} - /// TODO #[derive(Debug, Clone, PartialEq, Eq)] pub struct Asm { @@ -30,8 +27,8 @@ pub struct Section { pub body: T, } -/// An object file is made up of multiple sections, with each section corresponding to -/// distinct types of executable code or data. +/// An object file is made up of multiple sections, with each section corresponding to distinct +/// types of executable code or data. /// /// For more details: impl Section { @@ -78,10 +75,10 @@ impl Block { } } -/// The assembler implements a number of directives that control the assembly of instructions -/// into an object file. +/// The assembler implements several directives that control the assembly of instructions into an +/// object file. /// -/// For more details: +/// For more information: #[derive(Debug, Clone, PartialEq, Eq)] pub enum Directive { /// .align integer @@ -135,7 +132,7 @@ impl fmt::Display for Directive { } } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum SectionType { Text, Data, @@ -158,7 +155,7 @@ impl fmt::Display for SectionType { } } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum SymbolType { Function, Object, @@ -286,7 +283,7 @@ impl fmt::Display for Instruction { /// If the enum variant contains `bool`, /// It means that different instructions exist /// depending on whether the operand is signed or not. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum RType { Add(DataSize), Sub(DataSize), @@ -621,7 +618,7 @@ impl fmt::Display for RType { } } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum IType { Load { data_size: DataSize, @@ -730,7 +727,7 @@ impl fmt::Display for IType { } } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum SType { Store(DataSize), } @@ -768,7 +765,7 @@ impl fmt::Display for SType { } } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum BType { Beq, Bne, @@ -791,7 +788,7 @@ impl fmt::Display for BType { } } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum UType { Lui, } @@ -804,11 +801,11 @@ impl fmt::Display for UType { } } -/// The assembler implements a number of convenience psuedo-instructions that are formed from -/// instructions in the base ISA, but have implicit arguments or in some case reversed arguments, -/// that result in distinct semantics. +/// The assembler implements several convenience psuedo-instructions that are formed from multiple +/// instructions in the base ISA, but have implicit arguments or reversed arguments that result in +/// distinct semantics. /// -/// For more details: +/// For more information: /// - /// - (110p) #[derive(Debug, Clone, PartialEq, Eq)] @@ -935,10 +932,10 @@ impl fmt::Display for Immediate { } /// The relocation function creates synthesize operand values that are resolved -/// at program link time and are used as immediate parameters to specific instructions. +/// at program link time and are used as immediate parameters for specific instructions. /// /// For more details: -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum RelocationFunction { /// %hi Hi20, @@ -1054,10 +1051,10 @@ impl fmt::Display for DataSize { } } -// TODO: Add calling convention information (caller/callee-save registers) /// ABI name for RISC-V integer and floating-point register /// /// For more details: (109p) +// TODO: Add calling convention information (caller/callee-save registers) #[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)] pub enum Register { Zero, diff --git a/src/asm/write_asm.rs b/src/asm/write_asm.rs index e73ca8c..db24075 100644 --- a/src/asm/write_asm.rs +++ b/src/asm/write_asm.rs @@ -29,7 +29,7 @@ impl WriteLine for Section { fn write_line(&self, indent: usize, write: &mut dyn Write) -> Result<()> { for directive in &self.header { write_indent(indent + INDENT, write)?; - writeln!(write, "{}", directive.write_string())?; + writeln!(write, "{}", directive)?; } self.body.write_line(indent, write)?; @@ -52,7 +52,7 @@ impl WriteLine for Variable { writeln!(write, "{}:", self.label.0)?; for directive in &self.directives { write_indent(indent + INDENT, write)?; - writeln!(write, "{}", directive.write_string())?; + writeln!(write, "{}", directive)?; } Ok(()) @@ -67,7 +67,7 @@ impl WriteLine for Block { for instruction in &self.instructions { write_indent(indent + INDENT, write)?; - writeln!(write, "{}", instruction.write_string())?; + writeln!(write, "{}", instruction)?; } Ok(()) diff --git a/src/asmgen/mod.rs b/src/asmgen/mod.rs index 7d38ed6..1a60f53 100644 --- a/src/asmgen/mod.rs +++ b/src/asmgen/mod.rs @@ -2,7 +2,7 @@ use crate::asm; use crate::ir; use crate::Translate; -#[derive(Default, Debug)] +#[derive(Default, Clone, Copy, Debug)] pub struct Asmgen {} impl Translate for Asmgen { diff --git a/src/c/ast_equiv.rs b/src/c/ast_equiv.rs index 11d80a5..f757b8b 100644 --- a/src/c/ast_equiv.rs +++ b/src/c/ast_equiv.rs @@ -495,11 +495,7 @@ impl IsEquiv for Enumerator { impl IsEquiv for TypeQualifier { fn is_equiv(&self, other: &Self) -> bool { - #[allow(clippy::match_like_matches_macro)] - match (self, other) { - (Self::Const, Self::Const) => true, - _ => false, - } + matches!((self, other), (Self::Const, Self::Const)) } } diff --git a/src/c/parse.rs b/src/c/parse.rs index 95a6bee..ce7d327 100644 --- a/src/c/parse.rs +++ b/src/c/parse.rs @@ -16,7 +16,7 @@ pub enum Error { } /// TODO(document) -#[derive(Default, Debug)] +#[derive(Default, Clone, Copy, Debug)] pub struct Parse {} impl> Translate

for Parse { diff --git a/src/ir/dtype.rs b/src/ir/dtype.rs index a57c7c3..10c3776 100644 --- a/src/ir/dtype.rs +++ b/src/ir/dtype.rs @@ -116,17 +116,15 @@ pub enum Dtype { } impl BaseDtype { - /// Apply `StorageClassSpecifier` to `BaseDtype` + /// Apply `StorageClassSpecifier` to `BaseDtype`. /// - /// let's say declaration is `typedef int i32_t;`, if `self` represents `int` - /// and `type_qualifier` represents `typedef`, `self` is transformed to - /// representing `typedef int` after function performs. + /// Let's say declaration is `typedef int i32_t;`, if `self` represents `int` and + /// `type_qualifier` represents `typedef`, `self` is transformed to representing `typedef int`. /// /// # Arguments /// - /// * `self` - Part that has been converted to 'BaseDtype' on the declaration - /// * `storage_class` - storage class requiring apply to 'self' immediately - /// + /// * `self` - Part that has been converted to 'BaseDtype' on the declaration. + /// * `storage_class` - storage class requiring to apply to 'self' immediately. #[inline] fn apply_storage_class( &mut self, @@ -136,24 +134,23 @@ impl BaseDtype { ast::StorageClassSpecifier::Typedef => { // duplicate `typedef` is allowed self.is_typedef = true; + Ok(()) } - _ => panic!("unsupported storage class"), + scs => Err(DtypeError::Misc { + message: format!("unsupported storage class specifier: {scs:#?}"), + }), } - - Ok(()) } - /// Apply `TypeSpecifier` to `BaseDtype` + /// Apply `TypeSpecifier` to `BaseDtype`. /// - /// let's say declaration is `const int a;`, if `self` represents `int` - /// and `type_specifier` represents `const`, `self` is transformed to - /// representing `const int` after function performs. + /// Let's say the declaration is `const int a;`, if `self` represents `int` and + /// `type_specifier` represents `const`, `self` is transformed to representing `const int`. /// /// # Arguments /// - /// * `self` - Part that has been converted to 'BaseDtype' on the declaration - /// * `type_qualifier` - type qualifiers requiring apply to 'self' immediately - /// + /// * `self` - Part that has been converted to 'BaseDtype' on the declaration. + /// * `type_qualifier` - type qualifiers requiring to apply to 'self' immediately. #[inline] fn apply_type_specifier( &mut self, @@ -206,17 +203,15 @@ impl BaseDtype { Ok(()) } - /// Apply `Typequalifier` to `BaseDtype` + /// Apply `Typequalifier` to `BaseDtype`. /// - /// let's say declaration is `const int a;`, if `self` represents `int` - /// and `type_qualifier` represents `const`, `self` is transformed to - /// representing `const int` after function performs. + /// Let's say the declaration is `const int a;`, if `self` represents `int` and `type_qualifier` + /// represents `const`, `self` is transformed to representing `const int`. /// /// # Arguments /// - /// * `self` - Part that has been converted to 'BaseDtype' on the declaration - /// * `type_qualifier` - type qualifiers requiring apply to 'self' immediately - /// + /// * `self` - Part that has been converted to 'BaseDtype' on the declaration. + /// * `type_qualifier` - type qualifiers requiring to apply to 'self' immediately. #[inline] fn apply_type_qualifier( &mut self, @@ -227,9 +222,12 @@ impl BaseDtype { // duplicate `const` is allowed self.is_const = true; } - _ => panic!("type qualifier is unsupported except `const`"), + tq => { + return Err(DtypeError::Misc { + message: format!("unsupported typq qualifier: {tq:#?}"), + }) + } } - Ok(()) } @@ -244,7 +242,11 @@ impl BaseDtype { ast::SpecifierQualifier::TypeQualifier(type_qualifier) => { self.apply_type_qualifier(&type_qualifier.node)? } - ast::SpecifierQualifier::Extension(_) => panic!("unsupported specifier qualifier"), + sq => { + return Err(DtypeError::Misc { + message: format!("unsupported specifier qualifier: {sq:#?}"), + }) + } } Ok(()) @@ -264,25 +266,27 @@ impl BaseDtype { ast::DeclarationSpecifier::TypeQualifier(type_qualifier) => { self.apply_type_qualifier(&type_qualifier.node)? } - _ => panic!("is_unsupported"), + ds => { + return Err(DtypeError::Misc { + message: format!("unsupported declaration qualifier: {ds:#?}"), + }) + } } Ok(()) } - /// Apply `PointerQualifier` to `BaseDtype` + /// Apply `PointerQualifier` to `BaseDtype`. /// - /// let's say pointer declarator is `* const` of `const int * const a;`. - /// If `self` represents nothing, and `pointer_qualifier` represents `const` - /// between first and second asterisk, `self` is transformed to - /// representing `const` after function performs. This information is used later - /// when generating `Dtype`. + /// let's say pointer declarator is `* const` of `const int * const a;`. If `self` represents + /// nothing, and `pointer_qualifier` represents `const` between the first and second asterisk, + /// `self` is transformed to representing `const`. This information is used later when + /// generating `Dtype`. /// /// # Arguments /// - /// * `self` - Part that has been converted to 'BaseDtype' on the pointer declarator - /// * `pointer_qualifier` - Pointer qualifiers requiring apply to 'BaseDtype' immediately - /// + /// * `self` - Part that has been converted to 'BaseDtype' on the pointer declarator. + /// * `pointer_qualifier` - Pointer qualifiers required to apply to 'BaseDtype' immediately. pub fn apply_pointer_qualifier( &mut self, pointer_qualifier: &ast::PointerQualifier, @@ -291,8 +295,10 @@ impl BaseDtype { ast::PointerQualifier::TypeQualifier(type_qualifier) => { self.apply_type_qualifier(&type_qualifier.node)?; } - ast::PointerQualifier::Extension(_) => { - panic!("ast::PointerQualifier::Extension is unsupported") + pq => { + return Err(DtypeError::Misc { + message: format!("unsupported pointer qualifier: {pq:#?}"), + }) } } @@ -329,8 +335,8 @@ impl TryFrom for Dtype { /// /// # Example /// - /// For declaration is `const unsigned int * p`, `specifiers` is `const unsigned int`, - /// and the result is `Dtype::Int{ width: 4, is_signed: false, is_const: ture }` + /// For declaration is `const unsigned int * p`, `specifiers` is `const unsigned int`, and the + /// result is `Dtype::Int { width: 4, is_signed: false, is_const: true }`. fn try_from(spec: BaseDtype) -> Result { assert!( !(spec.scalar.is_none() @@ -398,7 +404,7 @@ impl TryFrom for Dtype { return Ok(dtype); } - // Creates `dtype` from scalar. + // Creates `dtype` from the scalar. let mut dtype = if let Some(t) = spec.scalar { match t { ast::TypeSpecifier::Void => Self::unit(), @@ -420,7 +426,7 @@ impl TryFrom for Dtype { ast::TypeSpecifier::Short => Self::SHORT, ast::TypeSpecifier::Long => Self::LONG, _ => panic!( - "Dtype::try_from::: {:?} is not a size modifiers", + "Dtype::try_from::: {:?} is not a size modifier", spec.size_modifiers ), }, @@ -470,14 +476,14 @@ impl TryFrom for Dtype { impl TryFrom<&ast::TypeName> for Dtype { type Error = DtypeError; - /// Derive a data type from typename. + /// Derive a data type from `type_name`. fn try_from(type_name: &ast::TypeName) -> Result { let mut spec = BaseDtype::default(); BaseDtype::apply_specifier_qualifiers(&mut spec, &type_name.specifiers)?; let mut dtype = Self::try_from(spec)?; if let Some(declarator) = &type_name.declarator { - dtype = dtype.with_ast_declarator(&declarator.node)?.deref().clone(); + dtype = dtype.with_ast_declarator(&declarator.node)?.into_inner(); } Ok(dtype) } @@ -486,19 +492,20 @@ impl TryFrom<&ast::TypeName> for Dtype { impl TryFrom<&ast::ParameterDeclaration> for Dtype { type Error = DtypeError; - /// Generate `Dtype` based on parameter declaration + /// Generate `Dtype` based on parameter declaration. fn try_from(parameter_decl: &ast::ParameterDeclaration) -> Result { let mut spec = BaseDtype::default(); BaseDtype::apply_declaration_specifiers(&mut spec, ¶meter_decl.specifiers)?; let mut dtype = Self::try_from(spec)?; if let Some(declarator) = ¶meter_decl.declarator { - dtype = dtype.with_ast_declarator(&declarator.node)?.deref().clone(); + dtype = dtype.with_ast_declarator(&declarator.node)?.into_inner(); - // A function call with an array argument performs array-to-pointer conversion. - // For this reason, when `declarator` is from function parameter declaration - // and `base_dtype` is `Dtype::Array`, `base_dtype` is transformed to pointer type. - // https://www.eskimo.com/~scs/cclass/notes/sx10f.html + // A function call with an array argument performs array-to-pointer conversion. For this + // reason, when `declarator` is from function parameter declaration and `base_dtype` is + // `Dtype::Array`, `base_dtype` is transformed to pointer type. + // + // For more information: if let Some(inner) = dtype.get_array_inner() { dtype = Self::pointer(inner.clone()); } @@ -540,7 +547,7 @@ impl Dtype { pub const SIZE_OF_DOUBLE: usize = 8; /// TODO(document) - // signed option cannot be applied to boolean value + /// A boolean value cannot be signed. pub const BOOL: Self = Self::Int { width: 1, is_signed: false, @@ -607,17 +614,16 @@ impl Dtype { /// # Examples /// /// Suppose the C declaration is `int *a[2][3]`. Then `a`'s `ir::Dtype` should be `[2 x [3 x - /// int*]]`. But in the AST, it is parsed as `Array(3, Array(2, Pointer(int)))`, reversing the - /// order of `2` and `3`. In the recursive translation of declaration into Dtype, we need to - /// insert `3` inside `[2 * int*]`. + /// int*]]`. But in the AST, it is parsed as `Array(3, Array(2, Pointer(int)))`, reversing + /// the order of `2` and `3`. In the recursive translation of a declaration into Dtype, we + /// need to insert `3` inside `[2 * int*]`. pub fn array(base_dtype: Dtype, size: usize) -> Self { match base_dtype { Self::Array { inner, size: old_size, } => { - let inner = inner.deref().clone(); - let inner = Self::array(inner, size); + let inner = Self::array(*inner, size); Self::Array { inner: Box::new(inner), size: old_size, @@ -669,17 +675,17 @@ impl Dtype { let align_of = fields .iter() - .map(|f| f.deref().size_align_of(structs)) + .map(|f| f.size_align_of(structs)) .collect::, _>>()? - .iter() - .map(|(_, a)| *a) + .into_iter() + .map(|(_, a)| a) .max() .unwrap_or(0); let mut offsets = Vec::new(); let mut offset = 0; for field in &fields { - let (size_of_dtype, align_of_dtype) = field.deref().size_align_of(structs)?; + let (size_of_dtype, align_of_dtype) = field.size_align_of(structs)?; let pad = if (offset % align_of_dtype) != 0 { align_of_dtype - (offset % align_of_dtype) @@ -702,7 +708,9 @@ impl Dtype { size_align_offsets: Some((size_of, align_of, offsets)), }) } else { - panic!("struct type is needed") + Err(DtypeError::Misc { + message: "struct type is needed".to_string(), + }) } } @@ -801,9 +809,7 @@ impl Dtype { pub fn is_scalar(&self) -> bool { match self { Self::Unit { .. } => todo!(), - Self::Int { .. } => true, - Self::Float { .. } => true, - Self::Pointer { .. } => true, + Self::Int { .. } | Self::Float { .. } | Self::Pointer { .. } => true, _ => false, } } @@ -812,20 +818,19 @@ impl Dtype { pub fn is_int_signed(&self) -> bool { match self { Self::Int { is_signed, .. } => *is_signed, - _ => panic!("only `Dtype::Int` can be judged whether it is sigend"), + _ => panic!("only `Dtype::Int` can be judged whether it is signed"), } } 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::Array { .. } => true, - Self::Struct { is_const, .. } => *is_const, - Self::Function { .. } => true, - Self::Typedef { is_const, .. } => *is_const, + Self::Unit { is_const } + | Self::Int { is_const, .. } + | Self::Float { is_const, .. } + | Self::Typedef { is_const, .. } + | Self::Pointer { is_const, .. } + | Self::Struct { is_const, .. } => *is_const, + Self::Function { .. } | Self::Array { .. } => true, } } @@ -833,11 +838,11 @@ impl Dtype { /// Check if `Dtype` is constant. if it is constant, the variable of `Dtype` is not assignable. pub fn is_immutable(&self, structs: &HashMap>) -> 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::Array { .. } => true, + Self::Unit { is_const } + | Self::Int { is_const, .. } + | Self::Float { is_const, .. } + | Self::Pointer { is_const, .. } => *is_const, + Self::Array { .. } | Self::Function { .. } => true, Self::Struct { name, is_const, .. } => { let name = name.as_ref().expect("`name` must be exist"); let struct_type = structs @@ -856,7 +861,7 @@ impl Dtype { || fields .iter() .any(|f| { - // If an array is wrapped in a struct and the array's inner type is not + // If an array is wrapped in a struct and the array's inner type is not // constant, it is assignable to another object of the same struct type. if let Self::Array { inner, .. } = f.deref() { inner.is_immutable_for_array_struct_field_inner(structs) @@ -865,7 +870,6 @@ impl Dtype { } }) } - Self::Function { .. } => true, Self::Typedef { .. } => panic!("typedef should be replaced by real dtype"), } } @@ -943,14 +947,16 @@ impl Dtype { .expect("`struct_type` must have its definition"); let (size_of, align_of, _) = struct_type .get_struct_size_align_offsets() - .expect("`struct_type` must be stcut type") + .expect("`struct_type` must be struct type") .as_ref() .unwrap(); Ok((*size_of, *align_of)) } Self::Function { .. } => Ok((0, 1)), - Self::Typedef { .. } => panic!("typedef should be replaced by real dtype"), + Self::Typedef { .. } => Err(DtypeError::Misc { + message: "typedef should be replaced by real dtype".to_string(), + }), } } @@ -978,10 +984,10 @@ impl Dtype { .expect("`offsets` must be `Some`"); assert_eq!(fields.len(), offsets.len()); - for (field, offset) in izip!(fields, offsets) { + for (field, &offset) in izip!(fields, offsets) { if let Some(name) = field.name() { if name == field_name { - return Some((*offset, field.deref().clone())); + return Some((offset, field.deref().clone())); } } else { let field_dtype = field.deref(); @@ -989,7 +995,7 @@ impl Dtype { field_dtype.get_offset_struct_field(field_name, structs), continue ); - return Some((*offset + offset_inner, dtype)); + return Some((offset + offset_inner, dtype)); } } @@ -1000,14 +1006,14 @@ impl Dtype { } #[must_use] - pub fn set_signed(self, is_signed: bool) -> Self { + pub fn set_signed(&self, is_signed: bool) -> Self { match self { Self::Int { width, is_const, .. } => Self::Int { - width, + width: *width, is_signed, - is_const, + is_const: *is_const, }, _ => panic!("`signed` and `unsigned` only be applied to `Dtype::Int`"), } @@ -1025,14 +1031,16 @@ impl Dtype { Ok((dtype, is_typedef)) } - /// Derive a data type and its name from struct declaration + /// Derive a data type and its name from the struct declaration. pub fn try_from_ast_struct_declaration( declaration: &ast::StructDeclaration, ) -> Result>, DtypeError> { let field_decl = if let ast::StructDeclaration::Field(field_decl) = declaration { &field_decl.node } else { - panic!("ast::StructDeclaration::StaticAssert is unsupported") + return Err(DtypeError::Misc { + message: "ast::StructDeclaration::StaticAssert is unsupported".to_string(), + }); }; let mut spec = BaseDtype::default(); @@ -1050,9 +1058,11 @@ impl Dtype { .collect::, _>>()?; if fields.is_empty() { - // If anonymous field is `Dtype::Struct`, structure type of this field - // can use this field's field as its field. - // For exampe, let's `struct A { struct { int f; }} t;`, `t.f` is valid. + // If an anonymous field is `Dtype::Struct`, the structure type of this field can use + // this field's field as its field. + // + // For example, let's `struct A { struct { + // int f; }} t;`, `t.f` is valid. if let Self::Struct { name, .. } = &dtype { if name.is_none() { // Note that `const` qualifier has no effect in this time. @@ -1068,18 +1078,15 @@ impl Dtype { } } - /// Generate `Dtype` based on declarator and `self` which has scalar type. + /// Generate `Dtype` based on declarator and `self` which has a scalar type. /// - /// let's say declaration is `const int * const * const a;`. - /// In general `self` start with `const int` which has scalar type and - /// `declarator` represents `* const * const` with `ast::Declarator` + /// Let's say declaration is `const int * const * const a;`. In general `self` start with `const + /// int` which has a scalar type and `declarator` represents `* const * const` with + /// `ast::Declarator`. /// /// # Arguments /// - /// * `declarator` - Parts requiring conversion to 'Dtype' on the declaration - /// * `decl_spec` - Containing information that should be referenced - /// when creating `Dtype` from `Declarator`. - /// + /// * `declarator` - Parts requiring conversion to 'Dtype' on the declaration. pub fn with_ast_declarator( mut self, declarator: &ast::Declarator, @@ -1107,7 +1114,7 @@ impl Dtype { // If function parameter is (void), remove it if params.len() == 1 && params[0] == Dtype::unit() { - let _ = params.pop(); + let _unused = params.pop(); } Self::function(self, params) @@ -1133,20 +1140,22 @@ impl Dtype { } } - /// Generates `Dtype` based on declarator and `self` which has scalar type. + /// Generates `Dtype` based on declarator and `self` which has a scalar type. /// /// Let's say the AST declaration is `int a[2][3]`; `self` represents `int [2]`; and /// `array_size` is `[3]`. Then this function should return `int [2][3]`. /// /// # Arguments /// - /// * `array_size` - the array size to add to the dtype `self` - /// + /// * `array_size` - the array size to add to `self`. pub fn with_ast_array_size(self, array_size: &ast::ArraySize) -> Result { let expr = if let ast::ArraySize::VariableExpression(expr) = array_size { &expr.node } else { - panic!("`ArraySize` is unsupported except `ArraySize::VariableExpression`") + return Err(DtypeError::Misc { + message: "`ArraySize` is unsupported except `ArraySize::VariableExpression`" + .to_string(), + }); }; let constant = Constant::try_from(expr) @@ -1165,22 +1174,18 @@ impl Dtype { Ok(Self::array(self, value as usize)) } - pub fn resolve_typedefs( - self, - typedefs: &HashMap, - structs: &HashMap>, - ) -> Result { - let dtype = match &self { + pub fn resolve_typedefs(self, typedefs: &HashMap) -> Result { + let dtype = match self { Self::Unit { .. } | Self::Int { .. } | Self::Float { .. } => self, Self::Pointer { inner, is_const } => { - let inner = inner.deref().clone().resolve_typedefs(typedefs, structs)?; - Self::pointer(inner).set_const(*is_const) + let inner = inner.resolve_typedefs(typedefs)?; + Self::pointer(inner).set_const(is_const) } Self::Array { inner, size } => { - let inner = inner.deref().clone().resolve_typedefs(typedefs, structs)?; + let inner = inner.resolve_typedefs(typedefs)?; Self::Array { inner: Box::new(inner), - size: *size, + size, } } Self::Struct { @@ -1189,40 +1194,39 @@ impl Dtype { is_const, .. } => { - if let Some(fields) = fields { - let resolved_dtypes = fields - .iter() - .map(|f| f.deref().clone().resolve_typedefs(typedefs, structs)) - .collect::, _>>()?; - - assert_eq!(fields.len(), resolved_dtypes.len()); - let fields = izip!(fields, resolved_dtypes) - .map(|(f, d)| Named::new(f.name().cloned(), d)) + let (name, fields) = if let Some(fields) = fields { + let fields = fields + .into_iter() + .map(|f| { + let (d, name) = f.destruct(); + let d = d.resolve_typedefs(typedefs).unwrap(); + Named::new(name, d) + }) .collect::>(); - - Self::structure(name.clone(), Some(fields)).set_const(*is_const) + (name, Some(fields)) } else { assert!(name.is_some()); - self - } + (name, fields) + }; + Self::structure(name, fields).set_const(is_const) } Self::Function { ret, params } => { - let ret = ret.deref().clone().resolve_typedefs(typedefs, structs)?; + let ret = ret.resolve_typedefs(typedefs)?; let params = params - .iter() - .map(|p| p.clone().resolve_typedefs(typedefs, structs)) + .into_iter() + .map(|p| p.resolve_typedefs(typedefs)) .collect::, _>>()?; Self::function(ret, params) } Self::Typedef { name, is_const } => { let dtype = typedefs - .get(name) + .get(&name) .ok_or_else(|| DtypeError::Misc { message: format!("unknown type name `{}`", name), })? .clone(); - let is_const = dtype.is_const() || *is_const; + let is_const = dtype.is_const() || is_const; dtype.set_const(is_const) } @@ -1238,33 +1242,29 @@ impl Dtype { structs: &mut HashMap>, tempid_counter: &mut usize, ) -> Result { - let dtype = match &self { + let dtype = match self { Self::Unit { .. } | Self::Int { .. } | Self::Float { .. } => self, Self::Pointer { inner, is_const } => { - let inner = inner.deref(); - - // the pointer type can have undeclared struct type as inner. - // For example, let's `struct A { struct B *p }`, even if `struct B` has not been - // declared before, it can be used as inner type of the pointer. - if let Self::Struct { name, fields, .. } = inner { + // Pointer types can have an undeclared struct type as inner. + // + // For example, consider `struct A { struct B *p }`, even if `struct B` has not + // been declared before, it can be used as the inner type of the pointer. + if let Self::Struct { name, fields, .. } = inner.deref() { if fields.is_none() { let name = name.as_ref().expect("`name` must be `Some`"); let _ = structs.entry(name.to_string()).or_insert(None); - return Ok(self.clone()); + return Ok(Self::pointer(*inner).set_const(is_const)); } } - let resolved_inner = inner.clone().resolve_structs(structs, tempid_counter)?; - Self::pointer(resolved_inner).set_const(*is_const) + let resolved_inner = inner.resolve_structs(structs, tempid_counter)?; + Self::pointer(resolved_inner).set_const(is_const) } Self::Array { inner, size } => { - let inner = inner - .deref() - .clone() - .resolve_structs(structs, tempid_counter)?; + let inner = inner.resolve_structs(structs, tempid_counter)?; Self::Array { inner: Box::new(inner), - size: *size, + size, } } Self::Struct { @@ -1273,19 +1273,18 @@ impl Dtype { is_const, .. } => { - if let Some(fields) = fields { - let resolved_dtypes = fields - .iter() - .map(|f| f.deref().clone().resolve_structs(structs, tempid_counter)) - .collect::, _>>()?; - - assert_eq!(fields.len(), resolved_dtypes.len()); - let fields = izip!(fields, resolved_dtypes) - .map(|(f, d)| Named::new(f.name().cloned(), d)) + let (name, fields) = if let Some(fields) = fields { + let fields = fields + .into_iter() + .map(|f| { + let (d, name) = f.destruct(); + let d = d.resolve_structs(structs, tempid_counter).unwrap(); + Named::new(name, d) + }) .collect::>(); let name = if let Some(name) = name { - name.clone() + name } else { let tempid = *tempid_counter; *tempid_counter += 1; @@ -1295,8 +1294,7 @@ impl Dtype { let filled_struct = resolved_struct.fill_size_align_offsets_of_struct(structs)?; - let prev_dtype = structs.insert(name.clone(), Some(filled_struct)); - if let Some(prev_dtype) = prev_dtype { + if let Some(prev_dtype) = structs.insert(name.clone(), Some(filled_struct)) { if prev_dtype.is_some() { return Err(DtypeError::Misc { message: format!("redefinition of {}", name), @@ -1304,10 +1302,10 @@ impl Dtype { } } - Self::structure(Some(name), None).set_const(*is_const) + (name, None) } else { - let name = name.as_ref().expect("`name` must be exist"); - let struct_type = structs.get(name).ok_or_else(|| DtypeError::Misc { + let name = name.expect("`name` must exist"); + let struct_type = structs.get(&name).ok_or_else(|| DtypeError::Misc { message: format!("unknown struct name `{}`", name), })?; if struct_type.is_none() { @@ -1316,17 +1314,15 @@ impl Dtype { }); } - self - } + (name, fields) + }; + Self::structure(Some(name), fields).set_const(is_const) } Self::Function { ret, params } => { - let ret = ret - .deref() - .clone() - .resolve_structs(structs, tempid_counter)?; + let ret = ret.resolve_structs(structs, tempid_counter)?; let params = params - .iter() - .map(|p| p.clone().resolve_structs(structs, tempid_counter)) + .into_iter() + .map(|p| p.resolve_structs(structs, tempid_counter)) .collect::, _>>()?; Self::function(ret, params) @@ -1418,7 +1414,7 @@ fn check_no_duplicate_field(fields: &[Named], field_names: &mut HashSet Option<(u128, usize, bool)> { + pub fn get_int(&self) -> Option<(u128, usize, bool)> { if let Value::Int { value, width, is_signed, } = self { - Some((value, width, is_signed)) + Some((*value, *width, *is_signed)) } else { None } @@ -221,6 +221,7 @@ impl Value { Ok(value) } + #[allow(clippy::result_unit_err)] pub fn try_from_initializer( initializer: &ast::Initializer, dtype: &Dtype, @@ -348,20 +349,19 @@ impl RegisterMap { } fn write(&mut self, rid: RegisterId, value: Value) { - let _ = self.inner.insert(rid, value); + let _unused = self.inner.insert(rid, value); } } /// Bidirectional map between the name of a global variable and memory box id #[derive(Default, Debug, PartialEq, Clone)] struct GlobalMap { - /// Map name of a global variable to memory box id + /// Map the name of a global variable to the 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. + /// Since IR treats global variables as `Constant::GlobalVariable`, the interpreter should be + /// able to generate pointer values by inferring `bid` from the `name` of the global variable. var_to_bid: HashMap, - /// Map memory box id to the name of a global variable + /// Map the 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. @@ -481,11 +481,11 @@ mod calculator { ast::BinaryOperator::BitwiseXor => lhs ^ rhs, ast::BinaryOperator::BitwiseOr => lhs | rhs, ast::BinaryOperator::Equals => { - let result = if lhs == rhs { 1 } else { 0 }; + let result = (lhs == rhs).into(); return Ok(Value::int(result, 1, false)); } ast::BinaryOperator::NotEquals => { - let result = if lhs != rhs { 1 } else { 0 }; + let result = (lhs != rhs).into(); return Ok(Value::int(result, 1, false)); } ast::BinaryOperator::Less => { @@ -494,7 +494,7 @@ mod calculator { } else { lhs < rhs }; - let result = if condition { 1 } else { 0 }; + let result = condition.into(); return Ok(Value::int(result, 1, false)); } ast::BinaryOperator::Greater => { @@ -503,7 +503,7 @@ mod calculator { } else { lhs > rhs }; - let result = if condition { 1 } else { 0 }; + let result = condition.into(); return Ok(Value::int(result, 1, false)); } ast::BinaryOperator::LessOrEqual => { @@ -512,7 +512,7 @@ mod calculator { } else { lhs <= rhs }; - let result = if condition { 1 } else { 0 }; + let result = condition.into(); return Ok(Value::int(result, 1, false)); } ast::BinaryOperator::GreaterOrEqual => { @@ -521,7 +521,7 @@ mod calculator { } else { lhs >= rhs }; - let result = if condition { 1 } else { 0 }; + let result = condition.into(); return Ok(Value::int(result, 1, false)); } _ => todo!( @@ -566,30 +566,30 @@ mod calculator { let order = lhs .partial_cmp(&rhs) .expect("`lhs` and `rhs` must be not NAN"); - let result = if Ordering::Equal == order { 1 } else { 0 }; + let result = (Ordering::Equal == order).into(); return Ok(Value::int(result, 1, false)); } ast::BinaryOperator::NotEquals => { let order = lhs .partial_cmp(&rhs) .expect("`lhs` and `rhs` must be not NAN"); - let result = if Ordering::Equal != order { 1 } else { 0 }; + let result = (Ordering::Equal != order).into(); return Ok(Value::int(result, 1, false)); } ast::BinaryOperator::Less => { - let result = if lhs.lt(&rhs) { 1 } else { 0 }; + let result = lhs.lt(&rhs).into(); return Ok(Value::int(result, 1, false)); } ast::BinaryOperator::Greater => { - let result = if lhs.gt(&rhs) { 1 } else { 0 }; + let result = lhs.gt(&rhs).into(); return Ok(Value::int(result, 1, false)); } ast::BinaryOperator::LessOrEqual => { - let result = if lhs.le(&rhs) { 1 } else { 0 }; + let result = lhs.le(&rhs).into(); return Ok(Value::int(result, 1, false)); } ast::BinaryOperator::GreaterOrEqual => { - let result = if lhs.ge(&rhs) { 1 } else { 0 }; + let result = lhs.ge(&rhs).into(); return Ok(Value::int(result, 1, false)); } _ => todo!( @@ -653,19 +653,11 @@ mod calculator { }, ) => match op { ast::BinaryOperator::Equals => { - let result = if bid == other_bid && offset == other_offset { - 1 - } else { - 0 - }; + let result = (bid == other_bid && offset == other_offset).into(); Ok(Value::int(result, 1, false)) } ast::BinaryOperator::NotEquals => { - let result = if !(bid == other_bid && offset == other_offset) { - 1 - } else { - 0 - }; + let result = (!(bid == other_bid && offset == other_offset)).into(); Ok(Value::int(result, 1, false)) } _ => todo!( @@ -708,7 +700,7 @@ mod calculator { ast::UnaryOperator::Negate => { // Check if it is boolean assert!(width == 1); - let result = if value == 0 { 1 } else { 0 }; + let result = (value == 0).into(); Ok(Value::int(result, width, is_signed)) } _ => todo!( @@ -786,7 +778,7 @@ mod calculator { } (Value::Int { value, .. }, Dtype::Pointer { inner, .. }) => { if value == 0 { - Ok(Value::pointer(None, 0, inner.deref().clone())) + Ok(Value::pointer(None, 0, *inner)) } else { panic!( "calculate_typecast: not support case \ @@ -1074,7 +1066,7 @@ impl Byte { izip!(fields, offsets).for_each(|(f, o)| { let result = Self::value_to_bytes(f.deref(), structs); let size_of_data = f.deref().dtype().size_align_of(structs).unwrap().0; - let _ = values.splice(*o..(*o + size_of_data), result.iter().cloned()); + let _unused = values.splice(*o..(*o + size_of_data), result.into_iter()); }); values @@ -1144,7 +1136,7 @@ impl Memory { let block = self.inner[bid].as_mut().unwrap(); if 0 <= offset && end <= block.len() { - let _ = block.splice(offset as usize..end, bytes.iter().cloned()); + let _unused = block.splice(offset as usize..end, bytes.into_iter()); Ok(()) } else { Err(()) @@ -1154,8 +1146,9 @@ impl Memory { #[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` + /// Maps each global variable to a pointer value. + /// + /// When a function call occurs, `registers` can be initialized by `global_registers` pub global_map: GlobalMap, pub stack_frame: StackFrame<'i>, pub stack: Vec>, @@ -1330,7 +1323,7 @@ impl<'i> State<'i> { } args.iter() - .map(|a| self.interp_operand(a.clone())) + .map(|a| self.interp_operand(a)) .collect::, _>>() } @@ -1349,7 +1342,7 @@ impl<'i> State<'i> { arg.args .iter() - .map(|a| self.interp_operand(a.clone()).unwrap()) + .map(|a| self.interp_operand(a).unwrap()) .collect::>() .into_iter() .enumerate() @@ -1374,7 +1367,7 @@ impl<'i> State<'i> { arg_then, arg_else, } => { - let value = self.interp_operand(condition.clone())?; + let value = self.interp_operand(condition)?; let (value, width, _) = value.get_int().expect("`condition` must be `Value::Int`"); // Check if it is boolean assert!(width == 1); @@ -1386,7 +1379,7 @@ impl<'i> State<'i> { default, cases, } => { - let value = self.interp_operand(value.clone())?; + let value = self.interp_operand(value)?; // TODO: consider different integer `width` in the future let arg = cases @@ -1396,7 +1389,7 @@ impl<'i> State<'i> { .unwrap_or_else(|| default); self.interp_jump(arg) } - BlockExit::Return { value } => Ok(Some(self.interp_operand(value.clone())?)), + BlockExit::Return { value } => Ok(Some(self.interp_operand(value)?)), BlockExit::Unreachable => Err(InterpreterError::Unreachable), } } @@ -1405,8 +1398,8 @@ impl<'i> State<'i> { let result = match instruction { Instruction::Nop => Value::unit(), Instruction::BinOp { op, lhs, rhs, .. } => { - let lhs = self.interp_operand(lhs.clone())?; - let rhs = self.interp_operand(rhs.clone())?; + let lhs = self.interp_operand(lhs)?; + let rhs = self.interp_operand(rhs)?; calculator::calculate_binary_operator_expression(op, lhs, rhs).map_err(|_| { InterpreterError::Misc { @@ -1417,7 +1410,7 @@ impl<'i> State<'i> { })? } Instruction::UnaryOp { op, operand, .. } => { - let operand = self.interp_operand(operand.clone())?; + let operand = self.interp_operand(operand)?; calculator::calculate_unary_operator_expression(op, operand).map_err(|_| { InterpreterError::Misc { @@ -1428,8 +1421,8 @@ impl<'i> State<'i> { })? } Instruction::Store { ptr, value, .. } => { - let ptr = self.interp_operand(ptr.clone())?; - let value = self.interp_operand(value.clone())?; + let ptr = self.interp_operand(ptr)?; + let value = self.interp_operand(value)?; let (bid, offset, _) = self.interp_ptr(&ptr)?; self.memory .store(bid, offset, &value, &self.ir.structs) @@ -1444,12 +1437,12 @@ impl<'i> State<'i> { Value::Unit } Instruction::Load { ptr, .. } => { - let ptr = self.interp_operand(ptr.clone())?; + let ptr = self.interp_operand(ptr)?; let (bid, offset, dtype) = self.interp_ptr(&ptr)?; self.memory.load(bid, offset, &dtype, &self.ir.structs)? } Instruction::Call { callee, args, .. } => { - let ptr = self.interp_operand(callee.clone())?; + let ptr = self.interp_operand(callee)?; // Get function name from pointer let (bid, _, _) = ptr.get_pointer().expect("`ptr` must be `Value::Pointer`"); @@ -1503,7 +1496,7 @@ impl<'i> State<'i> { value, target_dtype, } => { - let value = self.interp_operand(value.clone())?; + let value = self.interp_operand(value)?; calculator::calculate_typecast(value, target_dtype.clone()).map_err(|_| { InterpreterError::Misc { func_name: self.stack_frame.func_name.clone(), @@ -1513,10 +1506,10 @@ impl<'i> State<'i> { })? } Instruction::GetElementPtr { ptr, offset, dtype } => { - let ptr = self.interp_operand(ptr.clone())?; + let ptr = self.interp_operand(ptr)?; let (value, _, _) = self - .interp_operand(offset.clone())? + .interp_operand(offset)? .get_int() .expect("`idx` must be `Value::Int`"); @@ -1542,12 +1535,10 @@ impl<'i> State<'i> { Ok(()) } - fn interp_operand(&self, operand: Operand) -> Result { - match &operand { + fn interp_operand(&self, operand: &Operand) -> Result { + match operand { Operand::Constant(value) => Ok(self.interp_constant(value.clone())), - Operand::Register { rid, .. } => { - Ok(self.stack_frame.registers.read(rid.clone()).clone()) - } + Operand::Register { rid, .. } => Ok(self.stack_frame.registers.read(*rid).clone()), } } diff --git a/src/ir/mod.rs b/src/ir/mod.rs index a044b65..e694e81 100644 --- a/src/ir/mod.rs +++ b/src/ir/mod.rs @@ -3,7 +3,6 @@ mod dtype; mod equiv; mod interp; -#[allow(clippy::all)] mod parse; mod visualize; mod write_ir; @@ -49,14 +48,14 @@ impl TryFrom for Declaration { /// /// # 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. + /// If `int g = 0;` is declared, `dtype` is `ir::Dtype::Int{ width:32, is_signed:true, + /// is_const:false }`. /// - /// Conversely, if `int foo();` is declared, `dtype` is - /// `ir::Dtype::Function{ret: Scalar(Int), params: []}`. - /// Thus, in this case, `ir::Declaration::Function` is generated. + /// 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. fn try_from(dtype: Dtype) -> Result { match &dtype { Dtype::Unit { .. } => Err(DtypeError::Misc { @@ -192,7 +191,6 @@ pub struct Block { } #[derive(Debug, Clone, PartialEq, Eq)] -#[allow(clippy::large_enum_variant)] pub enum Instruction { Nop, BinOp { @@ -228,27 +226,30 @@ pub enum Instruction { GetElementPtr { ptr: Operand, offset: Operand, - dtype: Box, + dtype: Dtype, }, } impl HasDtype for Instruction { fn dtype(&self) -> Dtype { match self { - Self::Nop => Dtype::unit(), - Self::BinOp { dtype, .. } => dtype.clone(), - Self::UnaryOp { dtype, .. } => dtype.clone(), - Self::Store { .. } => Dtype::unit(), + Self::Nop | Self::Store { .. } => Dtype::unit(), + Self::BinOp { dtype, .. } + | Self::UnaryOp { dtype, .. } + | Self::Call { + return_type: dtype, .. + } + | Self::TypeCast { + target_dtype: dtype, + .. + } + | Self::GetElementPtr { dtype, .. } => dtype.clone(), 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(), - Self::GetElementPtr { dtype, .. } => dtype.deref().clone(), } } } @@ -307,43 +308,30 @@ impl fmt::Display for Instruction { match self { Instruction::Nop => write!(f, "nop"), Instruction::BinOp { op, lhs, rhs, .. } => { - write!( - f, - "{} {} {}", - op.write_operation(), - lhs.write_string(), - rhs.write_string() - ) + write!(f, "{} {} {}", op.write_operation(), lhs, rhs) } Instruction::UnaryOp { op, operand, .. } => { - write!(f, "{} {}", op.write_operation(), operand.write_string()) + write!(f, "{} {}", op.write_operation(), operand) } Instruction::Store { ptr, value } => { - write!(f, "store {} {}", value.write_string(), ptr.write_string()) + write!(f, "store {} {}", value, ptr) } - Instruction::Load { ptr } => write!(f, "load {}", ptr.write_string()), + Instruction::Load { ptr } => write!(f, "load {}", ptr), Instruction::Call { callee, args, .. } => { write!( f, "call {}({})", - callee.write_string(), - args.iter().format_with(", ", |operand, f| f(&format_args!( - "{}", - operand.write_string() - ))) + callee, + args.iter() + .format_with(", ", |operand, f| f(&format_args!("{}", operand))) ) } Instruction::TypeCast { value, target_dtype, - } => write!(f, "typecast {} to {}", value.write_string(), target_dtype), + } => write!(f, "typecast {} to {}", value, target_dtype), Instruction::GetElementPtr { ptr, offset, .. } => { - write!( - f, - "getelementptr {} offset {}", - ptr.write_string(), - offset.write_string() - ) + write!(f, "getelementptr {} offset {}", ptr, offset) } } } @@ -356,12 +344,12 @@ pub enum BlockExit { }, ConditionalJump { condition: Operand, - arg_then: Box, - arg_else: Box, + arg_then: JumpArg, + arg_else: JumpArg, }, Switch { value: Operand, - default: Box, + default: JumpArg, cases: Vec<(Constant, JumpArg)>, }, Return { @@ -402,13 +390,7 @@ impl fmt::Display for BlockExit { condition, arg_then, arg_else, - } => write!( - f, - "br {}, {}, {}", - condition.write_string(), - arg_then, - arg_else - ), + } => write!(f, "br {}, {}, {}", condition, arg_then, arg_else), BlockExit::Switch { value, default, @@ -416,7 +398,7 @@ impl fmt::Display for BlockExit { } => write!( f, "switch {} default {} [\n{}\n ]", - value.write_string(), + value, default, cases.iter().format_with("\n", |(v, b), f| f(&format_args!( " {}:{} {}", @@ -425,7 +407,7 @@ impl fmt::Display for BlockExit { b ))) ), - BlockExit::Return { value } => write!(f, "ret {}", value.write_string()), + BlockExit::Return { value } => write!(f, "ret {}", value), BlockExit::Unreachable => write!(f, "\t\t\t\t; error state"), } } @@ -499,8 +481,8 @@ impl Operand { 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), + Self::Constant(value) => write!(f, "{}:{}", value, value.dtype()), + Self::Register { rid, dtype } => write!(f, "{}:{}", rid, dtype), } } } @@ -514,7 +496,7 @@ impl HasDtype for Operand { } } -#[derive(Debug, Eq, Clone)] +#[derive(Debug, Eq, Clone, Copy)] pub enum RegisterId { /// Registers holding pointers to local allocations. /// @@ -747,11 +729,12 @@ impl TryFrom<&ast::Expression> for Constant { ast::Expression::UnaryOperator(unary) => { let constant = Self::try_from(&unary.node.operand.node)?; // When an IR is generated, there are cases where some expressions must be - // interpreted unconditionally as compile-time constant value. In this case, - // we need to translate also the expression applied `minus` unary operator - // to compile-time constant value directly. - // Let's say expression is `case -1: { .. }`, - // `-1` must be interpreted to compile-time constant value. + // interpreted unconditionally as a compile-time constant value. In this case, we + // need to also translate the expression applied `minus` unary operator to a + // compile-time constant value directly. + // + // Let's say the expression is `case -1: { .. }`, `-1` must be interpreted to a + // compile-time constant value. match &unary.node.operator.node { ast::UnaryOperator::Minus => Ok(constant.minus()), ast::UnaryOperator::Plus => Ok(constant), @@ -1006,6 +989,14 @@ impl Named { pub fn name(&self) -> Option<&String> { self.name.as_ref() } + + pub fn destruct(self) -> (T, Option) { + (self.inner, self.name) + } + + pub fn into_inner(self) -> T { + self.inner + } } impl fmt::Display for Named { diff --git a/src/ir/parse.rs b/src/ir/parse.rs index f76f59e..6faa95a 100644 --- a/src/ir/parse.rs +++ b/src/ir/parse.rs @@ -74,7 +74,7 @@ peg::parser! { rule named_decl() -> Named = "var" __ dtype:dtype() __ var:global_variable() _ "=" _ initializer:initializer() { Named::new(Some(var), Declaration::Variable { - dtype: dtype, + dtype, initializer, }) } @@ -218,7 +218,7 @@ peg::parser! { // For this reason, we need to check the dtype of the result to confirm the dtype // of `GetElementPtr` instruction when parsing IR. let instruction = if let Instruction::GetElementPtr { ptr, offset, .. } = instruction { - Instruction::GetElementPtr { ptr, offset, dtype: Box::new(dtype) } + Instruction::GetElementPtr { ptr, offset, dtype } } else { instruction }; @@ -317,7 +317,7 @@ peg::parser! { Instruction::GetElementPtr{ ptr, offset, - dtype: Box::new(Dtype::unit()), // TODO + dtype: Dtype::unit(), // TODO } } / @@ -375,11 +375,11 @@ peg::parser! { } / "br" __ condition:operand() _ "," _ arg_then:jump_arg() _ "," _ arg_else:jump_arg() { - BlockExit::ConditionalJump { condition, arg_then: Box::new(arg_then), arg_else: Box::new(arg_else) } + BlockExit::ConditionalJump { condition, arg_then, arg_else } } / "switch" __ value:operand() __ "default" __ default:jump_arg() _ "[" _ cases:(switch_case() ** __) _ "]" { - BlockExit::Switch { value, default: Box::new(default), cases } + BlockExit::Switch { value, default, cases } } / "ret" __ value:operand() { @@ -663,12 +663,12 @@ peg::parser! { #[derive(Debug)] pub enum Error { - IoError(std::io::Error), - ParseError(peg::error::ParseError), - ResolveError, + Io(std::io::Error), + Parse(peg::error::ParseError), + Resolve, } -#[derive(Default, Debug)] +#[derive(Default, Clone, Copy, Debug)] pub struct Parse {} impl> Translate

for Parse { @@ -676,8 +676,8 @@ impl> Translate

for Parse { 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)?; + let ir = fs::read_to_string(source).map_err(Error::Io)?; + let ir = ir_parse::translation_unit(&ir).map_err(Error::Parse)?; Ok(ir) } } @@ -688,7 +688,8 @@ fn resolve_structs(struct_type: Dtype, structs: &mut HashMap String { - format!("{}:{}", self, self.dtype()) + format!("{}", self) } } diff --git a/src/irgen/mod.rs b/src/irgen/mod.rs index 1ca13e9..03169c8 100644 --- a/src/irgen/mod.rs +++ b/src/irgen/mod.rs @@ -4,10 +4,10 @@ use lang_c::ast::*; use crate::*; -#[derive(Default, Debug)] +#[derive(Default, Clone, Copy, Debug)] pub struct Irgen {} -#[derive(Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug)] pub struct IrgenError {} impl fmt::Display for IrgenError { diff --git a/src/lib.rs b/src/lib.rs index 47cceee..97f6dc0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,44 +1,51 @@ //! KECC: KAIST Educational C Compiler. -// Tries to deny all lints (`rustc -W help`). +#![deny(clippy::all)] +#![deny(rustdoc::all)] #![deny(warnings)] +// Tries to deny all rustc allow lints. +// #![deny(absolute_paths_not_starting_with_crate)] -#![deny(anonymous_parameters)] +// Old, historical lint // #![deny(box_pointers)] -#![deny(deprecated_in_future)] #![deny(elided_lifetimes_in_paths)] #![deny(explicit_outlives_requirements)] -#![deny(rustdoc::invalid_html_tags)] #![deny(keyword_idents)] +#![deny(let_underscore_drop)] #![deny(macro_use_extern_crate)] +#![deny(meta_variable_misuse)] +#![deny(missing_abi)] +#![deny(missing_copy_implementations)] #![deny(missing_debug_implementations)] -// #![deny(missing_docs)] TODO -#![deny(rustdoc::missing_doc_code_examples)] +// TODO +// #![deny(missing_docs)] #![deny(non_ascii_idents)] +#![deny(noop_method_call)] #![deny(pointer_structural_match)] +#![deny(rust_2021_incompatible_closure_captures)] +#![deny(rust_2021_incompatible_or_patterns)] +#![deny(rust_2021_prefixes_incompatible_syntax)] +#![deny(rust_2021_prelude_collisions)] +// Necessary for skeleton code. // #![deny(single_use_lifetimes)] +#![deny(trivial_casts)] #![deny(trivial_numeric_casts)] -#![deny(unaligned_references)] +// Necessary for skeleton code. // #![deny(unreachable_pub)] +#![deny(unsafe_code)] +#![deny(unsafe_op_in_unsafe_fn)] #![deny(unstable_features)] // Necessary for `build-bin` trick. // #![deny(unused_crate_dependencies)] #![deny(unused_extern_crates)] #![deny(unused_import_braces)] #![deny(unused_lifetimes)] +#![deny(unused_macro_rules)] #![deny(unused_qualifications)] #![deny(unused_results)] +#![deny(unused_tuple_struct_fields)] +// Allowed for more flexible variants. // #![deny(variant_size_differences)] -#![deny(rust_2018_idioms)] -#![deny(rustdoc::all)] -// Necessary 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)] -#![allow(clippy::result_unit_err)] -#![allow(clippy::vec_init_then_push)] -#![allow(clippy::collapsible_match)] mod tests; mod utils; diff --git a/src/opt/deadcode.rs b/src/opt/deadcode.rs index dd7b67b..fe92b22 100644 --- a/src/opt/deadcode.rs +++ b/src/opt/deadcode.rs @@ -4,7 +4,7 @@ use crate::*; pub type Deadcode = FunctionPass>; -#[derive(Default, Debug)] +#[derive(Default, Clone, Copy, Debug)] pub struct DeadcodeInner {} impl Optimize for DeadcodeInner { diff --git a/src/opt/gvn.rs b/src/opt/gvn.rs index 6a5202c..7ce926a 100644 --- a/src/opt/gvn.rs +++ b/src/opt/gvn.rs @@ -3,7 +3,7 @@ use crate::*; pub type Gvn = FunctionPass; -#[derive(Default, Debug)] +#[derive(Default, Clone, Copy, Debug)] pub struct GvnInner {} impl Optimize for GvnInner { diff --git a/src/opt/mem2reg.rs b/src/opt/mem2reg.rs index cba6300..aff6e86 100644 --- a/src/opt/mem2reg.rs +++ b/src/opt/mem2reg.rs @@ -4,7 +4,7 @@ use crate::*; pub type Mem2reg = FunctionPass; -#[derive(Default, Debug)] +#[derive(Default, Clone, Copy, Debug)] pub struct Mem2regInner {} impl Optimize for Mem2regInner { diff --git a/src/opt/mod.rs b/src/opt/mod.rs index 0f8bf9b..f93d0db 100644 --- a/src/opt/mod.rs +++ b/src/opt/mod.rs @@ -22,7 +22,7 @@ pub trait Optimize { pub type O0 = Null; pub type O1 = Repeat<(SimplifyCfg, (Mem2reg, (Gvn, Deadcode)))>; -#[derive(Default, Debug)] +#[derive(Default, Clone, Copy, Debug)] pub struct Null {} #[derive(Default, Debug)] @@ -66,9 +66,9 @@ where { fn optimize(&mut self, code: &mut ir::TranslationUnit) -> bool { code.decls - .iter_mut() - .map(|(_, decl)| self.optimize(decl)) - .fold(false, |l, r| l || r) + .values_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 cdc01c7..3509d42 100644 --- a/src/opt/simplify_cfg.rs +++ b/src/opt/simplify_cfg.rs @@ -10,19 +10,19 @@ pub type SimplifyCfg = FunctionPass< >; /// Simplifies block exits by propagating constants. -#[derive(Default, Debug)] +#[derive(Default, Clone, Copy, Debug)] pub struct SimplifyCfgConstProp {} /// Retains only those blocks that are reachable from the init. -#[derive(Default, Debug)] +#[derive(Default, Clone, Copy, Debug)] pub struct SimplifyCfgReach {} /// Merges two blocks if a block is pointed to only by another -#[derive(Default, Debug)] +#[derive(Default, Clone, Copy, Debug)] pub struct SimplifyCfgMerge {} /// Removes empty blocks -#[derive(Default, Debug)] +#[derive(Default, Clone, Copy, Debug)] pub struct SimplifyCfgEmpty {} impl Optimize for SimplifyCfgConstProp { diff --git a/src/tests.rs b/src/tests.rs index fb7be31..121de38 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -28,7 +28,7 @@ fn ast_initializer(number: i32) -> ast::Initializer { let expr = ast::Expression::Constant(Box::new(span::Node::new( ast::Constant::Integer(ast::Integer { base: ast::IntegerBase::Decimal, - number: Box::from(&number.to_string() as &str), + number: Box::from(number.to_string()), suffix: ast::IntegerSuffix { size: ast::IntegerSize::Int, unsigned: false, @@ -125,7 +125,7 @@ pub fn test_irgen(path: &Path) { // Compile c file: If fails, test is vacuously success if !Command::new("clang") - .args(&[ + .args([ "-fsanitize=float-divide-by-zero", "-fsanitize=undefined", "-fno-sanitize-recover=all", @@ -149,7 +149,7 @@ pub fn test_irgen(path: &Path) { let status = some_or!( child - .wait_timeout_ms(500) + .wait_timeout_ms(1000) .expect("failed to obtain exit status from child process"), { println!("timeout occurs"); @@ -177,15 +177,15 @@ pub fn test_irgen(path: &Path) { // Interpret resolved ir let args = Vec::new(); let result = ir::interp(&ir, args).unwrap_or_else(|interp_error| panic!("{}", interp_error)); - // We only allow main function whose return type is `int` + // We only allow a main function whose return type is `int` let (value, width, is_signed) = result.get_int().expect("non-integer value occurs"); assert_eq!(width, 32); assert!(is_signed); - // When obtain status from `clang` executable process, value is truncated to byte size. - // For this reason, we make `fuzzer` generate the C source code which returns value - // typecasted to `unsigned char`. However, during `creduce` reduce the code, typecasting - // may be deleted. So, we truncate result value to byte size one more time here. + // When obtaining status from `clang` executable process, the status value is truncated to byte + // size. For this reason, we make `fuzzer` generate the C source code which returns values + // typecasted to `unsigned char`. However, during `creduce` to reduce the code, typecasting may + // be nullified. So, we truncate the result value to byte size one more time here. println!("clang: {}, kecc: {}", status as u8, value as u8); assert_eq!(status as u8, value as u8); } @@ -199,7 +199,7 @@ pub fn test_irparse(path: &Path) { .unwrap_or_else(|_| panic!("parse failed {}", path.display())); // Test parse - let _ = c::Parse::default() + let _unused = c::Parse::default() .translate(&path) .expect("failed to parse the given program"); @@ -226,7 +226,8 @@ pub fn test_irparse(path: &Path) { test_irparse_for_optimized_ir(ir1, &temp_dir.path().join("ir2.ir"), Mem2reg::default()); let ir3 = test_irparse_for_optimized_ir(ir2, &temp_dir.path().join("ir3.ir"), Deadcode::default()); - let _ = test_irparse_for_optimized_ir(ir3, &temp_dir.path().join("ir4.ir"), Gvn::default()); + let _unused = + test_irparse_for_optimized_ir(ir3, &temp_dir.path().join("ir4.ir"), Gvn::default()); temp_dir.close().expect("temp dir deletion failed"); } @@ -327,7 +328,7 @@ pub fn test_asmgen(path: &Path) { // Compile the assembly code if !Command::new("riscv64-linux-gnu-gcc") - .args(&["-static", &asm_path_str, "-o", &bin_path_str]) + .args(["-static", &asm_path_str, "-o", &bin_path_str]) .stderr(Stdio::null()) .status() .unwrap() @@ -338,14 +339,14 @@ pub fn test_asmgen(path: &Path) { // Emulate the executable let mut child = Command::new("qemu-riscv64-static") - .args(&[&bin_path_str]) + .args([&bin_path_str]) .stderr(Stdio::piped()) .spawn() .expect("failed to execute the compiled executable"); let status = some_or!( child - .wait_timeout_ms(500) + .wait_timeout_ms(1000) .expect("failed to obtain exit status from child process"), { println!("timeout occurs"); @@ -393,7 +394,7 @@ pub fn test_end_to_end(path: &Path) { // Compile c file: If fails, test is vacuously success if !Command::new("clang") - .args(&[ + .args([ "-fsanitize=float-divide-by-zero", "-fsanitize=undefined", "-fno-sanitize-recover=all", @@ -422,7 +423,7 @@ pub fn test_end_to_end(path: &Path) { let status = some_or!( child - .wait_timeout_ms(500) + .wait_timeout_ms(1000) .expect("failed to obtain exit status from child process"), { println!("timeout occurs"); @@ -452,15 +453,15 @@ pub fn test_end_to_end(path: &Path) { let _ = O1::default().optimize(&mut ir); let args = Vec::new(); let result = ir::interp(&ir, args).unwrap_or_else(|interp_error| panic!("{}", interp_error)); - // We only allow main function whose return type is `int` + // We only allow a main function whose return type is `int` let (value, width, is_signed) = result.get_int().expect("non-integer value occurs"); assert_eq!(width, 32); assert!(is_signed); - // When obtain status from `clang` executable process, value is truncated to byte size. - // For this reason, we make `fuzzer` generate the C source code which returns value - // typecasted to `unsigned char`. However, during `creduce` reduce the code, typecasting - // may be deleted. So, we truncate result value to byte size one more time here. + // When obtaining status from `clang` executable process, the status value is truncated to byte + // size. For this reason, we make `fuzzer` generate the C source code which returns values + // typecasted to `unsigned char`. However, during `creduce` to reduce the code, typecasting may + // be nullified. So, we truncate the result value to byte size one more time here. println!( "clang: {}, kecc interp: {}", clang_status as u8, value as u8 @@ -487,7 +488,7 @@ pub fn test_end_to_end(path: &Path) { // Compile the assembly code if !Command::new("riscv64-linux-gnu-gcc") - .args(&["-static", &asm_path_str, "-o", &bin_path_str]) + .args(["-static", &asm_path_str, "-o", &bin_path_str]) .stderr(Stdio::null()) .status() .unwrap() @@ -498,14 +499,14 @@ pub fn test_end_to_end(path: &Path) { // Emulate the executable let mut child = Command::new("qemu-riscv64-static") - .args(&[&bin_path_str]) + .args([&bin_path_str]) .stderr(Stdio::piped()) .spawn() .expect("failed to execute the compiled executable"); let status = some_or!( child - .wait_timeout_ms(500) + .wait_timeout_ms(1000) .expect("failed to obtain exit status from child process"), { println!("timeout occurs"); diff --git a/tests/test_examples.rs b/tests/test_examples.rs index 06e8f19..a0e399a 100644 --- a/tests/test_examples.rs +++ b/tests/test_examples.rs @@ -205,9 +205,9 @@ fn test_examples_asmgen_small() { test_dir(Path::new(dir), OsStr::new("ir"), |path| { let file_name = &path .file_name() - .expect("`path` must have file name") + .expect("`path` must have a file name") .to_str() - .expect("must be transformed to `&str`"); + .expect("must be transformable to `&str`"); if !ASMGEN_SMALL_TEST_IGNORE_LIST.contains(file_name) { test_asmgen(path) } @@ -221,9 +221,9 @@ fn test_examples_asmgen_large() { test_dir(Path::new(dir), OsStr::new("ir"), |path| { let file_name = &path .file_name() - .expect("`path` must have file name") + .expect("`path` must have a file name") .to_str() - .expect("must be transformed to `&str`"); + .expect("must be transformable to `&str`"); if ASMGEN_SMALL_TEST_IGNORE_LIST.contains(file_name) { test_asmgen(path) }