diff --git a/Cargo.lock b/Cargo.lock index d362783ff0d38..0b50492bfa560 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5151,9 +5151,9 @@ checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799" [[package]] name = "async-trait" -version = "0.1.82" +version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", diff --git a/aptos-move/aptos-gas-schedule/src/gas_schedule/move_stdlib.rs b/aptos-move/aptos-gas-schedule/src/gas_schedule/move_stdlib.rs index c25afc6e515ba..c5063257d9ff1 100644 --- a/aptos-move/aptos-gas-schedule/src/gas_schedule/move_stdlib.rs +++ b/aptos-move/aptos-gas-schedule/src/gas_schedule/move_stdlib.rs @@ -3,8 +3,11 @@ //! This module defines the gas parameters for Move Stdlib. -use crate::{gas_feature_versions::RELEASE_V1_18, gas_schedule::NativeGasParameters}; -use aptos_gas_algebra::{InternalGas, InternalGasPerByte}; +use crate::{ + gas_feature_versions::{RELEASE_V1_18, RELEASE_V1_22}, + gas_schedule::NativeGasParameters, +}; +use aptos_gas_algebra::{InternalGas, InternalGasPerAbstractValueUnit, InternalGasPerByte}; crate::gas_schedule::macros::define_gas_parameters!( MoveStdlibGasParameters, @@ -36,5 +39,8 @@ crate::gas_schedule::macros::define_gas_parameters!( [bcs_serialized_size_base: InternalGas, { RELEASE_V1_18.. => "bcs.serialized_size.base" }, 735], [bcs_serialized_size_per_byte_serialized: InternalGasPerByte, { RELEASE_V1_18.. => "bcs.serialized_size.per_byte_serialized" }, 36], [bcs_serialized_size_failure: InternalGas, { RELEASE_V1_18.. => "bcs.serialized_size.failure" }, 3676], + + [mem_swap_base: InternalGas, { RELEASE_V1_22.. => "mem.swap.base" }, 367], + [mem_swap_per_abs_val_unit: InternalGasPerAbstractValueUnit, { RELEASE_V1_22.. => "mem.swap.per_abs_val_unit"}, 14], ] ); diff --git a/aptos-move/aptos-gas-schedule/src/ver.rs b/aptos-move/aptos-gas-schedule/src/ver.rs index f798c42b40145..d958276339a46 100644 --- a/aptos-move/aptos-gas-schedule/src/ver.rs +++ b/aptos-move/aptos-gas-schedule/src/ver.rs @@ -69,7 +69,7 @@ /// global operations. /// - V1 /// - TBA -pub const LATEST_GAS_FEATURE_VERSION: u64 = gas_feature_versions::RELEASE_V1_21; +pub const LATEST_GAS_FEATURE_VERSION: u64 = gas_feature_versions::RELEASE_V1_22; pub mod gas_feature_versions { pub const RELEASE_V1_8: u64 = 11; @@ -86,4 +86,5 @@ pub mod gas_feature_versions { pub const RELEASE_V1_19: u64 = 23; pub const RELEASE_V1_20: u64 = 24; pub const RELEASE_V1_21: u64 = 25; + pub const RELEASE_V1_22: u64 = 26; } diff --git a/aptos-move/e2e-move-tests/src/tests/code_publishing.data/pack_stdlib/sources/mem.move b/aptos-move/e2e-move-tests/src/tests/code_publishing.data/pack_stdlib/sources/mem.move new file mode 120000 index 0000000000000..13c1b4e1d1a8f --- /dev/null +++ b/aptos-move/e2e-move-tests/src/tests/code_publishing.data/pack_stdlib/sources/mem.move @@ -0,0 +1 @@ +../../../../../../framework/move-stdlib/sources/mem.move \ No newline at end of file diff --git a/aptos-move/e2e-move-tests/src/tests/code_publishing.data/pack_stdlib_incompat/sources/mem.move b/aptos-move/e2e-move-tests/src/tests/code_publishing.data/pack_stdlib_incompat/sources/mem.move new file mode 120000 index 0000000000000..13c1b4e1d1a8f --- /dev/null +++ b/aptos-move/e2e-move-tests/src/tests/code_publishing.data/pack_stdlib_incompat/sources/mem.move @@ -0,0 +1 @@ +../../../../../../framework/move-stdlib/sources/mem.move \ No newline at end of file diff --git a/aptos-move/framework/move-stdlib/doc/mem.md b/aptos-move/framework/move-stdlib/doc/mem.md new file mode 100644 index 0000000000000..cce483a926275 --- /dev/null +++ b/aptos-move/framework/move-stdlib/doc/mem.md @@ -0,0 +1,113 @@ + + + +# Module `0x1::mem` + +Module with methods for safe memory manipulation. +I.e. swapping/replacing non-copyable/non-droppable types. + + +- [Function `swap`](#0x1_mem_swap) +- [Function `replace`](#0x1_mem_replace) +- [Specification](#@Specification_0) + - [Function `swap`](#@Specification_0_swap) + - [Function `replace`](#@Specification_0_replace) + + +
+ + + + + +## Function `swap` + +Swap contents of two passed mutable references. + + +
public fun swap<T>(left: &mut T, right: &mut T)
+
+ + + +
+Implementation + + +
public native fun swap<T>(left: &mut T, right: &mut T);
+
+ + + +
+ + + +## Function `replace` + +Replace value reference points to with the given new value, +and return value it had before. + + +
public fun replace<T>(ref: &mut T, new: T): T
+
+ + + +
+Implementation + + +
public fun replace<T>(ref: &mut T, new: T): T {
+    swap(ref, &mut new);
+    new
+}
+
+ + + +
+ + + +## Specification + + + + +### Function `swap` + + +
public fun swap<T>(left: &mut T, right: &mut T)
+
+ + + + +
pragma opaque;
+aborts_if false;
+ensures right == old(left);
+ensures left == old(right);
+
+ + + + + +### Function `replace` + + +
public fun replace<T>(ref: &mut T, new: T): T
+
+ + + + +
pragma opaque;
+aborts_if false;
+ensures result == old(ref);
+ensures ref == new;
+
+ + +[move-book]: https://aptos.dev/move/book/SUMMARY diff --git a/aptos-move/framework/move-stdlib/doc/overview.md b/aptos-move/framework/move-stdlib/doc/overview.md index 8eb0c67f05113..649873e8ab2f5 100644 --- a/aptos-move/framework/move-stdlib/doc/overview.md +++ b/aptos-move/framework/move-stdlib/doc/overview.md @@ -20,6 +20,7 @@ For on overview of the Move language, see the [Move Book][move-book]. - [`0x1::features`](features.md#0x1_features) - [`0x1::fixed_point32`](fixed_point32.md#0x1_fixed_point32) - [`0x1::hash`](hash.md#0x1_hash) +- [`0x1::mem`](mem.md#0x1_mem) - [`0x1::option`](option.md#0x1_option) - [`0x1::signer`](signer.md#0x1_signer) - [`0x1::string`](string.md#0x1_string) diff --git a/aptos-move/framework/move-stdlib/sources/mem.move b/aptos-move/framework/move-stdlib/sources/mem.move new file mode 100644 index 0000000000000..98758c43eba38 --- /dev/null +++ b/aptos-move/framework/move-stdlib/sources/mem.move @@ -0,0 +1,27 @@ +/// Module with methods for safe memory manipulation. +/// I.e. swapping/replacing non-copyable/non-droppable types. +module std::mem { + /// Swap contents of two passed mutable references. + public native fun swap(left: &mut T, right: &mut T); + + /// Replace value reference points to with the given new value, + /// and return value it had before. + public fun replace(ref: &mut T, new: T): T { + swap(ref, &mut new); + new + } + + spec swap(left: &mut T, right: &mut T) { + pragma opaque; + aborts_if false; + ensures right == old(left); + ensures left == old(right); + } + + spec replace(ref: &mut T, new: T): T { + pragma opaque; + aborts_if false; + ensures result == old(ref); + ensures ref == new; + } +} diff --git a/aptos-move/framework/move-stdlib/src/natives/mem.rs b/aptos-move/framework/move-stdlib/src/natives/mem.rs new file mode 100644 index 0000000000000..e13dccfd20ab3 --- /dev/null +++ b/aptos-move/framework/move-stdlib/src/natives/mem.rs @@ -0,0 +1,75 @@ +// Copyright © Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 + +// Copyright (c) The Diem Core Contributors +// Copyright (c) The Move Contributors +// SPDX-License-Identifier: Apache-2.0 + +//! Implementation of native functions for utf8 strings. + +use aptos_gas_schedule::gas_params::natives::move_stdlib::{ + MEM_SWAP_BASE, MEM_SWAP_PER_ABS_VAL_UNIT, +}; +use aptos_native_interface::{ + safely_pop_arg, RawSafeNative, SafeNativeBuilder, SafeNativeContext, SafeNativeError, + SafeNativeResult, +}; +use move_core_types::vm_status::StatusCode; +use move_vm_runtime::native_functions::NativeFunction; +use move_vm_types::{ + loaded_data::runtime_types::Type, + natives::function::PartialVMError, + values::{Reference, Value}, +}; +use smallvec::{smallvec, SmallVec}; +use std::collections::VecDeque; + +/*************************************************************************************************** + * native fun native_swap + * + * gas cost: MEM_SWAP_BASE + MEM_SWAP_PER_ABS_VAL_UNIT * abstract_size_of_arguments + * + **************************************************************************************************/ +fn native_swap( + context: &mut SafeNativeContext, + _ty_args: Vec, + mut args: VecDeque, +) -> SafeNativeResult> { + debug_assert!(args.len() == 2); + + if args.len() != 2 { + return Err(SafeNativeError::InvariantViolation(PartialVMError::new( + StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR, + ))); + } + + let cost = MEM_SWAP_BASE + + MEM_SWAP_PER_ABS_VAL_UNIT + * (context.abs_val_size(&args[0]) + context.abs_val_size(&args[1])); + context.charge(cost)?; + + let ref1 = safely_pop_arg!(args, Reference); + let ref0 = safely_pop_arg!(args, Reference); + + ref0.swap_ref(|value0| { + let mut value1_opt = Option::None; + ref1.swap_ref(|value1| { + value1_opt = Option::Some(value1); + Ok(value0) + })?; + Ok(value1_opt.unwrap()) + })?; + + Ok(smallvec![]) +} + +/*************************************************************************************************** + * module + **************************************************************************************************/ +pub fn make_all( + builder: &SafeNativeBuilder, +) -> impl Iterator + '_ { + let natives = [("swap", native_swap as RawSafeNative)]; + + builder.make_named_natives(natives) +} diff --git a/aptos-move/framework/move-stdlib/src/natives/mod.rs b/aptos-move/framework/move-stdlib/src/natives/mod.rs index 56b37bd332960..24c995f7e9cec 100644 --- a/aptos-move/framework/move-stdlib/src/natives/mod.rs +++ b/aptos-move/framework/move-stdlib/src/natives/mod.rs @@ -7,6 +7,7 @@ pub mod bcs; pub mod hash; +pub mod mem; pub mod signer; pub mod string; #[cfg(feature = "testing")] @@ -33,6 +34,7 @@ pub fn all_natives( builder.with_incremental_gas_charging(false, |builder| { add_natives!("bcs", bcs::make_all(builder)); add_natives!("hash", hash::make_all(builder)); + add_natives!("mem", mem::make_all(builder)); add_natives!("signer", signer::make_all(builder)); add_natives!("string", string::make_all(builder)); #[cfg(feature = "testing")] diff --git a/aptos-move/framework/move-stdlib/tests/mem_tests.move b/aptos-move/framework/move-stdlib/tests/mem_tests.move new file mode 100644 index 0000000000000..14144ee4b3513 --- /dev/null +++ b/aptos-move/framework/move-stdlib/tests/mem_tests.move @@ -0,0 +1,65 @@ +#[test_only] +module std::mem_tests { + use std::vector; + use std::mem::{swap, replace}; + + #[test] + fun test_swap_ints() { + let a = 1; + let b = 2; + let v = vector[3, 4, 5, 6]; + + swap(&mut a, &mut b); + assert!(a == 2, 0); + assert!(b == 1, 1); + + swap(&mut a, vector::borrow_mut(&mut v, 0)); + assert!(a == 3, 0); + assert!(vector::borrow(&v, 0) == &2, 1); + + swap(vector::borrow_mut(&mut v, 2), &mut a); + assert!(a == 5, 0); + assert!(vector::borrow(&v, 2) == &3, 1); + } + + + #[test] + fun test_replace_ints() { + let a = 1; + let b = 2; + + assert!(replace(&mut a, b) == 1, 0); + assert!(a == 2, 1); + } + + #[test_only] + struct SomeStruct has drop { + f: u64, + v: vector, + } + + #[test] + fun test_swap_struct() { + let a = 1; + let s1 = SomeStruct { f: 2, v: vector[3, 4]}; + let s2 = SomeStruct { f: 5, v: vector[6, 7]}; + let vs = vector[SomeStruct { f: 8, v: vector[9, 10]}, SomeStruct { f: 11, v: vector[12, 13]}]; + + + swap(&mut s1, &mut s2); + assert!(&s1 == &SomeStruct { f: 5, v: vector[6, 7]}, 0); + assert!(&s2 == &SomeStruct { f: 2, v: vector[3, 4]}, 1); + + swap(&mut s1.f, &mut a); + assert!(s1.f == 1, 2); + assert!(a == 5, 3); + + swap(&mut s1.f, vector::borrow_mut(&mut s1.v, 0)); + assert!(s1.f == 6, 4); + assert!(vector::borrow(&s1.v, 0) == &1, 5); + + swap(&mut s2, vector::borrow_mut(&mut vs, 0)); + assert!(&s2 == &SomeStruct { f: 8, v: vector[9, 10]}, 6); + assert!(vector::borrow(&vs, 0) == &SomeStruct { f: 2, v: vector[3, 4]}, 7); + } +} diff --git a/third_party/move/move-vm/types/src/values/values_impl.rs b/third_party/move/move-vm/types/src/values/values_impl.rs index 006c16d6b9dd1..9db5b3a678ef7 100644 --- a/third_party/move/move-vm/types/src/values/values_impl.rs +++ b/third_party/move/move-vm/types/src/values/values_impl.rs @@ -903,6 +903,81 @@ impl Reference { } } +/************************************************************************************** + * + * Swap reference (Move) + * + * Implementation of the Move operation to swap contents of a reference. + * + *************************************************************************************/ + +impl ContainerRef { + pub fn swap_ref(self, swap_with: F) -> PartialVMResult<()> + where + F: FnOnce(Value) -> PartialVMResult, + { + // same as read_ref, but without consuming self. + // But safe because even though we copy here, and temporarily leave a duplicate inside, + // we replace it in write_ref below. + let old_value = Value(ValueImpl::Container(self.container().copy_value()?)); + self.write_ref(swap_with(old_value)?)?; + + Ok(()) + } +} + +impl IndexedRef { + pub fn swap_ref(self, swap_with: F) -> PartialVMResult<()> + where + F: FnOnce(Value) -> PartialVMResult, + { + use Container::*; + + // same as read_ref, but without consuming self. + // But safe because even though we copy here, and temporarily leave a duplicate inside, + // we replace it in write_ref below. + let old_value = Value(match self.container_ref.container() { + Vec(r) => r.borrow()[self.idx].copy_value()?, + Struct(r) => r.borrow()[self.idx].copy_value()?, + + VecU8(r) => ValueImpl::U8(r.borrow()[self.idx]), + VecU16(r) => ValueImpl::U16(r.borrow()[self.idx]), + VecU32(r) => ValueImpl::U32(r.borrow()[self.idx]), + VecU64(r) => ValueImpl::U64(r.borrow()[self.idx]), + VecU128(r) => ValueImpl::U128(r.borrow()[self.idx]), + VecU256(r) => ValueImpl::U256(r.borrow()[self.idx]), + VecBool(r) => ValueImpl::Bool(r.borrow()[self.idx]), + VecAddress(r) => ValueImpl::Address(r.borrow()[self.idx]), + + Locals(r) => r.borrow()[self.idx].copy_value()?, + }); + + self.write_ref(swap_with(old_value)?)?; + Ok(()) + } +} + +impl ReferenceImpl { + pub fn swap_ref(self, swap_with: F) -> PartialVMResult<()> + where + F: FnOnce(Value) -> PartialVMResult, + { + match self { + Self::ContainerRef(r) => r.swap_ref(swap_with), + Self::IndexedRef(r) => r.swap_ref(swap_with), + } + } +} + +impl Reference { + pub fn swap_ref(self, swap_with: F) -> PartialVMResult<()> + where + F: FnOnce(Value) -> PartialVMResult, + { + self.0.swap_ref(swap_with) + } +} + /*************************************************************************************** * * Borrows (Move)