Skip to content

Commit

Permalink
[move] Implement generic comparison method in move - rust part
Browse files Browse the repository at this point in the history
  • Loading branch information
igor-aptos committed Nov 13, 2024
1 parent e174527 commit 3144f47
Show file tree
Hide file tree
Showing 5 changed files with 307 additions and 4 deletions.
10 changes: 8 additions & 2 deletions aptos-move/aptos-gas-schedule/src/gas_schedule/move_stdlib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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_24},
gas_schedule::NativeGasParameters,
};
use aptos_gas_algebra::{InternalGas, InternalGasPerAbstractValueUnit, InternalGasPerByte};

crate::gas_schedule::macros::define_gas_parameters!(
MoveStdlibGasParameters,
Expand Down Expand Up @@ -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],

[cmp_compare_base: InternalGas, { RELEASE_V1_24.. => "cmp.compare.base" }, 367],
[cmp_compare_per_abs_val_unit: InternalGasPerAbstractValueUnit, { RELEASE_V1_24.. => "cmp.compare.per_abs_val_unit"}, 14],
]
);
7 changes: 7 additions & 0 deletions aptos-move/aptos-native-interface/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
75 changes: 75 additions & 0 deletions aptos-move/framework/move-stdlib/src/natives/cmp.rs
Original file line number Diff line number Diff line change
@@ -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 * dereferenced_size_of_both_values
*
**************************************************************************************************/
fn native_compare(
context: &mut SafeNativeContext,
_ty_args: Vec<Type>,
args: VecDeque<Value>,
) -> SafeNativeResult<SmallVec<[Value; 1]>> {
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<Item = (String, NativeFunction)> + '_ {
let natives = [("compare", native_compare 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 @@ -6,6 +6,7 @@
// SPDX-License-Identifier: Apache-2.0

pub mod bcs;
pub mod cmp;
pub mod hash;
pub mod signer;
pub mod string;
Expand All @@ -32,6 +33,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!("signer", signer::make_all(builder));
add_natives!("string", string::make_all(builder));
Expand Down
217 changes: 215 additions & 2 deletions third_party/move/move-vm/types/src/values/values_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use move_core_types::{
};
use std::{
cell::RefCell,
cmp::Ordering,
fmt::{self, Debug, Display, Formatter},
iter,
rc::Rc,
Expand Down Expand Up @@ -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<Ordering> {
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
)),
)
},
};

Expand Down Expand Up @@ -595,12 +650,65 @@ impl Container {

Ok(res)
}

fn compare(&self, other: &Self) -> PartialVMResult<Ordering> {
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<bool> {
self.container().equals(other.container())
}

fn compare(&self, other: &Self) -> PartialVMResult<Ordering> {
self.container().compare(other.container())
}
}

impl IndexedRef {
Expand Down Expand Up @@ -704,12 +812,117 @@ impl IndexedRef {
};
Ok(res)
}

fn compare(&self, other: &Self) -> PartialVMResult<Ordering> {
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::<u8>()?
.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::<u8>()?)
},

(Locals(r1), VecU16(r2)) | (Struct(r1), VecU16(r2)) => r1.borrow()[self.idx]
.as_value_ref::<u16>()?
.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::<u16>()?)
},

(Locals(r1), VecU32(r2)) | (Struct(r1), VecU32(r2)) => r1.borrow()[self.idx]
.as_value_ref::<u32>()?
.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::<u32>()?)
},

(Locals(r1), VecU64(r2)) | (Struct(r1), VecU64(r2)) => r1.borrow()[self.idx]
.as_value_ref::<u64>()?
.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::<u64>()?)
},

(Locals(r1), VecU128(r2)) | (Struct(r1), VecU128(r2)) => r1.borrow()[self.idx]
.as_value_ref::<u128>()?
.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::<u128>()?)
},

(Locals(r1), VecU256(r2)) | (Struct(r1), VecU256(r2)) => r1.borrow()[self.idx]
.as_value_ref::<u256::U256>()?
.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::<u256::U256>()?)
},

(Locals(r1), VecBool(r2)) | (Struct(r1), VecBool(r2)) => r1.borrow()[self.idx]
.as_value_ref::<bool>()?
.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::<bool>()?)
},

(Locals(r1), VecAddress(r2)) | (Struct(r1), VecAddress(r2)) => r1.borrow()[self.idx]
.as_value_ref::<AccountAddress>()?
.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::<AccountAddress>()?)
},

// 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<bool> {
self.0.equals(&other.0)
}

pub fn compare(&self, other: &Self) -> PartialVMResult<Ordering> {
self.0.compare(&other.0)
}
}

/***************************************************************************************
Expand Down

0 comments on commit 3144f47

Please sign in to comment.