diff --git a/examples/array5.c b/examples/array5.c index 02120d9..9c6337b 100644 --- a/examples/array5.c +++ b/examples/array5.c @@ -1,10 +1,13 @@ +int g_a[5] = {1, 2, 3}; + int main() { int init = 1; - int a[5] = {init, 2, 3, 4, -5}; + int a[5] = {init, 2, 3, 4, -5, 6}; int sum = 0; for(int i = 0; i < 5; i++) { sum += a[i]; + sum += g_a[i]; } return sum; diff --git a/src/c/parse.rs b/src/c/parse.rs index 16eba49..e61da7d 100644 --- a/src/c/parse.rs +++ b/src/c/parse.rs @@ -175,7 +175,7 @@ impl AssertSupported for StructField { impl AssertSupported for StructDeclarator { fn assert_supported(&self) { self.declarator.assert_supported(); - assert_eq!(true, self.bit_width.is_none()); + assert!(self.bit_width.is_none()); } } diff --git a/src/ir/dtype.rs b/src/ir/dtype.rs index 97640b1..adacf38 100644 --- a/src/ir/dtype.rs +++ b/src/ir/dtype.rs @@ -4,9 +4,11 @@ use core::ops::Deref; use failure::Fail; use lang_c::ast; use lang_c::span::Node; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::hash::Hash; +use itertools::izip; + use crate::ir::*; #[derive(Debug, PartialEq, Fail)] @@ -26,6 +28,7 @@ struct BaseDtype { size_modifiers: Vec, signed_option: Option, typedef_name: Option, + struct_type: Option, is_const: bool, is_typedef: bool, } @@ -52,6 +55,11 @@ pub enum Dtype { inner: Box, size: usize, }, + Struct { + name: Option, + fields: Vec>, + is_const: bool, + }, Function { ret: Box, params: Vec, @@ -139,6 +147,14 @@ impl BaseDtype { } self.typedef_name = Some(identifier.node.name.clone()); } + ast::TypeSpecifier::Struct(struct_type) => { + if self.struct_type.is_some() { + return Err(DtypeError::Misc { + message: "two or more struct type in declaration specifiers".to_string(), + }); + } + self.struct_type = Some(struct_type.node.clone()); + } _ => todo!("apply_type_specifier: support {:?}", type_specifier), } @@ -172,7 +188,7 @@ impl BaseDtype { Ok(()) } - pub fn apply_typename_specifier( + pub fn apply_specifier_qualifier( &mut self, typename_specifier: &ast::SpecifierQualifier, ) -> Result<(), DtypeError> { @@ -237,12 +253,12 @@ impl BaseDtype { Ok(()) } - pub fn apply_typename_specifiers( + pub fn apply_specifier_qualifiers( &mut self, typename_specifiers: &[Node], ) -> Result<(), DtypeError> { for ast_spec in typename_specifiers { - self.apply_typename_specifier(&ast_spec.node)?; + self.apply_specifier_qualifier(&ast_spec.node)?; } Ok(()) @@ -275,88 +291,129 @@ impl TryFrom for Dtype { && spec.size_modifiers.is_empty() && spec.signed_option.is_none() && spec.typedef_name.is_none() + && spec.struct_type.is_none() && !spec.is_const), "BaseDtype is empty" ); - let mut dtype = if let Some(name) = spec.typedef_name { - if spec.scalar.is_some() || spec.signed_option.is_some() { + if let Some(name) = spec.typedef_name { + if !(spec.scalar.is_none() + && spec.size_modifiers.is_empty() + && spec.signed_option.is_none() + && spec.struct_type.is_none()) + { return Err(DtypeError::Misc { - message: "typedef' cannot be used with scalar type or signed option" - .to_string(), + message: "`typedef` can only be used with `const`".to_string(), }); } - Self::typedef(name) - } else { - // Creates `dtype` from scalar. - let mut dtype = if let Some(t) = spec.scalar { - match t { - ast::TypeSpecifier::Void => Self::unit(), - ast::TypeSpecifier::Bool => Self::BOOL, - ast::TypeSpecifier::Char => Self::CHAR, - ast::TypeSpecifier::Int => Self::INT, - ast::TypeSpecifier::Float => Self::FLOAT, - ast::TypeSpecifier::Double => Self::DOUBLE, - _ => panic!("Dtype::try_from::: {:?} is not a scalar type", t), - } - } else { - Self::default() - }; + let dtype = Self::typedef(name).set_const(spec.is_const); - let number_of_modifier = spec.size_modifiers.len(); - dtype = match number_of_modifier { - 0 => dtype, - 1 => match spec.size_modifiers[0] { - ast::TypeSpecifier::Short => Self::SHORT, - ast::TypeSpecifier::Long => Self::LONG, - _ => panic!( - "Dtype::try_from::: {:?} is not a size modifiers", - spec.size_modifiers - ), - }, - 2 => { - if spec.size_modifiers[0] != ast::TypeSpecifier::Long - || spec.size_modifiers[1] != ast::TypeSpecifier::Long - { - return Err(DtypeError::Misc { - message: "two or more size modifiers in declaration specifiers" - .to_string(), - }); - } - Self::LONGLONG - } - _ => { - return Err(DtypeError::Misc { - message: "two or more size modifiers in declaration specifiers".to_string(), - }) - } - }; + return Ok(dtype); + } - // 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!( - "Dtype::try_from::: {:?} is not a signed option", - signed_option - ), - }; - - if dtype.get_int_width().is_none() { - return Err(DtypeError::Misc { - message: "`signed` and `unsigned` only be applied to `Dtype::Int`" - .to_string(), - }); - } - - dtype = dtype.set_signed(is_signed); + if let Some(struct_type) = spec.struct_type { + if !(spec.scalar.is_none() + && spec.size_modifiers.is_empty() + && spec.signed_option.is_none() + && spec.typedef_name.is_none()) + { + return Err(DtypeError::Misc { + message: "`struct` can only be used with `const`".to_string(), + }); } - dtype + assert_eq!(struct_type.kind.node, ast::StructKind::Struct); + let struct_name = struct_type.identifier.map(|i| i.node.name); + let fields = if let Some(declarations) = struct_type.declarations { + declarations + .iter() + .map(|d| Self::try_from_ast_struct_declaration(&d.node)) + .collect::, _>>()? + .concat() + } else { + Vec::new() + }; + + let mut field_names = HashSet::new(); + for field in &fields { + if let Some(name) = field.name() { + if !field_names.insert(name.clone()) { + return Err(DtypeError::Misc { + message: format!("`{}` is arleady used in struct", name), + }); + } + } + } + + let dtype = Self::structure(struct_name, fields).set_const(spec.is_const); + + return Ok(dtype); + } + + // Creates `dtype` from scalar. + let mut dtype = if let Some(t) = spec.scalar { + match t { + ast::TypeSpecifier::Void => Self::unit(), + ast::TypeSpecifier::Bool => Self::BOOL, + ast::TypeSpecifier::Char => Self::CHAR, + ast::TypeSpecifier::Int => Self::INT, + ast::TypeSpecifier::Float => Self::FLOAT, + ast::TypeSpecifier::Double => Self::DOUBLE, + _ => panic!("Dtype::try_from::: {:?} is not a scalar type", t), + } + } else { + Self::default() }; + let number_of_modifier = spec.size_modifiers.len(); + dtype = match number_of_modifier { + 0 => dtype, + 1 => match spec.size_modifiers[0] { + ast::TypeSpecifier::Short => Self::SHORT, + ast::TypeSpecifier::Long => Self::LONG, + _ => panic!( + "Dtype::try_from::: {:?} is not a size modifiers", + spec.size_modifiers + ), + }, + 2 => { + if spec.size_modifiers[0] != ast::TypeSpecifier::Long + || spec.size_modifiers[1] != ast::TypeSpecifier::Long + { + return Err(DtypeError::Misc { + message: "two or more size modifiers in declaration specifiers".to_string(), + }); + } + Self::LONGLONG + } + _ => { + return Err(DtypeError::Misc { + message: "two or more size modifiers in declaration specifiers".to_string(), + }) + } + }; + + // 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!( + "Dtype::try_from::: {:?} is not a signed option", + signed_option + ), + }; + + if dtype.get_int_width().is_none() { + return Err(DtypeError::Misc { + message: "`signed` and `unsigned` only be applied to `Dtype::Int`".to_string(), + }); + } + + dtype = dtype.set_signed(is_signed); + } + dtype = dtype.set_const(spec.is_const); Ok(dtype) @@ -369,11 +426,11 @@ impl TryFrom<&ast::TypeName> for Dtype { /// Derive a data type from typename. fn try_from(type_name: &ast::TypeName) -> Result { let mut spec = BaseDtype::default(); - BaseDtype::apply_typename_specifiers(&mut spec, &type_name.specifiers)?; + BaseDtype::apply_specifier_qualifiers(&mut spec, &type_name.specifiers)?; let mut dtype = Self::try_from(spec)?; if let Some(declarator) = &type_name.declarator { - dtype = dtype.with_ast_declarator(&declarator.node)?; + dtype = dtype.with_ast_declarator(&declarator.node)?.deref().clone(); } Ok(dtype) } @@ -389,7 +446,7 @@ impl TryFrom<&ast::ParameterDeclaration> for Dtype { let mut dtype = Self::try_from(spec)?; if let Some(declarator) = ¶meter_decl.declarator { - dtype = dtype.with_ast_declarator(&declarator.node)?; + dtype = dtype.with_ast_declarator(&declarator.node)?.deref().clone(); // A function call with an array argument performs array-to-pointer conversion. // For this reason, when `declarator` is from function parameter declaration @@ -487,6 +544,15 @@ impl Dtype { } } + #[inline] + pub fn structure(name: Option, fields: Vec>) -> Self { + Self::Struct { + name, + fields, + is_const: false, + } + } + #[inline] pub fn function(ret: Dtype, params: Vec) -> Self { Self::Function { @@ -522,7 +588,7 @@ impl Dtype { } #[inline] - pub fn get_pointer_inner(&self) -> Option<&Dtype> { + pub fn get_pointer_inner(&self) -> Option<&Self> { if let Self::Pointer { inner, .. } = self { Some(inner.deref()) } else { @@ -531,7 +597,7 @@ impl Dtype { } #[inline] - pub fn get_array_inner(&self) -> Option<&Dtype> { + pub fn get_array_inner(&self) -> Option<&Self> { if let Self::Array { inner, .. } = self { Some(inner.deref()) } else { @@ -540,7 +606,16 @@ impl Dtype { } #[inline] - pub fn get_function_inner(&self) -> Option<(&Dtype, &Vec)> { + pub fn get_struct_fields(&self) -> Option<&Vec>> { + if let Self::Struct { fields, .. } = self { + Some(fields) + } else { + None + } + } + + #[inline] + pub fn get_function_inner(&self) -> Option<(&Self, &Vec)> { if let Self::Function { ret, params } = self { Some((ret.deref(), params)) } else { @@ -567,6 +642,7 @@ impl Dtype { } #[inline] + /// Check if `Dtype` is constant. if it is constant, the variable of `Dtype` is not assignable. pub fn is_const(&self) -> bool { match self { Self::Unit { is_const } => *is_const, @@ -574,6 +650,21 @@ impl Dtype { Self::Float { is_const, .. } => *is_const, Self::Pointer { is_const, .. } => *is_const, Self::Array { .. } => true, + Self::Struct { + fields, is_const, .. + } => { + *is_const + // If any of the fields in the structure type is constant, return `true`. + || fields.iter().any(|f| { + // If an array is wrapped in a struct and the array's inner type is not + // constant, it is assignable to another object of the same struct type. + if let Self::Array { inner, .. } = f.deref() { + inner.is_const() + } else { + f.deref().is_const() + } + }) + } Self::Function { .. } => true, Self::Typedef { .. } => panic!("typedef should be replaced by real dtype"), } @@ -592,6 +683,11 @@ impl Dtype { Self::Float { width, .. } => Self::Float { width, is_const }, Self::Pointer { inner, .. } => Self::Pointer { inner, is_const }, Self::Array { .. } => self, + Self::Struct { name, fields, .. } => Self::Struct { + name, + fields, + is_const, + }, Self::Function { .. } => self, Self::Typedef { name, .. } => Self::Typedef { name, is_const }, } @@ -615,6 +711,7 @@ impl Dtype { align_of_inner, )) } + Self::Struct { .. } => todo!(), Self::Function { .. } => Ok((0, 1)), Self::Typedef { .. } => panic!("typedef should be replaced by real dtype"), } @@ -645,6 +742,38 @@ impl Dtype { Ok((dtype, is_typedef)) } + /// Derive a data type and its name from struct declaration + pub fn try_from_ast_struct_declaration( + declaration: &ast::StructDeclaration, + ) -> Result>, DtypeError> { + let field_decl = if let ast::StructDeclaration::Field(field_decl) = declaration { + &field_decl.node + } else { + panic!("ast::StructDeclaration::StaticAssert is unsupported") + }; + + let mut spec = BaseDtype::default(); + BaseDtype::apply_specifier_qualifiers(&mut spec, &field_decl.specifiers)?; + let dtype = Self::try_from(spec)?; + + let fields = field_decl + .declarators + .iter() + .map(|d| { + dtype + .clone() + .with_ast_declarator(&d.node.declarator.as_ref().unwrap().node) + }) + .collect::, _>>()?; + + if fields.is_empty() { + // Add anonymous field + Ok(vec![Named::new(None, dtype)]) + } else { + Ok(fields) + } + } + /// Generate `Dtype` based on declarator and `self` which has scalar type. /// /// let's say declaration is `const int * const * const a;`. @@ -657,7 +786,10 @@ impl Dtype { /// * `decl_spec` - Containing information that should be referenced /// when creating `Dtype` from `Declarator`. /// - pub fn with_ast_declarator(mut self, declarator: &ast::Declarator) -> Result { + pub fn with_ast_declarator( + mut self, + declarator: &ast::Declarator, + ) -> Result, DtypeError> { for derived_decl in &declarator.derived { self = match &derived_decl.node { ast::DerivedDeclarator::Pointer(pointer_qualifiers) => { @@ -696,7 +828,10 @@ impl Dtype { let declarator_kind = &declarator.kind; match &declarator_kind.node { - ast::DeclaratorKind::Abstract | ast::DeclaratorKind::Identifier(_) => Ok(self), + ast::DeclaratorKind::Abstract => Ok(Named::new(None, self)), + ast::DeclaratorKind::Identifier(identifier) => { + Ok(Named::new(Some(identifier.node.name.clone()), self)) + } ast::DeclaratorKind::Declarator(declarator) => { self.with_ast_declarator(&declarator.node) } @@ -740,15 +875,32 @@ impl Dtype { Self::Unit { .. } | Self::Int { .. } | Self::Float { .. } => self, Self::Pointer { inner, is_const } => { let inner = inner.deref().clone().resolve_typedefs(typedefs)?; - Dtype::pointer(inner).set_const(*is_const) + Self::pointer(inner).set_const(*is_const) } Self::Array { inner, size } => { let inner = inner.deref().clone().resolve_typedefs(typedefs)?; - Dtype::Array { + Self::Array { inner: Box::new(inner), size: *size, } } + Self::Struct { + name, + fields, + is_const, + } => { + let resolved_dtypes = fields + .iter() + .map(|f| f.deref().clone().resolve_typedefs(typedefs)) + .collect::, _>>()?; + + assert_eq!(fields.len(), resolved_dtypes.len()); + let fields = izip!(fields, resolved_dtypes) + .map(|(f, d)| Named::new(f.name().cloned(), d)) + .collect::>(); + + Self::structure(name.clone(), fields).set_const(*is_const) + } Self::Function { ret, params } => { let ret = ret.deref().clone().resolve_typedefs(typedefs)?; let params = params @@ -756,7 +908,7 @@ impl Dtype { .map(|p| p.clone().resolve_typedefs(typedefs)) .collect::, _>>()?; - Dtype::function(ret, params) + Self::function(ret, params) } Self::Typedef { name, is_const } => { let dtype = typedefs @@ -797,6 +949,38 @@ impl fmt::Display for Dtype { write!(f, "{}*{}", inner, if *is_const { "const" } else { "" }) } Self::Array { inner, size, .. } => write!(f, "[{} x {}]", size, inner,), + Self::Struct { + name, + fields, + is_const, + } => { + let fields = fields + .iter() + .map(|f| { + format!( + "{}:{}", + if let Some(name) = f.name() { + name + } else { + "%anonymous" + }, + f.deref() + ) + }) + .collect::>() + .join(", "); + write!( + f, + "{} struct {}:<{}>", + if *is_const { "const" } else { "" }, + if let Some(name) = name { + name + } else { + "%anonymous" + }, + fields + ) + } Self::Function { ret, params } => write!( f, "{} ({})", diff --git a/src/ir/interp.rs b/src/ir/interp.rs index 0b3dbd2..582b80d 100644 --- a/src/ir/interp.rs +++ b/src/ir/interp.rs @@ -9,18 +9,24 @@ use itertools::izip; use crate::ir::*; use crate::*; -// TODO: the variants of Value will be added in the future +// TODO: delete `allow(dead_code)` +/// Even though `Undef`, `Int`, `Float` are constructed and actively used at run-time, +/// the rust compiler analyzes these elements are dead code. +/// For this reason, we add `allow(dead_code)` mark above these elements respectively. #[derive(Debug, PartialEq, Clone)] pub enum Value { + #[allow(dead_code)] Undef { dtype: Dtype, }, Unit, + #[allow(dead_code)] Int { value: u128, width: usize, is_signed: bool, }, + #[allow(dead_code)] Float { /// `value` may be `f32`, but it is possible to consider it as `f64`. /// @@ -41,6 +47,70 @@ pub enum Value { }, } +impl TryFrom for Value { + type Error = (); + + fn try_from(constant: Constant) -> Result { + let value = match constant { + Constant::Undef { dtype } => Self::Undef { dtype }, + Constant::Unit => Self::Unit, + Constant::Int { + value, + width, + is_signed, + } => Self::Int { + value, + width, + is_signed, + }, + Constant::Float { value, width } => Self::Float { + value: value.into_inner(), + width, + }, + _ => panic!(), + }; + + Ok(value) + } +} + +impl TryFrom<(&ast::Initializer, &Dtype)> for Value { + type Error = (); + + fn try_from((initializer, dtype): (&ast::Initializer, &Dtype)) -> Result { + match initializer { + ast::Initializer::Expression(expr) => match dtype { + Dtype::Int { .. } | Dtype::Float { .. } | Dtype::Pointer { .. } => { + let constant = Constant::try_from(&expr.node)?; + let value = Self::try_from(constant)?; + + calculator::calculate_typecast(value, dtype.clone()) + } + _ => Err(()), + }, + ast::Initializer::List(items) => match dtype { + Dtype::Array { inner, size } => { + let inner_dtype = inner.deref().clone(); + let num_of_items = items.len(); + let values = (0..*size) + .map(|i| { + if i < num_of_items { + Self::try_from((&items[i].node.initializer.node, &inner_dtype)) + } else { + Self::default_from_dtype(&inner_dtype) + } + }) + .collect::, _>>()?; + + Ok(Self::array(inner_dtype, values)) + } + Dtype::Struct { .. } => todo!(), + _ => todo!(), + }, + } + } +} + impl HasDtype for Value { fn dtype(&self) -> Dtype { match self { @@ -71,7 +141,7 @@ impl Value { } #[inline] - pub fn int(value: u128, width: usize, is_signed: bool) -> Self { + fn int(value: u128, width: usize, is_signed: bool) -> Self { Self::Int { value, width, @@ -98,7 +168,7 @@ impl Value { } #[inline] - fn get_int(self) -> Option<(u128, usize, bool)> { + pub fn get_int(self) -> Option<(u128, usize, bool)> { if let Value::Int { value, width, @@ -130,23 +200,26 @@ impl Value { } #[inline] - fn default_from_dtype(dtype: &Dtype) -> Self { - match dtype { - ir::Dtype::Unit { .. } => Self::unit(), - ir::Dtype::Int { + fn default_from_dtype(dtype: &Dtype) -> Result { + let value = match dtype { + Dtype::Unit { .. } => Self::unit(), + Dtype::Int { width, is_signed, .. } => Self::int(u128::default(), *width, *is_signed), - ir::Dtype::Float { width, .. } => Self::float(f64::default(), *width), - ir::Dtype::Pointer { inner, .. } => Self::nullptr(inner.deref().clone()), - ir::Dtype::Array { inner, size } => { - let values = (0..*size) - .map(|_| Self::default_from_dtype(inner)) - .collect(); + Dtype::Float { width, .. } => Self::float(f64::default(), *width), + Dtype::Pointer { inner, .. } => Self::nullptr(inner.deref().clone()), + Dtype::Array { inner, size } => { + let values = iter::repeat(Self::default_from_dtype(inner)) + .take(*size) + .collect::, _>>()?; Self::array(inner.deref().clone(), values) } - ir::Dtype::Function { .. } => panic!("function type does not have a default value"), - ir::Dtype::Typedef { .. } => panic!("typedef should be replaced by real dtype"), - } + Dtype::Struct { .. } => todo!(), + Dtype::Function { .. } => panic!("function type does not have a default value"), + Dtype::Typedef { .. } => panic!("typedef should be replaced by real dtype"), + }; + + Ok(value) } } @@ -627,8 +700,8 @@ impl Byte { I: Iterator, { match dtype { - ir::Dtype::Unit { .. } => Ok(Value::Unit), - ir::Dtype::Int { + Dtype::Unit { .. } => Ok(Value::Unit), + Dtype::Int { width, is_signed, .. } => { let value = some_or!( @@ -642,7 +715,7 @@ impl Byte { let value = Self::bytes_to_u128(&value, *is_signed); Ok(Value::int(value, *width, *is_signed)) } - ir::Dtype::Float { width, .. } => { + Dtype::Float { width, .. } => { let size = (*width + Dtype::BITS_OF_BYTE - 1) / Dtype::BITS_OF_BYTE; let value = some_or!( bytes @@ -661,7 +734,7 @@ impl Byte { Ok(Value::float(value, *width)) } - ir::Dtype::Pointer { inner, .. } => { + Dtype::Pointer { inner, .. } => { let value = some_or!( bytes .by_ref() @@ -685,7 +758,7 @@ impl Byte { }, ) } - ir::Dtype::Array { inner, size } => { + Dtype::Array { inner, size } => { let (inner_size, inner_align) = inner.size_align_of().unwrap(); let padding = std::cmp::max(inner_size, inner_align) - inner_size; let values = (0..*size) @@ -700,8 +773,9 @@ impl Byte { values, }) } - ir::Dtype::Function { .. } => panic!("function value cannot be constructed from bytes"), - ir::Dtype::Typedef { .. } => panic!("typedef should be replaced by real dtype"), + Dtype::Struct { .. } => todo!(), + Dtype::Function { .. } => panic!("function value cannot be constructed from bytes"), + Dtype::Typedef { .. } => panic!("typedef should be replaced by real dtype"), } } @@ -776,22 +850,30 @@ impl Memory { } fn load(&self, bid: usize, offset: isize, dtype: &Dtype) -> Result { - assert!(0 <= offset); - let offset = offset as usize; let size = dtype.size_align_of().unwrap().0; - let mut iter = self.inner[bid].as_ref().unwrap()[offset..(offset + size)].iter(); - Byte::bytes_to_value(&mut iter, dtype) + let end = offset as usize + size; + let block = self.inner[bid].as_ref().unwrap(); + + if 0 <= offset && end <= block.len() { + let mut iter = block[offset as usize..end].iter(); + Byte::bytes_to_value(&mut iter, dtype) + } else { + Ok(Value::undef(dtype.clone())) + } } - fn store(&mut self, bid: usize, offset: isize, value: &Value) { - assert!(0 <= offset); - let offset = offset as usize; + fn store(&mut self, bid: usize, offset: isize, value: &Value) -> Result<(), ()> { let size = value.dtype().size_align_of().unwrap().0; + let end = offset as usize + size; let bytes = Byte::value_to_bytes(value); - self.inner[bid] - .as_mut() - .unwrap() - .splice(offset..(offset + size), bytes.iter().cloned()); + let block = self.inner[bid].as_mut().unwrap(); + + if 0 <= offset && end <= block.len() { + block.splice(offset as usize..end, bytes.iter().cloned()); + Ok(()) + } else { + Err(()) + } } } @@ -851,41 +933,34 @@ impl<'i> State<'i> { // Initialize allocated memory space match decl { - 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) - }; + Declaration::Variable { dtype, initializer } => { + let value = if let Some(initializer) = initializer { + Value::try_from((initializer, dtype)).map_err(|_| { + InterpreterError::Misc { + func_name: self.stack_frame.func_name.clone(), + pc: self.stack_frame.pc, + msg: format!( + "fail to translate `Initializer` and `{}` to `Value`", + dtype + ), + } + })? + } else { + Value::default_from_dtype(&dtype) + .expect("default value must be derived from `dtype`") + }; - // Type cast - let value = - calculator::calculate_typecast(value, dtype.clone()).map_err(|_| { - InterpreterError::Misc { - func_name: self.stack_frame.func_name.clone(), - pc: self.stack_frame.pc, - msg: "calculate_typecast when initialize global variable" - .into(), - } - })?; - - self.memory.store(bid, 0, &value); - } - ir::Dtype::Array { .. } => { - let value = if let Some(_list_init) = initializer { - // TODO: is type cast required? - todo!("Initializer::List is needed") - } else { - Value::default_from_dtype(&dtype) - }; - - self.memory.store(bid, 0, &value); - } - ir::Dtype::Function { .. } => panic!("function variable does not exist"), - ir::Dtype::Typedef { .. } => panic!("typedef should be replaced by real dtype"), - }, + self.memory + .store(bid, 0, &value) + .map_err(|_| InterpreterError::Misc { + func_name: self.stack_frame.func_name.clone(), + pc: self.stack_frame.pc, + msg: format!( + "fail to store {:?} into memory with bid: {}, offset: {}", + value, bid, 0, + ), + })? + } // If functin declaration, skip initialization Declaration::Function { .. } => (), } @@ -1077,7 +1152,16 @@ impl<'i> State<'i> { 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); + self.memory + .store(bid, offset, &value) + .map_err(|_| InterpreterError::Misc { + func_name: self.stack_frame.func_name.clone(), + pc: self.stack_frame.pc, + msg: format!( + "fail to store {:?} into memory with bid: {}, offset: {}", + value, bid, offset, + ), + })?; Value::Unit } Instruction::Load { ptr, .. } => { @@ -1177,21 +1261,6 @@ 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, - width, - is_signed, - } => Value::Int { - value, - width, - is_signed, - }, - Constant::Float { value, width } => Value::Float { - value: value.into_inner(), - width, - }, Constant::GlobalVariable { name, dtype } => { let bid = self .global_map @@ -1205,6 +1274,7 @@ impl<'i> State<'i> { dtype, } } + constant => Value::try_from(constant).expect("constant must be transformed to value"), } } diff --git a/src/ir/mod.rs b/src/ir/mod.rs index 7eef6e7..1c0aa46 100644 --- a/src/ir/mod.rs +++ b/src/ir/mod.rs @@ -23,7 +23,7 @@ pub struct TranslationUnit { pub enum Declaration { Variable { dtype: Dtype, - initializer: Option, + initializer: Option, }, Function { signature: FunctionSignature, @@ -54,7 +54,8 @@ impl TryFrom for Declaration { Dtype::Int { .. } | Dtype::Float { .. } | Dtype::Pointer { .. } - | Dtype::Array { .. } => Ok(Declaration::Variable { + | Dtype::Array { .. } + | Dtype::Struct { .. } => Ok(Declaration::Variable { dtype, initializer: None, }), @@ -68,7 +69,7 @@ impl TryFrom for Declaration { } impl Declaration { - pub fn get_variable(&self) -> Option<(&Dtype, &Option)> { + pub fn get_variable(&self) -> Option<(&Dtype, &Option)> { if let Self::Variable { dtype, initializer } = self { Some((dtype, initializer)) } else { @@ -607,17 +608,6 @@ impl TryFrom<&ast::Expression> for Constant { } } -impl TryFrom<&ast::Initializer> for Constant { - type Error = (); - - fn try_from(initializer: &ast::Initializer) -> Result { - match initializer { - ast::Initializer::Expression(expr) => Self::try_from(&expr.node), - ast::Initializer::List(_) => todo!(), - } - } -} - impl Constant { const DECIMAL: u32 = 10; const OCTAL: u32 = 8; @@ -757,7 +747,7 @@ impl HasDtype for Constant { } } -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Eq, Hash, Clone)] pub struct Named { name: Option, inner: T, diff --git a/src/ir/write_ir.rs b/src/ir/write_ir.rs index 400b16f..d51a769 100644 --- a/src/ir/write_ir.rs +++ b/src/ir/write_ir.rs @@ -43,7 +43,7 @@ impl WriteLine for (&String, &Declaration) { "{} = {} {}", name, if let Some(init) = initializer { - init.to_string() + init.write_string() } else { "default".to_string() }, diff --git a/src/lib.rs b/src/lib.rs index 889d4dc..9135206 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,7 @@ mod write_base; mod asm; mod c; -mod ir; +pub mod ir; mod asmgen; mod irgen; diff --git a/src/tests.rs b/src/tests.rs index 06dff1b..e178b78 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -78,12 +78,15 @@ pub fn test_irgen(unit: &TranslationUnit, path: &Path) { Ok(result) => result, Err(interp_error) => panic!("{}", interp_error), }; - let result = if let ir::Value::Int { .. } = &result { - result - } else { - panic!("non-integer value occurs") - }; + // We only allow main function whose return type is `int` + let (value, width, is_signed) = result.get_int().expect("non-integer value occurs"); + assert_eq!(width, 32); + assert!(is_signed); - println!("kecc: {:?}\ngcc: {:?}", result, status); - assert_eq!(result, ir::Value::int(status as u128, 32, true)); + // When obtain status from `gcc` executable process, value is truncated to byte size. + // For this reason, we make `fuzzer` generate the C source code which returns value + // typecasted to `unsigned char`. However, during `creduce` reduce the code, typecasting + // may be deleted. So, we truncate result value to byte size one more time here. + println!("gcc: {}, kecc: {}", status as i8, value as i8); + assert_eq!(status as i8, value as i8); } diff --git a/tests/fuzz.py b/tests/fuzz.py index ee3d61c..bc962a2 100644 --- a/tests/fuzz.py +++ b/tests/fuzz.py @@ -21,7 +21,7 @@ REPLACE_DICT = { "extern ": "", "__restrict": "", "long __undefined;": "", - "return 0;": "return crc32_context % 128;", + "return 0;": "return (unsigned char)(crc32_context);", r"__attribute__\s*\(\(.*\)\)": "", "_Float128": "double", "long double": "double", diff --git a/tests/reduce-criteria-template.sh b/tests/reduce-criteria-template.sh index 4c79709..af857de 100644 --- a/tests/reduce-criteria-template.sh +++ b/tests/reduce-criteria-template.sh @@ -1,4 +1,56 @@ #!/usr/bin/env bash +rm -f out*.txt + +#ulimit -t 3000 +#ulimit -v 2000000 + +if + [ $FUZZ_ARG = '-i' ] &&\ + (! clang -pedantic -Wall -Werror=strict-prototypes -O1 -c test_reduced.c > out.txt 2>&1 ||\ + grep 'main-return-type' out.txt ||\ + grep 'conversions than data arguments' out.txt ||\ + grep 'int-conversion' out.txt ||\ + grep 'incompatible redeclaration' out.txt ||\ + grep 'ordered comparison between pointer and zero' out.txt ||\ + grep 'ordered comparison between pointer and integer' out.txt ||\ + grep 'eliding middle term' out.txt ||\ + grep 'end of non-void function' out.txt ||\ + grep 'invalid in C99' out.txt ||\ + grep 'specifies type' out.txt ||\ + grep 'should return a value' out.txt ||\ + grep 'uninitialized' out.txt ||\ + grep 'incompatible pointer to' out.txt ||\ + grep 'incompatible integer to' out.txt ||\ + grep 'type specifier missing' out.txt ||\ + grep 'implicit-function-declaration' out.txt ||\ + grep 'infinite-recursion' out.txt ||\ + grep 'pointer-bool-conversion' out.txt ||\ + grep 'non-void function does not return a value' out.txt ||\ + grep 'too many arguments in call' out.txt ||\ + ! gcc -Wall -Wextra -O2 test_reduced.c > outa.txt 2>&1 ||\ + grep 'uninitialized' outa.txt ||\ + grep 'without a cast' outa.txt ||\ + grep 'control reaches end' outa.txt ||\ + grep 'return type defaults' outa.txt ||\ + grep 'cast from pointer to integer' outa.txt ||\ + grep 'useless type name in empty declaration' outa.txt ||\ + grep 'no semicolon at end' outa.txt ||\ + grep 'type defaults to' outa.txt ||\ + grep 'too few arguments for format' outa.txt ||\ + grep 'incompatible pointer' outa.txt ||\ + grep 'ordered comparison of pointer with integer' outa.txt ||\ + grep 'declaration does not declare anything' outa.txt ||\ + grep 'expects type' outa.txt ||\ + grep 'pointer from integer' outa.txt ||\ + grep 'incompatible implicit' outa.txt ||\ + grep 'excess elements in struct initializer' outa.txt ||\ + grep 'comparison between pointer and integer' outa.txt ||\ + ! gcc -O1 test_reduced.c > cc_out1.txt 2>&1 ||\ + ! gcc -O2 test_reduced.c > cc_out2.txt 2>&1) +then + exit 1 +fi + 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