Skip to content

Commit

Permalink
[move][stdlib] Implement mem::swap native move call (#14786)
Browse files Browse the repository at this point in the history
## Description
If we have a field that contains non-copyable type, it is impossible to change it, and get the old value back. 

Adding two methods:
* native mem::swap, that implements swap of contents of two mutable references
* mem::replace, as a simple wrapper based on mem::swap 

## How Has This Been Tested?
provided unit tests

## Type of Change
- [x] New feature

## Which Components or Systems Does This Change Impact?
- [x] Move/Aptos Virtual Machine
  • Loading branch information
igor-aptos authored Nov 14, 2024
1 parent 2e349cf commit 6255194
Show file tree
Hide file tree
Showing 14 changed files with 660 additions and 10 deletions.
2 changes: 2 additions & 0 deletions aptos-move/aptos-gas-schedule/src/gas_schedule/move_stdlib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,7 @@ crate::gas_schedule::macros::define_gas_parameters!(

[vector_move_range_base: InternalGas, { RELEASE_V1_24.. => "vector.move_range.base" }, 4000],
[vector_move_range_per_index_moved: InternalGasPerArg, { RELEASE_V1_24.. => "vector.move_range.per_index_moved" }, 10],

[mem_swap_base: InternalGas, { RELEASE_V1_24.. => "mem.swap.base" }, 1500],
]
);
115 changes: 115 additions & 0 deletions aptos-move/framework/move-stdlib/doc/mem.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@

<a id="0x1_mem"></a>

# Module `0x1::mem`

Module with methods for safe memory manipulation.


- [Function `swap`](#0x1_mem_swap)
- [Function `replace`](#0x1_mem_replace)
- [Specification](#@Specification_0)
- [Function `swap`](#@Specification_0_swap)
- [Function `replace`](#@Specification_0_replace)


<pre><code></code></pre>



<a id="0x1_mem_swap"></a>

## Function `swap`

Swap contents of two passed mutable references.

Move prevents from having two mutable references to the same value,
so <code>left</code> and <code>right</code> references are always distinct.


<pre><code><b>public</b>(<b>friend</b>) <b>fun</b> <a href="mem.md#0x1_mem_swap">swap</a>&lt;T&gt;(left: &<b>mut</b> T, right: &<b>mut</b> T)
</code></pre>



<details>
<summary>Implementation</summary>


<pre><code><b>public</b>(<b>friend</b>) <b>native</b> <b>fun</b> <a href="mem.md#0x1_mem_swap">swap</a>&lt;T&gt;(left: &<b>mut</b> T, right: &<b>mut</b> T);
</code></pre>



</details>

<a id="0x1_mem_replace"></a>

## Function `replace`

Replace the value reference points to with the given new value,
and return the value it had before.


<pre><code><b>public</b>(<b>friend</b>) <b>fun</b> <a href="mem.md#0x1_mem_replace">replace</a>&lt;T&gt;(ref: &<b>mut</b> T, new: T): T
</code></pre>



<details>
<summary>Implementation</summary>


<pre><code><b>public</b>(<b>friend</b>) <b>fun</b> <a href="mem.md#0x1_mem_replace">replace</a>&lt;T&gt;(ref: &<b>mut</b> T, new: T): T {
<a href="mem.md#0x1_mem_swap">swap</a>(ref, &<b>mut</b> new);
new
}
</code></pre>



</details>

<a id="@Specification_0"></a>

## Specification


<a id="@Specification_0_swap"></a>

### Function `swap`


<pre><code><b>public</b>(<b>friend</b>) <b>fun</b> <a href="mem.md#0x1_mem_swap">swap</a>&lt;T&gt;(left: &<b>mut</b> T, right: &<b>mut</b> T)
</code></pre>




<pre><code><b>pragma</b> opaque;
<b>aborts_if</b> <b>false</b>;
<b>ensures</b> right == <b>old</b>(left);
<b>ensures</b> left == <b>old</b>(right);
</code></pre>



<a id="@Specification_0_replace"></a>

### Function `replace`


<pre><code><b>public</b>(<b>friend</b>) <b>fun</b> <a href="mem.md#0x1_mem_replace">replace</a>&lt;T&gt;(ref: &<b>mut</b> T, new: T): T
</code></pre>




<pre><code><b>pragma</b> opaque;
<b>aborts_if</b> <b>false</b>;
<b>ensures</b> result == <b>old</b>(ref);
<b>ensures</b> ref == new;
</code></pre>


[move-book]: https://aptos.dev/move/book/SUMMARY
1 change: 1 addition & 0 deletions aptos-move/framework/move-stdlib/doc/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
16 changes: 12 additions & 4 deletions aptos-move/framework/move-stdlib/doc/vector.md
Original file line number Diff line number Diff line change
Expand Up @@ -351,9 +351,11 @@ Moves range of elements <code>[removal_position, removal_position + length)</cod
to vector <code><b>to</b></code>, inserting them starting at the <code>insert_position</code>.
In the <code>from</code> vector, elements after the selected range are moved left to fill the hole
(i.e. range is removed, while the order of the rest of the elements is kept)
In the <code><b>to</b></code> vector, elements after the <code>insert_position</code> are moved to the right to make space for new elements
(i.e. range is inserted, while the order of the rest of the elements is kept).
Move prevents from having two mutable references to the same value, so <code>from</code> and <code><b>to</b></code> vectors are always distinct.
In the <code><b>to</b></code> vector, elements after the <code>insert_position</code> are moved to the right to make
space for new elements (i.e. range is inserted, while the order of the rest of the
elements is kept).
Move prevents from having two mutable references to the same value, so <code>from</code> and <code><b>to</b></code>
vectors are always distinct.


<pre><code><b>public</b>(<b>friend</b>) <b>fun</b> <a href="vector.md#0x1_vector_move_range">move_range</a>&lt;T&gt;(from: &<b>mut</b> <a href="vector.md#0x1_vector">vector</a>&lt;T&gt;, removal_position: u64, length: u64, <b>to</b>: &<b>mut</b> <a href="vector.md#0x1_vector">vector</a>&lt;T&gt;, insert_position: u64)
Expand All @@ -365,7 +367,13 @@ Move prevents from having two mutable references to the same value, so <code>fro
<summary>Implementation</summary>


<pre><code><b>native</b> <b>public</b>(<b>friend</b>) <b>fun</b> <a href="vector.md#0x1_vector_move_range">move_range</a>&lt;T&gt;(from: &<b>mut</b> <a href="vector.md#0x1_vector">vector</a>&lt;T&gt;, removal_position: u64, length: u64, <b>to</b>: &<b>mut</b> <a href="vector.md#0x1_vector">vector</a>&lt;T&gt;, insert_position: u64);
<pre><code><b>native</b> <b>public</b>(<b>friend</b>) <b>fun</b> <a href="vector.md#0x1_vector_move_range">move_range</a>&lt;T&gt;(
from: &<b>mut</b> <a href="vector.md#0x1_vector">vector</a>&lt;T&gt;,
removal_position: u64,
length: u64,
<b>to</b>: &<b>mut</b> <a href="vector.md#0x1_vector">vector</a>&lt;T&gt;,
insert_position: u64
);
</code></pre>


Expand Down
34 changes: 34 additions & 0 deletions aptos-move/framework/move-stdlib/sources/mem.move
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/// Module with methods for safe memory manipulation.
module std::mem {
// TODO - functions here are `public(friend)` here for one release,
// and to be changed to `public` one release later.
#[test_only]
friend std::mem_tests;

/// Swap contents of two passed mutable references.
///
/// Move prevents from having two mutable references to the same value,
/// so `left` and `right` references are always distinct.
public(friend) native fun swap<T>(left: &mut T, right: &mut T);

/// Replace the value reference points to with the given new value,
/// and return the value it had before.
public(friend) fun replace<T>(ref: &mut T, new: T): T {
swap(ref, &mut new);
new
}

spec swap<T>(left: &mut T, right: &mut T) {
pragma opaque;
aborts_if false;
ensures right == old(left);
ensures left == old(right);
}

spec replace<T>(ref: &mut T, new: T): T {
pragma opaque;
aborts_if false;
ensures result == old(ref);
ensures ref == new;
}
}
18 changes: 13 additions & 5 deletions aptos-move/framework/move-stdlib/sources/vector.move
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ module std::vector {
/// Aborts if `i` or `j` is out of bounds.
native public fun swap<Element>(self: &mut vector<Element>, i: u64, j: u64);

// TODO - functions here are `public(friend)` here for one release,
// TODO - function `move_range` here is `public(friend)` for one release,
// and to be changed to `public` one release later.
#[test_only]
friend std::vector_tests;
Expand All @@ -70,10 +70,18 @@ module std::vector {
/// to vector `to`, inserting them starting at the `insert_position`.
/// In the `from` vector, elements after the selected range are moved left to fill the hole
/// (i.e. range is removed, while the order of the rest of the elements is kept)
/// In the `to` vector, elements after the `insert_position` are moved to the right to make space for new elements
/// (i.e. range is inserted, while the order of the rest of the elements is kept).
/// Move prevents from having two mutable references to the same value, so `from` and `to` vectors are always distinct.
native public(friend) fun move_range<T>(from: &mut vector<T>, removal_position: u64, length: u64, to: &mut vector<T>, insert_position: u64);
/// In the `to` vector, elements after the `insert_position` are moved to the right to make
/// space for new elements (i.e. range is inserted, while the order of the rest of the
/// elements is kept).
/// Move prevents from having two mutable references to the same value, so `from` and `to`
/// vectors are always distinct.
native public(friend) fun move_range<T>(
from: &mut vector<T>,
removal_position: u64,
length: u64,
to: &mut vector<T>,
insert_position: u64
);

/// Return an vector of size one containing element `e`.
public fun singleton<Element>(e: Element): vector<Element> {
Expand Down
64 changes: 64 additions & 0 deletions aptos-move/framework/move-stdlib/src/natives/mem.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright © Aptos Foundation
// SPDX-License-Identifier: Apache-2.0

//! Implementation of native functions for memory manipulation.

use aptos_gas_schedule::gas_params::natives::move_stdlib::MEM_SWAP_BASE;
use aptos_native_interface::{
safely_pop_arg, RawSafeNative, SafeNativeBuilder, SafeNativeContext, SafeNativeError,
SafeNativeResult,
};
use aptos_types::error;
use move_vm_runtime::native_functions::NativeFunction;
use move_vm_types::{
loaded_data::runtime_types::Type,
values::{Reference, Value},
};
use smallvec::{smallvec, SmallVec};
use std::collections::VecDeque;

/// The feature is not enabled.
pub const EFEATURE_NOT_ENABLED: u64 = 1;

/***************************************************************************************************
* native fun native_swap
*
* gas cost: MEM_SWAP_BASE
*
**************************************************************************************************/
fn native_swap(
context: &mut SafeNativeContext,
_ty_args: Vec<Type>,
mut args: VecDeque<Value>,
) -> SafeNativeResult<SmallVec<[Value; 1]>> {
if !context
.get_feature_flags()
.is_native_memory_operations_enabled()
{
return Err(SafeNativeError::Abort {
abort_code: error::unavailable(EFEATURE_NOT_ENABLED),
});
}

debug_assert!(args.len() == 2);

context.charge(MEM_SWAP_BASE)?;

let left = safely_pop_arg!(args, Reference);
let right = safely_pop_arg!(args, Reference);

left.swap_values(right)?;

Ok(smallvec![])
}

/***************************************************************************************************
* module
**************************************************************************************************/
pub fn make_all(
builder: &SafeNativeBuilder,
) -> impl Iterator<Item = (String, NativeFunction)> + '_ {
let natives = [("swap", native_swap as RawSafeNative)];

builder.make_named_natives(natives)
}
2 changes: 2 additions & 0 deletions aptos-move/framework/move-stdlib/src/natives/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
pub mod bcs;
pub mod cmp;
pub mod hash;
pub mod mem;
pub mod signer;
pub mod string;
#[cfg(feature = "testing")]
Expand Down Expand Up @@ -36,6 +37,7 @@ pub fn all_natives(
add_natives!("bcs", bcs::make_all(builder));
add_natives!("cmp", cmp::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));
add_natives!("vector", vector::make_all(builder));
Expand Down
Loading

0 comments on commit 6255194

Please sign in to comment.