Update homework 1 and 2

This commit is contained in:
Jeehoon Kang
2020-03-26 03:38:20 +09:00
parent 241a66fcc1
commit 8938a7ad8f
40 changed files with 5171 additions and 1504 deletions

612
src/ir/dtype.rs Normal file
View File

@@ -0,0 +1,612 @@
use core::convert::TryFrom;
use core::fmt;
use core::ops::Deref;
use itertools::izip;
use lang_c::ast;
use lang_c::span::Node;
use std::hash::Hash;
use failure::Fail;
#[derive(Debug, PartialEq, Fail)]
pub enum DtypeError {
/// For uncommon error
#[fail(display = "{}", message)]
Misc { message: String },
}
pub trait HasDtype {
fn dtype(&self) -> Dtype;
}
#[derive(Default)]
struct BaseDtype {
scalar: Option<ast::TypeSpecifier>,
signed_option: Option<ast::TypeSpecifier>,
is_const: bool,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub enum Dtype {
Unit {
is_const: bool,
},
Int {
width: usize,
is_signed: bool,
is_const: bool,
},
Float {
width: usize,
is_const: bool,
},
Pointer {
inner: Box<Dtype>,
is_const: bool,
},
Function {
ret: Box<Dtype>,
params: Vec<Dtype>,
},
}
impl 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.
///
/// # Arguments
///
/// * `self` - Part that has been converted to 'BaseDtype' on the declaration
/// * `type_qualifier` - type qualifiers requiring apply to 'self' immediately
///
#[inline]
fn apply_type_specifier(
&mut self,
type_specifier: &ast::TypeSpecifier,
) -> Result<(), DtypeError> {
match type_specifier {
ast::TypeSpecifier::Unsigned | ast::TypeSpecifier::Signed => {
if self.signed_option.is_some() {
return Err(DtypeError::Misc {
message: "duplicate signed option".to_string(),
});
}
self.signed_option = Some(type_specifier.clone());
}
ast::TypeSpecifier::Void
| ast::TypeSpecifier::Char
| ast::TypeSpecifier::Int
| ast::TypeSpecifier::Float => {
if self.scalar.is_some() {
return Err(DtypeError::Misc {
message: "two or more scalar types in declaration specifiers".to_string(),
});
}
self.scalar = Some(type_specifier.clone());
}
_ => todo!("support more like `double` in the future"),
}
Ok(())
}
/// 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.
///
/// # Arguments
///
/// * `self` - Part that has been converted to 'BaseDtype' on the declaration
/// * `type_qualifier` - type qualifiers requiring apply to 'self' immediately
///
#[inline]
fn apply_type_qualifier(
&mut self,
type_qualifier: &ast::TypeQualifier,
) -> Result<(), DtypeError> {
match type_qualifier {
ast::TypeQualifier::Const => {
// duplicate `const` is allowed
self.is_const = true;
}
_ => panic!("type qualifier is unsupported except `const`"),
}
Ok(())
}
pub fn apply_typename_specifier(
&mut self,
typename_specifier: &ast::SpecifierQualifier,
) -> Result<(), DtypeError> {
match typename_specifier {
ast::SpecifierQualifier::TypeSpecifier(type_specifier) => {
self.apply_type_specifier(&type_specifier.node)?
}
ast::SpecifierQualifier::TypeQualifier(type_qualifier) => {
self.apply_type_qualifier(&type_qualifier.node)?
}
}
Ok(())
}
pub fn apply_declaration_specifier(
&mut self,
declaration_specifier: &ast::DeclarationSpecifier,
) -> Result<(), DtypeError> {
match declaration_specifier {
// TODO: `dtype` must be defined taking into account all specifier information.
ast::DeclarationSpecifier::StorageClass(_storage_class_spec) => {
todo!("analyze storage class specifier keyword to create correct `dtype`")
}
ast::DeclarationSpecifier::TypeSpecifier(type_specifier) => {
self.apply_type_specifier(&type_specifier.node)?
}
ast::DeclarationSpecifier::TypeQualifier(type_qualifier) => {
self.apply_type_qualifier(&type_qualifier.node)?
}
_ => panic!("is_unsupported"),
}
Ok(())
}
/// 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`.
///
/// # Arguments
///
/// * `self` - Part that has been converted to 'BaseDtype' on the pointer declarator
/// * `pointer_qualifier` - Pointer qualifiers requiring apply to 'BaseDtype' immediately
///
pub fn apply_pointer_qualifier(
&mut self,
pointer_qualifier: &ast::PointerQualifier,
) -> Result<(), DtypeError> {
match pointer_qualifier {
ast::PointerQualifier::TypeQualifier(type_qualifier) => {
self.apply_type_qualifier(&type_qualifier.node)?;
}
ast::PointerQualifier::Extension(_) => {
panic!("ast::PointerQualifier::Extension is unsupported")
}
}
Ok(())
}
pub fn apply_typename_specifiers(
&mut self,
typename_specifiers: &[Node<ast::SpecifierQualifier>],
) -> Result<(), DtypeError> {
for ast_spec in typename_specifiers {
self.apply_typename_specifier(&ast_spec.node)?;
}
Ok(())
}
pub fn apply_declaration_specifiers(
&mut self,
declaration_specifiers: &[Node<ast::DeclarationSpecifier>],
) -> Result<(), DtypeError> {
for ast_spec in declaration_specifiers {
self.apply_declaration_specifier(&ast_spec.node)?;
}
Ok(())
}
}
impl TryFrom<BaseDtype> for Dtype {
type Error = DtypeError;
/// Derive a data type containing scalar type from specifiers.
///
/// # Example
///
/// For declaration is `const unsigned int * p`, `specifiers` is `const unsigned int`,
/// and the result is `Dtype::Int{ width: 32, is_signed: false, is_const: ture }`
fn try_from(spec: BaseDtype) -> Result<Self, DtypeError> {
assert!(
!(spec.scalar.is_none() && spec.signed_option.is_none() && !spec.is_const),
"BaseDtype is empty"
);
// Creates `dtype` from scalar.
let mut dtype = if let Some(t) = spec.scalar {
match t {
ast::TypeSpecifier::Void => Self::unit(),
ast::TypeSpecifier::Unsigned | ast::TypeSpecifier::Signed => {
panic!("Signed option to scalar is not supported")
}
ast::TypeSpecifier::Bool => Self::BOOL,
ast::TypeSpecifier::Char => Self::CHAR,
ast::TypeSpecifier::Short => Self::SHORT,
ast::TypeSpecifier::Int => Self::INT,
ast::TypeSpecifier::Long => Self::LONG,
ast::TypeSpecifier::Float => Self::FLOAT,
ast::TypeSpecifier::Double => Self::DOUBLE,
_ => panic!("Unsupported ast::TypeSpecifier"),
}
} else {
Dtype::default()
};
// Applies signedness.
if let Some(signed_option) = spec.signed_option {
let is_signed = match signed_option {
ast::TypeSpecifier::Signed => true,
ast::TypeSpecifier::Unsigned => false,
_ => panic!(
"`signed_option` must be `TypeSpecifier::Signed` or `TypeSpecifier::Unsigned`"
),
};
dtype = dtype.set_signed(is_signed);
}
// Applies constness.
assert!(!dtype.is_const());
dtype = dtype.set_const(spec.is_const);
Ok(dtype)
}
}
impl TryFrom<&ast::TypeName> for Dtype {
type Error = DtypeError;
/// Derive a data type from typename.
fn try_from(type_name: &ast::TypeName) -> Result<Self, Self::Error> {
let mut spec = BaseDtype::default();
BaseDtype::apply_typename_specifiers(&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)?;
}
Ok(dtype)
}
}
impl TryFrom<&ast::ParameterDeclaration> for Dtype {
type Error = DtypeError;
/// 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)?;
}
Ok(dtype)
}
}
impl Dtype {
pub const BOOL: Self = Self::int(1);
pub const CHAR: Self = Self::int(8);
pub const SHORT: Self = Self::int(16);
pub const INT: Self = Self::int(32);
pub const LONG: Self = Self::int(64);
pub const LONGLONG: Self = Self::int(64);
pub const FLOAT: Self = Self::float(32);
pub const DOUBLE: Self = Self::float(64);
const WIDTH_OF_BYTE: usize = 8;
// TODO: consider architecture dependency in the future
const WIDTH_OF_POINTER: usize = 32;
#[inline]
pub const fn unit() -> Self {
Self::Unit { is_const: false }
}
#[inline]
pub const fn int(width: usize) -> Self {
Self::Int {
width,
is_signed: true,
is_const: false,
}
}
#[inline]
pub const fn float(width: usize) -> Self {
Self::Float {
width,
is_const: false,
}
}
#[inline]
pub fn pointer(inner: Dtype) -> Self {
Self::Pointer {
inner: Box::new(inner),
is_const: false,
}
}
#[inline]
pub fn function(ret: Dtype, params: Vec<Dtype>) -> Self {
Self::Function {
ret: Box::new(ret),
params,
}
}
#[inline]
pub fn get_int_width(&self) -> Option<usize> {
if let Self::Int { width, .. } = self {
Some(*width)
} else {
None
}
}
#[inline]
pub fn get_float_width(&self) -> Option<usize> {
if let Self::Float { width, .. } = self {
Some(*width)
} else {
None
}
}
#[inline]
pub fn get_pointer_inner(&self) -> Option<&Dtype> {
if let Self::Pointer { inner, .. } = self {
Some(inner.deref())
} else {
None
}
}
#[inline]
pub fn get_function_inner(&self) -> Option<(&Dtype, &Vec<Dtype>)> {
if let Self::Function { ret, params } = self {
Some((ret.deref(), params))
} else {
None
}
}
#[inline]
pub fn is_scalar(&self) -> bool {
match self {
Self::Unit { .. } => todo!(),
Self::Int { .. } => true,
Self::Float { .. } => true,
_ => false,
}
}
#[inline]
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"),
}
}
#[inline]
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::Function { .. } => {
panic!("there should be no case that check whether `Function` is `const`")
}
}
}
pub fn set_const(self, is_const: bool) -> Self {
match self {
Self::Unit { .. } => Self::Unit { is_const },
Self::Int {
width, is_signed, ..
} => Self::Int {
width,
is_signed,
is_const,
},
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`"),
}
}
/// Return byte size of `Dtype`
pub fn size_of(&self) -> Result<usize, DtypeError> {
// TODO: consider complex type like array, structure in the future
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(),
}),
}
}
/// 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),
Self::Function { .. } => Err(DtypeError::Misc {
message: "`alignof` cannot be used with function types".to_string(),
}),
}
}
pub fn set_signed(self, is_signed: bool) -> Self {
match self {
Self::Int {
width, is_const, ..
} => Self::Int {
width,
is_signed,
is_const,
},
_ => panic!("`signed` and `unsigned` only be applied to `Dtype::Int`"),
}
}
/// Derive a data type from declaration specifiers.
pub fn try_from_ast_declaration_specifiers(
specifiers: &[Node<ast::DeclarationSpecifier>],
) -> Result<Self, DtypeError> {
let mut spec = BaseDtype::default();
BaseDtype::apply_declaration_specifiers(&mut spec, specifiers)?;
Self::try_from(spec)
}
/// Generate `Dtype` based on declarator and `base_dtype` which has scalar type.
///
/// let's say declaration is `const int * const * const a;`.
/// In general `base_dtype` start with `const int` which has scalar type and
/// `declarator` represents `* const * const` with `ast::Declarator`
///
/// # Arguments
///
/// * `declarator` - Parts requiring conversion to 'Dtype' on the declaration
/// * `base_dtype` - Part that has been converted to 'Dtype' on the declaration
///
pub fn with_ast_declarator(mut self, declarator: &ast::Declarator) -> Result<Self, DtypeError> {
for derived_decl in &declarator.derived {
self = match &derived_decl.node {
ast::DerivedDeclarator::Pointer(pointer_qualifiers) => {
let mut specifier = BaseDtype::default();
for qualifier in pointer_qualifiers {
specifier.apply_pointer_qualifier(&qualifier.node)?;
}
Self::pointer(self).set_const(specifier.is_const)
}
ast::DerivedDeclarator::Array(_array_decl) => todo!(),
ast::DerivedDeclarator::Function(func_decl) => {
let params = func_decl
.node
.parameters
.iter()
.map(|p| Self::try_from(&p.node))
.collect::<Result<Vec<_>, _>>()?;
Self::function(self, params)
}
ast::DerivedDeclarator::KRFunction(kr_func_decl) => {
// K&R function is allowed only when it has no parameter
assert!(kr_func_decl.is_empty());
Self::function(self, Vec::new())
}
};
}
let declarator_kind = &declarator.kind;
match &declarator_kind.node {
ast::DeclaratorKind::Abstract => panic!("ast::DeclaratorKind::Abstract is unsupported"),
ast::DeclaratorKind::Identifier(_) => Ok(self),
ast::DeclaratorKind::Declarator(declarator) => {
self.with_ast_declarator(&declarator.node)
}
}
}
/// Check whether type conflict exists between the two `Dtype` objects.
///
/// let's say expression is `const int a = 0; int b = 0; int c = a + b`.
/// Although `const int` of `a` and `int` of `b` looks different, `Plus`(+) operations between
/// these two types are possible without any type-casting. There is no conflict between
/// `const int` and `int`.
///
/// However, only the outermost const is ignored.
/// If check equivalence between `const int *const` and `int *`, result is false. Because
/// the second `const` (means left most `const`) of the `const int *const` is missed in `int *`.
/// By the way, outermost `const` (means right most `const`) is not a consideration here.
pub fn is_compatible(&self, other: &Self) -> bool {
match (self, other) {
(Self::Unit { .. }, Self::Unit { .. })
| (Self::Int { .. }, Self::Int { .. })
| (Self::Float { .. }, Self::Float { .. })
| (Self::Pointer { .. }, Self::Pointer { .. }) => {
self.clone().set_const(false) == other.clone().set_const(false)
}
(
Self::Function { ret, params },
Self::Function {
ret: other_ret,
params: other_params,
},
) => {
ret == other_ret
&& params.len() == other_params.len()
&& izip!(params, other_params).all(|(l, r)| l.is_compatible(r))
}
_ => false,
}
}
}
impl fmt::Display for Dtype {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Unit { is_const } => write!(f, "{}unit", if *is_const { "const " } else { "" }),
Self::Int {
width,
is_signed,
is_const,
} => write!(
f,
"{}{}{}",
if *is_const { "const " } else { "" },
if *is_signed { "i" } else { "u" },
width
),
Self::Float { width, is_const } => {
write!(f, "{}f{}", if *is_const { "const " } else { "" }, width)
}
Self::Pointer { inner, is_const } => {
write!(f, "{}* {}", inner, if *is_const { "const" } else { "" })
}
Self::Function { ret, params } => write!(
f,
"{} ({})",
ret,
params
.iter()
.map(|p| p.to_string())
.collect::<Vec<_>>()
.join(", ")
),
}
}
}
impl Default for Dtype {
fn default() -> Self {
// default dtype is `int`(i32)
Self::INT
}
}

638
src/ir/interp.rs Normal file
View File

@@ -0,0 +1,638 @@
use core::fmt;
use core::mem;
use failure::Fail;
use std::collections::HashMap;
use itertools::izip;
use crate::ir::*;
use crate::*;
// TODO: the variants of Value will be added in the future
#[derive(Debug, PartialEq, Clone)]
pub enum Value {
Undef,
Unit,
Int(i32),
Float(f32),
Bool(bool),
Pointer { bid: Option<usize>, offset: usize },
}
impl Value {
#[inline]
fn pointer(bid: Option<usize>, offset: usize) -> Self {
Self::Pointer { bid, offset }
}
#[inline]
fn get_bool(self) -> Option<bool> {
if let Value::Bool(value) = self {
Some(value)
} else {
None
}
}
#[inline]
fn get_pointer(self) -> Option<(Option<usize>, usize)> {
if let Value::Pointer { bid, offset } = self {
Some((bid, offset))
} else {
None
}
}
#[inline]
fn nullptr() -> Self {
Self::Pointer {
bid: None,
offset: 0,
}
}
#[inline]
fn default_from_dtype(dtype: &Dtype) -> Self {
match dtype {
// TODO: consider `Unit` value in the future
ir::Dtype::Unit { .. } => todo!(),
ir::Dtype::Int { width, .. } => match width {
32 => Self::Int(i32::default()),
_ => todo!("other cases will be covered"),
},
ir::Dtype::Float { .. } => Self::Float(f32::default()),
ir::Dtype::Pointer { .. } => Self::nullptr(),
ir::Dtype::Function { .. } => panic!("function types do not have a default value"),
}
}
}
#[derive(Debug, PartialEq, Fail)]
pub enum InterpreterError {
#[fail(display = "current block is unreachable")]
Unreachable,
#[fail(display = "ir has no main function")]
NoMainFunction,
#[fail(display = "ir has no function definition of {} function", func_name)]
NoFunctionDefinition { func_name: String },
#[fail(display = "{}:{} / {}", func_name, pc, msg)]
Misc {
func_name: String,
pc: Pc,
msg: String,
},
}
#[derive(Debug, PartialEq, Clone, Copy)]
pub struct Pc {
pub bid: BlockId,
pub iid: usize,
}
impl fmt::Display for Pc {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.bid, self.iid)
}
}
impl Pc {
fn new(bid: BlockId) -> Pc {
Pc { bid, iid: 0 }
}
fn increment(&mut self) {
self.iid += 1;
}
}
#[derive(Default, Debug, PartialEq, Clone)]
struct RegisterMap {
inner: HashMap<RegisterId, Value>,
}
impl RegisterMap {
fn read(&self, rid: RegisterId) -> &Value {
self.inner
.get(&rid)
.expect("`rid` must be assigned before it can be used")
}
fn write(&mut self, rid: RegisterId, value: Value) {
let _ = self.inner.insert(rid, value);
}
}
#[derive(Default, Debug, PartialEq, Clone)]
/// Bidirectional map between the name of a global variable and memory box id
struct GlobalMap {
/// Map name of a global variable to 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.
var_to_bid: HashMap<String, usize>,
/// Map 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.
bid_to_var: HashMap<usize, String>,
}
impl GlobalMap {
/// Create a bi-directional mapping between `var` and `bid`.
fn insert(&mut self, var: String, bid: usize) -> Result<(), InterpreterError> {
if self.var_to_bid.insert(var.clone(), bid).is_some() {
panic!("variable name should be unique in IR")
}
if self.bid_to_var.insert(bid, var).is_some() {
panic!("`bid` is connected to only one `var`")
}
Ok(())
}
fn get_bid(&self, var: &str) -> Option<usize> {
self.var_to_bid.get(var).cloned()
}
fn get_var(&self, bid: usize) -> Option<String> {
self.bid_to_var.get(&bid).cloned()
}
}
#[derive(Debug, PartialEq, Clone)]
struct StackFrame<'i> {
pub pc: Pc,
pub registers: RegisterMap,
pub func_name: String,
pub func_def: &'i FunctionDefinition,
}
impl<'i> StackFrame<'i> {
fn new(bid: BlockId, func_name: String, func_def: &'i FunctionDefinition) -> Self {
StackFrame {
pc: Pc::new(bid),
registers: Default::default(),
func_name,
func_def,
}
}
}
mod calculator {
use super::Value;
use lang_c::ast;
pub fn calculate_binary_operator_expression(
op: &ast::BinaryOperator,
lhs: Value,
rhs: Value,
) -> Result<Value, ()> {
match (op, lhs, rhs) {
(_, Value::Undef, _) => Err(()),
(_, _, Value::Undef) => Err(()),
(ast::BinaryOperator::Plus, Value::Int(lhs), Value::Int(rhs)) => {
Ok(Value::Int(lhs + rhs))
}
(ast::BinaryOperator::Minus, Value::Int(lhs), Value::Int(rhs)) => {
Ok(Value::Int(lhs - rhs))
}
(ast::BinaryOperator::Equals, Value::Int(lhs), Value::Int(rhs)) => {
Ok(Value::Bool(lhs == rhs))
}
(ast::BinaryOperator::NotEquals, Value::Int(lhs), Value::Int(rhs)) => {
Ok(Value::Bool(lhs != rhs))
}
(ast::BinaryOperator::Less, Value::Int(lhs), Value::Int(rhs)) => {
Ok(Value::Bool(lhs < rhs))
}
(ast::BinaryOperator::GreaterOrEqual, Value::Int(lhs), Value::Int(rhs)) => {
Ok(Value::Bool(lhs >= rhs))
}
_ => todo!(),
}
}
pub fn calculate_unary_operator_expression(
op: &ast::UnaryOperator,
operand: Value,
) -> Result<Value, ()> {
match (op, operand) {
(_, Value::Undef) => Err(()),
(ast::UnaryOperator::Plus, Value::Int(value)) => Ok(Value::Int(value)),
(ast::UnaryOperator::Minus, Value::Int(value)) => Ok(Value::Int(-value)),
(ast::UnaryOperator::Negate, Value::Bool(value)) => Ok(Value::Bool(!value)),
_ => todo!(),
}
}
pub fn calculate_typecast(value: &Value, dtype: &crate::ir::Dtype) -> Result<Value, ()> {
match (value, dtype) {
(Value::Int(_), crate::ir::Dtype::Int { .. }) => Ok(value.clone()),
(Value::Bool(b), crate::ir::Dtype::Int { .. }) => {
Ok(Value::Int(if *b { 1 } else { 0 }))
}
_ => todo!("calculate_typecast ({:?}) {:?}", dtype, value),
}
}
}
#[derive(Default, Debug, PartialEq)]
struct Memory {
// TODO: memory type should change to Vec<Vec<Byte>>
inner: Vec<Vec<Value>>,
}
impl Memory {
fn alloc(&mut self, dtype: &Dtype) -> Result<usize, InterpreterError> {
let memory_block = match dtype {
Dtype::Unit { .. } => vec![],
Dtype::Int { width, .. } => match width {
32 => vec![Value::Undef],
_ => todo!(),
},
Dtype::Float { .. } => todo!(),
Dtype::Pointer { .. } => vec![Value::Undef],
Dtype::Function { .. } => vec![],
};
self.inner.push(memory_block);
Ok(self.inner.len() - 1)
}
fn load(&self, bid: usize, offset: usize) -> &Value {
&self.inner[bid][offset]
}
fn store(&mut self, bid: usize, offset: usize, value: Value) {
self.inner[bid][offset] = value;
}
}
// TODO: allocation fields will be added in the future
// TODO: program fields will be added in the future
#[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`
pub global_map: GlobalMap,
pub stack_frame: StackFrame<'i>,
pub stack: Vec<StackFrame<'i>>,
pub memory: Memory,
pub ir: &'i TranslationUnit,
}
impl<'i> State<'i> {
fn new(ir: &'i TranslationUnit, args: Vec<Value>) -> Result<State, InterpreterError> {
// Interpreter starts with the main function
let func_name = String::from("main");
let func = ir
.decls
.get(&func_name)
.ok_or_else(|| InterpreterError::NoMainFunction)?;
let (_, func_def) = func
.get_function()
.ok_or_else(|| InterpreterError::NoMainFunction)?;
let func_def = func_def
.as_ref()
.ok_or_else(|| InterpreterError::NoFunctionDefinition {
func_name: func_name.clone(),
})?;
// Create State
let mut state = State {
global_map: GlobalMap::default(),
stack_frame: StackFrame::new(func_def.bid_init, func_name, func_def),
stack: Vec::new(),
memory: Default::default(),
ir,
};
state.alloc_global_variables()?;
// Initialize state with main function and args
state.write_args(func_def.bid_init, args)?;
state.alloc_local_variables()?;
Ok(state)
}
fn alloc_global_variables(&mut self) -> Result<(), InterpreterError> {
for (name, decl) in &self.ir.decls {
// Memory allocation
let bid = self.memory.alloc(&decl.dtype())?;
self.global_map.insert(name.clone(), bid)?;
// Initialize allocated memory space
match decl {
Declaration::Variable { dtype, initializer } => {
let value = if let Some(constant) = initializer {
self.interp_constant(constant.clone())
} else {
Value::default_from_dtype(dtype)
};
self.memory.store(bid, 0, value);
}
// If functin declaration, skip initialization
Declaration::Function { .. } => (),
}
}
Ok(())
}
fn alloc_local_variables(&mut self) -> Result<(), InterpreterError> {
// add alloc register
for (id, allocation) in self.stack_frame.func_def.allocations.iter().enumerate() {
let bid = self.memory.alloc(&allocation)?;
let ptr = Value::pointer(Some(bid), 0);
let rid = RegisterId::local("".to_string(), id);
self.stack_frame.registers.write(rid, ptr)
}
Ok(())
}
fn write_args(&mut self, bid_init: BlockId, args: Vec<Value>) -> Result<(), InterpreterError> {
for (i, value) in args.iter().enumerate() {
self.stack_frame
.registers
.write(RegisterId::arg(bid_init, i), value.clone());
}
Ok(())
}
fn step(&mut self) -> Result<Option<Value>, InterpreterError> {
let block = self
.stack_frame
.func_def
.blocks
.get(&self.stack_frame.pc.bid)
.expect("block matched with `bid` must be exist");
// If it's time to execute an instruction, do so.
if let Some(instr) = block.instructions.get(self.stack_frame.pc.iid) {
self.interp_instruction(instr)?;
return Ok(None);
}
// Execute a block exit.
let return_value = some_or!(self.interp_block_exit(&block.exit)?, return Ok(None));
// If it's returning from a function, pop the stack frame.
// TODO: free memory allocated in the callee
// restore previous state
let prev_stack_frame = some_or!(self.stack.pop(), return Ok(Some(return_value)));
self.stack_frame = prev_stack_frame;
// create temporary register to write return value
let register = RegisterId::temp(self.stack_frame.pc.bid, self.stack_frame.pc.iid);
self.stack_frame.registers.write(register, return_value);
self.stack_frame.pc.increment();
Ok(None)
}
fn run(&mut self) -> Result<Value, InterpreterError> {
loop {
if let Some(value) = self.step()? {
return Ok(value);
}
}
}
fn interp_args(
&self,
signature: &FunctionSignature,
args: &[Operand],
) -> Result<Vec<Value>, InterpreterError> {
// Check that the dtype of each args matches the expected
if !(args.len() == signature.params.len()
&& izip!(args, &signature.params).all(|(a, d)| a.dtype().is_compatible(d)))
{
panic!("dtype of args and params must be compatible")
}
args.iter()
.map(|a| self.interp_operand(a.clone()))
.collect::<Result<Vec<_>, _>>()
}
fn interp_jump(&mut self, arg: &JumpArg) -> Result<Option<Value>, InterpreterError> {
let block = self
.stack_frame
.func_def
.blocks
.get(&arg.bid)
.expect("block matched with `arg.bid` must be exist");
assert_eq!(arg.args.len(), block.phinodes.len());
for (a, d) in izip!(&arg.args, &block.phinodes) {
assert!(a.dtype().is_compatible(&d));
}
for (i, a) in arg.args.iter().enumerate() {
let v = self.interp_operand(a.clone()).unwrap();
self.stack_frame
.registers
.inner
.insert(RegisterId::arg(arg.bid, i), v)
.unwrap();
}
self.stack_frame.pc = Pc::new(arg.bid);
Ok(None)
}
fn interp_block_exit(
&mut self,
block_exit: &BlockExit,
) -> Result<Option<Value>, InterpreterError> {
match block_exit {
BlockExit::Jump { arg } => self.interp_jump(arg),
BlockExit::ConditionalJump {
condition,
arg_then,
arg_else,
} => {
let value = self.interp_operand(condition.clone())?;
let value = value.get_bool().expect("`condition` must be `Value::Bool`");
self.interp_jump(if value { arg_then } else { arg_else })
}
BlockExit::Switch {
value,
default,
cases,
} => {
let value = self.interp_operand(value.clone())?;
// TODO: consider different integer `width` in the future
let arg = cases
.iter()
.find(|(c, _)| value == self.interp_constant(c.clone()))
.map(|(_, arg)| arg)
.unwrap_or_else(|| default);
self.interp_jump(arg)
}
BlockExit::Return { value } => Ok(Some(self.interp_operand(value.clone())?)),
BlockExit::Unreachable => Err(InterpreterError::Unreachable),
}
}
fn interp_instruction(&mut self, instruction: &Instruction) -> Result<(), InterpreterError> {
let result = match instruction {
Instruction::BinOp { op, lhs, rhs, .. } => {
let lhs = self.interp_operand(lhs.clone())?;
let rhs = self.interp_operand(rhs.clone())?;
calculator::calculate_binary_operator_expression(&op, lhs, rhs).map_err(|_| {
InterpreterError::Misc {
func_name: self.stack_frame.func_name.clone(),
pc: self.stack_frame.pc,
msg: "calculate_binary_operator_expression".into(),
}
})?
}
Instruction::UnaryOp { op, operand, .. } => {
let operand = self.interp_operand(operand.clone())?;
calculator::calculate_unary_operator_expression(&op, operand).map_err(|_| {
InterpreterError::Misc {
func_name: self.stack_frame.func_name.clone(),
pc: self.stack_frame.pc,
msg: "calculate_unary_operator_expression".into(),
}
})?
}
Instruction::Store { ptr, value, .. } => {
let ptr = self.interp_operand(ptr.clone())?;
let value = self.interp_operand(value.clone())?;
let (bid, offset) = self.interp_ptr(ptr)?;
self.memory.store(bid, offset, value);
Value::Unit
}
Instruction::Load { ptr, .. } => {
let ptr = self.interp_operand(ptr.clone())?;
let (bid, offset) = self.interp_ptr(ptr)?;
self.memory.load(bid, offset).clone()
}
Instruction::Call { callee, args, .. } => {
let ptr = self.interp_operand(callee.clone())?;
// Get function name from pointer
let (bid, _) = ptr.get_pointer().expect("`ptr` must be `Value::Pointer`");
let bid = bid.expect("pointer for global variable must have bid value");
let callee_name = self
.global_map
.get_var(bid)
.expect("bid must have relation with global variable");
let func = self
.ir
.decls
.get(&callee_name)
.expect("function must be declared before being called");
let (func_signature, func_def) = func
.get_function()
.expect("`func` must be function declaration");
let func_def =
func_def
.as_ref()
.ok_or_else(|| InterpreterError::NoFunctionDefinition {
func_name: callee_name.clone(),
})?;
let args = self.interp_args(func_signature, args)?;
let stack_frame = StackFrame::new(func_def.bid_init, callee_name, func_def);
let prev_stack_frame = mem::replace(&mut self.stack_frame, stack_frame);
self.stack.push(prev_stack_frame);
// Initialize state with function obtained by callee and args
self.write_args(func_def.bid_init, args)?;
self.alloc_local_variables()?;
return Ok(());
}
Instruction::TypeCast {
value,
target_dtype,
} => {
let value = self.interp_operand(value.clone())?;
calculator::calculate_typecast(&value, target_dtype).map_err(|_| {
InterpreterError::Misc {
func_name: self.stack_frame.func_name.clone(),
pc: self.stack_frame.pc,
msg: "calculate_typecast".into(),
}
})?
}
};
let register = RegisterId::temp(self.stack_frame.pc.bid, self.stack_frame.pc.iid);
self.stack_frame.registers.write(register, result);
self.stack_frame.pc.increment();
Ok(())
}
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())
}
}
}
fn interp_constant(&self, value: Constant) -> Value {
match value {
Constant::Unit => Value::Unit,
// TODO: consider `width` and `is_signed` in the future
Constant::Int { value, .. } => Value::Int(value as i32),
Constant::Float { value, .. } => Value::Float(value as f32),
Constant::GlobalVariable { name, .. } => {
let bid = self
.global_map
.get_bid(&name)
.expect("The name matching `bid` must exist.");
// Generate appropriate pointer from `bid`
Value::Pointer {
bid: Some(bid),
offset: 0,
}
}
}
}
fn interp_ptr(&mut self, pointer: Value) -> Result<(usize, usize), InterpreterError> {
let (bid, offset) = pointer
.get_pointer()
.ok_or_else(|| InterpreterError::Misc {
func_name: self.stack_frame.func_name.clone(),
pc: self.stack_frame.pc,
msg: "Accessing memory with non-pointer".into(),
})?;
let bid = bid.ok_or_else(|| InterpreterError::Misc {
func_name: self.stack_frame.func_name.clone(),
pc: self.stack_frame.pc,
msg: "Accessing memory with constant pointer".into(),
})?;
Ok((bid, offset))
}
}
#[inline]
pub fn interp(ir: &TranslationUnit, args: Vec<Value>) -> Result<Value, InterpreterError> {
let mut init_state = State::new(ir, args)?;
init_state.run()
}

578
src/ir/mod.rs Normal file
View File

@@ -0,0 +1,578 @@
mod dtype;
mod interp;
mod write_ir;
use core::convert::TryFrom;
use core::fmt;
use core::ops::Deref;
use lang_c::ast;
use std::collections::HashMap;
use std::hash::{Hash, Hasher};
pub use dtype::{Dtype, DtypeError, HasDtype};
pub use interp::{interp, Value};
#[derive(Debug, PartialEq)]
pub struct TranslationUnit {
pub decls: HashMap<String, Declaration>,
}
#[derive(Debug, PartialEq, Clone)]
pub enum Declaration {
Variable {
dtype: Dtype,
initializer: Option<Constant>,
},
Function {
signature: FunctionSignature,
definition: Option<FunctionDefinition>,
},
}
impl TryFrom<Dtype> for Declaration {
type Error = DtypeError;
/// Create an appropriate declaration according to `dtype`.
///
/// # 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.
///
/// 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 {
message: "A variable of type `void` cannot be declared".to_string(),
}),
Dtype::Int { .. } | Dtype::Float { .. } | Dtype::Pointer { .. } => {
Ok(Declaration::Variable {
dtype,
initializer: None,
})
}
Dtype::Function { .. } => Ok(Declaration::Function {
signature: FunctionSignature::new(dtype),
definition: None,
}),
}
}
}
impl Declaration {
pub fn get_variable(&self) -> Option<(&Dtype, &Option<Constant>)> {
if let Self::Variable { dtype, initializer } = self {
Some((dtype, initializer))
} else {
None
}
}
pub fn get_function(&self) -> Option<(&FunctionSignature, &Option<FunctionDefinition>)> {
if let Self::Function {
signature,
definition,
} = self
{
Some((signature, definition))
} else {
None
}
}
pub fn get_function_mut(
&mut self,
) -> Option<(&mut FunctionSignature, &mut Option<FunctionDefinition>)> {
if let Self::Function {
signature,
definition,
} = self
{
Some((signature, definition))
} else {
None
}
}
/// Check if type is conflicting for pre-declared one
///
/// In case of `Variable`, need to check if the two types are exactly the same.
/// On the other hand, in the case of `Function`, outermost `const` of return type and
/// parameters one is not an issue of concern.
pub fn is_compatible(&self, other: &Declaration) -> bool {
match (self, other) {
(Self::Variable { dtype, .. }, Self::Variable { dtype: other, .. }) => dtype == other,
(
Self::Function { signature, .. },
Self::Function {
signature: other, ..
},
) => signature.dtype().is_compatible(&other.dtype()),
_ => false,
}
}
}
impl HasDtype for Declaration {
fn dtype(&self) -> Dtype {
match self {
Self::Variable { dtype, .. } => dtype.clone(),
Self::Function { signature, .. } => signature.dtype(),
}
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct FunctionDefinition {
/// Memory allocations for local variables. The allocation is performed at the beginning of a
/// function invocation.
pub allocations: Vec<Dtype>,
/// Basic blocks.
pub blocks: HashMap<BlockId, Block>,
/// The initial block id.
pub bid_init: BlockId,
}
#[derive(Debug, PartialEq, Clone)]
pub struct FunctionSignature {
pub ret: Dtype,
pub params: Vec<Dtype>,
}
impl FunctionSignature {
pub fn new(dtype: Dtype) -> Self {
let (ret, params) = dtype
.get_function_inner()
.expect("function signature's dtype must be function type");
Self {
ret: ret.clone(),
params: params.clone(),
}
}
}
impl HasDtype for FunctionSignature {
fn dtype(&self) -> Dtype {
Dtype::function(self.ret.clone(), self.params.clone())
}
}
#[derive(Debug, Eq, Clone)]
pub enum RegisterId {
/// Registers holding pointers to local allocations.
///
/// # Fields
///
/// - `name`: only for debugging purposes.
/// - `id`: local allocation id.
Local { name: String, id: usize },
/// Registers holding block arguments.
///
/// # Fields
///
/// - `bid`: When it is the initial block id, then it holds a function argument; otherwise, it
/// holds a phinode value.
/// - `aid`: the argument index.
Arg { bid: BlockId, aid: usize },
/// Registers holding the results of instructions.
///
/// # Fields
///
/// - `bid`: the instruction's block id.
/// - `iid`: the instruction's id in the block.
Temp { bid: BlockId, iid: usize },
}
impl RegisterId {
pub fn local(name: String, id: usize) -> Self {
Self::Local { name, id }
}
pub fn arg(bid: BlockId, aid: usize) -> Self {
Self::Arg { bid, aid }
}
pub fn temp(bid: BlockId, iid: usize) -> Self {
Self::Temp { bid, iid }
}
}
impl fmt::Display for RegisterId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Local { name, id } => write!(f, "%(local:{}:{})", name, id),
Self::Arg { bid, aid } => write!(f, "%(arg:{}:{})", bid, aid),
Self::Temp { bid, iid } => write!(f, "%({}:{})", bid, iid),
}
}
}
impl PartialEq<RegisterId> for RegisterId {
fn eq(&self, other: &RegisterId) -> bool {
match (self, other) {
(Self::Local { id, .. }, Self::Local { id: other_id, .. }) => id == other_id,
(
Self::Arg { bid, aid },
Self::Arg {
bid: other_bid,
aid: other_aid,
},
) => bid == other_bid && aid == other_aid,
(
Self::Temp { bid, iid },
Self::Temp {
bid: other_bid,
iid: other_iid,
},
) => bid == other_bid && iid == other_iid,
_ => false,
}
}
}
impl Hash for RegisterId {
fn hash<H: Hasher>(&self, state: &mut H) {
match self {
Self::Local { id, .. } => id.hash(state),
Self::Arg { bid, aid } => {
// TODO: needs to distinguish arg/temp?
bid.hash(state);
aid.hash(state);
}
Self::Temp { bid, iid } => {
bid.hash(state);
iid.hash(state);
}
}
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum Constant {
Unit,
Int {
value: u128,
width: usize,
is_signed: bool,
},
Float {
/// `value` may be `f32`, but it is possible to consider it as `f64`.
///
/// * Casting from an f32 to an f64 is perfect and lossless (f32 -> f64)
/// * Casting from an f64 to an f32 will produce the closest possible value (f64 -> f32)
/// https://doc.rust-lang.org/stable/reference/expressions/operator-expr.html#type-cast-expressions
value: f64,
width: usize,
},
GlobalVariable {
name: String,
dtype: Dtype,
},
}
impl TryFrom<&ast::Constant> for Constant {
type Error = ();
fn try_from(constant: &ast::Constant) -> Result<Self, Self::Error> {
match constant {
ast::Constant::Integer(integer) => {
let is_signed = !integer.suffix.unsigned;
let dtype = match integer.suffix.size {
ast::IntegerSize::Int => Dtype::INT,
ast::IntegerSize::Long => Dtype::LONG,
ast::IntegerSize::LongLong => Dtype::LONGLONG,
}
.set_signed(is_signed);
let value = if is_signed {
integer.number.parse::<i128>().unwrap() as u128
} else {
integer.number.parse::<u128>().unwrap()
};
Ok(Self::int(value, dtype))
}
ast::Constant::Float(float) => {
let (dtype, value) = match float.suffix.format {
ast::FloatFormat::Float => {
// Casting from an f32 to an f64 is perfect and lossless (f32 -> f64)
// https://doc.rust-lang.org/stable/reference/expressions/operator-expr.html#type-cast-expressions
(Dtype::FLOAT, float.number.parse::<f32>().unwrap() as f64)
}
ast::FloatFormat::Double => {
(Dtype::DOUBLE, float.number.parse::<f64>().unwrap())
}
ast::FloatFormat::LongDouble => {
panic!("`FloatFormat::LongDouble` is_unsupported")
}
ast::FloatFormat::TS18661Format(_) => {
panic!("`FloatFormat::TS18661Format` is_unsupported")
}
};
Ok(Self::float(value, dtype))
}
ast::Constant::Character(character) => {
let dtype = Dtype::CHAR;
let value = character.parse::<char>().unwrap() as u128;
Ok(Self::int(value, dtype))
}
}
}
}
impl TryFrom<&ast::Expression> for Constant {
type Error = ();
fn try_from(expr: &ast::Expression) -> Result<Self, Self::Error> {
if let ast::Expression::Constant(constant) = expr {
Self::try_from(&constant.node)
} else {
Err(())
}
}
}
impl TryFrom<&ast::Initializer> for Constant {
type Error = ();
fn try_from(initializer: &ast::Initializer) -> Result<Self, Self::Error> {
if let ast::Initializer::Expression(expr) = &initializer {
Self::try_from(&expr.node)
} else {
Err(())
}
}
}
impl Constant {
#[inline]
pub fn is_integer_constant(&self) -> bool {
if let Self::Int { .. } = self {
true
} else {
false
}
}
pub fn unit() -> Self {
Constant::Unit
}
#[inline]
pub fn int(value: u128, dtype: Dtype) -> Self {
let width = dtype.get_int_width().expect("`dtype` must be `Dtype::Int`");
let is_signed = dtype.is_int_signed();
Constant::Int {
value,
width,
is_signed,
}
}
#[inline]
pub fn float(value: f64, dtype: Dtype) -> Self {
let width = dtype
.get_float_width()
.expect("`dtype` must be `Dtype::Float`");
Constant::Float { value, width }
}
#[inline]
pub fn global_variable(name: String, dtype: Dtype) -> Self {
Self::GlobalVariable { name, dtype }
}
}
impl fmt::Display for Constant {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Unit => write!(f, "unit"),
Self::Int { value, .. } => write!(f, "{}", value),
Self::Float { value, .. } => write!(f, "{}", value),
Self::GlobalVariable { name, .. } => write!(f, "%{}", name),
}
}
}
impl HasDtype for Constant {
fn dtype(&self) -> Dtype {
match self {
Self::Unit => Dtype::unit(),
Self::Int {
width, is_signed, ..
} => Dtype::int(*width).set_signed(*is_signed),
Self::Float { width, .. } => Dtype::float(*width),
Self::GlobalVariable { dtype, .. } => Dtype::pointer(dtype.clone()),
}
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum Operand {
Constant(Constant),
Register { rid: RegisterId, dtype: Dtype },
}
impl Operand {
pub fn constant(value: Constant) -> Self {
Self::Constant(value)
}
pub fn register(rid: RegisterId, dtype: Dtype) -> Self {
Self::Register { rid, dtype }
}
pub fn get_constant(&self) -> Option<&Constant> {
if let Self::Constant(constant) = self {
Some(constant)
} else {
None
}
}
pub fn get_register(&self) -> Option<(&RegisterId, &Dtype)> {
if let Self::Register { rid, dtype } = self {
Some((rid, dtype))
} else {
None
}
}
}
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),
}
}
}
impl HasDtype for Operand {
fn dtype(&self) -> Dtype {
match self {
Self::Constant(value) => value.dtype(),
Self::Register { dtype, .. } => dtype.clone(),
}
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum Instruction {
// TODO: the variants of Instruction will be added in the future
BinOp {
op: ast::BinaryOperator,
lhs: Operand,
rhs: Operand,
dtype: Dtype,
},
UnaryOp {
op: ast::UnaryOperator,
operand: Operand,
dtype: Dtype,
},
Store {
ptr: Operand,
value: Operand,
},
Load {
ptr: Operand,
},
Call {
callee: Operand,
args: Vec<Operand>,
return_type: Dtype,
},
TypeCast {
value: Operand,
target_dtype: Dtype,
},
}
impl HasDtype for Instruction {
fn dtype(&self) -> Dtype {
match self {
Self::BinOp { dtype, .. } => dtype.clone(),
Self::UnaryOp { dtype, .. } => dtype.clone(),
Self::Store { .. } => Dtype::unit(),
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(),
}
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub struct BlockId(pub usize);
impl fmt::Display for BlockId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "b{}", self.0)
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct JumpArg {
pub bid: BlockId,
pub args: Vec<Operand>,
}
impl JumpArg {
pub fn new(bid: BlockId, args: Vec<Operand>) -> Self {
Self { bid, args }
}
}
impl fmt::Display for JumpArg {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}({:?})", self.bid, self.args)
}
}
// TODO
#[derive(Debug, PartialEq, Clone)]
pub enum BlockExit {
Jump {
arg: JumpArg,
},
ConditionalJump {
condition: Operand,
arg_then: JumpArg,
arg_else: JumpArg,
},
Switch {
value: Operand,
default: JumpArg,
cases: Vec<(Constant, JumpArg)>,
},
Return {
value: Operand,
},
Unreachable,
}
#[derive(Debug, PartialEq, Clone)]
pub struct Block {
pub phinodes: Vec<Dtype>,
pub instructions: Vec<Instruction>,
pub exit: BlockExit,
}

220
src/ir/write_ir.rs Normal file
View File

@@ -0,0 +1,220 @@
use crate::ir::*;
use std::io::{Result, Write};
use crate::write_base::*;
use crate::*;
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);
(name, decl).write_line(indent, write)?;
}
Ok(())
}
}
impl WriteLine for (&String, &Declaration) {
fn write_line(&self, indent: usize, write: &mut dyn Write) -> Result<()> {
let name = self.0;
let decl = self.1;
match decl {
Declaration::Variable { dtype, .. } => {
writeln!(write, "{} = {}", name, dtype)?;
}
Declaration::Function {
signature,
definition,
} => {
let declaration = format!(
"{} @{}({})",
signature.ret,
name,
signature
.params
.iter()
.map(|d| d.to_string())
.collect::<Vec<_>>()
.join(", "),
);
match definition.as_ref() {
Some(defintion) => {
// print meta data for function
writeln!(
write,
"; function meta data:\n; bid_init: {}\n; allocations: {}",
defintion.bid_init,
defintion
.allocations
.iter()
.enumerate()
.map(|(i, a)| format!("{}:{}", i, a))
.collect::<Vec<_>>()
.join(", ")
)?;
// print function definition
writeln!(write, "define {} {{", declaration)?;
for (id, block) in &defintion.blocks {
writeln!(write, "; <BoxId> {}", 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)?;
}
}
}
}
Ok(())
}
}
impl WriteLine for (&BlockId, &Block) {
fn write_line(&self, indent: usize, write: &mut dyn Write) -> Result<()> {
for (i, instr) in self.1.instructions.iter().enumerate() {
write_indent(indent, write)?;
writeln!(
write,
"{}:{} = {}",
RegisterId::temp(*self.0, i),
instr.dtype(),
instr.write_string()
)?;
}
write_indent(indent, write)?;
writeln!(write, "{}", self.1.exit.write_string())?;
Ok(())
}
}
impl WriteString for Instruction {
fn write_string(&self) -> String {
match self {
Instruction::BinOp { op, lhs, rhs, .. } => format!(
"{} {} {}",
op.write_operation(),
lhs.write_string(),
rhs.write_string()
),
Instruction::UnaryOp { op, operand, .. } => {
format!("{} {}", op.write_operation(), operand.write_string(),)
}
Instruction::Store { ptr, value } => {
format!("store {} {}", value.write_string(), ptr.write_string())
}
Instruction::Load { ptr } => format!("load {}", ptr.write_string()),
Instruction::Call { callee, args, .. } => format!(
"call {}({})",
callee,
args.iter()
.map(WriteString::write_string)
.collect::<Vec<_>>()
.join(", ")
),
Instruction::TypeCast {
value,
target_dtype,
} => format!("typecast {} to {}", value.write_string(), target_dtype),
}
}
}
impl WriteString for Operand {
fn write_string(&self) -> String {
format!("{}:{}", self, self.dtype())
}
}
impl WriteOp for ast::BinaryOperator {
fn write_operation(&self) -> String {
match self {
Self::Multiply => "mul",
Self::Divide => "div",
Self::Modulo => "mod",
Self::Plus => "add",
Self::Minus => "sub",
Self::Equals => "cmp eq",
Self::NotEquals => "cmp ne",
Self::Less => "cmp lt",
Self::LessOrEqual => "cmp le",
Self::Greater => "cmp gt",
Self::GreaterOrEqual => "cmp ge",
_ => todo!(),
}
.to_string()
}
}
impl WriteOp for ast::UnaryOperator {
fn write_operation(&self) -> String {
match self {
Self::Minus => "minus",
_ => todo!(),
}
.to_string()
}
}
impl WriteString for BlockExit {
fn write_string(&self) -> String {
match self {
BlockExit::Jump { arg } => format!("j {}", arg),
BlockExit::ConditionalJump {
condition,
arg_then,
arg_else,
} => format!(
"br {}, {}, {}",
condition.write_string(),
arg_then,
arg_else
),
BlockExit::Switch {
value,
default,
cases,
} => format!(
"switch {}, default: {} [\n{}\n ]",
value.write_string(),
default,
cases
.iter()
.map(|(v, b)| format!(" {}:{}, {}", v, v.dtype(), b))
.collect::<Vec<_>>()
.join("\n")
),
BlockExit::Return { value } => format!("ret {}", value.write_string()),
BlockExit::Unreachable => "<unreachable>\t\t\t\t; error state".to_string(),
}
}
}