Update skeleton

This commit is contained in:
Jeehoon Kang
2020-04-23 00:50:00 +09:00
parent 7569d5ad83
commit 9917ffcbd5
22 changed files with 992 additions and 116 deletions

View File

@@ -946,7 +946,11 @@ impl fmt::Display for Dtype {
write!(f, "{}f{}", if *is_const { "const " } else { "" }, width)
}
Self::Pointer { inner, is_const } => {
write!(f, "{}*{}", inner, if *is_const { "const" } else { "" })
if *is_const {
write!(f, "*const {}", inner)
} else {
write!(f, "*{}", inner)
}
}
Self::Array { inner, size, .. } => write!(f, "[{} x {}]", size, inner,),
Self::Struct {

View File

@@ -1,5 +1,6 @@
mod dtype;
mod interp;
mod parse;
mod write_ir;
use core::convert::TryFrom;
@@ -13,13 +14,14 @@ use std::hash::{Hash, Hasher};
pub use dtype::{Dtype, DtypeError, HasDtype};
pub use interp::{interp, Value};
pub use parse::Parse;
#[derive(Debug, PartialEq)]
#[derive(Debug, Clone, PartialEq)]
pub struct TranslationUnit {
pub decls: HashMap<String, Declaration>,
}
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, Clone, PartialEq)]
pub enum Declaration {
Variable {
dtype: Dtype,
@@ -312,7 +314,16 @@ impl JumpArg {
impl fmt::Display for JumpArg {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}({:?})", self.bid, self.args)
write!(
f,
"{}({})",
self.bid,
self.args
.iter()
.map(|a| a.to_string())
.collect::<Vec<_>>()
.join(", ")
)
}
}
@@ -728,7 +739,7 @@ impl fmt::Display for Constant {
}
),
Self::Float { value, .. } => write!(f, "{}", value),
Self::GlobalVariable { name, .. } => write!(f, "%{}", name),
Self::GlobalVariable { name, .. } => write!(f, "@{}", name),
}
}
}

327
src/ir/parse.rs Normal file
View File

@@ -0,0 +1,327 @@
use std::fs;
use std::path::Path;
use lang_c::ast::{BinaryOperator, UnaryOperator};
use crate::ir::*;
use crate::Translate;
peg::parser! {
grammar ir_parse() for str {
rule whitespace() = quiet!{[' ' | '\n' | '\t']}
rule _() = whitespace()*
rule __() = whitespace()+
pub rule translation_unit() -> TranslationUnit
= _ ds:(named_decl() ** __) _ {
let mut decls = HashMap::new();
for decl in ds {
let result = decls.insert(decl.name.unwrap(), decl.inner);
assert!(result.is_none());
}
TranslationUnit { decls }
}
rule named_decl() -> Named<Declaration> =
"var" __ dtype:dtype() __ var:global_variable() _ "=" _ initializer:initializer() {
Named::new(Some(var), Declaration::Variable {
dtype: dtype,
initializer,
})
}
/
"fun" __ dtype:dtype() __ var:global_variable() _ "{" _ fun_body:fun_body() _ "}" {
Named::new(Some(var), Declaration::Function {
signature: FunctionSignature::new(Dtype::function(Dtype::int(32), Vec::new())),
definition: Some(fun_body),
})
}
/
"fun" __ dtype:dtype() __ var:global_variable() {
Named::new(Some(var), Declaration::Function {
signature: FunctionSignature::new(Dtype::function(Dtype::int(32), Vec::new())),
definition: None,
})
}
rule dtype() -> Dtype =
"unit" { Dtype::unit() }
/
"u" n:number() { Dtype::int(n).set_signed(false) }
/
"i" n:number() { Dtype::int(n) }
/
"*" _ inner:dtype() { Dtype::pointer(inner) }
/ expected!("dtype")
rule id() -> String
= n:$(['_' | 'a'..='z' | 'A'..='Z']['_' | 'a'..='z' | 'A'..='Z' | '0'..='9']*) {
String::from(n)
}
/ expected!("id")
rule global_variable() -> String
= "@" id:id() {
id
}
/ expected!("global-variable")
rule arg() -> usize // TODO
= "<arg>" {
todo!()
}
rule fun_body() -> FunctionDefinition
= "init:" __ "bid:" _ bid_init:bid() _ "allocations:" _ allocations:(allocation() ** __) _ blocks:(block() ** __) {
FunctionDefinition {
allocations: allocations.into_iter().map(|a| a.1).collect(),
blocks: blocks.into_iter().collect(),
bid_init,
}
}
rule allocation() -> (usize, Named<Dtype>)
= "%l" number:number() ":" dtype:dtype() ":" name:id() {
(number, Named::new(Some(name), dtype))
}
rule block() -> (BlockId, Block)
= "block" __ bid:bid() _ ":" _ phinodes:(phinode() ** __) _ instructions:(instruction() ** __) _ exit:exit() {
if !phinodes.iter().enumerate().all(|(i1, (bid2, i2, _))| bid == *bid2 && i1 == *i2) {
panic!("Phinode id mismatches");
}
if !instructions.iter().enumerate().all(|(i1, (bid2, i2, _))| bid == *bid2 && i1 == *i2) {
panic!("Instruction id mismatches");
}
(bid,
Block {
phinodes: phinodes.into_iter().map(|(_, _, phi)| phi).collect(),
instructions: instructions.into_iter().map(|(_, _, instr)| instr).collect(),
exit,
})
}
rule number() -> usize
= n:$(['0'..='9']+) {
n.parse().unwrap()
}
/ expected!("number")
rule bid() -> BlockId
= "b" n:number() {
BlockId(n)
}
/ expected!("bid")
rule phinode() -> (BlockId, usize, Named<Dtype>)
= "%" bid:bid() ":p" number:number() ":" dtype:dtype() name:(":" name:id() { name })? {
(bid, number, Named::new(name, dtype))
}
/ expected!("phinode")
rule instruction() -> (BlockId, usize, Named<Instruction>)
= "%" bid:bid() ":i" number:number() ":" dtype:dtype() name:(":" name:id() { name })? _ "=" _ instruction:instruction_inner() {
(bid, number, Named::new(name, instruction))
}
/ expected!("instruction")
rule instruction_inner() -> Instruction =
"call" __ callee:operand() _ "(" _ args:(operand() ** (_ "," _)) _ ")" {
Instruction::Call {
callee,
args,
return_type: Dtype::unit(), // TODO
}
}
/
"load" __ ptr:operand() {
Instruction::Load { ptr }
}
/
"store" __ value:operand() __ ptr:operand() {
Instruction::Store { ptr, value }
}
/
"typecast" __ value:operand() __ "to" __ target_dtype:dtype() {
Instruction::TypeCast { value, target_dtype }
}
/
"minus" __ operand:operand() {
let dtype = operand.dtype();
Instruction::UnaryOp {
op: UnaryOperator::Minus,
operand,
dtype,
}
}
/
op:arith_op() __ lhs:operand() __ rhs:operand() {
let dtype = lhs.dtype();
assert_eq!(&dtype, &rhs.dtype());
Instruction::BinOp {
op,
lhs,
rhs,
dtype,
}
}
/
"cmp" __ op:comparison_op() __ lhs:operand() __ rhs:operand() {
assert_eq!(lhs.dtype(), rhs.dtype());
Instruction::BinOp {
op,
lhs,
rhs,
dtype: Dtype::BOOL,
}
}
/
"<instruction>" {
todo!()
}
/ expected!("instruction_inner")
rule arith_op() -> BinaryOperator =
"add" { BinaryOperator::Plus }
/
"sub" { BinaryOperator::Minus }
rule comparison_op() -> BinaryOperator =
"eq" { BinaryOperator::Equals }
/
"ne" { BinaryOperator::NotEquals }
/
"lt" { BinaryOperator::Less }
/
"le" { BinaryOperator::LessOrEqual }
/
"gt" { BinaryOperator::Greater }
/
"ge" { BinaryOperator::GreaterOrEqual }
rule exit() -> BlockExit =
"j" __ arg:jump_arg() {
BlockExit::Jump { arg }
}
/
"br" __ condition:operand() __ arg_then:jump_arg() __ arg_else:jump_arg() {
BlockExit::ConditionalJump { condition, arg_then, arg_else }
}
/
"switch" __ value:operand() __ "default" __ default:jump_arg() _ "[" _ cases:(switch_case() ** __) _ "]" {
BlockExit::Switch { value, default, cases }
}
/
"ret" __ value:operand() {
BlockExit::Return { value }
}
/
"unreachable" {
BlockExit::Unreachable
}
rule constant() -> Constant =
n:number() {
Constant::int(n as _, Dtype::int(128)) // TODO: the right dtype
}
/
"undef" {
Constant::undef(Dtype::unit()) // TODO
}
/
"unit" {
Constant::undef(Dtype::unit()) // TODO
}
/
"<constant>" {
todo!()
}
rule register_id() -> RegisterId =
"%l" id:number() {
RegisterId::local(id)
}
/
"%" bid:bid() ":p" id:number() {
RegisterId::arg(bid, id)
}
/
"%" bid:bid() ":i" id:number() {
RegisterId::temp(bid, id)
}
rule operand() -> Operand =
name:global_variable() {
Operand::Constant(Constant::GlobalVariable {
name,
dtype: Dtype::unit(), // TODO
})
}
/
constant:constant() ":" dtype:dtype() {
let constant = match (&constant, &dtype) {
(Constant::Int { value, .. }, Dtype::Int { width, is_signed, .. }) => {
Constant::Int {
value: *value,
width: *width,
is_signed: *is_signed,
}
}
(Constant::Undef { .. }, _) => {
Constant::undef(dtype.clone())
}
_ => constant.clone(),
};
Operand::Constant(constant)
}
/
rid:register_id() ":" dtype:dtype() {
Operand::Register { rid, dtype }
}
rule jump_arg() -> JumpArg
= bid:bid() _ "(" _ args:(operand() ** (_ "," _)) _ ")" {
JumpArg { bid, args }
}
rule switch_case() -> (Constant, JumpArg)
= operand:operand() __ jump_arg:jump_arg() {
let constant = operand.get_constant().unwrap().clone();
(constant, jump_arg)
}
rule initializer() -> Option<lang_c::ast::Initializer> =
"default" {
None
}
/
"<initializer>" {
todo!()
}
}
}
#[derive(Debug)]
pub enum Error {
IoError(std::io::Error),
ParseError(peg::error::ParseError<peg::str::LineCol>),
}
#[derive(Default)]
pub struct Parse {}
impl<P: AsRef<Path>> Translate<P> for Parse {
type Target = TranslationUnit;
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)?;
Ok(ir)
}
}

View File

@@ -9,21 +9,14 @@ use lang_c::ast;
impl WriteLine for TranslationUnit {
fn write_line(&self, indent: usize, write: &mut dyn Write) -> Result<()> {
write_indent(indent, write)?;
writeln!(write, "<variable list>")?;
writeln!(write)?;
for (name, decl) in &self.decls {
let _ = some_or!(decl.get_variable(), continue);
(name, decl).write_line(indent, write)?;
}
writeln!(write)?;
writeln!(write)?;
write_indent(indent, write)?;
writeln!(write, "<function list>")?;
writeln!(write)?;
for (name, decl) in &self.decls {
let _ = some_or!(decl.get_function(), continue);
writeln!(write)?;
(name, decl).write_line(indent, write)?;
}
@@ -40,73 +33,56 @@ impl WriteLine for (&String, &Declaration) {
Declaration::Variable { dtype, initializer } => {
writeln!(
write,
"{} = {} {}",
"var {} @{} = {}",
dtype,
name,
if let Some(init) = initializer {
init.write_string()
} else {
"default".to_string()
},
dtype
}
)?;
}
Declaration::Function {
signature,
definition,
} => {
let declaration = format!(
"{} @{}({})",
signature.ret,
name,
signature
.params
.iter()
.map(|d| d.to_string())
.collect::<Vec<_>>()
.join(", "),
);
if let Some(definition) = definition.as_ref() {
// print function definition
writeln!(write, "fun {} @{} {{", signature.ret, name)?;
// print meta data for function
writeln!(
write,
"init:\n bid: {}\n allocations: \n{}",
definition.bid_init,
definition
.allocations
.iter()
.enumerate()
.map(|(i, a)| format!(
" %l{}:{}{}",
i,
a.deref(),
if let Some(name) = a.name() {
format!(":{}", name)
} else {
"".into()
}
))
.collect::<Vec<_>>()
.join("\n")
)?;
match definition.as_ref() {
Some(defintion) => {
// print function definition
writeln!(write, "define {} {{", declaration)?;
// print meta data for function
writeln!(
write,
"init:\n bid: {}\n allocations: \n{}\n",
defintion.bid_init,
defintion
.allocations
.iter()
.enumerate()
.map(|(i, a)| format!(
" %l{}:{}{}",
i,
a.deref(),
if let Some(name) = a.name() {
format!(":{}", name)
} else {
"".into()
}
))
.collect::<Vec<_>>()
.join("\n")
)?;
for (id, block) in &defintion.blocks {
writeln!(write, "block {}", id)?;
(id, block).write_line(indent + 1, write)?;
writeln!(write)?;
}
writeln!(write, "}}")?;
writeln!(write)?;
}
None => {
// print declaration line only
writeln!(write, "declare {}", declaration)?;
writeln!(write)?;
for (id, block) in &definition.blocks {
writeln!(write, "\nblock {}:", id)?;
(id, block).write_line(indent + 1, write)?;
}
writeln!(write, "}}")?;
} else {
// print declaration line only
writeln!(write, "fun {} @{}", signature.ret, name)?;
writeln!(write)?;
}
}
}
@@ -260,7 +236,7 @@ impl WriteString for BlockExit {
default,
cases,
} => format!(
"switch {}, default: {} [\n{}\n ]",
"switch {} default: {} [\n{}\n ]",
value.write_string(),
default,
cases

View File

@@ -1,5 +1,9 @@
#![deny(warnings)]
// Neccessary 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)]
mod tests;
mod utils;
@@ -18,7 +22,11 @@ pub use utils::*;
pub use write_base::write;
pub use c::Parse;
pub use ir::Parse as IrParse;
pub use asmgen::Asmgen;
pub use irgen::Irgen;
pub use opt::{Deadcode, Gvn, Mem2reg, Optimize, Repeat, SimplifyCfg, O0, O1};
pub use opt::{
Deadcode, FunctionPass, Gvn, Mem2reg, Optimize, Repeat, SimplifyCfg, SimplifyCfgConstProp,
SimplifyCfgEmpty, SimplifyCfgMerge, SimplifyCfgReach, O0, O1,
};

View File

@@ -9,7 +9,9 @@ mod simplify_cfg;
pub use deadcode::Deadcode;
pub use gvn::Gvn;
pub use mem2reg::Mem2reg;
pub use simplify_cfg::SimplifyCfg;
pub use simplify_cfg::{
SimplifyCfg, SimplifyCfgConstProp, SimplifyCfgEmpty, SimplifyCfgMerge, SimplifyCfgReach,
};
use crate::ir;
@@ -63,7 +65,10 @@ where
T: Optimize<ir::FunctionDefinition>,
{
fn optimize(&mut self, code: &mut ir::TranslationUnit) -> bool {
code.decls.iter_mut().any(|(_, decl)| self.optimize(decl))
code.decls
.iter_mut()
.map(|(_, decl)| self.optimize(decl))
.fold(false, |l, r| l || r)
}
}

View File

@@ -3,7 +3,7 @@ use crate::opt::FunctionPass;
use crate::*;
pub type SimplifyCfg =
FunctionPass<Repeat<(SimplifyCfgConstProp, (SimplifyCfgReach, SimplifyCfgMerge))>>;
FunctionPass<Repeat<(SimplifyCfgConstProp, (SimplifyCfgReach, (SimplifyCfgMerge, SimplifyCfgEmpty)))>>;
/// Simplifies block exits by propagating constants.
#[derive(Default)]
@@ -17,6 +17,10 @@ pub struct SimplifyCfgReach {}
#[derive(Default)]
pub struct SimplifyCfgMerge {}
/// Removes empty blocks
#[derive(Default)]
pub struct SimplifyCfgEmpty {}
impl Optimize<FunctionDefinition> for SimplifyCfgConstProp {
fn optimize(&mut self, _code: &mut FunctionDefinition) -> bool {
todo!("homework 3")
@@ -34,3 +38,9 @@ impl Optimize<FunctionDefinition> for SimplifyCfgMerge {
todo!("homework 3")
}
}
impl Optimize<FunctionDefinition> for SimplifyCfgEmpty {
fn optimize(&mut self, _code: &mut FunctionDefinition) -> bool {
todo!("homework 3")
}
}

View File

@@ -1,5 +1,6 @@
use lang_c::ast::*;
use std::fs::{self, File};
use std::io::{stderr, Write};
use std::path::Path;
use std::process::{Command, Stdio};
use tempfile::tempdir;
@@ -32,7 +33,7 @@ pub fn test_irgen(unit: &TranslationUnit, path: &Path) {
.expect("failed to parse the given program");
let file_path = path.display().to_string();
let bin_path = path.with_extension("exe").as_path().display().to_string();
let bin_path = path.with_extension("irgen").as_path().display().to_string();
// Compile c file: If fails, test is vacuously success
if !Command::new("gcc")
@@ -90,3 +91,108 @@ pub fn test_irgen(unit: &TranslationUnit, path: &Path) {
println!("gcc: {}, kecc: {}", status as i8, value as i8);
assert_eq!(status as i8, value as i8);
}
pub fn test_irparse(unit: &TranslationUnit, path: &Path) {
// Check if the file has .c extension
assert_eq!(path.extension(), Some(std::ffi::OsStr::new("c")));
// Test parse
c::Parse::default()
.translate(&path)
.expect("failed to parse the given program");
let file_path = path.display().to_string();
let bin_path = path
.with_extension("irparse")
.as_path()
.display()
.to_string();
// Compile c file: If fails, test is vacuously success
if !Command::new("gcc")
.args(&["-O1", &file_path, "-o", &bin_path])
.stderr(Stdio::null())
.status()
.unwrap()
.success()
{
return;
}
// Execute compiled executable
let mut child = Command::new(fs::canonicalize(bin_path.clone()).unwrap())
.spawn()
.expect("failed to execute the compiled executable");
Command::new("rm")
.arg(bin_path)
.status()
.expect("failed to remove compiled executable");
let status = some_or!(
child
.wait_timeout_ms(500)
.expect("failed to obtain exit status from child process"),
{
println!("timeout occurs");
child.kill().unwrap();
child.wait().unwrap();
return;
}
);
let _status = some_or!(status.code(), return);
let ir = match Irgen::default().translate(unit) {
Ok(ir) => ir,
Err(irgen_error) => panic!("{}", irgen_error),
};
let temp_dir = tempdir().expect("temp dir creation failed");
let temp_file_path = temp_dir.path().join("temp.c");
let mut temp_file = File::create(&temp_file_path).unwrap();
crate::write(&ir, &mut temp_file).unwrap();
let new_ir = ir::Parse::default()
.translate(&temp_file_path.as_path())
.expect("parse failed while parsing the output from implemented printer");
drop(temp_file);
assert_eq!(ir, new_ir);
temp_dir.close().expect("temp dir deletion failed");
}
pub fn test_opt<P1: AsRef<Path>, P2: AsRef<Path>, O: Optimize<ir::TranslationUnit>>(
from: &P1,
to: &P2,
opt: &mut O,
) {
let from = ir::Parse::default()
.translate(from)
.expect("parse failed while parsing the output from implemented printer");
let mut ir = from.clone();
let to = ir::Parse::default()
.translate(to)
.expect("parse failed while parsing the output from implemented printer");
opt.optimize(&mut ir);
if ir != to {
stderr()
.lock()
.write_fmt(format_args!(
"[test_opt] actual outcome mismatches with the expected outcome.\n\n[before opt]"
))
.unwrap();
crate::write(&from, &mut stderr()).unwrap();
stderr()
.lock()
.write_fmt(format_args!("\n[after opt]"))
.unwrap();
crate::write(&ir, &mut stderr()).unwrap();
stderr()
.lock()
.write_fmt(format_args!("\n[after opt (expected)]"))
.unwrap();
crate::write(&to, &mut stderr()).unwrap();
panic!("[test_opt]");
}
}