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
/hw*.zip
/final.zip
target
*.zip
rustfmt.toml
**/*.rs.bk

12
Cargo.lock generated
View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

@@ -8,5 +8,5 @@ cargo fmt --all -- --check # run `cargo fmt` to auto-correct format.
cargo clippy
# Run tests.
RUST_MIN_STACK=33554432 cargo test --release test_examples_write_c
RUST_MIN_STACK=33554432 cargo test --release test_examples_write_c -- --nocapture
RUST_MIN_STACK=33554432 python3 tests/fuzz.py --print -n80 --seed 22

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
# Clears the previous submissions.
rm -rf hw2.zip hw3.zip hw4.zip hw5.zip hw6.zip hw7.zip
rm -rf irgen.zip simplify_cfg.zip mem2reg.zip gvn.zip deadcode.zip asmgen.zip final.zip
# Creates new submissions.
zip hw2.zip -j src/c/write_c.rs src/irgen/mod.rs
zip hw3.zip -j src/opt/opt_utils.rs src/opt/simplify_cfg.rs
zip hw4.zip -j src/opt/opt_utils.rs src/opt/mem2reg.rs
zip hw5.zip -j src/opt/opt_utils.rs src/opt/gvn.rs
zip hw6.zip -j src/opt/opt_utils.rs src/opt/deadcode.rs
zip hw7.zip -r src/c/write_c.rs src/irgen/mod.rs src/opt/opt_utils.rs src/asmgen/*.rs
zip irgen.zip -j src/c/write_c.rs src/irgen/mod.rs
zip simplify_cfg.zip -j src/opt/opt_utils.rs src/opt/simplify_cfg.rs
zip mem2reg.zip -j src/opt/opt_utils.rs src/opt/mem2reg.rs
zip gvn.zip -j src/opt/opt_utils.rs src/opt/gvn.rs
zip deadcode.zip -j src/opt/opt_utils.rs src/opt/deadcode.rs
zip asmgen.zip -r src/c/write_c.rs src/irgen/mod.rs src/opt/opt_utils.rs src/asmgen/*.rs
zip final.zip -r src/

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::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Todo {}
/// TODO
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Asm {
@@ -30,8 +27,8 @@ pub struct Section<T> {
pub body: T,
}
/// An object file is made up of multiple sections, with each section corresponding to
/// distinct types of executable code or data.
/// An object file is made up of multiple sections, with each section corresponding to distinct
/// types of executable code or data.
///
/// For more details: <https://github.com/michaeljclark/michaeljclark.github.io/blob/master/asm.md#sections>
impl<T> Section<T> {
@@ -78,10 +75,10 @@ impl Block {
}
}
/// The assembler implements a number of directives that control the assembly of instructions
/// into an object file.
/// The assembler implements several directives that control the assembly of instructions into an
/// object file.
///
/// For more details: <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)]
pub enum Directive {
/// .align integer
@@ -135,7 +132,7 @@ impl fmt::Display for Directive {
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SectionType {
Text,
Data,
@@ -158,7 +155,7 @@ impl fmt::Display for SectionType {
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SymbolType {
Function,
Object,
@@ -286,7 +283,7 @@ impl fmt::Display for Instruction {
/// If the enum variant contains `bool`,
/// It means that different instructions exist
/// depending on whether the operand is signed or not.
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RType {
Add(DataSize),
Sub(DataSize),
@@ -621,7 +618,7 @@ impl fmt::Display for RType {
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IType {
Load {
data_size: DataSize,
@@ -730,7 +727,7 @@ impl fmt::Display for IType {
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SType {
Store(DataSize),
}
@@ -768,7 +765,7 @@ impl fmt::Display for SType {
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BType {
Beq,
Bne,
@@ -791,7 +788,7 @@ impl fmt::Display for BType {
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UType {
Lui,
}
@@ -804,11 +801,11 @@ impl fmt::Display for UType {
}
}
/// The assembler implements a number of convenience psuedo-instructions that are formed from
/// instructions in the base ISA, but have implicit arguments or in some case reversed arguments,
/// that result in distinct semantics.
/// The assembler implements several convenience psuedo-instructions that are formed from multiple
/// instructions in the base ISA, but have implicit arguments or reversed arguments that result in
/// distinct semantics.
///
/// For more details:
/// For more information:
/// - <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)
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -935,10 +932,10 @@ impl fmt::Display for Immediate {
}
/// The relocation function creates synthesize operand values that are resolved
/// at program link time and are used as immediate parameters to specific instructions.
/// at program link time and are used as immediate parameters for specific instructions.
///
/// For more details: <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 {
/// %hi
Hi20,
@@ -1054,10 +1051,10 @@ impl fmt::Display for DataSize {
}
}
// TODO: Add calling convention information (caller/callee-save registers)
/// ABI name for RISC-V integer and floating-point register
///
/// For more details: <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)]
pub enum Register {
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<()> {
for directive in &self.header {
write_indent(indent + INDENT, write)?;
writeln!(write, "{}", directive.write_string())?;
writeln!(write, "{}", directive)?;
}
self.body.write_line(indent, write)?;
@@ -52,7 +52,7 @@ impl WriteLine for Variable {
writeln!(write, "{}:", self.label.0)?;
for directive in &self.directives {
write_indent(indent + INDENT, write)?;
writeln!(write, "{}", directive.write_string())?;
writeln!(write, "{}", directive)?;
}
Ok(())
@@ -67,7 +67,7 @@ impl WriteLine for Block {
for instruction in &self.instructions {
write_indent(indent + INDENT, write)?;
writeln!(write, "{}", instruction.write_string())?;
writeln!(write, "{}", instruction)?;
}
Ok(())

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,44 +1,51 @@
//! KECC: KAIST Educational C Compiler.
// Tries to deny all lints (`rustc -W help`).
#![deny(clippy::all)]
#![deny(rustdoc::all)]
#![deny(warnings)]
// Tries to deny all rustc allow lints.
// <https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html>
#![deny(absolute_paths_not_starting_with_crate)]
#![deny(anonymous_parameters)]
// Old, historical lint
// #![deny(box_pointers)]
#![deny(deprecated_in_future)]
#![deny(elided_lifetimes_in_paths)]
#![deny(explicit_outlives_requirements)]
#![deny(rustdoc::invalid_html_tags)]
#![deny(keyword_idents)]
#![deny(let_underscore_drop)]
#![deny(macro_use_extern_crate)]
#![deny(meta_variable_misuse)]
#![deny(missing_abi)]
#![deny(missing_copy_implementations)]
#![deny(missing_debug_implementations)]
// #![deny(missing_docs)] TODO
#![deny(rustdoc::missing_doc_code_examples)]
// TODO
// #![deny(missing_docs)]
#![deny(non_ascii_idents)]
#![deny(noop_method_call)]
#![deny(pointer_structural_match)]
#![deny(rust_2021_incompatible_closure_captures)]
#![deny(rust_2021_incompatible_or_patterns)]
#![deny(rust_2021_prefixes_incompatible_syntax)]
#![deny(rust_2021_prelude_collisions)]
// Necessary for skeleton code.
// #![deny(single_use_lifetimes)]
#![deny(trivial_casts)]
#![deny(trivial_numeric_casts)]
#![deny(unaligned_references)]
// Necessary for skeleton code.
// #![deny(unreachable_pub)]
#![deny(unsafe_code)]
#![deny(unsafe_op_in_unsafe_fn)]
#![deny(unstable_features)]
// Necessary for `build-bin` trick.
// #![deny(unused_crate_dependencies)]
#![deny(unused_extern_crates)]
#![deny(unused_import_braces)]
#![deny(unused_lifetimes)]
#![deny(unused_macro_rules)]
#![deny(unused_qualifications)]
#![deny(unused_results)]
#![deny(unused_tuple_struct_fields)]
// Allowed for more flexible variants.
// #![deny(variant_size_differences)]
#![deny(rust_2018_idioms)]
#![deny(rustdoc::all)]
// Necessary for skeleton code.
#![allow(unreachable_code)]
// Necessary to allow `iter.fold(false, |l, r| l || r)`. It's used when iteration should not be
// short-circuited.
#![allow(clippy::unnecessary_fold)]
#![allow(clippy::result_unit_err)]
#![allow(clippy::vec_init_then_push)]
#![allow(clippy::collapsible_match)]
mod tests;
mod utils;

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,19 +10,19 @@ pub type SimplifyCfg = FunctionPass<
>;
/// Simplifies block exits by propagating constants.
#[derive(Default, Debug)]
#[derive(Default, Clone, Copy, Debug)]
pub struct SimplifyCfgConstProp {}
/// Retains only those blocks that are reachable from the init.
#[derive(Default, Debug)]
#[derive(Default, Clone, Copy, Debug)]
pub struct SimplifyCfgReach {}
/// Merges two blocks if a block is pointed to only by another
#[derive(Default, Debug)]
#[derive(Default, Clone, Copy, Debug)]
pub struct SimplifyCfgMerge {}
/// Removes empty blocks
#[derive(Default, Debug)]
#[derive(Default, Clone, Copy, Debug)]
pub struct SimplifyCfgEmpty {}
impl Optimize<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(
ast::Constant::Integer(ast::Integer {
base: ast::IntegerBase::Decimal,
number: Box::from(&number.to_string() as &str),
number: Box::from(number.to_string()),
suffix: ast::IntegerSuffix {
size: ast::IntegerSize::Int,
unsigned: false,
@@ -125,7 +125,7 @@ pub fn test_irgen(path: &Path) {
// Compile c file: If fails, test is vacuously success
if !Command::new("clang")
.args(&[
.args([
"-fsanitize=float-divide-by-zero",
"-fsanitize=undefined",
"-fno-sanitize-recover=all",
@@ -149,7 +149,7 @@ pub fn test_irgen(path: &Path) {
let status = some_or!(
child
.wait_timeout_ms(500)
.wait_timeout_ms(1000)
.expect("failed to obtain exit status from child process"),
{
println!("timeout occurs");
@@ -177,15 +177,15 @@ pub fn test_irgen(path: &Path) {
// Interpret resolved ir
let args = Vec::new();
let result = ir::interp(&ir, args).unwrap_or_else(|interp_error| panic!("{}", interp_error));
// We only allow main function whose return type is `int`
// We only allow a main function whose return type is `int`
let (value, width, is_signed) = result.get_int().expect("non-integer value occurs");
assert_eq!(width, 32);
assert!(is_signed);
// When obtain status from `clang` executable process, value is truncated to byte size.
// For this reason, we make `fuzzer` generate the C source code which returns value
// typecasted to `unsigned char`. However, during `creduce` reduce the code, typecasting
// may be deleted. So, we truncate result value to byte size one more time here.
// When obtaining status from `clang` executable process, the status value is truncated to byte
// size. For this reason, we make `fuzzer` generate the C source code which returns values
// typecasted to `unsigned char`. However, during `creduce` to reduce the code, typecasting may
// be nullified. So, we truncate the result value to byte size one more time here.
println!("clang: {}, kecc: {}", status as u8, value as u8);
assert_eq!(status as u8, value as u8);
}
@@ -199,7 +199,7 @@ pub fn test_irparse(path: &Path) {
.unwrap_or_else(|_| panic!("parse failed {}", path.display()));
// Test parse
let _ = c::Parse::default()
let _unused = c::Parse::default()
.translate(&path)
.expect("failed to parse the given program");
@@ -226,7 +226,8 @@ pub fn test_irparse(path: &Path) {
test_irparse_for_optimized_ir(ir1, &temp_dir.path().join("ir2.ir"), Mem2reg::default());
let ir3 =
test_irparse_for_optimized_ir(ir2, &temp_dir.path().join("ir3.ir"), Deadcode::default());
let _ = test_irparse_for_optimized_ir(ir3, &temp_dir.path().join("ir4.ir"), Gvn::default());
let _unused =
test_irparse_for_optimized_ir(ir3, &temp_dir.path().join("ir4.ir"), Gvn::default());
temp_dir.close().expect("temp dir deletion failed");
}
@@ -327,7 +328,7 @@ pub fn test_asmgen(path: &Path) {
// Compile the assembly code
if !Command::new("riscv64-linux-gnu-gcc")
.args(&["-static", &asm_path_str, "-o", &bin_path_str])
.args(["-static", &asm_path_str, "-o", &bin_path_str])
.stderr(Stdio::null())
.status()
.unwrap()
@@ -338,14 +339,14 @@ pub fn test_asmgen(path: &Path) {
// Emulate the executable
let mut child = Command::new("qemu-riscv64-static")
.args(&[&bin_path_str])
.args([&bin_path_str])
.stderr(Stdio::piped())
.spawn()
.expect("failed to execute the compiled executable");
let status = some_or!(
child
.wait_timeout_ms(500)
.wait_timeout_ms(1000)
.expect("failed to obtain exit status from child process"),
{
println!("timeout occurs");
@@ -393,7 +394,7 @@ pub fn test_end_to_end(path: &Path) {
// Compile c file: If fails, test is vacuously success
if !Command::new("clang")
.args(&[
.args([
"-fsanitize=float-divide-by-zero",
"-fsanitize=undefined",
"-fno-sanitize-recover=all",
@@ -422,7 +423,7 @@ pub fn test_end_to_end(path: &Path) {
let status = some_or!(
child
.wait_timeout_ms(500)
.wait_timeout_ms(1000)
.expect("failed to obtain exit status from child process"),
{
println!("timeout occurs");
@@ -452,15 +453,15 @@ pub fn test_end_to_end(path: &Path) {
let _ = O1::default().optimize(&mut ir);
let args = Vec::new();
let result = ir::interp(&ir, args).unwrap_or_else(|interp_error| panic!("{}", interp_error));
// We only allow main function whose return type is `int`
// We only allow a main function whose return type is `int`
let (value, width, is_signed) = result.get_int().expect("non-integer value occurs");
assert_eq!(width, 32);
assert!(is_signed);
// When obtain status from `clang` executable process, value is truncated to byte size.
// For this reason, we make `fuzzer` generate the C source code which returns value
// typecasted to `unsigned char`. However, during `creduce` reduce the code, typecasting
// may be deleted. So, we truncate result value to byte size one more time here.
// When obtaining status from `clang` executable process, the status value is truncated to byte
// size. For this reason, we make `fuzzer` generate the C source code which returns values
// typecasted to `unsigned char`. However, during `creduce` to reduce the code, typecasting may
// be nullified. So, we truncate the result value to byte size one more time here.
println!(
"clang: {}, kecc interp: {}",
clang_status as u8, value as u8
@@ -487,7 +488,7 @@ pub fn test_end_to_end(path: &Path) {
// Compile the assembly code
if !Command::new("riscv64-linux-gnu-gcc")
.args(&["-static", &asm_path_str, "-o", &bin_path_str])
.args(["-static", &asm_path_str, "-o", &bin_path_str])
.stderr(Stdio::null())
.status()
.unwrap()
@@ -498,14 +499,14 @@ pub fn test_end_to_end(path: &Path) {
// Emulate the executable
let mut child = Command::new("qemu-riscv64-static")
.args(&[&bin_path_str])
.args([&bin_path_str])
.stderr(Stdio::piped())
.spawn()
.expect("failed to execute the compiled executable");
let status = some_or!(
child
.wait_timeout_ms(500)
.wait_timeout_ms(1000)
.expect("failed to obtain exit status from child process"),
{
println!("timeout occurs");

View File

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