Эх сурвалжийг харах

Fixed size array in storage

Signed-off-by: Sean Young <sean@mess.org>
Sean Young 4 жил өмнө
parent
commit
12e3de0750

+ 73 - 42
src/codegen/expression.rs

@@ -1510,56 +1510,87 @@ fn array_subscript(
 
     if let Type::StorageRef(ty) = array_ty {
         let elem_ty = ty.storage_array_elem();
-        let elem_size = elem_ty.storage_slots(ns);
-        let slot_ty = Type::Uint(256);
-
-        if let Ok(array_length) = eval_const_number(&array_length, Some(contract_no), ns) {
-            if array_length.1.mul(elem_size.clone()).to_u64().is_some() {
-                // we need to calculate the storage offset. If this can be done with 64 bit
-                // arithmetic it will be much more efficient on wasm
-                return Expression::Add(
+        let slot_ty = ns.storage_type();
+
+        if ns.target == Target::Solana {
+            let elem_size = elem_ty.deref_any().size_of(ns);
+
+            Expression::Add(
+                *loc,
+                elem_ty,
+                Box::new(array),
+                Box::new(Expression::Multiply(
                     *loc,
-                    elem_ty,
-                    Box::new(array),
-                    Box::new(Expression::ZeroExt(
+                    slot_ty.clone(),
+                    Box::new(
+                        cast(
+                            &index_loc,
+                            Expression::Variable(index_loc, coerced_ty, pos),
+                            &slot_ty,
+                            false,
+                            ns,
+                            &mut Vec::new(),
+                        )
+                        .unwrap(),
+                    ),
+                    Box::new(Expression::NumberLiteral(*loc, slot_ty, elem_size)),
+                )),
+            )
+        } else {
+            let elem_size = elem_ty.storage_slots(ns);
+
+            if let Ok(array_length) = eval_const_number(&array_length, Some(contract_no), ns) {
+                if array_length.1.mul(elem_size.clone()).to_u64().is_some() {
+                    // we need to calculate the storage offset. If this can be done with 64 bit
+                    // arithmetic it will be much more efficient on wasm
+                    return Expression::Add(
                         *loc,
-                        slot_ty,
-                        Box::new(Expression::Multiply(
+                        elem_ty,
+                        Box::new(array),
+                        Box::new(Expression::ZeroExt(
                             *loc,
-                            Type::Uint(64),
-                            Box::new(
-                                cast(
-                                    &index_loc,
-                                    Expression::Variable(index_loc, coerced_ty, pos),
-                                    &Type::Uint(64),
-                                    false,
-                                    ns,
-                                    &mut Vec::new(),
-                                )
-                                .unwrap(),
-                            ),
-                            Box::new(Expression::NumberLiteral(*loc, Type::Uint(64), elem_size)),
+                            slot_ty,
+                            Box::new(Expression::Multiply(
+                                *loc,
+                                Type::Uint(64),
+                                Box::new(
+                                    cast(
+                                        &index_loc,
+                                        Expression::Variable(index_loc, coerced_ty, pos),
+                                        &Type::Uint(64),
+                                        false,
+                                        ns,
+                                        &mut Vec::new(),
+                                    )
+                                    .unwrap(),
+                                ),
+                                Box::new(Expression::NumberLiteral(
+                                    *loc,
+                                    Type::Uint(64),
+                                    elem_size,
+                                )),
+                            )),
                         )),
-                    )),
-                );
+                    );
+                }
             }
-        }
 
-        array_offset(
-            loc,
-            array,
-            cast(
-                &index_loc,
-                Expression::Variable(index_loc, coerced_ty, pos),
-                &ns.storage_type(),
-                false,
+            array_offset(
+                loc,
+                array,
+                cast(
+                    &index_loc,
+                    Expression::Variable(index_loc, coerced_ty, pos),
+                    &ns.storage_type(),
+                    false,
+                    ns,
+                    &mut Vec::new(),
+                )
+                .unwrap(),
+                elem_ty,
                 ns,
-                &mut Vec::new(),
             )
-            .unwrap(),
-            elem_ty,
-            ns,
-        )
+        }
     } else {
         match array_ty.deref_memory() {
             Type::Bytes(array_length) => {

+ 157 - 0
src/emit/solana.rs

@@ -12,6 +12,7 @@ use num_traits::ToPrimitive;
 use tiny_keccak::{Hasher, Keccak};
 
 use super::ethabiencoder;
+use super::loop_builder::LoopBuilder;
 use super::{Contract, ReturnCode, TargetRuntime, Variable};
 
 pub struct SolanaTarget {
@@ -430,6 +431,40 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
             let new_offset = contract.context.i32_type().const_zero();
 
             contract.builder.build_store(offset_ptr, new_offset);
+        } else if let ast::Type::Array(elem_ty, dim) = ty {
+            let dim = dim[0].as_ref().unwrap().to_u64().unwrap();
+            let elem_size = elem_ty.size_of(contract.ns).to_u64().unwrap();
+
+            // loop over the array
+            let mut builder = LoopBuilder::new(contract, function);
+
+            // we need a phi for the offset
+            let offset_phi =
+                builder.add_loop_phi(contract, "offset", slot.get_type(), (*slot).into());
+
+            let _ = builder.over(
+                contract,
+                contract.context.i64_type().const_zero(),
+                contract.context.i64_type().const_int(dim, false),
+            );
+
+            let mut offset_val = offset_phi.into_int_value();
+
+            let elem_ty = ty.array_deref();
+
+            self.storage_delete(contract, &elem_ty.deref_any(), &mut offset_val, function);
+
+            offset_val = contract.builder.build_int_add(
+                offset_val,
+                contract.context.i32_type().const_int(elem_size, false),
+                "new_offset",
+            );
+
+            // set the offset for the next iteration of the loop
+            builder.set_loop_phi_value(contract, "offset", offset_val.into());
+
+            // done
+            builder.finish(contract);
         } else if let ast::Type::Struct(struct_no) = ty {
             for (i, field) in contract.ns.structs[*struct_no].fields.iter().enumerate() {
                 let field_offset = contract.ns.structs[*struct_no].offsets[i].to_u64().unwrap();
@@ -1103,6 +1138,78 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
                 contract.builder.build_store(elem, val);
             }
 
+            dest.into()
+        } else if let ast::Type::Array(elem_ty, dim) = ty {
+            let llvm_ty = contract.llvm_type(ty.deref_any());
+            // LLVMSizeOf() produces an i64 and malloc takes i32
+            let size = contract.builder.build_int_truncate(
+                llvm_ty.size_of().unwrap(),
+                contract.context.i32_type(),
+                "size_of",
+            );
+
+            let new = contract
+                .builder
+                .build_call(
+                    contract.module.get_function("__malloc").unwrap(),
+                    &[size.into()],
+                    "",
+                )
+                .try_as_basic_value()
+                .left()
+                .unwrap()
+                .into_pointer_value();
+
+            let dest = contract.builder.build_pointer_cast(
+                new,
+                llvm_ty.ptr_type(AddressSpace::Generic),
+                "dest",
+            );
+
+            let dim = dim[0].as_ref().unwrap().to_u64().unwrap();
+            let elem_size = elem_ty.size_of(contract.ns).to_u64().unwrap();
+
+            // loop over the array
+            let mut builder = LoopBuilder::new(contract, function);
+
+            // we need a phi for the offset
+            let offset_phi =
+                builder.add_loop_phi(contract, "offset", slot.get_type(), (*slot).into());
+
+            let index = builder.over(
+                contract,
+                contract.context.i64_type().const_zero(),
+                contract.context.i64_type().const_int(dim, false),
+            );
+
+            let elem = unsafe {
+                contract.builder.build_gep(
+                    dest,
+                    &[contract.context.i64_type().const_zero(), index],
+                    "array_member",
+                )
+            };
+            let elem_ty = ty.array_deref();
+
+            let mut offset_val = offset_phi.into_int_value();
+
+            let val =
+                self.storage_load(contract, &elem_ty.deref_memory(), &mut offset_val, function);
+
+            contract.builder.build_store(elem, val);
+
+            offset_val = contract.builder.build_int_add(
+                offset_val,
+                contract.context.i32_type().const_int(elem_size, false),
+                "new_offset",
+            );
+
+            // set the offset for the next iteration of the loop
+            builder.set_loop_phi_value(contract, "offset", offset_val.into());
+
+            // done
+            builder.finish(contract);
+
             dest.into()
         } else {
             contract.builder.build_load(
@@ -1269,7 +1376,57 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
                 ],
                 "copied",
             );
+        } else if let ast::Type::Array(elem_ty, dim) = ty {
+            // FIXME call delete if pointer null
+            let dim = dim[0].as_ref().unwrap().to_u64().unwrap();
+            let elem_size = elem_ty.size_of(contract.ns).to_u64().unwrap();
+
+            // loop over the array
+            let mut builder = LoopBuilder::new(contract, function);
+
+            // we need a phi for the offset
+            let offset_phi =
+                builder.add_loop_phi(contract, "offset", slot.get_type(), (*slot).into());
+
+            let index = builder.over(
+                contract,
+                contract.context.i64_type().const_zero(),
+                contract.context.i64_type().const_int(dim, false),
+            );
+
+            let elem = unsafe {
+                contract.builder.build_gep(
+                    val.into_pointer_value(),
+                    &[contract.context.i64_type().const_zero(), index],
+                    "array_member",
+                )
+            };
+
+            let mut offset_val = offset_phi.into_int_value();
+
+            let elem_ty = ty.array_deref();
+
+            self.storage_store(
+                contract,
+                &elem_ty.deref_any(),
+                &mut offset_val,
+                contract.builder.build_load(elem, "array_elem"),
+                function,
+            );
+
+            offset_val = contract.builder.build_int_add(
+                offset_val,
+                contract.context.i32_type().const_int(elem_size, false),
+                "new_offset",
+            );
+
+            // set the offset for the next iteration of the loop
+            builder.set_loop_phi_value(contract, "offset", offset_val.into());
+
+            // done
+            builder.finish(contract);
         } else if let ast::Type::Struct(struct_no) = ty {
+            // FIXME call delete if pointer null
             for (i, field) in contract.ns.structs[*struct_no].fields.iter().enumerate() {
                 let field_offset = contract.ns.structs[*struct_no].offsets[i].to_u64().unwrap();
 

+ 207 - 0
tests/solana_tests/arrays.rs

@@ -339,3 +339,210 @@ fn dynamic_array_dynamic_elements() {
         ]
     );
 }
+
+#[test]
+fn fixed_array_fixed_elements_storage() {
+    let mut vm = build_solidity(
+        r#"
+        contract foo {
+            int64[4] store;
+
+            function set_elem(uint index, int64 val) public {
+                store[index] = val;
+            }
+
+            function get_elem(uint index) public returns (int64) {
+                return store[index];
+            }
+
+            function set(int64[4] x) public {
+                store = x;
+            }
+
+            function get() public returns (int64[4]) {
+                return store;
+            }
+
+            function del() public {
+                delete store;
+            }
+        }"#,
+    );
+
+    vm.constructor(&[]);
+
+    vm.function(
+        "set_elem",
+        &[
+            Token::Uint(ethereum_types::U256::from(2)),
+            Token::Int(ethereum_types::U256::from(12123123)),
+        ],
+    );
+
+    vm.function(
+        "set_elem",
+        &[
+            Token::Uint(ethereum_types::U256::from(3)),
+            Token::Int(ethereum_types::U256::from(123456789)),
+        ],
+    );
+
+    let returns = vm.function("get_elem", &[Token::Uint(ethereum_types::U256::from(2))]);
+
+    assert_eq!(
+        returns,
+        vec![Token::Int(ethereum_types::U256::from(12123123)),],
+    );
+
+    let returns = vm.function("get", &[]);
+
+    assert_eq!(
+        returns,
+        vec![Token::FixedArray(vec![
+            Token::Int(ethereum_types::U256::from(0)),
+            Token::Int(ethereum_types::U256::from(0)),
+            Token::Int(ethereum_types::U256::from(12123123)),
+            Token::Int(ethereum_types::U256::from(123456789)),
+        ]),],
+    );
+
+    vm.function(
+        "set",
+        &[Token::FixedArray(vec![
+            Token::Int(ethereum_types::U256::from(1)),
+            Token::Int(ethereum_types::U256::from(2)),
+            Token::Int(ethereum_types::U256::from(3)),
+            Token::Int(ethereum_types::U256::from(4)),
+        ])],
+    );
+
+    let returns = vm.function("get", &[]);
+
+    assert_eq!(
+        returns,
+        vec![Token::FixedArray(vec![
+            Token::Int(ethereum_types::U256::from(1)),
+            Token::Int(ethereum_types::U256::from(2)),
+            Token::Int(ethereum_types::U256::from(3)),
+            Token::Int(ethereum_types::U256::from(4)),
+        ]),],
+    );
+
+    vm.function("del", &[]);
+
+    let returns = vm.function("get", &[]);
+
+    assert_eq!(
+        returns,
+        vec![Token::FixedArray(vec![
+            Token::Int(ethereum_types::U256::from(0)),
+            Token::Int(ethereum_types::U256::from(0)),
+            Token::Int(ethereum_types::U256::from(0)),
+            Token::Int(ethereum_types::U256::from(0)),
+        ]),],
+    );
+}
+
+#[test]
+fn fixed_array_dynamic_elements_storage() {
+    let mut vm = build_solidity(
+        r#"
+        contract foo {
+            string[4] store;
+
+            function set_elem(uint index, string val) public {
+                store[index] = val;
+            }
+
+            function get_elem(uint index) public returns (string) {
+                return store[index];
+            }
+
+            function set(string[4] x) public {
+                store = x;
+            }
+
+            function get() public returns (string[4]) {
+                return store;
+            }
+
+            function del() public {
+                delete store;
+            }
+        }"#,
+    );
+
+    vm.constructor(&[]);
+
+    vm.function(
+        "set_elem",
+        &[
+            Token::Uint(ethereum_types::U256::from(2)),
+            Token::String(String::from("abcd")),
+        ],
+    );
+
+    vm.function(
+        "set_elem",
+        &[
+            Token::Uint(ethereum_types::U256::from(3)),
+            Token::String(String::from(
+                "you can lead a horse to water but you can’t make him drink",
+            )),
+        ],
+    );
+
+    let returns = vm.function("get_elem", &[Token::Uint(ethereum_types::U256::from(2))]);
+
+    assert_eq!(returns, vec![Token::String(String::from("abcd"))]);
+
+    let returns = vm.function("get", &[]);
+
+    assert_eq!(
+        returns,
+        vec![Token::FixedArray(vec![
+            Token::String(String::from("")),
+            Token::String(String::from("")),
+            Token::String(String::from("abcd")),
+            Token::String(String::from(
+                "you can lead a horse to water but you can’t make him drink"
+            )),
+        ]),],
+    );
+
+    vm.function(
+        "set",
+        &[Token::FixedArray(vec![
+            Token::String(String::from("a")),
+            Token::String(String::from("b")),
+            Token::String(String::from("c")),
+            Token::String(String::from("d")),
+        ])],
+    );
+
+    let returns = vm.function("get", &[]);
+
+    assert_eq!(
+        returns,
+        vec![Token::FixedArray(vec![
+            Token::String(String::from("a")),
+            Token::String(String::from("b")),
+            Token::String(String::from("c")),
+            Token::String(String::from("d")),
+        ]),],
+    );
+
+    vm.function("del", &[]);
+
+    let returns = vm.function("get", &[]);
+
+    assert_eq!(
+        returns,
+        vec![Token::FixedArray(vec![
+            Token::String(String::from("")),
+            Token::String(String::from("")),
+            Token::String(String::from("")),
+            Token::String(String::from("")),
+        ]),],
+    );
+}