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 8fba8a6838102..c8e6342424141 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,9 @@ use crate::{ gas_feature_versions::{RELEASE_V1_18, RELEASE_V1_24}, gas_schedule::NativeGasParameters, }; -use aptos_gas_algebra::{InternalGas, InternalGasPerArg, InternalGasPerByte}; +use aptos_gas_algebra::{ + InternalGas, InternalGasPerAbstractValueUnit, InternalGasPerArg, InternalGasPerByte, +}; crate::gas_schedule::macros::define_gas_parameters!( MoveStdlibGasParameters, @@ -44,5 +46,8 @@ 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], + + [cmp_compare_base: InternalGas, { RELEASE_V1_24.. => "cmp.compare.base" }, 367], + [cmp_compare_per_abs_val_unit: InternalGasPerAbstractValueUnit, { RELEASE_V1_24.. => "cmp.cper_abs_val_unit"}, 14], ] ); diff --git a/aptos-move/aptos-native-interface/src/context.rs b/aptos-move/aptos-native-interface/src/context.rs index 1e4b116b748cd..91c46d92c304b 100644 --- a/aptos-move/aptos-native-interface/src/context.rs +++ b/aptos-move/aptos-native-interface/src/context.rs @@ -94,6 +94,13 @@ impl<'a, 'b, 'c, 'd> SafeNativeContext<'a, 'b, 'c, 'd> { .abstract_value_size(val, self.gas_feature_version) } + /// Computes the abstract size of the input value. + pub fn abs_val_size_dereferenced(&self, val: &Value) -> AbstractValueSize { + self.misc_gas_params + .abs_val + .abstract_value_size_dereferenced(val, self.gas_feature_version) + } + /// Returns the current gas feature version. pub fn gas_feature_version(&self) -> u64 { self.gas_feature_version diff --git a/aptos-move/framework/move-stdlib/src/natives/cmp.rs b/aptos-move/framework/move-stdlib/src/natives/cmp.rs new file mode 100644 index 0000000000000..7ef34392d1e83 --- /dev/null +++ b/aptos-move/framework/move-stdlib/src/natives/cmp.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 value comparison. + +use aptos_gas_schedule::gas_params::natives::move_stdlib::{ + CMP_COMPARE_BASE, CMP_COMPARE_PER_ABS_VAL_UNIT, +}; +use aptos_native_interface::{ + 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::{Struct, Value}, +}; +use smallvec::{smallvec, SmallVec}; +use std::collections::VecDeque; + +const ORDERING_LESS_THAN_VARIANT: u16 = 0; +const ORDERING_EQUAL_VARIANT: u16 = 1; +const ORDERING_GREATER_THAN_VARIANT: u16 = 2; + +/*************************************************************************************************** + * native fun native_compare + * + * gas cost: CMP_COMPARE_BASE + CMP_COMPARE_PER_ABS_VAL_UNIT * derefernce_size_of_both_values + * + **************************************************************************************************/ +fn native_compare( + context: &mut SafeNativeContext, + _ty_args: Vec, + 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 = CMP_COMPARE_BASE + + CMP_COMPARE_PER_ABS_VAL_UNIT + * (context.abs_val_size_dereferenced(&args[0]) + + context.abs_val_size_dereferenced(&args[1])); + context.charge(cost)?; + + let ordering = args[0].compare(&args[1])?; + let ordering_move_variant = match ordering { + std::cmp::Ordering::Less => ORDERING_LESS_THAN_VARIANT, + std::cmp::Ordering::Equal => ORDERING_EQUAL_VARIANT, + std::cmp::Ordering::Greater => ORDERING_GREATER_THAN_VARIANT, + }; + + Ok(smallvec![Value::struct_(Struct::pack(vec![Value::u16( + ordering_move_variant + )]))]) +} + +/*************************************************************************************************** + * module + **************************************************************************************************/ +pub fn make_all( + builder: &SafeNativeBuilder, +) -> impl Iterator + '_ { + let natives = [("compare", native_compare 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 c773378cb2e71..1ea4a32ebc93d 100644 --- a/aptos-move/framework/move-stdlib/src/natives/mod.rs +++ b/aptos-move/framework/move-stdlib/src/natives/mod.rs @@ -6,6 +6,7 @@ // SPDX-License-Identifier: Apache-2.0 pub mod bcs; +pub mod cmp; pub mod hash; pub mod mem; pub mod signer; @@ -34,6 +35,7 @@ pub fn all_natives( builder.with_incremental_gas_charging(false, |builder| { 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)); 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 7a0ca5696d36f..64e320ab11828 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 @@ -23,6 +23,7 @@ use move_core_types::{ }; use std::{ cell::RefCell, + cmp::Ordering, fmt::{self, Debug, Display, Formatter}, iter, mem, rc::Rc, @@ -536,8 +537,62 @@ impl ValueImpl { | (ContainerRef(_), _) | (IndexedRef(_), _) | (DelayedFieldID { .. }, _) => { - return Err(PartialVMError::new(StatusCode::INTERNAL_TYPE_ERROR) - .with_message(format!("cannot compare values: {:?}, {:?}", self, other))) + return Err( + PartialVMError::new(StatusCode::INTERNAL_TYPE_ERROR).with_message(format!( + "inconsistent argument types passed to equals check: {:?}, {:?}", + self, other + )), + ) + }, + }; + + Ok(res) + } + + fn compare(&self, other: &Self) -> PartialVMResult { + use ValueImpl::*; + + let res = match (self, other) { + (U8(l), U8(r)) => l.cmp(r), + (U16(l), U16(r)) => l.cmp(r), + (U32(l), U32(r)) => l.cmp(r), + (U64(l), U64(r)) => l.cmp(r), + (U128(l), U128(r)) => l.cmp(r), + (U256(l), U256(r)) => l.cmp(r), + (Bool(l), Bool(r)) => l.cmp(r), + (Address(l), Address(r)) => l.cmp(r), + + (Container(l), Container(r)) => l.compare(r)?, + + (ContainerRef(l), ContainerRef(r)) => l.compare(r)?, + (IndexedRef(l), IndexedRef(r)) => l.compare(r)?, + + // Disallow comparison for delayed values. + // (see `ValueImpl::equals` above for details on reasoning behind it) + (DelayedFieldID { .. }, DelayedFieldID { .. }) => { + return Err(PartialVMError::new(StatusCode::VM_EXTENSION_ERROR) + .with_message("cannot compare delayed values".to_string())) + }, + + (Invalid, _) + | (U8(_), _) + | (U16(_), _) + | (U32(_), _) + | (U64(_), _) + | (U128(_), _) + | (U256(_), _) + | (Bool(_), _) + | (Address(_), _) + | (Container(_), _) + | (ContainerRef(_), _) + | (IndexedRef(_), _) + | (DelayedFieldID { .. }, _) => { + return Err( + PartialVMError::new(StatusCode::INTERNAL_TYPE_ERROR).with_message(format!( + "inconsistent argument types passed to comparison: {:?}, {:?}", + self, other + )), + ) }, }; @@ -595,12 +650,65 @@ impl Container { Ok(res) } + + fn compare(&self, other: &Self) -> PartialVMResult { + use Container::*; + + let res = match (self, other) { + (Vec(l), Vec(r)) | (Struct(l), Struct(r)) => { + let l = &l.borrow(); + let r = &r.borrow(); + + for (v1, v2) in l.iter().zip(r.iter()) { + let value_cmp = v1.compare(v2)?; + if value_cmp.is_ne() { + return Ok(value_cmp); + } + } + + l.len().cmp(&r.len()) + }, + (VecU8(l), VecU8(r)) => l.borrow().cmp(&*r.borrow()), + (VecU16(l), VecU16(r)) => l.borrow().cmp(&*r.borrow()), + (VecU32(l), VecU32(r)) => l.borrow().cmp(&*r.borrow()), + (VecU64(l), VecU64(r)) => l.borrow().cmp(&*r.borrow()), + (VecU128(l), VecU128(r)) => l.borrow().cmp(&*r.borrow()), + (VecU256(l), VecU256(r)) => l.borrow().cmp(&*r.borrow()), + (VecBool(l), VecBool(r)) => l.borrow().cmp(&*r.borrow()), + (VecAddress(l), VecAddress(r)) => l.borrow().cmp(&*r.borrow()), + + (Locals(_), _) + | (Vec(_), _) + | (Struct(_), _) + | (VecU8(_), _) + | (VecU16(_), _) + | (VecU32(_), _) + | (VecU64(_), _) + | (VecU128(_), _) + | (VecU256(_), _) + | (VecBool(_), _) + | (VecAddress(_), _) => { + return Err( + PartialVMError::new(StatusCode::INTERNAL_TYPE_ERROR).with_message(format!( + "cannot compare container values: {:?}, {:?}", + self, other + )), + ) + }, + }; + + Ok(res) + } } impl ContainerRef { fn equals(&self, other: &Self) -> PartialVMResult { self.container().equals(other.container()) } + + fn compare(&self, other: &Self) -> PartialVMResult { + self.container().compare(other.container()) + } } impl IndexedRef { @@ -704,12 +812,117 @@ impl IndexedRef { }; Ok(res) } + + fn compare(&self, other: &Self) -> PartialVMResult { + use Container::*; + + let res = match ( + self.container_ref.container(), + other.container_ref.container(), + ) { + // VecC <=> VecR impossible + (Vec(r1), Vec(r2)) + | (Vec(r1), Struct(r2)) + | (Vec(r1), Locals(r2)) + | (Struct(r1), Vec(r2)) + | (Struct(r1), Struct(r2)) + | (Struct(r1), Locals(r2)) + | (Locals(r1), Vec(r2)) + | (Locals(r1), Struct(r2)) + | (Locals(r1), Locals(r2)) => r1.borrow()[self.idx].compare(&r2.borrow()[other.idx])?, + + (VecU8(r1), VecU8(r2)) => r1.borrow()[self.idx].cmp(&r2.borrow()[other.idx]), + (VecU16(r1), VecU16(r2)) => r1.borrow()[self.idx].cmp(&r2.borrow()[other.idx]), + (VecU32(r1), VecU32(r2)) => r1.borrow()[self.idx].cmp(&r2.borrow()[other.idx]), + (VecU64(r1), VecU64(r2)) => r1.borrow()[self.idx].cmp(&r2.borrow()[other.idx]), + (VecU128(r1), VecU128(r2)) => r1.borrow()[self.idx].cmp(&r2.borrow()[other.idx]), + (VecU256(r1), VecU256(r2)) => r1.borrow()[self.idx].cmp(&r2.borrow()[other.idx]), + (VecBool(r1), VecBool(r2)) => r1.borrow()[self.idx].cmp(&r2.borrow()[other.idx]), + (VecAddress(r1), VecAddress(r2)) => r1.borrow()[self.idx].cmp(&r2.borrow()[other.idx]), + + // Comparison between a generic and a specialized container. + (Locals(r1), VecU8(r2)) | (Struct(r1), VecU8(r2)) => r1.borrow()[self.idx] + .as_value_ref::()? + .cmp(&r2.borrow()[other.idx]), + (VecU8(r1), Locals(r2)) | (VecU8(r1), Struct(r2)) => { + r1.borrow()[self.idx].cmp(r2.borrow()[other.idx].as_value_ref::()?) + }, + + (Locals(r1), VecU16(r2)) | (Struct(r1), VecU16(r2)) => r1.borrow()[self.idx] + .as_value_ref::()? + .cmp(&r2.borrow()[other.idx]), + (VecU16(r1), Locals(r2)) | (VecU16(r1), Struct(r2)) => { + r1.borrow()[self.idx].cmp(r2.borrow()[other.idx].as_value_ref::()?) + }, + + (Locals(r1), VecU32(r2)) | (Struct(r1), VecU32(r2)) => r1.borrow()[self.idx] + .as_value_ref::()? + .cmp(&r2.borrow()[other.idx]), + (VecU32(r1), Locals(r2)) | (VecU32(r1), Struct(r2)) => { + r1.borrow()[self.idx].cmp(r2.borrow()[other.idx].as_value_ref::()?) + }, + + (Locals(r1), VecU64(r2)) | (Struct(r1), VecU64(r2)) => r1.borrow()[self.idx] + .as_value_ref::()? + .cmp(&r2.borrow()[other.idx]), + (VecU64(r1), Locals(r2)) | (VecU64(r1), Struct(r2)) => { + r1.borrow()[self.idx].cmp(r2.borrow()[other.idx].as_value_ref::()?) + }, + + (Locals(r1), VecU128(r2)) | (Struct(r1), VecU128(r2)) => r1.borrow()[self.idx] + .as_value_ref::()? + .cmp(&r2.borrow()[other.idx]), + (VecU128(r1), Locals(r2)) | (VecU128(r1), Struct(r2)) => { + r1.borrow()[self.idx].cmp(r2.borrow()[other.idx].as_value_ref::()?) + }, + + (Locals(r1), VecU256(r2)) | (Struct(r1), VecU256(r2)) => r1.borrow()[self.idx] + .as_value_ref::()? + .cmp(&r2.borrow()[other.idx]), + (VecU256(r1), Locals(r2)) | (VecU256(r1), Struct(r2)) => { + r1.borrow()[self.idx].cmp(r2.borrow()[other.idx].as_value_ref::()?) + }, + + (Locals(r1), VecBool(r2)) | (Struct(r1), VecBool(r2)) => r1.borrow()[self.idx] + .as_value_ref::()? + .cmp(&r2.borrow()[other.idx]), + (VecBool(r1), Locals(r2)) | (VecBool(r1), Struct(r2)) => { + r1.borrow()[self.idx].cmp(r2.borrow()[other.idx].as_value_ref::()?) + }, + + (Locals(r1), VecAddress(r2)) | (Struct(r1), VecAddress(r2)) => r1.borrow()[self.idx] + .as_value_ref::()? + .cmp(&r2.borrow()[other.idx]), + (VecAddress(r1), Locals(r2)) | (VecAddress(r1), Struct(r2)) => { + r1.borrow()[self.idx].cmp(r2.borrow()[other.idx].as_value_ref::()?) + }, + + // All other combinations are illegal. + (Vec(_), _) + | (VecU8(_), _) + | (VecU16(_), _) + | (VecU32(_), _) + | (VecU64(_), _) + | (VecU128(_), _) + | (VecU256(_), _) + | (VecBool(_), _) + | (VecAddress(_), _) => { + return Err(PartialVMError::new(StatusCode::INTERNAL_TYPE_ERROR) + .with_message(format!("cannot compare references {:?}, {:?}", self, other))) + }, + }; + Ok(res) + } } impl Value { pub fn equals(&self, other: &Self) -> PartialVMResult { self.0.equals(&other.0) } + + pub fn compare(&self, other: &Self) -> PartialVMResult { + self.0.compare(&other.0) + } } /***************************************************************************************