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.
This commit is contained in:
Janggun Lee
2022-11-21 15:27:04 +09:00
parent cb698a5e43
commit 094cbfdd2c
40 changed files with 446 additions and 498 deletions

6
.gitignore vendored
View File

@@ -1,4 +1,4 @@
/target target
/hw*.zip *.zip
/final.zip rustfmt.toml
**/*.rs.bk **/*.rs.bk

12
Cargo.lock generated
View File

@@ -33,9 +33,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.0.17" version = "4.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06badb543e734a2d6568e19a40af66ed5364360b9226184926f89d229b4b4267" checksum = "91b9970d7505127a162fdaa9b96428d28a479ba78c9ec7550a63a5d9863db682"
dependencies = [ dependencies = [
"atty", "atty",
"bitflags", "bitflags",
@@ -48,9 +48,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.0.13" version = "4.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c42f169caba89a7d512b5418b09864543eeb4d497416c917d7137863bd2076ad" checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro-error", "proc-macro-error",
@@ -178,9 +178,9 @@ checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1"
[[package]] [[package]]
name = "ordered-float" name = "ordered-float"
version = "3.3.0" version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f74e330193f90ec45e2b257fa3ef6df087784157ac1ad2c1e71c62837b03aa7" checksum = "d84eb1409416d254e4a9c8fa56cc24701755025b458f0fcd8e59e1f5f40c23bf"
dependencies = [ dependencies = [
"num-traits", "num-traits",
] ]

View File

@@ -29,12 +29,12 @@ required-features = ["build-bin"]
build-bin = ["clap"] build-bin = ["clap"]
[dependencies] [dependencies]
clap = { version = "4.0.17", features = ["derive"], optional = true } clap = { version = "4.0.22", features = ["derive"], optional = true }
thiserror = "1.0.37" thiserror = "1.0.37"
lang-c = "0.14.0" lang-c = "0.14.0"
itertools = "0.10.5" itertools = "0.10.5"
tempfile = "3.3.0" tempfile = "3.3.0"
ordered-float = "3.3.0" ordered-float = "3.4.0"
hexf-parse = "0.2.1" hexf-parse = "0.2.1"
wait-timeout = "0.2.0" wait-timeout = "0.2.0"
peg = "0.8.1" peg = "0.8.1"

View File

@@ -59,9 +59,11 @@ RUST_MIN_STACK=33554432 cargo nextest run test_examples_end_to_end # run irge
## Fuzzing ## Fuzzing
We encourage you to do homework using the test-driven development approach (TDD). You randomly We encourage you to do homework using the test-driven development (TDD)approach. You will
generate test input, and if it fails, then reduce it as much as possible and manually inspect the randomly generate a test input, and if it fails,
reduced test input. For example: reduce it as much as possible and
manually inspect the reduced test input.
For example:
```sh ```sh
# Randomly generates test inputs and tests them # Randomly generates test inputs and tests them
@@ -75,8 +77,7 @@ cat tests/test_reduced.c
``` ```
`<fuzz-option>` can be `--print` or `--irgen`. It shall be the one used in [Run](#run). `<fuzz-option>` can be `--print` or `--irgen`. It shall be the one used in [Run](#run).
For more information, please refer to the [Fuzzer User's Manual](tests/README.md).
For more information on usage, please refer to the [Fuzzer User's Manual](tests/README.md).
### Install ### 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 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 We use `csmith` to randomly generate C source codes.
built by the test script. For more information, we refer to the `csmith` will be automatically downloaded and built by the test script.
[Csmith](https://embed.cs.utah.edu/csmith/) homepage. For more information, we refer to the [Csmith](https://embed.cs.utah.edu/csmith/) homepage.
### Reduce ### Reduce
When the fuzzer finds a buggy input program for your compiler, it is highly likely that the input When the fuzzer finds a buggy input program for your compiler,
program is too big to manually inspect. We use `creduce` that reduces the buggy input program as the input program is likely too big to manually inspect.
much as possible. 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 Suppose `tests/test_polished.c` is the buggy input program.
program to `tests/test_reduced.c`: Then the following script reduces the program to `tests/test_reduced.c`:
```sh ```sh
python3 tests/fuzz.py <fuzz-option> --reduce python3 tests/fuzz.py <fuzz-option> --reduce
@@ -114,15 +115,17 @@ python3 tests/fuzz.py <fuzz-option> --reduce
`<fuzz-option>` can be `--print` or `--irgen`. It shall be the one used in [Run](#run). `<fuzz-option>` 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 The script performs unguided test-case reduction using `creduce`: given a buggy program, it
reduces the program; check if the reduced program still fails on the test, and if so, replaces the randomly reduces the program;
given program with the reduced one; repeat until you get a small enough buggy program. For more check if the reduced program still fails on the test, and
information, we refer to the [Creduce](https://embed.cs.utah.edu/creduce/) homepage. 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 **[NOTICE]** The fuzzer only supports Ubuntu 20.04.
doesn't, please run the fuzzer in 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 ## Running RISC-V Binaries
@@ -156,19 +159,22 @@ echo $?
### Debugging Assembly ### 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 ```sh
# Link to an RISC-V executable with `-ggdb` option # Link to an RISC-V executable with `-ggdb` option
riscv64-linux-gnu-gcc -ggdb -static hello.S -o hello riscv64-linux-gnu-gcc -ggdb -static hello.S -o hello
# Emulate the executable and wait for a debugging connection from GDB # 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 $ gdb-multiarch
@@ -194,7 +200,7 @@ Remote debugging using localhost:8888
warning: No executable has been specified and target does not support warning: No executable has been specified and target does not support
determining executable automatically. Try using the "file" command. determining executable automatically. Try using the "file" command.
0x0000000000010348 in ?? () 0x0000000000010348 in ?? ()
(gdb) file hello (gdb) file hello
A program is being debugged already. A program is being debugged already.
Are you sure you want to change the file? (y or n) y Are you sure you want to change the file? (y or n) y
Reading symbols from hello... Reading symbols from hello...
@@ -205,10 +211,11 @@ Dump of assembler code for function main:
0x000000000001044c <+6>: sd s0,96(sp) 0x000000000001044c <+6>: sd s0,96(sp)
0x000000000001044e <+8>: addi s0,sp,104 0x000000000001044e <+8>: addi s0,sp,104
End of assembler dump. 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/ - Full guide: http://sourceware.org/gdb/current/onlinedocs/gdb/
- Cheatsheet: https://cs.brown.edu/courses/cs033/docs/guides/gdb.pdf - Cheatsheet: https://cs.brown.edu/courses/cs033/docs/guides/gdb.pdf
@@ -224,4 +231,5 @@ make run
## Submission ## Submission
- Submit the corresponding files to [gg.kaist.ac.kr](https://gg.kaist.ac.kr). - 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.

View File

@@ -212,8 +212,8 @@ fn compile_ir(
assert_eq!(width, 32); assert_eq!(width, 32);
assert!(is_signed); assert!(is_signed);
// When obtain status from `gcc` executable process, status value is truncated to byte size. // When obtaining status from `gcc` executable process, the status value is truncated to
// So, we also truncate result value to byte size before printing it. // byte size. So, we also truncate the result value to byte size before printing it.
println!("[result] {:?}", value as u8); println!("[result] {:?}", value as u8);
return; return;
} }

View File

@@ -1 +1 @@
1.64.0 1.65.0

View File

@@ -1,3 +0,0 @@
#!/usr/bin/env bash
rsync --exclude=".git" --delete --archive ./ ../kecc-public

View File

@@ -8,4 +8,4 @@ cargo fmt --all -- --check # run `cargo fmt` to auto-correct format.
cargo clippy cargo clippy
# Run tests. # Run tests.
RUST_MIN_STACK=33554432 cargo test --release test_examples_asmgen RUST_MIN_STACK=33554432 cargo test --release test_examples_asmgen_small -- --nocapture

View File

@@ -8,4 +8,4 @@ cargo fmt --all -- --check # run `cargo fmt` to auto-correct format.
cargo clippy cargo clippy
# Run tests. # Run tests.
RUST_MIN_STACK=33554432 cargo test --release test_examples_asmgen_small RUST_MIN_STACK=33554432 cargo test --release test_examples_asmgen -- --nocapture

View File

@@ -8,4 +8,4 @@ cargo fmt --all -- --check # run `cargo fmt` to auto-correct format.
cargo clippy cargo clippy
# Run tests. # Run tests.
RUST_MIN_STACK=33554432 cargo test --release test_examples_deadcode RUST_MIN_STACK=33554432 cargo test --release test_examples_deadcode -- --nocapture

View File

@@ -8,4 +8,4 @@ cargo fmt --all -- --check # run `cargo fmt` to auto-correct format.
cargo clippy cargo clippy
# Run tests. # Run tests.
RUST_MIN_STACK=33554432 cargo test --release test_examples_gvn RUST_MIN_STACK=33554432 cargo test --release test_examples_gvn -- --nocapture

View File

@@ -8,4 +8,4 @@ cargo fmt --all -- --check # run `cargo fmt` to auto-correct format.
cargo clippy cargo clippy
# Run tests. # 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

View File

@@ -8,5 +8,5 @@ cargo fmt --all -- --check # run `cargo fmt` to auto-correct format.
cargo clippy cargo clippy
# Run tests. # 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 RUST_MIN_STACK=33554432 python3 tests/fuzz.py --irgen -n80 --seed 22

View File

@@ -8,4 +8,4 @@ cargo fmt --all -- --check # run `cargo fmt` to auto-correct format.
cargo clippy cargo clippy
# Run tests. # Run tests.
RUST_MIN_STACK=33554432 cargo test --release test_examples_mem2reg RUST_MIN_STACK=33554432 cargo test --release test_examples_mem2reg -- --nocapture

View File

@@ -8,4 +8,4 @@ cargo fmt --all -- --check # run `cargo fmt` to auto-correct format.
cargo clippy cargo clippy
# Run tests. # 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

View File

@@ -8,5 +8,5 @@ cargo fmt --all -- --check # run `cargo fmt` to auto-correct format.
cargo clippy cargo clippy
# Run tests. # 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 RUST_MIN_STACK=33554432 python3 tests/fuzz.py --print -n80 --seed 22

View File

@@ -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))

View File

@@ -1,13 +1,13 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Clears the previous submissions. # 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. # Creates new submissions.
zip hw2.zip -j src/c/write_c.rs src/irgen/mod.rs zip irgen.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 simplify_cfg.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 mem2reg.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 gvn.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 deadcode.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 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/ zip final.zip -r src/

View File

@@ -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

View File

@@ -6,9 +6,6 @@ use crate::write_base::*;
use core::convert::TryFrom; use core::convert::TryFrom;
use core::fmt; use core::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Todo {}
/// TODO /// TODO
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct Asm { pub struct Asm {
@@ -30,8 +27,8 @@ pub struct Section<T> {
pub body: T, pub body: T,
} }
/// An object file is made up of multiple sections, with each section corresponding to /// An object file is made up of multiple sections, with each section corresponding to distinct
/// distinct types of executable code or data. /// types of executable code or data.
/// ///
/// For more details: <https://github.com/michaeljclark/michaeljclark.github.io/blob/master/asm.md#sections> /// For more details: <https://github.com/michaeljclark/michaeljclark.github.io/blob/master/asm.md#sections>
impl<T> Section<T> { impl<T> Section<T> {
@@ -78,10 +75,10 @@ impl Block {
} }
} }
/// The assembler implements a number of directives that control the assembly of instructions /// The assembler implements several directives that control the assembly of instructions into an
/// into an object file. /// object file.
/// ///
/// For more details: <https://github.com/michaeljclark/michaeljclark.github.io/blob/master/asm.md#assembler-directives> /// For more information: <https://github.com/michaeljclark/michaeljclark.github.io/blob/master/asm.md#assembler-directives>
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum Directive { pub enum Directive {
/// .align integer /// .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 { pub enum SectionType {
Text, Text,
Data, Data,
@@ -158,7 +155,7 @@ impl fmt::Display for SectionType {
} }
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SymbolType { pub enum SymbolType {
Function, Function,
Object, Object,
@@ -286,7 +283,7 @@ impl fmt::Display for Instruction {
/// If the enum variant contains `bool`, /// If the enum variant contains `bool`,
/// It means that different instructions exist /// It means that different instructions exist
/// depending on whether the operand is signed or not. /// depending on whether the operand is signed or not.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RType { pub enum RType {
Add(DataSize), Add(DataSize),
Sub(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 { pub enum IType {
Load { Load {
data_size: DataSize, 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 { pub enum SType {
Store(DataSize), 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 { pub enum BType {
Beq, Beq,
Bne, Bne,
@@ -791,7 +788,7 @@ impl fmt::Display for BType {
} }
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UType { pub enum UType {
Lui, Lui,
} }
@@ -804,11 +801,11 @@ impl fmt::Display for UType {
} }
} }
/// The assembler implements a number of convenience psuedo-instructions that are formed from /// The assembler implements several convenience psuedo-instructions that are formed from multiple
/// instructions in the base ISA, but have implicit arguments or in some case reversed arguments, /// instructions in the base ISA, but have implicit arguments or reversed arguments that result in
/// that result in distinct semantics. /// distinct semantics.
/// ///
/// For more details: /// For more information:
/// - <https://github.com/michaeljclark/michaeljclark.github.io/blob/master/asm.md#assembler-pseudo-instructions> /// - <https://github.com/michaeljclark/michaeljclark.github.io/blob/master/asm.md#assembler-pseudo-instructions>
/// - <https://riscv.org/wp-content/uploads/2017/05/riscv-spec-v2.2.pdf> (110p) /// - <https://riscv.org/wp-content/uploads/2017/05/riscv-spec-v2.2.pdf> (110p)
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
@@ -935,10 +932,10 @@ impl fmt::Display for Immediate {
} }
/// The relocation function creates synthesize operand values that are resolved /// 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: <https://github.com/riscv-non-isa/riscv-asm-manual/blob/master/riscv-asm.md> /// For more details: <https://github.com/riscv-non-isa/riscv-asm-manual/blob/master/riscv-asm.md>
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RelocationFunction { pub enum RelocationFunction {
/// %hi /// %hi
Hi20, 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 /// ABI name for RISC-V integer and floating-point register
/// ///
/// For more details: <https://content.riscv.org/wp-content/uploads/2017/05/riscv-spec-v2.2.pdf> (109p) /// For more details: <https://content.riscv.org/wp-content/uploads/2017/05/riscv-spec-v2.2.pdf> (109p)
// TODO: Add calling convention information (caller/callee-save registers)
#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)] #[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)]
pub enum Register { pub enum Register {
Zero, Zero,

View File

@@ -29,7 +29,7 @@ impl<T: WriteLine> WriteLine for Section<T> {
fn write_line(&self, indent: usize, write: &mut dyn Write) -> Result<()> { fn write_line(&self, indent: usize, write: &mut dyn Write) -> Result<()> {
for directive in &self.header { for directive in &self.header {
write_indent(indent + INDENT, write)?; write_indent(indent + INDENT, write)?;
writeln!(write, "{}", directive.write_string())?; writeln!(write, "{}", directive)?;
} }
self.body.write_line(indent, write)?; self.body.write_line(indent, write)?;
@@ -52,7 +52,7 @@ impl WriteLine for Variable {
writeln!(write, "{}:", self.label.0)?; writeln!(write, "{}:", self.label.0)?;
for directive in &self.directives { for directive in &self.directives {
write_indent(indent + INDENT, write)?; write_indent(indent + INDENT, write)?;
writeln!(write, "{}", directive.write_string())?; writeln!(write, "{}", directive)?;
} }
Ok(()) Ok(())
@@ -67,7 +67,7 @@ impl WriteLine for Block {
for instruction in &self.instructions { for instruction in &self.instructions {
write_indent(indent + INDENT, write)?; write_indent(indent + INDENT, write)?;
writeln!(write, "{}", instruction.write_string())?; writeln!(write, "{}", instruction)?;
} }
Ok(()) Ok(())

View File

@@ -2,7 +2,7 @@ use crate::asm;
use crate::ir; use crate::ir;
use crate::Translate; use crate::Translate;
#[derive(Default, Debug)] #[derive(Default, Clone, Copy, Debug)]
pub struct Asmgen {} pub struct Asmgen {}
impl Translate<ir::TranslationUnit> for Asmgen { impl Translate<ir::TranslationUnit> for Asmgen {

View File

@@ -495,11 +495,7 @@ impl IsEquiv for Enumerator {
impl IsEquiv for TypeQualifier { impl IsEquiv for TypeQualifier {
fn is_equiv(&self, other: &Self) -> bool { fn is_equiv(&self, other: &Self) -> bool {
#[allow(clippy::match_like_matches_macro)] matches!((self, other), (Self::Const, Self::Const))
match (self, other) {
(Self::Const, Self::Const) => true,
_ => false,
}
} }
} }

View File

@@ -16,7 +16,7 @@ pub enum Error {
} }
/// TODO(document) /// TODO(document)
#[derive(Default, Debug)] #[derive(Default, Clone, Copy, Debug)]
pub struct Parse {} pub struct Parse {}
impl<P: AsRef<Path>> Translate<P> for Parse { impl<P: AsRef<Path>> Translate<P> for Parse {

View File

@@ -116,17 +116,15 @@ pub enum Dtype {
} }
impl BaseDtype { impl BaseDtype {
/// Apply `StorageClassSpecifier` to `BaseDtype` /// Apply `StorageClassSpecifier` to `BaseDtype`.
/// ///
/// let's say declaration is `typedef int i32_t;`, if `self` represents `int` /// Let's say declaration is `typedef int i32_t;`, if `self` represents `int` and
/// and `type_qualifier` represents `typedef`, `self` is transformed to /// `type_qualifier` represents `typedef`, `self` is transformed to representing `typedef int`.
/// representing `typedef int` after function performs.
/// ///
/// # Arguments /// # Arguments
/// ///
/// * `self` - Part that has been converted to 'BaseDtype' on the declaration /// * `self` - Part that has been converted to 'BaseDtype' on the declaration.
/// * `storage_class` - storage class requiring apply to 'self' immediately /// * `storage_class` - storage class requiring to apply to 'self' immediately.
///
#[inline] #[inline]
fn apply_storage_class( fn apply_storage_class(
&mut self, &mut self,
@@ -136,24 +134,23 @@ impl BaseDtype {
ast::StorageClassSpecifier::Typedef => { ast::StorageClassSpecifier::Typedef => {
// duplicate `typedef` is allowed // duplicate `typedef` is allowed
self.is_typedef = true; 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` /// Let's say the declaration is `const int a;`, if `self` represents `int` and
/// and `type_specifier` represents `const`, `self` is transformed to /// `type_specifier` represents `const`, `self` is transformed to representing `const int`.
/// representing `const int` after function performs.
/// ///
/// # Arguments /// # Arguments
/// ///
/// * `self` - Part that has been converted to 'BaseDtype' on the declaration /// * `self` - Part that has been converted to 'BaseDtype' on the declaration.
/// * `type_qualifier` - type qualifiers requiring apply to 'self' immediately /// * `type_qualifier` - type qualifiers requiring to apply to 'self' immediately.
///
#[inline] #[inline]
fn apply_type_specifier( fn apply_type_specifier(
&mut self, &mut self,
@@ -206,17 +203,15 @@ impl BaseDtype {
Ok(()) Ok(())
} }
/// Apply `Typequalifier` to `BaseDtype` /// Apply `Typequalifier` to `BaseDtype`.
/// ///
/// let's say declaration is `const int a;`, if `self` represents `int` /// Let's say the declaration is `const int a;`, if `self` represents `int` and `type_qualifier`
/// and `type_qualifier` represents `const`, `self` is transformed to /// represents `const`, `self` is transformed to representing `const int`.
/// representing `const int` after function performs.
/// ///
/// # Arguments /// # Arguments
/// ///
/// * `self` - Part that has been converted to 'BaseDtype' on the declaration /// * `self` - Part that has been converted to 'BaseDtype' on the declaration.
/// * `type_qualifier` - type qualifiers requiring apply to 'self' immediately /// * `type_qualifier` - type qualifiers requiring to apply to 'self' immediately.
///
#[inline] #[inline]
fn apply_type_qualifier( fn apply_type_qualifier(
&mut self, &mut self,
@@ -227,9 +222,12 @@ impl BaseDtype {
// duplicate `const` is allowed // duplicate `const` is allowed
self.is_const = true; self.is_const = true;
} }
_ => panic!("type qualifier is unsupported except `const`"), tq => {
return Err(DtypeError::Misc {
message: format!("unsupported typq qualifier: {tq:#?}"),
})
}
} }
Ok(()) Ok(())
} }
@@ -244,7 +242,11 @@ impl BaseDtype {
ast::SpecifierQualifier::TypeQualifier(type_qualifier) => { ast::SpecifierQualifier::TypeQualifier(type_qualifier) => {
self.apply_type_qualifier(&type_qualifier.node)? 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(()) Ok(())
@@ -264,25 +266,27 @@ impl BaseDtype {
ast::DeclarationSpecifier::TypeQualifier(type_qualifier) => { ast::DeclarationSpecifier::TypeQualifier(type_qualifier) => {
self.apply_type_qualifier(&type_qualifier.node)? self.apply_type_qualifier(&type_qualifier.node)?
} }
_ => panic!("is_unsupported"), ds => {
return Err(DtypeError::Misc {
message: format!("unsupported declaration qualifier: {ds:#?}"),
})
}
} }
Ok(()) Ok(())
} }
/// Apply `PointerQualifier` to `BaseDtype` /// Apply `PointerQualifier` to `BaseDtype`.
/// ///
/// let's say pointer declarator is `* const` of `const int * const a;`. /// let's say pointer declarator is `* const` of `const int * const a;`. If `self` represents
/// If `self` represents nothing, and `pointer_qualifier` represents `const` /// nothing, and `pointer_qualifier` represents `const` between the first and second asterisk,
/// between first and second asterisk, `self` is transformed to /// `self` is transformed to representing `const`. This information is used later when
/// representing `const` after function performs. This information is used later /// generating `Dtype`.
/// when generating `Dtype`.
/// ///
/// # Arguments /// # Arguments
/// ///
/// * `self` - Part that has been converted to 'BaseDtype' on the pointer declarator /// * `self` - Part that has been converted to 'BaseDtype' on the pointer declarator.
/// * `pointer_qualifier` - Pointer qualifiers requiring apply to 'BaseDtype' immediately /// * `pointer_qualifier` - Pointer qualifiers required to apply to 'BaseDtype' immediately.
///
pub fn apply_pointer_qualifier( pub fn apply_pointer_qualifier(
&mut self, &mut self,
pointer_qualifier: &ast::PointerQualifier, pointer_qualifier: &ast::PointerQualifier,
@@ -291,8 +295,10 @@ impl BaseDtype {
ast::PointerQualifier::TypeQualifier(type_qualifier) => { ast::PointerQualifier::TypeQualifier(type_qualifier) => {
self.apply_type_qualifier(&type_qualifier.node)?; self.apply_type_qualifier(&type_qualifier.node)?;
} }
ast::PointerQualifier::Extension(_) => { pq => {
panic!("ast::PointerQualifier::Extension is unsupported") return Err(DtypeError::Misc {
message: format!("unsupported pointer qualifier: {pq:#?}"),
})
} }
} }
@@ -329,8 +335,8 @@ impl TryFrom<BaseDtype> for Dtype {
/// ///
/// # Example /// # Example
/// ///
/// For declaration is `const unsigned int * p`, `specifiers` is `const unsigned int`, /// For declaration is `const unsigned int * p`, `specifiers` is `const unsigned int`, and the
/// and the result is `Dtype::Int{ width: 4, is_signed: false, is_const: ture }` /// result is `Dtype::Int { width: 4, is_signed: false, is_const: true }`.
fn try_from(spec: BaseDtype) -> Result<Self, DtypeError> { fn try_from(spec: BaseDtype) -> Result<Self, DtypeError> {
assert!( assert!(
!(spec.scalar.is_none() !(spec.scalar.is_none()
@@ -398,7 +404,7 @@ impl TryFrom<BaseDtype> for Dtype {
return Ok(dtype); return Ok(dtype);
} }
// Creates `dtype` from scalar. // Creates `dtype` from the scalar.
let mut dtype = if let Some(t) = spec.scalar { let mut dtype = if let Some(t) = spec.scalar {
match t { match t {
ast::TypeSpecifier::Void => Self::unit(), ast::TypeSpecifier::Void => Self::unit(),
@@ -420,7 +426,7 @@ impl TryFrom<BaseDtype> for Dtype {
ast::TypeSpecifier::Short => Self::SHORT, ast::TypeSpecifier::Short => Self::SHORT,
ast::TypeSpecifier::Long => Self::LONG, ast::TypeSpecifier::Long => Self::LONG,
_ => panic!( _ => panic!(
"Dtype::try_from::<BaseDtype>: {:?} is not a size modifiers", "Dtype::try_from::<BaseDtype>: {:?} is not a size modifier",
spec.size_modifiers spec.size_modifiers
), ),
}, },
@@ -470,14 +476,14 @@ impl TryFrom<BaseDtype> for Dtype {
impl TryFrom<&ast::TypeName> for Dtype { impl TryFrom<&ast::TypeName> for Dtype {
type Error = DtypeError; type Error = DtypeError;
/// Derive a data type from typename. /// Derive a data type from `type_name`.
fn try_from(type_name: &ast::TypeName) -> Result<Self, Self::Error> { fn try_from(type_name: &ast::TypeName) -> Result<Self, Self::Error> {
let mut spec = BaseDtype::default(); let mut spec = BaseDtype::default();
BaseDtype::apply_specifier_qualifiers(&mut spec, &type_name.specifiers)?; BaseDtype::apply_specifier_qualifiers(&mut spec, &type_name.specifiers)?;
let mut dtype = Self::try_from(spec)?; let mut dtype = Self::try_from(spec)?;
if let Some(declarator) = &type_name.declarator { 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) Ok(dtype)
} }
@@ -486,19 +492,20 @@ impl TryFrom<&ast::TypeName> for Dtype {
impl TryFrom<&ast::ParameterDeclaration> for Dtype { impl TryFrom<&ast::ParameterDeclaration> for Dtype {
type Error = DtypeError; type Error = DtypeError;
/// Generate `Dtype` based on parameter declaration /// Generate `Dtype` based on parameter declaration.
fn try_from(parameter_decl: &ast::ParameterDeclaration) -> Result<Self, Self::Error> { fn try_from(parameter_decl: &ast::ParameterDeclaration) -> Result<Self, Self::Error> {
let mut spec = BaseDtype::default(); let mut spec = BaseDtype::default();
BaseDtype::apply_declaration_specifiers(&mut spec, &parameter_decl.specifiers)?; BaseDtype::apply_declaration_specifiers(&mut spec, &parameter_decl.specifiers)?;
let mut dtype = Self::try_from(spec)?; let mut dtype = Self::try_from(spec)?;
if let Some(declarator) = &parameter_decl.declarator { if let Some(declarator) = &parameter_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. // A function call with an array argument performs array-to-pointer conversion. For this
// For this reason, when `declarator` is from function parameter declaration // reason, when `declarator` is from function parameter declaration and `base_dtype` is
// and `base_dtype` is `Dtype::Array`, `base_dtype` is transformed to pointer type. // `Dtype::Array`, `base_dtype` is transformed to pointer type.
// https://www.eskimo.com/~scs/cclass/notes/sx10f.html //
// For more information: <https://www.eskimo.com/~scs/cclass/notes/sx10f.html>
if let Some(inner) = dtype.get_array_inner() { if let Some(inner) = dtype.get_array_inner() {
dtype = Self::pointer(inner.clone()); dtype = Self::pointer(inner.clone());
} }
@@ -540,7 +547,7 @@ impl Dtype {
pub const SIZE_OF_DOUBLE: usize = 8; pub const SIZE_OF_DOUBLE: usize = 8;
/// TODO(document) /// TODO(document)
// signed option cannot be applied to boolean value /// A boolean value cannot be signed.
pub const BOOL: Self = Self::Int { pub const BOOL: Self = Self::Int {
width: 1, width: 1,
is_signed: false, is_signed: false,
@@ -607,17 +614,16 @@ impl Dtype {
/// # Examples /// # Examples
/// ///
/// Suppose the C declaration is `int *a[2][3]`. Then `a`'s `ir::Dtype` should be `[2 x [3 x /// 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 /// int*]]`. But in the AST, it is parsed as `Array(3, Array(2, Pointer(int)))`, reversing
/// order of `2` and `3`. In the recursive translation of declaration into Dtype, we need to /// the order of `2` and `3`. In the recursive translation of a declaration into Dtype, we
/// insert `3` inside `[2 * int*]`. /// need to insert `3` inside `[2 * int*]`.
pub fn array(base_dtype: Dtype, size: usize) -> Self { pub fn array(base_dtype: Dtype, size: usize) -> Self {
match base_dtype { match base_dtype {
Self::Array { Self::Array {
inner, inner,
size: old_size, size: old_size,
} => { } => {
let inner = inner.deref().clone(); let inner = Self::array(*inner, size);
let inner = Self::array(inner, size);
Self::Array { Self::Array {
inner: Box::new(inner), inner: Box::new(inner),
size: old_size, size: old_size,
@@ -669,17 +675,17 @@ impl Dtype {
let align_of = fields let align_of = fields
.iter() .iter()
.map(|f| f.deref().size_align_of(structs)) .map(|f| f.size_align_of(structs))
.collect::<Result<Vec<_>, _>>()? .collect::<Result<Vec<_>, _>>()?
.iter() .into_iter()
.map(|(_, a)| *a) .map(|(_, a)| a)
.max() .max()
.unwrap_or(0); .unwrap_or(0);
let mut offsets = Vec::new(); let mut offsets = Vec::new();
let mut offset = 0; let mut offset = 0;
for field in &fields { 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 { let pad = if (offset % align_of_dtype) != 0 {
align_of_dtype - (offset % align_of_dtype) align_of_dtype - (offset % align_of_dtype)
@@ -702,7 +708,9 @@ impl Dtype {
size_align_offsets: Some((size_of, align_of, offsets)), size_align_offsets: Some((size_of, align_of, offsets)),
}) })
} else { } 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 { pub fn is_scalar(&self) -> bool {
match self { match self {
Self::Unit { .. } => todo!(), Self::Unit { .. } => todo!(),
Self::Int { .. } => true, Self::Int { .. } | Self::Float { .. } | Self::Pointer { .. } => true,
Self::Float { .. } => true,
Self::Pointer { .. } => true,
_ => false, _ => false,
} }
} }
@@ -812,20 +818,19 @@ impl Dtype {
pub fn is_int_signed(&self) -> bool { pub fn is_int_signed(&self) -> bool {
match self { match self {
Self::Int { is_signed, .. } => *is_signed, 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 { pub fn is_const(&self) -> bool {
match self { match self {
Self::Unit { is_const } => *is_const, Self::Unit { is_const }
Self::Int { is_const, .. } => *is_const, | Self::Int { is_const, .. }
Self::Float { is_const, .. } => *is_const, | Self::Float { is_const, .. }
Self::Pointer { is_const, .. } => *is_const, | Self::Typedef { is_const, .. }
Self::Array { .. } => true, | Self::Pointer { is_const, .. }
Self::Struct { is_const, .. } => *is_const, | Self::Struct { is_const, .. } => *is_const,
Self::Function { .. } => true, Self::Function { .. } | Self::Array { .. } => true,
Self::Typedef { is_const, .. } => *is_const,
} }
} }
@@ -833,11 +838,11 @@ impl Dtype {
/// Check if `Dtype` is constant. if it is constant, the variable of `Dtype` is not assignable. /// Check if `Dtype` is constant. if it is constant, the variable of `Dtype` is not assignable.
pub fn is_immutable(&self, structs: &HashMap<String, Option<Dtype>>) -> bool { pub fn is_immutable(&self, structs: &HashMap<String, Option<Dtype>>) -> bool {
match self { match self {
Self::Unit { is_const } => *is_const, Self::Unit { is_const }
Self::Int { is_const, .. } => *is_const, | Self::Int { is_const, .. }
Self::Float { is_const, .. } => *is_const, | Self::Float { is_const, .. }
Self::Pointer { is_const, .. } => *is_const, | Self::Pointer { is_const, .. } => *is_const,
Self::Array { .. } => true, Self::Array { .. } | Self::Function { .. } => true,
Self::Struct { name, is_const, .. } => { Self::Struct { name, is_const, .. } => {
let name = name.as_ref().expect("`name` must be exist"); let name = name.as_ref().expect("`name` must be exist");
let struct_type = structs let struct_type = structs
@@ -856,7 +861,7 @@ impl Dtype {
|| fields || fields
.iter() .iter()
.any(|f| { .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. // constant, it is assignable to another object of the same struct type.
if let Self::Array { inner, .. } = f.deref() { if let Self::Array { inner, .. } = f.deref() {
inner.is_immutable_for_array_struct_field_inner(structs) 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"), Self::Typedef { .. } => panic!("typedef should be replaced by real dtype"),
} }
} }
@@ -943,14 +947,16 @@ impl Dtype {
.expect("`struct_type` must have its definition"); .expect("`struct_type` must have its definition");
let (size_of, align_of, _) = struct_type let (size_of, align_of, _) = struct_type
.get_struct_size_align_offsets() .get_struct_size_align_offsets()
.expect("`struct_type` must be stcut type") .expect("`struct_type` must be struct type")
.as_ref() .as_ref()
.unwrap(); .unwrap();
Ok((*size_of, *align_of)) Ok((*size_of, *align_of))
} }
Self::Function { .. } => Ok((0, 1)), 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`"); .expect("`offsets` must be `Some`");
assert_eq!(fields.len(), offsets.len()); 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 let Some(name) = field.name() {
if name == field_name { if name == field_name {
return Some((*offset, field.deref().clone())); return Some((offset, field.deref().clone()));
} }
} else { } else {
let field_dtype = field.deref(); let field_dtype = field.deref();
@@ -989,7 +995,7 @@ impl Dtype {
field_dtype.get_offset_struct_field(field_name, structs), field_dtype.get_offset_struct_field(field_name, structs),
continue continue
); );
return Some((*offset + offset_inner, dtype)); return Some((offset + offset_inner, dtype));
} }
} }
@@ -1000,14 +1006,14 @@ impl Dtype {
} }
#[must_use] #[must_use]
pub fn set_signed(self, is_signed: bool) -> Self { pub fn set_signed(&self, is_signed: bool) -> Self {
match self { match self {
Self::Int { Self::Int {
width, is_const, .. width, is_const, ..
} => Self::Int { } => Self::Int {
width, width: *width,
is_signed, is_signed,
is_const, is_const: *is_const,
}, },
_ => panic!("`signed` and `unsigned` only be applied to `Dtype::Int`"), _ => panic!("`signed` and `unsigned` only be applied to `Dtype::Int`"),
} }
@@ -1025,14 +1031,16 @@ impl Dtype {
Ok((dtype, is_typedef)) 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( pub fn try_from_ast_struct_declaration(
declaration: &ast::StructDeclaration, declaration: &ast::StructDeclaration,
) -> Result<Vec<Named<Self>>, DtypeError> { ) -> Result<Vec<Named<Self>>, DtypeError> {
let field_decl = if let ast::StructDeclaration::Field(field_decl) = declaration { let field_decl = if let ast::StructDeclaration::Field(field_decl) = declaration {
&field_decl.node &field_decl.node
} else { } else {
panic!("ast::StructDeclaration::StaticAssert is unsupported") return Err(DtypeError::Misc {
message: "ast::StructDeclaration::StaticAssert is unsupported".to_string(),
});
}; };
let mut spec = BaseDtype::default(); let mut spec = BaseDtype::default();
@@ -1050,9 +1058,11 @@ impl Dtype {
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
if fields.is_empty() { if fields.is_empty() {
// If anonymous field is `Dtype::Struct`, structure type of this field // If an anonymous field is `Dtype::Struct`, the structure type of this field can use
// can use this field's field as its field. // this field's field as its field.
// For exampe, let's `struct A { struct { int f; }} t;`, `t.f` is valid. //
// For example, let's `struct A { struct {
// int f; }} t;`, `t.f` is valid.
if let Self::Struct { name, .. } = &dtype { if let Self::Struct { name, .. } = &dtype {
if name.is_none() { if name.is_none() {
// Note that `const` qualifier has no effect in this time. // 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;`. /// Let's say declaration is `const int * const * const a;`. In general `self` start with `const
/// In general `self` start with `const int` which has scalar type and /// int` which has a scalar type and `declarator` represents `* const * const` with
/// `declarator` represents `* const * const` with `ast::Declarator` /// `ast::Declarator`.
/// ///
/// # Arguments /// # Arguments
/// ///
/// * `declarator` - Parts requiring conversion to 'Dtype' on the declaration /// * `declarator` - Parts requiring conversion to 'Dtype' on the declaration.
/// * `decl_spec` - Containing information that should be referenced
/// when creating `Dtype` from `Declarator`.
///
pub fn with_ast_declarator( pub fn with_ast_declarator(
mut self, mut self,
declarator: &ast::Declarator, declarator: &ast::Declarator,
@@ -1107,7 +1114,7 @@ impl Dtype {
// If function parameter is (void), remove it // If function parameter is (void), remove it
if params.len() == 1 && params[0] == Dtype::unit() { if params.len() == 1 && params[0] == Dtype::unit() {
let _ = params.pop(); let _unused = params.pop();
} }
Self::function(self, params) 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 /// 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]`. /// `array_size` is `[3]`. Then this function should return `int [2][3]`.
/// ///
/// # Arguments /// # 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<Self, DtypeError> { pub fn with_ast_array_size(self, array_size: &ast::ArraySize) -> Result<Self, DtypeError> {
let expr = if let ast::ArraySize::VariableExpression(expr) = array_size { let expr = if let ast::ArraySize::VariableExpression(expr) = array_size {
&expr.node &expr.node
} else { } 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) let constant = Constant::try_from(expr)
@@ -1165,22 +1174,18 @@ impl Dtype {
Ok(Self::array(self, value as usize)) Ok(Self::array(self, value as usize))
} }
pub fn resolve_typedefs( pub fn resolve_typedefs(self, typedefs: &HashMap<String, Dtype>) -> Result<Self, DtypeError> {
self, let dtype = match self {
typedefs: &HashMap<String, Dtype>,
structs: &HashMap<String, Option<Dtype>>,
) -> Result<Self, DtypeError> {
let dtype = match &self {
Self::Unit { .. } | Self::Int { .. } | Self::Float { .. } => self, Self::Unit { .. } | Self::Int { .. } | Self::Float { .. } => self,
Self::Pointer { inner, is_const } => { Self::Pointer { inner, is_const } => {
let inner = inner.deref().clone().resolve_typedefs(typedefs, structs)?; let inner = inner.resolve_typedefs(typedefs)?;
Self::pointer(inner).set_const(*is_const) Self::pointer(inner).set_const(is_const)
} }
Self::Array { inner, size } => { Self::Array { inner, size } => {
let inner = inner.deref().clone().resolve_typedefs(typedefs, structs)?; let inner = inner.resolve_typedefs(typedefs)?;
Self::Array { Self::Array {
inner: Box::new(inner), inner: Box::new(inner),
size: *size, size,
} }
} }
Self::Struct { Self::Struct {
@@ -1189,40 +1194,39 @@ impl Dtype {
is_const, is_const,
.. ..
} => { } => {
if let Some(fields) = fields { let (name, fields) = if let Some(fields) = fields {
let resolved_dtypes = fields let fields = fields
.iter() .into_iter()
.map(|f| f.deref().clone().resolve_typedefs(typedefs, structs)) .map(|f| {
.collect::<Result<Vec<_>, _>>()?; let (d, name) = f.destruct();
let d = d.resolve_typedefs(typedefs).unwrap();
assert_eq!(fields.len(), resolved_dtypes.len()); Named::new(name, d)
let fields = izip!(fields, resolved_dtypes) })
.map(|(f, d)| Named::new(f.name().cloned(), d))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
(name, Some(fields))
Self::structure(name.clone(), Some(fields)).set_const(*is_const)
} else { } else {
assert!(name.is_some()); assert!(name.is_some());
self (name, fields)
} };
Self::structure(name, fields).set_const(is_const)
} }
Self::Function { ret, params } => { Self::Function { ret, params } => {
let ret = ret.deref().clone().resolve_typedefs(typedefs, structs)?; let ret = ret.resolve_typedefs(typedefs)?;
let params = params let params = params
.iter() .into_iter()
.map(|p| p.clone().resolve_typedefs(typedefs, structs)) .map(|p| p.resolve_typedefs(typedefs))
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
Self::function(ret, params) Self::function(ret, params)
} }
Self::Typedef { name, is_const } => { Self::Typedef { name, is_const } => {
let dtype = typedefs let dtype = typedefs
.get(name) .get(&name)
.ok_or_else(|| DtypeError::Misc { .ok_or_else(|| DtypeError::Misc {
message: format!("unknown type name `{}`", name), message: format!("unknown type name `{}`", name),
})? })?
.clone(); .clone();
let is_const = dtype.is_const() || *is_const; let is_const = dtype.is_const() || is_const;
dtype.set_const(is_const) dtype.set_const(is_const)
} }
@@ -1238,33 +1242,29 @@ impl Dtype {
structs: &mut HashMap<String, Option<Dtype>>, structs: &mut HashMap<String, Option<Dtype>>,
tempid_counter: &mut usize, tempid_counter: &mut usize,
) -> Result<Self, DtypeError> { ) -> Result<Self, DtypeError> {
let dtype = match &self { let dtype = match self {
Self::Unit { .. } | Self::Int { .. } | Self::Float { .. } => self, Self::Unit { .. } | Self::Int { .. } | Self::Float { .. } => self,
Self::Pointer { inner, is_const } => { Self::Pointer { inner, is_const } => {
let inner = inner.deref(); // Pointer types can have an undeclared struct type as inner.
//
// the pointer type can have undeclared struct type as inner. // For example, consider `struct A { struct B *p }`, even if `struct B` has not
// For example, let's `struct A { struct B *p }`, even if `struct B` has not been // been declared before, it can be used as the inner type of the pointer.
// declared before, it can be used as inner type of the pointer. if let Self::Struct { name, fields, .. } = inner.deref() {
if let Self::Struct { name, fields, .. } = inner {
if fields.is_none() { if fields.is_none() {
let name = name.as_ref().expect("`name` must be `Some`"); let name = name.as_ref().expect("`name` must be `Some`");
let _ = structs.entry(name.to_string()).or_insert(None); 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)?; let resolved_inner = inner.resolve_structs(structs, tempid_counter)?;
Self::pointer(resolved_inner).set_const(*is_const) Self::pointer(resolved_inner).set_const(is_const)
} }
Self::Array { inner, size } => { Self::Array { inner, size } => {
let inner = inner let inner = inner.resolve_structs(structs, tempid_counter)?;
.deref()
.clone()
.resolve_structs(structs, tempid_counter)?;
Self::Array { Self::Array {
inner: Box::new(inner), inner: Box::new(inner),
size: *size, size,
} }
} }
Self::Struct { Self::Struct {
@@ -1273,19 +1273,18 @@ impl Dtype {
is_const, is_const,
.. ..
} => { } => {
if let Some(fields) = fields { let (name, fields) = if let Some(fields) = fields {
let resolved_dtypes = fields let fields = fields
.iter() .into_iter()
.map(|f| f.deref().clone().resolve_structs(structs, tempid_counter)) .map(|f| {
.collect::<Result<Vec<_>, _>>()?; let (d, name) = f.destruct();
let d = d.resolve_structs(structs, tempid_counter).unwrap();
assert_eq!(fields.len(), resolved_dtypes.len()); Named::new(name, d)
let fields = izip!(fields, resolved_dtypes) })
.map(|(f, d)| Named::new(f.name().cloned(), d))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let name = if let Some(name) = name { let name = if let Some(name) = name {
name.clone() name
} else { } else {
let tempid = *tempid_counter; let tempid = *tempid_counter;
*tempid_counter += 1; *tempid_counter += 1;
@@ -1295,8 +1294,7 @@ impl Dtype {
let filled_struct = let filled_struct =
resolved_struct.fill_size_align_offsets_of_struct(structs)?; resolved_struct.fill_size_align_offsets_of_struct(structs)?;
let prev_dtype = structs.insert(name.clone(), Some(filled_struct)); if let Some(prev_dtype) = structs.insert(name.clone(), Some(filled_struct)) {
if let Some(prev_dtype) = prev_dtype {
if prev_dtype.is_some() { if prev_dtype.is_some() {
return Err(DtypeError::Misc { return Err(DtypeError::Misc {
message: format!("redefinition of {}", name), message: format!("redefinition of {}", name),
@@ -1304,10 +1302,10 @@ impl Dtype {
} }
} }
Self::structure(Some(name), None).set_const(*is_const) (name, None)
} else { } else {
let name = name.as_ref().expect("`name` must be exist"); let name = name.expect("`name` must exist");
let struct_type = structs.get(name).ok_or_else(|| DtypeError::Misc { let struct_type = structs.get(&name).ok_or_else(|| DtypeError::Misc {
message: format!("unknown struct name `{}`", name), message: format!("unknown struct name `{}`", name),
})?; })?;
if struct_type.is_none() { 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 } => { Self::Function { ret, params } => {
let ret = ret let ret = ret.resolve_structs(structs, tempid_counter)?;
.deref()
.clone()
.resolve_structs(structs, tempid_counter)?;
let params = params let params = params
.iter() .into_iter()
.map(|p| p.clone().resolve_structs(structs, tempid_counter)) .map(|p| p.resolve_structs(structs, tempid_counter))
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
Self::function(ret, params) Self::function(ret, params)
@@ -1418,7 +1414,7 @@ fn check_no_duplicate_field(fields: &[Named<Dtype>], field_names: &mut HashSet<S
let field_dtype = field.deref(); let field_dtype = field.deref();
let fields = field_dtype let fields = field_dtype
.get_struct_fields() .get_struct_fields()
.expect("`field_dtype` must be struct type") .expect("`field_dtype` must be a struct type")
.as_ref() .as_ref()
.expect("struct type must have its definition"); .expect("struct type must have its definition");
if !check_no_duplicate_field(fields, field_names) { if !check_no_duplicate_field(fields, field_names) {

View File

@@ -289,7 +289,7 @@ fn is_equiv_block_exit(lhs: &BlockExit, rhs: &BlockExit, map: &HashMap<BlockId,
if !is_equiv_operand(value, value_other, map) { if !is_equiv_operand(value, value_other, map) {
return false; return false;
} }
if !is_equiv_arg(default.deref(), default_other.deref(), map) { if !is_equiv_arg(default, default_other, map) {
return false; return false;
} }
if cases.len() != cases_other.len() { if cases.len() != cases_other.len() {

View File

@@ -142,14 +142,14 @@ impl Value {
} }
#[inline] #[inline]
pub fn get_int(self) -> Option<(u128, usize, bool)> { pub fn get_int(&self) -> Option<(u128, usize, bool)> {
if let Value::Int { if let Value::Int {
value, value,
width, width,
is_signed, is_signed,
} = self } = self
{ {
Some((value, width, is_signed)) Some((*value, *width, *is_signed))
} else { } else {
None None
} }
@@ -221,6 +221,7 @@ impl Value {
Ok(value) Ok(value)
} }
#[allow(clippy::result_unit_err)]
pub fn try_from_initializer( pub fn try_from_initializer(
initializer: &ast::Initializer, initializer: &ast::Initializer,
dtype: &Dtype, dtype: &Dtype,
@@ -348,20 +349,19 @@ impl RegisterMap {
} }
fn write(&mut self, rid: RegisterId, value: Value) { 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 /// Bidirectional map between the name of a global variable and memory box id
#[derive(Default, Debug, PartialEq, Clone)] #[derive(Default, Debug, PartialEq, Clone)]
struct GlobalMap { 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`, /// Since IR treats global variables as `Constant::GlobalVariable`, the interpreter should be
/// the interpreter should be able to generate pointer values by infer 'bid' /// able to generate pointer values by inferring `bid` from the `name` of the global variable.
/// from the 'name' of the global variable.
var_to_bid: HashMap<String, usize>, var_to_bid: HashMap<String, usize>,
/// 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 /// 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. /// from `bid` of the `callee` which is a function pointer.
@@ -481,11 +481,11 @@ mod calculator {
ast::BinaryOperator::BitwiseXor => lhs ^ rhs, ast::BinaryOperator::BitwiseXor => lhs ^ rhs,
ast::BinaryOperator::BitwiseOr => lhs | rhs, ast::BinaryOperator::BitwiseOr => lhs | rhs,
ast::BinaryOperator::Equals => { ast::BinaryOperator::Equals => {
let result = if lhs == rhs { 1 } else { 0 }; let result = (lhs == rhs).into();
return Ok(Value::int(result, 1, false)); return Ok(Value::int(result, 1, false));
} }
ast::BinaryOperator::NotEquals => { ast::BinaryOperator::NotEquals => {
let result = if lhs != rhs { 1 } else { 0 }; let result = (lhs != rhs).into();
return Ok(Value::int(result, 1, false)); return Ok(Value::int(result, 1, false));
} }
ast::BinaryOperator::Less => { ast::BinaryOperator::Less => {
@@ -494,7 +494,7 @@ mod calculator {
} else { } else {
lhs < rhs lhs < rhs
}; };
let result = if condition { 1 } else { 0 }; let result = condition.into();
return Ok(Value::int(result, 1, false)); return Ok(Value::int(result, 1, false));
} }
ast::BinaryOperator::Greater => { ast::BinaryOperator::Greater => {
@@ -503,7 +503,7 @@ mod calculator {
} else { } else {
lhs > rhs lhs > rhs
}; };
let result = if condition { 1 } else { 0 }; let result = condition.into();
return Ok(Value::int(result, 1, false)); return Ok(Value::int(result, 1, false));
} }
ast::BinaryOperator::LessOrEqual => { ast::BinaryOperator::LessOrEqual => {
@@ -512,7 +512,7 @@ mod calculator {
} else { } else {
lhs <= rhs lhs <= rhs
}; };
let result = if condition { 1 } else { 0 }; let result = condition.into();
return Ok(Value::int(result, 1, false)); return Ok(Value::int(result, 1, false));
} }
ast::BinaryOperator::GreaterOrEqual => { ast::BinaryOperator::GreaterOrEqual => {
@@ -521,7 +521,7 @@ mod calculator {
} else { } else {
lhs >= rhs lhs >= rhs
}; };
let result = if condition { 1 } else { 0 }; let result = condition.into();
return Ok(Value::int(result, 1, false)); return Ok(Value::int(result, 1, false));
} }
_ => todo!( _ => todo!(
@@ -566,30 +566,30 @@ mod calculator {
let order = lhs let order = lhs
.partial_cmp(&rhs) .partial_cmp(&rhs)
.expect("`lhs` and `rhs` must be not NAN"); .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)); return Ok(Value::int(result, 1, false));
} }
ast::BinaryOperator::NotEquals => { ast::BinaryOperator::NotEquals => {
let order = lhs let order = lhs
.partial_cmp(&rhs) .partial_cmp(&rhs)
.expect("`lhs` and `rhs` must be not NAN"); .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)); return Ok(Value::int(result, 1, false));
} }
ast::BinaryOperator::Less => { 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)); return Ok(Value::int(result, 1, false));
} }
ast::BinaryOperator::Greater => { 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)); return Ok(Value::int(result, 1, false));
} }
ast::BinaryOperator::LessOrEqual => { 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)); return Ok(Value::int(result, 1, false));
} }
ast::BinaryOperator::GreaterOrEqual => { 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)); return Ok(Value::int(result, 1, false));
} }
_ => todo!( _ => todo!(
@@ -653,19 +653,11 @@ mod calculator {
}, },
) => match op { ) => match op {
ast::BinaryOperator::Equals => { ast::BinaryOperator::Equals => {
let result = if bid == other_bid && offset == other_offset { let result = (bid == other_bid && offset == other_offset).into();
1
} else {
0
};
Ok(Value::int(result, 1, false)) Ok(Value::int(result, 1, false))
} }
ast::BinaryOperator::NotEquals => { ast::BinaryOperator::NotEquals => {
let result = if !(bid == other_bid && offset == other_offset) { let result = (!(bid == other_bid && offset == other_offset)).into();
1
} else {
0
};
Ok(Value::int(result, 1, false)) Ok(Value::int(result, 1, false))
} }
_ => todo!( _ => todo!(
@@ -708,7 +700,7 @@ mod calculator {
ast::UnaryOperator::Negate => { ast::UnaryOperator::Negate => {
// Check if it is boolean // Check if it is boolean
assert!(width == 1); assert!(width == 1);
let result = if value == 0 { 1 } else { 0 }; let result = (value == 0).into();
Ok(Value::int(result, width, is_signed)) Ok(Value::int(result, width, is_signed))
} }
_ => todo!( _ => todo!(
@@ -786,7 +778,7 @@ mod calculator {
} }
(Value::Int { value, .. }, Dtype::Pointer { inner, .. }) => { (Value::Int { value, .. }, Dtype::Pointer { inner, .. }) => {
if value == 0 { if value == 0 {
Ok(Value::pointer(None, 0, inner.deref().clone())) Ok(Value::pointer(None, 0, *inner))
} else { } else {
panic!( panic!(
"calculate_typecast: not support case \ "calculate_typecast: not support case \
@@ -1074,7 +1066,7 @@ impl Byte {
izip!(fields, offsets).for_each(|(f, o)| { izip!(fields, offsets).for_each(|(f, o)| {
let result = Self::value_to_bytes(f.deref(), structs); let result = Self::value_to_bytes(f.deref(), structs);
let size_of_data = f.deref().dtype().size_align_of(structs).unwrap().0; 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 values
@@ -1144,7 +1136,7 @@ impl Memory {
let block = self.inner[bid].as_mut().unwrap(); let block = self.inner[bid].as_mut().unwrap();
if 0 <= offset && end <= block.len() { 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(()) Ok(())
} else { } else {
Err(()) Err(())
@@ -1154,8 +1146,9 @@ impl Memory {
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
struct State<'i> { struct State<'i> {
/// A data structure that maps each global variable to a pointer value /// Maps each global variable to a pointer value.
/// When function call occurs, `registers` can be initialized by `global_registers` ///
/// When a function call occurs, `registers` can be initialized by `global_registers`
pub global_map: GlobalMap, pub global_map: GlobalMap,
pub stack_frame: StackFrame<'i>, pub stack_frame: StackFrame<'i>,
pub stack: Vec<StackFrame<'i>>, pub stack: Vec<StackFrame<'i>>,
@@ -1330,7 +1323,7 @@ impl<'i> State<'i> {
} }
args.iter() args.iter()
.map(|a| self.interp_operand(a.clone())) .map(|a| self.interp_operand(a))
.collect::<Result<Vec<_>, _>>() .collect::<Result<Vec<_>, _>>()
} }
@@ -1349,7 +1342,7 @@ impl<'i> State<'i> {
arg.args arg.args
.iter() .iter()
.map(|a| self.interp_operand(a.clone()).unwrap()) .map(|a| self.interp_operand(a).unwrap())
.collect::<Vec<_>>() .collect::<Vec<_>>()
.into_iter() .into_iter()
.enumerate() .enumerate()
@@ -1374,7 +1367,7 @@ impl<'i> State<'i> {
arg_then, arg_then,
arg_else, 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`"); let (value, width, _) = value.get_int().expect("`condition` must be `Value::Int`");
// Check if it is boolean // Check if it is boolean
assert!(width == 1); assert!(width == 1);
@@ -1386,7 +1379,7 @@ impl<'i> State<'i> {
default, default,
cases, cases,
} => { } => {
let value = self.interp_operand(value.clone())?; let value = self.interp_operand(value)?;
// TODO: consider different integer `width` in the future // TODO: consider different integer `width` in the future
let arg = cases let arg = cases
@@ -1396,7 +1389,7 @@ impl<'i> State<'i> {
.unwrap_or_else(|| default); .unwrap_or_else(|| default);
self.interp_jump(arg) 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), BlockExit::Unreachable => Err(InterpreterError::Unreachable),
} }
} }
@@ -1405,8 +1398,8 @@ impl<'i> State<'i> {
let result = match instruction { let result = match instruction {
Instruction::Nop => Value::unit(), Instruction::Nop => Value::unit(),
Instruction::BinOp { op, lhs, rhs, .. } => { Instruction::BinOp { op, lhs, rhs, .. } => {
let lhs = self.interp_operand(lhs.clone())?; let lhs = self.interp_operand(lhs)?;
let rhs = self.interp_operand(rhs.clone())?; let rhs = self.interp_operand(rhs)?;
calculator::calculate_binary_operator_expression(op, lhs, rhs).map_err(|_| { calculator::calculate_binary_operator_expression(op, lhs, rhs).map_err(|_| {
InterpreterError::Misc { InterpreterError::Misc {
@@ -1417,7 +1410,7 @@ impl<'i> State<'i> {
})? })?
} }
Instruction::UnaryOp { op, operand, .. } => { 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(|_| { calculator::calculate_unary_operator_expression(op, operand).map_err(|_| {
InterpreterError::Misc { InterpreterError::Misc {
@@ -1428,8 +1421,8 @@ impl<'i> State<'i> {
})? })?
} }
Instruction::Store { ptr, value, .. } => { Instruction::Store { ptr, value, .. } => {
let ptr = self.interp_operand(ptr.clone())?; let ptr = self.interp_operand(ptr)?;
let value = self.interp_operand(value.clone())?; let value = self.interp_operand(value)?;
let (bid, offset, _) = self.interp_ptr(&ptr)?; let (bid, offset, _) = self.interp_ptr(&ptr)?;
self.memory self.memory
.store(bid, offset, &value, &self.ir.structs) .store(bid, offset, &value, &self.ir.structs)
@@ -1444,12 +1437,12 @@ impl<'i> State<'i> {
Value::Unit Value::Unit
} }
Instruction::Load { ptr, .. } => { Instruction::Load { ptr, .. } => {
let ptr = self.interp_operand(ptr.clone())?; let ptr = self.interp_operand(ptr)?;
let (bid, offset, dtype) = self.interp_ptr(&ptr)?; let (bid, offset, dtype) = self.interp_ptr(&ptr)?;
self.memory.load(bid, offset, &dtype, &self.ir.structs)? self.memory.load(bid, offset, &dtype, &self.ir.structs)?
} }
Instruction::Call { callee, args, .. } => { Instruction::Call { callee, args, .. } => {
let ptr = self.interp_operand(callee.clone())?; let ptr = self.interp_operand(callee)?;
// Get function name from pointer // Get function name from pointer
let (bid, _, _) = ptr.get_pointer().expect("`ptr` must be `Value::Pointer`"); let (bid, _, _) = ptr.get_pointer().expect("`ptr` must be `Value::Pointer`");
@@ -1503,7 +1496,7 @@ impl<'i> State<'i> {
value, value,
target_dtype, target_dtype,
} => { } => {
let value = self.interp_operand(value.clone())?; let value = self.interp_operand(value)?;
calculator::calculate_typecast(value, target_dtype.clone()).map_err(|_| { calculator::calculate_typecast(value, target_dtype.clone()).map_err(|_| {
InterpreterError::Misc { InterpreterError::Misc {
func_name: self.stack_frame.func_name.clone(), func_name: self.stack_frame.func_name.clone(),
@@ -1513,10 +1506,10 @@ impl<'i> State<'i> {
})? })?
} }
Instruction::GetElementPtr { ptr, offset, dtype } => { Instruction::GetElementPtr { ptr, offset, dtype } => {
let ptr = self.interp_operand(ptr.clone())?; let ptr = self.interp_operand(ptr)?;
let (value, _, _) = self let (value, _, _) = self
.interp_operand(offset.clone())? .interp_operand(offset)?
.get_int() .get_int()
.expect("`idx` must be `Value::Int`"); .expect("`idx` must be `Value::Int`");
@@ -1542,12 +1535,10 @@ impl<'i> State<'i> {
Ok(()) Ok(())
} }
fn interp_operand(&self, operand: Operand) -> Result<Value, InterpreterError> { fn interp_operand(&self, operand: &Operand) -> Result<Value, InterpreterError> {
match &operand { match operand {
Operand::Constant(value) => Ok(self.interp_constant(value.clone())), Operand::Constant(value) => Ok(self.interp_constant(value.clone())),
Operand::Register { rid, .. } => { Operand::Register { rid, .. } => Ok(self.stack_frame.registers.read(*rid).clone()),
Ok(self.stack_frame.registers.read(rid.clone()).clone())
}
} }
} }

View File

@@ -3,7 +3,6 @@
mod dtype; mod dtype;
mod equiv; mod equiv;
mod interp; mod interp;
#[allow(clippy::all)]
mod parse; mod parse;
mod visualize; mod visualize;
mod write_ir; mod write_ir;
@@ -49,14 +48,14 @@ impl TryFrom<Dtype> for Declaration {
/// ///
/// # Example /// # Example
/// ///
/// If `int g = 0;` is declared, `dtype` is /// If `int g = 0;` is declared, `dtype` is `ir::Dtype::Int{ width:32, is_signed:true,
/// `ir::Dtype::Int{ width:32, is_signed:true, is_const:false }`. /// is_const:false }`.
/// In this case, `ir::Declaration::Variable{ dtype, initializer: Some(Constant::I32(1)) }`
/// is generated.
/// ///
/// Conversely, if `int foo();` is declared, `dtype` is /// In this case, `ir::Declaration::Variable{ dtype, initializer:
/// `ir::Dtype::Function{ret: Scalar(Int), params: []}`. /// Some(Constant::I32(1)) }` is generated.
/// Thus, in this case, `ir::Declaration::Function` 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<Self, Self::Error> { fn try_from(dtype: Dtype) -> Result<Self, Self::Error> {
match &dtype { match &dtype {
Dtype::Unit { .. } => Err(DtypeError::Misc { Dtype::Unit { .. } => Err(DtypeError::Misc {
@@ -192,7 +191,6 @@ pub struct Block {
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
#[allow(clippy::large_enum_variant)]
pub enum Instruction { pub enum Instruction {
Nop, Nop,
BinOp { BinOp {
@@ -228,27 +226,30 @@ pub enum Instruction {
GetElementPtr { GetElementPtr {
ptr: Operand, ptr: Operand,
offset: Operand, offset: Operand,
dtype: Box<Dtype>, dtype: Dtype,
}, },
} }
impl HasDtype for Instruction { impl HasDtype for Instruction {
fn dtype(&self) -> Dtype { fn dtype(&self) -> Dtype {
match self { match self {
Self::Nop => Dtype::unit(), Self::Nop | Self::Store { .. } => Dtype::unit(),
Self::BinOp { dtype, .. } => dtype.clone(), Self::BinOp { dtype, .. }
Self::UnaryOp { dtype, .. } => dtype.clone(), | Self::UnaryOp { dtype, .. }
Self::Store { .. } => Dtype::unit(), | Self::Call {
return_type: dtype, ..
}
| Self::TypeCast {
target_dtype: dtype,
..
}
| Self::GetElementPtr { dtype, .. } => dtype.clone(),
Self::Load { ptr } => ptr Self::Load { ptr } => ptr
.dtype() .dtype()
.get_pointer_inner() .get_pointer_inner()
.expect("Load instruction must have pointer value as operand") .expect("Load instruction must have pointer value as operand")
.deref()
.clone() .clone()
.set_const(false), .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 { match self {
Instruction::Nop => write!(f, "nop"), Instruction::Nop => write!(f, "nop"),
Instruction::BinOp { op, lhs, rhs, .. } => { Instruction::BinOp { op, lhs, rhs, .. } => {
write!( write!(f, "{} {} {}", op.write_operation(), lhs, rhs)
f,
"{} {} {}",
op.write_operation(),
lhs.write_string(),
rhs.write_string()
)
} }
Instruction::UnaryOp { op, operand, .. } => { Instruction::UnaryOp { op, operand, .. } => {
write!(f, "{} {}", op.write_operation(), operand.write_string()) write!(f, "{} {}", op.write_operation(), operand)
} }
Instruction::Store { ptr, value } => { 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, .. } => { Instruction::Call { callee, args, .. } => {
write!( write!(
f, f,
"call {}({})", "call {}({})",
callee.write_string(), callee,
args.iter().format_with(", ", |operand, f| f(&format_args!( args.iter()
"{}", .format_with(", ", |operand, f| f(&format_args!("{}", operand)))
operand.write_string()
)))
) )
} }
Instruction::TypeCast { Instruction::TypeCast {
value, value,
target_dtype, target_dtype,
} => write!(f, "typecast {} to {}", value.write_string(), target_dtype), } => write!(f, "typecast {} to {}", value, target_dtype),
Instruction::GetElementPtr { ptr, offset, .. } => { Instruction::GetElementPtr { ptr, offset, .. } => {
write!( write!(f, "getelementptr {} offset {}", ptr, offset)
f,
"getelementptr {} offset {}",
ptr.write_string(),
offset.write_string()
)
} }
} }
} }
@@ -356,12 +344,12 @@ pub enum BlockExit {
}, },
ConditionalJump { ConditionalJump {
condition: Operand, condition: Operand,
arg_then: Box<JumpArg>, arg_then: JumpArg,
arg_else: Box<JumpArg>, arg_else: JumpArg,
}, },
Switch { Switch {
value: Operand, value: Operand,
default: Box<JumpArg>, default: JumpArg,
cases: Vec<(Constant, JumpArg)>, cases: Vec<(Constant, JumpArg)>,
}, },
Return { Return {
@@ -402,13 +390,7 @@ impl fmt::Display for BlockExit {
condition, condition,
arg_then, arg_then,
arg_else, arg_else,
} => write!( } => write!(f, "br {}, {}, {}", condition, arg_then, arg_else),
f,
"br {}, {}, {}",
condition.write_string(),
arg_then,
arg_else
),
BlockExit::Switch { BlockExit::Switch {
value, value,
default, default,
@@ -416,7 +398,7 @@ impl fmt::Display for BlockExit {
} => write!( } => write!(
f, f,
"switch {} default {} [\n{}\n ]", "switch {} default {} [\n{}\n ]",
value.write_string(), value,
default, default,
cases.iter().format_with("\n", |(v, b), f| f(&format_args!( cases.iter().format_with("\n", |(v, b), f| f(&format_args!(
" {}:{} {}", " {}:{} {}",
@@ -425,7 +407,7 @@ impl fmt::Display for BlockExit {
b b
))) )))
), ),
BlockExit::Return { value } => write!(f, "ret {}", value.write_string()), BlockExit::Return { value } => write!(f, "ret {}", value),
BlockExit::Unreachable => write!(f, "<unreachable>\t\t\t\t; error state"), BlockExit::Unreachable => write!(f, "<unreachable>\t\t\t\t; error state"),
} }
} }
@@ -499,8 +481,8 @@ impl Operand {
impl fmt::Display for Operand { impl fmt::Display for Operand {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Self::Constant(value) => write!(f, "{}", value), Self::Constant(value) => write!(f, "{}:{}", value, value.dtype()),
Self::Register { rid, .. } => write!(f, "{}", rid), 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 { pub enum RegisterId {
/// Registers holding pointers to local allocations. /// Registers holding pointers to local allocations.
/// ///
@@ -747,11 +729,12 @@ impl TryFrom<&ast::Expression> for Constant {
ast::Expression::UnaryOperator(unary) => { ast::Expression::UnaryOperator(unary) => {
let constant = Self::try_from(&unary.node.operand.node)?; let constant = Self::try_from(&unary.node.operand.node)?;
// When an IR is generated, there are cases where some expressions must be // When an IR is generated, there are cases where some expressions must be
// interpreted unconditionally as compile-time constant value. In this case, // interpreted unconditionally as a compile-time constant value. In this case, we
// we need to translate also the expression applied `minus` unary operator // need to also translate the expression applied `minus` unary operator to a
// to compile-time constant value directly. // compile-time constant value directly.
// Let's say expression is `case -1: { .. }`, //
// `-1` must be interpreted to compile-time constant value. // Let's say the expression is `case -1: { .. }`, `-1` must be interpreted to a
// compile-time constant value.
match &unary.node.operator.node { match &unary.node.operator.node {
ast::UnaryOperator::Minus => Ok(constant.minus()), ast::UnaryOperator::Minus => Ok(constant.minus()),
ast::UnaryOperator::Plus => Ok(constant), ast::UnaryOperator::Plus => Ok(constant),
@@ -1006,6 +989,14 @@ impl<T> Named<T> {
pub fn name(&self) -> Option<&String> { pub fn name(&self) -> Option<&String> {
self.name.as_ref() self.name.as_ref()
} }
pub fn destruct(self) -> (T, Option<String>) {
(self.inner, self.name)
}
pub fn into_inner(self) -> T {
self.inner
}
} }
impl<T: fmt::Display> fmt::Display for Named<T> { impl<T: fmt::Display> fmt::Display for Named<T> {

View File

@@ -74,7 +74,7 @@ peg::parser! {
rule named_decl() -> Named<Declaration> = rule named_decl() -> Named<Declaration> =
"var" __ dtype:dtype() __ var:global_variable() _ "=" _ initializer:initializer() { "var" __ dtype:dtype() __ var:global_variable() _ "=" _ initializer:initializer() {
Named::new(Some(var), Declaration::Variable { Named::new(Some(var), Declaration::Variable {
dtype: dtype, dtype,
initializer, initializer,
}) })
} }
@@ -218,7 +218,7 @@ peg::parser! {
// For this reason, we need to check the dtype of the result to confirm the dtype // For this reason, we need to check the dtype of the result to confirm the dtype
// of `GetElementPtr` instruction when parsing IR. // of `GetElementPtr` instruction when parsing IR.
let instruction = if let Instruction::GetElementPtr { ptr, offset, .. } = instruction { let instruction = if let Instruction::GetElementPtr { ptr, offset, .. } = instruction {
Instruction::GetElementPtr { ptr, offset, dtype: Box::new(dtype) } Instruction::GetElementPtr { ptr, offset, dtype }
} else { } else {
instruction instruction
}; };
@@ -317,7 +317,7 @@ peg::parser! {
Instruction::GetElementPtr{ Instruction::GetElementPtr{
ptr, ptr,
offset, 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() { "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() ** __) _ "]" { "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() { "ret" __ value:operand() {
@@ -663,12 +663,12 @@ peg::parser! {
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
IoError(std::io::Error), Io(std::io::Error),
ParseError(peg::error::ParseError<peg::str::LineCol>), Parse(peg::error::ParseError<peg::str::LineCol>),
ResolveError, Resolve,
} }
#[derive(Default, Debug)] #[derive(Default, Clone, Copy, Debug)]
pub struct Parse {} pub struct Parse {}
impl<P: AsRef<Path>> Translate<P> for Parse { impl<P: AsRef<Path>> Translate<P> for Parse {
@@ -676,8 +676,8 @@ impl<P: AsRef<Path>> Translate<P> for Parse {
type Error = Error; type Error = Error;
fn translate(&mut self, source: &P) -> Result<Self::Target, Self::Error> { fn translate(&mut self, source: &P) -> Result<Self::Target, Self::Error> {
let ir = fs::read_to_string(source).map_err(Error::IoError)?; let ir = fs::read_to_string(source).map_err(Error::Io)?;
let ir = ir_parse::translation_unit(&ir).map_err(Error::ParseError)?; let ir = ir_parse::translation_unit(&ir).map_err(Error::Parse)?;
Ok(ir) Ok(ir)
} }
} }
@@ -688,7 +688,8 @@ fn resolve_structs(struct_type: Dtype, structs: &mut HashMap<String, Option<Dtyp
.get_struct_name() .get_struct_name()
.expect("`struct_type` must be struct type") .expect("`struct_type` must be struct type")
.as_ref() .as_ref()
.expect("`struct_type` must have a name"); .expect("`struct_type` must have a name")
.clone();
let fields = struct_type let fields = struct_type
.get_struct_fields() .get_struct_fields()
.expect("`struct_type` must be struct type") .expect("`struct_type` must be struct type")
@@ -716,10 +717,9 @@ fn resolve_structs(struct_type: Dtype, structs: &mut HashMap<String, Option<Dtyp
} }
let filled_struct = struct_type let filled_struct = struct_type
.clone()
.fill_size_align_offsets_of_struct(structs) .fill_size_align_offsets_of_struct(structs)
.expect("`struct_type` must be struct type"); .expect("`struct_type` must be struct type");
let result = structs.insert(name.clone(), Some(filled_struct)); let result = structs.insert(name, Some(filled_struct));
assert!(result.is_some()); assert!(result.is_some());
} }

View File

@@ -4,7 +4,6 @@ use std::collections::HashMap;
use crate::ir::*; use crate::ir::*;
use crate::some_or; use crate::some_or;
use crate::write_base::*;
use crate::Translate; use crate::Translate;
#[derive(Default, Debug)] #[derive(Default, Debug)]
@@ -118,7 +117,7 @@ impl Visualizer {
subgraphs.push(subgraph); subgraphs.push(subgraph);
} }
let _ = self.function_first_instruction.insert( let _unused = self.function_first_instruction.insert(
name.to_string(), name.to_string(),
self.get_block_first_instruction(name, definition.bid_init), self.get_block_first_instruction(name, definition.bid_init),
); );
@@ -196,7 +195,7 @@ impl Visualizer {
nodes.push(format!( nodes.push(format!(
"{} [label=\"{}\"]", "{} [label=\"{}\"]",
self.translate_instruction_node(name, *bid, iid), self.translate_instruction_node(name, *bid, iid),
instruction.write_string() instruction
)); ));
} }
@@ -204,7 +203,7 @@ impl Visualizer {
nodes.push(format!( nodes.push(format!(
"{} [label=\"{}\"]", "{} [label=\"{}\"]",
self.translate_block_exit_node(name, *bid), self.translate_block_exit_node(name, *bid),
block.exit.write_string() block.exit
)); ));
let edges = (0..block.instructions.len()) let edges = (0..block.instructions.len())
@@ -218,7 +217,7 @@ impl Visualizer {
} else { } else {
self.translate_instruction_node(name, *bid, 0) self.translate_instruction_node(name, *bid, 0)
}; };
let _ = self let _unused = self
.block_first_instruction .block_first_instruction
.insert((name.to_string(), *bid), first_instruction); .insert((name.to_string(), *bid), first_instruction);

View File

@@ -147,12 +147,12 @@ impl WriteLine for (&BlockId, &Block) {
} else { } else {
"".into() "".into()
}, },
instr.write_string() instr
)?; )?;
} }
write_indent(indent, write)?; write_indent(indent, write)?;
writeln!(write, "{}", self.1.exit.write_string())?; writeln!(write, "{}", self.1.exit)?;
Ok(()) Ok(())
} }
@@ -166,7 +166,7 @@ impl WriteString for Instruction {
impl WriteString for Operand { impl WriteString for Operand {
fn write_string(&self) -> String { fn write_string(&self) -> String {
format!("{}:{}", self, self.dtype()) format!("{}", self)
} }
} }

View File

@@ -4,10 +4,10 @@ use lang_c::ast::*;
use crate::*; use crate::*;
#[derive(Default, Debug)] #[derive(Default, Clone, Copy, Debug)]
pub struct Irgen {} pub struct Irgen {}
#[derive(Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug)]
pub struct IrgenError {} pub struct IrgenError {}
impl fmt::Display for IrgenError { impl fmt::Display for IrgenError {

View File

@@ -1,44 +1,51 @@
//! KECC: KAIST Educational C Compiler. //! KECC: KAIST Educational C Compiler.
// Tries to deny all lints (`rustc -W help`). #![deny(clippy::all)]
#![deny(rustdoc::all)]
#![deny(warnings)] #![deny(warnings)]
// Tries to deny all rustc allow lints.
// <https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html>
#![deny(absolute_paths_not_starting_with_crate)] #![deny(absolute_paths_not_starting_with_crate)]
#![deny(anonymous_parameters)] // Old, historical lint
// #![deny(box_pointers)] // #![deny(box_pointers)]
#![deny(deprecated_in_future)]
#![deny(elided_lifetimes_in_paths)] #![deny(elided_lifetimes_in_paths)]
#![deny(explicit_outlives_requirements)] #![deny(explicit_outlives_requirements)]
#![deny(rustdoc::invalid_html_tags)]
#![deny(keyword_idents)] #![deny(keyword_idents)]
#![deny(let_underscore_drop)]
#![deny(macro_use_extern_crate)] #![deny(macro_use_extern_crate)]
#![deny(meta_variable_misuse)]
#![deny(missing_abi)]
#![deny(missing_copy_implementations)]
#![deny(missing_debug_implementations)] #![deny(missing_debug_implementations)]
// #![deny(missing_docs)] TODO // TODO
#![deny(rustdoc::missing_doc_code_examples)] // #![deny(missing_docs)]
#![deny(non_ascii_idents)] #![deny(non_ascii_idents)]
#![deny(noop_method_call)]
#![deny(pointer_structural_match)] #![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(single_use_lifetimes)]
#![deny(trivial_casts)]
#![deny(trivial_numeric_casts)] #![deny(trivial_numeric_casts)]
#![deny(unaligned_references)] // Necessary for skeleton code.
// #![deny(unreachable_pub)] // #![deny(unreachable_pub)]
#![deny(unsafe_code)]
#![deny(unsafe_op_in_unsafe_fn)]
#![deny(unstable_features)] #![deny(unstable_features)]
// Necessary for `build-bin` trick. // Necessary for `build-bin` trick.
// #![deny(unused_crate_dependencies)] // #![deny(unused_crate_dependencies)]
#![deny(unused_extern_crates)] #![deny(unused_extern_crates)]
#![deny(unused_import_braces)] #![deny(unused_import_braces)]
#![deny(unused_lifetimes)] #![deny(unused_lifetimes)]
#![deny(unused_macro_rules)]
#![deny(unused_qualifications)] #![deny(unused_qualifications)]
#![deny(unused_results)] #![deny(unused_results)]
#![deny(unused_tuple_struct_fields)]
// Allowed for more flexible variants.
// #![deny(variant_size_differences)] // #![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 tests;
mod utils; mod utils;

View File

@@ -4,7 +4,7 @@ use crate::*;
pub type Deadcode = FunctionPass<Repeat<DeadcodeInner>>; pub type Deadcode = FunctionPass<Repeat<DeadcodeInner>>;
#[derive(Default, Debug)] #[derive(Default, Clone, Copy, Debug)]
pub struct DeadcodeInner {} pub struct DeadcodeInner {}
impl Optimize<FunctionDefinition> for DeadcodeInner { impl Optimize<FunctionDefinition> for DeadcodeInner {

View File

@@ -3,7 +3,7 @@ use crate::*;
pub type Gvn = FunctionPass<GvnInner>; pub type Gvn = FunctionPass<GvnInner>;
#[derive(Default, Debug)] #[derive(Default, Clone, Copy, Debug)]
pub struct GvnInner {} pub struct GvnInner {}
impl Optimize<ir::FunctionDefinition> for GvnInner { impl Optimize<ir::FunctionDefinition> for GvnInner {

View File

@@ -4,7 +4,7 @@ use crate::*;
pub type Mem2reg = FunctionPass<Mem2regInner>; pub type Mem2reg = FunctionPass<Mem2regInner>;
#[derive(Default, Debug)] #[derive(Default, Clone, Copy, Debug)]
pub struct Mem2regInner {} pub struct Mem2regInner {}
impl Optimize<FunctionDefinition> for Mem2regInner { impl Optimize<FunctionDefinition> for Mem2regInner {

View File

@@ -22,7 +22,7 @@ pub trait Optimize<T> {
pub type O0 = Null; pub type O0 = Null;
pub type O1 = Repeat<(SimplifyCfg, (Mem2reg, (Gvn, Deadcode)))>; pub type O1 = Repeat<(SimplifyCfg, (Mem2reg, (Gvn, Deadcode)))>;
#[derive(Default, Debug)] #[derive(Default, Clone, Copy, Debug)]
pub struct Null {} pub struct Null {}
#[derive(Default, Debug)] #[derive(Default, Debug)]
@@ -66,9 +66,9 @@ where
{ {
fn optimize(&mut self, code: &mut ir::TranslationUnit) -> bool { fn optimize(&mut self, code: &mut ir::TranslationUnit) -> bool {
code.decls code.decls
.iter_mut() .values_mut()
.map(|(_, decl)| self.optimize(decl)) .map(|decl| self.optimize(decl))
.fold(false, |l, r| l || r) .fold(false, |l, r| l | r)
} }
} }

View File

@@ -10,19 +10,19 @@ pub type SimplifyCfg = FunctionPass<
>; >;
/// Simplifies block exits by propagating constants. /// Simplifies block exits by propagating constants.
#[derive(Default, Debug)] #[derive(Default, Clone, Copy, Debug)]
pub struct SimplifyCfgConstProp {} pub struct SimplifyCfgConstProp {}
/// Retains only those blocks that are reachable from the init. /// Retains only those blocks that are reachable from the init.
#[derive(Default, Debug)] #[derive(Default, Clone, Copy, Debug)]
pub struct SimplifyCfgReach {} pub struct SimplifyCfgReach {}
/// Merges two blocks if a block is pointed to only by another /// Merges two blocks if a block is pointed to only by another
#[derive(Default, Debug)] #[derive(Default, Clone, Copy, Debug)]
pub struct SimplifyCfgMerge {} pub struct SimplifyCfgMerge {}
/// Removes empty blocks /// Removes empty blocks
#[derive(Default, Debug)] #[derive(Default, Clone, Copy, Debug)]
pub struct SimplifyCfgEmpty {} pub struct SimplifyCfgEmpty {}
impl Optimize<FunctionDefinition> for SimplifyCfgConstProp { impl Optimize<FunctionDefinition> for SimplifyCfgConstProp {

View File

@@ -28,7 +28,7 @@ fn ast_initializer(number: i32) -> ast::Initializer {
let expr = ast::Expression::Constant(Box::new(span::Node::new( let expr = ast::Expression::Constant(Box::new(span::Node::new(
ast::Constant::Integer(ast::Integer { ast::Constant::Integer(ast::Integer {
base: ast::IntegerBase::Decimal, base: ast::IntegerBase::Decimal,
number: Box::from(&number.to_string() as &str), number: Box::from(number.to_string()),
suffix: ast::IntegerSuffix { suffix: ast::IntegerSuffix {
size: ast::IntegerSize::Int, size: ast::IntegerSize::Int,
unsigned: false, unsigned: false,
@@ -125,7 +125,7 @@ pub fn test_irgen(path: &Path) {
// Compile c file: If fails, test is vacuously success // Compile c file: If fails, test is vacuously success
if !Command::new("clang") if !Command::new("clang")
.args(&[ .args([
"-fsanitize=float-divide-by-zero", "-fsanitize=float-divide-by-zero",
"-fsanitize=undefined", "-fsanitize=undefined",
"-fno-sanitize-recover=all", "-fno-sanitize-recover=all",
@@ -149,7 +149,7 @@ pub fn test_irgen(path: &Path) {
let status = some_or!( let status = some_or!(
child child
.wait_timeout_ms(500) .wait_timeout_ms(1000)
.expect("failed to obtain exit status from child process"), .expect("failed to obtain exit status from child process"),
{ {
println!("timeout occurs"); println!("timeout occurs");
@@ -177,15 +177,15 @@ pub fn test_irgen(path: &Path) {
// Interpret resolved ir // Interpret resolved ir
let args = Vec::new(); let args = Vec::new();
let result = ir::interp(&ir, args).unwrap_or_else(|interp_error| panic!("{}", interp_error)); 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"); let (value, width, is_signed) = result.get_int().expect("non-integer value occurs");
assert_eq!(width, 32); assert_eq!(width, 32);
assert!(is_signed); assert!(is_signed);
// When obtain status from `clang` executable process, value is truncated to byte size. // When obtaining status from `clang` executable process, the status value is truncated to byte
// For this reason, we make `fuzzer` generate the C source code which returns value // size. For this reason, we make `fuzzer` generate the C source code which returns values
// typecasted to `unsigned char`. However, during `creduce` reduce the code, typecasting // typecasted to `unsigned char`. However, during `creduce` to reduce the code, typecasting may
// may be deleted. So, we truncate result value to byte size one more time here. // be nullified. So, we truncate the result value to byte size one more time here.
println!("clang: {}, kecc: {}", status as u8, value as u8); println!("clang: {}, kecc: {}", status as u8, value as u8);
assert_eq!(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())); .unwrap_or_else(|_| panic!("parse failed {}", path.display()));
// Test parse // Test parse
let _ = c::Parse::default() let _unused = c::Parse::default()
.translate(&path) .translate(&path)
.expect("failed to parse the given program"); .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()); test_irparse_for_optimized_ir(ir1, &temp_dir.path().join("ir2.ir"), Mem2reg::default());
let ir3 = let ir3 =
test_irparse_for_optimized_ir(ir2, &temp_dir.path().join("ir3.ir"), Deadcode::default()); 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"); temp_dir.close().expect("temp dir deletion failed");
} }
@@ -327,7 +328,7 @@ pub fn test_asmgen(path: &Path) {
// Compile the assembly code // Compile the assembly code
if !Command::new("riscv64-linux-gnu-gcc") 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()) .stderr(Stdio::null())
.status() .status()
.unwrap() .unwrap()
@@ -338,14 +339,14 @@ pub fn test_asmgen(path: &Path) {
// Emulate the executable // Emulate the executable
let mut child = Command::new("qemu-riscv64-static") let mut child = Command::new("qemu-riscv64-static")
.args(&[&bin_path_str]) .args([&bin_path_str])
.stderr(Stdio::piped()) .stderr(Stdio::piped())
.spawn() .spawn()
.expect("failed to execute the compiled executable"); .expect("failed to execute the compiled executable");
let status = some_or!( let status = some_or!(
child child
.wait_timeout_ms(500) .wait_timeout_ms(1000)
.expect("failed to obtain exit status from child process"), .expect("failed to obtain exit status from child process"),
{ {
println!("timeout occurs"); println!("timeout occurs");
@@ -393,7 +394,7 @@ pub fn test_end_to_end(path: &Path) {
// Compile c file: If fails, test is vacuously success // Compile c file: If fails, test is vacuously success
if !Command::new("clang") if !Command::new("clang")
.args(&[ .args([
"-fsanitize=float-divide-by-zero", "-fsanitize=float-divide-by-zero",
"-fsanitize=undefined", "-fsanitize=undefined",
"-fno-sanitize-recover=all", "-fno-sanitize-recover=all",
@@ -422,7 +423,7 @@ pub fn test_end_to_end(path: &Path) {
let status = some_or!( let status = some_or!(
child child
.wait_timeout_ms(500) .wait_timeout_ms(1000)
.expect("failed to obtain exit status from child process"), .expect("failed to obtain exit status from child process"),
{ {
println!("timeout occurs"); println!("timeout occurs");
@@ -452,15 +453,15 @@ pub fn test_end_to_end(path: &Path) {
let _ = O1::default().optimize(&mut ir); let _ = O1::default().optimize(&mut ir);
let args = Vec::new(); let args = Vec::new();
let result = ir::interp(&ir, args).unwrap_or_else(|interp_error| panic!("{}", interp_error)); 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"); let (value, width, is_signed) = result.get_int().expect("non-integer value occurs");
assert_eq!(width, 32); assert_eq!(width, 32);
assert!(is_signed); assert!(is_signed);
// When obtain status from `clang` executable process, value is truncated to byte size. // When obtaining status from `clang` executable process, the status value is truncated to byte
// For this reason, we make `fuzzer` generate the C source code which returns value // size. For this reason, we make `fuzzer` generate the C source code which returns values
// typecasted to `unsigned char`. However, during `creduce` reduce the code, typecasting // typecasted to `unsigned char`. However, during `creduce` to reduce the code, typecasting may
// may be deleted. So, we truncate result value to byte size one more time here. // be nullified. So, we truncate the result value to byte size one more time here.
println!( println!(
"clang: {}, kecc interp: {}", "clang: {}, kecc interp: {}",
clang_status as u8, value as u8 clang_status as u8, value as u8
@@ -487,7 +488,7 @@ pub fn test_end_to_end(path: &Path) {
// Compile the assembly code // Compile the assembly code
if !Command::new("riscv64-linux-gnu-gcc") 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()) .stderr(Stdio::null())
.status() .status()
.unwrap() .unwrap()
@@ -498,14 +499,14 @@ pub fn test_end_to_end(path: &Path) {
// Emulate the executable // Emulate the executable
let mut child = Command::new("qemu-riscv64-static") let mut child = Command::new("qemu-riscv64-static")
.args(&[&bin_path_str]) .args([&bin_path_str])
.stderr(Stdio::piped()) .stderr(Stdio::piped())
.spawn() .spawn()
.expect("failed to execute the compiled executable"); .expect("failed to execute the compiled executable");
let status = some_or!( let status = some_or!(
child child
.wait_timeout_ms(500) .wait_timeout_ms(1000)
.expect("failed to obtain exit status from child process"), .expect("failed to obtain exit status from child process"),
{ {
println!("timeout occurs"); println!("timeout occurs");

View File

@@ -205,9 +205,9 @@ fn test_examples_asmgen_small() {
test_dir(Path::new(dir), OsStr::new("ir"), |path| { test_dir(Path::new(dir), OsStr::new("ir"), |path| {
let file_name = &path let file_name = &path
.file_name() .file_name()
.expect("`path` must have file name") .expect("`path` must have a file name")
.to_str() .to_str()
.expect("must be transformed to `&str`"); .expect("must be transformable to `&str`");
if !ASMGEN_SMALL_TEST_IGNORE_LIST.contains(file_name) { if !ASMGEN_SMALL_TEST_IGNORE_LIST.contains(file_name) {
test_asmgen(path) test_asmgen(path)
} }
@@ -221,9 +221,9 @@ fn test_examples_asmgen_large() {
test_dir(Path::new(dir), OsStr::new("ir"), |path| { test_dir(Path::new(dir), OsStr::new("ir"), |path| {
let file_name = &path let file_name = &path
.file_name() .file_name()
.expect("`path` must have file name") .expect("`path` must have a file name")
.to_str() .to_str()
.expect("must be transformed to `&str`"); .expect("must be transformable to `&str`");
if ASMGEN_SMALL_TEST_IGNORE_LIST.contains(file_name) { if ASMGEN_SMALL_TEST_IGNORE_LIST.contains(file_name) {
test_asmgen(path) test_asmgen(path)
} }