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

View File

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

View File

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

View File

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

View File

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

View File

@@ -21,4 +21,4 @@ pub use c::Parse;
pub use asmgen::Asmgen;
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::*;
#[derive(Default)]
pub struct Gvn {}
pub type Gvn = FunctionPass<Repeat<GvnInner>>;
impl Optimize<ir::TranslationUnit> for Gvn {
fn optimize(&mut self, _code: &mut ir::TranslationUnit) -> bool {
todo!()
#[derive(Default)]
pub struct GvnInner {}
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::*;
#[derive(Default)]
pub struct Mem2reg {}
pub type Mem2reg = FunctionPass<Mem2regInner>;
impl Optimize<ir::TranslationUnit> for Mem2reg {
fn optimize(&mut self, _code: &mut ir::TranslationUnit) -> bool {
todo!()
#[derive(Default)]
pub struct Mem2regInner {}
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 mem2reg;
mod opt_utils;
mod simplify_cfg;
pub use deadcode::Deadcode;
pub use gvn::Gvn;
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;
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> {
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)]
pub struct Repeat<O> {
inner: O,
}
#[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 O0 {
impl Optimize<ir::TranslationUnit> for Null {
fn optimize(&mut self, _code: &mut ir::TranslationUnit) -> bool {
false
}
@@ -53,3 +60,23 @@ impl<T, O: Optimize<T>> Optimize<T> for Repeat<O> {
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::opt::FunctionPass;
use crate::*;
pub type SimplifyCfg = 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)
}
}
pub type SimplifyCfg =
FunctionPass<Repeat<(SimplifyCfgConstProp, (SimplifyCfgReach, SimplifyCfgMerge))>>;
/// Simplifies block exits by propagating constants.
#[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>;
}