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 ad7b66286253d..5968d4daa2810 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
@@ -7,7 +7,7 @@ use crate::{
gas_feature_versions::{RELEASE_V1_18, RELEASE_V1_22},
gas_schedule::NativeGasParameters,
};
-use aptos_gas_algebra::{InternalGas, InternalGasPerByte};
+use aptos_gas_algebra::{InternalGas, InternalGasPerArg, InternalGasPerByte};
crate::gas_schedule::macros::define_gas_parameters!(
MoveStdlibGasParameters,
@@ -41,5 +41,8 @@ crate::gas_schedule::macros::define_gas_parameters!(
[bcs_serialized_size_failure: InternalGas, { RELEASE_V1_18.. => "bcs.serialized_size.failure" }, 3676],
[mem_swap_base: InternalGas, { RELEASE_V1_22.. => "mem.swap.base" }, 1500],
+
+ [vector_range_move_base: InternalGas, { RELEASE_V1_22.. => "vector.range_move.base" }, 4000],
+ [vector_range_move_per_index_moved: InternalGasPerArg, { RELEASE_V1_22.. => "vector.range_move.per_index_moved" }, 10],
]
);
diff --git a/aptos-move/aptos-vm/src/natives.rs b/aptos-move/aptos-vm/src/natives.rs
index 25515277c7bab..b982b6863186a 100644
--- a/aptos-move/aptos-vm/src/natives.rs
+++ b/aptos-move/aptos-vm/src/natives.rs
@@ -169,7 +169,6 @@ pub fn aptos_natives_with_builder(
#[allow(unreachable_code)]
aptos_move_stdlib::natives::all_natives(CORE_CODE_ADDRESS, builder)
.into_iter()
- .filter(|(_, name, _, _)| name.as_str() != "vector")
.chain(aptos_framework::natives::all_natives(
CORE_CODE_ADDRESS,
builder,
diff --git a/aptos-move/framework/move-stdlib/doc/vector.md b/aptos-move/framework/move-stdlib/doc/vector.md
index d5e6a7bfa2ecd..11966dde2cfa3 100644
--- a/aptos-move/framework/move-stdlib/doc/vector.md
+++ b/aptos-move/framework/move-stdlib/doc/vector.md
@@ -24,6 +24,7 @@ the return on investment didn't seem worth it for these simple functions.
- [Function `pop_back`](#0x1_vector_pop_back)
- [Function `destroy_empty`](#0x1_vector_destroy_empty)
- [Function `swap`](#0x1_vector_swap)
+- [Function `range_move`](#0x1_vector_range_move)
- [Function `singleton`](#0x1_vector_singleton)
- [Function `reverse`](#0x1_vector_reverse)
- [Function `reverse_slice`](#0x1_vector_reverse_slice)
@@ -340,6 +341,34 @@ Aborts if i
or j
is out of bounds.
+
+
+
+
+## Function `range_move`
+
+Moves range of elements [removal_position, removal_position + length)
from vector from
,
+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 the the right to make space for new elements
+(i.e. range is inserted, while the order of the rest of the elements is kept)
+
+
+
public fun range_move<T>(from: &mut vector<T>, removal_position: u64, length: u64, to: &mut vector<T>, insert_position: u64)
+
+
+
+
+
+Implementation
+
+
+native public fun range_move<T>(from: &mut vector<T>, removal_position: u64, length: u64, to: &mut vector<T>, insert_position: u64);
+
+
+
+
diff --git a/aptos-move/framework/move-stdlib/sources/vector.move b/aptos-move/framework/move-stdlib/sources/vector.move
index 1f7754a2a4cb8..3998390b6b841 100644
--- a/aptos-move/framework/move-stdlib/sources/vector.move
+++ b/aptos-move/framework/move-stdlib/sources/vector.move
@@ -61,6 +61,14 @@ module std::vector {
/// Aborts if `i` or `j` is out of bounds.
native public fun swap(self: &mut vector, i: u64, j: u64);
+ /// Moves range of elements `[removal_position, removal_position + length)` from vector `from`,
+ /// 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 the the right to make space for new elements
+ /// (i.e. range is inserted, while the order of the rest of the elements is kept)
+ native public fun range_move(from: &mut vector, removal_position: u64, length: u64, to: &mut vector, insert_position: u64);
+
/// Return an vector of size one containing element `e`.
public fun singleton(e: Element): vector {
let v = empty();
diff --git a/aptos-move/framework/move-stdlib/src/natives/mod.rs b/aptos-move/framework/move-stdlib/src/natives/mod.rs
index 24c995f7e9cec..c773378cb2e71 100644
--- a/aptos-move/framework/move-stdlib/src/natives/mod.rs
+++ b/aptos-move/framework/move-stdlib/src/natives/mod.rs
@@ -12,6 +12,7 @@ pub mod signer;
pub mod string;
#[cfg(feature = "testing")]
pub mod unit_test;
+pub mod vector;
use aptos_native_interface::SafeNativeBuilder;
use move_core_types::account_address::AccountAddress;
@@ -37,6 +38,7 @@ pub fn all_natives(
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));
#[cfg(feature = "testing")]
{
add_natives!("unit_test", unit_test::make_all(builder));
diff --git a/aptos-move/framework/move-stdlib/src/natives/vector.rs b/aptos-move/framework/move-stdlib/src/natives/vector.rs
new file mode 100644
index 0000000000000..06f7e4541d62e
--- /dev/null
+++ b/aptos-move/framework/move-stdlib/src/natives/vector.rs
@@ -0,0 +1,93 @@
+// 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::{
+ VECTOR_RANGE_MOVE_BASE, VECTOR_RANGE_MOVE_PER_INDEX_MOVED,
+};
+use aptos_native_interface::{
+ safely_pop_arg, RawSafeNative, SafeNativeBuilder, SafeNativeContext, SafeNativeError,
+ SafeNativeResult,
+};
+use move_core_types::gas_algebra::NumArgs;
+use move_vm_runtime::native_functions::NativeFunction;
+use move_vm_types::{
+ loaded_data::runtime_types::Type,
+ values::{Value, VectorRef},
+};
+use smallvec::{smallvec, SmallVec};
+use std::collections::VecDeque;
+
+/// The generic type supplied to aggregator snapshots is not supported.
+pub const EINDEX_OUT_OF_BOUNDS: u64 = 0x03_0001;
+
+/***************************************************************************************************
+ * native fun vector_move(from: &mut vector, removal_position: u64, length: u64, to: &mut vector, insert_position: u64)
+ *
+ * gas cost: VECTOR_RANGE_MOVE_BASE + EINDEX_OUT_OF_BOUNDS * num_elements_to_move
+ *
+ **************************************************************************************************/
+fn native_range_move(
+ context: &mut SafeNativeContext,
+ ty_args: Vec,
+ mut args: VecDeque,
+) -> SafeNativeResult> {
+ context.charge(VECTOR_RANGE_MOVE_BASE)?;
+
+ let map_err = |_| SafeNativeError::Abort {
+ abort_code: EINDEX_OUT_OF_BOUNDS,
+ };
+ let insert_position = usize::try_from(safely_pop_arg!(args, u64)).map_err(map_err)?;
+ let to = safely_pop_arg!(args, VectorRef);
+ let length = usize::try_from(safely_pop_arg!(args, u64)).map_err(map_err)?;
+ let removal_position = usize::try_from(safely_pop_arg!(args, u64)).map_err(map_err)?;
+ let from = safely_pop_arg!(args, VectorRef);
+
+ // need to fetch sizes here to charge upfront, and later in move_range, not sure if possible to combine
+ let to_len = to.len_usize_raw(&ty_args[0])?;
+ let from_len = from.len_usize_raw(&ty_args[0])?;
+
+ if removal_position
+ .checked_add(length)
+ .map_or(true, |end| end > from_len)
+ || insert_position > to_len
+ {
+ return Err(SafeNativeError::Abort {
+ abort_code: EINDEX_OUT_OF_BOUNDS,
+ });
+ }
+
+ // We are moving all elements in the range, all elements after range, and all elements after insertion point.
+ // We are counting "length" of moving block twice, as it both gets moved out and moved in.
+ context.charge(
+ VECTOR_RANGE_MOVE_PER_INDEX_MOVED
+ * NumArgs::new(
+ (from_len - removal_position)
+ .checked_add(to_len - insert_position)
+ .and_then(|v| v.checked_add(length))
+ .ok_or_else(|| SafeNativeError::Abort {
+ abort_code: EINDEX_OUT_OF_BOUNDS,
+ })? as u64,
+ ),
+ )?;
+
+ from.move_range(removal_position, length, &to, insert_position, &ty_args[0])?;
+
+ Ok(smallvec![])
+}
+
+/***************************************************************************************************
+ * module
+ **************************************************************************************************/
+pub fn make_all(
+ builder: &SafeNativeBuilder,
+) -> impl Iterator- + '_ {
+ let natives = [("range_move", native_range_move as RawSafeNative)];
+
+ builder.make_named_natives(natives)
+}
diff --git a/aptos-move/framework/move-stdlib/tests/vector_tests.move b/aptos-move/framework/move-stdlib/tests/vector_tests.move
index b8c9e19a4dd84..2868426ec0839 100644
--- a/aptos-move/framework/move-stdlib/tests/vector_tests.move
+++ b/aptos-move/framework/move-stdlib/tests/vector_tests.move
@@ -954,4 +954,14 @@ module std::vector_tests {
let v = vector[MoveOnly {}];
vector::destroy(v, |m| { let MoveOnly {} = m; })
}
+
+ #[test]
+ fun test_range_move_ints() {
+ let v = vector[3, 4, 5, 6];
+ let w = vector[1, 2];
+
+ V::range_move(&mut v, 1, 2, &mut w, 1);
+ assert!(&v == &vector[3, 6], 0);
+ assert!(&w == &vector[1, 4, 5, 2], 0);
+ }
}
diff --git a/third_party/move/move-vm/runtime/src/native_functions.rs b/third_party/move/move-vm/runtime/src/native_functions.rs
index 434c1163e2f8e..91227bf41d2d9 100644
--- a/third_party/move/move-vm/runtime/src/native_functions.rs
+++ b/third_party/move/move-vm/runtime/src/native_functions.rs
@@ -24,7 +24,7 @@ use move_vm_types::{
loaded_data::runtime_types::Type, natives::function::NativeResult, values::Value,
};
use std::{
- collections::{HashMap, VecDeque},
+ collections::{HashMap, HashSet, VecDeque},
fmt::Write,
sync::Arc,
};
@@ -81,8 +81,26 @@ impl NativeFunctions {
where
I: IntoIterator
- ,
{
+ let vector_bytecode_instruction_methods = HashSet::from([
+ "empty",
+ "length",
+ "borrow",
+ "borrow_mut",
+ "push_back",
+ "pop_back",
+ "destroy_empty",
+ "swap",
+ ]);
+
let mut map = HashMap::new();
for (addr, module_name, func_name, func) in natives.into_iter() {
+ if module_name.as_str() == "string"
+ && vector_bytecode_instruction_methods.contains(func_name.as_str())
+ {
+ println!("ERROR: Tried to register as native a vector bytecode_instruction method {}, skipping.", func_name.as_str());
+ continue;
+ }
+
let modules = map.entry(addr).or_insert_with(HashMap::new);
let funcs = modules
.entry(module_name.into_string())
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 9c35f8040d43e..a773656fa7a8e 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
@@ -2304,6 +2304,25 @@ fn check_elem_layout(ty: &Type, v: &Container) -> PartialVMResult<()> {
}
impl VectorRef {
+ pub fn len_usize_raw(&self, type_param: &Type) -> PartialVMResult {
+ let c: &Container = self.0.container();
+ check_elem_layout(type_param, c)?;
+
+ let len = match c {
+ Container::VecU8(r) => r.borrow().len(),
+ Container::VecU16(r) => r.borrow().len(),
+ Container::VecU32(r) => r.borrow().len(),
+ Container::VecU64(r) => r.borrow().len(),
+ Container::VecU128(r) => r.borrow().len(),
+ Container::VecU256(r) => r.borrow().len(),
+ Container::VecBool(r) => r.borrow().len(),
+ Container::VecAddress(r) => r.borrow().len(),
+ Container::Vec(r) => r.borrow().len(),
+ Container::Locals(_) | Container::Struct(_) => unreachable!(),
+ };
+ Ok(len)
+ }
+
pub fn len(&self, type_param: &Type) -> PartialVMResult {
let c: &Container = self.0.container();
check_elem_layout(type_param, c)?;
@@ -2449,6 +2468,64 @@ impl VectorRef {
self.0.mark_dirty();
Ok(())
}
+
+ pub fn move_range(
+ &self,
+ removal_position: usize,
+ length: usize,
+ to_self: &Self,
+ insert_position: usize,
+ type_param: &Type,
+ ) -> PartialVMResult<()> {
+ let from_c = self.0.container();
+ let to_c = to_self.0.container();
+ check_elem_layout(type_param, from_c)?;
+ check_elem_layout(type_param, to_c)?;
+
+ macro_rules! move_range {
+ ($from:expr, $to:expr) => {{
+ let mut from_v = $from.borrow_mut();
+ let mut to_v = $to.borrow_mut();
+
+ if removal_position.checked_add(length).map_or(true, |end| end > from_v.len())
+ || insert_position > to_v.len() {
+ return Err(PartialVMError::new(StatusCode::VECTOR_OPERATION_ERROR)
+ .with_sub_status(INDEX_OUT_OF_BOUNDS));
+ }
+
+ // Short-circuit with faster implementation some of the common cases.
+ // This includes all non-direct calls to move-range (i.e. insert/remove/append/split_off inside vector).
+ if length == 1 {
+ to_v.insert(insert_position, from_v.remove(removal_position));
+ } else if removal_position == 0 && length == from_v.len() && insert_position == to_v.len() {
+ to_v.append(&mut from_v);
+ } else if (removal_position + length == from_v.len() && insert_position == to_v.len()) {
+ to_v.append(&mut from_v.split_off(removal_position));
+ } else {
+ to_v.splice(insert_position..insert_position, from_v.splice(removal_position..(removal_position + length), []));
+ }
+ }};
+ }
+
+ match (from_c, to_c) {
+ (Container::VecU8(from_r), Container::VecU8(to_r)) => move_range!(from_r, to_r),
+ (Container::VecU16(from_r), Container::VecU16(to_r)) => move_range!(from_r, to_r),
+ (Container::VecU32(from_r), Container::VecU32(to_r)) => move_range!(from_r, to_r),
+ (Container::VecU64(from_r), Container::VecU64(to_r)) => move_range!(from_r, to_r),
+ (Container::VecU128(from_r), Container::VecU128(to_r)) => move_range!(from_r, to_r),
+ (Container::VecU256(from_r), Container::VecU256(to_r)) => move_range!(from_r, to_r),
+ (Container::VecBool(from_r), Container::VecBool(to_r)) => move_range!(from_r, to_r),
+ (Container::VecAddress(from_r), Container::VecAddress(to_r)) => {
+ move_range!(from_r, to_r)
+ },
+ (Container::Vec(from_r), Container::Vec(to_r)) => move_range!(from_r, to_r),
+ (_, _) => unreachable!(),
+ }
+
+ self.0.mark_dirty();
+ to_self.0.mark_dirty();
+ Ok(())
+ }
}
impl Vector {