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 {