From 9090fb7a661aee0860c73d1989873d721055a7bb Mon Sep 17 00:00:00 2001 From: Grant Wuerker Date: Wed, 9 Dec 2020 16:40:19 -0700 Subject: [PATCH] Memory and storage copying. notes --- compiler/src/yul/abi/functions.rs | 8 +- compiler/src/yul/mappers/assignments.rs | 22 +- compiler/src/yul/mappers/expressions.rs | 61 ++-- compiler/src/yul/operations.rs | 52 ++-- compiler/src/yul/runtime/functions.rs | 101 +++++-- compiler/tests/compile_errors.rs | 3 +- compiler/tests/evm_contracts.rs | 67 +++++ .../tests/fixtures/address_bytes10_map.fe | 2 +- .../fixtures/compile_errors/needs_mem_copy.fe | 5 + .../tests/fixtures/data_copying_stress.fe | 57 ++++ compiler/tests/fixtures/erc20_token.fe | 4 +- compiler/tests/fixtures/guest_book.fe | 2 +- compiler/tests/fixtures/sized_vals_in_sto.fe | 10 +- newsfragments/155.feature.md | 12 + semantics/src/builtins.rs | 5 + semantics/src/errors.rs | 9 + semantics/src/lib.rs | 76 +++-- semantics/src/namespace/types.rs | 1 + semantics/src/traversal/assignments.rs | 11 + semantics/src/traversal/expressions.rs | 278 +++++++++++------- semantics/src/traversal/functions.rs | 5 +- semantics/tests/analysis.rs | 13 +- semantics/tests/fixtures/guest_book.fe | 2 +- 23 files changed, 584 insertions(+), 222 deletions(-) create mode 100644 compiler/tests/fixtures/compile_errors/needs_mem_copy.fe create mode 100644 compiler/tests/fixtures/data_copying_stress.fe create mode 100644 newsfragments/155.feature.md create mode 100644 semantics/src/builtins.rs diff --git a/compiler/src/yul/abi/functions.rs b/compiler/src/yul/abi/functions.rs index 2d6fb89a56..00e3b4da35 100644 --- a/compiler/src/yul/abi/functions.rs +++ b/compiler/src/yul/abi/functions.rs @@ -195,7 +195,9 @@ fn decode_array( let total_size = literal_expression! { (total_size) }; match location { - AbiDecodeLocation::Calldata => expression! { ccopy(head_ptr, [total_size]) }, + AbiDecodeLocation::Calldata => { + expression! { ccopym(head_ptr, [total_size]) } + } AbiDecodeLocation::Memory => expression! { head_ptr }, } } @@ -206,7 +208,7 @@ fn decode_array( let data_size = literal_expression! { (data_size) }; let total_size = expression! { add(32, (mul([array_size], [data_size]))) }; - expression! { ccopy([data_ptr], [total_size]) } + expression! { ccopym([data_ptr], [total_size]) } } AbiDecodeLocation::Memory => { expression! { add(start, (mload(head_ptr))) } @@ -251,7 +253,7 @@ mod tests { fn test_decode_string_calldata() { assert_eq!( decode(FeString { max_size: 100 }, AbiDecodeLocation::Calldata).to_string(), - "function abi_decode_string100_calldata(start, head_ptr) -> val { val := ccopy(add(start, calldataload(head_ptr)), add(32, mul(calldataload(add(start, calldataload(head_ptr))), 1))) }" + "function abi_decode_string100_calldata(start, head_ptr) -> val { val := ccopym(add(start, calldataload(head_ptr)), add(32, mul(calldataload(add(start, calldataload(head_ptr))), 1))) }" ) } diff --git a/compiler/src/yul/mappers/assignments.rs b/compiler/src/yul/mappers/assignments.rs index 39fa366356..8a714758e4 100644 --- a/compiler/src/yul/mappers/assignments.rs +++ b/compiler/src/yul/mappers/assignments.rs @@ -38,32 +38,34 @@ pub fn assign( target_attributes.final_location(), ) { (Location::Memory, Location::Storage { .. }) => { - operations::mem_to_sto(typ, target, value) + operations::mcopys(typ, target, value) } (Location::Memory, Location::Value) => { let target = expr_as_ident(target)?; - let value = operations::mem_to_val(typ, value); + let value = operations::mload(typ, value); statement! { [target] := [value] } } - (Location::Memory, Location::Memory) => unimplemented!("memory copying"), + (Location::Memory, Location::Memory) => { + let target = expr_as_ident(target)?; + let ptr = operations::mcopym(typ, value); + statement! { [target] := [ptr] } + } (Location::Storage { .. }, Location::Storage { .. }) => { - unimplemented!("storage copying") + operations::scopys(typ, target, value) } (Location::Storage { .. }, Location::Value) => { let target = expr_as_ident(target)?; - let value = operations::sto_to_val(typ, value); + let value = operations::sload(typ, value); statement! { [target] := [value] } } (Location::Storage { .. }, Location::Memory) => { - let target = expr_as_ident(target)?; - let mptr = operations::sto_to_mem(typ, value); - statement! { [target] := [mptr] } + unreachable!("raw sto to mem assign") } (Location::Value, Location::Memory) => { - operations::val_to_mem(typ, target, value) + operations::mstore(typ, target, value) } (Location::Value, Location::Storage { .. }) => { - operations::val_to_sto(typ, target, value) + operations::sstore(typ, target, value) } (Location::Value, Location::Value) => { let target = expr_as_ident(target)?; diff --git a/compiler/src/yul/mappers/expressions.rs b/compiler/src/yul/mappers/expressions.rs index 046a6e5ac6..b034a1820e 100644 --- a/compiler/src/yul/mappers/expressions.rs +++ b/compiler/src/yul/mappers/expressions.rs @@ -3,6 +3,7 @@ use crate::yul::operations; use crate::yul::utils; use fe_parser::ast as fe; use fe_parser::span::Spanned; +use fe_semantics::builtins; use fe_semantics::namespace::types::{ FixedSize, Type, @@ -63,9 +64,10 @@ fn move_expression( FixedSize::try_from(typ).map_err(|_| CompileError::static_str("invalid attributes"))?; match (from.clone(), to.clone()) { - (Location::Storage { .. }, Location::Value) => Ok(operations::sto_to_val(typ, val)), - (Location::Memory { .. }, Location::Value) => Ok(operations::mem_to_val(typ, val)), - (Location::Storage { .. }, Location::Memory) => Ok(operations::sto_to_mem(typ, val)), + (Location::Storage { .. }, Location::Value) => Ok(operations::sload(typ, val)), + (Location::Memory, Location::Value) => Ok(operations::mload(typ, val)), + (Location::Memory, Location::Memory) => Ok(operations::mcopym(typ, val)), + (Location::Storage { .. }, Location::Memory) => Ok(operations::scopym(typ, val)), _ => Err(CompileError::str(format!( "invalid expression move: {:?} {:?}", from, to @@ -90,29 +92,30 @@ pub fn expr_call( context: &Context, exp: &Spanned, ) -> Result { - if let (Some(call_type), fe::Expr::Call { args, .. }) = (context.get_call(exp), &exp.node) { - let yul_args: Vec = args - .node - .iter() - .map(|val| call_arg(context, val)) - .collect::>()?; - - return match call_type { - CallType::SelfFunction { name } => { - let func_name = utils::func_name(name); - - Ok(expression! { [func_name]([yul_args...]) }) - } - CallType::TypeConstructor => { - if let Some(first_arg) = yul_args.first() { - Ok(first_arg.to_owned()) - } else { - Err(CompileError::static_str( - "type constructor expected a single parameter", - )) + if let fe::Expr::Call { args, func } = &exp.node { + if let Some(call_type) = context.get_call(func) { + let yul_args: Vec = args + .node + .iter() + .map(|val| call_arg(context, val)) + .collect::>()?; + + return match call_type { + CallType::TypeConstructor { .. } => Ok(yul_args[0].to_owned()), + CallType::SelfAttribute { func_name } => { + let func_name = utils::func_name(func_name); + + Ok(expression! { [func_name]([yul_args...]) }) } - } - }; + CallType::ValueAttribute { .. } => { + if let fe::Expr::Attribute { value, .. } = &func.node { + expr(context, value) + } else { + unreachable!() + } + } + }; + } } unreachable!() @@ -296,8 +299,8 @@ fn expr_attribute( ) -> Result { if let fe::Expr::Attribute { value, attr } = &exp.node { return match expr_name_str(value)? { - "msg" => expr_attribute_msg(attr), - "self" => expr_attribute_self(context, exp), + builtins::MSG => expr_attribute_msg(attr), + builtins::SELF => expr_attribute_self(context, exp), _ => Err(CompileError::static_str("invalid attributes")), }; } @@ -307,7 +310,7 @@ fn expr_attribute( fn expr_attribute_msg(attr: &Spanned<&str>) -> Result { match attr.node { - "sender" => Ok(expression! { caller() }), + builtins::SENDER => Ok(expression! { caller() }), _ => Err(CompileError::static_str("invalid msg attribute name")), } } @@ -476,7 +479,7 @@ mod tests { assert_eq!( result, - "scopy(dualkeccak256(0, mloadn(add($bar_array, mul($index, 20)), 20)), 160)" + "scopym(dualkeccak256(0, mloadn(add($bar_array, mul($index, 20)), 20)), 160)" ); } diff --git a/compiler/src/yul/operations.rs b/compiler/src/yul/operations.rs index ceabc93377..786fe13a1a 100644 --- a/compiler/src/yul/operations.rs +++ b/compiler/src/yul/operations.rs @@ -10,53 +10,57 @@ use yultsur::*; /// Loads a value from storage. /// /// The returned expression evaluates to a 256 bit value. -pub fn sto_to_val(typ: T, sptr: yul::Expression) -> yul::Expression { +pub fn sload(typ: T, sptr: yul::Expression) -> yul::Expression { let size = literal_expression! { (typ.size()) }; expression! { sloadn([sptr], [size]) } } -/// Copies a segment of storage into memory. -/// -/// The returned expression evaluates to a memory pointer. -pub fn sto_to_mem(typ: T, sptr: yul::Expression) -> yul::Expression { - let size = literal_expression! { (typ.size()) }; - expression! { scopy([sptr], [size]) } -} - /// Stores a 256 bit value in storage. -pub fn val_to_sto( - typ: T, - sptr: yul::Expression, - value: yul::Expression, -) -> yul::Statement { +pub fn sstore(typ: T, sptr: yul::Expression, value: yul::Expression) -> yul::Statement { let size = literal_expression! { (typ.size()) }; statement! { sstoren([sptr], [value], [size]) } } /// Stores a 256 bit value in memory. -pub fn val_to_mem( - typ: T, - mptr: yul::Expression, - value: yul::Expression, -) -> yul::Statement { +pub fn mstore(typ: T, mptr: yul::Expression, value: yul::Expression) -> yul::Statement { let size = literal_expression! { (typ.size()) }; statement! { mstoren([mptr], [value], [size]) } } /// Copies a segment of memory into storage. -pub fn mem_to_sto( +pub fn mcopys(typ: T, sptr: yul::Expression, mptr: yul::Expression) -> yul::Statement { + let size = literal_expression! { (typ.size()) }; + statement! { mcopys([mptr], [sptr], [size]) } +} + +/// Copies a segment of storage into memory. +/// +/// The returned expression evaluates to a memory pointer. +pub fn scopym(typ: T, sptr: yul::Expression) -> yul::Expression { + let size = literal_expression! { (typ.size()) }; + expression! { scopym([sptr], [size]) } +} + +/// Copies a segment of storage to another segment of storage. +pub fn scopys( typ: T, - sptr: yul::Expression, - mptr: yul::Expression, + dest_ptr: yul::Expression, + origin_ptr: yul::Expression, ) -> yul::Statement { let size = literal_expression! { (typ.size()) }; - statement! { mcopy([mptr], [sptr], [size]) } + statement! { scopys([origin_ptr], [dest_ptr], [size]) } +} + +/// Copies a segment of memory to another segment of memory. +pub fn mcopym(typ: T, ptr: yul::Expression) -> yul::Expression { + let size = literal_expression! { (typ.size()) }; + expression! { mcopym([ptr], [size]) } } /// Loads a value in memory. /// /// The returned expression evaluates to a 256 bit value. -pub fn mem_to_val(typ: T, mptr: yul::Expression) -> yul::Expression { +pub fn mload(typ: T, mptr: yul::Expression) -> yul::Expression { let size = literal_expression! { (typ.size()) }; expression! { mloadn([mptr], [size]) } } diff --git a/compiler/src/yul/runtime/functions.rs b/compiler/src/yul/runtime/functions.rs index 42e2e24b17..97c041d8f7 100644 --- a/compiler/src/yul/runtime/functions.rs +++ b/compiler/src/yul/runtime/functions.rs @@ -7,9 +7,11 @@ pub fn std() -> Vec { alloc(), alloc_mstoren(), free(), - ccopy(), - mcopy(), - scopy(), + ccopym(), + mcopys(), + scopym(), + mcopym(), + scopys(), mloadn(), sloadn(), cloadn(), @@ -62,9 +64,9 @@ pub fn free() -> yul::Statement { } /// Copy calldata to memory a newly allocated segment of memory. -pub fn ccopy() -> yul::Statement { +pub fn ccopym() -> yul::Statement { function_definition! { - function ccopy(cptr, size) -> mptr { + function ccopym(cptr, size) -> mptr { (mptr := alloc(size)) (calldatacopy(mptr, cptr, size)) } @@ -72,29 +74,94 @@ pub fn ccopy() -> yul::Statement { } /// Copy memory to a given segment of storage. -pub fn mcopy() -> yul::Statement { +pub fn mcopys() -> yul::Statement { function_definition! { - function mcopy(mptr, sptr, size) { - (for {(let i := 0)} (lt(i, size)) {(i := add(i, 1))} + function mcopys(mptr, sptr, size) { + (let offset := 0) + (for { } (lt((add(offset, 32)), size)) { } { - (let _mptr := add(mptr, i)) - (let _sptr := add(sptr, i)) - (sstoren(_sptr, (mloadn(_mptr, 1)), 1)) + (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)) }) } } } /// Copy storage to a newly allocated segment of memory. -pub fn scopy() -> yul::Statement { +pub fn scopym() -> yul::Statement { function_definition! { - function scopy(sptr, size) -> mptr { + function scopym(sptr, size) -> mptr { (mptr := alloc(size)) - (for {(let i := 0)} (lt(i, size)) {(i := add(i, 1))} + (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)) + }) + } + } +} + +/// Copies a segment of storage to another segment of storage. +pub fn scopys() -> yul::Statement { + function_definition! { + function scopys(ptr1, ptr2, size) { + (let offset := 0) + (for { } (lt((add(offset, 32)), size)) { } { - (let _mptr := add(mptr, i)) - (let _sptr := add(sptr, i)) - (mstoren(_mptr, (sloadn(_sptr, 1)), 1)) + (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)) + }) + } + } +} + +/// Copies a segment of memory to another segment of memory. +pub fn mcopym() -> yul::Statement { + function_definition! { + 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)) }) } } diff --git a/compiler/tests/compile_errors.rs b/compiler/tests/compile_errors.rs index 1d5d3ad739..87652e3f8f 100644 --- a/compiler/tests/compile_errors.rs +++ b/compiler/tests/compile_errors.rs @@ -25,7 +25,8 @@ use std::fs; case("return_lt_mixed_types.fe", "TypeError"), case("indexed_event.fe", "MoreThanThreeIndexedParams"), case("unary_minus_on_bool.fe", "TypeError"), - case("type_constructor_from_variable.fe", "NumericLiteralExpected") + case("type_constructor_from_variable.fe", "NumericLiteralExpected"), + case("needs_mem_copy.fe", "CannotMove") )] fn test_compile_errors(fixture_file: &str, expected_error: &str) { let src = fs::read_to_string(format!("tests/fixtures/compile_errors/{}", fixture_file)) diff --git a/compiler/tests/evm_contracts.rs b/compiler/tests/evm_contracts.rs index cec101cd2d..ec45f66c15 100644 --- a/compiler/tests/evm_contracts.rs +++ b/compiler/tests/evm_contracts.rs @@ -908,3 +908,70 @@ fn erc20_token() { ); }); } + +#[test] +fn data_copying_stress() { + with_executor(&|mut executor| { + let harness = deploy_contract(&mut executor, "data_copying_stress.fe", "Foo", vec![]); + + harness.test_function( + &mut executor, + "set_my_vals", + vec![ + string_token("my string"), + string_token("my other string"), + uint_token(26), + uint_token(42), + ], + None, + ); + + harness.test_function(&mut executor, "emit_my_event", vec![], None); + + harness.test_function(&mut executor, "set_to_my_other_vals", vec![], None); + + harness.test_function(&mut executor, "emit_my_event", vec![], None); + + let my_array = u256_array_token(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + let my_mutated_array = u256_array_token(vec![1, 2, 3, 5, 5, 6, 7, 8, 9, 10]); + + harness.test_function( + &mut executor, + "mutate_and_return", + vec![my_array.clone()], + Some(my_mutated_array), + ); + + harness.test_function( + &mut executor, + "clone_and_return", + vec![my_array.clone()], + Some(my_array.clone()), + ); + + harness.test_function( + &mut executor, + "clone_mutate_and_return", + vec![my_array.clone()], + Some(my_array), + ); + + harness.test_function( + &mut executor, + "assign_my_nums_and_return", + vec![], + Some(u256_array_token(vec![42, 26, 0, 1, 255])), + ); + + harness.events_emitted( + executor, + vec![ + ("MyEvent", vec![string_token("my string"), uint_token(26)]), + ( + "MyEvent", + vec![string_token("my other string"), uint_token(42)], + ), + ], + ); + }); +} diff --git a/compiler/tests/fixtures/address_bytes10_map.fe b/compiler/tests/fixtures/address_bytes10_map.fe index a6586ec9b2..699720cf50 100644 --- a/compiler/tests/fixtures/address_bytes10_map.fe +++ b/compiler/tests/fixtures/address_bytes10_map.fe @@ -2,7 +2,7 @@ contract Foo: pub bar: map pub def read_bar(key: address) -> bytes[10]: - return self.bar[key] + return self.bar[key].to_mem() pub def write_bar(key: address, value: bytes[10]): self.bar[key] = value \ No newline at end of file diff --git a/compiler/tests/fixtures/compile_errors/needs_mem_copy.fe b/compiler/tests/fixtures/compile_errors/needs_mem_copy.fe new file mode 100644 index 0000000000..21563e944e --- /dev/null +++ b/compiler/tests/fixtures/compile_errors/needs_mem_copy.fe @@ -0,0 +1,5 @@ +contract Foo: + my_sto_array: u256[10] + + pub def bar(my_array: u256[10]): + my_array = self.my_sto_array \ No newline at end of file diff --git a/compiler/tests/fixtures/data_copying_stress.fe b/compiler/tests/fixtures/data_copying_stress.fe new file mode 100644 index 0000000000..69d3662ef6 --- /dev/null +++ b/compiler/tests/fixtures/data_copying_stress.fe @@ -0,0 +1,57 @@ +contract Foo: + my_string: string42 + my_other_string: string42 + + my_u256: u256 + my_other_u256: u256 + + my_nums: u256[5] + + event MyEvent: + my_string: string42 + my_u256: u256 + + pub def set_my_vals( + my_string: string42, + my_other_string: string42, + my_u256: u256, + my_other_u256: u256 + ): + self.my_string = my_string + self.my_other_string = my_other_string + self.my_u256 = my_u256 + self.my_other_u256 = my_other_u256 + + pub def set_to_my_other_vals(): + self.my_string = self.my_other_string + self.my_u256 = self.my_other_u256 + + pub def mutate_and_return(my_array: u256[10]) -> u256[10]: + my_array[3] = 5 + return my_array + + pub def clone_and_return(my_array: u256[10]) -> u256[10]: + return my_array.clone() + + pub def clone_mutate_and_return(my_array: u256[10]) -> u256[10]: + my_array.clone()[3] = 5 + return my_array + + pub def assign_my_nums_and_return() -> u256[5]: + my_nums_mem: u256[5] + self.my_nums[0] = 42 + self.my_nums[1] = 26 + self.my_nums[2] = 0 + self.my_nums[3] = 1 + self.my_nums[4] = 255 + my_nums_mem = self.my_nums.to_mem() + return my_nums_mem + + pub def emit_my_event(): + self.emit_my_event_internal( + self.my_string.to_mem(), + self.my_u256.to_mem() + ) + + def emit_my_event_internal(some_string: string42, some_u256: u256): + emit MyEvent(some_string, some_u256) diff --git a/compiler/tests/fixtures/erc20_token.fe b/compiler/tests/fixtures/erc20_token.fe index 862b58b097..fe1fcee10d 100644 --- a/compiler/tests/fixtures/erc20_token.fe +++ b/compiler/tests/fixtures/erc20_token.fe @@ -25,10 +25,10 @@ contract ERC20: self._mint(msg.sender, 2600) pub def name() -> string100: - return self._name + return self._name.to_mem() pub def symbol() -> string100: - return self._symbol + return self._symbol.to_mem() pub def decimals() -> u8: return self._decimals diff --git a/compiler/tests/fixtures/guest_book.fe b/compiler/tests/fixtures/guest_book.fe index cc5aa078e2..6b105105bd 100644 --- a/compiler/tests/fixtures/guest_book.fe +++ b/compiler/tests/fixtures/guest_book.fe @@ -12,4 +12,4 @@ contract GuestBook: emit Signed(book_msg=book_msg) pub def get_msg(addr: address) -> BookMsg: - return self.guest_book[addr] \ No newline at end of file + return self.guest_book[addr].to_mem() \ No newline at end of file diff --git a/compiler/tests/fixtures/sized_vals_in_sto.fe b/compiler/tests/fixtures/sized_vals_in_sto.fe index 9108540ec6..6ef6c8f3a5 100644 --- a/compiler/tests/fixtures/sized_vals_in_sto.fe +++ b/compiler/tests/fixtures/sized_vals_in_sto.fe @@ -18,13 +18,17 @@ contract Foo: self.nums = x pub def read_nums() -> u256[42]: - return self.nums + return self.nums.to_mem() pub def write_str(x: string26): self.str = x pub def read_str() -> string26: - return self.str + return self.str.to_mem() pub def emit_event(): - emit MyEvent(self.num, self.nums, self.str) \ No newline at end of file + emit MyEvent( + self.num, + self.nums.to_mem(), + self.str.to_mem() + ) \ No newline at end of file diff --git a/newsfragments/155.feature.md b/newsfragments/155.feature.md new file mode 100644 index 0000000000..4e82b8a70b --- /dev/null +++ b/newsfragments/155.feature.md @@ -0,0 +1,12 @@ +Added builtin copying methods `clone()` and `to_mem()` to reference types. + + +usage: + +``` +# copy a segment of storage into memory and assign the new pointer +my_mem_array = self.my_sto_array.to_mem() + +# copy a segment of memory into another segment of memory and assign the new pointer +my_other_mem_array = my_mem_array.clone() +``` diff --git a/semantics/src/builtins.rs b/semantics/src/builtins.rs new file mode 100644 index 0000000000..c14432aa86 --- /dev/null +++ b/semantics/src/builtins.rs @@ -0,0 +1,5 @@ +pub const SELF: &str = "self"; +pub const SENDER: &str = "sender"; +pub const MSG: &str = "msg"; +pub const CLONE: &str = "clone"; +pub const TO_MEM: &str = "to_mem"; diff --git a/semantics/src/errors.rs b/semantics/src/errors.rs index 22b32ff054..ca6d1c05c2 100644 --- a/semantics/src/errors.rs +++ b/semantics/src/errors.rs @@ -17,6 +17,7 @@ pub enum ErrorKind { NotCallable, NumericLiteralExpected, MoreThanThreeIndexedParams, + WrongNumberOfParams, } #[derive(Debug, PartialEq)] @@ -116,6 +117,14 @@ impl SemanticError { } } + /// Create a new error with kind `WrongNumberOfParams` + pub fn wrong_number_of_params() -> Self { + SemanticError { + kind: ErrorKind::WrongNumberOfParams, + context: vec![], + } + } + /// Maps the error to a new error that contains the given span in its /// context. pub fn with_context(mut self, span: Span) -> Self { diff --git a/semantics/src/lib.rs b/semantics/src/lib.rs index 5f768db0fa..0cc7537f45 100644 --- a/semantics/src/lib.rs +++ b/semantics/src/lib.rs @@ -4,6 +4,7 @@ //! any semantic errors within a given AST and produces a `Context` instance //! that can be used to query contextual information attributed to AST nodes. +pub mod builtins; pub mod errors; pub mod namespace; mod traversal; @@ -37,6 +38,20 @@ pub enum Location { Value, } +impl Location { + /// The expected location of a value with the given type when being + /// assigned, returned, or passed. + pub fn assign_location(typ: Type) -> Result { + match typ { + Type::Base(_) => Ok(Location::Value), + Type::Array(_) => Ok(Location::Memory), + Type::Tuple(_) => Ok(Location::Memory), + Type::String(_) => Ok(Location::Memory), + Type::Map(_) => Err(SemanticError::cannot_move()), + } + } +} + /// Operations that need to be made available during runtime. #[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq)] pub enum RuntimeOperations { @@ -75,30 +90,28 @@ impl ExpressionAttributes { } } - /// Updates the expression attributes with a move to the type's default - /// location. Returns an error for map types. - /// - /// Base types are moved to the stack and reference types are moved to a - /// segment of memory. - pub fn with_default_move(mut self) -> Result { - let move_location = match self.typ { - Type::Base(_) => Location::Value, - Type::Array(_) => Location::Memory, - Type::Tuple(_) => Location::Memory, - Type::String(_) => Location::Memory, - Type::Map(_) => return Err(SemanticError::cannot_move()), - }; - - if self.location != move_location { - self.move_location = Some(move_location) + /// Adds a move to memory, if it is already in memory. + pub fn into_cloned(mut self) -> Result { + if self.location != Location::Memory { + Err(SemanticError::cannot_move()) + } else { + self.move_location = Some(Location::Memory); + Ok(self) } + } - Ok(self) + /// Adds a move to memory, if it is in storage. + pub fn into_cloned_from_sto(mut self) -> Result { + if !matches!(self.location, Location::Storage { .. }) { + Err(SemanticError::cannot_move()) + } else { + self.move_location = Some(Location::Memory); + Ok(self) + } } - /// Updates the expression attributes with a move to the stack. Returns an - /// error if the type cannot be moved there. - pub fn with_value_move(mut self) -> Result { + /// Adds a move to value, if it is in storage or memory. + pub fn into_loaded(mut self) -> Result { match self.typ { Type::Base(_) => {} _ => return Err(SemanticError::cannot_move()), @@ -111,6 +124,24 @@ impl ExpressionAttributes { Ok(self) } + /// Adds a move (if necessary) to value if it is a base type and ensures + /// reference types are in memory. + pub fn into_assignable(self) -> Result { + let assign_location = Location::assign_location(self.typ.to_owned())?; + + match assign_location { + Location::Value => self.into_loaded(), + Location::Memory => { + if self.final_location() == Location::Memory { + Ok(self) + } else { + Err(SemanticError::cannot_move()) + } + } + Location::Storage { .. } => unreachable!(), + } + } + /// The final location of an expression after a possible move. pub fn final_location(&self) -> Location { if let Some(location) = self.move_location.clone() { @@ -124,8 +155,9 @@ impl ExpressionAttributes { /// The type of a function call. #[derive(Clone, Debug, PartialEq)] pub enum CallType { - TypeConstructor, - SelfFunction { name: String }, + TypeConstructor { typ: Type }, + SelfAttribute { func_name: String }, + ValueAttribute, } /// Contains contextual information relating to a function definition AST node. diff --git a/semantics/src/namespace/types.rs b/semantics/src/namespace/types.rs index bbf628c173..07f91a47cb 100644 --- a/semantics/src/namespace/types.rs +++ b/semantics/src/namespace/types.rs @@ -154,6 +154,7 @@ impl Integer { } impl Type { + /// Returns true if the type is a tuple with 0 elements. pub fn is_empty_tuple(&self) -> bool { if let Type::Tuple(tuple) = &self { return tuple.is_empty(); diff --git a/semantics/src/traversal/assignments.rs b/semantics/src/traversal/assignments.rs index 86a6ada635..6ff3fd0586 100644 --- a/semantics/src/traversal/assignments.rs +++ b/semantics/src/traversal/assignments.rs @@ -5,6 +5,7 @@ use crate::namespace::scopes::{ }; use crate::traversal::expressions; use crate::Context; +use crate::Location; use fe_parser::ast as fe; use fe_parser::span::Spanned; use std::rc::Rc; @@ -32,6 +33,16 @@ pub fn assign( return Err(SemanticError::type_error()); } + if matches!( + ( + value_attributes.final_location(), + target_attributes.location + ), + (Location::Storage { .. }, Location::Memory) + ) { + return Err(SemanticError::cannot_move()); + } + return Ok(()); } } diff --git a/semantics/src/traversal/expressions.rs b/semantics/src/traversal/expressions.rs index 389c23f801..e1e026c221 100644 --- a/semantics/src/traversal/expressions.rs +++ b/semantics/src/traversal/expressions.rs @@ -5,6 +5,7 @@ use crate::namespace::scopes::{ Shared, }; +use crate::builtins; use crate::namespace::operations; use crate::namespace::types::{ Base, @@ -28,8 +29,6 @@ use fe_parser::ast as fe; use fe_parser::span::Spanned; use std::rc::Rc; -const SELF: &str = "self"; - /// Gather context information for expressions and check for type errors. pub fn expr( scope: Shared, @@ -63,13 +62,13 @@ pub fn expr( /// Gather context information for expressions and check for type errors. /// -/// Attributes a value move to the expression. -pub fn expr_with_value_move( +/// Also ensures that the expression is on the stack. +pub fn value_expr( scope: Shared, context: Shared, exp: &Spanned, ) -> Result { - let attributes = expr(Rc::clone(&scope), Rc::clone(&context), exp)?.with_value_move()?; + let attributes = expr(Rc::clone(&scope), Rc::clone(&context), exp)?.into_loaded()?; context.borrow_mut().add_expression(exp, attributes.clone()); @@ -78,13 +77,13 @@ pub fn expr_with_value_move( /// Gather context information for expressions and check for type errors. /// -/// Attributes the default move to the expression. -pub fn expr_with_default_move( +/// Also ensures that the expression is in the type's assigment location. +pub fn assignable_expr( scope: Shared, context: Shared, exp: &Spanned, ) -> Result { - let attributes = expr(Rc::clone(&scope), Rc::clone(&context), exp)?.with_default_move()?; + let attributes = expr(Rc::clone(&scope), Rc::clone(&context), exp)?.into_assignable()?; context.borrow_mut().add_expression(exp, attributes.clone()); @@ -126,7 +125,7 @@ pub fn slice_index( ) -> Result { if let fe::Slice::Index(index) = &slice.node { let spanned = spanned_expression(&slice.span, index.as_ref()); - let attributes = expr_with_value_move(scope, Rc::clone(&context), &spanned)?; + let attributes = value_expr(scope, Rc::clone(&context), &spanned)?; return Ok(attributes); } @@ -208,8 +207,8 @@ fn expr_attribute( ) -> Result { if let fe::Expr::Attribute { value, attr } = &exp.node { return match expr_name_str(value)? { - "msg" => expr_attribute_msg(attr), - "self" => expr_attribute_self(scope, attr), + builtins::MSG => expr_attribute_msg(attr), + builtins::SELF => expr_attribute_self(scope, attr), _ => Err(SemanticError::undefined_value()), }; } @@ -219,7 +218,7 @@ fn expr_attribute( fn expr_attribute_msg(attr: &Spanned<&str>) -> Result { match attr.node { - "sender" => Ok(ExpressionAttributes::new( + builtins::SENDER => Ok(ExpressionAttributes::new( Type::Base(Base::Address), Location::Value, )), @@ -248,8 +247,8 @@ fn expr_bin_operation( exp: &Spanned, ) -> Result { if let fe::Expr::BinOperation { left, op: _, right } = &exp.node { - let left_attributes = expr_with_value_move(Rc::clone(&scope), Rc::clone(&context), left)?; - let right_attributes = expr_with_value_move(Rc::clone(&scope), Rc::clone(&context), right)?; + let left_attributes = value_expr(Rc::clone(&scope), Rc::clone(&context), left)?; + let right_attributes = value_expr(Rc::clone(&scope), Rc::clone(&context), right)?; validate_types_equal(&left_attributes, &right_attributes)?; @@ -270,8 +269,7 @@ fn expr_unary_operation( ) -> Result { if let fe::Expr::UnaryOperation { op, operand } = &exp.node { if let fe::UnaryOperator::USub = &op.node { - let operand_attributes = - expr_with_value_move(Rc::clone(&scope), Rc::clone(&context), operand)?; + let operand_attributes = value_expr(Rc::clone(&scope), Rc::clone(&context), operand)?; if !matches!(operand_attributes.typ, Type::Base(Base::Numeric(_))) { return Err(SemanticError::type_error()); @@ -309,7 +307,7 @@ pub fn call_arg( match &arg.node { fe::CallArg::Arg(value) => { let spanned = spanned_expression(&arg.span, value); - let attributes = expr_with_default_move(scope, Rc::clone(&context), &spanned)?; + let attributes = assignable_expr(scope, Rc::clone(&context), &spanned)?; Ok(attributes) } @@ -323,50 +321,35 @@ fn expr_call( exp: &Spanned, ) -> Result { if let fe::Expr::Call { args, func } = &exp.node { - let argument_attributes: Vec = args - .node - .iter() - // Side effect: Performs semantic analysis on each call arg and adds its attributes to - // the context - .map(|argument| call_arg(Rc::clone(&scope), Rc::clone(&context), argument)) - .collect::>()?; - - return match &func.node { - fe::Expr::Attribute { value, attr } => { - let value = expr_name_string(value)?; - - if value == SELF { - context.borrow_mut().add_call( - exp, - CallType::SelfFunction { - name: attr.node.to_string(), - }, - ); - expr_call_self(scope, context, attr, argument_attributes) - } else { - Err(SemanticError::undefined_value()) - } + return match expr_call_type(Rc::clone(&scope), Rc::clone(&context), func)? { + CallType::TypeConstructor { typ } => { + expr_call_type_constructor(scope, context, typ, args) } - fe::Expr::Name(name) => { - if argument_attributes.len() != 1 { - return Err(SemanticError::type_error()); - } - - // Ensure something like u8(x) fails, only literals allowed e.g u8(1) - validate_is_numeric_literal(&args.node[0].node)?; - - context - .borrow_mut() - .add_call(exp, CallType::TypeConstructor); - expr_type_constructor(scope, context, *name) + CallType::SelfAttribute { func_name } => { + expr_call_self_attribute(scope, context, func_name, args) } - _ => Err(SemanticError::not_callable()), + CallType::ValueAttribute => expr_call_value_attribute(scope, context, func, args), }; } unreachable!() } +fn expr_call_type_constructor( + scope: Shared, + context: Shared, + typ: Type, + args: &Spanned>>, +) -> Result { + if args.node.len() != 1 { + return Err(SemanticError::wrong_number_of_params()); + } + + validate_is_numeric_literal(&args.node[0].node)?; + call_arg(scope, context, &args.node[0])?; + Ok(ExpressionAttributes::new(typ, Location::Value)) +} + fn validate_is_numeric_literal(call_arg: &fe::CallArg) -> Result<(), SemanticError> { let is_numeric_literal = if let fe::CallArg::Arg(fe::Expr::UnaryOperation { operand, op: _ }) = call_arg { @@ -382,62 +365,150 @@ fn validate_is_numeric_literal(call_arg: &fe::CallArg) -> Result<(), SemanticErr } } -fn expr_call_self( +fn expr_call_self_attribute( scope: Shared, - _context: Shared, - func_name: &Spanned<&str>, - argument_attributes: Vec, + context: Shared, + func_name: String, + args: &Spanned>>, ) -> Result { - let contract_scope = &scope.borrow().contract_scope(); - let called_func = contract_scope + if let Some(ContractFunctionDef { + is_public: _, + param_types, + return_type, + scope: _, + }) = scope .borrow() - .function_def(func_name.node.to_string()); - - match called_func { - Some(ContractFunctionDef { - is_public: _, - param_types, - return_type, - scope: _, - }) => { - if fixed_sizes_to_types(param_types) - != expression_attributes_to_types(argument_attributes) - { - return Err(SemanticError::type_error()); - } + .contract_scope() + .borrow() + .function_def(func_name) + { + let argument_attributes = args + .node + .iter() + .map(|arg| call_arg(Rc::clone(&scope), Rc::clone(&context), arg)) + .collect::, _>>()?; - Ok(ExpressionAttributes::new( - return_type.into(), - Location::Value, - )) + if fixed_sizes_to_types(param_types) != expression_attributes_to_types(argument_attributes) + { + return Err(SemanticError::type_error()); } - None => Err(SemanticError::undefined_value()), + + let return_location = match &return_type { + FixedSize::Base(_) => Location::Value, + _ => Location::Memory, + }; + Ok(ExpressionAttributes::new( + return_type.into(), + return_location, + )) + } else { + Err(SemanticError::undefined_value()) + } +} + +fn expr_call_value_attribute( + scope: Shared, + context: Shared, + func: &Spanned, + args: &Spanned>>, +) -> Result { + if let fe::Expr::Attribute { value, attr } = &func.node { + let value_attributes = expr(scope, context, value)?; + + // for now all of these function expect 0 arguments + if !args.node.is_empty() { + return Err(SemanticError::wrong_number_of_params()); + } + + return match attr.node { + builtins::CLONE => value_attributes.into_cloned(), + builtins::TO_MEM => value_attributes.into_cloned_from_sto(), + _ => Err(SemanticError::undefined_value()), + }; } + + unreachable!() } -fn expr_type_constructor( +fn expr_call_type( + scope: Shared, + context: Shared, + func: &Spanned, +) -> Result { + let call_type = match &func.node { + fe::Expr::Name(name) => expr_name_call_type(scope, Rc::clone(&context), name), + fe::Expr::Attribute { .. } => expr_attribute_call_type(scope, Rc::clone(&context), func), + _ => Err(SemanticError::not_callable()), + }?; + + context.borrow_mut().add_call(func, call_type.clone()); + Ok(call_type) +} + +fn expr_name_call_type( _scope: Shared, _context: Shared, - func_name: &str, -) -> Result { - let typ = match func_name { - "address" => Type::Base(Base::Address), - "u256" => Type::Base(Base::Numeric(Integer::U256)), - "u128" => Type::Base(Base::Numeric(Integer::U128)), - "u64" => Type::Base(Base::Numeric(Integer::U64)), - "u32" => Type::Base(Base::Numeric(Integer::U32)), - "u16" => Type::Base(Base::Numeric(Integer::U16)), - "u8" => Type::Base(Base::Numeric(Integer::U8)), - "i256" => Type::Base(Base::Numeric(Integer::I256)), - "i128" => Type::Base(Base::Numeric(Integer::I128)), - "i64" => Type::Base(Base::Numeric(Integer::I64)), - "i32" => Type::Base(Base::Numeric(Integer::I32)), - "i16" => Type::Base(Base::Numeric(Integer::I16)), - "i8" => Type::Base(Base::Numeric(Integer::I8)), - _ => return Err(SemanticError::undefined_value()), - }; + name: &str, +) -> Result { + match name { + "address" => Ok(CallType::TypeConstructor { + typ: Type::Base(Base::Address), + }), + "u256" => Ok(CallType::TypeConstructor { + typ: Type::Base(Base::Numeric(Integer::U256)), + }), + "u128" => Ok(CallType::TypeConstructor { + typ: Type::Base(Base::Numeric(Integer::U128)), + }), + "u64" => Ok(CallType::TypeConstructor { + typ: Type::Base(Base::Numeric(Integer::U64)), + }), + "u32" => Ok(CallType::TypeConstructor { + typ: Type::Base(Base::Numeric(Integer::U32)), + }), + "u16" => Ok(CallType::TypeConstructor { + typ: Type::Base(Base::Numeric(Integer::U16)), + }), + "u8" => Ok(CallType::TypeConstructor { + typ: Type::Base(Base::Numeric(Integer::U8)), + }), + "i256" => Ok(CallType::TypeConstructor { + typ: Type::Base(Base::Numeric(Integer::I256)), + }), + "i128" => Ok(CallType::TypeConstructor { + typ: Type::Base(Base::Numeric(Integer::I128)), + }), + "i64" => Ok(CallType::TypeConstructor { + typ: Type::Base(Base::Numeric(Integer::I64)), + }), + "i32" => Ok(CallType::TypeConstructor { + typ: Type::Base(Base::Numeric(Integer::I32)), + }), + "i16" => Ok(CallType::TypeConstructor { + typ: Type::Base(Base::Numeric(Integer::I16)), + }), + "i8" => Ok(CallType::TypeConstructor { + typ: Type::Base(Base::Numeric(Integer::I8)), + }), + _ => Err(SemanticError::undefined_value()), + } +} - Ok(ExpressionAttributes::new(typ, Location::Value)) +fn expr_attribute_call_type( + _scope: Shared, + _context: Shared, + exp: &Spanned, +) -> Result { + if let fe::Expr::Attribute { value, attr } = &exp.node { + return match value.node { + fe::Expr::Name(builtins::SELF) => Ok(CallType::SelfAttribute { + func_name: attr.node.to_string(), + }), + _ => Ok(CallType::ValueAttribute), + }; + } + + unreachable!() } fn expr_comp_operation( @@ -447,8 +518,8 @@ fn expr_comp_operation( ) -> Result { if let fe::Expr::CompOperation { left, op: _, right } = &exp.node { // comparison operands should be moved to the stack - let left_attributes = expr_with_value_move(Rc::clone(&scope), Rc::clone(&context), left)?; - let right_attributes = expr_with_value_move(Rc::clone(&scope), Rc::clone(&context), right)?; + let left_attributes = value_expr(Rc::clone(&scope), Rc::clone(&context), left)?; + let right_attributes = value_expr(Rc::clone(&scope), Rc::clone(&context), right)?; validate_types_equal(&left_attributes, &right_attributes)?; @@ -474,16 +545,15 @@ fn expr_ternary( } = &exp.node { // test attributes should be stored as a value - let test_attributes = expr_with_value_move(Rc::clone(&scope), Rc::clone(&context), test)?; + let test_attributes = value_expr(Rc::clone(&scope), Rc::clone(&context), test)?; // the return expressions should be stored in their default locations // // If, for example, one of the expressions is stored in memory and the other is // stored in storage, it's necessary that we move them to the same location. // This could be memory or the stack, depending on the type. - let if_expr_attributes = - expr_with_default_move(Rc::clone(&scope), Rc::clone(&context), if_expr)?; + let if_expr_attributes = assignable_expr(Rc::clone(&scope), Rc::clone(&context), if_expr)?; let else_expr_attributes = - expr_with_default_move(Rc::clone(&scope), Rc::clone(&context), else_expr)?; + assignable_expr(Rc::clone(&scope), Rc::clone(&context), else_expr)?; // Make sure the `test_attributes` is a boolean type. if Type::Base(Base::Bool) == test_attributes.typ { diff --git a/semantics/src/traversal/functions.rs b/semantics/src/traversal/functions.rs index fdc810f73e..fea07121de 100644 --- a/semantics/src/traversal/functions.rs +++ b/semantics/src/traversal/functions.rs @@ -352,8 +352,7 @@ fn call_arg( match &arg.node { fe::CallArg::Arg(value) => { let spanned = spanned_expression(&arg.span, value); - let _attributes = - expressions::expr_with_default_move(scope, Rc::clone(&context), &spanned)?; + let _attributes = expressions::assignable_expr(scope, Rc::clone(&context), &spanned)?; // TODO: Perform type checking } @@ -373,7 +372,7 @@ fn func_return( ) -> Result<(), SemanticError> { if let fe::FuncStmt::Return { value: Some(value) } = &stmt.node { let attributes = - expressions::expr_with_default_move(Rc::clone(&scope), Rc::clone(&context), value)?; + expressions::assignable_expr(Rc::clone(&scope), Rc::clone(&context), value)?; let host_func_def = scope .borrow() diff --git a/semantics/tests/analysis.rs b/semantics/tests/analysis.rs index a4266faaf8..7a1f0e6fc5 100644 --- a/semantics/tests/analysis.rs +++ b/semantics/tests/analysis.rs @@ -21,6 +21,16 @@ fn addr_val() -> ExpressionAttributes { ExpressionAttributes::new(Type::Base(Base::Address), Location::Value) } +fn bytes_sto() -> ExpressionAttributes { + ExpressionAttributes::new( + Type::Array(Array { + dimension: 100, + inner: Base::Byte, + }), + Location::Storage { nonce: None }, + ) +} + fn bytes_sto_moved() -> ExpressionAttributes { let mut expression = ExpressionAttributes::new( Type::Array(Array { @@ -88,7 +98,8 @@ fn guest_book_analysis() { (210, 218, &bytes_mem()), (249, 257, &bytes_mem()), (322, 337, &addr_bytes_map_sto()), - (322, 343, &bytes_sto_moved()), + (322, 343, &bytes_sto()), + (322, 352, &bytes_sto_moved()), (338, 342, &addr_val()), ] { assert_eq!( diff --git a/semantics/tests/fixtures/guest_book.fe b/semantics/tests/fixtures/guest_book.fe index cc5aa078e2..6b105105bd 100644 --- a/semantics/tests/fixtures/guest_book.fe +++ b/semantics/tests/fixtures/guest_book.fe @@ -12,4 +12,4 @@ contract GuestBook: emit Signed(book_msg=book_msg) pub def get_msg(addr: address) -> BookMsg: - return self.guest_book[addr] \ No newline at end of file + return self.guest_book[addr].to_mem() \ No newline at end of file