Fix hw1 fuzzer again

This commit is contained in:
Jeehoon Kang
2020-03-28 18:29:04 +09:00
parent 938390821f
commit 2a5a5e71ed
21 changed files with 500 additions and 110 deletions

12
Cargo.lock generated
View File

@@ -113,7 +113,7 @@ dependencies = [
[[package]] [[package]]
name = "itertools" name = "itertools"
version = "0.8.2" version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -125,14 +125,14 @@ version = "0.1.0"
dependencies = [ dependencies = [
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", "itertools 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lang-c 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "lang-c 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
name = "lang-c" name = "lang-c"
version = "0.7.0" version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
@@ -321,8 +321,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum failure_derive 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "030a733c8287d6213886dd487564ff5c8f6aae10278b3588ed177f9d18f8d231" "checksum failure_derive 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "030a733c8287d6213886dd487564ff5c8f6aae10278b3588ed177f9d18f8d231"
"checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" "checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb"
"checksum hermit-abi 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1010591b26bbfe835e9faeabeb11866061cc7dcebffd56ad7d0942d0e61aefd8" "checksum hermit-abi 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1010591b26bbfe835e9faeabeb11866061cc7dcebffd56ad7d0942d0e61aefd8"
"checksum itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484" "checksum itertools 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
"checksum lang-c 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "43d8e04e01e7e22312294e6aaa1e121192b103abf9408800fc20ee85c67ccc0f" "checksum lang-c 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "86efc420d5d7407655eb2ff1a77d7c81463307f1b204c886f7608cc2e6506d55"
"checksum libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)" = "eb147597cdf94ed43ab7a9038716637d2d1bf2bc571da995d0028dec06bd3018" "checksum libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)" = "eb147597cdf94ed43ab7a9038716637d2d1bf2bc571da995d0028dec06bd3018"
"checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" "checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b"
"checksum proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435" "checksum proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435"

View File

@@ -25,7 +25,7 @@ path = "bin/fuzz.rs"
[dependencies] [dependencies]
clap = { version = "2.33.0", features = ["yaml"] } clap = { version = "2.33.0", features = ["yaml"] }
lang-c = "0.7.0" lang-c = "0.8.0"
itertools = "0.8" itertools = "0.9.0"
failure = "0.1.6" failure = "0.1.7"
tempfile = "3.1.0" tempfile = "3.1.0"

View File

@@ -39,6 +39,21 @@ cargo test <test-name> # run a particular test
## Fuzzing ## 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, for homework 1, do:
```sh
# randomly generates test inputs and tests them
python3 tests/fuzz.py --print
# reduces the failing test input as much as possible
python3 tests/fuzz.py --print --reduce
# fix your code for the reduced test input
cat tests/test_reduced.c
```
### Install ### Install
```sh ```sh

View File

@@ -27,4 +27,20 @@ fn main() {
if matches.is_present("irgen") { if matches.is_present("irgen") {
kecc::test_irgen(&unit, Path::new(&input)); kecc::test_irgen(&unit, Path::new(&input));
} }
if matches.is_present("simplify-cfg") {
todo!("test simplify-cfg");
}
if matches.is_present("mem2erg") {
todo!("test mem2reg");
}
if matches.is_present("deadcode") {
todo!("test deadcode");
}
if matches.is_present("gvn") {
todo!("test gvn");
}
} }

View File

@@ -8,6 +8,18 @@ args:
short: i short: i
long: irgen long: irgen
help: Fuzzes irgen help: Fuzzes irgen
- simplify-cfg:
long: simplify-cfg
help: Performs simplify-cfg
- mem2reg:
long: mem2reg
help: Performs mem2reg
- deadcode:
long: deadcode
help: Performs deadcode elimination
- gvn:
long: gvn
help: Performs gvn
- INPUT: - INPUT:
help: Sets the input file to use help: Sets the input file to use
required: true required: true

View File

@@ -5,7 +5,9 @@ use clap::{crate_authors, crate_description, crate_version, App};
#[macro_use] #[macro_use]
extern crate kecc; extern crate kecc;
use kecc::{write, Asmgen, Irgen, Optimize, Parse, Translate, O1}; use kecc::{
write, Asmgen, Deadcode, Gvn, Irgen, Mem2reg, Optimize, Parse, SimplifyCfg, Translate, O1,
};
fn main() { fn main() {
let yaml = load_yaml!("kecc_cli.yml"); let yaml = load_yaml!("kecc_cli.yml");
@@ -26,6 +28,10 @@ fn main() {
Box::new(ok_or_exit!(::std::fs::File::open(output), 1)) Box::new(ok_or_exit!(::std::fs::File::open(output), 1))
}; };
if matches.is_present("parse") {
return;
}
if matches.is_present("print") { if matches.is_present("print") {
write(&unit, &mut output).unwrap(); write(&unit, &mut output).unwrap();
return; return;
@@ -45,6 +51,22 @@ fn main() {
if matches.is_present("optimize") { if matches.is_present("optimize") {
O1::default().optimize(&mut ir); O1::default().optimize(&mut ir);
} else {
if matches.is_present("simplify-cfg") {
SimplifyCfg::default().optimize(&mut ir);
}
if matches.is_present("mem2erg") {
Mem2reg::default().optimize(&mut ir);
}
if matches.is_present("deadcode") {
Deadcode::default().optimize(&mut ir);
}
if matches.is_present("gvn") {
Gvn::default().optimize(&mut ir);
}
} }
let asm = ok_or_exit!(Asmgen::default().translate(&ir), 1); let asm = ok_or_exit!(Asmgen::default().translate(&ir), 1);

View File

@@ -1,5 +1,8 @@
name: kecc name: kecc
args: args:
- parse:
long: parse
help: Parses the input file
- print: - print:
short: p short: p
long: print long: print
@@ -12,6 +15,18 @@ args:
short: O short: O
long: optimize long: optimize
help: Optimizes IR help: Optimizes IR
- simplify-cfg:
long: simplify-cfg
help: Performs simplify-cfg
- mem2reg:
long: mem2reg
help: Performs mem2reg
- deadcode:
long: deadcode
help: Performs deadcode elimination
- gvn:
long: gvn
help: Performs gvn
- output: - output:
short: o short: o
long: output long: output

View File

@@ -44,6 +44,10 @@ pub enum Dtype {
inner: Box<Dtype>, inner: Box<Dtype>,
is_const: bool, is_const: bool,
}, },
Array {
inner: Box<Dtype>,
size: usize,
},
Function { Function {
ret: Box<Dtype>, ret: Box<Dtype>,
params: Vec<Dtype>, params: Vec<Dtype>,
@@ -342,6 +346,30 @@ impl Dtype {
} }
} }
// 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*]`.
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);
Self::Array {
inner: Box::new(inner),
size: old_size,
}
}
Self::Function { .. } => panic!("array size cannot be applied to function type"),
inner => Self::Array {
inner: Box::new(inner),
size,
},
}
}
#[inline] #[inline]
pub fn function(ret: Dtype, params: Vec<Dtype>) -> Self { pub fn function(ret: Dtype, params: Vec<Dtype>) -> Self {
Self::Function { Self::Function {
@@ -411,9 +439,8 @@ impl Dtype {
Self::Int { is_const, .. } => *is_const, Self::Int { is_const, .. } => *is_const,
Self::Float { is_const, .. } => *is_const, Self::Float { is_const, .. } => *is_const,
Self::Pointer { is_const, .. } => *is_const, Self::Pointer { is_const, .. } => *is_const,
Self::Function { .. } => { Self::Array { .. } => true,
panic!("there should be no case that check whether `Function` is `const`") Self::Function { .. } => true,
}
} }
} }
@@ -429,36 +456,36 @@ impl Dtype {
}, },
Self::Float { width, .. } => Self::Float { width, is_const }, Self::Float { width, .. } => Self::Float { width, is_const },
Self::Pointer { inner, .. } => Self::Pointer { inner, is_const }, Self::Pointer { inner, .. } => Self::Pointer { inner, is_const },
Self::Function { .. } => panic!("`const` cannot be applied to `Dtype::Function`"), Self::Array { .. } => self,
Self::Function { .. } => self,
} }
} }
/// Return byte size of `Dtype` pub fn size_align_of(&self) -> Result<(usize, usize), DtypeError> {
pub fn size_of(&self) -> Result<usize, DtypeError> {
// TODO: consider complex type like array, structure in the future
match self { match self {
Self::Unit { .. } => Ok(0), Self::Unit { .. } => Ok((0, 1)),
Self::Int { width, .. } => Ok(*width / Self::WIDTH_OF_BYTE), Self::Int { width, .. } | Self::Float { width, .. } => {
Self::Float { width, .. } => Ok(*width / Self::WIDTH_OF_BYTE), let align_of = *width / Self::WIDTH_OF_BYTE;
Self::Pointer { .. } => Ok(Self::WIDTH_OF_POINTER / Self::WIDTH_OF_BYTE), let size_of = align_of;
Self::Function { .. } => Err(DtypeError::Misc {
message: "`sizeof` cannot be used with function types".to_string(),
}),
}
}
/// Return alignment requirements of `Dtype` Ok((size_of, align_of))
pub fn align_of(&self) -> Result<usize, DtypeError> { }
// TODO: consider complex type like array, structure in the future Self::Pointer { .. } => {
// TODO: when considering complex type like a structure, let align_of = Self::WIDTH_OF_POINTER / Self::WIDTH_OF_BYTE;
// the calculation method should be different from `Dtype::size_of`. let size_of = align_of;
match self {
Self::Unit { .. } => Ok(0), Ok((size_of, align_of))
Self::Int { width, .. } => Ok(*width / Self::WIDTH_OF_BYTE), }
Self::Float { width, .. } => Ok(*width / Self::WIDTH_OF_BYTE), Self::Array { inner, size, .. } => {
Self::Pointer { .. } => Ok(Self::WIDTH_OF_POINTER / Self::WIDTH_OF_BYTE), let (size_of_inner, align_of_inner) = inner.size_align_of()?;
Ok((
size * std::cmp::max(size_of_inner, align_of_inner),
align_of_inner,
))
}
Self::Function { .. } => Err(DtypeError::Misc { Self::Function { .. } => Err(DtypeError::Misc {
message: "`alignof` cannot be used with function types".to_string(), message: "`size_align_of` cannot be used with function types".to_string(),
}), }),
} }
} }
@@ -590,6 +617,7 @@ impl fmt::Display for Dtype {
Self::Pointer { inner, is_const } => { Self::Pointer { inner, is_const } => {
write!(f, "{}* {}", inner, if *is_const { "const" } else { "" }) write!(f, "{}* {}", inner, if *is_const { "const" } else { "" })
} }
Self::Array { inner, size, .. } => write!(f, "[{} x {}]", size, inner,),
Self::Function { ret, params } => write!( Self::Function { ret, params } => write!(
f, f,
"{} ({})", "{} ({})",

View File

@@ -11,6 +11,9 @@ use crate::*;
// TODO: the variants of Value will be added in the future // TODO: the variants of Value will be added in the future
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub enum Value { pub enum Value {
Undef {
dtype: Dtype,
},
Unit, Unit,
Int { Int {
value: u128, value: u128,
@@ -97,7 +100,8 @@ impl Value {
} => Self::int(u128::default(), *width, *is_signed), } => Self::int(u128::default(), *width, *is_signed),
ir::Dtype::Float { width, .. } => Self::float(f64::default(), *width), ir::Dtype::Float { width, .. } => Self::float(f64::default(), *width),
ir::Dtype::Pointer { .. } => Self::nullptr(), ir::Dtype::Pointer { .. } => Self::nullptr(),
ir::Dtype::Function { .. } => panic!("function types do not have a default value"), ir::Dtype::Array { .. } => panic!("array type does not have a default value"),
ir::Dtype::Function { .. } => panic!("function type does not have a default value"),
} }
} }
} }
@@ -225,6 +229,8 @@ mod calculator {
rhs: Value, rhs: Value,
) -> Result<Value, ()> { ) -> Result<Value, ()> {
match (op, lhs, rhs) { match (op, lhs, rhs) {
(_, Value::Undef { .. }, _) => Err(()),
(_, _, Value::Undef { .. }) => Err(()),
( (
op, op,
Value::Int { Value::Int {
@@ -273,6 +279,7 @@ mod calculator {
operand: Value, operand: Value,
) -> Result<Value, ()> { ) -> Result<Value, ()> {
match (op, operand) { match (op, operand) {
(_, Value::Undef { .. }) => Err(()),
( (
ast::UnaryOperator::Plus, ast::UnaryOperator::Plus,
Value::Int { Value::Int {
@@ -312,6 +319,7 @@ mod calculator {
pub fn calculate_typecast(value: Value, dtype: crate::ir::Dtype) -> Result<Value, ()> { pub fn calculate_typecast(value: Value, dtype: crate::ir::Dtype) -> Result<Value, ()> {
match (value, dtype) { match (value, dtype) {
(Value::Undef { .. }, _) => Err(()),
// TODO: distinguish zero/signed extension in the future // TODO: distinguish zero/signed extension in the future
// TODO: consider truncate in the future // TODO: consider truncate in the future
( (
@@ -336,14 +344,7 @@ struct Memory {
impl Memory { impl Memory {
fn alloc(&mut self, dtype: &Dtype) -> Result<usize, InterpreterError> { fn alloc(&mut self, dtype: &Dtype) -> Result<usize, InterpreterError> {
let memory_block = match dtype { let memory_block = Self::block_from_dtype(dtype);
ir::Dtype::Unit { .. }
| ir::Dtype::Int { .. }
| ir::Dtype::Float { .. }
| ir::Dtype::Pointer { .. } => vec![Value::default_from_dtype(dtype)],
ir::Dtype::Function { .. } => vec![],
};
self.inner.push(memory_block); self.inner.push(memory_block);
Ok(self.inner.len() - 1) Ok(self.inner.len() - 1)
@@ -356,6 +357,25 @@ impl Memory {
fn store(&mut self, bid: usize, offset: usize, value: Value) { fn store(&mut self, bid: usize, offset: usize, value: Value) {
self.inner[bid][offset] = value; self.inner[bid][offset] = value;
} }
fn block_from_dtype(dtype: &Dtype) -> Vec<Value> {
match dtype {
ir::Dtype::Unit { .. } => vec![],
ir::Dtype::Int { .. } | ir::Dtype::Float { .. } | ir::Dtype::Pointer { .. } => {
vec![Value::Undef {
dtype: dtype.clone(),
}]
}
ir::Dtype::Array { inner, size, .. } => {
let sub_vec = Self::block_from_dtype(inner.deref());
(0..*size).fold(vec![], |mut result, _| {
result.append(&mut sub_vec.clone());
result
})
}
ir::Dtype::Function { .. } => vec![],
}
}
} }
// TODO: allocation fields will be added in the future // TODO: allocation fields will be added in the future
@@ -414,16 +434,20 @@ impl<'i> State<'i> {
// Initialize allocated memory space // Initialize allocated memory space
match decl { match decl {
Declaration::Variable { dtype, initializer } => { Declaration::Variable { dtype, initializer } => match &dtype {
if dtype.get_function_inner().is_some() { ir::Dtype::Unit { .. } => (),
panic!("function variable does not exist") ir::Dtype::Int { .. } | ir::Dtype::Float { .. } | ir::Dtype::Pointer { .. } => {
} let value = if let Some(constant) = initializer {
self.interp_constant(constant.clone())
} else {
Value::default_from_dtype(&dtype)
};
if let Some(constant) = initializer {
let value = self.interp_constant(constant.clone());
self.memory.store(bid, 0, value); self.memory.store(bid, 0, value);
} }
} ir::Dtype::Array { .. } => todo!("Initializer::List is needed"),
ir::Dtype::Function { .. } => panic!("function variable does not exist"),
},
// If functin declaration, skip initialization // If functin declaration, skip initialization
Declaration::Function { .. } => (), Declaration::Function { .. } => (),
} }
@@ -578,6 +602,7 @@ impl<'i> State<'i> {
fn interp_instruction(&mut self, instruction: &Instruction) -> Result<(), InterpreterError> { fn interp_instruction(&mut self, instruction: &Instruction) -> Result<(), InterpreterError> {
let result = match instruction { let result = match instruction {
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.clone())?;
let rhs = self.interp_operand(rhs.clone())?; let rhs = self.interp_operand(rhs.clone())?;
@@ -685,6 +710,7 @@ impl<'i> State<'i> {
fn interp_constant(&self, value: Constant) -> Value { fn interp_constant(&self, value: Constant) -> Value {
match value { match value {
Constant::Undef { dtype } => Value::Undef { dtype },
Constant::Unit => Value::Unit, Constant::Unit => Value::Unit,
Constant::Int { Constant::Int {
value, value,

View File

@@ -49,12 +49,13 @@ impl TryFrom<Dtype> for Declaration {
Dtype::Unit { .. } => Err(DtypeError::Misc { Dtype::Unit { .. } => Err(DtypeError::Misc {
message: "A variable of type `void` cannot be declared".to_string(), message: "A variable of type `void` cannot be declared".to_string(),
}), }),
Dtype::Int { .. } | Dtype::Float { .. } | Dtype::Pointer { .. } => { Dtype::Int { .. }
Ok(Declaration::Variable { | Dtype::Float { .. }
dtype, | Dtype::Pointer { .. }
initializer: None, | Dtype::Array { .. } => Ok(Declaration::Variable {
}) dtype,
} initializer: None,
}),
Dtype::Function { .. } => Ok(Declaration::Function { Dtype::Function { .. } => Ok(Declaration::Function {
signature: FunctionSignature::new(dtype), signature: FunctionSignature::new(dtype),
definition: None, definition: None,
@@ -257,6 +258,9 @@ impl Hash for RegisterId {
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub enum Constant { pub enum Constant {
Undef {
dtype: Dtype,
},
Unit, Unit,
Int { Int {
value: u128, value: u128,
@@ -365,8 +369,14 @@ impl Constant {
} }
} }
#[inline]
pub fn undef(dtype: Dtype) -> Self {
Self::Undef { dtype }
}
#[inline]
pub fn unit() -> Self { pub fn unit() -> Self {
Constant::Unit Self::Unit
} }
#[inline] #[inline]
@@ -374,7 +384,7 @@ impl Constant {
let width = dtype.get_int_width().expect("`dtype` must be `Dtype::Int`"); let width = dtype.get_int_width().expect("`dtype` must be `Dtype::Int`");
let is_signed = dtype.is_int_signed(); let is_signed = dtype.is_int_signed();
Constant::Int { Self::Int {
value, value,
width, width,
is_signed, is_signed,
@@ -387,18 +397,27 @@ impl Constant {
.get_float_width() .get_float_width()
.expect("`dtype` must be `Dtype::Float`"); .expect("`dtype` must be `Dtype::Float`");
Constant::Float { value, width } Self::Float { value, width }
} }
#[inline] #[inline]
pub fn global_variable(name: String, dtype: Dtype) -> Self { pub fn global_variable(name: String, dtype: Dtype) -> Self {
Self::GlobalVariable { name, dtype } Self::GlobalVariable { name, dtype }
} }
pub fn is_undef(&self) -> bool {
if let Self::Undef { .. } = self {
true
} else {
false
}
}
} }
impl fmt::Display for Constant { impl fmt::Display for Constant {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Self::Undef { .. } => write!(f, "undef"),
Self::Unit => write!(f, "unit"), Self::Unit => write!(f, "unit"),
Self::Int { value, .. } => write!(f, "{}", value), Self::Int { value, .. } => write!(f, "{}", value),
Self::Float { value, .. } => write!(f, "{}", value), Self::Float { value, .. } => write!(f, "{}", value),
@@ -410,6 +429,7 @@ impl fmt::Display for Constant {
impl HasDtype for Constant { impl HasDtype for Constant {
fn dtype(&self) -> Dtype { fn dtype(&self) -> Dtype {
match self { match self {
Self::Undef { dtype } => dtype.clone(),
Self::Unit => Dtype::unit(), Self::Unit => Dtype::unit(),
Self::Int { Self::Int {
width, is_signed, .. width, is_signed, ..
@@ -450,6 +470,14 @@ impl Operand {
None None
} }
} }
pub fn get_register_mut(&mut self) -> Option<(&mut RegisterId, &mut Dtype)> {
if let Self::Register { rid, dtype } = self {
Some((rid, dtype))
} else {
None
}
}
} }
impl fmt::Display for Operand { impl fmt::Display for Operand {
@@ -471,8 +499,10 @@ impl HasDtype for Operand {
} }
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
#[allow(clippy::large_enum_variant)]
pub enum Instruction { pub enum Instruction {
// TODO: the variants of Instruction will be added in the future // TODO: the variants of Instruction will be added in the future
Nop,
BinOp { BinOp {
op: ast::BinaryOperator, op: ast::BinaryOperator,
lhs: Operand, lhs: Operand,
@@ -505,6 +535,7 @@ pub enum Instruction {
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::BinOp { dtype, .. } => dtype.clone(), Self::BinOp { dtype, .. } => dtype.clone(),
Self::UnaryOp { dtype, .. } => dtype.clone(), Self::UnaryOp { dtype, .. } => dtype.clone(),
Self::Store { .. } => Dtype::unit(), Self::Store { .. } => Dtype::unit(),
@@ -521,6 +552,16 @@ impl HasDtype for Instruction {
} }
} }
impl Instruction {
pub fn is_pure(&self) -> bool {
match self {
Self::Store { .. } => false,
Self::Call { .. } => false,
_ => true,
}
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub struct BlockId(pub usize); pub struct BlockId(pub usize);

View File

@@ -120,6 +120,7 @@ impl WriteLine for (&BlockId, &Block) {
impl WriteString for Instruction { impl WriteString for Instruction {
fn write_string(&self) -> String { fn write_string(&self) -> String {
match self { match self {
Instruction::Nop => "nop".into(),
Instruction::BinOp { op, lhs, rhs, .. } => format!( Instruction::BinOp { op, lhs, rhs, .. } => format!(
"{} {} {}", "{} {} {}",
op.write_operation(), op.write_operation(),

View File

@@ -21,4 +21,4 @@ pub use c::Parse;
pub use asmgen::Asmgen; pub use asmgen::Asmgen;
pub use irgen::Irgen; pub use irgen::Irgen;
pub use opt::{Gvn, Mem2reg, Optimize, Repeat, SimplifyCfg, Translate, O0, O1}; pub use opt::{Deadcode, Gvn, Mem2reg, Optimize, Repeat, SimplifyCfg, O0, O1};

14
src/opt/deadcode.rs Normal file
View File

@@ -0,0 +1,14 @@
use crate::ir::*;
use crate::opt::FunctionPass;
use crate::*;
pub type Deadcode = FunctionPass<DeadcodeInner>;
#[derive(Default)]
pub struct DeadcodeInner {}
impl Optimize<FunctionDefinition> for DeadcodeInner {
fn optimize(&mut self, _code: &mut FunctionDefinition) -> bool {
todo!("homework 6")
}
}

View File

@@ -1,11 +1,13 @@
use crate::ir; use crate::opt::FunctionPass;
use crate::*; use crate::*;
#[derive(Default)] pub type Gvn = FunctionPass<Repeat<GvnInner>>;
pub struct Gvn {}
impl Optimize<ir::TranslationUnit> for Gvn { #[derive(Default)]
fn optimize(&mut self, _code: &mut ir::TranslationUnit) -> bool { pub struct GvnInner {}
todo!()
impl Optimize<ir::FunctionDefinition> for GvnInner {
fn optimize(&mut self, _code: &mut ir::FunctionDefinition) -> bool {
todo!("homework 5")
} }
} }

View File

@@ -1,11 +1,14 @@
use crate::ir; use crate::ir::*;
use crate::opt::FunctionPass;
use crate::*; use crate::*;
#[derive(Default)] pub type Mem2reg = FunctionPass<Mem2regInner>;
pub struct Mem2reg {}
impl Optimize<ir::TranslationUnit> for Mem2reg { #[derive(Default)]
fn optimize(&mut self, _code: &mut ir::TranslationUnit) -> bool { pub struct Mem2regInner {}
todo!()
impl Optimize<FunctionDefinition> for Mem2regInner {
fn optimize(&mut self, _code: &mut FunctionDefinition) -> bool {
todo!("homework 4")
} }
} }

View File

@@ -1,35 +1,42 @@
use crate::*;
mod deadcode;
mod gvn; mod gvn;
mod mem2reg; mod mem2reg;
mod opt_utils;
mod simplify_cfg; mod simplify_cfg;
pub use deadcode::Deadcode;
pub use gvn::Gvn; pub use gvn::Gvn;
pub use mem2reg::Mem2reg; pub use mem2reg::Mem2reg;
pub use opt_utils::{
make_cfg, make_domtree, replace_operand, replace_operands, reverse_cfg, Domtree, Walk,
};
pub use simplify_cfg::SimplifyCfg; pub use simplify_cfg::SimplifyCfg;
use crate::ir; use crate::ir;
pub trait Translate<S> {
type Target;
type Error;
fn translate(&mut self, source: &S) -> Result<Self::Target, Self::Error>;
}
pub trait Optimize<T> { pub trait Optimize<T> {
fn optimize(&mut self, code: &mut T) -> bool; fn optimize(&mut self, code: &mut T) -> bool;
} }
pub type O0 = Null;
pub type O1 = Repeat<(SimplifyCfg, (Mem2reg, (Deadcode, Gvn)))>;
#[derive(Default)]
pub struct Null {}
#[derive(Default)] #[derive(Default)]
pub struct Repeat<O> { pub struct Repeat<O> {
inner: O, inner: O,
} }
#[derive(Default)] #[derive(Default)]
pub struct O0 {} pub struct FunctionPass<T: Optimize<ir::FunctionDefinition>> {
inner: T,
}
pub type O1 = Repeat<(SimplifyCfg, (Mem2reg, Gvn))>; impl Optimize<ir::TranslationUnit> for Null {
impl Optimize<ir::TranslationUnit> for O0 {
fn optimize(&mut self, _code: &mut ir::TranslationUnit) -> bool { fn optimize(&mut self, _code: &mut ir::TranslationUnit) -> bool {
false false
} }
@@ -53,3 +60,23 @@ impl<T, O: Optimize<T>> Optimize<T> for Repeat<O> {
true true
} }
} }
impl<T> Optimize<ir::TranslationUnit> for FunctionPass<T>
where
T: Optimize<ir::FunctionDefinition>,
{
fn optimize(&mut self, code: &mut ir::TranslationUnit) -> bool {
code.decls.iter_mut().any(|(_, decl)| self.optimize(decl))
}
}
impl<T> Optimize<ir::Declaration> for FunctionPass<T>
where
T: Optimize<ir::FunctionDefinition>,
{
fn optimize(&mut self, code: &mut ir::Declaration) -> bool {
let (_fsig, fdef) = some_or!(code.get_function_mut(), return false);
let fdef = some_or!(fdef, return false);
self.inner.optimize(fdef)
}
}

171
src/opt/opt_utils.rs Normal file
View File

@@ -0,0 +1,171 @@
#![allow(dead_code)]
use std::collections::HashMap;
use crate::ir::*;
/// "Replace-all-uses-with".
pub trait Walk {
fn walk<F>(&mut self, f: F) -> bool
where
F: FnMut(&mut Operand) -> bool;
}
impl Walk for FunctionDefinition {
fn walk<F>(&mut self, mut f: F) -> bool
where
F: FnMut(&mut Operand) -> bool,
{
self.blocks.iter_mut().any(|(_, block)| block.walk(&mut f))
}
}
impl Walk for Block {
fn walk<F>(&mut self, mut f: F) -> bool
where
F: FnMut(&mut Operand) -> bool,
{
self.instructions.iter_mut().any(|i| i.walk(&mut f)) || self.exit.walk(&mut f)
}
}
impl Walk for Instruction {
fn walk<F>(&mut self, mut f: F) -> bool
where
F: FnMut(&mut Operand) -> bool,
{
match self {
Self::Nop => false,
Self::BinOp { lhs, rhs, .. } => lhs.walk(&mut f) || rhs.walk(&mut f),
Self::UnaryOp { operand, .. } => operand.walk(&mut f),
Self::Store { ptr, value } => ptr.walk(&mut f) || value.walk(&mut f),
Self::Load { ptr } => ptr.walk(&mut f),
Self::Call { callee, args, .. } => {
callee.walk(&mut f) || args.iter_mut().any(|a| a.walk(&mut f))
}
Self::TypeCast { value, .. } => value.walk(&mut f),
}
}
}
impl Walk for BlockExit {
fn walk<F>(&mut self, mut f: F) -> bool
where
F: FnMut(&mut Operand) -> bool,
{
match self {
Self::Jump { arg } => arg.walk(&mut f),
Self::ConditionalJump {
condition,
arg_then,
arg_else,
} => condition.walk(&mut f) || arg_then.walk(&mut f) || arg_else.walk(&mut f),
Self::Switch {
value,
default,
cases,
} => {
value.walk(&mut f)
|| default.walk(&mut f)
|| cases.iter_mut().any(|(_, a)| a.walk(&mut f))
}
Self::Return { value } => value.walk(&mut f),
Self::Unreachable => false,
}
}
}
impl Walk for JumpArg {
fn walk<F>(&mut self, mut f: F) -> bool
where
F: FnMut(&mut Operand) -> bool,
{
self.args.iter_mut().any(|a| a.walk(&mut f))
}
}
impl Walk for Operand {
fn walk<F>(&mut self, mut f: F) -> bool
where
F: FnMut(&mut Operand) -> bool,
{
f(self)
}
}
pub fn replace_operand(operand: &mut Operand, from: &Operand, to: &Operand) -> bool {
if operand == from {
*operand = to.clone();
true
} else {
false
}
}
pub fn replace_operands(operand: &mut Operand, dict: &HashMap<RegisterId, Operand>) -> bool {
if let Some((rid, dtype)) = operand.get_register_mut() {
if let Some(val) = dict.get(rid) {
assert_eq!(*dtype, val.dtype());
*operand = val.clone();
return true;
}
}
false
}
pub fn make_cfg(fdef: &FunctionDefinition) -> HashMap<BlockId, Vec<JumpArg>> {
let mut result = HashMap::new();
for (bid, block) in &fdef.blocks {
let mut args = Vec::new();
match &block.exit {
BlockExit::Jump { arg } => args.push(arg.clone()),
BlockExit::ConditionalJump {
arg_then, arg_else, ..
} => {
args.push(arg_then.clone());
args.push(arg_else.clone());
}
BlockExit::Switch { default, cases, .. } => {
args.push(default.clone());
for (_, arg) in cases {
args.push(arg.clone());
}
}
_ => {}
}
result.insert(*bid, args);
}
result
}
pub fn reverse_cfg(
cfg: &HashMap<BlockId, Vec<JumpArg>>,
) -> HashMap<BlockId, Vec<(BlockId, JumpArg)>> {
let mut result = HashMap::new();
for (bid, jumps) in cfg {
for jump in jumps {
result
.entry(jump.bid)
.or_insert_with(Vec::new)
.push((*bid, jump.clone()));
}
}
result
}
pub struct Domtree {}
impl Domtree {
pub fn walk<F>(&self, _f: F)
where
F: FnMut(BlockId, BlockId),
{
todo!()
}
}
pub fn make_domtree(_cfg: &HashMap<BlockId, Vec<JumpArg>>) -> Domtree {
todo!()
}

View File

@@ -1,21 +1,9 @@
use crate::ir::*; use crate::ir::*;
use crate::opt::FunctionPass;
use crate::*; use crate::*;
pub type SimplifyCfg = Repeat<(SimplifyCfgConstProp, (SimplifyCfgReach, SimplifyCfgMerge))>; pub type SimplifyCfg =
FunctionPass<Repeat<(SimplifyCfgConstProp, (SimplifyCfgReach, SimplifyCfgMerge))>>;
impl Optimize<TranslationUnit> for SimplifyCfg {
fn optimize(&mut self, code: &mut TranslationUnit) -> bool {
code.decls.iter_mut().any(|(_, decl)| self.optimize(decl))
}
}
impl Optimize<Declaration> for SimplifyCfg {
fn optimize(&mut self, code: &mut Declaration) -> bool {
let (_fsig, fdef) = some_or!(code.get_function_mut(), return false);
let fdef = some_or!(fdef, return false);
self.optimize(fdef)
}
}
/// Simplifies block exits by propagating constants. /// Simplifies block exits by propagating constants.
#[derive(Default)] #[derive(Default)]

View File

@@ -44,3 +44,10 @@ macro_rules! some_or_exit {
} }
}}; }};
} }
pub trait Translate<S> {
type Target;
type Error;
fn translate(&mut self, source: &S) -> Result<Self::Target, Self::Error>;
}

View File

@@ -26,6 +26,7 @@ REPLACE_DICT = {
"_Float128": "long double", "_Float128": "long double",
"union": "struct", "union": "struct",
r"enum[\w\s]*\{[^\}]*\};": "", r"enum[\w\s]*\{[^\}]*\};": "",
r"typedef enum[\w\s]*\{[^;]*;[\s_A-Z]*;": "",
"const char \*const sys_errlist\[\];": "", "const char \*const sys_errlist\[\];": "",
r"[^\n]*printf[^;]*;": "", r"[^\n]*printf[^;]*;": "",
r"[^\n]*scanf[^;]*;": "", r"[^\n]*scanf[^;]*;": "",
@@ -191,14 +192,6 @@ def creduce(tests_dir, fuzz_arg):
def fuzz(tests_dir, fuzz_arg, num_iter): def fuzz(tests_dir, fuzz_arg, num_iter):
csmith_bin, csmith_inc = install_csmith(tests_dir) csmith_bin, csmith_inc = install_csmith(tests_dir)
try: try:
print("Building KECC..")
try:
proc = subprocess.Popen(["cargo", "build", "--release"], cwd=tests_dir)
proc.communicate()
except subprocess.TimeoutExpired as e:
proc.kill()
raise e
if num_iter is None: if num_iter is None:
print("Fuzzing with infinitely many test cases. Please press [ctrl+C] to break.") print("Fuzzing with infinitely many test cases. Please press [ctrl+C] to break.")
iterator = itertools.count(0) iterator = itertools.count(0)
@@ -247,6 +240,15 @@ if __name__ == "__main__":
raise Exception("Specify fuzzing argument") raise Exception("Specify fuzzing argument")
tests_dir = os.path.abspath(os.path.dirname(__file__)) tests_dir = os.path.abspath(os.path.dirname(__file__))
print("Building KECC..")
try:
proc = subprocess.Popen(["cargo", "build", "--release"], cwd=tests_dir)
proc.communicate()
except subprocess.TimeoutExpired as e:
proc.kill()
raise e
if args.reduce: if args.reduce:
creduce(tests_dir, fuzz_arg) creduce(tests_dir, fuzz_arg)
else: else:

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env bash #!/usr/bin/env bash
cargo run --manifest-path $PROJECT_DIR/Cargo.toml --release -- -p test_reduced.c >/dev/null 2>&1 &&\ cargo run --manifest-path $PROJECT_DIR/Cargo.toml --release -- --parse test_reduced.c >/dev/null 2>&1 &&\
! cargo run --manifest-path $PROJECT_DIR/Cargo.toml --release --bin fuzz -- $FUZZ_ARG test_reduced.c ! cargo run --manifest-path $PROJECT_DIR/Cargo.toml --release --bin fuzz -- $FUZZ_ARG test_reduced.c