From ac264ab4947d35ce25a7e0c91f86cbdfd9801856 Mon Sep 17 00:00:00 2001 From: John Cub Date: Tue, 31 May 2022 16:20:06 +0300 Subject: [PATCH] feat: add `std::alloc` and `std::ptr` --- sway-lib-std/src/alloc.sw | 45 +++++++ sway-lib-std/src/intrinsics.sw | 36 ++++++ sway-lib-std/src/lib.sw | 2 + sway-lib-std/src/ptr.sw | 112 ++++++++++++++++++ test/src/e2e_vm_tests/mod.rs | 2 + .../should_pass/stdlib/alloc/Forc.lock | 11 ++ .../should_pass/stdlib/alloc/Forc.toml | 8 ++ .../should_pass/stdlib/alloc/src/main.sw | 47 ++++++++ .../should_pass/stdlib/intrinsics/src/main.sw | 39 ++++-- .../should_pass/stdlib/ptr/Forc.lock | 11 ++ .../should_pass/stdlib/ptr/Forc.toml | 8 ++ .../should_pass/stdlib/ptr/src/main.sw | 95 +++++++++++++++ 12 files changed, 406 insertions(+), 10 deletions(-) create mode 100644 sway-lib-std/src/alloc.sw create mode 100644 sway-lib-std/src/ptr.sw create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/stdlib/alloc/Forc.lock create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/stdlib/alloc/Forc.toml create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/stdlib/alloc/src/main.sw create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/stdlib/ptr/Forc.lock create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/stdlib/ptr/Forc.toml create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/stdlib/ptr/src/main.sw diff --git a/sway-lib-std/src/alloc.sw b/sway-lib-std/src/alloc.sw new file mode 100644 index 00000000000..38952b6c8e3 --- /dev/null +++ b/sway-lib-std/src/alloc.sw @@ -0,0 +1,45 @@ +//! Library for allocating memory +//! Inspired from: https://doc.rust-lang.org/std/alloc/index.html +library alloc; + +use ::intrinsics::*; + +/// Allocates zeroed memory on the heap +/// +/// In FuelVM, the heap begins at `VM_MAX_RAM - 1` and grows downward. +/// Heap pointer `$hp` will always point to unallocated space. +/// +/// Initially the heap will look like this: +/// ... 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | +/// $hp^ ^VM_MAX_RAM +/// +/// After allocating with `let ptr = alloc(8)`: +/// ... 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | +/// $hp^ ^ptr ^VM_MAX_RAM +/// +/// After writing with `sw(ptr, u64::max())`: +/// ... 00 00 00 00 00 00 00 00 FF FF FF FF FF FF FF FF | +/// $hp^ ^ptr ^VM_MAX_RAM +/// +/// See: https://github.com/FuelLabs/fuel-specs/blob/master/specs/vm/main.md#vm-initialization +/// See: https://github.com/FuelLabs/fuel-specs/blob/master/specs/vm/opcodes.md#aloc-allocate-memory +pub fn alloc(size: u64) -> u64 { + asm(size: size, ptr) { + aloc size; + // `$hp` points to unallocated space and heap grows downward so + // our newly allocated space will be right after it + addi ptr hp i1; + ptr: u64 + } +} + +/// Reallocates the given area of memory +pub fn realloc(ptr: u64, size: u64, new_size: u64) -> u64 { + if new_size > size { + let new_ptr = alloc(new_size); + copy(new_ptr, ptr, size); + new_ptr + } else { + ptr + } +} diff --git a/sway-lib-std/src/intrinsics.sw b/sway-lib-std/src/intrinsics.sw index 30d04f9fdfc..b61ac9f91ac 100644 --- a/sway-lib-std/src/intrinsics.sw +++ b/sway-lib-std/src/intrinsics.sw @@ -10,3 +10,39 @@ pub fn is_reference_type() -> bool { pub fn size_of() -> u64 { __size_of::() } + +/// Returns the size of a value in bytes. +pub fn size_of_val(val: T) -> u64 { + __size_of_val::(val) +} + +/// Returns the address of the given value. +pub fn addr_of(val: T) -> u64 { + // TODO: Replace with intrinsic: https://github.com/FuelLabs/sway/issues/855 + if !__is_reference_type::() { + // std::revert not available here + asm() { + rvrt zero; + } + } + asm(ptr: val) { + ptr: u64 + } +} + +/// Copies data from source to destination. +pub fn copy(dst: u64, src: u64, size: u64) { + // TODO: Replace with intrinsic: https://github.com/FuelLabs/sway/issues/855 + asm(dst: dst, src: src, size: size) { + mcp dst src size; + }; +} + +/// Compares data at two points of memory. +pub fn raw_eq(first: u64, second: u64, len: u64) -> bool { + // TODO: Replace with intrinsic: https://github.com/FuelLabs/sway/issues/855 + asm(first: first, second: second, len: len, result) { + meq result first second len; + result: bool + } +} diff --git a/sway-lib-std/src/lib.sw b/sway-lib-std/src/lib.sw index 738a30a6136..a32f7216a54 100644 --- a/sway-lib-std/src/lib.sw +++ b/sway-lib-std/src/lib.sw @@ -6,6 +6,8 @@ dep logging; dep assert; dep option; dep result; +dep alloc; +dep ptr; dep constants; dep contract_id; dep context; diff --git a/sway-lib-std/src/ptr.sw b/sway-lib-std/src/ptr.sw new file mode 100644 index 00000000000..7edd4d32b0e --- /dev/null +++ b/sway-lib-std/src/ptr.sw @@ -0,0 +1,112 @@ +//! Library for working with addresses in memory +//! Inspired from: https://doc.rust-lang.org/std/primitive.pointer.html +//! Inspired from: https://doc.rust-lang.org/std/ptr/index.html +library ptr; + +use ::assert::*; +use ::intrinsics::*; + +/// A point in memory with unknown type +pub struct RawPointer { + addr: u64, +} + +impl RawPointer { + pub fn new(addr: u64) -> Self { + RawPointer { + addr: addr, + } + } + + /// Creates a new pointer to the given reference-type value + pub fn from(val: T) -> Self { + assert(is_reference_type::()); + RawPointer { + addr: addr_of(val), + } + } + + pub fn addr(self) -> u64 { + self.addr + } + + /// Creates a new pointer with the given offset added to the current address + pub fn add(self, offset: u64) -> Self { + RawPointer { + addr: self.addr + offset, + } + } + + /// Creates a new pointer with the given offset subtracted from the current address + pub fn sub(self, offset: u64) -> Self { + RawPointer { + addr: self.addr - offset, + } + } + + /// Reads the given type of value from the pointer's address + pub fn read(self) -> T { + if is_reference_type::() { + asm(r1: self.addr) { + r1: T + } + } else { + asm(r1: self.addr) { + lw r1 r1 i0; + r1: T + } + } + } + + /// Writes the given value to the pointer's address + pub fn write(self, val: T) { + if is_reference_type::() { + copy(self.addr, addr_of(val), size_of::()); + } else { + asm(ptr: self.addr, val: val) { + sw ptr val i0; + }; + } + } + + /// Copies the data at the given pointer to the pointer's address + pub fn copy_from(self, src: Self, len: u64) { + copy(self.addr, src.addr, len); + } + + /// Copies the data at the pointer's address to the given pointer + pub fn copy_to(self, dst: Self, len: u64) { + copy(dst.addr, self.addr, len); + } + + // Non-generic aliases to workaround generics bugs + // See: https://github.com/FuelLabs/sway/issues/1628 + pub fn read_bool(self) -> bool { + asm(r1: self.addr) { + lw r1 r1 i0; + r1: bool + } + } + pub fn read_u64(self) -> u64 { + asm(r1: self.addr) { + lw r1 r1 i0; + r1: u64 + } + } + pub fn write_bool(self, val: bool) { + asm(ptr: self.addr, val: val) { + sw ptr val i0; + }; + } + pub fn write_u64(self, val: u64) { + asm(ptr: self.addr, val: val) { + sw ptr val i0; + }; + } +} + +impl core::ops::Eq for RawPointer { + fn eq(self, other: Self) -> bool { + self.addr == other.addr + } +} diff --git a/test/src/e2e_vm_tests/mod.rs b/test/src/e2e_vm_tests/mod.rs index c7cf4171f39..a1ba0e92ed8 100644 --- a/test/src/e2e_vm_tests/mod.rs +++ b/test/src/e2e_vm_tests/mod.rs @@ -142,6 +142,8 @@ pub fn run(filter_regex: Option) { ("should_pass/stdlib/u128_test", ProgramState::Return(1)), // true ("should_pass/stdlib/u128_div_test", ProgramState::Return(1)), // true ("should_pass/stdlib/u128_mul_test", ProgramState::Return(1)), // true + ("should_pass/stdlib/alloc", ProgramState::Return(1)), // true + ("should_pass/stdlib/ptr", ProgramState::Return(1)), // true ( "should_pass/language/generic_structs", ProgramState::Return(1), // true diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/alloc/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/alloc/Forc.lock new file mode 100644 index 00000000000..9a0055bc149 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/alloc/Forc.lock @@ -0,0 +1,11 @@ +[[package]] +name = 'core' +dependencies = [] + +[[package]] +name = 'std' +dependencies = ['core'] + +[[package]] +name = 'std_alloc_test' +dependencies = ['std'] diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/alloc/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/alloc/Forc.toml new file mode 100644 index 00000000000..8463757af9d --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/alloc/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "std_alloc_test" + +[dependencies] +std = { path = "../../../../../../../sway-lib-std" } diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/alloc/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/alloc/src/main.sw new file mode 100644 index 00000000000..6106c1167e6 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/alloc/src/main.sw @@ -0,0 +1,47 @@ +script; + +use core::num::*; +use std::alloc::*; +use std::intrinsics::*; +use std::context::registers::*; +use std::assert::assert; + +fn lw(ptr: u64) -> u64 { + asm(r1: ptr) { + lw r1 r1 i0; + r1: u64 + } +} + +fn sw(ptr: u64, val: u64) { + asm(r1: ptr, val: val) { + sw r1 val i0; + }; +} + +fn main() -> bool { + let hp_start = heap_ptr(); + + // Allocate some memory + let hp = heap_ptr(); + let ptr = alloc(8); + assert(ptr == hp - 8 + 1); + assert(heap_ptr() == hp - 8); + + // Read from it + let val = lw(ptr); + assert(val == 0); + + // Write to it + let val = ~u64::max(); + sw(ptr, val); + assert(lw(ptr) == val); + + // Grow it + let hp = heap_ptr(); + let ptr = realloc(ptr, 8, 16); + assert(ptr == hp - 16 + 1); + assert(heap_ptr() == hp - 16); + + true +} diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/intrinsics/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/intrinsics/src/main.sw index 41ce5fa4022..eaa85bfb929 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/intrinsics/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/intrinsics/src/main.sw @@ -12,10 +12,6 @@ fn is_ref_type(param: T) -> bool { is_reference_type::() } -fn get_size_of(param: T) -> u64 { - size_of::() -} - fn main() -> bool { let zero = ~b256::min(); let a: u64 = 1; @@ -43,12 +39,35 @@ fn main() -> bool { assert(is_ref_type(e)); assert(is_ref_type(f)); - assert(get_size_of(a) == 8); - assert(get_size_of(b) == 8); - assert(get_size_of(c) == 8); - assert(get_size_of(d) == 8); - assert(get_size_of(e) == 32); - assert(get_size_of(f) == 16); + assert(size_of::() == 8); + assert(size_of::() == 8); + assert(size_of::() == 8); + assert(size_of::() == 8); + assert(size_of::() == 32); + assert(size_of::() == 16); + assert(size_of::<[u16; + 3]>() == 24); + assert(size_of::() == 16); + + assert(size_of_val(a) == 8); + assert(size_of_val(b) == 8); + assert(size_of_val(c) == 8); + assert(size_of_val(d) == 8); + assert(size_of_val(e) == 32); + assert(size_of_val(f) == 16); + + assert(addr_of(test_struct) == asm(r1: test_struct) { + r1: u64 + }); + + let test_struct_2 = TestStruct { + field_1: true, + field_2: 12, + }; + assert(!raw_eq(addr_of(test_struct), addr_of(test_struct_2), size_of_val(test_struct))); + + copy(addr_of(test_struct), addr_of(test_struct_2), size_of_val(test_struct)); + assert(raw_eq(addr_of(test_struct), addr_of(test_struct_2), size_of_val(test_struct))); true } diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/ptr/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/ptr/Forc.lock new file mode 100644 index 00000000000..1e8b1197f1d --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/ptr/Forc.lock @@ -0,0 +1,11 @@ +[[package]] +name = 'core' +dependencies = [] + +[[package]] +name = 'std' +dependencies = ['core'] + +[[package]] +name = 'std_ptr_test' +dependencies = ['std'] diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/ptr/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/ptr/Forc.toml new file mode 100644 index 00000000000..2006d6f6cfc --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/ptr/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "std_ptr_test" + +[dependencies] +std = { path = "../../../../../../../sway-lib-std" } diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/ptr/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/ptr/src/main.sw new file mode 100644 index 00000000000..581517a2802 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/stdlib/ptr/src/main.sw @@ -0,0 +1,95 @@ +script; + +use std::ptr::*; +use std::alloc::*; +use std::intrinsics::*; +use std::assert::assert; + +// We use this to workaround some generics issues +// See: https://github.com/FuelLabs/sway/issues/1628 +fn fix(v: U) -> T { + asm(r1: v) { + r1: T + } +} + +struct TestStruct { + boo: bool, + uwu: u64, +} + +struct ExtendedTestStruct { + boo: bool, + uwu: u64, + kek: bool, + bur: u64, +} + +fn main() -> bool { + // Create a struct + let foo = TestStruct { + boo: true, + uwu: 42, + }; + let foo_len = size_of::(); + assert(foo_len == 16); + + // Get a pointer to it + let foo_ptr = ~RawPointer::from(foo); + assert(foo_ptr.addr == asm(r1: foo) { + r1: u64 + }); + + // Get another pointer to it and compare + let foo_ptr_2 = ~RawPointer::from(foo); + assert(foo_ptr_2 == foo_ptr); + + // Copy the struct into a buffer (copy_from) + let buf_ptr = ~RawPointer::new(alloc(16)); + buf_ptr.copy_from(foo_ptr, foo_len); + assert(asm(r1: buf_ptr.addr, r2: foo_ptr.addr, r3: foo_len) { + meq r1 r1 r2 r3; + r1: bool + }); + + // Copy the struct into a buffer (copy_to) + let buf_ptr = ~RawPointer::new(alloc(16)); + foo_ptr.copy_to(buf_ptr, foo_len); + assert(asm(r1: buf_ptr.addr, r2: foo_ptr.addr, r3: foo_len) { + meq r1 r1 r2 r3; + r1: bool + }); + + // Read the pointer as a TestStruct + let foo: TestStruct = buf_ptr.read(); + assert(foo.boo == true); + assert(foo.uwu == 42); + + // Read fields of the struct + let uwu_ptr = buf_ptr.add(size_of::()); + let uwu: u64 = uwu_ptr.read_u64(); + assert(uwu == 42); + let boo_ptr = uwu_ptr.sub(size_of::()); + let boo: bool = boo_ptr.read_bool(); + assert(boo == true); + + // Write values into a buffer + let buf_ptr = ~RawPointer::new(alloc(16)); + buf_ptr.write_bool(true); + buf_ptr.add(size_of::()).write_u64(42); + let foo: TestStruct = buf_ptr.read(); + assert(foo.boo == true); + assert(foo.uwu == 42); + + // Write structs into a buffer + let buf_ptr = ~RawPointer::new(alloc(32)); + buf_ptr.write(foo); + buf_ptr.add(size_of::()).write(foo); + let bar: ExtendedTestStruct = fix::(buf_ptr.read()); + assert(bar.boo == true); + assert(bar.uwu == 42); + assert(bar.kek == true); + assert(bar.bur == 42); + + true +}