From 883bf53f574b523cabb3825bd5e735f32cd2b4df Mon Sep 17 00:00:00 2001 From: Christoph Burgdorf Date: Tue, 26 Jan 2021 16:32:46 +0100 Subject: [PATCH] WIP --- analyzer/src/lib.rs | 21 +- analyzer/src/namespace/operations.rs | 1 + analyzer/src/namespace/types.rs | 72 ++++++- analyzer/src/traversal/declarations.rs | 1 - analyzer/src/traversal/expressions.rs | 96 ++++++++- analyzer/src/traversal/module.rs | 7 +- compiler/src/yul/mappers/expressions.rs | 28 ++- compiler/src/yul/mappers/module.rs | 2 +- compiler/src/yul/names.rs | 17 ++ compiler/src/yul/operations/calls.rs | 41 +++- compiler/src/yul/runtime/functions/data.rs | 11 + compiler/src/yul/runtime/functions/mod.rs | 2 + compiler/src/yul/runtime/functions/structs.rs | 122 +++++++++++ compiler/src/yul/runtime/mod.rs | 9 +- compiler/tests/evm_contracts.rs | 3 +- compiler/tests/fixtures/structs.fe | 10 +- output/Foo/Foo_ir.yul | 192 ++++++++++++++++++ 17 files changed, 609 insertions(+), 26 deletions(-) create mode 100644 compiler/src/yul/runtime/functions/structs.rs create mode 100644 output/Foo/Foo_ir.yul diff --git a/analyzer/src/lib.rs b/analyzer/src/lib.rs index f6e523b735..e1f0dbd061 100644 --- a/analyzer/src/lib.rs +++ b/analyzer/src/lib.rs @@ -19,6 +19,7 @@ use crate::namespace::scopes::{ use crate::namespace::types::{ Contract, FixedSize, + Struct, Type, }; use fe_parser::ast as fe; @@ -55,6 +56,7 @@ impl Location { Type::Array(_) => Ok(Location::Memory), Type::Tuple(_) => Ok(Location::Memory), Type::String(_) => Ok(Location::Memory), + Type::Struct(_) => Ok(Location::Memory), Type::Map(_) => Err(SemanticError::cannot_move()), } } @@ -71,6 +73,8 @@ pub struct ContractAttributes { pub events: Vec, /// Static strings that the contract defines pub string_literals: HashSet, + /// Structs that have been defined by the user + pub structs: Vec, /// External contracts that may be called from within this contract. pub external_contracts: Vec, } @@ -108,6 +112,14 @@ impl From> for ContractAttributes { } }); + let structs = scope.borrow().get_module_type_defs(|typ| { + if let Type::Struct(val) = typ { + Some(val.to_owned()) + } else { + None + } + }); + ContractAttributes { public_functions, init_function, @@ -118,6 +130,7 @@ impl From> for ContractAttributes { .map(|event| event.to_owned()) .collect::>(), string_literals: scope.borrow().string_defs.clone(), + structs, external_contracts, } } @@ -248,12 +261,8 @@ impl Context { } /// Attribute contextual information to an expression node. - pub fn add_expression( - &mut self, - spanned: &Spanned, - attributes: ExpressionAttributes, - ) { - self.expressions.insert(spanned.span, attributes); + pub fn add_expression>(&mut self, span: T, attributes: ExpressionAttributes) { + self.expressions.insert(span.into(), attributes); } /// Get information that has been attributed to an expression node. diff --git a/analyzer/src/namespace/operations.rs b/analyzer/src/namespace/operations.rs index 306d5378a7..f1553a927d 100644 --- a/analyzer/src/namespace/operations.rs +++ b/analyzer/src/namespace/operations.rs @@ -17,6 +17,7 @@ pub fn index(value: Type, index: Type) -> Result { Type::Tuple(_) => Err(SemanticError::not_subscriptable()), Type::String(_) => Err(SemanticError::not_subscriptable()), Type::Contract(_) => Err(SemanticError::not_subscriptable()), + Type::Struct(_) => Err(SemanticError::not_subscriptable()), } } diff --git a/analyzer/src/namespace/types.rs b/analyzer/src/namespace/types.rs index 36b9e2e99c..2f79782c9a 100644 --- a/analyzer/src/namespace/types.rs +++ b/analyzer/src/namespace/types.rs @@ -1,6 +1,13 @@ use crate::errors::SemanticError; -use fe_parser::ast as fe; -use std::collections::HashMap; +use fe::StructStmt; +use fe_parser::{ + ast as fe, + span::Spanned, +}; +use std::collections::{ + BTreeMap, + HashMap, +}; use std::convert::TryFrom; use std::num::{ IntErrorKind, @@ -103,6 +110,7 @@ pub enum Type { Tuple(Tuple), String(FeString), Contract(Contract), + Struct(Struct), } #[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq)] @@ -112,6 +120,7 @@ pub enum FixedSize { Tuple(Tuple), String(FeString), Contract(Contract), + Struct(Struct), } #[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq)] @@ -157,6 +166,12 @@ pub struct Tuple { pub items: Vec, } +#[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq)] +pub struct Struct { + pub name: String, + pub fields: BTreeMap, +} + #[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq)] pub struct FeString { pub max_size: usize, @@ -168,6 +183,33 @@ pub struct Contract { pub functions: Vec, } +impl Struct { + pub fn new(name: &str) -> Struct { + Struct { + name: name.to_string(), + fields: BTreeMap::new(), + } + } +} + +pub fn to_struct( + name: &str, + struct_stmts: &[Spanned], + defs: &HashMap, +) -> Result { + let mut val = Struct::new(name); + for stmt in struct_stmts { + let StructStmt::StructField { name, typ } = &stmt.node; + let field_type = type_desc(defs, &typ.node)?; + if let Type::Base(base_typ) = field_type { + val.fields.insert(name.node.to_string(), base_typ); + } else { + unimplemented!("Fields can only be base types at this point") + } + } + Ok(Type::Struct(val)) +} + impl TryFrom<&str> for FeString { type Error = String; @@ -258,6 +300,7 @@ impl From for Type { FixedSize::Tuple(tuple) => Type::Tuple(tuple), FixedSize::String(string) => Type::String(string), FixedSize::Contract(contract) => Type::Contract(contract), + FixedSize::Struct(val) => Type::Struct(val), } } } @@ -270,6 +313,7 @@ impl FeSized for FixedSize { FixedSize::Tuple(tuple) => tuple.size(), FixedSize::String(string) => string.size(), FixedSize::Contract(contract) => contract.size(), + FixedSize::Struct(val) => val.size(), } } } @@ -301,6 +345,7 @@ impl AbiEncoding for FixedSize { FixedSize::Tuple(tuple) => tuple.abi_name(), FixedSize::String(string) => string.abi_name(), FixedSize::Contract(contract) => contract.abi_name(), + FixedSize::Struct(val) => val.abi_name(), } } @@ -311,6 +356,7 @@ impl AbiEncoding for FixedSize { FixedSize::Tuple(tuple) => tuple.abi_safe_name(), FixedSize::String(string) => string.abi_safe_name(), FixedSize::Contract(contract) => contract.abi_safe_name(), + FixedSize::Struct(val) => val.abi_safe_name(), } } @@ -321,6 +367,7 @@ impl AbiEncoding for FixedSize { FixedSize::Tuple(tuple) => tuple.abi_type(), FixedSize::String(string) => string.abi_type(), FixedSize::Contract(contract) => contract.abi_type(), + FixedSize::Struct(val) => val.abi_type(), } } } @@ -348,6 +395,7 @@ impl TryFrom for FixedSize { Type::Base(base) => Ok(FixedSize::Base(base)), Type::Tuple(tuple) => Ok(FixedSize::Tuple(tuple)), Type::String(string) => Ok(FixedSize::String(string)), + Type::Struct(val) => Ok(FixedSize::Struct(val)), Type::Map(_) => Err(SemanticError::type_error()), Type::Contract(contract) => Ok(FixedSize::Contract(contract)), } @@ -543,6 +591,26 @@ impl FeSized for Tuple { } } +impl FeSized for Struct { + fn size(&self) -> usize { + self.fields.values().map(|val| val.size()).sum() + } +} + +impl AbiEncoding for Struct { + fn abi_name(&self) -> String { + unimplemented!(); + } + + fn abi_safe_name(&self) -> String { + unimplemented!(); + } + + fn abi_type(&self) -> AbiType { + unimplemented!(); + } +} + impl AbiEncoding for Tuple { fn abi_name(&self) -> String { unimplemented!(); diff --git a/analyzer/src/traversal/declarations.rs b/analyzer/src/traversal/declarations.rs index 8e4d471a48..9a75977cfd 100644 --- a/analyzer/src/traversal/declarations.rs +++ b/analyzer/src/traversal/declarations.rs @@ -31,7 +31,6 @@ pub fn var_decl( return Err(SemanticError::type_error()); } } - scope.borrow_mut().add_var(name, declared_type.clone()); context.borrow_mut().add_declaration(stmt, declared_type); diff --git a/analyzer/src/traversal/expressions.rs b/analyzer/src/traversal/expressions.rs index 34d8585e48..80585eb77e 100644 --- a/analyzer/src/traversal/expressions.rs +++ b/analyzer/src/traversal/expressions.rs @@ -13,6 +13,7 @@ use crate::namespace::types::{ FeString, FixedSize, Integer, + Struct, Type, U256, }; @@ -45,7 +46,7 @@ pub fn expr( fe::Expr::Num(_) => expr_num(exp), fe::Expr::Bool(_) => expr_bool(exp), fe::Expr::Subscript { .. } => expr_subscript(scope, Rc::clone(&context), exp), - fe::Expr::Attribute { .. } => expr_attribute(scope, exp), + fe::Expr::Attribute { .. } => expr_attribute(scope, Rc::clone(&context), exp), fe::Expr::Ternary { .. } => expr_ternary(scope, Rc::clone(&context), exp), fe::Expr::BoolOperation { .. } => unimplemented!(), fe::Expr::BinOperation { .. } => expr_bin_operation(scope, Rc::clone(&context), exp), @@ -138,7 +139,6 @@ pub fn expr_name_str<'a>(exp: &Spanned>) -> Result<&'a str, Semanti if let fe::Expr::Name(name) = exp.node { return Ok(name); } - unreachable!() } @@ -198,6 +198,10 @@ fn expr_name( Location::Memory, )), Some(FixedSize::Tuple(_)) => unimplemented!(), + Some(FixedSize::Struct(val)) => Ok(ExpressionAttributes::new( + Type::Struct(val), + Location::Memory, + )), None => Err(SemanticError::undefined_value()), }; } @@ -275,6 +279,7 @@ fn expr_subscript( fn expr_attribute( scope: Shared, + context: Shared, exp: &Spanned, ) -> Result { if let fe::Expr::Attribute { value, attr } = &exp.node { @@ -286,10 +291,19 @@ fn expr_attribute( TxField, }; + let object_name = expr_name_str(value)?; + + // Before we try to match any pre-defined objects, try matching as a + // custom type + if let Some(FixedSize::Struct(_)) = scope.borrow().variable_def(object_name.to_string()) + { + return expr_attribute_custom_type(Rc::clone(&scope), context, value, attr) + } + let val = |t| Ok(ExpressionAttributes::new(Type::Base(t), Location::Value)); let err = || Err(SemanticError::undefined_value()); - return match Object::from_str(expr_name_str(value)?) { + return match Object::from_str(object_name) { Ok(Object::Self_) => expr_attribute_self(scope, attr), Ok(Object::Block) => match BlockField::from_str(attr.node) { @@ -322,6 +336,36 @@ fn expr_attribute( unreachable!() } +fn expr_attribute_custom_type( + scope: Shared, + context: Shared, + value: &Spanned, + attr: &Spanned<&str>, +) -> Result { + let val_str = expr_name_str(value)?; + let custom_type = scope + .borrow() + .variable_def(val_str.to_string()) + .ok_or_else(SemanticError::undefined_value)?; + context.borrow_mut().add_expression( + value, + ExpressionAttributes::new(custom_type.clone().into(), Location::Memory), + ); + match custom_type { + FixedSize::Struct(val) => { + let field = val + .fields + .get(attr.node) + .ok_or_else(SemanticError::undefined_value)?; + Ok(ExpressionAttributes::new( + Type::Base(field.clone()), + Location::Memory, + )) + } + _ => Err(SemanticError::undefined_value()), + } +} + fn expr_attribute_self( scope: Shared, attr: &Spanned<&str>, @@ -431,12 +475,41 @@ fn expr_call( unreachable!() } +fn expr_call_struct_constructor( + scope: Shared, + context: Shared, + typ: Struct, + args: &Spanned>>, +) -> Result { + let argument_attributes = expr_call_args(Rc::clone(&scope), Rc::clone(&context), args)?; + + // TODO: implement From for Type + let struct_types: Vec = typ + .fields + .values() + .map(|val| Type::Base(val.clone())) + .collect(); + + if struct_types != expression_attributes_to_types(argument_attributes) { + return Err(SemanticError::type_error()); + } + + Ok(ExpressionAttributes::new( + Type::Struct(typ), + Location::Memory, + )) +} + fn expr_call_type_constructor( scope: Shared, context: Shared, typ: Type, args: &Spanned>>, ) -> Result { + if let Type::Struct(val) = typ { + return expr_call_struct_constructor(scope, context, val, args); + } + if args.node.len() != 1 { return Err(SemanticError::wrong_number_of_params()); } @@ -507,6 +580,17 @@ fn validate_str_literal_fits_type( Err(SemanticError::type_error()) } +fn expr_call_args( + scope: Shared, + context: Shared, + args: &Spanned>>, +) -> Result, SemanticError> { + args.node + .iter() + .map(|arg| call_arg(Rc::clone(&scope), Rc::clone(&context), arg)) + .collect::, _>>() +} + fn expr_call_self_attribute( scope: Shared, context: Shared, @@ -524,11 +608,7 @@ fn expr_call_self_attribute( .borrow() .function_def(func_name) { - let argument_attributes = args - .node - .iter() - .map(|arg| call_arg(Rc::clone(&scope), Rc::clone(&context), arg)) - .collect::, _>>()?; + let argument_attributes = expr_call_args(Rc::clone(&scope), Rc::clone(&context), args)?; if param_types.len() != argument_attributes.len() { return Err(SemanticError::wrong_number_of_params()); diff --git a/analyzer/src/traversal/module.rs b/analyzer/src/traversal/module.rs index 90d4b084af..fb9b2de69d 100644 --- a/analyzer/src/traversal/module.rs +++ b/analyzer/src/traversal/module.rs @@ -17,10 +17,15 @@ pub fn module(context: Shared, module: &fe::Module) -> Result<(), Seman for stmt in module.body.iter() { match &stmt.node { fe::ModuleStmt::TypeDef { .. } => type_def(Rc::clone(&scope), stmt)?, + fe::ModuleStmt::StructDef { name, body } => { + let typ = types::to_struct(name.node, body, &scope.borrow().type_defs)?; + // TODO: I'm not sure if this is supposed to use add_type_def or if structs + // should get their own add_struct_def handling + scope.borrow_mut().add_type_def(name.node.to_string(), typ); + } fe::ModuleStmt::ContractDef { .. } => { contracts::contract_def(Rc::clone(&scope), Rc::clone(&context), stmt)? } - fe::ModuleStmt::StructDef { .. } => unimplemented!(), fe::ModuleStmt::FromImport { .. } => unimplemented!(), fe::ModuleStmt::SimpleImport { .. } => unimplemented!(), } diff --git a/compiler/src/yul/mappers/expressions.rs b/compiler/src/yul/mappers/expressions.rs index b830023234..42652a728f 100644 --- a/compiler/src/yul/mappers/expressions.rs +++ b/compiler/src/yul/mappers/expressions.rs @@ -3,11 +3,14 @@ use crate::yul::names; use crate::yul::operations::calls as call_operations; use crate::yul::operations::data as data_operations; use crate::yul::utils; -use fe_analyzer::builtins; use fe_analyzer::namespace::types::{ FixedSize, Type, }; +use fe_analyzer::{ + builtins, + ExpressionAttributes, +}; use fe_analyzer::{ CallType, Context, @@ -98,6 +101,9 @@ fn expr_call(context: &Context, exp: &Spanned) -> Result>()?; return match call_type { + CallType::TypeConstructor { + typ: Type::Struct(val), + } => Ok(call_operations::struct_new_call(val, yul_args)), CallType::TypeConstructor { .. } => Ok(yul_args[0].to_owned()), CallType::SelfAttribute { func_name } => { let func_name = names::func_name(func_name); @@ -341,7 +347,25 @@ fn expr_attribute( Object, TxField, }; - return match Object::from_str(expr_name_str(value)?) { + + let object_name = expr_name_str(value)?; + + // Before we try to match any known pre-defined objects, try matching as a + // custom type + if let Some(ExpressionAttributes { + typ: Type::Struct(val), + .. + }) = context.get_expression(&*value) + { + let custom_type = format!("${}", object_name); + return Ok(call_operations::struct_get_call( + val, + &custom_type, + attr.node, + )); + } + + return match Object::from_str(object_name) { Ok(Object::Self_) => expr_attribute_self(context, exp), Ok(Object::Block) => match BlockField::from_str(attr.node) { diff --git a/compiler/src/yul/mappers/module.rs b/compiler/src/yul/mappers/module.rs index d33ac4d1ea..218e219368 100644 --- a/compiler/src/yul/mappers/module.rs +++ b/compiler/src/yul/mappers/module.rs @@ -22,7 +22,7 @@ pub fn module(context: &Context, module: &fe::Module) -> Result unimplemented!(), + fe::ModuleStmt::StructDef { .. } => {} fe::ModuleStmt::FromImport { .. } => unimplemented!(), fe::ModuleStmt::SimpleImport { .. } => unimplemented!(), } diff --git a/compiler/src/yul/names.rs b/compiler/src/yul/names.rs index 34b58e155e..321e865291 100644 --- a/compiler/src/yul/names.rs +++ b/compiler/src/yul/names.rs @@ -45,6 +45,23 @@ pub fn contract_call(contract_name: &str, func_name: &str) -> yul::Identifier { identifier! { (name) } } +/// Generates a function name for to interact with a certain struct type +pub fn struct_api(struct_name: &str, func_name: &str) -> yul::Identifier { + let name = format!("struct_{}_{}", struct_name, func_name); + identifier! { (name) } +} + +/// Generates a function name for creating a certain struct type +pub fn struct_api_new(struct_name: &str) -> yul::Identifier { + struct_api(struct_name, "new") +} + +/// Generates a function name for reading a named property of a certain struct +/// type +pub fn struct_api_get(struct_name: &str, field_name: &str) -> yul::Identifier { + struct_api(struct_name, &format!("get_{}", field_name)) +} + #[cfg(test)] mod tests { use crate::yul::names::{ diff --git a/compiler/src/yul/operations/calls.rs b/compiler/src/yul/operations/calls.rs index 56532f61ca..1998103917 100644 --- a/compiler/src/yul/operations/calls.rs +++ b/compiler/src/yul/operations/calls.rs @@ -1,5 +1,8 @@ use crate::yul::names; -use fe_analyzer::namespace::types::Contract; +use fe_analyzer::namespace::types::{ + Contract, + Struct, +}; use yultsur::*; /// Make a call to a contract of the given type and address with a set of @@ -13,3 +16,39 @@ pub fn contract_call( let func_name = names::contract_call(&contract.name, &func_name); expression! { [func_name]([address], [params...]) } } + +pub fn struct_new_call(struct_type: &Struct, params: Vec) -> yul::Expression { + let function_name = names::struct_api_new(&struct_type.name); + expression! { [function_name]([params...]) } +} + +pub fn struct_get_call(struct_type: &Struct, ptr_name: &str, field_name: &str) -> yul::Expression { + let function_name = names::struct_api_get(&struct_type.name, field_name); + let ptr_name_exp = identifier_expression! {(ptr_name)}; + expression! { [function_name]([ptr_name_exp]) } +} + +#[cfg(test)] +mod tests { + use crate::yul::operations::calls; + use fe_analyzer::namespace::types::{ + Base, + Struct, + }; + use yultsur::*; + + #[test] + fn test_struct_new_call() { + let mut val = Struct::new("Foo"); + val.fields.insert("bar".to_string(), Base::Bool); + val.fields.insert("bar2".to_string(), Base::Bool); + let params = vec![ + identifier_expression! { (1) }, + identifier_expression! { (2) }, + ]; + assert_eq!( + calls::struct_new_call(&val, params).to_string(), + "struct_Foo_new(1, 2)" + ) + } +} diff --git a/compiler/src/yul/runtime/functions/data.rs b/compiler/src/yul/runtime/functions/data.rs index 256d6e12f4..e302c9d475 100644 --- a/compiler/src/yul/runtime/functions/data.rs +++ b/compiler/src/yul/runtime/functions/data.rs @@ -50,6 +50,17 @@ pub fn ccopym() -> yul::Statement { } } +pub fn encode_dummy_tuple() -> yul::Statement { + function_definition! { + function encode_dummy_tuple(val_1, val_2) -> mptr { + (mptr := alloc(32)) + (mstore(mptr, val_1)) + (let second_ptr := alloc(32)) + (mstore(second_ptr, val_2)) + } + } +} + /// Load a static string from data into a newly allocated segment of memory. pub fn load_data_string() -> yul::Statement { function_definition! { diff --git a/compiler/src/yul/runtime/functions/mod.rs b/compiler/src/yul/runtime/functions/mod.rs index 43dd9ff692..3fbbf4185f 100644 --- a/compiler/src/yul/runtime/functions/mod.rs +++ b/compiler/src/yul/runtime/functions/mod.rs @@ -4,6 +4,7 @@ use yultsur::*; pub mod abi; pub mod calls; pub mod data; +pub mod structs; /// Returns all functions that should be available during runtime. pub fn std() -> Vec { @@ -14,6 +15,7 @@ pub fn std() -> Vec { data::free(), data::ccopym(), data::load_data_string(), + data::encode_dummy_tuple(), data::mcopys(), data::scopym(), data::mcopym(), diff --git a/compiler/src/yul/runtime/functions/structs.rs b/compiler/src/yul/runtime/functions/structs.rs new file mode 100644 index 0000000000..505703041f --- /dev/null +++ b/compiler/src/yul/runtime/functions/structs.rs @@ -0,0 +1,122 @@ +use crate::yul::names; +use fe_analyzer::namespace::types::Struct; +use yultsur::*; + +/// Generate a YUL function that can be used to create an instance of +/// `struct_type` +pub fn generate_new_fn(struct_type: &Struct) -> yul::Statement { + let function_name = names::struct_api_new(&struct_type.name); + + let params = struct_type + .fields + .keys() + .map(|key| { + identifier! {(key)} + }) + .collect::>(); + + let body = struct_type + .fields + .keys() + .enumerate() + .map(|(index, key)| { + if index == 0 { + let param_identifier_exp = identifier_expression! {(key)}; + statements! { + (return_val := alloc(32)) + (mstore(return_val, [param_identifier_exp])) + } + } else { + let ptr_identifier = format!("{}_ptr", key); + let ptr_identifier = identifier! {(ptr_identifier)}; + let ptr_identifier_exp = identifier_expression! {(ptr_identifier)}; + let param_identifier_exp = identifier_expression! {(key)}; + statements! { + (let [ptr_identifier] := alloc(32)) + (mstore([ptr_identifier_exp], [param_identifier_exp])) + } + } + }) + .flatten() + .collect::>(); + + function_definition! { + function [function_name]([params...]) -> return_val { + [body...] + } + } +} + +/// Generate a YUL function that can be used to read a property of `struct_type` +pub fn generate_get_fn(struct_type: &Struct, field_name: &str) -> yul::Statement { + let function_name = names::struct_api_get(&struct_type.name, field_name); + let fields: Vec = struct_type.fields.keys().cloned().collect(); + // TODO: Fixme + let field_offset = fields.iter().position(|field| field == field_name).unwrap() * 32; + + let offset = literal_expression! {(field_offset)}; + let return_expression = expression! { add(ptr, [offset]) }; + let body = statement! { (return_val := [return_expression]) }; + function_definition! { + function [function_name](ptr) -> return_val { + [body] + } + } +} + +/// Builds a set of functions used to interact with structs used in a contract +pub fn struct_apis(struct_type: Struct) -> Vec { + [ + vec![generate_new_fn(&struct_type)], + struct_type + .fields + .keys() + .map(|field| generate_get_fn(&struct_type, field)) + .collect(), + ] + .concat() +} + +#[cfg(test)] +mod tests { + use crate::yul::runtime::functions::structs; + use fe_analyzer::namespace::types::{ + Base, + Struct, + }; + + // TODO + // #[test] + // fn test_empty_struck() { + // assert_eq!( + // struct_apis(Struct::new("Foo")).to_string(), + // "function fuuuk() -> return_val { return_val := alloc(0) }" + // ) + // } + + #[test] + fn test_struct_api_generation() { + let mut val = Struct::new("Foo"); + val.fields.insert("bar".to_string(), Base::Bool); + val.fields.insert("bar2".to_string(), Base::Bool); + assert_eq!( + structs::generate_new_fn(&val).to_string(), + "function struct_Foo_new(bar, bar2) -> return_val { return_val := alloc(32) mstore(return_val, bar) let bar2_ptr := alloc(32) mstore(bar2_ptr, bar2) }" + ) + } + + #[test] + fn test_struct_getter_generation() { + let mut val = Struct::new("Foo"); + val.fields.insert("bar".to_string(), Base::Bool); + val.fields.insert("bar2".to_string(), Base::Bool); + assert_eq!( + structs::generate_get_fn(&val, &val.fields.keys().nth(0).unwrap()).to_string(), + "function struct_Foo_get_bar(ptr) -> return_val { return_val := add(ptr, 0) }" + ); + assert_eq!( + structs::generate_get_fn(&val, &val.fields.keys().nth(1).unwrap()).to_string(), + "function struct_Foo_get_bar2(ptr) -> return_val { return_val := add(ptr, 32) }" + ) + } +} diff --git a/compiler/src/yul/runtime/mod.rs b/compiler/src/yul/runtime/mod.rs index f619013534..bbb2c857e6 100644 --- a/compiler/src/yul/runtime/mod.rs +++ b/compiler/src/yul/runtime/mod.rs @@ -86,7 +86,14 @@ pub fn build(context: &Context, contract: &Spanned) -> Vec>() + .concat(); + + return [std, encoding, decoding, contract_calls, struct_apis].concat(); } panic!("missing contract attributes") diff --git a/compiler/tests/evm_contracts.rs b/compiler/tests/evm_contracts.rs index f571883ad6..f5f6dcd267 100644 --- a/compiler/tests/evm_contracts.rs +++ b/compiler/tests/evm_contracts.rs @@ -955,7 +955,8 @@ fn sized_vals_in_sto() { #[test] fn structs() { with_executor(&|mut executor| { - let harness = deploy_contract(&mut executor, "structs.fe", "Foo", vec![]); + let harness = deploy_contract(&mut executor, "structs.fe", "Foo", &[]); + harness.test_function(&mut executor, "bar", &[], Some(&uint_token(500))); }); } diff --git a/compiler/tests/fixtures/structs.fe b/compiler/tests/fixtures/structs.fe index 912f9a4ca1..b9e53ba426 100644 --- a/compiler/tests/fixtures/structs.fe +++ b/compiler/tests/fixtures/structs.fe @@ -2,6 +2,12 @@ struct House: price: u256 size: u256 +contract Foo: -contract City: - pub house: House + pub def bar() -> u256: + # Consider requiring KW syntax to instantiate structs + building: House = House(300, 500) + assert building.size == 500 + assert building.price == 300 + return building.size + \ No newline at end of file diff --git a/output/Foo/Foo_ir.yul b/output/Foo/Foo_ir.yul new file mode 100644 index 0000000000..a1d0e501a6 --- /dev/null +++ b/output/Foo/Foo_ir.yul @@ -0,0 +1,192 @@ +object \"Contract\" { + code { + let size := datasize(\"runtime\") datacopy(0, dataoffset(\"runtime\"), size) return(0, size) + } + object \"runtime\" { + code { + function $$bar() -> return_val { + let $building := struct_House_new(300, 500) if iszero(eq(mloadn(struct_House_get_size($building), 32), 500)) { + revert(0, 0) + } + if iszero(eq(mloadn(struct_House_get_price($building), 32), 300)) { + revert(0, 0) + } + { + return_val := mloadn(struct_House_get_size($building), 32) leave + } + + } + function avail() -> ptr { + ptr := mload(0x00) if eq(ptr, 0x00) { + ptr := 0x20 + } + + } + function alloc(size) -> ptr { + ptr := mload(0x00) if eq(ptr, 0x00) { + ptr := 0x20 + } + mstore(0x00, add(ptr, size)) + } + function alloc_mstoren(val, size) -> ptr { + ptr := alloc(size) mstoren(ptr, val, size) + } + function free(ptr) { + mstore(0x00, ptr) + } + function ccopym(cptr, size) -> mptr { + mptr := alloc(size) calldatacopy(mptr, cptr, size) + } + function load_data_string(code_ptr, size) -> mptr { + mptr := alloc(32) mstore(mptr, size) let content_ptr := alloc(size) datacopy(content_ptr, code_ptr, size) + } + function encode_dummy_tuple(val_1, val_2) -> mptr { + mptr := alloc(32) mstore(mptr, val_1) let second_ptr := alloc(32) mstore(second_ptr, val_2) + } + function mcopys(mptr, sptr, size) { + let offset := 0 for { + + } + lt(add(offset, 32), size) { + + } + { + let _mptr := add(mptr, offset) let _sptr := add(sptr, offset) sstore(_sptr, mload(_mptr)) offset := add(offset, 32) + } + let rem := sub(size, offset) if gt(rem, 0) { + let _mptr := add(mptr, offset) let _sptr := add(sptr, offset) sstoren(_sptr, mloadn(_mptr, rem), rem) + } + + } + function scopym(sptr, size) -> mptr { + mptr := alloc(size) let offset := 0 for { + + } + lt(add(offset, 32), size) { + + } + { + let _mptr := add(mptr, offset) let _sptr := add(sptr, offset) mstore(_mptr, sload(_sptr)) offset := add(offset, 32) + } + let rem := sub(size, offset) if gt(rem, 0) { + let _mptr := add(mptr, offset) let _sptr := add(sptr, offset) mstoren(_mptr, sloadn(_sptr, rem), rem) + } + + } + function mcopym(ptr1, size) -> ptr2 { + ptr2 := alloc(size) let offset := 0 for { + + } + lt(add(offset, 32), size) { + + } + { + let _ptr1 := add(ptr1, offset) let _ptr2 := add(ptr2, offset) mstore(_ptr2, mload(_ptr1)) offset := add(offset, 32) + } + let rem := sub(size, offset) if gt(rem, 0) { + let _ptr1 := add(ptr1, offset) let _ptr2 := add(ptr2, offset) mstoren(_ptr2, mloadn(_ptr1, rem), rem) + } + + } + function scopys(ptr1, ptr2, size) { + let offset := 0 for { + + } + lt(add(offset, 32), size) { + + } + { + let _ptr1 := add(ptr1, offset) let _ptr2 := add(ptr2, offset) sstore(_ptr2, sload(_ptr1)) offset := add(offset, 32) + } + let rem := sub(size, offset) if gt(rem, 0) { + let _ptr1 := add(ptr1, offset) let _ptr2 := add(ptr2, offset) sstoren(_ptr2, sloadn(_ptr1, rem), rem) + } + + } + function mloadn(ptr, size) -> val { + val := shr(sub(256, mul(8, size)), mload(ptr)) + } + function sloadn(ptr, size) -> val { + val := shr(sub(256, mul(8, size)), sload(ptr)) + } + function cloadn(ptr, size) -> val { + val := shr(sub(256, mul(8, size)), calldataload(ptr)) + } + function mstoren(ptr, val, size) { + let size_bits := mul(8, size) let left := shl(sub(256, size_bits), val) let right := shr(size_bits, mload(add(ptr, size))) mstore(ptr, or(left, right)) + } + function sstoren(ptr, val, size) { + let size_bits := mul(8, size) let left := shl(sub(256, size_bits), val) let right := shr(size_bits, sload(add(ptr, size))) sstore(ptr, or(left, right)) + } + function dualkeccak256(a, b) -> return_val { + let ptr := avail() mstore(ptr, a) mstore(add(ptr, 32), b) return_val := keccak256(ptr, 64) + } + function ceil32(n) -> return_val { + return_val := mul(div(add(n, 31), 32), 32) + } + function ternary(test, if_expr, else_expr) -> result { + switch test case 1 { + result := if_expr + } + case 0 { + result := else_expr + } + + } + function abi_unpack(mptr, array_size, inner_data_size) { + for { + let i := 0 + } + lt(i, array_size) { + i := add(i, 1) + } + { + let val_ptr := add(mptr, mul(i, inner_data_size)) let val := mloadn(val_ptr, inner_data_size) pop(alloc_mstoren(val, 32)) + } + + } + function abi_pack_calldata(mptr, array_size, inner_data_size) -> packed_ptr { + packed_ptr := avail() for { + let i := 0 + } + lt(i, array_size) { + i := add(i, 1) + } + { + let val_ptr := add(mptr, mul(i, 32)) let val := calldataload(val_ptr) pop(alloc_mstoren(val, inner_data_size)) + } + + } + function abi_pack_mem(mptr, array_size, inner_data_size) -> packed_ptr { + packed_ptr := avail() for { + let i := 0 + } + lt(i, array_size) { + i := add(i, 1) + } + { + let val_ptr := add(mptr, mul(i, 32)) let val := mload(val_ptr) pop(alloc_mstoren(val, inner_data_size)) + } + + } + function abi_encode_uint256(val_0) -> ptr { + ptr := avail() pop(alloc_mstoren(val_0, 32)) + } + function struct_House_new(price, size) -> return_val { + return_val := alloc(32) mstore(return_val, price) let size_ptr := alloc(32) mstore(size_ptr, size) + } + function struct_House_get_price(ptr) -> return_val { + return_val := add(ptr, 0) + } + function struct_House_get_size(ptr) -> return_val { + return_val := add(ptr, 32) + } + switch cloadn(0, 4) case 0xfebb0f7e { + let raw_return := $$bar() return(abi_encode_uint256(raw_return), add(32, 0)) + } + + } + + } + +}