diff --git a/sway-lib-std/src/lib.sw b/sway-lib-std/src/lib.sw index 04c30cb9f8f..e3b9be440d2 100644 --- a/sway-lib-std/src/lib.sw +++ b/sway-lib-std/src/lib.sw @@ -26,5 +26,6 @@ dep reentrancy; dep vm/mod; dep flags; dep u128; +dep vec; use core::*; diff --git a/sway-lib-std/src/vec.sw b/sway-lib-std/src/vec.sw new file mode 100644 index 00000000000..e3f9b59c068 --- /dev/null +++ b/sway-lib-std/src/vec.sw @@ -0,0 +1,142 @@ +library vec; + +use ::alloc::{alloc, realloc}; +use ::intrinsics::size_of; +use ::mem::{read, write}; +use ::option::Option; + +struct RawVec { + ptr: u64, + cap: u64, +} + +impl RawVec { + /// Create a new `RawVec` with zero capacity. + fn new() -> Self { + RawVec { + ptr: alloc(0), + cap: 0, + } + } + + /// Creates a `RawVec` (on the heap) with exactly the capacity for a + /// `[T; capacity]`. This is equivalent to calling `RawVec::new` when + /// `capacity` is `0`. + fn with_capacity(capacity: u64) -> Self { + RawVec { + ptr: alloc(capacity * size_of::()), + cap: capacity, + } + } + + /// Gets the pointer of the allocation. + fn ptr(self) -> u64 { + self.ptr + } + + /// Gets the capacity of the allocation. + fn capacity(self) -> u64 { + self.cap + } + + /// Grow the capacity of the vector by doubling its current capacity. The + /// `realloc` function / allocates memory on the heap and copies the data + /// from the old allocation to the new allocation + fn grow(mut self) { + let new_cap = match self.cap { + 0 => 1, _ => 2 * self.cap, + }; + + self.ptr = realloc(self.ptr, self.cap * size_of::(), new_cap * size_of::()); + self.cap = new_cap; + } +} + +/// A contiguous growable array type, written as `Vec`, short for 'vector'. +pub struct Vec { + buf: RawVec, + len: u64, +} + +impl Vec { + /// Constructs a new, empty `Vec`. + /// + /// The vector will not allocate until elements are pushed onto it. + pub fn new() -> Self { + Vec { + buf: ~RawVec::new(), + len: 0, + } + } + + /// Constructs a new, empty `Vec` with the specified capacity. + /// + /// The vector will be able to hold exactly `capacity` elements without + /// reallocating. If `capacity` is 0, the vector will not allocate. + /// + /// It is important to note that although the returned vector has the + /// *capacity* specified, the vector will have a zero *length*. + pub fn with_capacity(capacity: u64) -> Self { + Vec { + buf: ~RawVec::with_capacity(capacity), + len: 0, + } + } + + /// Appends an element to the back of a collection. + pub fn push(mut self, value: T) { + // If there is insufficient capacity, grow the buffer. + if self.len == self.buf.capacity() { + self.buf.grow(); + }; + + // Get a pointer to the end of the buffer, where the new element will + // be inserted. + let end = self.buf.ptr() + self.len * size_of::(); + + // Write `value` at pointer `end` + write(end, value); + + // Increment length. + self.len = self.len + 1; + } + + /// Gets the capacity of the allocation. + pub fn capacity(self) -> u64 { + self.buf.cap + } + + /// Clears the vector, removing all values. + /// + /// Note that this method has no effect on the allocated capacity + /// of the vector. + pub fn clear(mut self) { + self.len = 0; + } + + /// Returns a vector element at `index`, or None if `index` is out of + /// bounds. + pub fn get(self, index: u64) -> Option { + // First check that index is within bounds. + if self.len <= index { + return Option::None::(); + }; + + // Get a pointer to the desired element using `index` + let ptr = self.buf.ptr() + index * size_of::(); + + // Read from `ptr` + Option::Some(read(ptr)) + } + + /// Returns the number of elements in the vector, also referred to + /// as its 'length'. + pub fn len(self) -> u64 { + self.len + } + + /// Returns `true` if the vector contains no elements. + pub fn is_empty(self) -> bool { + self.len == 0 + } +} diff --git a/test/src/e2e_vm_tests/mod.rs b/test/src/e2e_vm_tests/mod.rs index b0d857f803c..b03499a779c 100644 --- a/test/src/e2e_vm_tests/mod.rs +++ b/test/src/e2e_vm_tests/mod.rs @@ -222,6 +222,7 @@ pub fn run(locked: bool, filter_regex: Option) { ), ("should_pass/stdlib/b512_test", ProgramState::Return(1)), // true ("should_pass/stdlib/block_height", ProgramState::Return(1)), // true + ("should_pass/stdlib/vec", ProgramState::Return(1)), // true ( "should_pass/language/trait_override_bug", ProgramState::Return(7), diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/vec/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/vec/Forc.lock new file mode 100644 index 00000000000..3916e287d8f --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/vec/Forc.lock @@ -0,0 +1,14 @@ +[[package]] +name = 'core' +source = 'path+from-root-E8B33641BFB485A7' +dependencies = [] + +[[package]] +name = 'std' +source = 'path+from-root-E8B33641BFB485A7' +dependencies = ['core'] + +[[package]] +name = 'vec' +source = 'root' +dependencies = ['std'] diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/vec/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/vec/Forc.toml new file mode 100644 index 00000000000..8e27b1601f0 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/vec/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "vec" + +[dependencies] +std = { path = "../../../../../../../sway-lib-std" } diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/vec/json_abi_oracle.json b/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/vec/json_abi_oracle.json new file mode 100644 index 00000000000..f0586b752c7 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/vec/json_abi_oracle.json @@ -0,0 +1,14 @@ +[ + { + "inputs": [], + "name": "main", + "outputs": [ + { + "components": null, + "name": "", + "type": "bool" + } + ], + "type": "function" + } +] \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/vec/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/vec/src/main.sw new file mode 100644 index 00000000000..6e580151b97 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/vec/src/main.sw @@ -0,0 +1,709 @@ +script; + +use std::{assert::assert, hash::sha256, option::Option, revert::revert, vec::Vec}; + +struct SimpleStruct { + x: u32, + y: b256, +} + +enum SimpleEnum { + X: (), + Y: b256, + Z: (b256, + b256), +} + +fn main() -> bool { + test_vector_new_u8(); + test_vector_new_b256(); + test_vector_new_struct(); + test_vector_new_enum(); + test_vector_new_tuple(); + test_vector_new_string(); + test_vector_new_array(); + test_vector_with_capacity_u64(); + true +} + +fn test_vector_new_u8() { + let mut vector: Vec = ~Vec::new::(); + + let number0 = 0u8; + let number1 = 1u8; + let number2 = 2u8; + let number3 = 3u8; + let number4 = 4u8; + let number5 = 5u8; + let number6 = 6u8; + let number7 = 7u8; + let number8 = 8u8; + + assert(vector.len() == 0); + assert(vector.capacity() == 0); + assert(vector.is_empty()); + + vector.push(number0); + vector.push(number1); + vector.push(number2); + vector.push(number3); + vector.push(number4); + + assert(vector.len() == 5); + assert(vector.capacity() == 8); + assert(vector.is_empty() == false); + + match vector.get(0) { + Option::Some(val) => assert(val == number0), Option::None => revert(0), + } + + // Push after get + vector.push(number5); + vector.push(number6); + vector.push(number7); + vector.push(number8); + + match vector.get(4) { + Option::Some(val) => assert(val == number4), Option::None => revert(0), + } + + match vector.get(number6) { + Option::Some(val) => assert(val == number6), Option::None => revert(0), + } + + assert(vector.len() == 9); + assert(vector.capacity() == 16); + assert(!vector.is_empty()); + + // Test after capacity change + match vector.get(4) { + Option::Some(val) => assert(val == number4), Option::None => revert(0), + } + + match vector.get(6) { + Option::Some(val) => assert(val == number6), Option::None => revert(0), + } + + vector.clear(); + + // Empty after clear + assert(vector.len() == 0); + assert(vector.capacity() == 16); + assert(vector.is_empty() == true); + + match vector.get(0) { + Option::Some(val) => revert(0), Option::None => (), + } + + // Make sure pushing again after clear() works + vector.push(number0); + vector.push(number1); + vector.push(number2); + vector.push(number3); + vector.push(number4); + + assert(vector.len() == 5); + assert(vector.capacity() == 16); + assert(vector.is_empty() == false); + + match vector.get(4) { + Option::Some(val) => assert(val == number4), Option::None => revert(0), + } + + // Out of bounds access + match vector.get(5) { + Option::Some(val) => revert(0), Option::None => (), + } +} + +fn test_vector_new_b256() { + let mut vector: Vec = ~Vec::new::(); + + let b0 = 0x0000000000000000000000000000000000000000000000000000000000000000; + let b1 = 0x0000000000000000000000000000000000000000000000000000000000000001; + let b2 = 0x0000000000000000000000000000000000000000000000000000000000000002; + let b3 = 0x0000000000000000000000000000000000000000000000000000000000000003; + let b4 = 0x0000000000000000000000000000000000000000000000000000000000000004; + let b5 = 0x0000000000000000000000000000000000000000000000000000000000000005; + let b6 = 0x0000000000000000000000000000000000000000000000000000000000000006; + let b7 = 0x0000000000000000000000000000000000000000000000000000000000000007; + let b8 = 0x0000000000000000000000000000000000000000000000000000000000000008; + + assert(vector.len() == 0); + assert(vector.capacity() == 0); + assert(vector.is_empty() == true); + + vector.push(b0); + vector.push(b1); + vector.push(b2); + vector.push(b3); + vector.push(b4); + + assert(vector.len() == 5); + assert(vector.capacity() == 8); + assert(vector.is_empty() == false); + + match vector.get(0) { + Option::Some(val) => assert(val == b0), Option::None => revert(0), + } + + // Push after get + vector.push(b5); + vector.push(b6); + vector.push(b7); + vector.push(b8); + + match vector.get(4) { + Option::Some(val) => assert(val == b4), Option::None => revert(0), + } + + match vector.get(6) { + Option::Some(val) => assert(val == b6), Option::None => revert(0), + } + + assert(vector.len() == 9); + assert(vector.capacity() == 16); + assert(vector.is_empty() == false); + + // Test after capacity change + match vector.get(4) { + Option::Some(val) => assert(val == b4), Option::None => revert(0), + } + + match vector.get(6) { + Option::Some(val) => assert(val == b6), Option::None => revert(0), + } + + vector.clear(); + + // Empty after clear + assert(vector.len() == 0); + assert(vector.capacity() == 16); + assert(vector.is_empty() == true); + + match vector.get(0) { + Option::Some(val) => revert(0), Option::None => (), + } + + // Make sure pushing again after clear() works + vector.push(b0); + vector.push(b1); + vector.push(b2); + vector.push(b3); + vector.push(b4); + + assert(vector.len() == 5); + assert(vector.capacity() == 16); + assert(vector.is_empty() == false); + + match vector.get(4) { + Option::Some(val) => assert(val == b4), Option::None => revert(0), + } + + // Out of bounds access + match vector.get(5) { + Option::Some(val) => revert(0), Option::None => (), + } +} + +fn test_vector_new_struct() { + let mut vector: Vec = ~Vec::new::(); + + let number0 = 0u32; + let number1 = 1u32; + let number2 = 2u32; + let number3 = 3u32; + let number4 = 4u32; + let number5 = 5u32; + let number6 = 6u32; + let number7 = 7u32; + let number8 = 8u32; + + let b0 = 0x0000000000000000000000000000000000000000000000000000000000000000; + let b1 = 0x0000000000000000000000000000000000000000000000000000000000000001; + let b2 = 0x0000000000000000000000000000000000000000000000000000000000000002; + let b3 = 0x0000000000000000000000000000000000000000000000000000000000000003; + let b4 = 0x0000000000000000000000000000000000000000000000000000000000000004; + let b5 = 0x0000000000000000000000000000000000000000000000000000000000000005; + let b6 = 0x0000000000000000000000000000000000000000000000000000000000000006; + let b7 = 0x0000000000000000000000000000000000000000000000000000000000000007; + let b8 = 0x0000000000000000000000000000000000000000000000000000000000000008; + + assert(vector.len() == 0); + assert(vector.capacity() == 0); + assert(vector.is_empty() == true); + + vector.push(SimpleStruct { + x: number0, y: b0 + }); + vector.push(SimpleStruct { + x: number1, y: b1 + }); + vector.push(SimpleStruct { + x: number2, y: b2 + }); + vector.push(SimpleStruct { + x: number3, y: b3 + }); + vector.push(SimpleStruct { + x: number4, y: b4 + }); + + assert(vector.len() == 5); + assert(vector.capacity() == 8); + assert(vector.is_empty() == false); + + match vector.get(0) { + Option::Some(val) => { + assert(val.x == number0); + assert(val.y == b0); + }, + Option::None => revert(0), + } + + // Push after get + vector.push(SimpleStruct { + x: number5, y: b5 + }); + vector.push(SimpleStruct { + x: number6, y: b6 + }); + vector.push(SimpleStruct { + x: number7, y: b7 + }); + vector.push(SimpleStruct { + x: number8, y: b8 + }); + + match vector.get(4) { + Option::Some(val) => { + assert(val.x == number4); + assert(val.y == b4); + }, + Option::None => revert(0), + } + + match vector.get(6) { + Option::Some(val) => { + assert(val.x == number6); + assert(val.y == b6); + }, + Option::None => revert(0), + } + + assert(vector.len() == 9); + assert(vector.capacity() == 16); + assert(vector.is_empty() == false); + + // Test after capacity change + match vector.get(4) { + Option::Some(val) => { + assert(val.x == number4); + assert(val.y == b4); + }, + Option::None => revert(0), + } + + match vector.get(6) { + Option::Some(val) => { + assert(val.x == number6); + assert(val.y == b6); + }, + Option::None => revert(0), + } + + vector.clear(); + + // Empty after clear + assert(vector.len() == 0); + assert(vector.capacity() == 16); + assert(vector.is_empty() == true); + + match vector.get(0) { + Option::Some(val) => revert(0), Option::None => (), + } + + // Make sure pushing again after clear() works + vector.push(SimpleStruct { + x: number0, y: b0 + }); + vector.push(SimpleStruct { + x: number1, y: b1 + }); + vector.push(SimpleStruct { + x: number2, y: b2 + }); + vector.push(SimpleStruct { + x: number3, y: b3 + }); + vector.push(SimpleStruct { + x: number4, y: b4 + }); + + assert(vector.len() == 5); + assert(vector.capacity() == 16); + assert(vector.is_empty() == false); + + match vector.get(4) { + Option::Some(val) => { + assert(val.x == number4); + assert(val.y == b4); + }, + Option::None => revert(0), + } + + // Out of bounds access + match vector.get(5) { + Option::Some(val) => revert(0), Option::None => (), + } +} + +fn test_vector_new_enum() { + let mut vector: Vec = ~Vec::new::(); + + let b0 = 0x0000000000000000000000000000000000000000000000000000000000000000; + let b1 = 0x0000000000000000000000000000000000000000000000000000000000000001; + let b2 = 0x0000000000000000000000000000000000000000000000000000000000000002; + + assert(vector.len() == 0); + assert(vector.capacity() == 0); + assert(vector.is_empty() == true); + + vector.push(SimpleEnum::Y(b0)); + vector.push(SimpleEnum::X); + vector.push(SimpleEnum::Z((b1, b2))); + + assert(vector.len() == 3); + assert(vector.capacity() == 4); + assert(vector.is_empty() == false); + + match vector.get(0) { + Option::Some(val) => { + match val { + SimpleEnum::Y(b) => assert(b == b0), _ => revert(0), + } + }, + Option::None => revert(0), + } + + match vector.get(1) { + Option::Some(val) => { + match val { + SimpleEnum::X => { + }, + _ => revert(0), + } + }, + Option::None => revert(0), + } + + match vector.get(2) { + Option::Some(val) => { + match val { + SimpleEnum::Z(t) => { + assert(t.0 == b1); + assert(t.1 == b2); + }, + _ => revert(0), + } + }, + Option::None => revert(0), + } +} + +fn test_vector_new_tuple() { + let mut vector: Vec<(u16, b256)> = ~Vec::new::<(u16, b256)>(); + + let number0 = 0u16; + let number1 = 1u16; + let number2 = 2u16; + let number3 = 3u16; + let number4 = 4u16; + let number5 = 5u16; + let number6 = 6u16; + let number7 = 7u16; + let number8 = 8u16; + + let b0 = 0x0000000000000000000000000000000000000000000000000000000000000000; + let b1 = 0x0000000000000000000000000000000000000000000000000000000000000001; + let b2 = 0x0000000000000000000000000000000000000000000000000000000000000002; + let b3 = 0x0000000000000000000000000000000000000000000000000000000000000003; + let b4 = 0x0000000000000000000000000000000000000000000000000000000000000004; + let b5 = 0x0000000000000000000000000000000000000000000000000000000000000005; + let b6 = 0x0000000000000000000000000000000000000000000000000000000000000006; + let b7 = 0x0000000000000000000000000000000000000000000000000000000000000007; + let b8 = 0x0000000000000000000000000000000000000000000000000000000000000008; + + assert(vector.len() == 0); + assert(vector.capacity() == 0); + assert(vector.is_empty() == true); + + vector.push((number0, b0)); + vector.push((number1, b1)); + vector.push((number2, b2)); + vector.push((number3, b3)); + vector.push((number4, b4)); + + assert(vector.len() == 5); + assert(vector.capacity() == 8); + assert(vector.is_empty() == false); + + match vector.get(0) { + Option::Some(val) => { + assert(val.0 == number0); + assert(val.1 == b0); + }, + Option::None => revert(0), + } + + // Push after get + vector.push((number5, b5)); + vector.push((number6, b6)); + vector.push((number7, b7)); + vector.push((number8, b8)); + + match vector.get(4) { + Option::Some(val) => { + assert(val.0 == number4); + assert(val.1 == b4); + }, + Option::None => revert(0), + } + + match vector.get(6) { + Option::Some(val) => { + assert(val.0 == number6); + assert(val.1 == b6); + }, + Option::None => revert(0), + } + + assert(vector.len() == 9); + assert(vector.capacity() == 16); + assert(vector.is_empty() == false); + + // Test after capacity change + match vector.get(4) { + Option::Some(val) => { + assert(val.0 == number4); + assert(val.1 == b4); + }, + Option::None => revert(0), + } + + match vector.get(6) { + Option::Some(val) => { + assert(val.0 == number6); + assert(val.1 == b6); + }, + Option::None => revert(0), + } + + vector.clear(); + + // Empty after clear + assert(vector.len() == 0); + assert(vector.capacity() == 16); + assert(vector.is_empty() == true); + + match vector.get(0) { + Option::Some(val) => revert(0), Option::None => (), + } + + // Make sure pushing again after clear() works + vector.push((number0, b0)); + vector.push((number1, b1)); + vector.push((number2, b2)); + vector.push((number3, b3)); + vector.push((number4, b4)); + + assert(vector.len() == 5); + assert(vector.capacity() == 16); + assert(vector.is_empty() == false); + + match vector.get(4) { + Option::Some(val) => { + assert(val.0 == number4); + assert(val.1 == b4); + }, + Option::None => revert(0), + } + + // Out of bounds access + match vector.get(5) { + Option::Some(val) => revert(0), Option::None => (), + } +} + +fn test_vector_new_string() { + let mut vector: Vec = ~Vec::new::(); + + let s0 = "fuel"; + let s1 = "john"; + let s2 = "nick"; + + assert(vector.len() == 0); + assert(vector.capacity() == 0); + assert(vector.is_empty() == true); + + vector.push(s0); + vector.push(s1); + vector.push(s2); + + assert(vector.len() == 3); + assert(vector.capacity() == 4); + assert(vector.is_empty() == false); + + // Can't compare strings directly. Compare their hashes instead. + match vector.get(0) { + Option::Some(val) => { + assert(sha256(val) == sha256(s0)); + }, + Option::None => revert(0), + } + + match vector.get(1) { + Option::Some(val) => { + assert(sha256(val) == sha256(s1)); + }, + Option::None => revert(0), + } + + match vector.get(2) { + Option::Some(val) => { + assert(sha256(val) == sha256(s2)); + }, + Option::None => revert(0), + } +} + +fn test_vector_new_array() { + let mut vector: Vec<[u64; + 3]> = ~Vec::new::<[u64; + 3]>(); + + let a0 = [0, 1, 2]; + let a1 = [3, 4, 5]; + let a2 = [6, 7, 8]; + + assert(vector.len() == 0); + assert(vector.capacity() == 0); + assert(vector.is_empty() == true); + + vector.push(a0); + vector.push(a1); + vector.push(a2); + + assert(vector.len() == 3); + assert(vector.capacity() == 4); + assert(vector.is_empty() == false); + + // Can't compare strings directly. Compare their hashes instead. + match vector.get(0) { + Option::Some(val) => { + assert(val[0] == a0[0]); + assert(val[1] == a0[1]); + assert(val[2] == a0[2]); + }, + Option::None => revert(0), + } + + match vector.get(1) { + Option::Some(val) => { + assert(val[0] == a1[0]); + assert(val[1] == a1[1]); + assert(val[2] == a1[2]); + }, + Option::None => revert(0), + } + + match vector.get(2) { + Option::Some(val) => { + assert(val[0] == a2[0]); + assert(val[1] == a2[1]); + assert(val[2] == a2[2]); + }, + Option::None => revert(0), + } +} + +fn test_vector_with_capacity_u64() { + let mut vector: Vec = ~Vec::with_capacity::(8); + + let number0 = 0; + let number1 = 1; + let number2 = 2; + let number3 = 3; + let number4 = 4; + let number5 = 5; + let number6 = 6; + let number7 = 7; + let number8 = 8; + + assert(vector.len() == 0); + assert(vector.capacity() == 8); + assert(vector.is_empty() == true); + + vector.push(number0); + vector.push(number1); + vector.push(number2); + vector.push(number3); + vector.push(number4); + + assert(vector.len() == 5); + assert(vector.capacity() == 8); + assert(vector.is_empty() == false); + + match vector.get(0) { + Option::Some(val) => assert(val == number0), Option::None => revert(0), + } + + // Push after get + vector.push(number5); + vector.push(number6); + vector.push(number7); + vector.push(number8); + + match vector.get(4) { + Option::Some(val) => assert(val == number4), Option::None => revert(0), + } + + match vector.get(6) { + Option::Some(val) => assert(val == number6), Option::None => revert(0), + } + + assert(vector.len() == 9); + assert(vector.capacity() == 16); + assert(vector.is_empty() == false); + + vector.clear(); + + // Empty after clear + assert(vector.len() == 0); + assert(vector.capacity() == 16); + assert(vector.is_empty() == true); + + match vector.get(0) { + Option::Some(val) => revert(0), Option::None => (), + } + + // Make sure pushing again after clear() works + vector.push(number0); + vector.push(number1); + vector.push(number2); + vector.push(number3); + vector.push(number4); + + assert(vector.len() == 5); + assert(vector.capacity() == 16); + assert(vector.is_empty() == false); + + match vector.get(4) { + Option::Some(val) => assert(val == number4), Option::None => revert(0), + } + + // Out of bounds access + match vector.get(5) { + Option::Some(val) => revert(0), Option::None => (), + } +}